diff --git a/!main project/GeekbrainsUI.xcodeproj/project.pbxproj b/!main project/GeekbrainsUI.xcodeproj/project.pbxproj new file mode 100644 index 0000000..d94fe9a --- /dev/null +++ b/!main project/GeekbrainsUI.xcodeproj/project.pbxproj @@ -0,0 +1,886 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXBuildFile section */ + 9116BAC9239D2607005BF293 /* LikeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9116BAC8239D2607005BF293 /* LikeButton.swift */; }; + 9117976323DEC4FD0038D4CF /* ThreeCirclesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9117976123DEC4FD0038D4CF /* ThreeCirclesViewController.swift */; }; + 9117976423DEC4FD0038D4CF /* ThreeCirclesViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9117976223DEC4FD0038D4CF /* ThreeCirclesViewController.xib */; }; + 9117976923E015480038D4CF /* UserRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9117976823E015480038D4CF /* UserRepository.swift */; }; + 911E5A6E239A9F73008CD4D6 /* CustomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 911E5A6D239A9F73008CD4D6 /* CustomView.swift */; }; + 911E5A70239D08B4008CD4D6 /* CircleShadowImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 911E5A6F239D08B4008CD4D6 /* CircleShadowImage.swift */; }; + 911E5A72239D1113008CD4D6 /* Friend.swift in Sources */ = {isa = PBXBuildFile; fileRef = 911E5A71239D1113008CD4D6 /* Friend.swift */; }; + 911E5A74239D1377008CD4D6 /* Group.swift in Sources */ = {isa = PBXBuildFile; fileRef = 911E5A73239D1377008CD4D6 /* Group.swift */; }; + 9132FDA923B4876000D0689A /* PopAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9132FDA823B4876000D0689A /* PopAnimator.swift */; }; + 9132FDAB23B48AEC00D0689A /* ImageViewerPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9132FDAA23B48AEC00D0689A /* ImageViewerPresenter.swift */; }; + 9132FDAD23B48D3800D0689A /* UIApplication+Controller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9132FDAC23B48D3800D0689A /* UIApplication+Controller.swift */; }; + 9132FDBA23B61F5D00D0689A /* FriendsPhotoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9132FDB923B61F5D00D0689A /* FriendsPhotoViewController.swift */; }; + 9132FDBD23B6323600D0689A /* CustomAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9132FDBC23B6323600D0689A /* CustomAnimator.swift */; }; + 9132FDBF23B632CE00D0689A /* CustomInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9132FDBE23B632CE00D0689A /* CustomInteractor.swift */; }; + 914E7F4823E176EB0022BE72 /* FriendsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 914E7F4723E176EB0022BE72 /* FriendsPresenter.swift */; }; + 914E7F4A23E176FD0022BE72 /* FriendsConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 914E7F4923E176FD0022BE72 /* FriendsConfigurator.swift */; }; + 914E8ACE239E5A960020FFD0 /* Gradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 914E8ACD239E5A960020FFD0 /* Gradient.swift */; }; + 915B9DA72389B93800944194 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 915B9DA62389B93800944194 /* AppDelegate.swift */; }; + 915B9DA92389B93800944194 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 915B9DA82389B93800944194 /* SceneDelegate.swift */; }; + 915B9DAB2389B93800944194 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 915B9DAA2389B93800944194 /* LoginViewController.swift */; }; + 915B9DAE2389B93800944194 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 915B9DAC2389B93800944194 /* Main.storyboard */; }; + 915B9DB02389B93C00944194 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 915B9DAF2389B93C00944194 /* Assets.xcassets */; }; + 915B9DB32389B93C00944194 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 915B9DB12389B93C00944194 /* LaunchScreen.storyboard */; }; + 9161587823A3C19300F16F42 /* MessageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9161587723A3C19300F16F42 /* MessageViewController.swift */; }; + 9161587F23A4F7A700F16F42 /* CustomCollectionViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9161587E23A4F7A700F16F42 /* CustomCollectionViewLayout.swift */; }; + 9164263423CDEF5D00BD5E79 /* LoginController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9164263323CDEF5D00BD5E79 /* LoginController.swift */; }; + 9164FD312393C92B0089EE2F /* FriendList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9164FD302393C92B0089EE2F /* FriendList.swift */; }; + 9164FD352393E33E0089EE2F /* PhotoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9164FD342393E33E0089EE2F /* PhotoController.swift */; }; + 917FCC6523DB23560024C01D /* FriendRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 917FCC6423DB23560024C01D /* FriendRepository.swift */; }; + 917FCC6823DB30A10024C01D /* VKDatabase.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 917FCC6623DB30A10024C01D /* VKDatabase.xcdatamodeld */; }; + 917FCC6F23DB33B10024C01D /* FriendCD+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 917FCC6D23DB33B00024C01D /* FriendCD+CoreDataClass.swift */; }; + 917FCC7023DB33B10024C01D /* FriendCD+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 917FCC6E23DB33B10024C01D /* FriendCD+CoreDataProperties.swift */; }; + 9188BF8623CFB36C0094B95A /* VKApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9188BF8523CFB36C0094B95A /* VKApi.swift */; }; + 9188BF8823CFB3FC0094B95A /* Singleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9188BF8723CFB3FC0094B95A /* Singleton.swift */; }; + 9190B5A723A79950009F054F /* ShareButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9190B5A623A79950009F054F /* ShareButton.swift */; }; + 9190B5A923A79BB8009F054F /* CommentButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9190B5A823A79BB8009F054F /* CommentButton.swift */; }; + 9190B5AD23A7A531009F054F /* ViewButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9190B5AC23A7A531009F054F /* ViewButton.swift */; }; + 9190B5AF23A7AB0F009F054F /* News.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9190B5AE23A7AB0F009F054F /* News.swift */; }; + 9190B5B123A8BEF5009F054F /* Code Nice To Use in Resources */ = {isa = PBXBuildFile; fileRef = 9190B5B023A8BEF5009F054F /* Code Nice To Use */; }; + 9190B5BB23AA75BC009F054F /* AnimationApple.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9190B5BA23AA75BC009F054F /* AnimationApple.swift */; }; + 9190B5BD23ACB34E009F054F /* myMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9190B5BC23ACB34E009F054F /* myMenu.swift */; }; + 9194659923AE72B800FB9D6B /* TransitionTestVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9194659823AE72B800FB9D6B /* TransitionTestVC.swift */; }; + 9194659E23AF997200FB9D6B /* FadePopPushAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9194659D23AF997200FB9D6B /* FadePopPushAnimation.swift */; }; + 919465A023AFA16400FB9D6B /* OtherAnimations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9194659F23AFA16400FB9D6B /* OtherAnimations.swift */; }; + 919465A623AFA7A500FB9D6B /* TransitionAnimations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 919465A523AFA7A500FB9D6B /* TransitionAnimations.swift */; }; + 919465A823AFAC0200FB9D6B /* CustomNavigationControllerAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 919465A723AFAC0200FB9D6B /* CustomNavigationControllerAnimation.swift */; }; + 91A97D772396A07900015775 /* GroupList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91A97D762396A07900015775 /* GroupList.swift */; }; + 91A97D7B2396A62700015775 /* NewGroupList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91A97D7A2396A62700015775 /* NewGroupList.swift */; }; + 91ADF80C23D4942500C35F7D /* VKUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91ADF80B23D4942500C35F7D /* VKUser.swift */; }; + 91ADF80E23D4957100C35F7D /* VKGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91ADF80D23D4957100C35F7D /* VKGroup.swift */; }; + 91ADF81023D4999700C35F7D /* VKGroupJSONSerialistion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91ADF80F23D4999700C35F7D /* VKGroupJSONSerialistion.swift */; }; + 91CB233723D9D57A003C9EA8 /* UserCD+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91CB233523D9D579003C9EA8 /* UserCD+CoreDataClass.swift */; }; + 91CB233823D9D57A003C9EA8 /* UserCD+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91CB233623D9D579003C9EA8 /* UserCD+CoreDataProperties.swift */; }; + 91CB234223D9D97F003C9EA8 /* LoadIndicators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91CB234023D9D97F003C9EA8 /* LoadIndicators.swift */; }; + 91CB234323D9D97F003C9EA8 /* EmitterSnow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91CB234123D9D97F003C9EA8 /* EmitterSnow.swift */; }; + 91CB234523DACC7B003C9EA8 /* CommonResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91CB234423DACC7B003C9EA8 /* CommonResponse.swift */; }; + 91E752C223E1C1180028BEE4 /* CommonRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91E752C123E1C1180028BEE4 /* CommonRepository.swift */; }; + 91E752C423E1C2BA0028BEE4 /* GroupRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91E752C323E1C2BA0028BEE4 /* GroupRepository.swift */; }; + 91E752C723E1C6750028BEE4 /* GroupsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91E752C623E1C6750028BEE4 /* GroupsPresenter.swift */; }; + 91E752C923E1D8410028BEE4 /* PhotoRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91E752C823E1D8410028BEE4 /* PhotoRepository.swift */; }; + 91E752CB23E1D8AC0028BEE4 /* VKPhotosRealm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91E752CA23E1D8AC0028BEE4 /* VKPhotosRealm.swift */; }; + 91E752CD23E951640028BEE4 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 91E752CC23E951640028BEE4 /* GoogleService-Info.plist */; }; + 91E752CF23E998150028BEE4 /* UserFirebase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91E752CE23E998150028BEE4 /* UserFirebase.swift */; }; + 91E752D123E9A7120028BEE4 /* FirstScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91E752D023E9A7110028BEE4 /* FirstScreen.swift */; }; + 91E752D423E9C1430028BEE4 /* PhotoPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91E752D323E9C1430028BEE4 /* PhotoPresenter.swift */; }; + 91E752D623E9C7FD0028BEE4 /* PhotosConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91E752D523E9C7FD0028BEE4 /* PhotosConfigurator.swift */; }; + 91E752E523EAC47A0028BEE4 /* FriendCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91E752E423EAC47A0028BEE4 /* FriendCell.swift */; }; + 91E752F523EC25E70028BEE4 /* VKWall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91E752F423EC25E70028BEE4 /* VKWall.swift */; }; + 91E752F723EC29CE0028BEE4 /* GroupsConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91E752F623EC29CE0028BEE4 /* GroupsConfigurator.swift */; }; + 91E769F823F2CD640028BEE4 /* MessageCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 91E769F223F2CD630028BEE4 /* MessageCell.xib */; }; + 91E769F923F2CD640028BEE4 /* FriendCellwithXIB.xib in Resources */ = {isa = PBXBuildFile; fileRef = 91E769F323F2CD630028BEE4 /* FriendCellwithXIB.xib */; }; + 91E769FA23F2CD640028BEE4 /* GroupCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91E769F423F2CD630028BEE4 /* GroupCell.swift */; }; + 91E769FB23F2CD640028BEE4 /* FriendCellwithXIB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91E769F523F2CD640028BEE4 /* FriendCellwithXIB.swift */; }; + 91E769FC23F2CD640028BEE4 /* PhotoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91E769F623F2CD640028BEE4 /* PhotoCell.swift */; }; + 91E769FD23F2CD640028BEE4 /* MessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91E769F723F2CD640028BEE4 /* MessageCell.swift */; }; + 91F2178E23D501FA003FD677 /* VKPhotos.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91F2178D23D501FA003FD677 /* VKPhotos.swift */; }; + 91F2179023D6E222003FD677 /* VKGroupWithNestedContainers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91F2178F23D6E222003FD677 /* VKGroupWithNestedContainers.swift */; }; + 91F2179223D6F145003FD677 /* FileCacher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91F2179123D6F145003FD677 /* FileCacher.swift */; }; + 91F217AE23D71D25003FD677 /* CoreDataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91F217AD23D71D25003FD677 /* CoreDataStack.swift */; }; + 91F217B523D82EAC003FD677 /* VKUserRealm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91F217B423D82EAC003FD677 /* VKUserRealm.swift */; }; + 91F217B723D8330F003FD677 /* VKGroupRealm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91F217B623D8330F003FD677 /* VKGroupRealm.swift */; }; + 91F217B923D83349003FD677 /* VKWorkWithDBRealm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91F217B823D83349003FD677 /* VKWorkWithDBRealm.swift */; }; + 91FA97E823AD13D300B9A053 /* groupAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91FA97E723AD13D300B9A053 /* groupAnimation.swift */; }; + B0AD8CFCEE008E5181BDC939 /* Pods_GeekbrainsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A2ABFBA4702DF36A58EFB1CE /* Pods_GeekbrainsUI.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 004C7893353DC4EB2081909F /* Pods-GeekbrainsUI.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GeekbrainsUI.release.xcconfig"; path = "Target Support Files/Pods-GeekbrainsUI/Pods-GeekbrainsUI.release.xcconfig"; sourceTree = ""; }; + 9116BAC8239D2607005BF293 /* LikeButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LikeButton.swift; sourceTree = ""; }; + 9117976123DEC4FD0038D4CF /* ThreeCirclesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeCirclesViewController.swift; sourceTree = ""; }; + 9117976223DEC4FD0038D4CF /* ThreeCirclesViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ThreeCirclesViewController.xib; sourceTree = ""; }; + 9117976823E015480038D4CF /* UserRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRepository.swift; sourceTree = ""; }; + 911E5A6D239A9F73008CD4D6 /* CustomView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomView.swift; sourceTree = ""; }; + 911E5A6F239D08B4008CD4D6 /* CircleShadowImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleShadowImage.swift; sourceTree = ""; }; + 911E5A71239D1113008CD4D6 /* Friend.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Friend.swift; sourceTree = ""; }; + 911E5A73239D1377008CD4D6 /* Group.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Group.swift; sourceTree = ""; }; + 9132FDA823B4876000D0689A /* PopAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopAnimator.swift; sourceTree = ""; }; + 9132FDAA23B48AEC00D0689A /* ImageViewerPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewerPresenter.swift; sourceTree = ""; }; + 9132FDAC23B48D3800D0689A /* UIApplication+Controller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Controller.swift"; sourceTree = ""; }; + 9132FDB923B61F5D00D0689A /* FriendsPhotoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendsPhotoViewController.swift; sourceTree = ""; }; + 9132FDBC23B6323600D0689A /* CustomAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAnimator.swift; sourceTree = ""; }; + 9132FDBE23B632CE00D0689A /* CustomInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomInteractor.swift; sourceTree = ""; }; + 914E7F4723E176EB0022BE72 /* FriendsPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendsPresenter.swift; sourceTree = ""; }; + 914E7F4923E176FD0022BE72 /* FriendsConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendsConfigurator.swift; sourceTree = ""; }; + 914E8ACD239E5A960020FFD0 /* Gradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Gradient.swift; sourceTree = ""; }; + 915B9DA32389B93800944194 /* GeekbrainsUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GeekbrainsUI.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 915B9DA62389B93800944194 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 915B9DA82389B93800944194 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 915B9DAA2389B93800944194 /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; + 915B9DAD2389B93800944194 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 915B9DAF2389B93C00944194 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 915B9DB22389B93C00944194 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 915B9DB42389B93C00944194 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9161587723A3C19300F16F42 /* MessageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageViewController.swift; sourceTree = ""; }; + 9161587E23A4F7A700F16F42 /* CustomCollectionViewLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomCollectionViewLayout.swift; sourceTree = ""; }; + 9164263323CDEF5D00BD5E79 /* LoginController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginController.swift; sourceTree = ""; }; + 9164FD302393C92B0089EE2F /* FriendList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FriendList.swift; path = GeekbrainsUI/Screens/FriendList.swift; sourceTree = SOURCE_ROOT; }; + 9164FD342393E33E0089EE2F /* PhotoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoController.swift; sourceTree = ""; }; + 917FCC6423DB23560024C01D /* FriendRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendRepository.swift; sourceTree = ""; }; + 917FCC6723DB30A10024C01D /* VKDatabase.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = VKDatabase.xcdatamodel; sourceTree = ""; }; + 917FCC6D23DB33B00024C01D /* FriendCD+CoreDataClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "FriendCD+CoreDataClass.swift"; sourceTree = ""; }; + 917FCC6E23DB33B10024C01D /* FriendCD+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "FriendCD+CoreDataProperties.swift"; sourceTree = ""; }; + 9188BF8523CFB36C0094B95A /* VKApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VKApi.swift; sourceTree = ""; }; + 9188BF8723CFB3FC0094B95A /* Singleton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Singleton.swift; sourceTree = ""; }; + 9190B5A623A79950009F054F /* ShareButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareButton.swift; sourceTree = ""; }; + 9190B5A823A79BB8009F054F /* CommentButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentButton.swift; sourceTree = ""; }; + 9190B5AC23A7A531009F054F /* ViewButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewButton.swift; sourceTree = ""; }; + 9190B5AE23A7AB0F009F054F /* News.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = News.swift; sourceTree = ""; }; + 9190B5B023A8BEF5009F054F /* Code Nice To Use */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "Code Nice To Use"; sourceTree = ""; }; + 9190B5BA23AA75BC009F054F /* AnimationApple.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationApple.swift; sourceTree = ""; }; + 9190B5BC23ACB34E009F054F /* myMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = myMenu.swift; sourceTree = ""; }; + 9194659823AE72B800FB9D6B /* TransitionTestVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransitionTestVC.swift; sourceTree = ""; }; + 9194659D23AF997200FB9D6B /* FadePopPushAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FadePopPushAnimation.swift; sourceTree = ""; }; + 9194659F23AFA16400FB9D6B /* OtherAnimations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OtherAnimations.swift; sourceTree = ""; }; + 919465A523AFA7A500FB9D6B /* TransitionAnimations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransitionAnimations.swift; sourceTree = ""; }; + 919465A723AFAC0200FB9D6B /* CustomNavigationControllerAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomNavigationControllerAnimation.swift; sourceTree = ""; }; + 91A97D762396A07900015775 /* GroupList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupList.swift; sourceTree = ""; }; + 91A97D7A2396A62700015775 /* NewGroupList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewGroupList.swift; sourceTree = ""; }; + 91ADF80B23D4942500C35F7D /* VKUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VKUser.swift; sourceTree = ""; }; + 91ADF80D23D4957100C35F7D /* VKGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VKGroup.swift; sourceTree = ""; }; + 91ADF80F23D4999700C35F7D /* VKGroupJSONSerialistion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VKGroupJSONSerialistion.swift; sourceTree = ""; }; + 91CB233523D9D579003C9EA8 /* UserCD+CoreDataClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UserCD+CoreDataClass.swift"; path = "GeekbrainsUI/Model/UserCD+CoreDataClass.swift"; sourceTree = SOURCE_ROOT; }; + 91CB233623D9D579003C9EA8 /* UserCD+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UserCD+CoreDataProperties.swift"; path = "GeekbrainsUI/Model/UserCD+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; }; + 91CB234023D9D97F003C9EA8 /* LoadIndicators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadIndicators.swift; sourceTree = ""; }; + 91CB234123D9D97F003C9EA8 /* EmitterSnow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmitterSnow.swift; sourceTree = ""; }; + 91CB234423DACC7B003C9EA8 /* CommonResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonResponse.swift; sourceTree = ""; }; + 91E752C123E1C1180028BEE4 /* CommonRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonRepository.swift; sourceTree = ""; }; + 91E752C323E1C2BA0028BEE4 /* GroupRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupRepository.swift; sourceTree = ""; }; + 91E752C623E1C6750028BEE4 /* GroupsPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupsPresenter.swift; sourceTree = ""; }; + 91E752C823E1D8410028BEE4 /* PhotoRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoRepository.swift; sourceTree = ""; }; + 91E752CA23E1D8AC0028BEE4 /* VKPhotosRealm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VKPhotosRealm.swift; sourceTree = ""; }; + 91E752CC23E951640028BEE4 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 91E752CE23E998150028BEE4 /* UserFirebase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserFirebase.swift; sourceTree = ""; }; + 91E752D023E9A7110028BEE4 /* FirstScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstScreen.swift; sourceTree = ""; }; + 91E752D323E9C1430028BEE4 /* PhotoPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoPresenter.swift; sourceTree = ""; }; + 91E752D523E9C7FD0028BEE4 /* PhotosConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotosConfigurator.swift; sourceTree = ""; }; + 91E752E423EAC47A0028BEE4 /* FriendCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FriendCell.swift; path = GeekbrainsUI/Screens/FriendCell.swift; sourceTree = SOURCE_ROOT; }; + 91E752F423EC25E70028BEE4 /* VKWall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VKWall.swift; sourceTree = ""; }; + 91E752F623EC29CE0028BEE4 /* GroupsConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupsConfigurator.swift; sourceTree = ""; }; + 91E769F223F2CD630028BEE4 /* MessageCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MessageCell.xib; sourceTree = ""; }; + 91E769F323F2CD630028BEE4 /* FriendCellwithXIB.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = FriendCellwithXIB.xib; sourceTree = ""; }; + 91E769F423F2CD630028BEE4 /* GroupCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupCell.swift; sourceTree = ""; }; + 91E769F523F2CD640028BEE4 /* FriendCellwithXIB.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FriendCellwithXIB.swift; sourceTree = ""; }; + 91E769F623F2CD640028BEE4 /* PhotoCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoCell.swift; sourceTree = ""; }; + 91E769F723F2CD640028BEE4 /* MessageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCell.swift; sourceTree = ""; }; + 91F2178D23D501FA003FD677 /* VKPhotos.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VKPhotos.swift; sourceTree = ""; }; + 91F2178F23D6E222003FD677 /* VKGroupWithNestedContainers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VKGroupWithNestedContainers.swift; sourceTree = ""; }; + 91F2179123D6F145003FD677 /* FileCacher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileCacher.swift; sourceTree = ""; }; + 91F217AD23D71D25003FD677 /* CoreDataStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataStack.swift; sourceTree = ""; }; + 91F217B423D82EAC003FD677 /* VKUserRealm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = VKUserRealm.swift; path = ../../../Model/Realm/VKUserRealm.swift; sourceTree = ""; }; + 91F217B623D8330F003FD677 /* VKGroupRealm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = VKGroupRealm.swift; path = ../../../Model/Realm/VKGroupRealm.swift; sourceTree = ""; }; + 91F217B823D83349003FD677 /* VKWorkWithDBRealm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VKWorkWithDBRealm.swift; sourceTree = ""; }; + 91FA97E723AD13D300B9A053 /* groupAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = groupAnimation.swift; sourceTree = ""; }; + A2ABFBA4702DF36A58EFB1CE /* Pods_GeekbrainsUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GeekbrainsUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E70018ABE5696F2E677B123A /* Pods-GeekbrainsUI.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GeekbrainsUI.debug.xcconfig"; path = "Target Support Files/Pods-GeekbrainsUI/Pods-GeekbrainsUI.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 915B9DA02389B93800944194 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + B0AD8CFCEE008E5181BDC939 /* Pods_GeekbrainsUI.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 8A02F61D128A3174BC0EFB4B /* Pods */ = { + isa = PBXGroup; + children = ( + E70018ABE5696F2E677B123A /* Pods-GeekbrainsUI.debug.xcconfig */, + 004C7893353DC4EB2081909F /* Pods-GeekbrainsUI.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 9117976723E0152E0038D4CF /* RealmRepository */ = { + isa = PBXGroup; + children = ( + 9117976823E015480038D4CF /* UserRepository.swift */, + 91E752C323E1C2BA0028BEE4 /* GroupRepository.swift */, + 91E752C823E1D8410028BEE4 /* PhotoRepository.swift */, + 91E752C123E1C1180028BEE4 /* CommonRepository.swift */, + ); + path = RealmRepository; + sourceTree = ""; + }; + 911E5A6C239A999F008CD4D6 /* UI */ = { + isa = PBXGroup; + children = ( + 91CB233523D9D579003C9EA8 /* UserCD+CoreDataClass.swift */, + 91CB233623D9D579003C9EA8 /* UserCD+CoreDataProperties.swift */, + 9190B5BA23AA75BC009F054F /* AnimationApple.swift */, + 911E5A6D239A9F73008CD4D6 /* CustomView.swift */, + 911E5A6F239D08B4008CD4D6 /* CircleShadowImage.swift */, + 9116BAC8239D2607005BF293 /* LikeButton.swift */, + 914E8ACD239E5A960020FFD0 /* Gradient.swift */, + 9194659823AE72B800FB9D6B /* TransitionTestVC.swift */, + 9161587E23A4F7A700F16F42 /* CustomCollectionViewLayout.swift */, + 9190B5A623A79950009F054F /* ShareButton.swift */, + 9190B5A823A79BB8009F054F /* CommentButton.swift */, + 9190B5AC23A7A531009F054F /* ViewButton.swift */, + 9190B5BC23ACB34E009F054F /* myMenu.swift */, + 91FA97E723AD13D300B9A053 /* groupAnimation.swift */, + 9194659F23AFA16400FB9D6B /* OtherAnimations.swift */, + 919465A523AFA7A500FB9D6B /* TransitionAnimations.swift */, + 919465A723AFAC0200FB9D6B /* CustomNavigationControllerAnimation.swift */, + 9117976123DEC4FD0038D4CF /* ThreeCirclesViewController.swift */, + 9117976223DEC4FD0038D4CF /* ThreeCirclesViewController.xib */, + ); + path = UI; + sourceTree = ""; + }; + 911E5A75239D1581008CD4D6 /* Screens */ = { + isa = PBXGroup; + children = ( + 91E752D223E9C1200028BEE4 /* PhotoController */, + 91E752C523E1C64F0028BEE4 /* GroupList */, + 914E7F4623E176BB0022BE72 /* FriendList */, + 915B9DAA2389B93800944194 /* LoginViewController.swift */, + 91A97D7A2396A62700015775 /* NewGroupList.swift */, + 9132FDB923B61F5D00D0689A /* FriendsPhotoViewController.swift */, + 9164263323CDEF5D00BD5E79 /* LoginController.swift */, + 91E752D023E9A7110028BEE4 /* FirstScreen.swift */, + ); + path = Screens; + sourceTree = ""; + }; + 9132FDA723B4873C00D0689A /* AnimationPop */ = { + isa = PBXGroup; + children = ( + 9132FDA823B4876000D0689A /* PopAnimator.swift */, + 9132FDAA23B48AEC00D0689A /* ImageViewerPresenter.swift */, + 9132FDAC23B48D3800D0689A /* UIApplication+Controller.swift */, + ); + path = AnimationPop; + sourceTree = ""; + }; + 9132FDBB23B6321D00D0689A /* MusicPlayerAnimators */ = { + isa = PBXGroup; + children = ( + 9132FDBC23B6323600D0689A /* CustomAnimator.swift */, + 9132FDBE23B632CE00D0689A /* CustomInteractor.swift */, + ); + path = MusicPlayerAnimators; + sourceTree = ""; + }; + 914E7F4623E176BB0022BE72 /* FriendList */ = { + isa = PBXGroup; + children = ( + 9164FD302393C92B0089EE2F /* FriendList.swift */, + 914E7F4723E176EB0022BE72 /* FriendsPresenter.swift */, + 914E7F4923E176FD0022BE72 /* FriendsConfigurator.swift */, + ); + path = FriendList; + sourceTree = ""; + }; + 915B9D9A2389B93800944194 = { + isa = PBXGroup; + children = ( + 915B9DA52389B93800944194 /* GeekbrainsUI */, + 915B9DA42389B93800944194 /* Products */, + 8A02F61D128A3174BC0EFB4B /* Pods */, + D2AA31166F3BDD12AE7CF30C /* Frameworks */, + ); + sourceTree = ""; + }; + 915B9DA42389B93800944194 /* Products */ = { + isa = PBXGroup; + children = ( + 915B9DA32389B93800944194 /* GeekbrainsUI.app */, + ); + name = Products; + sourceTree = ""; + }; + 915B9DA52389B93800944194 /* GeekbrainsUI */ = { + isa = PBXGroup; + children = ( + 91CB233F23D9D97F003C9EA8 /* Animations */, + 91F217AC23D71D01003FD677 /* Database */, + 9132FDBB23B6321D00D0689A /* MusicPlayerAnimators */, + 9132FDA723B4873C00D0689A /* AnimationPop */, + 9194659C23AF994B00FB9D6B /* AnimatedTransitions */, + 9190B5B023A8BEF5009F054F /* Code Nice To Use */, + 9161587623A3C16700F16F42 /* Cells */, + 911E5A75239D1581008CD4D6 /* Screens */, + 911E5A6C239A999F008CD4D6 /* UI */, + 9164FD3D23955A800089EE2F /* Model */, + 915B9DA62389B93800944194 /* AppDelegate.swift */, + 915B9DA82389B93800944194 /* SceneDelegate.swift */, + 91E752CC23E951640028BEE4 /* GoogleService-Info.plist */, + 915B9DAC2389B93800944194 /* Main.storyboard */, + 915B9DAF2389B93C00944194 /* Assets.xcassets */, + 915B9DB12389B93C00944194 /* LaunchScreen.storyboard */, + 915B9DB42389B93C00944194 /* Info.plist */, + 91E752CE23E998150028BEE4 /* UserFirebase.swift */, + ); + path = GeekbrainsUI; + sourceTree = ""; + }; + 9161587623A3C16700F16F42 /* Cells */ = { + isa = PBXGroup; + children = ( + 91E769F523F2CD640028BEE4 /* FriendCellwithXIB.swift */, + 91E769F223F2CD630028BEE4 /* MessageCell.xib */, + 91E769F323F2CD630028BEE4 /* FriendCellwithXIB.xib */, + 91E769F423F2CD630028BEE4 /* GroupCell.swift */, + 91E769F723F2CD640028BEE4 /* MessageCell.swift */, + 91E769F623F2CD640028BEE4 /* PhotoCell.swift */, + 91E752E423EAC47A0028BEE4 /* FriendCell.swift */, + 9161587723A3C19300F16F42 /* MessageViewController.swift */, + ); + path = Cells; + sourceTree = ""; + }; + 9164FD3D23955A800089EE2F /* Model */ = { + isa = PBXGroup; + children = ( + 91F217B323D82E7A003FD677 /* Realm */, + 911E5A71239D1113008CD4D6 /* Friend.swift */, + 911E5A73239D1377008CD4D6 /* Group.swift */, + 9190B5AE23A7AB0F009F054F /* News.swift */, + 9188BF8523CFB36C0094B95A /* VKApi.swift */, + 9188BF8723CFB3FC0094B95A /* Singleton.swift */, + 91ADF80B23D4942500C35F7D /* VKUser.swift */, + 91ADF80D23D4957100C35F7D /* VKGroup.swift */, + 91ADF80F23D4999700C35F7D /* VKGroupJSONSerialistion.swift */, + 91F2178D23D501FA003FD677 /* VKPhotos.swift */, + 91F2178F23D6E222003FD677 /* VKGroupWithNestedContainers.swift */, + 91F2179123D6F145003FD677 /* FileCacher.swift */, + 91CB234423DACC7B003C9EA8 /* CommonResponse.swift */, + 91E752F423EC25E70028BEE4 /* VKWall.swift */, + ); + path = Model; + sourceTree = ""; + }; + 917FCC6023DB22540024C01D /* CoreData */ = { + isa = PBXGroup; + children = ( + 917FCC6223DB227D0024C01D /* Model */, + ); + path = CoreData; + sourceTree = ""; + }; + 917FCC6123DB226A0024C01D /* Realm */ = { + isa = PBXGroup; + children = ( + 9117976723E0152E0038D4CF /* RealmRepository */, + 917FCC6323DB22840024C01D /* Model */, + ); + path = Realm; + sourceTree = ""; + }; + 917FCC6223DB227D0024C01D /* Model */ = { + isa = PBXGroup; + children = ( + 917FCC6423DB23560024C01D /* FriendRepository.swift */, + 917FCC6D23DB33B00024C01D /* FriendCD+CoreDataClass.swift */, + 917FCC6E23DB33B10024C01D /* FriendCD+CoreDataProperties.swift */, + ); + path = Model; + sourceTree = ""; + }; + 917FCC6323DB22840024C01D /* Model */ = { + isa = PBXGroup; + children = ( + 91F217B423D82EAC003FD677 /* VKUserRealm.swift */, + 91F217B623D8330F003FD677 /* VKGroupRealm.swift */, + 91E752CA23E1D8AC0028BEE4 /* VKPhotosRealm.swift */, + ); + path = Model; + sourceTree = ""; + }; + 9194659C23AF994B00FB9D6B /* AnimatedTransitions */ = { + isa = PBXGroup; + children = ( + 9194659D23AF997200FB9D6B /* FadePopPushAnimation.swift */, + ); + path = AnimatedTransitions; + sourceTree = ""; + }; + 91CB233F23D9D97F003C9EA8 /* Animations */ = { + isa = PBXGroup; + children = ( + 91CB234023D9D97F003C9EA8 /* LoadIndicators.swift */, + 91CB234123D9D97F003C9EA8 /* EmitterSnow.swift */, + ); + path = Animations; + sourceTree = ""; + }; + 91E752C523E1C64F0028BEE4 /* GroupList */ = { + isa = PBXGroup; + children = ( + 91A97D762396A07900015775 /* GroupList.swift */, + 91E752C623E1C6750028BEE4 /* GroupsPresenter.swift */, + 91E752F623EC29CE0028BEE4 /* GroupsConfigurator.swift */, + ); + path = GroupList; + sourceTree = ""; + }; + 91E752D223E9C1200028BEE4 /* PhotoController */ = { + isa = PBXGroup; + children = ( + 9164FD342393E33E0089EE2F /* PhotoController.swift */, + 91E752D323E9C1430028BEE4 /* PhotoPresenter.swift */, + 91E752D523E9C7FD0028BEE4 /* PhotosConfigurator.swift */, + ); + path = PhotoController; + sourceTree = ""; + }; + 91F217AC23D71D01003FD677 /* Database */ = { + isa = PBXGroup; + children = ( + 917FCC6123DB226A0024C01D /* Realm */, + 917FCC6023DB22540024C01D /* CoreData */, + 91F217AD23D71D25003FD677 /* CoreDataStack.swift */, + 917FCC6623DB30A10024C01D /* VKDatabase.xcdatamodeld */, + ); + path = Database; + sourceTree = ""; + }; + 91F217B323D82E7A003FD677 /* Realm */ = { + isa = PBXGroup; + children = ( + 91F217B823D83349003FD677 /* VKWorkWithDBRealm.swift */, + ); + path = Realm; + sourceTree = ""; + }; + D2AA31166F3BDD12AE7CF30C /* Frameworks */ = { + isa = PBXGroup; + children = ( + A2ABFBA4702DF36A58EFB1CE /* Pods_GeekbrainsUI.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 915B9DA22389B93800944194 /* GeekbrainsUI */ = { + isa = PBXNativeTarget; + buildConfigurationList = 915B9DB72389B93C00944194 /* Build configuration list for PBXNativeTarget "GeekbrainsUI" */; + buildPhases = ( + D4277C56730214865E0344C6 /* [CP] Check Pods Manifest.lock */, + 915B9D9F2389B93800944194 /* Sources */, + 915B9DA02389B93800944194 /* Frameworks */, + 915B9DA12389B93800944194 /* Resources */, + 26CB0979E08B04F32ADD8123 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = GeekbrainsUI; + productName = GeekbrainsUI; + productReference = 915B9DA32389B93800944194 /* GeekbrainsUI.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 915B9D9B2389B93800944194 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1120; + LastUpgradeCheck = 1120; + ORGANIZATIONNAME = "raskin-sa"; + TargetAttributes = { + 915B9DA22389B93800944194 = { + CreatedOnToolsVersion = 11.2.1; + }; + }; + }; + buildConfigurationList = 915B9D9E2389B93800944194 /* Build configuration list for PBXProject "GeekbrainsUI" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 915B9D9A2389B93800944194; + productRefGroup = 915B9DA42389B93800944194 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 915B9DA22389B93800944194 /* GeekbrainsUI */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 915B9DA12389B93800944194 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9117976423DEC4FD0038D4CF /* ThreeCirclesViewController.xib in Resources */, + 91E752CD23E951640028BEE4 /* GoogleService-Info.plist in Resources */, + 915B9DB32389B93C00944194 /* LaunchScreen.storyboard in Resources */, + 9190B5B123A8BEF5009F054F /* Code Nice To Use in Resources */, + 91E769F823F2CD640028BEE4 /* MessageCell.xib in Resources */, + 915B9DB02389B93C00944194 /* Assets.xcassets in Resources */, + 915B9DAE2389B93800944194 /* Main.storyboard in Resources */, + 91E769F923F2CD640028BEE4 /* FriendCellwithXIB.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 26CB0979E08B04F32ADD8123 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-GeekbrainsUI/Pods-GeekbrainsUI-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-GeekbrainsUI/Pods-GeekbrainsUI-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-GeekbrainsUI/Pods-GeekbrainsUI-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + D4277C56730214865E0344C6 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-GeekbrainsUI-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 915B9D9F2389B93800944194 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9194659E23AF997200FB9D6B /* FadePopPushAnimation.swift in Sources */, + 91F217B523D82EAC003FD677 /* VKUserRealm.swift in Sources */, + 917FCC7023DB33B10024C01D /* FriendCD+CoreDataProperties.swift in Sources */, + 9188BF8823CFB3FC0094B95A /* Singleton.swift in Sources */, + 9190B5BD23ACB34E009F054F /* myMenu.swift in Sources */, + 9132FDA923B4876000D0689A /* PopAnimator.swift in Sources */, + 911E5A74239D1377008CD4D6 /* Group.swift in Sources */, + 91ADF80C23D4942500C35F7D /* VKUser.swift in Sources */, + 91E752CB23E1D8AC0028BEE4 /* VKPhotosRealm.swift in Sources */, + 915B9DAB2389B93800944194 /* LoginViewController.swift in Sources */, + 91E752C423E1C2BA0028BEE4 /* GroupRepository.swift in Sources */, + 9190B5A723A79950009F054F /* ShareButton.swift in Sources */, + 91A97D7B2396A62700015775 /* NewGroupList.swift in Sources */, + 911E5A6E239A9F73008CD4D6 /* CustomView.swift in Sources */, + 9132FDBA23B61F5D00D0689A /* FriendsPhotoViewController.swift in Sources */, + 91E752C223E1C1180028BEE4 /* CommonRepository.swift in Sources */, + 91E769FB23F2CD640028BEE4 /* FriendCellwithXIB.swift in Sources */, + 91E752C723E1C6750028BEE4 /* GroupsPresenter.swift in Sources */, + 917FCC6523DB23560024C01D /* FriendRepository.swift in Sources */, + 9117976323DEC4FD0038D4CF /* ThreeCirclesViewController.swift in Sources */, + 914E7F4823E176EB0022BE72 /* FriendsPresenter.swift in Sources */, + 914E8ACE239E5A960020FFD0 /* Gradient.swift in Sources */, + 91F2179223D6F145003FD677 /* FileCacher.swift in Sources */, + 915B9DA72389B93800944194 /* AppDelegate.swift in Sources */, + 91A97D772396A07900015775 /* GroupList.swift in Sources */, + 915B9DA92389B93800944194 /* SceneDelegate.swift in Sources */, + 9190B5A923A79BB8009F054F /* CommentButton.swift in Sources */, + 91E752F723EC29CE0028BEE4 /* GroupsConfigurator.swift in Sources */, + 917FCC6823DB30A10024C01D /* VKDatabase.xcdatamodeld in Sources */, + 9190B5BB23AA75BC009F054F /* AnimationApple.swift in Sources */, + 919465A823AFAC0200FB9D6B /* CustomNavigationControllerAnimation.swift in Sources */, + 91CB234223D9D97F003C9EA8 /* LoadIndicators.swift in Sources */, + 91CB233723D9D57A003C9EA8 /* UserCD+CoreDataClass.swift in Sources */, + 91E752CF23E998150028BEE4 /* UserFirebase.swift in Sources */, + 9164263423CDEF5D00BD5E79 /* LoginController.swift in Sources */, + 91FA97E823AD13D300B9A053 /* groupAnimation.swift in Sources */, + 91ADF80E23D4957100C35F7D /* VKGroup.swift in Sources */, + 9116BAC9239D2607005BF293 /* LikeButton.swift in Sources */, + 9132FDBF23B632CE00D0689A /* CustomInteractor.swift in Sources */, + 91CB233823D9D57A003C9EA8 /* UserCD+CoreDataProperties.swift in Sources */, + 917FCC6F23DB33B10024C01D /* FriendCD+CoreDataClass.swift in Sources */, + 91E752D423E9C1430028BEE4 /* PhotoPresenter.swift in Sources */, + 911E5A70239D08B4008CD4D6 /* CircleShadowImage.swift in Sources */, + 9190B5AF23A7AB0F009F054F /* News.swift in Sources */, + 91E752D123E9A7120028BEE4 /* FirstScreen.swift in Sources */, + 91F217B723D8330F003FD677 /* VKGroupRealm.swift in Sources */, + 9188BF8623CFB36C0094B95A /* VKApi.swift in Sources */, + 9161587823A3C19300F16F42 /* MessageViewController.swift in Sources */, + 919465A023AFA16400FB9D6B /* OtherAnimations.swift in Sources */, + 91E752F523EC25E70028BEE4 /* VKWall.swift in Sources */, + 9164FD352393E33E0089EE2F /* PhotoController.swift in Sources */, + 91E752E523EAC47A0028BEE4 /* FriendCell.swift in Sources */, + 91F2179023D6E222003FD677 /* VKGroupWithNestedContainers.swift in Sources */, + 9194659923AE72B800FB9D6B /* TransitionTestVC.swift in Sources */, + 91E752D623E9C7FD0028BEE4 /* PhotosConfigurator.swift in Sources */, + 91E769FC23F2CD640028BEE4 /* PhotoCell.swift in Sources */, + 9132FDAB23B48AEC00D0689A /* ImageViewerPresenter.swift in Sources */, + 914E7F4A23E176FD0022BE72 /* FriendsConfigurator.swift in Sources */, + 91E769FA23F2CD640028BEE4 /* GroupCell.swift in Sources */, + 91CB234323D9D97F003C9EA8 /* EmitterSnow.swift in Sources */, + 91F217AE23D71D25003FD677 /* CoreDataStack.swift in Sources */, + 9164FD312393C92B0089EE2F /* FriendList.swift in Sources */, + 919465A623AFA7A500FB9D6B /* TransitionAnimations.swift in Sources */, + 91E769FD23F2CD640028BEE4 /* MessageCell.swift in Sources */, + 91E752C923E1D8410028BEE4 /* PhotoRepository.swift in Sources */, + 91CB234523DACC7B003C9EA8 /* CommonResponse.swift in Sources */, + 9132FDBD23B6323600D0689A /* CustomAnimator.swift in Sources */, + 9190B5AD23A7A531009F054F /* ViewButton.swift in Sources */, + 9132FDAD23B48D3800D0689A /* UIApplication+Controller.swift in Sources */, + 91F2178E23D501FA003FD677 /* VKPhotos.swift in Sources */, + 91F217B923D83349003FD677 /* VKWorkWithDBRealm.swift in Sources */, + 9117976923E015480038D4CF /* UserRepository.swift in Sources */, + 911E5A72239D1113008CD4D6 /* Friend.swift in Sources */, + 9161587F23A4F7A700F16F42 /* CustomCollectionViewLayout.swift in Sources */, + 91ADF81023D4999700C35F7D /* VKGroupJSONSerialistion.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 915B9DAC2389B93800944194 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 915B9DAD2389B93800944194 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 915B9DB12389B93C00944194 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 915B9DB22389B93C00944194 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 915B9DB52389B93C00944194 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.2; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 915B9DB62389B93C00944194 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.2; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 915B9DB82389B93C00944194 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E70018ABE5696F2E677B123A /* Pods-GeekbrainsUI.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 5C3KKHP5ML; + INFOPLIST_FILE = GeekbrainsUI/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Pods/Realm/core", + ); + PRODUCT_BUNDLE_IDENTIFIER = home.GeekbrainsUI; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 915B9DB92389B93C00944194 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 004C7893353DC4EB2081909F /* Pods-GeekbrainsUI.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 5C3KKHP5ML; + INFOPLIST_FILE = GeekbrainsUI/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Pods/Realm/core", + ); + PRODUCT_BUNDLE_IDENTIFIER = home.GeekbrainsUI; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 915B9D9E2389B93800944194 /* Build configuration list for PBXProject "GeekbrainsUI" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 915B9DB52389B93C00944194 /* Debug */, + 915B9DB62389B93C00944194 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 915B9DB72389B93C00944194 /* Build configuration list for PBXNativeTarget "GeekbrainsUI" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 915B9DB82389B93C00944194 /* Debug */, + 915B9DB92389B93C00944194 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCVersionGroup section */ + 917FCC6623DB30A10024C01D /* VKDatabase.xcdatamodeld */ = { + isa = XCVersionGroup; + children = ( + 917FCC6723DB30A10024C01D /* VKDatabase.xcdatamodel */, + ); + currentVersion = 917FCC6723DB30A10024C01D /* VKDatabase.xcdatamodel */; + path = VKDatabase.xcdatamodeld; + sourceTree = ""; + versionGroupType = wrapper.xcdatamodel; + }; +/* End XCVersionGroup section */ + }; + rootObject = 915B9D9B2389B93800944194 /* Project object */; +} diff --git a/!main project/GeekbrainsUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/!main project/GeekbrainsUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..46fb613 --- /dev/null +++ b/!main project/GeekbrainsUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/!main project/GeekbrainsUI.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/!main project/GeekbrainsUI.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/!main project/GeekbrainsUI.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/!main project/GeekbrainsUI.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/!main project/GeekbrainsUI.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/!main project/GeekbrainsUI.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/!main project/GeekbrainsUI.xcodeproj/project.xcworkspace/xcuserdata/raskin-sa.xcuserdatad/IDEFindNavigatorScopes.plist b/!main project/GeekbrainsUI.xcodeproj/project.xcworkspace/xcuserdata/raskin-sa.xcuserdatad/IDEFindNavigatorScopes.plist new file mode 100644 index 0000000..5dd5da8 --- /dev/null +++ b/!main project/GeekbrainsUI.xcodeproj/project.xcworkspace/xcuserdata/raskin-sa.xcuserdatad/IDEFindNavigatorScopes.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/!main project/GeekbrainsUI.xcodeproj/project.xcworkspace/xcuserdata/raskin-sa.xcuserdatad/UserInterfaceState.xcuserstate b/!main project/GeekbrainsUI.xcodeproj/project.xcworkspace/xcuserdata/raskin-sa.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..7019b2c Binary files /dev/null and b/!main project/GeekbrainsUI.xcodeproj/project.xcworkspace/xcuserdata/raskin-sa.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/!main project/GeekbrainsUI.xcodeproj/project.xcworkspace/xcuserdata/raskin-sa.xcuserdatad/WorkspaceSettings.xcsettings b/!main project/GeekbrainsUI.xcodeproj/project.xcworkspace/xcuserdata/raskin-sa.xcuserdatad/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..dd7403b --- /dev/null +++ b/!main project/GeekbrainsUI.xcodeproj/project.xcworkspace/xcuserdata/raskin-sa.xcuserdatad/WorkspaceSettings.xcsettings @@ -0,0 +1,16 @@ + + + + + BuildLocationStyle + UseAppPreferences + CustomBuildLocationType + RelativeToDerivedData + DerivedDataLocationStyle + Default + IssueFilterStyle + ShowActiveSchemeOnly + LiveSourceIssuesEnabled + + + diff --git a/!main project/GeekbrainsUI.xcodeproj/xcshareddata/xcschemes/GeekbrainsUI.xcscheme b/!main project/GeekbrainsUI.xcodeproj/xcshareddata/xcschemes/GeekbrainsUI.xcscheme new file mode 100644 index 0000000..ff0b5e8 --- /dev/null +++ b/!main project/GeekbrainsUI.xcodeproj/xcshareddata/xcschemes/GeekbrainsUI.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/GeekbrainsUI.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/!main project/GeekbrainsUI.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..5512d53 --- /dev/null +++ b/!main project/GeekbrainsUI.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,975 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/GeekbrainsUI.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/xcschememanagement.plist b/!main project/GeekbrainsUI.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..dc68a3d --- /dev/null +++ b/!main project/GeekbrainsUI.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,22 @@ + + + + + SchemeUserState + + GeekbrainsUI.xcscheme_^#shared#^_ + + orderHint + 1 + + + SuppressBuildableAutocreation + + 915B9DA22389B93800944194 + + primary + + + + + diff --git a/!main project/GeekbrainsUI.xcworkspace/contents.xcworkspacedata b/!main project/GeekbrainsUI.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..dce7f00 --- /dev/null +++ b/!main project/GeekbrainsUI.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/!main project/GeekbrainsUI.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/!main project/GeekbrainsUI.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/!main project/GeekbrainsUI.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/!main project/GeekbrainsUI.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/!main project/GeekbrainsUI.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/!main project/GeekbrainsUI.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/!main project/GeekbrainsUI.xcworkspace/xcuserdata/raskin-sa.xcuserdatad/IDEFindNavigatorScopes.plist b/!main project/GeekbrainsUI.xcworkspace/xcuserdata/raskin-sa.xcuserdatad/IDEFindNavigatorScopes.plist new file mode 100644 index 0000000..5dd5da8 --- /dev/null +++ b/!main project/GeekbrainsUI.xcworkspace/xcuserdata/raskin-sa.xcuserdatad/IDEFindNavigatorScopes.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/!main project/GeekbrainsUI.xcworkspace/xcuserdata/raskin-sa.xcuserdatad/UserInterfaceState.xcuserstate b/!main project/GeekbrainsUI.xcworkspace/xcuserdata/raskin-sa.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..917f550 Binary files /dev/null and b/!main project/GeekbrainsUI.xcworkspace/xcuserdata/raskin-sa.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/!main project/GeekbrainsUI.xcworkspace/xcuserdata/raskin-sa.xcuserdatad/WorkspaceSettings.xcsettings b/!main project/GeekbrainsUI.xcworkspace/xcuserdata/raskin-sa.xcuserdatad/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..dd7403b --- /dev/null +++ b/!main project/GeekbrainsUI.xcworkspace/xcuserdata/raskin-sa.xcuserdatad/WorkspaceSettings.xcsettings @@ -0,0 +1,16 @@ + + + + + BuildLocationStyle + UseAppPreferences + CustomBuildLocationType + RelativeToDerivedData + DerivedDataLocationStyle + Default + IssueFilterStyle + ShowActiveSchemeOnly + LiveSourceIssuesEnabled + + + diff --git a/!main project/GeekbrainsUI.xcworkspace/xcuserdata/raskin-sa.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/!main project/GeekbrainsUI.xcworkspace/xcuserdata/raskin-sa.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..1918f49 --- /dev/null +++ b/!main project/GeekbrainsUI.xcworkspace/xcuserdata/raskin-sa.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,1300 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/GeekbrainsUI.xcworkspace/xcuserdata/raskin-sa.xcuserdatad/xcdebugger/Expressions.xcexplist b/!main project/GeekbrainsUI.xcworkspace/xcuserdata/raskin-sa.xcuserdatad/xcdebugger/Expressions.xcexplist new file mode 100644 index 0000000..fd216d5 --- /dev/null +++ b/!main project/GeekbrainsUI.xcworkspace/xcuserdata/raskin-sa.xcuserdatad/xcdebugger/Expressions.xcexplist @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/!main project/GeekbrainsUI/AnimatedTransitions/FadePopPushAnimation.swift b/!main project/GeekbrainsUI/AnimatedTransitions/FadePopPushAnimation.swift new file mode 100644 index 0000000..4e03644 --- /dev/null +++ b/!main project/GeekbrainsUI/AnimatedTransitions/FadePopPushAnimation.swift @@ -0,0 +1,43 @@ +// +// FadePopPushAnimation.swift +// GeekbrainsUI +// +// Created by raskin-sa on 22/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit + + +class FadePushAnimation: NSObject, UIViewControllerAnimatedTransitioning{ + func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { + return 1.0 + } + func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + guard let toVC = transitionContext.viewController(forKey: .to) else{return} + + transitionContext.containerView.addSubview(toVC.view) + toVC.view.alpha = 0 + + UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {toVC.view.alpha = 1.0}) + {completion in transitionContext.completeTransition(completion)} + + }// func animateTransition +}//class FadePushAnimation + +class FadePopAnimation: NSObject, UIViewControllerAnimatedTransitioning{ + func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { + return 1.0 + } + func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + guard let toVC = transitionContext.viewController(forKey: .to) else{return} + + transitionContext.containerView.addSubview(toVC.view) + + UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {toVC.view.alpha = 0.0}) + {completion in transitionContext.completeTransition(completion)} + + }// func animateTransition +}//class FadePopAnimation + + diff --git a/!main project/GeekbrainsUI/AnimationPop/ImageViewerPresenter.swift b/!main project/GeekbrainsUI/AnimationPop/ImageViewerPresenter.swift new file mode 100644 index 0000000..74a61ce --- /dev/null +++ b/!main project/GeekbrainsUI/AnimationPop/ImageViewerPresenter.swift @@ -0,0 +1,34 @@ +// +// ImageViewerPresenter.swift +// GeekbrainsUI +// +// Created by raskin-sa on 26/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit + +protocol ImageViewPresenterSource { + var source: UIView? {get} +} + +class ImageViewPresenter: NSObject, UIViewControllerTransitioningDelegate, UINavigationControllerDelegate { + var animatorSource: ImageViewPresenterSource? + var animator = PopAnimator() + + init(delegate: ImageViewPresenterSource){ + animatorSource = delegate + } + + func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC:UIViewController) -> UIViewControllerAnimatedTransitioning? { + guard let sourceView = animatorSource?.source, + let origin = sourceView.superview?.convert(sourceView.frame,to: UIApplication.topViewController()!.navigationController!.view) else{ + return nil + } + animator.originFrame = CGRect(x: origin.minX, + y: origin.minY, + width: origin.size.width, + height: origin.size.height) + return animator + }// func navigationController(_ navigationController: UINavigationController, +}//class ImageViewPresenter diff --git a/!main project/GeekbrainsUI/AnimationPop/PopAnimator.swift b/!main project/GeekbrainsUI/AnimationPop/PopAnimator.swift new file mode 100644 index 0000000..dba6cf6 --- /dev/null +++ b/!main project/GeekbrainsUI/AnimationPop/PopAnimator.swift @@ -0,0 +1,43 @@ +// +// PopAnimator.swift +// GeekbrainsUI +// +// Created by raskin-sa on 26/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit + +class PopAnimator:NSObject, UIViewControllerAnimatedTransitioning{ + let duration = 1.0 + var originFrame = CGRect.zero + + func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { + return duration + } + + func animateTransition(using transitionContext: UIViewControllerContextTransitioning){ + let containerView = transitionContext.containerView + let toView = transitionContext.view(forKey: .to)! + let recipeView = toView + + let finalFrame = recipeView.frame + let xScaleFactor = originFrame.width / finalFrame.width + let yScaleFactor = originFrame.height / finalFrame.height + let scaleTransform = CGAffineTransform(scaleX: xScaleFactor, y: yScaleFactor) + + recipeView.transform = scaleTransform + recipeView.center = CGPoint(x: originFrame.midX, y: originFrame.midY) + + recipeView.clipsToBounds = true + + containerView.addSubview(toView) + containerView.bringSubviewToFront(toView) + + UIView.animate(withDuration: duration, animations:{ + recipeView.transform = .identity + recipeView.center = CGPoint(x: finalFrame.midX, y: finalFrame.midY) + }) {isComplete in transitionContext.completeTransition(isComplete)} + }//func animateTransition + +}//class PopAnimator: diff --git a/!main project/GeekbrainsUI/AnimationPop/UIApplication+Controller.swift b/!main project/GeekbrainsUI/AnimationPop/UIApplication+Controller.swift new file mode 100644 index 0000000..ba6d062 --- /dev/null +++ b/!main project/GeekbrainsUI/AnimationPop/UIApplication+Controller.swift @@ -0,0 +1,27 @@ +// +// UIApplication+Controller.swift +// GeekbrainsUI +// +// Created by raskin-sa on 26/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit + +extension UIApplication { + class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController?{ + if let navigationController = controller as? UINavigationController { + return topViewController(controller: navigationController.visibleViewController) + } // if let navigationController = controller as? + if let tabController = controller as? UITabBarController { + if let selected = tabController.selectedViewController { + return topViewController(controller: selected) + } + }//if let tabController = controller as? + if let presented = controller?.presentedViewController { + return topViewController(controller: presented) + } + return controller + } + +} //extension UIApplication diff --git a/!main project/GeekbrainsUI/Animations/EmitterSnow.swift b/!main project/GeekbrainsUI/Animations/EmitterSnow.swift new file mode 100644 index 0000000..b525741 --- /dev/null +++ b/!main project/GeekbrainsUI/Animations/EmitterSnow.swift @@ -0,0 +1,41 @@ +// +// EmitterSnow.swift +// GeekbrainsUI +// +// Created by raskin-sa on 23/01/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + +import UIKit + +class EmitterSnow{ + //функция посыпает снегом + func letItSnow(duration: Double, view: UIView){ + let emitterSnow = CAEmitterCell() + emitterSnow.contents = UIImage(named:"snow")?.cgImage + emitterSnow.scale = 0.01 + emitterSnow.scaleRange = 0.03 + emitterSnow.birthRate = 40 + emitterSnow.lifetime = 10 + emitterSnow.velocity = -30 + emitterSnow.velocityRange = -20 + emitterSnow.yAcceleration = 30 + emitterSnow.xAcceleration = 5 + emitterSnow.spin = -0.5 + emitterSnow.spinRange = 1.0 + + let snowEmitterLayer = CAEmitterLayer() + + // центр анимации + snowEmitterLayer.emitterPosition = CGPoint(x: view.bounds.width / 2, y: 50) + snowEmitterLayer.emitterSize = CGSize(width: view.bounds.width, height: 0) + snowEmitterLayer.emitterShape = .circle + snowEmitterLayer.beginTime = CACurrentMediaTime() + 0.5 + // snowEmitterLayer.timeOffset = 10 + snowEmitterLayer.duration = duration + snowEmitterLayer.emitterCells = [emitterSnow] + + view.layer.addSublayer(snowEmitterLayer) + + } +}//class EmitterSnow diff --git a/!main project/GeekbrainsUI/Animations/LoadIndicators.swift b/!main project/GeekbrainsUI/Animations/LoadIndicators.swift new file mode 100644 index 0000000..716cffb --- /dev/null +++ b/!main project/GeekbrainsUI/Animations/LoadIndicators.swift @@ -0,0 +1,67 @@ +// +// LoadIndicators.swift +// GeekbrainsUI +// +// Created by raskin-sa on 23/01/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + +import UIKit + +class LoadIndicators { + + + + //MARK: здесь будет функция, выводящая индикатор + //функция выводит три мигающих круга + + // @IBOutlet weak var animationCircle1: UIImageView! + // @IBOutlet weak var animationCircle2: UIImageView! + // @IBOutlet weak var animationCircle3: UIImageView! + // @IBOutlet weak var animationLabel1: UILabel! + + func animationLoad(duration: Double){ + // + // //animationLabel1.isHidden = false + // animationCircle1.isHidden = false + // + // UIView.animate( withDuration: duration/5, + // delay: 0.0, + // options: [.repeat,.autoreverse], + // animations:{ self.animationCircle1.alpha = 0.3}, + // completion: { _ in UIView.animate( withDuration: duration/5, + // delay: 0.0, + // options: [], + // animations:{ self.animationCircle1.alpha = 1; self.animationCircle1.isHidden = true}) }) + // + // animationCircle2.isHidden = false + // UIView.animate( withDuration: duration/5, + // delay: 1.0, + // options: [.repeat,.autoreverse], + // animations:{ self.animationCircle2.alpha = 0.3}, + // completion: { _ in UIView.animate( withDuration: duration/5, + // delay: 0.0, + // options: [], + // animations:{ self.animationCircle2.alpha = 1; self.animationCircle2.isHidden = true}) }) + // + // animationCircle3.isHidden = false + // UIView.animate( withDuration: duration/5, + // delay: 2.0, + // options: [.repeat,.autoreverse], + // animations:{ self.animationCircle3.alpha = 0.3}, + // completion: { _ in UIView.animate( withDuration: duration/5, + // delay: 0.0, + // options: [], + // animations:{ self.animationCircle3.alpha = 1; self.animationCircle3.isHidden = true}) }) + // + // animationLabel1.isHidden = true + // + // /* + // //делаем кнопку недоступной пока идет снег (= duration) + // self.toGroup.isEnabled = false + // DispatchQueue.main.asyncAfter(deadline: .now() + duration) { + // self.snowButton.isEnabled = true + // } + // */ + } +}// class LoadIndicators diff --git a/!main project/GeekbrainsUI/AppDelegate.swift b/!main project/GeekbrainsUI/AppDelegate.swift new file mode 100644 index 0000000..2ae7b78 --- /dev/null +++ b/!main project/GeekbrainsUI/AppDelegate.swift @@ -0,0 +1,64 @@ +// +// AppDelegate.swift +// GeekbrainsUI +// +// Created by raskin-sa on 23/11/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit +import RealmSwift +import Foundation +import Firebase + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + + setRealmConfig(version: 7) + + FirebaseApp.configure() + + return true + + } + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } + + func setRealmConfig(version: UInt64) { + let config = Realm.Configuration( + // Set the new schema version. This must be greater than the previously used + // version (if you've never set a schema version before, the version is 0). + schemaVersion: version, + // Set the block which will be called automatically when opening a Realm with + // a schema version lower than the one set above + migrationBlock: { migration, oldSchemaVersion in + // We haven’t migrated anything yet, so oldSchemaVersion == 0 + if (oldSchemaVersion < 1) { + // Nothing to do! + // Realm will automatically detect new properties and removed properties + // And will update the schema on disk automatically + } + }) + Realm.Configuration.defaultConfiguration = config + + print(config.fileURL!) + } + +} + diff --git a/!main project/GeekbrainsUI/Application/GlobalVariables.swift b/!main project/GeekbrainsUI/Application/GlobalVariables.swift new file mode 100644 index 0000000..87888e6 --- /dev/null +++ b/!main project/GeekbrainsUI/Application/GlobalVariables.swift @@ -0,0 +1,53 @@ +// +// CommonResponse.swift +// GeekbrainsUI +// +// Created by raskin-sa on 24/01/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + +import Foundation +import UIKit + +let debugMode = 1 //1 - debug on, 0 - debug off + +struct CommonResponse: Decodable { + var response: CommonResponseArray +} + +struct CommonResponseArray: Decodable{ + var count: Int + var items: [T] +} + +enum DatabaseType { + case Realm + case CoreData +} + + typealias Out = Swift.Result + + var databaseMode = true //флаг пишем ли в БД или только с Web работаем + var webMode = false //флаг работаем офлайн или с обращением к интернету + +func showYesNoMessage(view: UIViewController, title: String, messagetext: String, completion:@escaping (_ result:Bool) -> Void) { + + // Создаем контроллер + let alert = UIAlertController(title: title, message: messagetext, preferredStyle: .alert) + // Создаем кнопку для UIAlertController + let action = UIAlertAction(title: "Cancel", style: .cancel, handler: {action in + completion(false) + }) + alert.addAction(action) + + let action2 = UIAlertAction(title: "Delete", style: .destructive, handler: {action in + completion(true) + + }) + alert.addAction(action2) + // Показываем UIAlertController + view.present(alert, animated: true, completion: nil ) + +} + + diff --git a/!main project/GeekbrainsUI/Assets.xcassets/120px-VK.com-logo.svg.png b/!main project/GeekbrainsUI/Assets.xcassets/120px-VK.com-logo.svg.png new file mode 100644 index 0000000..56e36f0 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/120px-VK.com-logo.svg.png differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/AppIcon.appiconset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d8db8d6 --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/AserTwo.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/AserTwo.imageset/Contents.json new file mode 100644 index 0000000..196a43e --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/AserTwo.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "User_with_smile.svg.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/AserTwo.imageset/User_with_smile.svg.png b/!main project/GeekbrainsUI/Assets.xcassets/AserTwo.imageset/User_with_smile.svg.png new file mode 100644 index 0000000..42b2031 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/AserTwo.imageset/User_with_smile.svg.png differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/Group2.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/Group2.imageset/Contents.json new file mode 100644 index 0000000..08a951f --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/Group2.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Group2-1.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Group2.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/Group2.imageset/Group2-1.png b/!main project/GeekbrainsUI/Assets.xcassets/Group2.imageset/Group2-1.png new file mode 100644 index 0000000..810fb52 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/Group2.imageset/Group2-1.png differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/Group2.imageset/Group2.png b/!main project/GeekbrainsUI/Assets.xcassets/Group2.imageset/Group2.png new file mode 100644 index 0000000..810fb52 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/Group2.imageset/Group2.png differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/Group4.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/Group4.imageset/Contents.json new file mode 100644 index 0000000..e3c7d79 --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/Group4.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Group4.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/Group4.imageset/Group4.png b/!main project/GeekbrainsUI/Assets.xcassets/Group4.imageset/Group4.png new file mode 100644 index 0000000..53daaa1 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/Group4.imageset/Group4.png differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/LaunchImage.launchimage/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/LaunchImage.launchimage/Contents.json new file mode 100644 index 0000000..51a0779 --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/LaunchImage.launchimage/Contents.json @@ -0,0 +1,190 @@ +{ + "images" : [ + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "12.0", + "subtype" : "2688h", + "scale" : "3x" + }, + { + "orientation" : "landscape", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "12.0", + "subtype" : "2688h", + "scale" : "3x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "12.0", + "subtype" : "1792h", + "scale" : "2x" + }, + { + "orientation" : "landscape", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "12.0", + "subtype" : "1792h", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "11.0", + "subtype" : "2436h", + "scale" : "3x" + }, + { + "orientation" : "landscape", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "11.0", + "subtype" : "2436h", + "scale" : "3x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "8.0", + "subtype" : "736h", + "scale" : "3x" + }, + { + "orientation" : "landscape", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "8.0", + "subtype" : "736h", + "scale" : "3x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "8.0", + "subtype" : "667h", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "subtype" : "retina4", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "scale" : "1x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "subtype" : "retina4", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "to-status-bar", + "scale" : "1x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "scale" : "1x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "to-status-bar", + "scale" : "1x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "scale" : "1x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "to-status-bar", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "scale" : "2x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "to-status-bar", + "scale" : "2x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/NewGroup.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/NewGroup.imageset/Contents.json new file mode 100644 index 0000000..b418199 --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/NewGroup.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "NewGroup.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/NewGroup.imageset/NewGroup.png b/!main project/GeekbrainsUI/Assets.xcassets/NewGroup.imageset/NewGroup.png new file mode 100644 index 0000000..f044d03 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/NewGroup.imageset/NewGroup.png differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/NewUser.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/NewUser.imageset/Contents.json new file mode 100644 index 0000000..a04d5b9 --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/NewUser.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "NewUser.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/NewUser.imageset/NewUser.png b/!main project/GeekbrainsUI/Assets.xcassets/NewUser.imageset/NewUser.png new file mode 100644 index 0000000..233fea6 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/NewUser.imageset/NewUser.png differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/NikolayRasskazov1.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/NikolayRasskazov1.imageset/Contents.json new file mode 100644 index 0000000..8120b2f --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/NikolayRasskazov1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "NikolayRasskazov1.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/NikolayRasskazov1.imageset/NikolayRasskazov1.jpg b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/NikolayRasskazov1.imageset/NikolayRasskazov1.jpg new file mode 100644 index 0000000..84a0684 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/NikolayRasskazov1.imageset/NikolayRasskazov1.jpg differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/NikolayRasskazov2.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/NikolayRasskazov2.imageset/Contents.json new file mode 100644 index 0000000..5a78cb5 --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/NikolayRasskazov2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "NikolayRasskazov2.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/NikolayRasskazov2.imageset/NikolayRasskazov2.jpg b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/NikolayRasskazov2.imageset/NikolayRasskazov2.jpg new file mode 100644 index 0000000..3faa2b6 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/NikolayRasskazov2.imageset/NikolayRasskazov2.jpg differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/berseneva1.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/berseneva1.imageset/Contents.json new file mode 100644 index 0000000..ebecd43 --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/berseneva1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "berseneva1.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/berseneva1.imageset/berseneva1.png b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/berseneva1.imageset/berseneva1.png new file mode 100644 index 0000000..51879cf Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/berseneva1.imageset/berseneva1.png differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/berseneva2.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/berseneva2.imageset/Contents.json new file mode 100644 index 0000000..d37a965 --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/berseneva2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "berseneva2.jpeg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/berseneva2.imageset/berseneva2.jpeg b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/berseneva2.imageset/berseneva2.jpeg new file mode 100644 index 0000000..d658d02 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/berseneva2.imageset/berseneva2.jpeg differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/berseneva3.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/berseneva3.imageset/Contents.json new file mode 100644 index 0000000..340994f --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/berseneva3.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "berseneva3.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/berseneva3.imageset/berseneva3.jpg b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/berseneva3.imageset/berseneva3.jpg new file mode 100644 index 0000000..86b00bc Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/berseneva3.imageset/berseneva3.jpg differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/lautaro martinez1.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/lautaro martinez1.imageset/Contents.json new file mode 100644 index 0000000..2b85ca2 --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/lautaro martinez1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "lautaro martinez1.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/lautaro martinez1.imageset/lautaro martinez1.jpg b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/lautaro martinez1.imageset/lautaro martinez1.jpg new file mode 100644 index 0000000..78e7a67 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/lautaro martinez1.imageset/lautaro martinez1.jpg differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/lautaro martinez2.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/lautaro martinez2.imageset/Contents.json new file mode 100644 index 0000000..565a3fe --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/lautaro martinez2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "lautaro-martinez-2.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/lautaro martinez2.imageset/lautaro-martinez-2.jpg b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/lautaro martinez2.imageset/lautaro-martinez-2.jpg new file mode 100644 index 0000000..aa51841 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/lautaro martinez2.imageset/lautaro-martinez-2.jpg differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/lautaro martinez3.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/lautaro martinez3.imageset/Contents.json new file mode 100644 index 0000000..0db9aa5 --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/lautaro martinez3.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "lautaro martinez3.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/lautaro martinez3.imageset/lautaro martinez3.jpg b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/lautaro martinez3.imageset/lautaro martinez3.jpg new file mode 100644 index 0000000..cd6e2ff Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/lautaro martinez3.imageset/lautaro martinez3.jpg differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/sergei afanasiev1.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/sergei afanasiev1.imageset/Contents.json new file mode 100644 index 0000000..4a37468 --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/sergei afanasiev1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "sergei afanasiev1.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/sergei afanasiev1.imageset/sergei afanasiev1.jpg b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/sergei afanasiev1.imageset/sergei afanasiev1.jpg new file mode 100644 index 0000000..114e6fa Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/sergei afanasiev1.imageset/sergei afanasiev1.jpg differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/sergei afanasiev2.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/sergei afanasiev2.imageset/Contents.json new file mode 100644 index 0000000..202f9c0 --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/sergei afanasiev2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "sergei afanasiev2.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/sergei afanasiev2.imageset/sergei afanasiev2.jpg b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/sergei afanasiev2.imageset/sergei afanasiev2.jpg new file mode 100644 index 0000000..562872a Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/sergei afanasiev2.imageset/sergei afanasiev2.jpg differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/stezhko1.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/stezhko1.imageset/Contents.json new file mode 100644 index 0000000..68f3817 --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/stezhko1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "stezhko1.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/stezhko1.imageset/stezhko1.jpg b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/stezhko1.imageset/stezhko1.jpg new file mode 100644 index 0000000..f06491c Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/stezhko1.imageset/stezhko1.jpg differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/stezhko2.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/stezhko2.imageset/Contents.json new file mode 100644 index 0000000..c30941f --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/stezhko2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "stezhko2.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/stezhko2.imageset/stezhko2.jpg b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/stezhko2.imageset/stezhko2.jpg new file mode 100644 index 0000000..6565e47 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/stezhko2.imageset/stezhko2.jpg differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/stezhko3.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/stezhko3.imageset/Contents.json new file mode 100644 index 0000000..f4ecd19 --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/stezhko3.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "stezhko3.jpeg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/stezhko3.imageset/stezhko3.jpeg b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/stezhko3.imageset/stezhko3.jpeg new file mode 100644 index 0000000..3fa14b2 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/PhotoArray/stezhko3.imageset/stezhko3.jpeg differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/UserFour.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/UserFour.imageset/Contents.json new file mode 100644 index 0000000..262a97e --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/UserFour.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "UserFour.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/UserFour.imageset/UserFour.png b/!main project/GeekbrainsUI/Assets.xcassets/UserFour.imageset/UserFour.png new file mode 100644 index 0000000..37e7782 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/UserFour.imageset/UserFour.png differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/UserOne.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/UserOne.imageset/Contents.json new file mode 100644 index 0000000..5c1fa81 --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/UserOne.imageset/Contents.json @@ -0,0 +1,18 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "UserOne-1.jpg" + }, + { + "idiom" : "iphone" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "auto-scaling" : "auto" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/UserOne.imageset/UserOne-1.jpg b/!main project/GeekbrainsUI/Assets.xcassets/UserOne.imageset/UserOne-1.jpg new file mode 100644 index 0000000..6b43012 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/UserOne.imageset/UserOne-1.jpg differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/UserThree.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/UserThree.imageset/Contents.json new file mode 100644 index 0000000..03d0741 --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/UserThree.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "UserThree.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/UserThree.imageset/UserThree.jpg b/!main project/GeekbrainsUI/Assets.xcassets/UserThree.imageset/UserThree.jpg new file mode 100644 index 0000000..f4721ce Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/UserThree.imageset/UserThree.jpg differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/UserTwo.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/UserTwo.imageset/Contents.json new file mode 100644 index 0000000..372467e --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/UserTwo.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "UserTwo.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "compression-type" : "automatic" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/UserTwo.imageset/UserTwo.jpg b/!main project/GeekbrainsUI/Assets.xcassets/UserTwo.imageset/UserTwo.jpg new file mode 100644 index 0000000..7029ed7 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/UserTwo.imageset/UserTwo.jpg differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/VK logo-1.imageset/120px-VK.com-logo.svg.png b/!main project/GeekbrainsUI/Assets.xcassets/VK logo-1.imageset/120px-VK.com-logo.svg.png new file mode 100644 index 0000000..56e36f0 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/VK logo-1.imageset/120px-VK.com-logo.svg.png differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/VK logo-1.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/VK logo-1.imageset/Contents.json new file mode 100644 index 0000000..f36e78e --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/VK logo-1.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "120px-VK.com-logo.svg.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/VK logo.imageset/120px-VK.com-logo.svg.png b/!main project/GeekbrainsUI/Assets.xcassets/VK logo.imageset/120px-VK.com-logo.svg.png new file mode 100644 index 0000000..56e36f0 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/VK logo.imageset/120px-VK.com-logo.svg.png differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/VK logo.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/VK logo.imageset/Contents.json new file mode 100644 index 0000000..f36e78e --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/VK logo.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "120px-VK.com-logo.svg.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/blackCircle.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/blackCircle.imageset/Contents.json new file mode 100644 index 0000000..684244d --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/blackCircle.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "blackCircle.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/blackCircle.imageset/blackCircle.jpg b/!main project/GeekbrainsUI/Assets.xcassets/blackCircle.imageset/blackCircle.jpg new file mode 100644 index 0000000..41e8fa7 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/blackCircle.imageset/blackCircle.jpg differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/blackCircle1.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/blackCircle1.imageset/Contents.json new file mode 100644 index 0000000..51ae867 --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/blackCircle1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "black_circle1.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/blackCircle1.imageset/black_circle1.jpg b/!main project/GeekbrainsUI/Assets.xcassets/blackCircle1.imageset/black_circle1.jpg new file mode 100644 index 0000000..8ffcbc7 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/blackCircle1.imageset/black_circle1.jpg differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/colibri.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/colibri.imageset/Contents.json new file mode 100644 index 0000000..1b4961c --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/colibri.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "a-hummingbird-5a2a9ce61dbad4.6791529915127421181218.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/colibri.imageset/a-hummingbird-5a2a9ce61dbad4.6791529915127421181218.jpg b/!main project/GeekbrainsUI/Assets.xcassets/colibri.imageset/a-hummingbird-5a2a9ce61dbad4.6791529915127421181218.jpg new file mode 100644 index 0000000..68049cf Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/colibri.imageset/a-hummingbird-5a2a9ce61dbad4.6791529915127421181218.jpg differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/dislike.imageset/43179012-heart-icon-love-sign-life-symbol-linear-outline-icon-on-white-background-vector.jpg b/!main project/GeekbrainsUI/Assets.xcassets/dislike.imageset/43179012-heart-icon-love-sign-life-symbol-linear-outline-icon-on-white-background-vector.jpg new file mode 100644 index 0000000..f5c4dd6 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/dislike.imageset/43179012-heart-icon-love-sign-life-symbol-linear-outline-icon-on-white-background-vector.jpg differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/dislike.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/dislike.imageset/Contents.json new file mode 100644 index 0000000..c45fe6f --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/dislike.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "43179012-heart-icon-love-sign-life-symbol-linear-outline-icon-on-white-background-vector.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/eagle.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/eagle.imageset/Contents.json new file mode 100644 index 0000000..5df6952 --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/eagle.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "images.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/eagle.imageset/images.png b/!main project/GeekbrainsUI/Assets.xcassets/eagle.imageset/images.png new file mode 100644 index 0000000..048e8d7 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/eagle.imageset/images.png differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/images.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/images.imageset/Contents.json new file mode 100644 index 0000000..f8f827e --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/images.imageset/Contents.json @@ -0,0 +1,20 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/like.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/like.imageset/Contents.json new file mode 100644 index 0000000..968a57d --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/like.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "images.jpeg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/like.imageset/images.jpeg b/!main project/GeekbrainsUI/Assets.xcassets/like.imageset/images.jpeg new file mode 100644 index 0000000..0cb727e Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/like.imageset/images.jpeg differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/news1.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/news1.imageset/Contents.json new file mode 100644 index 0000000..43832e6 --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/news1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "news1.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/news1.imageset/news1.jpg b/!main project/GeekbrainsUI/Assets.xcassets/news1.imageset/news1.jpg new file mode 100644 index 0000000..070ae7f Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/news1.imageset/news1.jpg differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/news2.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/news2.imageset/Contents.json new file mode 100644 index 0000000..9274960 --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/news2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "news2.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/news2.imageset/news2.jpg b/!main project/GeekbrainsUI/Assets.xcassets/news2.imageset/news2.jpg new file mode 100644 index 0000000..e746301 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/news2.imageset/news2.jpg differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/news3.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/news3.imageset/Contents.json new file mode 100644 index 0000000..bdd863b --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/news3.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "news3.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/news3.imageset/news3.jpg b/!main project/GeekbrainsUI/Assets.xcassets/news3.imageset/news3.jpg new file mode 100644 index 0000000..ceda0a1 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/news3.imageset/news3.jpg differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/news4.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/news4.imageset/Contents.json new file mode 100644 index 0000000..0386667 --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/news4.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "news4.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/news4.imageset/news4.jpg b/!main project/GeekbrainsUI/Assets.xcassets/news4.imageset/news4.jpg new file mode 100644 index 0000000..e19210e Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/news4.imageset/news4.jpg differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/news5.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/news5.imageset/Contents.json new file mode 100644 index 0000000..3c89700 --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/news5.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "news5.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/news5.imageset/news5.jpg b/!main project/GeekbrainsUI/Assets.xcassets/news5.imageset/news5.jpg new file mode 100644 index 0000000..971a564 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/news5.imageset/news5.jpg differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/news6.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/news6.imageset/Contents.json new file mode 100644 index 0000000..8ce67a2 --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/news6.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "news6.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/news6.imageset/news6.jpg b/!main project/GeekbrainsUI/Assets.xcassets/news6.imageset/news6.jpg new file mode 100644 index 0000000..b89a7e6 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/news6.imageset/news6.jpg differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/pigeon.imageset/1555481080_pigeon-51.jpg b/!main project/GeekbrainsUI/Assets.xcassets/pigeon.imageset/1555481080_pigeon-51.jpg new file mode 100644 index 0000000..e8b6e09 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/pigeon.imageset/1555481080_pigeon-51.jpg differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/pigeon.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/pigeon.imageset/Contents.json new file mode 100644 index 0000000..abfb4ae --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/pigeon.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "1555481080_pigeon-51.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "original" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/snow.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/snow.imageset/Contents.json new file mode 100644 index 0000000..f164cbb --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/snow.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "FAVPNG_snowflake_njTk6fAR.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Assets.xcassets/snow.imageset/FAVPNG_snowflake_njTk6fAR.png b/!main project/GeekbrainsUI/Assets.xcassets/snow.imageset/FAVPNG_snowflake_njTk6fAR.png new file mode 100644 index 0000000..0f0421d Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/snow.imageset/FAVPNG_snowflake_njTk6fAR.png differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/snow1.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/snow1.imageset/Contents.json new file mode 100644 index 0000000..c3539e5 --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/snow1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Снежинка.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git "a/!main project/GeekbrainsUI/Assets.xcassets/snow1.imageset/\320\241\320\275\320\265\320\266\320\270\320\275\320\272\320\260.png" "b/!main project/GeekbrainsUI/Assets.xcassets/snow1.imageset/\320\241\320\275\320\265\320\266\320\270\320\275\320\272\320\260.png" new file mode 100644 index 0000000..4af196d Binary files /dev/null and "b/!main project/GeekbrainsUI/Assets.xcassets/snow1.imageset/\320\241\320\275\320\265\320\266\320\270\320\275\320\272\320\260.png" differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/snow2.imageset/06579292.png b/!main project/GeekbrainsUI/Assets.xcassets/snow2.imageset/06579292.png new file mode 100644 index 0000000..2bc22a2 Binary files /dev/null and b/!main project/GeekbrainsUI/Assets.xcassets/snow2.imageset/06579292.png differ diff --git a/!main project/GeekbrainsUI/Assets.xcassets/snow2.imageset/Contents.json b/!main project/GeekbrainsUI/Assets.xcassets/snow2.imageset/Contents.json new file mode 100644 index 0000000..c042d2c --- /dev/null +++ b/!main project/GeekbrainsUI/Assets.xcassets/snow2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "06579292.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git "a/!main project/GeekbrainsUI/Assets.xcassets/\320\233\321\216\320\261\320\270\321\202\320\265\320\273\320\270 \320\272\320\276\321\200\320\276\320\262.imageset/Contents.json" "b/!main project/GeekbrainsUI/Assets.xcassets/\320\233\321\216\320\261\320\270\321\202\320\265\320\273\320\270 \320\272\320\276\321\200\320\276\320\262.imageset/Contents.json" new file mode 100644 index 0000000..162f3f5 --- /dev/null +++ "b/!main project/GeekbrainsUI/Assets.xcassets/\320\233\321\216\320\261\320\270\321\202\320\265\320\273\320\270 \320\272\320\276\321\200\320\276\320\262.imageset/Contents.json" @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Group3.jpeg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git "a/!main project/GeekbrainsUI/Assets.xcassets/\320\233\321\216\320\261\320\270\321\202\320\265\320\273\320\270 \320\272\320\276\321\200\320\276\320\262.imageset/Group3.jpeg" "b/!main project/GeekbrainsUI/Assets.xcassets/\320\233\321\216\320\261\320\270\321\202\320\265\320\273\320\270 \320\272\320\276\321\200\320\276\320\262.imageset/Group3.jpeg" new file mode 100644 index 0000000..a2d8018 Binary files /dev/null and "b/!main project/GeekbrainsUI/Assets.xcassets/\320\233\321\216\320\261\320\270\321\202\320\265\320\273\320\270 \320\272\320\276\321\200\320\276\320\262.imageset/Group3.jpeg" differ diff --git "a/!main project/GeekbrainsUI/Assets.xcassets/\320\236\320\264\320\275\320\276\320\272\320\273\320\260\321\201\321\201\320\275\320\270\320\272\320\270.imageset/Contents.json" "b/!main project/GeekbrainsUI/Assets.xcassets/\320\236\320\264\320\275\320\276\320\272\320\273\320\260\321\201\321\201\320\275\320\270\320\272\320\270.imageset/Contents.json" new file mode 100644 index 0000000..d7846a7 --- /dev/null +++ "b/!main project/GeekbrainsUI/Assets.xcassets/\320\236\320\264\320\275\320\276\320\272\320\273\320\260\321\201\321\201\320\275\320\270\320\272\320\270.imageset/Contents.json" @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Group1.jpeg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git "a/!main project/GeekbrainsUI/Assets.xcassets/\320\236\320\264\320\275\320\276\320\272\320\273\320\260\321\201\321\201\320\275\320\270\320\272\320\270.imageset/Group1.jpeg" "b/!main project/GeekbrainsUI/Assets.xcassets/\320\236\320\264\320\275\320\276\320\272\320\273\320\260\321\201\321\201\320\275\320\270\320\272\320\270.imageset/Group1.jpeg" new file mode 100644 index 0000000..3183712 Binary files /dev/null and "b/!main project/GeekbrainsUI/Assets.xcassets/\320\236\320\264\320\275\320\276\320\272\320\273\320\260\321\201\321\201\320\275\320\270\320\272\320\270.imageset/Group1.jpeg" differ diff --git a/!main project/GeekbrainsUI/BarButtonItem_Add.swift b/!main project/GeekbrainsUI/BarButtonItem_Add.swift new file mode 100644 index 0000000..de53119 --- /dev/null +++ b/!main project/GeekbrainsUI/BarButtonItem_Add.swift @@ -0,0 +1,15 @@ +// +// BarButtonItem_Add.swift +// GeekbrainsUI +// +// Created by raskin-sa on 02/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit + +class BarButtonItemAdd: UIBarButtonItem { + + + } + diff --git a/!main project/GeekbrainsUI/Base.lproj/LaunchScreen.storyboard b/!main project/GeekbrainsUI/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..552692b --- /dev/null +++ b/!main project/GeekbrainsUI/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/GeekbrainsUI/Base.lproj/Main.storyboard b/!main project/GeekbrainsUI/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3630e6 --- /dev/null +++ b/!main project/GeekbrainsUI/Base.lproj/Main.storyboard @@ -0,0 +1,14546 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +YnBsaXN0MDDUAQIDBAUGaWpYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoK8QGAcI +ExQbICElJi0wNTg5PkFCRU9XW19jZlUkbnVsbNUJCgsMDQ4PEBESVk5TU2l6ZVYkY2xhc3NcTlNJbWFn +ZUZsYWdzVk5TUmVwc1dOU0NvbG9ygAKAFxIgwAAAgAOAEVp7MTIwLCAxMjB90hUKFhpaTlMub2JqZWN0 +c6MXGBmABIAKgA2AENIVChwfoh0egAWABoAJEADSIgojJF8QFE5TVElGRlJlcHJlc2VudGF0aW9ugAeA +CE8R7iZNTQAqAADhCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMFBwsNFR0tEBkjNxQfK0QUHytEFB8rRBQfK0QUHytEFB8r +RBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8r +RBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8r +RBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8r +RBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8r +RBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBAZIzcNFR0tAwUH +CwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAgMEBhglNFEvSWafQmWN3Ux1ov5MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWi +/kJljdwvSGWeGCUzUAIDBAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAUHChAmOlF+RmyW60x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0ZrluolOU98BAYJDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGyo6 +W0VqlOhMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU5xooOFgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEBgkwSmiiTHWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/8vSWafAgQFCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAcKDzpZfMJMdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/OVh7wAQGCQ4AAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBAYJOll7wUx1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/zlYe8ACBAUIAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwSmiiTHWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/8vSGSdAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABsqOltMdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Gig4VwAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQcKEEVqlOhMdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqT5gQGCQ4AAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJjpRfkx1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/yQ4TnoAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwQGRmyW60x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0VrlekCAwQGAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYJjRSTHWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/8XJDJOAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvSWafTHWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/8uR2ObAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCZY3dTHWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9BZIzbAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMFBwtMdaL+THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9LdKL9AwUG +CgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0UGytMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/DBMa +KQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEaJDlMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/EBkj +NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9agKr/ZIev/2SHr/9sjbP/cJG1 +/3CRtf9wkbX/bI2z/2SHr/9Zf6n/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/16DrP+gts7/2OHr//T3+v////////////////////////// +////////////////////////////9Pb5/9Hb5/+Xr8r/UXmm/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9QeKX/a42z/3CRtf92lbn/fJq8 +/3yavP98mrz/fJq8/3yavP98mrz/fJq8/3yavP98mrz/Yoav/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/f5y9//T3+v////////////////////////////////////////// +////////////////////////////////////////////ytbk/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/1J5pv9liLD/ZIev/2SHr/9wkbX/cJG1 +/3CRtf9wkbX/cJG1/3CRtf9wkbX/cJG1/3CRtf9wkbX/dpW5/2KGr/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/3qZu//y9fn///////////////////// +/////////////////////////////////////////////v7+/7zM3f9QeKX/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9Reab/8vX4//////////////////////////////////////////////// +/////////////////////////////////////////////////3GStv9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/fJq8//f5+/////////////////////////// +///////////////////////////////////////////////////////d5e7/WH6p/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/7XG2f////////////////////////// +//////////////////////////////////////////////////////+nu9L/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/VHun/4Gevv/V3+n///////////////////////////////////// +/////////////////////////////////////////////////5auyf9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9QeKX/7fH2//////////////////////////////// +////////////////////////////////////////////////////////////haHA/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/5Cpxv////////////////////////// +///////////////////////////////////////////////////////y9fn/U3qn/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Od6T/wtDg//////////////////////////////// +/////////////////////////////////////////////////6K3z/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/+LpsP///////////////////////////////////// +////////////////////////////////////////////////////////////c5O3/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/1J6pv/w9Pf///////////////////// +////////////////////////////////////////////////////////////k6zH/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/W4Cr//v8/f////////////////////////// +/////////////////////////////////////////////////6G2z/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o//V3+n///////////////////////////////////// +///////////////////////////////////////////////////////u8vb/Tnek/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/+mutH///////////////////// +////////////////////////////////////////////////////////////4Ofv/012o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/9Lc6P////////////////////////// +/////////////////////////////////////////////////6C1zv9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/2+Qtf////////////////////////////////////////// +//////////////////////////////////////////////////////+lutH/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9bgav/+fr8//////////////// +/////////////////////////////////////////////////////////////////4Kfv/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/6/B1v////////////////////////// +/////////////////////////////////////////////////560zf9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/8HP3/////////////////////////////////////////// +//////////////////////////////////////////////////H0+P9Yfqn/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/scPX//////////////// +/////////////////////////////////////////////////////////////////9jh6/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/5uyy/////////////////////////// +/////////////////////////////////////////////////5uyy/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/ZIev//z9/v////////////////////////////////////////// +/////////////////////////////////////////////////5Osx/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/XoOs//n6/P////////// +//////////////////////////////////////////////////////////////////////98mrz/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/4+oxf////////////////////////// +/////////////////////////////////////////////////5auyf9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/usnb//////////////////////////////////////////////// +////////////////////////////////////////////2+Pt/012pP9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/7DC1/////////// +///////////////////////////////////////////////////////////////////////c5O3/TXaj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/4ahwf////////////////////////// +/////////////////////////////////////////////////5Grx/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9pi7L//P3+//////////////////////////////////////////////// +///////////////////////////////////////9/f7/c5O3/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/16DrP/5+vz///// +////////////////////////////////////////////////////////////////////////////iqXD +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/3+dvf////////////////////////// +/////////////////////////////////////////////////42nxf9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o//G0+L///////////////////////////////////////////////////// +//////////////////////////////////////+nu9L/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/+wwtb///// +////////////////////////////////////////////////////////////////////////////5+3z +/1J6pv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/3+dvf////////////////////////// +/////////////////////////////////////////////////4ikwv9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/3aVuf/+/v////////////////////////////////////////////////////// +/////////////////////////////////9ni7P9Pd6T/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9dgqz/+Pr7 +//////////////////////////////////////////////////////////////////////////////// +/6m90/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/3+dvf////////////////////////// +/////////////////////////////////////////////////4SgwP9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/UHil/+Dn7/////////////////////////////////////////////////////////// +////////////////////////////9/n7/2aJsP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/sMLW +//////////////////////////////////////////////////////////////////////////////// +//v8/f9pjLL/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/4Cdvv////////////////////////// +/////////////////////////////////////////////////4Cdvv9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/oLXO//////////////////////////////////////////////////////////////// +////////////////////////////kqvH/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/XYKs +//j6+/////////////////////////////////////////////////////////////////////////// +///////U3un/TXaj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/4eiwf////////////////////////// +/////////////////////////////////////////////////32bvP9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9liLD/+Pr7//////////////////////////////////////////////////////////////// +//////////////////////+/zt7/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/6i80v////////////////////////////////////////////////////////////////////////// +////////////karG/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/5auyf////////////////////////// +/////////////////////////////////////////////////4Cdvv9MdaP/THWj/0x1o/9MdaP/THWj +/012pP/U3un///////////////////////////////////////////////////////////////////// +/////////////////+Tq8f9VfKj/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/1V8qP/u8vb///////////////////////////////////////////////////////////////////// +////////////8/b5/2GFrv9MdaP/THWj/0x1o/9MdaP/THWj/6q+0/////////////////////////// +/////////////////////////////////////////////////4WhwP9MdaP/THWj/0x1o/9MdaP/THWj +/6O40P////////////////////////////////////////////////////////////////////////// +////////////9/n7/2mLsv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/+TrMf///////////////////////////////////////////////////////////////////// +/////////////////9bf6v9QeKX/THWj/0x1o/9MdaP/THWj/8jU4v////////////////////////// +/////////////////////////////////////////////////5mxy/9MdaP/THWj/0x1o/9MdaP/fpu9 +//39/v////////////////////////////////////////////////////////////////////////// +////////////iqXD/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9Od6T/4Ofv//////////////////////////////////////////////////////////////// +//////////////////////+7y9z/TXaj/0x1o/9MdaP/UXmm//T3+v////////////////////////// +/////////////////////////////////////////////////8jV4/9MdaP/THWj/0x1o/9ukLX/9Pb5 +//////////////////////////////////////////////////////////////////////////////// +//////+1xtn/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/fpu9//////////////////////////////////////////////////////////////// +////////////////////////////u8vc/1N6p/9MdaP/la3J//////////////////////////////// +//////////////////////////////////////////////////7+/v97mbv/THWj/4Cdvv/y9fj///// +//////////////////////////////////////////////////////////////////////////////// +/9vj7f9Reab/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/8XT4f////////////////////////////////////////////////////////// +//////////////////////////////////Dz9//W4Or//f3+//////////////////////////////// +///////////////////////////////////////////////////////7/P3/5uzy//////////////// +////////////////////////////////////////////////////////////////////////////+fv8 +/2mMsv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/2CErf/2+Pr///////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////o7jQ +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/+bssz///////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////5+vz/WX+p +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9Od6T/3eTt//////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////W4Or/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/cpK2//39/v////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////x9Pj/VHun +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/6m80/////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////qr7T +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/093pP/Y4ev///////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////v7/ +/5Grx/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9nirH/9/n7//////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//z9/v+Np8X/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/lKzI//////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////+/v//m7LL/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/8bT4v////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////52zzP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/1V8qP/h6O////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////+it8//THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9nirH/9Pb5//////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////nrTN/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/eJe6//n7/P////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////5Orx/5uyzP+7y9z/+/z9 +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////5+1zv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/4aiwf/9/f7///// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////h6O//VHun/0x1o/9MdaP/bo+0 +/+vv9f////////////////////////////////////////////////////////////////////////// +//////////////////////////////////7+//+OqMX/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/+IpML/+/z9 +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////9/nb3/THWj/0x1o/9MdaP/THWj +/2CFrf/o7fP///////////////////////////////////////////////////////////////////// +///////////////////////////////////////7/P3/eJe5/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/gp+/ +//f5+/////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////Dz9/9NdqP/THWj/0x1o/9MdaP/THWj +/0x1o/9kh6//7vL2//////////////////////////////////////////////////////////////// +////////////////////////////////////////////8vX4/2KGr/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/26PtP/q7/T///////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////8jU4v9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/b5C1//b4+v////////////////////////////////////////////////////////// +/////////////////////////////////////////////////9Te6f9NdqT/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9Yfqn/xNLh//////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////7DC1v9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/36bvf/7/P3///////////////////////////////////////////////////// +//////////////////////////////////////////////////////+Zscv/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/4mkwv/t8fb///////////////////////////////////////////////////// +/////////////////////////////////////////////////5qxy/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/+Np8X//v7+//////////////////////////////////////////////// +///////////////////////////////////////////////////////u8vb/UHil/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9VfKj/oLXO//Dz9/////////////////////////////////////////// +/////////////////////////////////////////////////5Gqxv9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/kavH//7+/v////////////////////////////////////////// +////////////////////////////////////////////////////////////fZu8/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/1N6p/+ascv/4ujw//////////////////////////////// +/////////////////////////////////////////////////3qYuv9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/4Ofv//2+Pr///////////////////////////////////// +////////////////////////////////////////////////////////////dpW5/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/2yOtP+pvdP/2OHr//v8/f////////// +////////////////////////////////////////////z9rm/053pP9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9ihq//v87e//7+/v////////////////////////// +//////////////////////////////////////////////////7+/v+xw9f/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/1J5pv9ykrb/iqXD +/520zf+gts7/q7/U/6u/1P+rv9T/q7/U/6e70v9/nb3/Tnek/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/2eKsf+ds8z/v87e/8PR4P+/zt7/t8ja +/7fI2v+3yNr/t8ja/7DC1/+rv9T/q7/U/6u/1P+qvtP/lKzI/2OHr/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMdKUBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Ex0p +QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEaJDlMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/EBkj +NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0UGytMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/DBMa +KQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMFBwtMdaL+THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9LdKL9AwUG +CgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCZY3cTHWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9BZIvaAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvSGWeTHWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/8uR2KaAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYJTNQTHWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/8XIzFNAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwQGRmuW6kx1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0VrlekBAgMFAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJTlPfEx1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/yQ3TXgAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAYJDkVqlOdMdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RGmS5AQGCA0AAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABooOFhMdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/GSc2VAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvSWafTHWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/8uR2KaAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwQGN1V3ukx1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/zlYer8CAwQHAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAYJDjlYe8BMdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/OVd5vgQGCQ4AAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIEBQgvSGWeTHWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/8uR2KaAgMEBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGig4 +V0Vqk+ZMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/RGmS5BknNlQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAQGCQ4lOE97RmuW6kx1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0VrlekkOE15BAYIDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAgMEBhckMk4uR2ObQWSM20x1ov5MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj +/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/S3Si +/UFki9ouR2KaFyMxTQECAwUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEBgkNExsqEBkjNxQfK0QUHytEFB8rRBQfK0QUHytEFB8r +RBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8r +RBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8r +RBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8r +RBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8r +RBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBAZIzcNExsqAwQG +CQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAQAQAAAwAAAAEAeAAAAQEAAwAAAAEAeAAAAQIAAwAAAAQAAOHOAQMAAwAAAAEA +AQAAAQYAAwAAAAEAAgAAAQoAAwAAAAEAAQAAAREABAAAAAEAAAAIARIAAwAAAAEAAQAAARUAAwAAAAEA +BAAAARYAAwAAAAEAeAAAARcABAAAAAEAAOEAARwAAwAAAAEAAQAAASgAAwAAAAEAAgAAAVIAAwAAAAEA +AQAAAVMAAwAAAAQAAOHWh3MABwAADEgAAOHeAAAAAAAIAAgACAAIAAEAAQABAAEAAAxITGlubwIQAABt +bnRyUkdCIFhZWiAHzgACAAkABgAxAABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA9tYA +AQAAAADTLUhQICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFj +cHJ0AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8AAAABRia3B0AAACBAAAABRyWFlaAAACGAAAABRn +WFlaAAACLAAAABRiWFlaAAACQAAAABRkbW5kAAACVAAAAHBkbWRkAAACxAAAAIh2dWVkAAADTAAAAIZ2 +aWV3AAAD1AAAACRsdW1pAAAD+AAAABRtZWFzAAAEDAAAACR0ZWNoAAAEMAAAAAxyVFJDAAAEPAAACAxn +VFJDAAAEPAAACAxiVFJDAAAEPAAACAx0ZXh0AAAAAENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBh +Y2thcmQgQ29tcGFueQAAZGVzYwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAABJzUkdC +IElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAABvogAAOPUA +AAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAAAA+EAAC2z2Rlc2MAAAAAAAAAFklFQyBo +dHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkZXNjAAAAAAAAAC5JRUMgNjE5NjYtMi4x +IERlZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERl +ZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAAAAAA +AAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAALFJl +ZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBpbiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAHZpZXcAAAAAABOk/gAUXy4AEM8UAAPtzAAEEwsAA1yeAAAAAVhZWiAAAAAAAEwJVgBQAAAA +Vx/nbWVhcwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAAAAAA +AAQAAAAABQAKAA8AFAAZAB4AIwAoAC0AMgA3ADsAQABFAEoATwBUAFkAXgBjAGgAbQByAHcAfACBAIYA +iwCQAJUAmgCfAKQAqQCuALIAtwC8AMEAxgDLANAA1QDbAOAA5QDrAPAA9gD7AQEBBwENARMBGQEfASUB +KwEyATgBPgFFAUwBUgFZAWABZwFuAXUBfAGDAYsBkgGaAaEBqQGxAbkBwQHJAdEB2QHhAekB8gH6AgMC +DAIUAh0CJgIvAjgCQQJLAlQCXQJnAnECegKEAo4CmAKiAqwCtgLBAssC1QLgAusC9QMAAwsDFgMhAy0D +OANDA08DWgNmA3IDfgOKA5YDogOuA7oDxwPTA+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4EjASaBKgE +tgTEBNME4QTwBP4FDQUcBSsFOgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3BkgGWQZqBnsG +jAadBq8GwAbRBuMG9QcHBxkHKwc9B08HYQd0B4YHmQesB78H0gflB/gICwgfCDIIRghaCG4IggiWCKoI +vgjSCOcI+wkQCSUJOglPCWQJeQmPCaQJugnPCeUJ+woRCicKPQpUCmoKgQqYCq4KxQrcCvMLCwsiCzkL +UQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyODKcMwAzZDPMNDQ0mDUANWg10DY4NqQ3DDd4N+A4TDi4O +SQ5kDn8Omw62DtIO7g8JDyUPQQ9eD3oPlg+zD88P7BAJECYQQxBhEH4QmxC5ENcQ9RETETERTxFtEYwR +qhHJEegSBxImEkUSZBKEEqMSwxLjEwMTIxNDE2MTgxOkE8UT5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYV +eBWbFb0V4BYDFiYWSRZsFo8WshbWFvoXHRdBF2UXiReuF9IX9xgbGEAYZRiKGK8Y1Rj6GSAZRRlrGZEZ +txndGgQaKhpRGncanhrFGuwbFBs7G2MbihuyG9ocAhwqHFIcexyjHMwc9R0eHUcdcB2ZHcMd7B4WHkAe +ah6UHr4e6R8THz4faR+UH78f6iAVIEEgbCCYIMQg8CEcIUghdSGhIc4h+yInIlUigiKvIt0jCiM4I2Yj +lCPCI/AkHyRNJHwkqyTaJQklOCVoJZclxyX3JicmVyaHJrcm6CcYJ0kneierJ9woDSg/KHEooijUKQYp +OClrKZ0p0CoCKjUqaCqbKs8rAis2K2krnSvRLAUsOSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLyQv +Wi+RL8cv/jA1MGwwpDDbMRIxSjGCMbox8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1TTWHNcI1 +/TY3NnI2rjbpNyQ3YDecN9c4FDhQOIw4yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7qjvoPCc8ZTykPOM9 +Ij1hPaE94D4gPmA+oD7gPyE/YT+iP+JAI0BkQKZA50EpQWpBrEHuQjBCckK1QvdDOkN9Q8BEA0RHRIpE +zkUSRVVFmkXeRiJGZ0arRvBHNUd7R8BIBUhLSJFI10kdSWNJqUnwSjdKfUrESwxLU0uaS+JMKkxyTLpN +Ak1KTZNN3E4lTm5Ot08AT0lPk0/dUCdQcVC7UQZRUFGbUeZSMVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVV +wlYPVlxWqVb3V0RXklfgWC9YfVjLWRpZaVm4WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1f +D19hX7NgBWBXYKpg/GFPYaJh9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/aJZo +7GlDaZpp8WpIap9q92tPa6dr/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CGcOBxOnGVcfByS3KmcwFz +XXO4dBR0cHTMdSh1hXXhdj52m3b4d1Z3s3gReG54zHkqeYl553pGeqV7BHtje8J8IXyBfOF9QX2hfgF+ +Yn7CfyN/hH/lgEeAqIEKgWuBzYIwgpKC9INXg7qEHYSAhOOFR4Wrhg6GcobXhzuHn4gEiGmIzokziZmJ +/opkisqLMIuWi/yMY4zKjTGNmI3/jmaOzo82j56QBpBukNaRP5GokhGSepLjk02TtpQglIqU9JVflcmW +NJaflwqXdZfgmEyYuJkkmZCZ/JpomtWbQpuvnByciZz3nWSd0p5Anq6fHZ+Ln/qgaaDYoUehtqImopaj +BqN2o+akVqTHpTilqaYapoum/adup+CoUqjEqTepqaocqo+rAqt1q+msXKzQrUStuK4trqGvFq+LsACw +dbDqsWCx1rJLssKzOLOutCW0nLUTtYq2AbZ5tvC3aLfguFm40blKucK6O7q1uy67p7whvJu9Fb2Pvgq+ +hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TEUcTOxUvFyMZGxsPHQce/yD3IvMk6ybnKOMq3yzbLtsw1zLXN +Nc21zjbOts83z7jQOdC60TzRvtI/0sHTRNPG1EnUy9VO1dHWVdbY11zX4Nhk2OjZbNnx2nba+9uA3AXc +it0Q3ZbeHN6i3ynfr+A24L3hROHM4lPi2+Nj4+vkc+T85YTmDeaW5x/nqegy6LzpRunQ6lvq5etw6/vs +hu0R7ZzuKO6070DvzPBY8OXxcvH/8ozzGfOn9DT0wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH/Jj9 +Kf26/kv+3P9t///SJygpKlokY2xhc3NuYW1lWCRjbGFzc2VzXxAQTlNCaXRtYXBJbWFnZVJlcKMpKyxa +TlNJbWFnZVJlcFhOU09iamVjdNInKC4vV05TQXJyYXmiLizSFQoxH6IdM4AFgAuACdIiCjYkgAyACE8S +AAORXk1NACoAA4QIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEBAgMFAQIDBQIDBAYCAwQH +AgMFBwIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUI +AgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUI +AgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUI +AgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUI +AgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUI +AgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUI +AgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUI +AgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUI +AgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUI +AgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUI +AgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUHAgMEBwIDBAYBAgMFAQIDBQAAAAEAAAABAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAwUHCQQHCg4LERckDBMaKQ4WHzAPFyAy +ERslOxIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8 +EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8 +EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8 +EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8 +EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8 +EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8 +EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8 +EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8 +EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8 +EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8 +EhwmPBIcJjwSHCY8EhwmPBIcJjwRGyU7DxcgMg4WHzAMExopCxEXJAQHCg4DBQcJAAAAAQAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAABAwQGCQMFBwsFCAsSBgkNFAcMEBoIDRIdDBIaJw0VHSwUHyxEFSEvSRckMk4YJTRR +Gik5WhsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpb +Gyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpb +Gyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpb +Gyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpb +Gyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpb +Gyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpb +Gyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpb +Gyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpb +Gyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpb +Gyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpb +Gyo6WxsqOlsbKjpbGyo6WxsqOlsaKTlaGCU0URckMk4VIS9JFB8sRA0VHSwMEhonCA0SHQcMEBoGCQ0U +BQgLEgMFBwsDBAYJAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAIDBAYEBwkOEx0pPxglNFAnPFSDLENekzhWeLs7Wn7GQmaM3ENoj+BEaJHlRGmS5UVqk+ZFapPn +RWqU50VqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapTo +RWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapTo +RWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapTo +RWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapTo +RWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapTo +RWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapTo +RWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapTo +RWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapTo +RWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapTo +RWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapTo +RWqU6EVqlOhFapToRWqU6EVqlOhFapTnRWqT50Vqk+ZEaZLlRGiR5UNoj+BCZozcO1p+xThVeLosQ12S +JzxTghgkM08THSg+BAcJDgIDBAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAgAAAgMEBwkO +BQkLEgkOFSAMExwrHCw8XiEzR28wSmmiNVFzskFki9pEaZLkS3Og+0x1ov5MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1ov5Lc6D7RGmS40Fki9k1UHKx +MElooSEzRm4cLDtdDBMcKwkOFSAFCQoSBAcIDgAAAgIAAAEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBBQcIDQkNEBoeLUBj +JThPeztafMI/YYbSRWmT5kdslutHb5rySHCc9Etzn/pLc6D7THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/S3Og+0tzn/pIcJz0 +R2+a8kdslutFaZPmP2CG0TtZfMEkN055HSw/YQgMDxkEBgcMAAABAQAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQMFBwoEBwoPDBIYJxEaIzcnO1OC +LkZjmkRokOFIbpnvS3Si/Ux1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9LdKL9SG2Z7kRnkOAtRWKYJjpSgBAZIjULERclBAcKDwMFBwoAAAABAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwQGChUhLUYcLD1fOVZ5vj5fhtFGbJbr +SG+a8UpzoPtLdKH9THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/S3Sh/UpzoPtIb5nxRmyV6z5fhtA4Vnm8HCo7XRUfK0QCBAYJAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQABAQMGCAwSCg4UHx0uQGQlOlF/QmSM2kdsmOxLdKH9 +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/S3Sh/UdsmOxCZIzaJDhPfBwsPmEJDhMeBQgLEQABAQMAAAAB +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAwQGBwgMERglOVB+LUZimkNnj99HbZjtSnOf+0t0of1MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0t0ofxKc5/6R22Y7UNnj98tRWGYJThPewcMEBgCBAUH +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAQEBAQMFCQ4THA8XIDEuR2ScNlR2t0lwnPVMdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0lwnPU2U3W1LkZjmQ4XHzEIDhIc +AQEDBAAAAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAABAQQHCA0KEBUiLUVhlzZTc7RGa5brSXCd9Ux1ov5MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1ov5IcJz0RWuV6jVScrIsRGCV +Cg8UIQQGBwwAAAEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAABAQIDBQoQFSIRGyU7NlN0tD5fhc9Kcp/5THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/SnKf+D1ehM41UnKz +ERolOQoPFSEBAgIFAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAEDBAYHCQ0THC1FYJY2U3O0R22Y7kpyn/hMdaL/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWi/0pyn/hHbZju +NVJzsyxEYJQIDRIcAgQFBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAEBAgMIDBEZDxchMjZScrI+X4TOSnKf+Ux1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Kcp/4 +PV6EzjRRcrIOFx8vBwwPFwABAgMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAUICxElOVB+LkdjnUZslupKcp/4THWi/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaL/ +SnKf+EZrluouRmCZJThNegUHCxEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB +AwUHCgkOFB8tRmKZNlR2t0lwnPVMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0lwnPU2UnOzLURflQkOFB4DBQcJAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBAYK +FSEtRh4vQGRDZo/gSXCd9Ux1ov5MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1ov5IcJz0QWaO3h4tPmIVHytEAgQGCQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQIEBgkP +HCw9YCU6UX9HbZftTHWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/Rm2X7SU4T3scKjtcAwYJDgAAAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQUHCA0NExkn +OFZ5vUFkjNtLc6D6THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/S3Og+kFki9k4Vni7DBIYJgQGBwwAAAEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQECAwgNERoQGiM3 +P1+G0EdsmOxLdKH9THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/S3Sh/Udsl+s/X4XPDxkhNgcMDxkBAQIDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAYJDh4tP2MnO1OC +RmyW7Et0of1MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0t0ofxGbJXqJTlSfhwrPl8EBggNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQgLESU4TnsuRmKa +SG+a8Ux1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9IbpnxLEVhliM3TXcFBwoRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwQGCg8VITpafcJDaJHh +S3Og+0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9Lc6D7QmeP3zlZe8AKDxUhAgMEBgAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQIFBwkODRQcKz9ghtJIbpnv +S3Si/Ux1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9LdKH9R22Z7T5fhs8MFBsrBAcJDgAAAQEAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIEBQkTHik/HCw9XkRqkudLdKH9 +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/S3Sh/URqkuYcKjtcExwnPQIEBQgAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMFBwsXJjNQIDRHb0ZslutMdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0ZslusgMUVsFyMxTQMFBwsAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUIDBIoPFSEMUpoo0hvm/JMdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0hvmvIvSWaeJjtSfwUICxIAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYJDRQsQ16TNVFysklwnPRMdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0lwnPQ0UG+uK0JbjwYJDRQAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcMERo4Vne7QWSL2kpzoPpMdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0pzn/pAYovYN1R3uQcMEBoAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQgMEh07W37FRGmS5EtzofxMdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0tzoPtDaJLjOlp+xAgMERwAAAABAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAwUHCQwTGihCZYzcS3Og+kx1ov9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1ov9KcqD5QWSM2wwTGScDBQYIAAAAAQAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBBAcJDg0VHS1DZ4/gTHWi/kx1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9LdKL9QmaP3w0VHCwEBwgNAAABAQAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAgMECxAVIxQeKUJEaZLkTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RGmR5BMdKUAKDxUhAQICBAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAgMFDRMZKBYhLUdEaZLlTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RGmS5RUgLEQMEhglAQIDBQAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwQGDhYfMRckM1BFapPmTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RGqT5hcjMk8OFR4wAQMEBgAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwQHDxchMxglNVJFapPnTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqT5xckNFEOFiAyAgMEBwAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUHERojOBooN1dFapTnTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU5xooN1YRGiM3AgMFBwAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9Od6T/Tnek/093pP9Pd6T/T3ek/1B4pP9Pd6X/UHil/1F5pf9ReaX/UXml/1F5pf9ReaX/ +UXml/1B4pf9Pd6X/UHik/093pP9OdqT/Tnak/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/052pP9Wfaj/WX+p/2CErv9hha7/YYWu/2KGr/9nibD/aIqx/2uNs/9rjbP/a42z/2uNs/9rjbP/ +a42z/2iKsf9nibD/Yoav/1+Erv9Zf6j/Vn2n/012pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Tnek/094pf9WfKf/WH6o/12CrP9fg6z/ +YIWu/2KGr/9tjrP/b5C1/3aVuP93lrn/d5a5/3iXuf99mrz/f5u8/4Cevv+Bnr7/gZ6+/4Gevv+Bnr7/ +gJ6+/3+bvP99mrz/eJe5/3aVuP9ukLT/bI6y/2KGr/9gha7/XoKr/1yBq/9Wfaj/VHun/053pP9NdqP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Od6T/Wn+q/2OGr/+OqcX/m7PM/8DO3//J1eP/ +3OXu/+Hp8P/p7vT/6/D1/+zw9f/s8PX/7PD1/+zw9f/t8fb/7fH2/+7y9v/u8vb/7vL2/+7y9v/u8vb/ +7vL2/+3x9v/t8fb/7PD1/+zv9f/r8PX/6e70/+Hn7//c4+z/w9Hh/7nK3P+VrMj/h6LB/1d+qv9Pd6b/ +TXaj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9NdqP/Tnej/093pf9QeKX/UXml/1F5pf9Reab/UXmm/1J6pv9Seqb/Unqm/1J6pv9Seqb/ +Unqm/1J6pv9Seqb/Unqm/1J6pv9Seqb/Unqm/1J6pv9Seqb/Unqm/1J6pv9Seqb/UXmm/1B4pP9Pd6T/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXaj/1F5pv9Wfaj/bY60/3iWuv+kutD/scTX/9bf6v/f5+// +8vX4//b4+v/+//////////////////////////////////////////////////////////////////// +/////////////////////////////////v7///b4+f/x9Pf/2uLs/9Db5/+qvdP/nLLL/2iLs/9dgq3/ +T3ek/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +TXaj/013o/9Pd6X/T3el/094pP9Pd6T/T3ek/1B4pP9QeKX/UXml/1F5pf9ReaX/UXml/1F5pf9ReaX/ +UXml/1F5pf9ReaX/UXml/1F5pf9ReaX/UXml/1F5pf9ReaX/UXml/1F5pf9ReaX/UXml/1F5pv9QeKb/ +UHik/093pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9Pd6X/Unqn/2SHr/9oirH/ao2z/2yOs/9wkLb/cZG3/3aVuP92lbn/dpW5/3aVuf92lbn/ +dpW5/3aVuf92lbn/dpW5/3aVuf92lbn/dpW5/3aVuf92lbn/dpW5/3aVuf92lbn/c5O3/2KGsP9dgq3/ +Tnek/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Unmm/3OTt/+GosH/1N7p/+Pq8f/y9fj/9ff5//r7/f/7/P3/ +/v7///7+//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////7+///+/v//+vv8//n6/P/z9vn/6/D1/8LQ4P+twNX/ +WoCq/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9NdqP/ +UHem/1N5p/9ghK3/YoWu/2GFrv9hha7/YYWu/2KGr/9qjLL/a42z/2uNs/9rjbP/a42z/2uNs/9rjbP/ +a42z/2uNs/9rjbP/a42z/2uNs/9rjbP/a42z/2uNs/9rjbP/a42z/2uNs/9rjbP/bI6z/3CQtv9uj7X/ +Yoav/12Crf9Od6T/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9NdqP/ +UXmm/1R7qP9ihq7/Z4qx/3qYu/9+m7z/gJ6+/4Gfv/+HocD/iKLB/4umxP+MpsT/jKbE/4ymxP+MpsT/ +jKbE/4ymxP+MpsT/jKbE/4ymxP+MpsT/jKbE/4ymxP+MpsT/jKbE/4ymxP+MpsT/iaTD/3iXuv9yk7f/ +XYKs/1h+qf9PeKT/TXaj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9NdqP/VXyn/4ahwf+bssz/6O3z//b4+v/+//////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////+vv8/9Xf6v/Az9// +YYWt/1B4pf9NdqP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXaj/1F5pv9UfKj/ +ZIev/2mKsv91lbj/d5a5/3iWuf93lrn/d5a5/3mXuf9/nb7/gZ6+/4Gevv+Bnr7/gZ6+/4Gevv+Bnr7/ +gZ6+/4Gevv+Bnr7/gZ6+/4Gevv+Bnr7/gZ6+/4Gevv+Bnr7/gZ6+/4Gevv+Bnr7/gZ+//4ehwP+FoMD/ +d5e6/3KTt/9hha7/XIGr/1B4pf9OdqT/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Reab/ +b5C1/4Gevv/R3Oj/4Ojv/+vv9f/t8fb/7vL2/+7y9v/u8vf/7vL3/+/z9//v8/f/7/P3/+/z9//v8/f/ +7/P3/+/z9//v8/f/7/P3/+/z9//v8/f/7/P3/+/z9//v8/f/7/P3/+/z9//v8/f/7/L3/+vw9P/j6vD/ +tsba/6K30P9bgav/T3el/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/012o/9Pd6b/Yoaw/9Hc5v/m7PH/+/z+//7+//////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////n6/P/n7PP/ +fpy9/2iLsv9QeKX/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/UXmm/3GRtv+EoMD/ +1d7p/+Tq8P/r8Pb/7PH2/+zw9f/s8PX/7PD1/+zw9f/u8vb/7vL2/+7y9v/u8vb/7vL2/+7y9v/u8vb/ +7vL2/+7y9v/u8vb/7vL2/+7y9v/u8vb/7vL2/+7y9v/u8vb/7vL2/+7y9v/u8vb/7vL2/+7y9//u8vf/ +7PD1/+js8//P2+f/vMze/2WIr/9VfKf/TXak/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Teqb/ +epm8/4+px//l6/H/9Pf5//7+//////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////7+/v/3+fv/ +y9fk/7fI2v9nirL/WH6q/053pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/012o/9Pd6b/YYWv/83Z5P/h6O7/7fH2//H0+P/5+vz/+vv9//////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////u8vf/ +hqLB/3CRtv9Reab/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9NdqP/VXyn/4Kfv/+YsMr/ +6u70//j5+/////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////z9/v/k6vH/0Nvn/26Qtf9bgav/Tnek/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Yf6n/ +orfP/7jI2v/09/r//v7///////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////+//// ++Pn7/+zw9f+ovNL/k6vH/1Z9qP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9NdqP/UXml/2SHsP9tjrT/i6bE/5mxy//R3Ob/3ubt//v8/f////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////y9fj/ +n7XN/4mkwv9VfKf/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Pd6X/YYWu/83Y5v/j6fH/ ++vz9//7///////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////7/P3/7PD1/4+oxf95l7r/U3qm/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Zf6n/ +pLnQ/7rK2//2+Pr///////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////b4+v+6ydz/pLjR/1qAqf9NdqP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXaj/1J5p/9Yfqr/dZW4/4OgwP+7y9v/y9fj//H1+f/4+vz/ +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////z9fn/ +pLnP/46oxP9WfKj/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/012pP9WfKj/aoyy/9zj7v/x8/j/ +/f7+//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////8PP3/5Krxv98mrv/U3qm/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9VfKj/ +i6XE/6G2z//y9fn///////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////39/v/q8PX/1eDq/2WHsf9ReKf/TXaj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/012o/9Od6P/Unmm/1R7p/9eg6z/bY60/7vL3P/P2ub/ ++fr8//////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////09vn/ +q7/V/5Wuyv9Xfaj/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/1N7pv99m7z/k6zH/+7z9v/9/v7/ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////7vL3/4aiwf9wkbb/UXmm/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Ue6f/ +gJ2+/5auyf/w8/f//v7+//////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////7+///09/n/4Ojv/26Otf9Zfqr/Tnek/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Od6T/W4Gr/6i80v++zd3/ +9vj7//////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////09vn/ +rcDV/5evyv9Xfaj/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/1V8qP+LpsP/obfO//L1+f////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////9/v7/6/D1/4Cdvv9qjLP/UHil/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Od6T/ +WH6p/22OtP/e5+7/8vb4//7+//////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////+/v//8PP4/5mxyv+DoL//VHun/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/UHil/2SHsP96mLv/ +6e70//v8/f////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////09vn/ +rL/V/5auyv9Xfaj/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/1yCq/+8y9z/0tzn//n7/P////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////7+/v/x9Pj/3OTt/2eKsP9Seqb/TXaj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9NdqP/ +UHim/2OHsP/U3+n/6e/0//z9/v////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////8/b5/6m80v+Tq8f/Vn2o/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Tnak/1d9qf9sjrT/ +4+jx//f4+//+//////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////09vn/ +rL/V/5auyv9Xfaj/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9NdqP/T3il/2GGrv/J1uL/3ubt//v8/f////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////z9/v/o7fP/0tzo/2GGrv9Od6T/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +TXaj/1qAqf+jt8//ucja//b4+v////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////+vz9/9vj7P/F0uH/XoSs/012o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/053pP9fha3/ +xtLh/9zj7P/6/P3///////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////09vn/ +q77U/5Wtyf9Xfaj/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9QeKX/Z4mx/32avP/o7fT/+vv9//////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////X4+v+4yNr/orfP/1h/qf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/1Z9qP+Tq8f/qbzS//P2+f////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////P3+/+bs8v/R3Of/Zomw/1N6pv9NdqP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9cgqv/ +vczd/9Pd6P/5+/z///////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////z9vn/ +q77U/5Wtyf9Wfaj/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Seab/dJO3/4qkwv/v8vf///////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/v7///L1+f+ou9L/kqrH/1Z9qP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/094pf9hha7/d5a5/+bs8//5+vz///////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////z8/f/s8PX/i6bE/3WVuf9Seqb/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Zf6r/ +p7rS/73L3f/2+Pv///////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////z9vn/ +q73T/5WsyP9Wfaj/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9ZgKr/qb3T/7/O3v/2+fv///////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////+/v// +8/b4/+Dn7v9zlLj/XoSt/093pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/052pP9Xfqn/bI6z/93k7v/x9Pj//v7+//////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////x9fj/m7LM/4Whwf9UfKf/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Yfqn/ +obbP/7fH2v/19/r///////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////z9vn/ +qr3T/5SsyP9Wfaj/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/093pP9fg6z/uMnb/87a5v/5+vz///////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////8/f7/ +6O3y/9Pd6P9oi7H/VXyn/012pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9Od6T/XIKr/6y/1f/C0OD/9/n7//////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////5+/z/09zo/73L3f9cgqv/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Wfaj/ +k6zH/6m90v/z9vn///////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////z9vn/ +p7vR/5Gqxv9Wfaj/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/T3ek/16Drf9zk7j/4+nw//b4+v/+/v////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////z9vn/ +q77U/5Wtyf9Yfqn/Tnak/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/V36p/5uyzP+xw9f/9Pf6//////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////7/P3/4Ofv/8vX5P9jh6//UXmm/012o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Wfaj/ +j6nF/6W60P/z9vn///////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////z9vn/ +prrR/5Cpxv9Wfaj/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/UHml/2uMs/+Bnb7/6vD1//z9/v////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////v8/f/t8fb/ +mLDK/4Kfv/9Ue6f/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/UHil/2WIr/97mbr/5+zz//n6/P////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////+/z9/+rv9f+HosH/cZG2/1F5pv9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9VfKf/ +iKLC/56zzf/y9fj///////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////y9fn/ +pLnP/46oxP9VfKj/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WH+p/6O2z/+5x9r/9fj6//////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////+/z9/+Pp8f/O2ef/ +Z4qw/1V8p/9NdqT/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/Tnek/1p/qv9vj7T/3eXu//H0+P/+/v7///////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////H0+P+Wrsn/gJ2+/1R7p/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Ue6f/ +hqHB/5yyzP/x9Pj///////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////y9fn/ +o7jO/42nw/9VfKj/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/012o/9Pd6X/XoKs/7TF2P/J1eP/+Pn7//////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////+fv8/9Td6f++zN7/ +XYOs/012pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/053pP9cgqv/q77V/8HP4P/3+fv///////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////r7/P/V3+r/v87f/16Dq/9NdqP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Te6f/ +gJy9/5atyP/w9Pj///////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////y9fn/ +nrXO/4ikw/9VfKj/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/094pf9iha7/d5W5/+Lq8f/1+Pr//v7///////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////9/f7/7vH2/4+pxv95mLv/ +U3qm/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9Xfqn/mrHM/7DC1//09/r///////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////z8/f/j6vH/ztrm/2eJsP9Ue6f/TXaj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Te6f/ +fpq8/5Srx//w9Pj///////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////y9fj/ +nbTO/4ejw/9VfKf/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/1F5pv9vkLX/haHA/+vw9v/8/f7///////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////7+///z9vn/4ejw/3+cvv9qjLP/ +UHil/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9QeKX/ZYiv/3uZuv/n7PP/+fr8//////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////7/P3/7PD1/5Grx/97mrz/U3qm/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Teqb/ +eZm7/4+qxv/w8/f///////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////x9fj/ +nLLM/4ahwf9UfKf/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/1qAqv+tv9X/w9Dg//f5+/////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////b4+v+7ytz/prrR/12Cq/9QeKX/ +TXaj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9Od6T/Wn+q/2+PtP/d5e7/8fT4//7+/v////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////8vX5/6K4z/+Mp8T/Vn2o/012o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Seqb/ +eZi6/4+pxf/v8/f///////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////x9Pj/ +m7LM/4Whwf9Ue6f/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9NdqP/UHim/2CFrv++zN3/09zo//n7/P////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////+/z9/+/z9/+ou9L/kqrH/1Z9qP9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Tnek/1yCq/+rvtT/wc/f//f5+/////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////+/z9/+Dn7//L1+T/Yoav/1B4pv9NdqP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Seqb/ +eZi6/4+pxf/v8/f///////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////x9Pj/ +lq/J/4Cevv9Ue6f/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9ReaX/bI20/4Gev//n7PP/+Pn8//7///////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////7/P3/4ejw/83Z5v9qjLL/WH6p/053pP9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/1d+qf+ascv/sMLW//T3+v////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////f3+/+zx9f/Y4uv/b4+1/1uAq/9Od6T/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Seqb/ +eZi6/4+pxf/v8/f///////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////w9Pj/ +lq/J/4Cevv9Te6f/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/012o/9Ue6b/fZq8/5Orx//v8vf//v7///////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////7////4+vz/0Nzo/7rL3f9fg6z/T3ek/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/1B4pf9kh6//epi6/+bs8v/4+vv///////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////z9/v/w9Pj/qr7T/5StyP9Wfaj/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Seqb/ +eZi6/4+pxf/v8/f///////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////w9Pj/ +lKvH/36avP9Te6f/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/093pf9ghK7/wdDg/9fh6//6+/3///////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////j5+//n7PL/hqHA/3CQtf9ReaX/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/053pP9Zfqr/bY60/93l7f/x9Pf//f7+//////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////2+Pv/vMzc/6e80f9cgaz/ +T3el/012o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Teqb/ +eZi6/4+pxf/w8/f///////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////w8/f/ +k6vH/32avP9Teqb/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +Tnak/1h+qf9rjbP/0tzo/+bs8v/8/f7///////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////f3+/+3x9f/a4uv/dJO3/2CErf9Pd6T/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Od6T/XIKr/6u+1P/Bz9//9/n7//////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////+/v//8vX4/9/n7/93lrn/ +Yoau/094pf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Seqb/ +eZi7/4+pxv/v8/f///////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////w8/f/ +j6rH/3mZvP9Teqb/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +VXyo/42nxP+juM//7/L3//z8/f////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////8/b5/6q+1P+Ursn/WX+q/093pf9MdqP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/V36p/5qxy/+wwtb/9Pf6//////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////+/z9/+rv9f+Ho8H/ +cZK2/1F5pv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Teqb/ +eZm8/4+qx//w8/f///////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////v8/f/ +j6nG/3mYu/9Seqb/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x2o/9Pd6X/ +W4Cr/5+2zv+1xtn/9ff6//////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////7////4+fv/6u70/5avyv+Bnr//VHun/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/UHil/2SHr/96mLr/5uzy//j6+/////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////j6/P/O2eX/ +uMja/1yCq/9NdqP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Te6f/ +gJu8/5asx//w9Pj///////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////v8/f/ +jqfE/3iWuf9Seqb/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/093pP9fg63/ +c5O3/9zj7P/v8vb//f7+//////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////j6/P/O2eX/ucna/2OHr/9Ue6f/TXaj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Tnek/1l+qv9tjrT/3OTt//Dz9//9/v7/ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////v8/f/d5e3/ +yNXi/2eKsP9VfKf/TXaj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Ue6f/ +gZ2+/5euyf/x9Pj///////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////v8/f/ +jqfE/3iWuf9Seqb/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/1F5pf9ujrX/ +hJ/A/+ft8v/4+vv///////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////P3+//P2+v+7y9z/pbrR/1l/qv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/053pP9bgar/pbrR/7vL3P/2+Pr/ +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////6+/3/ +7PD2/5auyf+Anb7/VHun/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9VfKj/ +i6bD/6G3zv/y9fn///////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////v8/f/ +j6nG/3mYu/9Seqb/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXak/1yCrP+3yNr/ +zdnl//j6/P////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////8/f7/6u7z/9ff6f9yk7f/X4St/093pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Wfaj/k6zI/6m90//x9fj/ +/f7+//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +8/b5/6m80/+Tq8j/WX+p/093pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Wfaj/ +j6jF/6W50P/z9vn///////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////w8/f/ +j6rH/3mZvP9Teqb/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9OdqT/Vn6p/2iMsv/J1eP/ +3eXt//v8/f////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////7////5+/3/2uLr/8XS4P9kh7D/U3qn/012o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Pd6T/XYKs/3KStv/e5e7/ +8fT4//7+/v////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/P3+/+rv8//W4On/b5C2/1yBrP9Od6T/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Xfqn/ +nLPM/7LE1//09/r///////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////w9Pf/ +lKvH/36avP9Te6b/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9WfKj/jqjF/6S50P/u8ff/ ++vv9//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////j5+//o7PP/iKTC/3KTt/9Teqf/TXak/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9NdqP/U3qn/2aJsf/R2+b/ +5uvx//z9/v////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/v7///X4+f/j6/D/gJy//2uMtP9ReqX/TXaj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Yf6n/ +orfP/7jI2v/1+Pr///////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////x9Pj/ +lq7I/4Cdvf9Ue6f/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXaj/1F5pv9dg6z/o7fP/7jH2v/1+Pr/ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/f3+/+zw9f/Z4uz/dpS4/2KFrv9PeKX/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXak/1d+qf+Urcj/ +qr7T//P2+f////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////7+///4+fz/ztnl/7jI2v9fg63/T3el/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9bgav/ +tcXY/8vW4//4+vz///////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////y9fn/ +o7nQ/42oxf9VfKj/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Unmm/3KSt/+HosL/4+nx//P2+f/+/v// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +8vb5/6S50P+PqcX/WH+q/093pf9NdqP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/1R7p/+Cn7// +mLDK/+7x9v/8/P3///////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////7/P3/3+bu/8vX5P9tjrT/W4Cr/053pP9MdaP/THWj/0x1o/9MdaP/THWj/012o/9dg6v/ +vcvd/9Pc6P/5+/z///////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////z9vn/ +q77U/5Wtyf9Wfaj/THWj/0x1o/9MdaP/THWj/012o/9PeKX/V36p/4ahwf+bscz/7/L3//39/v////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////7////3+Pv/ +6Ozz/5Crx/97mrz/U3qm/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/012pP9Wfaf/ +aYyx/9Hc6P/m7PL//P3+//////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////+vv9//D0+P+4yNr/orfP/1mAqf9NdqP/THWj/0x1o/9MdaP/TXaj/093pv9jh7D/ +3OTt//H0+P/9/v7///////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////4+vz/ +ydbj/7PF2P9bgav/THWj/0x1o/9MdaP/THWj/1B4pf9mibH/eZi7/9bf6f/o7fL//P3+//////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////f5+//G0+H/ +scPW/2GFrv9Teqf/TXaj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Od6T/ +X4St/8LR4P/Y4uv/+vv9//////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////j6/P/K1uP/tsfZ/2iLsf9Zf6n/T3ik/012o/9MdaP/Tnak/1d+qv9sj7X/ +4ujw//b4+v/+//////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////5+/z/ +1N7p/7/O3v9hhq7/UXmm/012o/9NdqP/Unmm/1h+qf96mLv/jqfG/+bs8f/2+Pn//v7///////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////+/z9//H0+P+zxdf/ +nbTM/1h+qf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +VHun/4Oev/+Zr8r/8fT4//////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////7////4+fv/7vL2/7bH2v+it8//XYKt/1F4p/9NdqP/VHun/4WhwP+bssv/ +8PP4//7+//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// ++Pn7/+fs8/+GocD/cJC1/1F5pv9Seqb/c5O4/4aiwf/U3+n/5Ovx//v8/f/+/v////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////7/P3/4+nx/8/a5/9tjrX/ +W4Cs/053pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +Unmm/3KSt/+Io8L/6O30//j6/P////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////+Pr8/8rW4/+3yNr/c5K3/2aIsP9ehK3/Zomw/5mxy/+twNX/ +9Pb5//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/v7+//Dz9/+ZsMr/haHA/2WIsP9libD/iaTD/5yyzf/m7fH/9Pf4//7+//////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////5+/z/09zp/77M3v9fhK7/ +T3em/012o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +TXaj/1F5pv9hhq7/vczc/9Lc5//5+/z///////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////v////j5+//09vn/3+fv/9ni7P/I1OL/y9bk/+rv9f/x9Pj/ +/v7///////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////3+/v/t8fb/6O30/9bg6f/X4On/7PD2//H0+P/9/f7//v7///////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////n7/P/o7vT/iKTB/3KTtv9Seqb/ +TXaj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9agKr/q77T/8HP3v/2+fv//v////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////+/v//8vX4/+7y9v/e5e7/3+bu//n6/P/9/f7/ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////7/P3/+fr8/+vw9P/r8PT//f3+//////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////f7+//D0+P/d5u//dpa4/2KGrv9PeKX/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9QeKX/aIuy/32cvf/m6vH/+Pj6//7///////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////v7+//7+/v/6/P3/+vz9//////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////z9/v/8/f7///////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////9fj6/7jH2v+jt8//W4Gr/093pf9NdqP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9Od6T/W4Cr/2+Ptf/Z4uv/7fH1//39/v////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////8/b5/6a50f+QqMb/Vn2o/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/T3ek/1qAqv+bssz/scPX//T3+v////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////5+vz/5uvy/3WWuP9fha3/T3ek/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/1V8p/+Io8P/nrTO/+7y9v/7/P3///////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////7+///1+Pr/4Ojw/2uNsv9Wfaf/TXak/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/052pP9Wfqj/aYyx/9Da5//k6vH//Pz9//////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////v8/f/f5u7/ydXj/2CErf9OdqT/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9Od6T/X4Ss/7/N3v/V3un/+vv8//////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////v8/f/e5e7/yNTj/1+ErP9NdqP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Unqm/3mXuv+PqMX/7fH2//39/v////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////3+/v/v8/f/2uPs/2aJsf9Seaf/TXaj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/UHil/2mLsv9+m73/4ujw//T2+f/+/v////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////7+///z9vj/3+fu/3KRt/9dgaz/Tnek/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXaj/1B4pf9dgqz/p7vR/7zL3P/2+Pv///////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////+/v//8vX5/6u/0/+Vrsj/Vn2o/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Wfaj/k6vI/6m80//v8/f/+/z9//////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////9vj7/73N3P+ovdH/YYWu/1R7p/9NdqP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Od6T/WX6p/2uMsv/M2OX/4Ofv//v8/f////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////v7///X3+v/n7PP/la7K/4Cev/9Ue6f/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/T3ek/1+DrP+5ytz/z9vn//j6/P/+//// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////7+///y9fn/qr7U/5Wuyf9dgqz/ +U3qn/012o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/1F5pv9xkbX/h6LA/+fs8//4+fv/ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////9/v7/8fT4/+Lp8f+Sq8f/ +fpu9/1N7p/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/093pP9hha7/dZS4/9ri7P/t8fb/ +/f3+//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////P3+//D0+P+nu9L/ +kqvI/1+ErP9VfKf/TXak/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x2o/9Pd6X/Wn+q/5Wvyf+rv9T/ +9Pb5//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////7+///x9Pj/ +5Orx/52zzf+Io8L/VXyn/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/VHun/4Ofv/+ZsMr/ +6u/1//j6/P////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////+/v// +8/b6/7LD1v+ds8z/YIWt/1V8p/9NdqT/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXak/1R7p/9kiK// +vsze/9Pc6P/5+/z///////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/v7///T3+f/n7fL/n7TO/4qkw/9VfKf/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9agKr/ +qr3U/8DO3//09/n//P39//////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////19/r/s8TX/560zf9iha3/Vnyn/012pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Pd6T/ +YIWu/3OUuP/U3ej/5+zy//z9/v////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////+/v//9Pf5/+ft8/+juND/jqjF/1V8qP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9NdqP/ +U3qn/2SHsP/B0N//1uDq//n6/f/+/v////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////X4+v+3x9r/orfQ/2GGrf9VfKf/TXak/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +TXak/1J6p/9ykrX/h6LA/+Xr8f/2+Pn//v7///////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////7+///19/n/6O3z/6C1zv+LpcP/VXyo/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/093pP9hha7/dJS4/9Xd6P/o7PL//P3+//////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////z9/v/7/P7/9Pf5//P3+f/3+Pv/+Pn7//7///////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////9ff6/7TF2P+ftc7/YYWt/1V8p/9NdqT/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x2o/9Pd6X/Vn2p/4Cevv+Wrsn/6vD1//n7/P////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////P3+/+ru8//f5u7/r8HW/6u+1P+/zt7/ytbj//T3+v/7/P3///////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////v7///T3+f/n7fP/obbP/4ymxP9VfKj/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/UXml/22Ptf+Bn7//2+Tt/+zx9v/9/v7///////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////8/f3/9/n6/9fg6v/K1uP/mbDL/5Styf+qvdP/tcXZ/+Hp8P/r8PX/+/v9//39/v////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////19/r/tcbZ/6C2zv9fhK3/U3un/012o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/TXaj/1F4pv9af6v/jafD/6K3zv/w8/j//f3+//////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////z9/v/n7PL/1N7p/3aVuf9kh7D/WH+p/1d+qP9Zf6r/X4Os/3uau/+NqMT/4Obv/+/y9//9/v7/ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////+/v//8/b5/+Tr8v+Uq8j/f5u9/1N7p/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9Seqb/eZe6/42nxP/h5/D/8fP4//3+/v////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////r7/f/Z4uv/xNLg/2OGsP9Seaf/TXaj/0x1o/9MdaP/UHil/2aJsP94l7n/zNfl/97l7v/5+vz/ +/f3+//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////v7///L1+f+ou9L/k6vH/1uAq/9ReKb/ +TXaj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9NdqP/Unqn/1uBrP+OqcT/o7nP/+7y9//7/P3///////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////H0+P+assr/hKG//1V8p/9NdqP/THWj/0x1o/9MdaP/TXaj/093pf9Ue6f/bY+0/4Cdvv/b5Oz/ +7PH1//39/v////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////3+/v/v8/f/3ubu/4Gfvv9tj7T/ +UXml/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/1N6pv95mbv/janF/9/l7v/u8fb//P7+//7///////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/v7+/+7y9v+JpML/c5O3/1J6pv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Od6T/W4Gr/2yPtP/I1OL/ +3OPs//j7/P/9/v7///////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////7/P3/7PH2/5auyP+Anb3/ +Vn2o/093pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/012o/9Teqf/XIGr/4mlw/+etc7/6+/0//j5+/////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////+/v// +8vX4/93l7f9nirH/Unqm/012o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/T3ek/1R7p/9wkLX/ +haC//+Dn8P/x9Pj//v7+//////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////P3+/+br8f/S3Of/ +cJG3/12Crf9Od6T/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Unqm/3WVuf+IpMP/2eHr/+nt8//6+/3/ +/f3+//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////9/f7/ +7fH2/9fg6/9hha3/TXaj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/093pP9eg63/ +cJG2/8/a5v/i6fD/+vz9//7///////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////v7///T3+P/j6u// +gJ3A/2uNtf9ReaX/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXaj/1J5pv9Yfqn/epm6/42oxP/e5u7/ +7vL2//3+/v////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////5+/z/ +09zn/73L3P9cgqv/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Pd6T/ +VXyn/3mXuv+OqMX/6Ozy//j4+v/+//////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////7+///3+fz/ +zdjk/7fH2f9cgqz/TXak/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9QeKX/Zomw/3iXuf/K1uT/ +3OTt//T3+v/4+vz///////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////4+vz/ +zNfj/7bG2P9bgav/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +UHil/2eJsf96mLv/2ODq/+ru8//8/f7///////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////7/P3/ +3eXt/8jV4/9oi7H/Vn2o/012pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9NdqP/T3el/1N6p/9nirD/ +dpW4/7/O3v/R3Of/+fr8//////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////2+Pv/ +vMvc/6a60f9Zf6r/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +TXaj/094pf9Xfqn/hqLB/5uyzP/t8fb/+/z9//////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// ++vv9/+zx9v+ds83/h6LC/1R8p/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/012pP9VfKf/ +Yoau/6m90/+9zN3/6e70//L1+P/8/f7//f7+//////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////1+Pr/ +t8fa/6G2z/9Yf6n/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9Seab/cpK3/4aiwf/f5e//7/L3//3+/v////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////T3+v+uwdX/mLDK/1h/qf9NdqP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9OdqT/ +UHil/1l/qv9ihq//kKrF/6K4zv/k6fL/8fP4//3+/v////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////z9vn/ +qbzS/5Orx/9Wfaj/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9NdqP/UXmm/1uBq/+Sq8f/p7vS//L1+P/+/v7///////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////z9/v/n7PL/0dvn/2KGr/9Pd6X/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9Teqb/epm7/4ynxP/P2ef/3eTv//H09//19/n//f3+//7+/v////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////z9vn/ +pbnQ/4+oxf9Wfaj/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/1N7p/9+m73/k6vH/+Pq8f/y9fj//v7+//////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////7+/v/x9Pj/3OTu/2mLsf9Ue6f/TXak/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9NdqP/U3qn/1d9qf9lia//cJG1/6O40P+0xdn/6O3z//L1+P/+/v////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////y9fj/ +n7XO/4mkw/9VfKf/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/012o/9Teqf/XYKs/5Wuyf+qvtT/8vX4//7+/v////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +///////////9/v7/7fH2/4ikwf9yk7b/Unmm/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/012o/9Teqf/XIGs/42nxP+etM3/093p/9/m7//w9Pf/9Pf5//v8/v/8/f7/ +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////x9fj/ +nLLM/4ahwf9UfKf/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/VHun/4Cev/+Ursn/4+jw//Hz9//8/v7//v////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////7/P3/4ymxP92lbn/Unqm/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9NdqT/Tnel/1Z8p/9Zf6n/ZYew/2+Ptf+etM3/rsDV/93k7f/o7PL/ +/P3+//////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////v8/f/ +jabD/3eVuP9Seqb/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXaj/1R7p/9dgqv/iqXD/561zv/r7vP/+Pj6//7///////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////7vL3/4ijwf9ykrb/UXmm/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9NdqP/UXin/1p/q/+Io8L/l6/K/8jT4v/T3Oj/ +6e70/+7x9//09/n/9vj6//r7/f/7/P3///////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////n7/P/o7vP/ +haDA/2+Ptf9ReaX/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Seqb/dpW5/4mkw//X3+n/5uvx//X3+v/4+fv/ +/v////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////7////3+Pr/5uvx/4Gev/9sjbT/UXml/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXaj/053pP9VfKf/V36o/16Drf9jh6// +epm7/4Whwf+swNT/uMna/9fg6//g5+//9/n7//v8/f////////////////////////////////////// +////////////////////////////////////////////////////////////////+vv9/9ri6//F0uD/ +ZIiw/1N7p/9NdqP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9NdqP/Unmm/1d9qf9wkrb/fp2+/73M3f/O2eX/ +9/n7//7+/v////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/v7+//b4+v/C0OD/rcDV/16Drf9QeKb/TXaj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9QeKX/ +ZIiw/26Qtf+Xr8r/orjQ/8HP3//K1uT/4unw/+ft8v/t8fb/7/L3//D09//x9Pj/8/b5//P2+f/09vn/ +9Pb5//X3+v/19/r/9ff6//X3+v/19/r/9ff6//X3+v/19/r/9Pf6//P3+f/w8/j/6e70/8XS4f+xwtf/ +XIKr/053pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/053pP9dgq3/aoyz/6a70v+4ydv/ +5Orx/+zw9v/y9vj/8/f5//f4+//4+fv/9/r8//f6/P/4+fv/9/n7//b5+//2+fv/9vn7//b5+//2+fv/ +9vn7//b5+//2+fv/9vj6//b3+v/1+Pr/9ff6//X3+v/19/r/9ff6//X3+v/19/r/9ff6//L1+f/x9Pj/ +7PD1/+Lp8P+tv9X/mbDL/1d+qf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9NdqP/ +T3el/1F4pv9Xfqj/WH+p/16CrP9fhK3/Zoiw/2uLsv9/nb7/hqLB/5WtyP+ascv/p7zS/6m90/+rv9T/ +rMDV/7TG2P+1x9n/tcfZ/7XH2f+1x9n/tcfZ/7XH2f+1x9j/ssPY/63A1f+UrMf/iaTC/2SJr/9cgqv/ +Tnek/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Pd6T/UXml/1h+qf9eg6z/ +dpW4/4Ccvf+juM//rL/U/8TR4P/I1OL/ydbj/8nW4//I1OL/x9Ph/8DP3//Azt7/wM7e/8DO3v/Azt7/ +wM7e/8DO3v+/zt7/usnc/7jI3P+2yNn/tcfZ/7XH2f+1x9n/tcfZ/7XH2f+0xtj/ssTW/6O40P+bssz/ +fJq7/3OTt/9cgqv/V36o/052pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9NdqP/UHem/1R6qP9qjLL/cJC1/3+dvv+EocH/karG/5OsyP+Vrsn/ +lq/J/561zv+fts7/n7bO/5+2zv+fts7/n7bO/5+2zv+fts7/nLLM/5ivyv99m7z/c5O3/1R8p/9Od6T/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Pd6T/ +YYWu/2qMsv+NpsT/l67J/63A1f+xw9j/tMXX/7TF1/+xw9j/sMLX/6u+0/+qvdP/qr3T/6q90/+qvdP/ +qr3T/6q90/+pvdP/pLjR/6K30P+gt8//n7bO/5+2zv+fts7/n7bO/5+2zv+etc3/nLPM/42nxP+GocD/ +ZYmx/12Drf9Pd6T/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXaj/053o/9QeKX/UXml/1R7p/9Ue6j/Vn2n/1Z9qP9Xfaj/ +WH2o/1d+qf9Yfqn/WH6p/1h+qf9Yfqn/WH6p/1h+qf9Xfqn/WH6p/1d+qP9Seqf/UXmm/012o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdqP/ +T3el/1B4pv9Vfaf/V36o/1l/qv9agKr/W4Gr/1uBq/9agKr/WoCq/1mAqv9ZgKr/WYCq/1mAqv9ZgKr/ +WYCq/1mAqv9Zf6r/WYCp/1l/qf9Yfqn/WH6p/1h+qf9Yfqn/WH6p/1h+qf9Yfqn/V36p/1Z8qP9Ue6f/ +UHil/093pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUIERokOBooOFdFapToTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU6BooOFcRGiQ4AgMFCAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwUHERojOBooN1dFapTnTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqU5xooN1YRGiM3AgMFBwAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwQHDxchMxglNVJFapPnTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWqT5xckNFEOFiAyAgMEBwAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwQGDhYfMRckM1BFapPmTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RGqT5hcjMk8OFR4wAQMEBgAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAgMFDRMZKBYhLUdEaZLlTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RGmS5RUgLEQMEhglAQIDBQAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAgMECxAVIxQeKUJEaZLkTHWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RGmR5BMdKUAKDxUhAQICBAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBBAcJDg0VHS1DZ4/gTHWi/kx1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9LdKL9QmaP3w0VHCwEBwgNAAABAQAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAwUHCQwTGihCZYzcS3Og+kx1ov9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1ov9KcqD5QWSM2wwTGScDBQYIAAAAAQAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQgMEh07W37ERGmS40tzofxMdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0tzoPtDaJHiOlp9wwgMERwAAAABAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcMERo4Vne6QWSL2UpzoPpMdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0pzn/pAYorXN1R2uAcMEBoAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYJDRQsQl2SNVBxsUlwnPRMdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0lwm/Q0UG+tK0JbjgYJDBQAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUICxIoO1SDMUlookhvmvJMdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0hvmvIvSWWdJjtRfgUICxIAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMFBwsXJTJOIDNGbUZslutMdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0ZrleogMUVsFyMxTQMEBgoAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIEBQgTHSg+HCs8XURqkuZLdKH9 +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/S3Sh/URpkuYcKjpbExwmPAIDBQgAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQIFBwkODRQcKz9fhtFIbZnu +S3Si/Ux1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9LdKH9R22Z7T5fhs8LExoqAwYIDQAAAQEAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwQGCg8VITpZfMFDZ5Dg +S3Og+0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9Lc6D7QmaP3zlYe8AJDhQgAQIDBQAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQcLESQ4TXktRmGY +SG6a8Ux1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9IbpnwLEVglSM3THYFBwoQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAYIDR0sP2EmOlOA +RmyV6kt0ofxMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0t0ofxGbJXqJTlRfBwrPV0EBggNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQECAwcMDxkPGSE2 +P1+G0EdsmOxLdKH9THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/S3Sh/EZrluo+XoTODxggNAcLDhgBAQIDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQQGBwwMEhgm +OFZ5vUFkjNtLc6D6THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/SnOf+kFiitc4VHa5CxIXJQQGBwsAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEDBgkO +HCo7XCU4T3tGbZftTHWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/Rm2X7CQ4TnkbKjpaAwYJDQAAAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBAYJ +FR8rRB4tPmJBZo7eSHCc9Ex1ov5MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x0ov5IcJz0QWaO3h0sPV8UHypBAgQGCQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB +AwUHCgkOFB8tRWGXNlN1tUlwnPVMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0lwnPQ1UnGxLERdkwkNEx0DBAYJAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAUICxElOE97LkZimUVrlepJcZ74THWi/kx1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaL+ +SnKf+EZrlektRl+VJDhMdwUHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAABAgMHCw4WDhYdLjNPcK08XILJSnKe+Ex1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9Kcp74 +PV6EzjRRcrIOFR0vBwoOFwABAgMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAACAwQGCAwRGitCXZA0UHCvR22Y7Upyn/hMdaL/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWi/kpxn/hHbJju +NVJysixEX5QIDBEcAgMEBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAQICBAkOFCAQGSQ4NVJysz1ehM5Kcp/4THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/SnKe+D1eg801UnGy +ERokOAoPFSABAgIEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAABAQQGBwwKDxQhLERglTVScrJFa5XqSHCc9Ex1ov5MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x0ov5IcJz0RWuV6TVRcLEsRF6U +Cg8UIAQGBwwAAAEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAQEBAQMECA4SHA4XHzEuRWKYNlJ0tElwnPVMdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0lwnPQ1UnGxLUVflQ4VHi8IDBEb +AQEDBAAAAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgQFBwcLEBglOE56LUVgl0Nmj95HbZjsSnOf+kt0ofxMdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0t0ofxKc5/6R2yX7ENljd0sRV6UJDhMdwcKDhcCAwQH +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQABAQMFCAsRCQ4THhwsPmEkOE98QmSL2Udsl+tLdKH9 +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/S3Sh/EZrlupAY4rYJDhOeBwsPV4IDRMeBQcLEQABAQIAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgQGCRUfK0QcKjtdOFZ4uz5fhc9GbJXr +SG+Z8UpzoPtLdKH9THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/S3Sh/UpzoPtIb5nxRmyV6z1ehM03VXe5Gyo6WhQfKkECBAYJAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQMFBwkEBwoOCxEXJRAYIjUmOlJ/ +LUVil0RmkOBIbZnuS3Si/Ux1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9LdKH9R22Z7UNmkN8sRV+VJTpQfBAYITQLERYkBAYJDQMEBgkAAAABAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBBAYHDAgMDxkdKz9g +JDZOeDtZfME/YIbRRGmT5UZrlupHb5rySHCc9Etyn/pLc6D7THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/S3Og+0tyn/pIcJz0 +R2+a8kZrlupEaZPlPmCF0DpZe8AjNkx2HCs8XggMDxcEBgcKAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQAAAgIEBwgO +BQkKEgkOFSAMExwrHCs6XCEyRWwvSWafNFBvrkBji9hDaJLjS3Of+kx1ov5MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ +THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0t0ov1Kcp/5Q2iR4kBjitc0UG6t +L0lkniExRWscKTpbCxMaKggOFB8FCAoSBAcIDgAAAgIAAAEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAIDBAYEBwkOExwnPRgjMU4mPFJ/K0NcjzdUdrk6WX3EQmaM3ENoj+BEaJHlRGmS5UVqk+ZFapPn +RWqU50VqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapTo +RWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapTo +RWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapTo +RWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapTo +RWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapTo +RWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapTo +RWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapTo +RWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapTo +RWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapTo +RWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapToRWqU6EVqlOhFapTo +RWqU6EVqlOhFapToRWqU6EVqlOhFapTnRWqT50Vqk+ZEaZLlRGiR5EJnj+BBZYzbOll8wzdUdbgrQ1uO +JjtRfxgjMEwTHCY8AwUIDQECAwUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAABAgQGCAIFBwoFBwsSBggNFAcMEBoIDRIcDBEZJg0THCsUHipAFSAtRhcjMk4YJTRR +Gik5WhsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpb +Gyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpb +Gyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpb +Gyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpb +Gyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpb +Gyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpb +Gyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpb +Gyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpb +Gyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpb +Gyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpbGyo6WxsqOlsbKjpb +Gyo6WxsqOlsbKjpbGyo6WxsqOlsaKTlaGCU0URcjMk4VIC1GFB4qQA0THCsMERkmCA0SHAcMEBoGCA0U +BQcLEgIFBwoCBAYIAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAwQGBwQGCQsLDxUiDBEYJw4WHy8PFyAy +ERslOxIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8 +EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8 +EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8 +EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8 +EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8 +EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8 +EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8 +EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8 +EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8 +EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8EhwmPBIcJjwSHCY8 +EhwmPBIcJjwSHCY8EhwmPBIcJjwRGyU7DxcgMg4WHy8MERgnCw8VIgQGCQsDBAYHAAAAAQAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEBAgMFAQIDBQIDBAYCAwQH +AgMFBwIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUI +AgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUI +AgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUI +AgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUI +AgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUI +AgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUI +AgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUI +AgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUI +AgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUI +AgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUI +AgMFCAIDBQgCAwUIAgMFCAIDBQgCAwUHAgMEBwIDBAYBAgMFAQIDBQAAAAEAAAABAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAABIBAAADAAAAAQDwAAABAQADAAAAAQDwAAABAgADAAAABAADhPYBAwADAAAAAQAB +AAABBgADAAAAAQACAAABCgADAAAAAQABAAABEQAEAAAAAgADhQYBEgADAAAAAQABAAABFQADAAAAAQAE +AAABFgADAAAAAQCIAAABFwAEAAAAAgADhP4BGgAFAAAAAQADhOYBGwAFAAAAAQADhO4BHAADAAAAAQAB +AAABKAADAAAAAQACAAABUgADAAAAAQABAAABUwADAAAABAADhQ6HcwAHAAAMSAADhRYAAAAAAAAAkAAA +AAEAAACQAAAAAQAIAAgACAAIAAH+AAABhgAAAAAIAAH+CAABAAEAAQABAAAMSExpbm8CEAAAbW50clJH +QiBYWVogB84AAgAJAAYAMQAAYWNzcE1TRlQAAAAASUVDIHNSR0IAAAAAAAAAAAAAAAAAAPbWAAEAAAAA +0y1IUCAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARY3BydAAA +AVAAAAAzZGVzYwAAAYQAAABsd3RwdAAAAfAAAAAUYmtwdAAAAgQAAAAUclhZWgAAAhgAAAAUZ1hZWgAA +AiwAAAAUYlhZWgAAAkAAAAAUZG1uZAAAAlQAAABwZG1kZAAAAsQAAACIdnVlZAAAA0wAAACGdmlldwAA +A9QAAAAkbHVtaQAAA/gAAAAUbWVhcwAABAwAAAAkdGVjaAAABDAAAAAMclRSQwAABDwAAAgMZ1RSQwAA +BDwAAAgMYlRSQwAABDwAAAgMdGV4dAAAAABDb3B5cmlnaHQgKGMpIDE5OTggSGV3bGV0dC1QYWNrYXJk +IENvbXBhbnkAAGRlc2MAAAAAAAAAEnNSR0IgSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAASc1JHQiBJRUM2 +MTk2Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFhZ +WiAAAAAAAADzUQABAAAAARbMWFlaIAAAAAAAAAAAAAAAAAAAAABYWVogAAAAAAAAb6IAADj1AAADkFhZ +WiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9kZXNjAAAAAAAAABZJRUMgaHR0cDov +L3d3dy5pZWMuY2gAAAAAAAAAAAAAABZJRUMgaHR0cDovL3d3dy5pZWMuY2gAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAAAAAAAAAuSUVDIDYxOTY2LTIuMSBEZWZh +dWx0IFJHQiBjb2xvdXIgc3BhY2UgLSBzUkdCAAAAAAAAAAAAAAAuSUVDIDYxOTY2LTIuMSBEZWZhdWx0 +IFJHQiBjb2xvdXIgc3BhY2UgLSBzUkdCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRlc2MAAAAAAAAALFJl +ZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBpbiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAACxSZWZlcmVu +Y2UgVmlld2luZyBDb25kaXRpb24gaW4gSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAB2aWV3AAAAAAATpP4AFF8uABDPFAAD7cwABBMLAANcngAAAAFYWVogAAAAAABMCVYAUAAAAFcf521l +YXMAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAKPAAAAAnNpZyAAAAAAQ1JUIGN1cnYAAAAAAAAEAAAA +AAUACgAPABQAGQAeACMAKAAtADIANwA7AEAARQBKAE8AVABZAF4AYwBoAG0AcgB3AHwAgQCGAIsAkACV +AJoAnwCkAKkArgCyALcAvADBAMYAywDQANUA2wDgAOUA6wDwAPYA+wEBAQcBDQETARkBHwElASsBMgE4 +AT4BRQFMAVIBWQFgAWcBbgF1AXwBgwGLAZIBmgGhAakBsQG5AcEByQHRAdkB4QHpAfIB+gIDAgwCFAId +AiYCLwI4AkECSwJUAl0CZwJxAnoChAKOApgCogKsArYCwQLLAtUC4ALrAvUDAAMLAxYDIQMtAzgDQwNP +A1oDZgNyA34DigOWA6IDrgO6A8cD0wPgA+wD+QQGBBMEIAQtBDsESARVBGMEcQR+BIwEmgSoBLYExATT +BOEE8AT+BQ0FHAUrBToFSQVYBWcFdwWGBZYFpgW1BcUF1QXlBfYGBgYWBicGNwZIBlkGagZ7BowGnQav +BsAG0QbjBvUHBwcZBysHPQdPB2EHdAeGB5kHrAe/B9IH5Qf4CAsIHwgyCEYIWghuCIIIlgiqCL4I0gjn +CPsJEAklCToJTwlkCXkJjwmkCboJzwnlCfsKEQonCj0KVApqCoEKmAquCsUK3ArzCwsLIgs5C1ELaQuA +C5gLsAvIC+EL+QwSDCoMQwxcDHUMjgynDMAM2QzzDQ0NJg1ADVoNdA2ODakNww3eDfgOEw4uDkkOZA5/ +DpsOtg7SDu4PCQ8lD0EPXg96D5YPsw/PD+wQCRAmEEMQYRB+EJsQuRDXEPURExExEU8RbRGMEaoRyRHo +EgcSJhJFEmQShBKjEsMS4xMDEyMTQxNjE4MTpBPFE+UUBhQnFEkUahSLFK0UzhTwFRIVNBVWFXgVmxW9 +FeAWAxYmFkkWbBaPFrIW1hb6Fx0XQRdlF4kXrhfSF/cYGxhAGGUYihivGNUY+hkgGUUZaxmRGbcZ3RoE +GioaURp3Gp4axRrsGxQbOxtjG4obshvaHAIcKhxSHHscoxzMHPUdHh1HHXAdmR3DHeweFh5AHmoelB6+ +HukfEx8+H2kflB+/H+ogFSBBIGwgmCDEIPAhHCFIIXUhoSHOIfsiJyJVIoIiryLdIwojOCNmI5QjwiPw +JB8kTSR8JKsk2iUJJTglaCWXJccl9yYnJlcmhya3JugnGCdJJ3onqyfcKA0oPyhxKKIo1CkGKTgpaymd +KdAqAio1KmgqmyrPKwIrNitpK50r0SwFLDksbiyiLNctDC1BLXYtqy3hLhYuTC6CLrcu7i8kL1ovkS/H +L/4wNTBsMKQw2zESMUoxgjG6MfIyKjJjMpsy1DMNM0YzfzO4M/E0KzRlNJ402DUTNU01hzXCNf02NzZy +Nq426TckN2A3nDfXOBQ4UDiMOMg5BTlCOX85vDn5OjY6dDqyOu87LTtrO6o76DwnPGU8pDzjPSI9YT2h +PeA+ID5gPqA+4D8hP2E/oj/iQCNAZECmQOdBKUFqQaxB7kIwQnJCtUL3QzpDfUPARANER0SKRM5FEkVV +RZpF3kYiRmdGq0bwRzVHe0fASAVIS0iRSNdJHUljSalJ8Eo3Sn1KxEsMS1NLmkviTCpMcky6TQJNSk2T +TdxOJU5uTrdPAE9JT5NP3VAnUHFQu1EGUVBRm1HmUjFSfFLHUxNTX1OqU/ZUQlSPVNtVKFV1VcJWD1Zc +VqlW91dEV5JX4FgvWH1Yy1kaWWlZuFoHWlZaplr1W0VblVvlXDVchlzWXSddeF3JXhpebF69Xw9fYV+z +YAVgV2CqYPxhT2GiYfViSWKcYvBjQ2OXY+tkQGSUZOllPWWSZedmPWaSZuhnPWeTZ+loP2iWaOxpQ2ma +afFqSGqfavdrT2una/9sV2yvbQhtYG25bhJua27Ebx5veG/RcCtwhnDgcTpxlXHwcktypnMBc11zuHQU +dHB0zHUodYV14XY+dpt2+HdWd7N4EXhueMx5KnmJeed6RnqlewR7Y3vCfCF8gXzhfUF9oX4BfmJ+wn8j +f4R/5YBHgKiBCoFrgc2CMIKSgvSDV4O6hB2EgITjhUeFq4YOhnKG14c7h5+IBIhpiM6JM4mZif6KZIrK +izCLlov8jGOMyo0xjZiN/45mjs6PNo+ekAaQbpDWkT+RqJIRknqS45NNk7aUIJSKlPSVX5XJljSWn5cK +l3WX4JhMmLiZJJmQmfyaaJrVm0Kbr5wcnImc951kndKeQJ6unx2fi5/6oGmg2KFHobaiJqKWowajdqPm +pFakx6U4pammGqaLpv2nbqfgqFKoxKk3qamqHKqPqwKrdavprFys0K1ErbiuLa6hrxavi7AAsHWw6rFg +sdayS7LCszizrrQltJy1E7WKtgG2ebbwt2i34LhZuNG5SrnCuju6tbsuu6e8IbybvRW9j74KvoS+/796 +v/XAcMDswWfB48JfwtvDWMPUxFHEzsVLxcjGRsbDx0HHv8g9yLzJOsm5yjjKt8s2y7bMNcy1zTXNtc42 +zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp22vvbgNwF3IrdEN2W +3hzeot8p36/gNuC94UThzOJT4tvjY+Pr5HPk/OWE5g3mlucf56noMui86Ubp0Opb6uXrcOv77IbtEe2c +7ijutO9A78zwWPDl8XLx//KM8xnzp/Q09ML1UPXe9m32+/eK+Bn4qPk4+cf6V/rn+3f8B/yY/Sn9uv5L +/tz/bf//0hUKOh+iHTyABYAOgAnSIgo/JIAPgAhPEgAH9m5NTQAqAAfpCAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAABAQIAAQECAAICBAMEBgkDBQcLBAUIDAMGBwwEBggNBAYIDgUHChAF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEAQGCA4EBggNAwYHDAQFCAwDBQcLAwQGCQACAgQA +AQECAAEBAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAEBAgMDBQcDBAYJBQcKDwgNEhwKEBYiCxAXJAsTGigMExsqDBUdLA8WHzEP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxYfMQwVHSwMExsqCxMaKAsQFyQKEBYiCA0SHAUHCg8D +BAYJAwMFBwABAQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAEBAgMEBgkDBQcLBgkNFAoRFyQNFR0tDhYeLw8YIjUQGSM3ERolOxMeKUAU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEEx4pQBEaJTsQGSM3DxgiNQ4WHi8NFR0tChEXJAYJDRQD +BQcLAwQGCQABAQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEAAAEBAQIEBgUHCg8GCQ0UBwsQGAoQFiML +EhknDRQbKw4XITMQGSM3ERsmPBUfK0MWIS5IGCQyThsqOlsdLT5hHS4/Yx8vQmcfMENpIDFFayE0R3Ai +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyITRHcCAxRWsfMENpHy9CZx0uP2MdLT5hGyo6WxgkMk4W +IS5IFR8rQxEbJjwQGSM3DhchMw0UGysLEhknChAWIwcLDxgGCQwUBQcJDwECBAYAAAEBAAABAQAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQIDAwQCAwMFBgoMEw4VHi8SHCc9FyIxTB8xQ2kk +N014Jz1Ugy9GY5syTGqmNE9urTdWd7o5WXvBOlp9wztcgMk8XYLLPF2CzD1eg8w9XoPNPV6Ezj5fhNA+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+E0D1ehM49XoPNPV6DzDxdgsw8XYLLO1yAyTpafcM5 +WXvBN1Z3ujRPbqwyTGqlL0ZjmSc8U4MkNkx3HzBCaRciMUoSHCc8DhUeLgYKDBMCAwMFAgMDBAAAAAEA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQIDAwUCAwQGCAwQGRIcKD4YJTRRHS5AZCpAWowv +SWafNFBwrz1eg81CZY3dRWmS5UlxnfZMdaL+THWi/kx1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1ov5M +daL+SXGd9kVpkuRCZY3cPV6DzDRPb64vSGWeKj9Yix0uQGMYJTNQEhwoPQgMDxkCAwQGAgMDBQAAAQEA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAQEBAgMBAQIEAwQHCgcLDxkJDhQfCxIZJxEaIzcTHig/GCUxTyAyRmwlOU98KUBZijNNa6k3 +VHW3O1l8w0Fki9lFaZLlR2yW60pynvhMdaL+THWi/kx1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1ov5M +daL+SnKe+EdslupFaZLkQWOL2DtZe8I3U3S2M01qpyk/WYolOU97IDJGbBgkMU4THSg/ERkiNwsSGScJ +DhMfBwsPGAMEBgoBAQIDAQECAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAQECAwMFBgkEBggMChAVIBciMEsdLD1fIzVKdC9IZZ01UXKyN1V4uz1dgss/YYjUQWOL2EJokeNE +apTnRWyW60hvnPNJcZ73SnKf+Ut0ov1MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/S3Si/Upyn/lJcZ73SG+c80VslutEapTnQmiQ40Fji9g/YYfUPV2CyzdVd7o1UXKxL0dlnCI1SXIc +KzxdFSIuSAoOFSADBQcLAwQGCQABAQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAQECBAQGCAwFBwoQDRQcKx4tP2MmOlF+LkdimT5fhdBGbJbrSG6Z8EpzoPpMdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/SnKg+khume9Ga5bqPl+Ezy1FYZcl +OU98HSw+YAwTGioEBgkOAwUHCwEBAgMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQIDBQUICxEG +Cg4WChAWIhEaJToVIC1GHCo7XCk/V4gwSWWeNlJys0JljNtIbpnwSXCb80tzofxMdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/S3Oh+0lwm/NIbpnvQWWM2zZRcbAv +SGScKT1WhhoqOloUHyxEEBokOAoPFiIGCg4WBQgLEQECAwUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQgLERAYITQV +ICxFHSw9YC1FYJc1UXGyOFZ4vT9hiNRCZo/fRGmU5khwm/NKc6D6S3Sg+0t0o/5MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/S3Sj/ktzoPtKcqD6SG+b80Rpk+ZC +Zo7fP2GH1DhWeLw1UXGxLURflRwrPF4UHipCDxcgMgUHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgoOFhUgLEUb +KjpbJjpRfzpafcRFapToR22X7UpyoPpMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/SnKg+Udtl+1FapTnOlp9xCU4T3saKDhYFB4qQgYKDhYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQIAAQECAwUHDAkOFB4MEhooEx0pPyEyRW0o +PVSEL0lknkBhh9NHbZftSW+a8UpzoPtMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/SnOg+0lvmvFHbZftP2CG0i9IY5wnO1KBIDFEahIcJz4LEhknCQ4THgIFBwsAAQECAAEBAgAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQMDBAYDAwUHDBEXJBsqPF0kOE56KkJdkTlYer8/ +YonWQmaP30dumvFKcqD6S3Og+0t0o/5MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/S3Sj/ktzoPpKcqD5R26a8EJmj98/YonWOVd6vypCXI8kN014Gyo7WwsQFiMCAwQGAgMDBQAAAQEA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAgMDBQcDBAYJDxUfLyQ5T3wwSmiiN1V2uUVqlehM +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RGqU5zdUdbcvSWafJDhOeg0VHS0CBAUIAgMEBgABAQIA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAEBAgMBAQIDBQcLEA0TGyoRGSQ3Gig4WC5GYpg3VXa5PV2BykZtmO5M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Rm2Y7T1cgMk3VHW3LkVglxkoN1YQGSI2DBMaKQUHChAB +AQIDAQECAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAECAwMFBgkDBggMDhYdLiE0SXIsRF6UMk1sqT9hh9NFapXoR22Y7UpyoPpM +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/SnKg+UZtl+1EapTnPWCG0jJNbKcrQ16SITRIcA0UHS0D +BQcLAwQGCQABAQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAQECAwMGCAwEBwoPERwnPC1EX5U6WXzCP2CF0UdumvBMdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/R26Z8D5fhc85WHvALENfkxEbJTsE +BgkOAwUHCwEBAgMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAABAQIAAQECBAgKEQ0VHS0RHCY8HC0+YTRPbaw/YIXRQ2WM3EhwnPRMdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/SG+c80JljNs+X4XPM05tqhwsPWAR +GyU7DRUcLQQHChAAAQECAAEBAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAABAQMDBAYDAwUHDhQbKyIzSXAtRF+UM05uq0Fki9lHbprwSXCc80pzofxMdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/SnOh/Elwm/NHbpnwQWOK2DJObqss +Q1+TITNJcA0TGikCAwQGAgMDBQAAAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAEBAgMDBQcDBAYJERkjNyxEXpM6WXvBP2CF0EdumfBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/R26Z8D5fhc85 +WHvAK0NekhAZIjYCBAUIAgMEBgABAQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAwUHCwwQGCQPFR8vGyg5WDNNa6c/YIXQQ2WM20hwnPRMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/SG+c80JljNs+ +X4XPMk1rpxknN1UNFR0tChAWIgMFBwsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAACQ4THxsrPF0kOU98LEZimT9hhtNHbpnwSXCb80pzofxMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/SnOh/Elwm/NH +bpnwP2CG0ixFX5YkN0x4Gyo5WgkNEx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAADBIaKCQ4TnowSmiiN1V2uUVqlehMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/RGqU5zdTc7UvSGSdJDZLdgsSGScAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQIDBQUICxEG +Cg4WEh0oPytCXJA3VXa5PV2BykZtmO5MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/RmyX7T1cf8c3U3O1K0FajRIcJz0GCg4VBQgLEAECAwUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQgLERAYITQV +ICxFITJGbjlYe79FapXoR22Y7UpyoPpMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/SnKg+UZtl+1EapTnOFd5viAxRWsUHipCDxcgMgUHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgoOFhUgLEUb +KjpbKD1UhD9iidZMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/P2KJ1Sc7UoEaKDhXFB4qQgYKDhUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAgMBAQIECg8WIh0sPWEm +OlF/MElln0Jmj99MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/QmaO3i9HY5wlOE57HCo7XQoPFSEBAQIDAQECAwAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQECAwMFBgkEBggMERslOi1FYJY6 +Wn3EP2GG0kdumvFMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/R26a8D9hhtE6Wn3CLEVflBEaJTkDBQcLAwQGCQABAQIAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQECBAQGCAwFBwoQFSAtRjVRcbJF +apToR22X7UpyoPpMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/SnKf+Udtl+xFapPmNVFxsBQfK0QEBgkOAwUHCwEBAgMAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwUHCgoPFSENFBwrGyo6XDlXebxH +bZftSW+a8UpzoPtMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/SnOg+0lvmvBHbZfsOVZ4uxoqOVoMExopCQ8UHwMEBgoAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwsPGBciMEseLT9jKT5YiT9hh9RK +cqD6S3Og+0t0o/5MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/S3Si/ktzoPpKcp/5P2CH0ic9VYYcKz1fFSEuSAcKDxcAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQ4UHx0sPV8mOlF+MEllnkJmj99M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/QmaO3i5HY5skOE56Gyo7XAkOEx4AAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEAAAEBCxEZJyM2SnMuR2KZNlNyskRpk+ZM +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RGmT5TRQcLAsRGCWITNIcQsRGSYAAAEBAAABAQAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQIDAwQCAwMFERokOC9IZJ0+X4XQQmWM20hvnPRM +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/SG+b80Fli9k9X4PNLkhjmxEaIzcCAwMFAgMDBAAAAAEA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQIDAwUCAwQGEx4oPzVRcrJGbJbrSG6Z8EpzoPpM +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/SnKg+kdumO5Fa5XpNFFxsBMdKD8CAwQGAgMDBQAAAQEA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgMEBgYJDBMIDBAZGCQyTzhWd7pIbpnwSXCb80tzofxM +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/SnOh+0lwmvJHbpjuN1Z1uRckMk0HDA8YBgkMEgEDAwYA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAcKDw4WHjASHSg/IDJGbjxegstKc6D6S3Sg+0t0o/5M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/S3Sj/ktzoPtKcqD6PFyByyAxRmsSGyc8DhUeLQQGCQ8A +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgkNFBIdJz4YJjRSJTpPfT9hiNRMdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PmGH0yU4TnoXJDJOEhsmOwUJDBMA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwsQGRYkMEwdL0BlKUFYi0Bji9lMdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/QGOK2Ck+V4gdLD5hFiEvSQcLDxgA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAChAWIyAwRGkqQFqMM01sqENokeNMdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Q2eQ4jFNaqUoP1eIHjBCZgoPFSIA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACxIZJyQ3TXgvSWafN1R1t0RqlOdMdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RGmT5jZTc7QuR2ObIzZLdQsRGCYA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADRQcKyc8VIQ0UHCvOll8w0Zsl+tMdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWuW6jpZe8AzT26rJzxTgQwTGyoA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADxcgMy5HY5o9XoPNQWSL2Uhvm/NMdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/SG+b8kBiidg8XIHLLUVhmQ8XIDIA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBkjNzJMaqZCZY3dRWmS5UlxnvdMdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/SXGe9kRokeRBZIzbMUtppRAZIzYA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAABAQIAAQECERslOzRPbqxFaZLlR2yW60pyn/lMdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/SXKf+UZrlepDaJHkM05tqxAbJTsA +AQECAAEBAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAEBAgMDBQcDBAYJFR8sRDdWd7tJcZ32SnKe+Et0ov1MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/S3Si/UpxnvZJcJ30N1V3uRUfK0MD +BAUIAwMEBgABAQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAEBAgMEBgkDBQcLFiEuSDlZe8FMdaL+THWi/kx1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/S3Wj/0x0ov1LdKL9OVh7wBUhLUcD +BQYKAwQFCAABAQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAQIDBAUHCQ8GCQwTGCQxTjpafcNMdaL+THWi/kx1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x0ov1MdKL9Oll8whgjMU0G +CAsSBQYJDgECAgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAgQFCAgMERsKEBYjGyk5WjtcgMhMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/S3Wj/0x1o/9LdaP/O1yAyBkpOFgJ +EBUhBwwQGQIEBQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAwUGCgoPFSENFBsrHSw9YDxdgcpMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PFyByhwsPF4M +ExopCQ8UHwMEBgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAwUHCwsQFyQOFR4vHi0/YzxdgstMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PF2Cyx0tPWAN +FRwsChAVIQMFBwsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAYIDQwTGSgQGSE1HzBBZz1eg81MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PF2DzR8vQWYP +FyE0DBIZJwMFCA0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAYJDg0UGysRGiQ5IDFDaj1ehM5MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV6DzR8wQ2kQ +GSM3DBMbKgQGCA0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAYJDg0VHC0RGyU7IDJEbD1ehM5MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV6Ezh8xRGsQ +GiU6DBQcLAQGCQ4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKDw8VHi8THCg+IjJGbj1fhc9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+EzyIyRm0T +HCc9DxUeLgQHCQ8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THak/093pP9PeKX/UHil/1F5pv9Seab/Unmm/1J5pv9Seab/U3mm/1N7p/9Ue6f/VHyo/1V7p/9V +fKj/VXyo/1V8qP9VfKj/VXyo/1V8qP9VfKj/VXun/1R8qP9Ue6f/U3un/1N5pv9Seab/UXim/1B4pf9P +d6X/T3ek/0x1pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/Tnel/1V7pv9Xfaj/WX6p/1yCq/9eg6z/XoOs/16DrP9eg6z/YISs/2KGr/9kh6//ZIiw/2eJr/9n +irD/Z4qw/2eKsP9nirD/Z4qw/2eKsP9nirD/Z4mv/2SIsP9kh6//Yoav/2CErP9eg6z/XIKq/1h+qf9W +faf/VHum/053pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/T3il/1d9qP9agKr/XYGr/2GGrv9kh6//ZIev/2SHr/9kh6//Zomw/2qLsv9sjbP/bY60/2+QtP9w +kbX/cJG1/3CRtf9wkbX/cJG1/3CRtf9wkbX/b5C0/22OtP9sjbP/aouy/2aJsP9kh6//YYWu/1yBqv9Z +f6n/Vn2n/093pf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/TXak/093pf9QeKb/VHuo/12CrP9hha7/ZIiw/2yNs/9vkLX/cZG2/3SUuP92 +lbn/eZe6/4Cdvv+Dn7//haHA/4ijwv+KpcP/iqXD/4qlw/+KpcP/jKbE/46oxf+Qqcb/kKrG/5Orx/+T +rMf/k6zH/5Osx/+TrMf/k6zH/5Osx/+TrMf/k6vH/5Cqxv+Qqcb/jqjF/4ymxP+KpcP/iKPC/4Shv/+C +n77/f529/3mXuv92lbn/dJO4/2+Qtf9tjrT/aYuy/2KGr/9eg63/WoCr/1F5pv9NdqT/TXaj/0x1pP9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/T3il/1d9p/9agKn/Zomv/3+dvf+LpsP/lq7J/6q+0/+1xtn/usrc/8XT4f/K +1+T/zdrl/9Pd6f/W4Or/1+Dq/9jh6//Z4ev/2eHr/9nh6//Z4ev/2eHs/9vj6//b4+z/3OTs/9vj7f/c +5O3/3OTt/9zk7f/c5O3/3OTt/9zk7f/c5O3/2+Pt/9zk7P/b4+z/2+Pr/9nh7P/Z4ev/2ODr/9fg6v/W +3+r/093o/83Y5f/K1uP/xNHg/7bH2f+wwtb/pbrR/5Cpxf+FocD/eJe5/12CrP9QeKX/T3ek/012pP9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/UHim/1qAqf9eg6z/b5C0/4+pxv+gts7/rsHV/8rW5P/Y4ev/3+bv/+3y9v/0 +9/r/9vn7//39/v////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////f3+//b4+v/09vn/6+/0/9ri7P/R2+f/wtDg/6a60f+Xr8r/hqLB/2KGr/9Reab/UHil/012pP9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWk/012o/9NdqT/Tnek/1J6p/9Te6f/VHyo/1R7p/9VfKj/VXyo/1Z9qf9Wfan/V32p/1d+qv9Y +fqr/WH6q/1h+qv9Yfqr/WH6q/1h+qv9Yfqr/WH6q/1h+qv9Yfqr/WH6q/1h+qv9Yfqr/WH6q/1h+qv9Y +fqr/WH6q/1h+qv9Yfqr/WH6q/1h+qv9Yfqr/WH6q/1h+qv9Yfqr/Vn2p/1N6p/9Reab/UHil/012pP9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/T3el/1V9qP9Yf6r/Y4iv/3mXuv+EoL//kKrG/6u+0/+3yNr/wtDg/9bg6v/h6PD/5uzy//H1+f/2 ++fv/+Pr8//3+/v////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////f7+//j5+//2+Pr/8PP3/+Lp8P/c5O3/0dvn/7zM3f+xw9f/oLfP/4Ccvv9vkLb/Z4mx/1R8qP9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWk/012o/9N +dqT/Tnek/1F5p/9Seqf/Unqn/1J5pv9Seab/Unmm/1J5pv9Seab/U3qn/1R7p/9VfKj/VXyo/1V8qP9V +fKj/VXyo/1V8qP9VfKj/VXyo/1V8qP9VfKj/VXyo/1V8qP9VfKj/VXyo/1V8qP9VfKj/VXyo/1V8qP9V +fKj/VXyo/1V8qP9VfKj/VXyo/1V8qP9VfKj/VXyo/1V8qP9VfKj/VXyo/1Z9qf9Wfan/VXyo/1J6p/9R +eab/UHil/012pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWk/093o/9Pd6T/VXum/16Drf9kh6//ZIiw/2eJr/9nirD/aYuw/2qMs/9sjbP/bY60/2+QtP9w +kbX/cJG1/3CRtf9wkbX/cJG1/3CRtf9wkbX/cJG1/3CRtf9wkbX/cJG1/3CRtf9wkbX/cJG1/3CRtf9w +kbX/cJG1/3CRtf9wkbX/cJG1/3CRtf9wkbX/cJG1/3CRtf9wkbX/a42z/2KGrv9dgqz/WX+p/1B4pv9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/VXyo/2qLsf9zkrb/iqTC/7fI2//O2uf/1d/q/+Ho8P/o7fP/6/D1//P1+P/2+Pr/+Pn7//v8/f/9 +/f7//f3+//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////39/v/9/f7/+/v9//b4+v/09vn/8PP3/+nu9P/l6/L/1+Dr/7rK2/+sv9T/lK3H/2SHsP9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXak/1B3pP9R +eKX/VHqn/1yBqv9fg6z/X4Os/16DrP9eg6z/XoOs/16DrP9eg6z/YIWt/2WIr/9nirD/Z4qw/2eKsP9n +irD/Z4qw/2eKsP9nirD/Z4qw/2eKsP9nirD/Z4qw/2eKsP9nirD/Z4qw/2eKsP9nirD/Z4qw/2eKsP9n +irD/Z4qw/2eKsP9nirD/Z4qw/2eKsP9nirD/Z4qw/2eKsP9nirD/aYuw/2qMs/9sjbP/aIqy/2GFrf9d +gqz/WX+p/1B4pv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/TXak/093pP9QeKX/Vn2o/2WIsP9rjbP/bY60/26QtP9wkbX/cZK2/3WUuP92lbn/eJa6/3qZu/98 +mrz/fJq8/3yavP98mrz/fJq8/3yavP98mrz/fJq8/3yavP98mrz/fJq8/3yavP98mrz/fJq8/3yavP98 +mrz/fJq8/3yavP98mrz/fJq8/3yavP98mrz/fJq8/3yavP98mrz/dZW4/2mLs/9ihq//XYKs/1F5pv9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/WH+q/3OStv9/nL3/nbLM/9bh6//09/r/9vn7//39/v////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////8vX5/9fg6v/K1uT/q77T/2uNtP9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXak/1F4pf9S +eab/V32p/2CErf9liLD/ZYev/2SIsP9kh6//ZIev/2SHr/9kh6//Z4qx/22Os/9wkbX/cJG1/3CRtf9w +kbX/cJG1/3CRtf9wkbX/cJG1/3CRtf9wkbX/cJG1/3CRtf9wkbX/cJG1/3CRtf9wkbX/cJG1/3CRtf9w +kbX/cJG1/3CRtf9wkbX/cJG1/3CRtf9wkbX/cJG1/3CRtf9wkbX/cZK2/3WUuP92lbn/cZG2/2eKsv9i +hq//XYKs/1F5pv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Tnel/1V8p/9X +fqn/YISt/2+Rtv94l7r/fpy9/4qkw/+Qqcb/kKrG/5Orx/+TrMf/lazI/5avyf+Yr8r/mbDL/5uyy/+c +s8z/nLPM/5yzzP+cs8z/nLPM/5yzzP+cs8z/nLPM/5yzzP+cs8z/nLPM/5yzzP+cs8z/nLPM/5yzzP+c +s8z/nLPM/5yzzP+cs8z/nLPM/5yzzP+cs8z/nLPM/5yzzP+cs8z/l7DJ/46nxv+JpMP/gZ2//3CStv9o +i7L/YYav/1R7p/9NdqT/TXaj/0x1pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWk/012o/9N +dqT/YYWu/4ijwv+cssz/ssTX/+Dn8P/2+fv/+Pr8//3+/v////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////9fj6/+Hn7//X4Or/t8fa/3WVuP9V +fKj/U3qm/053pf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/T3el/1V8qP9Yfqr/YIWu/3OSt/97 +mbv/f5y9/4eiwf+LpcP/i6XD/4qlw/+KpcP/iqXD/4qlw/+KpcP/jKfE/5Gqxv+TrMf/k6zH/5Osx/+T +rMf/k6zH/5Osx/+TrMf/k6zH/5Osx/+TrMf/k6zH/5Osx/+TrMf/k6zH/5Osx/+TrMf/k6zH/5Osx/+T +rMf/k6zH/5Osx/+TrMf/k6zH/5Osx/+TrMf/k6zH/5Osx/+TrMf/lazI/5avyf+Yr8r/lK3I/42mxf+J +pMP/g5/A/3aWuf9wkbb/Z4qy/1h+qf9Pd6X/T3ek/0x1pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/VHyo/2eJsP9v +kLX/hqHB/7PF2P/K1uT/ztnm/9bg6v/a4+z/2+Ts/9vj7f/c5O3/3OTt/93l7v/d5e7/3eXu/9/m7//f +5u//3+bv/9/m7//f5u//3+bv/9/m7//f5u//3+bv/9/m7//f5u//3+bv/9/m7//f5u//3+bv/9/m7//f +5u//3+bv/9/m7//f5u//3+bv/9/m7//f5u//3+bv/9/m7//f5u//3eXu/9nh6//X4Or/ytXj/63B1f+g +ts7/i6bE/2SHrv9Pd6T/T3ej/0x1pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXak/093pP9Q +eKX/cZG2/7TG2P/V3+n/3+fu//P1+f/9/f7//f3+//////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////P3+//X3+v/y9fn/0Nrn/4qmw/9o +i7H/YYat/1N6p/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/VXyo/2eKsP9wkbX/h6PB/7fH2v/O +2eb/0dvo/9bg6v/Z4uz/2eLr/9nh7P/Z4ev/2eHr/9nh6//Z4ev/2uLs/9vj7P/c5O3/3OTt/9zk7f/c +5O3/3OTt/9zk7f/c5O3/3OTt/9zk7f/c5O3/3OTt/9zk7f/c5O3/3OTt/9zk7f/c5O3/3OTt/9zk7f/c +5O3/3OTt/9zk7f/c5O3/3OTt/9zk7f/c5O3/3OTt/9zk7f/c5O3/3OTt/93l7v/d5e7/3OTt/9ni7P/Y +4ev/0Nvn/8HP3/+5ydv/oLbO/26PtP9VfKf/U3qm/053pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/V36p/2+Qtf96 +mbv/mLDL/9Te6f/y9fn/9ff6//z9/v////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////7+/v/+/v7/7vL2/8zY5f+8 +zN3/obfP/2uNs/9QeKX/T3ek/012pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXak/1B4pf9R +eab/eZi7/8rW4//y9fj/9ff5//z9/v////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////3OTt/5StyP9x +krb/aIux/1V8qP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WH6q/3CRtf98mrz/mrLL/9nh7P/3 ++fv/+fr8//3+/v////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////9/n7/+Xr8v/d5e7/vMvd/3mYuv9Yfqn/VXyn/093pf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/W4Gr/3qYu/+J +pMP/pLjR/9rj7P/19/r/9/n7//39/v////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////7+/v/+/v7/8vX4/9jh6//M +2OX/ssTY/3+cvf9liLD/X4Os/1J6p/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXak/093pP9Q +eKX/bo+0/62/1P/L1uP/0drm/9rj7P/g5+//5evy//Dz9//19/r/9/n7//39/v////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////3ubu/5uyzP96 +mbv/b5C1/1d+qf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWk/012o/9NdqT/YIWt/4Whwf+YsMr/sMLX/+Ho7//5 ++vz/+vv8//7+//////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////+fr8/+vw9f/l6/L/xNLh/4SgwP9jh6//XoOs/1F5pv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Yoav/5Cqxf+m +u9H/vMzc/+bs8//8/f7//P3+//////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////+/z9//P2+f/v +8/f/2OHr/6m90/+Sq8f/gZ6+/12CrP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWk/012o/9N +dqT/WH+q/3CRtv97mrz/haHA/5avyv+gts7/r8LW/9Db5v/f5+7/5+3y//f5+/////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////4+nx/6m90v+N +p8T/fZu7/1yBrP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWk/093o/9Pd6T/cJC1/7DC1//R2+j/3OTt//L1+f/9 +/v7//f7+//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////f7+//n6/P/3+fv/2OHr/5mwyv96mLr/b4+0/1d+qf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Zomx/5uyy/+1 +xtn/x9Ti/+3x9v////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////6e70/73M3f+nu9L/karG/2KGr/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/Tnak/1J6pv9Ue6f/X4St/3aVuP+Bnr7/lq7J/8DP3v/V3+n/3+fu//X3+v////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////5evy/7DC1v+W +rsn/hKC//16Drf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXak/093pP9QeKX/d5a5/8bT4v/t8fb/8fT4//v8/f// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////4ejw/6O4z/+FocD/d5a4/1qAq/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ZIew/5Stx/+s +v9T/wM/e/+vv9f////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////7vL3/8vX5P+6ytz/n7XO/2iLsv9NdqT/TXaj/0x1pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/TXak/1F5pf9Seqb/W4Gq/2uNs/90lLf/g6DA/6S5z/+zxdj/w9Hg/+Do8P/w9Pj/8/b5//z9/v// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////5uzy/7LD1/+Z +sMr/hqHA/1+Erf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/UHim/1qAqf9eg6z/g6C//8zX5f/x9Pj/9Pb5//z9/v// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////4Ofv/5+1zf+Anb3/c5O2/1l/qv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/X4St/4ahwf+Z +sMv/ssPY/+bs8v////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////9/n7/+fs8//f5u//vMvd/3WUuP9Seab/UXil/012pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWk/052o/9OdqT/UXil/1Z9qf9Zf6r/X4St/2qMsv9wkbX/iKPB/7nJ2//R2+f/3OTt//T2+f// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////5+3z/7fH2v+f +tc7/i6XD/2CFrv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WH6q/3GStf99m7z/nLTM/9zj7f/7/P3//Pz9//7///// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////3uXu/5mxy/94l7r/bY+0/1d9qf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/XYKs/3+cvf+Q +qcb/q77U/+Tq8f////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////P3+//X3+v/y9fn/ytbk/3uZvP9Teqf/Unmm/012pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWk/053o/9Od6T/a46z/6W50f/C0OD/0dvn//D0+P// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////6O3z/7nJ2/+i +t8//jafE/2GFrv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/W4Gr/3yau/+LpsP/qLzS/+Lp8P////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////3OTt/5auyf9zk7f/aoyy/1V8qP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WX+q/3OTt/+A +nb7/n7XO/93l7f/8/f3//P39//////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////f3+//f5+//19/r/0Nvn/4ijwv9jh6//XoOs/1F5pv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/053o/9Od6P/ZYmv/5Gqxv+ovNL/vczd/+nv9P/+ +/////v////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////6O3z/7nJ2/+i +t8//jafE/2GFrv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/YIWu/4qkwv+etM3/tsbZ/+ft8/////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////v////z8/f/7/P3/1+Dq/46oxv9qjLP/Y4av/1N7p/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/UXmm/12Cq/9i +hq7/hqLB/8/a5v/z9vn/9vj6//z9/v////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////z9/v/8/f7/3eXu/6K3z/+Dn7//dpW4/1l/qv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWk/0x1o/9MdaT/Vn2p/2uMtP91lLn/l67K/9ri7P/8 +/P3//Pz9//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////6O3z/7jI2/+h +ts//jKbE/2GFrv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/aYyy/6W60P/C0d//0dzn//D09/////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////P3+//X3+f/y9fj/y9fk/36cvP9Xfqj/VXym/053pf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXak/1F5pf9S +eqb/epm6/8jV4//w9Pf/8/b5//z9/f////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////5Ovx/67A1f+TrMf/gp6+/12DrP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/T3il/1h9qf9bgKv/g5/A/9Pd6P/7 +/P3//Pz9//7///////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////6O3z/7jI2/+h +ts//jKbE/2GFrv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/bo+1/7PF1//V3+n/3+fu//X3+v////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////+/z9//L1+P/u8vb/xtPi/3aWuP9Od6T/Tnej/0x1pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXak/1B4pP9R +eaX/dJS3/7rK3P/d5e7/5evy//f5+/////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////6e70/73L3P+nutH/kanF/2KGr/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/T3el/1V7p/9Yfan/fpu9/8vW5P/x +9Pj/9Pb5//z9/v////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////6O3z/7jI2/+h +ts//jKbE/2GFrv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/Tnel/1J6pv9UfKj/dpa5/73N3f/f5+7/5+3y//f5+/////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////9/n7/+Tq8f/c5O3/uMja/3KTtv9Od6P/Tnej/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWk/012o/9N +dqT/aIux/560zf+5ydr/ytbj/+7y9v////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////8/b5/9ji6//M2eX/rMDU/22PtP9NdqP/TXaj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THak/093pP9PeKX/c5O3/7jJ2//c +5O3/5Orx//f5+/////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////6O3z/7fH2v+g +tc7/i6XD/2GFrv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/Unqn/2GErP9nibD/i6TC/9Hc6P/19/r/9/n7//39/v////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////7fL2/8nV4/+3yNr/nbPN/2aKsf9MdaT/THWj/0x1pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/Yoav/5Cpxf+mutH/vMvc/+nu9P////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////+Pn7/+ft8//g5+//u8vc/3KStv9NdqP/TXaj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/bY+1/7HC1v/S +3Oj/3eTt//T3+v////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////6O3z/7fH2v+g +tc7/i6XD/2GFrv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/VHyo/2eJsP9vkLX/k6vH/9vk7f////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////6e70/7vL3P+lutH/j6nF/2KGr/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/XYOs/4Kevv+TrMf/rsDV/+Pq8f/+/v///v7///////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////+fv8/+3x9v/n7fP/xNLh/32bvP9agKr/V32o/094pf9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/a42z/6q90//J +1eP/1t/q//L1+P////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////5+3z/7fH2v+f +tc7/i6XD/2CFrv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/Wn+q/3aVuP+En7//orfP/+Hn7/////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////z9/v/8 +/f7/4ejw/63A1f+Sq8f/gZ6+/12CrP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/VHuo/2aJsP9uj7X/karH/9fg6v/6+/z/+/z8//7+//////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////v7+//n6/P/4+fv/1+Dr/5auyP91lbj/a42y/1Z9qf9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Z4qx/52zzf+4 +yNv/ydXk/+7y9v////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////5+3z/7fG2f+f +tM3/i6TC/2CFrv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/ZIiw/5StyP+swNX/wM/f/+vw9f////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////f3+//b4+v/0 +9vn/0dzn/46nxf9rjbP/ZIev/1N7p/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/T3il/1h+qf9bgav/g5/A/9Hc5//5+vz/+vv8//7+//////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////4Ofv/6G3z/+Cn7//dZW4/1l/qv9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ZIiw/5euyf+v +wdb/w9Dg/+vw9f////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////5+3z/7bG2f+e +tM3/iqTC/2CFrv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/aYuy/6S50P/Bz9//0Nvn//Dz9/////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////P3+//T2+f/x +9Pj/y9bk/36cvf9Yfqn/VXyn/093pf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/T3el/1V8p/9Yfqn/e5q7/8TQ4P/n7PL/7fD1//n7/P////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////5uzy/7HD1/+YsMr/haHA/1+Erf9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Y4ev/5Orx/+q +vdP/v83e/+rv9P////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////5+zz/7XG2P+d +s8z/iaTB/2CErv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/TXak/1F4pf9Seab/cpG2/7DD1//Q2+f/2+Tt//T2+f////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////9vj6/+Lp8P/Z +4uv/uMna/3aVuP9VfKf/U3qm/053pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THak/093pP9PeKX/bI60/6a70v/D0eH/0tzo//D0+P////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////8PT4/9Hb5//C0OD/pbnQ/2mMs/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/YYWu/4umw/+g +ts7/t8ja/+jt8/////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////5+zz/7TG2P+c +s8z/iKTB/2CErv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/UHim/1qAqf9eg6z/gp++/8nV5P/t8fb/8fT4//v8/f////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////6u/1/8DO3v+r +vtT/lKzI/2aJsf9Pd6X/T3ek/0x1pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/ZYiw/5iwyv+xw9f/xNLh/+zw9f////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////9vj6/+Ho8P/Y4ev/tcbZ/2+Qtf9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/X4St/4ijwf+b +ssv/tMXY/+bs8v////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////5uzy/7TF2P+b +ssv/iKPB/1+Erf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/Unmm/16DrP9kh6//iqXD/9bf6v/8/f7//P3+//////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////5Ovx/67A1f+T +rMf/gp6+/12DrP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/YISu/4ikwv+cs83/tMbZ/+br8//+/v///v7///////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////+Pr8/+jt8//h6PD/v83e/3qZvP9Yfqr/VXyo/093pf9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/X4St/4WhwP+Y +sMr/scPX/+bs8v////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////5uzy/7PE2P+a +scv/h6LB/1+Erf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/V36p/26PtP95mLr/mrHL/9vk7f/8/f7//P3+//////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////f7+//j5/P/2+Pv/2eHs/5+1zv+C +nr//dZS4/1l/qv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/VXyo/2qMsf9zk7b/la3I/9jh6v/6+/z/+/z8//7+//////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////f7+//j5+//2+Pr/1d7p/5Grxv9wkbX/Z4qw/1V8qP9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/XYKs/4Gdvf+S +qsb/rb/U/+Tq8f////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////5evy/7HD1v+X +r8n/haG//16Drf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/Yoav/4+nxP+luND/u8nb/+nu9P////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////+fr8/+rv9P/k6vH/wtHg/4Cdvf9e +hKz/WoCp/1B5pv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/UHim/1qAqf9eg6z/haHA/9Lc6P/5+vz/+vv8//7+//////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////3+bv/5yzzP98mrz/cJG1/1h+qv9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/XIKs/3+bvP+P +qMX/q73T/+Pq8f////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////5evy/7DC1v+W +rsn/hKC//16Drf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/Z4qx/5+0zf+6ydv/y9bk/+7y9v////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////9vj7/+Tq8f/b4+3/uMjb/3CRtv9N +dqT/TXaj/0x1pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/T3il/1d9p/9agKn/fZu7/8TR4P/n7PL/7fD1//n7/P////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////5evy/67B1f+Urcj/gp++/16Drf9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/XIGs/32au/+N +psT/qbzS/+Pp8f////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////5evy/6/B1f+V +rcj/g5++/16Drf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXak/1J5pv9T +eqf/cZG3/62/1P/L1uT/2ODq//L1+f////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////8PP3/9Db5//Bz9//pLnQ/2qMsv9N +dqP/TXaj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/TXak/093pf9QeKb/bI61/6a60v/C0OH/0dvo//D0+P////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////8fT4/9Lc6P/E0eH/prrR/2uNs/9N +dqP/TXaj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/W4Cr/3mYuv+I +o8L/pbrR/+Lo8P////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////5Ovy/63A1f+S +rMj/gZ6+/12Drf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/UXmm/12Cq/9i +hq7/hKHA/8nV4//r8PX/8PP3//r8/f////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////39/v/9/f7/4efw/6m90/+Np8X/fZu9/1yBrP9M +daT/THWj/0x1pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ZYiw/5evyv+wwtf/w9Hh/+zw9f////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////9/n7/+Tq8f/c5O3/uMja/3GStv9N +dqP/TXaj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WoCr/3iWuf+G +ocH/pLjQ/+Ho8P////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////5Orx/6zA1f+R +q8f/gJ6+/12CrP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/U3qn/2KGrv9p +i7L/jqjF/9fg6//8/f7//P3+//////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////39/v/9/f7/2uLs/5auyf9zk7f/aoyy/1V8qP9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/X4Su/4ijwv+bss3/tMXZ/+Xr8//+/v///v7///////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////+fr8/+rv9P/k6vH/wtDf/36cvf9c +gqv/WH+p/1B4pf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WoCr/3aVuP+E +oMD/orfP/+Ho8P////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////5Orx/6u/1f+Q +qsf/f52+/12CrP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WX+q/3OTt/+A +nb7/n7XO/93l7v/8/f7//P3+//////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////+fv8/+3w9v/n7PP/yNTj/4mkwv9qjLL/Y4au/1N7p/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/VXyo/2qMsf9zk7b/la3I/9jh6v/6+/z/+/z8//7+//// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////f7+//n6/P/3+fv/2OHr/5qxy/97 +mbv/cJC1/1d+qf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WX+q/3SUt/+B +nr7/oLbO/+Dn7/////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////4+rx/6q90/+O +qMX/fpu8/1yCrP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ZIiw/5euyf+v +wdb/w9Dg/+vw9f////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////7/P3/83Y5f+9zN3/o7jQ/2+Qtf9VfKj/U3qm/053pf9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/UHim/1qAqf9eg6z/haHA/9Lc6P/5+vz/+vv8//7+//// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////4unw/6e70v+K +pcP/e5m7/1uBq/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WH+q/3OTtv9/ +nb3/n7XN/9/n7/////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////4+nx/6m90/+N +p8X/fZu8/1yBrP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/aoyz/6i80v/G +0+L/1N7p//H0+P////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////6e70/73M3f+nu9L/karG/2KGr/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/T3il/1d9p/9agKn/fZu7/8TR4P/n7PL/7fD1//n7/P// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////6O3z/7jJ2/+h +t8//jKfF/2KGrv9NdqT/TXaj/0x1pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WH+q/3OTtv9/ +nb3/n7XN/9/n7/////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////4+nx/6i90v+M +p8T/fJu7/1yBrP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Tnel/1R7p/9Wfan/dpW5/7TG2f/U +3un/3ubu//X3+v////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////f7+//j5/P/2+Pv/3eTu/6q+1P+Rqsf/gJ2+/12CrP9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXak/093pf9QeKb/bI61/6a60f/C0OD/0dvn//D0+P// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////9Pb5/9vk7f/Q +2+f/sMPX/3GRtf9ReaX/UHik/012pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WH+q/3OTtv9/ +nb3/n7XN/9/n7/////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////4unw/6a60v+J +pMP/epi7/1uBq/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/VHun/2SHr/9sjbP/jabF/8/a5v/w +8/j/8/b5//z8/v////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////+Pr8/+nu8//i6fD/w9Hf/4SgwP9liK//X4Os/1J6pv9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ZYiw/5evyf+wwtb/w9Hg/+zw9f// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////+fv8/+3x9v/n +7fP/wtDg/3eXuf9Seqb/UXml/012pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WH+q/3OTtv9/ +nb3/n7XN/9/n7/////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////4unw/6W60f+I +pML/eZi6/1uBq/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Vn2p/2yNs/92lbn/mK/K/9zk7v/+ +/v///v7///////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////9vj7/+Lp8P/Z4uz/tsfa/3KStv9Pd6T/T3ej/0x1pP9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/X4Su/4ijwf+bssz/tMXY/+Xr8v/+ +/v7//v7+//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////+/z9//H0+P/t +8fb/zNfl/4mlwv9oi7H/YYat/1N6p/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WH+q/3OTtv9/ +nb3/n7XN/9/n7/////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////4ejw/6W60f+H +o8L/eZi6/1qAq/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWk/012o/9NdqT/XoOs/4Ccvv+Rqcb/rL7U/+Pp8f/+ +/v///v7///////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////3+/v/9/v7/7fL2/8zY5f+8zN3/oLbO/2uNsv9Pd6P/T3ej/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/VXyo/2mLsf9ykrb/lKzI/9fh6v/5 ++/z/+vz8//7+//////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////v7///r8/P/5 ++/z/3+jv/62/1f+TrMj/gp6+/12Drf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WH+q/3OTtv9/ +nb3/n7XN/9/n7/////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////4ejw/6O4z/+F +ocD/d5a4/1qAq/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWk/093o/9Pd6T/bY6z/6e80//F0+L/097p//H0+P// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////v7///r7/P/5+vz/2+Ps/6G2z/+Dn7//dpW5/1l/qv9MdaT/THWj/0x1pP9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/UHim/1l/qf9dgqz/hKC//9Hc6P/4 ++vv/+fv8//7+/v////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////6u/0/77N3v+pvdP/kqvH/2OHr/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WH+q/3OTtv9/ +nb3/n7XN/9/n7/////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////4ejw/6K3z/+E +oMD/dpW4/1qAq/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXak/093pP9QeKX/dJO3/7zM3f/g5+//5+3z//j5+/// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////f7+//n6/P/3+fv/093p/4qlwv9mibD/YISs/1J6p/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/T3el/1Z9p/9Zf6n/fJu7/8PQ4P/m +7PL/7PD1//n7/P////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////7/P3/83Z5v+9zd7/orjQ/26Qtf9Te6f/Unqm/012pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WX+q/3OTtv+A +nb3/n7XN/+Dn7/////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////4Ofv/6K3z/+D +n7//dpW4/1l/qv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Unmm/16DrP9kh6//hKHA/8fT4v/n7fP/7fH2//n7/P// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////9/n7/+br8v/e5e7/v83e/3+cvP9ghKz/W4Cp/1F5pv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXak/093pf9QeKb/bI60/6a60f/C +0N//0dvn//D09/////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////+fv8/+3w9f/n7PL/xtLh/4Ogv/9ihq7/XYKr/1F5pv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WH+q/3OTt/9/ +nb7/n7XO/9/n7/////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////4Ofv/6C2z/+B +nr//dJS4/1l/qv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/XIGs/3yau/+MpsT/p7vR/93k7v/4+fv/+fr8//7+/v// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////6u/1/8DP3v+rv9T/la7I/2iLs/9Seqf/UXmm/012pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ZYiw/5evyf+w +wtb/w9Hg/+zw9f////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////v////z8/f/7/P3/1+Dq/42oxf9pjLL/Yoau/1N7p/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WX+q/3OTt/+A +nb7/n7XO/+Dn7/////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////4Ofv/5+1zv+A +nb7/c5O3/1l/qv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/YYWu/4ulw/+gtc7/t8fa/+jt8/////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////5Orx/63A1f+Sq8f/gZ6+/12CrP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/X4Su/4ijwf+b +ssz/tMXY/+Xr8v/+/v7//v7+//////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////z8/f/8/P3/3uXu/6K3z/+EoMD/dpW4/1qAq/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WX+q/3SUuP+B +nr//oLbP/+Dn7/////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////3+fv/5+0zv9/ +nL7/c5K3/1h/qv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/TXak/1F5pv9Seqf/a420/52zzP+2xtn/yNTi/+3x9v////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////+/z9//P2+f/v +8/f/1N7p/5yzzP+Bnr7/dJS3/1l/qv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/VXyo/2mLsf9y +krb/lKzI/9fh6v/5+/z/+vz8//7+//////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////7////+////7fL2/8rX5P+5ytv/nrXN/2iLsf9NdqP/TXaj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WoCr/3iWuP+G +ocD/pLjP/+Ho8P////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////3+fv/560zP9+ +nLz/cpK1/1h/qv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/UHim/1uAqf9fg6z/gJ29/8HP3//i6fD/6e7z//j6/P////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////8/b5/9vj7P/P +2ub/s8TY/3mYuv9dgqz/WX+p/1B4pv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/UHim/1l/qf9d +gqz/hKC//9Hc6P/4+vv/+fv8//7+/v////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////9ff6/97m7v/U3un/ssTX/2+Qtf9NdqP/TXaj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WoCr/3mXuf+H +osH/pbnQ/+Ho8P////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////3+bv/520zP99 +m7z/cZK1/1h+qv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/Unqn/1+DrP9liLD/iqTC/9Pe6f/4+vv/+fv8//7+/v////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////7/P3/8/a5v+/ +zt7/o7jP/2iLsv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/T3el/1Z9p/9Z +f6n/fJq7/8HQ3//k6/H/6vD0//n6/P////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////9/n7/+bs8v/e5u7/vs3e/36cvP9eg6z/WoCp/1B4pv9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/W4Gr/3yZu/+L +pcP/qLvS/+Lp8P////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////3+fv/560zP9+ +nLz/cpK1/1h/qv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWk/0x1o/9MdaT/WX+q/3STuP+Bnb7/n7TO/9vk7P/5+/z/+vz8//7+//////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////v7///r7/P/5+vz/5Orx/7rK3P+l +utH/j6nF/2KGr/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXak/093pf9Q +eKb/a4y0/6G3zv+8y9z/zNjk/+/y9/////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////f3+//f5+//19/r/2OHr/52zzP+Anb3/c5O2/1l/qv9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/XYKs/4Gevv+S +q8f/rcDV/+Tq8f////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////3+fv/5+0zv9/ +nL7/c5K3/1h/qv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/012o/9NdqP/aIux/520zf+4ydv/ydbk/+3x9f/+/v7//v7+//////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////+vv9/+/z9v/q7/T/y9jk/46nxf9v +kLX/Z4mw/1R8qP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/Y4ev/5Gqxv+ovNL/vczd/+rv9P////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////5Orx/6y/1P+Rqsb/gJ29/12CrP9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/XoOt/4Sgv/+W +rsn/sMLW/+Xr8v////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////4Ofv/5+1zv+A +nb7/c5O3/1l/qv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWk/012o/9NdqT/b5C1/7LE2P/U3un/3ubu//X3+v////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////+fr8/+rv9P/k6vH/wM/f/3mXuv9V +fKj/U3qm/053pf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/XYOt/4Kevv+TrMj/rcDV/+Ho8P/7/P3//Pz9//7///////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////6u/0/77N3v+pvdP/k6zI/2eKsf9Reab/UHil/012pP9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/X4St/4ijwf+b +ssv/tMXY/+bs8v////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////4Ofv/6C2z/+B +nr//dJS4/1l/qv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/UXmm/12DrP9ih6//gZ+//7/O3v/e5u7/5uzy//f5+/////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////3+/v/9/v7/7/P3/9Pd6P/F0uH/qLzS/3CQtf9T +eqb/Unml/012pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/U3un/2OGrv9qjLL/jKbE/9Db5v/y9fj/9ff5//z9/v////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////9vj7/+Tq8P/b4+z/vMvb/3uZvP9cgav/WH6p/1B4pf9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Yoav/4+pxf+l +utH/u8vc/+nu9P////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////4ejv/6K3z/+E +oL//dpW4/1qAqv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/XIGs/36bvP+Op8X/qLvS/9vj7f/19/r/9/n7//39/v////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////v7///r7/P/5+vz/3eTt/6S50f+Io8L/eZi7/12CrP9O +d6X/Tnek/0x1pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/Tnel/1N6pv9VfKj/e5m7/8jV4//u8vb/8vX4//v8/f////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////P3+//b4+v/z9vn/z9rm/4Whwf9hha7/XIGr/1F5pv9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Y4ev/5Osx/+q +vtP/v87e/+rv9P////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////4ejw/6O4z/+F +ocD/d5a4/1qAq/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/YYav/46nxP+juND/usnb/+ju9P////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////f7+//n6/P/3+fv/1N3p/4ynxP9pi7L/Yoau/1N6p/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/TXak/1J5pf9Teqb/dJS3/7bH2f/X4er/4ejv//X4+v////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////f7+//j5+//2+Pr/2ODr/5yzzP9+m73/cpK3/1l/qv9N +dqT/TXaj/0x1pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ZYmw/5mwyv+y +xNf/xdLh/+zx9f////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////4unw/6e70f+K +pcL/e5m6/1uBq/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/T3el/1V8qP9Y +fqr/cJG2/6K2z/+6ydv/y9bk/+7y9v////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////9/n7/+Tq8f/c5O3/vczd/4Gevv9ihq7/XYKr/1F5pv9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWk/053pP9Od6X/ZYmw/5OryP+qvdP/v83e/+rv9P////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////z9/v/8/f7/6/D1/8rW5P+5ydv/nrTO/2qMsf9P +d6T/T3ej/0x1pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/aYuy/6O4z//A +zt7/z9rm//Dz9/////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////5evy/67B1v+U +rcn/gp+//16Drf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/VXyo/2mLsf9y +krb/j6nF/8nV5P/m7PP/7PD2//n7/P////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////6e70/7vL3P+lutH/kKrG/2iKsv9Teqf/Unmm/012pP9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/XYOs/4Kevv+TrMf/rsDV/+Tr8f////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////9ff6/+Dn7//W3+r/tcbZ/3GRtv9Q +eKX/T3ek/012pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/a42z/6m80v/I +1OL/1d7p//L1+P////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////5uzy/7LE2P+Z +scv/hqLB/1+Erf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WH6q/3KStv9+ +m73/nrTN/93k7v/9/f7//f3+//////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////4unw/6e70v+KpcP/e5m7/1uBq/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/WX+q/3WUuP+Cnr//oLXO/9ri7P/4+fv/+fr8//7+/v////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////+Pn7/+ft8//g5+//wtHg/4iiwv9q +jLP/Y4av/1N7p/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWk/012o/9NdqT/bpC1/7LC1//T +3Oj/3uTt//T3+v////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////6e70/7vL3P+l +utH/j6nF/2KGr/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Tnel/1J6pv9UfKj/Zomx/4qlw/+c +ssz/tMXY/+Xq8v/9/f7//f3+//////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////+/z9//H0+P/t8fb/0Nvn/5ivyv97mbv/cJC1/1d+qf9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/UHmm/1uBqf9fhaz/gZ+9/8XT4v/n7fP/7fH2//n7/P////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////f3+//f5+//19/r/4Ofv/7bH2f+h +t87/jKfD/2KGrv9NdqP/TXaj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXak/093pP9QeKX/dpW5/8PS4P/p +7/T/7vP2//r7/f////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////7/P3/8zY5f+8 +zN3/oLbO/2iLsv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Unqn/2CErP9mibD/gp6+/7rK3P/W +3+r/4Ofv//X3+v////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////8fX4/9Xe6f/H1OL/rL/U/3aWuf9bgav/WH6p/094pf9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWk/053o/9Od6T/c5O2/7vL3f/g5+//5+3z//j5+/////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////7vL3/8zY5P+7 +y9z/oLbN/2iLsv9NdqP/TXaj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXak/1B4pf9Reab/epi7/8vY5f/0 +9/r/9vn7//39/v////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////8vX4/9Xf6v/I +1eP/qb3T/2uNs/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/VHyo/2aJsP9ukLX/kKnG/9Ld6P/0 +9vn/9vj6//39/v////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////7fH2/8fU4v+1xtn/m7LL/2aJsf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/053o/9Od6P/bI+z/6m80//H1OP/1d7q//H1+P////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////8/b5/9jh6v/M +2OT/s8TX/4Gfv/9oi7L/YYav/1R7p/9NdqT/TXaj/0x1pP9MdaP/UXmm/12CrP9ihq//h6PC/9Hc6P/2 ++fv/+Pr8//3+/v////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////9ff6/9/n7//V +3+r/tcfa/3eWuf9Xfqn/VXyn/053pf9MdaP/T3el/1Z9qP9Zf6r/Zoqx/4Kev/+Pqcb/qb3T/9zk7f/2 ++Pr/+Pn7//3+/v////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////f7+//j5/P/2+Pv/4Obw/7LF1/+cs8z/iKTB/2CErv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWk/0x1o/9MdaT/XoOt/4WgwP+Xrsn/scLW/+Xr8v////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////+/z9//L1+f/u +8vf/2+Pt/7PF1/+gts3/jKfD/2aIsP9Seab/UXil/012pP9MdaP/WoCr/3aVuP+EoMD/orfP/9/m7//9 +/f7//f3+//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////P39//T2+f/x +9Pf/0dvn/5Cpxf9wkLX/Z4mw/1V8qP9MdaP/VXyo/2qMsv9zk7f/i6bD/7nJ2//R3Of/3OTt//L1+P/9 +/f7//f3+//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////+fr8/+rv9P/k6vH/xdPh/4mjw/9qjLP/Y4av/1N7p/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WH6q/3KStv9+m73/nrTN/9/m7/////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////7vL3/8zY5P+7y9z/obfO/22Otf9Teqf/Unmm/012pP9MdaP/XoOt/4Ofv/+Vrcn/r8HW/+Xr8v// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////7+/v/+ +/v7/3eXt/5yyzP97mbv/cJC1/1d+qf9MdaP/WX+q/3OTt/+Anb7/nLPN/9bf6f/y9fj/9ff5//z9/v// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////9vj7/+Tq8f/b4+3/ucnb/3OTuP9Reab/UHil/012pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/VXyo/2mLsf9ykrb/kqvH/9Hb5//x9Pj/9Pb5//z9/v// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////8/b5/9jh6v/M2OT/t8jZ/5Coxv97mLv/eJa6/3GStv9ukLX/fpy9/5+1zv+vwdb/w9Dg/+vw9f// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////7+/v/+ +/v7/5evy/7TF2P+bssz/karG/3ybvf9yk7f/fZy9/5SsyP+ftc7/tcXZ/9/n7v/19/n/9/n6//39/v// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////7+///+ +/v//7vL3/8/a5v+/zt7/pLnQ/2uNs/9QeKX/T3ek/012pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/T3el/1V8qP9Yfqr/dpa6/7XG2P/T3uj/3ubt//T3+v// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////+/z9//L1+f/u8vf/5evy/9Hc6P/I1eP/w9Hh/7nJ2v+0xdj/v87e/9jg6//j6fH/6u70//j6/P// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////9vj7/+Tq8P/b4+z/1d7o/8bT4v/Azt7/yNTi/9jh6//g5+//5+3z//X3+v/8/f7//P3+//////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////v////v8/P/6 +/Pz/3eXt/6K40P+FocH/d5a6/1uBq/9NdqT/TXaj/0x1pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/aoyz/6e80f/F0+H/097o//H0+P// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////Pz9//P2+f/w8/f/6e/0/93k7f/W4Or/4Ofv//P2+f/9/f7//f3+//////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////v////z8/f/7/P3/9vj6/+vw9f/m7PL/7PD1//n7/P////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////v7///r8/P/5 ++/z/1eDp/42nxf9pjLL/Yoau/1N7p/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/ZIiw/5Stx/+swNT/wM/e/+nv9P/9 +/v7//f7+//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////P3+//b4+v/z9vn/7/L2/+Tr8v/g5+//5+3z//b3+v/9/f7//f3+//////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////z8/f/8/P3/+Pn7//Dz9//s8PX/8PP3//v8/f////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////+Pr8/+rw9P/j +6/H/w9Lg/4Kfv/9ihq7/XYKr/1F5pv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/V32p/26PtP95l7r/mbDK/9jg6//4 ++fv/+fr8//7+/v////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////z8/f/8/P3/+vv8//f5+//1+Pr/9/n7//3+/v////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////7////+/////f7///r8/P/5+/z/+vz8//7+//////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////7vL2/8rV5P+5 +yNv/n7TO/22PtP9Te6f/Unqm/012pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/UXmm/1uAqv9ghK3/haHA/9Hb5//2 ++Pr/+Pn7//3+/v////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////6O70/7rJ2/+j +uND/jqfE/2GGr/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/T3il/1h9qP9bgKr/fJm7/77N3v/f +5u//5+zz//f5+/////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////7+///+/v//4+nx/6y+1P+R +qcb/gJy9/12CrP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXak/1B4pf9Reab/aYyy/5qxy/+y +xNf/xdLh/+zx9f////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////v7///v8/P/6+/z/1uDp/4+pxv9r +jrP/ZIiv/1N7p/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/X4Su/4ijwf+b +ssz/tMXY/+bs8/////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////v7///r7/P/5+vz/0dvn/4Gevv9Z +f6n/Vn2n/093pf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/W4Cr/3mYuv+I +o8L/o7nQ/9zj7f/3+fv/+fr8//3+/v////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////P39//P2+f/w9Pf/ydbj/32bu/9W +faf/VHum/053pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/UXmm/1yCq/9h +hq7/gp+//8TR4P/l6vH/6+/0//n6/P////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////9/n7/+fs8//f5u//u8rd/3OTt/9P +d6X/T3ek/0x1pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWk/053o/9O +d6T/cpO2/7nI2//d5O3/5erx//f5+/////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////9fj6/+Dn7//W4Or/tMXY/26Qtf9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/053o/9O +d6P/a42y/6W50P/Cz9//0dvn//Dz9/////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////9/n7/+Xr8v/d5e7/ucnc/3KStv9O +dqT/Tnaj/0x1pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWk/0x1o/9M +daT/XIGs/32bvP+Np8T/qb3S/+Hn8P/9/f7//f3+//////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////+vv9/+/z9v/q7/T/xNLg/3iXuv9S +eqb/UXml/012pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/VXyo/2mLsf9ykrb/la3I/9ri7P/9/f7//f3+//////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////P3+//T2+f/x9Pj/ytXj/3uavP9U +e6f/Unqm/052pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/U3qn/2KGrf9pi7H/iaTB/8jU4//o7fP/7fH2//r7/P////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////f3+//b4+v/09vn/0dzn/4ymxP9p +jLL/Yoau/1N7p/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/Tnel/1N6pv9VfKj/b5C1/6S40f++zN7/ztjm/+/z9/////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////z9/v/8/f7/4unx/6/B1f+V +rcj/g5++/16Drf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/Y4ev/5Kqx/+pvNP/vsze/+rv9P////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////6u/0/7/O3v+q +vtP/k6zH/2OHr/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/XYKt/4Gdvv+Sqsj/q77U/93k7v/2+Pr/+Pn7//3+/v////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////7/P3/8/a5v+/ +zt7/p7vS/3WVuP9dgqz/WX+p/1B4pv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/Unqm/2CErP9mia//haHA/8LQ3//h6PD/6O3z//j6/P////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////+vv9/+7y9v/p +7vT/z9rm/5qyzP+Anr7/c5S3/1l/qv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWk/093o/9Pd6T/cpK1/7XG2v/Y4ev/4ejw//b4+v////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////7+///+ +/v//4+nx/6zA1f+Rq8f/gJ6+/12CrP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/093o/9Pd6P/aoyy/6C2zv+7y93/zNjl/+zx9v/9/v7//f7+//////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////7+///+ +/v//6u/1/8DP3/+swNX/mLDL/3CRtv9cgaz/WH6p/1B4pv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWk/0x1o/9MdaT/WoCq/3aVuf+EoL//obbP/9zk7P/5+vz/+vv8//7+//// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////+Pr8/+jt8//h6PD/yNTj/5avyf99m7z/cZK1/1h+qv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Unqn/2GFrf9nirH/i6bD/9Pd6f/3+fv/+fr8//3+/v// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////z9/v/8/f7/4Ofw/6m90/+Np8X/fZu8/1yBrP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/UXmm/1yBqv9hha3/gJ29/8DO3//f5u//5+zz//f5+/// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////z9/v/8/f7/5+3z/77N3v+pvdP/lq/K/3KStv9fhK3/W4Cq/1B5pv9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXak/1F5pv9Seqf/aYuz/5WuyP+sv9T/wM/e/+vv9f// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////+Pr8/+nt9P/i6PH/zNbl/561zf+Io8H/eZi5/1uAq/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/XoOt/4Kevv+UrMj/rsDV/+Xr8v// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////7+///+/v//5evy/7TF2P+bssv/iKPB/1+Erf9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WX+q/3WUt/+Cnr7/nrTN/9Xe6f/x +9Pj/9Pb5//z9/v////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////7+///+/v//7PD2/8bT4f+0xdj/n7XN/3WUuf9ghK7/W4Cr/1F5pv9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/UHim/1qAqv9eg63/fJq8/7bH2v/U +3un/3ubu//X3+v////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////+fv8/+zw9f/m7PL/z9rm/6C2zf+JpMH/epi5/1uBq/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/aoyz/6i80v/G +0+L/1N7p//H0+P////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////5+zz/7XG2P+ds8z/iaTB/2CErv9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Y4ew/5Osx/+q +vtT/vs3d/+Tr8v/4+vv/+fv8//7+/v////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////7fH2/8fU4f+1xtj/oLbN/3aVuf9h +ha7/XIGr/1F5pv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/VXyo/2iKsf9x +kbb/j6jF/8rW5P/o7fP/7fH2//r7/P////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////+fv8/+3w9v/n7PP/0dro/6O5z/+N +p8T/fZu7/1yBrP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Tnel/1N6pv9V +fKj/eJe5/77N3v/h6O//6O3z//j6+/////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////6O3z/7nJ2/+i +t8//jafE/2GFrv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXak/1J5pf9T +eqb/b5C1/6a60f/C0OD/0dvn/+7y9//9/f7//f3+//////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////7vL2/8rW5P+5 +ydv/o7jQ/3aWuf9gha7/W4Gr/1F5pv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWk/053pP9O +d6X/XIKs/3iXuf+GosD/orjO/9ri7P/2+Pr/+Pn7//3+/v////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////+vv8/+3x9v/o +7fP/0Nvn/6K2zv+KpML/e5i6/1uBq/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/Unqn/2GFrf9nirH/i6XD/9Db5//09vn/9vj6//39/v////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////5+3z/7bG2f+etM3/iqTC/2CFrv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/UXmm/1yBqv9hha3/fpu8/7jI2v/V3un/3+bu//T2+v/+/v///v7///////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////v7///r7/P/5+vz/9Pf5/+vv9v/m7PP/6O30/+zx9v/u8vf/8vX5//r8/f/+ +/////v////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////7fH2/8jU4v+2xtn/obbO/3WVuf9gha7/W4Gr/1F5pv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/TXak/1F5pv9Seqf/Y4ew/4aiwf+Xr8r/sMPX/+Ho7//6/Pz/+/z8//7///////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////+vv9/+/z9v/q7/T/3eXt/8HP3/+0xdj/usrb/8bT4f/M2OT/2OHq//Dz9//8 +/P3//Pz9//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////+fv8/+3x9v/n7fP/0Nvn/6K3z/+LpcP/fJm7/1uBq/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/V32p/22PtP94l7r/mLDL/9ni6//5+/z/+vz8//7+//////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////+fr8/+rv9P/k6vH/0dzo/67A1f+bssz/o7jQ/7PF2P+7y9z/y9jk/+vv9f/7 +/P3//Pz9//7///////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////5+3z/7fH2v+ftc7/i6XD/2CFrv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/VHuo/2WJr/9tj7T/iaXC/8DP3//c5e3/5Ovx//f5+/////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////v7+//n7/P/4+vv/6u/0/87Z5f/Azt7/ssPX/5auyP+Io8H/jqjE/5qxyv+gts3/rsHU/8rV5P/Y +4Ov/4Obw//L1+P/6+/3/+/z9//7+//////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////7fH2/8nV4/+3x9r/obbP/3KTt/9cgqz/WH+p/1B4pv9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/Tnel/1V7p/9Xfan/aoyz/5Cpxv+juND/usnb/+bs8//9/f7//f3+//////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////+vv8/+3x9v/o7fP/zNfl/5StyP94l7r/cZO3/2aIsf9fhK7/YYWv/2WJsf9nirL/cpK3/4ajwf+R +q8b/qb3S/9jh6//w8/f/8/b5//z8/f////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////+fv8/+zw9v/m7PP/zNfm/5iwyf9+m7z/cpK1/1h+qv9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WoCr/3iXuf+GosH/pLnQ/9/m7//9/f7//f3+//////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////+Pr7/+jt8//h6O//vszd/3eXuf9Ue6f/Unqm/052pP9MdaP/THWj/0x1o/9MdaP/VHuo/2aJr/9u +j7T/jafE/8zX5f/r7/X/8PP3//r7/f////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////7+///+/v//4unx/6q90/+OqMX/fpu8/1yCrP9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/V32p/22Ps/94l7n/kqvG/8bT4v/g5+//5+3z//f5+//+ +/////v////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////8vX4/9Xf6v/I1eP/qr7U/3CRtf9Seqb/UXml/012pP9MdaP/THWj/0x1o/9MdaP/Unqm/2CErP9m +ia//f5y8/7DC1v/J1eP/1d7q/+7y9f/6+/z/+/z8//7+//////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////7+///+/v//6e70/77N3v+pvdP/lK3J/2yNs/9Xfan/VXun/053pf9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/T3il/1d9qf9agKv/bY61/5Ksx/+lutH/u8vc/+br8v/8 +/P3//Pz9//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////5uzy/7HD1v+YsMn/haHA/2GFrf9OdqT/Tnaj/0x1pP9MdaP/THWj/0x1o/9MdaP/Tnal/1J6pv9U +e6j/X4St/3eWuv+Cn7//nbTN/9Lc6P/t8fb/8fT4//v8/f////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////+Pn7/+ft8//g5+//w9Hg/4qlwv9tj7P/ZYmv/1R7p/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/W4Gr/3mYuv+IpML/pLrR/9/m7v/7 +/P3//Pz9//7///////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////3+fv/5+1zf9/nb3/c5O2/1h/qv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/UXmm/1uBqv9gha3/gp++/8bT4v/o7fP/7fH2//r7/P////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////v////z8/f/7/P3/2+Ls/5ixyv94l7n/bY+z/1d9qf9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/V36p/26PtP95mLr/kqvH/8TR4P/d +5O3/5erx//X4+v/9/v7//f7+//////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////z8/f/8 +/P3/2eHr/5auyP9zk7b/aoyx/1V8qP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/T3il/1h+qP9bgar/dpW4/6y/1P/H0+L/1N7p/+7x9v/7/P3//Pz9//7///////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////z8/f/8/P3/4ujw/7DD1v+Wr8n/haLA/2KGr/9R +eab/UHil/012pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/T3il/1h+qf9bgav/bY60/46qxv+g +t8//tsjb/+Pp8P/5+vz/+vv8//7+//////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////P3+//b4+v/z +9vn/zdnl/3+dvv9ZgKr/Vn2o/094pf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/TXak/1B4pf9Reab/XoOt/3iXuf+FocD/oLbO/9fg6v/y9fj/9ff5//z9/v////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////7////+////9Pf6/97l7f/U3ej/tsbZ/3uZu/9d +gqz/WX+p/1B4pv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WX+q/3WVuP+C +n7//n7bO/9ri7P/3+fv/+fr8//3+/v////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////Pz9//P2+f/w +8/f/x9Ti/3aVuP9NdqP/TXaj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/Unmm/16DrP9kh6//hqLB/8zX5P/u8vb/8vX4//v8/f////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////P3+//X3+f/y9fj/ztnl/4aiwv9i +hq//XYKs/1F5pv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Vn2p/2uNsv91 +lbj/jafE/73M3v/V3ur/3uXv//H0+P/6+/3/+/z9//7+//////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////+fv8/+zw9f/m +7PL/wM7e/3OUt/9NdqP/TXaj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/UHim/1qAqf9eg6z/epm6/7LE2P/O2ub/2uPs//H1+P/9/v7//f7+//////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////f3+//f5+v/19/n/1+Hq/52yzf9/ +nL7/c5K4/1h/qv9MdaT/THWj/0x1pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/T3el/1Z9qP9Z +f6r/Z4qx/4Kfvv+QqsX/p7zR/9jh6v/v8/b/8/b4//v8/f////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////9Pb5/93k7f/S +2+f/scLW/22OtP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/TXak/1F4pf9Seab/YYWu/4Ccvf+PqMX/qrzS/93l7v/4+fv/+fr8//7+/v// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////z9/v/8/f7/6vD1/8nV4/+3 +yNr/nbPM/2eLsf9NdqP/TXaj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/VHuo/2aJr/9uj7T/jafE/8vX5P/q7/T/7/P2//r7/f////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////8vX4/9Xe6f/I +1OL/qbzS/2uNs/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/VHyo/2eJsP9vkLX/karG/9Te6f/2+Pr/+Pn7//3+/v// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////9ff6/97m7v/U +3un/ssTY/2+Qtf9NdqT/TXaj/0x1pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/Unqm/2CErP9mia//fpu8/67B1P/G0+H/0dzn/+br8v/x9Pj/9Pb5//z9/v////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////8PP3/9Hb5//C +z9//pbnQ/2mLsv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Unqn/2GErP9nibD/hJ++/7vK3f/Y4Ov/4efw//X4+v/+ +/////v////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////9/n7/+bs8v/e +5u7/v87e/3+dvf9gha3/W4Gq/1F5pv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/Tnal/1J6pv9Ue6j/XoOt/3KSt/98mrz/kavH/73M3f/S3ej/3eXt//T3+v////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////7fH2/8jV4v+2 +x9n/nLPL/2aJsf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Tnel/1J6pv9UfKj/Zomx/4qmw/+cs8z/tMbY/+Tp8f/8 +/P3//Pz9//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////f3+//f5+//1 +9/r/2eLs/6K30P+GosL/eJe6/1qAq/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/T3el/1V8p/9Yfqn/c5O3/6m90//E0uH/0t3o//H0+P////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////7PD1/8PR4P+w +wtb/l6/J/2WIsP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WH6q/3KStv9+m73/nbTN/9zj7f/7 +/P3//Pz9//7///////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////5uzy/7LE2P+Zscv/hqLB/1+Erf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/Tnek/1N6pv9VfKf/aYuy/5Ksxv+mu9H/tcfZ/9Pd6P/i6fD/6O7z//X3+v/7 +/P3//Pz9//7///////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////6u/0/7/O3v+q +vtP/k6zH/2OHr/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/VXyo/2mLsf9ykrb/jqfE/8TR4f/g +5u//5+zz//j5+/////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////6/D1/8LQ4P+uwdb/lq7K/2WJsP9NdqT/TXaj/0x1pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWk/093pP9Pd6X/Vn2o/2OGsP9qjLP/eZe7/5evyf+mutH/ucnb/97l7v/x +9Pj/9Pb5//z9/v////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////6O3z/7fH2v+g +tc7/i6XD/2GFrv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/T3el/1V8qP9Yfqr/bI60/5Styf+o +vdP/vc3e/+nu8//+/v7//v7+//////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////9vj6/+Lp8P/Z4uv/tsfa/3KStf9Pd6T/T3ej/0x1pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/W4Gr/3qYuv+JpML/orfP/9Te6f/t +8fb/8fT4//v8/f////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////5uzy/7PE2P+a +scv/h6LB/1+Erf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/XIGs/32bvP+N +p8X/qb3T/+Lo8P/+/v7//v7+//////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////+/z9//L1+P/u8vb/x9Ti/3eWuf9QeKX/T3ek/012pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/V36p/2+PtP96mLr/jafE/7TF2f/H +1OP/0Nrn/9/n7//o7fP/7fH2//f4+v/8/P3//Pz9//////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////5uvy/7HD1/+Y +r8r/haHA/1+Drf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/WH6q/3GStf99 +m7z/lq/J/8rV4//j6fD/6u7z//j6/P////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////P3+//X3+f/y9fj/zNjk/4Gdv/9bgKv/WH2p/094pf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/T3il/1h+qf9bgav/Y4ev/3OTt/97 +mbv/iqTD/6i80v+3x9r/xtPi/+Tq8f/z9vn/9vj6//z9/v////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////5Ovx/67A1f+T +rMf/gp6+/12DrP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/UHim/1h+qf9c +gaz/cJG2/5iwy/+swNX/wM/f/+rv9P/+/v7//v7+//////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////v////z8/f/7/P3/2eHr/5SuyP9yk7b/aYyx/1V8qP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Tnel/1N6pv9V +fKj/aIqx/42nxf+gtc7/tMTY/9zk7f/w8/f/8/b5//z8/f////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////5Orx/6y/1P+R +qsb/gJ29/12CrP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/XYKs/4Cevv+Rq8f/rMDV/+Pp8P/+/v7//v7+//////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////3+bv/520zP99m7z/cZK1/1h+qv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXak/1J5pf9T +eqb/YYWt/32avP+LpcP/m7HL/7jJ2//I1eP/z9vn/9/m7v/m7PL/6+/1//P3+f/4+vz/+fv8//7+//// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////4unw/6i80v+L +psP/fJq7/1uBq/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/WX+q/3OUt/+Anr7/l7DK/8jU4v/f5u7/5+zy//X4+v/9/v7//f7+//////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////3ubu/5yzzP97mrv/cJG1/1d+qf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWk/053pP9O +d6X/U3un/1yBrP9hha7/Z4qx/3WTuP97mLv/iaPD/6W50P+zxNj/wc7e/9vj7f/p7fP/7vH2//r7/P// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////4Ofv/5+0zf+A +nL3/c5K2/1l/qv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/UHim/1l/qf9dgqz/bo+0/5Gqx/+it8//uMja/+Lo8P/4+fv/+fr8//7+/v////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////3uXu/5mwy/94lrr/bY60/1d9qf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/TXak/1J5pv9Teqf/ZYiw/4ijwv+ascv/rL/V/9Da5v/i6PD/6e3z//j6/P// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////3ubu/5uxy/96 +mLr/b4+0/1d+qf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/WX+q/3aVuP+Dn7//oLbO/9nh6//2+Pr/+Pn7//3+/v////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////3eXu/5ivyv92lbn/bI2z/1Z9qf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/TXak/1F4pf9Seab/X4Ot/3qYuv+HosH/lazI/6/B1f+9y9z/xNHg/9Td6f/b +4+3/3+bv/+bs8v/q7/T/7fL1//P1+f/2+Pr/+Pn7//z+/v/+/////v////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////P3+//b4+v/z9vn/0tzo/5Cpxv9v +j7X/Z4mw/1R7qP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/Vn2p/2yNsv92lbj/jabD/7rK3P/R2+f/2OHr/+jt8//v8/f/8/b5//v8/f// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////+/z9//Dz9//s8PX/zNfk/4ymxP9sjbP/ZIev/1R7p/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWk/012o/9NdqT/UXqm/1uAq/9fhK3/ZIev/2yPtf9xkrf/eZi7/4ikwv+Q +qsb/m7PM/7PE2P++zd7/x9Tj/9jh6//h6PD/6O3z//X3+v/8/P3//Pz9//////////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////9vj7/+Tq8P/b4+z/u8vb/3mYuv9Z +gKn/Vn2n/094pf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/T3el/1Z9qP9Zf6r/ZIiw/3yavP+Ho8L/mbHL/73M3f/P2ub/2+Ps//L1+P/+ +/v7//v7+//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////7+/v/+ +/v7/8PP3/9Ld6P/E0uH/qL3T/3KSt/9Wfan/VHun/053pf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/VHuo/2SIr/9s +jrT/e5q7/5qxzP+pvdP/tcbZ/8zY5f/Y4ev/4Ojw//P1+P/7/P3//Pz9//7///////////////////// +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////8/b5/9vj7P/P2ub/r8HW/26QtP9O +d6T/Tnej/0x1pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/UXmm/12CrP9ihq//eZi7/6i80v+/zt7/z9rm/+7y9v/+ +/v7//v7+//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////7+/v/+ +/v7/6+/0/8TS4f+xw9f/mLDK/2WIsP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Unqm/16DrP9k +iK//b5C1/4ejwf+Sq8f/m7LM/6y/1P+1xtn/vMvc/8rW5P/R2+f/1N7p/9nh6//c5O3/3eXu/+Ho7//i +6fD/4+rw/+bs8//n7fP/6O3z/+ft8//o7fP/6O30/+rv9P/q7/X/6u/1/+rv9f/q7/X/6u/1/+rv9f/q +7/X/6u/1/+rv9f/q7/X/6u/1/+nu9P/p7vT/5uzy/+Lp8f/f5+//097p/7vK3P+vwdb/l67J/2aKsP9O +d6P/Tnej/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/UHim/1l/qf9dgqz/bpC0/5Kqx/+juM//scLW/8rX5P/Y +4ev/2+Tt/+Tp8f/n7PP/6e30/+3y9v/v8/f/8PT4/+/z9//w9Pj/7/P3//D0+P/v8/f/7/P3/+3y9v/t +8vb/7fL2/+3y9v/t8vb/7fL2/+3y9v/t8vb/7fL2/+3y9v/t8vb/7fL2/+zw9f/s8PX/6+/1/+vw9f/q +7/X/6u/1/+rv9f/q7/X/6u/1/+rv9f/q7/X/6u/1/+rv9P/q7/T/6e7z/+bs8//l6/L/4enw/9vi7P/X +4Or/yNTi/6e80v+YsMr/haHA/1+Erf9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Tnal/1J6pv9U +e6j/V36p/2CErv9jh6//Zoqx/2yNs/9vkLX/c5K3/3iYuv98mrz/gp+//4+oxf+Vrcj/mbDK/6O40P+n +u9L/q77U/7HD1/+1xtn/tcfZ/7fH2v+3yNr/usnb/73O3f/Az97/wM/e/8DP3v/Az97/wM/e/8DP3v/A +z97/wM/e/8DP3v/Az97/v8/e/77M3f+9zN3/tcbZ/6e70f+ftc3/k6zH/3qZuv9ukLT/Zomw/1R8qP9M +daT/THWj/0x1pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXak/1B4pf9Reab/V32p/2KHr/9oi7L/cZK3/4Sgv/+N +p8T/l6/J/6u+0/+1xtj/u8vb/8nV4//P2ub/0Nvm/9Hb5//S3Of/0dvn/9Db5v/P2ub/ztnl/8rW5P/J +1eP/ydXj/8nV4//J1eP/ydXj/8nV4//J1eP/ydXj/8nV4//J1eP/x9Tj/8XS4f/D0eH/w9Dg/8DQ3//A +z97/wM/e/8DP3v/Az97/wM/e/8DP3v/Az97/v8/e/8DO3v+/zt7/u8vc/7LD1/+uwNX/pbnQ/5OsyP+K +pcP/gZ6//26PtP9liLD/X4Os/1J6p/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXak/1F4pf9Seab/Wn+q/2qMsv9ykrb/eJe5/4SgwP+K +pcP/j6nG/5iwyv+dtM3/nrTN/5+2zv+gts7/orjQ/6m90v+rv9T/q7/U/6u/1P+rv9T/q7/U/6u/1P+r +v9T/q7/U/6u/1P+rv9T/qr7T/6i80/+nu9L/nbTN/4mkwv9/nb3/c5O3/1qBqv9Od6T/Tnej/0x1pP9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Unqn/2GFrf9n +irH/dZS4/4+pxf+ds8z/pbrQ/7fH2v+/zt7/wM/f/8LQ3//D0eD/wtDf/8DP3/+/zt7/vc3d/7nJ2/+3 +yNr/t8ja/7fI2v+3yNr/t8ja/7fI2v+3yNr/t8ja/7fI2v+3yNr/tsbZ/7HE2P+wwtf/rsHX/63A1P+r +v9T/q7/U/6u/1P+rv9T/q7/U/6u/1P+rv9T/q7/T/6q+1P+qvtP/pbrR/5mwyv+UrMj/h6LB/3CRtv9j +h6//XoOs/1F5pv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXak/1B3pP9ReKX/V3yo/2OHrv9pi7H/bY+z/3eVuf97 +mbv/f5y9/4WhwP+JpML/iaTC/4umw/+LpsP/jqjE/5Grxv+Urcf/lK3H/5Stx/+Urcf/lK3H/5Stx/+U +rcf/lK3H/5Stx/+Urcf/k6zH/5Krxv+Rqsb/iaTC/3uZuv9zk7b/aoyx/1d+qP9Od6P/Tnej/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/UXmm/1yBqv9h +ha3/a42y/3+cvP+JpMH/j6nE/52zzP+juM//pLjQ/6W6z/+mutD/pbrP/6S40P+juM//orbP/561zP+d +s8z/nbPM/52zzP+ds8z/nbPM/52zzP+ds8z/nbPM/52zzP+ds8z/m7LL/5mwy/+Xr8r/l6/J/5StyP+U +rcf/lK3H/5Stx/+Urcf/lK3H/5Stx/+Urcf/k63H/5Ssx/+TrMf/j6jF/4aiwP+Cnr7/eZe6/2eKsP9e +g6z/WoCp/1B4pv9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWk/012o/9NdqT/T3il/1N6p/9VfKj/Vn2o/1qAq/9b +gav/XYKs/16Erf9gha7/YIWu/2GFrv9hha7/YYWv/2OHr/9jh7D/Y4ew/2OHsP9jh7D/Y4ew/2OHsP9j +h7D/Y4ew/2OHsP9jh7D/Y4ev/2KGsP9ihq//YISu/1qBq/9Yf6r/VX2p/093pf9MdaT/THWj/0x1pP9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/TXak/1F5pv9S +eqf/Vn2p/1yBrP9ghK7/YoWv/2aKsf9oi7L/aIyy/2mLs/9pjLP/aYuz/2iMsv9oi7L/Z4uy/2eKsf9m +irH/Zoqx/2aKsf9mirH/Zoqx/2aKsf9mirH/Zoqx/2aKsf9mirH/Zoqx/2WIsP9liLD/ZIew/2SIsP9j +h7D/Y4ew/2OHsP9jh7D/Y4ew/2OHsP9jh7D/Y4ev/2OHsP9jh6//Yoav/1+Erf9eg63/W4Gr/1R7qP9R +eab/UHil/012pP9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKEA8WHzATHSlAIjNHbz1fhdBMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+F0CIzR28T +HSlADxYfMAQHChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAcKDw8VHi8THCg+IjJGbj1fhc9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV+EzyIyRm0T +HCc9DxUeLgQHCQ8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAYJDg0VHC0RGyU7IDJEbD1ehM5MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV6Ezh8xRGsQ +GiU6DBQcLAQGCQ4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAYJDg0UGysRGiQ5IDFDaj1ehM5MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PV6DzR8wQ2kQ +GSM3DBMbKgQGCA0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAABAYIDQwTGSgQGSE1HzBBZz1eg81MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PF2DzR8vQWYP +FyE0DBIZJwMFCA0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAwUHCwsQFyQOFR4vHi0/YzxdgstMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PF2Cyx0tPWAN +FRwsChAVIQMFBwsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAwUGCgoPFSENFBsrHSw9YDxdgcpMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PFyByhwsPF4M +ExopCQ8UHwMEBgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAgQFCAgMERsKEBYjGyk5WjtcgMhMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/S3Wj/0x1o/9LdaP/O1yAyBkpOFgJ +EBUhBwwQGQIEBQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAQIDBAUHCQ8GCQwTGCQxTjpafcNMdaL+THWi/kx1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x0ov1MdKL9Oll8whgjMU0G +CAsSBQYJDgECAgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAEBAgMEBgkDBQcLFiEuSDlZe8FMdaL+THWi/kx1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/S3Wj/0x0ov1LdKL9OVh7wBUhLUcD +BQYKAwQFCAABAQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAEBAgMDBQcDBAYJFR8sRDdWd7tJcZ32SnKe+Et0ov1MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/S3Si/UpxnfZJcJz0N1V2uRUfK0MD +BAUIAwMEBgABAQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAABAQIAAQECERslOzRPbqtFaZLkR2yW6kpyn/lMdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/SXKf+EZrlepDaJHjM05tqxAbJToA +AQECAAEBAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBkjNzJMaqVCZY3cRWmS5ElxnvdMdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/SXGd9kRokeNBZIvaMUtppBAZIjYA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADxcgMy5HY5k9XoPMQWSL2Ehvm/NMdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/SG+b8kBiidc8XIHKLUVhmA8XIDIA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADRMbKyc8VIM0T2+uOll8wkZrlutMdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RWuW6jpZeb8zT2yqJzxRgAwTGyoA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACxIZJyQ2THcvSGWeN1N0tkRqlOdMdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RGmT5jZTcrMuR2KaIzZKdAsRGCYA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACg8WIiAwQmkqP1iLM01qqENnkeJMdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/Q2eQ4TFMaaUoPlaHHi9BZgoPFSEA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwsQGBYjMEsdLkBjKUBYikBji9hMdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/QGOK2Ck+VocdLD1gFiEuSAcLDxgA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgkMFBIcJzwYJTNQJTlPez9hh9RMdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/PmCH0yU4TXkXIzFNEhslOgUIDBMA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAcKDw4VHi4SHCg9IDFGbDxdgstKcqD6S3Og+0t0o/5M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/S3Sj/ktzoPtKcqD6PFyByiAxRGsSGyU7DhUcLQQGCQ4A +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgMDBgYJDBMIDA8ZGCQyTjhWdrpIbpnvSXCb80tzoftM +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/SnOh+0lwmvJHbpjuN1V1uBYjMk0GCg8XBQgMEgECAwUA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQIDAwUCAwQGEx0oPzVRcrFGa5bqSG6Z70pyoPpM +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/SnKg+kdumO5Fa5XpNFFwsBIcKD4BAgMFAQIDBAAAAAEA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQIDAwQCAwMFERokNy9IY50+X4TPQmWL20hvnPNM +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/SG+b80Fki9k9XoPNLkdjmxAZIzYBAgMEAQIDAwAAAAEA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEAAAEBCxEZJiI0SXItRWGXNVFxsURpk+VM +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RGmS5TRQcK4sRF+UITNIbwsRFyYAAAABAAAAAQAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQ4THxwrPF0lOU98L0hknEJmjt9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/QmWO3i5HYpkkN014Gyo6WgkNEx4AAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwsPGBYhL0gdLD5gKD1Xhj9hh9NK +cqD5S3Og+kt0o/5MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/S3Si/ktzoPpKcp/5P2CH0ic9VIQcKzxdFSEtRgcKDxcAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwQGCgkPFCAMExoqGio5WzlWeLxH +bZftSW+a8UpzoPtMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/SnOg+khume9GbJbqOFV3uhopOFgMEhkoCQ4THgMEBgoAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQECAwMFBwsEBgkOFB8sRDVRcbFF +apTnR22X7UpyoPlMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/SnKf+UZslupEaZLkNFBwrhQfKkMEBggNAwUGCgEBAgMAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAgMEBgkDBQcLERolOixFX5U6 +Wn3EP2GG0kdumvFMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/R26Z8D5fhc85WHvAK0NekhEaIzgDBQYKAwQFCAABAQIAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAgMBAQIDCg8VIRwqPF0l +OE97L0dknEJmjt5MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/QmaO3i5HYpkkOE14Gyo6WgoPFSEBAQIDAQECAwAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgoOFhQeKkIa +KDhYJztSgT9iidZMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/P2GI1SY7UX4ZJzZUEx4pPwYJDRUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQcKEA8XIDIU +HipCIDFFazhXeb5EapTnRm2X7UpyoPlMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/SnKf+UZsl+xEaZPmN1Z5vCAxQ2kTHik/DxcfMAQHCg8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQIDBQUICxEG +Cg4WEh0oPitBW483VHW3PVyAyUZtmO1MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/RmyX7DxcfsY2U3KzKkFZjBIbJjwGCQ0VBQcKEAECAwUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAACxIZJyQ3TXgvSWafN1R1t0RqlOdMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/RGmT5jZTcrMuR2KaIzZKdAsRGCYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAACQ0THhsqO1skN055LEVhlj5fhdFGbZjuSG+a8kpzoftMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/SnOh+0lwm/NH +bpnvPmCF0SxEX5MjNkt1Gyk5WAgNEh0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAwUHCwoQFSENFRwsGSc1UzFLaaQ9XYLLQWOK2Ehvm/JMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/SG+c80Jli9s+ +X4TPMk1ppxkmNlQNFBssCg8VIQMFBgsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAABAQIDAwUCAwQGDxggMypAW403VXe6PV2Cy0ZtmO5MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/R26Z7z5fhM85 +WHq/K0JdkRAZITUCAwQHAgMDBgAAAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAQIDAwQCAwMFDBMaJyExRW0rQVyPMkxrqEBjitdHbpnwSXCb80pzofxMdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/SnOh+0lvm/NHbZnvQWKK2DJObaos +Q16TITNHcA0TGikCAwMGAgMDBQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAQEAAAEBBAYKDwwUGysQGiQ5Gys8XjNObao+X4XPQmWM20hvnPNMdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/SG+b80Jli9o+X4POM05sqRwsPF8R +GyU6DRUcLAQGCg8AAAEBAAABAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAQECAwMFBwsEBgkOERslOyxDX5M5WHvAPl+Fz0dumfBMdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/R22Z7z5fg845V3m+LENdkhEaJToE +BgkOAwUHCwEBAgMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAEBAgMEBgkDBQcLDRQdLSE0SHArQ16SMk1spz1ghtJEapTnRm2X7UpyoPlM +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/SnKf+UZsl+xEaZPmPV+G0TJMaaUrQlyQITNGbg0UHS0D +BQcLAwQGCQABAQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAEBAgMBAQIDBQcKEAwTGikQGSI2GSc3Vi5FX5Y3U3S2PVx/yEZsmO1M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RmyX7DxcfsY2U3KzLUVelBkmNVQQGCE1DBIZKAUHChAB +AQIDAQECAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAgIDBAYCBAUIDRUdLSQ3TXkvSGWeN1N0tkRqlOdM +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/RGmT5jZTcrMuR2KaIzZLdQ0UGywCAwQHAgMDBgAAAQEA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQIDAwUCAwQGCxAWIhspOlskNkx3KkFbjzlXer0/ +YonVQmaO3kdumvBKcp/5S3Og+kt0ov5MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/S3Si/ktzoPpKcp/5R26a8EJljd4/YYjVOFZ4vSpBWowjNkp0Gyk4WAoQFSICAwMGAgMDBQAAAAEA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQIAAQECAgUHCwkOEx4LEhknEhwnPiAxRGon +O1KBL0hjnD9ghtFHbZfsSW+a8EpzoPtMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/SnOg+khume9GbJbqPl+Fzy5IYpkmO1F+HzFDaBIbJjwLERgmCQ0SHQIEBwoAAAEBAAABAQAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgoOFRQeKkIa +KDhXJThOezpafcJFapPmR22X7Epyn/lMdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/SnKf+UZslupEaZLkOVh7wCQ4TXgZJzZUEx4pPwYJDRUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQcKEA8XIDIU +HipCHCs8Xi1EX5Q1UXGwOFZ4uz9hh9NCZo7eRGmT5Uhvm/NKcqD6S3Og+0t0o/5MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/S3Sj/ktzoPtKcqD6SG+b80Rpk+VC +Zo7eP2GH0jdVd7o0UHCuK0NekhwrO1sTHik/DxcfMAQHCg8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQIDBQUICxAG +Cg4VCg8VIRAaJDgUHytEGik6Wik9VYYvR2ScNlFxsEFkjNtIbpnvSXCb80tzoftMdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/SnOh+0lwmvJHbpjuQWSK2TRRcK8u +R2KaKD1UhBopOFgUHypCEBojNwoOFCAGCQ0VBQcKEAECAwUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAQECAwMFBwsEBgkODBMaKR0rPmAlOE97LURhlz5fhM5Ga5bqSG6Z70pyoPpMdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/SnKg+kdumO5Fa5XpPV+DzSxEX5Uk +OE15HCs8XgwTGScEBggMAwUGCQEBAgMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAEBAgMEBgkDBQcLCg4VIBUhLkgcKjxdIjRJci9HZZw1UXKxN1V3uTxdgss+YYfTQGOK2EJnkOFE +aZPmRWuW6khvm/JJcZ72SnKf+Et0ov1MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/S3Wj/0x1o/9L +daP/S3Si/UlynvhJcZ32SG+a8kVrlupEaZPmQmeQ4UBiitg+YIfTPFyCyzZVdbg0UXCwLUdimiI0SHEb +KjpbFSEtRwkOEx0DBQYJAwQFBwABAQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAEBAgMBAQIDAwQGCgcLDxcJDhMeCxIZJhEZIjcTHSg/GCQxTiAxRWslOE56KT5XiTJNaqU2 +U3O0Oll7wEBiidhEaJHkRmuV6kpynvhMdaL+THWi/kx1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x0ov1M +dKL9SnGe9kZrlepEaJHjQGKJ1zpZer82U3KzMk1ppSk+VoclOE15IDFEahcjMU0SHCg+EBgiNgsSGSYJ +DhMeBwsPFwMEBgoBAQIDAQECAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQIDAwUCAwQGBwwPGBIbJzwXJDJOHSw+YSg/V4gu +R2ObM09uqzxcgctBZIzbRGiR5ElxnfVMdaL+THWi/kx1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9M +daP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/THWj/0x1o/9MdaP/S3Wj/0x0ov1L +dKL9SXCc9ENokeNBZIvaPFyByjNPbKouR2KaKD5Whx0sPWAXIzFNEhslOwYKDxcBAgMFAQIDBAAAAAEA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQIDAwQCAwMFBgkMEg4VHS4SGyY7FiIvSh8vQmYj +Nkt1JztTgS1GYZkxS2mlM09trDdVd7o5WXvBOlp9wztbf8g8XIHKPFyByz1eg8w9XoPNPV6Ezj5fhNA+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+ +X4XRPl+F0T5fhdE+X4XRPl+F0T5fhdE+X4XRPl+E0D1ehM49XoPNPV6DzDxcgcs8XIHKO1t/yDpZfcI5 +WHvAN1R3uTNPbasxS2mkLUZhmCc7UoAjNkp0Hy9BZRYiLkkSGyU6DhUcLQUIDBEBAgMEAQIDAwAAAAEA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEAAAEBAQIEBQQHCQ8FCQwTBgsPGAoPFSEL +ERgmDRMaKg4XITIQGSM2ERsmOhUeKkIWIC1GGCMxTBspOVkdLD1fHS0+Yh8vQmYfMENpIDFFayE0R3Ai +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyIjVJciI1SXIi +NUlyIjVJciI1SXIiNUlyIjVJciI1SXIiNUlyITRHcCAxRWsfMENpHy9CZh0tPmIdLD1fGyk5WRcjMUwV +IC1GFB4qQhEbJToQGSI2DhcgMg0TGioLERgmCg8VIQYKDxgFCAwTBAYJDwECAwUAAAABAAAAAQAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAEBAgMDBQcDBAYJBgcLEQoQFiINExsqDhUdLQ8XITQQGSM3ERolOxMeKUAU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEFB8rRBQfK0QU +HytEFB8rRBQfK0QUHytEFB8rRBQfK0QUHytEEx4pQBEaJTsQGSM3DxchNA4VHS0NExsqChAWIgYHCxED +BAYJAwMFBwABAQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAABAQMDBAYDAwUHBQYJDggMERkKDxUgCxAWIgsSGigMExsqDBUdLA8WHzEP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxghMw8YITMP +GCEzDxghMw8YITMPGCEzDxghMw8YITMPGCEzDxYfMQwVHSwMExsqCxIaKAsQFiIKDxUgCAwRGQUGCQ4D +AwUHAwMEBgAAAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAABAQIAAQECAAICBAMDBQgDBAYKBAQHCwMGBwwEBggNBAYIDgUHChAF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEQUHChEF +BwoRBQcKEQUHChEFBwoRBQcKEQUHChEFBwoRBQcKEAQGCA4EBggNAwYHDAQEBwsDBAYKAwMFCAACAgQA +AQECAAEBAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASAQAAAwAAAAEBaAAAAQEA +AwAAAAEBaAAAAQIAAwAAAAQAB+n2AQMAAwAAAAEAAQAAAQYAAwAAAAEAAgAAAQoAAwAAAAEAAQAAAREA +BAAAAAQAB+oOARIAAwAAAAEAAQAAARUAAwAAAAEABAAAARYAAwAAAAEAWwAAARcABAAAAAQAB+n+ARoA +BQAAAAEAB+nmARsABQAAAAEAB+nuARwAAwAAAAEAAQAAASgAAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMA +AwAAAAQAB+oeh3MABwAADEgAB+omAAAAAAAAANgAAAABAAAA2AAAAAEACAAIAAgACAAB/+AAAf/gAAH/ +4AAB6WAAAAAIAAH/6AAD/8gABf+oAAEAAQABAAEAAAxITGlubwIQAABtbnRyUkdCIFhZWiAHzgACAAkA +BgAxAABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLUhQICAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFjcHJ0AAABUAAAADNkZXNjAAAB +hAAAAGx3dHB0AAAB8AAAABRia3B0AAACBAAAABRyWFlaAAACGAAAABRnWFlaAAACLAAAABRiWFlaAAAC +QAAAABRkbW5kAAACVAAAAHBkbWRkAAACxAAAAIh2dWVkAAADTAAAAIZ2aWV3AAAD1AAAACRsdW1pAAAD ++AAAABRtZWFzAAAEDAAAACR0ZWNoAAAEMAAAAAxyVFJDAAAEPAAACAxnVFJDAAAEPAAACAxiVFJDAAAE +PAAACAx0ZXh0AAAAAENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBhY2thcmQgQ29tcGFueQAAZGVz +YwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAABJzUkdCIElFQzYxOTY2LTIuMQAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWFlaIAAAAAAAAPNRAAEA +AAABFsxYWVogAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3 +hQAAGNpYWVogAAAAAAAAJKAAAA+EAAC2z2Rlc2MAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAA +AAAAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAABkZXNjAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNvbG91 +ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNvbG91ciBz +cGFjZSAtIHNSR0IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAAAAAAAAAsUmVmZXJlbmNlIFZpZXdp +bmcgQ29uZGl0aW9uIGluIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENv +bmRpdGlvbiBpbiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHZpZXcAAAAAABOk +/gAUXy4AEM8UAAPtzAAEEwsAA1yeAAAAAVhZWiAAAAAAAEwJVgBQAAAAVx/nbWVhcwAAAAAAAAABAAAA +AAAAAAAAAAAAAAAAAAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAAAAAAAAQAAAAABQAKAA8AFAAZAB4A +IwAoAC0AMgA3ADsAQABFAEoATwBUAFkAXgBjAGgAbQByAHcAfACBAIYAiwCQAJUAmgCfAKQAqQCuALIA +twC8AMEAxgDLANAA1QDbAOAA5QDrAPAA9gD7AQEBBwENARMBGQEfASUBKwEyATgBPgFFAUwBUgFZAWAB +ZwFuAXUBfAGDAYsBkgGaAaEBqQGxAbkBwQHJAdEB2QHhAekB8gH6AgMCDAIUAh0CJgIvAjgCQQJLAlQC +XQJnAnECegKEAo4CmAKiAqwCtgLBAssC1QLgAusC9QMAAwsDFgMhAy0DOANDA08DWgNmA3IDfgOKA5YD +ogOuA7oDxwPTA+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4EjASaBKgEtgTEBNME4QTwBP4FDQUcBSsF +OgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3BkgGWQZqBnsGjAadBq8GwAbRBuMG9QcHBxkH +Kwc9B08HYQd0B4YHmQesB78H0gflB/gICwgfCDIIRghaCG4IggiWCKoIvgjSCOcI+wkQCSUJOglPCWQJ +eQmPCaQJugnPCeUJ+woRCicKPQpUCmoKgQqYCq4KxQrcCvMLCwsiCzkLUQtpC4ALmAuwC8gL4Qv5DBIM +KgxDDFwMdQyODKcMwAzZDPMNDQ0mDUANWg10DY4NqQ3DDd4N+A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUP +QQ9eD3oPlg+zD88P7BAJECYQQxBhEH4QmxC5ENcQ9RETETERTxFtEYwRqhHJEegSBxImEkUSZBKEEqMS +wxLjEwMTIxNDE2MTgxOkE8UT5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYVeBWbFb0V4BYDFiYWSRZsFo8W +shbWFvoXHRdBF2UXiReuF9IX9xgbGEAYZRiKGK8Y1Rj6GSAZRRlrGZEZtxndGgQaKhpRGncanhrFGuwb +FBs7G2MbihuyG9ocAhwqHFIcexyjHMwc9R0eHUcdcB2ZHcMd7B4WHkAeah6UHr4e6R8THz4faR+UH78f +6iAVIEEgbCCYIMQg8CEcIUghdSGhIc4h+yInIlUigiKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQkl +OCVoJZclxyX3JicmVyaHJrcm6CcYJ0kneierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqbKs8r +Ais2K2krnSvRLAUsOSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLyQvWi+RL8cv/jA1MGwwpDDbMRIx +SjGCMbox8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1TTWHNcI1/TY3NnI2rjbpNyQ3YDecN9c4 +FDhQOIw4yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7qjvoPCc8ZTykPOM9Ij1hPaE94D4gPmA+oD7gPyE/ +YT+iP+JAI0BkQKZA50EpQWpBrEHuQjBCckK1QvdDOkN9Q8BEA0RHRIpEzkUSRVVFmkXeRiJGZ0arRvBH +NUd7R8BIBUhLSJFI10kdSWNJqUnwSjdKfUrESwxLU0uaS+JMKkxyTLpNAk1KTZNN3E4lTm5Ot08AT0lP +k0/dUCdQcVC7UQZRUFGbUeZSMVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYPVlxWqVb3V0RXklfgWC9Y +fVjLWRpZaVm4WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1fD19hX7NgBWBXYKpg/GFPYaJh +9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/aJZo7GlDaZpp8WpIap9q92tPa6dr +/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CGcOBxOnGVcfByS3KmcwFzXXO4dBR0cHTMdSh1hXXhdj52 +m3b4d1Z3s3gReG54zHkqeYl553pGeqV7BHtje8J8IXyBfOF9QX2hfgF+Yn7CfyN/hH/lgEeAqIEKgWuB +zYIwgpKC9INXg7qEHYSAhOOFR4Wrhg6GcobXhzuHn4gEiGmIzokziZmJ/opkisqLMIuWi/yMY4zKjTGN +mI3/jmaOzo82j56QBpBukNaRP5GokhGSepLjk02TtpQglIqU9JVflcmWNJaflwqXdZfgmEyYuJkkmZCZ +/JpomtWbQpuvnByciZz3nWSd0p5Anq6fHZ+Ln/qgaaDYoUehtqImopajBqN2o+akVqTHpTilqaYapoum +/adup+CoUqjEqTepqaocqo+rAqt1q+msXKzQrUStuK4trqGvFq+LsACwdbDqsWCx1rJLssKzOLOutCW0 +nLUTtYq2AbZ5tvC3aLfguFm40blKucK6O7q1uy67p7whvJu9Fb2Pvgq+hL7/v3q/9cBwwOzBZ8Hjwl/C +28NYw9TEUcTOxUvFyMZGxsPHQce/yD3IvMk6ybnKOMq3yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzR +vtI/0sHTRNPG1EnUy9VO1dHWVdbY11zX4Nhk2OjZbNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A24L3h +ROHM4lPi2+Nj4+vkc+T85YTmDeaW5x/nqegy6LzpRunQ6lvq5etw6/vshu0R7ZzuKO6070DvzPBY8OXx +cvH/8ozzGfOn9DT0wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH/Jj9Kf26/kv+3P9t///SJyhDRF5O +U011dGFibGVBcnJheaNDLizVRkdISQpKS0xNTldOU1doaXRlXE5TQ29tcG9uZW50c1xOU0NvbG9yU3Bh +Y2VfEBJOU0N1c3RvbUNvbG9yU3BhY2VEMCAwAEMwIDAQA4ASgBbUUFFSClNUVVZUTlNJRFVOU0lDQ1dO +U01vZGVsEAmAExAAgBXSWApZWldOUy5kYXRhTxERaAAAEWhhcHBsAgAAAG1udHJHUkFZWFlaIAfcAAgA +FwAPAC4AD2Fjc3BBUFBMAAAAAG5vbmUAAAAAAAAAAAAAAAAAAAAAAAD21gABAAAAANMtYXBwbAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABWRlc2MAAADAAAAAeWRzY20A +AAE8AAAH6GNwcnQAAAkkAAAAI3d0cHQAAAlIAAAAFGtUUkMAAAlcAAAIDGRlc2MAAAAAAAAAH0dlbmVy +aWMgR3JheSBHYW1tYSAyLjIgUHJvZmlsZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABtbHVjAAAAAAAAAB8A +AAAMc2tTSwAAAC4AAAGEZGFESwAAADgAAAGyY2FFUwAAADgAAAHqdmlWTgAAAEAAAAIicHRCUgAAAEoA +AAJidWtVQQAAACwAAAKsZnJGVQAAAD4AAALYaHVIVQAAADQAAAMWemhUVwAAAB4AAANKbmJOTwAAADoA +AANoY3NDWgAAACgAAAOiaGVJTAAAACQAAAPKaXRJVAAAAE4AAAPucm9STwAAACoAAAQ8ZGVERQAAAE4A +AARma29LUgAAACIAAAS0c3ZTRQAAADgAAAGyemhDTgAAAB4AAATWamFKUAAAACYAAAT0ZWxHUgAAACoA +AAUacHRQTwAAAFIAAAVEbmxOTAAAAEAAAAWWZXNFUwAAAEwAAAXWdGhUSAAAADIAAAYidHJUUgAAACQA +AAZUZmlGSQAAAEYAAAZ4aHJIUgAAAD4AAAa+cGxQTAAAAEoAAAb8cnVSVQAAADoAAAdGZW5VUwAAADwA +AAeAYXJFRwAAACwAAAe8AFYBYQBlAG8AYgBlAGMAbgDhACAAcwBpAHYA4QAgAGcAYQBtAGEAIAAyACwA +MgBHAGUAbgBlAHIAaQBzAGsAIABnAHIA5QAgADIALAAyACAAZwBhAG0AbQBhAHAAcgBvAGYAaQBsAEcA +YQBtAG0AYQAgAGQAZQAgAGcAcgBpAHMAbwBzACAAZwBlAG4A6AByAGkAYwBhACAAMgAuADIAQx6lAHUA +IABoAOwAbgBoACAATQDgAHUAIAB4AOEAbQAgAEMAaAB1AG4AZwAgAEcAYQBtAG0AYQAgADIALgAyAFAA +ZQByAGYAaQBsACAARwBlAG4A6QByAGkAYwBvACAAZABhACAARwBhAG0AYQAgAGQAZQAgAEMAaQBuAHoA +YQBzACAAMgAsADIEFwQwBDMEMAQ7BEwEPQQwACAARwByAGEAeQAtBDMEMAQ8BDAAIAAyAC4AMgBQAHIA +bwBmAGkAbAAgAGcA6QBuAOkAcgBpAHEAdQBlACAAZwByAGkAcwAgAGcAYQBtAG0AYQAgADIALAAyAMEA +bAB0AGEAbADhAG4AbwBzACAAcwB6APwAcgBrAGUAIABnAGEAbQBtAGEAIAAyAC4AMpAadShwcJaOUUle +pgAgADIALgAyACCCcl9pY8+P8ABHAGUAbgBlAHIAaQBzAGsAIABnAHIA5QAgAGcAYQBtAG0AYQAgADIA +LAAyAC0AcAByAG8AZgBpAGwATwBiAGUAYwBuAOEAIAFhAGUAZADhACAAZwBhAG0AYQAgADIALgAyBdIF +0AXeBdQAIAXQBeQF1QXoACAF2wXcBdwF2QAgADIALgAyAFAAcgBvAGYAaQBsAG8AIABnAHIAaQBnAGkA +bwAgAGcAZQBuAGUAcgBpAGMAbwAgAGQAZQBsAGwAYQAgAGcAYQBtAG0AYQAgADIALAAyAEcAYQBtAGEA +IABnAHIAaQAgAGcAZQBuAGUAcgBpAGMBAwAgADIALAAyAEEAbABsAGcAZQBtAGUAaQBuAGUAcwAgAEcA +cgBhAHUAcwB0AHUAZgBlAG4ALQBQAHIAbwBmAGkAbAAgAEcAYQBtAG0AYQAgADIALAAyx3y8GAAg1ozA +yQAgrBC5yAAgADIALgAyACDVBLhc0wzHfGZukBpwcF6mfPtlcAAgADIALgAyACBjz4/wZYdO9k4Agiww +sDDsMKQwrDDzMN4AIAAyAC4AMgAgMNcw7TDVMKEwpDDrA5MDtQO9A7kDugPMACADkwO6A8EDuQAgA5MD +rAO8A7wDsQAgADIALgAyAFAAZQByAGYAaQBsACAAZwBlAG4A6QByAGkAYwBvACAAZABlACAAYwBpAG4A +egBlAG4AdABvAHMAIABkAGEAIABHAGEAbQBtAGEAIAAyACwAMgBBAGwAZwBlAG0AZQBlAG4AIABnAHIA +aQBqAHMAIABnAGEAbQBtAGEAIAAyACwAMgAtAHAAcgBvAGYAaQBlAGwAUABlAHIAZgBpAGwAIABnAGUA +bgDpAHIAaQBjAG8AIABkAGUAIABnAGEAbQBtAGEAIABkAGUAIABnAHIAaQBzAGUAcwAgADIALAAyDiMO +MQ4HDioONQ5BDgEOIQ4hDjIOQA4BDiMOIg5MDhcOMQ5IDicORA4bACAAMgAuADIARwBlAG4AZQBsACAA +RwByAGkAIABHAGEAbQBhACAAMgAsADIAWQBsAGUAaQBuAGUAbgAgAGgAYQByAG0AYQBhAG4AIABnAGEA +bQBtAGEAIAAyACwAMgAgAC0AcAByAG8AZgBpAGkAbABpAEcAZQBuAGUAcgBpAQ0AawBpACAARwByAGEA +eQAgAEcAYQBtAG0AYQAgADIALgAyACAAcAByAG8AZgBpAGwAVQBuAGkAdwBlAHIAcwBhAGwAbgB5ACAA +cAByAG8AZgBpAGwAIABzAHoAYQByAG8BWwBjAGkAIABnAGEAbQBtAGEAIAAyACwAMgQeBDEESQQwBE8A +IARBBDUEQAQwBE8AIAQzBDAEPAQ8BDAAIAAyACwAMgAtBD8EQAQ+BEQEOAQ7BEwARwBlAG4AZQByAGkA +YwAgAEcAcgBhAHkAIABHAGEAbQBtAGEAIAAyAC4AMgAgAFAAcgBvAGYAaQBsAGUGOgYnBkUGJwAgADIA +LgAyACAGRAZIBkYAIAYxBkUGJwYvBkoAIAY5BicGRXRleHQAAAAAQ29weXJpZ2h0IEFwcGxlIEluYy4s +IDIwMTIAAFhZWiAAAAAAAADzUQABAAAAARbMY3VydgAAAAAAAAQAAAAABQAKAA8AFAAZAB4AIwAoAC0A +MgA3ADsAQABFAEoATwBUAFkAXgBjAGgAbQByAHcAfACBAIYAiwCQAJUAmgCfAKQAqQCuALIAtwC8AMEA +xgDLANAA1QDbAOAA5QDrAPAA9gD7AQEBBwENARMBGQEfASUBKwEyATgBPgFFAUwBUgFZAWABZwFuAXUB +fAGDAYsBkgGaAaEBqQGxAbkBwQHJAdEB2QHhAekB8gH6AgMCDAIUAh0CJgIvAjgCQQJLAlQCXQJnAnEC +egKEAo4CmAKiAqwCtgLBAssC1QLgAusC9QMAAwsDFgMhAy0DOANDA08DWgNmA3IDfgOKA5YDogOuA7oD +xwPTA+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4EjASaBKgEtgTEBNME4QTwBP4FDQUcBSsFOgVJBVgF +ZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3BkgGWQZqBnsGjAadBq8GwAbRBuMG9QcHBxkHKwc9B08H +YQd0B4YHmQesB78H0gflB/gICwgfCDIIRghaCG4IggiWCKoIvgjSCOcI+wkQCSUJOglPCWQJeQmPCaQJ +ugnPCeUJ+woRCicKPQpUCmoKgQqYCq4KxQrcCvMLCwsiCzkLUQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwM +dQyODKcMwAzZDPMNDQ0mDUANWg10DY4NqQ3DDd4N+A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUPQQ9eD3oP +lg+zD88P7BAJECYQQxBhEH4QmxC5ENcQ9RETETERTxFtEYwRqhHJEegSBxImEkUSZBKEEqMSwxLjEwMT +IxNDE2MTgxOkE8UT5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYVeBWbFb0V4BYDFiYWSRZsFo8WshbWFvoX +HRdBF2UXiReuF9IX9xgbGEAYZRiKGK8Y1Rj6GSAZRRlrGZEZtxndGgQaKhpRGncanhrFGuwbFBs7G2Mb +ihuyG9ocAhwqHFIcexyjHMwc9R0eHUcdcB2ZHcMd7B4WHkAeah6UHr4e6R8THz4faR+UH78f6iAVIEEg +bCCYIMQg8CEcIUghdSGhIc4h+yInIlUigiKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQklOCVoJZcl +xyX3JicmVyaHJrcm6CcYJ0kneierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqbKs8rAis2K2kr +nSvRLAUsOSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLyQvWi+RL8cv/jA1MGwwpDDbMRIxSjGCMbox +8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1TTWHNcI1/TY3NnI2rjbpNyQ3YDecN9c4FDhQOIw4 +yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7qjvoPCc8ZTykPOM9Ij1hPaE94D4gPmA+oD7gPyE/YT+iP+JA +I0BkQKZA50EpQWpBrEHuQjBCckK1QvdDOkN9Q8BEA0RHRIpEzkUSRVVFmkXeRiJGZ0arRvBHNUd7R8BI +BUhLSJFI10kdSWNJqUnwSjdKfUrESwxLU0uaS+JMKkxyTLpNAk1KTZNN3E4lTm5Ot08AT0lPk0/dUCdQ +cVC7UQZRUFGbUeZSMVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYPVlxWqVb3V0RXklfgWC9YfVjLWRpZ +aVm4WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1fD19hX7NgBWBXYKpg/GFPYaJh9WJJYpxi +8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/aJZo7GlDaZpp8WpIap9q92tPa6dr/2xXbK9t +CG1gbbluEm5rbsRvHm94b9FwK3CGcOBxOnGVcfByS3KmcwFzXXO4dBR0cHTMdSh1hXXhdj52m3b4d1Z3 +s3gReG54zHkqeYl553pGeqV7BHtje8J8IXyBfOF9QX2hfgF+Yn7CfyN/hH/lgEeAqIEKgWuBzYIwgpKC +9INXg7qEHYSAhOOFR4Wrhg6GcobXhzuHn4gEiGmIzokziZmJ/opkisqLMIuWi/yMY4zKjTGNmI3/jmaO +zo82j56QBpBukNaRP5GokhGSepLjk02TtpQglIqU9JVflcmWNJaflwqXdZfgmEyYuJkkmZCZ/JpomtWb +QpuvnByciZz3nWSd0p5Anq6fHZ+Ln/qgaaDYoUehtqImopajBqN2o+akVqTHpTilqaYapoum/adup+Co +UqjEqTepqaocqo+rAqt1q+msXKzQrUStuK4trqGvFq+LsACwdbDqsWCx1rJLssKzOLOutCW0nLUTtYq2 +AbZ5tvC3aLfguFm40blKucK6O7q1uy67p7whvJu9Fb2Pvgq+hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TE +UcTOxUvFyMZGxsPHQce/yD3IvMk6ybnKOMq3yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzRvtI/0sHT +RNPG1EnUy9VO1dHWVdbY11zX4Nhk2OjZbNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A24L3hROHM4lPi +2+Nj4+vkc+T85YTmDeaW5x/nqegy6LzpRunQ6lvq5etw6/vshu0R7ZzuKO6070DvzPBY8OXxcvH/8ozz +GfOn9DT0wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH/Jj9Kf26/kv+3P9t//+AFNInKFxdXU5TTXV0 +YWJsZURhdGGjXF4sVk5TRGF0YdInKGBhXE5TQ29sb3JTcGFjZaJiLFxOU0NvbG9yU3BhY2XSJyhkZVdO +U0NvbG9yomQs0icoZ2hXTlNJbWFnZaJnLF8QD05TS2V5ZWRBcmNoaXZlctFrbFRyb290gAEAAAAIAAAA +EQAAABoAAAAjAAAALQAAADIAAAA3AAAAUgAAAFgAAABjAAAAagAAAHEAAAB+AAAAhQAAAI0AAACPAAAA +kQAAAJYAAACYAAAAmgAAAKUAAACqAAAAtQAAALkAAAC7AAAAvQAAAL8AAADBAAAAxgAAAMkAAADLAAAA +zQAAAM8AAADRAAAA1gAAAO0AAADvAAAA8QAA7xsAAO8gAADvKwAA7zQAAO9HAADvSwAA71YAAO9fAADv +ZAAA72wAAO9vAADvdAAA73cAAO95AADvewAA730AAO+CAADvhAAA74YABIDqAASA7wAEgPIABID0AASA +9gAEgPgABID9AASA/wAEgQEADHd1AAx3egAMd4kADHeNAAx3mAAMd6AADHetAAx3ugAMd88ADHfUAAx3 +2AAMd9oADHfcAAx33gAMd+cADHfsAAx38gAMd/oADHf8AAx3/gAMeAAADHgCAAx4BwAMeA8ADIl7AAyJ +fQAMiYIADImQAAyJlAAMiZsADImgAAyJrQAMibAADIm9AAyJwgAMicoADInNAAyJ0gAMidoADIndAAyJ +7wAMifIADIn3AAAAAAAABAEAAAAAAAAAbQAAAAAAAAAAAAAAAAAMifk + + + + + + + + diff --git a/!main project/GeekbrainsUI/Cells/FriendCellwithXIB.swift b/!main project/GeekbrainsUI/Cells/FriendCellwithXIB.swift new file mode 100644 index 0000000..a68c63c --- /dev/null +++ b/!main project/GeekbrainsUI/Cells/FriendCellwithXIB.swift @@ -0,0 +1,24 @@ +// +// FriendCellwithXIB.swift +// GeekbrainsUI +// +// Created by raskin-sa on 27/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit + +class FriendCellwithXIB: UICollectionViewCell { + + @IBOutlet weak var friendsPhoto: UIImageView! + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + } + + public func refreshWith(friend: Friend){ + friendsPhoto.image = UIImage(named: "\(friend.avatarPath).jpg") + }// + +}//class FriendCellwithXIB: UICollectionViewCell diff --git a/!main project/GeekbrainsUI/Cells/FriendCellwithXIB.xib b/!main project/GeekbrainsUI/Cells/FriendCellwithXIB.xib new file mode 100644 index 0000000..cfefc65 --- /dev/null +++ b/!main project/GeekbrainsUI/Cells/FriendCellwithXIB.xib @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/GeekbrainsUI/Cells/GroupCell.swift b/!main project/GeekbrainsUI/Cells/GroupCell.swift new file mode 100644 index 0000000..5069a92 --- /dev/null +++ b/!main project/GeekbrainsUI/Cells/GroupCell.swift @@ -0,0 +1,59 @@ +// +// GroupCell.swift +// GeekbrainsUI +// +// Created by raskin-sa on 03/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit + +class GroupCell: UITableViewCell { + + @IBOutlet weak var localConstraint: NSLayoutConstraint! + @IBOutlet weak var groupname:UILabel! + @IBOutlet weak var groupimage: UIImageView! + + + func setAnimation() { + UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 12, options: [], animations: { + self.groupimage.transform = CGAffineTransform(scaleX: 0.5, y: 0.5) + }, completion: {_ in UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 12, options: [], animations: { + self.groupimage.transform = CGAffineTransform(scaleX: 1.0, y: 1.0) }) + }) +} + + var delegate : CellImageTapDelegate? + var tapGestureRecognizer = UITapGestureRecognizer() + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + initialize() + } + + private func initialize() { + tapGestureRecognizer.addTarget(self, action: #selector(self.imageTapped(gestureRecgonizer:))) + self.addGestureRecognizer(tapGestureRecognizer) + } + + @objc func imageTapped(gestureRecgonizer: UITapGestureRecognizer) { + delegate?.tableCell(didClickedImageOf: self) + } + + func renderCell(model: VKGroupRealm){ + + let groupName = model.groupName + + groupname.text = groupName + + if let url = URL(string: model.avatarPath){ + groupimage.kf.setImage(with: url) + } + } +}// class GroupCell + +class NewGroupCell: UITableViewCell { + + @IBOutlet weak var newgroupname:UILabel! + +} diff --git a/!main project/GeekbrainsUI/Cells/MessageCell.swift b/!main project/GeekbrainsUI/Cells/MessageCell.swift new file mode 100644 index 0000000..8b21a36 --- /dev/null +++ b/!main project/GeekbrainsUI/Cells/MessageCell.swift @@ -0,0 +1,101 @@ +// +// MessageCell.swift +// GeekbrainsUI +// +// Created by raskin-sa on 13/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit + +class MessageCell: UITableViewCell { + + @IBOutlet weak var avatar: UIImageView! + @IBOutlet weak var time: UILabel! + @IBOutlet weak var username: UILabel! + @IBOutlet weak var message: UILabel! + + + @IBAction func likeButtonPressed(_ sender: Any) { + (sender as! LikeButton).like() + + } + + @IBAction func commentButtonPressed(_ sender: Any) { + (sender as! CustomCommentButton).comment() + + } + + @IBAction func shareButtonPressed(_ sender: Any) { + (sender as! CustomShareButton).share() + } + + @IBAction func viewedButtonPressed(_ sender: Any) { + (sender as! CustomViewButton).setLook() + } + + var presenter = ImagePresenter() + + override func prepareForReuse() { + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(showImage)) + + avatar.addGestureRecognizer(tapGesture) + } + + @objc func showImage(){ + let storyboard = UIStoryboard(name: "Main", bundle: nil) + let vc = storyboard.instantiateViewController(withIdentifier: "ImageController") + + let origin = superview!.convert(avatar.frame, to: (UIApplication.shared.windows.first?.rootViewController as? UINavigationController)?.view) + presenter.size = origin + vc.transitioningDelegate = presenter + (UIApplication.shared.windows.first?.rootViewController as? UINavigationController)?.topViewController?.present(vc, animated: true, completion: nil) + }//func showImage + +}// class MessageCell + +class ImagePresenter: NSObject, UIViewControllerTransitioningDelegate { + let animator = Animator() + + var size = CGRect.zero + + func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { + return animator + } + } + +class Animator: NSObject, UIViewControllerAnimatedTransitioning{ + var frame = CGRect.zero + + func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { + return 0.5 + } + + func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + let container = transitionContext.containerView + let toView = transitionContext.view(forKey: .to) + let fromView = transitionContext.view(forKey: .from) + + let initialFrame = frame + let finalFrame = fromView?.frame + + let xScale = initialFrame.width/finalFrame!.width + let yScale = initialFrame.height/finalFrame!.height + + let scaleTransform = CGAffineTransform(scaleX: xScale, y: yScale) + + toView?.transform = scaleTransform + toView?.center = CGPoint(x: frame.midX, y: frame.midY) + toView?.clipsToBounds = true + + container.addSubview(toView!) + container.bringSubviewToFront(toView!) + + UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: { + toView?.transform = .identity + toView?.center = CGPoint(x: finalFrame!.midX, y: finalFrame!.midY) + }){ complete in transitionContext.completeTransition(true) + + } + } +} diff --git a/!main project/GeekbrainsUI/Cells/MessageCell.xib b/!main project/GeekbrainsUI/Cells/MessageCell.xib new file mode 100644 index 0000000..2ee6ddc --- /dev/null +++ b/!main project/GeekbrainsUI/Cells/MessageCell.xib @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/GeekbrainsUI/Cells/MessageViewController.swift b/!main project/GeekbrainsUI/Cells/MessageViewController.swift new file mode 100644 index 0000000..612d1b1 --- /dev/null +++ b/!main project/GeekbrainsUI/Cells/MessageViewController.swift @@ -0,0 +1,44 @@ +// +// MessageViewController.swift +// GeekbrainsUI +// +// Created by raskin-sa on 13/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +//3. Создать экран новостей. Добавить туда таблицу и сделать ячейку для новости. Ячейка должна содержать то же самое, что и в оригинальном приложении ВКонтакте: надпись, фотографии, кнопки «Мне нравится», «Комментировать», «Поделиться» и индикатор количества просмотров. Сделать поддержку только одной фотографии, которая должна быть квадратной формы и растягиваться на всю ширину ячейки. Высота ячейки должна вычисляться автоматически. + +import UIKit + + +class NewsDatabase { + static func getNews() -> [News]{ + return myNews + } +} + +class MessageViewController: UITableViewController{ + + var messageArray = NewsDatabase.getNews() + + override func viewDidLoad() { + tableView.register(UINib(nibName: "MessageCell", bundle: nil), forCellReuseIdentifier: "SimpleMessage") + + tableView.estimatedRowHeight = 100.0 + tableView.rowHeight = UITableView.automaticDimension + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return messageArray.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "SimpleMessage", for: indexPath) as? MessageCell + cell?.message.text = messageArray[indexPath.row].newsText + cell?.username.text = messageArray[indexPath.row].writer + cell?.avatar.image = UIImage(named: messageArray[indexPath.row].imagePath) + return cell! + } + + +}// class MessageViewController diff --git a/!main project/GeekbrainsUI/Cells/PhotoCell.swift b/!main project/GeekbrainsUI/Cells/PhotoCell.swift new file mode 100644 index 0000000..c3c4344 --- /dev/null +++ b/!main project/GeekbrainsUI/Cells/PhotoCell.swift @@ -0,0 +1,25 @@ +// +// PhotoCell.swift +// GeekbrainsUI +// +// Created by raskin-sa on 16/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit + +class PhotoCell: UICollectionViewCell { + + @IBOutlet weak var photo: UIImageView! + @IBOutlet weak var cellUsername: UILabel! + + func renderCell(model: VKPhotosRealm, username: String ) { + cellUsername.text = username + + let sizesRealm = model.sizes.filter("type == %@","x") + let urlToBe = sizesRealm[0].url + let url = URL(string: urlToBe) + + photo.kf.setImage(with: url) + } +} diff --git a/!main project/GeekbrainsUI/Code Nice To Use/AnimatedTransitions b/!main project/GeekbrainsUI/Code Nice To Use/AnimatedTransitions new file mode 100644 index 0000000..e69de29 diff --git a/!main project/GeekbrainsUI/Code Nice To Use/Animations II.swift b/!main project/GeekbrainsUI/Code Nice To Use/Animations II.swift new file mode 100644 index 0000000..5a93a0c --- /dev/null +++ b/!main project/GeekbrainsUI/Code Nice To Use/Animations II.swift @@ -0,0 +1,70 @@ +// +// Animations II.swift +// GeekbrainsUI +// +// Created by raskin-sa on 18/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +//KeyFrame animations +UIView.animateKeyframes(withDuration: 3.0, delay: 0, animations: { UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.33){ + self.logo.center.x += 100 +} + UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.33){ + self.logo.center.x += 100 + } + UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.33){ + self.logo.center.x += 100 + } + }, completion: nil) + + /// + ///Stroke Animations = закрашивает контуры + /// + + let strokeAnimationStart = CABasicAnimation(keyPath: "strokeStart") + strokeAnimationStart.fromValue = 0 + strokeAnimationStart.toValue = 1.0 + + let strokeAnimationEnd = CABasicAnimation(keyPath: "strokeEnd") + strokeAnimationEnd.fromValue = 0 + strokeAnimationEnd.toValue = 1.2 + + let groupAnimation = CAAnimationGroup() + groupAnimation.duration = 1.0 + groupAnimation.animations = [strokeAnimationStart, strokeAnimationEnd] + groupAnimation.autoreverses = true + groupAnimation.repeatCount = .infinity + groupAnimation.isRemovedOnCompletion = false + groupAnimation.fillMode = .forwards + + customLayer.add(groupAnimation, forKey: nil) + +func drawCircleStrokeGroupAnimation(strokeColor:CGColor){ + let circleLayer = CAShapeLayer() + circleLayer.path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 50, height: 50) , cornerRadius: 25).cgPath + + circleLayer.strokeColor = strokeColor + circleLayer.backgroundColor = UIColor.systemTeal.cgColor + circleLayer.lineWidth = 3 + layer.addSublayer(circleLayer) + + + let strokeAnimationStart = CABasicAnimation(keyPath: "strokeStart") + strokeAnimationStart.fromValue = 0 + strokeAnimationStart.toValue = 1.0 + + let strokeAnimationEnd = CABasicAnimation(keyPath: "strokeEnd") + strokeAnimationEnd.fromValue = 0 + strokeAnimationEnd.toValue = 1.2 + + let groupAnimation = CAAnimationGroup() + groupAnimation.duration = 1.0 + groupAnimation.animations = [strokeAnimationStart, strokeAnimationEnd] + groupAnimation.autoreverses = true + groupAnimation.repeatCount = .infinity + groupAnimation.isRemovedOnCompletion = false + groupAnimation.fillMode = .forwards + + circleLayer.add(groupAnimation, forKey: nil) +} diff --git a/!main project/GeekbrainsUI/Code Nice To Use/Async load image without KingFisher b/!main project/GeekbrainsUI/Code Nice To Use/Async load image without KingFisher new file mode 100644 index 0000000..7f9cae8 --- /dev/null +++ b/!main project/GeekbrainsUI/Code Nice To Use/Async load image without KingFisher @@ -0,0 +1,11 @@ + +// DispatchQueue.global().async { +// if let data = try? Data(contentsOf: url!) { +// DispatchQueue.main.async { +// cell.userimage.image = UIImage(data: data) +// } +// } else{ +// cell.userimage.image = UIImage(named: "NewGroup.jpg") +// } +// +// }//DispatchQueue.global().async diff --git a/!main project/GeekbrainsUI/Code Nice To Use/SubscribeToOneRecord b/!main project/GeekbrainsUI/Code Nice To Use/SubscribeToOneRecord index e69de29..2d6dbaa 100644 --- a/!main project/GeekbrainsUI/Code Nice To Use/SubscribeToOneRecord +++ b/!main project/GeekbrainsUI/Code Nice To Use/SubscribeToOneRecord @@ -0,0 +1,23 @@ + +//в переменные класса добавить опционал + var currentGroup: VKGroupRealm? + +//в ViewDidload добавить вызов + subscribeGroup() + + +func subscribeGroup(){ + let realm = try! Realm() + currentGroup = realm.object(ofType: VKGroupRealm.self, forPrimaryKey:17032179) + tokenUser = currentGroup?.observe{ change in + switch change { + case .change (let changes): + for change in changes { + if change.name == "groupName" { + print("Cтарое значение = \(change.oldValue). Новое значение = \(change.newValue)") + }//if change.name == "groupName" + }// for + default: break + }//switch + }//observe +}//func subscribeGroup() diff --git a/!main project/GeekbrainsUI/Code Nice To Use/TestForRealmDB b/!main project/GeekbrainsUI/Code Nice To Use/TestForRealmDB new file mode 100644 index 0000000..c26f777 --- /dev/null +++ b/!main project/GeekbrainsUI/Code Nice To Use/TestForRealmDB @@ -0,0 +1,68 @@ +/************* Если менялась структура Realm между запусками +//добавить в AppDelegate *********************/ + +let config = Realm.Configuration( + // Set the new schema version. This must be greater than the previously used + // version (if you've never set a schema version before, the version is 0). + schemaVersion: 1, + // Set the block which will be called automatically when opening a Realm with + // a schema version lower than the one set above + migrationBlock: { migration, oldSchemaVersion in + // We haven’t migrated anything yet, so oldSchemaVersion == 0 + if (oldSchemaVersion < 1) { + // Nothing to do! + // Realm will automatically detect new properties and removed properties + // And will update the schema on disk automatically + } + }) + + // Tell Realm to use this new configuration object for the default Realm + Realm.Configuration.defaultConfiguration = config + +/********* конец добавления в AppDelegate ********************/ + + let realmRepository = VKWorkWithDBRealm() + realmRepository.addPhoto(id: 1, albumID: 0, ownerID: 0, sizes: ["маленькое","среднее","большое"], text: "", date: 0) + + realmRepository.addPhoto(id: 2, albumID: 0, ownerID: 0, sizes: ["маленькое","большое"], text: "", date: 0) + + let photos = realmRepository.getPhotos() + print(photos) + + let groups = realmRepository.getGroups() + print(groups) + +let realmRepository = VKWorkWithDBRealm() + realmRepository.addUser(id: 2, lastName: "lastName2", firstName: "FirstName2", avatarPath: "AvatarPath2", isOnline: 0) +// let userNew = realmRepository.getUser(id: 1, lastName: nil, avatarPath: nil) +// print(userNew) +// let userNew2 = realmRepository.getUser(id: 0, lastName: nil, avatarPath: "AvatarPath2") +// print(userNew2) +// + let users = realmRepository.getUsers() + print (users) + +// realmRepository.addGroup(id: 1, groupName: "group1", avatarPath: "AvatarPath1") +// realmRepository.addGroup(id: 2, groupName: "group2", avatarPath: "AvatarPath2") +// realmRepository.addGroup(id: 3, groupName: "group3", avatarPath: "AvatarPath3") +// let groups = realmRepository.getGroups() +// print(groups) +// +// let user1 = realmRepository.getGroup(id: 1, groupName: nil, avatarPath: nil) +// print (user1) +// +// let user2 = realmRepository.getGroup(id: nil, groupName: "group2", avatarPath: nil) +// print (user2) +// +// let user3 = realmRepository.getGroup(id: nil, groupName: nil, avatarPath: "AvatarPath3") +// print (user3) +// +// realmRepository.deleteUsers() +// let users = realmRepository.getUsers() +// print (users) +// let groups = realmRepository.getGroups() +// print(groups) + print("удаляем юзеров") + realmRepository.deleteVKObject(object: VKObjects.VKUser) + print(users) + diff --git a/!main project/GeekbrainsUI/Code Nice To Use/animations.swift b/!main project/GeekbrainsUI/Code Nice To Use/animations.swift new file mode 100644 index 0000000..a11b832 --- /dev/null +++ b/!main project/GeekbrainsUI/Code Nice To Use/animations.swift @@ -0,0 +1,140 @@ +// +// Анимации.swift +// GeekbrainsUI +// +// Created by raskin-sa on 15/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import Foundation + + func testAnimation() { + //для constraint' ов + // self.view.layoutIfNeeded() + /* + UIView.animate( withDuration: 2.0, + delay: 2.0, + options: [.repeat, .autoreverse], + animations:{ //self.groupSearchBar.frame.origin.y -= 300 + + //self.spaceConstraint.constant = 50 + //для constraint' ов + // self.view.layoutIfNeeded() + }) + */ + /* + //Анимация на слое. Изменяет св-во во времени + let animation = CABasicAnimation(keyPath: "position.y") + animation.fromValue = groupSearchBar.layer.position.y + animation.toValue = groupSearchBar.layer.position.y + 100 + animation.duration = 2 + groupSearchBar.layer.add(animation, forKey: "changePosition") + */ + + /* + //Анимация на слое, изменяет свойство во времени с разной скоростью по частям + let animation = CAKeyframeAnimation() + animation.keyPath = "position.y" + animation.values = [groupSearchBar.layer.position.y, + groupSearchBar.layer.position.y + 100, + groupSearchBar.layer.position.y + 200, + groupSearchBar.layer.position.y + 300] + + + animation.keyTimes = [0, 0.4, 0.5, 1.0] + animation.duration = 5 + //чтобы отследить конец анимации + animation.fillMode = .forwards + animation.isRemovedOnCompletion = false + + groupSearchBar.layer.add(animation, forKey: "changePosition") + */ + /* + // bounced animation + UIView.animate(withDuration: 2.0, delay: 1, usingSpringWithDamping: 0.6, initialSpringVelocity: 0, + options: [], animations: {self.groupSearchBar.frame.origin.y += 100}) + */ + + /* + //код анимации на слое с перемещением Layer'а по итогам анимации + CATransaction.begin() + + // print(groupSearchBar.frame) + CATransaction.setCompletionBlock{ + // print("до присвоения в CATransaction.setCompletionBlock") + // print(self.groupSearchBar.frame) + + //!!!!! необходимо присвоить слою новые координаты + self.groupSearchBar.frame = CGRect(x:self.groupSearchBar.frame.minX, + y:self.groupSearchBar.layer.position.y + 300, + width: self.groupSearchBar.frame.width, + height: self.groupSearchBar.frame.height) + // print("после присвоения в CATransaction.setCompletionBlock") + // print(self.groupSearchBar.frame) + } + let animation = CAKeyframeAnimation() + animation.keyPath = "position.y" + animation.values = [groupSearchBar.layer.position.y, + groupSearchBar.layer.position.y + 100, + groupSearchBar.layer.position.y + 200, + groupSearchBar.layer.position.y + 300] + + + animation.keyTimes = [0, 0.4, 0.5, 1.0] + animation.duration = 5 + //чтобы отследить конец анимации + animation.fillMode = .forwards + animation.isRemovedOnCompletion = false + groupSearchBar.layer.add(animation, forKey: "changePosition") + CATransaction.commit() + */ + +/* + // emitter animation = снежинки + + let emitterSnow = CAEmitterCell() + emitterSnow.contents = UIImage(named:"snow")?.cgImage + emitterSnow.scale = 0.01 + emitterSnow.scaleRange = 0.05 + emitterSnow.birthRate = 40 + emitterSnow.lifetime = 10 + emitterSnow.velocity = -30 + emitterSnow.velocityRange = -20 + emitterSnow.yAcceleration = 30 + emitterSnow.xAcceleration = 5 + emitterSnow.spin = -0.5 + emitterSnow.spinRange = 1.0 + + let snowEmitterLayer = CAEmitterLayer() + snowEmitterLayer.emitterPosition = CGPoint(x: view.bounds.width / 2, y: 50) + snowEmitterLayer.emitterSize = CGSize(width: view.bounds.width, height: 0) + snowEmitterLayer.emitterShape = .line + snowEmitterLayer.beginTime = CACurrentMediaTime() + 2 + snowEmitterLayer.timeOffset = 10 + snowEmitterLayer.emitterCells = [emitterSnow] + view.layer.addSublayer(snowEmitterLayer) + */ + /* + //анимация для лайка (увеличить размер/вернуть) + UIView.animate( + withDuration: 0.5, + delay: 3, + options: [], + animations:{ self.groupSearchBar.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)}, + completion: {_ in UIView.animate(withDuration: 0.5, delay: 0, options: [], animations: {self.groupSearchBar.transform = CGAffineTransform(scaleX: 1.0, y: 1.0) + }) + }) + */ + /* + код позволяет таблице выезжать с разворотом + + override func tableView(_ tableView: UITableView, willDisplay cell: + UITableViewCell, forRowAt indexPath: IndexPath) { + let rotationAngleInRadians = 360.0 * CGFloat(.pi/360.0) + // let rotationTransform = CATransform3DMakeRotation(rotationAngleInRadians, -500, 100, 0) + let rotationTransform = CATransform3DMakeRotation(rotationAngleInRadians, 0, 0, 1) + cell.layer.transform = rotationTransform + UIView.animate(withDuration: 1.0, animations: {cell.layer.transform = CATransform3DIdentity}) + } + */ + } diff --git a/!main project/GeekbrainsUI/Code Nice To Use/animationsII b/!main project/GeekbrainsUI/Code Nice To Use/animationsII new file mode 100644 index 0000000..26046fd --- /dev/null +++ b/!main project/GeekbrainsUI/Code Nice To Use/animationsII @@ -0,0 +1,30 @@ + +/* +class AnimatorTransition: NSObject, UIViewControllerAnimatedTransitioning { + func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { + return 1.0 + } + + func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + guard let source = transitionContext.viewController(forKey: .from), + let to = transitionContext.viewController(forKey: .to) else{ + return + } + transitionContext.containerView.addSubview(to.view) + + to.view.frame = CGRect(x:0, + y: transitionContext.containerView.frame.height, + width: source.view.frame.width, + height: source.view.frame.height) + + UIView.animate(withDuration: transitionDuration(using: transitionContext),animations: { + source.view.frame = CGRect(x: 0, y: -source.view.frame.height, width: source.view.frame.width, height: source.view.frame.height) + + to.view.frame = CGRect(x:0, + y: 0, + width: source.view.frame.width, + height: source.view.frame.height) + }) {isCompleted in transitionContext.completeTransition(isCompleted)} + }//UIView.animate + }//class AnimatorTransition + */ diff --git a/!main project/GeekbrainsUI/Code Nice To Use/vkAPI with generic and common network query b/!main project/GeekbrainsUI/Code Nice To Use/vkAPI with generic and common network query new file mode 100644 index 0000000..2c1bd73 --- /dev/null +++ b/!main project/GeekbrainsUI/Code Nice To Use/vkAPI with generic and common network query @@ -0,0 +1,148 @@ + +// +// VKApi.swift +// GeekbrainsUI +// +// Created by raskin-sa on 15/01/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + +import WebKit +import Alamofire + +enum RequestError: Error { + case failedRequest(message: String) + case decodableError +} + + + +class VKAPi { + private let vkURL = "https://api.vk.com/method/" + //https://jsonplaceholder.typicode.com/users + + func requestServer(requestURL: String, + params: Parameters, + completion: @escaping (Swift.Result) -> Void){ + Alamofire.request(requestURL, + method: .post, + parameters: params) + .responseData {(response) in + + switch response.result { + case .failure(let error): + completion(.failure(RequestError.failedRequest(message: error.localizedDescription))) + case .success(let data): + do{ + let response = try JSONDecoder().decode(T.self, from: data) + completion(.success(response)) + }catch let error { + completion(.failure(RequestError.decodableError)) + } + } + }//switch + }//func requestServer + + //Получение списка друзей + func getFriendList(token: String, result: @escaping (Swift.Result<[VKUser], Error>) -> Void) { + + let requestURL = vkURL + "friends.get" + let params = ["v": "5.103", + "access_token":token, + "order":"name", + "fields":"online,city,domain,photo_50"] as [String:Any] + + requestServer(requestURL: requestURL, params: params) {(users:Swift.Result) in + switch users { + case .failure(let error): + result(.failure(error)) + case .success(let friends): + result(.success(friends.response.items)) + }//switch + } + }//func getFriendList + + //Получение фотографий человека + func getPhotosList(token: String, userId: Int, completed: @escaping () ->()) { + let requestURL = vkURL + "photos.get" + let params = ["v": "5.103", + "access_token":token, + "owner_id":String(userId), + "album_id":"wall" + ] + + + Alamofire.request(requestURL, + method: .post, + parameters: params).responseData(completionHandler: { (response) in + + guard let data = response.value else { + return + } + do{ + let response = try JSONDecoder().decode(PhotosResponse.self, from: data) + print ("Получение фотографий текущего пользователя") + + myPhotos = response.response.items + print(myPhotos) + + } catch{ + print (error) + } + completed() + }) + + }//func getPhotosList + + //Получение групп текущего пользователя + func getGroupsList(token: String, userId: String, completed: @escaping () ->()){ + let requestURL = vkURL + "groups.get" + let params = ["v": "5.103", + "access_token":token, + "user_id":userId, + "extended":"1" + ] + + + Alamofire.request(requestURL, + method: .post, + parameters: params).responseData { (response) in + + guard let data = response.value else{ + return + } + + do{ + let response = try JSONDecoder().decode(GroupResponse.self, from: data) + print ("Получение групп текущего пользователя") + myGroup = response.response.items + print(myGroup) + } catch{ + print (error) + } + completed() + + } + }//func getGroupsList + + //Получение групп по поисковому запросу; + func searchGroups (token: String, query: String, completion: () -> ()){ + let requestURL = vkURL + "groups.search" + let params = ["v": "5.103", + "access_token":token, + "q":query, + "type":"group", + "count":"3", + "country_id":"1" //Россия + ] + + + Alamofire.request(requestURL, + method: .post, + parameters: params).responseJSON(completionHandler: { (response) in + (print("\nПолучение групп по поисковому запросу \(query)"),print(response.value!)) + }) + }//func searchGroups + +}//class VKAPi + diff --git a/!main project/GeekbrainsUI/Database/CoreData/Model/FriendCD+CoreDataProperties.swift b/!main project/GeekbrainsUI/Database/CoreData/Model/FriendCD+CoreDataProperties.swift index c1cf664..62b7300 100644 --- a/!main project/GeekbrainsUI/Database/CoreData/Model/FriendCD+CoreDataProperties.swift +++ b/!main project/GeekbrainsUI/Database/CoreData/Model/FriendCD+CoreDataProperties.swift @@ -24,3 +24,9 @@ extension FriendCD { @NSManaged public var isOnline: Int16 } + +extension FriendCD { + func toCommonItem() -> VKUser { + return VKUser(lastName: self.lastName ?? "", firstName: self.firstName ?? "", avatarPath: self.avatarPath ?? "", isOnline: Int(self.isOnline), id: Int(self.id)) + } +} diff --git a/!main project/GeekbrainsUI/Database/CoreData/Model/FriendRepository.swift b/!main project/GeekbrainsUI/Database/CoreData/Model/FriendRepository.swift index 9931acf..ce5ebe6 100644 --- a/!main project/GeekbrainsUI/Database/CoreData/Model/FriendRepository.swift +++ b/!main project/GeekbrainsUI/Database/CoreData/Model/FriendRepository.swift @@ -15,18 +15,20 @@ protocol Repository { func getAll() -> [Entity] func get(id:Int) -> Entity? } -class FriendCoreDataRepository { + +class FriendCoreDataRepository: Repository { typealias Entity = VKUser -let context: NSManagedObjectContext - -init(stack: CoreDataStack){ - self.context = stack.context -} + let context: NSManagedObjectContext + + init(stack: CoreDataStack){ + self.context = stack.context + } private func queryCD(with predicate: NSPredicate?, sortDescriptors:[NSSortDescriptor]?=nil)->[FriendCD]{ let fetchRequest = NSFetchRequest(entityName: "FriendCD") fetchRequest.predicate = predicate + fetchRequest.sortDescriptors = sortDescriptors do{ return try (context.fetch(fetchRequest) as? [FriendCD] ?? []) }catch{ @@ -34,35 +36,43 @@ init(stack: CoreDataStack){ } }//private func queryCD - private func query(with predicate: NSPredicate?, sortDescriptors:[NSSortDescriptor]?=nil)->[VKUser]{ - let fetchRequest = NSFetchRequest(entityName: "FriendCD") - fetchRequest.predicate = predicate - do{ - let objects = try (context.fetch(fetchRequest)) as? [FriendCD] - return objects?.map{$0.toCommonItem() } ?? [] - }catch{ - return [] - } - }//private func query - -func getAll() -> [VKUser]{ - return query(with: nil, sortDescriptors: [NSSortDescriptor(key: "id", ascending: true)]) -} - -func get(id: Int) -> VKUser? { - return query(with: NSPredicate(format: "id = %@","\(id)" )).first -} + private func query(with predicate: NSPredicate?, sortDescriptors:[NSSortDescriptor]?=nil)->[VKUser]{ + let fetchRequest = NSFetchRequest(entityName: "FriendCD") + fetchRequest.predicate = predicate + fetchRequest.sortDescriptors = sortDescriptors + do{ + let objects = try (context.fetch(fetchRequest)) as? [FriendCD] + return objects?.map{$0.toCommonItem() } ?? [] + }catch{ + print(error) + return [] + } + }//private func query + + func getAll() -> [VKUser]{ + return query(with: nil, sortDescriptors: [NSSortDescriptor(key: "id", ascending: true)]) + } + + func get(id: Int) -> VKUser? { + return query(with: NSPredicate(format: "id = %@","\(id)" )).first + } func create(entity: VKUser) -> Bool { - guard let entityDescription = NSEntityDescription.entity(forEntityName: "FriendCD", in: context) else {return false} + guard let entityDescription = NSEntityDescription.entity(forEntityName: "FriendCD", in: context) else { + return false + } + + if get(id: entity.id) != nil { + return false + } let friendEntity = NSManagedObject(entity: entityDescription, insertInto: context) -// friendEntity.setValue(<#T##value: Any?##Any?#>, forKey: <#T##String#>) -// friendEntity.setValue(<#T##value: Any?##Any?#>, forKey: <#T##String#>) -// friendEntity.setValue(<#T##value: Any?##Any?#>, forKey: <#T##String#>) -// friendEntity.setValue(<#T##value: Any?##Any?#>, forKey: <#T##String#>) -// friendEntity.setValue(<#T##value: Any?##Any?#>, forKey: <#T##String#>) -// friendEntity.setValue(<#T##value: Any?##Any?#>, forKey: <#T##String#>) + friendEntity.setValue(entity.id, forKey: "id") + friendEntity.setValue(entity.avatarPath, forKey: "avatarPath") + //friendEntity.setValue(entity.deactivated, forKey: "deactivated") + friendEntity.setValue(entity.firstName, forKey: "firstName") + friendEntity.setValue(entity.lastName, forKey: "lastName") + friendEntity.setValue(entity.isOnline, forKey: "isOnline") return save() }//func create @@ -71,6 +81,7 @@ func get(id: Int) -> VKUser? { try context.save() return true }catch{ + print(error) return false } } diff --git a/!main project/GeekbrainsUI/Database/CoreDataStack.swift b/!main project/GeekbrainsUI/Database/CoreDataStack.swift new file mode 100644 index 0000000..ad1955f --- /dev/null +++ b/!main project/GeekbrainsUI/Database/CoreDataStack.swift @@ -0,0 +1,24 @@ +// +// CoreData.swift +// GeekbrainsUI +// +// Created by raskin-sa on 21/01/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + +import Foundation +import CoreData + +class CoreDataStack { + static let shared = CoreDataStack() + + private let storeContainer: NSPersistentContainer + let context: NSManagedObjectContext + + private init(){ + storeContainer = NSPersistentContainer(name: "VKDatabase") + storeContainer.loadPersistentStores {( _, error) in + } + context = storeContainer.viewContext + } +}//class CoreDataStack diff --git a/!main project/GeekbrainsUI/Database/Realm/Model/VKLoginRealm.swift b/!main project/GeekbrainsUI/Database/Realm/Model/VKLoginRealm.swift new file mode 100644 index 0000000..9aea1e9 --- /dev/null +++ b/!main project/GeekbrainsUI/Database/Realm/Model/VKLoginRealm.swift @@ -0,0 +1,9 @@ +// +// VKLoginRealm.swift +// GeekbrainsUI +// +// Created by raskin-sa on 19/02/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + +import Foundation diff --git a/!main project/GeekbrainsUI/Database/Realm/Model/VKPhotosRealm.swift b/!main project/GeekbrainsUI/Database/Realm/Model/VKPhotosRealm.swift index 49d4892..71caa48 100644 --- a/!main project/GeekbrainsUI/Database/Realm/Model/VKPhotosRealm.swift +++ b/!main project/GeekbrainsUI/Database/Realm/Model/VKPhotosRealm.swift @@ -6,4 +6,71 @@ // Copyright © 2020 raskin-sa. All rights reserved. // -import Foundation +import RealmSwift + +class VKPhotosRealm: Object { + @objc dynamic var id = 0 + + @objc dynamic var albumID = 0 + @objc dynamic var ownerID = 0 + @objc dynamic var text = "" + @objc dynamic var date = 0 + @objc dynamic var postID = 0 + @objc dynamic var likes: PhotoLikesRealm? + var sizes = List() + // let reposts: Reposts? + + override class func primaryKey() -> String? { + return "id" + } + + func toModel() -> VKPhoto { + var sizes = [VKPhotoSizes]() + + //выгрузка массива sizes + //MARK: не работает почему-то + sizes.forEach { size in + let oneSize = VKPhotoSizes(type: size.type, + url: size.url, + width: size.width, + height: size.height) + sizes.append(oneSize) + }// sizes.forEach + + //инициализация likes + //MARK: работает + let likes = VKPhotoLikes(userLikes: self.likes?.userLikes ?? 0, count: self.likes?.count ?? 0) + + return VKPhoto(id: id, + albumID: albumID, + ownerID: ownerID, + sizes: sizes, + text: text, + date: date, + postID: postID, + likes: likes, + reposts: nil, + lat: 0, + long: 0) + } + +}//class VKUserRealm + +class PhotoSizesRealm: Object { + @objc dynamic var url = "" + @objc dynamic var type = "" + @objc dynamic var width = 0 + @objc dynamic var height = 0 +} + +class PhotoLikesRealm: Object { + @objc dynamic var userLikes = 0 + @objc dynamic var count = 0 + + enum CodingKeys: String, CodingKey{ + case userLikes = "user_likes" + case count + } +} + + diff --git a/!main project/GeekbrainsUI/Database/Realm/RealmRepository/CommonRepository.swift b/!main project/GeekbrainsUI/Database/Realm/RealmRepository/CommonRepository.swift index e5074f1..c22e6ec 100644 --- a/!main project/GeekbrainsUI/Database/Realm/RealmRepository/CommonRepository.swift +++ b/!main project/GeekbrainsUI/Database/Realm/RealmRepository/CommonRepository.swift @@ -6,4 +6,55 @@ // Copyright © 2020 raskin-sa. All rights reserved. // -import Foundation + +import RealmSwift + +class CommonRepository { + + func getAllfromTable (vkObject: VKObjects) throws -> Results{ + var object:Object.Type + + let realm = try Realm() + switch vkObject{ + case .VKGroup: + object = VKGroupRealm.self + case .VKPhoto: + object = VKPhotosRealm.self + case .VKUser: + object = VKUserRealm.self + }//switch + // tmpdefault = realm.objects(object.self).filter("FALSEPREDICATE") + //MARK: падает при повторном вызове после search + return realm.objects(object.self) as! Results + }//func getAllfromTable + + // MARK: - Deletion + func deleteVKObject(object: VKObjects){ + let realm = try! Realm() + + switch object{ + case .VKUser: + try! realm.write { + return realm.delete(realm.objects(VKUserRealm.self)) + } + case .VKGroup: + try! realm.write { + return realm.delete(realm.objects(VKGroupRealm.self)) + } + case .VKPhoto: + try! realm.write { + realm.delete(realm.objects(VKPhotosRealm.self)) + } + }//switch object + + }//deleteVKObject(object: VKObjects) + + func deleteAllRealmTables () { + let realm = try! Realm() + try! realm.write { + realm.deleteAll() + } + + } +} //class CommonRepository + diff --git a/!main project/GeekbrainsUI/Database/Realm/RealmRepository/GroupRepository.swift b/!main project/GeekbrainsUI/Database/Realm/RealmRepository/GroupRepository.swift index af7236e..63e6750 100644 --- a/!main project/GeekbrainsUI/Database/Realm/RealmRepository/GroupRepository.swift +++ b/!main project/GeekbrainsUI/Database/Realm/RealmRepository/GroupRepository.swift @@ -6,4 +6,55 @@ // Copyright © 2020 raskin-sa. All rights reserved. // -import Foundation +import RealmSwift + +protocol GroupsSource{ + func getAllGroups() throws -> Results + func addGroups (groups:[VKGroup]) + func searchGroups(name: String) throws -> Results +} + +class GroupRepository: GroupsSource { + + var localCommonRepository = CommonRepository() + + func getAllGroups() throws -> Results{ + let realm = try Realm() + do { + return try realm.objects(VKGroupRealm.self) as Results + // return try localCommonRepository.getAllfromTable(vkObject: .VKGroup) as Results + } catch { + throw error + } + }//func getAllUsers + + func addGroups (groups:[VKGroup]) { + do { + let realm = try Realm() + try realm.write { + var groupsToAdd = [VKGroupRealm]() + groups.forEach{ + group in + let groupRealm = VKGroupRealm() + groupRealm.id = group.id + groupRealm.groupName = group.groupName + groupRealm.avatarPath = group.avatarPath + groupsToAdd.append(groupRealm) + }//groups.forEach + realm.add(groupsToAdd, update: .modified) + }//realm.write + // print(realm.objects(VKGroupRealm.self)) + }catch{ + print(error) + } + }// func addGroups + + func searchGroups(name: String) throws -> Results { + do { + let realm = try Realm() + return realm.objects(VKGroupRealm.self).filter("groupName CONTAINS[c] %@", name) + } catch{ + throw error + } + }//func searchUsers +}//class class GroupRepository diff --git a/!main project/GeekbrainsUI/Database/Realm/RealmRepository/LoginRepository.swift b/!main project/GeekbrainsUI/Database/Realm/RealmRepository/LoginRepository.swift new file mode 100644 index 0000000..5432646 --- /dev/null +++ b/!main project/GeekbrainsUI/Database/Realm/RealmRepository/LoginRepository.swift @@ -0,0 +1,9 @@ +// +// LoginRepository.swift +// GeekbrainsUI +// +// Created by raskin-sa on 19/02/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + +import Foundation diff --git a/!main project/GeekbrainsUI/Database/Realm/RealmRepository/PhotoRepository.swift b/!main project/GeekbrainsUI/Database/Realm/RealmRepository/PhotoRepository.swift index 2a6d0b4..f68830a 100644 --- a/!main project/GeekbrainsUI/Database/Realm/RealmRepository/PhotoRepository.swift +++ b/!main project/GeekbrainsUI/Database/Realm/RealmRepository/PhotoRepository.swift @@ -6,4 +6,61 @@ // Copyright © 2020 raskin-sa. All rights reserved. // -import Foundation +import RealmSwift + +protocol PhotosRepositorySource: class { + func getAllPhotos() throws -> Results + func getPhotosByUser(userID: Int) throws -> Results + func addPhotos (photos:[VKPhoto]) +} + +class PhotoRepository: PhotosRepositorySource { + + var localCommonRepository = CommonRepository() + + func getAllPhotos() throws -> Results{ + let realm = try Realm() + do { + return try realm.objects(VKPhotosRealm.self) as Results + // return try (localCommonRepository.getAllfromTable(vkObject: .VKPhoto) as? Results)! + } catch { + throw error + } + }//func getAllPhotos + + func getPhotosByUser(userID: Int) throws -> Results{ + let realm = try Realm() + do { + return try realm.objects(VKPhotosRealm.self).filter("ownerID == %@", userID) as Results + } catch { + throw error + } + }//func PhotosByUser + + func addPhotos (photos:[VKPhoto]) { + do { + let realm = try! Realm() + try realm.write { + var photosToAdd = [VKPhotosRealm]() + photos.forEach{ + photo in + let photosRealm = VKPhotosRealm() + + photosRealm.id = photo.id + photosRealm.likes = photo.likes.map{ $0.toRealm()} + photosRealm.sizes.append(objectsIn: photo.sizes.map{ $0.toRealm() }) + photosRealm.albumID = photo.albumID + photosRealm.ownerID = photo.ownerID + photosRealm.text = photo.text + photosToAdd.append(photosRealm) + }//groups.forEach + + realm.add(photosToAdd, update: .modified) + }//realm.write + // print(realm.objects(VKPhotosRealm.self)) + }catch{ + print(error) + } + }// func addGroups + +}//class class GroupRepository diff --git a/!main project/GeekbrainsUI/Database/Realm/RealmRepository/UserRepository.swift b/!main project/GeekbrainsUI/Database/Realm/RealmRepository/UserRepository.swift index d2c121b..6763e45 100644 --- a/!main project/GeekbrainsUI/Database/Realm/RealmRepository/UserRepository.swift +++ b/!main project/GeekbrainsUI/Database/Realm/RealmRepository/UserRepository.swift @@ -6,4 +6,75 @@ // Copyright © 2020 raskin-sa. All rights reserved. // -import Foundation +import RealmSwift + +protocol FriendsSource{ + func getAllUsers() throws -> Results + func getUser(id:Int, lastName: String?, avatarPath: String?) -> VKUserRealm? + func addUsers(users:[VKUser]) + func searchUsers(name: String) throws -> Results +} + +class UserRepository: FriendsSource { + + var localCommonRepository = CommonRepository() + + func getAllUsers() throws -> Results{ + let realm = try Realm() + do { + // return try localCommonRepository.getAllfromTable(vkObject: .VKUser) as Results + return try realm.objects(VKUserRealm.self) as Results + } catch { + throw error + } + }//func getAllUsers + + //MARK: Поиск пользователя по параметру на выбор + func getUser(id:Int, lastName: String?, avatarPath: String?) -> VKUserRealm? { + + var filter: String + let realm = try! Realm() + + if id != 0 { + filter = "id == %@" + return realm.objects(VKUserRealm.self).filter(filter, id).first + } else + { + guard let localavatarPath = avatarPath else {return nil} + filter = "avatarPath == %@" + return realm.objects(VKUserRealm.self).filter(filter, localavatarPath).first + } + } + + func addUsers(users:[VKUser]){ + do { + let realm = try Realm() + try realm.write { + var usersToAdd = [VKUserRealm]() + users.forEach { user in + let userRealm = VKUserRealm() + userRealm.id = user.id + userRealm.firstName = user.firstName + userRealm.lastName = user.lastName + userRealm.userName = String(user.firstName + " " + user.lastName) + userRealm.avatarPath = user.avatarPath + usersToAdd.append(userRealm) + } + realm.add(usersToAdd, update: .modified) + } + // print(realm.objects(VKUserRealm.self)) + }catch{ + print(error) + } + }//func addUsers + + func searchUsers(name: String) throws -> Results { + do { + let realm = try Realm() + return realm.objects(VKUserRealm.self).filter("userName CONTAINS[c] %@", name) + } catch{ + throw error + } + }//func searchUsers + +}//class UserRepository diff --git a/!main project/GeekbrainsUI/Database/VKDatabase.xcdatamodeld/VKDatabase.xcdatamodel/contents b/!main project/GeekbrainsUI/Database/VKDatabase.xcdatamodeld/VKDatabase.xcdatamodel/contents index b9a23d7..69e9bb7 100644 --- a/!main project/GeekbrainsUI/Database/VKDatabase.xcdatamodeld/VKDatabase.xcdatamodel/contents +++ b/!main project/GeekbrainsUI/Database/VKDatabase.xcdatamodeld/VKDatabase.xcdatamodel/contents @@ -1,4 +1,14 @@ - + + + + + + + + + + + \ No newline at end of file diff --git a/!main project/GeekbrainsUI/GeekBrainsUI.xcdatamodeld/GeekBrainsUI.xcdatamodel/contents b/!main project/GeekbrainsUI/GeekBrainsUI.xcdatamodeld/GeekBrainsUI.xcdatamodel/contents new file mode 100644 index 0000000..b1fe02f --- /dev/null +++ b/!main project/GeekbrainsUI/GeekBrainsUI.xcdatamodeld/GeekBrainsUI.xcdatamodel/contents @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/!main project/GeekbrainsUI/Info.plist b/!main project/GeekbrainsUI/Info.plist new file mode 100644 index 0000000..7e0321a --- /dev/null +++ b/!main project/GeekbrainsUI/Info.plist @@ -0,0 +1,69 @@ + + + + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/!main project/GeekbrainsUI/Model/CommonResponse.swift b/!main project/GeekbrainsUI/Model/CommonResponse.swift deleted file mode 100644 index d804609..0000000 --- a/!main project/GeekbrainsUI/Model/CommonResponse.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// CommonResponse.swift -// GeekbrainsUI -// -// Created by raskin-sa on 24/01/2020. -// Copyright © 2020 raskin-sa. All rights reserved. -// - -import Foundation - -struct CommonResponse: Decodable { - var response: CommonResponseArray -} - -struct CommonResponseArray: Decodable{ - var count: Int - var items: [T] -} diff --git a/!main project/GeekbrainsUI/Model/FileCacher.swift b/!main project/GeekbrainsUI/Model/FileCacher.swift new file mode 100644 index 0000000..9a4f6aa --- /dev/null +++ b/!main project/GeekbrainsUI/Model/FileCacher.swift @@ -0,0 +1,37 @@ +// +// FileCacher.swift +// GeekbrainsUI +// +// Created by raskin-sa on 21/01/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + +import Foundation + +enum CacheWriterStatus{ + case success + case fileExist + case memoryException +} + +class Cacher { + let fileManager = FileManager.default + + func writeFile(file: Data, filename: String) -> CacheWriterStatus{ + let documentsDirectory = fileManager.urls(for: .documentDirectory, + in: .userDomainMask).first! + + let filePath = documentsDirectory.appendingPathComponent(filename) + + if !fileManager.fileExists(atPath: filePath.path) { + do{ + try file.write(to: filePath) + }catch{ + return .memoryException + } + } else {return .fileExist} + + return .success + }//func writeFile + +}//class Cacher diff --git a/!main project/GeekbrainsUI/Model/Friend.swift b/!main project/GeekbrainsUI/Model/Friend.swift new file mode 100644 index 0000000..a852223 --- /dev/null +++ b/!main project/GeekbrainsUI/Model/Friend.swift @@ -0,0 +1,23 @@ +// +// Friend.swift +// GeekbrainsUI +// +// Created by raskin-sa on 08/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +import Foundation + + + +/**** +прошлая версия. Заполнение не из vk.com + *****/ + +struct Friend{ + var userName:String + var avatarPath:String + var isOnline:Int + var id:Int +} + + + diff --git a/!main project/GeekbrainsUI/Model/Group.swift b/!main project/GeekbrainsUI/Model/Group.swift new file mode 100644 index 0000000..4678cb5 --- /dev/null +++ b/!main project/GeekbrainsUI/Model/Group.swift @@ -0,0 +1,28 @@ +// +// Group.swift +// GeekbrainsUI +// +// Created by raskin-sa on 08/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import Foundation + +struct Group{ + var id: Int + var groupName:String + var avatarPath:String + +} + +//var myGroup:[Group] = [ +//Group(groupName:"Одноклассники", avatarPath:"Group2", id:1), +//Group(groupName:"Семья", avatarPath:"Group1", id:2), +//Group(groupName:"Рязань-2000", avatarPath:"2", id:3), +//Group(groupName:"Любители коров", avatarPath:"3", id:4) +//] + + + + + diff --git a/!main project/GeekbrainsUI/Model/News.swift b/!main project/GeekbrainsUI/Model/News.swift new file mode 100644 index 0000000..0ba67fa --- /dev/null +++ b/!main project/GeekbrainsUI/Model/News.swift @@ -0,0 +1,55 @@ +// +// News.swift +// GeekbrainsUI +// +// Created by raskin-sa on 16/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import Foundation + +struct News{ + var id:Int + var newsPath:String + var newsText:String + var imagePath:String + var writer: String +} + +var myNews: [News] = +[ + News(id:1, newsPath:"www.yandex.ru", + newsText: "Opel снова начал продажи в России после ухода с рынка, сообщает РИА Новости. С 16 декабря первые модели немецкого автопроизводителя поступили в автосалоны официальных дилеров в крупных городах РФ.", + imagePath:"news1", + writer: "Петр Васильев"), + + News(id:2, + newsPath:"www.yandex.ru", + newsText: "Мэрия Москвы опубликовала проект территориальной схемы вывоза мусора до 2029 года, который предполагает вывоз части отходов в соседние регионы. Об этом 16 декабря сообщило РИА Новости.Трансфигурация с приставкой эко: Подмосковье удвоит объем вторичной переработки коммунальных отходов к 2020 году. Планируется вывозить около 15 млн т. мусора за десять лет в Калужскую область и около 1 млн т. — во Владимирскую область. Остальной мусор будет вывозиться на комплексы переработки отходов в Московской области. \n С 2022 года мусор также будет вывозиться на четыре мусоросжигательных завода в Наро-Фоминске, Солнечногорске, Воскресенске и Ногинске, которые планирует построить дочерняя компания «Ростеха» РТ-Инвест. В общей сложности за 10 лет в Московскую область планируется вывезти около 34 млн т. мусора. Ранее, 19 ноября, появились сообщения о том, что москвичи поддержали переход столицы на раздельный сбор мусора. Первый этап перехода на раздельный сбор мусора начнется в Москве в 2020 году. На контейнерных площадках возле домов появятся специальные емкости для вторичного сырья. Синие — для сбора перерабатываемого мусора, серые — для смешанных отходов. Планируется, что к системе сортировки мусора подключатся учреждения социальной сферы, магазины и рынки.", + imagePath:"news2", + writer: "Николай Сидоров"), + + News(id:3, + newsPath:"www.yandex.ru", + newsText: "Премьер-министр Финляндии Санна Марин гордится тем, что в ее стране даже продавец может стать главой правительства. Об этом она заявила, комментируя слова главы МВД Эстонии Марта Хельме, раскритиковавшего ее и членов ее кабинета.", + imagePath:"news3", + writer: "Роман Верещагин"), + + News(id:6, + newsPath:"www.yandex.ru", + newsText: "Rambler Group, которая владеет эксклюзивными правами на показ матчей Английской премьер-лиги (АПЛ) по футболу, намерена взыскать 180,345 млрд руб. (около $2,9 млрд) со стримингового сервиса Twitch.", + imagePath:"news6", + writer: "Петр Васильев"), + + News(id:4, + newsPath:"www.yandex.ru", + newsText: "За 2019 год Россия отгрузила продукции военно-технического комплекса для продажи зарубежным клиентам на сумму в $13 млрд. Об этом заявил президент России Владимир Путин в ходе заседания комиссии по вопросам военно-технического сотрудничества Российской Федерации с иностранными государствами в понедельник, 16 декабря.Отмечается, что нынешний результат в разы лучше показателей 2018 года. Путин в своем выступлении ", + imagePath:"news4", + writer: "Леонид Андреев"), + + News(id:5, + newsPath:"www.yandex.ru", + newsText: "Санкт-Петербургский городской суд отказался переводить под домашний арест историка Олега Соколова. Таким образом, в следственном изоляторе как минимум до 8 января.", + imagePath:"news5", + writer: "Василий Кукушкин"), +] diff --git a/!main project/GeekbrainsUI/Model/Realm/VKGroupRealm.swift b/!main project/GeekbrainsUI/Model/Realm/VKGroupRealm.swift new file mode 100644 index 0000000..594a197 --- /dev/null +++ b/!main project/GeekbrainsUI/Model/Realm/VKGroupRealm.swift @@ -0,0 +1,27 @@ +// +// VKGroupRealm.swift +// GeekbrainsUI +// +// Created by raskin-sa on 22/01/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + +import RealmSwift + +class VKGroupRealm: Object { + @objc dynamic var id = 0 + @objc dynamic var groupName = "" + @objc dynamic var avatarPath = "" + + override class func primaryKey() -> String? { + return "id" + } + + //создание индекса + override class func indexedProperties() -> [String] { + return["groupName"] + } + func toModel()->VKGroup{ + return VKGroup(id: id, groupName: groupName, avatarPath: avatarPath) + } +} diff --git a/!main project/GeekbrainsUI/Model/Realm/VKUserRealm.swift b/!main project/GeekbrainsUI/Model/Realm/VKUserRealm.swift new file mode 100644 index 0000000..7897bb3 --- /dev/null +++ b/!main project/GeekbrainsUI/Model/Realm/VKUserRealm.swift @@ -0,0 +1,39 @@ +// +// VKUserRealm.swift +// GeekbrainsUI +// +// Created by raskin-sa on 22/01/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + +import RealmSwift + +class VKUserRealm: Object { + @objc dynamic var id = 0 + @objc dynamic var lastName = "" + @objc dynamic var firstName = "" + @objc dynamic var userName = "" + @objc dynamic var avatarPath = "" + @objc dynamic var photo = "" + @objc dynamic var deactivated: String? + @objc dynamic var isOnline = 0 + + override class func primaryKey() -> String? { + return "id" + } + + //создание индекса + override class func indexedProperties() -> [String] { + return["lastName","deactivated"] + } + + var VKUserList = List() + + func toModel() -> VKUser { + return VKUser(lastName: lastName, firstName: firstName, avatarPath: avatarPath, isOnline: isOnline, id: id) + } +}//class VKUserRealm + + + + diff --git a/!main project/GeekbrainsUI/Model/Realm/VKWorkWithDBRealm.swift b/!main project/GeekbrainsUI/Model/Realm/VKWorkWithDBRealm.swift new file mode 100644 index 0000000..2c695fe --- /dev/null +++ b/!main project/GeekbrainsUI/Model/Realm/VKWorkWithDBRealm.swift @@ -0,0 +1,18 @@ +// +// VKWorkWithDBRealm.swift +// GeekbrainsUI +// +// Created by raskin-sa on 22/01/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + +import Foundation +import RealmSwift + +enum VKObjects{ + case VKUser + case VKGroup + case VKPhoto +} + + diff --git a/!main project/GeekbrainsUI/Model/Singleton.swift b/!main project/GeekbrainsUI/Model/Singleton.swift new file mode 100644 index 0000000..54fce98 --- /dev/null +++ b/!main project/GeekbrainsUI/Model/Singleton.swift @@ -0,0 +1,17 @@ +// +// Singleton.swift +// GeekbrainsUI +// +// Created by raskin-sa on 15/01/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + + + +class Session { + static let shared = Session() + private init () {} + + var token = "" + var userId = "0" +} diff --git a/!main project/GeekbrainsUI/Model/VKApi.swift b/!main project/GeekbrainsUI/Model/VKApi.swift new file mode 100644 index 0000000..a432739 --- /dev/null +++ b/!main project/GeekbrainsUI/Model/VKApi.swift @@ -0,0 +1,112 @@ +// +// VKApi.swift +// GeekbrainsUI +// +// Created by raskin-sa on 15/01/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + +import WebKit +import Alamofire + +enum RequestError: Error { + case failedRequest(message: String) + case decodableError +} + +enum WebRequestEntities{ + case VKUser + case VKGroup + case VKPhoto +} + + +class VKAPi { + let vkURL = "https://api.vk.com/method/" + //https://jsonplaceholder.typicode.com/users + +// typealias Out = Swift.Result + + func sendRequest(url:String, method: HTTPMethod = .get, params: Parameters, webRequestEntity: WebRequestEntities, completion: @escaping (Out<[T],Error>)-> Void){ + + Alamofire.request(url,method: method,parameters: params).responseData { (result) in + guard let data = result.value else {return} + + do{ + let result = try JSONDecoder().decode(CommonResponse.self, from: data) + //сохраняем в глобальные переменные то, что пришло из Web + switch webRequestEntity{ + case .VKUser: + webVKUsers = result.response.items as! [VKUser] + case .VKGroup: + webVKGroups = result.response.items as! [VKGroup] + case .VKPhoto: + webVKPhotos = result.response.items as! [VKPhoto] + }//switch + + completion(.success(result.response.items)) + }catch{ + completion(.failure(error)) + } + } + }//func sendRequest + + //Получение списка друзей + func getFriendList(token: String,completion: @escaping (Out<[VKUser],Error>)-> Void) { + + let requestURL = vkURL + "friends.get" + let params = ["v": "5.103", + "access_token":token, + "order":"name", + "fields":"online,city,domain,photo_50"] + + sendRequest(url: requestURL, params: params, webRequestEntity: WebRequestEntities.VKUser ) {completion($0)} + + }//func getFriendList + + //Получение фотографий человека + func getPhotosList(token: String, userId: Int, completion: @escaping (Out<[VKPhoto], Error>)-> Void) { + let requestURL = vkURL + "photos.get" + let params = ["v": "5.103", + "access_token":token, + "owner_id":String(userId), + "extended":"1", + "album_id":"wall" + ] + + sendRequest(url: requestURL, params: params, webRequestEntity: WebRequestEntities.VKPhoto) {completion($0)} + }//func getPhotosList + + //Получение групп текущего пользователя + func getGroupsList(token: String, userId: String, completion: @escaping (Out<[VKGroup], Error>)-> Void) { + let requestURL = vkURL + "groups.get" + let params = ["v": "5.103", + "access_token":token, + "user_id":userId, + "extended":"1" + ] + + sendRequest(url: requestURL, params: params, webRequestEntity: WebRequestEntities.VKGroup) {completion($0)} + }//func getGroupsList + + //Получение групп по поисковому запросу; + func searchGroups (token: String, query: String, completion: () -> ()){ + let requestURL = vkURL + "groups.search" + let params = ["v": "5.103", + "access_token":token, + "q":query, + "type":"group", + "count":"3", + "country_id":"1" //Россия + ] + + Alamofire.request(requestURL, + method: .post, + parameters: params).responseJSON(completionHandler: { (response) in + (print("\nПолучение групп по поисковому запросу \(query)"),print(response.value!)) + }) + }//func searchGroups + +}//class VKAPi + + diff --git a/!main project/GeekbrainsUI/Model/VKGroup.swift b/!main project/GeekbrainsUI/Model/VKGroup.swift new file mode 100644 index 0000000..8102957 --- /dev/null +++ b/!main project/GeekbrainsUI/Model/VKGroup.swift @@ -0,0 +1,75 @@ +// +// VKGroup.swift +// GeekbrainsUI +// +// Created by raskin-sa on 19/01/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + + + +// MARK: - Group +struct VKGroup: Codable { + let id: Int + let groupName:String + let avatarPath:String + // let screenName: String + // let isClosed: Int + // let type: String + // let isAdmin, isMember, isAdvertiser: Int + // + // let photo100, photo200: String + + enum CodingKeys: String, CodingKey { + case id + case groupName = "name" + case avatarPath = "photo_50" + // case screenName = "screen_name" + // case isClosed = "is_closed" + // case type + // case isAdmin = "is_admin" + // case isMember = "is_member" + // case isAdvertiser = "is_advertiser" + // + // case photo100 = "photo_100" + // case photo200 = "photo_200" + } +}//struct VKGroup: + +struct GroupResponse: Codable { + let response: GroupResponseData +} + +// MARK: - Response +struct GroupResponseData: Codable { + let count: Int + let items: [VKGroup] +} + +var webVKGroups = [VKGroup]() + +//extension GroupResponse{ +// func getGroups(groups: [VKGroup]) -> [Group]{ +// //Функция имеет два назначения: +// //1. Сохранить данные пришедшие из Web в экземпляре структуры +// //2. Преобразовать модель данных из web-запроса в нужную нам модель для отображения, если они различаются +// var myGroups = [Group]() +// +// for a in 0...groups.count-1 { +// myGroups.append(Group(id:groups[a].id, groupName:groups[a].groupName, avatarPath:groups[a].avatarPath )) +// } +// return myGroups +// } +//} + func convertGroups(groups: [VKGroup]) -> [Group]{ + //Функция имеет два назначения: + //1. Сохранить данные пришедшие из Web в экземпляре структуры + //2. Преобразовать модель данных из web-запроса в нужную нам модель для отображения, если они различаются + var myGroups = [Group]() + + for a in 0...groups.count-1 { + myGroups.append(Group(id:groups[a].id, groupName:groups[a].groupName, avatarPath:groups[a].avatarPath )) + } + return myGroups + } + diff --git a/!main project/GeekbrainsUI/Model/VKGroupJSONSerialistion.swift b/!main project/GeekbrainsUI/Model/VKGroupJSONSerialistion.swift new file mode 100644 index 0000000..b4578d6 --- /dev/null +++ b/!main project/GeekbrainsUI/Model/VKGroupJSONSerialistion.swift @@ -0,0 +1,63 @@ +// +// VKGroupJSONSerialistion.swift +// GeekbrainsUI +// +// Created by raskin-sa on 19/01/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + +// +//РЕАЛИЗАЦИЯ вэб-запроса не через протокол Codable а через JSONSerialization +// + +import Alamofire + + let vkURLJSON = "https://api.vk.com/method/" + +struct VKGroupJSON: Codable { +let id: Int + let name: String + + init (json: [String:Any]){ + self.id = json["id"] as! Int + self.name = json["name"] as! String + } +} + +//Получение групп текущего пользователя + func getGroupsListJSON(token: String, userId: String){ + let requestURL = vkURLJSON + "groups.get" + let params = ["v": "5.103", + "access_token":token, + "user_id":userId, + "extended":"1" + ] + + + Alamofire.request(requestURL, + method: .post, + parameters: params).responseData { (response) in + + guard response.value != nil else{ + return + } + + + + _ = String(bytes: response.data!, encoding: .utf8) + let jsonObject = try? JSONSerialization.jsonObject(with: response.value!, options: .mutableContainers) + + let json = jsonObject as! [String: Any] + let response = json["response"] as! [String: Any] + var results = [VKGroupJSON]() + + for value in response["items"] as! [Any]{ + let group = value as! [String: Any] + results.append(VKGroupJSON(json: group)) + } + print ("Получение групп по текущему запросу пользователя и парсинг") + + print(results) + + } + }//func getGroupsList diff --git a/!main project/GeekbrainsUI/Model/VKGroupWithNestedContainers.swift b/!main project/GeekbrainsUI/Model/VKGroupWithNestedContainers.swift new file mode 100644 index 0000000..553223c --- /dev/null +++ b/!main project/GeekbrainsUI/Model/VKGroupWithNestedContainers.swift @@ -0,0 +1,43 @@ +// +// VKGroupWithNestedContainers.swift +// GeekbrainsUI +// +// Created by raskin-sa on 21/01/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + +import Foundation + + +struct GroupVK: Decodable { + var id: Int = 0 + var name: String = "" + var isAdmin: Int = 0 + var lat: Double = 0.0 + + + + enum CodingKeys: String, CodingKey { + case id + case name + case isAdmin = "is_admin" + case address + } + + enum AddressKeys: String, CodingKey { + case geo + } + + enum GeoKeys: String, CodingKey{ + case lat + } + + init (from decoder: Decoder) throws { + let mainContainer = try decoder.container(keyedBy: CodingKeys.self) + let addressContainer = try mainContainer.nestedContainer(keyedBy: AddressKeys.self, forKey: .address) + let geoContainer = try addressContainer.nestedContainer(keyedBy: GeoKeys.self, forKey: .geo) + self.lat = try geoContainer.decode(Double.self, forKey: .lat) + self.id = try mainContainer.decode(Int.self, forKey: .id) + } +} + diff --git a/!main project/GeekbrainsUI/Model/VKLogin.swift b/!main project/GeekbrainsUI/Model/VKLogin.swift new file mode 100644 index 0000000..22ab144 --- /dev/null +++ b/!main project/GeekbrainsUI/Model/VKLogin.swift @@ -0,0 +1,9 @@ +// +// VKLogin.swift +// GeekbrainsUI +// +// Created by raskin-sa on 19/02/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + +import Foundation diff --git a/!main project/GeekbrainsUI/Model/VKPhotos.swift b/!main project/GeekbrainsUI/Model/VKPhotos.swift new file mode 100644 index 0000000..0676353 --- /dev/null +++ b/!main project/GeekbrainsUI/Model/VKPhotos.swift @@ -0,0 +1,86 @@ +// +// VKPhotos.swift +// GeekbrainsUI +// +// Created by raskin-sa on 20/01/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + +import Foundation + +// MARK: - Item +struct VKPhoto: Codable { + let id, albumID, ownerID: Int + let sizes: [VKPhotoSizes] + // let smallPhoto, midPhoto, bigPhoto: String + let text: String + let date, postID: Int? + let likes: VKPhotoLikes? + let reposts: PhotoReposts? + let lat, long: Double? + + enum CodingKeys: String, CodingKey { + case id + case albumID = "album_id" + case ownerID = "owner_id" + case sizes + case text, date + case postID = "post_id" + case likes, reposts, lat, long + } +} + +// MARK: - Size +struct VKPhotoSizes: Codable { + let type: String + let url: String + let width, height: Int + + func toRealm() -> PhotoSizesRealm { + let photoRealm = PhotoSizesRealm() + photoRealm.url = url + photoRealm.type = type + photoRealm.width = width + photoRealm.height = height + + return photoRealm + } +} + +// MARK: - Likes +struct VKPhotoLikes: Codable { + let userLikes, count: Int + + enum CodingKeys: String, CodingKey { + case userLikes = "user_likes" + case count + } + + func toRealm() -> PhotoLikesRealm { + let photoLikeRealm = PhotoLikesRealm() + photoLikeRealm.userLikes = userLikes + photoLikeRealm.count = count + + return photoLikeRealm + } +}//struct VKPhotoLikes: Codable + +// MARK: - Reposts +struct PhotoReposts: Codable { + let count: Int +} + +// MARK: - Welcome +struct PhotosResponse: Codable { + let response: PhotosResponseData +} + +// MARK: - Response +struct PhotosResponseData: Codable { + let count: Int + let items: [VKPhoto] +} + +var webVKPhotos = [VKPhoto]() + + diff --git a/!main project/GeekbrainsUI/Model/VKUser.swift b/!main project/GeekbrainsUI/Model/VKUser.swift new file mode 100644 index 0000000..d8edb67 --- /dev/null +++ b/!main project/GeekbrainsUI/Model/VKUser.swift @@ -0,0 +1,63 @@ +// +// VKUser.swift +// GeekbrainsUI +// +// Created by raskin-sa on 19/01/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + + + +// MARK: - Response +struct VKUser: Codable { + var lastName:String + var firstName:String + var avatarPath:String + var isOnline:Int + var id:Int + + + enum CodingKeys: String, CodingKey { + case lastName = "last_name" + case firstName = "first_name" + case avatarPath = "photo_50" + case isOnline = "online" + case id + + } +} + +struct UserResponse: Codable { + let response: UserResponseData +} + +// MARK: - Welcome +struct UserResponseData: Codable { + let count: Int + let items: [VKUser] +} + +var webVKUsers = [VKUser]() + + func convertFriends(users: [VKUser]) -> [Friend]{ + //Функция имеет два назначения: + //1. Сохранить данные пришедшие из Web в экземпляре структуры + //2. Преобразовать модель данных из web-запроса в нужную нам модель для отображения + var myFriends = [Friend]() + + if users.count == 0 {return myFriends} + for a in 0...users.count-1 { + myFriends.append(Friend(userName: users[a].firstName + " " + users[a].lastName, avatarPath: users[a].avatarPath, isOnline: users[a].isOnline, id: users[a].id)) + } + return myFriends + } + + func convertFriend(user: VKUser) -> Friend{ + //Функция имеет два назначения: + //1. Сохранить данные пришедшие из Web в экземпляре структуры + //2. Преобразовать модель данных из web-запроса в нужную нам модель для отображения + + return Friend(userName: user.firstName + " " + user.lastName, avatarPath: user.avatarPath, isOnline: user.isOnline, id: user.id) + } + + diff --git a/!main project/GeekbrainsUI/Model/VKWall.swift b/!main project/GeekbrainsUI/Model/VKWall.swift new file mode 100644 index 0000000..df734d1 --- /dev/null +++ b/!main project/GeekbrainsUI/Model/VKWall.swift @@ -0,0 +1,414 @@ +// +// VKWall.swift +// GeekbrainsUI +// +// Created by raskin-sa on 06/02/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + +import Foundation + + +// MARK: - Welcome +struct VKWallResponse: Codable { + let response: VKWallResponseData +} + +// MARK: - Response +struct VKWallResponseData: Codable { + let count: Int + let items: [WKWall] + let profiles: [Profile] + let groups: [JSONAny] +} + +// MARK: - Item +struct WKWall: Codable { + let id, fromID, ownerID, date: Int + let postType, text: String + let canDelete, canPin: Int + let canArchive, isArchived: Bool + let attachments: [Attachment] + let postSource: PostSource + let comments: Comments + let likes: Likes + let reposts: Reposts + let views: Views + let isFavorite: Bool + + enum CodingKeys: String, CodingKey { + case id + case fromID = "from_id" + case ownerID = "owner_id" + case date + case postType = "post_type" + case text + case canDelete = "can_delete" + case canPin = "can_pin" + case canArchive = "can_archive" + case isArchived = "is_archived" + case attachments + case postSource = "post_source" + case comments, likes, reposts, views + case isFavorite = "is_favorite" + } +} + +// MARK: - Attachment +struct Attachment: Codable { + let type: String + let photo: Photo +} + +// MARK: - Photo +struct Photo: Codable { + let id, albumID, ownerID: Int + let sizes: [Size] + let text: String + let date, postID: Int + + enum CodingKeys: String, CodingKey { + case id + case albumID = "album_id" + case ownerID = "owner_id" + case sizes, text, date + case postID = "post_id" + } +} + +// MARK: - Size +struct Size: Codable { + let type: String + let url: String + let width, height: Int +} + +// MARK: - Comments +struct Comments: Codable { + let count, canPost: Int + let groupsCanPost: Bool + let canClose: Int + + enum CodingKeys: String, CodingKey { + case count + case canPost = "can_post" + case groupsCanPost = "groups_can_post" + case canClose = "can_close" + } +} + +// MARK: - Likes +struct Likes: Codable { + let count, userLikes, canLike, canPublish: Int + + enum CodingKeys: String, CodingKey { + case count + case userLikes = "user_likes" + case canLike = "can_like" + case canPublish = "can_publish" + } +} + +// MARK: - PostSource +struct PostSource: Codable { + let type, platform, data: String +} + +// MARK: - Reposts +struct Reposts: Codable { + let count, userReposted: Int + + enum CodingKeys: String, CodingKey { + case count + case userReposted = "user_reposted" + } +} + +// MARK: - Views +struct Views: Codable { + let count: Int +} + +// MARK: - Profile +struct Profile: Codable { + let id: Int + let firstName, lastName: String + let isClosed, canAccessClosed: Bool + let sex: Int + let screenName: String + let photo50, photo100: String + let online, onlineApp, onlineMobile: Int + let onlineInfo: OnlineInfo + + enum CodingKeys: String, CodingKey { + case id + case firstName = "first_name" + case lastName = "last_name" + case isClosed = "is_closed" + case canAccessClosed = "can_access_closed" + case sex + case screenName = "screen_name" + case photo50 = "photo_50" + case photo100 = "photo_100" + case online + case onlineApp = "online_app" + case onlineMobile = "online_mobile" + case onlineInfo = "online_info" + } +} + +// MARK: - OnlineInfo +struct OnlineInfo: Codable { + let visible, isOnline: Bool + let appID: Int + let isMobile: Bool + + enum CodingKeys: String, CodingKey { + case visible + case isOnline = "is_online" + case appID = "app_id" + case isMobile = "is_mobile" + } +} + +// MARK: - Encode/decode helpers + +class JSONNull: Codable, Hashable { + + public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool { + return true + } + + public var hashValue: Int { + return 0 + } + + public init() {} + + public required init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + if !container.decodeNil() { + throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull")) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encodeNil() + } +} + +class JSONCodingKey: CodingKey { + let key: String + + required init?(intValue: Int) { + return nil + } + + required init?(stringValue: String) { + key = stringValue + } + + var intValue: Int? { + return nil + } + + var stringValue: String { + return key + } +} + +class JSONAny: Codable { + + let value: Any + + static func decodingError(forCodingPath codingPath: [CodingKey]) -> DecodingError { + let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Cannot decode JSONAny") + return DecodingError.typeMismatch(JSONAny.self, context) + } + + static func encodingError(forValue value: Any, codingPath: [CodingKey]) -> EncodingError { + let context = EncodingError.Context(codingPath: codingPath, debugDescription: "Cannot encode JSONAny") + return EncodingError.invalidValue(value, context) + } + + static func decode(from container: SingleValueDecodingContainer) throws -> Any { + if let value = try? container.decode(Bool.self) { + return value + } + if let value = try? container.decode(Int64.self) { + return value + } + if let value = try? container.decode(Double.self) { + return value + } + if let value = try? container.decode(String.self) { + return value + } + if container.decodeNil() { + return JSONNull() + } + throw decodingError(forCodingPath: container.codingPath) + } + + static func decode(from container: inout UnkeyedDecodingContainer) throws -> Any { + if let value = try? container.decode(Bool.self) { + return value + } + if let value = try? container.decode(Int64.self) { + return value + } + if let value = try? container.decode(Double.self) { + return value + } + if let value = try? container.decode(String.self) { + return value + } + if let value = try? container.decodeNil() { + if value { + return JSONNull() + } + } + if var container = try? container.nestedUnkeyedContainer() { + return try decodeArray(from: &container) + } + if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self) { + return try decodeDictionary(from: &container) + } + throw decodingError(forCodingPath: container.codingPath) + } + + static func decode(from container: inout KeyedDecodingContainer, forKey key: JSONCodingKey) throws -> Any { + if let value = try? container.decode(Bool.self, forKey: key) { + return value + } + if let value = try? container.decode(Int64.self, forKey: key) { + return value + } + if let value = try? container.decode(Double.self, forKey: key) { + return value + } + if let value = try? container.decode(String.self, forKey: key) { + return value + } + if let value = try? container.decodeNil(forKey: key) { + if value { + return JSONNull() + } + } + if var container = try? container.nestedUnkeyedContainer(forKey: key) { + return try decodeArray(from: &container) + } + if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key) { + return try decodeDictionary(from: &container) + } + throw decodingError(forCodingPath: container.codingPath) + } + + static func decodeArray(from container: inout UnkeyedDecodingContainer) throws -> [Any] { + var arr: [Any] = [] + while !container.isAtEnd { + let value = try decode(from: &container) + arr.append(value) + } + return arr + } + + static func decodeDictionary(from container: inout KeyedDecodingContainer) throws -> [String: Any] { + var dict = [String: Any]() + for key in container.allKeys { + let value = try decode(from: &container, forKey: key) + dict[key.stringValue] = value + } + return dict + } + + static func encode(to container: inout UnkeyedEncodingContainer, array: [Any]) throws { + for value in array { + if let value = value as? Bool { + try container.encode(value) + } else if let value = value as? Int64 { + try container.encode(value) + } else if let value = value as? Double { + try container.encode(value) + } else if let value = value as? String { + try container.encode(value) + } else if value is JSONNull { + try container.encodeNil() + } else if let value = value as? [Any] { + var container = container.nestedUnkeyedContainer() + try encode(to: &container, array: value) + } else if let value = value as? [String: Any] { + var container = container.nestedContainer(keyedBy: JSONCodingKey.self) + try encode(to: &container, dictionary: value) + } else { + throw encodingError(forValue: value, codingPath: container.codingPath) + } + } + } + + static func encode(to container: inout KeyedEncodingContainer, dictionary: [String: Any]) throws { + for (key, value) in dictionary { + let key = JSONCodingKey(stringValue: key)! + if let value = value as? Bool { + try container.encode(value, forKey: key) + } else if let value = value as? Int64 { + try container.encode(value, forKey: key) + } else if let value = value as? Double { + try container.encode(value, forKey: key) + } else if let value = value as? String { + try container.encode(value, forKey: key) + } else if value is JSONNull { + try container.encodeNil(forKey: key) + } else if let value = value as? [Any] { + var container = container.nestedUnkeyedContainer(forKey: key) + try encode(to: &container, array: value) + } else if let value = value as? [String: Any] { + var container = container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key) + try encode(to: &container, dictionary: value) + } else { + throw encodingError(forValue: value, codingPath: container.codingPath) + } + } + } + + static func encode(to container: inout SingleValueEncodingContainer, value: Any) throws { + if let value = value as? Bool { + try container.encode(value) + } else if let value = value as? Int64 { + try container.encode(value) + } else if let value = value as? Double { + try container.encode(value) + } else if let value = value as? String { + try container.encode(value) + } else if value is JSONNull { + try container.encodeNil() + } else { + throw encodingError(forValue: value, codingPath: container.codingPath) + } + } + + public required init(from decoder: Decoder) throws { + if var arrayContainer = try? decoder.unkeyedContainer() { + self.value = try JSONAny.decodeArray(from: &arrayContainer) + } else if var container = try? decoder.container(keyedBy: JSONCodingKey.self) { + self.value = try JSONAny.decodeDictionary(from: &container) + } else { + let container = try decoder.singleValueContainer() + self.value = try JSONAny.decode(from: container) + } + } + + public func encode(to encoder: Encoder) throws { + if let arr = self.value as? [Any] { + var container = encoder.unkeyedContainer() + try JSONAny.encode(to: &container, array: arr) + } else if let dict = self.value as? [String: Any] { + var container = encoder.container(keyedBy: JSONCodingKey.self) + try JSONAny.encode(to: &container, dictionary: dict) + } else { + var container = encoder.singleValueContainer() + try JSONAny.encode(to: &container, value: self.value) + } + } +} diff --git a/!main project/GeekbrainsUI/MusicPlayerAnimators/CustomAnimator.swift b/!main project/GeekbrainsUI/MusicPlayerAnimators/CustomAnimator.swift new file mode 100644 index 0000000..1da9727 --- /dev/null +++ b/!main project/GeekbrainsUI/MusicPlayerAnimators/CustomAnimator.swift @@ -0,0 +1,80 @@ +// +// CustomAnimator.swift +// GeekbrainsUI +// +// Created by raskin-sa on 27/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + + +import UIKit + +class CustomAnimator : NSObject, UIViewControllerAnimatedTransitioning { + var duration : TimeInterval + var isPresenting : Bool + var originFrame : CGRect + var image : UIImage + + + public let CustomAnimatorTag = 99 + + init(duration : TimeInterval, isPresenting : Bool, originFrame : CGRect, image : UIImage) { + self.duration = duration + self.isPresenting = isPresenting + self.originFrame = originFrame + self.image = image + } + + func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + + let container = transitionContext.containerView + + guard let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from) else { return } + guard let toView = transitionContext.view(forKey: UITransitionContextViewKey.to) else { return } + + self.isPresenting ? container.addSubview(toView) : container.insertSubview(toView, belowSubview: fromView) + + let detailView = isPresenting ? toView : fromView + + toView.frame = isPresenting ? CGRect(x: fromView.frame.width, y: 0, width: toView.frame.width, height: toView.frame.height) : toView.frame + toView.alpha = isPresenting ? 0 : 1 + toView.layoutIfNeeded() + + UIView.animate(withDuration: duration, animations: { + detailView.frame = self.isPresenting ? fromView.frame : CGRect(x: toView.frame.width, y: 0, width: toView.frame.width, height: toView.frame.height) + detailView.alpha = self.isPresenting ? 1 : 0 + }, completion: { (finished) in + transitionContext.completeTransition(!transitionContext.transitionWasCancelled) + }) + + guard let artwork = detailView.viewWithTag(CustomAnimatorTag) as? UIImageView else { return } + + artwork.image = image + artwork.alpha = 0 + + + let transitionImageView = UIImageView(frame: isPresenting ? originFrame : artwork.frame) + transitionImageView.image = image + + container.addSubview(transitionImageView) + + UIView.animate(withDuration: duration, animations: { + transitionImageView.frame = self.isPresenting ? artwork.frame : self.originFrame + detailView.frame = self.isPresenting ? fromView.frame : CGRect(x: toView.frame.width, y: 0, width: toView.frame.width, height: toView.frame.height) + detailView.alpha = self.isPresenting ? 1 : 0 + }, completion: { (finished) in + transitionContext.completeTransition(!transitionContext.transitionWasCancelled) + transitionImageView.removeFromSuperview() + artwork.alpha = 1 + }) + + + }// func animateTransition + + + func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { + return duration + }//func transitionDuration + +}//class CustomAnimator + diff --git a/!main project/GeekbrainsUI/MusicPlayerAnimators/CustomInteractor.swift b/!main project/GeekbrainsUI/MusicPlayerAnimators/CustomInteractor.swift new file mode 100644 index 0000000..09ad3a2 --- /dev/null +++ b/!main project/GeekbrainsUI/MusicPlayerAnimators/CustomInteractor.swift @@ -0,0 +1,68 @@ +// +// CustomInteractor.swift +// CustomNavigationAnimations-Starter +// +// Created by raskin-sa on 26/12/2019. +// Copyright © 2019 Sam Stone. All rights reserved. +// + +import UIKit + +/* + The idea of this class is to attach a UIScreenEdgePanGestureRecognizer (the swipe to go back gesture) to the destination ViewController that updates the custom transition based on the percent of the swipe the user has made on the ViewController. + We’ll create 3 properties, one to store the NavigationController, one to reference whether we should complete a transition (if we’re more then half way through the swipe and the user lets go, we complete the transition, if not, we cancel the transition), and one to reference whether there is a transition in progress. We’ll initialise the class with a failable initialiser, that contains a parameter ViewController we want to attach to. */ + +class CustomInteractor : UIPercentDrivenInteractiveTransition { + + var navigationController : UINavigationController + var shouldCompleteTransition = false + var transitionInProgress = false + + init?(attachTo viewController : UIViewController) { + if let nav = viewController.navigationController { + self.navigationController = nav + super.init() + setupBackGesture(view: viewController.view) + } else { + return nil + } + } + + private func setupBackGesture(view : UIView) { + let swipeBackGesture = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleBackGesture(_:))) + swipeBackGesture.edges = .left + view.addGestureRecognizer(swipeBackGesture) + } + + @objc private func handleBackGesture(_ gesture : UIScreenEdgePanGestureRecognizer) { + let viewTranslation = gesture.translation(in: gesture.view?.superview) + let progress = viewTranslation.x / self.navigationController.view.frame.width + + switch gesture.state { + case .began: + transitionInProgress = true + navigationController.popViewController(animated: true) + break + case .changed: + shouldCompleteTransition = progress > 0.5 + update(progress) + break + case .cancelled: + transitionInProgress = false + cancel() + break + case .ended: + transitionInProgress = false + shouldCompleteTransition ? finish() : cancel() + break + default: + return + } + finish() + + }//@objc private func handleBackGesture + + +}//class CustomInteractor + + diff --git a/!main project/GeekbrainsUI/SceneDelegate.swift b/!main project/GeekbrainsUI/SceneDelegate.swift new file mode 100644 index 0000000..0b037a2 --- /dev/null +++ b/!main project/GeekbrainsUI/SceneDelegate.swift @@ -0,0 +1,67 @@ +// +// SceneDelegate.swift +// GeekbrainsUI +// +// Created by raskin-sa on 23/11/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + + /* + guard let windowsScene = (scene as? UIWindowScene) else { return } + + if let value = UserDefaults.standard.value(forKey: "access_token"){ + window = UIWindow(windowScene: windowsScene) + let storyboard = UIStoryboard(name: "Main", bundle: nil) + let tabBarVC = storyboard.instantiateViewController(withIdentifier: "MainTab") + window?.rootViewController = UINavigationController(rootViewController: tabBarVC) + window?.makeKeyAndVisible() + print("token = \(value)") + } else{ + print("token not exists") + } + */ + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + + +} + diff --git a/!main project/GeekbrainsUI/Screens/FirstScreen.swift b/!main project/GeekbrainsUI/Screens/FirstScreen.swift index e83e48f..cdb9f3e 100644 --- a/!main project/GeekbrainsUI/Screens/FirstScreen.swift +++ b/!main project/GeekbrainsUI/Screens/FirstScreen.swift @@ -6,4 +6,64 @@ // Copyright © 2020 raskin-sa. All rights reserved. // -import Foundation +import UIKit + +class FirstScreen: UIViewController { + @IBOutlet weak var currentUser: UIButton! + @IBOutlet weak var changeUser: UIButton! + @IBOutlet weak var workOfflie: UIButton! + @IBOutlet weak var cleanDatabase: UIButton! + + override func viewDidLoad() { + super .viewDidLoad() + + initUI() + } + + func initUI(){ + currentUser.setTitle("Логин текущим пользователем", for: .normal) + changeUser.isHidden = true + changeUser.setTitle("Cменить пользователя \(Session.shared.userId)", for: .normal) + workOfflie.setTitle("Работать через Firebase (БД офлайн)", for: .normal) + cleanDatabase.setTitle("Очистить локальную БД", for: .normal) + } + + @IBAction func currentUserPressed(_ sender: Any) { + webMode = true //работаем с интернетом + transitionToTabBar(screenName: "VKLoginController") + } + + @IBAction func changeUserPressed(_ sender: Any) { + } + + @IBAction func workOfflinePressed(_ sender: Any) { + webMode = false //работаем без интернета + transitionToTabBar(screenName: "LocalLoginController") + } + + @IBAction func cleanDatabasePressed(_ sender: Any) { + let cr = CommonRepository() + + showYesNoMessage(view: self, title: "Внимание!", messagetext: "Вы действительно хотите удалить все данные из локальной БД!") { (result) in + if result { //User has clicked on Ok +// cr.deleteVKObject(object: .VKUser) +// cr.deleteVKObject(object: .VKGroup) +// cr.deleteVKObject(object: .VKPhoto) + + cr.deleteAllRealmTables () + } else { //User has clicked on Cancel + return + } + } + + + } + + private func transitionToTabBar(screenName: String) { + let storyboard = UIStoryboard(name: "Main", bundle: nil) + let vc = storyboard.instantiateViewController(identifier: screenName) + vc.modalPresentationStyle = .custom + self.navigationController?.pushViewController(vc, animated: true) + } + +}//class FirstScreen: UIViewController diff --git a/!main project/GeekbrainsUI/Screens/FriendCell.swift b/!main project/GeekbrainsUI/Screens/FriendCell.swift new file mode 100644 index 0000000..ddb3bf1 --- /dev/null +++ b/!main project/GeekbrainsUI/Screens/FriendCell.swift @@ -0,0 +1,18 @@ +import UIKit + +class FriendCell : UITableViewCell { + @IBOutlet weak var username: UILabel! + @IBOutlet weak var userimage: UIImageView! + @IBOutlet weak var myShadowView: CircleShadowImage! + + func renderCell(model: VKUserRealm) { + let firstname = model.firstName + let lastname = model.lastName + + username.text = firstname + " " + lastname + + if let url = URL(string: model.avatarPath){ + userimage.kf.setImage(with: url) + } + } +} diff --git a/!main project/GeekbrainsUI/Screens/FriendList.swift b/!main project/GeekbrainsUI/Screens/FriendList.swift new file mode 100644 index 0000000..f447ee9 --- /dev/null +++ b/!main project/GeekbrainsUI/Screens/FriendList.swift @@ -0,0 +1,107 @@ +// +// FriendList.swift +// GeekbrainsUI +// +// Created by raskin-sa on 01/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit +import Kingfisher + +struct Section{ + var title: String + var items: [T] +} + +protocol FriendsListView: class{ + func updateTable() +} + +class FriendList: UITableViewController, UINavigationControllerDelegate { + + @IBOutlet weak var searchBar: UISearchBar! + + var presenter: FriendsPresenter? + var configurator: FriendsConfigurator? + + override func viewDidLoad() { + super.viewDidLoad() + + //MARK: пока здесь, т.к. нет больше вызовов + //ViewController.configurator = FriendsConfiguratorImplementation() + self.configurator = FriendsConfiguratorImplementation() + + configurator?.configure(view: self) + searchBar.delegate = self + presenter?.viewDidLoad() + }// override func viewDidLoad() + + override func numberOfSections(in tableView: UITableView) -> Int{ + return presenter?.numberOfSections() ?? 0 + } + + + //заполняем строки именем пользователя и, если получится, картинкой + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: "FriendsTemplate", for: indexPath) as? FriendCell, + let vkUserRealm = presenter?.getVKUserRealmAtIndex(indexPath: indexPath) + else{ + return UITableViewCell() + } + //настраиваем ячейку, фотографией и ФИО + cell.renderCell(model: vkUserRealm) + + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + + guard let vkUserRealm = presenter?.getVKUserRealmAtIndex(indexPath: indexPath) else {return} + + //Получаем фотки из Web и переходим в след. ViewController + presenter?.getPhotosByVKUserRealmAndGo(view: self, userRealm: vkUserRealm) + }//override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) + + override func sectionIndexTitles(for tableView: UITableView) -> [String]? { + return presenter?.getSectionIndexTitles() + } + + override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + let view = UIView() + view.backgroundColor = .systemGray6 + let label = UILabel() + label.text = presenter?.getTitleForSection(section: section) + label.frame = CGRect(x: 10, y: 2, width: 14, height: 15) + label.textColor = UIColor.darkGray + label.adjustsFontSizeToFitWidth = true + view.addSubview(label) + return view + + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return presenter?.numberOfRowsInSection(section: section) ?? 0 + } + +}// class FriendList + +extension FriendList: FriendsListView{ + func updateTable() { + tableView.reloadData() + } +} + +extension FriendList: UISearchBarDelegate { + func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { + presenter?.searchFriends(name: searchText) + }//func searchBar + + func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { + view.endEditing(true) + } + +}//class FriendList + + + diff --git a/!main project/GeekbrainsUI/Screens/FriendList/FriendsConfigurator.swift b/!main project/GeekbrainsUI/Screens/FriendList/FriendsConfigurator.swift index ecf3354..6b3f441 100644 --- a/!main project/GeekbrainsUI/Screens/FriendList/FriendsConfigurator.swift +++ b/!main project/GeekbrainsUI/Screens/FriendList/FriendsConfigurator.swift @@ -6,4 +6,17 @@ // Copyright © 2020 raskin-sa. All rights reserved. // -import Foundation +import UIKit + +protocol FriendsConfigurator { + func configure(view: FriendList) +} + +class FriendsConfiguratorImplementation: FriendsConfigurator{ + func configure(view: FriendList) { + view.presenter = FriendsPresenterImplementation(userDB: UserRepository(), photosDB: PhotoRepository() , view: view) + } +} + +//код инициализации конфигуратора +//ViewController.configurator = FriendsConfiguratorImplementation() diff --git a/!main project/GeekbrainsUI/Screens/FriendList/FriendsPresenter.swift b/!main project/GeekbrainsUI/Screens/FriendList/FriendsPresenter.swift index 67e257f..5b30b79 100644 --- a/!main project/GeekbrainsUI/Screens/FriendList/FriendsPresenter.swift +++ b/!main project/GeekbrainsUI/Screens/FriendList/FriendsPresenter.swift @@ -7,3 +7,198 @@ // import Foundation +import RealmSwift + +protocol FriendsPresenter{ + // typealias Out = Swift.Result + + func viewDidLoad() + func searchFriends(name: String) + func getPhotosByVKUserRealmAndGo(view: FriendList, userRealm: VKUserRealm) + + func numberOfSections() -> Int + func numberOfRowsInSection(section: Int) -> Int + func getSectionIndexTitles() -> [String]? + func getVKUserRealmAtIndex(indexPath: IndexPath) -> VKUserRealm? + func getTitleForSection(section: Int) -> String? + + func getPhotoControllerCount() -> Int + func getVKPhotoAtIndex(indexPath: IndexPath) -> VKPhotosRealm? + func getVKPhotoSizesAtIndex(indexPath: IndexPath) -> List? + func getVKPhotoLikesAtIndex(indexPath: IndexPath) -> PhotoLikesRealm? +} + +struct SectionRealm{ + var title: String + var items: Results +} + +class FriendsPresenterImplementation : FriendsPresenter { + var friends = [Friend]() + + private var vkAPI: VKAPi + private var userDB: FriendsSource + private var photosDB: PhotosRepositorySource + + //mvc + private var sortedFriendsResults = [Section]() + private var friendsResult: Results! + + private var photosResult: Results! + + private weak var view:FriendsListView? + + init (userDB: FriendsSource, photosDB: PhotosRepositorySource, view: FriendsListView) { + vkAPI = VKAPi() + self.userDB = userDB + self.photosDB = photosDB + self.view = view + } + + func viewDidLoad() { + + getFriendsFromApiAndDB() + } + + func getFriendsFromDatabase(){ + do { + self.friendsResult = try userDB.getAllUsers() + self.makeSortedSections() + self.view?.updateTable() + }catch { print(error)} + }//func getFriendsFromDatabase() + + + func getFriendsFromApiAndDB(){ + + if webMode{ + //Получаем пользователей из Web + vkAPI.getFriendList(token: Session.shared.token){ result in + switch result { + case .success(let webUsers): //массив VKUser из Web + do{ + try self.userDB.addUsers(users: webUsers) + //выгружаем из БД строго после Web-запроса и после добавления в БД + self.getFriendsFromDatabase() + }catch { + print("we got error in database.getAllUsers(): \(error)") + } + case .failure(let error): + print("we got error in getFriendsFromApi(): \(error)") + }//switch + }//completion + }// if webMode{ + else + { + self.getFriendsFromDatabase() + } + + }//func getFriendsFromApi() + + func searchFriends(name: String) { + do { + self.friendsResult = name.isEmpty ? try userDB.getAllUsers() : try userDB.searchUsers(name: name) + makeSortedSections() + self.view?.updateTable() + }catch { + print(error) + } + } + + func getPhotosFromDatabase(userID: Int){ + do { + self.photosResult = try photosDB.getPhotosByUser(userID: userID) + }catch { print("Ошибка в функции getPhotosFromDatabase: \(error)")} + } + + func getPhotosByVKUserRealmAndGo(view: FriendList, userRealm: VKUserRealm) { + + if webMode{ + vkAPI.getPhotosList(token: Session.shared.token, userId: userRealm.id){ result in + switch result { + case .success(let photos): + //сохраняем фото в БД + self.photosDB.addPhotos(photos: photos) + //извлекаем из БД + do{ + try self.photosResult = self.photosDB.getPhotosByUser(userID: userRealm.id) + }catch {print("we got error in photosDB.getPhotosByUser(): \(error)")} + + //вызываем след. ViewController + self.moveToNextViewController(view: view, userRealm: userRealm) + + case .failure(let error): + print("we got error in vkAPI.getPhotosList: \(error)") + }//switch case + }//completion + }//if webMode + else{ + //берем данные только из БД + do{ + try self.photosResult = self.photosDB.getPhotosByUser(userID: userRealm.id) + }catch { + print("we got error in photosDB.getPhotosByUser(): \(error)") + } + //вызываем след. ViewController + self.moveToNextViewController(view: view, userRealm: userRealm) + }//else if webMode + } + + func moveToNextViewController(view: FriendList, userRealm: VKUserRealm ){ + let storyboard = UIStoryboard(name: "Main", bundle: nil) + let viewController = storyboard.instantiateViewController(identifier: "PhotoController") as! PhotoController + + viewController.tmpFriend = convertFriend(user:userRealm.toModel()) + viewController.tmpVKUserRealm = userRealm + // viewController.photoArray = photos + //viewController.configurator = config + viewController.presenter = self + view.navigationController?.pushViewController(viewController, animated: true) + } + + private func makeSortedSections(){ + let groupedFriends = Dictionary.init(grouping: friendsResult){$0.lastName.prefix(1) } + sortedFriendsResults = groupedFriends.map { Section(title: String($0.key), items: $0.value) } + sortedFriendsResults.sort {$0.title < $1.title} + }// func makeSortedSections() + +}//class + +extension FriendsPresenterImplementation { + func numberOfRowsInSection(section: Int) -> Int { + return sortedFriendsResults[section].items.count + } + + func getVKUserRealmAtIndex(indexPath: IndexPath) -> VKUserRealm? { + return sortedFriendsResults[indexPath.section].items[indexPath.row] + } + + func numberOfSections() -> Int { + return sortedFriendsResults.count + } + + func getSectionIndexTitles() -> [String]? { + return sortedFriendsResults.map{$0.title} + } + + func getTitleForSection(section: Int) -> String? { + return sortedFriendsResults[section].title + } + + func getPhotoControllerCount() -> Int { + return photosResult.count + } + + func getVKPhotoAtIndex(indexPath: IndexPath) -> VKPhotosRealm? { + return photosResult[indexPath.row] + } + + func getVKPhotoSizesAtIndex(indexPath: IndexPath) -> List? { + return photosResult[indexPath.row].sizes + } + + func getVKPhotoLikesAtIndex(indexPath: IndexPath) -> PhotoLikesRealm? { + return photosResult[indexPath.row].likes + } +} + diff --git a/!main project/GeekbrainsUI/Screens/FriendsPhotoViewController.swift b/!main project/GeekbrainsUI/Screens/FriendsPhotoViewController.swift new file mode 100644 index 0000000..ad5f811 --- /dev/null +++ b/!main project/GeekbrainsUI/Screens/FriendsPhotoViewController.swift @@ -0,0 +1,46 @@ +// +// FriendsPhotoViewController.swift +// GeekbrainsUI +// +// Created by raskin-sa on 27/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit +import Kingfisher + +class FriendsPhotoViewController : UIViewController { + + @IBOutlet weak var friendPhotolikeButton: LikeButton! + @IBOutlet weak var username: UILabel! + @IBOutlet weak var friendPhoto: UIImageView! + + + + public var friend : Friend? + public var imageURL: String? + public var likeCount: Int? + public var userLiked:Int? + + override func viewDidLoad() { + super.viewDidLoad() + + guard let imageURL = self.imageURL else {return} + + if let friend = self.friend { + guard let url = URL(string: imageURL ) else {return} + // print(url) + friendPhoto.kf.setImage(with: url) + username.text = friend.userName + friendPhotolikeButton.setLikeCount(likeCount: self.likeCount ?? 0, userLiked: self.userLiked ?? 0) + + } + + } + + @IBAction func likeButtonPressed(_ sender: Any) { + (sender as! LikeButton).like() + } + +}// class FriendsPhotoViewController + diff --git a/!main project/GeekbrainsUI/Screens/GroupList/GroupList.swift b/!main project/GeekbrainsUI/Screens/GroupList/GroupList.swift new file mode 100644 index 0000000..13f667f --- /dev/null +++ b/!main project/GeekbrainsUI/Screens/GroupList/GroupList.swift @@ -0,0 +1,167 @@ +// +// GroupList.swift +// GeekbrainsUI +// +// Created by raskin-sa on 03/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + + + +import UIKit +import RealmSwift + +protocol GroupListView: class { + func updateTable() + func updateForObserver(deletions: [Int], insertions: [Int], modifications: [Int]) +} + +protocol CellImageTapDelegate { + func tableCell(didClickedImageOf tableCell: GroupCell) +} + +class GroupList: UITableViewController, CellImageTapDelegate/*, ImageViewPresenterSource*/ { + + + var presenter: GroupsPresenter? + var configurator: GroupsConfigurator? + + + @IBOutlet weak var groupSearchBar: UISearchBar! + + var customRefreshControl = UIRefreshControl() + + + override func viewDidLoad() { + super.viewDidLoad() + + addRefreshControl() + groupSearchBar.delegate = self //чтобы поиск отрабатывал + + self.configurator = GroupsConfiguratorImplementation() + configurator?.configure(view: self) + + presenter?.viewDidLoad() + }//viewdidload + + func tableCell(didClickedImageOf tableCell: GroupCell) { + // функция вызывает анимацию сжимающейся иконки + //из ячейки класса GroupCell, на которую нажали + tableCell.setAnimation() + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + //return self.groups.count + return presenter?.numberOfRows() ?? 0 + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + + guard let cell = tableView.dequeueReusableCell(withIdentifier: "GroupTemplate", for: indexPath) as? GroupCell else{ return UITableViewCell()} + guard let vkGroupRealm = presenter?.getVKGroupRealmAtIndex(indexPath: indexPath) else {return cell} + + cell.delegate = self + cell.renderCell(model: vkGroupRealm) + + return cell + } // override func tableView(_ tableView: UITableView, cellForRowAt + + //запускаем снег по нажатию соответствующей кнопки + @IBOutlet weak var snowButton: UIBarButtonItem! + @IBAction func snowButtonPressed(_ sender: Any) { + let duration = 30.0 + let emitterSnow = EmitterSnow() + + emitterSnow.letItSnow(duration: duration, view: view) + + //делаем кнопку недоступной пока идет снег (= duration) + self.snowButton.isEnabled = false + DispatchQueue.main.asyncAfter(deadline: .now() + duration) { + self.snowButton.isEnabled = true + } + } + @IBAction func AddButtonPressed(_ sender: Any) { + let l_count: Int = presenter?.numberOfRows() ?? 0 + array_append(name: "NewGroup\(l_count)", id: l_count) + } + + @IBAction func toGroupDetailButtonPressed(_ sender: Any) { + let animationDuration = 9.0 + // animationLoad(duration: animationDuration) + + self.perform(#selector(navigateToViewController), with: nil, afterDelay: animationDuration) + } + + @objc func navigateToViewController() { + let storyboard = UIStoryboard(name: "Main", bundle: nil) + let viewController = storyboard.instantiateViewController(identifier: "MessageViewController") as! MessageViewController + self.navigationController?.pushViewController(viewController, animated: true) + } + + func array_append ( name: String, id: Int){ + // groups.append(Group(groupName: name, avatarPath: "no", id: id)) + tableView.reloadData() + } + + override func tableView(_ tableView: UITableView, willDisplay cell: + UITableViewCell, forRowAt indexPath: IndexPath) { + + } + + //Добавляем ф-л удаления строчки через UITableViewCell.EditingStyle + override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) + { + if editingStyle == .delete{ + //groupsResult.remove(at: indexPath.row) + tableView.reloadData() + } + } + func array_delete ( row: Int){ + // groups.remove(at: row) + tableView.reloadData() + } + + func addRefreshControl(){ + customRefreshControl.attributedTitle = NSAttributedString(string:"refreshing...") + customRefreshControl.addTarget(self, action: #selector(refreshTable), for: .valueChanged) + tableView.addSubview(customRefreshControl) + } + + @objc func refreshTable(){ + //фейковая функция, имитирует обновление из интернета + + //функция DispatchQueue дает паузу в 3 сек... + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + //... и закрывает refreshing + self.customRefreshControl.endRefreshing() + } + } + +}//class GroupList + +extension GroupList: UISearchBarDelegate { + func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { + presenter?.searchGroups(name: searchText) + tableView.reloadData() + } + + func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { + view.endEditing(true) + } +} + +extension GroupList: GroupListView { + func updateTable() { + tableView.reloadData() + } + + func updateForObserver(deletions: [Int], insertions: [Int], modifications: [Int]){ + tableView.beginUpdates() + tableView.deleteRows(at: deletions.map {IndexPath(row: $0, section: 0)}, with: .none) + tableView.insertRows(at: insertions.map {IndexPath(row: $0, section: 0)}, with: .none) + tableView.reloadRows(at: modifications.map {IndexPath(row: $0, section: 0)}, with: .none) + tableView.endUpdates() + } +}//extension + + diff --git a/!main project/GeekbrainsUI/Screens/GroupList/GroupsConfigurator.swift b/!main project/GeekbrainsUI/Screens/GroupList/GroupsConfigurator.swift new file mode 100644 index 0000000..ae281f3 --- /dev/null +++ b/!main project/GeekbrainsUI/Screens/GroupList/GroupsConfigurator.swift @@ -0,0 +1,19 @@ +// +// GroupsConfigurator.swift +// GeekbrainsUI +// +// Created by raskin-sa on 06/02/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + +import UIKit + +protocol GroupsConfigurator { + func configure(view: GroupList) +} + +class GroupsConfiguratorImplementation: GroupsConfigurator{ + func configure( view: GroupList) { + view.presenter = GroupPresenterImplementation (groupDB: GroupRepository(), view: view) + } +} diff --git a/!main project/GeekbrainsUI/Screens/GroupList/GroupsPresenter.swift b/!main project/GeekbrainsUI/Screens/GroupList/GroupsPresenter.swift index 3d327aa..a0e55ff 100644 --- a/!main project/GeekbrainsUI/Screens/GroupList/GroupsPresenter.swift +++ b/!main project/GeekbrainsUI/Screens/GroupList/GroupsPresenter.swift @@ -7,3 +7,106 @@ // import Foundation +import RealmSwift + +protocol GroupsPresenter { + func viewDidLoad() + func searchGroups(name: String) + + func numberOfRows() -> Int + func getVKGroupRealmAtIndex(indexPath: IndexPath) -> VKGroupRealm? +} + +class GroupPresenterImplementation: GroupsPresenter { + private var vkAPI: VKAPi + private weak var view: GroupListView? + var groupRepository = GroupRepository() + var groupsResult: Results! + var token: NotificationToken? + var tokenUser: NotificationToken? + + private var groupDB: GroupsSource + + init (groupDB: GroupsSource, view: GroupListView){ + self.view = view + self.groupDB = groupDB + vkAPI = VKAPi() + } + + deinit { + token?.invalidate() + } + + func viewDidLoad(){ + getGroupsFromApiAndDB() + } + + func getGroupsFromDatabase(){ + do { + self.groupsResult = try groupDB.getAllGroups() + self.view?.updateTable() + setupObserver() + }catch + { print(error)} + }//func getGroupsFromDatabase() + + func getGroupsFromApiAndDB(){ + if webMode{ + //Получаем пользователей из Web + vkAPI.getGroupsList(token: Session.shared.token, userId: Session.shared.userId){ result in + switch result { + case .success(let webGroups): //массив VKGroup из Web + do{ + try self.groupDB.addGroups(groups: webGroups) + //выгружаем из БД строго после Web-запроса и после добавления в БД + self.getGroupsFromDatabase() + }catch { + print("we got error in database.getAllUsers(): \(error)") + } + case .failure(let error): + print("we got error in getFriendsFromApi(): \(error)") + }//switch + }//completion + }// if webMode{ + else + { + self.getGroupsFromDatabase() + } + }//func getGroupsFromApiAndDB() + + func searchGroups(name: String) { + do { + self.groupsResult = name.isEmpty ? try groupDB.getAllGroups() : try groupDB.searchGroups(name: name) + + self.view?.updateTable() + }catch { + print(error) + } + } + + func setupObserver() { + //MARK: отслеживание изменений в БД + token = groupsResult.observe {[weak self] results in + switch results{ + case .error(let error): + print(error) + case .initial(_): + self?.view?.updateTable() + case let .update(_, deletions, insertions, modifications): + self?.view?.updateForObserver(deletions: deletions, insertions: insertions, modifications: modifications) + }// switch results + }//completion + }//func setupObserver() + +}//class GroupPresenterImplementation + +extension GroupPresenterImplementation { + func numberOfRows() -> Int { + return groupsResult?.count ?? 0 + } + func getVKGroupRealmAtIndex(indexPath: IndexPath) -> VKGroupRealm? { + return groupsResult[indexPath.row] + } +} + + diff --git a/!main project/GeekbrainsUI/Screens/LoginController/LoginConfigurator.swift b/!main project/GeekbrainsUI/Screens/LoginController/LoginConfigurator.swift new file mode 100644 index 0000000..4f0a532 --- /dev/null +++ b/!main project/GeekbrainsUI/Screens/LoginController/LoginConfigurator.swift @@ -0,0 +1,9 @@ +// +// LoginConfigurator.swift +// GeekbrainsUI +// +// Created by raskin-sa on 19/02/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + +import Foundation diff --git a/!main project/GeekbrainsUI/Screens/LoginController/LoginController.swift b/!main project/GeekbrainsUI/Screens/LoginController/LoginController.swift new file mode 100644 index 0000000..50f9d9f --- /dev/null +++ b/!main project/GeekbrainsUI/Screens/LoginController/LoginController.swift @@ -0,0 +1,161 @@ +// +// LoginController.swift +// GeekbrainsUI +// +// Created by raskin-sa on 14/01/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + +import UIKit +import Alamofire +import WebKit + + +private let debugMode = 1 //1 - debug on, 0 - debug off + + + +class VKLoginController: UIViewController, ActivityIndicatorPresenter { + var activityIndicator = UIActivityIndicatorView() + + let VKSecret = "7281379" + + @IBOutlet weak var webView: WKWebView! { + didSet { + + webView.navigationDelegate = self + Session.shared.webView = webView + } + } + + // var vkAPI = VKAPi() + // typealias Out = Swift.Result + + // var webView: WKWebView! + + + override func viewDidLoad() { + super.viewDidLoad() + + self.view.addSubview(webView) + + showActivityIndicator() + doWebConnect + { + result in + self.hideActivityIndicator() + } + + + }//override func viewDidLoad() + + @IBAction func createAndAuth(_ sender: Any) { + } + + private func transitionToTabBar() { + let storyboard = UIStoryboard(name: "Main", bundle: nil) + let vc = storyboard.instantiateViewController(identifier: "MainTab") + vc.modalPresentationStyle = .custom + self.navigationController?.pushViewController(vc, animated: true) + } + + private func doWebConnect(completion: @escaping (Out)-> Void){ + // private func doWebConnect(){ + // let webViewConfig = WKWebViewConfiguration() + // webView = WKWebView(frame: .zero, configuration: webViewConfig) + // self.view = webView + + + var urlComponent = URLComponents() + urlComponent.scheme = "https" + urlComponent.host = "oauth.vk.com" + urlComponent.path = "/authorize" + urlComponent.queryItems = [URLQueryItem(name: "client_id", value: VKSecret), + URLQueryItem(name: "redirect_uri", value: "https://oauth.vk.com/authorize/blank.html"), + URLQueryItem(name: "display", value: "mobile"), + URLQueryItem(name: "scope", value: "262150"), + URLQueryItem(name: "response_type", value: "token"), + URLQueryItem(name: "v", value: "5.103") + ] + + //информирует, что пользователь переходит на другую страницу, напр. с токеном + // webView.navigationDelegate = self + // view.addSubview(webView) + // view.bringSubviewToFront(webView) + + // отладим удаление cookies + // webView.configuration.websiteDataStore.httpCookieStore.getAllCookies {[weak self] cookies in cookies.forEach{self?.webView.configuration.websiteDataStore.httpCookieStore.delete($0) + // } + // } + + let request = try URLRequest(url: urlComponent.url!) + do { + let loadResult = try webView.load(request) + completion(.success(loadResult)) + }catch{ + completion(.failure(error)) + } + } +}// class VKLoginController: UIViewController + + +extension VKLoginController: WKNavigationDelegate { + //Функция возвращает текст ошибки, если она случается. Для отладки + func webView(_ webView: WKWebView, + didFailProvisionalNavigation navigation: WKNavigation!, + withError error: Error) { + + if debugMode == 1 {print(error)} + + } + + func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) { + + if debugMode == 1 { + if let response = navigationResponse.response as? HTTPURLResponse { + print("response description = \(response.description)") + print("response.statusCode = \(response.statusCode)") + + } + } + + view.bringSubviewToFront(webView) + + guard let url = navigationResponse.response.url, + url.path == "/blank.html", + let fragment = url.fragment else { + decisionHandler(.allow) + return + } + + let params = fragment.components(separatedBy: "&") + .map { $0.components(separatedBy: "=")} + .reduce([String: String]()) { + value, params in + var dict = value + let key = params[0] + let value = params[1] + dict[key] = value + return dict + } + if debugMode == 1 {print(params)} + + Session.shared.token = params["access_token"]! + Session.shared.userId = params["user_id"] ?? "0" + + //реализация KeyChain + UserDefaults.standard.set(params["access_token"], forKey: "access_token") + UserDefaults.standard.set(params["user_id"], forKey: "user_id") + + if debugMode == 1 + { + print(UserDefaults.standard) + } + + + decisionHandler(.cancel) + transitionToTabBar() + + } +}// extension + diff --git a/!main project/GeekbrainsUI/Screens/LoginController/LoginPresenter.swift b/!main project/GeekbrainsUI/Screens/LoginController/LoginPresenter.swift new file mode 100644 index 0000000..2282d0a --- /dev/null +++ b/!main project/GeekbrainsUI/Screens/LoginController/LoginPresenter.swift @@ -0,0 +1,9 @@ +// +// LoginPresenter.swift +// GeekbrainsUI +// +// Created by raskin-sa on 19/02/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + +import Foundation diff --git a/!main project/GeekbrainsUI/Screens/LoginViewController.swift b/!main project/GeekbrainsUI/Screens/LoginViewController.swift new file mode 100644 index 0000000..e3d12d6 --- /dev/null +++ b/!main project/GeekbrainsUI/Screens/LoginViewController.swift @@ -0,0 +1,148 @@ +// +// ViewController.swift +// GeekbrainsUI +// +// Created by raskin-sa on 23/11/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit +import Alamofire +import WebKit +import FirebaseAuth +import FirebaseDatabase + +private let debugMode = 0 //1 - debug on, 0 - debug off + + + + + +class LoginViewController: UIViewController, UITextFieldDelegate //для фокусирования курсора на текстовое поле +{ + @IBOutlet weak var startButton: UIButton! + @IBOutlet weak var Login2: UITextField! + @IBOutlet weak var Password: UITextField! + @IBOutlet weak var logo: UIImageView! + @IBOutlet weak var scrollView: UIScrollView! + + //для функции анимации на лого + var propertyAnimator: UIViewPropertyAnimator = UIViewPropertyAnimator(duration: 1.0, dampingRatio: 1.0, animations:{}) + + var vkAPI = VKAPi() + private var users = [UserFirebase]() + + private var login: String? + private var password: String? + + override func viewDidLoad() { + super.viewDidLoad() + + let HideAction = UITapGestureRecognizer(target: self, action: #selector(hideKeyboard)) + view.addGestureRecognizer(HideAction) + } + + //функция, убирающая клавиатуру с экрана при нажатии вне текстового поля + @objc func hideKeyboard(){ + view.endEditing(true) + } + + @IBAction func RegPressed(_ sender: Any) { + + + if !self.readAuthData() {return} + //MARK: код создания нового пользователя в FireBase + Auth.auth().createUser(withEmail: login!, password: password!){ + (result,error) in + if result != nil { + self.showLoginError(title:"Поздравляем!", messagetext: "Успешно зарегистрирован пользователь: \(String( result!.user.email!))") + } else{ + print(error as Any) + } + } + } + + @IBAction func nextWindow(_ sender: Any) { + + // MARK: FireBase Sign in сущ-го пользователя + + if !readAuthData() {return} + Auth.auth().signIn(withEmail: login!, password: password!){ + (result, error) in + if let id = result?.user.uid{ + print(id) + + //go to tabbar + let storyboard = UIStoryboard(name: "Main", bundle: nil) + let vc = storyboard.instantiateViewController(identifier: "MainTab") + self.navigationController?.pushViewController(vc, animated: true) + } + else { + self.showLoginError(title:"Ошибка", messagetext: "Что-то пошло не так: \(String(describing: error))") + } + } + } + + + func checkLogin()-> Bool { + guard let loginInput = Login2.text, let passwordInput = Password.text else {return false} + + if (loginInput == "")||(passwordInput == "") { + return false + } else{ + return true + } + }//func + + func showLoginError(title: String, messagetext: String){ + //добавляем обработку ошибок + + // Создаем контроллер + let alert = UIAlertController(title: title, message: messagetext, preferredStyle: .alert) + // Создаем кнопку для UIAlertController + let action = UIAlertAction(title: "OK", style: .cancel, handler: { _ in + self.Login2.becomeFirstResponder()}) //в handler добавили текстовое поле, чтобы на него сфокусироваться + // Добавляем кнопку на UIAlertController + alert.addAction(action) + // Показываем UIAlertController + present(alert, animated: true, completion: nil) + } + + private func readAuthData() -> Bool { + + let checkResult = checkLogin() + if !checkResult { + showLoginError(title:"Ошибка", messagetext: "Авторизуйтесь для работы с приложением") + return false + } + + login = Login2.text! + password = Password.text! + return true + } + + override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool { + return readAuthData() + } + + func modifyFireBase(){ + + let ref = Database.database().reference(withPath: "users") + ref.observe(.value) { (snapshot) in + for node in snapshot.children { + if let object = node as? DataSnapshot { + print(object.value!) + + if let user = UserFirebase(snapshot: object){ + self.users.append(user) + } + } + } + } + }// func modifyFireBase + +}//class + + + + diff --git a/!main project/GeekbrainsUI/Screens/NewGroupList.swift b/!main project/GeekbrainsUI/Screens/NewGroupList.swift new file mode 100644 index 0000000..2716179 --- /dev/null +++ b/!main project/GeekbrainsUI/Screens/NewGroupList.swift @@ -0,0 +1,38 @@ +// +// NewGroupList.swift +// GeekbrainsUI +// +// Created by raskin-sa on 03/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit + +class NewGroupList: UITableViewController { + + var lv_newgrouplist = ["NewGroup1", "NewGroup2"] + + override func viewDidLoad() { + super.viewDidLoad() + + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + + return lv_newgrouplist.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + + let cell = tableView.dequeueReusableCell(withIdentifier: "NewGroupTemplate", for: indexPath) as! NewGroupCell + + var ls_indexrow:String? + + ls_indexrow = lv_newgrouplist[indexPath.row] + + cell.newgroupname.text = ls_indexrow! + + return cell + } + +} diff --git a/!main project/GeekbrainsUI/Screens/PhotoController/PhotoController.swift b/!main project/GeekbrainsUI/Screens/PhotoController/PhotoController.swift new file mode 100644 index 0000000..8054781 --- /dev/null +++ b/!main project/GeekbrainsUI/Screens/PhotoController/PhotoController.swift @@ -0,0 +1,111 @@ + + +import UIKit +import Kingfisher + +private let reuseIdentifier = "PhotoCell" + +protocol PhotoListView: class{ + +} + +class PhotoController: UICollectionViewController, PhotoListView, UICollectionViewDelegateFlowLayout, UINavigationControllerDelegate { + + var presenter: FriendsPresenter? + var configurator: PhotosConfigurator? + + var tmpVKUserRealm: VKUserRealm? //текущий элемент Друг типа структура, на котором стоим + var tmpFriend: Friend? + // var viewClicked: ((UIView)->())? = nil + // var photoArray: [VKPhoto]? + + private var selectedFrame: CGRect? = .zero + var customInteractor: CustomInteractor? + var tmpImage: UIImage? // текущая картинка, на которой стоим + + + override func viewDidLoad() { + super.viewDidLoad() + guard let tmpVKUserRealm = self.tmpVKUserRealm else { + print ("error. tmpFriend is not initialised!") + return + } + self.tmpFriend = convertFriend(user: tmpVKUserRealm.toModel()) + // configurator?.configure(view: self, VKUserRealm: tmpVKUserRealm ) + + self.navigationController?.delegate = self + + } + + + override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return presenter?.getPhotoControllerCount() ?? 1 + } + + override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PhotoCell", for: indexPath) as! PhotoCell + let defaultUsername = tmpFriend!.userName + + guard let model = presenter?.getVKPhotoAtIndex(indexPath: indexPath) else {return cell} + + cell.renderCell(model: model, username: defaultUsername) + + tmpImage = cell.photo.image + return cell + } + + //вызывает экран MusicPlayerViewController + //запоминает выбранную (песню = экземпляр структуры) и фрейм + override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + // self.selectedSong = songs[indexPath.row] + let theAttributes:UICollectionViewLayoutAttributes! = collectionView.layoutAttributesForItem(at: indexPath) + selectedFrame = collectionView.convert(theAttributes.frame, to: collectionView.superview) + // let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PhotoCell", for: indexPath) as! PhotoCell + + + let storyboard = UIStoryboard(name: "Main", bundle: nil) + let viewController = storyboard.instantiateViewController(identifier: "FriendsPhotoViewController") as! FriendsPhotoViewController + + guard let localSizes = presenter?.getVKPhotoSizesAtIndex(indexPath: indexPath) else {return} + let localLikes = presenter?.getVKPhotoLikesAtIndex(indexPath: indexPath) + + //передаем в след. ViewController ссылку на большую картинку + //которая хранится в массиве sizes с типом X + let urlToBe = localSizes.filter("type == %@","x")[0].url + viewController.friend = tmpFriend + viewController.imageURL = urlToBe + viewController.likeCount = localLikes?.count + viewController.userLiked = localLikes?.userLikes + + self.navigationController?.pushViewController(viewController, animated: true) + + } + + + //вызывает анимацию перехода + // We want to create a property that stores the frame of the selected cell. In the didSelectItem function, we want to modify it a little bit so we grab the cells frame, but specifically the frame of the cell relative to that of the ViewController: + func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { + guard let frame = self.selectedFrame else { return nil } + guard let tmpimage = self.tmpImage else {return nil} + + // Now we have the selectedFrame, and we can access the selected image from our array of songs, we’re finally ready to implement the CustomAnimator. Because we’ve inherited from UINavigationControllerDelegate, we can implement the method that is used to override the default animation. + switch operation { + case .push: + self.customInteractor = CustomInteractor(attachTo: toVC) + return CustomAnimator(duration: TimeInterval(UINavigationController.hideShowBarDuration), isPresenting: true, originFrame: frame, image: tmpimage) + default: + return CustomAnimator(duration: TimeInterval(UINavigationController.hideShowBarDuration), isPresenting: false, originFrame: frame, image: tmpimage) + } + }//func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: + + func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { + guard let ci = customInteractor else { return nil } + return ci.transitionInProgress ? customInteractor : nil + } + + @IBAction func likeButtonPressed(_ sender: Any) { + (sender as! LikeButton).like() + } + +}// class PhotoController + diff --git a/!main project/GeekbrainsUI/Screens/PhotoController/PhotoPresenter.swift b/!main project/GeekbrainsUI/Screens/PhotoController/PhotoPresenter.swift index 641401e..4007ab9 100644 --- a/!main project/GeekbrainsUI/Screens/PhotoController/PhotoPresenter.swift +++ b/!main project/GeekbrainsUI/Screens/PhotoController/PhotoPresenter.swift @@ -6,4 +6,40 @@ // Copyright © 2020 raskin-sa. All rights reserved. // -import Foundation +import RealmSwift + +protocol PhotosPresenter{ + func viewDidLoad() + func getVKPhotosRealmAtIndex(indexPath: IndexPath) -> VKPhotosRealm? +} + +class PhotosPresenterImplementation: PhotosPresenter { + + private var vkAPI: VKAPi + private var database: PhotosRepositorySource + private var localVKUserRealm: VKUserRealm + + private var photosResult: Results! + private weak var view: PhotoController? + + + init (database: PhotosRepositorySource, view: PhotoListView, VKUserRealm: VKUserRealm) { + vkAPI = VKAPi() + self.database = database + self.localVKUserRealm = VKUserRealm + + do + { + self.photosResult = try database.getAllPhotos() + }catch { print(error)} + } + + func viewDidLoad() { + } +}//class PhotosPresenterImplementation + +extension PhotosPresenterImplementation { + func getVKPhotosRealmAtIndex(indexPath: IndexPath) -> VKPhotosRealm? { + return photosResult[indexPath.row] + } +} diff --git a/!main project/GeekbrainsUI/Screens/PhotoController/PhotosConfigurator.swift b/!main project/GeekbrainsUI/Screens/PhotoController/PhotosConfigurator.swift index 059d049..7a15104 100644 --- a/!main project/GeekbrainsUI/Screens/PhotoController/PhotosConfigurator.swift +++ b/!main project/GeekbrainsUI/Screens/PhotoController/PhotosConfigurator.swift @@ -6,4 +6,14 @@ // Copyright © 2020 raskin-sa. All rights reserved. // -import Foundation +protocol PhotosConfigurator { + func configure(view: PhotoController, VKUserRealm: VKUserRealm) +} + +class PhotosConfiguratorImplementation: PhotosConfigurator{ + func configure(view: PhotoController, VKUserRealm: VKUserRealm) { +// view.presenter = PhotosPresenterImplementation(database: PhotoRepository(), +// view: view, +// VKUserRealm: VKUserRealm) + } +} diff --git a/!main project/GeekbrainsUI/UI/Animations/AnimationApple.swift b/!main project/GeekbrainsUI/UI/Animations/AnimationApple.swift new file mode 100644 index 0000000..bd9f713 --- /dev/null +++ b/!main project/GeekbrainsUI/UI/Animations/AnimationApple.swift @@ -0,0 +1,128 @@ +// +// AnimationApple.swift +// GeekbrainsUI +// +// Created by raskin-sa on 18/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit + +class AnimationApple: UIView { + + let firstBirdPath: UIBezierPath = { + + let freeform = UIBezierPath() + // freeform.move(to: CGPoint(x:110.89, y:99.2 )) + freeform.move(to: CGPoint(x:50, y:50 )) + freeform.addLine(to: CGPoint(x: 50, y: 150)) + freeform.addLine(to: CGPoint(x: 150, y: 50)) + freeform.addLine(to: CGPoint(x: 50, y: 50)) + // freeform.addLine(to: CGPoint(x:150, y:150)) + // freeform.close() + + return freeform +} () + + let secondBirdPath: UIBezierPath = { + + let freeform1 = UIBezierPath() + freeform1.move(to: CGPoint(x:50, y:50 )) + freeform1.addLine(to: CGPoint(x: 50, y: 150)) + freeform1.addLine(to: CGPoint(x: 260, y: 160)) + freeform1.addLine(to: CGPoint(x: 260, y: 60)) + freeform1.addLine(to: CGPoint(x:160, y:60)) + freeform1.addLine(to: CGPoint(x:50, y:50)) + // freeform.close() + + return freeform1 + } () + + + override func draw(_ rect: CGRect) { + super.draw(rect) + + /* + let firstBirdLayer = CAShapeLayer() + // firstBirdLayer.path = firstBirdPath.cgPath + + + firstBirdLayer.strokeColor = UIColor.systemTeal.cgColor + firstBirdLayer.backgroundColor = UIColor.systemTeal.cgColor + firstBirdLayer.lineWidth = 3 + layer.addSublayer(firstBirdLayer) + */ + /* + let secondBirdLayer = CAShapeLayer() + // secondBirdLayer.path = secondBirdPath.cgPath + + + secondBirdLayer.strokeColor = UIColor.systemTeal.cgColor + secondBirdLayer.backgroundColor = UIColor.systemTeal.cgColor + secondBirdLayer.lineWidth = 3 + layer.addSublayer(secondBirdLayer) + */ + let firstBirdAnimation = CAKeyframeAnimation(keyPath: "position") + firstBirdAnimation.path = firstBirdPath.cgPath + firstBirdAnimation.duration = 10.0 + firstBirdAnimation.rotationMode = .rotateAuto + firstBirdAnimation.repeatCount = .infinity + + let firstBirdPictureLayer = CAShapeLayer() + firstBirdPictureLayer.bounds = CGRect(x: 0, y: 0, width: 35, height: 35) + firstBirdPictureLayer.contents = UIImage(named:"eagle")?.cgImage + firstBirdPictureLayer.add(firstBirdAnimation, forKey: nil) + + let secondBirdAnimation = CAKeyframeAnimation(keyPath: "position") + secondBirdAnimation.path = secondBirdPath.cgPath + secondBirdAnimation.calculationMode = CAAnimationCalculationMode.paced + secondBirdAnimation.duration = 10.0 + secondBirdAnimation.rotationMode = .rotateAuto + secondBirdAnimation.repeatCount = .infinity + + + let secondBirdPictureLayer = CAShapeLayer() + let colibri = UIImage(named:"colibri")?.cgImage + + secondBirdPictureLayer.bounds = CGRect(x: 0, y: 0, width: 35, height: 35) + + secondBirdPictureLayer.contents = colibri + + secondBirdPictureLayer.add(secondBirdAnimation,forKey: nil) + + + /* + let groupAnimation = CAAnimationGroup() + groupAnimation.duration = 5.0 + groupAnimation.animations = [firstBirdAnimation, secondBirdAnimation] + // groupAnimation.autoreverses = true + groupAnimation.repeatCount = .infinity + groupAnimation.isRemovedOnCompletion = false + groupAnimation.fillMode = .forwards + */ + + //layer.add(groupAnimation, forKey: nil) + // likeLayer.add(animationsGroup, forKey: nil) + + layer.addSublayer(firstBirdPictureLayer) + layer.addSublayer(secondBirdPictureLayer) + } + + +} //class AnimationApple: appleLogoPath.cgPath + +/* + + let animationsGroup = CAAnimationGroup() + animationsGroup.duration = 0.5 + animationsGroup.fillMode = CAMediaTimingFillMode.backwards + + let translation = CABasicAnimation(keyPath: "position.x") + translation.toValue = 100 + + + let alpha = CABasicAnimation(keyPath: "opacity") + translation.toValue = 0 + animationsGroup.animations = [translation, alpha] + + */ diff --git a/!main project/GeekbrainsUI/UI/Animations/CircleShadowImage.swift b/!main project/GeekbrainsUI/UI/Animations/CircleShadowImage.swift new file mode 100644 index 0000000..486f246 --- /dev/null +++ b/!main project/GeekbrainsUI/UI/Animations/CircleShadowImage.swift @@ -0,0 +1,76 @@ +// +// CircleShadowImage.swift +// GeekbrainsUI +// +// Created by raskin-sa on 08/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit + +class CircleShadowImage : UIView{ + var image: UIImageView! + + override init(frame:CGRect){ + super.init(frame: frame) + addImage() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + addImage() + } + + func addImage(){ + image = UIImageView(frame: frame) + addSubview(image) + } + + override func layoutSubviews() { + image.frame = bounds + + layer.backgroundColor = UIColor.clear.cgColor + layer.shadowColor = UIColor.black.cgColor + layer.shadowOpacity = 1.0 + layer.shadowRadius = 4.0 + layer.shadowOffset = CGSize(width: 0, height: 1) + + image.layer.cornerRadius = bounds.size.height / 2 + image.layer.masksToBounds = true + + } +}//class + + /* + //функция скругляет прямоугольник у фото и добавляет тень + func roundAndShadowCell (cell:FriendCell){ + + //настройки для тени + let width: CGFloat = 75 //ширина рисунка + let height: CGFloat = 75 //высота рисунка + let contactRect = CGRect(x: 0, y: 20, width: width, height: height) //прямоугольник тени. Смещаем на 20 пунктов вниз + + //задаем параметры View, в которой будет жить тень. Сама view задана в классе FriendCell + cell.myShadowView.clipsToBounds = false //тень иначе не работает + cell.myShadowView.layer.shadowColor = UIColor.black.cgColor //цвет тени - черный + cell.myShadowView.layer.shadowOpacity = 0.4 //прозрачность + cell.myShadowView.layer.shadowOffset = CGSize.zero //смещение = 0 + cell.myShadowView.layer.shadowRadius = 10 + // cell.myShadowView.backgroundColor = UIColor.link//фон такой же, как у рисунка + cell.myShadowView.layer.shadowPath = UIBezierPath(roundedRect: contactRect, cornerRadius: 35).cgPath //устанавливаем тень по координатам заданного прямоугольника ContactRect + cell.myShadowView.layer.borderWidth = 0 + //cell.myShadowView.layer.borderColor = UIColor.link.cgColor + + //кусок про скругленные углы. + + //Все операции выполняем на view из-под рисунка + + cell.userimage.layer.cornerRadius = 35 + cell.userimage.clipsToBounds = true //возвращаем true чтобы скругленный прямоугольник получился + cell.userimage.layer.borderWidth = 3 + cell.userimage.layer.borderColor = UIColor.white.cgColor + cell.myShadowView.addSubview(cell.userimage) + // cell.userimage.addSubview(cell.myShadowView) + + } +*/ diff --git a/!main project/GeekbrainsUI/UI/Animations/CommentButton.swift b/!main project/GeekbrainsUI/UI/Animations/CommentButton.swift new file mode 100644 index 0000000..38a6540 --- /dev/null +++ b/!main project/GeekbrainsUI/UI/Animations/CommentButton.swift @@ -0,0 +1,79 @@ +// +// CommentButton.swift +// GeekbrainsUI +// +// Created by raskin-sa on 16/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit + +class CustomCommentButton: UIButton{ + var Commented:Bool = false + var commentCount: Int = 0 + + func comment(){ + Commented = !Commented + + if Commented{ + setCommented() + } else { + disableComment() + } + + } + override init(frame: CGRect) { + super.init(frame: frame) + setupDefault() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupDefault() + } + + private func setupDefault(){ + setTitle(String(describing: commentCount), for: .normal) + tintColor = .gray + } + + private func setCommented(){ + commentCount += 1 + setTitle(String(describing: commentCount), for: .normal) + // animateComment(commented: Commented) + tintColor = .red + } + + private func disableComment(){ + commentCount -= 1 + setTitle(String(describing: commentCount), for: .normal) + // animateComment(commented: Commented) + tintColor = .gray + } + + func animateComment(commented: Bool){ + // bounced-анимация увеличивает размер лайка при нажатии лайка и уменьшает при dislike + var localScaleX: CGFloat = 1.0 + var localScaleY: CGFloat = 1.0 + + if commented { + localScaleX = 1.5 + localScaleY = 1.5 + } else{ + localScaleX = 0.75 + localScaleY = 0.75 + } + + UIView.animate( + withDuration: 0.5, + delay: 0, + options: [], + animations:{ self.transform = CGAffineTransform(scaleX: localScaleX, y: localScaleY)}, + completion: {_ in UIView.animate(withDuration: 0.5, delay: 0, options: [], animations: {self.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)} + ) + }) + }// func animateComment + +}//class CustomCommentButton + + diff --git a/!main project/GeekbrainsUI/UI/Animations/CustomCollectionViewLayout.swift b/!main project/GeekbrainsUI/UI/Animations/CustomCollectionViewLayout.swift new file mode 100644 index 0000000..e81cbf7 --- /dev/null +++ b/!main project/GeekbrainsUI/UI/Animations/CustomCollectionViewLayout.swift @@ -0,0 +1,88 @@ +// +// CustomCollectionViewLayout.swift +// GeekbrainsUI +// +// Created by raskin-sa on 14/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit + +enum CollectionCustomSize { + case small + case wide +} +class CustomCollectionViewLayout: UICollectionViewLayout { + + var cacheAttributes = [IndexPath: UICollectionViewLayoutAttributes]() //сохраняет в себе информацию о каждой из ячеек - как она рендерится и т.д. + var columns = 2 + var cellHeight = 200 + var containerHeight: CGFloat = 0 //высота контента + + override func prepare() { + guard let collection = collectionView else{ + return + } + let itemsCount = collection.numberOfItems(inSection: 0) //суммарное кол-во ячеек в коллекции + // let itemsCount = 7 + let commonWidth = collection.frame.width // ширина широкой ячейки + let smallWidth = collection.frame.width / CGFloat(columns) //ширина узкой ячейки + + + + var x: CGFloat = 0 + var y: CGFloat = 0 + + for element in 0.. следующая ячейка будет ниже + + case .small: + attributeForIndex.frame = CGRect(x: x, + y: y, + width: smallWidth, + height: CGFloat(cellHeight)) + + //для узкой ячейки проверяем: первая она или вторая в ряду. Для первой не нужно добавлять Y к следующей ячейке, т.к. две узкие ячейки помещаются на одном уровне + if (element + 2) % (columns + 1) == 0 || element == itemsCount - 1{ + y += CGFloat(cellHeight) + x = CGFloat(0) + } else{ + x += smallWidth + } + + } + + cacheAttributes[indexPath] = attributeForIndex + }//for + + containerHeight = y + + }// func prepare() + + override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { + return cacheAttributes[indexPath] + + } + + override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { + return cacheAttributes.values.filter{ + rect.intersects($0.frame) + } + } + + override var collectionViewContentSize: CGSize{ + return CGSize(width: collectionView!.frame.width, height: containerHeight) + } + +}//class CustomCollectionViewLayout diff --git a/!main project/GeekbrainsUI/UI/Animations/CustomNavigationControllerAnimation.swift b/!main project/GeekbrainsUI/UI/Animations/CustomNavigationControllerAnimation.swift new file mode 100644 index 0000000..14a56f6 --- /dev/null +++ b/!main project/GeekbrainsUI/UI/Animations/CustomNavigationControllerAnimation.swift @@ -0,0 +1,62 @@ +// +// CustomNavigationControllerAnimation.swift +// GeekbrainsUI +// +// Created by raskin-sa on 22/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit + +//пример с видеолекции +class CustomNavigationControllerAnimation: UINavigationController, UINavigationControllerDelegate{ + + func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController)-> UIViewControllerAnimatedTransitioning?{ + switch operation{ + case .pop: return CustomPopAnimator() + case .push: return CustomPushAnimator() + case .none: return nil + @unknown default: return nil + } + }//func + +}// class CustomNavigationControllerAnimation + + +//пример с методички +class CustomNavigationController: UINavigationController, UINavigationControllerDelegate { + + override func viewDidLoad() { + super.viewDidLoad() + delegate = self + } + + let interactiveTransition = CustomInteractiveTransition() + + func navigationController(_ navigationController: UINavigationController, + interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) + -> UIViewControllerInteractiveTransitioning? { + return interactiveTransition.hasStarted ? interactiveTransition : nil + } + + func navigationController(_ navigationController: UINavigationController, + animationControllerFor operation: UINavigationController.Operation, + from fromVC: UIViewController, + to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { + + if operation == .push { + self.interactiveTransition.viewController = toVC + return CustomPushAnimator() + } else if operation == .pop { + if navigationController.viewControllers.first != toVC{ self.interactiveTransition.viewController = toVC + } + return CustomPopAnimator() + + } + + return nil + } + + +} + diff --git a/!main project/GeekbrainsUI/UI/Animations/CustomView.swift b/!main project/GeekbrainsUI/UI/Animations/CustomView.swift new file mode 100644 index 0000000..b3f29a9 --- /dev/null +++ b/!main project/GeekbrainsUI/UI/Animations/CustomView.swift @@ -0,0 +1,71 @@ +// +// CustomView.swift +// GeekbrainsUI +// +// Created by raskin-sa on 06/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// +import UIKit + + class CustomView: UIView{ + + @IBInspectable var cornerRadius:CGFloat=20 + { + didSet{ + setNeedsDisplay() + } + } + override func draw(_ rect: CGRect) { + super .draw(rect) + +// guard let context = UIGraphicsGetCurrentContext() else{ +// return +// } + + self.layer.cornerRadius = cornerRadius + } +} + + class CustomUIImageView: UIImageView{ + @IBInspectable var cornerRadius:CGFloat=20 + { + didSet{ + setNeedsDisplay() + } + } + + @IBInspectable var shadowColor:CGColor = UIColor.black.cgColor + { + didSet{ + setNeedsDisplay() + } + } + @IBInspectable var shadowOpacity: CGFloat = 1 + { + didSet{ + setNeedsDisplay() + } + } + @IBInspectable var shadowRadius: Int = 10 + { + didSet{ + setNeedsDisplay() + } + } + @IBInspectable var shadowOffset: CGSize = .zero + { + didSet{ + setNeedsDisplay() + } + } + + + override func draw(_ rect: CGRect) { + super .draw(rect) + + self.layer.cornerRadius = cornerRadius + self.backgroundColor = UIColor.link + } + + +} diff --git a/!main project/GeekbrainsUI/UI/Animations/Gradient.swift b/!main project/GeekbrainsUI/UI/Animations/Gradient.swift new file mode 100644 index 0000000..09607d9 --- /dev/null +++ b/!main project/GeekbrainsUI/UI/Animations/Gradient.swift @@ -0,0 +1,47 @@ +// +// Gradient.swift +// GeekbrainsUI +// +// Created by raskin-sa on 09/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit + +class CustomGradient: UIView{ + var testView : UIView! + + override init(frame: CGRect) { + super.init(frame: frame) + // addImage() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + // addImage() + testView = UIView(frame: frame) + testView.frame = testView.bounds + + let gradientLayer = CAGradientLayer() + gradientLayer.frame = testView.frame + gradientLayer.colors = [UIColor.systemTeal.cgColor, UIColor.link.cgColor] + + gradientLayer.locations = [0 as NSNumber, 1 as NSNumber] + //направление градиента + gradientLayer.startPoint = CGPoint(x: 0, y: 0) + gradientLayer.endPoint = CGPoint(x: 0, y: 1) // от 0,0 до 0,1 означает строго вертикальный градиент + layer.addSublayer(gradientLayer) + + } + /* + func addImage(){ + testView = UIView(frame: frame) + addSubview(testView) + } + */ + /* override func layoutSubviews() { + + + + }*/ +} diff --git a/!main project/GeekbrainsUI/UI/Animations/LikeButton.swift b/!main project/GeekbrainsUI/UI/Animations/LikeButton.swift new file mode 100644 index 0000000..3df5c4b --- /dev/null +++ b/!main project/GeekbrainsUI/UI/Animations/LikeButton.swift @@ -0,0 +1,109 @@ +// +// LikeButton.swift +// GeekbrainsUI +// +// Created by raskin-sa on 08/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit +class LikeButton: UIButton{ + var liked:Bool = false + var likeCount = 0 + + func like(){ + liked = !liked + + if liked{ + setLiked() + } else { + disableLike() + } + + } + override init(frame: CGRect) { + super.init(frame: frame) + + setupDefault(userLiked: 0) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupDefault(userLiked: 0) + } + + public func setLikeCount(likeCount:Int, userLiked:Int){ + self.likeCount = likeCount + setupDefault(userLiked: userLiked) + } + + private func setupDefault(userLiked: Int){ + + // let img = UIImage(named: "dislike") + /* let myButton = UIButton(type: UIButton.ButtonType.custom) + myButton.frame = CGRect.init(x: 10, y: 10, width: 100, height: 45) + myButton.setImage(img, for: .normal) + //myButton.addTarget(self, action: #selector(self.buttonClicked(_:)), for: UIControl.Event.touchUpInside) + addSubview(myButton) */ + + // setImage(UIImage(named: "suit.heart"), for: .normal) + + // let image = UIImage(named: "name") as UIImage? + // self.frame = CGRect(100, 100, 100, 100) + // self.setImage(image, for: .normal) + //self.view.addSubview(button) + + setTitle(String(describing: likeCount), for: .normal) + if userLiked == 1 { + self.liked = true + tintColor = .red + }else{ + self.liked = false + tintColor = .gray + } + + // imageEdgeInsets = UIEdgeInsets(top: 25, left: 0, bottom: 0, right: 0) + titleEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: -10) + // imageView?.contentMode = .scaleAspectFit + } + + private func setLiked(){ + animateLike(liked: liked) + likeCount += 1 + // setImage(UIImage(named: "like"), for: .normal) + setTitle(String(describing: likeCount), for: .normal) + tintColor = .red + } + + private func disableLike(){ + animateLike(liked: liked) + likeCount -= 1 + // setImage(UIImage(named: "dislike"), for: .normal) + setTitle(String(describing: likeCount), for: .normal) + tintColor = .gray + } + + func animateLike(liked: Bool){ + // bounced-анимация увеличивает размер лайка при нажатии лайка и уменьшает при dislike + var localScaleX: CGFloat = 1.0 + var localScaleY: CGFloat = 1.0 + + if liked { + localScaleX = 1.5 + localScaleY = 1.5 + } else{ + localScaleX = 0.75 + localScaleY = 0.75 + } + + UIView.animate( + withDuration: 0.5, + delay: 0, + options: [], + animations:{ self.transform = CGAffineTransform(scaleX: localScaleX, y: localScaleY)}, + completion: {_ in UIView.animate(withDuration: 0.5, delay: 0, options: [], animations: {self.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)} + ) + }) + }// func animateLike + +}//class LikeButton diff --git a/!main project/GeekbrainsUI/UI/Animations/OtherAnimations.swift b/!main project/GeekbrainsUI/UI/Animations/OtherAnimations.swift new file mode 100644 index 0000000..1200676 --- /dev/null +++ b/!main project/GeekbrainsUI/UI/Animations/OtherAnimations.swift @@ -0,0 +1,54 @@ +// +// OtherAnimations.swift +// GeekbrainsUI +// +// Created by raskin-sa on 22/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit +/* +@IBOutlet weak var animationShow: AnimationApple! + +@IBOutlet weak var animationShow2: groupAnimation! + +@IBAction func animationButtonPressed(_ sender: Any) { + if animationShow.isHidden == true{ + animationShow.isHidden = false + // animationShow2.isHidden = false + + scrollView.drawRectStrokeGroupAnimation(object: Login2, strokeColor: UIColor.green.cgColor,flag: true) //animationShow2.drawRectStrokeGroupAnimation(object: Login2, strokeColor: UIColor.green.cgColor) + + } else{ + animationShow.isHidden = true + // animationShow2.isHidden = true + + scrollView.drawRectStrokeGroupAnimation(object: Login2, strokeColor: UIColor.green.cgColor,flag: false) + } +} +*/ + + +extension LoginViewController{ + +@objc func panRecognize(_ recognizer: UIPanGestureRecognizer){ + switch recognizer.state{ + case .began: + propertyAnimator = UIViewPropertyAnimator(duration: 1.0, dampingRatio: 1.0, animations:{ + self.logo.transform = CGAffineTransform(scaleX: 1.5, y: 1.5) + }) + + case .changed: + let translation = recognizer.translation(in: self.view) + propertyAnimator.fractionComplete = translation.y / 100 + case .ended: + propertyAnimator.stopAnimation(true) + propertyAnimator.addAnimations { + self.logo.transform = .identity + } + propertyAnimator.startAnimation() + default: break + } +} +}// extension LoginViewController + diff --git a/!main project/GeekbrainsUI/UI/Animations/ProgressAnimator.swift b/!main project/GeekbrainsUI/UI/Animations/ProgressAnimator.swift new file mode 100644 index 0000000..15d063b --- /dev/null +++ b/!main project/GeekbrainsUI/UI/Animations/ProgressAnimator.swift @@ -0,0 +1,43 @@ +// +// ProgressAnimator.swift +// GeekbrainsUI +// +// Created by raskin-sa on 18/02/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + +import UIKit + +/// Used for ViewControllers that need to present an activity indicator when loading data. +public protocol ActivityIndicatorPresenter { + + /// The activity indicator + var activityIndicator: UIActivityIndicatorView { get } + + /// Show the activity indicator in the view + func showActivityIndicator() + + /// Hide the activity indicator in the view + func hideActivityIndicator() +}// protocol ActivityIndicatorPresenter + +public extension ActivityIndicatorPresenter where Self: UIViewController { + + func showActivityIndicator() { + DispatchQueue.main.async { + + self.activityIndicator.style = UIActivityIndicatorView.Style.large + self.activityIndicator.frame = CGRect(x: 0, y: 0, width: 80, height: 80) //or whatever size you would like + self.activityIndicator.center = CGPoint(x: self.view.bounds.size.width / 2, y: self.view.bounds.height / 2) + self.view.addSubview(self.activityIndicator) + self.activityIndicator.startAnimating() + } + } + + func hideActivityIndicator() { + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.activityIndicator.removeFromSuperview() + } + } +}//public extension ActivityIndicatorPresenter diff --git a/!main project/GeekbrainsUI/UI/Animations/ShareButton.swift b/!main project/GeekbrainsUI/UI/Animations/ShareButton.swift new file mode 100644 index 0000000..0bf78a3 --- /dev/null +++ b/!main project/GeekbrainsUI/UI/Animations/ShareButton.swift @@ -0,0 +1,79 @@ +// +// ShareButton.swift +// GeekbrainsUI +// +// Created by raskin-sa on 16/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit +class CustomShareButton: UIButton{ + var shared:Bool = false + var shareCount: Int = 0 + + func share(){ + shared = !shared + + if shared{ + setShared() + } else { + disableShare() + } + + } + override init(frame: CGRect) { + super.init(frame: frame) + setupDefault() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupDefault() + } + + private func setupDefault(){ + setTitle(String(describing: shareCount), for: .normal) + } + + private func setShared(){ + shareCount += 1 + setTitle(String(describing: shareCount), for: .normal) + + // animateShare(shared: shared) + tintColor = .red + + } + + private func disableShare(){ + shareCount -= 1 + setTitle(String(describing: shareCount), for: .normal) + + // animateShare(shared: shared) + tintColor = .gray + } + + func animateShare(shared: Bool){ + // bounced-анимация увеличивает размер лайка при нажатии лайка и уменьшает при dislike + var localScaleX: CGFloat = 1.0 + var localScaleY: CGFloat = 1.0 + + if shared { + localScaleX = 1.5 + localScaleY = 1.5 + } else{ + localScaleX = 0.75 + localScaleY = 0.75 + } + + UIView.animate( + withDuration: 0.5, + delay: 0, + options: [], + animations:{ self.transform = CGAffineTransform(scaleX: localScaleX, y: localScaleY)}, + completion: {_ in UIView.animate(withDuration: 0.5, delay: 0, options: [], animations: {self.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)} + ) + }) + }// func animateShare + +}//class ShareButton + diff --git a/!main project/GeekbrainsUI/UI/ThreeCirclesViewController.swift b/!main project/GeekbrainsUI/UI/Animations/ThreeCirclesViewController.swift similarity index 81% rename from !main project/GeekbrainsUI/UI/ThreeCirclesViewController.swift rename to !main project/GeekbrainsUI/UI/Animations/ThreeCirclesViewController.swift index ecd009b..db7114d 100644 --- a/!main project/GeekbrainsUI/UI/ThreeCirclesViewController.swift +++ b/!main project/GeekbrainsUI/UI/Animations/ThreeCirclesViewController.swift @@ -16,7 +16,10 @@ class ThreeCirclesViewController: UIViewController { // Do any additional setup after loading the view. } - + @IBOutlet weak var animationCirlce1: UIImageView! + @IBOutlet weak var animationCircle2: UIImageView! + @IBOutlet weak var animationCirlce3: UIImageView! + /* // MARK: - Navigation diff --git a/!main project/GeekbrainsUI/UI/Animations/ThreeCirclesViewController.xib b/!main project/GeekbrainsUI/UI/Animations/ThreeCirclesViewController.xib new file mode 100644 index 0000000..2b9e9f4 --- /dev/null +++ b/!main project/GeekbrainsUI/UI/Animations/ThreeCirclesViewController.xib @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/GeekbrainsUI/UI/Animations/TransitionAnimations.swift b/!main project/GeekbrainsUI/UI/Animations/TransitionAnimations.swift new file mode 100644 index 0000000..d93160b --- /dev/null +++ b/!main project/GeekbrainsUI/UI/Animations/TransitionAnimations.swift @@ -0,0 +1,173 @@ +// +// TransitionAnimations.swift +// GeekbrainsUI +// +// Created by raskin-sa on 22/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit + +class CustomPushAnimator: NSObject, UIViewControllerAnimatedTransitioning{ + func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { + return 0.5 + } + + func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + + guard let source = transitionContext.viewController(forKey: .from) else {return} + guard let destination = transitionContext.viewController(forKey: .to) else {return} + + transitionContext.containerView.addSubview(destination.view) + destination.view.frame = source.view.frame + + //двигаем второй экран направо с поворотом 90 градусов вверх + let translation = CGAffineTransform(rotationAngle: -(CGFloat.pi/2)) + let shift = CGAffineTransform(translationX: source.view.frame.width, y: -source.view.frame.height/2) + destination.view.transform = translation.concatenating(shift) + + + + UIView.animateKeyframes(withDuration: self.transitionDuration(using: transitionContext), + delay: 0, + options: .calculationModePaced, + animations: { + //первый экран уходит налево вверх + UIView.addKeyframe(withRelativeStartTime: 0.0, + relativeDuration: 0.4, + animations: { + + let translation = CGAffineTransform(rotationAngle: (CGFloat.pi/2)) + let shift = CGAffineTransform(translationX: -(source.view.frame.width)/4, y: -source.view.frame.height/2) + source.view.transform = translation.concatenating(shift) + + }) + //второй экран появляется из правого верхнего угла + UIView.addKeyframe(withRelativeStartTime: 0.5, + relativeDuration: 0.4, + animations: { + let translation = CGAffineTransform(rotationAngle: (CGFloat.pi/16)) + let shift = CGAffineTransform(translationX: -(destination.view.frame.width)/16, y: 0) + destination.view.transform = translation.concatenating(shift) + }) + + + //второй экран встает на окончательное место + UIView.addKeyframe(withRelativeStartTime: 1.0, + relativeDuration: 0.4, + animations: { + destination.view.transform = .identity + }) + }) { finished in + if finished && !transitionContext.transitionWasCancelled { + source.view.transform = .identity + } + transitionContext.completeTransition(finished && !transitionContext.transitionWasCancelled) + } + }// func animateTransition + + +}//class CustomPushAnimator + +class CustomPopAnimator: NSObject, UIViewControllerAnimatedTransitioning{ + func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { + return 0.6 + } + + func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + guard let source = transitionContext.viewController(forKey: .from) else { return } + guard let destination = transitionContext.viewController(forKey: .to) else { return } + + transitionContext.containerView.addSubview(destination.view) + // transitionContext.containerView.sendSubviewToBack(destination.view) + destination.view.frame = source.view.frame + + //двигаем новый экран влево-вверх + let translation = CGAffineTransform(rotationAngle: (CGFloat.pi/2)) //90 градусов влево-вверх + let shift = CGAffineTransform(translationX: -source.view.frame.width, y: -source.view.frame.height/2) //назад по Х и вверх по Y + destination.view.transform = translation.concatenating(shift) + + UIView.animateKeyframes(withDuration: self.transitionDuration(using: transitionContext), + delay: 0, + options: .calculationModePaced, + animations: { + //исходный экран уходит направо-вверх + UIView.addKeyframe(withRelativeStartTime: 0, + relativeDuration: 0.75, + animations: { + let translation = CGAffineTransform(rotationAngle: -(CGFloat.pi/2)) + let shift = CGAffineTransform(translationX: destination.view.frame.width/4, y: -destination.view.frame.height/2) + source.view.transform = translation.concatenating(shift) + }) + //новый экран появляется слева + UIView.addKeyframe(withRelativeStartTime: 0.2, + relativeDuration: 0.4, + animations: { + let translation = CGAffineTransform(rotationAngle: -(CGFloat.pi/4)) + let shift = CGAffineTransform(translationX: destination.view.frame.width/4, y: 0) + destination.view.transform = translation.concatenating(shift) + }) + UIView.addKeyframe(withRelativeStartTime: 0.4, + relativeDuration: 0.4, + animations: { + destination.view.transform = .identity + }) + + + }) { finished in + if finished && !transitionContext.transitionWasCancelled { + source.removeFromParent() + } else if transitionContext.transitionWasCancelled { + destination.view.transform = .identity + } + transitionContext.completeTransition(finished && !transitionContext.transitionWasCancelled) + } + } + + +}//class CustomPopAnimator + +/////////// +/// закрываем экран жестом +///// + + +class CustomInteractiveTransition: UIPercentDrivenInteractiveTransition{ + + var viewController: UIViewController? { didSet { + let recognizer = UIScreenEdgePanGestureRecognizer(target: self, + action: #selector(handleScreenEdgeGesture(_:))) + recognizer.edges = [.left] + viewController?.view.addGestureRecognizer(recognizer) + } + }// var viewController + + var hasStarted: Bool = false + var shouldFinish: Bool = false + + @objc func handleScreenEdgeGesture(_ recognizer: UIScreenEdgePanGestureRecognizer) { + switch recognizer.state { + case .began: + self.hasStarted = true + self.viewController?.navigationController?.popViewController(animated: true) + case .changed: + let translation = recognizer.translation(in: recognizer.view) + let relativeTranslation = translation.x / (recognizer.view?.bounds.width ?? 1) + let progress = max(0, min(1, relativeTranslation)) + + self.shouldFinish = progress > 0.33 + + self.update(progress) + case .ended: + self.hasStarted = false + self.shouldFinish ? self.finish() : self.cancel() + case .cancelled: + self.hasStarted = false + self.cancel() + default: return + } + } + +}//class CustomInteractiveTransition + + diff --git a/!main project/GeekbrainsUI/UI/Animations/TransitionTestVC.swift b/!main project/GeekbrainsUI/UI/Animations/TransitionTestVC.swift new file mode 100644 index 0000000..24cbcf3 --- /dev/null +++ b/!main project/GeekbrainsUI/UI/Animations/TransitionTestVC.swift @@ -0,0 +1,61 @@ +// +// TransitionTestVC.swift +// GeekbrainsUI +// +// Created by raskin-sa on 21/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit + +class TransitionVC: UIViewController, UINavigationControllerDelegate { + var transitionInteraction: UIPercentDrivenInteractiveTransition? + + override func viewDidLoad() { + super.viewDidLoad() + + let edgeRecognizer = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(edgeRecognize(_:))) + + edgeRecognizer.edges = .left + view.addGestureRecognizer(edgeRecognizer) + + navigationController?.delegate = self + } + + var hasStarted: Bool = false + var shouldFinish: Bool = false + + @objc func edgeRecognize(_ gesture: UIScreenEdgePanGestureRecognizer){ + let translation = gesture.translation(in: gesture.view) + let percentComplete = translation.x / gesture.view!.bounds.size.width + + switch gesture.state{ + case .began: + self.hasStarted = true + transitionInteraction = UIPercentDrivenInteractiveTransition() + navigationController?.popViewController(animated: true) + case .changed: + self.shouldFinish = percentComplete > 0.33 + print(percentComplete) + transitionInteraction?.update(percentComplete) + case .ended: + self.hasStarted = false + let velocity = gesture.velocity(in: gesture.view) + print(percentComplete) + if velocity.x > 0 || percentComplete > 0.5 { + transitionInteraction?.finish() + }else { + transitionInteraction?.cancel() + } + + + default:break + }//switch + transitionInteraction = nil + }//func edgeRecognize + + func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { + // return self.hasStarted ? transitionInteraction : nil + return transitionInteraction + } +}// class TransitionVC diff --git a/!main project/GeekbrainsUI/UI/Animations/UserCD+CoreDataClass.swift b/!main project/GeekbrainsUI/UI/Animations/UserCD+CoreDataClass.swift new file mode 100644 index 0000000..3cca094 --- /dev/null +++ b/!main project/GeekbrainsUI/UI/Animations/UserCD+CoreDataClass.swift @@ -0,0 +1,15 @@ +// +// UserCD+CoreDataClass.swift +// +// +// Created by raskin-sa on 21/01/2020. +// +// + +import Foundation +import CoreData + +@objc(UserCD) +public class UserCD: NSManagedObject { + +} diff --git a/!main project/GeekbrainsUI/UI/Animations/UserCD+CoreDataProperties.swift b/!main project/GeekbrainsUI/UI/Animations/UserCD+CoreDataProperties.swift new file mode 100644 index 0000000..a6e6f1c --- /dev/null +++ b/!main project/GeekbrainsUI/UI/Animations/UserCD+CoreDataProperties.swift @@ -0,0 +1,23 @@ +// +// UserCD+CoreDataProperties.swift +// +// +// Created by raskin-sa on 21/01/2020. +// +// + +import Foundation +import CoreData + + +extension UserCD { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "UserCD") + } + + @NSManaged public var id: Int64 + @NSManaged public var photo: String? + @NSManaged public var name: String? + +} diff --git a/!main project/GeekbrainsUI/UI/Animations/ViewButton.swift b/!main project/GeekbrainsUI/UI/Animations/ViewButton.swift new file mode 100644 index 0000000..c2957b1 --- /dev/null +++ b/!main project/GeekbrainsUI/UI/Animations/ViewButton.swift @@ -0,0 +1,80 @@ +// +// ViewButton.swift +// GeekbrainsUI +// +// Created by raskin-sa on 16/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit + +class CustomViewButton: UIButton{ + var viewed:Bool = false + var viewedCount: Int = 0 + + func setLook(){ + viewed = !viewed + + if viewed{ + addLook() + } else { + disableLook() + } + + } + override init(frame: CGRect) { + super.init(frame: frame) + setupDefault() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupDefault() + } + + private func setupDefault(){ + setTitle(String(describing: viewedCount), for: .normal) + tintColor = .gray + } + + private func addLook(){ + viewedCount += 1 + setTitle(String(describing: viewedCount), for: .normal) + // animateLook(viewed: viewed) + tintColor = .red + } + + private func disableLook(){ + viewedCount -= 1 + setTitle(String(describing: viewedCount), for: .normal) + // animateLook(viewed: viewed) + tintColor = .gray + } + + func animateLook(viewed: Bool){ + // bounced-анимация увеличивает размер лайка при нажатии лайка и уменьшает при dislike + var localScaleX: CGFloat = 1.0 + var localScaleY: CGFloat = 1.0 + + if viewed { + localScaleX = 1.5 + localScaleY = 1.5 + } else{ + localScaleX = 0.75 + localScaleY = 0.75 + } + + UIView.animate( + withDuration: 0.5, + delay: 0, + options: [], + animations:{ self.transform = CGAffineTransform(scaleX: localScaleX, y: localScaleY)}, + completion: {_ in UIView.animate(withDuration: 0.5, delay: 0, options: [], animations: {self.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)} + ) + }) + }// func animateComment + +}//class CustomCommentButton + + + diff --git a/!main project/GeekbrainsUI/UI/Animations/groupAnimation.swift b/!main project/GeekbrainsUI/UI/Animations/groupAnimation.swift new file mode 100644 index 0000000..c82c537 --- /dev/null +++ b/!main project/GeekbrainsUI/UI/Animations/groupAnimation.swift @@ -0,0 +1,87 @@ +// +// groupAnimation.swift +// GeekbrainsUI +// +// Created by raskin-sa on 20/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit + +class groupAnimation:UIView{ + + override func draw(_ rect: CGRect) { + super.draw(rect) + }//draw + +}//class groupAnimation:UIView + +extension UIView{ + public func drawRectStrokeGroupAnimation(object:UIControl, strokeColor:CGColor, flag: Bool){ + let rectLayer = CAShapeLayer() + + rectLayer.path = UIBezierPath(rect: CGRect(x: object.frame.minX, y: object.frame.minY, width: object.frame.width, height: object.frame.height)).cgPath + + rectLayer.strokeColor = strokeColor + //rectLayer.opacity = 0.5 + + rectLayer.lineWidth = 3 + + + + let strokeAnimationStart = CABasicAnimation(keyPath: "strokeStart") + strokeAnimationStart.fromValue = 0 + strokeAnimationStart.toValue = 1.0 + + let strokeAnimationEnd = CABasicAnimation(keyPath: "strokeEnd") + strokeAnimationEnd.fromValue = 0 + strokeAnimationEnd.toValue = 1.2 + + let groupAnimation = CAAnimationGroup() + groupAnimation.duration = 1.0 + groupAnimation.animations = [strokeAnimationStart, strokeAnimationEnd] + groupAnimation.autoreverses = true + groupAnimation.repeatCount = .infinity + groupAnimation.isRemovedOnCompletion = true + groupAnimation.fillMode = .removed + + if flag{ //запускаем анимацию + //к основному слою вьюхи добавили слой анимации, CAShapelayer() + layer.addSublayer(rectLayer) + //в слой анимации добавили саму анимацию + rectLayer.add(groupAnimation, forKey: "strokeGroup") + //на слой анимации добавили сверху слой объекта, иначе был бы черный квадрат + // rectLayer.addSublayer(object.layer) + } + else{//останавливаем анимацию + // rectLayer.strokeColor = UIColor.clear.cgColor + groupAnimation.animations = [] + groupAnimation.repeatCount = 0 + rectLayer.lineWidth = 0 + rectLayer.path = nil + rectLayer.lineWidth = 0 + rectLayer.opacity = 0 + rectLayer.removeAllAnimations() + + + rectLayer.removeFromSuperlayer() + /* + for layer in layer.sublayers!{ + if layer.name == "rectLayer" { + layer.removeFromSuperlayer() + } + }//for +*/ + // rectLayer.addSublayer(object.layer) + } //else + + // object.layer.addSublayer(rectLayer) + } +} + +/* + if let strokeEnd = rectLayer.presentation()?.strokeEnd{ + + rectLayer.strokeEnd = strokeEnd + + }*/ diff --git a/!main project/GeekbrainsUI/UI/Animations/myMenu.swift b/!main project/GeekbrainsUI/UI/Animations/myMenu.swift new file mode 100644 index 0000000..8fcb7f9 --- /dev/null +++ b/!main project/GeekbrainsUI/UI/Animations/myMenu.swift @@ -0,0 +1,65 @@ +// +// myMenu.swift +// GeekbrainsUI +// +// Created by raskin-sa on 20/12/2019. +// Copyright © 2019 raskin-sa. All rights reserved. +// + +import UIKit + +class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource, UITextFieldDelegate { + + @IBOutlet weak var textBox: UITextField! + @IBOutlet weak var dropDown: UIPickerView! + + var list = ["1", "2", "3"] + +override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view, typically from a nib. +} + +override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. +} + + +public func numberOfComponents(in pickerView: UIPickerView) -> Int{ + return 1 + +} + +public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int{ + + return list.count + +} + +func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { + + self.view.endEditing(true) + return list[row] + +} + +func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + + self.textBox.text = self.list[row] + self.dropDown.isHidden = true + +} + +func textFieldDidBeginEditing(_ textField: UITextField) { + + if textField == self.textBox { + self.dropDown.isHidden = false + //if you dont want the users to se the keyboard type: + + textField.endEditing(true) + } + +} +} + diff --git a/!main project/GeekbrainsUI/UI/Screens/FirstScreen/FirstScreenConfigurator.swift b/!main project/GeekbrainsUI/UI/Screens/FirstScreen/FirstScreenConfigurator.swift new file mode 100644 index 0000000..be66881 --- /dev/null +++ b/!main project/GeekbrainsUI/UI/Screens/FirstScreen/FirstScreenConfigurator.swift @@ -0,0 +1,9 @@ +// +// FirstScreenConfigurator.swift +// GeekbrainsUI +// +// Created by raskin-sa on 20/02/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + +import Foundation diff --git a/!main project/GeekbrainsUI/UI/Screens/FirstScreen/FirstScreenPresenter.swift b/!main project/GeekbrainsUI/UI/Screens/FirstScreen/FirstScreenPresenter.swift new file mode 100644 index 0000000..7d9bae7 --- /dev/null +++ b/!main project/GeekbrainsUI/UI/Screens/FirstScreen/FirstScreenPresenter.swift @@ -0,0 +1,9 @@ +// +// FirstScreenPresenter.swift +// GeekbrainsUI +// +// Created by raskin-sa on 20/02/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + +import Foundation diff --git a/!main project/GeekbrainsUI/UI/Screens/ProfileController/ProfileController.swift b/!main project/GeekbrainsUI/UI/Screens/ProfileController/ProfileController.swift new file mode 100644 index 0000000..55f695a --- /dev/null +++ b/!main project/GeekbrainsUI/UI/Screens/ProfileController/ProfileController.swift @@ -0,0 +1,9 @@ +// +// ProfileController.swift +// GeekbrainsUI +// +// Created by raskin-sa on 20/02/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + +import Foundation diff --git a/!main project/GeekbrainsUI/UI/Tabbar/MainTab.swift b/!main project/GeekbrainsUI/UI/Tabbar/MainTab.swift new file mode 100644 index 0000000..61a7e69 --- /dev/null +++ b/!main project/GeekbrainsUI/UI/Tabbar/MainTab.swift @@ -0,0 +1,9 @@ +// +// MainTab.swift +// GeekbrainsUI +// +// Created by raskin-sa on 20/02/2020. +// Copyright © 2020 raskin-sa. All rights reserved. +// + +import Foundation diff --git a/!main project/GeekbrainsUI/UI/ThreeCirclesViewController.xib b/!main project/GeekbrainsUI/UI/ThreeCirclesViewController.xib deleted file mode 100644 index 4a94676..0000000 --- a/!main project/GeekbrainsUI/UI/ThreeCirclesViewController.xib +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/!main project/GeekbrainsUI/UserFirebase.swift b/!main project/GeekbrainsUI/UserFirebase.swift index c764ed4..92cf71e 100644 --- a/!main project/GeekbrainsUI/UserFirebase.swift +++ b/!main project/GeekbrainsUI/UserFirebase.swift @@ -6,4 +6,30 @@ // Copyright © 2020 raskin-sa. All rights reserved. // -import Foundation +import FirebaseDatabase + +class UserFirebase { + var username: String? + var age: Int? + var city: String? + var ref: DatabaseReference? //cсылка на текущий объект. Через нее можно удалять/изменять объект + + init?(snapshot: DataSnapshot){ + guard let dict = snapshot.value as? [String: Any] else{ + return nil + } + + username = dict["username"] as? String + age = dict["age"] as? Int + city = dict["city"] as? String + ref = snapshot.ref + + }//init + + +}//class UserFirebase + + //MARK: код добавляет столбец +// users.first?.ref?.updateChildValues(["weight":85]) + //MARK: код удаляет пользователя + // users.first?.ref?.removeValue() diff --git a/!main project/Podfile b/!main project/Podfile new file mode 100644 index 0000000..440105f --- /dev/null +++ b/!main project/Podfile @@ -0,0 +1,17 @@ +# Uncomment the next line to define a global platform for your project +# platform :ios, '9.0' + +target 'GeekbrainsUI' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! +pod 'RealmSwift' +pod 'Alamofire' +pod 'Kingfisher', '~> 5.0' +pod 'Firebase/Analytics' +pod 'Firebase/Auth' +pod 'Firebase/Database' +pod 'Firebase/Crashlytics' + + # Pods for GeekbrainsUI + +end diff --git a/!main project/Podfile.lock b/!main project/Podfile.lock new file mode 100644 index 0000000..3fc9492 --- /dev/null +++ b/!main project/Podfile.lock @@ -0,0 +1,175 @@ +PODS: + - Alamofire (4.9.1) + - Firebase/Analytics (6.16.0): + - Firebase/Core + - Firebase/Auth (6.16.0): + - Firebase/CoreOnly + - FirebaseAuth (~> 6.4.3) + - Firebase/Core (6.16.0): + - Firebase/CoreOnly + - FirebaseAnalytics (= 6.2.2) + - Firebase/CoreOnly (6.16.0): + - FirebaseCore (= 6.6.1) + - Firebase/Crashlytics (6.16.0): + - Firebase/CoreOnly + - FirebaseCrashlytics (~> 4.0.0-beta.2) + - Firebase/Database (6.16.0): + - Firebase/CoreOnly + - FirebaseDatabase (~> 6.1.4) + - FirebaseAnalytics (6.2.2): + - FirebaseCore (~> 6.6) + - FirebaseInstanceID (~> 4.3) + - GoogleAppMeasurement (= 6.2.2) + - GoogleUtilities/AppDelegateSwizzler (~> 6.0) + - GoogleUtilities/MethodSwizzler (~> 6.0) + - GoogleUtilities/Network (~> 6.0) + - "GoogleUtilities/NSData+zlib (~> 6.0)" + - nanopb (= 0.3.9011) + - FirebaseAnalyticsInterop (1.5.0) + - FirebaseAuth (6.4.3): + - FirebaseAuthInterop (~> 1.0) + - FirebaseCore (~> 6.6) + - GoogleUtilities/AppDelegateSwizzler (~> 6.5) + - GoogleUtilities/Environment (~> 6.5) + - GTMSessionFetcher/Core (~> 1.1) + - FirebaseAuthInterop (1.0.0) + - FirebaseCore (6.6.1): + - FirebaseCoreDiagnostics (~> 1.2) + - FirebaseCoreDiagnosticsInterop (~> 1.2) + - GoogleUtilities/Environment (~> 6.5) + - GoogleUtilities/Logger (~> 6.5) + - FirebaseCoreDiagnostics (1.2.0): + - FirebaseCoreDiagnosticsInterop (~> 1.2) + - GoogleDataTransportCCTSupport (~> 1.3) + - GoogleUtilities/Environment (~> 6.5) + - GoogleUtilities/Logger (~> 6.5) + - nanopb (~> 0.3.901) + - FirebaseCoreDiagnosticsInterop (1.2.0) + - FirebaseCrashlytics (4.0.0-beta.3): + - FirebaseAnalyticsInterop (~> 1.2) + - FirebaseCore (~> 6.6) + - FirebaseInstanceID (~> 4.3) + - PromisesObjC (~> 1.2) + - FirebaseDatabase (6.1.4): + - FirebaseAuthInterop (~> 1.0) + - FirebaseCore (~> 6.0) + - leveldb-library (~> 1.22) + - FirebaseInstallations (1.1.0): + - FirebaseCore (~> 6.6) + - GoogleUtilities/UserDefaults (~> 6.5) + - PromisesObjC (~> 1.2) + - FirebaseInstanceID (4.3.0): + - FirebaseCore (~> 6.6) + - FirebaseInstallations (~> 1.0) + - GoogleUtilities/Environment (~> 6.5) + - GoogleUtilities/UserDefaults (~> 6.5) + - GoogleAppMeasurement (6.2.2): + - GoogleUtilities/AppDelegateSwizzler (~> 6.0) + - GoogleUtilities/MethodSwizzler (~> 6.0) + - GoogleUtilities/Network (~> 6.0) + - "GoogleUtilities/NSData+zlib (~> 6.0)" + - nanopb (= 0.3.9011) + - GoogleDataTransport (3.3.1) + - GoogleDataTransportCCTSupport (1.3.1): + - GoogleDataTransport (~> 3.3) + - nanopb (~> 0.3.901) + - GoogleUtilities/AppDelegateSwizzler (6.5.1): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Environment (6.5.1) + - GoogleUtilities/Logger (6.5.1): + - GoogleUtilities/Environment + - GoogleUtilities/MethodSwizzler (6.5.1): + - GoogleUtilities/Logger + - GoogleUtilities/Network (6.5.1): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (6.5.1)" + - GoogleUtilities/Reachability (6.5.1): + - GoogleUtilities/Logger + - GoogleUtilities/UserDefaults (6.5.1): + - GoogleUtilities/Logger + - GTMSessionFetcher/Core (1.3.1) + - Kingfisher (5.13.0): + - Kingfisher/Core (= 5.13.0) + - Kingfisher/Core (5.13.0) + - leveldb-library (1.22) + - nanopb (0.3.9011): + - nanopb/decode (= 0.3.9011) + - nanopb/encode (= 0.3.9011) + - nanopb/decode (0.3.9011) + - nanopb/encode (0.3.9011) + - PromisesObjC (1.2.8) + - Realm (4.3.1): + - Realm/Headers (= 4.3.1) + - Realm/Headers (4.3.1) + - RealmSwift (4.3.1): + - Realm (= 4.3.1) + +DEPENDENCIES: + - Alamofire + - Firebase/Analytics + - Firebase/Auth + - Firebase/Crashlytics + - Firebase/Database + - Kingfisher (~> 5.0) + - RealmSwift + +SPEC REPOS: + trunk: + - Alamofire + - Firebase + - FirebaseAnalytics + - FirebaseAnalyticsInterop + - FirebaseAuth + - FirebaseAuthInterop + - FirebaseCore + - FirebaseCoreDiagnostics + - FirebaseCoreDiagnosticsInterop + - FirebaseCrashlytics + - FirebaseDatabase + - FirebaseInstallations + - FirebaseInstanceID + - GoogleAppMeasurement + - GoogleDataTransport + - GoogleDataTransportCCTSupport + - GoogleUtilities + - GTMSessionFetcher + - Kingfisher + - leveldb-library + - nanopb + - PromisesObjC + - Realm + - RealmSwift + +SPEC CHECKSUMS: + Alamofire: 85e8a02c69d6020a0d734f6054870d7ecb75cf18 + Firebase: 497158b816d0a86fc31babbd05546fcd7e6083ff + FirebaseAnalytics: cf95d3aab897612783020fbd98401d5366f135ee + FirebaseAnalyticsInterop: 3f86269c38ae41f47afeb43ebf32a001f58fcdae + FirebaseAuth: 5ce2b03a3d7fe56b7a6e4c5ec7ff1522890b1d6f + FirebaseAuthInterop: 0ffa57668be100582bb7643d4fcb7615496c41fc + FirebaseCore: 85064903ed6c28e47fec9c7bd149d94ba1b6b6e7 + FirebaseCoreDiagnostics: 5e78803ab276bc5b50340e3c539c06c3de35c649 + FirebaseCoreDiagnosticsInterop: 296e2c5f5314500a850ad0b83e9e7c10b011a850 + FirebaseCrashlytics: 81ac4950302353dc6b5f8d5cc287397725b54c97 + FirebaseDatabase: 0144e0706a4761f1b0e8679572eba8095ddb59be + FirebaseInstallations: 575cd32f2aec0feeb0e44f5d0110a09e5e60b47b + FirebaseInstanceID: 6668efc1655a4052c083f287a7141f1ead12f9c2 + GoogleAppMeasurement: d0560d915abf15e692e8538ba1d58442217b6aff + GoogleDataTransport: 0048df6388dab1c254799f2a30365b1dffe20422 + GoogleDataTransportCCTSupport: f880d70972efa2ed1be4e9173a0f4c5f3dc2d176 + GoogleUtilities: 06eb53bb579efe7099152735900dd04bf09e7275 + GTMSessionFetcher: cea130bbfe5a7edc8d06d3f0d17288c32ffe9925 + Kingfisher: 0d334cada987fcddbe9b5cec516406e1ee9d4748 + leveldb-library: 55d93ee664b4007aac644a782d11da33fba316f7 + nanopb: 18003b5e52dab79db540fe93fe9579f399bd1ccd + PromisesObjC: c119f3cd559f50b7ae681fa59dc1acd19173b7e6 + Realm: 31dc40934081ef740f60feedc46d88473cbfeb40 + RealmSwift: b5199e9a41f67b99f51acf6874a6166f5fbe93d1 + +PODFILE CHECKSUM: b74a28c3a66700466c956a4451099909423c4a27 + +COCOAPODS: 1.8.4 diff --git a/!main project/Pods/Alamofire/LICENSE b/!main project/Pods/Alamofire/LICENSE new file mode 100644 index 0000000..38a301a --- /dev/null +++ b/!main project/Pods/Alamofire/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 Alamofire Software Foundation (http://alamofire.org/) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/!main project/Pods/Alamofire/README.md b/!main project/Pods/Alamofire/README.md new file mode 100644 index 0000000..bafa09a --- /dev/null +++ b/!main project/Pods/Alamofire/README.md @@ -0,0 +1,234 @@ +![Alamofire: Elegant Networking in Swift](https://raw.githubusercontent.com/Alamofire/Alamofire/master/alamofire.png) + +[![Build Status](https://travis-ci.org/Alamofire/Alamofire.svg?branch=master)](https://travis-ci.org/Alamofire/Alamofire) +[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/Alamofire.svg)](https://img.shields.io/cocoapods/v/Alamofire.svg) +[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) +[![Platform](https://img.shields.io/cocoapods/p/Alamofire.svg?style=flat)](https://alamofire.github.io/Alamofire) +[![Twitter](https://img.shields.io/badge/twitter-@AlamofireSF-blue.svg?style=flat)](https://twitter.com/AlamofireSF) +[![Gitter](https://badges.gitter.im/Alamofire/Alamofire.svg)](https://gitter.im/Alamofire/Alamofire?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) + +Alamofire is an HTTP networking library written in Swift. + +- [Features](#features) +- [Component Libraries](#component-libraries) +- [Requirements](#requirements) +- [Migration Guides](#migration-guides) +- [Communication](#communication) +- [Installation](#installation) +- [Usage](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md) + - **Intro -** [Making a Request](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#making-a-request), [Response Handling](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#response-handling), [Response Validation](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#response-validation), [Response Caching](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#response-caching) + - **HTTP -** [HTTP Methods](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#http-methods), [Parameter Encoding](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#parameter-encoding), [HTTP Headers](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#http-headers), [Authentication](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#authentication) + - **Large Data -** [Downloading Data to a File](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#downloading-data-to-a-file), [Uploading Data to a Server](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#uploading-data-to-a-server) + - **Tools -** [Statistical Metrics](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#statistical-metrics), [cURL Command Output](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#curl-command-output) +- [Advanced Usage](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md) + - **URL Session -** [Session Manager](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#session-manager), [Session Delegate](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#session-delegate), [Request](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#request) + - **Routing -** [Routing Requests](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#routing-requests), [Adapting and Retrying Requests](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#adapting-and-retrying-requests) + - **Model Objects -** [Custom Response Serialization](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#custom-response-serialization) + - **Connection -** [Security](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#security), [Network Reachability](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#network-reachability) +- [Open Radars](#open-radars) +- [FAQ](#faq) +- [Credits](#credits) +- [Donations](#donations) +- [License](#license) + +## Features + +- [x] Chainable Request / Response Methods +- [x] URL / JSON / plist Parameter Encoding +- [x] Upload File / Data / Stream / MultipartFormData +- [x] Download File using Request or Resume Data +- [x] Authentication with URLCredential +- [x] HTTP Response Validation +- [x] Upload and Download Progress Closures with Progress +- [x] cURL Command Output +- [x] Dynamically Adapt and Retry Requests +- [x] TLS Certificate and Public Key Pinning +- [x] Network Reachability +- [x] Comprehensive Unit and Integration Test Coverage +- [x] [Complete Documentation](https://alamofire.github.io/Alamofire) + +## Component Libraries + +In order to keep Alamofire focused specifically on core networking implementations, additional component libraries have been created by the [Alamofire Software Foundation](https://github.com/Alamofire/Foundation) to bring additional functionality to the Alamofire ecosystem. + +- [AlamofireImage](https://github.com/Alamofire/AlamofireImage) - An image library including image response serializers, `UIImage` and `UIImageView` extensions, custom image filters, an auto-purging in-memory cache and a priority-based image downloading system. +- [AlamofireNetworkActivityIndicator](https://github.com/Alamofire/AlamofireNetworkActivityIndicator) - Controls the visibility of the network activity indicator on iOS using Alamofire. It contains configurable delay timers to help mitigate flicker and can support `URLSession` instances not managed by Alamofire. + +## Requirements + +- iOS 8.0+ / macOS 10.10+ / tvOS 9.0+ / watchOS 2.0+ +- Xcode 9.3+ +- Swift 4.0+ + +## Migration Guides + +- [Alamofire 4.0 Migration Guide](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Alamofire%204.0%20Migration%20Guide.md) +- [Alamofire 3.0 Migration Guide](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Alamofire%203.0%20Migration%20Guide.md) +- [Alamofire 2.0 Migration Guide](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Alamofire%202.0%20Migration%20Guide.md) + +## Communication +- If you **need help with making network requests**, use [Stack Overflow](https://stackoverflow.com/questions/tagged/alamofire) and tag `alamofire`. +- If you need to **find or understand an API**, check [our documentation](http://alamofire.github.io/Alamofire/) or [Apple's documentation for `URLSession`](https://developer.apple.com/documentation/foundation/url_loading_system), on top of which Alamofire is built. +- If you need **help with an Alamofire feature**, use [our forum on swift.org](https://forums.swift.org/c/related-projects/alamofire). +- If you'd like to **discuss Alamofire best practices**, use [our forum on swift.org](https://forums.swift.org/c/related-projects/alamofire). +- If you'd like to **discuss a feature request**, use [our forum on swift.org](https://forums.swift.org/c/related-projects/alamofire). +- If you **found a bug**, open an issue and follow the guide. The more detail the better! +- If you **want to contribute**, submit a pull request. + +## Installation + +### CocoaPods + +[CocoaPods](https://cocoapods.org) is a dependency manager for Cocoa projects. You can install it with the following command: + +```bash +$ gem install cocoapods +``` + +> CocoaPods 1.7+ is required to build Alamofire 4.9+. + +To integrate Alamofire into your Xcode project using CocoaPods, specify it in your `Podfile`: + +```ruby +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '10.0' +use_frameworks! + +target '' do + pod 'Alamofire', '~> 4.9' +end +``` + +Then, run the following command: + +```bash +$ pod install +``` + +### Carthage + +[Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks. + +You can install Carthage with [Homebrew](https://brew.sh/) using the following command: + +```bash +$ brew install carthage +``` + +To integrate Alamofire into your Xcode project using Carthage, specify it in your `Cartfile`: + +```ogdl +github "Alamofire/Alamofire" ~> 4.9 +``` + +Run `carthage update` to build the framework and drag the built `Alamofire.framework` into your Xcode project. + +### Swift Package Manager + +The [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler. It is in early development, but Alamofire does support its use on supported platforms. + +Once you have your Swift package set up, adding Alamofire as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`. + +#### Swift 4 + +```swift +dependencies: [ + .package(url: "https://github.com/Alamofire/Alamofire.git", from: "4.9") +] +``` + +### Manually + +If you prefer not to use any of the aforementioned dependency managers, you can integrate Alamofire into your project manually. + +#### Embedded Framework + +- Open up Terminal, `cd` into your top-level project directory, and run the following command "if" your project is not initialized as a git repository: + + ```bash + $ git init + ``` + +- Add Alamofire as a git [submodule](https://git-scm.com/docs/git-submodule) by running the following command: + + ```bash + $ git submodule add https://github.com/Alamofire/Alamofire.git + ``` + +- Open the new `Alamofire` folder, and drag the `Alamofire.xcodeproj` into the Project Navigator of your application's Xcode project. + + > It should appear nested underneath your application's blue project icon. Whether it is above or below all the other Xcode groups does not matter. + +- Select the `Alamofire.xcodeproj` in the Project Navigator and verify the deployment target matches that of your application target. +- Next, select your application project in the Project Navigator (blue project icon) to navigate to the target configuration window and select the application target under the "Targets" heading in the sidebar. +- In the tab bar at the top of that window, open the "General" panel. +- Click on the `+` button under the "Embedded Binaries" section. +- You will see two different `Alamofire.xcodeproj` folders each with two different versions of the `Alamofire.framework` nested inside a `Products` folder. + + > It does not matter which `Products` folder you choose from, but it does matter whether you choose the top or bottom `Alamofire.framework`. + +- Select the top `Alamofire.framework` for iOS and the bottom one for OS X. + + > You can verify which one you selected by inspecting the build log for your project. The build target for `Alamofire` will be listed as either `Alamofire iOS`, `Alamofire macOS`, `Alamofire tvOS` or `Alamofire watchOS`. + +- And that's it! + + > The `Alamofire.framework` is automagically added as a target dependency, linked framework and embedded framework in a copy files build phase which is all you need to build on the simulator and a device. + +## Open Radars + +The following radars have some effect on the current implementation of Alamofire. + +- [`rdar://21349340`](http://www.openradar.me/radar?id=5517037090635776) - Compiler throwing warning due to toll-free bridging issue in test case +- `rdar://26870455` - Background URL Session Configurations do not work in the simulator +- `rdar://26849668` - Some URLProtocol APIs do not properly handle `URLRequest` +- [`rdar://36082113`](http://openradar.appspot.com/radar?id=4942308441063424) - `URLSessionTaskMetrics` failing to link on watchOS 3.0+ + +## Resolved Radars + +The following radars have been resolved over time after being filed against the Alamofire project. + +- [`rdar://26761490`](http://www.openradar.me/radar?id=5010235949318144) - Swift string interpolation causing memory leak with common usage (Resolved on 9/1/17 in Xcode 9 beta 6). + +## FAQ + +### What's the origin of the name Alamofire? + +Alamofire is named after the [Alamo Fire flower](https://aggie-horticulture.tamu.edu/wildseed/alamofire.html), a hybrid variant of the Bluebonnet, the official state flower of Texas. + +### What logic belongs in a Router vs. a Request Adapter? + +Simple, static data such as paths, parameters and common headers belong in the `Router`. Dynamic data such as an `Authorization` header whose value can changed based on an authentication system belongs in a `RequestAdapter`. + +The reason the dynamic data MUST be placed into the `RequestAdapter` is to support retry operations. When a `Request` is retried, the original request is not rebuilt meaning the `Router` will not be called again. The `RequestAdapter` is called again allowing the dynamic data to be updated on the original request before retrying the `Request`. + +## Credits + +Alamofire is owned and maintained by the [Alamofire Software Foundation](http://alamofire.org). You can follow them on Twitter at [@AlamofireSF](https://twitter.com/AlamofireSF) for project updates and releases. + +### Security Disclosure + +If you believe you have identified a security vulnerability with Alamofire, you should report it as soon as possible via email to security@alamofire.org. Please do not post it to a public issue tracker. + +## Donations + +The [ASF](https://github.com/Alamofire/Foundation#members) is looking to raise money to officially stay registered as a federal non-profit organization. +Registering will allow us members to gain some legal protections and also allow us to put donations to use, tax free. +Donating to the ASF will enable us to: + +- Pay our yearly legal fees to keep the non-profit in good status +- Pay for our mail servers to help us stay on top of all questions and security issues +- Potentially fund test servers to make it easier for us to test the edge cases +- Potentially fund developers to work on one of our projects full-time + +The community adoption of the ASF libraries has been amazing. +We are greatly humbled by your enthusiasm around the projects, and want to continue to do everything we can to move the needle forward. +With your continued support, the ASF will be able to improve its reach and also provide better legal safety for the core members. +If you use any of our libraries for work, see if your employers would be interested in donating. +Any amount you can donate today to help us reach our goal would be greatly appreciated. + +[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=W34WPEE74APJQ) + +## License + +Alamofire is released under the MIT license. [See LICENSE](https://github.com/Alamofire/Alamofire/blob/master/LICENSE) for details. diff --git a/!main project/Pods/Alamofire/Source/AFError.swift b/!main project/Pods/Alamofire/Source/AFError.swift new file mode 100644 index 0000000..b163f60 --- /dev/null +++ b/!main project/Pods/Alamofire/Source/AFError.swift @@ -0,0 +1,460 @@ +// +// AFError.swift +// +// Copyright (c) 2014 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +/// `AFError` is the error type returned by Alamofire. It encompasses a few different types of errors, each with +/// their own associated reasons. +/// +/// - invalidURL: Returned when a `URLConvertible` type fails to create a valid `URL`. +/// - parameterEncodingFailed: Returned when a parameter encoding object throws an error during the encoding process. +/// - multipartEncodingFailed: Returned when some step in the multipart encoding process fails. +/// - responseValidationFailed: Returned when a `validate()` call fails. +/// - responseSerializationFailed: Returned when a response serializer encounters an error in the serialization process. +public enum AFError: Error { + /// The underlying reason the parameter encoding error occurred. + /// + /// - missingURL: The URL request did not have a URL to encode. + /// - jsonEncodingFailed: JSON serialization failed with an underlying system error during the + /// encoding process. + /// - propertyListEncodingFailed: Property list serialization failed with an underlying system error during + /// encoding process. + public enum ParameterEncodingFailureReason { + case missingURL + case jsonEncodingFailed(error: Error) + case propertyListEncodingFailed(error: Error) + } + + /// The underlying reason the multipart encoding error occurred. + /// + /// - bodyPartURLInvalid: The `fileURL` provided for reading an encodable body part isn't a + /// file URL. + /// - bodyPartFilenameInvalid: The filename of the `fileURL` provided has either an empty + /// `lastPathComponent` or `pathExtension. + /// - bodyPartFileNotReachable: The file at the `fileURL` provided was not reachable. + /// - bodyPartFileNotReachableWithError: Attempting to check the reachability of the `fileURL` provided threw + /// an error. + /// - bodyPartFileIsDirectory: The file at the `fileURL` provided is actually a directory. + /// - bodyPartFileSizeNotAvailable: The size of the file at the `fileURL` provided was not returned by + /// the system. + /// - bodyPartFileSizeQueryFailedWithError: The attempt to find the size of the file at the `fileURL` provided + /// threw an error. + /// - bodyPartInputStreamCreationFailed: An `InputStream` could not be created for the provided `fileURL`. + /// - outputStreamCreationFailed: An `OutputStream` could not be created when attempting to write the + /// encoded data to disk. + /// - outputStreamFileAlreadyExists: The encoded body data could not be writtent disk because a file + /// already exists at the provided `fileURL`. + /// - outputStreamURLInvalid: The `fileURL` provided for writing the encoded body data to disk is + /// not a file URL. + /// - outputStreamWriteFailed: The attempt to write the encoded body data to disk failed with an + /// underlying error. + /// - inputStreamReadFailed: The attempt to read an encoded body part `InputStream` failed with + /// underlying system error. + public enum MultipartEncodingFailureReason { + case bodyPartURLInvalid(url: URL) + case bodyPartFilenameInvalid(in: URL) + case bodyPartFileNotReachable(at: URL) + case bodyPartFileNotReachableWithError(atURL: URL, error: Error) + case bodyPartFileIsDirectory(at: URL) + case bodyPartFileSizeNotAvailable(at: URL) + case bodyPartFileSizeQueryFailedWithError(forURL: URL, error: Error) + case bodyPartInputStreamCreationFailed(for: URL) + + case outputStreamCreationFailed(for: URL) + case outputStreamFileAlreadyExists(at: URL) + case outputStreamURLInvalid(url: URL) + case outputStreamWriteFailed(error: Error) + + case inputStreamReadFailed(error: Error) + } + + /// The underlying reason the response validation error occurred. + /// + /// - dataFileNil: The data file containing the server response did not exist. + /// - dataFileReadFailed: The data file containing the server response could not be read. + /// - missingContentType: The response did not contain a `Content-Type` and the `acceptableContentTypes` + /// provided did not contain wildcard type. + /// - unacceptableContentType: The response `Content-Type` did not match any type in the provided + /// `acceptableContentTypes`. + /// - unacceptableStatusCode: The response status code was not acceptable. + public enum ResponseValidationFailureReason { + case dataFileNil + case dataFileReadFailed(at: URL) + case missingContentType(acceptableContentTypes: [String]) + case unacceptableContentType(acceptableContentTypes: [String], responseContentType: String) + case unacceptableStatusCode(code: Int) + } + + /// The underlying reason the response serialization error occurred. + /// + /// - inputDataNil: The server response contained no data. + /// - inputDataNilOrZeroLength: The server response contained no data or the data was zero length. + /// - inputFileNil: The file containing the server response did not exist. + /// - inputFileReadFailed: The file containing the server response could not be read. + /// - stringSerializationFailed: String serialization failed using the provided `String.Encoding`. + /// - jsonSerializationFailed: JSON serialization failed with an underlying system error. + /// - propertyListSerializationFailed: Property list serialization failed with an underlying system error. + public enum ResponseSerializationFailureReason { + case inputDataNil + case inputDataNilOrZeroLength + case inputFileNil + case inputFileReadFailed(at: URL) + case stringSerializationFailed(encoding: String.Encoding) + case jsonSerializationFailed(error: Error) + case propertyListSerializationFailed(error: Error) + } + + case invalidURL(url: URLConvertible) + case parameterEncodingFailed(reason: ParameterEncodingFailureReason) + case multipartEncodingFailed(reason: MultipartEncodingFailureReason) + case responseValidationFailed(reason: ResponseValidationFailureReason) + case responseSerializationFailed(reason: ResponseSerializationFailureReason) +} + +// MARK: - Adapt Error + +struct AdaptError: Error { + let error: Error +} + +extension Error { + var underlyingAdaptError: Error? { return (self as? AdaptError)?.error } +} + +// MARK: - Error Booleans + +extension AFError { + /// Returns whether the AFError is an invalid URL error. + public var isInvalidURLError: Bool { + if case .invalidURL = self { return true } + return false + } + + /// Returns whether the AFError is a parameter encoding error. When `true`, the `underlyingError` property will + /// contain the associated value. + public var isParameterEncodingError: Bool { + if case .parameterEncodingFailed = self { return true } + return false + } + + /// Returns whether the AFError is a multipart encoding error. When `true`, the `url` and `underlyingError` properties + /// will contain the associated values. + public var isMultipartEncodingError: Bool { + if case .multipartEncodingFailed = self { return true } + return false + } + + /// Returns whether the `AFError` is a response validation error. When `true`, the `acceptableContentTypes`, + /// `responseContentType`, and `responseCode` properties will contain the associated values. + public var isResponseValidationError: Bool { + if case .responseValidationFailed = self { return true } + return false + } + + /// Returns whether the `AFError` is a response serialization error. When `true`, the `failedStringEncoding` and + /// `underlyingError` properties will contain the associated values. + public var isResponseSerializationError: Bool { + if case .responseSerializationFailed = self { return true } + return false + } +} + +// MARK: - Convenience Properties + +extension AFError { + /// The `URLConvertible` associated with the error. + public var urlConvertible: URLConvertible? { + switch self { + case .invalidURL(let url): + return url + default: + return nil + } + } + + /// The `URL` associated with the error. + public var url: URL? { + switch self { + case .multipartEncodingFailed(let reason): + return reason.url + default: + return nil + } + } + + /// The `Error` returned by a system framework associated with a `.parameterEncodingFailed`, + /// `.multipartEncodingFailed` or `.responseSerializationFailed` error. + public var underlyingError: Error? { + switch self { + case .parameterEncodingFailed(let reason): + return reason.underlyingError + case .multipartEncodingFailed(let reason): + return reason.underlyingError + case .responseSerializationFailed(let reason): + return reason.underlyingError + default: + return nil + } + } + + /// The acceptable `Content-Type`s of a `.responseValidationFailed` error. + public var acceptableContentTypes: [String]? { + switch self { + case .responseValidationFailed(let reason): + return reason.acceptableContentTypes + default: + return nil + } + } + + /// The response `Content-Type` of a `.responseValidationFailed` error. + public var responseContentType: String? { + switch self { + case .responseValidationFailed(let reason): + return reason.responseContentType + default: + return nil + } + } + + /// The response code of a `.responseValidationFailed` error. + public var responseCode: Int? { + switch self { + case .responseValidationFailed(let reason): + return reason.responseCode + default: + return nil + } + } + + /// The `String.Encoding` associated with a failed `.stringResponse()` call. + public var failedStringEncoding: String.Encoding? { + switch self { + case .responseSerializationFailed(let reason): + return reason.failedStringEncoding + default: + return nil + } + } +} + +extension AFError.ParameterEncodingFailureReason { + var underlyingError: Error? { + switch self { + case .jsonEncodingFailed(let error), .propertyListEncodingFailed(let error): + return error + default: + return nil + } + } +} + +extension AFError.MultipartEncodingFailureReason { + var url: URL? { + switch self { + case .bodyPartURLInvalid(let url), .bodyPartFilenameInvalid(let url), .bodyPartFileNotReachable(let url), + .bodyPartFileIsDirectory(let url), .bodyPartFileSizeNotAvailable(let url), + .bodyPartInputStreamCreationFailed(let url), .outputStreamCreationFailed(let url), + .outputStreamFileAlreadyExists(let url), .outputStreamURLInvalid(let url), + .bodyPartFileNotReachableWithError(let url, _), .bodyPartFileSizeQueryFailedWithError(let url, _): + return url + default: + return nil + } + } + + var underlyingError: Error? { + switch self { + case .bodyPartFileNotReachableWithError(_, let error), .bodyPartFileSizeQueryFailedWithError(_, let error), + .outputStreamWriteFailed(let error), .inputStreamReadFailed(let error): + return error + default: + return nil + } + } +} + +extension AFError.ResponseValidationFailureReason { + var acceptableContentTypes: [String]? { + switch self { + case .missingContentType(let types), .unacceptableContentType(let types, _): + return types + default: + return nil + } + } + + var responseContentType: String? { + switch self { + case .unacceptableContentType(_, let responseType): + return responseType + default: + return nil + } + } + + var responseCode: Int? { + switch self { + case .unacceptableStatusCode(let code): + return code + default: + return nil + } + } +} + +extension AFError.ResponseSerializationFailureReason { + var failedStringEncoding: String.Encoding? { + switch self { + case .stringSerializationFailed(let encoding): + return encoding + default: + return nil + } + } + + var underlyingError: Error? { + switch self { + case .jsonSerializationFailed(let error), .propertyListSerializationFailed(let error): + return error + default: + return nil + } + } +} + +// MARK: - Error Descriptions + +extension AFError: LocalizedError { + public var errorDescription: String? { + switch self { + case .invalidURL(let url): + return "URL is not valid: \(url)" + case .parameterEncodingFailed(let reason): + return reason.localizedDescription + case .multipartEncodingFailed(let reason): + return reason.localizedDescription + case .responseValidationFailed(let reason): + return reason.localizedDescription + case .responseSerializationFailed(let reason): + return reason.localizedDescription + } + } +} + +extension AFError.ParameterEncodingFailureReason { + var localizedDescription: String { + switch self { + case .missingURL: + return "URL request to encode was missing a URL" + case .jsonEncodingFailed(let error): + return "JSON could not be encoded because of error:\n\(error.localizedDescription)" + case .propertyListEncodingFailed(let error): + return "PropertyList could not be encoded because of error:\n\(error.localizedDescription)" + } + } +} + +extension AFError.MultipartEncodingFailureReason { + var localizedDescription: String { + switch self { + case .bodyPartURLInvalid(let url): + return "The URL provided is not a file URL: \(url)" + case .bodyPartFilenameInvalid(let url): + return "The URL provided does not have a valid filename: \(url)" + case .bodyPartFileNotReachable(let url): + return "The URL provided is not reachable: \(url)" + case .bodyPartFileNotReachableWithError(let url, let error): + return ( + "The system returned an error while checking the provided URL for " + + "reachability.\nURL: \(url)\nError: \(error)" + ) + case .bodyPartFileIsDirectory(let url): + return "The URL provided is a directory: \(url)" + case .bodyPartFileSizeNotAvailable(let url): + return "Could not fetch the file size from the provided URL: \(url)" + case .bodyPartFileSizeQueryFailedWithError(let url, let error): + return ( + "The system returned an error while attempting to fetch the file size from the " + + "provided URL.\nURL: \(url)\nError: \(error)" + ) + case .bodyPartInputStreamCreationFailed(let url): + return "Failed to create an InputStream for the provided URL: \(url)" + case .outputStreamCreationFailed(let url): + return "Failed to create an OutputStream for URL: \(url)" + case .outputStreamFileAlreadyExists(let url): + return "A file already exists at the provided URL: \(url)" + case .outputStreamURLInvalid(let url): + return "The provided OutputStream URL is invalid: \(url)" + case .outputStreamWriteFailed(let error): + return "OutputStream write failed with error: \(error)" + case .inputStreamReadFailed(let error): + return "InputStream read failed with error: \(error)" + } + } +} + +extension AFError.ResponseSerializationFailureReason { + var localizedDescription: String { + switch self { + case .inputDataNil: + return "Response could not be serialized, input data was nil." + case .inputDataNilOrZeroLength: + return "Response could not be serialized, input data was nil or zero length." + case .inputFileNil: + return "Response could not be serialized, input file was nil." + case .inputFileReadFailed(let url): + return "Response could not be serialized, input file could not be read: \(url)." + case .stringSerializationFailed(let encoding): + return "String could not be serialized with encoding: \(encoding)." + case .jsonSerializationFailed(let error): + return "JSON could not be serialized because of error:\n\(error.localizedDescription)" + case .propertyListSerializationFailed(let error): + return "PropertyList could not be serialized because of error:\n\(error.localizedDescription)" + } + } +} + +extension AFError.ResponseValidationFailureReason { + var localizedDescription: String { + switch self { + case .dataFileNil: + return "Response could not be validated, data file was nil." + case .dataFileReadFailed(let url): + return "Response could not be validated, data file could not be read: \(url)." + case .missingContentType(let types): + return ( + "Response Content-Type was missing and acceptable content types " + + "(\(types.joined(separator: ","))) do not match \"*/*\"." + ) + case .unacceptableContentType(let acceptableTypes, let responseType): + return ( + "Response Content-Type \"\(responseType)\" does not match any acceptable types: " + + "\(acceptableTypes.joined(separator: ","))." + ) + case .unacceptableStatusCode(let code): + return "Response status code was unacceptable: \(code)." + } + } +} diff --git a/!main project/Pods/Alamofire/Source/Alamofire.swift b/!main project/Pods/Alamofire/Source/Alamofire.swift new file mode 100644 index 0000000..20c3672 --- /dev/null +++ b/!main project/Pods/Alamofire/Source/Alamofire.swift @@ -0,0 +1,465 @@ +// +// Alamofire.swift +// +// Copyright (c) 2014 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +/// Types adopting the `URLConvertible` protocol can be used to construct URLs, which are then used to construct +/// URL requests. +public protocol URLConvertible { + /// Returns a URL that conforms to RFC 2396 or throws an `Error`. + /// + /// - throws: An `Error` if the type cannot be converted to a `URL`. + /// + /// - returns: A URL or throws an `Error`. + func asURL() throws -> URL +} + +extension String: URLConvertible { + /// Returns a URL if `self` represents a valid URL string that conforms to RFC 2396 or throws an `AFError`. + /// + /// - throws: An `AFError.invalidURL` if `self` is not a valid URL string. + /// + /// - returns: A URL or throws an `AFError`. + public func asURL() throws -> URL { + guard let url = URL(string: self) else { throw AFError.invalidURL(url: self) } + return url + } +} + +extension URL: URLConvertible { + /// Returns self. + public func asURL() throws -> URL { return self } +} + +extension URLComponents: URLConvertible { + /// Returns a URL if `url` is not nil, otherwise throws an `Error`. + /// + /// - throws: An `AFError.invalidURL` if `url` is `nil`. + /// + /// - returns: A URL or throws an `AFError`. + public func asURL() throws -> URL { + guard let url = url else { throw AFError.invalidURL(url: self) } + return url + } +} + +// MARK: - + +/// Types adopting the `URLRequestConvertible` protocol can be used to construct URL requests. +public protocol URLRequestConvertible { + /// Returns a URL request or throws if an `Error` was encountered. + /// + /// - throws: An `Error` if the underlying `URLRequest` is `nil`. + /// + /// - returns: A URL request. + func asURLRequest() throws -> URLRequest +} + +extension URLRequestConvertible { + /// The URL request. + public var urlRequest: URLRequest? { return try? asURLRequest() } +} + +extension URLRequest: URLRequestConvertible { + /// Returns a URL request or throws if an `Error` was encountered. + public func asURLRequest() throws -> URLRequest { return self } +} + +// MARK: - + +extension URLRequest { + /// Creates an instance with the specified `method`, `urlString` and `headers`. + /// + /// - parameter url: The URL. + /// - parameter method: The HTTP method. + /// - parameter headers: The HTTP headers. `nil` by default. + /// + /// - returns: The new `URLRequest` instance. + public init(url: URLConvertible, method: HTTPMethod, headers: HTTPHeaders? = nil) throws { + let url = try url.asURL() + + self.init(url: url) + + httpMethod = method.rawValue + + if let headers = headers { + for (headerField, headerValue) in headers { + setValue(headerValue, forHTTPHeaderField: headerField) + } + } + } + + func adapt(using adapter: RequestAdapter?) throws -> URLRequest { + guard let adapter = adapter else { return self } + return try adapter.adapt(self) + } +} + +// MARK: - Data Request + +/// Creates a `DataRequest` using the default `SessionManager` to retrieve the contents of the specified `url`, +/// `method`, `parameters`, `encoding` and `headers`. +/// +/// - parameter url: The URL. +/// - parameter method: The HTTP method. `.get` by default. +/// - parameter parameters: The parameters. `nil` by default. +/// - parameter encoding: The parameter encoding. `URLEncoding.default` by default. +/// - parameter headers: The HTTP headers. `nil` by default. +/// +/// - returns: The created `DataRequest`. +@discardableResult +public func request( + _ url: URLConvertible, + method: HTTPMethod = .get, + parameters: Parameters? = nil, + encoding: ParameterEncoding = URLEncoding.default, + headers: HTTPHeaders? = nil) + -> DataRequest +{ + return SessionManager.default.request( + url, + method: method, + parameters: parameters, + encoding: encoding, + headers: headers + ) +} + +/// Creates a `DataRequest` using the default `SessionManager` to retrieve the contents of a URL based on the +/// specified `urlRequest`. +/// +/// - parameter urlRequest: The URL request +/// +/// - returns: The created `DataRequest`. +@discardableResult +public func request(_ urlRequest: URLRequestConvertible) -> DataRequest { + return SessionManager.default.request(urlRequest) +} + +// MARK: - Download Request + +// MARK: URL Request + +/// Creates a `DownloadRequest` using the default `SessionManager` to retrieve the contents of the specified `url`, +/// `method`, `parameters`, `encoding`, `headers` and save them to the `destination`. +/// +/// If `destination` is not specified, the contents will remain in the temporary location determined by the +/// underlying URL session. +/// +/// - parameter url: The URL. +/// - parameter method: The HTTP method. `.get` by default. +/// - parameter parameters: The parameters. `nil` by default. +/// - parameter encoding: The parameter encoding. `URLEncoding.default` by default. +/// - parameter headers: The HTTP headers. `nil` by default. +/// - parameter destination: The closure used to determine the destination of the downloaded file. `nil` by default. +/// +/// - returns: The created `DownloadRequest`. +@discardableResult +public func download( + _ url: URLConvertible, + method: HTTPMethod = .get, + parameters: Parameters? = nil, + encoding: ParameterEncoding = URLEncoding.default, + headers: HTTPHeaders? = nil, + to destination: DownloadRequest.DownloadFileDestination? = nil) + -> DownloadRequest +{ + return SessionManager.default.download( + url, + method: method, + parameters: parameters, + encoding: encoding, + headers: headers, + to: destination + ) +} + +/// Creates a `DownloadRequest` using the default `SessionManager` to retrieve the contents of a URL based on the +/// specified `urlRequest` and save them to the `destination`. +/// +/// If `destination` is not specified, the contents will remain in the temporary location determined by the +/// underlying URL session. +/// +/// - parameter urlRequest: The URL request. +/// - parameter destination: The closure used to determine the destination of the downloaded file. `nil` by default. +/// +/// - returns: The created `DownloadRequest`. +@discardableResult +public func download( + _ urlRequest: URLRequestConvertible, + to destination: DownloadRequest.DownloadFileDestination? = nil) + -> DownloadRequest +{ + return SessionManager.default.download(urlRequest, to: destination) +} + +// MARK: Resume Data + +/// Creates a `DownloadRequest` using the default `SessionManager` from the `resumeData` produced from a +/// previous request cancellation to retrieve the contents of the original request and save them to the `destination`. +/// +/// If `destination` is not specified, the contents will remain in the temporary location determined by the +/// underlying URL session. +/// +/// On the latest release of all the Apple platforms (iOS 10, macOS 10.12, tvOS 10, watchOS 3), `resumeData` is broken +/// on background URL session configurations. There's an underlying bug in the `resumeData` generation logic where the +/// data is written incorrectly and will always fail to resume the download. For more information about the bug and +/// possible workarounds, please refer to the following Stack Overflow post: +/// +/// - http://stackoverflow.com/a/39347461/1342462 +/// +/// - parameter resumeData: The resume data. This is an opaque data blob produced by `URLSessionDownloadTask` +/// when a task is cancelled. See `URLSession -downloadTask(withResumeData:)` for additional +/// information. +/// - parameter destination: The closure used to determine the destination of the downloaded file. `nil` by default. +/// +/// - returns: The created `DownloadRequest`. +@discardableResult +public func download( + resumingWith resumeData: Data, + to destination: DownloadRequest.DownloadFileDestination? = nil) + -> DownloadRequest +{ + return SessionManager.default.download(resumingWith: resumeData, to: destination) +} + +// MARK: - Upload Request + +// MARK: File + +/// Creates an `UploadRequest` using the default `SessionManager` from the specified `url`, `method` and `headers` +/// for uploading the `file`. +/// +/// - parameter file: The file to upload. +/// - parameter url: The URL. +/// - parameter method: The HTTP method. `.post` by default. +/// - parameter headers: The HTTP headers. `nil` by default. +/// +/// - returns: The created `UploadRequest`. +@discardableResult +public func upload( + _ fileURL: URL, + to url: URLConvertible, + method: HTTPMethod = .post, + headers: HTTPHeaders? = nil) + -> UploadRequest +{ + return SessionManager.default.upload(fileURL, to: url, method: method, headers: headers) +} + +/// Creates a `UploadRequest` using the default `SessionManager` from the specified `urlRequest` for +/// uploading the `file`. +/// +/// - parameter file: The file to upload. +/// - parameter urlRequest: The URL request. +/// +/// - returns: The created `UploadRequest`. +@discardableResult +public func upload(_ fileURL: URL, with urlRequest: URLRequestConvertible) -> UploadRequest { + return SessionManager.default.upload(fileURL, with: urlRequest) +} + +// MARK: Data + +/// Creates an `UploadRequest` using the default `SessionManager` from the specified `url`, `method` and `headers` +/// for uploading the `data`. +/// +/// - parameter data: The data to upload. +/// - parameter url: The URL. +/// - parameter method: The HTTP method. `.post` by default. +/// - parameter headers: The HTTP headers. `nil` by default. +/// +/// - returns: The created `UploadRequest`. +@discardableResult +public func upload( + _ data: Data, + to url: URLConvertible, + method: HTTPMethod = .post, + headers: HTTPHeaders? = nil) + -> UploadRequest +{ + return SessionManager.default.upload(data, to: url, method: method, headers: headers) +} + +/// Creates an `UploadRequest` using the default `SessionManager` from the specified `urlRequest` for +/// uploading the `data`. +/// +/// - parameter data: The data to upload. +/// - parameter urlRequest: The URL request. +/// +/// - returns: The created `UploadRequest`. +@discardableResult +public func upload(_ data: Data, with urlRequest: URLRequestConvertible) -> UploadRequest { + return SessionManager.default.upload(data, with: urlRequest) +} + +// MARK: InputStream + +/// Creates an `UploadRequest` using the default `SessionManager` from the specified `url`, `method` and `headers` +/// for uploading the `stream`. +/// +/// - parameter stream: The stream to upload. +/// - parameter url: The URL. +/// - parameter method: The HTTP method. `.post` by default. +/// - parameter headers: The HTTP headers. `nil` by default. +/// +/// - returns: The created `UploadRequest`. +@discardableResult +public func upload( + _ stream: InputStream, + to url: URLConvertible, + method: HTTPMethod = .post, + headers: HTTPHeaders? = nil) + -> UploadRequest +{ + return SessionManager.default.upload(stream, to: url, method: method, headers: headers) +} + +/// Creates an `UploadRequest` using the default `SessionManager` from the specified `urlRequest` for +/// uploading the `stream`. +/// +/// - parameter urlRequest: The URL request. +/// - parameter stream: The stream to upload. +/// +/// - returns: The created `UploadRequest`. +@discardableResult +public func upload(_ stream: InputStream, with urlRequest: URLRequestConvertible) -> UploadRequest { + return SessionManager.default.upload(stream, with: urlRequest) +} + +// MARK: MultipartFormData + +/// Encodes `multipartFormData` using `encodingMemoryThreshold` with the default `SessionManager` and calls +/// `encodingCompletion` with new `UploadRequest` using the `url`, `method` and `headers`. +/// +/// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative +/// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most +/// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to +/// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory +/// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be +/// used for larger payloads such as video content. +/// +/// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory +/// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`, +/// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk +/// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding +/// technique was used. +/// +/// - parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`. +/// - parameter encodingMemoryThreshold: The encoding memory threshold in bytes. +/// `multipartFormDataEncodingMemoryThreshold` by default. +/// - parameter url: The URL. +/// - parameter method: The HTTP method. `.post` by default. +/// - parameter headers: The HTTP headers. `nil` by default. +/// - parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete. +public func upload( + multipartFormData: @escaping (MultipartFormData) -> Void, + usingThreshold encodingMemoryThreshold: UInt64 = SessionManager.multipartFormDataEncodingMemoryThreshold, + to url: URLConvertible, + method: HTTPMethod = .post, + headers: HTTPHeaders? = nil, + encodingCompletion: ((SessionManager.MultipartFormDataEncodingResult) -> Void)?) +{ + return SessionManager.default.upload( + multipartFormData: multipartFormData, + usingThreshold: encodingMemoryThreshold, + to: url, + method: method, + headers: headers, + encodingCompletion: encodingCompletion + ) +} + +/// Encodes `multipartFormData` using `encodingMemoryThreshold` and the default `SessionManager` and +/// calls `encodingCompletion` with new `UploadRequest` using the `urlRequest`. +/// +/// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative +/// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most +/// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to +/// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory +/// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be +/// used for larger payloads such as video content. +/// +/// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory +/// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`, +/// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk +/// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding +/// technique was used. +/// +/// - parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`. +/// - parameter encodingMemoryThreshold: The encoding memory threshold in bytes. +/// `multipartFormDataEncodingMemoryThreshold` by default. +/// - parameter urlRequest: The URL request. +/// - parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete. +public func upload( + multipartFormData: @escaping (MultipartFormData) -> Void, + usingThreshold encodingMemoryThreshold: UInt64 = SessionManager.multipartFormDataEncodingMemoryThreshold, + with urlRequest: URLRequestConvertible, + encodingCompletion: ((SessionManager.MultipartFormDataEncodingResult) -> Void)?) +{ + return SessionManager.default.upload( + multipartFormData: multipartFormData, + usingThreshold: encodingMemoryThreshold, + with: urlRequest, + encodingCompletion: encodingCompletion + ) +} + +#if !os(watchOS) + +// MARK: - Stream Request + +// MARK: Hostname and Port + +/// Creates a `StreamRequest` using the default `SessionManager` for bidirectional streaming with the `hostname` +/// and `port`. +/// +/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. +/// +/// - parameter hostName: The hostname of the server to connect to. +/// - parameter port: The port of the server to connect to. +/// +/// - returns: The created `StreamRequest`. +@discardableResult +@available(iOS 9.0, macOS 10.11, tvOS 9.0, *) +public func stream(withHostName hostName: String, port: Int) -> StreamRequest { + return SessionManager.default.stream(withHostName: hostName, port: port) +} + +// MARK: NetService + +/// Creates a `StreamRequest` using the default `SessionManager` for bidirectional streaming with the `netService`. +/// +/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. +/// +/// - parameter netService: The net service used to identify the endpoint. +/// +/// - returns: The created `StreamRequest`. +@discardableResult +@available(iOS 9.0, macOS 10.11, tvOS 9.0, *) +public func stream(with netService: NetService) -> StreamRequest { + return SessionManager.default.stream(with: netService) +} + +#endif diff --git a/!main project/Pods/Alamofire/Source/DispatchQueue+Alamofire.swift b/!main project/Pods/Alamofire/Source/DispatchQueue+Alamofire.swift new file mode 100644 index 0000000..a54673c --- /dev/null +++ b/!main project/Pods/Alamofire/Source/DispatchQueue+Alamofire.swift @@ -0,0 +1,37 @@ +// +// DispatchQueue+Alamofire.swift +// +// Copyright (c) 2014 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Dispatch +import Foundation + +extension DispatchQueue { + static var userInteractive: DispatchQueue { return DispatchQueue.global(qos: .userInteractive) } + static var userInitiated: DispatchQueue { return DispatchQueue.global(qos: .userInitiated) } + static var utility: DispatchQueue { return DispatchQueue.global(qos: .utility) } + static var background: DispatchQueue { return DispatchQueue.global(qos: .background) } + + func after(_ delay: TimeInterval, execute closure: @escaping () -> Void) { + asyncAfter(deadline: .now() + delay, execute: closure) + } +} diff --git a/!main project/Pods/Alamofire/Source/MultipartFormData.swift b/!main project/Pods/Alamofire/Source/MultipartFormData.swift new file mode 100644 index 0000000..b840138 --- /dev/null +++ b/!main project/Pods/Alamofire/Source/MultipartFormData.swift @@ -0,0 +1,580 @@ +// +// MultipartFormData.swift +// +// Copyright (c) 2014 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +#if os(iOS) || os(watchOS) || os(tvOS) +import MobileCoreServices +#elseif os(macOS) +import CoreServices +#endif + +/// Constructs `multipart/form-data` for uploads within an HTTP or HTTPS body. There are currently two ways to encode +/// multipart form data. The first way is to encode the data directly in memory. This is very efficient, but can lead +/// to memory issues if the dataset is too large. The second way is designed for larger datasets and will write all the +/// data to a single file on disk with all the proper boundary segmentation. The second approach MUST be used for +/// larger datasets such as video content, otherwise your app may run out of memory when trying to encode the dataset. +/// +/// For more information on `multipart/form-data` in general, please refer to the RFC-2388 and RFC-2045 specs as well +/// and the w3 form documentation. +/// +/// - https://www.ietf.org/rfc/rfc2388.txt +/// - https://www.ietf.org/rfc/rfc2045.txt +/// - https://www.w3.org/TR/html401/interact/forms.html#h-17.13 +open class MultipartFormData { + + // MARK: - Helper Types + + struct EncodingCharacters { + static let crlf = "\r\n" + } + + struct BoundaryGenerator { + enum BoundaryType { + case initial, encapsulated, final + } + + static func randomBoundary() -> String { + return String(format: "alamofire.boundary.%08x%08x", arc4random(), arc4random()) + } + + static func boundaryData(forBoundaryType boundaryType: BoundaryType, boundary: String) -> Data { + let boundaryText: String + + switch boundaryType { + case .initial: + boundaryText = "--\(boundary)\(EncodingCharacters.crlf)" + case .encapsulated: + boundaryText = "\(EncodingCharacters.crlf)--\(boundary)\(EncodingCharacters.crlf)" + case .final: + boundaryText = "\(EncodingCharacters.crlf)--\(boundary)--\(EncodingCharacters.crlf)" + } + + return boundaryText.data(using: String.Encoding.utf8, allowLossyConversion: false)! + } + } + + class BodyPart { + let headers: HTTPHeaders + let bodyStream: InputStream + let bodyContentLength: UInt64 + var hasInitialBoundary = false + var hasFinalBoundary = false + + init(headers: HTTPHeaders, bodyStream: InputStream, bodyContentLength: UInt64) { + self.headers = headers + self.bodyStream = bodyStream + self.bodyContentLength = bodyContentLength + } + } + + // MARK: - Properties + + /// The `Content-Type` header value containing the boundary used to generate the `multipart/form-data`. + open lazy var contentType: String = "multipart/form-data; boundary=\(self.boundary)" + + /// The content length of all body parts used to generate the `multipart/form-data` not including the boundaries. + public var contentLength: UInt64 { return bodyParts.reduce(0) { $0 + $1.bodyContentLength } } + + /// The boundary used to separate the body parts in the encoded form data. + public var boundary: String + + private var bodyParts: [BodyPart] + private var bodyPartError: AFError? + private let streamBufferSize: Int + + // MARK: - Lifecycle + + /// Creates a multipart form data object. + /// + /// - returns: The multipart form data object. + public init() { + self.boundary = BoundaryGenerator.randomBoundary() + self.bodyParts = [] + + /// + /// The optimal read/write buffer size in bytes for input and output streams is 1024 (1KB). For more + /// information, please refer to the following article: + /// - https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Streams/Articles/ReadingInputStreams.html + /// + + self.streamBufferSize = 1024 + } + + // MARK: - Body Parts + + /// Creates a body part from the data and appends it to the multipart form data object. + /// + /// The body part data will be encoded using the following format: + /// + /// - `Content-Disposition: form-data; name=#{name}` (HTTP Header) + /// - Encoded data + /// - Multipart form boundary + /// + /// - parameter data: The data to encode into the multipart form data. + /// - parameter name: The name to associate with the data in the `Content-Disposition` HTTP header. + public func append(_ data: Data, withName name: String) { + let headers = contentHeaders(withName: name) + let stream = InputStream(data: data) + let length = UInt64(data.count) + + append(stream, withLength: length, headers: headers) + } + + /// Creates a body part from the data and appends it to the multipart form data object. + /// + /// The body part data will be encoded using the following format: + /// + /// - `Content-Disposition: form-data; name=#{name}` (HTTP Header) + /// - `Content-Type: #{generated mimeType}` (HTTP Header) + /// - Encoded data + /// - Multipart form boundary + /// + /// - parameter data: The data to encode into the multipart form data. + /// - parameter name: The name to associate with the data in the `Content-Disposition` HTTP header. + /// - parameter mimeType: The MIME type to associate with the data content type in the `Content-Type` HTTP header. + public func append(_ data: Data, withName name: String, mimeType: String) { + let headers = contentHeaders(withName: name, mimeType: mimeType) + let stream = InputStream(data: data) + let length = UInt64(data.count) + + append(stream, withLength: length, headers: headers) + } + + /// Creates a body part from the data and appends it to the multipart form data object. + /// + /// The body part data will be encoded using the following format: + /// + /// - `Content-Disposition: form-data; name=#{name}; filename=#{filename}` (HTTP Header) + /// - `Content-Type: #{mimeType}` (HTTP Header) + /// - Encoded file data + /// - Multipart form boundary + /// + /// - parameter data: The data to encode into the multipart form data. + /// - parameter name: The name to associate with the data in the `Content-Disposition` HTTP header. + /// - parameter fileName: The filename to associate with the data in the `Content-Disposition` HTTP header. + /// - parameter mimeType: The MIME type to associate with the data in the `Content-Type` HTTP header. + public func append(_ data: Data, withName name: String, fileName: String, mimeType: String) { + let headers = contentHeaders(withName: name, fileName: fileName, mimeType: mimeType) + let stream = InputStream(data: data) + let length = UInt64(data.count) + + append(stream, withLength: length, headers: headers) + } + + /// Creates a body part from the file and appends it to the multipart form data object. + /// + /// The body part data will be encoded using the following format: + /// + /// - `Content-Disposition: form-data; name=#{name}; filename=#{generated filename}` (HTTP Header) + /// - `Content-Type: #{generated mimeType}` (HTTP Header) + /// - Encoded file data + /// - Multipart form boundary + /// + /// The filename in the `Content-Disposition` HTTP header is generated from the last path component of the + /// `fileURL`. The `Content-Type` HTTP header MIME type is generated by mapping the `fileURL` extension to the + /// system associated MIME type. + /// + /// - parameter fileURL: The URL of the file whose content will be encoded into the multipart form data. + /// - parameter name: The name to associate with the file content in the `Content-Disposition` HTTP header. + public func append(_ fileURL: URL, withName name: String) { + let fileName = fileURL.lastPathComponent + let pathExtension = fileURL.pathExtension + + if !fileName.isEmpty && !pathExtension.isEmpty { + let mime = mimeType(forPathExtension: pathExtension) + append(fileURL, withName: name, fileName: fileName, mimeType: mime) + } else { + setBodyPartError(withReason: .bodyPartFilenameInvalid(in: fileURL)) + } + } + + /// Creates a body part from the file and appends it to the multipart form data object. + /// + /// The body part data will be encoded using the following format: + /// + /// - Content-Disposition: form-data; name=#{name}; filename=#{filename} (HTTP Header) + /// - Content-Type: #{mimeType} (HTTP Header) + /// - Encoded file data + /// - Multipart form boundary + /// + /// - parameter fileURL: The URL of the file whose content will be encoded into the multipart form data. + /// - parameter name: The name to associate with the file content in the `Content-Disposition` HTTP header. + /// - parameter fileName: The filename to associate with the file content in the `Content-Disposition` HTTP header. + /// - parameter mimeType: The MIME type to associate with the file content in the `Content-Type` HTTP header. + public func append(_ fileURL: URL, withName name: String, fileName: String, mimeType: String) { + let headers = contentHeaders(withName: name, fileName: fileName, mimeType: mimeType) + + //============================================================ + // Check 1 - is file URL? + //============================================================ + + guard fileURL.isFileURL else { + setBodyPartError(withReason: .bodyPartURLInvalid(url: fileURL)) + return + } + + //============================================================ + // Check 2 - is file URL reachable? + //============================================================ + + do { + let isReachable = try fileURL.checkPromisedItemIsReachable() + guard isReachable else { + setBodyPartError(withReason: .bodyPartFileNotReachable(at: fileURL)) + return + } + } catch { + setBodyPartError(withReason: .bodyPartFileNotReachableWithError(atURL: fileURL, error: error)) + return + } + + //============================================================ + // Check 3 - is file URL a directory? + //============================================================ + + var isDirectory: ObjCBool = false + let path = fileURL.path + + guard FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) && !isDirectory.boolValue else { + setBodyPartError(withReason: .bodyPartFileIsDirectory(at: fileURL)) + return + } + + //============================================================ + // Check 4 - can the file size be extracted? + //============================================================ + + let bodyContentLength: UInt64 + + do { + guard let fileSize = try FileManager.default.attributesOfItem(atPath: path)[.size] as? NSNumber else { + setBodyPartError(withReason: .bodyPartFileSizeNotAvailable(at: fileURL)) + return + } + + bodyContentLength = fileSize.uint64Value + } + catch { + setBodyPartError(withReason: .bodyPartFileSizeQueryFailedWithError(forURL: fileURL, error: error)) + return + } + + //============================================================ + // Check 5 - can a stream be created from file URL? + //============================================================ + + guard let stream = InputStream(url: fileURL) else { + setBodyPartError(withReason: .bodyPartInputStreamCreationFailed(for: fileURL)) + return + } + + append(stream, withLength: bodyContentLength, headers: headers) + } + + /// Creates a body part from the stream and appends it to the multipart form data object. + /// + /// The body part data will be encoded using the following format: + /// + /// - `Content-Disposition: form-data; name=#{name}; filename=#{filename}` (HTTP Header) + /// - `Content-Type: #{mimeType}` (HTTP Header) + /// - Encoded stream data + /// - Multipart form boundary + /// + /// - parameter stream: The input stream to encode in the multipart form data. + /// - parameter length: The content length of the stream. + /// - parameter name: The name to associate with the stream content in the `Content-Disposition` HTTP header. + /// - parameter fileName: The filename to associate with the stream content in the `Content-Disposition` HTTP header. + /// - parameter mimeType: The MIME type to associate with the stream content in the `Content-Type` HTTP header. + public func append( + _ stream: InputStream, + withLength length: UInt64, + name: String, + fileName: String, + mimeType: String) + { + let headers = contentHeaders(withName: name, fileName: fileName, mimeType: mimeType) + append(stream, withLength: length, headers: headers) + } + + /// Creates a body part with the headers, stream and length and appends it to the multipart form data object. + /// + /// The body part data will be encoded using the following format: + /// + /// - HTTP headers + /// - Encoded stream data + /// - Multipart form boundary + /// + /// - parameter stream: The input stream to encode in the multipart form data. + /// - parameter length: The content length of the stream. + /// - parameter headers: The HTTP headers for the body part. + public func append(_ stream: InputStream, withLength length: UInt64, headers: HTTPHeaders) { + let bodyPart = BodyPart(headers: headers, bodyStream: stream, bodyContentLength: length) + bodyParts.append(bodyPart) + } + + // MARK: - Data Encoding + + /// Encodes all the appended body parts into a single `Data` value. + /// + /// It is important to note that this method will load all the appended body parts into memory all at the same + /// time. This method should only be used when the encoded data will have a small memory footprint. For large data + /// cases, please use the `writeEncodedDataToDisk(fileURL:completionHandler:)` method. + /// + /// - throws: An `AFError` if encoding encounters an error. + /// + /// - returns: The encoded `Data` if encoding is successful. + public func encode() throws -> Data { + if let bodyPartError = bodyPartError { + throw bodyPartError + } + + var encoded = Data() + + bodyParts.first?.hasInitialBoundary = true + bodyParts.last?.hasFinalBoundary = true + + for bodyPart in bodyParts { + let encodedData = try encode(bodyPart) + encoded.append(encodedData) + } + + return encoded + } + + /// Writes the appended body parts into the given file URL. + /// + /// This process is facilitated by reading and writing with input and output streams, respectively. Thus, + /// this approach is very memory efficient and should be used for large body part data. + /// + /// - parameter fileURL: The file URL to write the multipart form data into. + /// + /// - throws: An `AFError` if encoding encounters an error. + public func writeEncodedData(to fileURL: URL) throws { + if let bodyPartError = bodyPartError { + throw bodyPartError + } + + if FileManager.default.fileExists(atPath: fileURL.path) { + throw AFError.multipartEncodingFailed(reason: .outputStreamFileAlreadyExists(at: fileURL)) + } else if !fileURL.isFileURL { + throw AFError.multipartEncodingFailed(reason: .outputStreamURLInvalid(url: fileURL)) + } + + guard let outputStream = OutputStream(url: fileURL, append: false) else { + throw AFError.multipartEncodingFailed(reason: .outputStreamCreationFailed(for: fileURL)) + } + + outputStream.open() + defer { outputStream.close() } + + self.bodyParts.first?.hasInitialBoundary = true + self.bodyParts.last?.hasFinalBoundary = true + + for bodyPart in self.bodyParts { + try write(bodyPart, to: outputStream) + } + } + + // MARK: - Private - Body Part Encoding + + private func encode(_ bodyPart: BodyPart) throws -> Data { + var encoded = Data() + + let initialData = bodyPart.hasInitialBoundary ? initialBoundaryData() : encapsulatedBoundaryData() + encoded.append(initialData) + + let headerData = encodeHeaders(for: bodyPart) + encoded.append(headerData) + + let bodyStreamData = try encodeBodyStream(for: bodyPart) + encoded.append(bodyStreamData) + + if bodyPart.hasFinalBoundary { + encoded.append(finalBoundaryData()) + } + + return encoded + } + + private func encodeHeaders(for bodyPart: BodyPart) -> Data { + var headerText = "" + + for (key, value) in bodyPart.headers { + headerText += "\(key): \(value)\(EncodingCharacters.crlf)" + } + headerText += EncodingCharacters.crlf + + return headerText.data(using: String.Encoding.utf8, allowLossyConversion: false)! + } + + private func encodeBodyStream(for bodyPart: BodyPart) throws -> Data { + let inputStream = bodyPart.bodyStream + inputStream.open() + defer { inputStream.close() } + + var encoded = Data() + + while inputStream.hasBytesAvailable { + var buffer = [UInt8](repeating: 0, count: streamBufferSize) + let bytesRead = inputStream.read(&buffer, maxLength: streamBufferSize) + + if let error = inputStream.streamError { + throw AFError.multipartEncodingFailed(reason: .inputStreamReadFailed(error: error)) + } + + if bytesRead > 0 { + encoded.append(buffer, count: bytesRead) + } else { + break + } + } + + return encoded + } + + // MARK: - Private - Writing Body Part to Output Stream + + private func write(_ bodyPart: BodyPart, to outputStream: OutputStream) throws { + try writeInitialBoundaryData(for: bodyPart, to: outputStream) + try writeHeaderData(for: bodyPart, to: outputStream) + try writeBodyStream(for: bodyPart, to: outputStream) + try writeFinalBoundaryData(for: bodyPart, to: outputStream) + } + + private func writeInitialBoundaryData(for bodyPart: BodyPart, to outputStream: OutputStream) throws { + let initialData = bodyPart.hasInitialBoundary ? initialBoundaryData() : encapsulatedBoundaryData() + return try write(initialData, to: outputStream) + } + + private func writeHeaderData(for bodyPart: BodyPart, to outputStream: OutputStream) throws { + let headerData = encodeHeaders(for: bodyPart) + return try write(headerData, to: outputStream) + } + + private func writeBodyStream(for bodyPart: BodyPart, to outputStream: OutputStream) throws { + let inputStream = bodyPart.bodyStream + + inputStream.open() + defer { inputStream.close() } + + while inputStream.hasBytesAvailable { + var buffer = [UInt8](repeating: 0, count: streamBufferSize) + let bytesRead = inputStream.read(&buffer, maxLength: streamBufferSize) + + if let streamError = inputStream.streamError { + throw AFError.multipartEncodingFailed(reason: .inputStreamReadFailed(error: streamError)) + } + + if bytesRead > 0 { + if buffer.count != bytesRead { + buffer = Array(buffer[0.. 0, outputStream.hasSpaceAvailable { + let bytesWritten = outputStream.write(buffer, maxLength: bytesToWrite) + + if let error = outputStream.streamError { + throw AFError.multipartEncodingFailed(reason: .outputStreamWriteFailed(error: error)) + } + + bytesToWrite -= bytesWritten + + if bytesToWrite > 0 { + buffer = Array(buffer[bytesWritten.. String { + if + let id = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as CFString, nil)?.takeRetainedValue(), + let contentType = UTTypeCopyPreferredTagWithClass(id, kUTTagClassMIMEType)?.takeRetainedValue() + { + return contentType as String + } + + return "application/octet-stream" + } + + // MARK: - Private - Content Headers + + private func contentHeaders(withName name: String, fileName: String? = nil, mimeType: String? = nil) -> [String: String] { + var disposition = "form-data; name=\"\(name)\"" + if let fileName = fileName { disposition += "; filename=\"\(fileName)\"" } + + var headers = ["Content-Disposition": disposition] + if let mimeType = mimeType { headers["Content-Type"] = mimeType } + + return headers + } + + // MARK: - Private - Boundary Encoding + + private func initialBoundaryData() -> Data { + return BoundaryGenerator.boundaryData(forBoundaryType: .initial, boundary: boundary) + } + + private func encapsulatedBoundaryData() -> Data { + return BoundaryGenerator.boundaryData(forBoundaryType: .encapsulated, boundary: boundary) + } + + private func finalBoundaryData() -> Data { + return BoundaryGenerator.boundaryData(forBoundaryType: .final, boundary: boundary) + } + + // MARK: - Private - Errors + + private func setBodyPartError(withReason reason: AFError.MultipartEncodingFailureReason) { + guard bodyPartError == nil else { return } + bodyPartError = AFError.multipartEncodingFailed(reason: reason) + } +} diff --git a/!main project/Pods/Alamofire/Source/NetworkReachabilityManager.swift b/!main project/Pods/Alamofire/Source/NetworkReachabilityManager.swift new file mode 100644 index 0000000..398ca82 --- /dev/null +++ b/!main project/Pods/Alamofire/Source/NetworkReachabilityManager.swift @@ -0,0 +1,238 @@ +// +// NetworkReachabilityManager.swift +// +// Copyright (c) 2014 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#if !os(watchOS) + +import Foundation +import SystemConfiguration + +/// The `NetworkReachabilityManager` class listens for reachability changes of hosts and addresses for both WWAN and +/// WiFi network interfaces. +/// +/// Reachability can be used to determine background information about why a network operation failed, or to retry +/// network requests when a connection is established. It should not be used to prevent a user from initiating a network +/// request, as it's possible that an initial request may be required to establish reachability. +open class NetworkReachabilityManager { + /// Defines the various states of network reachability. + /// + /// - unknown: It is unknown whether the network is reachable. + /// - notReachable: The network is not reachable. + /// - reachable: The network is reachable. + public enum NetworkReachabilityStatus { + case unknown + case notReachable + case reachable(ConnectionType) + } + + /// Defines the various connection types detected by reachability flags. + /// + /// - ethernetOrWiFi: The connection type is either over Ethernet or WiFi. + /// - wwan: The connection type is a WWAN connection. + public enum ConnectionType { + case ethernetOrWiFi + case wwan + } + + /// A closure executed when the network reachability status changes. The closure takes a single argument: the + /// network reachability status. + public typealias Listener = (NetworkReachabilityStatus) -> Void + + // MARK: - Properties + + /// Whether the network is currently reachable. + open var isReachable: Bool { return isReachableOnWWAN || isReachableOnEthernetOrWiFi } + + /// Whether the network is currently reachable over the WWAN interface. + open var isReachableOnWWAN: Bool { return networkReachabilityStatus == .reachable(.wwan) } + + /// Whether the network is currently reachable over Ethernet or WiFi interface. + open var isReachableOnEthernetOrWiFi: Bool { return networkReachabilityStatus == .reachable(.ethernetOrWiFi) } + + /// The current network reachability status. + open var networkReachabilityStatus: NetworkReachabilityStatus { + guard let flags = self.flags else { return .unknown } + return networkReachabilityStatusForFlags(flags) + } + + /// The dispatch queue to execute the `listener` closure on. + open var listenerQueue: DispatchQueue = DispatchQueue.main + + /// A closure executed when the network reachability status changes. + open var listener: Listener? + + open var flags: SCNetworkReachabilityFlags? { + var flags = SCNetworkReachabilityFlags() + + if SCNetworkReachabilityGetFlags(reachability, &flags) { + return flags + } + + return nil + } + + private let reachability: SCNetworkReachability + open var previousFlags: SCNetworkReachabilityFlags + + // MARK: - Initialization + + /// Creates a `NetworkReachabilityManager` instance with the specified host. + /// + /// - parameter host: The host used to evaluate network reachability. + /// + /// - returns: The new `NetworkReachabilityManager` instance. + public convenience init?(host: String) { + guard let reachability = SCNetworkReachabilityCreateWithName(nil, host) else { return nil } + self.init(reachability: reachability) + } + + /// Creates a `NetworkReachabilityManager` instance that monitors the address 0.0.0.0. + /// + /// Reachability treats the 0.0.0.0 address as a special token that causes it to monitor the general routing + /// status of the device, both IPv4 and IPv6. + /// + /// - returns: The new `NetworkReachabilityManager` instance. + public convenience init?() { + var address = sockaddr_in() + address.sin_len = UInt8(MemoryLayout.size) + address.sin_family = sa_family_t(AF_INET) + + guard let reachability = withUnsafePointer(to: &address, { pointer in + return pointer.withMemoryRebound(to: sockaddr.self, capacity: MemoryLayout.size) { + return SCNetworkReachabilityCreateWithAddress(nil, $0) + } + }) else { return nil } + + self.init(reachability: reachability) + } + + private init(reachability: SCNetworkReachability) { + self.reachability = reachability + + // Set the previous flags to an unreserved value to represent unknown status + self.previousFlags = SCNetworkReachabilityFlags(rawValue: 1 << 30) + } + + deinit { + stopListening() + } + + // MARK: - Listening + + /// Starts listening for changes in network reachability status. + /// + /// - returns: `true` if listening was started successfully, `false` otherwise. + @discardableResult + open func startListening() -> Bool { + var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil) + context.info = Unmanaged.passUnretained(self).toOpaque() + + let callbackEnabled = SCNetworkReachabilitySetCallback( + reachability, + { (_, flags, info) in + let reachability = Unmanaged.fromOpaque(info!).takeUnretainedValue() + reachability.notifyListener(flags) + }, + &context + ) + + let queueEnabled = SCNetworkReachabilitySetDispatchQueue(reachability, listenerQueue) + + listenerQueue.async { + self.previousFlags = SCNetworkReachabilityFlags(rawValue: 1 << 30) + + guard let flags = self.flags else { return } + + self.notifyListener(flags) + } + + return callbackEnabled && queueEnabled + } + + /// Stops listening for changes in network reachability status. + open func stopListening() { + SCNetworkReachabilitySetCallback(reachability, nil, nil) + SCNetworkReachabilitySetDispatchQueue(reachability, nil) + } + + // MARK: - Internal - Listener Notification + + func notifyListener(_ flags: SCNetworkReachabilityFlags) { + guard previousFlags != flags else { return } + previousFlags = flags + + listener?(networkReachabilityStatusForFlags(flags)) + } + + // MARK: - Internal - Network Reachability Status + + func networkReachabilityStatusForFlags(_ flags: SCNetworkReachabilityFlags) -> NetworkReachabilityStatus { + guard isNetworkReachable(with: flags) else { return .notReachable } + + var networkStatus: NetworkReachabilityStatus = .reachable(.ethernetOrWiFi) + + #if os(iOS) + if flags.contains(.isWWAN) { networkStatus = .reachable(.wwan) } + #endif + + return networkStatus + } + + func isNetworkReachable(with flags: SCNetworkReachabilityFlags) -> Bool { + let isReachable = flags.contains(.reachable) + let needsConnection = flags.contains(.connectionRequired) + let canConnectAutomatically = flags.contains(.connectionOnDemand) || flags.contains(.connectionOnTraffic) + let canConnectWithoutUserInteraction = canConnectAutomatically && !flags.contains(.interventionRequired) + + return isReachable && (!needsConnection || canConnectWithoutUserInteraction) + } +} + +// MARK: - + +extension NetworkReachabilityManager.NetworkReachabilityStatus: Equatable {} + +/// Returns whether the two network reachability status values are equal. +/// +/// - parameter lhs: The left-hand side value to compare. +/// - parameter rhs: The right-hand side value to compare. +/// +/// - returns: `true` if the two values are equal, `false` otherwise. +public func ==( + lhs: NetworkReachabilityManager.NetworkReachabilityStatus, + rhs: NetworkReachabilityManager.NetworkReachabilityStatus) + -> Bool +{ + switch (lhs, rhs) { + case (.unknown, .unknown): + return true + case (.notReachable, .notReachable): + return true + case let (.reachable(lhsConnectionType), .reachable(rhsConnectionType)): + return lhsConnectionType == rhsConnectionType + default: + return false + } +} + +#endif diff --git a/!main project/Pods/Alamofire/Source/Notifications.swift b/!main project/Pods/Alamofire/Source/Notifications.swift new file mode 100644 index 0000000..e1ac31b --- /dev/null +++ b/!main project/Pods/Alamofire/Source/Notifications.swift @@ -0,0 +1,55 @@ +// +// Notifications.swift +// +// Copyright (c) 2014 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +extension Notification.Name { + /// Used as a namespace for all `URLSessionTask` related notifications. + public struct Task { + /// Posted when a `URLSessionTask` is resumed. The notification `object` contains the resumed `URLSessionTask`. + public static let DidResume = Notification.Name(rawValue: "org.alamofire.notification.name.task.didResume") + + /// Posted when a `URLSessionTask` is suspended. The notification `object` contains the suspended `URLSessionTask`. + public static let DidSuspend = Notification.Name(rawValue: "org.alamofire.notification.name.task.didSuspend") + + /// Posted when a `URLSessionTask` is cancelled. The notification `object` contains the cancelled `URLSessionTask`. + public static let DidCancel = Notification.Name(rawValue: "org.alamofire.notification.name.task.didCancel") + + /// Posted when a `URLSessionTask` is completed. The notification `object` contains the completed `URLSessionTask`. + public static let DidComplete = Notification.Name(rawValue: "org.alamofire.notification.name.task.didComplete") + } +} + +// MARK: - + +extension Notification { + /// Used as a namespace for all `Notification` user info dictionary keys. + public struct Key { + /// User info dictionary key representing the `URLSessionTask` associated with the notification. + public static let Task = "org.alamofire.notification.key.task" + + /// User info dictionary key representing the responseData associated with the notification. + public static let ResponseData = "org.alamofire.notification.key.responseData" + } +} diff --git a/!main project/Pods/Alamofire/Source/ParameterEncoding.swift b/!main project/Pods/Alamofire/Source/ParameterEncoding.swift new file mode 100644 index 0000000..6195809 --- /dev/null +++ b/!main project/Pods/Alamofire/Source/ParameterEncoding.swift @@ -0,0 +1,483 @@ +// +// ParameterEncoding.swift +// +// Copyright (c) 2014 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +/// HTTP method definitions. +/// +/// See https://tools.ietf.org/html/rfc7231#section-4.3 +public enum HTTPMethod: String { + case options = "OPTIONS" + case get = "GET" + case head = "HEAD" + case post = "POST" + case put = "PUT" + case patch = "PATCH" + case delete = "DELETE" + case trace = "TRACE" + case connect = "CONNECT" +} + +// MARK: - + +/// A dictionary of parameters to apply to a `URLRequest`. +public typealias Parameters = [String: Any] + +/// A type used to define how a set of parameters are applied to a `URLRequest`. +public protocol ParameterEncoding { + /// Creates a URL request by encoding parameters and applying them onto an existing request. + /// + /// - parameter urlRequest: The request to have parameters applied. + /// - parameter parameters: The parameters to apply. + /// + /// - throws: An `AFError.parameterEncodingFailed` error if encoding fails. + /// + /// - returns: The encoded request. + func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest +} + +// MARK: - + +/// Creates a url-encoded query string to be set as or appended to any existing URL query string or set as the HTTP +/// body of the URL request. Whether the query string is set or appended to any existing URL query string or set as +/// the HTTP body depends on the destination of the encoding. +/// +/// The `Content-Type` HTTP header field of an encoded request with HTTP body is set to +/// `application/x-www-form-urlencoded; charset=utf-8`. +/// +/// There is no published specification for how to encode collection types. By default the convention of appending +/// `[]` to the key for array values (`foo[]=1&foo[]=2`), and appending the key surrounded by square brackets for +/// nested dictionary values (`foo[bar]=baz`) is used. Optionally, `ArrayEncoding` can be used to omit the +/// square brackets appended to array keys. +/// +/// `BoolEncoding` can be used to configure how boolean values are encoded. The default behavior is to encode +/// `true` as 1 and `false` as 0. +public struct URLEncoding: ParameterEncoding { + + // MARK: Helper Types + + /// Defines whether the url-encoded query string is applied to the existing query string or HTTP body of the + /// resulting URL request. + /// + /// - methodDependent: Applies encoded query string result to existing query string for `GET`, `HEAD` and `DELETE` + /// requests and sets as the HTTP body for requests with any other HTTP method. + /// - queryString: Sets or appends encoded query string result to existing query string. + /// - httpBody: Sets encoded query string result as the HTTP body of the URL request. + public enum Destination { + case methodDependent, queryString, httpBody + } + + /// Configures how `Array` parameters are encoded. + /// + /// - brackets: An empty set of square brackets is appended to the key for every value. + /// This is the default behavior. + /// - noBrackets: No brackets are appended. The key is encoded as is. + public enum ArrayEncoding { + case brackets, noBrackets + + func encode(key: String) -> String { + switch self { + case .brackets: + return "\(key)[]" + case .noBrackets: + return key + } + } + } + + /// Configures how `Bool` parameters are encoded. + /// + /// - numeric: Encode `true` as `1` and `false` as `0`. This is the default behavior. + /// - literal: Encode `true` and `false` as string literals. + public enum BoolEncoding { + case numeric, literal + + func encode(value: Bool) -> String { + switch self { + case .numeric: + return value ? "1" : "0" + case .literal: + return value ? "true" : "false" + } + } + } + + // MARK: Properties + + /// Returns a default `URLEncoding` instance. + public static var `default`: URLEncoding { return URLEncoding() } + + /// Returns a `URLEncoding` instance with a `.methodDependent` destination. + public static var methodDependent: URLEncoding { return URLEncoding() } + + /// Returns a `URLEncoding` instance with a `.queryString` destination. + public static var queryString: URLEncoding { return URLEncoding(destination: .queryString) } + + /// Returns a `URLEncoding` instance with an `.httpBody` destination. + public static var httpBody: URLEncoding { return URLEncoding(destination: .httpBody) } + + /// The destination defining where the encoded query string is to be applied to the URL request. + public let destination: Destination + + /// The encoding to use for `Array` parameters. + public let arrayEncoding: ArrayEncoding + + /// The encoding to use for `Bool` parameters. + public let boolEncoding: BoolEncoding + + // MARK: Initialization + + /// Creates a `URLEncoding` instance using the specified destination. + /// + /// - parameter destination: The destination defining where the encoded query string is to be applied. + /// - parameter arrayEncoding: The encoding to use for `Array` parameters. + /// - parameter boolEncoding: The encoding to use for `Bool` parameters. + /// + /// - returns: The new `URLEncoding` instance. + public init(destination: Destination = .methodDependent, arrayEncoding: ArrayEncoding = .brackets, boolEncoding: BoolEncoding = .numeric) { + self.destination = destination + self.arrayEncoding = arrayEncoding + self.boolEncoding = boolEncoding + } + + // MARK: Encoding + + /// Creates a URL request by encoding parameters and applying them onto an existing request. + /// + /// - parameter urlRequest: The request to have parameters applied. + /// - parameter parameters: The parameters to apply. + /// + /// - throws: An `Error` if the encoding process encounters an error. + /// + /// - returns: The encoded request. + public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest { + var urlRequest = try urlRequest.asURLRequest() + + guard let parameters = parameters else { return urlRequest } + + if let method = HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET"), encodesParametersInURL(with: method) { + guard let url = urlRequest.url else { + throw AFError.parameterEncodingFailed(reason: .missingURL) + } + + if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty { + let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters) + urlComponents.percentEncodedQuery = percentEncodedQuery + urlRequest.url = urlComponents.url + } + } else { + if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { + urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type") + } + + urlRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false) + } + + return urlRequest + } + + /// Creates percent-escaped, URL encoded query string components from the given key-value pair using recursion. + /// + /// - parameter key: The key of the query component. + /// - parameter value: The value of the query component. + /// + /// - returns: The percent-escaped, URL encoded query string components. + public func queryComponents(fromKey key: String, value: Any) -> [(String, String)] { + var components: [(String, String)] = [] + + if let dictionary = value as? [String: Any] { + for (nestedKey, value) in dictionary { + components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value) + } + } else if let array = value as? [Any] { + for value in array { + components += queryComponents(fromKey: arrayEncoding.encode(key: key), value: value) + } + } else if let value = value as? NSNumber { + if value.isBool { + components.append((escape(key), escape(boolEncoding.encode(value: value.boolValue)))) + } else { + components.append((escape(key), escape("\(value)"))) + } + } else if let bool = value as? Bool { + components.append((escape(key), escape(boolEncoding.encode(value: bool)))) + } else { + components.append((escape(key), escape("\(value)"))) + } + + return components + } + + /// Returns a percent-escaped string following RFC 3986 for a query string key or value. + /// + /// RFC 3986 states that the following characters are "reserved" characters. + /// + /// - General Delimiters: ":", "#", "[", "]", "@", "?", "/" + /// - Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "=" + /// + /// In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow + /// query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/" + /// should be percent-escaped in the query string. + /// + /// - parameter string: The string to be percent-escaped. + /// + /// - returns: The percent-escaped string. + public func escape(_ string: String) -> String { + let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4 + let subDelimitersToEncode = "!$&'()*+,;=" + + var allowedCharacterSet = CharacterSet.urlQueryAllowed + allowedCharacterSet.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)") + + var escaped = "" + + //========================================================================================================== + // + // Batching is required for escaping due to an internal bug in iOS 8.1 and 8.2. Encoding more than a few + // hundred Chinese characters causes various malloc error crashes. To avoid this issue until iOS 8 is no + // longer supported, batching MUST be used for encoding. This introduces roughly a 20% overhead. For more + // info, please refer to: + // + // - https://github.com/Alamofire/Alamofire/issues/206 + // + //========================================================================================================== + + if #available(iOS 8.3, *) { + escaped = string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? string + } else { + let batchSize = 50 + var index = string.startIndex + + while index != string.endIndex { + let startIndex = index + let endIndex = string.index(index, offsetBy: batchSize, limitedBy: string.endIndex) ?? string.endIndex + let range = startIndex.. String { + var components: [(String, String)] = [] + + for key in parameters.keys.sorted(by: <) { + let value = parameters[key]! + components += queryComponents(fromKey: key, value: value) + } + return components.map { "\($0)=\($1)" }.joined(separator: "&") + } + + private func encodesParametersInURL(with method: HTTPMethod) -> Bool { + switch destination { + case .queryString: + return true + case .httpBody: + return false + default: + break + } + + switch method { + case .get, .head, .delete: + return true + default: + return false + } + } +} + +// MARK: - + +/// Uses `JSONSerialization` to create a JSON representation of the parameters object, which is set as the body of the +/// request. The `Content-Type` HTTP header field of an encoded request is set to `application/json`. +public struct JSONEncoding: ParameterEncoding { + + // MARK: Properties + + /// Returns a `JSONEncoding` instance with default writing options. + public static var `default`: JSONEncoding { return JSONEncoding() } + + /// Returns a `JSONEncoding` instance with `.prettyPrinted` writing options. + public static var prettyPrinted: JSONEncoding { return JSONEncoding(options: .prettyPrinted) } + + /// The options for writing the parameters as JSON data. + public let options: JSONSerialization.WritingOptions + + // MARK: Initialization + + /// Creates a `JSONEncoding` instance using the specified options. + /// + /// - parameter options: The options for writing the parameters as JSON data. + /// + /// - returns: The new `JSONEncoding` instance. + public init(options: JSONSerialization.WritingOptions = []) { + self.options = options + } + + // MARK: Encoding + + /// Creates a URL request by encoding parameters and applying them onto an existing request. + /// + /// - parameter urlRequest: The request to have parameters applied. + /// - parameter parameters: The parameters to apply. + /// + /// - throws: An `Error` if the encoding process encounters an error. + /// + /// - returns: The encoded request. + public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest { + var urlRequest = try urlRequest.asURLRequest() + + guard let parameters = parameters else { return urlRequest } + + do { + let data = try JSONSerialization.data(withJSONObject: parameters, options: options) + + if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { + urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") + } + + urlRequest.httpBody = data + } catch { + throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error)) + } + + return urlRequest + } + + /// Creates a URL request by encoding the JSON object and setting the resulting data on the HTTP body. + /// + /// - parameter urlRequest: The request to apply the JSON object to. + /// - parameter jsonObject: The JSON object to apply to the request. + /// + /// - throws: An `Error` if the encoding process encounters an error. + /// + /// - returns: The encoded request. + public func encode(_ urlRequest: URLRequestConvertible, withJSONObject jsonObject: Any? = nil) throws -> URLRequest { + var urlRequest = try urlRequest.asURLRequest() + + guard let jsonObject = jsonObject else { return urlRequest } + + do { + let data = try JSONSerialization.data(withJSONObject: jsonObject, options: options) + + if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { + urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") + } + + urlRequest.httpBody = data + } catch { + throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error)) + } + + return urlRequest + } +} + +// MARK: - + +/// Uses `PropertyListSerialization` to create a plist representation of the parameters object, according to the +/// associated format and write options values, which is set as the body of the request. The `Content-Type` HTTP header +/// field of an encoded request is set to `application/x-plist`. +public struct PropertyListEncoding: ParameterEncoding { + + // MARK: Properties + + /// Returns a default `PropertyListEncoding` instance. + public static var `default`: PropertyListEncoding { return PropertyListEncoding() } + + /// Returns a `PropertyListEncoding` instance with xml formatting and default writing options. + public static var xml: PropertyListEncoding { return PropertyListEncoding(format: .xml) } + + /// Returns a `PropertyListEncoding` instance with binary formatting and default writing options. + public static var binary: PropertyListEncoding { return PropertyListEncoding(format: .binary) } + + /// The property list serialization format. + public let format: PropertyListSerialization.PropertyListFormat + + /// The options for writing the parameters as plist data. + public let options: PropertyListSerialization.WriteOptions + + // MARK: Initialization + + /// Creates a `PropertyListEncoding` instance using the specified format and options. + /// + /// - parameter format: The property list serialization format. + /// - parameter options: The options for writing the parameters as plist data. + /// + /// - returns: The new `PropertyListEncoding` instance. + public init( + format: PropertyListSerialization.PropertyListFormat = .xml, + options: PropertyListSerialization.WriteOptions = 0) + { + self.format = format + self.options = options + } + + // MARK: Encoding + + /// Creates a URL request by encoding parameters and applying them onto an existing request. + /// + /// - parameter urlRequest: The request to have parameters applied. + /// - parameter parameters: The parameters to apply. + /// + /// - throws: An `Error` if the encoding process encounters an error. + /// + /// - returns: The encoded request. + public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest { + var urlRequest = try urlRequest.asURLRequest() + + guard let parameters = parameters else { return urlRequest } + + do { + let data = try PropertyListSerialization.data( + fromPropertyList: parameters, + format: format, + options: options + ) + + if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { + urlRequest.setValue("application/x-plist", forHTTPHeaderField: "Content-Type") + } + + urlRequest.httpBody = data + } catch { + throw AFError.parameterEncodingFailed(reason: .propertyListEncodingFailed(error: error)) + } + + return urlRequest + } +} + +// MARK: - + +extension NSNumber { + fileprivate var isBool: Bool { return CFBooleanGetTypeID() == CFGetTypeID(self) } +} diff --git a/!main project/Pods/Alamofire/Source/Request.swift b/!main project/Pods/Alamofire/Source/Request.swift new file mode 100644 index 0000000..2be2ce0 --- /dev/null +++ b/!main project/Pods/Alamofire/Source/Request.swift @@ -0,0 +1,660 @@ +// +// Request.swift +// +// Copyright (c) 2014 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +/// A type that can inspect and optionally adapt a `URLRequest` in some manner if necessary. +public protocol RequestAdapter { + /// Inspects and adapts the specified `URLRequest` in some manner if necessary and returns the result. + /// + /// - parameter urlRequest: The URL request to adapt. + /// + /// - throws: An `Error` if the adaptation encounters an error. + /// + /// - returns: The adapted `URLRequest`. + func adapt(_ urlRequest: URLRequest) throws -> URLRequest +} + +// MARK: - + +/// A closure executed when the `RequestRetrier` determines whether a `Request` should be retried or not. +public typealias RequestRetryCompletion = (_ shouldRetry: Bool, _ timeDelay: TimeInterval) -> Void + +/// A type that determines whether a request should be retried after being executed by the specified session manager +/// and encountering an error. +public protocol RequestRetrier { + /// Determines whether the `Request` should be retried by calling the `completion` closure. + /// + /// This operation is fully asynchronous. Any amount of time can be taken to determine whether the request needs + /// to be retried. The one requirement is that the completion closure is called to ensure the request is properly + /// cleaned up after. + /// + /// - parameter manager: The session manager the request was executed on. + /// - parameter request: The request that failed due to the encountered error. + /// - parameter error: The error encountered when executing the request. + /// - parameter completion: The completion closure to be executed when retry decision has been determined. + func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) +} + +// MARK: - + +protocol TaskConvertible { + func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask +} + +/// A dictionary of headers to apply to a `URLRequest`. +public typealias HTTPHeaders = [String: String] + +// MARK: - + +/// Responsible for sending a request and receiving the response and associated data from the server, as well as +/// managing its underlying `URLSessionTask`. +open class Request { + + // MARK: Helper Types + + /// A closure executed when monitoring upload or download progress of a request. + public typealias ProgressHandler = (Progress) -> Void + + enum RequestTask { + case data(TaskConvertible?, URLSessionTask?) + case download(TaskConvertible?, URLSessionTask?) + case upload(TaskConvertible?, URLSessionTask?) + case stream(TaskConvertible?, URLSessionTask?) + } + + // MARK: Properties + + /// The delegate for the underlying task. + open internal(set) var delegate: TaskDelegate { + get { + taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() } + return taskDelegate + } + set { + taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() } + taskDelegate = newValue + } + } + + /// The underlying task. + open var task: URLSessionTask? { return delegate.task } + + /// The session belonging to the underlying task. + public let session: URLSession + + /// The request sent or to be sent to the server. + open var request: URLRequest? { return task?.originalRequest } + + /// The response received from the server, if any. + open var response: HTTPURLResponse? { return task?.response as? HTTPURLResponse } + + /// The number of times the request has been retried. + open internal(set) var retryCount: UInt = 0 + + let originalTask: TaskConvertible? + + var startTime: CFAbsoluteTime? + var endTime: CFAbsoluteTime? + + var validations: [() -> Void] = [] + + private var taskDelegate: TaskDelegate + private var taskDelegateLock = NSLock() + + // MARK: Lifecycle + + init(session: URLSession, requestTask: RequestTask, error: Error? = nil) { + self.session = session + + switch requestTask { + case .data(let originalTask, let task): + taskDelegate = DataTaskDelegate(task: task) + self.originalTask = originalTask + case .download(let originalTask, let task): + taskDelegate = DownloadTaskDelegate(task: task) + self.originalTask = originalTask + case .upload(let originalTask, let task): + taskDelegate = UploadTaskDelegate(task: task) + self.originalTask = originalTask + case .stream(let originalTask, let task): + taskDelegate = TaskDelegate(task: task) + self.originalTask = originalTask + } + + delegate.error = error + delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() } + } + + // MARK: Authentication + + /// Associates an HTTP Basic credential with the request. + /// + /// - parameter user: The user. + /// - parameter password: The password. + /// - parameter persistence: The URL credential persistence. `.ForSession` by default. + /// + /// - returns: The request. + @discardableResult + open func authenticate( + user: String, + password: String, + persistence: URLCredential.Persistence = .forSession) + -> Self + { + let credential = URLCredential(user: user, password: password, persistence: persistence) + return authenticate(usingCredential: credential) + } + + /// Associates a specified credential with the request. + /// + /// - parameter credential: The credential. + /// + /// - returns: The request. + @discardableResult + open func authenticate(usingCredential credential: URLCredential) -> Self { + delegate.credential = credential + return self + } + + /// Returns a base64 encoded basic authentication credential as an authorization header tuple. + /// + /// - parameter user: The user. + /// - parameter password: The password. + /// + /// - returns: A tuple with Authorization header and credential value if encoding succeeds, `nil` otherwise. + open class func authorizationHeader(user: String, password: String) -> (key: String, value: String)? { + guard let data = "\(user):\(password)".data(using: .utf8) else { return nil } + + let credential = data.base64EncodedString(options: []) + + return (key: "Authorization", value: "Basic \(credential)") + } + + // MARK: State + + /// Resumes the request. + open func resume() { + guard let task = task else { delegate.queue.isSuspended = false ; return } + + if startTime == nil { startTime = CFAbsoluteTimeGetCurrent() } + + task.resume() + + NotificationCenter.default.post( + name: Notification.Name.Task.DidResume, + object: self, + userInfo: [Notification.Key.Task: task] + ) + } + + /// Suspends the request. + open func suspend() { + guard let task = task else { return } + + task.suspend() + + NotificationCenter.default.post( + name: Notification.Name.Task.DidSuspend, + object: self, + userInfo: [Notification.Key.Task: task] + ) + } + + /// Cancels the request. + open func cancel() { + guard let task = task else { return } + + task.cancel() + + NotificationCenter.default.post( + name: Notification.Name.Task.DidCancel, + object: self, + userInfo: [Notification.Key.Task: task] + ) + } +} + +// MARK: - CustomStringConvertible + +extension Request: CustomStringConvertible { + /// The textual representation used when written to an output stream, which includes the HTTP method and URL, as + /// well as the response status code if a response has been received. + open var description: String { + var components: [String] = [] + + if let HTTPMethod = request?.httpMethod { + components.append(HTTPMethod) + } + + if let urlString = request?.url?.absoluteString { + components.append(urlString) + } + + if let response = response { + components.append("(\(response.statusCode))") + } + + return components.joined(separator: " ") + } +} + +// MARK: - CustomDebugStringConvertible + +extension Request: CustomDebugStringConvertible { + /// The textual representation used when written to an output stream, in the form of a cURL command. + open var debugDescription: String { + return cURLRepresentation() + } + + func cURLRepresentation() -> String { + var components = ["$ curl -v"] + + guard let request = self.request, + let url = request.url, + let host = url.host + else { + return "$ curl command could not be created" + } + + if let httpMethod = request.httpMethod, httpMethod != "GET" { + components.append("-X \(httpMethod)") + } + + if let credentialStorage = self.session.configuration.urlCredentialStorage { + let protectionSpace = URLProtectionSpace( + host: host, + port: url.port ?? 0, + protocol: url.scheme, + realm: host, + authenticationMethod: NSURLAuthenticationMethodHTTPBasic + ) + + if let credentials = credentialStorage.credentials(for: protectionSpace)?.values { + for credential in credentials { + guard let user = credential.user, let password = credential.password else { continue } + components.append("-u \(user):\(password)") + } + } else { + if let credential = delegate.credential, let user = credential.user, let password = credential.password { + components.append("-u \(user):\(password)") + } + } + } + + if session.configuration.httpShouldSetCookies { + if + let cookieStorage = session.configuration.httpCookieStorage, + let cookies = cookieStorage.cookies(for: url), !cookies.isEmpty + { + let string = cookies.reduce("") { $0 + "\($1.name)=\($1.value);" } + + #if swift(>=3.2) + components.append("-b \"\(string[.. URLSessionTask { + do { + let urlRequest = try self.urlRequest.adapt(using: adapter) + return queue.sync { session.dataTask(with: urlRequest) } + } catch { + throw AdaptError(error: error) + } + } + } + + // MARK: Properties + + /// The request sent or to be sent to the server. + open override var request: URLRequest? { + if let request = super.request { return request } + if let requestable = originalTask as? Requestable { return requestable.urlRequest } + + return nil + } + + /// The progress of fetching the response data from the server for the request. + open var progress: Progress { return dataDelegate.progress } + + var dataDelegate: DataTaskDelegate { return delegate as! DataTaskDelegate } + + // MARK: Stream + + /// Sets a closure to be called periodically during the lifecycle of the request as data is read from the server. + /// + /// This closure returns the bytes most recently received from the server, not including data from previous calls. + /// If this closure is set, data will only be available within this closure, and will not be saved elsewhere. It is + /// also important to note that the server data in any `Response` object will be `nil`. + /// + /// - parameter closure: The code to be executed periodically during the lifecycle of the request. + /// + /// - returns: The request. + @discardableResult + open func stream(closure: ((Data) -> Void)? = nil) -> Self { + dataDelegate.dataStream = closure + return self + } + + // MARK: Progress + + /// Sets a closure to be called periodically during the lifecycle of the `Request` as data is read from the server. + /// + /// - parameter queue: The dispatch queue to execute the closure on. + /// - parameter closure: The code to be executed periodically as data is read from the server. + /// + /// - returns: The request. + @discardableResult + open func downloadProgress(queue: DispatchQueue = DispatchQueue.main, closure: @escaping ProgressHandler) -> Self { + dataDelegate.progressHandler = (closure, queue) + return self + } +} + +// MARK: - + +/// Specific type of `Request` that manages an underlying `URLSessionDownloadTask`. +open class DownloadRequest: Request { + + // MARK: Helper Types + + /// A collection of options to be executed prior to moving a downloaded file from the temporary URL to the + /// destination URL. + public struct DownloadOptions: OptionSet { + /// Returns the raw bitmask value of the option and satisfies the `RawRepresentable` protocol. + public let rawValue: UInt + + /// A `DownloadOptions` flag that creates intermediate directories for the destination URL if specified. + public static let createIntermediateDirectories = DownloadOptions(rawValue: 1 << 0) + + /// A `DownloadOptions` flag that removes a previous file from the destination URL if specified. + public static let removePreviousFile = DownloadOptions(rawValue: 1 << 1) + + /// Creates a `DownloadFileDestinationOptions` instance with the specified raw value. + /// + /// - parameter rawValue: The raw bitmask value for the option. + /// + /// - returns: A new log level instance. + public init(rawValue: UInt) { + self.rawValue = rawValue + } + } + + /// A closure executed once a download request has successfully completed in order to determine where to move the + /// temporary file written to during the download process. The closure takes two arguments: the temporary file URL + /// and the URL response, and returns a two arguments: the file URL where the temporary file should be moved and + /// the options defining how the file should be moved. + public typealias DownloadFileDestination = ( + _ temporaryURL: URL, + _ response: HTTPURLResponse) + -> (destinationURL: URL, options: DownloadOptions) + + enum Downloadable: TaskConvertible { + case request(URLRequest) + case resumeData(Data) + + func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask { + do { + let task: URLSessionTask + + switch self { + case let .request(urlRequest): + let urlRequest = try urlRequest.adapt(using: adapter) + task = queue.sync { session.downloadTask(with: urlRequest) } + case let .resumeData(resumeData): + task = queue.sync { session.downloadTask(withResumeData: resumeData) } + } + + return task + } catch { + throw AdaptError(error: error) + } + } + } + + // MARK: Properties + + /// The request sent or to be sent to the server. + open override var request: URLRequest? { + if let request = super.request { return request } + + if let downloadable = originalTask as? Downloadable, case let .request(urlRequest) = downloadable { + return urlRequest + } + + return nil + } + + /// The resume data of the underlying download task if available after a failure. + open var resumeData: Data? { return downloadDelegate.resumeData } + + /// The progress of downloading the response data from the server for the request. + open var progress: Progress { return downloadDelegate.progress } + + var downloadDelegate: DownloadTaskDelegate { return delegate as! DownloadTaskDelegate } + + // MARK: State + + /// Cancels the request. + override open func cancel() { + cancel(createResumeData: true) + } + + /// Cancels the request. + /// + /// - parameter createResumeData: Determines whether resume data is created via the underlying download task or not. + open func cancel(createResumeData: Bool) { + if createResumeData { + downloadDelegate.downloadTask.cancel { self.downloadDelegate.resumeData = $0 } + } else { + downloadDelegate.downloadTask.cancel() + } + + NotificationCenter.default.post( + name: Notification.Name.Task.DidCancel, + object: self, + userInfo: [Notification.Key.Task: task as Any] + ) + } + + // MARK: Progress + + /// Sets a closure to be called periodically during the lifecycle of the `Request` as data is read from the server. + /// + /// - parameter queue: The dispatch queue to execute the closure on. + /// - parameter closure: The code to be executed periodically as data is read from the server. + /// + /// - returns: The request. + @discardableResult + open func downloadProgress(queue: DispatchQueue = DispatchQueue.main, closure: @escaping ProgressHandler) -> Self { + downloadDelegate.progressHandler = (closure, queue) + return self + } + + // MARK: Destination + + /// Creates a download file destination closure which uses the default file manager to move the temporary file to a + /// file URL in the first available directory with the specified search path directory and search path domain mask. + /// + /// - parameter directory: The search path directory. `.DocumentDirectory` by default. + /// - parameter domain: The search path domain mask. `.UserDomainMask` by default. + /// + /// - returns: A download file destination closure. + open class func suggestedDownloadDestination( + for directory: FileManager.SearchPathDirectory = .documentDirectory, + in domain: FileManager.SearchPathDomainMask = .userDomainMask) + -> DownloadFileDestination + { + return { temporaryURL, response in + let directoryURLs = FileManager.default.urls(for: directory, in: domain) + + if !directoryURLs.isEmpty { + return (directoryURLs[0].appendingPathComponent(response.suggestedFilename!), []) + } + + return (temporaryURL, []) + } + } +} + +// MARK: - + +/// Specific type of `Request` that manages an underlying `URLSessionUploadTask`. +open class UploadRequest: DataRequest { + + // MARK: Helper Types + + enum Uploadable: TaskConvertible { + case data(Data, URLRequest) + case file(URL, URLRequest) + case stream(InputStream, URLRequest) + + func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask { + do { + let task: URLSessionTask + + switch self { + case let .data(data, urlRequest): + let urlRequest = try urlRequest.adapt(using: adapter) + task = queue.sync { session.uploadTask(with: urlRequest, from: data) } + case let .file(url, urlRequest): + let urlRequest = try urlRequest.adapt(using: adapter) + task = queue.sync { session.uploadTask(with: urlRequest, fromFile: url) } + case let .stream(_, urlRequest): + let urlRequest = try urlRequest.adapt(using: adapter) + task = queue.sync { session.uploadTask(withStreamedRequest: urlRequest) } + } + + return task + } catch { + throw AdaptError(error: error) + } + } + } + + // MARK: Properties + + /// The request sent or to be sent to the server. + open override var request: URLRequest? { + if let request = super.request { return request } + + guard let uploadable = originalTask as? Uploadable else { return nil } + + switch uploadable { + case .data(_, let urlRequest), .file(_, let urlRequest), .stream(_, let urlRequest): + return urlRequest + } + } + + /// The progress of uploading the payload to the server for the upload request. + open var uploadProgress: Progress { return uploadDelegate.uploadProgress } + + var uploadDelegate: UploadTaskDelegate { return delegate as! UploadTaskDelegate } + + // MARK: Upload Progress + + /// Sets a closure to be called periodically during the lifecycle of the `UploadRequest` as data is sent to + /// the server. + /// + /// After the data is sent to the server, the `progress(queue:closure:)` APIs can be used to monitor the progress + /// of data being read from the server. + /// + /// - parameter queue: The dispatch queue to execute the closure on. + /// - parameter closure: The code to be executed periodically as data is sent to the server. + /// + /// - returns: The request. + @discardableResult + open func uploadProgress(queue: DispatchQueue = DispatchQueue.main, closure: @escaping ProgressHandler) -> Self { + uploadDelegate.uploadProgressHandler = (closure, queue) + return self + } +} + +// MARK: - + +#if !os(watchOS) + +/// Specific type of `Request` that manages an underlying `URLSessionStreamTask`. +@available(iOS 9.0, macOS 10.11, tvOS 9.0, *) +open class StreamRequest: Request { + enum Streamable: TaskConvertible { + case stream(hostName: String, port: Int) + case netService(NetService) + + func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask { + let task: URLSessionTask + + switch self { + case let .stream(hostName, port): + task = queue.sync { session.streamTask(withHostName: hostName, port: port) } + case let .netService(netService): + task = queue.sync { session.streamTask(with: netService) } + } + + return task + } + } +} + +#endif diff --git a/!main project/Pods/Alamofire/Source/Response.swift b/!main project/Pods/Alamofire/Source/Response.swift new file mode 100644 index 0000000..d05cfb0 --- /dev/null +++ b/!main project/Pods/Alamofire/Source/Response.swift @@ -0,0 +1,574 @@ +// +// Response.swift +// +// Copyright (c) 2014 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +/// Used to store all data associated with an non-serialized response of a data or upload request. +public struct DefaultDataResponse { + /// The URL request sent to the server. + public let request: URLRequest? + + /// The server's response to the URL request. + public let response: HTTPURLResponse? + + /// The data returned by the server. + public let data: Data? + + /// The error encountered while executing or validating the request. + public let error: Error? + + /// The timeline of the complete lifecycle of the request. + public let timeline: Timeline + + var _metrics: AnyObject? + + /// Creates a `DefaultDataResponse` instance from the specified parameters. + /// + /// - Parameters: + /// - request: The URL request sent to the server. + /// - response: The server's response to the URL request. + /// - data: The data returned by the server. + /// - error: The error encountered while executing or validating the request. + /// - timeline: The timeline of the complete lifecycle of the request. `Timeline()` by default. + /// - metrics: The task metrics containing the request / response statistics. `nil` by default. + public init( + request: URLRequest?, + response: HTTPURLResponse?, + data: Data?, + error: Error?, + timeline: Timeline = Timeline(), + metrics: AnyObject? = nil) + { + self.request = request + self.response = response + self.data = data + self.error = error + self.timeline = timeline + } +} + +// MARK: - + +/// Used to store all data associated with a serialized response of a data or upload request. +public struct DataResponse { + /// The URL request sent to the server. + public let request: URLRequest? + + /// The server's response to the URL request. + public let response: HTTPURLResponse? + + /// The data returned by the server. + public let data: Data? + + /// The result of response serialization. + public let result: Result + + /// The timeline of the complete lifecycle of the request. + public let timeline: Timeline + + /// Returns the associated value of the result if it is a success, `nil` otherwise. + public var value: Value? { return result.value } + + /// Returns the associated error value if the result if it is a failure, `nil` otherwise. + public var error: Error? { return result.error } + + var _metrics: AnyObject? + + /// Creates a `DataResponse` instance with the specified parameters derived from response serialization. + /// + /// - parameter request: The URL request sent to the server. + /// - parameter response: The server's response to the URL request. + /// - parameter data: The data returned by the server. + /// - parameter result: The result of response serialization. + /// - parameter timeline: The timeline of the complete lifecycle of the `Request`. Defaults to `Timeline()`. + /// + /// - returns: The new `DataResponse` instance. + public init( + request: URLRequest?, + response: HTTPURLResponse?, + data: Data?, + result: Result, + timeline: Timeline = Timeline()) + { + self.request = request + self.response = response + self.data = data + self.result = result + self.timeline = timeline + } +} + +// MARK: - + +extension DataResponse: CustomStringConvertible, CustomDebugStringConvertible { + /// The textual representation used when written to an output stream, which includes whether the result was a + /// success or failure. + public var description: String { + return result.debugDescription + } + + /// The debug textual representation used when written to an output stream, which includes the URL request, the URL + /// response, the server data, the response serialization result and the timeline. + public var debugDescription: String { + let requestDescription = request.map { "\($0.httpMethod ?? "GET") \($0)"} ?? "nil" + let requestBody = request?.httpBody.map { String(decoding: $0, as: UTF8.self) } ?? "None" + let responseDescription = response.map { "\($0)" } ?? "nil" + let responseBody = data.map { String(decoding: $0, as: UTF8.self) } ?? "None" + + return """ + [Request]: \(requestDescription) + [Request Body]: \n\(requestBody) + [Response]: \(responseDescription) + [Response Body]: \n\(responseBody) + [Result]: \(result) + [Timeline]: \(timeline.debugDescription) + """ + } +} + +// MARK: - + +extension DataResponse { + /// Evaluates the specified closure when the result of this `DataResponse` is a success, passing the unwrapped + /// result value as a parameter. + /// + /// Use the `map` method with a closure that does not throw. For example: + /// + /// let possibleData: DataResponse = ... + /// let possibleInt = possibleData.map { $0.count } + /// + /// - parameter transform: A closure that takes the success value of the instance's result. + /// + /// - returns: A `DataResponse` whose result wraps the value returned by the given closure. If this instance's + /// result is a failure, returns a response wrapping the same failure. + public func map(_ transform: (Value) -> T) -> DataResponse { + var response = DataResponse( + request: request, + response: self.response, + data: data, + result: result.map(transform), + timeline: timeline + ) + + response._metrics = _metrics + + return response + } + + /// Evaluates the given closure when the result of this `DataResponse` is a success, passing the unwrapped result + /// value as a parameter. + /// + /// Use the `flatMap` method with a closure that may throw an error. For example: + /// + /// let possibleData: DataResponse = ... + /// let possibleObject = possibleData.flatMap { + /// try JSONSerialization.jsonObject(with: $0) + /// } + /// + /// - parameter transform: A closure that takes the success value of the instance's result. + /// + /// - returns: A success or failure `DataResponse` depending on the result of the given closure. If this instance's + /// result is a failure, returns the same failure. + public func flatMap(_ transform: (Value) throws -> T) -> DataResponse { + var response = DataResponse( + request: request, + response: self.response, + data: data, + result: result.flatMap(transform), + timeline: timeline + ) + + response._metrics = _metrics + + return response + } + + /// Evaluates the specified closure when the `DataResponse` is a failure, passing the unwrapped error as a parameter. + /// + /// Use the `mapError` function with a closure that does not throw. For example: + /// + /// let possibleData: DataResponse = ... + /// let withMyError = possibleData.mapError { MyError.error($0) } + /// + /// - Parameter transform: A closure that takes the error of the instance. + /// - Returns: A `DataResponse` instance containing the result of the transform. + public func mapError(_ transform: (Error) -> E) -> DataResponse { + var response = DataResponse( + request: request, + response: self.response, + data: data, + result: result.mapError(transform), + timeline: timeline + ) + + response._metrics = _metrics + + return response + } + + /// Evaluates the specified closure when the `DataResponse` is a failure, passing the unwrapped error as a parameter. + /// + /// Use the `flatMapError` function with a closure that may throw an error. For example: + /// + /// let possibleData: DataResponse = ... + /// let possibleObject = possibleData.flatMapError { + /// try someFailableFunction(taking: $0) + /// } + /// + /// - Parameter transform: A throwing closure that takes the error of the instance. + /// + /// - Returns: A `DataResponse` instance containing the result of the transform. + public func flatMapError(_ transform: (Error) throws -> E) -> DataResponse { + var response = DataResponse( + request: request, + response: self.response, + data: data, + result: result.flatMapError(transform), + timeline: timeline + ) + + response._metrics = _metrics + + return response + } +} + +// MARK: - + +/// Used to store all data associated with an non-serialized response of a download request. +public struct DefaultDownloadResponse { + /// The URL request sent to the server. + public let request: URLRequest? + + /// The server's response to the URL request. + public let response: HTTPURLResponse? + + /// The temporary destination URL of the data returned from the server. + public let temporaryURL: URL? + + /// The final destination URL of the data returned from the server if it was moved. + public let destinationURL: URL? + + /// The resume data generated if the request was cancelled. + public let resumeData: Data? + + /// The error encountered while executing or validating the request. + public let error: Error? + + /// The timeline of the complete lifecycle of the request. + public let timeline: Timeline + + var _metrics: AnyObject? + + /// Creates a `DefaultDownloadResponse` instance from the specified parameters. + /// + /// - Parameters: + /// - request: The URL request sent to the server. + /// - response: The server's response to the URL request. + /// - temporaryURL: The temporary destination URL of the data returned from the server. + /// - destinationURL: The final destination URL of the data returned from the server if it was moved. + /// - resumeData: The resume data generated if the request was cancelled. + /// - error: The error encountered while executing or validating the request. + /// - timeline: The timeline of the complete lifecycle of the request. `Timeline()` by default. + /// - metrics: The task metrics containing the request / response statistics. `nil` by default. + public init( + request: URLRequest?, + response: HTTPURLResponse?, + temporaryURL: URL?, + destinationURL: URL?, + resumeData: Data?, + error: Error?, + timeline: Timeline = Timeline(), + metrics: AnyObject? = nil) + { + self.request = request + self.response = response + self.temporaryURL = temporaryURL + self.destinationURL = destinationURL + self.resumeData = resumeData + self.error = error + self.timeline = timeline + } +} + +// MARK: - + +/// Used to store all data associated with a serialized response of a download request. +public struct DownloadResponse { + /// The URL request sent to the server. + public let request: URLRequest? + + /// The server's response to the URL request. + public let response: HTTPURLResponse? + + /// The temporary destination URL of the data returned from the server. + public let temporaryURL: URL? + + /// The final destination URL of the data returned from the server if it was moved. + public let destinationURL: URL? + + /// The resume data generated if the request was cancelled. + public let resumeData: Data? + + /// The result of response serialization. + public let result: Result + + /// The timeline of the complete lifecycle of the request. + public let timeline: Timeline + + /// Returns the associated value of the result if it is a success, `nil` otherwise. + public var value: Value? { return result.value } + + /// Returns the associated error value if the result if it is a failure, `nil` otherwise. + public var error: Error? { return result.error } + + var _metrics: AnyObject? + + /// Creates a `DownloadResponse` instance with the specified parameters derived from response serialization. + /// + /// - parameter request: The URL request sent to the server. + /// - parameter response: The server's response to the URL request. + /// - parameter temporaryURL: The temporary destination URL of the data returned from the server. + /// - parameter destinationURL: The final destination URL of the data returned from the server if it was moved. + /// - parameter resumeData: The resume data generated if the request was cancelled. + /// - parameter result: The result of response serialization. + /// - parameter timeline: The timeline of the complete lifecycle of the `Request`. Defaults to `Timeline()`. + /// + /// - returns: The new `DownloadResponse` instance. + public init( + request: URLRequest?, + response: HTTPURLResponse?, + temporaryURL: URL?, + destinationURL: URL?, + resumeData: Data?, + result: Result, + timeline: Timeline = Timeline()) + { + self.request = request + self.response = response + self.temporaryURL = temporaryURL + self.destinationURL = destinationURL + self.resumeData = resumeData + self.result = result + self.timeline = timeline + } +} + +// MARK: - + +extension DownloadResponse: CustomStringConvertible, CustomDebugStringConvertible { + /// The textual representation used when written to an output stream, which includes whether the result was a + /// success or failure. + public var description: String { + return result.debugDescription + } + + /// The debug textual representation used when written to an output stream, which includes the URL request, the URL + /// response, the temporary and destination URLs, the resume data, the response serialization result and the + /// timeline. + public var debugDescription: String { + let requestDescription = request.map { "\($0.httpMethod ?? "GET") \($0)"} ?? "nil" + let requestBody = request?.httpBody.map { String(decoding: $0, as: UTF8.self) } ?? "None" + let responseDescription = response.map { "\($0)" } ?? "nil" + + return """ + [Request]: \(requestDescription) + [Request Body]: \n\(requestBody) + [Response]: \(responseDescription) + [TemporaryURL]: \(temporaryURL?.path ?? "nil") + [DestinationURL]: \(destinationURL?.path ?? "nil") + [ResumeData]: \(resumeData?.count ?? 0) bytes + [Result]: \(result) + [Timeline]: \(timeline.debugDescription) + """ + } +} + +// MARK: - + +extension DownloadResponse { + /// Evaluates the given closure when the result of this `DownloadResponse` is a success, passing the unwrapped + /// result value as a parameter. + /// + /// Use the `map` method with a closure that does not throw. For example: + /// + /// let possibleData: DownloadResponse = ... + /// let possibleInt = possibleData.map { $0.count } + /// + /// - parameter transform: A closure that takes the success value of the instance's result. + /// + /// - returns: A `DownloadResponse` whose result wraps the value returned by the given closure. If this instance's + /// result is a failure, returns a response wrapping the same failure. + public func map(_ transform: (Value) -> T) -> DownloadResponse { + var response = DownloadResponse( + request: request, + response: self.response, + temporaryURL: temporaryURL, + destinationURL: destinationURL, + resumeData: resumeData, + result: result.map(transform), + timeline: timeline + ) + + response._metrics = _metrics + + return response + } + + /// Evaluates the given closure when the result of this `DownloadResponse` is a success, passing the unwrapped + /// result value as a parameter. + /// + /// Use the `flatMap` method with a closure that may throw an error. For example: + /// + /// let possibleData: DownloadResponse = ... + /// let possibleObject = possibleData.flatMap { + /// try JSONSerialization.jsonObject(with: $0) + /// } + /// + /// - parameter transform: A closure that takes the success value of the instance's result. + /// + /// - returns: A success or failure `DownloadResponse` depending on the result of the given closure. If this + /// instance's result is a failure, returns the same failure. + public func flatMap(_ transform: (Value) throws -> T) -> DownloadResponse { + var response = DownloadResponse( + request: request, + response: self.response, + temporaryURL: temporaryURL, + destinationURL: destinationURL, + resumeData: resumeData, + result: result.flatMap(transform), + timeline: timeline + ) + + response._metrics = _metrics + + return response + } + + /// Evaluates the specified closure when the `DownloadResponse` is a failure, passing the unwrapped error as a parameter. + /// + /// Use the `mapError` function with a closure that does not throw. For example: + /// + /// let possibleData: DownloadResponse = ... + /// let withMyError = possibleData.mapError { MyError.error($0) } + /// + /// - Parameter transform: A closure that takes the error of the instance. + /// - Returns: A `DownloadResponse` instance containing the result of the transform. + public func mapError(_ transform: (Error) -> E) -> DownloadResponse { + var response = DownloadResponse( + request: request, + response: self.response, + temporaryURL: temporaryURL, + destinationURL: destinationURL, + resumeData: resumeData, + result: result.mapError(transform), + timeline: timeline + ) + + response._metrics = _metrics + + return response + } + + /// Evaluates the specified closure when the `DownloadResponse` is a failure, passing the unwrapped error as a parameter. + /// + /// Use the `flatMapError` function with a closure that may throw an error. For example: + /// + /// let possibleData: DownloadResponse = ... + /// let possibleObject = possibleData.flatMapError { + /// try someFailableFunction(taking: $0) + /// } + /// + /// - Parameter transform: A throwing closure that takes the error of the instance. + /// + /// - Returns: A `DownloadResponse` instance containing the result of the transform. + public func flatMapError(_ transform: (Error) throws -> E) -> DownloadResponse { + var response = DownloadResponse( + request: request, + response: self.response, + temporaryURL: temporaryURL, + destinationURL: destinationURL, + resumeData: resumeData, + result: result.flatMapError(transform), + timeline: timeline + ) + + response._metrics = _metrics + + return response + } +} + +// MARK: - + +protocol Response { + /// The task metrics containing the request / response statistics. + var _metrics: AnyObject? { get set } + mutating func add(_ metrics: AnyObject?) +} + +extension Response { + mutating func add(_ metrics: AnyObject?) { + #if !os(watchOS) + guard #available(iOS 10.0, macOS 10.12, tvOS 10.0, *) else { return } + guard let metrics = metrics as? URLSessionTaskMetrics else { return } + + _metrics = metrics + #endif + } +} + +// MARK: - + +@available(iOS 10.0, macOS 10.12, tvOS 10.0, *) +extension DefaultDataResponse: Response { +#if !os(watchOS) + /// The task metrics containing the request / response statistics. + public var metrics: URLSessionTaskMetrics? { return _metrics as? URLSessionTaskMetrics } +#endif +} + +@available(iOS 10.0, macOS 10.12, tvOS 10.0, *) +extension DataResponse: Response { +#if !os(watchOS) + /// The task metrics containing the request / response statistics. + public var metrics: URLSessionTaskMetrics? { return _metrics as? URLSessionTaskMetrics } +#endif +} + +@available(iOS 10.0, macOS 10.12, tvOS 10.0, *) +extension DefaultDownloadResponse: Response { +#if !os(watchOS) + /// The task metrics containing the request / response statistics. + public var metrics: URLSessionTaskMetrics? { return _metrics as? URLSessionTaskMetrics } +#endif +} + +@available(iOS 10.0, macOS 10.12, tvOS 10.0, *) +extension DownloadResponse: Response { +#if !os(watchOS) + /// The task metrics containing the request / response statistics. + public var metrics: URLSessionTaskMetrics? { return _metrics as? URLSessionTaskMetrics } +#endif +} diff --git a/!main project/Pods/Alamofire/Source/ResponseSerialization.swift b/!main project/Pods/Alamofire/Source/ResponseSerialization.swift new file mode 100644 index 0000000..9cc105a --- /dev/null +++ b/!main project/Pods/Alamofire/Source/ResponseSerialization.swift @@ -0,0 +1,715 @@ +// +// ResponseSerialization.swift +// +// Copyright (c) 2014 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +/// The type in which all data response serializers must conform to in order to serialize a response. +public protocol DataResponseSerializerProtocol { + /// The type of serialized object to be created by this `DataResponseSerializerType`. + associatedtype SerializedObject + + /// A closure used by response handlers that takes a request, response, data and error and returns a result. + var serializeResponse: (URLRequest?, HTTPURLResponse?, Data?, Error?) -> Result { get } +} + +// MARK: - + +/// A generic `DataResponseSerializerType` used to serialize a request, response, and data into a serialized object. +public struct DataResponseSerializer: DataResponseSerializerProtocol { + /// The type of serialized object to be created by this `DataResponseSerializer`. + public typealias SerializedObject = Value + + /// A closure used by response handlers that takes a request, response, data and error and returns a result. + public var serializeResponse: (URLRequest?, HTTPURLResponse?, Data?, Error?) -> Result + + /// Initializes the `ResponseSerializer` instance with the given serialize response closure. + /// + /// - parameter serializeResponse: The closure used to serialize the response. + /// + /// - returns: The new generic response serializer instance. + public init(serializeResponse: @escaping (URLRequest?, HTTPURLResponse?, Data?, Error?) -> Result) { + self.serializeResponse = serializeResponse + } +} + +// MARK: - + +/// The type in which all download response serializers must conform to in order to serialize a response. +public protocol DownloadResponseSerializerProtocol { + /// The type of serialized object to be created by this `DownloadResponseSerializerType`. + associatedtype SerializedObject + + /// A closure used by response handlers that takes a request, response, url and error and returns a result. + var serializeResponse: (URLRequest?, HTTPURLResponse?, URL?, Error?) -> Result { get } +} + +// MARK: - + +/// A generic `DownloadResponseSerializerType` used to serialize a request, response, and data into a serialized object. +public struct DownloadResponseSerializer: DownloadResponseSerializerProtocol { + /// The type of serialized object to be created by this `DownloadResponseSerializer`. + public typealias SerializedObject = Value + + /// A closure used by response handlers that takes a request, response, url and error and returns a result. + public var serializeResponse: (URLRequest?, HTTPURLResponse?, URL?, Error?) -> Result + + /// Initializes the `ResponseSerializer` instance with the given serialize response closure. + /// + /// - parameter serializeResponse: The closure used to serialize the response. + /// + /// - returns: The new generic response serializer instance. + public init(serializeResponse: @escaping (URLRequest?, HTTPURLResponse?, URL?, Error?) -> Result) { + self.serializeResponse = serializeResponse + } +} + +// MARK: - Timeline + +extension Request { + var timeline: Timeline { + let requestStartTime = self.startTime ?? CFAbsoluteTimeGetCurrent() + let requestCompletedTime = self.endTime ?? CFAbsoluteTimeGetCurrent() + let initialResponseTime = self.delegate.initialResponseTime ?? requestCompletedTime + + return Timeline( + requestStartTime: requestStartTime, + initialResponseTime: initialResponseTime, + requestCompletedTime: requestCompletedTime, + serializationCompletedTime: CFAbsoluteTimeGetCurrent() + ) + } +} + +// MARK: - Default + +extension DataRequest { + /// Adds a handler to be called once the request has finished. + /// + /// - parameter queue: The queue on which the completion handler is dispatched. + /// - parameter completionHandler: The code to be executed once the request has finished. + /// + /// - returns: The request. + @discardableResult + public func response(queue: DispatchQueue? = nil, completionHandler: @escaping (DefaultDataResponse) -> Void) -> Self { + delegate.queue.addOperation { + (queue ?? DispatchQueue.main).async { + var dataResponse = DefaultDataResponse( + request: self.request, + response: self.response, + data: self.delegate.data, + error: self.delegate.error, + timeline: self.timeline + ) + + dataResponse.add(self.delegate.metrics) + + completionHandler(dataResponse) + } + } + + return self + } + + /// Adds a handler to be called once the request has finished. + /// + /// - parameter queue: The queue on which the completion handler is dispatched. + /// - parameter responseSerializer: The response serializer responsible for serializing the request, response, + /// and data. + /// - parameter completionHandler: The code to be executed once the request has finished. + /// + /// - returns: The request. + @discardableResult + public func response( + queue: DispatchQueue? = nil, + responseSerializer: T, + completionHandler: @escaping (DataResponse) -> Void) + -> Self + { + delegate.queue.addOperation { + let result = responseSerializer.serializeResponse( + self.request, + self.response, + self.delegate.data, + self.delegate.error + ) + + var dataResponse = DataResponse( + request: self.request, + response: self.response, + data: self.delegate.data, + result: result, + timeline: self.timeline + ) + + dataResponse.add(self.delegate.metrics) + + (queue ?? DispatchQueue.main).async { completionHandler(dataResponse) } + } + + return self + } +} + +extension DownloadRequest { + /// Adds a handler to be called once the request has finished. + /// + /// - parameter queue: The queue on which the completion handler is dispatched. + /// - parameter completionHandler: The code to be executed once the request has finished. + /// + /// - returns: The request. + @discardableResult + public func response( + queue: DispatchQueue? = nil, + completionHandler: @escaping (DefaultDownloadResponse) -> Void) + -> Self + { + delegate.queue.addOperation { + (queue ?? DispatchQueue.main).async { + var downloadResponse = DefaultDownloadResponse( + request: self.request, + response: self.response, + temporaryURL: self.downloadDelegate.temporaryURL, + destinationURL: self.downloadDelegate.destinationURL, + resumeData: self.downloadDelegate.resumeData, + error: self.downloadDelegate.error, + timeline: self.timeline + ) + + downloadResponse.add(self.delegate.metrics) + + completionHandler(downloadResponse) + } + } + + return self + } + + /// Adds a handler to be called once the request has finished. + /// + /// - parameter queue: The queue on which the completion handler is dispatched. + /// - parameter responseSerializer: The response serializer responsible for serializing the request, response, + /// and data contained in the destination url. + /// - parameter completionHandler: The code to be executed once the request has finished. + /// + /// - returns: The request. + @discardableResult + public func response( + queue: DispatchQueue? = nil, + responseSerializer: T, + completionHandler: @escaping (DownloadResponse) -> Void) + -> Self + { + delegate.queue.addOperation { + let result = responseSerializer.serializeResponse( + self.request, + self.response, + self.downloadDelegate.fileURL, + self.downloadDelegate.error + ) + + var downloadResponse = DownloadResponse( + request: self.request, + response: self.response, + temporaryURL: self.downloadDelegate.temporaryURL, + destinationURL: self.downloadDelegate.destinationURL, + resumeData: self.downloadDelegate.resumeData, + result: result, + timeline: self.timeline + ) + + downloadResponse.add(self.delegate.metrics) + + (queue ?? DispatchQueue.main).async { completionHandler(downloadResponse) } + } + + return self + } +} + +// MARK: - Data + +extension Request { + /// Returns a result data type that contains the response data as-is. + /// + /// - parameter response: The response from the server. + /// - parameter data: The data returned from the server. + /// - parameter error: The error already encountered if it exists. + /// + /// - returns: The result data type. + public static func serializeResponseData(response: HTTPURLResponse?, data: Data?, error: Error?) -> Result { + guard error == nil else { return .failure(error!) } + + if let response = response, emptyDataStatusCodes.contains(response.statusCode) { return .success(Data()) } + + guard let validData = data else { + return .failure(AFError.responseSerializationFailed(reason: .inputDataNil)) + } + + return .success(validData) + } +} + +extension DataRequest { + /// Creates a response serializer that returns the associated data as-is. + /// + /// - returns: A data response serializer. + public static func dataResponseSerializer() -> DataResponseSerializer { + return DataResponseSerializer { _, response, data, error in + return Request.serializeResponseData(response: response, data: data, error: error) + } + } + + /// Adds a handler to be called once the request has finished. + /// + /// - parameter completionHandler: The code to be executed once the request has finished. + /// + /// - returns: The request. + @discardableResult + public func responseData( + queue: DispatchQueue? = nil, + completionHandler: @escaping (DataResponse) -> Void) + -> Self + { + return response( + queue: queue, + responseSerializer: DataRequest.dataResponseSerializer(), + completionHandler: completionHandler + ) + } +} + +extension DownloadRequest { + /// Creates a response serializer that returns the associated data as-is. + /// + /// - returns: A data response serializer. + public static func dataResponseSerializer() -> DownloadResponseSerializer { + return DownloadResponseSerializer { _, response, fileURL, error in + guard error == nil else { return .failure(error!) } + + guard let fileURL = fileURL else { + return .failure(AFError.responseSerializationFailed(reason: .inputFileNil)) + } + + do { + let data = try Data(contentsOf: fileURL) + return Request.serializeResponseData(response: response, data: data, error: error) + } catch { + return .failure(AFError.responseSerializationFailed(reason: .inputFileReadFailed(at: fileURL))) + } + } + } + + /// Adds a handler to be called once the request has finished. + /// + /// - parameter completionHandler: The code to be executed once the request has finished. + /// + /// - returns: The request. + @discardableResult + public func responseData( + queue: DispatchQueue? = nil, + completionHandler: @escaping (DownloadResponse) -> Void) + -> Self + { + return response( + queue: queue, + responseSerializer: DownloadRequest.dataResponseSerializer(), + completionHandler: completionHandler + ) + } +} + +// MARK: - String + +extension Request { + /// Returns a result string type initialized from the response data with the specified string encoding. + /// + /// - parameter encoding: The string encoding. If `nil`, the string encoding will be determined from the server + /// response, falling back to the default HTTP default character set, ISO-8859-1. + /// - parameter response: The response from the server. + /// - parameter data: The data returned from the server. + /// - parameter error: The error already encountered if it exists. + /// + /// - returns: The result data type. + public static func serializeResponseString( + encoding: String.Encoding?, + response: HTTPURLResponse?, + data: Data?, + error: Error?) + -> Result + { + guard error == nil else { return .failure(error!) } + + if let response = response, emptyDataStatusCodes.contains(response.statusCode) { return .success("") } + + guard let validData = data else { + return .failure(AFError.responseSerializationFailed(reason: .inputDataNil)) + } + + var convertedEncoding = encoding + + if let encodingName = response?.textEncodingName as CFString?, convertedEncoding == nil { + convertedEncoding = String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding( + CFStringConvertIANACharSetNameToEncoding(encodingName)) + ) + } + + let actualEncoding = convertedEncoding ?? .isoLatin1 + + if let string = String(data: validData, encoding: actualEncoding) { + return .success(string) + } else { + return .failure(AFError.responseSerializationFailed(reason: .stringSerializationFailed(encoding: actualEncoding))) + } + } +} + +extension DataRequest { + /// Creates a response serializer that returns a result string type initialized from the response data with + /// the specified string encoding. + /// + /// - parameter encoding: The string encoding. If `nil`, the string encoding will be determined from the server + /// response, falling back to the default HTTP default character set, ISO-8859-1. + /// + /// - returns: A string response serializer. + public static func stringResponseSerializer(encoding: String.Encoding? = nil) -> DataResponseSerializer { + return DataResponseSerializer { _, response, data, error in + return Request.serializeResponseString(encoding: encoding, response: response, data: data, error: error) + } + } + + /// Adds a handler to be called once the request has finished. + /// + /// - parameter encoding: The string encoding. If `nil`, the string encoding will be determined from the + /// server response, falling back to the default HTTP default character set, + /// ISO-8859-1. + /// - parameter completionHandler: A closure to be executed once the request has finished. + /// + /// - returns: The request. + @discardableResult + public func responseString( + queue: DispatchQueue? = nil, + encoding: String.Encoding? = nil, + completionHandler: @escaping (DataResponse) -> Void) + -> Self + { + return response( + queue: queue, + responseSerializer: DataRequest.stringResponseSerializer(encoding: encoding), + completionHandler: completionHandler + ) + } +} + +extension DownloadRequest { + /// Creates a response serializer that returns a result string type initialized from the response data with + /// the specified string encoding. + /// + /// - parameter encoding: The string encoding. If `nil`, the string encoding will be determined from the server + /// response, falling back to the default HTTP default character set, ISO-8859-1. + /// + /// - returns: A string response serializer. + public static func stringResponseSerializer(encoding: String.Encoding? = nil) -> DownloadResponseSerializer { + return DownloadResponseSerializer { _, response, fileURL, error in + guard error == nil else { return .failure(error!) } + + guard let fileURL = fileURL else { + return .failure(AFError.responseSerializationFailed(reason: .inputFileNil)) + } + + do { + let data = try Data(contentsOf: fileURL) + return Request.serializeResponseString(encoding: encoding, response: response, data: data, error: error) + } catch { + return .failure(AFError.responseSerializationFailed(reason: .inputFileReadFailed(at: fileURL))) + } + } + } + + /// Adds a handler to be called once the request has finished. + /// + /// - parameter encoding: The string encoding. If `nil`, the string encoding will be determined from the + /// server response, falling back to the default HTTP default character set, + /// ISO-8859-1. + /// - parameter completionHandler: A closure to be executed once the request has finished. + /// + /// - returns: The request. + @discardableResult + public func responseString( + queue: DispatchQueue? = nil, + encoding: String.Encoding? = nil, + completionHandler: @escaping (DownloadResponse) -> Void) + -> Self + { + return response( + queue: queue, + responseSerializer: DownloadRequest.stringResponseSerializer(encoding: encoding), + completionHandler: completionHandler + ) + } +} + +// MARK: - JSON + +extension Request { + /// Returns a JSON object contained in a result type constructed from the response data using `JSONSerialization` + /// with the specified reading options. + /// + /// - parameter options: The JSON serialization reading options. Defaults to `.allowFragments`. + /// - parameter response: The response from the server. + /// - parameter data: The data returned from the server. + /// - parameter error: The error already encountered if it exists. + /// + /// - returns: The result data type. + public static func serializeResponseJSON( + options: JSONSerialization.ReadingOptions, + response: HTTPURLResponse?, + data: Data?, + error: Error?) + -> Result + { + guard error == nil else { return .failure(error!) } + + if let response = response, emptyDataStatusCodes.contains(response.statusCode) { return .success(NSNull()) } + + guard let validData = data, validData.count > 0 else { + return .failure(AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength)) + } + + do { + let json = try JSONSerialization.jsonObject(with: validData, options: options) + return .success(json) + } catch { + return .failure(AFError.responseSerializationFailed(reason: .jsonSerializationFailed(error: error))) + } + } +} + +extension DataRequest { + /// Creates a response serializer that returns a JSON object result type constructed from the response data using + /// `JSONSerialization` with the specified reading options. + /// + /// - parameter options: The JSON serialization reading options. Defaults to `.allowFragments`. + /// + /// - returns: A JSON object response serializer. + public static func jsonResponseSerializer( + options: JSONSerialization.ReadingOptions = .allowFragments) + -> DataResponseSerializer + { + return DataResponseSerializer { _, response, data, error in + return Request.serializeResponseJSON(options: options, response: response, data: data, error: error) + } + } + + /// Adds a handler to be called once the request has finished. + /// + /// - parameter options: The JSON serialization reading options. Defaults to `.allowFragments`. + /// - parameter completionHandler: A closure to be executed once the request has finished. + /// + /// - returns: The request. + @discardableResult + public func responseJSON( + queue: DispatchQueue? = nil, + options: JSONSerialization.ReadingOptions = .allowFragments, + completionHandler: @escaping (DataResponse) -> Void) + -> Self + { + return response( + queue: queue, + responseSerializer: DataRequest.jsonResponseSerializer(options: options), + completionHandler: completionHandler + ) + } +} + +extension DownloadRequest { + /// Creates a response serializer that returns a JSON object result type constructed from the response data using + /// `JSONSerialization` with the specified reading options. + /// + /// - parameter options: The JSON serialization reading options. Defaults to `.allowFragments`. + /// + /// - returns: A JSON object response serializer. + public static func jsonResponseSerializer( + options: JSONSerialization.ReadingOptions = .allowFragments) + -> DownloadResponseSerializer + { + return DownloadResponseSerializer { _, response, fileURL, error in + guard error == nil else { return .failure(error!) } + + guard let fileURL = fileURL else { + return .failure(AFError.responseSerializationFailed(reason: .inputFileNil)) + } + + do { + let data = try Data(contentsOf: fileURL) + return Request.serializeResponseJSON(options: options, response: response, data: data, error: error) + } catch { + return .failure(AFError.responseSerializationFailed(reason: .inputFileReadFailed(at: fileURL))) + } + } + } + + /// Adds a handler to be called once the request has finished. + /// + /// - parameter options: The JSON serialization reading options. Defaults to `.allowFragments`. + /// - parameter completionHandler: A closure to be executed once the request has finished. + /// + /// - returns: The request. + @discardableResult + public func responseJSON( + queue: DispatchQueue? = nil, + options: JSONSerialization.ReadingOptions = .allowFragments, + completionHandler: @escaping (DownloadResponse) -> Void) + -> Self + { + return response( + queue: queue, + responseSerializer: DownloadRequest.jsonResponseSerializer(options: options), + completionHandler: completionHandler + ) + } +} + +// MARK: - Property List + +extension Request { + /// Returns a plist object contained in a result type constructed from the response data using + /// `PropertyListSerialization` with the specified reading options. + /// + /// - parameter options: The property list reading options. Defaults to `[]`. + /// - parameter response: The response from the server. + /// - parameter data: The data returned from the server. + /// - parameter error: The error already encountered if it exists. + /// + /// - returns: The result data type. + public static func serializeResponsePropertyList( + options: PropertyListSerialization.ReadOptions, + response: HTTPURLResponse?, + data: Data?, + error: Error?) + -> Result + { + guard error == nil else { return .failure(error!) } + + if let response = response, emptyDataStatusCodes.contains(response.statusCode) { return .success(NSNull()) } + + guard let validData = data, validData.count > 0 else { + return .failure(AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength)) + } + + do { + let plist = try PropertyListSerialization.propertyList(from: validData, options: options, format: nil) + return .success(plist) + } catch { + return .failure(AFError.responseSerializationFailed(reason: .propertyListSerializationFailed(error: error))) + } + } +} + +extension DataRequest { + /// Creates a response serializer that returns an object constructed from the response data using + /// `PropertyListSerialization` with the specified reading options. + /// + /// - parameter options: The property list reading options. Defaults to `[]`. + /// + /// - returns: A property list object response serializer. + public static func propertyListResponseSerializer( + options: PropertyListSerialization.ReadOptions = []) + -> DataResponseSerializer + { + return DataResponseSerializer { _, response, data, error in + return Request.serializeResponsePropertyList(options: options, response: response, data: data, error: error) + } + } + + /// Adds a handler to be called once the request has finished. + /// + /// - parameter options: The property list reading options. Defaults to `[]`. + /// - parameter completionHandler: A closure to be executed once the request has finished. + /// + /// - returns: The request. + @discardableResult + public func responsePropertyList( + queue: DispatchQueue? = nil, + options: PropertyListSerialization.ReadOptions = [], + completionHandler: @escaping (DataResponse) -> Void) + -> Self + { + return response( + queue: queue, + responseSerializer: DataRequest.propertyListResponseSerializer(options: options), + completionHandler: completionHandler + ) + } +} + +extension DownloadRequest { + /// Creates a response serializer that returns an object constructed from the response data using + /// `PropertyListSerialization` with the specified reading options. + /// + /// - parameter options: The property list reading options. Defaults to `[]`. + /// + /// - returns: A property list object response serializer. + public static func propertyListResponseSerializer( + options: PropertyListSerialization.ReadOptions = []) + -> DownloadResponseSerializer + { + return DownloadResponseSerializer { _, response, fileURL, error in + guard error == nil else { return .failure(error!) } + + guard let fileURL = fileURL else { + return .failure(AFError.responseSerializationFailed(reason: .inputFileNil)) + } + + do { + let data = try Data(contentsOf: fileURL) + return Request.serializeResponsePropertyList(options: options, response: response, data: data, error: error) + } catch { + return .failure(AFError.responseSerializationFailed(reason: .inputFileReadFailed(at: fileURL))) + } + } + } + + /// Adds a handler to be called once the request has finished. + /// + /// - parameter options: The property list reading options. Defaults to `[]`. + /// - parameter completionHandler: A closure to be executed once the request has finished. + /// + /// - returns: The request. + @discardableResult + public func responsePropertyList( + queue: DispatchQueue? = nil, + options: PropertyListSerialization.ReadOptions = [], + completionHandler: @escaping (DownloadResponse) -> Void) + -> Self + { + return response( + queue: queue, + responseSerializer: DownloadRequest.propertyListResponseSerializer(options: options), + completionHandler: completionHandler + ) + } +} + +/// A set of HTTP response status code that do not contain response data. +private let emptyDataStatusCodes: Set = [204, 205] diff --git a/!main project/Pods/Alamofire/Source/Result.swift b/!main project/Pods/Alamofire/Source/Result.swift new file mode 100644 index 0000000..e092808 --- /dev/null +++ b/!main project/Pods/Alamofire/Source/Result.swift @@ -0,0 +1,300 @@ +// +// Result.swift +// +// Copyright (c) 2014 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +/// Used to represent whether a request was successful or encountered an error. +/// +/// - success: The request and all post processing operations were successful resulting in the serialization of the +/// provided associated value. +/// +/// - failure: The request encountered an error resulting in a failure. The associated values are the original data +/// provided by the server as well as the error that caused the failure. +public enum Result { + case success(Value) + case failure(Error) + + /// Returns `true` if the result is a success, `false` otherwise. + public var isSuccess: Bool { + switch self { + case .success: + return true + case .failure: + return false + } + } + + /// Returns `true` if the result is a failure, `false` otherwise. + public var isFailure: Bool { + return !isSuccess + } + + /// Returns the associated value if the result is a success, `nil` otherwise. + public var value: Value? { + switch self { + case .success(let value): + return value + case .failure: + return nil + } + } + + /// Returns the associated error value if the result is a failure, `nil` otherwise. + public var error: Error? { + switch self { + case .success: + return nil + case .failure(let error): + return error + } + } +} + +// MARK: - CustomStringConvertible + +extension Result: CustomStringConvertible { + /// The textual representation used when written to an output stream, which includes whether the result was a + /// success or failure. + public var description: String { + switch self { + case .success: + return "SUCCESS" + case .failure: + return "FAILURE" + } + } +} + +// MARK: - CustomDebugStringConvertible + +extension Result: CustomDebugStringConvertible { + /// The debug textual representation used when written to an output stream, which includes whether the result was a + /// success or failure in addition to the value or error. + public var debugDescription: String { + switch self { + case .success(let value): + return "SUCCESS: \(value)" + case .failure(let error): + return "FAILURE: \(error)" + } + } +} + +// MARK: - Functional APIs + +extension Result { + /// Creates a `Result` instance from the result of a closure. + /// + /// A failure result is created when the closure throws, and a success result is created when the closure + /// succeeds without throwing an error. + /// + /// func someString() throws -> String { ... } + /// + /// let result = Result(value: { + /// return try someString() + /// }) + /// + /// // The type of result is Result + /// + /// The trailing closure syntax is also supported: + /// + /// let result = Result { try someString() } + /// + /// - parameter value: The closure to execute and create the result for. + public init(value: () throws -> Value) { + do { + self = try .success(value()) + } catch { + self = .failure(error) + } + } + + /// Returns the success value, or throws the failure error. + /// + /// let possibleString: Result = .success("success") + /// try print(possibleString.unwrap()) + /// // Prints "success" + /// + /// let noString: Result = .failure(error) + /// try print(noString.unwrap()) + /// // Throws error + public func unwrap() throws -> Value { + switch self { + case .success(let value): + return value + case .failure(let error): + throw error + } + } + + /// Evaluates the specified closure when the `Result` is a success, passing the unwrapped value as a parameter. + /// + /// Use the `map` method with a closure that does not throw. For example: + /// + /// let possibleData: Result = .success(Data()) + /// let possibleInt = possibleData.map { $0.count } + /// try print(possibleInt.unwrap()) + /// // Prints "0" + /// + /// let noData: Result = .failure(error) + /// let noInt = noData.map { $0.count } + /// try print(noInt.unwrap()) + /// // Throws error + /// + /// - parameter transform: A closure that takes the success value of the `Result` instance. + /// + /// - returns: A `Result` containing the result of the given closure. If this instance is a failure, returns the + /// same failure. + public func map(_ transform: (Value) -> T) -> Result { + switch self { + case .success(let value): + return .success(transform(value)) + case .failure(let error): + return .failure(error) + } + } + + /// Evaluates the specified closure when the `Result` is a success, passing the unwrapped value as a parameter. + /// + /// Use the `flatMap` method with a closure that may throw an error. For example: + /// + /// let possibleData: Result = .success(Data(...)) + /// let possibleObject = possibleData.flatMap { + /// try JSONSerialization.jsonObject(with: $0) + /// } + /// + /// - parameter transform: A closure that takes the success value of the instance. + /// + /// - returns: A `Result` containing the result of the given closure. If this instance is a failure, returns the + /// same failure. + public func flatMap(_ transform: (Value) throws -> T) -> Result { + switch self { + case .success(let value): + do { + return try .success(transform(value)) + } catch { + return .failure(error) + } + case .failure(let error): + return .failure(error) + } + } + + /// Evaluates the specified closure when the `Result` is a failure, passing the unwrapped error as a parameter. + /// + /// Use the `mapError` function with a closure that does not throw. For example: + /// + /// let possibleData: Result = .failure(someError) + /// let withMyError: Result = possibleData.mapError { MyError.error($0) } + /// + /// - Parameter transform: A closure that takes the error of the instance. + /// - Returns: A `Result` instance containing the result of the transform. If this instance is a success, returns + /// the same instance. + public func mapError(_ transform: (Error) -> T) -> Result { + switch self { + case .failure(let error): + return .failure(transform(error)) + case .success: + return self + } + } + + /// Evaluates the specified closure when the `Result` is a failure, passing the unwrapped error as a parameter. + /// + /// Use the `flatMapError` function with a closure that may throw an error. For example: + /// + /// let possibleData: Result = .success(Data(...)) + /// let possibleObject = possibleData.flatMapError { + /// try someFailableFunction(taking: $0) + /// } + /// + /// - Parameter transform: A throwing closure that takes the error of the instance. + /// + /// - Returns: A `Result` instance containing the result of the transform. If this instance is a success, returns + /// the same instance. + public func flatMapError(_ transform: (Error) throws -> T) -> Result { + switch self { + case .failure(let error): + do { + return try .failure(transform(error)) + } catch { + return .failure(error) + } + case .success: + return self + } + } + + /// Evaluates the specified closure when the `Result` is a success, passing the unwrapped value as a parameter. + /// + /// Use the `withValue` function to evaluate the passed closure without modifying the `Result` instance. + /// + /// - Parameter closure: A closure that takes the success value of this instance. + /// - Returns: This `Result` instance, unmodified. + @discardableResult + public func withValue(_ closure: (Value) throws -> Void) rethrows -> Result { + if case let .success(value) = self { try closure(value) } + + return self + } + + /// Evaluates the specified closure when the `Result` is a failure, passing the unwrapped error as a parameter. + /// + /// Use the `withError` function to evaluate the passed closure without modifying the `Result` instance. + /// + /// - Parameter closure: A closure that takes the success value of this instance. + /// - Returns: This `Result` instance, unmodified. + @discardableResult + public func withError(_ closure: (Error) throws -> Void) rethrows -> Result { + if case let .failure(error) = self { try closure(error) } + + return self + } + + /// Evaluates the specified closure when the `Result` is a success. + /// + /// Use the `ifSuccess` function to evaluate the passed closure without modifying the `Result` instance. + /// + /// - Parameter closure: A `Void` closure. + /// - Returns: This `Result` instance, unmodified. + @discardableResult + public func ifSuccess(_ closure: () throws -> Void) rethrows -> Result { + if isSuccess { try closure() } + + return self + } + + /// Evaluates the specified closure when the `Result` is a failure. + /// + /// Use the `ifFailure` function to evaluate the passed closure without modifying the `Result` instance. + /// + /// - Parameter closure: A `Void` closure. + /// - Returns: This `Result` instance, unmodified. + @discardableResult + public func ifFailure(_ closure: () throws -> Void) rethrows -> Result { + if isFailure { try closure() } + + return self + } +} diff --git a/!main project/Pods/Alamofire/Source/ServerTrustPolicy.swift b/!main project/Pods/Alamofire/Source/ServerTrustPolicy.swift new file mode 100644 index 0000000..fccdc02 --- /dev/null +++ b/!main project/Pods/Alamofire/Source/ServerTrustPolicy.swift @@ -0,0 +1,310 @@ +// +// ServerTrustPolicy.swift +// +// Copyright (c) 2014 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +/// Responsible for managing the mapping of `ServerTrustPolicy` objects to a given host. +open class ServerTrustPolicyManager { + /// The dictionary of policies mapped to a particular host. + public let policies: [String: ServerTrustPolicy] + + /// Initializes the `ServerTrustPolicyManager` instance with the given policies. + /// + /// Since different servers and web services can have different leaf certificates, intermediate and even root + /// certficates, it is important to have the flexibility to specify evaluation policies on a per host basis. This + /// allows for scenarios such as using default evaluation for host1, certificate pinning for host2, public key + /// pinning for host3 and disabling evaluation for host4. + /// + /// - parameter policies: A dictionary of all policies mapped to a particular host. + /// + /// - returns: The new `ServerTrustPolicyManager` instance. + public init(policies: [String: ServerTrustPolicy]) { + self.policies = policies + } + + /// Returns the `ServerTrustPolicy` for the given host if applicable. + /// + /// By default, this method will return the policy that perfectly matches the given host. Subclasses could override + /// this method and implement more complex mapping implementations such as wildcards. + /// + /// - parameter host: The host to use when searching for a matching policy. + /// + /// - returns: The server trust policy for the given host if found. + open func serverTrustPolicy(forHost host: String) -> ServerTrustPolicy? { + return policies[host] + } +} + +// MARK: - + +extension URLSession { + private struct AssociatedKeys { + static var managerKey = "URLSession.ServerTrustPolicyManager" + } + + var serverTrustPolicyManager: ServerTrustPolicyManager? { + get { + return objc_getAssociatedObject(self, &AssociatedKeys.managerKey) as? ServerTrustPolicyManager + } + set (manager) { + objc_setAssociatedObject(self, &AssociatedKeys.managerKey, manager, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } +} + +// MARK: - ServerTrustPolicy + +/// The `ServerTrustPolicy` evaluates the server trust generally provided by an `NSURLAuthenticationChallenge` when +/// connecting to a server over a secure HTTPS connection. The policy configuration then evaluates the server trust +/// with a given set of criteria to determine whether the server trust is valid and the connection should be made. +/// +/// Using pinned certificates or public keys for evaluation helps prevent man-in-the-middle (MITM) attacks and other +/// vulnerabilities. Applications dealing with sensitive customer data or financial information are strongly encouraged +/// to route all communication over an HTTPS connection with pinning enabled. +/// +/// - performDefaultEvaluation: Uses the default server trust evaluation while allowing you to control whether to +/// validate the host provided by the challenge. Applications are encouraged to always +/// validate the host in production environments to guarantee the validity of the server's +/// certificate chain. +/// +/// - performRevokedEvaluation: Uses the default and revoked server trust evaluations allowing you to control whether to +/// validate the host provided by the challenge as well as specify the revocation flags for +/// testing for revoked certificates. Apple platforms did not start testing for revoked +/// certificates automatically until iOS 10.1, macOS 10.12 and tvOS 10.1 which is +/// demonstrated in our TLS tests. Applications are encouraged to always validate the host +/// in production environments to guarantee the validity of the server's certificate chain. +/// +/// - pinCertificates: Uses the pinned certificates to validate the server trust. The server trust is +/// considered valid if one of the pinned certificates match one of the server certificates. +/// By validating both the certificate chain and host, certificate pinning provides a very +/// secure form of server trust validation mitigating most, if not all, MITM attacks. +/// Applications are encouraged to always validate the host and require a valid certificate +/// chain in production environments. +/// +/// - pinPublicKeys: Uses the pinned public keys to validate the server trust. The server trust is considered +/// valid if one of the pinned public keys match one of the server certificate public keys. +/// By validating both the certificate chain and host, public key pinning provides a very +/// secure form of server trust validation mitigating most, if not all, MITM attacks. +/// Applications are encouraged to always validate the host and require a valid certificate +/// chain in production environments. +/// +/// - disableEvaluation: Disables all evaluation which in turn will always consider any server trust as valid. +/// +/// - customEvaluation: Uses the associated closure to evaluate the validity of the server trust. +public enum ServerTrustPolicy { + case performDefaultEvaluation(validateHost: Bool) + case performRevokedEvaluation(validateHost: Bool, revocationFlags: CFOptionFlags) + case pinCertificates(certificates: [SecCertificate], validateCertificateChain: Bool, validateHost: Bool) + case pinPublicKeys(publicKeys: [SecKey], validateCertificateChain: Bool, validateHost: Bool) + case disableEvaluation + case customEvaluation((_ serverTrust: SecTrust, _ host: String) -> Bool) + + // MARK: - Bundle Location + + /// Returns all certificates within the given bundle with a `.cer` file extension. + /// + /// - parameter bundle: The bundle to search for all `.cer` files. + /// + /// - returns: All certificates within the given bundle. + public static func certificates(in bundle: Bundle = Bundle.main) -> [SecCertificate] { + var certificates: [SecCertificate] = [] + + let paths = Set([".cer", ".CER", ".crt", ".CRT", ".der", ".DER"].map { fileExtension in + bundle.paths(forResourcesOfType: fileExtension, inDirectory: nil) + }.joined()) + + for path in paths { + if + let certificateData = try? Data(contentsOf: URL(fileURLWithPath: path)) as CFData, + let certificate = SecCertificateCreateWithData(nil, certificateData) + { + certificates.append(certificate) + } + } + + return certificates + } + + /// Returns all public keys within the given bundle with a `.cer` file extension. + /// + /// - parameter bundle: The bundle to search for all `*.cer` files. + /// + /// - returns: All public keys within the given bundle. + public static func publicKeys(in bundle: Bundle = Bundle.main) -> [SecKey] { + var publicKeys: [SecKey] = [] + + for certificate in certificates(in: bundle) { + if let publicKey = publicKey(for: certificate) { + publicKeys.append(publicKey) + } + } + + return publicKeys + } + + // MARK: - Evaluation + + /// Evaluates whether the server trust is valid for the given host. + /// + /// - parameter serverTrust: The server trust to evaluate. + /// - parameter host: The host of the challenge protection space. + /// + /// - returns: Whether the server trust is valid. + public func evaluate(_ serverTrust: SecTrust, forHost host: String) -> Bool { + var serverTrustIsValid = false + + switch self { + case let .performDefaultEvaluation(validateHost): + let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil) + SecTrustSetPolicies(serverTrust, policy) + + serverTrustIsValid = trustIsValid(serverTrust) + case let .performRevokedEvaluation(validateHost, revocationFlags): + let defaultPolicy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil) + let revokedPolicy = SecPolicyCreateRevocation(revocationFlags) + SecTrustSetPolicies(serverTrust, [defaultPolicy, revokedPolicy] as CFTypeRef) + + serverTrustIsValid = trustIsValid(serverTrust) + case let .pinCertificates(pinnedCertificates, validateCertificateChain, validateHost): + if validateCertificateChain { + let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil) + SecTrustSetPolicies(serverTrust, policy) + + SecTrustSetAnchorCertificates(serverTrust, pinnedCertificates as CFArray) + SecTrustSetAnchorCertificatesOnly(serverTrust, true) + + serverTrustIsValid = trustIsValid(serverTrust) + } else { + let serverCertificatesDataArray = certificateData(for: serverTrust) + let pinnedCertificatesDataArray = certificateData(for: pinnedCertificates) + + outerLoop: for serverCertificateData in serverCertificatesDataArray { + for pinnedCertificateData in pinnedCertificatesDataArray { + if serverCertificateData == pinnedCertificateData { + serverTrustIsValid = true + break outerLoop + } + } + } + } + case let .pinPublicKeys(pinnedPublicKeys, validateCertificateChain, validateHost): + var certificateChainEvaluationPassed = true + + if validateCertificateChain { + let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil) + SecTrustSetPolicies(serverTrust, policy) + + certificateChainEvaluationPassed = trustIsValid(serverTrust) + } + + if certificateChainEvaluationPassed { + outerLoop: for serverPublicKey in ServerTrustPolicy.publicKeys(for: serverTrust) as [AnyObject] { + for pinnedPublicKey in pinnedPublicKeys as [AnyObject] { + if serverPublicKey.isEqual(pinnedPublicKey) { + serverTrustIsValid = true + break outerLoop + } + } + } + } + case .disableEvaluation: + serverTrustIsValid = true + case let .customEvaluation(closure): + serverTrustIsValid = closure(serverTrust, host) + } + + return serverTrustIsValid + } + + // MARK: - Private - Trust Validation + + private func trustIsValid(_ trust: SecTrust) -> Bool { + var isValid = false + + if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) { + isValid = SecTrustEvaluateWithError(trust, nil) + } else { + var result = SecTrustResultType.invalid + let status = SecTrustEvaluate(trust, &result) + + if status == errSecSuccess { + let unspecified = SecTrustResultType.unspecified + let proceed = SecTrustResultType.proceed + + isValid = result == unspecified || result == proceed + } + } + + return isValid + } + + // MARK: - Private - Certificate Data + + private func certificateData(for trust: SecTrust) -> [Data] { + var certificates: [SecCertificate] = [] + + for index in 0.. [Data] { + return certificates.map { SecCertificateCopyData($0) as Data } + } + + // MARK: - Private - Public Key Extraction + + private static func publicKeys(for trust: SecTrust) -> [SecKey] { + var publicKeys: [SecKey] = [] + + for index in 0.. SecKey? { + var publicKey: SecKey? + + let policy = SecPolicyCreateBasicX509() + var trust: SecTrust? + let trustCreationStatus = SecTrustCreateWithCertificates(certificate, policy, &trust) + + if let trust = trust, trustCreationStatus == errSecSuccess { + publicKey = SecTrustCopyPublicKey(trust) + } + + return publicKey + } +} diff --git a/!main project/Pods/Alamofire/Source/SessionDelegate.swift b/!main project/Pods/Alamofire/Source/SessionDelegate.swift new file mode 100644 index 0000000..4964f1e --- /dev/null +++ b/!main project/Pods/Alamofire/Source/SessionDelegate.swift @@ -0,0 +1,725 @@ +// +// SessionDelegate.swift +// +// Copyright (c) 2014 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +/// Responsible for handling all delegate callbacks for the underlying session. +open class SessionDelegate: NSObject { + + // MARK: URLSessionDelegate Overrides + + /// Overrides default behavior for URLSessionDelegate method `urlSession(_:didBecomeInvalidWithError:)`. + open var sessionDidBecomeInvalidWithError: ((URLSession, Error?) -> Void)? + + /// Overrides default behavior for URLSessionDelegate method `urlSession(_:didReceive:completionHandler:)`. + open var sessionDidReceiveChallenge: ((URLSession, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))? + + /// Overrides all behavior for URLSessionDelegate method `urlSession(_:didReceive:completionHandler:)` and requires the caller to call the `completionHandler`. + open var sessionDidReceiveChallengeWithCompletion: ((URLSession, URLAuthenticationChallenge, @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void)? + + /// Overrides default behavior for URLSessionDelegate method `urlSessionDidFinishEvents(forBackgroundURLSession:)`. + open var sessionDidFinishEventsForBackgroundURLSession: ((URLSession) -> Void)? + + // MARK: URLSessionTaskDelegate Overrides + + /// Overrides default behavior for URLSessionTaskDelegate method `urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:)`. + open var taskWillPerformHTTPRedirection: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest) -> URLRequest?)? + + /// Overrides all behavior for URLSessionTaskDelegate method `urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:)` and + /// requires the caller to call the `completionHandler`. + open var taskWillPerformHTTPRedirectionWithCompletion: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest, @escaping (URLRequest?) -> Void) -> Void)? + + /// Overrides default behavior for URLSessionTaskDelegate method `urlSession(_:task:didReceive:completionHandler:)`. + open var taskDidReceiveChallenge: ((URLSession, URLSessionTask, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))? + + /// Overrides all behavior for URLSessionTaskDelegate method `urlSession(_:task:didReceive:completionHandler:)` and + /// requires the caller to call the `completionHandler`. + open var taskDidReceiveChallengeWithCompletion: ((URLSession, URLSessionTask, URLAuthenticationChallenge, @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void)? + + /// Overrides default behavior for URLSessionTaskDelegate method `urlSession(_:task:needNewBodyStream:)`. + open var taskNeedNewBodyStream: ((URLSession, URLSessionTask) -> InputStream?)? + + /// Overrides all behavior for URLSessionTaskDelegate method `urlSession(_:task:needNewBodyStream:)` and + /// requires the caller to call the `completionHandler`. + open var taskNeedNewBodyStreamWithCompletion: ((URLSession, URLSessionTask, @escaping (InputStream?) -> Void) -> Void)? + + /// Overrides default behavior for URLSessionTaskDelegate method `urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)`. + open var taskDidSendBodyData: ((URLSession, URLSessionTask, Int64, Int64, Int64) -> Void)? + + /// Overrides default behavior for URLSessionTaskDelegate method `urlSession(_:task:didCompleteWithError:)`. + open var taskDidComplete: ((URLSession, URLSessionTask, Error?) -> Void)? + + // MARK: URLSessionDataDelegate Overrides + + /// Overrides default behavior for URLSessionDataDelegate method `urlSession(_:dataTask:didReceive:completionHandler:)`. + open var dataTaskDidReceiveResponse: ((URLSession, URLSessionDataTask, URLResponse) -> URLSession.ResponseDisposition)? + + /// Overrides all behavior for URLSessionDataDelegate method `urlSession(_:dataTask:didReceive:completionHandler:)` and + /// requires caller to call the `completionHandler`. + open var dataTaskDidReceiveResponseWithCompletion: ((URLSession, URLSessionDataTask, URLResponse, @escaping (URLSession.ResponseDisposition) -> Void) -> Void)? + + /// Overrides default behavior for URLSessionDataDelegate method `urlSession(_:dataTask:didBecome:)`. + open var dataTaskDidBecomeDownloadTask: ((URLSession, URLSessionDataTask, URLSessionDownloadTask) -> Void)? + + /// Overrides default behavior for URLSessionDataDelegate method `urlSession(_:dataTask:didReceive:)`. + open var dataTaskDidReceiveData: ((URLSession, URLSessionDataTask, Data) -> Void)? + + /// Overrides default behavior for URLSessionDataDelegate method `urlSession(_:dataTask:willCacheResponse:completionHandler:)`. + open var dataTaskWillCacheResponse: ((URLSession, URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)? + + /// Overrides all behavior for URLSessionDataDelegate method `urlSession(_:dataTask:willCacheResponse:completionHandler:)` and + /// requires caller to call the `completionHandler`. + open var dataTaskWillCacheResponseWithCompletion: ((URLSession, URLSessionDataTask, CachedURLResponse, @escaping (CachedURLResponse?) -> Void) -> Void)? + + // MARK: URLSessionDownloadDelegate Overrides + + /// Overrides default behavior for URLSessionDownloadDelegate method `urlSession(_:downloadTask:didFinishDownloadingTo:)`. + open var downloadTaskDidFinishDownloadingToURL: ((URLSession, URLSessionDownloadTask, URL) -> Void)? + + /// Overrides default behavior for URLSessionDownloadDelegate method `urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)`. + open var downloadTaskDidWriteData: ((URLSession, URLSessionDownloadTask, Int64, Int64, Int64) -> Void)? + + /// Overrides default behavior for URLSessionDownloadDelegate method `urlSession(_:downloadTask:didResumeAtOffset:expectedTotalBytes:)`. + open var downloadTaskDidResumeAtOffset: ((URLSession, URLSessionDownloadTask, Int64, Int64) -> Void)? + + // MARK: URLSessionStreamDelegate Overrides + +#if !os(watchOS) + + /// Overrides default behavior for URLSessionStreamDelegate method `urlSession(_:readClosedFor:)`. + @available(iOS 9.0, macOS 10.11, tvOS 9.0, *) + open var streamTaskReadClosed: ((URLSession, URLSessionStreamTask) -> Void)? { + get { + return _streamTaskReadClosed as? (URLSession, URLSessionStreamTask) -> Void + } + set { + _streamTaskReadClosed = newValue + } + } + + /// Overrides default behavior for URLSessionStreamDelegate method `urlSession(_:writeClosedFor:)`. + @available(iOS 9.0, macOS 10.11, tvOS 9.0, *) + open var streamTaskWriteClosed: ((URLSession, URLSessionStreamTask) -> Void)? { + get { + return _streamTaskWriteClosed as? (URLSession, URLSessionStreamTask) -> Void + } + set { + _streamTaskWriteClosed = newValue + } + } + + /// Overrides default behavior for URLSessionStreamDelegate method `urlSession(_:betterRouteDiscoveredFor:)`. + @available(iOS 9.0, macOS 10.11, tvOS 9.0, *) + open var streamTaskBetterRouteDiscovered: ((URLSession, URLSessionStreamTask) -> Void)? { + get { + return _streamTaskBetterRouteDiscovered as? (URLSession, URLSessionStreamTask) -> Void + } + set { + _streamTaskBetterRouteDiscovered = newValue + } + } + + /// Overrides default behavior for URLSessionStreamDelegate method `urlSession(_:streamTask:didBecome:outputStream:)`. + @available(iOS 9.0, macOS 10.11, tvOS 9.0, *) + open var streamTaskDidBecomeInputAndOutputStreams: ((URLSession, URLSessionStreamTask, InputStream, OutputStream) -> Void)? { + get { + return _streamTaskDidBecomeInputStream as? (URLSession, URLSessionStreamTask, InputStream, OutputStream) -> Void + } + set { + _streamTaskDidBecomeInputStream = newValue + } + } + + var _streamTaskReadClosed: Any? + var _streamTaskWriteClosed: Any? + var _streamTaskBetterRouteDiscovered: Any? + var _streamTaskDidBecomeInputStream: Any? + +#endif + + // MARK: Properties + + var retrier: RequestRetrier? + weak var sessionManager: SessionManager? + + var requests: [Int: Request] = [:] + private let lock = NSLock() + + /// Access the task delegate for the specified task in a thread-safe manner. + open subscript(task: URLSessionTask) -> Request? { + get { + lock.lock() ; defer { lock.unlock() } + return requests[task.taskIdentifier] + } + set { + lock.lock() ; defer { lock.unlock() } + requests[task.taskIdentifier] = newValue + } + } + + // MARK: Lifecycle + + /// Initializes the `SessionDelegate` instance. + /// + /// - returns: The new `SessionDelegate` instance. + public override init() { + super.init() + } + + // MARK: NSObject Overrides + + /// Returns a `Bool` indicating whether the `SessionDelegate` implements or inherits a method that can respond + /// to a specified message. + /// + /// - parameter selector: A selector that identifies a message. + /// + /// - returns: `true` if the receiver implements or inherits a method that can respond to selector, otherwise `false`. + open override func responds(to selector: Selector) -> Bool { + #if !os(macOS) + if selector == #selector(URLSessionDelegate.urlSessionDidFinishEvents(forBackgroundURLSession:)) { + return sessionDidFinishEventsForBackgroundURLSession != nil + } + #endif + + #if !os(watchOS) + if #available(iOS 9.0, macOS 10.11, tvOS 9.0, *) { + switch selector { + case #selector(URLSessionStreamDelegate.urlSession(_:readClosedFor:)): + return streamTaskReadClosed != nil + case #selector(URLSessionStreamDelegate.urlSession(_:writeClosedFor:)): + return streamTaskWriteClosed != nil + case #selector(URLSessionStreamDelegate.urlSession(_:betterRouteDiscoveredFor:)): + return streamTaskBetterRouteDiscovered != nil + case #selector(URLSessionStreamDelegate.urlSession(_:streamTask:didBecome:outputStream:)): + return streamTaskDidBecomeInputAndOutputStreams != nil + default: + break + } + } + #endif + + switch selector { + case #selector(URLSessionDelegate.urlSession(_:didBecomeInvalidWithError:)): + return sessionDidBecomeInvalidWithError != nil + case #selector(URLSessionDelegate.urlSession(_:didReceive:completionHandler:)): + return (sessionDidReceiveChallenge != nil || sessionDidReceiveChallengeWithCompletion != nil) + case #selector(URLSessionTaskDelegate.urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:)): + return (taskWillPerformHTTPRedirection != nil || taskWillPerformHTTPRedirectionWithCompletion != nil) + case #selector(URLSessionDataDelegate.urlSession(_:dataTask:didReceive:completionHandler:)): + return (dataTaskDidReceiveResponse != nil || dataTaskDidReceiveResponseWithCompletion != nil) + default: + return type(of: self).instancesRespond(to: selector) + } + } +} + +// MARK: - URLSessionDelegate + +extension SessionDelegate: URLSessionDelegate { + /// Tells the delegate that the session has been invalidated. + /// + /// - parameter session: The session object that was invalidated. + /// - parameter error: The error that caused invalidation, or nil if the invalidation was explicit. + open func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) { + sessionDidBecomeInvalidWithError?(session, error) + } + + /// Requests credentials from the delegate in response to a session-level authentication request from the + /// remote server. + /// + /// - parameter session: The session containing the task that requested authentication. + /// - parameter challenge: An object that contains the request for authentication. + /// - parameter completionHandler: A handler that your delegate method must call providing the disposition + /// and credential. + open func urlSession( + _ session: URLSession, + didReceive challenge: URLAuthenticationChallenge, + completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) + { + guard sessionDidReceiveChallengeWithCompletion == nil else { + sessionDidReceiveChallengeWithCompletion?(session, challenge, completionHandler) + return + } + + var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling + var credential: URLCredential? + + if let sessionDidReceiveChallenge = sessionDidReceiveChallenge { + (disposition, credential) = sessionDidReceiveChallenge(session, challenge) + } else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust { + let host = challenge.protectionSpace.host + + if + let serverTrustPolicy = session.serverTrustPolicyManager?.serverTrustPolicy(forHost: host), + let serverTrust = challenge.protectionSpace.serverTrust + { + if serverTrustPolicy.evaluate(serverTrust, forHost: host) { + disposition = .useCredential + credential = URLCredential(trust: serverTrust) + } else { + disposition = .cancelAuthenticationChallenge + } + } + } + + completionHandler(disposition, credential) + } + +#if !os(macOS) + + /// Tells the delegate that all messages enqueued for a session have been delivered. + /// + /// - parameter session: The session that no longer has any outstanding requests. + open func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) { + sessionDidFinishEventsForBackgroundURLSession?(session) + } + +#endif +} + +// MARK: - URLSessionTaskDelegate + +extension SessionDelegate: URLSessionTaskDelegate { + /// Tells the delegate that the remote server requested an HTTP redirect. + /// + /// - parameter session: The session containing the task whose request resulted in a redirect. + /// - parameter task: The task whose request resulted in a redirect. + /// - parameter response: An object containing the server’s response to the original request. + /// - parameter request: A URL request object filled out with the new location. + /// - parameter completionHandler: A closure that your handler should call with either the value of the request + /// parameter, a modified URL request object, or NULL to refuse the redirect and + /// return the body of the redirect response. + open func urlSession( + _ session: URLSession, + task: URLSessionTask, + willPerformHTTPRedirection response: HTTPURLResponse, + newRequest request: URLRequest, + completionHandler: @escaping (URLRequest?) -> Void) + { + guard taskWillPerformHTTPRedirectionWithCompletion == nil else { + taskWillPerformHTTPRedirectionWithCompletion?(session, task, response, request, completionHandler) + return + } + + var redirectRequest: URLRequest? = request + + if let taskWillPerformHTTPRedirection = taskWillPerformHTTPRedirection { + redirectRequest = taskWillPerformHTTPRedirection(session, task, response, request) + } + + completionHandler(redirectRequest) + } + + /// Requests credentials from the delegate in response to an authentication request from the remote server. + /// + /// - parameter session: The session containing the task whose request requires authentication. + /// - parameter task: The task whose request requires authentication. + /// - parameter challenge: An object that contains the request for authentication. + /// - parameter completionHandler: A handler that your delegate method must call providing the disposition + /// and credential. + open func urlSession( + _ session: URLSession, + task: URLSessionTask, + didReceive challenge: URLAuthenticationChallenge, + completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) + { + guard taskDidReceiveChallengeWithCompletion == nil else { + taskDidReceiveChallengeWithCompletion?(session, task, challenge, completionHandler) + return + } + + if let taskDidReceiveChallenge = taskDidReceiveChallenge { + let result = taskDidReceiveChallenge(session, task, challenge) + completionHandler(result.0, result.1) + } else if let delegate = self[task]?.delegate { + delegate.urlSession( + session, + task: task, + didReceive: challenge, + completionHandler: completionHandler + ) + } else { + urlSession(session, didReceive: challenge, completionHandler: completionHandler) + } + } + + /// Tells the delegate when a task requires a new request body stream to send to the remote server. + /// + /// - parameter session: The session containing the task that needs a new body stream. + /// - parameter task: The task that needs a new body stream. + /// - parameter completionHandler: A completion handler that your delegate method should call with the new body stream. + open func urlSession( + _ session: URLSession, + task: URLSessionTask, + needNewBodyStream completionHandler: @escaping (InputStream?) -> Void) + { + guard taskNeedNewBodyStreamWithCompletion == nil else { + taskNeedNewBodyStreamWithCompletion?(session, task, completionHandler) + return + } + + if let taskNeedNewBodyStream = taskNeedNewBodyStream { + completionHandler(taskNeedNewBodyStream(session, task)) + } else if let delegate = self[task]?.delegate { + delegate.urlSession(session, task: task, needNewBodyStream: completionHandler) + } + } + + /// Periodically informs the delegate of the progress of sending body content to the server. + /// + /// - parameter session: The session containing the data task. + /// - parameter task: The data task. + /// - parameter bytesSent: The number of bytes sent since the last time this delegate method was called. + /// - parameter totalBytesSent: The total number of bytes sent so far. + /// - parameter totalBytesExpectedToSend: The expected length of the body data. + open func urlSession( + _ session: URLSession, + task: URLSessionTask, + didSendBodyData bytesSent: Int64, + totalBytesSent: Int64, + totalBytesExpectedToSend: Int64) + { + if let taskDidSendBodyData = taskDidSendBodyData { + taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend) + } else if let delegate = self[task]?.delegate as? UploadTaskDelegate { + delegate.URLSession( + session, + task: task, + didSendBodyData: bytesSent, + totalBytesSent: totalBytesSent, + totalBytesExpectedToSend: totalBytesExpectedToSend + ) + } + } + +#if !os(watchOS) + + /// Tells the delegate that the session finished collecting metrics for the task. + /// + /// - parameter session: The session collecting the metrics. + /// - parameter task: The task whose metrics have been collected. + /// - parameter metrics: The collected metrics. + @available(iOS 10.0, macOS 10.12, tvOS 10.0, *) + @objc(URLSession:task:didFinishCollectingMetrics:) + open func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) { + self[task]?.delegate.metrics = metrics + } + +#endif + + /// Tells the delegate that the task finished transferring data. + /// + /// - parameter session: The session containing the task whose request finished transferring data. + /// - parameter task: The task whose request finished transferring data. + /// - parameter error: If an error occurred, an error object indicating how the transfer failed, otherwise nil. + open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + /// Executed after it is determined that the request is not going to be retried + let completeTask: (URLSession, URLSessionTask, Error?) -> Void = { [weak self] session, task, error in + guard let strongSelf = self else { return } + + strongSelf.taskDidComplete?(session, task, error) + + strongSelf[task]?.delegate.urlSession(session, task: task, didCompleteWithError: error) + + var userInfo: [String: Any] = [Notification.Key.Task: task] + + if let data = (strongSelf[task]?.delegate as? DataTaskDelegate)?.data { + userInfo[Notification.Key.ResponseData] = data + } + + NotificationCenter.default.post( + name: Notification.Name.Task.DidComplete, + object: strongSelf, + userInfo: userInfo + ) + + strongSelf[task] = nil + } + + guard let request = self[task], let sessionManager = sessionManager else { + completeTask(session, task, error) + return + } + + // Run all validations on the request before checking if an error occurred + request.validations.forEach { $0() } + + // Determine whether an error has occurred + var error: Error? = error + + if request.delegate.error != nil { + error = request.delegate.error + } + + /// If an error occurred and the retrier is set, asynchronously ask the retrier if the request + /// should be retried. Otherwise, complete the task by notifying the task delegate. + if let retrier = retrier, let error = error { + retrier.should(sessionManager, retry: request, with: error) { [weak self] shouldRetry, timeDelay in + guard shouldRetry else { completeTask(session, task, error) ; return } + + DispatchQueue.utility.after(timeDelay) { [weak self] in + guard let strongSelf = self else { return } + + let retrySucceeded = strongSelf.sessionManager?.retry(request) ?? false + + if retrySucceeded, let task = request.task { + strongSelf[task] = request + return + } else { + completeTask(session, task, error) + } + } + } + } else { + completeTask(session, task, error) + } + } +} + +// MARK: - URLSessionDataDelegate + +extension SessionDelegate: URLSessionDataDelegate { + /// Tells the delegate that the data task received the initial reply (headers) from the server. + /// + /// - parameter session: The session containing the data task that received an initial reply. + /// - parameter dataTask: The data task that received an initial reply. + /// - parameter response: A URL response object populated with headers. + /// - parameter completionHandler: A completion handler that your code calls to continue the transfer, passing a + /// constant to indicate whether the transfer should continue as a data task or + /// should become a download task. + open func urlSession( + _ session: URLSession, + dataTask: URLSessionDataTask, + didReceive response: URLResponse, + completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) + { + guard dataTaskDidReceiveResponseWithCompletion == nil else { + dataTaskDidReceiveResponseWithCompletion?(session, dataTask, response, completionHandler) + return + } + + var disposition: URLSession.ResponseDisposition = .allow + + if let dataTaskDidReceiveResponse = dataTaskDidReceiveResponse { + disposition = dataTaskDidReceiveResponse(session, dataTask, response) + } + + completionHandler(disposition) + } + + /// Tells the delegate that the data task was changed to a download task. + /// + /// - parameter session: The session containing the task that was replaced by a download task. + /// - parameter dataTask: The data task that was replaced by a download task. + /// - parameter downloadTask: The new download task that replaced the data task. + open func urlSession( + _ session: URLSession, + dataTask: URLSessionDataTask, + didBecome downloadTask: URLSessionDownloadTask) + { + if let dataTaskDidBecomeDownloadTask = dataTaskDidBecomeDownloadTask { + dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask) + } else { + self[downloadTask]?.delegate = DownloadTaskDelegate(task: downloadTask) + } + } + + /// Tells the delegate that the data task has received some of the expected data. + /// + /// - parameter session: The session containing the data task that provided data. + /// - parameter dataTask: The data task that provided data. + /// - parameter data: A data object containing the transferred data. + open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + if let dataTaskDidReceiveData = dataTaskDidReceiveData { + dataTaskDidReceiveData(session, dataTask, data) + } else if let delegate = self[dataTask]?.delegate as? DataTaskDelegate { + delegate.urlSession(session, dataTask: dataTask, didReceive: data) + } + } + + /// Asks the delegate whether the data (or upload) task should store the response in the cache. + /// + /// - parameter session: The session containing the data (or upload) task. + /// - parameter dataTask: The data (or upload) task. + /// - parameter proposedResponse: The default caching behavior. This behavior is determined based on the current + /// caching policy and the values of certain received headers, such as the Pragma + /// and Cache-Control headers. + /// - parameter completionHandler: A block that your handler must call, providing either the original proposed + /// response, a modified version of that response, or NULL to prevent caching the + /// response. If your delegate implements this method, it must call this completion + /// handler; otherwise, your app leaks memory. + open func urlSession( + _ session: URLSession, + dataTask: URLSessionDataTask, + willCacheResponse proposedResponse: CachedURLResponse, + completionHandler: @escaping (CachedURLResponse?) -> Void) + { + guard dataTaskWillCacheResponseWithCompletion == nil else { + dataTaskWillCacheResponseWithCompletion?(session, dataTask, proposedResponse, completionHandler) + return + } + + if let dataTaskWillCacheResponse = dataTaskWillCacheResponse { + completionHandler(dataTaskWillCacheResponse(session, dataTask, proposedResponse)) + } else if let delegate = self[dataTask]?.delegate as? DataTaskDelegate { + delegate.urlSession( + session, + dataTask: dataTask, + willCacheResponse: proposedResponse, + completionHandler: completionHandler + ) + } else { + completionHandler(proposedResponse) + } + } +} + +// MARK: - URLSessionDownloadDelegate + +extension SessionDelegate: URLSessionDownloadDelegate { + /// Tells the delegate that a download task has finished downloading. + /// + /// - parameter session: The session containing the download task that finished. + /// - parameter downloadTask: The download task that finished. + /// - parameter location: A file URL for the temporary file. Because the file is temporary, you must either + /// open the file for reading or move it to a permanent location in your app’s sandbox + /// container directory before returning from this delegate method. + open func urlSession( + _ session: URLSession, + downloadTask: URLSessionDownloadTask, + didFinishDownloadingTo location: URL) + { + if let downloadTaskDidFinishDownloadingToURL = downloadTaskDidFinishDownloadingToURL { + downloadTaskDidFinishDownloadingToURL(session, downloadTask, location) + } else if let delegate = self[downloadTask]?.delegate as? DownloadTaskDelegate { + delegate.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location) + } + } + + /// Periodically informs the delegate about the download’s progress. + /// + /// - parameter session: The session containing the download task. + /// - parameter downloadTask: The download task. + /// - parameter bytesWritten: The number of bytes transferred since the last time this delegate + /// method was called. + /// - parameter totalBytesWritten: The total number of bytes transferred so far. + /// - parameter totalBytesExpectedToWrite: The expected length of the file, as provided by the Content-Length + /// header. If this header was not provided, the value is + /// `NSURLSessionTransferSizeUnknown`. + open func urlSession( + _ session: URLSession, + downloadTask: URLSessionDownloadTask, + didWriteData bytesWritten: Int64, + totalBytesWritten: Int64, + totalBytesExpectedToWrite: Int64) + { + if let downloadTaskDidWriteData = downloadTaskDidWriteData { + downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite) + } else if let delegate = self[downloadTask]?.delegate as? DownloadTaskDelegate { + delegate.urlSession( + session, + downloadTask: downloadTask, + didWriteData: bytesWritten, + totalBytesWritten: totalBytesWritten, + totalBytesExpectedToWrite: totalBytesExpectedToWrite + ) + } + } + + /// Tells the delegate that the download task has resumed downloading. + /// + /// - parameter session: The session containing the download task that finished. + /// - parameter downloadTask: The download task that resumed. See explanation in the discussion. + /// - parameter fileOffset: If the file's cache policy or last modified date prevents reuse of the + /// existing content, then this value is zero. Otherwise, this value is an + /// integer representing the number of bytes on disk that do not need to be + /// retrieved again. + /// - parameter expectedTotalBytes: The expected length of the file, as provided by the Content-Length header. + /// If this header was not provided, the value is NSURLSessionTransferSizeUnknown. + open func urlSession( + _ session: URLSession, + downloadTask: URLSessionDownloadTask, + didResumeAtOffset fileOffset: Int64, + expectedTotalBytes: Int64) + { + if let downloadTaskDidResumeAtOffset = downloadTaskDidResumeAtOffset { + downloadTaskDidResumeAtOffset(session, downloadTask, fileOffset, expectedTotalBytes) + } else if let delegate = self[downloadTask]?.delegate as? DownloadTaskDelegate { + delegate.urlSession( + session, + downloadTask: downloadTask, + didResumeAtOffset: fileOffset, + expectedTotalBytes: expectedTotalBytes + ) + } + } +} + +// MARK: - URLSessionStreamDelegate + +#if !os(watchOS) + +@available(iOS 9.0, macOS 10.11, tvOS 9.0, *) +extension SessionDelegate: URLSessionStreamDelegate { + /// Tells the delegate that the read side of the connection has been closed. + /// + /// - parameter session: The session. + /// - parameter streamTask: The stream task. + open func urlSession(_ session: URLSession, readClosedFor streamTask: URLSessionStreamTask) { + streamTaskReadClosed?(session, streamTask) + } + + /// Tells the delegate that the write side of the connection has been closed. + /// + /// - parameter session: The session. + /// - parameter streamTask: The stream task. + open func urlSession(_ session: URLSession, writeClosedFor streamTask: URLSessionStreamTask) { + streamTaskWriteClosed?(session, streamTask) + } + + /// Tells the delegate that the system has determined that a better route to the host is available. + /// + /// - parameter session: The session. + /// - parameter streamTask: The stream task. + open func urlSession(_ session: URLSession, betterRouteDiscoveredFor streamTask: URLSessionStreamTask) { + streamTaskBetterRouteDiscovered?(session, streamTask) + } + + /// Tells the delegate that the stream task has been completed and provides the unopened stream objects. + /// + /// - parameter session: The session. + /// - parameter streamTask: The stream task. + /// - parameter inputStream: The new input stream. + /// - parameter outputStream: The new output stream. + open func urlSession( + _ session: URLSession, + streamTask: URLSessionStreamTask, + didBecome inputStream: InputStream, + outputStream: OutputStream) + { + streamTaskDidBecomeInputAndOutputStreams?(session, streamTask, inputStream, outputStream) + } +} + +#endif diff --git a/!main project/Pods/Alamofire/Source/SessionManager.swift b/!main project/Pods/Alamofire/Source/SessionManager.swift new file mode 100644 index 0000000..02c36a7 --- /dev/null +++ b/!main project/Pods/Alamofire/Source/SessionManager.swift @@ -0,0 +1,899 @@ +// +// SessionManager.swift +// +// Copyright (c) 2014 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +/// Responsible for creating and managing `Request` objects, as well as their underlying `NSURLSession`. +open class SessionManager { + + // MARK: - Helper Types + + /// Defines whether the `MultipartFormData` encoding was successful and contains result of the encoding as + /// associated values. + /// + /// - Success: Represents a successful `MultipartFormData` encoding and contains the new `UploadRequest` along with + /// streaming information. + /// - Failure: Used to represent a failure in the `MultipartFormData` encoding and also contains the encoding + /// error. + public enum MultipartFormDataEncodingResult { + case success(request: UploadRequest, streamingFromDisk: Bool, streamFileURL: URL?) + case failure(Error) + } + + // MARK: - Properties + + /// A default instance of `SessionManager`, used by top-level Alamofire request methods, and suitable for use + /// directly for any ad hoc requests. + public static let `default`: SessionManager = { + let configuration = URLSessionConfiguration.default + configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders + + return SessionManager(configuration: configuration) + }() + + /// Creates default values for the "Accept-Encoding", "Accept-Language" and "User-Agent" headers. + public static let defaultHTTPHeaders: HTTPHeaders = { + // Accept-Encoding HTTP Header; see https://tools.ietf.org/html/rfc7230#section-4.2.3 + let acceptEncoding: String = "gzip;q=1.0, compress;q=0.5" + + // Accept-Language HTTP Header; see https://tools.ietf.org/html/rfc7231#section-5.3.5 + let acceptLanguage = Locale.preferredLanguages.prefix(6).enumerated().map { index, languageCode in + let quality = 1.0 - (Double(index) * 0.1) + return "\(languageCode);q=\(quality)" + }.joined(separator: ", ") + + // User-Agent Header; see https://tools.ietf.org/html/rfc7231#section-5.5.3 + // Example: `iOS Example/1.0 (org.alamofire.iOS-Example; build:1; iOS 10.0.0) Alamofire/4.0.0` + let userAgent: String = { + if let info = Bundle.main.infoDictionary { + let executable = info[kCFBundleExecutableKey as String] as? String ?? "Unknown" + let bundle = info[kCFBundleIdentifierKey as String] as? String ?? "Unknown" + let appVersion = info["CFBundleShortVersionString"] as? String ?? "Unknown" + let appBuild = info[kCFBundleVersionKey as String] as? String ?? "Unknown" + + let osNameVersion: String = { + let version = ProcessInfo.processInfo.operatingSystemVersion + let versionString = "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)" + + let osName: String = { + #if os(iOS) + return "iOS" + #elseif os(watchOS) + return "watchOS" + #elseif os(tvOS) + return "tvOS" + #elseif os(macOS) + return "OS X" + #elseif os(Linux) + return "Linux" + #else + return "Unknown" + #endif + }() + + return "\(osName) \(versionString)" + }() + + let alamofireVersion: String = { + guard + let afInfo = Bundle(for: SessionManager.self).infoDictionary, + let build = afInfo["CFBundleShortVersionString"] + else { return "Unknown" } + + return "Alamofire/\(build)" + }() + + return "\(executable)/\(appVersion) (\(bundle); build:\(appBuild); \(osNameVersion)) \(alamofireVersion)" + } + + return "Alamofire" + }() + + return [ + "Accept-Encoding": acceptEncoding, + "Accept-Language": acceptLanguage, + "User-Agent": userAgent + ] + }() + + /// Default memory threshold used when encoding `MultipartFormData` in bytes. + public static let multipartFormDataEncodingMemoryThreshold: UInt64 = 10_000_000 + + /// The underlying session. + public let session: URLSession + + /// The session delegate handling all the task and session delegate callbacks. + public let delegate: SessionDelegate + + /// Whether to start requests immediately after being constructed. `true` by default. + open var startRequestsImmediately: Bool = true + + /// The request adapter called each time a new request is created. + open var adapter: RequestAdapter? + + /// The request retrier called each time a request encounters an error to determine whether to retry the request. + open var retrier: RequestRetrier? { + get { return delegate.retrier } + set { delegate.retrier = newValue } + } + + /// The background completion handler closure provided by the UIApplicationDelegate + /// `application:handleEventsForBackgroundURLSession:completionHandler:` method. By setting the background + /// completion handler, the SessionDelegate `sessionDidFinishEventsForBackgroundURLSession` closure implementation + /// will automatically call the handler. + /// + /// If you need to handle your own events before the handler is called, then you need to override the + /// SessionDelegate `sessionDidFinishEventsForBackgroundURLSession` and manually call the handler when finished. + /// + /// `nil` by default. + open var backgroundCompletionHandler: (() -> Void)? + + let queue = DispatchQueue(label: "org.alamofire.session-manager." + UUID().uuidString) + + // MARK: - Lifecycle + + /// Creates an instance with the specified `configuration`, `delegate` and `serverTrustPolicyManager`. + /// + /// - parameter configuration: The configuration used to construct the managed session. + /// `URLSessionConfiguration.default` by default. + /// - parameter delegate: The delegate used when initializing the session. `SessionDelegate()` by + /// default. + /// - parameter serverTrustPolicyManager: The server trust policy manager to use for evaluating all server trust + /// challenges. `nil` by default. + /// + /// - returns: The new `SessionManager` instance. + public init( + configuration: URLSessionConfiguration = URLSessionConfiguration.default, + delegate: SessionDelegate = SessionDelegate(), + serverTrustPolicyManager: ServerTrustPolicyManager? = nil) + { + self.delegate = delegate + self.session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: nil) + + commonInit(serverTrustPolicyManager: serverTrustPolicyManager) + } + + /// Creates an instance with the specified `session`, `delegate` and `serverTrustPolicyManager`. + /// + /// - parameter session: The URL session. + /// - parameter delegate: The delegate of the URL session. Must equal the URL session's delegate. + /// - parameter serverTrustPolicyManager: The server trust policy manager to use for evaluating all server trust + /// challenges. `nil` by default. + /// + /// - returns: The new `SessionManager` instance if the URL session's delegate matches; `nil` otherwise. + public init?( + session: URLSession, + delegate: SessionDelegate, + serverTrustPolicyManager: ServerTrustPolicyManager? = nil) + { + guard delegate === session.delegate else { return nil } + + self.delegate = delegate + self.session = session + + commonInit(serverTrustPolicyManager: serverTrustPolicyManager) + } + + private func commonInit(serverTrustPolicyManager: ServerTrustPolicyManager?) { + session.serverTrustPolicyManager = serverTrustPolicyManager + + delegate.sessionManager = self + + delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] session in + guard let strongSelf = self else { return } + DispatchQueue.main.async { strongSelf.backgroundCompletionHandler?() } + } + } + + deinit { + session.invalidateAndCancel() + } + + // MARK: - Data Request + + /// Creates a `DataRequest` to retrieve the contents of the specified `url`, `method`, `parameters`, `encoding` + /// and `headers`. + /// + /// - parameter url: The URL. + /// - parameter method: The HTTP method. `.get` by default. + /// - parameter parameters: The parameters. `nil` by default. + /// - parameter encoding: The parameter encoding. `URLEncoding.default` by default. + /// - parameter headers: The HTTP headers. `nil` by default. + /// + /// - returns: The created `DataRequest`. + @discardableResult + open func request( + _ url: URLConvertible, + method: HTTPMethod = .get, + parameters: Parameters? = nil, + encoding: ParameterEncoding = URLEncoding.default, + headers: HTTPHeaders? = nil) + -> DataRequest + { + var originalRequest: URLRequest? + + do { + originalRequest = try URLRequest(url: url, method: method, headers: headers) + let encodedURLRequest = try encoding.encode(originalRequest!, with: parameters) + return request(encodedURLRequest) + } catch { + return request(originalRequest, failedWith: error) + } + } + + /// Creates a `DataRequest` to retrieve the contents of a URL based on the specified `urlRequest`. + /// + /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. + /// + /// - parameter urlRequest: The URL request. + /// + /// - returns: The created `DataRequest`. + @discardableResult + open func request(_ urlRequest: URLRequestConvertible) -> DataRequest { + var originalRequest: URLRequest? + + do { + originalRequest = try urlRequest.asURLRequest() + let originalTask = DataRequest.Requestable(urlRequest: originalRequest!) + + let task = try originalTask.task(session: session, adapter: adapter, queue: queue) + let request = DataRequest(session: session, requestTask: .data(originalTask, task)) + + delegate[task] = request + + if startRequestsImmediately { request.resume() } + + return request + } catch { + return request(originalRequest, failedWith: error) + } + } + + // MARK: Private - Request Implementation + + private func request(_ urlRequest: URLRequest?, failedWith error: Error) -> DataRequest { + var requestTask: Request.RequestTask = .data(nil, nil) + + if let urlRequest = urlRequest { + let originalTask = DataRequest.Requestable(urlRequest: urlRequest) + requestTask = .data(originalTask, nil) + } + + let underlyingError = error.underlyingAdaptError ?? error + let request = DataRequest(session: session, requestTask: requestTask, error: underlyingError) + + if let retrier = retrier, error is AdaptError { + allowRetrier(retrier, toRetry: request, with: underlyingError) + } else { + if startRequestsImmediately { request.resume() } + } + + return request + } + + // MARK: - Download Request + + // MARK: URL Request + + /// Creates a `DownloadRequest` to retrieve the contents the specified `url`, `method`, `parameters`, `encoding`, + /// `headers` and save them to the `destination`. + /// + /// If `destination` is not specified, the contents will remain in the temporary location determined by the + /// underlying URL session. + /// + /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. + /// + /// - parameter url: The URL. + /// - parameter method: The HTTP method. `.get` by default. + /// - parameter parameters: The parameters. `nil` by default. + /// - parameter encoding: The parameter encoding. `URLEncoding.default` by default. + /// - parameter headers: The HTTP headers. `nil` by default. + /// - parameter destination: The closure used to determine the destination of the downloaded file. `nil` by default. + /// + /// - returns: The created `DownloadRequest`. + @discardableResult + open func download( + _ url: URLConvertible, + method: HTTPMethod = .get, + parameters: Parameters? = nil, + encoding: ParameterEncoding = URLEncoding.default, + headers: HTTPHeaders? = nil, + to destination: DownloadRequest.DownloadFileDestination? = nil) + -> DownloadRequest + { + do { + let urlRequest = try URLRequest(url: url, method: method, headers: headers) + let encodedURLRequest = try encoding.encode(urlRequest, with: parameters) + return download(encodedURLRequest, to: destination) + } catch { + return download(nil, to: destination, failedWith: error) + } + } + + /// Creates a `DownloadRequest` to retrieve the contents of a URL based on the specified `urlRequest` and save + /// them to the `destination`. + /// + /// If `destination` is not specified, the contents will remain in the temporary location determined by the + /// underlying URL session. + /// + /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. + /// + /// - parameter urlRequest: The URL request + /// - parameter destination: The closure used to determine the destination of the downloaded file. `nil` by default. + /// + /// - returns: The created `DownloadRequest`. + @discardableResult + open func download( + _ urlRequest: URLRequestConvertible, + to destination: DownloadRequest.DownloadFileDestination? = nil) + -> DownloadRequest + { + do { + let urlRequest = try urlRequest.asURLRequest() + return download(.request(urlRequest), to: destination) + } catch { + return download(nil, to: destination, failedWith: error) + } + } + + // MARK: Resume Data + + /// Creates a `DownloadRequest` from the `resumeData` produced from a previous request cancellation to retrieve + /// the contents of the original request and save them to the `destination`. + /// + /// If `destination` is not specified, the contents will remain in the temporary location determined by the + /// underlying URL session. + /// + /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. + /// + /// On the latest release of all the Apple platforms (iOS 10, macOS 10.12, tvOS 10, watchOS 3), `resumeData` is broken + /// on background URL session configurations. There's an underlying bug in the `resumeData` generation logic where the + /// data is written incorrectly and will always fail to resume the download. For more information about the bug and + /// possible workarounds, please refer to the following Stack Overflow post: + /// + /// - http://stackoverflow.com/a/39347461/1342462 + /// + /// - parameter resumeData: The resume data. This is an opaque data blob produced by `URLSessionDownloadTask` + /// when a task is cancelled. See `URLSession -downloadTask(withResumeData:)` for + /// additional information. + /// - parameter destination: The closure used to determine the destination of the downloaded file. `nil` by default. + /// + /// - returns: The created `DownloadRequest`. + @discardableResult + open func download( + resumingWith resumeData: Data, + to destination: DownloadRequest.DownloadFileDestination? = nil) + -> DownloadRequest + { + return download(.resumeData(resumeData), to: destination) + } + + // MARK: Private - Download Implementation + + private func download( + _ downloadable: DownloadRequest.Downloadable, + to destination: DownloadRequest.DownloadFileDestination?) + -> DownloadRequest + { + do { + let task = try downloadable.task(session: session, adapter: adapter, queue: queue) + let download = DownloadRequest(session: session, requestTask: .download(downloadable, task)) + + download.downloadDelegate.destination = destination + + delegate[task] = download + + if startRequestsImmediately { download.resume() } + + return download + } catch { + return download(downloadable, to: destination, failedWith: error) + } + } + + private func download( + _ downloadable: DownloadRequest.Downloadable?, + to destination: DownloadRequest.DownloadFileDestination?, + failedWith error: Error) + -> DownloadRequest + { + var downloadTask: Request.RequestTask = .download(nil, nil) + + if let downloadable = downloadable { + downloadTask = .download(downloadable, nil) + } + + let underlyingError = error.underlyingAdaptError ?? error + + let download = DownloadRequest(session: session, requestTask: downloadTask, error: underlyingError) + download.downloadDelegate.destination = destination + + if let retrier = retrier, error is AdaptError { + allowRetrier(retrier, toRetry: download, with: underlyingError) + } else { + if startRequestsImmediately { download.resume() } + } + + return download + } + + // MARK: - Upload Request + + // MARK: File + + /// Creates an `UploadRequest` from the specified `url`, `method` and `headers` for uploading the `file`. + /// + /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. + /// + /// - parameter file: The file to upload. + /// - parameter url: The URL. + /// - parameter method: The HTTP method. `.post` by default. + /// - parameter headers: The HTTP headers. `nil` by default. + /// + /// - returns: The created `UploadRequest`. + @discardableResult + open func upload( + _ fileURL: URL, + to url: URLConvertible, + method: HTTPMethod = .post, + headers: HTTPHeaders? = nil) + -> UploadRequest + { + do { + let urlRequest = try URLRequest(url: url, method: method, headers: headers) + return upload(fileURL, with: urlRequest) + } catch { + return upload(nil, failedWith: error) + } + } + + /// Creates a `UploadRequest` from the specified `urlRequest` for uploading the `file`. + /// + /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. + /// + /// - parameter file: The file to upload. + /// - parameter urlRequest: The URL request. + /// + /// - returns: The created `UploadRequest`. + @discardableResult + open func upload(_ fileURL: URL, with urlRequest: URLRequestConvertible) -> UploadRequest { + do { + let urlRequest = try urlRequest.asURLRequest() + return upload(.file(fileURL, urlRequest)) + } catch { + return upload(nil, failedWith: error) + } + } + + // MARK: Data + + /// Creates an `UploadRequest` from the specified `url`, `method` and `headers` for uploading the `data`. + /// + /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. + /// + /// - parameter data: The data to upload. + /// - parameter url: The URL. + /// - parameter method: The HTTP method. `.post` by default. + /// - parameter headers: The HTTP headers. `nil` by default. + /// + /// - returns: The created `UploadRequest`. + @discardableResult + open func upload( + _ data: Data, + to url: URLConvertible, + method: HTTPMethod = .post, + headers: HTTPHeaders? = nil) + -> UploadRequest + { + do { + let urlRequest = try URLRequest(url: url, method: method, headers: headers) + return upload(data, with: urlRequest) + } catch { + return upload(nil, failedWith: error) + } + } + + /// Creates an `UploadRequest` from the specified `urlRequest` for uploading the `data`. + /// + /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. + /// + /// - parameter data: The data to upload. + /// - parameter urlRequest: The URL request. + /// + /// - returns: The created `UploadRequest`. + @discardableResult + open func upload(_ data: Data, with urlRequest: URLRequestConvertible) -> UploadRequest { + do { + let urlRequest = try urlRequest.asURLRequest() + return upload(.data(data, urlRequest)) + } catch { + return upload(nil, failedWith: error) + } + } + + // MARK: InputStream + + /// Creates an `UploadRequest` from the specified `url`, `method` and `headers` for uploading the `stream`. + /// + /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. + /// + /// - parameter stream: The stream to upload. + /// - parameter url: The URL. + /// - parameter method: The HTTP method. `.post` by default. + /// - parameter headers: The HTTP headers. `nil` by default. + /// + /// - returns: The created `UploadRequest`. + @discardableResult + open func upload( + _ stream: InputStream, + to url: URLConvertible, + method: HTTPMethod = .post, + headers: HTTPHeaders? = nil) + -> UploadRequest + { + do { + let urlRequest = try URLRequest(url: url, method: method, headers: headers) + return upload(stream, with: urlRequest) + } catch { + return upload(nil, failedWith: error) + } + } + + /// Creates an `UploadRequest` from the specified `urlRequest` for uploading the `stream`. + /// + /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. + /// + /// - parameter stream: The stream to upload. + /// - parameter urlRequest: The URL request. + /// + /// - returns: The created `UploadRequest`. + @discardableResult + open func upload(_ stream: InputStream, with urlRequest: URLRequestConvertible) -> UploadRequest { + do { + let urlRequest = try urlRequest.asURLRequest() + return upload(.stream(stream, urlRequest)) + } catch { + return upload(nil, failedWith: error) + } + } + + // MARK: MultipartFormData + + /// Encodes `multipartFormData` using `encodingMemoryThreshold` and calls `encodingCompletion` with new + /// `UploadRequest` using the `url`, `method` and `headers`. + /// + /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative + /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most + /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to + /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory + /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be + /// used for larger payloads such as video content. + /// + /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory + /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`, + /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk + /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding + /// technique was used. + /// + /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. + /// + /// - parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`. + /// - parameter encodingMemoryThreshold: The encoding memory threshold in bytes. + /// `multipartFormDataEncodingMemoryThreshold` by default. + /// - parameter url: The URL. + /// - parameter method: The HTTP method. `.post` by default. + /// - parameter headers: The HTTP headers. `nil` by default. + /// - parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete. + open func upload( + multipartFormData: @escaping (MultipartFormData) -> Void, + usingThreshold encodingMemoryThreshold: UInt64 = SessionManager.multipartFormDataEncodingMemoryThreshold, + to url: URLConvertible, + method: HTTPMethod = .post, + headers: HTTPHeaders? = nil, + queue: DispatchQueue? = nil, + encodingCompletion: ((MultipartFormDataEncodingResult) -> Void)?) + { + do { + let urlRequest = try URLRequest(url: url, method: method, headers: headers) + + return upload( + multipartFormData: multipartFormData, + usingThreshold: encodingMemoryThreshold, + with: urlRequest, + queue: queue, + encodingCompletion: encodingCompletion + ) + } catch { + (queue ?? DispatchQueue.main).async { encodingCompletion?(.failure(error)) } + } + } + + /// Encodes `multipartFormData` using `encodingMemoryThreshold` and calls `encodingCompletion` with new + /// `UploadRequest` using the `urlRequest`. + /// + /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative + /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most + /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to + /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory + /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be + /// used for larger payloads such as video content. + /// + /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory + /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`, + /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk + /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding + /// technique was used. + /// + /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. + /// + /// - parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`. + /// - parameter encodingMemoryThreshold: The encoding memory threshold in bytes. + /// `multipartFormDataEncodingMemoryThreshold` by default. + /// - parameter urlRequest: The URL request. + /// - parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete. + open func upload( + multipartFormData: @escaping (MultipartFormData) -> Void, + usingThreshold encodingMemoryThreshold: UInt64 = SessionManager.multipartFormDataEncodingMemoryThreshold, + with urlRequest: URLRequestConvertible, + queue: DispatchQueue? = nil, + encodingCompletion: ((MultipartFormDataEncodingResult) -> Void)?) + { + DispatchQueue.global(qos: .utility).async { + let formData = MultipartFormData() + multipartFormData(formData) + + var tempFileURL: URL? + + do { + var urlRequestWithContentType = try urlRequest.asURLRequest() + urlRequestWithContentType.setValue(formData.contentType, forHTTPHeaderField: "Content-Type") + + let isBackgroundSession = self.session.configuration.identifier != nil + + if formData.contentLength < encodingMemoryThreshold && !isBackgroundSession { + let data = try formData.encode() + + let encodingResult = MultipartFormDataEncodingResult.success( + request: self.upload(data, with: urlRequestWithContentType), + streamingFromDisk: false, + streamFileURL: nil + ) + + (queue ?? DispatchQueue.main).async { encodingCompletion?(encodingResult) } + } else { + let fileManager = FileManager.default + let tempDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory()) + let directoryURL = tempDirectoryURL.appendingPathComponent("org.alamofire.manager/multipart.form.data") + let fileName = UUID().uuidString + let fileURL = directoryURL.appendingPathComponent(fileName) + + tempFileURL = fileURL + + var directoryError: Error? + + // Create directory inside serial queue to ensure two threads don't do this in parallel + self.queue.sync { + do { + try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil) + } catch { + directoryError = error + } + } + + if let directoryError = directoryError { throw directoryError } + + try formData.writeEncodedData(to: fileURL) + + let upload = self.upload(fileURL, with: urlRequestWithContentType) + + // Cleanup the temp file once the upload is complete + upload.delegate.queue.addOperation { + do { + try FileManager.default.removeItem(at: fileURL) + } catch { + // No-op + } + } + + (queue ?? DispatchQueue.main).async { + let encodingResult = MultipartFormDataEncodingResult.success( + request: upload, + streamingFromDisk: true, + streamFileURL: fileURL + ) + + encodingCompletion?(encodingResult) + } + } + } catch { + // Cleanup the temp file in the event that the multipart form data encoding failed + if let tempFileURL = tempFileURL { + do { + try FileManager.default.removeItem(at: tempFileURL) + } catch { + // No-op + } + } + + (queue ?? DispatchQueue.main).async { encodingCompletion?(.failure(error)) } + } + } + } + + // MARK: Private - Upload Implementation + + private func upload(_ uploadable: UploadRequest.Uploadable) -> UploadRequest { + do { + let task = try uploadable.task(session: session, adapter: adapter, queue: queue) + let upload = UploadRequest(session: session, requestTask: .upload(uploadable, task)) + + if case let .stream(inputStream, _) = uploadable { + upload.delegate.taskNeedNewBodyStream = { _, _ in inputStream } + } + + delegate[task] = upload + + if startRequestsImmediately { upload.resume() } + + return upload + } catch { + return upload(uploadable, failedWith: error) + } + } + + private func upload(_ uploadable: UploadRequest.Uploadable?, failedWith error: Error) -> UploadRequest { + var uploadTask: Request.RequestTask = .upload(nil, nil) + + if let uploadable = uploadable { + uploadTask = .upload(uploadable, nil) + } + + let underlyingError = error.underlyingAdaptError ?? error + let upload = UploadRequest(session: session, requestTask: uploadTask, error: underlyingError) + + if let retrier = retrier, error is AdaptError { + allowRetrier(retrier, toRetry: upload, with: underlyingError) + } else { + if startRequestsImmediately { upload.resume() } + } + + return upload + } + +#if !os(watchOS) + + // MARK: - Stream Request + + // MARK: Hostname and Port + + /// Creates a `StreamRequest` for bidirectional streaming using the `hostname` and `port`. + /// + /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. + /// + /// - parameter hostName: The hostname of the server to connect to. + /// - parameter port: The port of the server to connect to. + /// + /// - returns: The created `StreamRequest`. + @discardableResult + @available(iOS 9.0, macOS 10.11, tvOS 9.0, *) + open func stream(withHostName hostName: String, port: Int) -> StreamRequest { + return stream(.stream(hostName: hostName, port: port)) + } + + // MARK: NetService + + /// Creates a `StreamRequest` for bidirectional streaming using the `netService`. + /// + /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. + /// + /// - parameter netService: The net service used to identify the endpoint. + /// + /// - returns: The created `StreamRequest`. + @discardableResult + @available(iOS 9.0, macOS 10.11, tvOS 9.0, *) + open func stream(with netService: NetService) -> StreamRequest { + return stream(.netService(netService)) + } + + // MARK: Private - Stream Implementation + + @available(iOS 9.0, macOS 10.11, tvOS 9.0, *) + private func stream(_ streamable: StreamRequest.Streamable) -> StreamRequest { + do { + let task = try streamable.task(session: session, adapter: adapter, queue: queue) + let request = StreamRequest(session: session, requestTask: .stream(streamable, task)) + + delegate[task] = request + + if startRequestsImmediately { request.resume() } + + return request + } catch { + return stream(failedWith: error) + } + } + + @available(iOS 9.0, macOS 10.11, tvOS 9.0, *) + private func stream(failedWith error: Error) -> StreamRequest { + let stream = StreamRequest(session: session, requestTask: .stream(nil, nil), error: error) + if startRequestsImmediately { stream.resume() } + return stream + } + +#endif + + // MARK: - Internal - Retry Request + + func retry(_ request: Request) -> Bool { + guard let originalTask = request.originalTask else { return false } + + do { + let task = try originalTask.task(session: session, adapter: adapter, queue: queue) + + if let originalTask = request.task { + delegate[originalTask] = nil // removes the old request to avoid endless growth + } + + request.delegate.task = task // resets all task delegate data + + request.retryCount += 1 + request.startTime = CFAbsoluteTimeGetCurrent() + request.endTime = nil + + task.resume() + + return true + } catch { + request.delegate.error = error.underlyingAdaptError ?? error + return false + } + } + + private func allowRetrier(_ retrier: RequestRetrier, toRetry request: Request, with error: Error) { + DispatchQueue.utility.async { [weak self] in + guard let strongSelf = self else { return } + + retrier.should(strongSelf, retry: request, with: error) { shouldRetry, timeDelay in + guard let strongSelf = self else { return } + + guard shouldRetry else { + if strongSelf.startRequestsImmediately { request.resume() } + return + } + + DispatchQueue.utility.after(timeDelay) { + guard let strongSelf = self else { return } + + let retrySucceeded = strongSelf.retry(request) + + if retrySucceeded, let task = request.task { + strongSelf.delegate[task] = request + } else { + if strongSelf.startRequestsImmediately { request.resume() } + } + } + } + } + } +} diff --git a/!main project/Pods/Alamofire/Source/TaskDelegate.swift b/!main project/Pods/Alamofire/Source/TaskDelegate.swift new file mode 100644 index 0000000..5705737 --- /dev/null +++ b/!main project/Pods/Alamofire/Source/TaskDelegate.swift @@ -0,0 +1,466 @@ +// +// TaskDelegate.swift +// +// Copyright (c) 2014 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +/// The task delegate is responsible for handling all delegate callbacks for the underlying task as well as +/// executing all operations attached to the serial operation queue upon task completion. +open class TaskDelegate: NSObject { + + // MARK: Properties + + /// The serial operation queue used to execute all operations after the task completes. + public let queue: OperationQueue + + /// The data returned by the server. + public var data: Data? { return nil } + + /// The error generated throughout the lifecyle of the task. + public var error: Error? + + var task: URLSessionTask? { + set { + taskLock.lock(); defer { taskLock.unlock() } + _task = newValue + } + get { + taskLock.lock(); defer { taskLock.unlock() } + return _task + } + } + + var initialResponseTime: CFAbsoluteTime? + var credential: URLCredential? + var metrics: AnyObject? // URLSessionTaskMetrics + + private var _task: URLSessionTask? { + didSet { reset() } + } + + private let taskLock = NSLock() + + // MARK: Lifecycle + + init(task: URLSessionTask?) { + _task = task + + self.queue = { + let operationQueue = OperationQueue() + + operationQueue.maxConcurrentOperationCount = 1 + operationQueue.isSuspended = true + operationQueue.qualityOfService = .utility + + return operationQueue + }() + } + + func reset() { + error = nil + initialResponseTime = nil + } + + // MARK: URLSessionTaskDelegate + + var taskWillPerformHTTPRedirection: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest) -> URLRequest?)? + var taskDidReceiveChallenge: ((URLSession, URLSessionTask, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))? + var taskNeedNewBodyStream: ((URLSession, URLSessionTask) -> InputStream?)? + var taskDidCompleteWithError: ((URLSession, URLSessionTask, Error?) -> Void)? + + @objc(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:) + func urlSession( + _ session: URLSession, + task: URLSessionTask, + willPerformHTTPRedirection response: HTTPURLResponse, + newRequest request: URLRequest, + completionHandler: @escaping (URLRequest?) -> Void) + { + var redirectRequest: URLRequest? = request + + if let taskWillPerformHTTPRedirection = taskWillPerformHTTPRedirection { + redirectRequest = taskWillPerformHTTPRedirection(session, task, response, request) + } + + completionHandler(redirectRequest) + } + + @objc(URLSession:task:didReceiveChallenge:completionHandler:) + func urlSession( + _ session: URLSession, + task: URLSessionTask, + didReceive challenge: URLAuthenticationChallenge, + completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) + { + var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling + var credential: URLCredential? + + if let taskDidReceiveChallenge = taskDidReceiveChallenge { + (disposition, credential) = taskDidReceiveChallenge(session, task, challenge) + } else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust { + let host = challenge.protectionSpace.host + + if + let serverTrustPolicy = session.serverTrustPolicyManager?.serverTrustPolicy(forHost: host), + let serverTrust = challenge.protectionSpace.serverTrust + { + if serverTrustPolicy.evaluate(serverTrust, forHost: host) { + disposition = .useCredential + credential = URLCredential(trust: serverTrust) + } else { + disposition = .cancelAuthenticationChallenge + } + } + } else { + if challenge.previousFailureCount > 0 { + disposition = .rejectProtectionSpace + } else { + credential = self.credential ?? session.configuration.urlCredentialStorage?.defaultCredential(for: challenge.protectionSpace) + + if credential != nil { + disposition = .useCredential + } + } + } + + completionHandler(disposition, credential) + } + + @objc(URLSession:task:needNewBodyStream:) + func urlSession( + _ session: URLSession, + task: URLSessionTask, + needNewBodyStream completionHandler: @escaping (InputStream?) -> Void) + { + var bodyStream: InputStream? + + if let taskNeedNewBodyStream = taskNeedNewBodyStream { + bodyStream = taskNeedNewBodyStream(session, task) + } + + completionHandler(bodyStream) + } + + @objc(URLSession:task:didCompleteWithError:) + func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + if let taskDidCompleteWithError = taskDidCompleteWithError { + taskDidCompleteWithError(session, task, error) + } else { + if let error = error { + if self.error == nil { self.error = error } + + if + let downloadDelegate = self as? DownloadTaskDelegate, + let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data + { + downloadDelegate.resumeData = resumeData + } + } + + queue.isSuspended = false + } + } +} + +// MARK: - + +class DataTaskDelegate: TaskDelegate, URLSessionDataDelegate { + + // MARK: Properties + + var dataTask: URLSessionDataTask { return task as! URLSessionDataTask } + + override var data: Data? { + if dataStream != nil { + return nil + } else { + return mutableData + } + } + + var progress: Progress + var progressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)? + + var dataStream: ((_ data: Data) -> Void)? + + private var totalBytesReceived: Int64 = 0 + private var mutableData: Data + + private var expectedContentLength: Int64? + + // MARK: Lifecycle + + override init(task: URLSessionTask?) { + mutableData = Data() + progress = Progress(totalUnitCount: 0) + + super.init(task: task) + } + + override func reset() { + super.reset() + + progress = Progress(totalUnitCount: 0) + totalBytesReceived = 0 + mutableData = Data() + expectedContentLength = nil + } + + // MARK: URLSessionDataDelegate + + var dataTaskDidReceiveResponse: ((URLSession, URLSessionDataTask, URLResponse) -> URLSession.ResponseDisposition)? + var dataTaskDidBecomeDownloadTask: ((URLSession, URLSessionDataTask, URLSessionDownloadTask) -> Void)? + var dataTaskDidReceiveData: ((URLSession, URLSessionDataTask, Data) -> Void)? + var dataTaskWillCacheResponse: ((URLSession, URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)? + + func urlSession( + _ session: URLSession, + dataTask: URLSessionDataTask, + didReceive response: URLResponse, + completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) + { + var disposition: URLSession.ResponseDisposition = .allow + + expectedContentLength = response.expectedContentLength + + if let dataTaskDidReceiveResponse = dataTaskDidReceiveResponse { + disposition = dataTaskDidReceiveResponse(session, dataTask, response) + } + + completionHandler(disposition) + } + + func urlSession( + _ session: URLSession, + dataTask: URLSessionDataTask, + didBecome downloadTask: URLSessionDownloadTask) + { + dataTaskDidBecomeDownloadTask?(session, dataTask, downloadTask) + } + + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() } + + if let dataTaskDidReceiveData = dataTaskDidReceiveData { + dataTaskDidReceiveData(session, dataTask, data) + } else { + if let dataStream = dataStream { + dataStream(data) + } else { + mutableData.append(data) + } + + let bytesReceived = Int64(data.count) + totalBytesReceived += bytesReceived + let totalBytesExpected = dataTask.response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown + + progress.totalUnitCount = totalBytesExpected + progress.completedUnitCount = totalBytesReceived + + if let progressHandler = progressHandler { + progressHandler.queue.async { progressHandler.closure(self.progress) } + } + } + } + + func urlSession( + _ session: URLSession, + dataTask: URLSessionDataTask, + willCacheResponse proposedResponse: CachedURLResponse, + completionHandler: @escaping (CachedURLResponse?) -> Void) + { + var cachedResponse: CachedURLResponse? = proposedResponse + + if let dataTaskWillCacheResponse = dataTaskWillCacheResponse { + cachedResponse = dataTaskWillCacheResponse(session, dataTask, proposedResponse) + } + + completionHandler(cachedResponse) + } +} + +// MARK: - + +class DownloadTaskDelegate: TaskDelegate, URLSessionDownloadDelegate { + + // MARK: Properties + + var downloadTask: URLSessionDownloadTask { return task as! URLSessionDownloadTask } + + var progress: Progress + var progressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)? + + var resumeData: Data? + override var data: Data? { return resumeData } + + var destination: DownloadRequest.DownloadFileDestination? + + var temporaryURL: URL? + var destinationURL: URL? + + var fileURL: URL? { return destination != nil ? destinationURL : temporaryURL } + + // MARK: Lifecycle + + override init(task: URLSessionTask?) { + progress = Progress(totalUnitCount: 0) + super.init(task: task) + } + + override func reset() { + super.reset() + + progress = Progress(totalUnitCount: 0) + resumeData = nil + } + + // MARK: URLSessionDownloadDelegate + + var downloadTaskDidFinishDownloadingToURL: ((URLSession, URLSessionDownloadTask, URL) -> URL)? + var downloadTaskDidWriteData: ((URLSession, URLSessionDownloadTask, Int64, Int64, Int64) -> Void)? + var downloadTaskDidResumeAtOffset: ((URLSession, URLSessionDownloadTask, Int64, Int64) -> Void)? + + func urlSession( + _ session: URLSession, + downloadTask: URLSessionDownloadTask, + didFinishDownloadingTo location: URL) + { + temporaryURL = location + + guard + let destination = destination, + let response = downloadTask.response as? HTTPURLResponse + else { return } + + let result = destination(location, response) + let destinationURL = result.destinationURL + let options = result.options + + self.destinationURL = destinationURL + + do { + if options.contains(.removePreviousFile), FileManager.default.fileExists(atPath: destinationURL.path) { + try FileManager.default.removeItem(at: destinationURL) + } + + if options.contains(.createIntermediateDirectories) { + let directory = destinationURL.deletingLastPathComponent() + try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) + } + + try FileManager.default.moveItem(at: location, to: destinationURL) + } catch { + self.error = error + } + } + + func urlSession( + _ session: URLSession, + downloadTask: URLSessionDownloadTask, + didWriteData bytesWritten: Int64, + totalBytesWritten: Int64, + totalBytesExpectedToWrite: Int64) + { + if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() } + + if let downloadTaskDidWriteData = downloadTaskDidWriteData { + downloadTaskDidWriteData( + session, + downloadTask, + bytesWritten, + totalBytesWritten, + totalBytesExpectedToWrite + ) + } else { + progress.totalUnitCount = totalBytesExpectedToWrite + progress.completedUnitCount = totalBytesWritten + + if let progressHandler = progressHandler { + progressHandler.queue.async { progressHandler.closure(self.progress) } + } + } + } + + func urlSession( + _ session: URLSession, + downloadTask: URLSessionDownloadTask, + didResumeAtOffset fileOffset: Int64, + expectedTotalBytes: Int64) + { + if let downloadTaskDidResumeAtOffset = downloadTaskDidResumeAtOffset { + downloadTaskDidResumeAtOffset(session, downloadTask, fileOffset, expectedTotalBytes) + } else { + progress.totalUnitCount = expectedTotalBytes + progress.completedUnitCount = fileOffset + } + } +} + +// MARK: - + +class UploadTaskDelegate: DataTaskDelegate { + + // MARK: Properties + + var uploadTask: URLSessionUploadTask { return task as! URLSessionUploadTask } + + var uploadProgress: Progress + var uploadProgressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)? + + // MARK: Lifecycle + + override init(task: URLSessionTask?) { + uploadProgress = Progress(totalUnitCount: 0) + super.init(task: task) + } + + override func reset() { + super.reset() + uploadProgress = Progress(totalUnitCount: 0) + } + + // MARK: URLSessionTaskDelegate + + var taskDidSendBodyData: ((URLSession, URLSessionTask, Int64, Int64, Int64) -> Void)? + + func URLSession( + _ session: URLSession, + task: URLSessionTask, + didSendBodyData bytesSent: Int64, + totalBytesSent: Int64, + totalBytesExpectedToSend: Int64) + { + if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() } + + if let taskDidSendBodyData = taskDidSendBodyData { + taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend) + } else { + uploadProgress.totalUnitCount = totalBytesExpectedToSend + uploadProgress.completedUnitCount = totalBytesSent + + if let uploadProgressHandler = uploadProgressHandler { + uploadProgressHandler.queue.async { uploadProgressHandler.closure(self.uploadProgress) } + } + } + } +} diff --git a/!main project/Pods/Alamofire/Source/Timeline.swift b/!main project/Pods/Alamofire/Source/Timeline.swift new file mode 100644 index 0000000..596c1bd --- /dev/null +++ b/!main project/Pods/Alamofire/Source/Timeline.swift @@ -0,0 +1,136 @@ +// +// Timeline.swift +// +// Copyright (c) 2014 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +/// Responsible for computing the timing metrics for the complete lifecycle of a `Request`. +public struct Timeline { + /// The time the request was initialized. + public let requestStartTime: CFAbsoluteTime + + /// The time the first bytes were received from or sent to the server. + public let initialResponseTime: CFAbsoluteTime + + /// The time when the request was completed. + public let requestCompletedTime: CFAbsoluteTime + + /// The time when the response serialization was completed. + public let serializationCompletedTime: CFAbsoluteTime + + /// The time interval in seconds from the time the request started to the initial response from the server. + public let latency: TimeInterval + + /// The time interval in seconds from the time the request started to the time the request completed. + public let requestDuration: TimeInterval + + /// The time interval in seconds from the time the request completed to the time response serialization completed. + public let serializationDuration: TimeInterval + + /// The time interval in seconds from the time the request started to the time response serialization completed. + public let totalDuration: TimeInterval + + /// Creates a new `Timeline` instance with the specified request times. + /// + /// - parameter requestStartTime: The time the request was initialized. Defaults to `0.0`. + /// - parameter initialResponseTime: The time the first bytes were received from or sent to the server. + /// Defaults to `0.0`. + /// - parameter requestCompletedTime: The time when the request was completed. Defaults to `0.0`. + /// - parameter serializationCompletedTime: The time when the response serialization was completed. Defaults + /// to `0.0`. + /// + /// - returns: The new `Timeline` instance. + public init( + requestStartTime: CFAbsoluteTime = 0.0, + initialResponseTime: CFAbsoluteTime = 0.0, + requestCompletedTime: CFAbsoluteTime = 0.0, + serializationCompletedTime: CFAbsoluteTime = 0.0) + { + self.requestStartTime = requestStartTime + self.initialResponseTime = initialResponseTime + self.requestCompletedTime = requestCompletedTime + self.serializationCompletedTime = serializationCompletedTime + + self.latency = initialResponseTime - requestStartTime + self.requestDuration = requestCompletedTime - requestStartTime + self.serializationDuration = serializationCompletedTime - requestCompletedTime + self.totalDuration = serializationCompletedTime - requestStartTime + } +} + +// MARK: - CustomStringConvertible + +extension Timeline: CustomStringConvertible { + /// The textual representation used when written to an output stream, which includes the latency, the request + /// duration and the total duration. + public var description: String { + let latency = String(format: "%.3f", self.latency) + let requestDuration = String(format: "%.3f", self.requestDuration) + let serializationDuration = String(format: "%.3f", self.serializationDuration) + let totalDuration = String(format: "%.3f", self.totalDuration) + + // NOTE: Had to move to string concatenation due to memory leak filed as rdar://26761490. Once memory leak is + // fixed, we should move back to string interpolation by reverting commit 7d4a43b1. + let timings = [ + "\"Latency\": " + latency + " secs", + "\"Request Duration\": " + requestDuration + " secs", + "\"Serialization Duration\": " + serializationDuration + " secs", + "\"Total Duration\": " + totalDuration + " secs" + ] + + return "Timeline: { " + timings.joined(separator: ", ") + " }" + } +} + +// MARK: - CustomDebugStringConvertible + +extension Timeline: CustomDebugStringConvertible { + /// The textual representation used when written to an output stream, which includes the request start time, the + /// initial response time, the request completed time, the serialization completed time, the latency, the request + /// duration and the total duration. + public var debugDescription: String { + let requestStartTime = String(format: "%.3f", self.requestStartTime) + let initialResponseTime = String(format: "%.3f", self.initialResponseTime) + let requestCompletedTime = String(format: "%.3f", self.requestCompletedTime) + let serializationCompletedTime = String(format: "%.3f", self.serializationCompletedTime) + let latency = String(format: "%.3f", self.latency) + let requestDuration = String(format: "%.3f", self.requestDuration) + let serializationDuration = String(format: "%.3f", self.serializationDuration) + let totalDuration = String(format: "%.3f", self.totalDuration) + + // NOTE: Had to move to string concatenation due to memory leak filed as rdar://26761490. Once memory leak is + // fixed, we should move back to string interpolation by reverting commit 7d4a43b1. + let timings = [ + "\"Request Start Time\": " + requestStartTime, + "\"Initial Response Time\": " + initialResponseTime, + "\"Request Completed Time\": " + requestCompletedTime, + "\"Serialization Completed Time\": " + serializationCompletedTime, + "\"Latency\": " + latency + " secs", + "\"Request Duration\": " + requestDuration + " secs", + "\"Serialization Duration\": " + serializationDuration + " secs", + "\"Total Duration\": " + totalDuration + " secs" + ] + + return "Timeline: { " + timings.joined(separator: ", ") + " }" + } +} diff --git a/!main project/Pods/Alamofire/Source/Validation.swift b/!main project/Pods/Alamofire/Source/Validation.swift new file mode 100644 index 0000000..59e0bbb --- /dev/null +++ b/!main project/Pods/Alamofire/Source/Validation.swift @@ -0,0 +1,321 @@ +// +// Validation.swift +// +// Copyright (c) 2014 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +extension Request { + + // MARK: Helper Types + + fileprivate typealias ErrorReason = AFError.ResponseValidationFailureReason + + /// Used to represent whether validation was successful or encountered an error resulting in a failure. + /// + /// - success: The validation was successful. + /// - failure: The validation failed encountering the provided error. + public enum ValidationResult { + case success + case failure(Error) + } + + fileprivate struct MIMEType { + let type: String + let subtype: String + + var isWildcard: Bool { return type == "*" && subtype == "*" } + + init?(_ string: String) { + let components: [String] = { + let stripped = string.trimmingCharacters(in: .whitespacesAndNewlines) + + #if swift(>=3.2) + let split = stripped[..<(stripped.range(of: ";")?.lowerBound ?? stripped.endIndex)] + #else + let split = stripped.substring(to: stripped.range(of: ";")?.lowerBound ?? stripped.endIndex) + #endif + + return split.components(separatedBy: "/") + }() + + if let type = components.first, let subtype = components.last { + self.type = type + self.subtype = subtype + } else { + return nil + } + } + + func matches(_ mime: MIMEType) -> Bool { + switch (type, subtype) { + case (mime.type, mime.subtype), (mime.type, "*"), ("*", mime.subtype), ("*", "*"): + return true + default: + return false + } + } + } + + // MARK: Properties + + fileprivate var acceptableStatusCodes: [Int] { return Array(200..<300) } + + fileprivate var acceptableContentTypes: [String] { + if let accept = request?.value(forHTTPHeaderField: "Accept") { + return accept.components(separatedBy: ",") + } + + return ["*/*"] + } + + // MARK: Status Code + + fileprivate func validate( + statusCode acceptableStatusCodes: S, + response: HTTPURLResponse) + -> ValidationResult + where S.Iterator.Element == Int + { + if acceptableStatusCodes.contains(response.statusCode) { + return .success + } else { + let reason: ErrorReason = .unacceptableStatusCode(code: response.statusCode) + return .failure(AFError.responseValidationFailed(reason: reason)) + } + } + + // MARK: Content Type + + fileprivate func validate( + contentType acceptableContentTypes: S, + response: HTTPURLResponse, + data: Data?) + -> ValidationResult + where S.Iterator.Element == String + { + guard let data = data, data.count > 0 else { return .success } + + guard + let responseContentType = response.mimeType, + let responseMIMEType = MIMEType(responseContentType) + else { + for contentType in acceptableContentTypes { + if let mimeType = MIMEType(contentType), mimeType.isWildcard { + return .success + } + } + + let error: AFError = { + let reason: ErrorReason = .missingContentType(acceptableContentTypes: Array(acceptableContentTypes)) + return AFError.responseValidationFailed(reason: reason) + }() + + return .failure(error) + } + + for contentType in acceptableContentTypes { + if let acceptableMIMEType = MIMEType(contentType), acceptableMIMEType.matches(responseMIMEType) { + return .success + } + } + + let error: AFError = { + let reason: ErrorReason = .unacceptableContentType( + acceptableContentTypes: Array(acceptableContentTypes), + responseContentType: responseContentType + ) + + return AFError.responseValidationFailed(reason: reason) + }() + + return .failure(error) + } +} + +// MARK: - + +extension DataRequest { + /// A closure used to validate a request that takes a URL request, a URL response and data, and returns whether the + /// request was valid. + public typealias Validation = (URLRequest?, HTTPURLResponse, Data?) -> ValidationResult + + /// Validates the request, using the specified closure. + /// + /// If validation fails, subsequent calls to response handlers will have an associated error. + /// + /// - parameter validation: A closure to validate the request. + /// + /// - returns: The request. + @discardableResult + public func validate(_ validation: @escaping Validation) -> Self { + let validationExecution: () -> Void = { [unowned self] in + if + let response = self.response, + self.delegate.error == nil, + case let .failure(error) = validation(self.request, response, self.delegate.data) + { + self.delegate.error = error + } + } + + validations.append(validationExecution) + + return self + } + + /// Validates that the response has a status code in the specified sequence. + /// + /// If validation fails, subsequent calls to response handlers will have an associated error. + /// + /// - parameter range: The range of acceptable status codes. + /// + /// - returns: The request. + @discardableResult + public func validate(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int { + return validate { [unowned self] _, response, _ in + return self.validate(statusCode: acceptableStatusCodes, response: response) + } + } + + /// Validates that the response has a content type in the specified sequence. + /// + /// If validation fails, subsequent calls to response handlers will have an associated error. + /// + /// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes. + /// + /// - returns: The request. + @discardableResult + public func validate(contentType acceptableContentTypes: S) -> Self where S.Iterator.Element == String { + return validate { [unowned self] _, response, data in + return self.validate(contentType: acceptableContentTypes, response: response, data: data) + } + } + + /// Validates that the response has a status code in the default acceptable range of 200...299, and that the content + /// type matches any specified in the Accept HTTP header field. + /// + /// If validation fails, subsequent calls to response handlers will have an associated error. + /// + /// - returns: The request. + @discardableResult + public func validate() -> Self { + let contentTypes = { [unowned self] in + self.acceptableContentTypes + } + return validate(statusCode: acceptableStatusCodes).validate(contentType: contentTypes()) + } +} + +// MARK: - + +extension DownloadRequest { + /// A closure used to validate a request that takes a URL request, a URL response, a temporary URL and a + /// destination URL, and returns whether the request was valid. + public typealias Validation = ( + _ request: URLRequest?, + _ response: HTTPURLResponse, + _ temporaryURL: URL?, + _ destinationURL: URL?) + -> ValidationResult + + /// Validates the request, using the specified closure. + /// + /// If validation fails, subsequent calls to response handlers will have an associated error. + /// + /// - parameter validation: A closure to validate the request. + /// + /// - returns: The request. + @discardableResult + public func validate(_ validation: @escaping Validation) -> Self { + let validationExecution: () -> Void = { [unowned self] in + let request = self.request + let temporaryURL = self.downloadDelegate.temporaryURL + let destinationURL = self.downloadDelegate.destinationURL + + if + let response = self.response, + self.delegate.error == nil, + case let .failure(error) = validation(request, response, temporaryURL, destinationURL) + { + self.delegate.error = error + } + } + + validations.append(validationExecution) + + return self + } + + /// Validates that the response has a status code in the specified sequence. + /// + /// If validation fails, subsequent calls to response handlers will have an associated error. + /// + /// - parameter range: The range of acceptable status codes. + /// + /// - returns: The request. + @discardableResult + public func validate(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int { + return validate { [unowned self] _, response, _, _ in + return self.validate(statusCode: acceptableStatusCodes, response: response) + } + } + + /// Validates that the response has a content type in the specified sequence. + /// + /// If validation fails, subsequent calls to response handlers will have an associated error. + /// + /// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes. + /// + /// - returns: The request. + @discardableResult + public func validate(contentType acceptableContentTypes: S) -> Self where S.Iterator.Element == String { + return validate { [unowned self] _, response, _, _ in + let fileURL = self.downloadDelegate.fileURL + + guard let validFileURL = fileURL else { + return .failure(AFError.responseValidationFailed(reason: .dataFileNil)) + } + + do { + let data = try Data(contentsOf: validFileURL) + return self.validate(contentType: acceptableContentTypes, response: response, data: data) + } catch { + return .failure(AFError.responseValidationFailed(reason: .dataFileReadFailed(at: validFileURL))) + } + } + } + + /// Validates that the response has a status code in the default acceptable range of 200...299, and that the content + /// type matches any specified in the Accept HTTP header field. + /// + /// If validation fails, subsequent calls to response handlers will have an associated error. + /// + /// - returns: The request. + @discardableResult + public func validate() -> Self { + let contentTypes = { [unowned self] in + self.acceptableContentTypes + } + return validate(statusCode: acceptableStatusCodes).validate(contentType: contentTypes()) + } +} diff --git a/!main project/Pods/Firebase/CoreOnly/Sources/Firebase.h b/!main project/Pods/Firebase/CoreOnly/Sources/Firebase.h new file mode 100755 index 0000000..e5049ca --- /dev/null +++ b/!main project/Pods/Firebase/CoreOnly/Sources/Firebase.h @@ -0,0 +1,172 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +#if !defined(__has_include) + #error "Firebase.h won't import anything if your compiler doesn't support __has_include. Please \ + import the headers individually." +#else + #if __has_include() + #import + #endif + + #if __has_include() + #import + #endif + + #if __has_include() + #import + #endif + + #if __has_include() + #import + #endif + + #if __has_include() + #import + #if !__has_include() + #ifndef FIREBASE_ANALYTICS_SUPPRESS_WARNING + #warning "FirebaseAnalytics.framework is not included in your target. Please add \ +`Firebase/Analytics` to your Podfile or add FirebaseAnalytics.framework to your project to ensure \ +Firebase Dynamic Links works as intended." + #endif // #ifndef FIREBASE_ANALYTICS_SUPPRESS_WARNING + #endif + #endif + + #if __has_include() + #import + #endif + + #if __has_include() + #import + #endif + + #if __has_include() + #import + #if !__has_include() + #ifndef FIREBASE_ANALYTICS_SUPPRESS_WARNING + #warning "FirebaseAnalytics.framework is not included in your target. Please add \ +`Firebase/Analytics` to your Podfile or add FirebaseAnalytics.framework to your project to ensure \ +Firebase In App Messaging works as intended." + #endif // #ifndef FIREBASE_ANALYTICS_SUPPRESS_WARNING + #endif + #endif + + #if __has_include() + #import + #endif + + #if __has_include() + #import + #if !__has_include() + #ifndef FIREBASE_ANALYTICS_SUPPRESS_WARNING + #warning "FirebaseAnalytics.framework is not included in your target. Please add \ +`Firebase/Analytics` to your Podfile or add FirebaseAnalytics.framework to your project to ensure \ +Firebase Messaging works as intended." + #endif // #ifndef FIREBASE_ANALYTICS_SUPPRESS_WARNING + #endif +#endif + + #if __has_include() + #import + #endif + + #if __has_include() + #import + #endif + + #if __has_include() + #import + #endif + + #if __has_include() + #import + #endif + + #if __has_include() + #import + #endif + + #if __has_include() + #import + #endif + + #if __has_include() + #import + #endif + + #if __has_include() + #import + #endif + + #if __has_include() + #import + #endif + + #if __has_include() + #import + #endif + + #if __has_include() + #import + #endif + + #if __has_include() + #import + #endif + + #if __has_include() + #import + #endif + + #if __has_include() + #import + #if !__has_include() + #ifndef FIREBASE_ANALYTICS_SUPPRESS_WARNING + #warning "FirebaseAnalytics.framework is not included in your target. Please add \ +`Firebase/Analytics` to your Podfile or add FirebaseAnalytics.framework to your project to ensure \ +Firebase Performance works as intended." + #endif // #ifndef FIREBASE_ANALYTICS_SUPPRESS_WARNING + #endif + #endif + + #if __has_include() + #import + #if !__has_include() + #ifndef FIREBASE_ANALYTICS_SUPPRESS_WARNING + #warning "FirebaseAnalytics.framework is not included in your target. Please add \ +`Firebase/Analytics` to your Podfile or add FirebaseAnalytics.framework to your project to ensure \ +Firebase Remote Config works as intended." + #endif // #ifndef FIREBASE_ANALYTICS_SUPPRESS_WARNING + #endif + #endif + + #if __has_include() + #import + #endif + + #if __has_include() + #import + #endif + + #if __has_include() + #import + #endif + + #if __has_include() + #import + #endif + +#endif // defined(__has_include) diff --git a/!main project/Pods/Firebase/CoreOnly/Sources/module.modulemap b/!main project/Pods/Firebase/CoreOnly/Sources/module.modulemap new file mode 100755 index 0000000..3685b54 --- /dev/null +++ b/!main project/Pods/Firebase/CoreOnly/Sources/module.modulemap @@ -0,0 +1,4 @@ +module Firebase { + export * + header "Firebase.h" +} \ No newline at end of file diff --git a/!main project/Pods/Firebase/LICENSE b/!main project/Pods/Firebase/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/!main project/Pods/Firebase/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/!main project/Pods/Firebase/README.md b/!main project/Pods/Firebase/README.md new file mode 100644 index 0000000..5097a89 --- /dev/null +++ b/!main project/Pods/Firebase/README.md @@ -0,0 +1,254 @@ +# Firebase iOS Open Source Development [![Build Status](https://travis-ci.org/firebase/firebase-ios-sdk.svg?branch=master)](https://travis-ci.org/firebase/firebase-ios-sdk) + +This repository contains a subset of the Firebase iOS SDK source. It currently +includes FirebaseCore, FirebaseABTesting, FirebaseAuth, FirebaseDatabase, +FirebaseFirestore, FirebaseFunctions, FirebaseInstanceID, FirebaseInAppMessaging, +FirebaseInAppMessagingDisplay, FirebaseMessaging, FirebaseRemoteConfig, and +FirebaseStorage. + +The repository also includes GoogleUtilities source. The +[GoogleUtilities](GoogleUtilities/README.md) pod is +a set of utilities used by Firebase and other Google products. + +Firebase is an app development platform with tools to help you build, grow and +monetize your app. More information about Firebase can be found at +[https://firebase.google.com](https://firebase.google.com). + +## Installation + +See the three subsections for details about three different installation methods. +1. [Standard pod install](README.md#standard-pod-install) +1. [Installing from the GitHub repo](README.md#installing-from-github) +1. [Experimental Carthage](README.md#carthage-ios-only) + +### Standard pod install + +Go to +[https://firebase.google.com/docs/ios/setup](https://firebase.google.com/docs/ios/setup). + +### Installing from GitHub + +For releases starting with 5.0.0, the source for each release is also deployed +to CocoaPods master and available via standard +[CocoaPods Podfile syntax](https://guides.cocoapods.org/syntax/podfile.html#pod). + +These instructions can be used to access the Firebase repo at other branches, +tags, or commits. + +#### Background + +See +[the Podfile Syntax Reference](https://guides.cocoapods.org/syntax/podfile.html#pod) +for instructions and options about overriding pod source locations. + +#### Accessing Firebase Source Snapshots + +All of the official releases are tagged in this repo and available via CocoaPods. To access a local +source snapshot or unreleased branch, use Podfile directives like the following: + +To access FirebaseFirestore via a branch: +``` +pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +``` + +To access FirebaseMessaging via a checked out version of the firebase-ios-sdk repo do: + +``` +pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk' +pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk' +``` + +### Carthage (iOS only) + +Instructions for the experimental Carthage distribution are at +[Carthage](Carthage.md). + +### Rome + +Instructions for installing binary frameworks via +[Rome](https://github.com/CocoaPods/Rome) are at [Rome](Rome.md). + +## Development + +To develop Firebase software in this repository, ensure that you have at least +the following software: + + * Xcode 10.1 (or later) + * CocoaPods 1.7.2 (or later) + * [CocoaPods generate](https://github.com/square/cocoapods-generate) + +For the pod that you want to develop: + +`pod gen Firebase{name here}.podspec --local-sources=./ --auto-open --platforms=ios` + +Note: If the CocoaPods cache is out of date, you may need to run +`pod repo update` before the `pod gen` command. + +Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for +those platforms. Since 10.2, Xcode does not properly handle multi-platform +CocoaPods workspaces. + +Firestore has a self contained Xcode project. See +[Firestore/README.md](Firestore/README.md). + +### Development for Catalyst +* `pod gen {name here}.podspec --local-sources=./ --auto-open --platforms=ios` +* Check the Mac box in the App-iOS Build Settings +* Sign the App in the Settings Signing & Capabilities tab +* Click Pods in the Project Manager +* Add Signing to the iOS host app and unit test targets +* Select the Unit-unit scheme +* Run it to build and test + +### Adding a New Firebase Pod + +See [AddNewPod.md](AddNewPod.md). + +### Code Formatting + +To ensure that the code is formatted consistently, run the script +[./scripts/style.sh](https://github.com/firebase/firebase-ios-sdk/blob/master/scripts/style.sh) +before creating a PR. + +Travis will verify that any code changes are done in a style compliant way. Install +`clang-format` and `swiftformat`. +These commands will get the right versions: + +``` +brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/e3496d9/Formula/clang-format.rb +brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/7963c3d/Formula/swiftformat.rb +``` + +Note: if you already have a newer version of these installed you may need to +`brew switch` to this version. + +To update this section, find the versions of clang-format and swiftformat.rb to +match the versions in the CI failure logs +[here](https://github.com/Homebrew/homebrew-core/tree/master/Formula). + +### Running Unit Tests + +Select a scheme and press Command-u to build a component and run its unit tests. + +#### Viewing Code Coverage + +First, make sure that [xcov](https://github.com/nakiostudio/xcov) is installed with `gem install xcov`. + +After running the `AllUnitTests_iOS` scheme in Xcode, execute +`xcov --workspace Firebase.xcworkspace --scheme AllUnitTests_iOS --output_directory xcov_output` +at Example/ in the terminal. This will aggregate the coverage, and you can run `open xcov_output/index.html` to see the results. + +### Running Sample Apps +In order to run the sample apps and integration tests, you'll need valid +`GoogleService-Info.plist` files for those samples. The Firebase Xcode project contains dummy plist +files without real values, but can be replaced with real plist files. To get your own +`GoogleService-Info.plist` files: + +1. Go to the [Firebase Console](https://console.firebase.google.com/) +2. Create a new Firebase project, if you don't already have one +3. For each sample app you want to test, create a new Firebase app with the sample app's bundle +identifier (e.g. `com.google.Database-Example`) +4. Download the resulting `GoogleService-Info.plist` and replace the appropriate dummy plist file +(e.g. in [Example/Database/App/](Example/Database/App/)); + +Some sample apps like Firebase Messaging ([Example/Messaging/App](Example/Messaging/App)) require +special Apple capabilities, and you will have to change the sample app to use a unique bundle +identifier that you can control in your own Apple Developer account. + +## Specific Component Instructions +See the sections below for any special instructions for those components. + +### Firebase Auth + +If you're doing specific Firebase Auth development, see +[the Auth Sample README](Example/Auth/README.md) for instructions about +building and running the FirebaseAuth pod along with various samples and tests. + +### Firebase Database + +To run the Database Integration tests, make your database authentication rules +[public](https://firebase.google.com/docs/database/security/quickstart). + +### Firebase Storage + +To run the Storage Integration tests, follow the instructions in +[FIRStorageIntegrationTests.m](Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m). + +#### Push Notifications + +Push notifications can only be delivered to specially provisioned App IDs in the developer portal. +In order to actually test receiving push notifications, you will need to: + +1. Change the bundle identifier of the sample app to something you own in your Apple Developer +account, and enable that App ID for push notifications. +2. You'll also need to +[upload your APNs Provider Authentication Key or certificate to the Firebase Console](https://firebase.google.com/docs/cloud-messaging/ios/certs) +at **Project Settings > Cloud Messaging > [Your Firebase App]**. +3. Ensure your iOS device is added to your Apple Developer portal as a test device. + +#### iOS Simulator + +The iOS Simulator cannot register for remote notifications, and will not receive push notifications. +In order to receive push notifications, you'll have to follow the steps above and run the app on a +physical device. + +## Community Supported Efforts + +We've seen an amazing amount of interest and contributions to improve the Firebase SDKs, and we are +very grateful! We'd like to empower as many developers as we can to be able to use Firebase and +participate in the Firebase community. + +### tvOS, macOS, and Catalyst +Thanks to contributions from the community, many of Firebase SDKs now compile, run unit tests, and work on +tvOS, macOS, and Catalyst. + +For tvOS, checkout the [Sample](Example/tvOSSample). + +Keep in mind that macOS, Catalyst and tvOS are not officially supported by Firebase, and this +repository is actively developed primarily for iOS. While we can catch basic unit test issues with +Travis, there may be some changes where the SDK no longer works as expected on macOS or tvOS. If you +encounter this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues). + +During app setup in the console, you may get to a step that mentions something like "Checking if the app +has communicated with our servers". This relies on Analytics and will not work on macOS/tvOS/Catalyst. +**It's safe to ignore the message and continue**, the rest of the SDKs will work as expected. + +To install, add a subset of the following to the Podfile: + +``` +pod 'Firebase/ABTesting' +pod 'Firebase/Auth' +pod 'Firebase/Crashlytics' +pod 'Firebase/Database' +pod 'Firebase/Firestore' +pod 'Firebase/Functions' +pod 'Firebase/Messaging' +pod 'Firebase/RemoteConfig' +pod 'Firebase/Storage' +``` + +#### Additional Catalyst Notes + +* FirebaseAuth and FirebaseMessaging require adding `Keychain Sharing Capability` +to Build Settings. +* FirebaseFirestore requires signing the +[gRPC Resource target](https://github.com/firebase/firebase-ios-sdk/issues/3500#issuecomment-518741681). + +## Roadmap + +See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source +plans and directions. + +## Contributing + +See [Contributing](CONTRIBUTING.md) for more information on contributing to the Firebase +iOS SDK. + +## License + +The contents of this repository is licensed under the +[Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0). + +Your use of Firebase is governed by the +[Terms of Service for Firebase Services](https://firebase.google.com/terms/). diff --git a/!main project/Pods/FirebaseAnalytics/Frameworks/FIRAnalyticsConnector.framework/FIRAnalyticsConnector b/!main project/Pods/FirebaseAnalytics/Frameworks/FIRAnalyticsConnector.framework/FIRAnalyticsConnector new file mode 100755 index 0000000..1a7eaec Binary files /dev/null and b/!main project/Pods/FirebaseAnalytics/Frameworks/FIRAnalyticsConnector.framework/FIRAnalyticsConnector differ diff --git a/!main project/Pods/FirebaseAnalytics/Frameworks/FIRAnalyticsConnector.framework/Modules/module.modulemap b/!main project/Pods/FirebaseAnalytics/Frameworks/FIRAnalyticsConnector.framework/Modules/module.modulemap new file mode 100755 index 0000000..99a4b1d --- /dev/null +++ b/!main project/Pods/FirebaseAnalytics/Frameworks/FIRAnalyticsConnector.framework/Modules/module.modulemap @@ -0,0 +1,11 @@ +framework module FIRAnalyticsConnector { + export * + module * { export * } + link "sqlite3" + link "z" + link framework "CoreData" + link framework "Security" + link framework "StoreKit" + link framework "SystemConfiguration" + link framework "UIKit" +} diff --git a/!main project/Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/FirebaseAnalytics b/!main project/Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/FirebaseAnalytics new file mode 100755 index 0000000..fa7ceb9 Binary files /dev/null and b/!main project/Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/FirebaseAnalytics differ diff --git a/!main project/Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Headers/FIRAnalytics+AppDelegate.h b/!main project/Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Headers/FIRAnalytics+AppDelegate.h new file mode 100755 index 0000000..d499af6 --- /dev/null +++ b/!main project/Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Headers/FIRAnalytics+AppDelegate.h @@ -0,0 +1,62 @@ +#import + +#import "FIRAnalytics.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * Provides App Delegate handlers to be used in your App Delegate. + * + * To save time integrating Firebase Analytics in an application, Firebase Analytics does not + * require delegation implementation from the AppDelegate. Instead this is automatically done by + * Firebase Analytics. Should you choose instead to delegate manually, you can turn off the App + * Delegate Proxy by adding FirebaseAppDelegateProxyEnabled into your app's Info.plist and setting + * it to NO, and adding the methods in this category to corresponding delegation handlers. + * + * To handle Universal Links, you must return YES in + * [UIApplicationDelegate application:didFinishLaunchingWithOptions:]. + */ +@interface FIRAnalytics (AppDelegate) + +/** + * Handles events related to a URL session that are waiting to be processed. + * + * For optimal use of Firebase Analytics, call this method from the + * [UIApplicationDelegate application:handleEventsForBackgroundURLSession:completionHandler] + * method of the app delegate in your app. + * + * @param identifier The identifier of the URL session requiring attention. + * @param completionHandler The completion handler to call when you finish processing the events. + * Calling this completion handler lets the system know that your app's user interface is + * updated and a new snapshot can be taken. + */ ++ (void)handleEventsForBackgroundURLSession:(NSString *)identifier + completionHandler:(nullable void (^)(void))completionHandler; + +/** + * Handles the event when the app is launched by a URL. + * + * Call this method from [UIApplicationDelegate application:openURL:options:] (on iOS 9.0 and + * above), or [UIApplicationDelegate application:openURL:sourceApplication:annotation:] (on + * iOS 8.x and below) in your app. + * + * @param url The URL resource to open. This resource can be a network resource or a file. + */ ++ (void)handleOpenURL:(NSURL *)url; + +/** + * Handles the event when the app receives data associated with user activity that includes a + * Universal Link (on iOS 9.0 and above). + * + * Call this method from [UIApplication continueUserActivity:restorationHandler:] in your app + * delegate (on iOS 9.0 and above). + * + * @param userActivity The activity object containing the data associated with the task the user + * was performing. + */ ++ (void)handleUserActivity:(id)userActivity; + +@end + +NS_ASSUME_NONNULL_END + diff --git a/!main project/Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Headers/FIRAnalytics.h b/!main project/Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Headers/FIRAnalytics.h new file mode 100755 index 0000000..be0b1fa --- /dev/null +++ b/!main project/Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Headers/FIRAnalytics.h @@ -0,0 +1,136 @@ +#import + +#import "FIREventNames.h" +#import "FIRParameterNames.h" +#import "FIRUserPropertyNames.h" + +NS_ASSUME_NONNULL_BEGIN + +/// The top level Firebase Analytics singleton that provides methods for logging events and setting +/// user properties. See the developer guides for general +/// information on using Firebase Analytics in your apps. +/// +/// @note The Analytics SDK uses SQLite to persist events and other app-specific data. Calling +/// certain thread-unsafe global SQLite methods like `sqlite3_shutdown()` can result in +/// unexpected crashes at runtime. +NS_SWIFT_NAME(Analytics) +@interface FIRAnalytics : NSObject + +/// Logs an app event. The event can have up to 25 parameters. Events with the same name must have +/// the same parameters. Up to 500 event names are supported. Using predefined events and/or +/// parameters is recommended for optimal reporting. +/// +/// The following event names are reserved and cannot be used: +///
    +///
  • ad_activeview
  • +///
  • ad_click
  • +///
  • ad_exposure
  • +///
  • ad_impression
  • +///
  • ad_query
  • +///
  • adunit_exposure
  • +///
  • app_clear_data
  • +///
  • app_remove
  • +///
  • app_update
  • +///
  • error
  • +///
  • first_open
  • +///
  • in_app_purchase
  • +///
  • notification_dismiss
  • +///
  • notification_foreground
  • +///
  • notification_open
  • +///
  • notification_receive
  • +///
  • os_update
  • +///
  • screen_view
  • +///
  • session_start
  • +///
  • user_engagement
  • +///
+/// +/// @param name The name of the event. Should contain 1 to 40 alphanumeric characters or +/// underscores. The name must start with an alphabetic character. Some event names are +/// reserved. See FIREventNames.h for the list of reserved event names. The "firebase_", +/// "google_", and "ga_" prefixes are reserved and should not be used. Note that event names are +/// case-sensitive and that logging two events whose names differ only in case will result in +/// two distinct events. +/// @param parameters The dictionary of event parameters. Passing nil indicates that the event has +/// no parameters. Parameter names can be up to 40 characters long and must start with an +/// alphabetic character and contain only alphanumeric characters and underscores. Only NSString +/// and NSNumber (signed 64-bit integer and 64-bit floating-point number) parameter types are +/// supported. NSString parameter values can be up to 100 characters long. The "firebase_", +/// "google_", and "ga_" prefixes are reserved and should not be used for parameter names. ++ (void)logEventWithName:(NSString *)name + parameters:(nullable NSDictionary *)parameters + NS_SWIFT_NAME(logEvent(_:parameters:)); + +/// Sets a user property to a given value. Up to 25 user property names are supported. Once set, +/// user property values persist throughout the app lifecycle and across sessions. +/// +/// The following user property names are reserved and cannot be used: +///
    +///
  • first_open_time
  • +///
  • last_deep_link_referrer
  • +///
  • user_id
  • +///
+/// +/// @param value The value of the user property. Values can be up to 36 characters long. Setting the +/// value to nil removes the user property. +/// @param name The name of the user property to set. Should contain 1 to 24 alphanumeric characters +/// or underscores and must start with an alphabetic character. The "firebase_", "google_", and +/// "ga_" prefixes are reserved and should not be used for user property names. ++ (void)setUserPropertyString:(nullable NSString *)value forName:(NSString *)name + NS_SWIFT_NAME(setUserProperty(_:forName:)); + +/// Sets the user ID property. This feature must be used in accordance with +/// Google's Privacy Policy +/// +/// @param userID The user ID to ascribe to the user of this app on this device, which must be +/// non-empty and no more than 256 characters long. Setting userID to nil removes the user ID. ++ (void)setUserID:(nullable NSString *)userID; + +/// Sets the current screen name, which specifies the current visual context in your app. This helps +/// identify the areas in your app where users spend their time and how they interact with your app. +/// Must be called on the main thread. +/// +/// Note that screen reporting is enabled automatically and records the class name of the current +/// UIViewController for you without requiring you to call this method. If you implement +/// viewDidAppear in your UIViewController but do not call [super viewDidAppear:], that screen class +/// will not be automatically tracked. The class name can optionally be overridden by calling this +/// method in the viewDidAppear callback of your UIViewController and specifying the +/// screenClassOverride parameter. setScreenName:screenClass: must be called after +/// [super viewDidAppear:]. +/// +/// If your app does not use a distinct UIViewController for each screen, you should call this +/// method and specify a distinct screenName each time a new screen is presented to the user. +/// +/// The screen name and screen class remain in effect until the current UIViewController changes or +/// a new call to setScreenName:screenClass: is made. +/// +/// @param screenName The name of the current screen. Should contain 1 to 100 characters. Set to nil +/// to clear the current screen name. +/// @param screenClassOverride The name of the screen class. Should contain 1 to 100 characters. By +/// default this is the class name of the current UIViewController. Set to nil to revert to the +/// default class name. ++ (void)setScreenName:(nullable NSString *)screenName + screenClass:(nullable NSString *)screenClassOverride; + +/// Sets whether analytics collection is enabled for this app on this device. This setting is +/// persisted across app sessions. By default it is enabled. +/// +/// @param analyticsCollectionEnabled A flag that enables or disables Analytics collection. ++ (void)setAnalyticsCollectionEnabled:(BOOL)analyticsCollectionEnabled; + +/// Sets the interval of inactivity in seconds that terminates the current session. The default +/// value is 1800 seconds (30 minutes). +/// +/// @param sessionTimeoutInterval The custom time of inactivity in seconds before the current +/// session terminates. ++ (void)setSessionTimeoutInterval:(NSTimeInterval)sessionTimeoutInterval; + +/// The unique ID for this instance of the application. ++ (NSString *)appInstanceID; + +/// Clears all analytics data for this instance from the device and resets the app instance ID. +/// FIRAnalyticsConfiguration values will be reset to the default values. ++ (void)resetAnalyticsData; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Headers/FIREventNames.h b/!main project/Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Headers/FIREventNames.h new file mode 100755 index 0000000..c70c53e --- /dev/null +++ b/!main project/Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Headers/FIREventNames.h @@ -0,0 +1,407 @@ +/// @file FIREventNames.h +/// +/// Predefined event names. +/// +/// An Event is an important occurrence in your app that you want to measure. You can report up to +/// 500 different types of Events per app and you can associate up to 25 unique parameters with each +/// Event type. Some common events are suggested below, but you may also choose to specify custom +/// Event types that are associated with your specific app. Each event type is identified by a +/// unique name. Event names can be up to 40 characters long, may only contain alphanumeric +/// characters and underscores ("_"), and must start with an alphabetic character. The "firebase_", +/// "google_", and "ga_" prefixes are reserved and should not be used. + +#import + +/// Add Payment Info event. This event signifies that a user has submitted their payment information +/// to your app. +static NSString *const kFIREventAddPaymentInfo NS_SWIFT_NAME(AnalyticsEventAddPaymentInfo) = + @"add_payment_info"; + +/// E-Commerce Add To Cart event. This event signifies that an item was added to a cart for +/// purchase. Add this event to a funnel with kFIREventEcommercePurchase to gauge the effectiveness +/// of your checkout process. Note: If you supply the @c kFIRParameterValue parameter, you must +/// also supply the @c kFIRParameterCurrency parameter so that revenue metrics can be computed +/// accurately. Params: +/// +///
    +///
  • @c kFIRParameterQuantity (signed 64-bit integer as NSNumber)
  • +///
  • @c kFIRParameterItemID (NSString)
  • +///
  • @c kFIRParameterItemName (NSString)
  • +///
  • @c kFIRParameterItemCategory (NSString)
  • +///
  • @c kFIRParameterItemLocationID (NSString) (optional)
  • +///
  • @c kFIRParameterPrice (double as NSNumber) (optional)
  • +///
  • @c kFIRParameterCurrency (NSString) (optional)
  • +///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • +///
  • @c kFIRParameterOrigin (NSString) (optional)
  • +///
  • @c kFIRParameterDestination (NSString) (optional)
  • +///
  • @c kFIRParameterStartDate (NSString) (optional)
  • +///
  • @c kFIRParameterEndDate (NSString) (optional)
  • +///
+static NSString *const kFIREventAddToCart NS_SWIFT_NAME(AnalyticsEventAddToCart) = @"add_to_cart"; + +/// E-Commerce Add To Wishlist event. This event signifies that an item was added to a wishlist. +/// Use this event to identify popular gift items in your app. Note: If you supply the +/// @c kFIRParameterValue parameter, you must also supply the @c kFIRParameterCurrency +/// parameter so that revenue metrics can be computed accurately. Params: +/// +///
    +///
  • @c kFIRParameterQuantity (signed 64-bit integer as NSNumber)
  • +///
  • @c kFIRParameterItemID (NSString)
  • +///
  • @c kFIRParameterItemName (NSString)
  • +///
  • @c kFIRParameterItemCategory (NSString)
  • +///
  • @c kFIRParameterItemLocationID (NSString) (optional)
  • +///
  • @c kFIRParameterPrice (double as NSNumber) (optional)
  • +///
  • @c kFIRParameterCurrency (NSString) (optional)
  • +///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • +///
+static NSString *const kFIREventAddToWishlist NS_SWIFT_NAME(AnalyticsEventAddToWishlist) = + @"add_to_wishlist"; + +/// App Open event. By logging this event when an App becomes active, developers can understand how +/// often users leave and return during the course of a Session. Although Sessions are automatically +/// reported, this event can provide further clarification around the continuous engagement of +/// app-users. +static NSString *const kFIREventAppOpen NS_SWIFT_NAME(AnalyticsEventAppOpen) = @"app_open"; + +/// E-Commerce Begin Checkout event. This event signifies that a user has begun the process of +/// checking out. Add this event to a funnel with your kFIREventEcommercePurchase event to gauge the +/// effectiveness of your checkout process. Note: If you supply the @c kFIRParameterValue +/// parameter, you must also supply the @c kFIRParameterCurrency parameter so that revenue +/// metrics can be computed accurately. Params: +/// +///
    +///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • +///
  • @c kFIRParameterCurrency (NSString) (optional)
  • +///
  • @c kFIRParameterTransactionID (NSString) (optional)
  • +///
  • @c kFIRParameterStartDate (NSString) (optional)
  • +///
  • @c kFIRParameterEndDate (NSString) (optional)
  • +///
  • @c kFIRParameterNumberOfNights (signed 64-bit integer as NSNumber) (optional) for +/// hotel bookings
  • +///
  • @c kFIRParameterNumberOfRooms (signed 64-bit integer as NSNumber) (optional) for +/// hotel bookings
  • +///
  • @c kFIRParameterNumberOfPassengers (signed 64-bit integer as NSNumber) (optional) +/// for travel bookings
  • +///
  • @c kFIRParameterOrigin (NSString) (optional)
  • +///
  • @c kFIRParameterDestination (NSString) (optional)
  • +///
  • @c kFIRParameterTravelClass (NSString) (optional) for travel bookings
  • +///
+static NSString *const kFIREventBeginCheckout NS_SWIFT_NAME(AnalyticsEventBeginCheckout) = + @"begin_checkout"; + +/// Campaign Detail event. Log this event to supply the referral details of a re-engagement +/// campaign. Note: you must supply at least one of the required parameters kFIRParameterSource, +/// kFIRParameterMedium or kFIRParameterCampaign. Params: +/// +///
    +///
  • @c kFIRParameterSource (NSString)
  • +///
  • @c kFIRParameterMedium (NSString)
  • +///
  • @c kFIRParameterCampaign (NSString)
  • +///
  • @c kFIRParameterTerm (NSString) (optional)
  • +///
  • @c kFIRParameterContent (NSString) (optional)
  • +///
  • @c kFIRParameterAdNetworkClickID (NSString) (optional)
  • +///
  • @c kFIRParameterCP1 (NSString) (optional)
  • +///
+static NSString *const kFIREventCampaignDetails NS_SWIFT_NAME(AnalyticsEventCampaignDetails) = + @"campaign_details"; + +/// Checkout progress. Params: +/// +///
    +///
  • @c kFIRParameterCheckoutStep (unsigned 64-bit integer as NSNumber)
  • +///
  • @c kFIRParameterCheckoutOption (NSString) (optional)
  • +///
+static NSString *const kFIREventCheckoutProgress NS_SWIFT_NAME(AnalyticsEventCheckoutProgress) = + @"checkout_progress"; + +/// Earn Virtual Currency event. This event tracks the awarding of virtual currency in your app. Log +/// this along with @c kFIREventSpendVirtualCurrency to better understand your virtual economy. +/// Params: +/// +///
    +///
  • @c kFIRParameterVirtualCurrencyName (NSString)
  • +///
  • @c kFIRParameterValue (signed 64-bit integer or double as NSNumber)
  • +///
+static NSString *const kFIREventEarnVirtualCurrency + NS_SWIFT_NAME(AnalyticsEventEarnVirtualCurrency) = @"earn_virtual_currency"; + +/// E-Commerce Purchase event. This event signifies that an item was purchased by a user. Note: +/// This is different from the in-app purchase event, which is reported automatically for App +/// Store-based apps. Note: If you supply the @c kFIRParameterValue parameter, you must also +/// supply the @c kFIRParameterCurrency parameter so that revenue metrics can be computed +/// accurately. Params: +/// +///
    +///
  • @c kFIRParameterCurrency (NSString) (optional)
  • +///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • +///
  • @c kFIRParameterTransactionID (NSString) (optional)
  • +///
  • @c kFIRParameterTax (double as NSNumber) (optional)
  • +///
  • @c kFIRParameterShipping (double as NSNumber) (optional)
  • +///
  • @c kFIRParameterCoupon (NSString) (optional)
  • +///
  • @c kFIRParameterLocation (NSString) (optional)
  • +///
  • @c kFIRParameterStartDate (NSString) (optional)
  • +///
  • @c kFIRParameterEndDate (NSString) (optional)
  • +///
  • @c kFIRParameterNumberOfNights (signed 64-bit integer as NSNumber) (optional) for +/// hotel bookings
  • +///
  • @c kFIRParameterNumberOfRooms (signed 64-bit integer as NSNumber) (optional) for +/// hotel bookings
  • +///
  • @c kFIRParameterNumberOfPassengers (signed 64-bit integer as NSNumber) (optional) +/// for travel bookings
  • +///
  • @c kFIRParameterOrigin (NSString) (optional)
  • +///
  • @c kFIRParameterDestination (NSString) (optional)
  • +///
  • @c kFIRParameterTravelClass (NSString) (optional) for travel bookings
  • +///
+static NSString *const kFIREventEcommercePurchase NS_SWIFT_NAME(AnalyticsEventEcommercePurchase) = + @"ecommerce_purchase"; + +/// Generate Lead event. Log this event when a lead has been generated in the app to understand the +/// efficacy of your install and re-engagement campaigns. Note: If you supply the +/// @c kFIRParameterValue parameter, you must also supply the @c kFIRParameterCurrency +/// parameter so that revenue metrics can be computed accurately. Params: +/// +///
    +///
  • @c kFIRParameterCurrency (NSString) (optional)
  • +///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • +///
+static NSString *const kFIREventGenerateLead NS_SWIFT_NAME(AnalyticsEventGenerateLead) = + @"generate_lead"; + +/// Join Group event. Log this event when a user joins a group such as a guild, team or family. Use +/// this event to analyze how popular certain groups or social features are in your app. Params: +/// +///
    +///
  • @c kFIRParameterGroupID (NSString)
  • +///
+static NSString *const kFIREventJoinGroup NS_SWIFT_NAME(AnalyticsEventJoinGroup) = @"join_group"; + +/// Level Up event. This event signifies that a player has leveled up in your gaming app. It can +/// help you gauge the level distribution of your userbase and help you identify certain levels that +/// are difficult to pass. Params: +/// +///
    +///
  • @c kFIRParameterLevel (signed 64-bit integer as NSNumber)
  • +///
  • @c kFIRParameterCharacter (NSString) (optional)
  • +///
+static NSString *const kFIREventLevelUp NS_SWIFT_NAME(AnalyticsEventLevelUp) = @"level_up"; + +/// Login event. Apps with a login feature can report this event to signify that a user has logged +/// in. +static NSString *const kFIREventLogin NS_SWIFT_NAME(AnalyticsEventLogin) = @"login"; + +/// Post Score event. Log this event when the user posts a score in your gaming app. This event can +/// help you understand how users are actually performing in your game and it can help you correlate +/// high scores with certain audiences or behaviors. Params: +/// +///
    +///
  • @c kFIRParameterScore (signed 64-bit integer as NSNumber)
  • +///
  • @c kFIRParameterLevel (signed 64-bit integer as NSNumber) (optional)
  • +///
  • @c kFIRParameterCharacter (NSString) (optional)
  • +///
+static NSString *const kFIREventPostScore NS_SWIFT_NAME(AnalyticsEventPostScore) = @"post_score"; + +/// Present Offer event. This event signifies that the app has presented a purchase offer to a user. +/// Add this event to a funnel with the kFIREventAddToCart and kFIREventEcommercePurchase to gauge +/// your conversion process. Note: If you supply the @c kFIRParameterValue parameter, you must +/// also supply the @c kFIRParameterCurrency parameter so that revenue metrics can be computed +/// accurately. Params: +/// +///
    +///
  • @c kFIRParameterQuantity (signed 64-bit integer as NSNumber)
  • +///
  • @c kFIRParameterItemID (NSString)
  • +///
  • @c kFIRParameterItemName (NSString)
  • +///
  • @c kFIRParameterItemCategory (NSString)
  • +///
  • @c kFIRParameterItemLocationID (NSString) (optional)
  • +///
  • @c kFIRParameterPrice (double as NSNumber) (optional)
  • +///
  • @c kFIRParameterCurrency (NSString) (optional)
  • +///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • +///
+static NSString *const kFIREventPresentOffer NS_SWIFT_NAME(AnalyticsEventPresentOffer) = + @"present_offer"; + +/// E-Commerce Purchase Refund event. This event signifies that an item purchase was refunded. +/// Note: If you supply the @c kFIRParameterValue parameter, you must also supply the +/// @c kFIRParameterCurrency parameter so that revenue metrics can be computed accurately. +/// Params: +/// +///
    +///
  • @c kFIRParameterCurrency (NSString) (optional)
  • +///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • +///
  • @c kFIRParameterTransactionID (NSString) (optional)
  • +///
+static NSString *const kFIREventPurchaseRefund NS_SWIFT_NAME(AnalyticsEventPurchaseRefund) = + @"purchase_refund"; + +/// Remove from cart event. Params: +/// +///
    +///
  • @c kFIRParameterQuantity (signed 64-bit integer as NSNumber)
  • +///
  • @c kFIRParameterItemID (NSString)
  • +///
  • @c kFIRParameterItemName (NSString)
  • +///
  • @c kFIRParameterItemCategory (NSString)
  • +///
  • @c kFIRParameterItemLocationID (NSString) (optional)
  • +///
  • @c kFIRParameterPrice (double as NSNumber) (optional)
  • +///
  • @c kFIRParameterCurrency (NSString) (optional)
  • +///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • +///
  • @c kFIRParameterOrigin (NSString) (optional)
  • +///
  • @c kFIRParameterDestination (NSString) (optional)
  • +///
  • @c kFIRParameterStartDate (NSString) (optional)
  • +///
  • @c kFIRParameterEndDate (NSString) (optional)
  • +///
+static NSString *const kFIREventRemoveFromCart NS_SWIFT_NAME(AnalyticsEventRemoveFromCart) = + @"remove_from_cart"; + +/// Search event. Apps that support search features can use this event to contextualize search +/// operations by supplying the appropriate, corresponding parameters. This event can help you +/// identify the most popular content in your app. Params: +/// +///
    +///
  • @c kFIRParameterSearchTerm (NSString)
  • +///
  • @c kFIRParameterStartDate (NSString) (optional)
  • +///
  • @c kFIRParameterEndDate (NSString) (optional)
  • +///
  • @c kFIRParameterNumberOfNights (signed 64-bit integer as NSNumber) (optional) for +/// hotel bookings
  • +///
  • @c kFIRParameterNumberOfRooms (signed 64-bit integer as NSNumber) (optional) for +/// hotel bookings
  • +///
  • @c kFIRParameterNumberOfPassengers (signed 64-bit integer as NSNumber) (optional) +/// for travel bookings
  • +///
  • @c kFIRParameterOrigin (NSString) (optional)
  • +///
  • @c kFIRParameterDestination (NSString) (optional)
  • +///
  • @c kFIRParameterTravelClass (NSString) (optional) for travel bookings
  • +///
+static NSString *const kFIREventSearch NS_SWIFT_NAME(AnalyticsEventSearch) = @"search"; + +/// Select Content event. This general purpose event signifies that a user has selected some content +/// of a certain type in an app. The content can be any object in your app. This event can help you +/// identify popular content and categories of content in your app. Params: +/// +///
    +///
  • @c kFIRParameterContentType (NSString)
  • +///
  • @c kFIRParameterItemID (NSString)
  • +///
+static NSString *const kFIREventSelectContent NS_SWIFT_NAME(AnalyticsEventSelectContent) = + @"select_content"; + +/// Set checkout option. Params: +/// +///
    +///
  • @c kFIRParameterCheckoutStep (unsigned 64-bit integer as NSNumber)
  • +///
  • @c kFIRParameterCheckoutOption (NSString)
  • +///
+static NSString *const kFIREventSetCheckoutOption NS_SWIFT_NAME(AnalyticsEventSetCheckoutOption) = + @"set_checkout_option"; + +/// Share event. Apps with social features can log the Share event to identify the most viral +/// content. Params: +/// +///
    +///
  • @c kFIRParameterContentType (NSString)
  • +///
  • @c kFIRParameterItemID (NSString)
  • +///
+static NSString *const kFIREventShare NS_SWIFT_NAME(AnalyticsEventShare) = @"share"; + +/// Sign Up event. This event indicates that a user has signed up for an account in your app. The +/// parameter signifies the method by which the user signed up. Use this event to understand the +/// different behaviors between logged in and logged out users. Params: +/// +///
    +///
  • @c kFIRParameterSignUpMethod (NSString)
  • +///
+static NSString *const kFIREventSignUp NS_SWIFT_NAME(AnalyticsEventSignUp) = @"sign_up"; + +/// Spend Virtual Currency event. This event tracks the sale of virtual goods in your app and can +/// help you identify which virtual goods are the most popular objects of purchase. Params: +/// +///
    +///
  • @c kFIRParameterItemName (NSString)
  • +///
  • @c kFIRParameterVirtualCurrencyName (NSString)
  • +///
  • @c kFIRParameterValue (signed 64-bit integer or double as NSNumber)
  • +///
+static NSString *const kFIREventSpendVirtualCurrency + NS_SWIFT_NAME(AnalyticsEventSpendVirtualCurrency) = @"spend_virtual_currency"; + +/// Tutorial Begin event. This event signifies the start of the on-boarding process in your app. Use +/// this in a funnel with kFIREventTutorialComplete to understand how many users complete this +/// process and move on to the full app experience. +static NSString *const kFIREventTutorialBegin NS_SWIFT_NAME(AnalyticsEventTutorialBegin) = + @"tutorial_begin"; + +/// Tutorial End event. Use this event to signify the user's completion of your app's on-boarding +/// process. Add this to a funnel with kFIREventTutorialBegin to gauge the completion rate of your +/// on-boarding process. +static NSString *const kFIREventTutorialComplete NS_SWIFT_NAME(AnalyticsEventTutorialComplete) = + @"tutorial_complete"; + +/// Unlock Achievement event. Log this event when the user has unlocked an achievement in your +/// game. Since achievements generally represent the breadth of a gaming experience, this event can +/// help you understand how many users are experiencing all that your game has to offer. Params: +/// +///
    +///
  • @c kFIRParameterAchievementID (NSString)
  • +///
+static NSString *const kFIREventUnlockAchievement NS_SWIFT_NAME(AnalyticsEventUnlockAchievement) = + @"unlock_achievement"; + +/// View Item event. This event signifies that some content was shown to the user. This content may +/// be a product, a webpage or just a simple image or text. Use the appropriate parameters to +/// contextualize the event. Use this event to discover the most popular items viewed in your app. +/// Note: If you supply the @c kFIRParameterValue parameter, you must also supply the +/// @c kFIRParameterCurrency parameter so that revenue metrics can be computed accurately. +/// Params: +/// +///
    +///
  • @c kFIRParameterItemID (NSString)
  • +///
  • @c kFIRParameterItemName (NSString)
  • +///
  • @c kFIRParameterItemCategory (NSString)
  • +///
  • @c kFIRParameterItemLocationID (NSString) (optional)
  • +///
  • @c kFIRParameterPrice (double as NSNumber) (optional)
  • +///
  • @c kFIRParameterQuantity (signed 64-bit integer as NSNumber) (optional)
  • +///
  • @c kFIRParameterCurrency (NSString) (optional)
  • +///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • +///
  • @c kFIRParameterStartDate (NSString) (optional)
  • +///
  • @c kFIRParameterEndDate (NSString) (optional)
  • +///
  • @c kFIRParameterFlightNumber (NSString) (optional) for travel bookings
  • +///
  • @c kFIRParameterNumberOfPassengers (signed 64-bit integer as NSNumber) (optional) +/// for travel bookings
  • +///
  • @c kFIRParameterNumberOfNights (signed 64-bit integer as NSNumber) (optional) for +/// travel bookings
  • +///
  • @c kFIRParameterNumberOfRooms (signed 64-bit integer as NSNumber) (optional) for +/// travel bookings
  • +///
  • @c kFIRParameterOrigin (NSString) (optional)
  • +///
  • @c kFIRParameterDestination (NSString) (optional)
  • +///
  • @c kFIRParameterSearchTerm (NSString) (optional) for travel bookings
  • +///
  • @c kFIRParameterTravelClass (NSString) (optional) for travel bookings
  • +///
+static NSString *const kFIREventViewItem NS_SWIFT_NAME(AnalyticsEventViewItem) = @"view_item"; + +/// View Item List event. Log this event when the user has been presented with a list of items of a +/// certain category. Params: +/// +///
    +///
  • @c kFIRParameterItemCategory (NSString)
  • +///
+static NSString *const kFIREventViewItemList NS_SWIFT_NAME(AnalyticsEventViewItemList) = + @"view_item_list"; + +/// View Search Results event. Log this event when the user has been presented with the results of a +/// search. Params: +/// +///
    +///
  • @c kFIRParameterSearchTerm (NSString)
  • +///
+static NSString *const kFIREventViewSearchResults NS_SWIFT_NAME(AnalyticsEventViewSearchResults) = + @"view_search_results"; + +/// Level Start event. Log this event when the user starts a new level. Params: +/// +///
    +///
  • @c kFIRParameterLevelName (NSString)
  • +///
+static NSString *const kFIREventLevelStart NS_SWIFT_NAME(AnalyticsEventLevelStart) = + @"level_start"; + +/// Level End event. Log this event when the user finishes a level. Params: +/// +///
    +///
  • @c kFIRParameterLevelName (NSString)
  • +///
  • @c kFIRParameterSuccess (NSString)
  • +///
+static NSString *const kFIREventLevelEnd NS_SWIFT_NAME(AnalyticsEventLevelEnd) = @"level_end"; diff --git a/!main project/Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Headers/FIRParameterNames.h b/!main project/Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Headers/FIRParameterNames.h new file mode 100755 index 0000000..ad9fff7 --- /dev/null +++ b/!main project/Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Headers/FIRParameterNames.h @@ -0,0 +1,532 @@ +/// @file FIRParameterNames.h +/// +/// Predefined event parameter names. +/// +/// Params supply information that contextualize Events. You can associate up to 25 unique Params +/// with each Event type. Some Params are suggested below for certain common Events, but you are +/// not limited to these. You may supply extra Params for suggested Events or custom Params for +/// Custom events. Param names can be up to 40 characters long, may only contain alphanumeric +/// characters and underscores ("_"), and must start with an alphabetic character. Param values can +/// be up to 100 characters long. The "firebase_", "google_", and "ga_" prefixes are reserved and +/// should not be used. + +#import + +/// Game achievement ID (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterAchievementID : @"10_matches_won",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterAchievementID NS_SWIFT_NAME(AnalyticsParameterAchievementID) = + @"achievement_id"; + +/// Ad Network Click ID (NSString). Used for network-specific click IDs which vary in format. +///
+///     NSDictionary *params = @{
+///       kFIRParameterAdNetworkClickID : @"1234567",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterAdNetworkClickID + NS_SWIFT_NAME(AnalyticsParameterAdNetworkClickID) = @"aclid"; + +/// The store or affiliation from which this transaction occurred (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterAffiliation : @"Google Store",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterAffiliation NS_SWIFT_NAME(AnalyticsParameterAffiliation) = + @"affiliation"; + +/// The individual campaign name, slogan, promo code, etc. Some networks have pre-defined macro to +/// capture campaign information, otherwise can be populated by developer. Highly Recommended +/// (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterCampaign : @"winter_promotion",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterCampaign NS_SWIFT_NAME(AnalyticsParameterCampaign) = + @"campaign"; + +/// Character used in game (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterCharacter : @"beat_boss",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterCharacter NS_SWIFT_NAME(AnalyticsParameterCharacter) = + @"character"; + +/// The checkout step (1..N) (unsigned 64-bit integer as NSNumber). +///
+///     NSDictionary *params = @{
+///       kFIRParameterCheckoutStep : @"1",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterCheckoutStep NS_SWIFT_NAME(AnalyticsParameterCheckoutStep) = + @"checkout_step"; + +/// Some option on a step in an ecommerce flow (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterCheckoutOption : @"Visa",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterCheckoutOption + NS_SWIFT_NAME(AnalyticsParameterCheckoutOption) = @"checkout_option"; + +/// Campaign content (NSString). +static NSString *const kFIRParameterContent NS_SWIFT_NAME(AnalyticsParameterContent) = @"content"; + +/// Type of content selected (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterContentType : @"news article",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterContentType NS_SWIFT_NAME(AnalyticsParameterContentType) = + @"content_type"; + +/// Coupon code for a purchasable item (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterCoupon : @"zz123",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterCoupon NS_SWIFT_NAME(AnalyticsParameterCoupon) = @"coupon"; + +/// Campaign custom parameter (NSString). Used as a method of capturing custom data in a campaign. +/// Use varies by network. +///
+///     NSDictionary *params = @{
+///       kFIRParameterCP1 : @"custom_data",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterCP1 NS_SWIFT_NAME(AnalyticsParameterCP1) = @"cp1"; + +/// The name of a creative used in a promotional spot (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterCreativeName : @"Summer Sale",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterCreativeName NS_SWIFT_NAME(AnalyticsParameterCreativeName) = + @"creative_name"; + +/// The name of a creative slot (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterCreativeSlot : @"summer_banner2",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterCreativeSlot NS_SWIFT_NAME(AnalyticsParameterCreativeSlot) = + @"creative_slot"; + +/// Purchase currency in 3-letter +/// ISO_4217 format (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterCurrency : @"USD",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterCurrency NS_SWIFT_NAME(AnalyticsParameterCurrency) = + @"currency"; + +/// Flight or Travel destination (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterDestination : @"Mountain View, CA",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterDestination NS_SWIFT_NAME(AnalyticsParameterDestination) = + @"destination"; + +/// The arrival date, check-out date or rental end date for the item. This should be in +/// YYYY-MM-DD format (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterEndDate : @"2015-09-14",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterEndDate NS_SWIFT_NAME(AnalyticsParameterEndDate) = @"end_date"; + +/// Flight number for travel events (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterFlightNumber : @"ZZ800",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterFlightNumber NS_SWIFT_NAME(AnalyticsParameterFlightNumber) = + @"flight_number"; + +/// Group/clan/guild ID (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterGroupID : @"g1",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterGroupID NS_SWIFT_NAME(AnalyticsParameterGroupID) = @"group_id"; + +/// Index of an item in a list (signed 64-bit integer as NSNumber). +///
+///     NSDictionary *params = @{
+///       kFIRParameterIndex : @(1),
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterIndex NS_SWIFT_NAME(AnalyticsParameterIndex) = @"index"; + +/// Item brand (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterItemBrand : @"Google",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterItemBrand NS_SWIFT_NAME(AnalyticsParameterItemBrand) = + @"item_brand"; + +/// Item category (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterItemCategory : @"t-shirts",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterItemCategory NS_SWIFT_NAME(AnalyticsParameterItemCategory) = + @"item_category"; + +/// Item ID (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterItemID : @"p7654",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterItemID NS_SWIFT_NAME(AnalyticsParameterItemID) = @"item_id"; + +/// The Google Place ID (NSString) that +/// corresponds to the associated item. Alternatively, you can supply your own custom Location ID. +///
+///     NSDictionary *params = @{
+///       kFIRParameterItemLocationID : @"ChIJiyj437sx3YAR9kUWC8QkLzQ",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterItemLocationID + NS_SWIFT_NAME(AnalyticsParameterItemLocationID) = @"item_location_id"; + +/// Item name (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterItemName : @"abc",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterItemName NS_SWIFT_NAME(AnalyticsParameterItemName) = + @"item_name"; + +/// The list in which the item was presented to the user (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterItemList : @"Search Results",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterItemList NS_SWIFT_NAME(AnalyticsParameterItemList) = + @"item_list"; + +/// Item variant (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterItemVariant : @"Red",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterItemVariant NS_SWIFT_NAME(AnalyticsParameterItemVariant) = + @"item_variant"; + +/// Level in game (signed 64-bit integer as NSNumber). +///
+///     NSDictionary *params = @{
+///       kFIRParameterLevel : @(42),
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterLevel NS_SWIFT_NAME(AnalyticsParameterLevel) = @"level"; + +/// Location (NSString). The Google Place ID +/// that corresponds to the associated event. Alternatively, you can supply your own custom +/// Location ID. +///
+///     NSDictionary *params = @{
+///       kFIRParameterLocation : @"ChIJiyj437sx3YAR9kUWC8QkLzQ",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterLocation NS_SWIFT_NAME(AnalyticsParameterLocation) = + @"location"; + +/// The advertising or marketing medium, for example: cpc, banner, email, push. Highly recommended +/// (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterMedium : @"email",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterMedium NS_SWIFT_NAME(AnalyticsParameterMedium) = @"medium"; + +/// Number of nights staying at hotel (signed 64-bit integer as NSNumber). +///
+///     NSDictionary *params = @{
+///       kFIRParameterNumberOfNights : @(3),
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterNumberOfNights + NS_SWIFT_NAME(AnalyticsParameterNumberOfNights) = @"number_of_nights"; + +/// Number of passengers traveling (signed 64-bit integer as NSNumber). +///
+///     NSDictionary *params = @{
+///       kFIRParameterNumberOfPassengers : @(11),
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterNumberOfPassengers + NS_SWIFT_NAME(AnalyticsParameterNumberOfPassengers) = @"number_of_passengers"; + +/// Number of rooms for travel events (signed 64-bit integer as NSNumber). +///
+///     NSDictionary *params = @{
+///       kFIRParameterNumberOfRooms : @(2),
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterNumberOfRooms NS_SWIFT_NAME(AnalyticsParameterNumberOfRooms) = + @"number_of_rooms"; + +/// Flight or Travel origin (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterOrigin : @"Mountain View, CA",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterOrigin NS_SWIFT_NAME(AnalyticsParameterOrigin) = @"origin"; + +/// Purchase price (double as NSNumber). +///
+///     NSDictionary *params = @{
+///       kFIRParameterPrice : @(1.0),
+///       kFIRParameterCurrency : @"USD",  // e.g. $1.00 USD
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterPrice NS_SWIFT_NAME(AnalyticsParameterPrice) = @"price"; + +/// Purchase quantity (signed 64-bit integer as NSNumber). +///
+///     NSDictionary *params = @{
+///       kFIRParameterQuantity : @(1),
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterQuantity NS_SWIFT_NAME(AnalyticsParameterQuantity) = + @"quantity"; + +/// Score in game (signed 64-bit integer as NSNumber). +///
+///     NSDictionary *params = @{
+///       kFIRParameterScore : @(4200),
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterScore NS_SWIFT_NAME(AnalyticsParameterScore) = @"score"; + +/// The search string/keywords used (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterSearchTerm : @"periodic table",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterSearchTerm NS_SWIFT_NAME(AnalyticsParameterSearchTerm) = + @"search_term"; + +/// Shipping cost (double as NSNumber). +///
+///     NSDictionary *params = @{
+///       kFIRParameterShipping : @(9.50),
+///       kFIRParameterCurrency : @"USD",  // e.g. $9.50 USD
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterShipping NS_SWIFT_NAME(AnalyticsParameterShipping) = + @"shipping"; + +/// Sign up method (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterSignUpMethod : @"google",
+///       // ...
+///     };
+/// 
+/// +/// This constant has been deprecated. Use Method constant instead. +static NSString *const kFIRParameterSignUpMethod NS_SWIFT_NAME(AnalyticsParameterSignUpMethod) = + @"sign_up_method"; + +/// A particular approach used in an operation; for example, "facebook" or "email" in the context +/// of a sign_up or login event. (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterMethod : @"google",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterMethod NS_SWIFT_NAME(AnalyticsParameterMethod) = @"method"; + +/// The origin of your traffic, such as an Ad network (for example, google) or partner (urban +/// airship). Identify the advertiser, site, publication, etc. that is sending traffic to your +/// property. Highly recommended (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterSource : @"InMobi",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterSource NS_SWIFT_NAME(AnalyticsParameterSource) = @"source"; + +/// The departure date, check-in date or rental start date for the item. This should be in +/// YYYY-MM-DD format (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterStartDate : @"2015-09-14",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterStartDate NS_SWIFT_NAME(AnalyticsParameterStartDate) = + @"start_date"; + +/// Tax amount (double as NSNumber). +///
+///     NSDictionary *params = @{
+///       kFIRParameterTax : @(1.0),
+///       kFIRParameterCurrency : @"USD",  // e.g. $1.00 USD
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterTax NS_SWIFT_NAME(AnalyticsParameterTax) = @"tax"; + +/// If you're manually tagging keyword campaigns, you should use utm_term to specify the keyword +/// (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterTerm : @"game",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterTerm NS_SWIFT_NAME(AnalyticsParameterTerm) = @"term"; + +/// A single ID for a ecommerce group transaction (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterTransactionID : @"ab7236dd9823",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterTransactionID NS_SWIFT_NAME(AnalyticsParameterTransactionID) = + @"transaction_id"; + +/// Travel class (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterTravelClass : @"business",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterTravelClass NS_SWIFT_NAME(AnalyticsParameterTravelClass) = + @"travel_class"; + +/// A context-specific numeric value which is accumulated automatically for each event type. This is +/// a general purpose parameter that is useful for accumulating a key metric that pertains to an +/// event. Examples include revenue, distance, time and points. Value should be specified as signed +/// 64-bit integer or double as NSNumber. Notes: Values for pre-defined currency-related events +/// (such as @c kFIREventAddToCart) should be supplied using double as NSNumber and must be +/// accompanied by a @c kFIRParameterCurrency parameter. The valid range of accumulated values is +/// [-9,223,372,036,854.77, 9,223,372,036,854.77]. Supplying a non-numeric value, omitting the +/// corresponding @c kFIRParameterCurrency parameter, or supplying an invalid +/// currency code for conversion events will cause that +/// conversion to be omitted from reporting. +///
+///     NSDictionary *params = @{
+///       kFIRParameterValue : @(3.99),
+///       kFIRParameterCurrency : @"USD",  // e.g. $3.99 USD
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterValue NS_SWIFT_NAME(AnalyticsParameterValue) = @"value"; + +/// Name of virtual currency type (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterVirtualCurrencyName : @"virtual_currency_name",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterVirtualCurrencyName + NS_SWIFT_NAME(AnalyticsParameterVirtualCurrencyName) = @"virtual_currency_name"; + +/// The name of a level in a game (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterLevelName : @"room_1",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterLevelName NS_SWIFT_NAME(AnalyticsParameterLevelName) = + @"level_name"; + +/// The result of an operation. Specify 1 to indicate success and 0 to indicate failure (unsigned +/// integer as NSNumber). +///
+///     NSDictionary *params = @{
+///       kFIRParameterSuccess : @(1),
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterSuccess NS_SWIFT_NAME(AnalyticsParameterSuccess) = @"success"; + +/// Indicates that the associated event should either extend the current session +/// or start a new session if no session was active when the event was logged. +/// Specify YES to extend the current session or to start a new session; any +/// other value will not extend or start a session. +///
+///     NSDictionary *params = @{
+///       kFIRParameterExtendSession : @YES,
+///       // ...
+///     };
+/// 
+static NSString *const kFIRParameterExtendSession NS_SWIFT_NAME(AnalyticsParameterExtendSession) = + @"extend_session"; diff --git a/!main project/Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Headers/FIRUserPropertyNames.h b/!main project/Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Headers/FIRUserPropertyNames.h new file mode 100755 index 0000000..132aef7 --- /dev/null +++ b/!main project/Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Headers/FIRUserPropertyNames.h @@ -0,0 +1,29 @@ +/// @file FIRUserPropertyNames.h +/// +/// Predefined user property names. +/// +/// A UserProperty is an attribute that describes the app-user. By supplying UserProperties, you can +/// later analyze different behaviors of various segments of your userbase. You may supply up to 25 +/// unique UserProperties per app, and you can use the name and value of your choosing for each one. +/// UserProperty names can be up to 24 characters long, may only contain alphanumeric characters and +/// underscores ("_"), and must start with an alphabetic character. UserProperty values can be up to +/// 36 characters long. The "firebase_", "google_", and "ga_" prefixes are reserved and should not +/// be used. + +#import + +/// The method used to sign in. For example, "google", "facebook" or "twitter". +static NSString *const kFIRUserPropertySignUpMethod + NS_SWIFT_NAME(AnalyticsUserPropertySignUpMethod) = @"sign_up_method"; + +/// Indicates whether events logged by Google Analytics can be used to personalize ads for the user. +/// Set to "YES" to enable, or "NO" to disable. Default is enabled. See the +/// documentation for +/// more details and information about related settings. +/// +///
+///     [FIRAnalytics setUserPropertyString:@"NO"
+///                                 forName:kFIRUserPropertyAllowAdPersonalizationSignals];
+/// 
+static NSString *const kFIRUserPropertyAllowAdPersonalizationSignals + NS_SWIFT_NAME(AnalyticsUserPropertyAllowAdPersonalizationSignals) = @"allow_personalized_ads"; diff --git a/!main project/Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Headers/FirebaseAnalytics.h b/!main project/Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Headers/FirebaseAnalytics.h new file mode 100755 index 0000000..ed7588a --- /dev/null +++ b/!main project/Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Headers/FirebaseAnalytics.h @@ -0,0 +1,5 @@ +#import "FIRAnalytics+AppDelegate.h" +#import "FIRAnalytics.h" +#import "FIREventNames.h" +#import "FIRParameterNames.h" +#import "FIRUserPropertyNames.h" diff --git a/!main project/Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Modules/module.modulemap b/!main project/Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Modules/module.modulemap new file mode 100755 index 0000000..d7c5905 --- /dev/null +++ b/!main project/Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Modules/module.modulemap @@ -0,0 +1,12 @@ +framework module FirebaseAnalytics { + umbrella header "FirebaseAnalytics.h" + export * + module * { export * } + link "sqlite3" + link "z" + link framework "CoreData" + link framework "Security" + link framework "StoreKit" + link framework "SystemConfiguration" + link framework "UIKit" +} diff --git a/!main project/Pods/FirebaseAnalyticsInterop/Interop/Analytics/Public/FIRAnalyticsInterop.h b/!main project/Pods/FirebaseAnalyticsInterop/Interop/Analytics/Public/FIRAnalyticsInterop.h new file mode 100644 index 0000000..6581b53 --- /dev/null +++ b/!main project/Pods/FirebaseAnalyticsInterop/Interop/Analytics/Public/FIRAnalyticsInterop.h @@ -0,0 +1,66 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@protocol FIRAnalyticsInteropListener; + +NS_ASSUME_NONNULL_BEGIN + +/// Block typedef callback parameter to getUserPropertiesWithCallback:. +typedef void (^FIRAInteropUserPropertiesCallback)(NSDictionary *userProperties); + +/// Connector for bridging communication between Firebase SDKs and FirebaseAnalytics API. +@protocol FIRAnalyticsInterop + +/// Sets user property when trigger event is logged. This API is only available in the SDK. +- (void)setConditionalUserProperty:(NSDictionary *)conditionalUserProperty; + +/// Clears user property if set. +- (void)clearConditionalUserProperty:(NSString *)userPropertyName + forOrigin:(NSString *)origin + clearEventName:(NSString *)clearEventName + clearEventParameters:(NSDictionary *)clearEventParameters; + +/// Returns currently set user properties. +- (NSArray *> *)conditionalUserProperties:(NSString *)origin + propertyNamePrefix: + (NSString *)propertyNamePrefix; + +/// Returns the maximum number of user properties. +- (NSInteger)maxUserProperties:(NSString *)origin; + +/// Returns the user properties to a callback function. +- (void)getUserPropertiesWithCallback:(FIRAInteropUserPropertiesCallback)callback; + +/// Logs events. +- (void)logEventWithOrigin:(NSString *)origin + name:(NSString *)name + parameters:(nullable NSDictionary *)parameters; + +/// Sets user property. +- (void)setUserPropertyWithOrigin:(NSString *)origin name:(NSString *)name value:(id)value; + +/// Registers an Analytics listener for the given origin. +- (void)registerAnalyticsListener:(id)listener + withOrigin:(NSString *)origin; + +/// Unregisters an Analytics listener for the given origin. +- (void)unregisterAnalyticsListenerWithOrigin:(NSString *)origin; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAnalyticsInterop/Interop/Analytics/Public/FIRAnalyticsInteropListener.h b/!main project/Pods/FirebaseAnalyticsInterop/Interop/Analytics/Public/FIRAnalyticsInteropListener.h new file mode 100644 index 0000000..45cde55 --- /dev/null +++ b/!main project/Pods/FirebaseAnalyticsInterop/Interop/Analytics/Public/FIRAnalyticsInteropListener.h @@ -0,0 +1,24 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/// Handles events and messages from Analytics. +@protocol FIRAnalyticsInteropListener + +/// Triggers when an Analytics event happens for the registered origin with +/// `FIRAnalyticsInterop`s `registerAnalyticsListener:withOrigin:`. +- (void)messageTriggered:(NSString *)name parameters:(NSDictionary *)parameters; + +@end \ No newline at end of file diff --git a/!main project/Pods/FirebaseAnalyticsInterop/Interop/Analytics/Public/FIRInteropEventNames.h b/!main project/Pods/FirebaseAnalyticsInterop/Interop/Analytics/Public/FIRInteropEventNames.h new file mode 100644 index 0000000..efc54ab --- /dev/null +++ b/!main project/Pods/FirebaseAnalyticsInterop/Interop/Analytics/Public/FIRInteropEventNames.h @@ -0,0 +1,28 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/// @file FIRInteropEventNames.h + +#import + +/// Notification open event name. +static NSString *const kFIRIEventNotificationOpen = @"_no"; + +/// Notification foreground event name. +static NSString *const kFIRIEventNotificationForeground = @"_nf"; + +/// Campaign event name. +static NSString *const kFIRIEventFirebaseCampaign = @"_cmp"; diff --git a/!main project/Pods/FirebaseAnalyticsInterop/Interop/Analytics/Public/FIRInteropParameterNames.h b/!main project/Pods/FirebaseAnalyticsInterop/Interop/Analytics/Public/FIRInteropParameterNames.h new file mode 100644 index 0000000..ae440be --- /dev/null +++ b/!main project/Pods/FirebaseAnalyticsInterop/Interop/Analytics/Public/FIRInteropParameterNames.h @@ -0,0 +1,73 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +/// @file FIRInteropParameterNames.h +/// +/// Predefined event parameter names used by Firebase. This file is a subset of the +/// FirebaseAnalytics FIRParameterNames.h public header. +/// +/// The origin of your traffic, such as an Ad network (for example, google) or partner (urban +/// airship). Identify the advertiser, site, publication, etc. that is sending traffic to your +/// property. Highly recommended (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterSource : @"InMobi",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRIParameterSource NS_SWIFT_NAME(AnalyticsParameterSource) = @"source"; + +/// The advertising or marketing medium, for example: cpc, banner, email, push. Highly recommended +/// (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterMedium : @"email",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRIParameterMedium NS_SWIFT_NAME(AnalyticsParameterMedium) = @"medium"; + +/// The individual campaign name, slogan, promo code, etc. Some networks have pre-defined macro to +/// capture campaign information, otherwise can be populated by developer. Highly Recommended +/// (NSString). +///
+///     NSDictionary *params = @{
+///       kFIRParameterCampaign : @"winter_promotion",
+///       // ...
+///     };
+/// 
+static NSString *const kFIRIParameterCampaign NS_SWIFT_NAME(AnalyticsParameterCampaign) = + @"campaign"; + +/// Message identifier. +static NSString *const kFIRIParameterMessageIdentifier = @"_nmid"; + +/// Message name. +static NSString *const kFIRIParameterMessageName = @"_nmn"; + +/// Message send time. +static NSString *const kFIRIParameterMessageTime = @"_nmt"; + +/// Message device time. +static NSString *const kFIRIParameterMessageDeviceTime = @"_ndt"; + +/// Topic message. +static NSString *const kFIRIParameterTopic = @"_nt"; + +/// Stores the message_id of the last notification opened by the app. +static NSString *const kFIRIUserPropertyLastNotification = @"_ln"; diff --git a/!main project/Pods/FirebaseAnalyticsInterop/LICENSE b/!main project/Pods/FirebaseAnalyticsInterop/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/!main project/Pods/FirebaseAnalyticsInterop/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/!main project/Pods/FirebaseAnalyticsInterop/README.md b/!main project/Pods/FirebaseAnalyticsInterop/README.md new file mode 100644 index 0000000..3ddc8fb --- /dev/null +++ b/!main project/Pods/FirebaseAnalyticsInterop/README.md @@ -0,0 +1,251 @@ +# Firebase iOS Open Source Development [![Build Status](https://travis-ci.org/firebase/firebase-ios-sdk.svg?branch=master)](https://travis-ci.org/firebase/firebase-ios-sdk) + +This repository contains a subset of the Firebase iOS SDK source. It currently +includes FirebaseCore, FirebaseABTesting, FirebaseAuth, FirebaseDatabase, +FirebaseFirestore, FirebaseFunctions, FirebaseInstanceID, FirebaseInAppMessaging, +FirebaseInAppMessagingDisplay, FirebaseMessaging, FirebaseRemoteConfig, and +FirebaseStorage. + +The repository also includes GoogleUtilities source. The +[GoogleUtilities](GoogleUtilities/README.md) pod is +a set of utilities used by Firebase and other Google products. + +Firebase is an app development platform with tools to help you build, grow and +monetize your app. More information about Firebase can be found at +[https://firebase.google.com](https://firebase.google.com). + +## Installation + +See the three subsections for details about three different installation methods. +1. [Standard pod install](README.md#standard-pod-install) +1. [Installing from the GitHub repo](README.md#installing-from-github) +1. [Experimental Carthage](README.md#carthage-ios-only) + +### Standard pod install + +Go to +[https://firebase.google.com/docs/ios/setup](https://firebase.google.com/docs/ios/setup). + +### Installing from GitHub + +For releases starting with 5.0.0, the source for each release is also deployed +to CocoaPods master and available via standard +[CocoaPods Podfile syntax](https://guides.cocoapods.org/syntax/podfile.html#pod). + +These instructions can be used to access the Firebase repo at other branches, +tags, or commits. + +#### Background + +See +[the Podfile Syntax Reference](https://guides.cocoapods.org/syntax/podfile.html#pod) +for instructions and options about overriding pod source locations. + +#### Accessing Firebase Source Snapshots + +All of the official releases are tagged in this repo and available via CocoaPods. To access a local +source snapshot or unreleased branch, use Podfile directives like the following: + +To access FirebaseFirestore via a branch: +``` +pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +``` + +To access FirebaseMessaging via a checked out version of the firebase-ios-sdk repo do: + +``` +pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk' +pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk' +``` + +### Carthage (iOS only) + +Instructions for the experimental Carthage distribution are at +[Carthage](Carthage.md). + +### Rome + +Instructions for installing binary frameworks via +[Rome](https://github.com/CocoaPods/Rome) are at [Rome](Rome.md). + +## Development + +To develop Firebase software in this repository, ensure that you have at least +the following software: + + * Xcode 10.1 (or later) + * CocoaPods 1.7.2 (or later) + * [CocoaPods generate](https://github.com/square/cocoapods-generate) + +For the pod that you want to develop: + +`pod gen Firebase{name here}.podspec --local-sources=./ --auto-open --platforms=ios` + +Note: If the CocoaPods cache is out of date, you may need to run +`pod repo update` before the `pod gen` command. + +Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for +those platforms. Since 10.2, Xcode does not properly handle multi-platform +CocoaPods workspaces. + +Firestore has a self contained Xcode project. See +[Firestore/README.md](Firestore/README.md). + +### Development for Catalyst +* `pod gen {name here}.podspec --local-sources=./ --auto-open --platforms=ios` +* Check the Mac box in the App-iOS Build Settings +* Sign the App in the Settings Signing & Capabilities tab +* Click Pods in the Project Manager +* Add Signing to the iOS host app and unit test targets +* Select the Unit-unit scheme +* Run it to build and test + +### Adding a New Firebase Pod + +See [AddNewPod.md](AddNewPod.md). + +### Code Formatting + +To ensure that the code is formatted consistently, run the script +[./scripts/style.sh](https://github.com/firebase/firebase-ios-sdk/blob/master/scripts/style.sh) +before creating a PR. + +Travis will verify that any code changes are done in a style compliant way. Install +`clang-format` and `swiftformat`. +These commands will get the right versions: + +``` +brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/e3496d9/Formula/clang-format.rb +brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/7963c3d/Formula/swiftformat.rb +``` + +Note: if you already have a newer version of these installed you may need to +`brew switch` to this version. + +To update this section, find the versions of clang-format and swiftformat.rb to +match the versions in the CI failure logs +[here](https://github.com/Homebrew/homebrew-core/tree/master/Formula). + +### Running Unit Tests + +Select a scheme and press Command-u to build a component and run its unit tests. + +#### Viewing Code Coverage + +First, make sure that [xcov](https://github.com/nakiostudio/xcov) is installed with `gem install xcov`. + +After running the `AllUnitTests_iOS` scheme in Xcode, execute +`xcov --workspace Firebase.xcworkspace --scheme AllUnitTests_iOS --output_directory xcov_output` +at Example/ in the terminal. This will aggregate the coverage, and you can run `open xcov_output/index.html` to see the results. + +### Running Sample Apps +In order to run the sample apps and integration tests, you'll need valid +`GoogleService-Info.plist` files for those samples. The Firebase Xcode project contains dummy plist +files without real values, but can be replaced with real plist files. To get your own +`GoogleService-Info.plist` files: + +1. Go to the [Firebase Console](https://console.firebase.google.com/) +2. Create a new Firebase project, if you don't already have one +3. For each sample app you want to test, create a new Firebase app with the sample app's bundle +identifier (e.g. `com.google.Database-Example`) +4. Download the resulting `GoogleService-Info.plist` and replace the appropriate dummy plist file +(e.g. in [Example/Database/App/](Example/Database/App/)); + +Some sample apps like Firebase Messaging ([Example/Messaging/App](Example/Messaging/App)) require +special Apple capabilities, and you will have to change the sample app to use a unique bundle +identifier that you can control in your own Apple Developer account. + +## Specific Component Instructions +See the sections below for any special instructions for those components. + +### Firebase Auth + +If you're doing specific Firebase Auth development, see +[the Auth Sample README](Example/Auth/README.md) for instructions about +building and running the FirebaseAuth pod along with various samples and tests. + +### Firebase Database + +To run the Database Integration tests, make your database authentication rules +[public](https://firebase.google.com/docs/database/security/quickstart). + +### Firebase Storage + +To run the Storage Integration tests, follow the instructions in +[FIRStorageIntegrationTests.m](Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m). + +#### Push Notifications + +Push notifications can only be delivered to specially provisioned App IDs in the developer portal. +In order to actually test receiving push notifications, you will need to: + +1. Change the bundle identifier of the sample app to something you own in your Apple Developer +account, and enable that App ID for push notifications. +2. You'll also need to +[upload your APNs Provider Authentication Key or certificate to the Firebase Console](https://firebase.google.com/docs/cloud-messaging/ios/certs) +at **Project Settings > Cloud Messaging > [Your Firebase App]**. +3. Ensure your iOS device is added to your Apple Developer portal as a test device. + +#### iOS Simulator + +The iOS Simulator cannot register for remote notifications, and will not receive push notifications. +In order to receive push notifications, you'll have to follow the steps above and run the app on a +physical device. + +## Community Supported Efforts + +We've seen an amazing amount of interest and contributions to improve the Firebase SDKs, and we are +very grateful! We'd like to empower as many developers as we can to be able to use Firebase and +participate in the Firebase community. + +### tvOS, macOS, and Catalyst +Thanks to contributions from the community, FirebaseABTesting, FirebaseAuth, FirebaseCore, +FirebaseDatabase, FirebaseMessaging, FirebaseFirestore, +FirebaseFunctions, FirebaseRemoteConfig, and FirebaseStorage now compile, run unit tests, and work on +tvOS, macOS, and Catalyst. + +For tvOS, checkout the [Sample](Example/tvOSSample). + +Keep in mind that macOS, Catalyst and tvOS are not officially supported by Firebase, and this +repository is actively developed primarily for iOS. While we can catch basic unit test issues with +Travis, there may be some changes where the SDK no longer works as expected on macOS or tvOS. If you +encounter this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues). + +To install, add a subset of the following to the Podfile: + +``` +pod 'Firebase/ABTesting' +pod 'Firebase/Auth' +pod 'Firebase/Database' +pod 'Firebase/Firestore' +pod 'Firebase/Functions' +pod 'Firebase/Messaging' +pod 'Firebase/RemoteConfig' +pod 'Firebase/Storage' +``` + +#### Additional Catalyst Notes + +* FirebaseAuth and FirebaseMessaging require adding `Keychain Sharing Capability` +to Build Settings. +* FirebaseFirestore requires signing the +[gRPC Resource target](https://github.com/firebase/firebase-ios-sdk/issues/3500#issuecomment-518741681). + +## Roadmap + +See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source +plans and directions. + +## Contributing + +See [Contributing](CONTRIBUTING.md) for more information on contributing to the Firebase +iOS SDK. + +## License + +The contents of this repository is licensed under the +[Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0). + +Your use of Firebase is governed by the +[Terms of Service for Firebase Services](https://firebase.google.com/terms/). diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/CHANGELOG.md b/!main project/Pods/FirebaseAuth/Firebase/Auth/CHANGELOG.md new file mode 100644 index 0000000..ba0f5a7 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/CHANGELOG.md @@ -0,0 +1,239 @@ +# v6.4.1 +- [fixed] Added support of UISceneDelegate for URL redirect. (#4380) +- [fixed] Fixed rawNonce in encoder. (#4337) + +# v6.4.0 +- [feature] Added support for Sign-in with Apple. (#4183) + +# v6.3.1 +- [fixed] Removed usage of a deprecated property on iOS 13. (#4066) + +# v6.3.0 +- [added] Added methods allowing developers to link and reauthenticate with federated providers. (#3971) + +# v6.2.3 +- [fixed] Make sure the first valid auth domain is retrieved. (#3493) +- [fixed] Add assertion for Facebook generic IDP flow. (#3208) +- [fixed] Build for Catalyst. (#3549) + +# v6.2.2 +- [fixed] Fixed an issue where unlinking an email auth provider raised an incorrect error stating the account was not linked to an email auth provider. (#3405) +- [changed] Renamed internal Keychain classes. (#3473) + +# v6.2.1 +- [added] Add new client error MISSING_CLIENT_IDENTIFIER. (#3341) + +# v6.2.0 +- [feature] Expose `secret` of OAuth credential in public header. (#3089) +- [fixed] Fix a keychain issue where API key is incorrectly set. (#3239) + +# v6.1.2 +- [fixed] Fix line limits and linter warnings in public documentation. (#3139) + +# v6.1.1 +- [fixed] Fix an issue where a user can't link with email provider by email link. (#3030) + +# v6.1.0 +- [added] Add support of web.app as an auth domain. (#2959) +- [fixed] Fix an issue where the return type of `getStoredUserForAccessGroup:error:` is nonnull. (#2879) + +# v6.0.0 +- [added] Add support of single sign on. (#2684) +- [deprecated] Deprecate `reauthenticateAndRetrieveDataWithCredential:completion:`, `signInAndRetrieveDataWithCredential:completion:`, `linkAndRetrieveDataWithCredential:completion:`, `fetchProvidersForEmail:completion:`. (#2723, #2756) +- [added] Returned oauth secret token in Generic IDP sign-in for Twitter. (#2663) +- [removed] Remove pendingToken from public API. (#2676) +- [changed] `GULAppDelegateSwizzler` is used for the app delegate swizzling. (#2591) + +# v5.4.2 +- [added] Support new error code ERROR_INVALID_PROVIDER_ID. (#2629) + +# v5.4.1 +- [deprecated] Deprecate Microsoft and Yahoo OAuth Provider ID (#2517) +- [fixed] Fix an issue where an exception was thrown when linking OAuth credentials. (#2521) +- [fixed] Fix an issue where a wrong error was thrown when handling error with + FEDERATED_USER_ID_ALREADY_LINKED. (#2522) + +# v5.4.0 +- [added] Add support of Generic IDP (#2405). + +# v5.3.0 +- [changed] Use the new registerInternalLibrary API to register with FirebaseCore. (#2137) + +# v5.2.0 +- [added] Add support of Game Center sign in (#2127). + +# v5.1.0 +- [added] Add support of custom FDL domain link (#2121). + +# v5.0.5 +- [changed] Restore SafariServices framework dependency (#2002). + +# v5.0.4 +- [fixed] Fix analyzer issues (#1740). + +# v5.0.3 +- [added] Add `FIRAuthErrorCodeMalformedJWT`, which is raised on JWT token parsing. + failures during auth operations (#1436). +- [changed] Migrate to use FirebaseAuthInterop interfaces to access FirebaseAuth (#1501). + +# v5.0.2 +- [fixed] Fix an issue where JWT date timestamps weren't parsed correctly. (#1319) +- [fixed] Fix an issue where anonymous accounts weren't correctly promoted to + non-anonymous when linked with passwordless email auth accounts. (#1383) +- [fixed] Fix an exception from using an invalidated NSURLSession. (#1261) +- [fixed] Fix a data race issue caught by the sanitizer. (#1446) + +# v5.0.1 +- [fixed] Restore 4.x level of support for extensions (#1357). + +# v5.0.0 +- [added] Adds APIs for phone Auth testing to bypass the verification flow (#1192). +- [feature] Changes the callback block signature for sign in and create user methods + to provide an AuthDataResult that includes the user and user info (#1123, #1186). +- [changed] Removes GoogleToolboxForMac dependency (#1175). +- [removed] Removes miscellaneous deprecated APIs (#1188, #1200). + +# v4.6.1 +- [fixed] Fixes crash which occurred when certain Firebase IDTokens were being parsed (#1076). + +# v4.6.0 +- [added] Adds `getIDTokenResultWithCompletion:` and `getIDTokenResultForcingRefresh:completion:` APIs which + call back with an AuthTokenResult object. The Auth token result object contains the ID token JWT string and other properties associated with the token including the decoded available payload claims (#1004). + +- [added] Adds the `updateCurrentUser:completion:` API which sets the currentUser on the calling Auth instance to the provided user object (#1018). + +- [added] Adds client-side validation to prevent setting `handleCodeInApp` to false when performing + email-link authentication. If `handleCodeInApp` is set to false an invalid argument exception + is thrown (#931). + +- [added] Adds support for passing the deep link (which is embedded in the sign-in link sent via email) to the + `signInWithEmail:link:completion:` and `isSignInWithEmailLink:` methods during an + email/link sign-in flow (#1023). + +# v4.5.0 +- [added] Adds new API which provides a way to determine the sign-in methods associated with an + email address. +- [added] Adds new API which allows authentication using only an email link (Passwordless Authentication + with email link). + +# v4.4.4 +- [fixed] Addresses CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF warnings that surface in newer versions of + Xcode and CocoaPods. +- [fixed] Improves FIRUser documentation with clear message explaining when Firebase Auth attempts to validate + users and what happens when an invalidated user is detected (#694) . + +# v4.4.3 +- [added] Adds an explicit dependency on CoreGraphics from Firebase Auth. + +# v4.4.2 +- [fixed] Fixes bug where the FIRAuthResult object returned following a Phone Number authentication + always contained a nil FIRAdditionalUserInfo object. Now the FIRAdditionalUserInfo object is + never nil and its newUser field is populated correctly. + +# v4.4.0 +- [fixed] Adds new APIs which return an AuthDataResult object after successfully creating an + Email/Password user, signing in anonymously, signing in with Email/Password and signing + in with Custom Token. The AuthDataResult object contains the new user and additional + information pertaining to the new user. + +# v4.3.2 +- [fixed] Improves error handling for the phone number sign-in reCAPTCHA flow. +- [fixed] Improves error handling for phone number linking flow. +- [fixed] Fixes issue where after linking an anonymous user to a phone number the user remained + anonymous. + +# v4.3.1 +- [changed] Internal clean up. + +# v4.3.0 +- [added] Provides account creation and last sign-in dates as metadata to the user + object. +- [added] Returns more descriptive errors for some error cases of the phone number + sign-in reCAPTCHA flow. +- [fixed] Fixes an issue that invalid users were not automatically signed out earlier. +- [fixed] Fixes an issue that ID token listeners were not fired in some cases. + +# v4.2.1 +- [fixed] Fixes a threading issue in phone number auth that completion block was not + executed on the main thread in some error cases. + +# v4.2.0 +- [added] Adds new phone number verification API which makes use of an intelligent reCAPTCHA to verify the application. + +# v4.1.1 +- [changed] Improves some method documentation in headers. + +# v4.1.0 +- [added] Allows the app to handle continue URL natively, e.g., from password reset + email. +- [added] Allows the app to set language code, e.g., for sending password reset email. +- [fixed] Fixes an issue that user's phone number did not persist on client. +- [fixed] Fixes an issue that recover email action code type was reported as unknown. +- [feature] Improves app start-up time by moving initialization off from the main + thread. +- [fixed] Better reports missing email error when creating a new password user. +- [fixed] Changes console message logging levels to be more consistent with other + Firebase products on the iOS platform. + +# 2017-05-17 -- v4.0.0 +- [added] Adds Phone Number Authentication. +- [added] Adds support for generic OAuth2 identity providers. +- [added] Adds methods that return additional user data from identity providers if + available when authenticating users. +- [added] Improves session management by automatically refreshing tokens if possible + and signing out users if the session is detected invalidated, for example, + after the user changed password or deleted account from another device. +- [fixed] Fixes an issue that reauthentication creates new user account if the user + credential is valid but does not match the currently signed in user. +- [fixed] Fixes an issue that the "password" provider is not immediately listed on the + client side after adding a password to an account. +- [changed] Changes factory methods to return non-null FIRAuth instances or raises an + exception, instead of returning nullable instances. +- [changed] Changes auth state change listener to only be triggered when the user changes. +- [added] Adds a new listener which is triggered whenever the ID token is changed. +- [changed] Switches ERROR_EMAIL_ALREADY_IN_USE to + ERROR_ACCOUNT_EXISTS_WITH_DIFFERENT_CREDENTIAL when the email used in the + signInWithCredential: call is already in use by another account. +- [deprecated] Deprecates FIREmailPasswordAuthProvider in favor of FIREmailAuthProvider. +- [deprecated] Deprecates getTokenWithCompletion in favor of getIDTokenWithCompletion on + FIRUser. +- [fixed] Changes Swift API names to better align with Swift convention. + +# 2017-02-06 -- v3.1.1 +- [added] Allows handling of additional errors when sending OOB action emails. The + server can respond with the following new error messages: + INVALID_MESSAGE_PAYLOAD,INVALID_SENDER and INVALID_RECIPIENT_EMAIL. +- [fixed] Removes incorrect reference to FIRAuthErrorCodeCredentialTooOld in FIRUser.h. +- [added] Provides additional error information from server if available. + +# 2016-12-13 -- v3.1.0 +- [added] Adds FIRAuth methods that enable the app to follow up with user actions + delivered by email, such as verifying email address or reset password. +- [fixed] No longer applies the keychain workaround introduced in v3.0.5 on iOS 10.2 + simulator or above since the issue has been fixed. +- [fixed] Fixes nullability compilation warnings when used in Swift. +- [fixed] Better reports missing password error. + +# 2016-10-24 -- v3.0.6 +- [changed] Switches to depend on open sourced GoogleToolboxForMac and GTMSessionFetcher. +- [fixed] Improves logging of keychain error when initializing. + +# 2016-09-14 -- v3.0.5 +- [fixed] Works around a keychain issue in iOS 10 simulator. +- [fixed] Reports the correct error for invalid email when signing in with email and + password. + +# 2016-07-18 -- v3.0.4 +- [fixed] Fixes a race condition bug that could crash the app with an exception from + NSURLSession on iOS 9. + +# 2016-06-20 -- v3.0.3 +- [added] Adds documentation for all possible errors returned by each method. +- [fixed] Improves error handling and messages for a variety of error conditions. +- [fixed] Whether or not an user is considered anonymous is now consistent with other + platforms. +- [changed] A saved signed in user is now siloed between different Firebase projects + within the same app. + +# 2016-05-18 -- v3.0.2 +- Initial public release. diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/README.md b/!main project/Pods/FirebaseAuth/Firebase/Auth/README.md new file mode 100644 index 0000000..f6e123e --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/README.md @@ -0,0 +1,17 @@ +# Firebase Auth for iOS + +Firebase Auth enables apps to easily support multiple authentication options +for their end users. + +Please visit [our developer site](https://firebase.google.com/docs/auth/) for +integration instructions, documentation, support information, and terms of +service. + +# Firebase Auth Development + +Example/Auth contains a set of samples and tests that integrate with +FirebaseAuth. + +The unit tests run without any additional configuration along with the rest of +Firebase. See [Example/Auth/README.md](../../Example/Auth/README.md) for +information about setting up, running, and testing the samples. diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRActionCodeSettings.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRActionCodeSettings.m new file mode 100644 index 0000000..02807ff --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRActionCodeSettings.m @@ -0,0 +1,45 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRActionCodeSettings.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRActionCodeSettings + +- (instancetype)init { + self = [super init]; + if (self) { + _iOSBundleID = [NSBundle mainBundle].bundleIdentifier; + } + return self; +} + +- (void)setIOSBundleID:(NSString *)iOSBundleID { + _iOSBundleID = [iOSBundleID copy]; + } + +- (void)setAndroidPackageName:(NSString *)androidPackageName + installIfNotAvailable:(BOOL)installIfNotAvailable + minimumVersion:(nullable NSString *)minimumVersion { + _androidPackageName = [androidPackageName copy]; + _androidInstallIfNotAvailable = installIfNotAvailable; + _androidMinimumVersion = [minimumVersion copy]; + } + + @end + + NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuth.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuth.m new file mode 100644 index 0000000..7bfd80b --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuth.m @@ -0,0 +1,2052 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuth_Internal.h" + +#if __has_include() +#import +#endif + +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#import "FIREmailPasswordAuthCredential.h" +#import "FIRAdditionalUserInfo_Internal.h" +#import "FIRAuthCredential_Internal.h" +#import "FIRAuthDataResult_Internal.h" +#import "FIRAuthDispatcher.h" +#import "FIRAuthErrorUtils.h" +#import "FIRAuthExceptionUtils.h" +#import "FIRAuthGlobalWorkQueue.h" +#import "FIRAuthKeychainServices.h" +#import "FIRAuthOperationType.h" +#import "FIRAuthSettings.h" +#import "FIRAuthStoredUserManager.h" +#import "FIRAuthWebUtils.h" +#import "FIRUser_Internal.h" +#import "FirebaseAuth.h" +#import "FIRAuthBackend.h" +#import "FIRAuthRequestConfiguration.h" +#import "FIRCreateAuthURIRequest.h" +#import "FIRCreateAuthURIResponse.h" +#import "FIREmailLinkSignInRequest.h" +#import "FIREmailLinkSignInResponse.h" +#import "FIRGameCenterAuthCredential.h" +#import "FIRGetOOBConfirmationCodeRequest.h" +#import "FIRGetOOBConfirmationCodeResponse.h" +#import "FIROAuthCredential_Internal.h" +#import "FIRResetPasswordRequest.h" +#import "FIRResetPasswordResponse.h" +#import "FIRSendVerificationCodeRequest.h" +#import "FIRSendVerificationCodeResponse.h" +#import "FIRSetAccountInfoRequest.h" +#import "FIRSetAccountInfoResponse.h" +#import "FIRSignInWithGameCenterRequest.h" +#import "FIRSignInWithGameCenterResponse.h" +#import "FIRSignUpNewUserRequest.h" +#import "FIRSignUpNewUserResponse.h" +#import "FIRVerifyAssertionRequest.h" +#import "FIRVerifyAssertionResponse.h" +#import "FIRVerifyCustomTokenRequest.h" +#import "FIRVerifyCustomTokenResponse.h" +#import "FIRVerifyPasswordRequest.h" +#import "FIRVerifyPasswordResponse.h" +#import "FIRVerifyPhoneNumberRequest.h" +#import "FIRVerifyPhoneNumberResponse.h" + +#if TARGET_OS_IOS +#import "FIRAuthAPNSToken.h" +#import "FIRAuthAPNSTokenManager.h" +#import "FIRAuthAppCredentialManager.h" +#import "FIRPhoneAuthCredential_Internal.h" +#import "FIRAuthNotificationManager.h" +#import "FIRAuthURLPresenter.h" +#endif + +NS_ASSUME_NONNULL_BEGIN + +#pragma mark - Constants + +#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 +const NSNotificationName FIRAuthStateDidChangeNotification = @"FIRAuthStateDidChangeNotification"; +#else +NSString *const FIRAuthStateDidChangeNotification = @"FIRAuthStateDidChangeNotification"; +#endif // defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 + +/** @var kMaxWaitTimeForBackoff + @brief The maximum wait time before attempting to retry auto refreshing tokens after a failed + attempt. + @remarks This is the upper limit (in seconds) of the exponential backoff used for retrying + token refresh. + */ +static NSTimeInterval kMaxWaitTimeForBackoff = 16 * 60; + +/** @var kTokenRefreshHeadStart + @brief The amount of time before the token expires that proactive refresh should be attempted. + */ +NSTimeInterval kTokenRefreshHeadStart = 5 * 60; + +/** @var kUserKey + @brief Key of user stored in the keychain. Prefixed with a Firebase app name. + */ +static NSString *const kUserKey = @"%@_firebase_user"; + +/** @var kMissingEmailInvalidParameterExceptionReason + @brief The reason for @c invalidParameterException when the email used to initiate password + reset is nil. + */ +static NSString *const kMissingEmailInvalidParameterExceptionReason = + @"The email used to initiate password reset cannot be nil."; + +/** @var kHandleCodeInAppFalseExceptionReason + @brief The reason for @c invalidParameterException when the handleCodeInApp parameter is false + on the ActionCodeSettings object used to send the link for Email-link Authentication. + */ +static NSString *const kHandleCodeInAppFalseExceptionReason = + @"You must set handleCodeInApp in your ActionCodeSettings to true for Email-link " + "Authentication."; + +static NSString *const kInvalidEmailSignInLinkExceptionMessage = + @"The link provided is not valid for email/link sign-in. Please check the link by calling " + "isSignInWithEmailLink:link: on Auth before attempting to use it for email/link sign-in."; + +/** @var kPasswordResetRequestType + @brief The action code type value for resetting password in the check action code response. + */ +static NSString *const kPasswordResetRequestType = @"PASSWORD_RESET"; + +/** @var kVerifyEmailRequestType + @brief The action code type value for verifying email in the check action code response. + */ +static NSString *const kVerifyEmailRequestType = @"VERIFY_EMAIL"; + +/** @var kRecoverEmailRequestType + @brief The action code type value for recovering email in the check action code response. + */ +static NSString *const kRecoverEmailRequestType = @"RECOVER_EMAIL"; + +/** @var kEmailLinkSignInRequestType + @brief The action code type value for an email sign-in link in the check action code response. +*/ +static NSString *const kEmailLinkSignInRequestType = @"EMAIL_SIGNIN"; + +/** @var kMissingPasswordReason + @brief The reason why the @c FIRAuthErrorCodeWeakPassword error is thrown. + @remarks This error message will be localized in the future. + */ +static NSString *const kMissingPasswordReason = @"Missing Password"; + +/** @var gKeychainServiceNameForAppName + @brief A map from Firebase app name to keychain service names. + @remarks This map is needed for looking up the keychain service name after the FIRApp instance + is deleted, to remove the associated keychain item. Accessing should occur within a + @syncronized([FIRAuth class]) context. + */ +static NSMutableDictionary *gKeychainServiceNameForAppName; + +#pragma mark - FIRActionCodeInfo + +@implementation FIRActionCodeInfo { + /** @var _email + @brief The email address to which the code was sent. The new email address in the case of + FIRActionCodeOperationRecoverEmail. + */ + NSString *_email; + + /** @var _fromEmail + @brief The current email address in the case of FIRActionCodeOperationRecoverEmail. + */ + NSString *_fromEmail; +} + +- (NSString *)dataForKey:(FIRActionDataKey)key{ + switch (key) { + case FIRActionCodeEmailKey: + return _email; + case FIRActionCodeFromEmailKey: + return _fromEmail; + } +} + +- (instancetype)initWithOperation:(FIRActionCodeOperation)operation + email:(NSString *)email + newEmail:(nullable NSString *)newEmail { + self = [super init]; + if (self) { + _operation = operation; + if (newEmail) { + _email = [newEmail copy]; + _fromEmail = [email copy]; + } else { + _email = [email copy]; + } + } + return self; +} + +/** @fn actionCodeOperationForRequestType: + @brief Returns the corresponding operation type per provided request type string. + @param requestType Request type returned in in the server response. + @return The corresponding FIRActionCodeOperation for the supplied request type. + */ ++ (FIRActionCodeOperation)actionCodeOperationForRequestType:(NSString *)requestType { + if ([requestType isEqualToString:kPasswordResetRequestType]) { + return FIRActionCodeOperationPasswordReset; + } + if ([requestType isEqualToString:kVerifyEmailRequestType]) { + return FIRActionCodeOperationVerifyEmail; + } + if ([requestType isEqualToString:kRecoverEmailRequestType]) { + return FIRActionCodeOperationRecoverEmail; + } + if ([requestType isEqualToString:kEmailLinkSignInRequestType]) { + return FIRActionCodeOperationEmailLink; + } + return FIRActionCodeOperationUnknown; +} + +@end + +#pragma mark - FIRAuth + +#if TARGET_OS_IOS +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 +@interface FIRAuth () +#else +@interface FIRAuth () +#endif +#else +@interface FIRAuth () +#endif + +/** @property firebaseAppId + @brief The Firebase app ID. + */ +@property(nonatomic, copy, readonly) NSString *firebaseAppId; + +/** @property additionalFrameworkMarker + @brief Additional framework marker that will be added as part of the header of every request. + */ +@property(nonatomic, copy, nullable) NSString *additionalFrameworkMarker; + +/** @property storedUserManager + @brief The stored user manager. + */ +@property(nonatomic, strong, nullable) FIRAuthStoredUserManager *storedUserManager; + +/** @fn initWithApp: + @brief Creates a @c FIRAuth instance associated with the provided @c FIRApp instance. + @param app The application to associate the auth instance with. + */ +- (instancetype)initWithApp:(FIRApp *)app; + +@end + +@implementation FIRAuth { + /** @var _currentUser + @brief The current user. + */ + FIRUser *_currentUser; + + /** @var _firebaseAppName + @brief The Firebase app name. + */ + NSString *_firebaseAppName; + + /** @var _listenerHandles + @brief Handles returned from @c NSNotificationCenter for blocks which are "auth state did + change" notification listeners. + @remarks Mutations should occur within a @syncronized(self) context. + */ + NSMutableArray *_listenerHandles; + + /** @var _keychainServices + @brief The keychain service. + */ + FIRAuthKeychainServices *_keychainServices; + + /** @var _lastNotifiedUserToken + @brief The user access (ID) token used last time for posting auth state changed notification. + */ + NSString *_lastNotifiedUserToken; + + /** @var _autoRefreshTokens + @brief This flag denotes whether or not tokens should be automatically refreshed. + @remarks Will only be set to @YES if the another Firebase service is included (additionally to + Firebase Auth). + */ + BOOL _autoRefreshTokens; + + /** @var _autoRefreshScheduled + @brief Whether or not token auto-refresh is currently scheduled. + */ + BOOL _autoRefreshScheduled; + + /** @var _isAppInBackground + @brief A flag that is set to YES if the app is put in the background and no when the app is + returned to the foreground. + */ + BOOL _isAppInBackground; + + /** @var _applicationDidBecomeActiveObserver + @brief An opaque object to act as the observer for UIApplicationDidBecomeActiveNotification. + */ + id _applicationDidBecomeActiveObserver; + + /** @var _applicationDidBecomeActiveObserver + @brief An opaque object to act as the observer for + UIApplicationDidEnterBackgroundNotification. + */ + id _applicationDidEnterBackgroundObserver; +} + ++ (void)load { + [FIRApp registerInternalLibrary:(Class)self + withName:@"fire-auth" + withVersion:[NSString stringWithUTF8String:FirebaseAuthVersionStr]]; +} + ++ (void)initialize { + gKeychainServiceNameForAppName = [[NSMutableDictionary alloc] init]; +} + ++ (FIRAuth *)auth { + FIRApp *defaultApp = [FIRApp defaultApp]; + if (!defaultApp) { + [NSException raise:NSInternalInconsistencyException + format:@"The default FIRApp instance must be configured before the default FIRAuth" + @"instance can be initialized. One way to ensure that is to call " + @"`[FIRApp configure];` (`FirebaseApp.configure()` in Swift) in the App " + @"Delegate's `application:didFinishLaunchingWithOptions:` " + @"(`application(_:didFinishLaunchingWithOptions:)` in Swift)."]; + } + return [self authWithApp:defaultApp]; +} + ++ (FIRAuth *)authWithApp:(FIRApp *)app { + // Get the instance of Auth from the container, which will create or return the cached instance + // associated with this app. + id auth = FIR_COMPONENT(FIRAuthInterop, app.container); + return (FIRAuth *)auth; +} + +- (instancetype)initWithApp:(FIRApp *)app { + [FIRAuth setKeychainServiceNameForApp:app]; + self = [self initWithAPIKey:app.options.APIKey appName:app.name]; + if (self) { + _app = app; + #if TARGET_OS_IOS + _authURLPresenter = [[FIRAuthURLPresenter alloc] init]; + #endif + } + return self; +} + +- (nullable instancetype)initWithAPIKey:(NSString *)APIKey appName:(NSString *)appName { + self = [super init]; + if (self) { + _listenerHandles = [NSMutableArray array]; + _requestConfiguration = [[FIRAuthRequestConfiguration alloc] initWithAPIKey:APIKey]; + _settings = [[FIRAuthSettings alloc] init]; + _firebaseAppName = [appName copy]; + #if TARGET_OS_IOS + + static Class applicationClass = nil; + // iOS App extensions should not call [UIApplication sharedApplication], even if UIApplication + // responds to it. + if (![GULAppEnvironmentUtil isAppExtension]) { + Class cls = NSClassFromString(@"UIApplication"); + if (cls && [cls respondsToSelector:NSSelectorFromString(@"sharedApplication")]) { + applicationClass = cls; + } + } + UIApplication *application = [applicationClass sharedApplication]; + + [GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods]; + [GULSceneDelegateSwizzler proxyOriginalSceneDelegate]; + #endif // TARGET_OS_IOS + + // Continue with the rest of initialization in the work thread. + __weak FIRAuth *weakSelf = self; + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + // Load current user from Keychain. + FIRAuth *strongSelf = weakSelf; + if (!strongSelf) { + return; + } + NSString *keychainServiceName = + [FIRAuth keychainServiceNameForAppName:strongSelf->_firebaseAppName]; + if (keychainServiceName) { + strongSelf->_keychainServices = [[FIRAuthKeychainServices alloc] initWithService:keychainServiceName]; + strongSelf.storedUserManager = + [[FIRAuthStoredUserManager alloc] initWithServiceName:keychainServiceName]; + } + + NSError *error; + NSString *storedUserAccessGroup = [strongSelf.storedUserManager getStoredUserAccessGroupWithError:&error]; + if (!error) { + if (!storedUserAccessGroup) { + FIRUser *user; + if ([strongSelf getUser:&user error:&error]) { + [strongSelf updateCurrentUser:user byForce:NO savingToDisk:NO error:&error]; + self->_lastNotifiedUserToken = user.rawAccessToken; + } else { + FIRLogError(kFIRLoggerAuth, @"I-AUT000001", + @"Error loading saved user when starting up: %@", error); + } + } else { + [strongSelf useUserAccessGroup:storedUserAccessGroup error:&error]; + if (error) { + FIRLogError(kFIRLoggerAuth, @"I-AUT000001", + @"Error loading saved user when starting up: %@", error); + } + } + } else { + FIRLogError(kFIRLoggerAuth, @"I-AUT000001", + @"Error loading saved user when starting up: %@", error); + } + + #if TARGET_OS_IOS + // Initialize for phone number auth. + strongSelf->_tokenManager = + [[FIRAuthAPNSTokenManager alloc] initWithApplication:application]; + + strongSelf->_appCredentialManager = + [[FIRAuthAppCredentialManager alloc] initWithKeychain:strongSelf->_keychainServices]; + + strongSelf->_notificationManager = [[FIRAuthNotificationManager alloc] + initWithApplication:application + appCredentialManager:strongSelf->_appCredentialManager]; + + [GULAppDelegateSwizzler registerAppDelegateInterceptor:strongSelf]; + #if ((TARGET_OS_IOS || TARGET_OS_TV) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= 130000)) + if (@available(iOS 13, tvos 13, *)) { + [GULSceneDelegateSwizzler registerSceneDelegateInterceptor:strongSelf]; + } + #endif // ((TARGET_OS_IOS || TARGET_OS_TV) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= 130000)) + #endif // TARGET_OS_IOS + }); + } + return self; +} + +- (void)dealloc { + @synchronized (self) { + NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter]; + while (_listenerHandles.count != 0) { + FIRAuthStateDidChangeListenerHandle handleToRemove = _listenerHandles.lastObject; + [defaultCenter removeObserver:handleToRemove]; + [_listenerHandles removeLastObject]; + } + + #if TARGET_OS_IOS + [defaultCenter removeObserver:_applicationDidBecomeActiveObserver + name:UIApplicationDidBecomeActiveNotification + object:nil]; + [defaultCenter removeObserver:_applicationDidEnterBackgroundObserver + name:UIApplicationDidEnterBackgroundNotification + object:nil]; + #endif + } +} + +#pragma mark - Public API + +- (nullable FIRUser *)currentUser { + __block FIRUser *result; + dispatch_sync(FIRAuthGlobalWorkQueue(), ^{ + result = self->_currentUser; + }); + return result; +} + +- (void)fetchProvidersForEmail:(NSString *)email + completion:(nullable FIRProviderQueryCallback)completion { + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + FIRCreateAuthURIRequest *request = + [[FIRCreateAuthURIRequest alloc] initWithIdentifier:email + continueURI:@"http://www.google.com/" + requestConfiguration:self->_requestConfiguration]; + [FIRAuthBackend createAuthURI:request callback:^(FIRCreateAuthURIResponse *_Nullable response, + NSError *_Nullable error) { + if (completion) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(response.allProviders, error); + }); + } + }]; + }); +} + + +- (void)signInWithProvider:(id)provider + UIDelegate:(nullable id)UIDelegate + completion:(nullable FIRAuthDataResultCallback)completion { +#if TARGET_OS_IOS + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + FIRAuthDataResultCallback decoratedCallback = + [self signInFlowAuthDataResultCallbackByDecoratingCallback:completion]; + [provider getCredentialWithUIDelegate:UIDelegate + completion:^(FIRAuthCredential *_Nullable credential, + NSError *_Nullable error) { + if (error) { + decoratedCallback(nil, error); + return; + } + [self internalSignInAndRetrieveDataWithCredential:credential + isReauthentication:NO + callback:decoratedCallback]; + }]; + }); +#endif // TARGET_OS_IOS +} + +- (void)fetchSignInMethodsForEmail:(nonnull NSString *)email + completion:(nullable FIRSignInMethodQueryCallback)completion { + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + FIRCreateAuthURIRequest *request = + [[FIRCreateAuthURIRequest alloc] initWithIdentifier:email + continueURI:@"http://www.google.com/" + requestConfiguration:self->_requestConfiguration]; + [FIRAuthBackend createAuthURI:request callback:^(FIRCreateAuthURIResponse *_Nullable response, + NSError *_Nullable error) { + if (completion) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(response.signinMethods, error); + }); + } + }]; + }); +} + +- (void)signInWithEmail:(NSString *)email + password:(NSString *)password + completion:(nullable FIRAuthDataResultCallback)completion { + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + FIRAuthDataResultCallback decoratedCallback = + [self signInFlowAuthDataResultCallbackByDecoratingCallback:completion]; + [self internalSignInAndRetrieveDataWithEmail:email + password:password + completion:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { + decoratedCallback(authResult, error); + }]; + }); +} + +- (void)signInWithEmail:(NSString *)email + link:(NSString *)link + completion:(nullable FIRAuthDataResultCallback)completion { + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + FIRAuthDataResultCallback decoratedCallback = + [self signInFlowAuthDataResultCallbackByDecoratingCallback:completion]; + FIREmailPasswordAuthCredential *credential = + [[FIREmailPasswordAuthCredential alloc] initWithEmail:email link:link]; + [self internalSignInAndRetrieveDataWithCredential:credential + isReauthentication:NO + callback:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { + decoratedCallback(authResult, error); + }]; + }); +} + +/** @fn signInWithEmail:password:callback: + @brief Signs in using an email address and password. + @param email The user's email address. + @param password The user's password. + @param callback A block which is invoked when the sign in finishes (or is cancelled.) Invoked + asynchronously on the global auth work queue in the future. + @remarks This is the internal counterpart of this method, which uses a callback that does not + update the current user. + */ +- (void)signInWithEmail:(NSString *)email + password:(NSString *)password + callback:(FIRAuthResultCallback)callback { + + FIRVerifyPasswordRequest *request = + [[FIRVerifyPasswordRequest alloc] initWithEmail:email + password:password + requestConfiguration:_requestConfiguration]; + + if (![request.password length]) { + callback(nil, [FIRAuthErrorUtils wrongPasswordErrorWithMessage:nil]); + return; + } + [FIRAuthBackend verifyPassword:request + callback:^(FIRVerifyPasswordResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + callback(nil, error); + return; + } + [self completeSignInWithAccessToken:response.IDToken + accessTokenExpirationDate:response.approximateExpirationDate + refreshToken:response.refreshToken + anonymous:NO + callback:callback]; + }]; +} + +/** @fn internalSignInAndRetrieveDataWithEmail:password:callback: + @brief Signs in using an email address and password. + @param email The user's email address. + @param password The user's password. + @param completion A block which is invoked when the sign in finishes (or is cancelled.) Invoked + asynchronously on the global auth work queue in the future. + @remarks This is the internal counterpart of this method, which uses a callback that does not + update the current user. + */ +- (void)internalSignInAndRetrieveDataWithEmail:(NSString *)email + password:(NSString *)password + completion:(FIRAuthDataResultCallback)completion { + FIREmailPasswordAuthCredential *credentail = + [[FIREmailPasswordAuthCredential alloc] initWithEmail:email password:password]; + [self internalSignInAndRetrieveDataWithCredential:credentail + isReauthentication:NO + callback:completion]; +} + +/** @fn signInAndRetrieveDataWithGameCenterCredential:callback: + @brief Signs in using a game center credential. + @param credential The Game Center Auth Credential used to sign in. + @param callback A block which is invoked when the sign in finished (or is cancelled). Invoked + asynchronously on the global auth work queue in the future. + */ +- (void)signInAndRetrieveDataWithGameCenterCredential:(FIRGameCenterAuthCredential *)credential + callback:(FIRAuthDataResultCallback)callback { + FIRSignInWithGameCenterRequest *request = + [[FIRSignInWithGameCenterRequest alloc] initWithPlayerID:credential.playerID + publicKeyURL:credential.publicKeyURL + signature:credential.signature + salt:credential.salt + timestamp:credential.timestamp + displayName:credential.displayName + requestConfiguration:_requestConfiguration]; + [FIRAuthBackend signInWithGameCenter:request + callback:^(FIRSignInWithGameCenterResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + if (callback) { + callback(nil, error); + } + return; + } + + [self completeSignInWithAccessToken:response.IDToken + accessTokenExpirationDate:response.approximateExpirationDate + refreshToken:response.refreshToken + anonymous:NO + callback:^(FIRUser *_Nullable user, NSError *_Nullable error) { + if (error && callback) { + callback(nil, error); + return; + } + FIRAdditionalUserInfo *additionalUserInfo = + [[FIRAdditionalUserInfo alloc] initWithProviderID:FIRGameCenterAuthProviderID + profile:nil + username:nil + isNewUser:response.isNewUser]; + FIRAuthDataResult *result = user ? + [[FIRAuthDataResult alloc] initWithUser:user + additionalUserInfo:additionalUserInfo] : nil; + if (callback) { + callback(result, error); + } + }]; + }]; +} + +/** @fn internalSignInAndRetrieveDataWithEmail:link:completion: + @brief Signs in using an email and email sign-in link. + @param email The user's email address. + @param link The email sign-in link. + @param callback A block which is invoked when the sign in finishes (or is cancelled.) Invoked + asynchronously on the global auth work queue in the future. + */ +- (void)internalSignInAndRetrieveDataWithEmail:(nonnull NSString *)email + link:(nonnull NSString *)link + callback:(nullable FIRAuthDataResultCallback)callback { + if (![self isSignInWithEmailLink:link]) { + [FIRAuthExceptionUtils raiseInvalidParameterExceptionWithReason: + kInvalidEmailSignInLinkExceptionMessage]; + return; + } + NSDictionary *queryItems = [FIRAuthWebUtils parseURL:link]; + if (![queryItems count]) { + NSURLComponents *urlComponents = [NSURLComponents componentsWithString:link]; + queryItems = [FIRAuthWebUtils parseURL:urlComponents.query]; + } + NSString *actionCode = queryItems[@"oobCode"]; + + FIREmailLinkSignInRequest *request = + [[FIREmailLinkSignInRequest alloc] initWithEmail:email + oobCode:actionCode + requestConfiguration:_requestConfiguration]; + + [FIRAuthBackend emailLinkSignin:request + callback:^(FIREmailLinkSignInResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + if (callback) { + callback(nil, error); + } + return; + } + [self completeSignInWithAccessToken:response.IDToken + accessTokenExpirationDate:response.approximateExpirationDate + refreshToken:response.refreshToken + anonymous:NO + callback:^(FIRUser *_Nullable user, NSError *_Nullable error) { + if (error && callback) { + callback(nil, error); + return; + } + FIRAdditionalUserInfo *additionalUserInfo = + [[FIRAdditionalUserInfo alloc] initWithProviderID:FIREmailAuthProviderID + profile:nil + username:nil + isNewUser:response.isNewUser]; + FIRAuthDataResult *result = user ? + [[FIRAuthDataResult alloc] initWithUser:user + additionalUserInfo:additionalUserInfo] : nil; + if (callback) { + callback(result, error); + } + }]; + }]; +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +- (void)signInWithCredential:(FIRAuthCredential *)credential + completion:(nullable FIRAuthDataResultCallback)completion { + [self signInAndRetrieveDataWithCredential:credential completion:completion]; +} +#pragma clang diagnostic pop + +- (void)signInAndRetrieveDataWithCredential:(FIRAuthCredential *)credential + completion:(nullable FIRAuthDataResultCallback)completion { + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + FIRAuthDataResultCallback callback = + [self signInFlowAuthDataResultCallbackByDecoratingCallback:completion]; + [self internalSignInAndRetrieveDataWithCredential:credential + isReauthentication:NO + callback:callback]; + }); +} + +- (void)internalSignInWithCredential:(FIRAuthCredential *)credential + callback:(FIRAuthResultCallback)callback { + [self internalSignInAndRetrieveDataWithCredential:credential + isReauthentication:NO + callback:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { + callback(authResult.user, error); + }]; +} + +- (void)internalSignInAndRetrieveDataWithCredential:(FIRAuthCredential *)credential + isReauthentication:(BOOL)isReauthentication + callback:(nullable FIRAuthDataResultCallback)callback { + if ([credential isKindOfClass:[FIREmailPasswordAuthCredential class]]) { + // Special case for email/password credentials + FIREmailPasswordAuthCredential *emailPasswordCredential = + (FIREmailPasswordAuthCredential *)credential; + + if (emailPasswordCredential.link) { + // Email link sign in + [self internalSignInAndRetrieveDataWithEmail:emailPasswordCredential.email + link:emailPasswordCredential.link + callback:callback]; + } else { + // Email password sign in + FIRAuthResultCallback completeEmailSignIn = ^(FIRUser *_Nullable user, + NSError *_Nullable error) { + if (callback) { + if (error) { + callback(nil, error); + return; + } + FIRAdditionalUserInfo *additionalUserInfo = + [[FIRAdditionalUserInfo alloc] initWithProviderID:FIREmailAuthProviderID + profile:nil + username:nil + isNewUser:NO]; + FIRAuthDataResult *result = user ? + [[FIRAuthDataResult alloc] initWithUser:user + additionalUserInfo:additionalUserInfo] : nil; + callback(result, error); + } + }; + + [self signInWithEmail:emailPasswordCredential.email + password:emailPasswordCredential.password + callback:completeEmailSignIn]; + } + return; + } + + if ([credential isKindOfClass:[FIRGameCenterAuthCredential class]]) { + // Special case for Game Center credentials. + [self signInAndRetrieveDataWithGameCenterCredential:(FIRGameCenterAuthCredential *)credential + callback:callback]; + return; + } + + #if TARGET_OS_IOS + if ([credential isKindOfClass:[FIRPhoneAuthCredential class]]) { + // Special case for phone auth credentials + FIRPhoneAuthCredential *phoneCredential = (FIRPhoneAuthCredential *)credential; + FIRAuthOperationType operation = + isReauthentication ? FIRAuthOperationTypeReauth : FIRAuthOperationTypeSignUpOrSignIn; + [self signInWithPhoneCredential:phoneCredential + operation:operation + callback:^(FIRVerifyPhoneNumberResponse *_Nullable response, + NSError *_Nullable error) { + if (callback) { + if (error) { + callback(nil, error); + return; + } + + [self completeSignInWithAccessToken:response.IDToken + accessTokenExpirationDate:response.approximateExpirationDate + refreshToken:response.refreshToken + anonymous:NO + callback:^(FIRUser *_Nullable user, NSError *_Nullable error) { + if (error && callback) { + callback(nil, error); + return; + } + FIRAdditionalUserInfo *additionalUserInfo = + [[FIRAdditionalUserInfo alloc] initWithProviderID:FIRPhoneAuthProviderID + profile:nil + username:nil + isNewUser:response.isNewUser]; + FIRAuthDataResult *result = user ? + [[FIRAuthDataResult alloc] initWithUser:user + additionalUserInfo:additionalUserInfo] : nil; + if (callback) { + callback(result, error); + } + }]; + } + }]; + return; + } + #endif + FIRVerifyAssertionRequest *request = + [[FIRVerifyAssertionRequest alloc] initWithProviderID:credential.provider + requestConfiguration:_requestConfiguration]; + request.autoCreate = !isReauthentication; + [credential prepareVerifyAssertionRequest:request]; + [FIRAuthBackend verifyAssertion:request + callback:^(FIRVerifyAssertionResponse *response, NSError *error) { + if (error) { + if (callback) { + callback(nil, error); + } + return; + } + + if (response.needConfirmation) { + if (callback) { + NSString *email = response.email; + FIROAuthCredential *credential = + [[FIROAuthCredential alloc] initWithVerifyAssertionResponse:response]; + callback(nil, [FIRAuthErrorUtils accountExistsWithDifferentCredentialErrorWithEmail:email + updatedCredential:credential]); + } + return; + } + + if (!response.providerID.length) { + if (callback) { + callback(nil, [FIRAuthErrorUtils unexpectedResponseWithDeserializedResponse:response]); + } + return; + } + [self completeSignInWithAccessToken:response.IDToken + accessTokenExpirationDate:response.approximateExpirationDate + refreshToken:response.refreshToken + anonymous:NO + callback:^(FIRUser *_Nullable user, NSError *_Nullable error) { + if (callback) { + if (error) { + callback(nil, error); + return; + } + FIRAdditionalUserInfo *additionalUserInfo = + [FIRAdditionalUserInfo userInfoWithVerifyAssertionResponse:response]; + FIROAuthCredential *updatedOAuthCredential = + [[FIROAuthCredential alloc] initWithVerifyAssertionResponse:response]; + FIRAuthDataResult *result = user ? + [[FIRAuthDataResult alloc] initWithUser:user + additionalUserInfo:additionalUserInfo + credential:updatedOAuthCredential] : nil; + callback(result, error); + } + }]; + }]; +} + +- (void)signInAnonymouslyWithCompletion:(nullable FIRAuthDataResultCallback)completion { + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + FIRAuthDataResultCallback decoratedCallback = + [self signInFlowAuthDataResultCallbackByDecoratingCallback:completion]; + if (self->_currentUser.anonymous) { + FIRAuthDataResult *result = + [[FIRAuthDataResult alloc] initWithUser:self->_currentUser additionalUserInfo:nil]; + decoratedCallback(result, nil); + return; + } + [self internalSignInAnonymouslyWithCompletion:^(FIRSignUpNewUserResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + decoratedCallback(nil, error); + return; + } + [self completeSignInWithAccessToken:response.IDToken + accessTokenExpirationDate:response.approximateExpirationDate + refreshToken:response.refreshToken + anonymous:YES + callback:^(FIRUser * _Nullable user, NSError * _Nullable error) { + if (error) { + decoratedCallback(nil, error); + return; + } + FIRAdditionalUserInfo *additionalUserInfo = + [[FIRAdditionalUserInfo alloc] initWithProviderID:nil + profile:nil + username:nil + isNewUser:YES]; + FIRAuthDataResult *authDataResult = user ? + [[FIRAuthDataResult alloc] initWithUser:user + additionalUserInfo:additionalUserInfo] : nil; + decoratedCallback(authDataResult, error); + }]; + }]; + }); +} + +- (void)signInWithCustomToken:(NSString *)token + completion:(nullable FIRAuthDataResultCallback)completion { + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + FIRAuthDataResultCallback decoratedCallback = + [self signInFlowAuthDataResultCallbackByDecoratingCallback:completion]; + [self internalSignInAndRetrieveDataWithCustomToken:token + completion:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { + decoratedCallback(authResult, error); + }]; + }); +} + +- (void)createUserWithEmail:(NSString *)email + password:(NSString *)password + completion:(nullable FIRAuthDataResultCallback)completion { + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + FIRAuthDataResultCallback decoratedCallback = + [self signInFlowAuthDataResultCallbackByDecoratingCallback:completion]; + [self internalCreateUserWithEmail:email + password:password + completion:^(FIRSignUpNewUserResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + decoratedCallback(nil, error); + return; + } + [self completeSignInWithAccessToken:response.IDToken + accessTokenExpirationDate:response.approximateExpirationDate + refreshToken:response.refreshToken + anonymous:NO + callback:^(FIRUser *_Nullable user, NSError *_Nullable error) { + if (error) { + decoratedCallback(nil, error); + return; + } + FIRAdditionalUserInfo *additionalUserInfo = + [[FIRAdditionalUserInfo alloc] initWithProviderID:FIREmailAuthProviderID + profile:nil + username:nil + isNewUser:YES]; + FIRAuthDataResult *authDataResult = user ? + [[FIRAuthDataResult alloc] initWithUser:user + additionalUserInfo:additionalUserInfo] : nil; + decoratedCallback(authDataResult, error); + }]; + }]; + }); +} + +- (void)confirmPasswordResetWithCode:(NSString *)code + newPassword:(NSString *)newPassword + completion:(FIRConfirmPasswordResetCallback)completion { + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + FIRResetPasswordRequest *request = + [[FIRResetPasswordRequest alloc] initWithOobCode:code + newPassword:newPassword + requestConfiguration:self->_requestConfiguration]; + [FIRAuthBackend resetPassword:request callback:^(FIRResetPasswordResponse *_Nullable response, + NSError *_Nullable error) { + if (completion) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (error) { + completion(error); + return; + } + completion(nil); + }); + } + }]; + }); +} + +- (void)checkActionCode:(NSString *)code completion:(FIRCheckActionCodeCallBack)completion { + dispatch_async(FIRAuthGlobalWorkQueue(), ^ { + FIRResetPasswordRequest *request = + [[FIRResetPasswordRequest alloc] initWithOobCode:code + newPassword:nil + requestConfiguration:self->_requestConfiguration]; + [FIRAuthBackend resetPassword:request callback:^(FIRResetPasswordResponse *_Nullable response, + NSError *_Nullable error) { + if (completion) { + if (error) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, error); + }); + return; + } + FIRActionCodeOperation operation = + [FIRActionCodeInfo actionCodeOperationForRequestType:response.requestType]; + FIRActionCodeInfo *actionCodeInfo = + [[FIRActionCodeInfo alloc] initWithOperation:operation + email:response.email + newEmail:response.verifiedEmail]; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(actionCodeInfo, nil); + }); + } + }]; + }); +} + +- (void)verifyPasswordResetCode:(NSString *)code + completion:(FIRVerifyPasswordResetCodeCallback)completion { + [self checkActionCode:code completion:^(FIRActionCodeInfo *_Nullable info, + NSError *_Nullable error) { + if (completion) { + if (error) { + completion(nil, error); + return; + } + completion([info dataForKey:FIRActionCodeEmailKey], nil); + } + }]; +} + +- (void)applyActionCode:(NSString *)code completion:(FIRApplyActionCodeCallback)completion { + dispatch_async(FIRAuthGlobalWorkQueue(), ^ { + FIRSetAccountInfoRequest *request = + [[FIRSetAccountInfoRequest alloc] initWithRequestConfiguration:self->_requestConfiguration]; + request.OOBCode = code; + [FIRAuthBackend setAccountInfo:request callback:^(FIRSetAccountInfoResponse *_Nullable response, + NSError *_Nullable error) { + if (completion) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(error); + }); + } + }]; + }); +} + +- (void)sendPasswordResetWithEmail:(NSString *)email + completion:(nullable FIRSendPasswordResetCallback)completion { + [self sendPasswordResetWithNullableActionCodeSettings:nil email:email completion:completion]; +} + +- (void)sendPasswordResetWithEmail:(NSString *)email + actionCodeSettings:(FIRActionCodeSettings *)actionCodeSettings + completion:(nullable FIRSendPasswordResetCallback)completion { + [self sendPasswordResetWithNullableActionCodeSettings:actionCodeSettings + email:email + completion:completion]; +} + +/** @fn sendPasswordResetWithNullableActionCodeSettings:actionCodeSetting:email:completion: + @brief Initiates a password reset for the given email address and @FIRActionCodeSettings object. + + @param actionCodeSettings Optionally, An @c FIRActionCodeSettings object containing settings + related to the handling action codes. + @param email The email address of the user. + @param completion Optionally; a block which is invoked when the request finishes. Invoked + asynchronously on the main thread in the future. + */ +- (void)sendPasswordResetWithNullableActionCodeSettings:(nullable FIRActionCodeSettings *) + actionCodeSettings + email:(NSString *)email + completion:(nullable FIRSendPasswordResetCallback) + completion { + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + if (!email) { + [FIRAuthExceptionUtils raiseInvalidParameterExceptionWithReason: + kMissingEmailInvalidParameterExceptionReason]; + return; + } + FIRGetOOBConfirmationCodeRequest *request = + [FIRGetOOBConfirmationCodeRequest passwordResetRequestWithEmail:email + actionCodeSettings:actionCodeSettings + requestConfiguration:self->_requestConfiguration + ]; + [FIRAuthBackend getOOBConfirmationCode:request + callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response, + NSError *_Nullable error) { + if (completion) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(error); + }); + } + }]; + }); +} + +- (void)sendSignInLinkToEmail:(nonnull NSString *)email + actionCodeSettings:(nonnull FIRActionCodeSettings *)actionCodeSettings + completion:(nullable FIRSendSignInLinkToEmailCallback)completion { + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + if (!email) { + [FIRAuthExceptionUtils raiseInvalidParameterExceptionWithReason: + kMissingEmailInvalidParameterExceptionReason]; + } + + if (!actionCodeSettings.handleCodeInApp) { + [FIRAuthExceptionUtils raiseInvalidParameterExceptionWithReason: + kHandleCodeInAppFalseExceptionReason]; + } + FIRGetOOBConfirmationCodeRequest *request = + [FIRGetOOBConfirmationCodeRequest signInWithEmailLinkRequest:email + actionCodeSettings:actionCodeSettings + requestConfiguration:self->_requestConfiguration]; + [FIRAuthBackend getOOBConfirmationCode:request + callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response, + NSError *_Nullable error) { + if (completion) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(error); + }); + } + }]; + }); +} + +- (void)updateCurrentUser:(FIRUser *)user completion:(nullable FIRUserUpdateCallback)completion { + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + if (!user) { + if (completion) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion([FIRAuthErrorUtils nullUserErrorWithMessage:nil]); + }); + } + return; + } + void (^updateUserBlock)(FIRUser *user) = ^(FIRUser *user) { + NSError *error; + [self updateCurrentUser:user byForce:YES savingToDisk:YES error:(&error)]; + if (error) { + if (completion) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(error); + }); + } + return; + } if (completion) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil); + }); + } + }; + if (![user.requestConfiguration.APIKey isEqualToString:self->_requestConfiguration.APIKey]) { + // If the API keys are different, then we need to confirm that the user belongs to the same + // project before proceeding. + user.requestConfiguration = self->_requestConfiguration; + [user reloadWithCompletion:^(NSError *_Nullable error) { + if (error) { + if (completion) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(error); + }); + } + return; + } + updateUserBlock(user); + }]; + } else { + updateUserBlock(user); + } + }); +} + +- (BOOL)signOut:(NSError *_Nullable __autoreleasing *_Nullable)error { + __block BOOL result = YES; + dispatch_sync(FIRAuthGlobalWorkQueue(), ^{ + if (!self->_currentUser) { + return; + } + result = [self updateCurrentUser:nil byForce:NO savingToDisk:YES error:error]; + }); + return result; +} + +- (BOOL)signOutByForceWithUserID:(NSString *)userID error:(NSError *_Nullable *_Nullable)error { + if (_currentUser.uid != userID) { + return YES; + } + return [self updateCurrentUser:nil byForce:YES savingToDisk:YES error:error]; +} + +- (BOOL)isSignInWithEmailLink:(NSString *)link { + if (link.length == 0) { + return NO; + } + NSDictionary *queryItems = [FIRAuthWebUtils parseURL:link]; + if (![queryItems count]) { + NSURLComponents *urlComponents = [NSURLComponents componentsWithString:link]; + if (!urlComponents.query) { + return NO; + } + queryItems = [FIRAuthWebUtils parseURL:urlComponents.query]; + } + + if (![queryItems count]) { + return NO; + } + + NSString *actionCode = queryItems[@"oobCode"]; + NSString *mode = queryItems[@"mode"]; + + if (actionCode && [mode isEqualToString:@"signIn"]) { + return YES; + } + return NO; +} + +- (FIRAuthStateDidChangeListenerHandle)addAuthStateDidChangeListener: + (FIRAuthStateDidChangeListenerBlock)listener { + __block BOOL firstInvocation = YES; + __block NSString *previousUserID; + return [self addIDTokenDidChangeListener:^(FIRAuth *_Nonnull auth, FIRUser *_Nullable user) { + BOOL shouldCallListener = firstInvocation || + !(previousUserID == user.uid || [previousUserID isEqualToString:user.uid]); + firstInvocation = NO; + previousUserID = [user.uid copy]; + if (shouldCallListener) { + listener(auth, user); + } + }]; +} + +- (void)removeAuthStateDidChangeListener:(FIRAuthStateDidChangeListenerHandle)listenerHandle { + [self removeIDTokenDidChangeListener:listenerHandle]; +} + +- (FIRIDTokenDidChangeListenerHandle)addIDTokenDidChangeListener: + (FIRIDTokenDidChangeListenerBlock)listener { + if (!listener) { + [NSException raise:NSInvalidArgumentException format:@"listener must not be nil."]; + return nil; + } + FIRAuthStateDidChangeListenerHandle handle; + NSNotificationCenter *notifications = [NSNotificationCenter defaultCenter]; + handle = [notifications addObserverForName:FIRAuthStateDidChangeNotification + object:self + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification *_Nonnull notification) { + FIRAuth *auth = notification.object; + listener(auth, auth.currentUser); + }]; + @synchronized (self) { + [_listenerHandles addObject:handle]; + } + dispatch_async(dispatch_get_main_queue(), ^{ + listener(self, self->_currentUser); + }); + return handle; +} + +- (void)removeIDTokenDidChangeListener:(FIRIDTokenDidChangeListenerHandle)listenerHandle { + [[NSNotificationCenter defaultCenter] removeObserver:listenerHandle]; + @synchronized (self) { + [_listenerHandles removeObject:listenerHandle]; + } +} + +- (void)useAppLanguage { + dispatch_sync(FIRAuthGlobalWorkQueue(), ^{ + self->_requestConfiguration.languageCode = [[NSLocale preferredLanguages] firstObject]; + }); +} + +- (nullable NSString *)languageCode { + return _requestConfiguration.languageCode; +} + +- (void)setLanguageCode:(nullable NSString *)languageCode { + dispatch_sync(FIRAuthGlobalWorkQueue(), ^{ + self->_requestConfiguration.languageCode = [languageCode copy]; + }); +} + +- (nullable NSString *)additionalFrameworkMarker { + return self->_requestConfiguration.additionalFrameworkMarker; +} + +- (void)setAdditionalFrameworkMarker:(nullable NSString *)additionalFrameworkMarker { + dispatch_sync(FIRAuthGlobalWorkQueue(), ^{ + self->_requestConfiguration.additionalFrameworkMarker = [additionalFrameworkMarker copy]; + }); +} + +#if TARGET_OS_IOS +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-property-ivar" +// The warning is ignored because we use the token manager to get the token, instead of using the ivar. +- (nullable NSData *)APNSToken { + __block NSData *result = nil; + dispatch_sync(FIRAuthGlobalWorkQueue(), ^{ + result = self->_tokenManager.token.data; + }); + return result; +} +#pragma clang diagnostic pop + +#pragma mark - UIApplicationDelegate + +- (void)application:(UIApplication *)application +didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + [self setAPNSToken:deviceToken type:FIRAuthAPNSTokenTypeUnknown]; +} + +- (void)application:(UIApplication *)application +didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { + dispatch_sync(FIRAuthGlobalWorkQueue(), ^{ + [self->_tokenManager cancelWithError:error]; + }); +} + +- (void)application:(UIApplication *)application +didReceiveRemoteNotification:(NSDictionary *)userInfo +fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { + [self canHandleNotification:userInfo]; +} + +- (void)application:(UIApplication *)application +didReceiveRemoteNotification:(NSDictionary *)userInfo { + [self canHandleNotification:userInfo]; +} + +- (BOOL)application:(UIApplication *)app + openURL:(NSURL *)url + options:(NSDictionary *)options { + return [self canHandleURL:url]; +} + +- (BOOL)application:(UIApplication *)application + openURL:(NSURL *)url + sourceApplication:(nullable NSString *)sourceApplication + annotation:(id)annotation { + return [self canHandleURL:url]; +} + +- (void)setAPNSToken:(NSData *)token type:(FIRAuthAPNSTokenType)type { + dispatch_sync(FIRAuthGlobalWorkQueue(), ^{ + self->_tokenManager.token = [[FIRAuthAPNSToken alloc] initWithData:token type:type]; + }); +} + +- (BOOL)canHandleNotification:(NSDictionary *)userInfo { + __block BOOL result = NO; + dispatch_sync(FIRAuthGlobalWorkQueue(), ^{ + result = [self->_notificationManager canHandleNotification:userInfo]; + }); + return result; +} + +- (BOOL)canHandleURL:(NSURL *)URL { + __block BOOL result = NO; + dispatch_sync(FIRAuthGlobalWorkQueue(), ^{ + result = [self->_authURLPresenter canHandleURL:URL]; + }); + return result; +} + +#pragma mark - UISceneDelegate +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 +- (void)scene:(UIScene *)scene openURLContexts:(NSSet *)URLContexts API_AVAILABLE(ios(13.0)) { + for (UIOpenURLContext *urlContext in URLContexts) { + NSURL *url = [urlContext URL]; + [self canHandleURL:url]; + } +} +#endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 +#endif // TARGET_OS_IOS + +#pragma mark - Internal Methods + +#if TARGET_OS_IOS +/** @fn signInWithPhoneCredential:callback: + @brief Signs in using a phone credential. + @param credential The Phone Auth credential used to sign in. + @param operation The type of operation for which this sign-in attempt is initiated. + @param callback A block which is invoked when the sign in finishes (or is cancelled.) Invoked + asynchronously on the global auth work queue in the future. + */ +- (void)signInWithPhoneCredential:(FIRPhoneAuthCredential *)credential + operation:(FIRAuthOperationType)operation + callback:(FIRVerifyPhoneNumberResponseCallback)callback { + if (credential.temporaryProof.length && credential.phoneNumber.length) { + FIRVerifyPhoneNumberRequest *request = + [[FIRVerifyPhoneNumberRequest alloc] initWithTemporaryProof:credential.temporaryProof + phoneNumber:credential.phoneNumber + operation:operation + requestConfiguration:_requestConfiguration]; + [FIRAuthBackend verifyPhoneNumber:request callback:callback]; + return; + } + + if (!credential.verificationID.length) { + callback(nil, [FIRAuthErrorUtils missingVerificationIDErrorWithMessage:nil]); + return; + } + if (!credential.verificationCode.length) { + callback(nil, [FIRAuthErrorUtils missingVerificationCodeErrorWithMessage:nil]); + return; + } + FIRVerifyPhoneNumberRequest *request = + [[FIRVerifyPhoneNumberRequest alloc]initWithVerificationID:credential.verificationID + verificationCode:credential.verificationCode + operation:operation + requestConfiguration:_requestConfiguration]; + [FIRAuthBackend verifyPhoneNumber:request callback:callback]; +} + +#endif + +/** @fn internalSignInAndRetrieveDataWithCustomToken:completion: + @brief Signs in a Firebase user given a custom token. + @param token A self-signed custom auth token. + @param completion A block which is invoked when the custom token sign in request completes. + */ +- (void)internalSignInAndRetrieveDataWithCustomToken:(NSString *)token + completion:(nullable FIRAuthDataResultCallback) + completion { + FIRVerifyCustomTokenRequest *request = + [[FIRVerifyCustomTokenRequest alloc] initWithToken:token + requestConfiguration:_requestConfiguration]; + [FIRAuthBackend verifyCustomToken:request + callback:^(FIRVerifyCustomTokenResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + if (completion) { + completion(nil, error); + return; + } + } + [self completeSignInWithAccessToken:response.IDToken + accessTokenExpirationDate:response.approximateExpirationDate + refreshToken:response.refreshToken + anonymous:NO + callback:^(FIRUser *_Nullable user, + NSError *_Nullable error) { + if (error && completion) { + completion(nil, error); + return; + } + FIRAdditionalUserInfo *additonalUserInfo = + [[FIRAdditionalUserInfo alloc] initWithProviderID:nil + profile:nil + username:nil + isNewUser:response.isNewUser]; + FIRAuthDataResult *result = user ? + [[FIRAuthDataResult alloc] initWithUser:user additionalUserInfo:additonalUserInfo] : nil; + if (completion) { + completion(result, error); + } + }]; + }]; +} + +/** @fn internalCreateUserWithEmail:password:completion: + @brief Makes a backend request attempting to create a new Firebase user given an email address + and password. + @param email The email address used to create the new Firebase user. + @param password The password used to create the new Firebase user. + @param completion Optionally; a block which is invoked when the request finishes. + */ +- (void)internalCreateUserWithEmail:(NSString *)email + password:(NSString *)password + completion:(nullable FIRSignupNewUserCallback)completion { + FIRSignUpNewUserRequest *request = + [[FIRSignUpNewUserRequest alloc] initWithEmail:email + password:password + displayName:nil + requestConfiguration:_requestConfiguration]; + if (![request.password length]) { + completion(nil, [FIRAuthErrorUtils + weakPasswordErrorWithServerResponseReason:kMissingPasswordReason]); + return; + } + if (![request.email length]) { + completion(nil, [FIRAuthErrorUtils missingEmailErrorWithMessage:nil]); + return; + } + [FIRAuthBackend signUpNewUser:request callback:completion]; +} + +/** @fn internalSignInAnonymouslyWithCompletion: + @param completion A block which is invoked when the anonymous sign in request completes. + */ +- (void)internalSignInAnonymouslyWithCompletion:(FIRSignupNewUserCallback)completion { + FIRSignUpNewUserRequest *request = + [[FIRSignUpNewUserRequest alloc]initWithRequestConfiguration:_requestConfiguration]; + [FIRAuthBackend signUpNewUser:request + callback:completion]; +} + +/** @fn possiblyPostAuthStateChangeNotification + @brief Posts the auth state change notificaton if current user's token has been changed. + */ +- (void)possiblyPostAuthStateChangeNotification { + NSString *token = _currentUser.rawAccessToken; + if (_lastNotifiedUserToken == token || + (token != nil && [_lastNotifiedUserToken isEqualToString:token])) { + return; + } + _lastNotifiedUserToken = token; + if (_autoRefreshTokens) { + // Shedule new refresh task after successful attempt. + [self scheduleAutoTokenRefresh]; + } + NSMutableDictionary *internalNotificationParameters = [NSMutableDictionary dictionary]; + if (self.app) { + internalNotificationParameters[FIRAuthStateDidChangeInternalNotificationAppKey] = self.app; + } + if (token.length) { + internalNotificationParameters[FIRAuthStateDidChangeInternalNotificationTokenKey] = token; + } + internalNotificationParameters[FIRAuthStateDidChangeInternalNotificationUIDKey] = _currentUser.uid; + NSNotificationCenter *notifications = [NSNotificationCenter defaultCenter]; + dispatch_async(dispatch_get_main_queue(), ^{ + [notifications postNotificationName:FIRAuthStateDidChangeInternalNotification + object:self + userInfo:internalNotificationParameters]; + [notifications postNotificationName:FIRAuthStateDidChangeNotification + object:self]; + }); +} + +- (BOOL)updateKeychainWithUser:(FIRUser *)user error:(NSError *_Nullable *_Nullable)error { + if (user != _currentUser) { + // No-op if the user is no longer signed in. This is not considered an error as we don't check + // whether the user is still current on other callbacks of user operations either. + return YES; + } + if ([self saveUser:user error:error]) { + [self possiblyPostAuthStateChangeNotification]; + return YES; + } + return NO; +} + +/** @fn setKeychainServiceNameForApp + @brief Sets the keychain service name global data for the particular app. + @param app The Firebase app to set keychain service name for. + */ ++ (void)setKeychainServiceNameForApp:(FIRApp *)app { + @synchronized (self) { + gKeychainServiceNameForAppName[app.name] = + [@"firebase_auth_" stringByAppendingString:app.options.googleAppID]; + } +} + +/** @fn keychainServiceNameForAppName: + @brief Gets the keychain service name global data for the particular app by name. + @param appName The name of the Firebase app to get keychain service name for. + */ ++ (NSString *)keychainServiceNameForAppName:(NSString *)appName { + @synchronized (self) { + return gKeychainServiceNameForAppName[appName]; + } +} + +/** @fn deleteKeychainServiceNameForAppName: + @brief Deletes the keychain service name global data for the particular app by name. + @param appName The name of the Firebase app to delete keychain service name for. + */ ++ (void)deleteKeychainServiceNameForAppName:(NSString *)appName { + @synchronized (self) { + [gKeychainServiceNameForAppName removeObjectForKey:appName]; + } +} + +/** @fn scheduleAutoTokenRefreshWithDelay: + @brief Schedules a task to automatically refresh tokens on the current user. The token refresh + is scheduled 5 minutes before the scheduled expiration time. + @remarks If the token expires in less than 5 minutes, schedule the token refresh immediately. + */ +- (void)scheduleAutoTokenRefresh { + NSTimeInterval tokenExpirationInterval = + [_currentUser.accessTokenExpirationDate timeIntervalSinceNow] - kTokenRefreshHeadStart; + [self scheduleAutoTokenRefreshWithDelay:MAX(tokenExpirationInterval, 0) retry:NO]; +} + +/** @fn scheduleAutoTokenRefreshWithDelay: + @brief Schedules a task to automatically refresh tokens on the current user. + @param delay The delay in seconds after which the token refresh task should be scheduled to be + executed. + @param retry Flag to determine whether the invocation is a retry attempt or not. + */ +- (void)scheduleAutoTokenRefreshWithDelay:(NSTimeInterval)delay retry:(BOOL)retry { + NSString *accessToken = _currentUser.rawAccessToken; + if (!accessToken) { + return; + } + if (retry) { + FIRLogInfo(kFIRLoggerAuth, @"I-AUT000003", + @"Token auto-refresh re-scheduled in %02d:%02d " + @"because of error on previous refresh attempt.", + (int)ceil(delay) / 60, (int)ceil(delay) % 60); + } else { + FIRLogInfo(kFIRLoggerAuth, @"I-AUT000004", + @"Token auto-refresh scheduled in %02d:%02d for the new token.", + (int)ceil(delay) / 60, (int)ceil(delay) % 60); + } + _autoRefreshScheduled = YES; + __weak FIRAuth *weakSelf = self; + [[FIRAuthDispatcher sharedInstance] dispatchAfterDelay:delay + queue:FIRAuthGlobalWorkQueue() + task:^(void) { + FIRAuth *strongSelf = weakSelf; + if (!strongSelf) { + return; + } + if (![strongSelf->_currentUser.rawAccessToken isEqualToString:accessToken]) { + // Another auto refresh must have been scheduled, so keep _autoRefreshScheduled unchanged. + return; + } + strongSelf->_autoRefreshScheduled = NO; + if (strongSelf->_isAppInBackground) { + return; + } + NSString *uid = strongSelf->_currentUser.uid; + [strongSelf->_currentUser internalGetTokenForcingRefresh:YES + callback:^(NSString *_Nullable token, + NSError *_Nullable error) { + if (![strongSelf->_currentUser.uid isEqualToString:uid]) { + return; + } + if (error) { + // Kicks off exponential back off logic to retry failed attempt. Starts with one minute + // delay (60 seconds) if this is the first failed attempt. + NSTimeInterval rescheduleDelay; + if (retry) { + rescheduleDelay = MIN(delay * 2, kMaxWaitTimeForBackoff); + } else { + rescheduleDelay = 60; + } + [strongSelf scheduleAutoTokenRefreshWithDelay:rescheduleDelay retry:YES]; + } + }]; + }]; +} + +#pragma mark - + +/** @fn completeSignInWithTokenService:callback: + @brief Completes a sign-in flow once we have access and refresh tokens for the user. + @param accessToken The STS access token. + @param accessTokenExpirationDate The approximate expiration date of the access token. + @param refreshToken The STS refresh token. + @param anonymous Whether or not the user is anonymous. + @param callback Called when the user has been signed in or when an error occurred. Invoked + asynchronously on the global auth work queue in the future. + */ +- (void)completeSignInWithAccessToken:(nullable NSString *)accessToken + accessTokenExpirationDate:(nullable NSDate *)accessTokenExpirationDate + refreshToken:(nullable NSString *)refreshToken + anonymous:(BOOL)anonymous + callback:(FIRAuthResultCallback)callback { + [FIRUser retrieveUserWithAuth:self + accessToken:accessToken + accessTokenExpirationDate:accessTokenExpirationDate + refreshToken:refreshToken + anonymous:anonymous + callback:callback]; +} + +/** @fn signInFlowAuthResultCallbackByDecoratingCallback: + @brief Creates a FIRAuthResultCallback block which wraps another FIRAuthResultCallback; trying + to update the current user before forwarding it's invocations along to a subject block + @param callback Called when the user has been updated or when an error has occurred. Invoked + asynchronously on the main thread in the future. + @return Returns a block that updates the current user. + @remarks Typically invoked as part of the complete sign-in flow. For any other uses please + consider alternative ways of updating the current user. +*/ +- (FIRAuthResultCallback)signInFlowAuthResultCallbackByDecoratingCallback: + (nullable FIRAuthResultCallback)callback { + return ^(FIRUser *_Nullable user, NSError *_Nullable error) { + if (error) { + if (callback) { + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, error); + }); + } + return; + } + if (![self updateCurrentUser:user byForce:NO savingToDisk:YES error:&error]) { + if (callback) { + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, error); + }); + } + return; + } + if (callback) { + dispatch_async(dispatch_get_main_queue(), ^{ + callback(user, nil); + }); + } + }; +} + +/** @fn signInFlowAuthDataResultCallbackByDecoratingCallback: + @brief Creates a FIRAuthDataResultCallback block which wraps another FIRAuthDataResultCallback; + trying to update the current user before forwarding it's invocations along to a subject + block. + @param callback Called when the user has been updated or when an error has occurred. Invoked + asynchronously on the main thread in the future. + @return Returns a block that updates the current user. + @remarks Typically invoked as part of the complete sign-in flow. For any other uses please + consider alternative ways of updating the current user. +*/ +- (FIRAuthDataResultCallback)signInFlowAuthDataResultCallbackByDecoratingCallback: + (nullable FIRAuthDataResultCallback)callback { + return ^(FIRAuthDataResult *_Nullable authResult, NSError *_Nullable error) { + if (error) { + if (callback) { + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, error); + }); + } + return; + } + if (![self updateCurrentUser:authResult.user byForce:NO savingToDisk:YES error:&error]) { + if (callback) { + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, error); + }); + } + return; + } + if (callback) { + dispatch_async(dispatch_get_main_queue(), ^{ + callback(authResult, nil); + }); + } + }; +} + +#pragma mark - User-Related Methods + +/** @fn updateCurrentUser:byForce:savingToDisk:error: + @brief Update the current user; initializing the user's internal properties correctly, and + optionally saving the user to disk. + @remarks This method is called during: sign in and sign out events, as well as during class + initialization time. The only time the saveToDisk parameter should be set to NO is during + class initialization time because the user was just read from disk. + @param user The user to use as the current user (including nil, which is passed at sign out + time.) + @param saveToDisk Indicates the method should persist the user data to disk. + */ +- (BOOL)updateCurrentUser:(nullable FIRUser *)user + byForce:(BOOL)force + savingToDisk:(BOOL)saveToDisk + error:(NSError *_Nullable *_Nullable)error { + if (user == _currentUser) { + [self possiblyPostAuthStateChangeNotification]; + return YES; + } + BOOL success = YES; + if (saveToDisk) { + success = [self saveUser:user error:error]; + } + if (success || force) { + _currentUser = user; + [self possiblyPostAuthStateChangeNotification]; + } + return success; +} + +/** @fn saveUser:error: + @brief Persists user. + @param user The user to save. + @param outError Return value for any error which occurs. + @return @YES on success, @NO otherwise. + */ +- (BOOL)saveUser:(nullable FIRUser *)user + error:(NSError *_Nullable *_Nullable)outError { + BOOL success; + + if (!self.userAccessGroup) { + NSString *userKey = [NSString stringWithFormat:kUserKey, _firebaseAppName]; + if (!user) { + success = [_keychainServices removeDataForKey:userKey error:outError]; + } else { + // Encode the user object. + NSMutableData *archiveData = [NSMutableData data]; + NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:archiveData]; + [archiver encodeObject:user forKey:userKey]; + [archiver finishEncoding]; + + // Save the user object's encoded value. + success = [_keychainServices setData:archiveData forKey:userKey error:outError]; + } + } else { + if (!user) { + success = [self.storedUserManager removeStoredUserForAccessGroup:self.userAccessGroup + projectIdentifier:self.app.options.APIKey + error:outError]; + } else { + success = [self.storedUserManager setStoredUser:user + forAccessGroup:self.userAccessGroup + projectIdentifier:self.app.options.APIKey + error:outError]; + } + } + + return success; +} + +/** @fn getUser:error: + @brief Retrieves the saved user associated, if one exists, from the keychain. + @param outUser An out parameter which is populated with the saved user, if one exists. + @param error Return value for any error which occurs. + @return YES if the operation was a success (irrespective of whether or not a saved user existed + for the given @c firebaseAppId,) NO if an error occurred. + */ +- (BOOL)getUser:(FIRUser *_Nullable *)outUser + error:(NSError *_Nullable *_Nullable)error { + if (!self.userAccessGroup) { + NSString *userKey = [NSString stringWithFormat:kUserKey, _firebaseAppName]; + + NSError *keychainError; + NSData *encodedUserData = [_keychainServices dataForKey:userKey error:&keychainError]; + if (keychainError) { + if (error) { + *error = keychainError; + } + return NO; + } + if (!encodedUserData) { + *outUser = nil; + return YES; + } + NSKeyedUnarchiver *unarchiver = + [[NSKeyedUnarchiver alloc] initForReadingWithData:encodedUserData]; + FIRUser *user = [unarchiver decodeObjectOfClass:[FIRUser class] forKey:userKey]; + user.auth = self; + *outUser = user; + + return YES; + } else { + FIRUser *user = [self.storedUserManager getStoredUserForAccessGroup:self.userAccessGroup + projectIdentifier:self.app.options.APIKey + error:error]; + user.auth = self; + *outUser = user; + if (user) { + return YES; + } else { + if (error && *error) { + return NO; + } else { + return YES; + } + } + } +} + +#pragma mark - Interoperability + ++ (nonnull NSArray *)componentsToRegister { + FIRComponentCreationBlock authCreationBlock = + ^id _Nullable(FIRComponentContainer *_Nonnull container, BOOL *_Nonnull isCacheable) { + *isCacheable = YES; + return [[FIRAuth alloc] initWithApp:container.app]; + }; + FIRComponent *authInterop = [FIRComponent componentWithProtocol:@protocol(FIRAuthInterop) + instantiationTiming:FIRInstantiationTimingAlwaysEager + dependencies:@[] + creationBlock:authCreationBlock]; + return @[authInterop]; +} + +#pragma mark - FIRComponentLifecycleMaintainer + +- (void)appWillBeDeleted:(nonnull FIRApp *)app { + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + // This doesn't stop any request already issued, see b/27704535 . + NSString *keychainServiceName = [FIRAuth keychainServiceNameForAppName:app.name]; + if (keychainServiceName) { + [[self class] deleteKeychainServiceNameForAppName:app.name]; + FIRAuthKeychainServices *keychain = [[FIRAuthKeychainServices alloc] initWithService:keychainServiceName]; + NSString *userKey = [NSString stringWithFormat:kUserKey, app.name]; + [keychain removeDataForKey:userKey error:NULL]; + } + dispatch_async(dispatch_get_main_queue(), ^{ + // TODO: Move over to fire an event instead, once ready. + [[NSNotificationCenter defaultCenter] postNotificationName:FIRAuthStateDidChangeNotification + object:nil]; + }); + }); +} + +#pragma mark - FIRAuthInterop + +- (void)getTokenForcingRefresh:(BOOL)forceRefresh withCallback:(FIRTokenCallback)callback { + __weak FIRAuth *weakSelf = self; + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + FIRAuth *strongSelf = weakSelf; + // Enable token auto-refresh if not aleady enabled. + if (strongSelf && !strongSelf->_autoRefreshTokens) { + FIRLogInfo(kFIRLoggerAuth, @"I-AUT000002", @"Token auto-refresh enabled."); + strongSelf->_autoRefreshTokens = YES; + [strongSelf scheduleAutoTokenRefresh]; + +#if TARGET_OS_IOS || TARGET_OS_TV // TODO: Is a similar mechanism needed on macOS? + strongSelf->_applicationDidBecomeActiveObserver = [[NSNotificationCenter defaultCenter] + addObserverForName:UIApplicationDidBecomeActiveNotification + object:nil + queue:nil + usingBlock:^(NSNotification *notification) { + FIRAuth *strongSelf = weakSelf; + if (strongSelf) { + strongSelf->_isAppInBackground = NO; + if (!strongSelf->_autoRefreshScheduled) { + [weakSelf scheduleAutoTokenRefresh]; + } + } + }]; + strongSelf->_applicationDidEnterBackgroundObserver = [[NSNotificationCenter defaultCenter] + addObserverForName:UIApplicationDidEnterBackgroundNotification + object:nil + queue:nil + usingBlock:^(NSNotification *notification) { + FIRAuth *strongSelf = weakSelf; + if (strongSelf) { + strongSelf->_isAppInBackground = YES; + } + }]; +#endif + } + // Call back with 'nil' if there is no current user. + if (!strongSelf || !strongSelf->_currentUser) { + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, nil); + }); + return; + } + // Call back with current user token. + [strongSelf->_currentUser internalGetTokenForcingRefresh:forceRefresh + callback:^(NSString *_Nullable token, + NSError *_Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^{ + callback(token, error); + }); + }]; + }); +} + +- (nullable NSString *)getUserID { + return _currentUser.uid; +} + +#pragma mark - Keychain sharing + +- (BOOL)useUserAccessGroup:(NSString *_Nullable)accessGroup + error:(NSError *_Nullable *_Nullable)outError { + BOOL success; + success = [self.storedUserManager setStoredUserAccessGroup:accessGroup error:outError]; + if (!success) { + return NO; + } + + FIRUser *user = [self getStoredUserForAccessGroup:accessGroup error:outError]; + if (!user && outError && *outError) { + return NO; + } + success = [self updateCurrentUser:user byForce:NO savingToDisk:NO error:outError]; + if (!success) { + return NO; + } + + if(_userAccessGroup == nil && accessGroup != nil) { + NSString *userKey = [NSString stringWithFormat:kUserKey, _firebaseAppName]; + [_keychainServices removeDataForKey:userKey error:outError]; + } + _userAccessGroup = accessGroup; + self->_lastNotifiedUserToken = user.rawAccessToken; + + return YES; +} + +- (nullable FIRUser *)getStoredUserForAccessGroup:(NSString *_Nullable)accessGroup + error:(NSError *_Nullable *_Nullable)outError { + FIRUser *user; + if (!accessGroup) { + NSString *userKey = [NSString stringWithFormat:kUserKey, _firebaseAppName]; + NSData *encodedUserData = [_keychainServices dataForKey:userKey error:outError]; + if (!encodedUserData) { + return nil; + } + + NSKeyedUnarchiver *unarchiver = + [[NSKeyedUnarchiver alloc] initForReadingWithData:encodedUserData]; + user = [unarchiver decodeObjectOfClass:[FIRUser class] forKey:userKey]; + } else { + user = [self.storedUserManager getStoredUserForAccessGroup:self.userAccessGroup + projectIdentifier:self.app.options.APIKey + error:outError]; + } + + user.auth = self; + return user; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthDataResult.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthDataResult.m new file mode 100644 index 0000000..e6c4b92 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthDataResult.m @@ -0,0 +1,85 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRAuthDataResult_Internal.h" + +#import "FIRAdditionalUserInfo.h" +#import "FIRUser.h" +#import "FIROAuthCredential.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRAuthDataResult + +/** @var kAdditionalUserInfoCodingKey + @brief The key used to encode the additionalUserInfo property for NSSecureCoding. + */ +static NSString *const kAdditionalUserInfoCodingKey = @"additionalUserInfo"; + +/** @var kUserCodingKey + @brief The key used to encode the user property for NSSecureCoding. + */ +static NSString *const kUserCodingKey = @"user"; + +/** @var kCredentialCodingKey + @brief The key used to encode the credential for NSSecureCoding. + */ +static NSString *const kCredentialCodingKey = @"credential"; + +- (nullable instancetype)initWithUser:(FIRUser *)user + additionalUserInfo:(nullable FIRAdditionalUserInfo *)additionalUserInfo { + return [self initWithUser:user additionalUserInfo:additionalUserInfo credential:nil]; +} + +- (nullable instancetype)initWithUser:(FIRUser *)user + additionalUserInfo:(nullable FIRAdditionalUserInfo *)additionalUserInfo + credential:(nullable FIROAuthCredential *)credential { + self = [super init]; + if (self) { + _additionalUserInfo = additionalUserInfo; + _user = user; + _credential = credential; + } + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + FIRUser *user = + [aDecoder decodeObjectOfClass:[FIRUser class] forKey:kUserCodingKey]; + FIRAdditionalUserInfo *additionalUserInfo = + [aDecoder decodeObjectOfClass:[FIRAdditionalUserInfo class] + forKey:kAdditionalUserInfoCodingKey]; + FIROAuthCredential *credential = + [aDecoder decodeObjectOfClass:[FIROAuthCredential class] + forKey:kCredentialCodingKey]; + return [self initWithUser:user additionalUserInfo:additionalUserInfo credential:credential]; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_user forKey:kUserCodingKey]; + [aCoder encodeObject:_additionalUserInfo forKey:kAdditionalUserInfoCodingKey]; + [aCoder encodeObject:_credential forKey:kCredentialCodingKey]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthDataResult_Internal.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthDataResult_Internal.h new file mode 100644 index 0000000..1c6f31c --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthDataResult_Internal.h @@ -0,0 +1,46 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRAuthDataResult.h" + +@class FIROAuthCredential; + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRAuthDataResult () + +/** @fn initWithUser:additionalUserInfo: + @brief Designated initializer. + @param user The signed in user reference. + @param additionalUserInfo The additional user info if available. + */ +- (nullable instancetype)initWithUser:(FIRUser *)user + additionalUserInfo:(nullable FIRAdditionalUserInfo *)additionalUserInfo; + +/** @fn initWithUser:additionalUserInfo: + @brief Designated initializer. + @param user The signed in user reference. + @param additionalUserInfo The additional user info if available. + @param credential The updated OAuth credential if available. + */ +- (nullable instancetype)initWithUser:(FIRUser *)user + additionalUserInfo:(nullable FIRAdditionalUserInfo *)additionalUserInfo + credential:(nullable FIROAuthCredential *)credential + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthDispatcher.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthDispatcher.h new file mode 100644 index 0000000..f8ddca5 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthDispatcher.h @@ -0,0 +1,63 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** @typedef FIRAuthDispatcherImplBlock + @brief The type of block which can be set as the implementation for @c + dispatchAfterDelay:queue:callback: . + + @param delay The delay in seconds after which the task will be scheduled to execute. + @param queue The dispatch queue on which the task will be submitted. + @param task The task (block) to be scheduled for future execution. + */ +typedef void(^FIRAuthDispatcherImplBlock)(NSTimeInterval delay, + dispatch_queue_t queue, + void (^task)(void)); + +/** @class FIRAuthDispatchAfter + @brief A utility class used to facilitate scheduling tasks to be executed in the future. + */ +@interface FIRAuthDispatcher : NSObject + +/** @property dispatchAfterImplementation + @brief Allows custom implementation of dispatchAfterDelay:queue:callback:. + @remarks Set to nil to restore default implementation. + */ +@property(nonatomic, nullable, copy) FIRAuthDispatcherImplBlock dispatchAfterImplementation; + +/** @fn dispatchAfterDelay:queue:callback: + @brief Schedules task in the future after a specified delay. + + @param delay The delay in seconds after which the task will be scheduled to execute. + @param queue The dispatch queue on which the task will be submitted. + @param task The task (block) to be scheduled for future execution. + */ + - (void)dispatchAfterDelay:(NSTimeInterval)delay + queue:(dispatch_queue_t)queue + task:(void (^)(void))task; + +/** @fn sharedInstance + @brief Gets the shared instance of this class. + @returns The shared instance of this clss + */ ++ (instancetype)sharedInstance; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthDispatcher.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthDispatcher.m new file mode 100644 index 0000000..78ed2e3 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthDispatcher.m @@ -0,0 +1,46 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRAuthDispatcher.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRAuthDispatcher + +@synthesize dispatchAfterImplementation = _dispatchAfterImplementation; + ++ (instancetype)sharedInstance { + static dispatch_once_t onceToken; + static FIRAuthDispatcher *sharedInstance; + dispatch_once(&onceToken, ^{ + sharedInstance = [[self alloc] init]; + }); + return sharedInstance; +} + +- (void)dispatchAfterDelay:(NSTimeInterval)delay + queue:(dispatch_queue_t)queue + task:(void (^)(void))task { + if (_dispatchAfterImplementation) { + _dispatchAfterImplementation(delay, queue, task); + return; + } + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay * NSEC_PER_SEC), queue, task); +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthGlobalWorkQueue.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthGlobalWorkQueue.h new file mode 100644 index 0000000..55bb1a7 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthGlobalWorkQueue.h @@ -0,0 +1,31 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** @fn FIRAuthGlobalWorkQueue + @brief Retrieves the global serial work queue for Firebase Auth. + @return The global serial dispatch queue. + @remarks To ensure thread safety, all auth code must be executed in either this global work + queue, or a serial queue that has its target queue set to this work queue. All public method + implementations that may involve contested code shall dispatch to this work queue as the + first thing they do. + */ +extern dispatch_queue_t FIRAuthGlobalWorkQueue(void); + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthGlobalWorkQueue.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthGlobalWorkQueue.m new file mode 100644 index 0000000..6295b8b --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthGlobalWorkQueue.m @@ -0,0 +1,30 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRAuthGlobalWorkQueue.h" + +NS_ASSUME_NONNULL_BEGIN + +dispatch_queue_t FIRAuthGlobalWorkQueue() { + static dispatch_once_t once; + static dispatch_queue_t queue; + dispatch_once(&once, ^{ + queue = dispatch_queue_create("com.google.firebase.auth.globalWorkQueue", NULL); + }); + return queue; +} + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthOperationType.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthOperationType.h new file mode 100644 index 0000000..15d3dd7 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthOperationType.h @@ -0,0 +1,47 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + @brief Indicates the type of operation performed for RPCs that support the operation + parameter. + */ +typedef NS_ENUM(NSInteger, FIRAuthOperationType) { + /** Indicates that the operation type is uspecified. + */ + FIRAuthOperationTypeUnspecified = 0, + + /** Indicates that the operation type is sign in or sign up. + */ + FIRAuthOperationTypeSignUpOrSignIn = 1, + + /** Indicates that the operation type is reauthentication. + */ + FIRAuthOperationTypeReauth = 2, + + /** Indicates that the operation type is update. + */ + FIRAuthOperationTypeUpdate = 3, + + /** Indicates that the operation type is link. + */ + FIRAuthOperationTypeLink = 4, +}; + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthSerialTaskQueue.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthSerialTaskQueue.h new file mode 100644 index 0000000..cdae046 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthSerialTaskQueue.h @@ -0,0 +1,50 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** @typedef FIRAuthSerialTaskCompletionBlock + @brief The type of method a @c FIRAuthSerialTask must call when it is complete. + */ +typedef void (^FIRAuthSerialTaskCompletionBlock)(void); + +/** @typedef FIRAuthSerialTask + @brief Represents a unit of work submitted to a task queue. + @param complete The task MUST call the complete method when done. + */ +typedef void (^FIRAuthSerialTask)(FIRAuthSerialTaskCompletionBlock complete); + +/** @class FIRAuthSerialTaskQueue + @brief An easy to use serial task queue which supports a callback-based completion notification + system for easy asyncronous call chaining. + */ +@interface FIRAuthSerialTaskQueue : NSObject + +/** @fn enqueueTask: + @brief Enqueues a task for serial execution in the queue. + @remarks The task MUST call the complete method when done. This method is thread-safe. + The task block won't be executed concurrently with any other blocks in other task queues or + the global work queue as returned by @c FIRAuthGlobalWorkQueue , but an uncompleted task + (e.g. task block finished executation before complete method is called at a later time) + does not affect other task queues or the global work queue. + */ +- (void)enqueueTask:(FIRAuthSerialTask)task; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthSerialTaskQueue.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthSerialTaskQueue.m new file mode 100644 index 0000000..78005e0 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthSerialTaskQueue.m @@ -0,0 +1,56 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRAuthSerialTaskQueue.h" + +#import "FIRAuthGlobalWorkQueue.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRAuthSerialTaskQueue { + /** @var _dispatchQueue + @brief The asyncronous dispatch queue into which tasks are enqueued and processed + serially. + */ + dispatch_queue_t _dispatchQueue; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _dispatchQueue = dispatch_queue_create("com.google.firebase.auth.serialTaskQueue", NULL); + dispatch_set_target_queue(_dispatchQueue, FIRAuthGlobalWorkQueue()); + } + return self; +} + +- (void)enqueueTask:(FIRAuthSerialTask)task { + // This dispatch queue will run tasks serially in FIFO order, as long as it's not suspended. + dispatch_async(self->_dispatchQueue, ^{ + // But as soon as a task is started, stop other tasks from running until the task calls it's + // completion handler, which allows the queue to resume processing of tasks. This allows the + // task to perform other asyncronous actions on other dispatch queues and "get back to us" when + // all of their sub-tasks are complete. + dispatch_suspend(self->_dispatchQueue); + task(^{ + dispatch_resume(self->_dispatchQueue); + }); + }); +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthSettings.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthSettings.m new file mode 100644 index 0000000..8ed5bb6 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthSettings.m @@ -0,0 +1,33 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRAuthSettings.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRAuthSettings + +- (instancetype)init { + self = [super init]; + if (self) { + _appVerificationDisabledForTesting = NO; + } + return self; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthTokenResult.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthTokenResult.m new file mode 100644 index 0000000..3a06ac6 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthTokenResult.m @@ -0,0 +1,110 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRAuthTokenResult_Internal.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kExpirationDateKey + @brief The key used to encode the expirationDate property for NSSecureCoding. + */ +static NSString *const kExpirationDateKey = @"expiratinDate"; + +/** @var kTokenKey + @brief The key used to encode the token property for NSSecureCoding. + */ +static NSString *const kTokenKey = @"token"; + +/** @var kAuthDateKey + @brief The key used to encode the authDate property for NSSecureCoding. + */ +static NSString *const kAuthDateKey = @"authDate"; + +/** @var kIssuedDateKey + @brief The key used to encode the issuedDate property for NSSecureCoding. + */ +static NSString *const kIssuedDateKey = @"issuedDate"; + +/** @var kSignInProviderKey + @brief The key used to encode the signInProvider property for NSSecureCoding. + */ +static NSString *const kSignInProviderKey = @"signInProvider"; + +/** @var kClaimsKey + @brief The key used to encode the claims property for NSSecureCoding. + */ +static NSString *const kClaimsKey = @"claims"; + +@implementation FIRAuthTokenResult + +- (instancetype)initWithToken:(NSString *)token + expirationDate:(NSDate *)expirationDate + authDate:(NSDate *)authDate + issuedAtDate:(NSDate *)issuedAtDate + signInProvider:(NSString *)signInProvider + claims:(NSDictionary *)claims { + self = [super init]; + if (self) { + _token = token; + _expirationDate = expirationDate; + _authDate = authDate; + _issuedAtDate = issuedAtDate; + _signInProvider = signInProvider; + _claims = claims; + } + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + NSString *token = + [aDecoder decodeObjectOfClass:[NSDate class] forKey:kTokenKey]; + NSDate *expirationDate = + [aDecoder decodeObjectOfClass:[NSDate class] forKey:kExpirationDateKey]; + NSDate *authDate = + [aDecoder decodeObjectOfClass:[NSDate class] forKey:kAuthDateKey]; + NSDate *issuedAtDate = + [aDecoder decodeObjectOfClass:[NSDate class] forKey:kAuthDateKey]; + NSString *signInProvider = + [aDecoder decodeObjectOfClass:[NSString class] forKey:kSignInProviderKey]; + NSDictionary *claims = + [aDecoder decodeObjectOfClass:[NSDictionary class] forKey:kClaimsKey]; + + return [self initWithToken:token + expirationDate:expirationDate + authDate:authDate + issuedAtDate:issuedAtDate + signInProvider:signInProvider + claims:claims]; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_token forKey:kTokenKey]; + [aCoder encodeObject:_expirationDate forKey:kExpirationDateKey]; + [aCoder encodeObject:_authDate forKey:kAuthDateKey]; + [aCoder encodeObject:_issuedAtDate forKey:kIssuedDateKey]; + [aCoder encodeObject:_signInProvider forKey:kSignInProviderKey]; + [aCoder encodeObject:_claims forKey:kClaimsKey]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthTokenResult_Internal.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthTokenResult_Internal.h new file mode 100644 index 0000000..2914f2a --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuthTokenResult_Internal.h @@ -0,0 +1,37 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #import + + #import "FIRAuthTokenResult.h" + + NS_ASSUME_NONNULL_BEGIN + +/** @extension FIRAuthAPNSTokenResult + @brief An internal class used to expose internal methods of FIRAuthAPNSTokenResult. + */ +@interface FIRAuthTokenResult () + +- (instancetype)initWithToken:(NSString *)token + expirationDate:(NSDate *)expirationDate + authDate:(NSDate *)authDate + issuedAtDate:(NSDate *)issuedAtDate + signInProvider:(NSString *)signInProvider + claims:(NSDictionary *)claims; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuth_Internal.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuth_Internal.h new file mode 100644 index 0000000..ce01224 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Auth/FIRAuth_Internal.h @@ -0,0 +1,122 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuth.h" + +#import + +@class FIRAuthRequestConfiguration; +@class FIRAuthURLPresenter; + +#if TARGET_OS_IOS +@class FIRAuthAPNSTokenManager; +@class FIRAuthAppCredentialManager; +@class FIRAuthNotificationManager; +#endif + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRAuth () + +/** @property requestConfiguration + @brief The configuration object comprising of paramters needed to make a request to Firebase + Auth's backend. + */ +@property(nonatomic, copy, readonly) FIRAuthRequestConfiguration *requestConfiguration; + +#if TARGET_OS_IOS + +/** @property tokenManager + @brief The manager for APNs tokens used by phone number auth. + */ +@property(nonatomic, strong, readonly) FIRAuthAPNSTokenManager *tokenManager; + +/** @property appCredentailManager + @brief The manager for app credentials used by phone number auth. + */ +@property(nonatomic, strong, readonly) FIRAuthAppCredentialManager *appCredentialManager; + +/** @property notificationManager + @brief The manager for remote notifications used by phone number auth. + */ +@property(nonatomic, strong, readonly) FIRAuthNotificationManager *notificationManager; + +#endif // TARGET_OS_IOS + +/** @property authURLPresenter + @brief An object that takes care of presenting URLs via the auth instance. + */ +@property(nonatomic, strong, readonly) FIRAuthURLPresenter *authURLPresenter; + +/** @fn initWithAPIKey:appName: + @brief Designated initializer. + @param APIKey The Google Developers Console API key for making requests from your app. + @param appName The name property of the previously created @c FIRApp instance. + */ +- (nullable instancetype)initWithAPIKey:(NSString *)APIKey + appName:(NSString *)appName NS_DESIGNATED_INITIALIZER; + +/** @fn getUserID + @brief Gets the identifier of the current user, if any. + @return The identifier of the current user, or nil if there is no current user. + */ +- (nullable NSString *)getUserID; + +/** @fn updateKeychainWithUser:error: + @brief Updates the keychain for the given user. + @param user The user to be updated. + @param error The error caused the method to fail if the method returns NO. + @return Whether updating keychain has succeeded or not. + @remarks Called by @c FIRUser when user info or token changes occur. + */ +- (BOOL)updateKeychainWithUser:(FIRUser *)user error:(NSError *_Nullable *_Nullable)error; + +/** @fn internalSignInWithCredential:callback: + @brief Convenience method for @c internalSignInAndRetrieveDataWithCredential:callback: + This method doesn't return additional identity provider data. +*/ +- (void)internalSignInWithCredential:(FIRAuthCredential *)credential + callback:(FIRAuthResultCallback)callback; + +/** @fn internalSignInAndRetrieveDataWithCredential:callback: + @brief Asynchronously signs in Firebase with the given 3rd party credentials (e.g. a Facebook + login Access Token, a Google ID Token/Access Token pair, etc.) and returns additional + identity provider data. + @param credential The credential supplied by the IdP. + @param isReauthentication Indicates whether or not the current invocation originated from an + attempt to reauthenticate. + @param callback A block which is invoked when the sign in finishes (or is cancelled.) Invoked + asynchronously on the auth global work queue in the future. + @remarks This is the internal counterpart of this method, which uses a callback that does not + update the current user. + */ +- (void)internalSignInAndRetrieveDataWithCredential:(FIRAuthCredential *)credential + isReauthentication:(BOOL)isReauthentication + callback:(nullable FIRAuthDataResultCallback)callback; + +/** @fn signOutByForceWithUserID:error: + @brief Signs out the current user. + @param userID The ID of the user to force sign out. + @param error An optional out parameter for error results. + @return @YES when the sign out request was successful. @NO otherwise. + */ +- (BOOL)signOutByForceWithUserID:(NSString *)userID error:(NSError *_Nullable *_Nullable)error; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Email/FIREmailAuthProvider.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Email/FIREmailAuthProvider.m new file mode 100644 index 0000000..373c0b1 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Email/FIREmailAuthProvider.m @@ -0,0 +1,41 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIREmailAuthProvider.h" + +#import "FIREmailPasswordAuthCredential.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIREmailAuthProvider + +- (instancetype)init { + @throw [NSException exceptionWithName:@"Attempt to call unavailable initializer." + reason:@"This class is not meant to be initialized." + userInfo:nil]; +} + ++ (FIRAuthCredential *)credentialWithEmail:(NSString *)email password:(NSString *)password { + return [[FIREmailPasswordAuthCredential alloc] initWithEmail:email password:password]; +} + ++ (FIRAuthCredential *)credentialWithEmail:(NSString *)email link:(NSString *)link { + return [[FIREmailPasswordAuthCredential alloc] initWithEmail:email link:link]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Email/FIREmailPasswordAuthCredential.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Email/FIREmailPasswordAuthCredential.h new file mode 100644 index 0000000..dc719ac --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Email/FIREmailPasswordAuthCredential.h @@ -0,0 +1,61 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthCredential_Internal.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIREmailPasswordAuthCredential + @brief Internal implementation of FIRAuthCredential for Email/Password credentials. + */ +@interface FIREmailPasswordAuthCredential : FIRAuthCredential + +/** @property email + @brief The user's email address. + */ +@property(nonatomic, readonly) NSString *email; + +/** @property password + @brief The user's password. + */ +@property(nonatomic, readonly) NSString *password; + +/** @property link + @brief The email sign-in link. + */ +@property(nonatomic, readonly) NSString *link; + +/** @fn initWithEmail:password: + @brief Designated initializer. + @param email The user's email address. + @param password The user's password. + */ +- (nullable instancetype)initWithEmail:(NSString *)email password:(NSString *)password + NS_DESIGNATED_INITIALIZER; + +/** @fn initWithEmail:link: + @brief Designated initializer. + @param email The user's email address. + @param link The email sign-in link. + */ +- (nullable instancetype)initWithEmail:(NSString *)email link:(NSString *)link + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Email/FIREmailPasswordAuthCredential.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Email/FIREmailPasswordAuthCredential.m new file mode 100644 index 0000000..84c1461 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Email/FIREmailPasswordAuthCredential.m @@ -0,0 +1,90 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIREmailPasswordAuthCredential.h" + +#import "FIREmailAuthProvider.h" +#import "FIRAuthExceptionUtils.h" +#import "FIRVerifyAssertionRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIREmailPasswordAuthCredential () + +- (nullable instancetype)initWithProvider:(NSString *)provider NS_UNAVAILABLE; + +@end + +@implementation FIREmailPasswordAuthCredential + +- (nullable instancetype)initWithProvider:(NSString *)provider { + [FIRAuthExceptionUtils raiseMethodNotImplementedExceptionWithReason: + @"Please call the designated initializer."]; + return nil; +} + +- (nullable instancetype)initWithEmail:(NSString *)email password:(NSString *)password { + self = [super initWithProvider:FIREmailAuthProviderID]; + if (self) { + _email = [email copy]; + _password = [password copy]; + } + return self; +} + +- (nullable instancetype)initWithEmail:(NSString *)email link:(NSString *)link { + self = [super initWithProvider:FIREmailAuthProviderID]; + if (self) { + _email = [email copy]; + _link = [link copy]; + } + return self; +} + +- (void)prepareVerifyAssertionRequest:(FIRVerifyAssertionRequest *)request { + [FIRAuthExceptionUtils raiseMethodNotImplementedExceptionWithReason: + @"Attempt to call prepareVerifyAssertionRequest: on a FIREmailPasswordAuthCredential."]; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + NSString *email = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"email"]; + NSString *password = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"password"]; + NSString *link = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"link"]; + if (email.length && password.length) { + self = [self initWithEmail:email password:password]; + } else if (email.length && link.length) { + self = [self initWithEmail:email link:link]; + } else { + self = nil; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:self.email forKey:@"email"]; + [aCoder encodeObject:self.password forKey:@"password"]; + [aCoder encodeObject:self.link forKey:@"link"]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/FIRAuthCredential.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/FIRAuthCredential.m new file mode 100644 index 0000000..510d5f9 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/FIRAuthCredential.m @@ -0,0 +1,46 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRAuthCredential_Internal.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRAuthCredential + +- (instancetype)init { + @throw [NSException exceptionWithName:@"Attempt to call unavailable initializer." + reason:@"This class is an abstract base class. It's init method " + "should not be called directly." + userInfo:nil]; +} + +- (nullable instancetype)initWithProvider:(NSString *)provider { + self = [super init]; + if (self) { + _provider = [provider copy]; + } + return self; +} + +- (void)prepareVerifyAssertionRequest:(FIRVerifyAssertionRequest *)request { + @throw [NSException exceptionWithName:@"Attempt to call virtual method." + reason:@"This method must be overridden by a subclass." + userInfo:nil]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/FIRAuthCredential_Internal.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/FIRAuthCredential_Internal.h new file mode 100644 index 0000000..e060cda --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/FIRAuthCredential_Internal.h @@ -0,0 +1,41 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRAuthCredential.h" + +@class FIRVerifyAssertionRequest; + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRAuthCredential () + +/** @fn initWithProvider: + @brief Designated initializer. + @remarks This is the designated initializer for internal/friend subclasses. + @param provider The provider name. + */ +- (nullable instancetype)initWithProvider:(NSString *)provider NS_DESIGNATED_INITIALIZER; + +/** @fn prepareVerifyAssertionRequest: + @brief Called immediately before a request to the verifyAssertion endpoint is made. Implementers + should update the passed request instance with their credentials. + @param request The request to be updated with credentials. + */ +- (void)prepareVerifyAssertionRequest:(FIRVerifyAssertionRequest *)request; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/FIRAuthProvider.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/FIRAuthProvider.m new file mode 100644 index 0000000..6192ad6 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/FIRAuthProvider.m @@ -0,0 +1,66 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#pragma mark - Provider ID constants + +// Declared 'extern' in FIRGoogleAuthProvider.h +NSString *const FIRGoogleAuthProviderID = @"google.com"; + +// Declared 'extern' in FIRFacebookAuthProvider.h +NSString *const FIRFacebookAuthProviderID = @"facebook.com"; + +// Declared 'extern' in FIREmailAuthProvider.h +NSString *const FIREmailAuthProviderID = @"password"; + +// Declared 'extern' in FIRTwitterAuthProvider.h +NSString *const FIRTwitterAuthProviderID = @"twitter.com"; + +// Declared 'extern' in FIRGitHubAuthProvider.h +NSString *const FIRGitHubAuthProviderID = @"github.com"; + +// Declared 'extern' in FIRPhoneAuthProvider.h +NSString *const FIRPhoneAuthProviderID = @"phone"; + +// Declared 'extern' in FIRGameCenterAuthProvider.h +NSString *const FIRGameCenterAuthProviderID = @"gc.apple.com"; + +#pragma mark - sign-in methods constants + +// Declared 'extern' in FIRGoogleAuthProvider.h +NSString *const FIRGoogleAuthSignInMethod = @"google.com"; + +// Declared 'extern' in FIREmailAuthProvider.h +NSString *const FIREmailPasswordAuthSignInMethod = @"password"; + +// Declared 'extern' in FIREmailAuthProvider.h +NSString *const FIREmailLinkAuthSignInMethod = @"emailLink"; + +// Declared 'extern' in FIRTwitterAuthProvider.h +NSString *const FIRTwitterAuthSignInMethod = @"twitter.com"; + +// Declared 'extern' in FIRFacebookAuthProvider.h +NSString *const FIRFacebookAuthSignInMethod = @"facebook.com"; + +// Declared 'extern' in FIRGitHubAuthProvider.h +NSString *const FIRGitHubAuthSignInMethod = @"github.com"; + +// Declared 'extern' in FIRPhoneAuthProvider.h +NSString *const FIRPhoneAuthSignInMethod = @"phone"; + +// Declared 'extern' in FIRGameCenterAuthProvider.h +NSString *const FIRGameCenterAuthSignInMethod = @"gc.apple.com"; diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Facebook/FIRFacebookAuthCredential.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Facebook/FIRFacebookAuthCredential.h new file mode 100644 index 0000000..29849a1 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Facebook/FIRFacebookAuthCredential.h @@ -0,0 +1,36 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthCredential_Internal.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRFacebookAuthCredential + @brief Internal implementation of FIRAuthCredential for the Facebook IdP. + */ +@interface FIRFacebookAuthCredential : FIRAuthCredential + +/** @fn initWithAccessToken: + @brief Designated initializer. + @param accessToken The Access Token obtained from Facebook. + */ +- (nullable instancetype)initWithAccessToken:(NSString *)accessToken NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Facebook/FIRFacebookAuthCredential.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Facebook/FIRFacebookAuthCredential.m new file mode 100644 index 0000000..4f10860 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Facebook/FIRFacebookAuthCredential.m @@ -0,0 +1,71 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRFacebookAuthCredential.h" + +#import "FIRFacebookAuthProvider.h" +#import "FIRAuthExceptionUtils.h" +#import "FIRVerifyAssertionRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRFacebookAuthCredential () + +- (nullable instancetype)initWithProvider:(NSString *)provider NS_UNAVAILABLE; + +@end + +@implementation FIRFacebookAuthCredential { + NSString *_accessToken; +} + +- (nullable instancetype)initWithProvider:(NSString *)provider { + [FIRAuthExceptionUtils raiseMethodNotImplementedExceptionWithReason: + @"Please call the designated initializer."]; + return nil; +} + +- (nullable instancetype)initWithAccessToken:(NSString *)accessToken { + self = [super initWithProvider:FIRFacebookAuthProviderID]; + if (self) { + _accessToken = [accessToken copy]; + } + return self; +} + +- (void)prepareVerifyAssertionRequest:(FIRVerifyAssertionRequest *)request { + request.providerAccessToken = _accessToken; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + NSString *accessToken = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"accessToken"]; + self = [self initWithAccessToken:accessToken]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_accessToken forKey:@"accessToken"]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Facebook/FIRFacebookAuthProvider.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Facebook/FIRFacebookAuthProvider.m new file mode 100644 index 0000000..d850830 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Facebook/FIRFacebookAuthProvider.m @@ -0,0 +1,40 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRFacebookAuthProvider.h" + +#import "FIRFacebookAuthCredential.h" +#import "FIRAuthExceptionUtils.h" + +// FIRFacebookAuthProviderID is defined in FIRAuthProvider.m. + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRFacebookAuthProvider + +- (instancetype)init { + [FIRAuthExceptionUtils raiseMethodNotImplementedExceptionWithReason: + @"This class is not meant to be initialized."]; + return nil; +} + ++ (FIRAuthCredential *)credentialWithAccessToken:(NSString *)accessToken { + return [[FIRFacebookAuthCredential alloc] initWithAccessToken:accessToken]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/GameCenter/FIRGameCenterAuthCredential.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/GameCenter/FIRGameCenterAuthCredential.h new file mode 100644 index 0000000..ee1d21b --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/GameCenter/FIRGameCenterAuthCredential.h @@ -0,0 +1,80 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthCredential.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRGameCenterAuthCredential + @brief Internal implementation of FIRAuthCredential for Game Center credentials. + */ +@interface FIRGameCenterAuthCredential : FIRAuthCredential + +/** @property playerID + @brief The ID of the Game Center local player. + */ +@property(nonatomic, readonly) NSString *playerID; + +/** @property publicKeyURL + @brief The URL for the public encryption key. + */ +@property(nonatomic, readonly) NSURL *publicKeyURL; + +/** @property signature + @brief The verification signature data generated. + */ +@property(nonatomic, readonly) NSData *signature; + +/** @property salt + @brief A random string used to compute the hash and keep it randomized. + */ +@property(nonatomic, readonly) NSData *salt; + +/** @property timestamp + @brief The date and time that the signature was created. + */ +@property(nonatomic, readonly) uint64_t timestamp; + +/** @property displayName + @brief The date and time that the signature was created. + */ +@property(nonatomic, readonly) NSString *displayName; + +/** @fn initWithPlayerID:publicKeyURL:signature:salt:timestamp:displayName: + @brief Designated initializer. + @param publicKeyURL The URL for the public encryption key. + @param signature The verification signature generated. + @param salt A random string used to compute the hash and keep it randomized. + @param timestamp The date and time that the signature was created. + */ +- (nullable instancetype)initWithPlayerID:(NSString *)playerID + publicKeyURL:(NSURL *)publicKeyURL + signature:(NSData *)signature + salt:(NSData *)salt + timestamp:(uint64_t)timestamp + displayName:(NSString *)displayName NS_DESIGNATED_INITIALIZER; + +/** @fn initWithProvider: + @brief Initializer with a provider name. + @param provider The provider name. + */ +- (nullable instancetype)initWithProvider:(NSString *)provider NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/GameCenter/FIRGameCenterAuthCredential.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/GameCenter/FIRGameCenterAuthCredential.m new file mode 100644 index 0000000..91a4b68 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/GameCenter/FIRGameCenterAuthCredential.m @@ -0,0 +1,90 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRGameCenterAuthCredential.h" + +#import "FIRAuthExceptionUtils.h" +#import "FIRAuthCredential_Internal.h" +#import "FIRGameCenterAuthProvider.h" +#import "FIRVerifyAssertionRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRGameCenterAuthCredential + +- (nullable instancetype)initWithProvider:(NSString *)provider { + [FIRAuthExceptionUtils raiseMethodNotImplementedExceptionWithReason: + @"Please call the designated initializer."]; + return nil; +} + +- (nullable instancetype)initWithPlayerID:(NSString *)playerID + publicKeyURL:(NSURL *)publicKeyURL + signature:(NSData *)signature + salt:(NSData *)salt + timestamp:(uint64_t)timestamp + displayName:(NSString *)displayName { + self = [super initWithProvider:FIRGameCenterAuthProviderID]; + if (self) { + _playerID = [playerID copy]; + _publicKeyURL = [publicKeyURL copy]; + _signature = [signature copy]; + _salt = [salt copy]; + _timestamp = timestamp; + _displayName = [displayName copy]; + } + return self; +} + +- (void)prepareVerifyAssertionRequest:(FIRVerifyAssertionRequest *)request { + [FIRAuthExceptionUtils raiseMethodNotImplementedExceptionWithReason: + @"Attempt to call prepareVerifyAssertionRequest: on a FIRGameCenterAuthCredential."]; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + NSString *playerID = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"playerID"]; + NSURL *publicKeyURL = [aDecoder decodeObjectOfClass:[NSURL class] forKey:@"publicKeyURL"]; + NSData *signature = [aDecoder decodeObjectOfClass:[NSData class] forKey:@"signature"]; + NSData *salt = [aDecoder decodeObjectOfClass:[NSData class] forKey:@"salt"]; + NSNumber *timestamp = [aDecoder decodeObjectOfClass:[NSNumber class] forKey:@"timestamp"]; + NSString *displayName = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"displayName"]; + self = [self initWithPlayerID:playerID + publicKeyURL:publicKeyURL + signature:signature + salt:salt + timestamp:timestamp.unsignedLongLongValue + displayName:displayName]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:self.playerID forKey:@"playerID"]; + [aCoder encodeObject:self.publicKeyURL forKey:@"publicKeyURL"]; + [aCoder encodeObject:self.signature forKey:@"signature"]; + [aCoder encodeObject:self.salt forKey:@"salt"]; + [aCoder encodeObject:[NSNumber numberWithUnsignedLongLong:self.timestamp] forKey:@"timestamp"]; + [aCoder encodeObject:self.displayName forKey:@"displayName"]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/GameCenter/FIRGameCenterAuthProvider.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/GameCenter/FIRGameCenterAuthProvider.m new file mode 100644 index 0000000..af8e7e6 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/GameCenter/FIRGameCenterAuthProvider.m @@ -0,0 +1,88 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRGameCenterAuthProvider.h" + +#import + +#import "FIRAuthErrorUtils.h" +#import "FIRAuthExceptionUtils.h" +#import "FIRGameCenterAuthCredential.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRGameCenterAuthProvider + +- (instancetype)init { + [FIRAuthExceptionUtils raiseMethodNotImplementedExceptionWithReason: + @"This class is not meant to be initialized."]; + return nil; +} + ++ (void)getCredentialWithCompletion:(FIRGameCenterCredentialCallback)completion { + /** + Linking GameKit.framework without using it on macOS results in App Store rejection. + Thus we don't link GameKit.framework to our SDK directly. `optionalLocalPlayer` is used for + checking whether the APP that consuming our SDK has linked GameKit.framework. If not, a + `GameKitNotLinkedError` will be raised. + **/ + GKLocalPlayer * _Nullable optionalLocalPlayer = [[NSClassFromString(@"GKLocalPlayer") alloc] init]; + + if (!optionalLocalPlayer) { + if (completion) { + completion(nil, [FIRAuthErrorUtils gameKitNotLinkedError]); + } + return; + } + + __weak GKLocalPlayer *localPlayer = [[optionalLocalPlayer class] localPlayer]; + if (!localPlayer.isAuthenticated) { + if (completion) { + completion(nil, [FIRAuthErrorUtils localPlayerNotAuthenticatedError]); + } + return; + } + + [localPlayer generateIdentityVerificationSignatureWithCompletionHandler: + ^(NSURL *publicKeyURL, NSData *signature, NSData *salt, uint64_t timestamp, NSError *error) { + if (error) { + if (completion) { + completion(nil, error); + } + } else { + if (completion) { + /** + @c `localPlayer.alias` is actually the displayname needed, instead of + `localPlayer.displayname`. For more information, check + https://developer.apple.com/documentation/gamekit/gkplayer + **/ + NSString *displayName = localPlayer.alias; + FIRGameCenterAuthCredential *credential = + [[FIRGameCenterAuthCredential alloc] initWithPlayerID:localPlayer.playerID + publicKeyURL:publicKeyURL + signature:signature + salt:salt + timestamp:timestamp + displayName:displayName]; + completion(credential, nil); + } + } + }]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/GitHub/FIRGitHubAuthCredential.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/GitHub/FIRGitHubAuthCredential.h new file mode 100644 index 0000000..ba406f7 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/GitHub/FIRGitHubAuthCredential.h @@ -0,0 +1,41 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthCredential_Internal.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRGitHubAuthCredential + @brief Internal implementation of FIRAuthCredential for GitHub credentials. + */ +@interface FIRGitHubAuthCredential : FIRAuthCredential + +/** @property token + @brief The GitHub OAuth access token. + */ +@property(nonatomic, readonly) NSString *token; + +/** @fn initWithToken: + @brief Designated initializer. + @param token The GitHub OAuth access token. + */ +- (nullable instancetype)initWithToken:(NSString *)token NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/GitHub/FIRGitHubAuthCredential.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/GitHub/FIRGitHubAuthCredential.m new file mode 100644 index 0000000..f6b536d --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/GitHub/FIRGitHubAuthCredential.m @@ -0,0 +1,69 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRGitHubAuthCredential.h" + +#import "FIRGitHubAuthProvider.h" +#import "FIRAuthExceptionUtils.h" +#import "FIRVerifyAssertionRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRGitHubAuthCredential () + +- (nullable instancetype)initWithProvider:(NSString *)provider NS_UNAVAILABLE; + +@end + +@implementation FIRGitHubAuthCredential + +- (nullable instancetype)initWithProvider:(NSString *)provider { + [FIRAuthExceptionUtils raiseMethodNotImplementedExceptionWithReason: + @"Please call the designated initializer."]; + return nil; +} + +- (nullable instancetype)initWithToken:(NSString *)token { + self = [super initWithProvider:FIRGitHubAuthProviderID]; + if (self) { + _token = [token copy]; + } + return self; +} + +- (void)prepareVerifyAssertionRequest:(FIRVerifyAssertionRequest *)request { + request.providerAccessToken = _token; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + NSString *token = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"token"]; + self = [self initWithToken:token]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:self.token forKey:@"token"]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/GitHub/FIRGitHubAuthProvider.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/GitHub/FIRGitHubAuthProvider.m new file mode 100644 index 0000000..fa6be66 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/GitHub/FIRGitHubAuthProvider.m @@ -0,0 +1,40 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRGitHubAuthProvider.h" + +#import "FIRGitHubAuthCredential.h" +#import "FIRAuthExceptionUtils.h" + +// FIRGitHubAuthProviderID is defined in FIRAuthProvider.m. + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRGitHubAuthProvider + +- (instancetype)init { + [FIRAuthExceptionUtils raiseMethodNotImplementedExceptionWithReason: + @"This class is not meant to be initialized."]; + return nil; +} + ++ (FIRAuthCredential *)credentialWithToken:(NSString *)token { + return [[FIRGitHubAuthCredential alloc] initWithToken:token]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Google/FIRGoogleAuthCredential.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Google/FIRGoogleAuthCredential.h new file mode 100644 index 0000000..23e2d68 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Google/FIRGoogleAuthCredential.h @@ -0,0 +1,38 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthCredential_Internal.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRGoogleAuthCredential + @brief Internal implementation of FIRAuthCredential for the Google IdP. + */ +@interface FIRGoogleAuthCredential : FIRAuthCredential + +/** @fn initWithIDToken:accessToken: + @brief Designated initializer. + @param IDToken The ID Token obtained from Google. + @param accessToken The Access Token obtained from Google. + */ +- (nullable instancetype)initWithIDToken:(NSString *)IDToken accessToken:(NSString *)accessToken + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Google/FIRGoogleAuthCredential.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Google/FIRGoogleAuthCredential.m new file mode 100644 index 0000000..a4676d9 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Google/FIRGoogleAuthCredential.m @@ -0,0 +1,76 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRGoogleAuthCredential.h" + +#import "FIRGoogleAuthProvider.h" +#import "FIRAuthExceptionUtils.h" +#import "FIRVerifyAssertionRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRGoogleAuthCredential () + +- (nullable instancetype)initWithProvider:(NSString *)provider NS_UNAVAILABLE; + +@end + +@implementation FIRGoogleAuthCredential { + NSString *_IDToken; + NSString *_accessToken; +} + +- (nullable instancetype)initWithProvider:(NSString *)provider { + [FIRAuthExceptionUtils raiseMethodNotImplementedExceptionWithReason: + @"Please call the designated initializer."]; + return nil; +} + +- (nullable instancetype)initWithIDToken:(NSString *)IDToken accessToken:(NSString *)accessToken { + self = [super initWithProvider:FIRGoogleAuthProviderID]; + if (self) { + _IDToken = [IDToken copy]; + _accessToken = [accessToken copy]; + } + return self; +} + +- (void)prepareVerifyAssertionRequest:(FIRVerifyAssertionRequest *)request { + request.providerIDToken = _IDToken; + request.providerAccessToken = _accessToken; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + NSString *IDToken = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"IDToken"]; + NSString *accessToken = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"accessToken"]; + self = [self initWithIDToken:IDToken accessToken:accessToken]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_IDToken forKey:@"IDToken"]; + [aCoder encodeObject:_accessToken forKey:@"accessToken"]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Google/FIRGoogleAuthProvider.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Google/FIRGoogleAuthProvider.m new file mode 100644 index 0000000..93a33d8 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Google/FIRGoogleAuthProvider.m @@ -0,0 +1,41 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRGoogleAuthProvider.h" + +#import "FIRGoogleAuthCredential.h" +#import "FIRAuthExceptionUtils.h" + +// FIRGoogleAuthProviderID is defined in FIRAuthProvider.m. + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRGoogleAuthProvider + +- (instancetype)init { + [FIRAuthExceptionUtils raiseMethodNotImplementedExceptionWithReason: + @"This class is not meant to be initialized."]; + return nil; +} + ++ (FIRAuthCredential *)credentialWithIDToken:(NSString *)IDToken + accessToken:(NSString *)accessToken { + return [[FIRGoogleAuthCredential alloc] initWithIDToken:IDToken accessToken:accessToken]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/OAuth/FIROAuthCredential.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/OAuth/FIROAuthCredential.m new file mode 100644 index 0000000..68bd6a7 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/OAuth/FIROAuthCredential.m @@ -0,0 +1,131 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIROAuthCredential.h" + +#import "FIRAuthCredential_Internal.h" +#import "FIRAuthExceptionUtils.h" +#import "FIROAuthCredential_Internal.h" +#import "FIRVerifyAssertionRequest.h" +#import "FIRVerifyAssertionResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIROAuthCredential () + +@property(nonatomic, nullable) NSString *rawNonce; + +- (nullable instancetype)initWithProvider:(NSString *)provider NS_UNAVAILABLE; + +@end + +@implementation FIROAuthCredential + +- (nullable instancetype)initWithProvider:(NSString *)provider { + [FIRAuthExceptionUtils raiseMethodNotImplementedExceptionWithReason: + @"Please call the designated initializer."]; + return nil; +} + +- (instancetype)initWithProviderID:(NSString *)providerID + IDToken:(nullable NSString *)IDToken + rawNonce:(nullable NSString *)rawNonce + accessToken:(nullable NSString *)accessToken + secret:(nullable NSString *)secret + pendingToken:(nullable NSString *)pendingToken { + self = [super initWithProvider:providerID]; + if (self) { + _IDToken = IDToken; + _rawNonce = rawNonce; + _accessToken = accessToken; + _pendingToken = pendingToken; + _secret = secret; + } + return self; +} + +- (instancetype)initWithProviderID:(NSString *)providerID + sessionID:(NSString *)sessionID + OAuthResponseURLString:(NSString *)OAuthResponseURLString { + self = [self initWithProviderID:providerID + IDToken:nil + rawNonce:nil + accessToken:nil + secret:nil + pendingToken:nil]; + if (self) { + _OAuthResponseURLString = OAuthResponseURLString; + _sessionID = sessionID; + } + return self; +} + + +- (nullable instancetype)initWithVerifyAssertionResponse:(FIRVerifyAssertionResponse *)response { + if (response.oauthIDToken.length || response.oauthAccessToken.length || + response.oauthSecretToken.length) { + return [self initWithProviderID:response.providerID + IDToken:response.oauthIDToken + rawNonce:nil + accessToken:response.oauthAccessToken + secret:response.oauthSecretToken + pendingToken:response.pendingToken]; + } + return nil; +} + +- (void)prepareVerifyAssertionRequest:(FIRVerifyAssertionRequest *)request { + request.providerIDToken = _IDToken; + request.providerRawNonce = _rawNonce; + request.providerAccessToken = _accessToken; + request.requestURI = _OAuthResponseURLString; + request.sessionID = _sessionID; + request.providerOAuthTokenSecret = _secret; + request.pendingToken = _pendingToken; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + NSString *IDToken = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"IDToken"]; + NSString *rawNonce = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"rawNonce"]; + NSString *accessToken = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"accessToken"]; + NSString *pendingToken = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"pendingToken"]; + NSString *secret = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"secret"]; + self = [self initWithProviderID:self.provider + IDToken:IDToken + rawNonce:rawNonce + accessToken:accessToken + secret:secret + pendingToken:pendingToken]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:self.IDToken forKey:@"IDToken"]; + [aCoder encodeObject:self.rawNonce forKey:@"rawNonce"]; + [aCoder encodeObject:self.accessToken forKey:@"accessToken"]; + [aCoder encodeObject:self.pendingToken forKey:@"pendingToken"]; + [aCoder encodeObject:self.secret forKey:@"secret"]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/OAuth/FIROAuthCredential_Internal.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/OAuth/FIROAuthCredential_Internal.h new file mode 100644 index 0000000..f484b5c --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/OAuth/FIROAuthCredential_Internal.h @@ -0,0 +1,79 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIROAuthCredential.h" + +@class FIRVerifyAssertionResponse; + +NS_ASSUME_NONNULL_BEGIN + +/** @extension FIROAuthCredential + @brief Internal implementation of FIRAuthCredential for generic credentials. + */ +@interface FIROAuthCredential() + +/** @property OAuthResponseURLString + @brief A string representation of the response URL corresponding to this OAuthCredential. + */ +@property(nonatomic, readonly, nullable) NSString *OAuthResponseURLString; + +/** @property sessionID + @brief The session ID used when completing the headful-lite flow. + */ +@property(nonatomic, readonly, nullable) NSString *sessionID; + +/** @property pendingToken + @brief The pending token used when completing the headful-lite flow. + */ +@property(nonatomic, readonly, nullable) NSString *pendingToken; + +/** @fn initWithProviderId:IDToken:accessToken:secret:pendingToken + @brief Designated initializer. + @param providerID The provider ID associated with the credential being created. + @param IDToken The ID Token associated with the credential being created. + @param rawNonce The raw nonce associated with the Auth credential being created. + @param accessToken The access token associated with the credential being created. + @param secret The secret associated with the credential being created. + @param pendingToken The pending token associated with the credential being created. + */ +- (instancetype)initWithProviderID:(NSString *)providerID + IDToken:(nullable NSString *)IDToken + rawNonce:(nullable NSString *)rawNonce + accessToken:(nullable NSString *)accessToken + secret:(nullable NSString *)secret + pendingToken:(nullable NSString *)pendingToken NS_DESIGNATED_INITIALIZER; + +/** @fn initWithProviderId:sessionID:OAuthResponseURLString: + @brief Intitializer which takes a sessionID and an OAuthResponseURLString. + @param providerID The provider ID associated with the credential being created. + @param sessionID The session ID used when completing the headful-lite flow. + @param OAuthResponseURLString The error that occurred if any. + */ +- (instancetype)initWithProviderID:(NSString *)providerID + sessionID:(NSString *)sessionID + OAuthResponseURLString:(NSString *)OAuthResponseURLString; + +/** @fn initWithVerifyAssertionResponse + @brief Intitializer which takes an verifyAssertion response. + @param response The verifyAssertion Response to create the credential instance. + */ +- (nullable instancetype)initWithVerifyAssertionResponse:(FIRVerifyAssertionResponse *)response; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/OAuth/FIROAuthProvider.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/OAuth/FIROAuthProvider.m new file mode 100644 index 0000000..0f07aee --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/OAuth/FIROAuthProvider.m @@ -0,0 +1,384 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#import "FIROAuthProvider.h" + +#import +#import + +#import "FIRAuthBackend.h" +#import "FIRAuth_Internal.h" +#import "FIRAuthErrorUtils.h" +#import "FIRAuthGlobalWorkQueue.h" +#import "FIRAuthRequestConfiguration.h" +#import "FIRAuthWebUtils.h" +#import "FIRFacebookAuthProvider.h" +#import "FIROAuthCredential_Internal.h" +#import "FIROAuthCredential.h" + +#if TARGET_OS_IOS +#import "FIRAuthURLPresenter.h" +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** @typedef FIRHeadfulLiteURLCallBack + @brief The callback invoked at the end of the flow to fetch a headful-lite URL. + @param headfulLiteURL The headful lite URL. + @param error The error that occurred while fetching the headful-lite, if any. + */ +typedef void (^FIRHeadfulLiteURLCallBack)(NSURL *_Nullable headfulLiteURL, + NSError *_Nullable error); + +/** @var kHeadfulLiteURLStringFormat + @brief The format of the URL used to open the headful lite page during sign-in. + */ +NSString *const kHeadfulLiteURLStringFormat = @"https://%@/__/auth/handler?%@"; + +/** @var kauthTypeSignInWithRedirect + @brief The auth type to be specified in the sign-in request with redirect request and response. + */ +static NSString *const kAuthTypeSignInWithRedirect = @"signInWithRedirect"; + +@implementation FIROAuthProvider { + /** @var _auth + @brief The auth instance used for launching the URL presenter. + */ + FIRAuth *_auth; + + /** @var _callbackScheme + @brief The callback URL scheme used for headful-lite sign-in. + */ + NSString *_callbackScheme; +} + ++ (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID + IDToken:(NSString *)IDToken + accessToken:(nullable NSString *)accessToken { + return [[FIROAuthCredential alloc] initWithProviderID:providerID + IDToken:IDToken + rawNonce:nil + accessToken:accessToken + secret:nil + pendingToken:nil]; +} + ++ (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID + accessToken:(NSString *)accessToken { + return [[FIROAuthCredential alloc] initWithProviderID:providerID + IDToken:nil + rawNonce:nil + accessToken:accessToken + secret:nil + pendingToken:nil]; +} + ++ (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID + IDToken:(NSString *)IDToken + rawNonce:(nullable NSString *)rawNonce + accessToken:(nullable NSString *)accessToken { + return [[FIROAuthCredential alloc] initWithProviderID:providerID + IDToken:IDToken + rawNonce:rawNonce + accessToken:accessToken + secret:nil + pendingToken:nil]; +} + ++ (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID + IDToken:(NSString *)IDToken + rawNonce:(nullable NSString *)rawNonce { + return [[FIROAuthCredential alloc] initWithProviderID:providerID + IDToken:IDToken + rawNonce:rawNonce + accessToken:nil + secret:nil + pendingToken:nil]; +} + ++ (instancetype)providerWithProviderID:(NSString *)providerID { + return [[self alloc]initWithProviderID:providerID auth:[FIRAuth auth]]; +} + ++ (instancetype)providerWithProviderID:(NSString *)providerID auth:(FIRAuth *)auth { + return [[self alloc] initWithProviderID:providerID auth:auth]; +} + +#if TARGET_OS_IOS +- (void)getCredentialWithUIDelegate:(nullable id)UIDelegate + completion:(nullable FIRAuthCredentialCallback)completion { + if (![FIRAuthWebUtils isCallbackSchemeRegisteredForCustomURLScheme:self->_callbackScheme]) { + [NSException raise:NSInternalInconsistencyException + format:@"Please register custom URL scheme '%@' in the app's Info.plist file.", + self->_callbackScheme]; + } + __weak __typeof__(self) weakSelf = self; + __weak FIRAuth *weakAuth = _auth; + __weak NSString *weakProviderID = _providerID; + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + FIRAuthCredentialCallback callbackOnMainThread = ^(FIRAuthCredential *_Nullable credential, + NSError *_Nullable error) { + if (completion) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(credential, error); + }); + } + }; + NSString *eventID = [FIRAuthWebUtils randomStringWithLength:10]; + NSString *sessionID = [FIRAuthWebUtils randomStringWithLength:10]; + __strong __typeof__(self) strongSelf = weakSelf; + [strongSelf getHeadFulLiteURLWithEventID:eventID + sessionID:sessionID + completion:^(NSURL *_Nullable headfulLiteURL, + NSError *_Nullable error) { + if (error) { + callbackOnMainThread(nil, error); + return; + } + FIRAuthURLCallbackMatcher callbackMatcher = ^BOOL(NSURL *_Nullable callbackURL) { + return [FIRAuthWebUtils isExpectedCallbackURL:callbackURL + eventID:eventID + authType:kAuthTypeSignInWithRedirect + callbackScheme:strongSelf->_callbackScheme]; + }; + __strong FIRAuth *strongAuth = weakAuth; + [strongAuth.authURLPresenter presentURL:headfulLiteURL + UIDelegate:UIDelegate + callbackMatcher:callbackMatcher + completion:^(NSURL *_Nullable callbackURL, + NSError *_Nullable error) { + if (error) { + callbackOnMainThread(nil, error); + return; + } + NSString *OAuthResponseURLString = + [strongSelf OAuthResponseForURL:callbackURL error:&error]; + if (error) { + callbackOnMainThread(nil, error); + return; + } + __strong NSString *strongProviderID = weakProviderID; + FIROAuthCredential *credential = + [[FIROAuthCredential alloc] initWithProviderID:strongProviderID + sessionID:sessionID + OAuthResponseURLString:OAuthResponseURLString]; + callbackOnMainThread(credential, nil); + }]; + }]; + }); +} +#endif // TARGET_OS_IOS + +#pragma mark - Internal Methods + +/** @fn initWithProviderID:auth: + @brief returns an instance of @c FIROAuthProvider associated with the provided auth instance. + @param auth The Auth instance to be associated with the OAuthProvider instance. + @return An Instance of @c FIROAuthProvider. + */ +- (nullable instancetype)initWithProviderID:(NSString *)providerID auth:(FIRAuth *)auth { + NSAssert(![providerID isEqual:FIRFacebookAuthProviderID], + @"Sign in with Facebook is not supported via generic IDP; the Facebook TOS " + "dictate that you must use the Facebook iOS SDK for Facebook login."); + NSAssert(![providerID isEqual:@"apple.com"], + @"Sign in with Apple is not supported via generic IDP; You must use the Apple iOS SDK" + " for Sign in with Apple."); + self = [super init]; + if (self) { + _auth = auth; + _providerID = providerID; + _callbackScheme = [[[_auth.app.options.clientID componentsSeparatedByString:@"."] + reverseObjectEnumerator].allObjects componentsJoinedByString:@"."]; + } + return self; +} + +/** @fn OAuthResponseForURL:error: + @brief Parses the redirected URL and returns a string representation of the OAuth response URL. + @param URL The url to be parsed for an OAuth response URL. + @param error The error that occurred if any. + @return The OAuth response if successful. + */ +- (nullable NSString *)OAuthResponseForURL:(NSURL *)URL error:(NSError *_Nullable *_Nullable)error { + NSDictionary *URLQueryItems = + [FIRAuthWebUtils dictionaryWithHttpArgumentsString:URL.query]; + NSURL *deepLinkURL = [NSURL URLWithString:URLQueryItems[@"deep_link_id"]]; + URLQueryItems = + [FIRAuthWebUtils dictionaryWithHttpArgumentsString:deepLinkURL.query]; + NSString *queryItemLink = URLQueryItems[@"link"]; + if (queryItemLink) { + return queryItemLink; + } + if (!error) { + return nil; + } + NSData *errorData = [URLQueryItems[@"firebaseError"] dataUsingEncoding:NSUTF8StringEncoding]; + NSError *jsonError; + NSDictionary *errorDict = [NSJSONSerialization JSONObjectWithData:errorData + options:0 + error:&jsonError]; + if (jsonError) { + *error = [FIRAuthErrorUtils JSONSerializationErrorWithUnderlyingError:jsonError]; + return nil; + } + *error = [FIRAuthErrorUtils URLResponseErrorWithCode:errorDict[@"code"] + message:errorDict[@"message"]]; + if (!*error) { + NSString *reason; + if(errorDict[@"code"] && errorDict[@"message"]) { + reason = [NSString stringWithFormat:@"[%@] - %@",errorDict[@"code"], errorDict[@"message"]]; + } + *error = [FIRAuthErrorUtils webSignInUserInteractionFailureWithReason:reason]; + } + return nil; +} + +/** @fn getHeadFulLiteURLWithEventID:completion: + @brief Constructs a URL used for opening a headful-lite flow using a given event + ID and session ID. + @param eventID The event ID used for this purpose. + @param sessionID The session ID used when completing the headful lite flow. + @param completion The callback invoked after the URL has been constructed or an error + has been encountered. + */ +- (void)getHeadFulLiteURLWithEventID:(NSString *)eventID + sessionID:(NSString *)sessionID + completion:(FIRHeadfulLiteURLCallBack)completion { + __weak __typeof__(self) weakSelf = self; + [FIRAuthWebUtils fetchAuthDomainWithRequestConfiguration:_auth.requestConfiguration + completion:^(NSString *_Nullable authDomain, + NSError *_Nullable error) { + if (error) { + if (completion) { + completion(nil, error); + } + return; + } + __strong __typeof__(self) strongSelf = weakSelf; + NSString *bundleID = [NSBundle mainBundle].bundleIdentifier; + NSString *clienID = strongSelf->_auth.app.options.clientID; + NSString *apiKey = strongSelf->_auth.requestConfiguration.APIKey; + NSMutableDictionary *urlArguments = [@{ + @"apiKey" : apiKey, + @"authType" : @"signInWithRedirect", + @"ibi" : bundleID ?: @"", + @"clientId" : clienID, + @"sessionId" : [strongSelf hashforString:sessionID], + @"v" : [FIRAuthBackend authUserAgent], + @"eventId" : eventID, + @"providerId" : strongSelf->_providerID, + } mutableCopy]; + if (strongSelf.scopes.count) { + urlArguments[@"scopes"] = [strongSelf.scopes componentsJoinedByString:@","]; + } + if (strongSelf.customParameters.count) { + NSString *customParameters = [strongSelf customParametersStringWithError:&error]; + if (error) { + completion(nil, error); + return; + } + if (customParameters) { + urlArguments[@"customParameters"] = customParameters; + } + } + if (strongSelf->_auth.requestConfiguration.languageCode) { + urlArguments[@"hl"] = strongSelf->_auth.requestConfiguration.languageCode; + } + NSString *argumentsString = [strongSelf httpArgumentsStringForArgsDictionary:urlArguments]; + NSString *URLString = + [NSString stringWithFormat:kHeadfulLiteURLStringFormat, authDomain, argumentsString]; + if (completion) { + NSCharacterSet *set = [NSCharacterSet URLFragmentAllowedCharacterSet]; + completion([NSURL URLWithString: + [URLString stringByAddingPercentEncodingWithAllowedCharacters:set]], nil); + } + }]; +} + +/** @fn customParametersString + @brief Returns a JSON string representation of the custom parameters dictionary corresponding + to the OAuthProvider. + @return The JSON string representation of the custom parameters dictionary corresponding + to the OAuthProvider. + */ +- (nullable NSString *)customParametersStringWithError:(NSError *_Nullable *_Nullable)error { + if (!_customParameters.count) { + return nil; + } + + if (!error) { + return nil; + } + NSError *jsonError; + NSData *customParametersJSONData = + [NSJSONSerialization dataWithJSONObject:_customParameters + options:0 + error:&jsonError]; + if (jsonError) { + *error = [FIRAuthErrorUtils JSONSerializationErrorWithUnderlyingError:jsonError]; + return nil; + } + + NSString *customParamsRawJSON = + [[NSString alloc] initWithData:customParametersJSONData encoding:NSUTF8StringEncoding]; + return customParamsRawJSON; +} + +/** @fn hashforString: + @brief Returns the SHA256 hash representation of a given string object. + @param string The string for which a SHA256 hash is desired. + @return An hexadecimal string representation of the SHA256 hash. + */ +- (NSString *)hashforString:(NSString *)string { + NSData *sessionIDData = [string dataUsingEncoding:NSUTF8StringEncoding]; + NSMutableData *hashOutputData = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH]; + if (CC_SHA256(sessionIDData.bytes, + (CC_LONG)[sessionIDData length], + hashOutputData.mutableBytes)) { + } + return [self hexStringFromData:hashOutputData];; +} + +/** @fn hexStringFromData: + @brief Returns the hexadecimal string representation of an NSData object. + @param data The NSData object for which a hexadecical string is desired. + @return The hexadecimal string representation of the supplied NSData object. + */ +- (NSString *)hexStringFromData:(NSData *)data { + const unsigned char *dataBuffer = (const unsigned char *)[data bytes]; + NSMutableString *string = [[NSMutableString alloc] init]; + for (unsigned int i = 0; i < data.length; i++){ + [string appendFormat:@"%02lx", (unsigned long)dataBuffer[i]]; + } + return [string copy]; +} + +- (NSString *)httpArgumentsStringForArgsDictionary:(NSDictionary *)argsDictionary { + NSMutableArray* arguments = [NSMutableArray arrayWithCapacity:argsDictionary.count]; + NSString* key; + for (key in argsDictionary) { + NSString *description = [argsDictionary[key] description]; + [arguments addObject:[NSString stringWithFormat:@"%@=%@", + [FIRAuthWebUtils stringByUnescapingFromURLArgument:key], + [FIRAuthWebUtils stringByUnescapingFromURLArgument:description]]] ; + } + return [arguments componentsJoinedByString:@"&"]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Phone/FIRPhoneAuthCredential.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Phone/FIRPhoneAuthCredential.m new file mode 100644 index 0000000..f888b19 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Phone/FIRPhoneAuthCredential.m @@ -0,0 +1,97 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#if !TARGET_OS_OSX && !TARGET_OS_TV + +#import "FIRPhoneAuthCredential.h" + +#import "FIRPhoneAuthCredential_Internal.h" +#import "FIRAuthCredential_Internal.h" +#import "FIRAuthExceptionUtils.h" +#import "FIRVerifyAssertionRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRPhoneAuthCredential () + +- (nullable instancetype)initWithProvider:(NSString *)provider NS_UNAVAILABLE; + +@end + +@implementation FIRPhoneAuthCredential + +- (instancetype)initWithTemporaryProof:(NSString *)temporaryProof + phoneNumber:(NSString *)phoneNumber + providerID:(NSString *)providerID { + self = [super initWithProvider:providerID]; + if (self) { + _temporaryProof = [temporaryProof copy]; + _phoneNumber = [phoneNumber copy]; + } + return self; +} + +- (nullable instancetype)initWithProvider:(NSString *)provider { + [FIRAuthExceptionUtils raiseMethodNotImplementedExceptionWithReason: + @"Please call the designated initializer."]; + return nil; +} + +- (instancetype)initWithProviderID:(NSString *)providerID + verificationID:(NSString *)verificationID + verificationCode:(NSString *)verificationCode { + self = [super initWithProvider:providerID]; + if (self) { + _verificationID = [verificationID copy]; + _verificationCode = [verificationCode copy]; + } + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + NSString *verificationID = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"verificationID"]; + NSString *verificationCode = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"verificationCode"]; + NSString *temporaryProof = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"temporaryProof"]; + NSString *phoneNumber = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"phoneNumber"]; + if (temporaryProof.length && phoneNumber.length) { + self = [self initWithTemporaryProof:temporaryProof phoneNumber:phoneNumber providerID:self.provider]; + } else if (verificationID.length && verificationCode.length) { + self = [self initWithProviderID:self.provider verificationID:verificationID verificationCode:verificationCode]; + } else { + self = nil; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:self.verificationID forKey:@"verificationID"]; + [aCoder encodeObject:self.verificationCode forKey:@"verificationCode"]; + [aCoder encodeObject:self.temporaryProof forKey:@"temporaryProof"]; + [aCoder encodeObject:self.phoneNumber forKey:@"phoneNumber"]; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Phone/FIRPhoneAuthCredential_Internal.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Phone/FIRPhoneAuthCredential_Internal.h new file mode 100644 index 0000000..e6113c0 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Phone/FIRPhoneAuthCredential_Internal.h @@ -0,0 +1,74 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#if !TARGET_OS_OSX && !TARGET_OS_TV + +#import + +#import "FIRPhoneAuthCredential.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @extension FIRPhoneAuthCredential + @brief Internal implementation of FIRAuthCredential for Phone Auth credentials. + */ +@interface FIRPhoneAuthCredential () + +/** @var verificationID + @brief The verification ID obtained from invoking @c verifyPhoneNumber:completion: + */ +@property(nonatomic, readonly, nonnull) NSString *verificationID; + +/** @var verificationCode + @brief The verification code provided by the user. + */ +@property(nonatomic, readonly, nonnull) NSString *verificationCode; + +/** @var temporaryProof + @brief The a temporary proof code perftaining to this credential, returned from the backend. + */ +@property(nonatomic, readonly, nonnull) NSString *temporaryProof; + +/** @var phoneNumber + @brief The a phone number pertaining to this credential, returned from the backend. + */ +@property(nonatomic, readonly, nonnull) NSString *phoneNumber; + +/** @var initWithTemporaryProof:phoneNumber: + @brief Designated Initializer. + @param providerID The provider ID associated with the phone auth credential being created. + */ +- (instancetype)initWithTemporaryProof:(NSString *)temporaryProof + phoneNumber:(NSString *)phoneNumber + providerID:(NSString *)providerID NS_DESIGNATED_INITIALIZER; + +/** @var initWithProviderID:verificationID:verificationCode: + @brief Designated Initializer. + @param providerID The provider ID associated with the phone auth credential being created. + @param verificationID The verification ID associated witht Phone Auth credential being created. + @param verificationCode The verification code associated witht Phone Auth credential being + created. + */ +- (instancetype)initWithProviderID:(NSString *)providerID + verificationID:(NSString *)verificationID + verificationCode:(NSString *)verificationCode NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Phone/FIRPhoneAuthProvider.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Phone/FIRPhoneAuthProvider.m new file mode 100644 index 0000000..5aa66b0 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Phone/FIRPhoneAuthProvider.m @@ -0,0 +1,450 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#if !TARGET_OS_OSX && !TARGET_OS_TV + +#import "FIRPhoneAuthProvider.h" + +#import +#import "FIRPhoneAuthCredential_Internal.h" +#import +#import "FIRAuthAPNSToken.h" +#import "FIRAuthAPNSTokenManager.h" +#import "FIRAuthAppCredential.h" +#import "FIRAuthAppCredentialManager.h" +#import "FIRAuthGlobalWorkQueue.h" +#import "FIRAuth_Internal.h" +#import "FIRAuthURLPresenter.h" +#import "FIRAuthNotificationManager.h" +#import "FIRAuthErrorUtils.h" +#import "FIRAuthBackend.h" +#import "FIRAuthSettings.h" +#import "FIRAuthWebUtils.h" +#import "FirebaseAuthVersion.h" +#import +#import "FIRGetProjectConfigRequest.h" +#import "FIRGetProjectConfigResponse.h" +#import "FIRSendVerificationCodeRequest.h" +#import "FIRSendVerificationCodeResponse.h" +#import "FIRVerifyClientRequest.h" +#import "FIRVerifyClientResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @typedef FIRReCAPTCHAURLCallBack + @brief The callback invoked at the end of the flow to fetch a reCAPTCHA URL. + @param reCAPTCHAURL The reCAPTCHA URL. + @param error The error that occurred while fetching the reCAPTCHAURL, if any. + */ +typedef void (^FIRReCAPTCHAURLCallBack)(NSURL *_Nullable reCAPTCHAURL, NSError *_Nullable error); + +/** @typedef FIRVerifyClientCallback + @brief The callback invoked at the end of a client verification flow. + @param appCredential credential that proves the identity of the app during a phone + authentication flow. + @param error The error that occurred while verifying the app, if any. + */ +typedef void (^FIRVerifyClientCallback)(FIRAuthAppCredential *_Nullable appCredential, + NSString *_Nullable reCAPTCHAToken, + NSError *_Nullable error); + +/** @typedef FIRFetchAuthDomainCallback + @brief The callback invoked at the end of the flow to fetch the Auth domain. + @param authDomain The Auth domain. + @param error The error that occurred while fetching the auth domain, if any. + */ +typedef void (^FIRFetchAuthDomainCallback)(NSString *_Nullable authDomain, + NSError *_Nullable error); + +/** @var kauthTypeVerifyApp + @brief The auth type to be specified in the app verification request. + */ +static NSString *const kAuthTypeVerifyApp = @"verifyApp"; + +/** @var kReCAPTCHAURLStringFormat + @brief The format of the URL used to open the reCAPTCHA page during app verification. + */ +NSString *const kReCAPTCHAURLStringFormat = @"https://%@/__/auth/handler?"; + +@implementation FIRPhoneAuthProvider { + + /** @var _auth + @brief The auth instance used for verifying the phone number. + */ + FIRAuth *_auth; + + /** @var _callbackScheme + @brief The callback URL scheme used for reCAPTCHA fallback. + */ + NSString *_callbackScheme; +} + +/** @fn initWithAuth: + @brief returns an instance of @c FIRPhoneAuthProvider associated with the provided auth + instance. + @return An Instance of @c FIRPhoneAuthProvider. + */ +- (nullable instancetype)initWithAuth:(FIRAuth *)auth { + self = [super init]; + if (self) { + _auth = auth; + _callbackScheme = [[[_auth.app.options.clientID componentsSeparatedByString:@"."] + reverseObjectEnumerator].allObjects componentsJoinedByString:@"."]; + } + return self; +} + +- (void)verifyPhoneNumber:(NSString *)phoneNumber + UIDelegate:(nullable id)UIDelegate + completion:(nullable FIRVerificationResultCallback)completion { + if (![FIRAuthWebUtils isCallbackSchemeRegisteredForCustomURLScheme:_callbackScheme]) { + [NSException raise:NSInternalInconsistencyException + format:@"Please register custom URL scheme '%@' in the app's Info.plist file.", + _callbackScheme]; + } + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + FIRVerificationResultCallback callBackOnMainThread = ^(NSString *_Nullable verificationID, + NSError *_Nullable error) { + if (completion) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(verificationID, error); + }); + } + }; + [self internalVerifyPhoneNumber:phoneNumber + UIDelegate:UIDelegate + completion:^(NSString *_Nullable verificationID, + NSError *_Nullable error) { + if (!error) { + callBackOnMainThread(verificationID, nil); + return; + } else { + callBackOnMainThread(nil, error); + return; + } + }]; + }); +} + +- (FIRPhoneAuthCredential *)credentialWithVerificationID:(NSString *)verificationID + verificationCode:(NSString *)verificationCode { + return [[FIRPhoneAuthCredential alloc] initWithProviderID:FIRPhoneAuthProviderID + verificationID:verificationID + verificationCode:verificationCode]; +} + ++ (instancetype)provider { + return [[self alloc]initWithAuth:[FIRAuth auth]]; +} + ++ (instancetype)providerWithAuth:(FIRAuth *)auth { + return [[self alloc]initWithAuth:auth]; +} + +#pragma mark - Internal Methods + +/** @fn reCAPTCHATokenForURL:error: + @brief Parses the reCAPTCHA URL and returns the reCAPTCHA token. + @param URL The url to be parsed for a reCAPTCHA token. + @param error The error that occurred if any. + @return The reCAPTCHA token if successful. + */ +- (NSString *)reCAPTCHATokenForURL:(NSURL *)URL error:(NSError **)error { + NSURLComponents *actualURLComponents = [NSURLComponents componentsWithURL:URL resolvingAgainstBaseURL:NO]; + NSArray *queryItems = [actualURLComponents queryItems]; + NSString *deepLinkURL = [FIRAuthWebUtils queryItemValue:@"deep_link_id" from:queryItems]; + NSData *errorData; + if (deepLinkURL) { + actualURLComponents = [NSURLComponents componentsWithString:deepLinkURL]; + queryItems = [actualURLComponents queryItems]; + NSString *recaptchaToken = [FIRAuthWebUtils queryItemValue:@"recaptchaToken" from:queryItems]; + if (recaptchaToken) { + return recaptchaToken; + } + NSString *firebaseError = [FIRAuthWebUtils queryItemValue:@"firebaseError" from:queryItems]; + errorData = [firebaseError dataUsingEncoding:NSUTF8StringEncoding]; + } else { + errorData = nil; + } + NSError *jsonError; + NSDictionary *errorDict = [NSJSONSerialization JSONObjectWithData:errorData + options:0 + error:&jsonError]; + if (jsonError) { + *error = [FIRAuthErrorUtils JSONSerializationErrorWithUnderlyingError:jsonError]; + return nil; + } + *error = [FIRAuthErrorUtils URLResponseErrorWithCode:errorDict[@"code"] + message:errorDict[@"message"]]; + if (!*error) { + NSString *reason; + if(errorDict[@"code"] && errorDict[@"message"]) { + reason = [NSString stringWithFormat:@"[%@] - %@",errorDict[@"code"], errorDict[@"message"]]; + } else { + reason = [NSString stringWithFormat:@"An unknown error occurred with the following " + "response: %@", deepLinkURL]; + } + *error = [FIRAuthErrorUtils appVerificationUserInteractionFailureWithReason:reason]; + } + return nil; +} + +/** @fn internalVerifyPhoneNumber:completion: + @brief Starts the phone number authentication flow by sending a verifcation code to the + specified phone number. + @param phoneNumber The phone number to be verified. + @param completion The callback to be invoked when the verification flow is finished. + */ + +- (void)internalVerifyPhoneNumber:(NSString *)phoneNumber + UIDelegate:(nullable id)UIDelegate + completion:(nullable FIRVerificationResultCallback)completion { + if (!phoneNumber.length) { + completion(nil, [FIRAuthErrorUtils missingPhoneNumberErrorWithMessage:nil]); + return; + } + [_auth.notificationManager checkNotificationForwardingWithCallback: + ^(BOOL isNotificationBeingForwarded) { + if (!isNotificationBeingForwarded) { + completion(nil, [FIRAuthErrorUtils notificationNotForwardedError]); + return; + } + FIRVerificationResultCallback callback = ^(NSString *_Nullable verificationID, + NSError *_Nullable error) { + if (completion) { + completion(verificationID, error); + } + }; + [self verifyClientAndSendVerificationCodeToPhoneNumber:phoneNumber + retryOnInvalidAppCredential:YES + UIDelegate:UIDelegate + callback:callback]; + }]; +} + +/** @fn verifyClientAndSendVerificationCodeToPhoneNumber:retryOnInvalidAppCredential:callback: + @brief Starts the flow to verify the client via silent push notification. + @param retryOnInvalidAppCredential Whether of not the flow should be retried if an + FIRAuthErrorCodeInvalidAppCredential error is returned from the backend. + @param phoneNumber The phone number to be verified. + @param callback The callback to be invoked on the global work queue when the flow is + finished. + */ +- (void)verifyClientAndSendVerificationCodeToPhoneNumber:(NSString *)phoneNumber + retryOnInvalidAppCredential:(BOOL)retryOnInvalidAppCredential + UIDelegate:(nullable id)UIDelegate + callback:(FIRVerificationResultCallback)callback { + if (_auth.settings.isAppVerificationDisabledForTesting) { + FIRSendVerificationCodeRequest *request = + [[FIRSendVerificationCodeRequest alloc] initWithPhoneNumber:phoneNumber + appCredential:nil + reCAPTCHAToken:nil + requestConfiguration: + _auth.requestConfiguration]; + [FIRAuthBackend sendVerificationCode:request + callback:^(FIRSendVerificationCodeResponse *_Nullable response, + NSError *_Nullable error) { + callback(response.verificationID, error); + }]; + return; + } + [self verifyClientWithUIDelegate:UIDelegate + completion:^(FIRAuthAppCredential *_Nullable appCredential, + NSString *_Nullable reCAPTCHAToken, + NSError *_Nullable error) { + if (error) { + callback(nil, error); + return; + } + FIRSendVerificationCodeRequest * _Nullable request; + if (appCredential) { + request = + [[FIRSendVerificationCodeRequest alloc] + initWithPhoneNumber:phoneNumber + appCredential:appCredential + reCAPTCHAToken:nil + requestConfiguration:self->_auth.requestConfiguration]; + } else if (reCAPTCHAToken) { + request = + [[FIRSendVerificationCodeRequest alloc] + initWithPhoneNumber:phoneNumber + appCredential:nil + reCAPTCHAToken:reCAPTCHAToken + requestConfiguration:self->_auth.requestConfiguration]; + } + if (request) { + [FIRAuthBackend sendVerificationCode:request + callback:^(FIRSendVerificationCodeResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + if (error.code == FIRAuthErrorCodeInvalidAppCredential) { + if (retryOnInvalidAppCredential) { + [self->_auth.appCredentialManager clearCredential]; + [self verifyClientAndSendVerificationCodeToPhoneNumber:phoneNumber + retryOnInvalidAppCredential:NO + UIDelegate:UIDelegate + callback:callback]; + return; + } + callback(nil, [FIRAuthErrorUtils unexpectedResponseWithDeserializedResponse:nil + underlyingError:error]); + return; + } + callback(nil, error); + return; + } + callback(response.verificationID, nil); + }]; + } + }]; +} + +/** @fn verifyClientWithCompletion:completion: + @brief Continues the flow to verify the client via silent push notification. + @param completion The callback to be invoked when the client verification flow is finished. + */ +- (void)verifyClientWithUIDelegate:(nullable id)UIDelegate + completion:(FIRVerifyClientCallback)completion { + if (_auth.appCredentialManager.credential) { + completion(_auth.appCredentialManager.credential, nil, nil); + return; + } + [_auth.tokenManager getTokenWithCallback:^(FIRAuthAPNSToken *_Nullable token, + NSError *_Nullable error) { + if (!token) { + [self reCAPTCHAFlowWithUIDelegate:UIDelegate completion:completion]; + return; + } + FIRVerifyClientRequest *request = + [[FIRVerifyClientRequest alloc] initWithAppToken:token.string + isSandbox:token.type == FIRAuthAPNSTokenTypeSandbox + requestConfiguration:self->_auth.requestConfiguration]; + [FIRAuthBackend verifyClient:request callback:^(FIRVerifyClientResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + NSError *underlyingError = error.userInfo[NSUnderlyingErrorKey]; + BOOL isInvalidAppCredential = error.code == FIRAuthErrorCodeInternalError && + underlyingError.code == FIRAuthErrorCodeInvalidAppCredential; + if (error.code != FIRAuthErrorCodeMissingAppToken && !isInvalidAppCredential) { + completion(nil, nil, error); + return; + } else { + [self reCAPTCHAFlowWithUIDelegate:UIDelegate completion:completion]; + return; + } + } + NSTimeInterval timeout = [response.suggestedTimeOutDate timeIntervalSinceNow]; + [self->_auth.appCredentialManager + didStartVerificationWithReceipt:response.receipt + timeout:timeout + callback:^(FIRAuthAppCredential *credential) { + if (!credential.secret) { + FIRLogWarning(kFIRLoggerAuth, @"I-AUT000014", + @"Failed to receive remote notification to verify app identity within " + @"%.0f second(s)", timeout); + } + completion(credential, nil, nil); + }]; + }]; + }]; +} + +- (void)reCAPTCHAFlowWithUIDelegate:(nullable id)UIDelegate + completion:(FIRVerifyClientCallback)completion { + NSString *eventID = [FIRAuthWebUtils randomStringWithLength:10]; + [self reCAPTCHAURLWithEventID:eventID completion:^(NSURL *_Nullable reCAPTCHAURL, + NSError *_Nullable error) { + if (error) { + completion(nil, nil, error); + return; + } + FIRAuthURLCallbackMatcher callbackMatcher = ^BOOL(NSURL *_Nullable callbackURL) { + return [FIRAuthWebUtils isExpectedCallbackURL:callbackURL + eventID:eventID + authType:kAuthTypeVerifyApp + callbackScheme:self->_callbackScheme]; + }; + [self->_auth.authURLPresenter presentURL:reCAPTCHAURL + UIDelegate:UIDelegate + callbackMatcher:callbackMatcher + completion:^(NSURL *_Nullable callbackURL, + NSError *_Nullable error) { + if (error) { + completion(nil, nil, error); + return; + } + NSError *reCAPTCHAError; + NSString *reCAPTCHAToken = [self reCAPTCHATokenForURL:callbackURL error:&reCAPTCHAError]; + if (!reCAPTCHAToken) { + completion(nil, nil, reCAPTCHAError); + return; + } else { + completion(nil, reCAPTCHAToken, nil); + return; + } + }]; + }]; +} + +/** @fn reCAPTCHAURLWithEventID:completion: + @brief Constructs a URL used for opening a reCAPTCHA app verification flow using a given event + ID. + @param eventID The event ID used for this purpose. + @param completion The callback invoked after the URL has been constructed or an error + has been encountered. + */ +- (void)reCAPTCHAURLWithEventID:(NSString *)eventID completion:(FIRReCAPTCHAURLCallBack)completion { + [FIRAuthWebUtils fetchAuthDomainWithRequestConfiguration:_auth.requestConfiguration + completion:^(NSString *_Nullable authDomain, + NSError *_Nullable error) { + if (error) { + if (completion) { + completion(nil, error); + return; + } + } + NSString *bundleID = [NSBundle mainBundle].bundleIdentifier; + NSString *clientID = self->_auth.app.options.clientID; + NSString *apiKey = self->_auth.requestConfiguration.APIKey; + NSMutableArray *queryItems = [@[ + [NSURLQueryItem queryItemWithName:@"apiKey" value:apiKey], + [NSURLQueryItem queryItemWithName:@"authType" value:kAuthTypeVerifyApp], + [NSURLQueryItem queryItemWithName:@"ibi" value:bundleID ?: @""], + [NSURLQueryItem queryItemWithName:@"clientId" value:clientID], + [NSURLQueryItem queryItemWithName:@"v" value:[FIRAuthBackend authUserAgent]], + [NSURLQueryItem queryItemWithName:@"eventId" value:eventID] + ] mutableCopy + ]; + + if (self->_auth.requestConfiguration.languageCode) { + [queryItems addObject:[NSURLQueryItem queryItemWithName:@"hl"value: + self->_auth.requestConfiguration.languageCode]]; + } + NSURLComponents *components = [[NSURLComponents alloc] initWithString: + [NSString stringWithFormat:kReCAPTCHAURLStringFormat, authDomain]]; + [components setQueryItems:queryItems]; + if (completion) { + completion([components URL], nil); + } + }]; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Twitter/FIRTwitterAuthCredential.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Twitter/FIRTwitterAuthCredential.h new file mode 100644 index 0000000..423d595 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Twitter/FIRTwitterAuthCredential.h @@ -0,0 +1,48 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthCredential_Internal.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRTwitterAuthCredential + @brief Internal implementation of FIRAuthCredential for Twitter credentials. + */ +@interface FIRTwitterAuthCredential : FIRAuthCredential + +/** @property token + @brief The Twitter OAuth token. + */ +@property(nonatomic, readonly) NSString *token; + +/** @property secret + @brief The Twitter OAuth secret. + */ +@property(nonatomic, readonly) NSString *secret; + +/** @fn initWithToken:secret: + @brief Designated initializer. + @param token The Twitter OAuth token. + @param secret The Twitter OAuth secret. + */ +- (nullable instancetype)initWithToken:(NSString *)token secret:(NSString *)secret + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Twitter/FIRTwitterAuthCredential.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Twitter/FIRTwitterAuthCredential.m new file mode 100644 index 0000000..cb46615 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Twitter/FIRTwitterAuthCredential.m @@ -0,0 +1,73 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRTwitterAuthCredential.h" + +#import "FIRTwitterAuthProvider.h" +#import "FIRAuthExceptionUtils.h" +#import "FIRVerifyAssertionRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRTwitterAuthCredential () + +- (nullable instancetype)initWithProvider:(NSString *)provider NS_UNAVAILABLE; + +@end + +@implementation FIRTwitterAuthCredential + +- (nullable instancetype)initWithProvider:(NSString *)provider { + [FIRAuthExceptionUtils raiseMethodNotImplementedExceptionWithReason: + @"Please call the designated initializer."]; + return nil; +} + +- (nullable instancetype)initWithToken:(NSString *)token secret:(NSString *)secret { + self = [super initWithProvider:FIRTwitterAuthProviderID]; + if (self) { + _token = [token copy]; + _secret = [secret copy]; + } + return self; +} + +- (void)prepareVerifyAssertionRequest:(FIRVerifyAssertionRequest *)request { + request.providerAccessToken = _token; + request.providerOAuthTokenSecret = _secret; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + NSString *token = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"token"]; + NSString *secret = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"secret"]; + self = [self initWithToken:token secret:secret]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:self.token forKey:@"token"]; + [aCoder encodeObject:self.secret forKey:@"secret"]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Twitter/FIRTwitterAuthProvider.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Twitter/FIRTwitterAuthProvider.m new file mode 100644 index 0000000..33771b7 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/AuthProvider/Twitter/FIRTwitterAuthProvider.m @@ -0,0 +1,40 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRTwitterAuthProvider.h" + +#import "FIRTwitterAuthCredential.h" +#import "FIRAuthExceptionUtils.h" + +// FIRTwitterAuthProviderID is defined in FIRAuthProvider.m. + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRTwitterAuthProvider + +- (instancetype)init { + [FIRAuthExceptionUtils raiseMethodNotImplementedExceptionWithReason: + @"This class is not meant to be initialized."]; + return nil; +} + ++ (FIRAuthCredential *)credentialWithToken:(NSString *)token secret:(NSString *)secret { + return [[FIRTwitterAuthCredential alloc] initWithToken:token secret:secret]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/FIRAuthBackend.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/FIRAuthBackend.h new file mode 100644 index 0000000..9ced6a3 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/FIRAuthBackend.h @@ -0,0 +1,599 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FIRAuthRequestConfiguration; +@class FIRCreateAuthURIRequest; +@class FIRCreateAuthURIResponse; +@class FIRDeleteAccountRequest; +@class FIRDeleteAccountResponse; +@class FIREmailLinkSignInRequest; +@class FIREmailLinkSignInResponse; +@class FIRGetAccountInfoRequest; +@class FIRGetAccountInfoResponse; +@class FIRGetProjectConfigRequest; +@class FIRGetProjectConfigResponse; +@class FIRGetOOBConfirmationCodeRequest; +@class FIRGetOOBConfirmationCodeResponse; +@class FIRResetPasswordRequest; +@class FIRResetPasswordResponse; +@class FIRSecureTokenRequest; +@class FIRSecureTokenResponse; +@class FIRSetAccountInfoRequest; +@class FIRSetAccountInfoResponse; +@class FIRVerifyAssertionRequest; +@class FIRVerifyAssertionResponse; +@class FIRVerifyClientRequest; +@class FIRVerifyClientResponse; +@class FIRVerifyCustomTokenRequest; +@class FIRVerifyCustomTokenResponse; +@class FIRVerifyPasswordRequest; +@class FIRVerifyPasswordResponse; +@class FIRVerifyPhoneNumberRequest; +@class FIRVerifyPhoneNumberResponse; +@class FIRSendVerificationCodeRequest; +@class FIRSendVerificationCodeResponse; +@class FIRSignInWithGameCenterRequest; +@class FIRSignInWithGameCenterResponse; +@class FIRSignUpNewUserRequest; +@class FIRSignUpNewUserResponse; + +@protocol FIRAuthBackendImplementation; +@protocol FIRAuthBackendRPCIssuer; + +NS_ASSUME_NONNULL_BEGIN + +/** @typedef FIRAuthBackendRPCIssuerCompletionHandler + @brief The type of block used to return the result of a call to an endpoint. + @param data The HTTP response body. + @param error The error which occurred, if any. + @remarks One of response or error will be non-nil. + */ +typedef void (^FIRAuthBackendRPCIssuerCompletionHandler)(NSData *_Nullable data, + NSError *_Nullable error); + +/** @typedef FIRCreateAuthURIResponseCallback + @brief The type of block used to return the result of a call to the createAuthURI + endpoint. + @param response The received response, if any. + @param error The error which occurred, if any. + @remarks One of response or error will be non-nil. + */ +typedef void (^FIRCreateAuthURIResponseCallback) + (FIRCreateAuthURIResponse *_Nullable response, NSError *_Nullable error); + +/** @typedef FIRGetAccountInfoResponseCallback + @brief The type of block used to return the result of a call to the getAccountInfo + endpoint. + @param response The received response, if any. + @param error The error which occurred, if any. + @remarks One of response or error will be non-nil. + */ +typedef void (^FIRGetAccountInfoResponseCallback) + (FIRGetAccountInfoResponse *_Nullable response, NSError *_Nullable error); + +/** @typedef FIRGetProjectConfigResponseCallback + @brief The type of block used to return the result of a call to the getProjectInfo + endpoint. + @param response The received response, if any. + @param error The error which occurred, if any. + @remarks One of response or error will be non-nil. + */ +typedef void (^FIRGetProjectConfigResponseCallback) + (FIRGetProjectConfigResponse *_Nullable response, NSError *_Nullable error); + +/** @typedef FIRSetAccountInfoResponseCallback + @brief The type of block used to return the result of a call to the setAccountInfo + endpoint. + @param response The received response, if any. + @param error The error which occurred, if any. + @remarks One of response or error will be non-nil. + */ +typedef void (^FIRSetAccountInfoResponseCallback) + (FIRSetAccountInfoResponse *_Nullable response, NSError *_Nullable error); + +/** @typedef FIRSecureTokenResponseCallback + @brief The type of block used to return the result of a call to the token endpoint. + @param response The received response, if any. + @param error The error which occurred, if any. + @remarks One of response or error will be non-nil. + */ +typedef void (^FIRSecureTokenResponseCallback) + (FIRSecureTokenResponse *_Nullable response, NSError *_Nullable error); + +/** @typedef FIRVerifyAssertionResponseCallback + @brief The type of block used to return the result of a call to the verifyAssertion + endpoint. + @param response The received response, if any. + @param error The error which occurred, if any. + @remarks One of response or error will be non-nil. + */ +typedef void (^FIRVerifyAssertionResponseCallback) + (FIRVerifyAssertionResponse *_Nullable response, NSError *_Nullable error); + +/** @typedef FIRVerifyPasswordResponseCallback + @brief The type of block used to return the result of a call to the verifyPassword + endpoint. + @param response The received response, if any. + @param error The error which occurred, if any. + @remarks One of response or error will be non-nil. + */ +typedef void (^FIRVerifyPasswordResponseCallback) + (FIRVerifyPasswordResponse *_Nullable response, NSError *_Nullable error); + +/** @typedef FIREmailLinkSigninResponseCallback + @brief The type of block used to return the result of a call to the emailLinkSignin + endpoint. + @param response The received response, if any. + @param error The error which occurred, if any. + @remarks One of response or error will be non-nil. + */ +typedef void (^FIREmailLinkSigninResponseCallback) + (FIREmailLinkSignInResponse *_Nullable response, NSError *_Nullable error); + +/** @typedef FIRVerifyCustomTokenResponseCallback + @brief The type of block used to return the result of a call to the verifyCustomToken + endpoint. + @param response The received response, if any. + @param error The error which occurred, if any. + @remarks One of response or error will be non-nil. + */ +typedef void (^FIRVerifyCustomTokenResponseCallback) + (FIRVerifyCustomTokenResponse *_Nullable response, NSError *_Nullable error); + +/** @typedef FIRDeleteCallBack + @brief The type of block called when a request delete account has finished. + @param error The error which occurred, or nil if the request was successful. + */ +typedef void (^FIRDeleteCallBack)(NSError *_Nullable error); + +/** @typedef FIRGetOOBConfirmationCodeResponseCallback + @brief The type of block used to return the result of a call to the getOOBConfirmationCode + endpoint. + @param response The received response, if any. + @param error The error which occurred, if any. + @remarks One of response or error will be non-nil. + */ +typedef void (^FIRGetOOBConfirmationCodeResponseCallback) + (FIRGetOOBConfirmationCodeResponse *_Nullable response, NSError *_Nullable error); + +/** @typedef FIRSignupNewUserCallback + @brief The type of block used to return the result of a call to the signupNewUser endpoint. + @param response The received response, if any. + @param error The error which occurred, if any. + @remarks One of response or error will be non-nil. + */ +typedef void (^FIRSignupNewUserCallback) + (FIRSignUpNewUserResponse *_Nullable response, NSError *_Nullable error); + +/** @typedef FIRResetPasswordCallback + @brief The type of block used to return the result of a call to the resetPassword endpoint. + @param response The received response, if any. + @param error The error which occurred, if any. + @remarks One of response or error will be non-nil. + */ +typedef void (^FIRResetPasswordCallback) + (FIRResetPasswordResponse *_Nullable response, NSError *_Nullable error); + +/** @typedef FIRSendVerificationCodeResponseCallback + @brief The type of block used to return the result of a call to the sendVerificationCode + endpoint. + @param response The received response, if any. + @param error The error which occurred, if any. + @remarks One of response or error will be non-nil. + */ +typedef void (^FIRSendVerificationCodeResponseCallback) + (FIRSendVerificationCodeResponse *_Nullable response, NSError *_Nullable error); + +/** @typedef FIRVerifyPhoneNumberResponseCallback + @brief The type of block used to return the result of a call to the verifyPhoneNumber endpoint. + @param response The received response, if any. + @param error The error which occurred, if any. + @remarks One of response or error will be non-nil. + */ +typedef void (^FIRVerifyPhoneNumberResponseCallback) + (FIRVerifyPhoneNumberResponse *_Nullable response, NSError *_Nullable error); + +/** @typedef FIRVerifyClientResponseCallback + @brief The type of block used to return the result of a call to the verifyClient endpoint. + @param response The received response, if any. + @param error The error which occurred, if any. + @remarks One of response or error will be non-nil. + */ +typedef void (^FIRVerifyClientResponseCallback) + (FIRVerifyClientResponse *_Nullable response, NSError *_Nullable error); + +/** @typedef FIRSignInWithGameCenterResponseCallback + @brief The type of block used to return the result of a call to the SignInWithGameCenter endpoint. + @param response The received response, if any. + @param error The error which occurred, if any. + @remarks One of response or error will be non-nil. + */ +typedef void (^FIRSignInWithGameCenterResponseCallback) + (FIRSignInWithGameCenterResponse *_Nullable response, NSError *_Nullable error); + +/** @class FIRAuthBackend + @brief Simple static class with methods representing the backend RPCs. + @remarks All callback blocks passed as method parameters are invoked asynchronously on the + global work queue in the future. See + https://github.com/firebase/firebase-ios-sdk/tree/master/Firebase/Auth/Docs/threading.ml + */ +@interface FIRAuthBackend : NSObject + +/** @fn authUserAgent + @brief Retrieves the Firebase Auth user agent. + @return The Firebase Auth user agent. + */ ++ (NSString *)authUserAgent; + +/** @fn setBackendImplementation: + @brief Changes the default backend implementation to something else. + @param backendImplementation The backend implementation to use. + @remarks This is not, generally, safe to call in a scenario where other backend requests may + be occuring. This is specifically to help mock the backend for testing purposes. + */ ++ (void)setBackendImplementation:(id)backendImplementation; + +/** @fn setDefaultBackendImplementationWithRPCIssuer: + @brief Uses the default backend implementation, but with a custom RPC issuer. + @param RPCIssuer The RPC issuer to use. If @c nil, will use the default implementation. + @remarks This is not, generally, safe to call in a scenario where other backend requests may + be occuring. This is specifically to help test the backend interfaces (requests, responses, + and shared FIRAuthBackend logic.) + */ ++ (void)setDefaultBackendImplementationWithRPCIssuer: + (nullable id)RPCIssuer; + +/** @fn createAuthURI:callback: + @brief Calls the createAuthURI endpoint, which is responsible for creating the URI used by the + IdP to authenticate the user. + @param request The request parameters. + @param callback The callback. + */ ++ (void)createAuthURI:(FIRCreateAuthURIRequest *)request + callback:(FIRCreateAuthURIResponseCallback)callback; + +/** @fn getAccountInfo:callback: + @brief Calls the getAccountInfo endpoint, which returns account info for a given account. + @param request The request parameters. + @param callback The callback. + */ ++ (void)getAccountInfo:(FIRGetAccountInfoRequest *)request + callback:(FIRGetAccountInfoResponseCallback)callback; + +/** @fn getProjectConfig:callback: + @brief Calls the getProjectConfig endpoint, which returns configuration information for a given + project. + @param request An object wrapping the backend get request. + @param callback The callback. + */ ++ (void)getProjectConfig:(FIRGetProjectConfigRequest *)request + callback:(FIRGetProjectConfigResponseCallback)callback; + +/** @fn setAccountInfo:callback: + @brief Calls the setAccountInfo endpoint, which is responsible for setting account info for a + user, for example, to sign up a new user with email and password. + @param request The request parameters. + @param callback The callback. + */ ++ (void)setAccountInfo:(FIRSetAccountInfoRequest *)request + callback:(FIRSetAccountInfoResponseCallback)callback; + +/** @fn verifyAssertion:callback: + @brief Calls the verifyAssertion endpoint, which is responsible for authenticating a + user who has IDP-related credentials (an ID Token, an Access Token, etc.) + @param request The request parameters. + @param callback The callback. + */ ++ (void)verifyAssertion:(FIRVerifyAssertionRequest *)request + callback:(FIRVerifyAssertionResponseCallback)callback; + +/** @fn verifyCustomToken:callback: + @brief Calls the verifyCustomToken endpoint, which is responsible for authenticating a + user who has BYOAuth credentials (a self-signed token using their BYOAuth private key.) + @param request The request parameters. + @param callback The callback. + */ ++ (void)verifyCustomToken:(FIRVerifyCustomTokenRequest *)request + callback:(FIRVerifyCustomTokenResponseCallback)callback; + +/** @fn verifyPassword:callback: + @brief Calls the verifyPassword endpoint, which is responsible for authenticating a + user who has email and password credentials. + @param request The request parameters. + @param callback The callback. + */ ++ (void)verifyPassword:(FIRVerifyPasswordRequest *)request + callback:(FIRVerifyPasswordResponseCallback)callback; + +/** @fn emailLinkSignin:callback: + @brief Calls the emailLinkSignin endpoint, which is responsible for authenticating a + user through passwordless sign-in. + @param request The request parameters. + @param callback The callback. + */ ++ (void)emailLinkSignin:(FIREmailLinkSignInRequest *)request + callback:(FIREmailLinkSigninResponseCallback)callback; + +/** @fn secureToken:callback: + @brief Calls the token endpoint, which is responsible for performing STS token exchanges and + token refreshes. + @param request The request parameters. + @param callback The callback. + */ ++ (void)secureToken:(FIRSecureTokenRequest *)request + callback:(FIRSecureTokenResponseCallback)callback; + +/** @fn getOOBConfirmationCode:callback: + @brief Calls the getOOBConfirmationCode endpoint, which is responsible for sending email change + request emails, and password reset emails. + @param request The request parameters. + @param callback The callback. + */ ++ (void)getOOBConfirmationCode:(FIRGetOOBConfirmationCodeRequest *)request + callback:(FIRGetOOBConfirmationCodeResponseCallback)callback; + +/** @fn signUpNewUser: + @brief Calls the signUpNewUser endpoint, which is responsible anonymously signing up a user + or signing in a user anonymously. + @param request The request parameters. + @param callback The callback. + */ ++ (void)signUpNewUser:(FIRSignUpNewUserRequest *)request + callback:(FIRSignupNewUserCallback)callback; + +/** @fn resetPassword:callback + @brief Calls the resetPassword endpoint, which is responsible for resetting a user's password + given an OOB code and new password. + @param request The request parameters. + @param callback The callback. + */ ++ (void)resetPassword:(FIRResetPasswordRequest *)request + callback:(FIRResetPasswordCallback)callback; + +/** @fn deleteAccount: + @brief Calls the DeleteAccount endpoint, which is responsible for deleting a user. + @param request The request parameters. + @param callback The callback. + */ ++ (void)deleteAccount:(FIRDeleteAccountRequest *)request + callback:(FIRDeleteCallBack)callback; + +/** @fn SignInWithGameCenter:callback: + @brief Calls the SignInWithGameCenter endpoint, which is responsible for authenticating a user + who has Game Center credentials. + @param request The request parameters. + @param callback The callback. + */ ++ (void)signInWithGameCenter:(FIRSignInWithGameCenterRequest *)request + callback:(FIRSignInWithGameCenterResponseCallback)callback; + +#if TARGET_OS_IOS +/** @fn sendVerificationCode:callback: + @brief Calls the sendVerificationCode endpoint, which is responsible for sending the + verification code to a phone number specified in the request parameters. + @param request The request parameters. + @param callback The callback. + */ ++ (void)sendVerificationCode:(FIRSendVerificationCodeRequest *)request + callback:(FIRSendVerificationCodeResponseCallback)callback; + +/** @fn verifyPhoneNumber:callback: + @brief Calls the verifyPhoneNumber endpoint, which is responsible for sending the verification + code to a phone number specified in the request parameters. + @param request The request parameters. + @param callback The callback. + */ ++ (void)verifyPhoneNumber:(FIRVerifyPhoneNumberRequest *)request + callback:(FIRVerifyPhoneNumberResponseCallback)callback; + +/** @fn verifyClient:callback: + @brief Calls the verifyClient endpoint, which is responsible for sending the silent push + notification used for app validation to the device provided in the request parameters. + @param request The request parameters. + @param callback The callback. + */ ++ (void)verifyClient:(FIRVerifyClientRequest *)request + callback:(FIRVerifyClientResponseCallback)callback; +#endif + +@end + +/** @protocol FIRAuthBackendRPCIssuer + @brief Used to make FIRAuthBackend + */ +@protocol FIRAuthBackendRPCIssuer + +/** @fn asyncPostToURLWithRequestConfiguration:URL:body:contentType:completionHandler: + @brief Asynchronously seXnds a POST request. + @param requestConfiguration The request to be made. + @param URL The request URL. + @param body Request body. + @param contentType Content type of the body. + @param handler provided that handles POST response. Invoked asynchronously on the auth global + work queue in the future. + */ +- (void)asyncPostToURLWithRequestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + URL:(NSURL *)URL + body:(nullable NSData *)body + contentType:(NSString *)contentType + completionHandler:(FIRAuthBackendRPCIssuerCompletionHandler)handler; + +@end + +/** @protocol FIRAuthBackendImplementation + @brief Used to make FIRAuthBackend provide a layer of indirection to an actual RPC-based backend + or a mock backend. + */ +@protocol FIRAuthBackendImplementation + +/** @fn createAuthURI:callback: + @brief Calls the createAuthURI endpoint, which is responsible for creating the URI used by the + IdP to authenticate the user. + @param request The request parameters. + @param callback The callback. + */ +- (void)createAuthURI:(FIRCreateAuthURIRequest *)request + callback:(FIRCreateAuthURIResponseCallback)callback; + +/** @fn getAccountInfo:callback: + @brief Calls the getAccountInfo endpoint, which returns account info for a given account. + @param request The request parameters. + @param callback The callback. + */ +- (void)getAccountInfo:(FIRGetAccountInfoRequest *)request + callback:(FIRGetAccountInfoResponseCallback)callback; + +/** @fn getProjectConfig:callback: + @brief Calls the getProjectInfo endpoint, which returns configuration information for a given + project. + @param request The request parameters. + @param callback The callback. + */ +- (void)getProjectConfig:(FIRGetProjectConfigRequest *)request + callback:(FIRGetProjectConfigResponseCallback)callback; + +/** @fn setAccountInfo:callback: + @brief Calls the setAccountInfo endpoint, which is responsible for setting account info for a + user, for example, to sign up a new user with email and password. + @param request The request parameters. + @param callback The callback. + */ +- (void)setAccountInfo:(FIRSetAccountInfoRequest *)request + callback:(FIRSetAccountInfoResponseCallback)callback; + +/** @fn verifyAssertion:callback: + @brief Calls the verifyAssertion endpoint, which is responsible for authenticating a + user who has IDP-related credentials (an ID Token, an Access Token, etc.) + @param request The request parameters. + @param callback The callback. + */ +- (void)verifyAssertion:(FIRVerifyAssertionRequest *)request + callback:(FIRVerifyAssertionResponseCallback)callback; + +/** @fn verifyCustomToken:callback: + @brief Calls the verifyCustomToken endpoint, which is responsible for authenticating a + user who has BYOAuth credentials (a self-signed token using their BYOAuth private key.) + @param request The request parameters. + @param callback The callback. + */ +- (void)verifyCustomToken:(FIRVerifyCustomTokenRequest *)request + callback:(FIRVerifyCustomTokenResponseCallback)callback; + +/** @fn verifyPassword:callback: + @brief Calls the verifyPassword endpoint, which is responsible for authenticating a + user who has email and password credentials. + @param request The request parameters. + @param callback The callback. + */ +- (void)verifyPassword:(FIRVerifyPasswordRequest *)request + callback:(FIRVerifyPasswordResponseCallback)callback; + +/** @fn emailLinkSignin:callback: + @brief Calls the emailLinkSignin endpoint, which is responsible for authenticating a + user through passwordless sign-in. + @param request The request parameters. + @param callback The callback. + */ +- (void)emailLinkSignin:(FIREmailLinkSignInRequest *)request + callback:(FIREmailLinkSigninResponseCallback)callback; + +/** @fn secureToken:callback: + @brief Calls the token endpoint, which is responsible for performing STS token exchanges and + token refreshes. + @param request The request parameters. + @param callback The callback. + */ +- (void)secureToken:(FIRSecureTokenRequest *)request + callback:(FIRSecureTokenResponseCallback)callback; + +/** @fn getOOBConfirmationCode:callback: + @brief Calls the getOOBConfirmationCode endpoint, which is responsible for sending email change + request emails, email sign-in link emails, and password reset emails. + @param request The request parameters. + @param callback The callback. + */ +- (void)getOOBConfirmationCode:(FIRGetOOBConfirmationCodeRequest *)request + callback:(FIRGetOOBConfirmationCodeResponseCallback)callback; + +/** @fn signUpNewUser: + @brief Calls the signUpNewUser endpoint, which is responsible anonymously signing up a user + or signing in a user anonymously. + @param request The request parameters. + @param callback The callback. + */ +- (void)signUpNewUser:(FIRSignUpNewUserRequest *)request + callback:(FIRSignupNewUserCallback)callback; + +/** @fn deleteAccount: + @brief Calls the DeleteAccount endpoint, which is responsible for deleting a user. + @param request The request parameters. + @param callback The callback. + */ +- (void)deleteAccount:(FIRDeleteAccountRequest *)request + callback:(FIRDeleteCallBack)callback; + +#if TARGET_OS_IOS +/** @fn sendVerificationCode:callback: + @brief Calls the sendVerificationCode endpoint, which is responsible for sending the + verification code to a phone number specified in the request parameters. + @param request The request parameters. + @param callback The callback. + */ +- (void)sendVerificationCode:(FIRSendVerificationCodeRequest *)request + callback:(FIRSendVerificationCodeResponseCallback)callback; + +/** @fn verifyPhoneNumber:callback: + @brief Calls the verifyPhoneNumber endpoint, which is responsible for sending the verification + code to a phone number specified in the request parameters. + @param request The request parameters. + @param callback The callback. + */ +- (void)verifyPhoneNumber:(FIRVerifyPhoneNumberRequest *)request + callback:(FIRVerifyPhoneNumberResponseCallback)callback; + +/** @fn verifyClient:callback: + @brief Calls the verifyClient endpoint, which is responsible for sending the silent push + notification used for app validation to the device provided in the request parameters. + @param request The request parameters. + @param callback The callback. + */ +- (void)verifyClient:(FIRVerifyClientRequest *)request + callback:(FIRVerifyClientResponseCallback)callback; +#endif + +/** @fn SignInWithGameCenter:callback: + @brief Calls the SignInWithGameCenter endpoint, which is responsible for authenticating a user + who has Game Center credentials. + @param request The request parameters. + @param callback The callback. + */ +- (void)signInWithGameCenter:(FIRSignInWithGameCenterRequest *)request + callback:(FIRSignInWithGameCenterResponseCallback)callback; + +/** @fn resetPassword:callback + @brief Calls the resetPassword endpoint, which is responsible for resetting a user's password + given an OOB code and new password. + @param request The request parameters. + @param callback The callback. + */ +- (void)resetPassword:(FIRResetPasswordRequest *)request + callback:(FIRResetPasswordCallback)callback; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/FIRAuthBackend.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/FIRAuthBackend.m new file mode 100644 index 0000000..53505f7 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/FIRAuthBackend.m @@ -0,0 +1,1214 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRAuthBackend.h" + +#import +#import + +#import "FIRAuthErrorUtils.h" +#import "FIRAuthGlobalWorkQueue.h" +#import "FirebaseAuth.h" +#import "FIRAuthRPCRequest.h" +#import "FIRAuthRPCResponse.h" +#import "FIRCreateAuthURIRequest.h" +#import "FIRCreateAuthURIResponse.h" +#import "FIRDeleteAccountRequest.h" +#import "FIRDeleteAccountResponse.h" +#import "FIRGetAccountInfoRequest.h" +#import "FIRGetAccountInfoResponse.h" +#import "FIRSignInWithGameCenterRequest.h" +#import "FIRSignInWithGameCenterResponse.h" +#import "FIRGetOOBConfirmationCodeRequest.h" +#import "FIRGetOOBConfirmationCodeResponse.h" +#import "FIRGetProjectConfigRequest.h" +#import "FIRGetProjectConfigResponse.h" +#import "FIRResetPasswordRequest.h" +#import "FIRResetPasswordResponse.h" +#import "FIRSendVerificationCodeRequest.h" +#import "FIRSendVerificationCodeResponse.h" +#import "FIRSecureTokenRequest.h" +#import "FIRSecureTokenResponse.h" +#import "FIRSetAccountInfoRequest.h" +#import "FIRSetAccountInfoResponse.h" +#import "FIRSignUpNewUserRequest.h" +#import "FIRSignUpNewUserResponse.h" +#import "FIRVerifyAssertionRequest.h" +#import "FIRVerifyAssertionResponse.h" +#import "FIRVerifyClientRequest.h" +#import "FIRVerifyClientResponse.h" +#import "FIRVerifyCustomTokenRequest.h" +#import "FIRVerifyCustomTokenResponse.h" +#import "FIRVerifyPasswordRequest.h" +#import "FIRVerifyPasswordResponse.h" +#import "FIREmailLinkSignInRequest.h" +#import "FIREmailLinkSignInResponse.h" +#import "FIRVerifyPhoneNumberRequest.h" +#import "FIRVerifyPhoneNumberResponse.h" + +#import "FIROAuthCredential_Internal.h" +#if TARGET_OS_IOS +#import "FIRPhoneAuthCredential_Internal.h" +#import "FIRPhoneAuthProvider.h" +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** @var kClientVersionHeader + @brief HTTP header name for the client version. + */ +static NSString *const kClientVersionHeader = @"X-Client-Version"; + +/** @var kIosBundleIdentifierHeader + @brief HTTP header name for iOS bundle ID. + */ +static NSString *const kIosBundleIdentifierHeader = @"X-Ios-Bundle-Identifier"; + +/** @var kFirebaseLocalHeader + @brief HTTP header name for the firebase locale. + */ +static NSString *const kFirebaseLocalHeader = @"X-Firebase-Locale"; + +/** @var kFirebaseAuthCoreFrameworkMarker + @brief The marker in the HTTP header that indicates the request comes from Firebase Auth Core. + */ +static NSString *const kFirebaseAuthCoreFrameworkMarker = @"FirebaseCore-iOS"; + +/** @var kJSONContentType + @brief The value of the HTTP content-type header for JSON payloads. + */ +static NSString *const kJSONContentType = @"application/json"; + +/** @var kErrorDataKey + @brief Key for error data in NSError returned by @c GTMSessionFetcher. + */ +static NSString * const kErrorDataKey = @"data"; + +/** @var kErrorKey + @brief The key for the "error" value in JSON responses from the server. + */ +static NSString *const kErrorKey = @"error"; + +/** @var kErrorsKey + @brief The key for the "errors" value in JSON responses from the server. + */ +static NSString *const kErrorsKey = @"errors"; + +/** @var kReasonKey + @brief The key for the "reason" value in JSON responses from the server. + */ +static NSString *const kReasonKey = @"reason"; + +/** @var kInvalidKeyReasonValue + @brief The value for the "reason" key indicating an invalid API Key was received by the server. + */ +static NSString *const kInvalidKeyReasonValue = @"keyInvalid"; + +/** @var kAppNotAuthorizedReasonValue + @brief The value for the "reason" key indicating the App is not authorized to use Firebase + Authentication. + */ +static NSString *const kAppNotAuthorizedReasonValue = @"ipRefererBlocked"; + +/** @var kErrorMessageKey + @brief The key for an error's "message" value in JSON responses from the server. + */ +static NSString *const kErrorMessageKey = @"message"; + +/** @var kReturnIDPCredentialErrorMessageKey + @brief The key for "errorMessage" value in JSON responses from the server, In case + returnIDPCredential of a verifyAssertion request is set to @YES. + */ +static NSString *const kReturnIDPCredentialErrorMessageKey = @"errorMessage"; + +/** @var kUserNotFoundErrorMessage + @brief This is the error message returned when the user is not found, which means the user + account has been deleted given the token was once valid. + */ +static NSString *const kUserNotFoundErrorMessage = @"USER_NOT_FOUND"; + +/** @var kUserDeletedErrorMessage + @brief This is the error message the server will respond with if the user entered an invalid + email address. + */ +static NSString *const kUserDeletedErrorMessage = @"EMAIL_NOT_FOUND"; + +/** @var kInvalidLocalIDErrorMessage + @brief This is the error message the server responds with if the user local id in the id token + does not exit. + */ +static NSString *const kInvalidLocalIDErrorMessage = @"INVALID_LOCAL_ID"; + +/** @var kUserTokenExpiredErrorMessage + @brief The error returned by the server if the token issue time is older than the account's + valid_since time. + */ +static NSString *const kUserTokenExpiredErrorMessage = @"TOKEN_EXPIRED"; + +/** @var kTooManyRequestsErrorMessage + @brief This is the error message the server will respond with if too many requests were made to + a server method. + */ +static NSString *const kTooManyRequestsErrorMessage = @"TOO_MANY_ATTEMPTS_TRY_LATER"; + +/** @var kInvalidTokenCustomErrorMessage + @brief This is the error message the server will respond with if there is a validation error + with the custom token. + */ +static NSString *const kInvalidCustomTokenErrorMessage = @"INVALID_CUSTOM_TOKEN"; + +/** @var kCustomTokenMismatch + @brief This is the error message the server will respond with if the service account and API key + belong to different projects. + */ +static NSString *const kCustomTokenMismatch = @"CREDENTIAL_MISMATCH"; + +/** @var kInvalidCredentialErrorMessage + @brief This is the error message the server responds with if the IDP token or requestUri is + invalid. + */ +static NSString *const kInvalidCredentialErrorMessage = @"INVALID_IDP_RESPONSE"; + +/** @var kUserDisabledErrorMessage + @brief The error returned by the server if the user account is diabled. + */ +static NSString *const kUserDisabledErrorMessage = @"USER_DISABLED"; + +/** @var kOperationNotAllowedErrorMessage + @brief This is the error message the server will respond with if Admin disables IDP specified by + provider. + */ +static NSString *const kOperationNotAllowedErrorMessage = @"OPERATION_NOT_ALLOWED"; + +/** @var kPasswordLoginDisabledErrorMessage + @brief This is the error message the server responds with if password login is disabled. + */ +static NSString *const kPasswordLoginDisabledErrorMessage = @"PASSWORD_LOGIN_DISABLED"; + +/** @var kEmailAlreadyInUseErrorMessage + @brief This is the error message the server responds with if the email address already exists. + */ +static NSString *const kEmailAlreadyInUseErrorMessage = @"EMAIL_EXISTS"; + +/** @var kInvalidEmailErrorMessage + @brief The error returned by the server if the email is invalid. + */ +static NSString *const kInvalidEmailErrorMessage = @"INVALID_EMAIL"; + +/** @var kInvalidIdentifierErrorMessage + @brief The error returned by the server if the identifier is invalid. + */ +static NSString *const kInvalidIdentifierErrorMessage = @"INVALID_IDENTIFIER"; + +/** @var kWrongPasswordErrorMessage + @brief This is the error message the server will respond with if the user entered a wrong + password. + */ +static NSString *const kWrongPasswordErrorMessage = @"INVALID_PASSWORD"; + +/** @var kCredentialTooOldErrorMessage + @brief This is the error message the server responds with if account change is attempted 5 + minutes after signing in. + */ +static NSString *const kCredentialTooOldErrorMessage = @"CREDENTIAL_TOO_OLD_LOGIN_AGAIN"; + +/** @var kFederatedUserIDAlreadyLinkedMessage + @brief This is the error message the server will respond with if the federated user ID has been + already linked with another account. + */ +static NSString *const kFederatedUserIDAlreadyLinkedMessage = @"FEDERATED_USER_ID_ALREADY_LINKED"; + +/** @var kInvalidUserTokenErrorMessage + @brief This is the error message the server responds with if user's saved auth credential is + invalid, and the user needs to sign in again. + */ +static NSString *const kInvalidUserTokenErrorMessage = @"INVALID_ID_TOKEN"; + +/** @var kWeakPasswordErrorMessagePrefix + @brief This is the prefix for the error message the server responds with if user's new password + to be set is too weak. + */ +static NSString *const kWeakPasswordErrorMessagePrefix = @"WEAK_PASSWORD"; + +/** @var kExpiredActionCodeErrorMessage + @brief This is the error message the server will respond with if the action code is expired. + */ +static NSString *const kExpiredActionCodeErrorMessage = @"EXPIRED_OOB_CODE"; + +/** @var kInvalidActionCodeErrorMessage + @brief This is the error message the server will respond with if the action code is invalid. + */ +static NSString *const kInvalidActionCodeErrorMessage = @"INVALID_OOB_CODE"; + +/** @var kMissingEmailErrorMessage + @brief This is the error message the server will respond with if the email address is missing + during a "send password reset email" attempt. + */ +static NSString *const kMissingEmailErrorMessage = @"MISSING_EMAIL"; + +/** @var kInvalidSenderEmailErrorMessage + @brief This is the error message the server will respond with if the sender email is invalid + during a "send password reset email" attempt. + */ +static NSString *const kInvalidSenderEmailErrorMessage = @"INVALID_SENDER"; + +/** @var kInvalidMessagePayloadErrorMessage + @brief This is the error message the server will respond with if there are invalid parameters in + the payload during a "send password reset email" attempt. + */ +static NSString *const kInvalidMessagePayloadErrorMessage = @"INVALID_MESSAGE_PAYLOAD"; + +/** @var kInvalidRecipientEmailErrorMessage + @brief This is the error message the server will respond with if the recipient email is invalid. + */ +static NSString *const kInvalidRecipientEmailErrorMessage = @"INVALID_RECIPIENT_EMAIL"; + +/** @var kMissingIosBundleIDErrorMessage + @brief This is the error message the server will respond with if iOS bundle ID is missing but + the iOS App store ID is provided. + */ +static NSString *const kMissingIosBundleIDErrorMessage = @"MISSING_IOS_BUNDLE_ID"; + +/** @var kMissingAndroidPackageNameErrorMessage + @brief This is the error message the server will respond with if Android Package Name is missing + but the flag indicating the app should be installed is set to true. + */ +static NSString *const kMissingAndroidPackageNameErrorMessage = @"MISSING_ANDROID_PACKAGE_NAME"; + +/** @var kUnauthorizedDomainErrorMessage + @brief This is the error message the server will respond with if the domain of the continue URL + specified is not whitelisted in the firebase console. + */ +static NSString *const kUnauthorizedDomainErrorMessage = @"UNAUTHORIZED_DOMAIN"; + +/** @var kInvalidProviderIDErrorMessage + @brief This is the error message the server will respond with if the provider id given for the + web operation is invalid. + */ +static NSString *const kInvalidProviderIDErrorMessage = @"INVALID_PROVIDER_ID"; + +/** @var kInvalidDynamicLinkDomainErrorMessage + @brief This is the error message the server will respond with if the dynamic link domain + provided in the request is invalid. + */ +static NSString *const kInvalidDynamicLinkDomainErrorMessage = @"INVALID_DYNAMIC_LINK_DOMAIN"; + +/** @var kInvalidContinueURIErrorMessage + @brief This is the error message the server will respond with if the continue URL provided in + the request is invalid. + */ +static NSString *const kInvalidContinueURIErrorMessage = @"INVALID_CONTINUE_URI"; + +/** @var kMissingContinueURIErrorMessage + @brief This is the error message the server will respond with if there was no continue URI + present in a request that required one. + */ +static NSString *const kMissingContinueURIErrorMessage = @"MISSING_CONTINUE_URI"; + +/** @var kInvalidPhoneNumberErrorMessage + @brief This is the error message the server will respond with if an incorrectly formatted phone + number is provided. + */ +static NSString *const kInvalidPhoneNumberErrorMessage = @"INVALID_PHONE_NUMBER"; + +/** @var kInvalidVerificationCodeErrorMessage + @brief This is the error message the server will respond with if an invalid verification code is + provided. + */ +static NSString *const kInvalidVerificationCodeErrorMessage = @"INVALID_CODE"; + +/** @var kInvalidSessionInfoErrorMessage + @brief This is the error message the server will respond with if an invalid session info + (verification ID) is provided. + */ +static NSString *const kInvalidSessionInfoErrorMessage = @"INVALID_SESSION_INFO"; + +/** @var kSessionExpiredErrorMessage + @brief This is the error message the server will respond with if the SMS code has expired before + it is used. + */ +static NSString *const kSessionExpiredErrorMessage = @"SESSION_EXPIRED"; + +/** @var kMissingOrInvalidNonceErrorMessage + @brief This is the error message the server will respond with if the nonce is missing or invalid. + */ +static NSString *const kMissingOrInvalidNonceErrorMessage = @"MISSING_OR_INVALID_NONCE"; + +/** @var kMissingAppTokenErrorMessage + @brief This is the error message the server will respond with if the APNS token is missing in a + verifyClient request. + */ +static NSString *const kMissingAppTokenErrorMessage = @"MISSING_IOS_APP_TOKEN"; + +/** @var kMissingAppCredentialErrorMessage + @brief This is the error message the server will respond with if the app token is missing in a + sendVerificationCode request. + */ +static NSString *const kMissingAppCredentialErrorMessage = @"MISSING_APP_CREDENTIAL"; + +/** @var kInvalidAppCredentialErrorMessage + @brief This is the error message the server will respond with if the app credential in a + sendVerificationCode request is invalid. + */ +static NSString *const kInvalidAppCredentialErrorMessage = @"INVALID_APP_CREDENTIAL"; + +/** @var kQuoutaExceededErrorMessage + @brief This is the error message the server will respond with if the quota for SMS text messages + has been exceeded for the project. + */ +static NSString *const kQuoutaExceededErrorMessage = @"QUOTA_EXCEEDED"; + +/** @var kAppNotVerifiedErrorMessage + @brief This is the error message the server will respond with if Firebase could not verify the + app during a phone authentication flow. + */ +static NSString *const kAppNotVerifiedErrorMessage = @"APP_NOT_VERIFIED"; + +/** @var kMissingClientIdentifier + @brief This is the error message the server will respond with if Firebase could not verify the + app during a phone authentication flow when a real phone number is used and app verification + is disabled for testing. + */ +static NSString *const kMissingClientIdentifier = @"MISSING_CLIENT_IDENTIFIER"; + +/** @var kCaptchaCheckFailedErrorMessage + @brief This is the error message the server will respond with if the reCAPTCHA token provided is + invalid. + */ +static NSString *const kCaptchaCheckFailedErrorMessage = @"CAPTCHA_CHECK_FAILED"; + +/** @var kInvalidPendingToken + @brief Generic IDP error codes. + */ +static NSString *const kInvalidPendingToken = @"INVALID_PENDING_TOKEN"; + +/** @var gBackendImplementation + @brief The singleton FIRAuthBackendImplementation instance to use. + */ +static id gBackendImplementation; + +/** @class FIRAuthBackendRPCImplementation + @brief The default RPC-based backend implementation. + */ +@interface FIRAuthBackendRPCImplementation : NSObject + +/** @property RPCIssuer + @brief An instance of FIRAuthBackendRPCIssuer for making RPC requests. Allows the RPC + requests/responses to be easily faked. + */ +@property(nonatomic, strong) id RPCIssuer; + +@end + +@implementation FIRAuthBackend + ++ (id)implementation { + if (!gBackendImplementation) { + gBackendImplementation = [[FIRAuthBackendRPCImplementation alloc] init]; + } + return gBackendImplementation; +} + ++ (void)setBackendImplementation:(id)backendImplementation { + gBackendImplementation = backendImplementation; +} + ++ (void)setDefaultBackendImplementationWithRPCIssuer: + (nullable id)RPCIssuer { + FIRAuthBackendRPCImplementation *defaultImplementation = + [[FIRAuthBackendRPCImplementation alloc] init]; + if (RPCIssuer) { + defaultImplementation.RPCIssuer = RPCIssuer; + } + gBackendImplementation = defaultImplementation; +} + ++ (void)createAuthURI:(FIRCreateAuthURIRequest *)request + callback:(FIRCreateAuthURIResponseCallback)callback { + [[self implementation] createAuthURI:request callback:callback]; +} + ++ (void)getAccountInfo:(FIRGetAccountInfoRequest *)request + callback:(FIRGetAccountInfoResponseCallback)callback { + [[self implementation] getAccountInfo:request callback:callback]; +} + ++ (void)getProjectConfig:(FIRGetProjectConfigRequest *)request + callback:(FIRGetProjectConfigResponseCallback)callback { + [[self implementation] getProjectConfig:request callback:callback]; +} + ++ (void)setAccountInfo:(FIRSetAccountInfoRequest *)request + callback:(FIRSetAccountInfoResponseCallback)callback { + [[self implementation] setAccountInfo:request callback:callback]; +} + ++ (void)verifyAssertion:(FIRVerifyAssertionRequest *)request + callback:(FIRVerifyAssertionResponseCallback)callback { + [[self implementation] verifyAssertion:request callback:callback]; +} + ++ (void)verifyCustomToken:(FIRVerifyCustomTokenRequest *)request + callback:(FIRVerifyCustomTokenResponseCallback)callback { + [[self implementation] verifyCustomToken:request callback:callback]; +} + ++ (void)verifyPassword:(FIRVerifyPasswordRequest *)request + callback:(FIRVerifyPasswordResponseCallback)callback { + [[self implementation] verifyPassword:request callback:callback]; +} + ++ (void)emailLinkSignin:(FIREmailLinkSignInRequest *)request + callback:(FIREmailLinkSigninResponseCallback)callback { + [[self implementation] emailLinkSignin:request callback:callback]; +} + ++ (void)secureToken:(FIRSecureTokenRequest *)request + callback:(FIRSecureTokenResponseCallback)callback { + [[self implementation] secureToken:request callback:callback]; +} + ++ (void)getOOBConfirmationCode:(FIRGetOOBConfirmationCodeRequest *)request + callback:(FIRGetOOBConfirmationCodeResponseCallback)callback { + [[self implementation] getOOBConfirmationCode:request callback:callback]; +} + ++ (void)signUpNewUser:(FIRSignUpNewUserRequest *)request + callback:(FIRSignupNewUserCallback)callback { + [[self implementation] signUpNewUser:request callback:callback]; +} + ++ (void)deleteAccount:(FIRDeleteAccountRequest *)request callback:(FIRDeleteCallBack)callback { + [[self implementation] deleteAccount:request callback:callback]; +} + ++ (void)signInWithGameCenter:(FIRSignInWithGameCenterRequest *)request + callback:(FIRSignInWithGameCenterResponseCallback)callback { + [[self implementation] signInWithGameCenter:request callback:callback]; +} + +#if TARGET_OS_IOS ++ (void)sendVerificationCode:(FIRSendVerificationCodeRequest *)request + callback:(FIRSendVerificationCodeResponseCallback)callback { + [[self implementation] sendVerificationCode:request callback:callback]; +} + ++ (void)verifyPhoneNumber:(FIRVerifyPhoneNumberRequest *)request + callback:(FIRVerifyPhoneNumberResponseCallback)callback { + [[self implementation] verifyPhoneNumber:request callback:callback]; +} + ++ (void)verifyClient:(id)request callback:(FIRVerifyClientResponseCallback)callback { + [[self implementation] verifyClient:request callback:callback]; +} +#endif + ++ (void)resetPassword:(FIRResetPasswordRequest *)request + callback:(FIRResetPasswordCallback)callback { + [[self implementation] resetPassword:request callback:callback]; +} + ++ (NSString *)authUserAgent { + return [NSString stringWithFormat:@"FirebaseAuth.iOS/%s %@", + FirebaseAuthVersionStr, GTMFetcherStandardUserAgentString(nil)]; +} + +@end + +@interface FIRAuthBackendRPCIssuerImplementation : NSObject +@end + +@implementation FIRAuthBackendRPCIssuerImplementation { + /** @var The session fetcher service. + */ + GTMSessionFetcherService *_fetcherService; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _fetcherService = [[GTMSessionFetcherService alloc] init]; + _fetcherService.userAgent = [FIRAuthBackend authUserAgent]; + _fetcherService.callbackQueue = FIRAuthGlobalWorkQueue(); + + // Avoid reusing the session to prevent + // https://github.com/firebase/firebase-ios-sdk/issues/1261 + _fetcherService.reuseSession = NO; + } + return self; +} + +- (void)asyncPostToURLWithRequestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + URL:(NSURL *)URL + body:(nullable NSData *)body + contentType:(NSString *)contentType + completionHandler:(void (^)(NSData *_Nullable, + NSError *_Nullable))handler { + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; + [request setValue:contentType forHTTPHeaderField:@"Content-Type"]; + NSString *additionalFrameworkMarker = requestConfiguration.additionalFrameworkMarker ?: + kFirebaseAuthCoreFrameworkMarker; + NSString *clientVersion = [NSString stringWithFormat:@"iOS/FirebaseSDK/%s/%@", + FirebaseAuthVersionStr, + additionalFrameworkMarker]; + [request setValue:clientVersion forHTTPHeaderField:kClientVersionHeader]; + NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier]; + [request setValue:bundleID forHTTPHeaderField:kIosBundleIdentifierHeader]; + + NSArray *preferredLocalizations = [NSBundle mainBundle].preferredLocalizations; + if (preferredLocalizations.count) { + NSString *acceptLanguage = preferredLocalizations.firstObject; + [request setValue:acceptLanguage forHTTPHeaderField:@"Accept-Language"]; + } + NSString *languageCode = requestConfiguration.languageCode; + if (languageCode.length) { + [request setValue:languageCode forHTTPHeaderField:kFirebaseLocalHeader]; + } + GTMSessionFetcher *fetcher = [_fetcherService fetcherWithRequest:request]; + fetcher.bodyData = body; + [fetcher beginFetchWithCompletionHandler:handler]; +} + +@end + +@implementation FIRAuthBackendRPCImplementation + +- (instancetype)init { + self = [super init]; + if (self) { + _RPCIssuer = [[FIRAuthBackendRPCIssuerImplementation alloc] init]; + } + return self; +} + +- (void)createAuthURI:(FIRCreateAuthURIRequest *)request + callback:(FIRCreateAuthURIResponseCallback)callback { + FIRCreateAuthURIResponse *response = [[FIRCreateAuthURIResponse alloc] init]; + [self postWithRequest:request response:response callback:^(NSError *error) { + if (error) { + callback(nil, error); + } else { + callback(response, nil); + } + }]; +} + +- (void)getAccountInfo:(FIRGetAccountInfoRequest *)request + callback:(FIRGetAccountInfoResponseCallback)callback { + FIRGetAccountInfoResponse *response = [[FIRGetAccountInfoResponse alloc] init]; + [self postWithRequest:request response:response callback:^(NSError *error) { + if (error) { + callback(nil, error); + } else { + callback(response, nil); + } + }]; +} + +- (void)getProjectConfig:(FIRGetProjectConfigRequest *)request + callback:(FIRGetProjectConfigResponseCallback)callback { + FIRGetProjectConfigResponse *response = [[FIRGetProjectConfigResponse alloc] init]; + [self postWithRequest:request response:response callback:^(NSError *error) { + if (error) { + callback(nil, error); + } else { + callback(response, nil); + } + }]; +} + +- (void)setAccountInfo:(FIRSetAccountInfoRequest *)request + callback:(FIRSetAccountInfoResponseCallback)callback { + FIRSetAccountInfoResponse *response = [[FIRSetAccountInfoResponse alloc] init]; + [self postWithRequest:request response:response callback:^(NSError *error) { + if (error) { + callback(nil, error); + } else { + callback(response, nil); + } + }]; +} + +- (void)verifyAssertion:(FIRVerifyAssertionRequest *)request + callback:(FIRVerifyAssertionResponseCallback)callback { + FIRVerifyAssertionResponse *response = [[FIRVerifyAssertionResponse alloc] init]; + [self postWithRequest:request response:response callback:^(NSError *error) { + if (error) { + callback(nil, error); + return; + } + callback(response, nil); + }]; +} + +- (void)verifyCustomToken:(FIRVerifyCustomTokenRequest *)request + callback:(FIRVerifyCustomTokenResponseCallback)callback { + FIRVerifyCustomTokenResponse *response = [[FIRVerifyCustomTokenResponse alloc] init]; + [self postWithRequest:request response:response callback:^(NSError *error) { + if (error) { + callback(nil, error); + } else { + callback(response, nil); + } + }]; +} + +- (void)verifyPassword:(FIRVerifyPasswordRequest *)request + callback:(FIRVerifyPasswordResponseCallback)callback { + FIRVerifyPasswordResponse *response = [[FIRVerifyPasswordResponse alloc] init]; + [self postWithRequest:request response:response callback:^(NSError *error) { + if (error) { + callback(nil, error); + } else { + callback(response, nil); + } + }]; +} + +- (void)emailLinkSignin:(FIREmailLinkSignInRequest *)request + callback:(FIREmailLinkSigninResponseCallback)callback { + FIREmailLinkSignInResponse *response = [[FIREmailLinkSignInResponse alloc] init]; + [self postWithRequest:request response:response callback:^(NSError *error) { + if (error) { + callback(nil, error); + } else { + callback(response, nil); + } + }]; +} + +- (void)secureToken:(FIRSecureTokenRequest *)request + callback:(FIRSecureTokenResponseCallback)callback { + FIRSecureTokenResponse *response = [[FIRSecureTokenResponse alloc] init]; + [self postWithRequest:request response:response callback:^(NSError *error) { + if (error) { + callback(nil, error); + } else { + callback(response, nil); + } + }]; +} + +- (void)getOOBConfirmationCode:(FIRGetOOBConfirmationCodeRequest *)request + callback:(FIRGetOOBConfirmationCodeResponseCallback)callback { + FIRGetOOBConfirmationCodeResponse *response = [[FIRGetOOBConfirmationCodeResponse alloc] init]; + [self postWithRequest:request response:response callback:^(NSError *error) { + if (error) { + callback(nil, error); + } else { + callback(response, nil); + } + }]; +} + +- (void)signUpNewUser:(FIRSignUpNewUserRequest *)request + callback:(FIRSignupNewUserCallback)callback{ + FIRSignUpNewUserResponse *response = [[FIRSignUpNewUserResponse alloc] init]; + [self postWithRequest:request response:response callback:^(NSError *error) { + if (error) { + callback(nil, error); + } else { + callback(response, nil); + } + }]; +} + +- (void)deleteAccount:(FIRDeleteAccountRequest *)request callback:(FIRDeleteCallBack)callback { + FIRDeleteAccountResponse *response = [[FIRDeleteAccountResponse alloc] init]; + [self postWithRequest:request response:response callback:callback]; +} + +#if TARGET_OS_IOS +- (void)sendVerificationCode:(FIRSendVerificationCodeRequest *)request + callback:(FIRSendVerificationCodeResponseCallback)callback { + FIRSendVerificationCodeResponse *response = [[FIRSendVerificationCodeResponse alloc] init]; + [self postWithRequest:request response:response callback:^(NSError *error) { + if (error) { + callback(nil, error); + } else { + callback(response, error); + } + }]; +} + +- (void)verifyPhoneNumber:(FIRVerifyPhoneNumberRequest *)request + callback:(FIRVerifyPhoneNumberResponseCallback)callback { + FIRVerifyPhoneNumberResponse *response = [[FIRVerifyPhoneNumberResponse alloc] init]; + [self postWithRequest:request response:response callback:^(NSError *error) { + if (error) { + callback(nil, error); + return; + } + // Check whether or not the successful response is actually the special case phone auth flow + // that returns a temporary proof and phone number. + if (response.phoneNumber.length && response.temporaryProof.length) { + FIRPhoneAuthCredential *credential = + [[FIRPhoneAuthCredential alloc] initWithTemporaryProof:response.temporaryProof + phoneNumber:response.phoneNumber + providerID:FIRPhoneAuthProviderID]; + callback(nil, + [FIRAuthErrorUtils credentialAlreadyInUseErrorWithMessage:nil + credential:credential + email:nil]); + return; + } + callback(response, nil); + }]; +} + +- (void)verifyClient:(id)request callback:(FIRVerifyClientResponseCallback)callback { + FIRVerifyClientResponse *response = [[FIRVerifyClientResponse alloc] init]; + [self postWithRequest:request response:response callback:^(NSError *error) { + if (error) { + callback(nil, error); + return; + } + callback(response, nil); + }]; +} +#endif + +- (void)resetPassword:(FIRResetPasswordRequest *)request + callback:(FIRResetPasswordCallback)callback { + FIRResetPasswordResponse *response = [[FIRResetPasswordResponse alloc] init]; + [self postWithRequest:request response:response callback:^(NSError *error) { + if (error) { + callback(nil, error); + return; + } + callback(response, nil); + }]; +} + +- (void)signInWithGameCenter:(FIRSignInWithGameCenterRequest *)request + callback:(FIRSignInWithGameCenterResponseCallback)callback { + FIRSignInWithGameCenterResponse *response = [[FIRSignInWithGameCenterResponse alloc] init]; + [self postWithRequest:request response:response callback:^(NSError *error) { + if (error) { + if (callback) { + callback(nil, error); + } + } else { + if (callback) { + callback(response, nil); + } + } + }]; +} + +#pragma mark - Generic RPC handling methods + +/** @fn postWithRequest:response:callback: + @brief Calls the RPC using HTTP POST. + @remarks Possible error responses: + @see FIRAuthInternalErrorCodeRPCRequestEncodingError + @see FIRAuthInternalErrorCodeJSONSerializationError + @see FIRAuthInternalErrorCodeNetworkError + @see FIRAuthInternalErrorCodeUnexpectedErrorResponse + @see FIRAuthInternalErrorCodeUnexpectedResponse + @see FIRAuthInternalErrorCodeRPCResponseDecodingError + @param request The request. + @param response The empty response to be filled. + @param callback The callback for both success and failure. + */ +- (void)postWithRequest:(id)request + response:(id)response + callback:(void (^)(NSError * _Nullable error))callback { + NSError *error; + NSData *bodyData; + if ([request containsPostBody]) { + id postBody = [request unencodedHTTPRequestBodyWithError:&error]; + if (!postBody) { + callback([FIRAuthErrorUtils RPCRequestEncodingErrorWithUnderlyingError:error]); + return; + } + + NSJSONWritingOptions JSONWritingOptions = 0; + #if DEBUG + JSONWritingOptions |= NSJSONWritingPrettyPrinted; + #endif + + if ([NSJSONSerialization isValidJSONObject:postBody]) { + bodyData = [NSJSONSerialization dataWithJSONObject:postBody + options:JSONWritingOptions + error:&error]; + if (!bodyData) { + // This is an untested case. This happens exclusively when there is an error in the framework + // implementation of dataWithJSONObject:options:error:. This shouldn't normally occur as + // isValidJSONObject: should return NO in any case we should encounter an error. + error = [FIRAuthErrorUtils JSONSerializationErrorWithUnderlyingError:error]; + } + } else { + error = [FIRAuthErrorUtils JSONSerializationErrorForUnencodableType]; + } + if (!bodyData) { + callback(error); + return; + } + } + + [_RPCIssuer asyncPostToURLWithRequestConfiguration:[request requestConfiguration] + URL:[request requestURL] + body:bodyData + contentType:kJSONContentType + completionHandler:^(NSData *data, NSError *error) { + // If there is an error with no body data at all, then this must be a network error. + if (error && !data) { + callback([FIRAuthErrorUtils networkErrorWithUnderlyingError:error]); + return; + } + + // Try to decode the HTTP response data which may contain either a successful response or error + // message. + NSError *jsonError; + NSDictionary * dictionary = + [NSJSONSerialization JSONObjectWithData:data + options:NSJSONReadingMutableLeaves + error:&jsonError]; + if (!dictionary) { + if (error) { + // We have an error, but we couldn't decode the body, so we have no additional information + // other than the raw response and the original NSError (the jsonError is infered by the + // error code (FIRAuthErrorCodeUnexpectedHTTPResponse, and is irrelevant.) + callback([FIRAuthErrorUtils unexpectedErrorResponseWithData:data underlyingError:error]); + } else { + // This is supposed to be a "successful" response, but we couldn't deserialize the body. + callback([FIRAuthErrorUtils unexpectedResponseWithData:data underlyingError:jsonError]); + } + return; + } + if (![dictionary isKindOfClass:[NSDictionary class]]) { + if (error) { + callback([FIRAuthErrorUtils unexpectedErrorResponseWithDeserializedResponse:dictionary]); + } else { + callback([FIRAuthErrorUtils unexpectedResponseWithDeserializedResponse:dictionary]); + } + return; + } + + // At this point we either have an error with successfully decoded details in the body, or we + // have a response which must pass further validation before we know it's truly successful. + // We deal with the case where we have an error with successfully decoded error details first: + if (error) { + NSDictionary *errorDictionary = dictionary[kErrorKey]; + if ([errorDictionary isKindOfClass:[NSDictionary class]]) { + id errorMessage = errorDictionary[kErrorMessageKey]; + if ([errorMessage isKindOfClass:[NSString class]]) { + NSString *errorMessageString = (NSString *)errorMessage; + + // Contruct client error. + NSError *clientError = [[self class] clientErrorWithServerErrorMessage:errorMessageString + errorDictionary:errorDictionary + response:response]; + if (clientError) { + callback(clientError); + return; + } + } + // Not a message we know, return the message directly. + if (errorMessage) { + NSError *unexpecterErrorResponse = + [FIRAuthErrorUtils unexpectedErrorResponseWithDeserializedResponse:errorDictionary]; + callback(unexpecterErrorResponse); + return; + } + } + // No error message at all, return the decoded response. + callback([FIRAuthErrorUtils unexpectedErrorResponseWithDeserializedResponse:dictionary]); + return; + } + + // Finally, we try to populate the response object with the JSON values. + if (![response setWithDictionary:dictionary error:&error]) { + callback([FIRAuthErrorUtils RPCResponseDecodingErrorWithDeserializedResponse:dictionary + underlyingError:error]); + return; + } + // In case returnIDPCredential of a verifyAssertion request is set to @YES, the server may + // return a 200 with a response that may contain a server error. + if ([request isKindOfClass:[FIRVerifyAssertionRequest class]]) { + FIRVerifyAssertionRequest *verifyAssertionRequest = (FIRVerifyAssertionRequest *)request; + if (verifyAssertionRequest.returnIDPCredential) { + NSString *errorMessage = dictionary[kReturnIDPCredentialErrorMessageKey]; + if ([errorMessage isKindOfClass:[NSString class]]) { + NSString *errorString = (NSString *)errorMessage; + NSError *clientError = [[self class] clientErrorWithServerErrorMessage:errorString + errorDictionary:@{} + response:response]; + if (clientError) { + callback(clientError); + return; + } + } + } + } + // Success! The response object originally passed in can be used by the caller. + callback(nil); + }]; +} + +/** @fn clientErrorWithServerErrorMessage:errorDictionary: + @brief Translates known server errors to client errors. + @param serverErrorMessage The error message from the server. + @param errorDictionary The error part of the response from the server. + @param response The response from the server RPC. + @return A client error, if any. + */ ++ (nullable NSError *)clientErrorWithServerErrorMessage:(NSString *)serverErrorMessage + errorDictionary:(NSDictionary *)errorDictionary + response:(id)response { + NSString *shortErrorMessage = serverErrorMessage; + NSString *serverDetailErrorMessage; + NSRange colonRange = [serverErrorMessage rangeOfString:@":"]; + if (colonRange.location != NSNotFound) { + shortErrorMessage = [serverErrorMessage substringToIndex:colonRange.location]; + shortErrorMessage = + [shortErrorMessage stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + serverDetailErrorMessage = [serverErrorMessage substringFromIndex:colonRange.location + 1]; + serverDetailErrorMessage = [serverDetailErrorMessage stringByTrimmingCharactersInSet: + [NSCharacterSet whitespaceCharacterSet]]; + } + + // Delegate the responsibility for constructing the client error to the response object, + // if possible. + SEL clientErrorWithServerErrorMessageSelector = + @selector(clientErrorWithShortErrorMessage:detailErrorMessage:); + if ([response respondsToSelector:clientErrorWithServerErrorMessageSelector]) { + NSError *error = [response clientErrorWithShortErrorMessage:shortErrorMessage + detailErrorMessage:serverDetailErrorMessage]; + if (error) { + return error; + } + } + + if ([shortErrorMessage isEqualToString:kUserNotFoundErrorMessage]) { + return [FIRAuthErrorUtils userNotFoundErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kUserDeletedErrorMessage]) { + return [FIRAuthErrorUtils userNotFoundErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kInvalidLocalIDErrorMessage]) { + // This case shouldn't be necessary but it is for now: b/27908364 . + return [FIRAuthErrorUtils userNotFoundErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kUserTokenExpiredErrorMessage]) { + return [FIRAuthErrorUtils userTokenExpiredErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kTooManyRequestsErrorMessage]) { + return [FIRAuthErrorUtils tooManyRequestsErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kInvalidCustomTokenErrorMessage]) { + return [FIRAuthErrorUtils invalidCustomTokenErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kCustomTokenMismatch]) { + return [FIRAuthErrorUtils customTokenMistmatchErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kInvalidCredentialErrorMessage] || + [shortErrorMessage isEqualToString:kInvalidPendingToken]) { + return [FIRAuthErrorUtils invalidCredentialErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kUserDisabledErrorMessage]) { + return [FIRAuthErrorUtils userDisabledErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kOperationNotAllowedErrorMessage]) { + return [FIRAuthErrorUtils operationNotAllowedErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kPasswordLoginDisabledErrorMessage]) { + return [FIRAuthErrorUtils operationNotAllowedErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kEmailAlreadyInUseErrorMessage]) { + return [FIRAuthErrorUtils emailAlreadyInUseErrorWithEmail:nil]; + } + + if ([shortErrorMessage isEqualToString:kInvalidEmailErrorMessage]) { + return [FIRAuthErrorUtils invalidEmailErrorWithMessage:serverDetailErrorMessage]; + } + + // "INVALID_IDENTIFIER" can be returned by createAuthURI RPC. Considering email addresses are + // currently the only identifiers, we surface the FIRAuthErrorCodeInvalidEmail error code in this + // case. + if ([shortErrorMessage isEqualToString:kInvalidIdentifierErrorMessage]) { + return [FIRAuthErrorUtils invalidEmailErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kWrongPasswordErrorMessage]) { + return [FIRAuthErrorUtils wrongPasswordErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kCredentialTooOldErrorMessage]) { + return [FIRAuthErrorUtils requiresRecentLoginErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kInvalidUserTokenErrorMessage]) { + return [FIRAuthErrorUtils invalidUserTokenErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kFederatedUserIDAlreadyLinkedMessage]) { + FIROAuthCredential *credential; + NSString *email; + if ([response isKindOfClass:[FIRVerifyAssertionResponse class]]) { + FIRVerifyAssertionResponse *verifyAssertion = (FIRVerifyAssertionResponse *)response; + credential = + [[FIROAuthCredential alloc] initWithVerifyAssertionResponse:verifyAssertion]; + email = verifyAssertion.email; + } + return [FIRAuthErrorUtils credentialAlreadyInUseErrorWithMessage:serverDetailErrorMessage + credential:credential + email:email]; + } + + if ([shortErrorMessage isEqualToString:kWeakPasswordErrorMessagePrefix]) { + return [FIRAuthErrorUtils weakPasswordErrorWithServerResponseReason:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kExpiredActionCodeErrorMessage]) { + return [FIRAuthErrorUtils expiredActionCodeErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kInvalidActionCodeErrorMessage]) { + return [FIRAuthErrorUtils invalidActionCodeErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kMissingEmailErrorMessage]) { + return [FIRAuthErrorUtils missingEmailErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kInvalidSenderEmailErrorMessage]) { + return [FIRAuthErrorUtils invalidSenderErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kInvalidMessagePayloadErrorMessage]) { + return [FIRAuthErrorUtils invalidMessagePayloadErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kInvalidRecipientEmailErrorMessage]) { + return [FIRAuthErrorUtils invalidRecipientEmailErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kMissingIosBundleIDErrorMessage]) { + return [FIRAuthErrorUtils missingIosBundleIDErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kMissingAndroidPackageNameErrorMessage]) { + return [FIRAuthErrorUtils missingAndroidPackageNameErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kUnauthorizedDomainErrorMessage]) { + return [FIRAuthErrorUtils unauthorizedDomainErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kInvalidContinueURIErrorMessage]) { + return [FIRAuthErrorUtils invalidContinueURIErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kInvalidProviderIDErrorMessage]) { + return [FIRAuthErrorUtils invalidProviderIDErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kInvalidDynamicLinkDomainErrorMessage]) { + return [FIRAuthErrorUtils invalidDynamicLinkDomainErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kMissingContinueURIErrorMessage]) { + return [FIRAuthErrorUtils missingContinueURIErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kInvalidPhoneNumberErrorMessage]) { + return [FIRAuthErrorUtils invalidPhoneNumberErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kInvalidSessionInfoErrorMessage]) { + return [FIRAuthErrorUtils invalidVerificationIDErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kInvalidVerificationCodeErrorMessage]) { + return [FIRAuthErrorUtils invalidVerificationCodeErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kSessionExpiredErrorMessage]) { + return [FIRAuthErrorUtils sessionExpiredErrorWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kMissingAppTokenErrorMessage]) { + return [FIRAuthErrorUtils missingAppTokenErrorWithUnderlyingError:nil]; + } + + if ([shortErrorMessage isEqualToString:kMissingAppCredentialErrorMessage]) { + return [FIRAuthErrorUtils missingAppCredentialWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kInvalidAppCredentialErrorMessage]) { + return [FIRAuthErrorUtils invalidAppCredentialWithMessage:serverDetailErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kQuoutaExceededErrorMessage]) { + return [FIRAuthErrorUtils quotaExceededErrorWithMessage:serverErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kAppNotVerifiedErrorMessage]) { + return [FIRAuthErrorUtils appNotVerifiedErrorWithMessage:serverErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kMissingClientIdentifier]) { + return [FIRAuthErrorUtils missingClientIdentifierErrorWithMessage:serverErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kCaptchaCheckFailedErrorMessage]) { + return [FIRAuthErrorUtils captchaCheckFailedErrorWithMessage:serverErrorMessage]; + } + + if ([shortErrorMessage isEqualToString:kMissingOrInvalidNonceErrorMessage]) { + return [FIRAuthErrorUtils missingOrInvalidNonceErrorWithMessage:serverDetailErrorMessage]; + } + + // In this case we handle an error that might be specified in the underlying errors dictionary, + // the error message in determined based on the @c reason key in the dictionary. + if (errorDictionary[kErrorsKey]) { + // Check for underlying error with reason = keyInvalid; + id underlyingErrors = errorDictionary[kErrorsKey]; + if ([underlyingErrors isKindOfClass:[NSArray class]]) { + NSArray *underlyingErrorsArray = (NSArray *)underlyingErrors; + for (id underlyingError in underlyingErrorsArray) { + if ([underlyingError isKindOfClass:[NSDictionary class]]) { + NSDictionary *underlyingErrorDictionary = (NSDictionary *)underlyingError; + NSString *reason = underlyingErrorDictionary[kReasonKey]; + if ([reason hasPrefix:kInvalidKeyReasonValue]) { + return [FIRAuthErrorUtils invalidAPIKeyError]; + } + if ([reason isEqualToString:kAppNotAuthorizedReasonValue]) { + return [FIRAuthErrorUtils appNotAuthorizedError]; + } + } + } + } + } + return nil; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/FIRAuthRPCRequest.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/FIRAuthRPCRequest.h new file mode 100644 index 0000000..9ca4f44 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/FIRAuthRPCRequest.h @@ -0,0 +1,57 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FIRAuthRequestConfiguration; + +NS_ASSUME_NONNULL_BEGIN + +/** @protocol FIRAuthRPCRequest + @brief The generic interface for an RPC request needed by @c FIRAuthBackend. + */ +@protocol FIRAuthRPCRequest + +/** @fn requestURL + @brief Gets the request's full URL. + */ +- (NSURL *)requestURL; + +@optional + +/** @fn containsPostBody + @brief Returns whether the request contains a post body or not. Requests without a post body + are get requests. + @remarks The default implementation returns YES. + */ +- (BOOL)containsPostBody; + +/** @fn UnencodedHTTPRequestBodyWithError: + @brief Creates unencoded HTTP body representing the request. + @param error An out field for an error which occurred constructing the request. + @return The HTTP body data representing the request before any encoding, or nil for error. + */ +- (nullable id)unencodedHTTPRequestBodyWithError:(NSError *_Nullable *_Nullable)error; + +/** @fn requestConfiguration + @brief Obtains the request configurations if available. + @return Returns the request configurations. + */ +- (FIRAuthRequestConfiguration *)requestConfiguration; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/FIRAuthRPCResponse.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/FIRAuthRPCResponse.h new file mode 100644 index 0000000..2b26161 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/FIRAuthRPCResponse.h @@ -0,0 +1,49 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** @protocol FIRAuthRPCResponse + @brief The generic interface for an RPC response needed by @c FIRAuthBackend. + */ +@protocol FIRAuthRPCResponse + +/** @fn setFieldsWithDictionary:error: + @brief Sets the response instance from the decoded JSON response. + @param dictionary The dictionary decoded from HTTP JSON response. + @param error An out field for an error which occurred constructing the request. + @return Whether the operation was successful or not. + */ +- (BOOL)setWithDictionary:(NSDictionary *)dictionary + error:(NSError *_Nullable *_Nullable)error; + +@optional + +/** @fn clientErrorWithshortErrorMessage:detailErrorMessage + @brief This optional method allows response classes to create client errors given a short error + message and a detail error message from the server. + @param shortErrorMessage The short error message from the server. + @param detailErrorMessage The detailed error message from the server. + @return A client error, if any. + */ +- (nullable NSError *)clientErrorWithShortErrorMessage:(NSString *)shortErrorMessage + detailErrorMessage:(nullable NSString *)detailErrorMessage; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/FIRAuthRequestConfiguration.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/FIRAuthRequestConfiguration.h new file mode 100644 index 0000000..91e8e71 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/FIRAuthRequestConfiguration.h @@ -0,0 +1,52 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRAuthRequestConfiguration + @brief Defines configurations to be added to a request to Firebase Auth's backend. + */ +@interface FIRAuthRequestConfiguration : NSObject + +/** @property APIKey + @brief The Firebase Auth API key used in the request. + */ +@property(nonatomic, copy, readonly) NSString *APIKey; + +/** @property LanguageCode + @brief The language code used in the request. + */ +@property(nonatomic, copy, nullable) NSString *languageCode; + +/** @property additionalFrameworkMarker + @brief Additional framework marker that will be added as part of the header of every request. + */ +@property(nonatomic, copy, nullable) NSString *additionalFrameworkMarker; + +- (instancetype)init NS_UNAVAILABLE; + +/** @fn initWithRequestClass:APIKey:authLanguage: + @brief Designated initializer. + @param APIKey The API key to be used in the request. + */ +- (nullable instancetype)initWithAPIKey:(NSString *)APIKey NS_DESIGNATED_INITIALIZER; +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/FIRAuthRequestConfiguration.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/FIRAuthRequestConfiguration.m new file mode 100644 index 0000000..a4ee5dd --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/FIRAuthRequestConfiguration.m @@ -0,0 +1,34 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRAuthRequestConfiguration.h" +#import "FIRAuthExceptionUtils.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRAuthRequestConfiguration + +- (nullable instancetype)initWithAPIKey:(NSString *)APIKey { + self = [super init]; + if (self) { + _APIKey = [APIKey copy]; + } + return self; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/FIRIdentityToolkitRequest.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/FIRIdentityToolkitRequest.h new file mode 100644 index 0000000..b5ca726 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/FIRIdentityToolkitRequest.h @@ -0,0 +1,66 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRequestConfiguration.h" + +@class FIRAuthRequestConfiguration; + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRIdentityToolkitRequest + @brief Represents a request to an identity toolkit endpoint. + */ +@interface FIRIdentityToolkitRequest : NSObject + +/** @property endpoint + @brief Gets the RPC's endpoint. + */ +@property(nonatomic, copy, readonly) NSString *endpoint; + +/** @property APIKey + @brief Gets the client's API key used for the request. + */ +@property(nonatomic, copy, readonly) NSString *APIKey; + +/** @fn init + @brief Please use initWithEndpoint:APIKey: + */ +- (instancetype)init NS_UNAVAILABLE; + +/** @fn initWithEndpoint:APIKey: + @brief Designated initializer. + @param endpoint The endpoint name. + @param requestConfiguration An object containing configurations to be added to the request. + */ +- (nullable instancetype)initWithEndpoint:(NSString *)endpoint + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_DESIGNATED_INITIALIZER; + +/** @fn requestURL + @brief Gets the request's full URL. + */ +- (NSURL *)requestURL; + +/** @fn requestConfiguration + @brief Gets the request's configuration. + */ +- (FIRAuthRequestConfiguration *)requestConfiguration; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/FIRIdentityToolkitRequest.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/FIRIdentityToolkitRequest.m new file mode 100644 index 0000000..a1ab482 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/FIRIdentityToolkitRequest.m @@ -0,0 +1,72 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRIdentityToolkitRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kAPIURLFormat + @brief URL format for server API calls. + */ +static NSString *const kAPIURLFormat = @"https://%@/identitytoolkit/v3/relyingparty/%@?key=%@"; + +/** @var gAPIHost + @brief Host for server API calls. + */ +static NSString *gAPIHost = @"www.googleapis.com"; + +@implementation FIRIdentityToolkitRequest { + FIRAuthRequestConfiguration *_requestConfiguration; +} + +- (nullable instancetype)initWithEndpoint:(NSString *)endpoint + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration { + self = [super init]; + if (self) { + _endpoint = [endpoint copy]; + _APIKey = [requestConfiguration.APIKey copy]; + _requestConfiguration = requestConfiguration; + } + return self; +} + +- (BOOL)containsPostBody { + return YES; +} + +- (NSURL *)requestURL { + NSString *URLString = [NSString stringWithFormat:kAPIURLFormat, gAPIHost, _endpoint, _APIKey]; + NSURL *URL = [NSURL URLWithString:URLString]; + return URL; +} + +- (FIRAuthRequestConfiguration *)requestConfiguration { + return _requestConfiguration; +} + +#pragma mark - Internal API for development + ++ (NSString *)host { + return gAPIHost; +} + ++ (void)setHost:(NSString *)host { + gAPIHost = host; +} + +NS_ASSUME_NONNULL_END + +@end diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRCreateAuthURIRequest.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRCreateAuthURIRequest.h new file mode 100644 index 0000000..a4fb6f5 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRCreateAuthURIRequest.h @@ -0,0 +1,88 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCRequest.h" +#import "FIRIdentityToolkitRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRCreateAuthURIRequest + @brief Represents the parameters for the createAuthUri endpoint. + @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/createAuthUri + */ +@interface FIRCreateAuthURIRequest : FIRIdentityToolkitRequest + +/** @property identifier + @brief The email or federated ID of the user. + */ +@property(nonatomic, copy) NSString *identifier; + +/** @property continueURI + @brief The URI to which the IDP redirects the user after the federated login flow. + */ +@property(nonatomic, copy) NSString *continueURI; + +/** @property openIDRealm + @brief Optional realm for OpenID protocol. The sub string "scheme://domain:port" of the param + "continueUri" is used if this is not set. + */ +@property(nonatomic, copy, nullable) NSString *openIDRealm; + +/** @property providerID + @brief The IdP ID. For white listed IdPs it's a short domain name e.g. google.com, aol.com, + live.net and yahoo.com. For other OpenID IdPs it's the OP identifier. + */ +@property(nonatomic, copy, nullable) NSString *providerID; + +/** @property clientID + @brief The relying party OAuth client ID. + */ +@property(nonatomic, copy, nullable) NSString *clientID; + +/** @property context + @brief The opaque value used by the client to maintain context info between the authentication + request and the IDP callback. + */ +@property(nonatomic, copy, nullable) NSString *context; + +/** @property appID + @brief The iOS client application's bundle identifier. + */ +@property(nonatomic, copy, nullable) NSString *appID; + +/** @fn initWithEndpoint:requestConfiguration:requestConfiguration. + @brief Please use initWithIdentifier:continueURI:requestConfiguration: instead. + */ +- (nullable instancetype)initWithEndpoint:(NSString *)endpoint + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_UNAVAILABLE; + +/** @fn initWithIdentifier:continueURI:requestConfiguration: + @brief Designated initializer. + @param identifier The email or federated ID of the user. + @param continueURI The URI to which the IDP redirects the user after the federated login flow. + @param requestConfiguration An object containing configurations to be added to the request. + */ +- (nullable instancetype)initWithIdentifier:(NSString *)identifier + continueURI:(NSString *)continueURI + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRCreateAuthURIRequest.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRCreateAuthURIRequest.m new file mode 100644 index 0000000..de97d4d --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRCreateAuthURIRequest.m @@ -0,0 +1,99 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRCreateAuthURIRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kCreateAuthURIEndpoint + @brief The "createAuthUri" endpoint. + */ +static NSString *const kCreateAuthURIEndpoint = @"createAuthUri"; + +/** @var kProviderIDKey + @brief The key for the "providerId" value in the request. + */ +static NSString *const kProviderIDKey = @"providerId"; + +/** @var kIdentifierKey + @brief The key for the "identifier" value in the request. + */ +static NSString *const kIdentifierKey = @"identifier"; + +/** @var kContinueURIKey + @brief The key for the "continueUri" value in the request. + */ +static NSString *const kContinueURIKey = @"continueUri"; + +/** @var kOpenIDRealmKey + @brief The key for the "openidRealm" value in the request. + */ +static NSString *const kOpenIDRealmKey = @"openidRealm"; + +/** @var kClientIDKey + @brief The key for the "clientId" value in the request. + */ +static NSString *const kClientIDKey = @"clientId"; + +/** @var kContextKey + @brief The key for the "context" value in the request. + */ +static NSString *const kContextKey = @"context"; + +/** @var kAppIDKey + @brief The key for the "appId" value in the request. + */ +static NSString *const kAppIDKey = @"appId"; + +@implementation FIRCreateAuthURIRequest + +- (nullable instancetype)initWithIdentifier:(NSString *)identifier + continueURI:(NSString *)continueURI + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration { + self = [super initWithEndpoint:kCreateAuthURIEndpoint requestConfiguration:requestConfiguration]; + if (self) { + _identifier = [identifier copy]; + _continueURI = [continueURI copy]; + } + return self; +} + +- (nullable id)unencodedHTTPRequestBodyWithError:(NSError *_Nullable *_Nullable)error { + NSMutableDictionary *postBody = [@{ + kIdentifierKey : _identifier, + kContinueURIKey : _continueURI + } mutableCopy]; + if (_providerID) { + postBody[kProviderIDKey] = _providerID; + } + if (_openIDRealm) { + postBody[kOpenIDRealmKey] = _openIDRealm; + } + if (_clientID) { + postBody[kClientIDKey] = _clientID; + } + if (_context) { + postBody[kContextKey] = _context; + } + if (_appID) { + postBody[kAppIDKey] = _appID; + } + return postBody; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRCreateAuthURIResponse.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRCreateAuthURIResponse.h new file mode 100644 index 0000000..8e8f7b0 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRCreateAuthURIResponse.h @@ -0,0 +1,61 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRCreateAuthURIResponse + @brief Represents the parameters for the createAuthUri endpoint. + @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/createAuthUri + */ +@interface FIRCreateAuthURIResponse : NSObject + +/** @property authUri + @brief The URI used by the IDP to authenticate the user. + */ +@property(nonatomic, strong, readonly, nullable) NSString *authURI; + +/** @property registered + @brief Whether the user is registered if the identifier is an email. + */ +@property(nonatomic, assign, readonly) BOOL registered; + +/** @property providerId + @brief The provider ID of the auth URI. + */ +@property(nonatomic, strong, readonly, nullable) NSString *providerID; + +/** @property forExistingProvider + @brief True if the authUri is for user's existing provider. + */ +@property(nonatomic, assign, readonly) BOOL forExistingProvider; + +/** @property allProviders + @brief A list of provider IDs the passed @c identifier could use to sign in with. + */ +@property(nonatomic, copy, readonly, nullable) NSArray *allProviders; + +/** @property signinMethods + @brief A list of sign-in methods available for the passed @c identifier. + */ +@property(nonatomic, copy, readonly, nullable) NSArray *signinMethods; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRCreateAuthURIResponse.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRCreateAuthURIResponse.m new file mode 100644 index 0000000..474582e --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRCreateAuthURIResponse.m @@ -0,0 +1,36 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRCreateAuthURIResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRCreateAuthURIResponse + +- (BOOL)setWithDictionary:(NSDictionary *)dictionary + error:(NSError *_Nullable *_Nullable)error { + _providerID = [dictionary[@"providerId"] copy]; + _authURI = [dictionary[@"authUri"] copy]; + _registered = [dictionary[@"registered"] boolValue]; + _forExistingProvider = [dictionary[@"forExistingProvider"] boolValue]; + _allProviders = [dictionary[@"allProviders"] copy]; + _signinMethods = [dictionary[@"signinMethods"] copy]; + return YES; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRDeleteAccountRequest.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRDeleteAccountRequest.h new file mode 100644 index 0000000..89a7d26 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRDeleteAccountRequest.h @@ -0,0 +1,50 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCRequest.h" +#import "FIRIdentityToolkitRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRDeleteAccountRequest + @brief Represents the parameters for the deleteAccount endpoint. + @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/deleteAccount + */ +@interface FIRDeleteAccountRequest : FIRIdentityToolkitRequest + +/** @fn initWithEndpoint:requestConfiguration:requestConfiguration. + @brief Please use initWitLocalID:accessToken:requestConfiguration instead. + */ +- (nullable instancetype)initWithEndpoint:(NSString *)endpoint + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_UNAVAILABLE; + +/** @fn initWitLocalID:accessToken:requestConfiguration. + @brief Designated initializer. + @param localID The local ID. + @param accessToken The access token. + @param requestConfiguration An object containing configurations to be added to the request. + */ +- (nullable instancetype)initWitLocalID:(NSString *)localID + accessToken:(NSString *)accessToken + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRDeleteAccountRequest.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRDeleteAccountRequest.m new file mode 100644 index 0000000..701d446 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRDeleteAccountRequest.m @@ -0,0 +1,69 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRDeleteAccountRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kCreateAuthURIEndpoint + @brief The "deleteAccount" endpoint. + */ +static NSString *const kDeleteAccountEndpoint = @"deleteAccount"; + +/** @var kIDTokenKey + @brief The key for the "idToken" value in the request. This is actually the STS Access Token, + despite it's confusing (backwards compatiable) parameter name. + */ +static NSString *const kIDTokenKey = @"idToken"; + +/** @var kLocalIDKey + @brief The key for the "localID" value in the request. + */ +static NSString *const kLocalIDKey = @"localId"; + +@implementation FIRDeleteAccountRequest { + /** @var _accessToken + @brief The STS Access Token of the authenticated user. + */ + NSString *_accessToken; + + /** @var _localID + @brief The localID of the user. + */ + NSString *_localID; +} + +- (nullable instancetype)initWitLocalID:(NSString *)localID + accessToken:(NSString *)accessToken + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration { + self = [super initWithEndpoint:kDeleteAccountEndpoint requestConfiguration:requestConfiguration]; + if (self) { + _localID = [localID copy]; + _accessToken = [accessToken copy]; + } + return self; +} + +- (nullable id)unencodedHTTPRequestBodyWithError:(NSError *_Nullable *_Nullable)error { + NSMutableDictionary *postBody = [NSMutableDictionary dictionary]; + postBody[kIDTokenKey] = _accessToken; + postBody[kLocalIDKey] = _localID; + return postBody; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRDeleteAccountResponse.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRDeleteAccountResponse.h new file mode 100644 index 0000000..cf09f94 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRDeleteAccountResponse.h @@ -0,0 +1,30 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRDeleteAccountResponse + @brief Represents the response from the deleteAccount endpoint. + @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/deleteAccount + */ +@interface FIRDeleteAccountResponse : NSObject +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRDeleteAccountResponse.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRDeleteAccountResponse.m new file mode 100644 index 0000000..d75d2eb --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRDeleteAccountResponse.m @@ -0,0 +1,30 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRDeleteAccountResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRDeleteAccountResponse + +- (BOOL)setWithDictionary:(NSDictionary *)dictionary + error:(NSError *_Nullable *_Nullable)error { + return YES; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIREmailLinkSignInRequest.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIREmailLinkSignInRequest.h new file mode 100644 index 0000000..e1b10d8 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIREmailLinkSignInRequest.h @@ -0,0 +1,66 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCRequest.h" +#import "FIRIdentityToolkitRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIREmailLinkSignInRequest + @brief Represents the parameters for the emailLinkSignin endpoint. + */ +@interface FIREmailLinkSignInRequest : FIRIdentityToolkitRequest + +#pragma mark - Components of "postBody" + +/** @property email + @brief The email identifier used to complete the email link sign-in. + */ +@property(nonatomic, copy, readonly) NSString *email; + +/** @property oobCode + @brief The OOB code used to complete the email link sign-in flow. + */ +@property(nonatomic, copy, readonly) NSString *oobCode; + +/** @property idToken + @brief The ID Token code potentially used to complete the email link sign-in flow. + */ +@property(nonatomic, copy) NSString *IDToken; + +/** @fn initWithEndpoint:requestConfiguration: + @brief Please use initWithProviderID:requestConfifuration instead. + */ +- (instancetype)initWithEndpoint:(NSString *)endpoint + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration NS_UNAVAILABLE; + +/** @fn initWithProviderID:requestConfifuration + @brief Designated initializer. + @param email The email identifier used to complete hte email link sign-in flow. + @param oobCode The OOB code used to complete the email link sign-in flow. + @param requestConfiguration An object containing configurations to be added to the request. + + */ +- (instancetype)initWithEmail:(NSString *)email + oobCode:(NSString *)oobCode + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIREmailLinkSignInRequest.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIREmailLinkSignInRequest.m new file mode 100644 index 0000000..2750f9f --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIREmailLinkSignInRequest.m @@ -0,0 +1,74 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIREmailLinkSignInRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kEmailLinkSigninEndpoint + @brief The "EmailLinkSignin" endpoint. + */ +static NSString *const kEmailLinkSigninEndpoint = @"emailLinkSignin"; + +/** @var kEmailKey + @brief The key for the "identifier" value in the request. + */ +static NSString *const kEmailKey = @"email"; + +/** @var kEmailLinkKey + @brief The key for the "emailLink" value in the request. + */ +static NSString *const kOOBCodeKey = @"oobCode"; + +/** @var kIDTokenKey + @brief The key for the "IDToken" value in the request. + */ +static NSString *const kIDTokenKey = @"idToken"; + +/** @var kPostBodyKey + @brief The key for the "postBody" value in the request. + */ +static NSString *const kPostBodyKey = @"postBody"; + +@implementation FIREmailLinkSignInRequest + +- (instancetype)initWithEmail:(NSString *)email + oobCode:(NSString *)oobCode + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration { + self = [super initWithEndpoint:kEmailLinkSigninEndpoint + requestConfiguration:requestConfiguration]; + if (self) { + _email = email; + _oobCode = oobCode; + } + return self; +} + +- (nullable id)unencodedHTTPRequestBodyWithError:(NSError *_Nullable *_Nullable)error { + NSMutableDictionary *postBody = [@{ + kEmailKey : _email, + kOOBCodeKey : _oobCode, + } mutableCopy]; + + if (_IDToken) { + postBody[kIDTokenKey] = _IDToken; + } + return postBody; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIREmailLinkSignInResponse.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIREmailLinkSignInResponse.h new file mode 100644 index 0000000..df0a127 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIREmailLinkSignInResponse.h @@ -0,0 +1,54 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#import + +#import "FIRAuthRPCResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRVerifyAssertionResponse + @brief Represents the response from the emailLinkSignin endpoint. + */ +@interface FIREmailLinkSignInResponse : NSObject + +/** @property IDToken + @brief The ID token in the email link sign-in response. + */ +@property(nonatomic, copy, readonly) NSString *IDToken; + +/** @property email + @brief The email returned by the IdP. + */ +@property(nonatomic, strong, readonly, nullable) NSString *email; + +/** @property refreshToken + @brief The refreshToken returned by the server. + */ +@property(nonatomic, strong, readonly, nullable) NSString *refreshToken; + +/** @property approximateExpirationDate + @brief The approximate expiration date of the access token. + */ +@property(nonatomic, copy, readonly, nullable) NSDate *approximateExpirationDate; + +/** @property isNewUser + @brief Flag indicating that the user signing in is a new user and not a returning user. + */ +@property(nonatomic, assign) BOOL isNewUser; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIREmailLinkSignInResponse.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIREmailLinkSignInResponse.m new file mode 100644 index 0000000..f58cab5 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIREmailLinkSignInResponse.m @@ -0,0 +1,36 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIREmailLinkSignInResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIREmailLinkSignInResponse + +- (BOOL)setWithDictionary:(NSDictionary *)dictionary + error:(NSError *_Nullable *_Nullable)error { + _email = [dictionary[@"email"] copy]; + _IDToken = [dictionary[@"idToken"] copy]; + _isNewUser = [dictionary[@"isNewUser"] boolValue]; + _refreshToken = [dictionary[@"refreshToken"] copy]; + _approximateExpirationDate = [dictionary[@"expiresIn"] isKindOfClass:[NSString class]] ? + [NSDate dateWithTimeIntervalSinceNow:[dictionary[@"expiresIn"] doubleValue]] : nil; + return YES; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetAccountInfoRequest.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetAccountInfoRequest.h new file mode 100644 index 0000000..a5a8a20 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetAccountInfoRequest.h @@ -0,0 +1,53 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCRequest.h" +#import "FIRIdentityToolkitRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRGetAccountInfoRequest + @brief Represents the parameters for the getAccountInfo endpoint. + @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/getAccountInfo + */ +@interface FIRGetAccountInfoRequest : FIRIdentityToolkitRequest + +/** @property accessToken + @brief The STS Access Token for the authenticated user. + */ +@property(nonatomic, copy) NSString *accessToken; + +/** @fn initWithEndpoint:requestConfiguration:requestConfiguration + @brief Please use initWithAccessToken:requestConfiguration: instead. + */ +- (nullable instancetype)initWithEndpoint:(NSString *)endpoint + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_UNAVAILABLE; + +/** @fn initWithAccessToken:requestConfiguration + @brief Designated initializer. + @param accessToken The Access Token of the authenticated user. + @param requestConfiguration An object containing configurations to be added to the request. + */ +- (nullable instancetype)initWithAccessToken:(NSString *)accessToken + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetAccountInfoRequest.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetAccountInfoRequest.m new file mode 100644 index 0000000..e707937 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetAccountInfoRequest.m @@ -0,0 +1,52 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRGetAccountInfoRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kGetAccountInfoEndpoint + @brief The "getAccountInfo" endpoint. + */ +static NSString *const kGetAccountInfoEndpoint = @"getAccountInfo"; + +/** @var kIDTokenKey + @brief The key for the "idToken" value in the request. This is actually the STS Access Token, + despite it's confusing (backwards compatiable) parameter name. + */ +static NSString *const kIDTokenKey = @"idToken"; + +@implementation FIRGetAccountInfoRequest + +- (nullable instancetype)initWithAccessToken:(NSString *)accessToken + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration { + self = [super initWithEndpoint:kGetAccountInfoEndpoint + requestConfiguration:requestConfiguration]; + if (self) { + _accessToken = [accessToken copy]; + } + return self; +} + +- (nullable id)unencodedHTTPRequestBodyWithError:(NSError *_Nullable *_Nullable)error { + return @{ + kIDTokenKey : _accessToken + }; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetAccountInfoResponse.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetAccountInfoResponse.h new file mode 100644 index 0000000..6c30dbe --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetAccountInfoResponse.h @@ -0,0 +1,156 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRGetAccountInfoResponseProviderUserInfo + @brief Represents the provider user info part of the response from the getAccountInfo endpoint. + @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/getAccountInfo + */ +@interface FIRGetAccountInfoResponseProviderUserInfo : NSObject + +/** @property providerID + @brief The ID of the identity provider. + */ +@property(nonatomic, strong, readonly, nullable) NSString *providerID; + +/** @property displayName + @brief The user's display name at the identity provider. + */ +@property(nonatomic, strong, readonly, nullable) NSString *displayName; + +/** @property photoURL + @brief The user's photo URL at the identity provider. + */ +@property(nonatomic, strong, readonly, nullable) NSURL *photoURL; + +/** @property federatedID + @brief The user's identifier at the identity provider. + */ +@property(nonatomic, strong, readonly, nullable) NSString *federatedID; + +/** @property email + @brief The user's email at the identity provider. + */ +@property(nonatomic, strong, readonly, nullable) NSString *email; + +/** @property phoneNumber + @brief A phone number associated with the user. + */ +@property(nonatomic, readonly, nullable) NSString *phoneNumber; + +/** @fn init + @brief Please use initWithDictionary: + */ +- (instancetype)init NS_UNAVAILABLE; + +/** @fn initWithAPIKey: + @brief Designated initializer. + @param dictionary The provider user info data from endpoint. + */ +- (instancetype)initWithDictionary:(NSDictionary *)dictionary NS_DESIGNATED_INITIALIZER; + +@end + +/** @class FIRGetAccountInfoResponseUser + @brief Represents the firebase user info part of the response from the getAccountInfo endpoint. + @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/getAccountInfo + */ +@interface FIRGetAccountInfoResponseUser : NSObject + +/** @property localID + @brief The ID of the user. + */ +@property(nonatomic, strong, readonly, nullable) NSString *localID; + +/** @property email + @brief The email or the user. + */ +@property(nonatomic, strong, readonly, nullable) NSString *email; + +/** @property emailVerified + @brief Whether the email has been verified. + */ +@property(nonatomic, assign, readonly) BOOL emailVerified; + +/** @property displayName + @brief The display name of the user. + */ +@property(nonatomic, strong, readonly, nullable) NSString *displayName; + +/** @property photoURL + @brief The user's photo URL. + */ +@property(nonatomic, strong, readonly, nullable) NSURL *photoURL; + +/** @property creationDate + @brief The user's creation date. + */ +@property(nonatomic, strong, readonly, nullable) NSDate *creationDate; + +/** @property lastSignInDate + @brief The user's last login date. + */ +@property(nonatomic, strong, readonly, nullable) NSDate *lastLoginDate; + +/** @property providerUserInfo + @brief The user's profiles at the associated identity providers. + */ +@property(nonatomic, strong, readonly, nullable) + NSArray *providerUserInfo; + +/** @property passwordHash + @brief Information about user's password. + @remarks This is not necessarily the hash of user's actual password. + */ +@property(nonatomic, strong, readonly, nullable) NSString *passwordHash; + +/** @property phoneNumber + @brief A phone number associated with the user. + */ +@property(nonatomic, readonly, nullable) NSString *phoneNumber; + +/** @fn init + @brief Please use initWithDictionary: + */ +- (instancetype)init NS_UNAVAILABLE; + +/** @fn initWithAPIKey: + @brief Designated initializer. + @param dictionary The provider user info data from endpoint. + */ +- (instancetype)initWithDictionary:(NSDictionary *)dictionary NS_DESIGNATED_INITIALIZER; + +@end + +/** @class FIRGetAccountInfoResponse + @brief Represents the response from the setAccountInfo endpoint. + @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/getAccountInfo + */ +@interface FIRGetAccountInfoResponse : NSObject + +/** @property providerUserInfo + @brief The requested users' profiles. + */ +@property(nonatomic, strong, readonly, nullable) NSArray *users; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetAccountInfoResponse.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetAccountInfoResponse.m new file mode 100644 index 0000000..cb78b78 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetAccountInfoResponse.m @@ -0,0 +1,108 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRGetAccountInfoResponse.h" + +#import "FIRAuthErrorUtils.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kErrorKey + @brief The key for the "error" value in JSON responses from the server. + */ +static NSString *const kErrorKey = @"error"; + +@implementation FIRGetAccountInfoResponseProviderUserInfo + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary { + self = [super init]; + if (self) { + _providerID = [dictionary[@"providerId"] copy]; + _displayName = [dictionary[@"displayName"] copy]; + NSString *photoURL = dictionary[@"photoUrl"]; + if (photoURL) { + _photoURL = [NSURL URLWithString:photoURL]; + } + _federatedID = [dictionary[@"federatedId"] copy]; + _email = [dictionary[@"email"] copy]; + _phoneNumber = [dictionary[@"phoneNumber"] copy]; + } + return self; +} + +@end + +@implementation FIRGetAccountInfoResponseUser + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary { + self = [super init]; + if (self) { + NSArray *providerUserInfoData = dictionary[@"providerUserInfo"]; + if (providerUserInfoData) { + NSMutableArray *providerUserInfoArray = + [NSMutableArray arrayWithCapacity:providerUserInfoData.count]; + for (NSDictionary *dictionary in providerUserInfoData) { + [providerUserInfoArray addObject: + [[FIRGetAccountInfoResponseProviderUserInfo alloc] initWithDictionary:dictionary]]; + } + _providerUserInfo = [providerUserInfoArray copy]; + } + _localID = [dictionary[@"localId"] copy]; + _displayName = [dictionary[@"displayName"] copy]; + _email = [dictionary[@"email"] copy]; + NSString *photoURL = dictionary[@"photoUrl"]; + if (photoURL) { + _photoURL = [NSURL URLWithString:photoURL]; + } + if ([dictionary[@"createdAt"] isKindOfClass:[NSString class]]) { + // Divide by 1000 in order to convert miliseconds to seconds. + NSTimeInterval creationDateTimeInterval = [dictionary[@"createdAt"] doubleValue] / 1000; + _creationDate = [NSDate dateWithTimeIntervalSince1970:creationDateTimeInterval]; + } + if ([dictionary[@"lastLoginAt"] isKindOfClass:[NSString class]]) { + // Divide by 1000 in order to convert miliseconds to seconds + NSTimeInterval creationDateTimeInterval = [dictionary[@"lastLoginAt"] doubleValue] / 1000; + _lastLoginDate = [NSDate dateWithTimeIntervalSince1970:creationDateTimeInterval]; + } + _emailVerified = [dictionary[@"emailVerified"] boolValue]; + _passwordHash = [dictionary[@"passwordHash"] copy]; + _phoneNumber = [dictionary[@"phoneNumber"] copy]; + } + return self; +} + +@end + +@implementation FIRGetAccountInfoResponse + +- (BOOL)setWithDictionary:(NSDictionary *)dictionary + error:(NSError *_Nullable *_Nullable)error { + NSArray *usersData = dictionary[@"users"]; + // The client side never sends a getAccountInfo request with multiple localID, so only one user + // data is expected in the response. + if (![usersData isKindOfClass:[NSArray class]] || usersData.count != 1) { + if (error) { + *error = [FIRAuthErrorUtils unexpectedResponseWithDeserializedResponse:dictionary]; + } + return NO; + } + _users = @[ [[FIRGetAccountInfoResponseUser alloc] initWithDictionary:usersData.firstObject] ]; + return YES; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetOOBConfirmationCodeRequest.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetOOBConfirmationCodeRequest.h new file mode 100644 index 0000000..b74f2f8 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetOOBConfirmationCodeRequest.h @@ -0,0 +1,180 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCRequest.h" +#import "FIRIdentityToolkitRequest.h" + +@class FIRActionCodeSettings; + +NS_ASSUME_NONNULL_BEGIN + +/** @enum FIRGetOOBConfirmationCodeRequestType + @brief Types of OOB Confirmation Code requests. + */ +typedef NS_ENUM(NSInteger, FIRGetOOBConfirmationCodeRequestType) { + /** @var FIRGetOOBConfirmationCodeRequestTypePasswordReset + @brief Requests a password reset code. + */ + FIRGetOOBConfirmationCodeRequestTypePasswordReset, + + /** @var FIRGetOOBConfirmationCodeRequestTypeVerifyEmail + @brief Requests an email verification code. + */ + FIRGetOOBConfirmationCodeRequestTypeVerifyEmail, + + /** @var FIRGetOOBConfirmationCodeRequestTypeEmailLink + @brief Requests an email sign-in link. + */ + FIRGetOOBConfirmationCodeRequestTypeEmailLink, + + /** @var FIRGetOOBConfirmationCodeRequestTypeVerifyBeforeUpdateEmail + @brief Requests an verify before update email. + */ + FIRGetOOBConfirmationCodeRequestTypeVerifyBeforeUpdateEmail, +}; + +/** @enum FIRGetOOBConfirmationCodeRequest + @brief Represents the parameters for the getOOBConfirmationCode endpoint. + */ +@interface FIRGetOOBConfirmationCodeRequest : FIRIdentityToolkitRequest + +/** @property requestType + @brief The types of OOB Confirmation Code to request. + */ +@property(nonatomic, assign, readonly) FIRGetOOBConfirmationCodeRequestType requestType; + +/** @property email + @brief The email of the user. + @remarks For password reset. + */ +@property(nonatomic, copy, nullable, readonly) NSString *email; + +/** @property updatedEmail + @brief The new email to be updated. + @remarks For verifyBeforeUpdateEmail. + */ +@property(nonatomic, copy, nullable, readonly) NSString *updatedEmail; + +/** @property accessToken + @brief The STS Access Token of the authenticated user. + @remarks For email change. + */ +@property(nonatomic, copy, nullable, readonly) NSString *accessToken; + +/** @property continueURL + @brief This URL represents the state/Continue URL in the form of a universal link. + */ +@property(nonatomic, copy, nullable, readonly) NSString *continueURL; + +/** @property iOSBundleID + @brief The iOS bundle Identifier, if available. + */ +@property(nonatomic, copy, nullable, readonly) NSString *iOSBundleID; + +/** @property androidPackageName + @brief The Android package name, if available. + */ +@property(nonatomic, copy, nullable, readonly) NSString *androidPackageName; + +/** @property androidMinimumVersion + @brief The minimum Android version supported, if available. + */ +@property(nonatomic, copy, nullable, readonly) NSString *androidMinimumVersion; + +/** @property androidInstallIfNotAvailable + @brief Indicates whether or not the Android app should be installed if not already available. + */ +@property(nonatomic, assign, readonly) BOOL androidInstallApp; + +/** @property handleCodeInApp + @brief Indicates whether the action code link will open the app directly or after being + redirected from a Firebase owned web widget. + */ +@property(assign, nonatomic) BOOL handleCodeInApp; + +/** @property dynamicLinkDomain + @brief The Firebase Dynamic Link domain used for out of band code flow. + */ +@property(copy, nonatomic, nullable) NSString *dynamicLinkDomain; + + +/** @fn passwordResetRequestWithEmail:actionCodeSettings:requestConfiguration: + @brief Creates a password reset request. + @param email The user's email address. + @param actionCodeSettings An object of FIRActionCodeSettings which specifies action code + settings to be applied to the password reset request. + @param requestConfiguration An object containing configurations to be added to the request. + @return A password reset request. + */ ++ (nullable FIRGetOOBConfirmationCodeRequest *) + passwordResetRequestWithEmail:(NSString *)email + actionCodeSettings:(nullable FIRActionCodeSettings *)actionCodeSettings + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration; + +/** @fn verifyEmailRequestWithAccessToken:actionCodeSettings:requestConfiguration: + @brief Creates a password reset request. + @param accessToken The user's STS Access Token. + @param actionCodeSettings An object of FIRActionCodeSettings which specifies action code + settings to be applied to the email verification request. + @param requestConfiguration An object containing configurations to be added to the request. + @return A password reset request. + */ ++ (nullable FIRGetOOBConfirmationCodeRequest *) + verifyEmailRequestWithAccessToken:(NSString *)accessToken + actionCodeSettings:(nullable FIRActionCodeSettings *)actionCodeSettings + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration; + +/** @fn signInWithEmailLinkRequest:actionCodeSettings:requestConfiguration: + @brief Creates a sign-in with email link. + @param email The user's email address. + @param actionCodeSettings An object of FIRActionCodeSettings which specifies action code + settings to be applied to the email sign-in link. + @param requestConfiguration An object containing configurations to be added to the request. + @return An email sign-in link request. + */ ++ (nullable FIRGetOOBConfirmationCodeRequest *) + signInWithEmailLinkRequest:(NSString *)email + actionCodeSettings:(nullable FIRActionCodeSettings *)actionCodeSettings + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration; + + +/** @fn verifyBeforeUpdateEmailWithAccessToken:newEmail:actionCodeSettings:requestConfiguration: + @brief Creates a verifyBeforeUpdateEmail request. + @param accessToken The user's STS Access Token. + @param newEmail The user's email address to be updated. + @param actionCodeSettings An object of FIRActionCodeSettings which specifies action code + settings to be applied to the password reset request. + @param requestConfiguration An object containing configurations to be added to the request. + @return A verifyBeforeUpdateEmail request. + */ ++ (nullable FIRGetOOBConfirmationCodeRequest *) + verifyBeforeUpdateEmailWithAccessToken:(NSString *)accessToken + newEmail:(NSString *)newEmail + actionCodeSettings:(nullable FIRActionCodeSettings *)actionCodeSettings + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration; + +/** @fn init + @brief Please use a factory method. + */ +- (nullable instancetype)initWithEndpoint:(NSString *)endpoint + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetOOBConfirmationCodeRequest.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetOOBConfirmationCodeRequest.m new file mode 100644 index 0000000..ad6adcb --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetOOBConfirmationCodeRequest.m @@ -0,0 +1,288 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRGetOOBConfirmationCodeRequest.h" + +#import "FIRActionCodeSettings.h" + +#import "FIRAuthErrorUtils.h" +#import "FIRAuth_Internal.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kEndpoint + @brief The getOobConfirmationCode endpoint name. + */ +static NSString *const kGetOobConfirmationCodeEndpoint = @"getOobConfirmationCode"; + +/** @var kRequestTypeKey + @brief The name of the required "requestType" property in the request. + */ +static NSString *const kRequestTypeKey = @"requestType"; + +/** @var kEmailKey + @brief The name of the "email" property in the request. + */ +static NSString *const kEmailKey = @"email"; + +/** @var kNewEmailKey + @brief The name of the "newEmail" property in the request. + */ +static NSString *const kNewEmailKey = @"newEmail"; + +/** @var kIDTokenKey + @brief The key for the "idToken" value in the request. This is actually the STS Access Token, + despite it's confusing (backwards compatiable) parameter name. + */ +static NSString *const kIDTokenKey = @"idToken"; + +/** @var kContinueURLKey + @brief The key for the "continue URL" value in the request. + */ +static NSString *const kContinueURLKey = @"continueUrl"; + +/** @var kIosBundeIDKey + @brief The key for the "iOS Bundle Identifier" value in the request. + */ +static NSString *const kIosBundleIDKey = @"iOSBundleId"; + +/** @var kAndroidPackageNameKey + @brief The key for the "Android Package Name" value in the request. + */ +static NSString *const kAndroidPackageNameKey = @"androidPackageName"; + +/** @var kAndroidInstallAppKey + @brief The key for the request parameter indicating whether the android app should be installed + or not. + */ +static NSString *const kAndroidInstallAppKey = @"androidInstallApp"; + +/** @var kAndroidMinimumVersionKey + @brief The key for the "minimum Android version supported" value in the request. + */ +static NSString *const kAndroidMinimumVersionKey = @"androidMinimumVersion"; + +/** @var kCanHandleCodeInAppKey + @brief The key for the request parameter indicating whether the action code can be handled in + the app or not. + */ +static NSString *const kCanHandleCodeInAppKey = @"canHandleCodeInApp"; + +/** @var kDynamicLinkDomainKey + @brief The key for the "dynamic link domain" value in the request. + */ +static NSString *const kDynamicLinkDomainKey = @"dynamicLinkDomain"; + +/** @var kPasswordResetRequestTypeValue + @brief The value for the "PASSWORD_RESET" request type. + */ +static NSString *const kPasswordResetRequestTypeValue = @"PASSWORD_RESET"; + +/** @var kEmailLinkSignInTypeValue + @brief The value for the "EMAIL_SIGNIN" request type. + */ +static NSString *const kEmailLinkSignInTypeValue= @"EMAIL_SIGNIN"; + +/** @var kVerifyEmailRequestTypeValue + @brief The value for the "VERIFY_EMAIL" request type. + */ +static NSString *const kVerifyEmailRequestTypeValue = @"VERIFY_EMAIL"; + +/** @var kVerifyBeforeUpdateEmailRequestTypeValue + @brief The value for the "VERIFY_AND_CHANGE_EMAIL" request type. + */ +static NSString *const kVerifyBeforeUpdateEmailRequestTypeValue = @"VERIFY_AND_CHANGE_EMAIL"; + +@interface FIRGetOOBConfirmationCodeRequest () + +/** @fn initWithRequestType:email:APIKey: + @brief Designated initializer. + @param requestType The types of OOB Confirmation Code to request. + @param email The email of the user. + @param newEmail The email of the user to be updated. + @param accessToken The STS Access Token of the currently signed in user. + @param actionCodeSettings An object of FIRActionCodeSettings which specifies action code + settings to be applied to the OOB code request. + @param requestConfiguration An object containing configurations to be added to the request. + */ +- (nullable instancetype)initWithRequestType:(FIRGetOOBConfirmationCodeRequestType)requestType + email:(nullable NSString *)email + newEmail:(nullable NSString *)newEmail + accessToken:(nullable NSString *)accessToken + actionCodeSettings:(nullable FIRActionCodeSettings *)actionCodeSettings + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_DESIGNATED_INITIALIZER; + +@end + +@implementation FIRGetOOBConfirmationCodeRequest + +/** @var requestTypeStringValueForRequestType: + @brief Returns the string equivilent for an @c FIRGetOOBConfirmationCodeRequestType value. + */ ++ (NSString *)requestTypeStringValueForRequestType: + (FIRGetOOBConfirmationCodeRequestType)requestType { + switch (requestType) { + case FIRGetOOBConfirmationCodeRequestTypePasswordReset: + return kPasswordResetRequestTypeValue; + case FIRGetOOBConfirmationCodeRequestTypeVerifyEmail: + return kVerifyEmailRequestTypeValue; + case FIRGetOOBConfirmationCodeRequestTypeEmailLink: + return kEmailLinkSignInTypeValue; + case FIRGetOOBConfirmationCodeRequestTypeVerifyBeforeUpdateEmail: + return kVerifyBeforeUpdateEmailRequestTypeValue; + // No default case so that we get a compiler warning if a new value was added to the enum. + } +} + ++ (nullable FIRGetOOBConfirmationCodeRequest *) + passwordResetRequestWithEmail:(NSString *)email + actionCodeSettings:(nullable FIRActionCodeSettings *)actionCodeSettings + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration { + return [[self alloc] initWithRequestType:FIRGetOOBConfirmationCodeRequestTypePasswordReset + email:email + newEmail:nil + accessToken:nil + actionCodeSettings:actionCodeSettings + requestConfiguration:requestConfiguration]; +} + ++ (nullable FIRGetOOBConfirmationCodeRequest *) + verifyEmailRequestWithAccessToken:(NSString *)accessToken + actionCodeSettings:(nullable FIRActionCodeSettings *)actionCodeSettings + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration { + return [[self alloc] initWithRequestType:FIRGetOOBConfirmationCodeRequestTypeVerifyEmail + email:nil + newEmail:nil + accessToken:accessToken + actionCodeSettings:actionCodeSettings + requestConfiguration:requestConfiguration]; +} + ++ (nullable FIRGetOOBConfirmationCodeRequest *) + signInWithEmailLinkRequest:(NSString *)email + actionCodeSettings:(nullable FIRActionCodeSettings *)actionCodeSettings + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration { + return [[self alloc] initWithRequestType:FIRGetOOBConfirmationCodeRequestTypeEmailLink + email:email + newEmail:nil + accessToken:nil + actionCodeSettings:actionCodeSettings + requestConfiguration:requestConfiguration]; +} + ++ (nullable FIRGetOOBConfirmationCodeRequest *) + verifyBeforeUpdateEmailWithAccessToken:(NSString *)accessToken + newEmail:(NSString *)newEmail + actionCodeSettings:(nullable FIRActionCodeSettings *)actionCodeSettings + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration { + return [[self alloc] + initWithRequestType:FIRGetOOBConfirmationCodeRequestTypeVerifyBeforeUpdateEmail + email:nil + newEmail:newEmail + accessToken:accessToken + actionCodeSettings:actionCodeSettings + requestConfiguration:requestConfiguration]; +} + +- (nullable instancetype)initWithRequestType:(FIRGetOOBConfirmationCodeRequestType)requestType + email:(nullable NSString *)email + newEmail:(nullable NSString *)newEmail + accessToken:(nullable NSString *)accessToken + actionCodeSettings:(nullable FIRActionCodeSettings *)actionCodeSettings + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration { + self = [super initWithEndpoint:kGetOobConfirmationCodeEndpoint + requestConfiguration:requestConfiguration]; + if (self) { + _requestType = requestType; + _email = email; + _updatedEmail = newEmail; + _accessToken = accessToken; + _continueURL = actionCodeSettings.URL.absoluteString; + _iOSBundleID = actionCodeSettings.iOSBundleID; + _androidPackageName = actionCodeSettings.androidPackageName; + _androidMinimumVersion = actionCodeSettings.androidMinimumVersion; + _androidInstallApp = actionCodeSettings.androidInstallIfNotAvailable; + _handleCodeInApp = actionCodeSettings.handleCodeInApp; + _dynamicLinkDomain = actionCodeSettings.dynamicLinkDomain; + } + return self; +} + +- (nullable id)unencodedHTTPRequestBodyWithError:(NSError *_Nullable *_Nullable)error { + NSMutableDictionary *body = [@{ + kRequestTypeKey : [[self class] requestTypeStringValueForRequestType:_requestType] + } mutableCopy]; + + // For password reset requests, we only need an email address in addition to the already required + // fields. + if (_requestType == FIRGetOOBConfirmationCodeRequestTypePasswordReset) { + body[kEmailKey] = _email; + } + + // For verify email requests, we only need an STS Access Token in addition to the already required + // fields. + if (_requestType == FIRGetOOBConfirmationCodeRequestTypeVerifyEmail) { + body[kIDTokenKey] = _accessToken; + } + + // For email sign-in link requests, we only need an email address in addition to the already + // required fields. + if (_requestType == FIRGetOOBConfirmationCodeRequestTypeEmailLink) { + body[kEmailKey] = _email; + } + + // For email sign-in link requests, we only need an STS Access Token, a new email address in + // addition to the already required fields. + if (_requestType == FIRGetOOBConfirmationCodeRequestTypeVerifyBeforeUpdateEmail) { + body[kNewEmailKey] = _updatedEmail; + body[kIDTokenKey] = _accessToken; + } + + if (_continueURL) { + body[kContinueURLKey] = _continueURL; + } + + if (_iOSBundleID) { + body[kIosBundleIDKey] = _iOSBundleID; + } + + if (_androidPackageName) { + body[kAndroidPackageNameKey] = _androidPackageName; + } + + if (_androidMinimumVersion) { + body[kAndroidMinimumVersionKey] = _androidMinimumVersion; + } + + if (_androidInstallApp) { + body[kAndroidInstallAppKey] = @YES; + } + + if (_handleCodeInApp) { + body[kCanHandleCodeInAppKey] = @YES; + } + + if (_dynamicLinkDomain) { + body[kDynamicLinkDomainKey] = _dynamicLinkDomain; + } + + return body; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetOOBConfirmationCodeResponse.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetOOBConfirmationCodeResponse.h new file mode 100644 index 0000000..69aa458 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetOOBConfirmationCodeResponse.h @@ -0,0 +1,35 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRGetOOBConfirmationCodeResponse + @brief Represents the response from the getOobConfirmationCode endpoint. + */ +@interface FIRGetOOBConfirmationCodeResponse : NSObject + +/** @property OOBCode + @brief The OOB code returned by the server in some cases. + */ +@property(nonatomic, copy, readonly, nullable) NSString *OOBCode; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetOOBConfirmationCodeResponse.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetOOBConfirmationCodeResponse.m new file mode 100644 index 0000000..0b6c416 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetOOBConfirmationCodeResponse.m @@ -0,0 +1,36 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRGetOOBConfirmationCodeResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kOOBCodeKey + @brief The name of the field in the response JSON for the OOB code. + */ +static NSString *const kOOBCodeKey = @"oobCode"; + +@implementation FIRGetOOBConfirmationCodeResponse + +- (BOOL)setWithDictionary:(NSDictionary *)dictionary + error:(NSError *_Nullable *_Nullable)error { + _OOBCode = [dictionary[kOOBCodeKey] copy]; + return YES; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetProjectConfigRequest.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetProjectConfigRequest.h new file mode 100644 index 0000000..7c37e8d --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetProjectConfigRequest.h @@ -0,0 +1,41 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRIdentityToolkitRequest.h" + +#import "FIRAuthRPCRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRGetProjectConfigRequest : FIRIdentityToolkitRequest + +/** @fn initWithEndpoint:requestConfiguration: + @brief Please use initWithRequestConfiguration: + */ +- (nullable instancetype)initWithEndpoint:(NSString *)endpoint + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_UNAVAILABLE; + +/** @fn initWithTemporaryProof:phoneNumberAPIKey + @brief Designated initializer. + @param requestConfiguration An object containing configurations to be added to the request. + */ +- (nullable instancetype)initWithRequestConfiguration: + (FIRAuthRequestConfiguration *)requestConfiguration NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetProjectConfigRequest.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetProjectConfigRequest.m new file mode 100644 index 0000000..acfcc02 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetProjectConfigRequest.m @@ -0,0 +1,40 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRGetProjectConfigRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kGetProjectConfigEndPoint + @brief The "getProjectConfig" endpoint. + */ +static NSString *const kGetProjectConfigEndPoint = @"getProjectConfig"; + +@implementation FIRGetProjectConfigRequest + +- (nullable instancetype)initWithRequestConfiguration: + (FIRAuthRequestConfiguration *)requestConfiguration { + return [super initWithEndpoint:kGetProjectConfigEndPoint + requestConfiguration:requestConfiguration]; +} + +- (BOOL)containsPostBody { + return NO; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetProjectConfigResponse.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetProjectConfigResponse.h new file mode 100644 index 0000000..bd27cd2 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetProjectConfigResponse.h @@ -0,0 +1,40 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRGetProjectConfigResponse + @brief Represents the response from the getProjectConfig endpoint. + */ +@interface FIRGetProjectConfigResponse : NSObject + +/** @property projectID + @brief The unique ID pertaining to the current project. + */ +@property(nonatomic, strong, readonly, nullable) NSString *projectID; + +/** @property authorizedDomains + @brief A list of domains whitelisted for the current project. + */ +@property(nonatomic, strong, readonly, nullable) NSArray *authorizedDomains; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetProjectConfigResponse.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetProjectConfigResponse.m new file mode 100644 index 0000000..030edd1 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRGetProjectConfigResponse.m @@ -0,0 +1,42 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRGetProjectConfigResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRGetProjectConfigResponse + +- (BOOL)setWithDictionary:(NSDictionary *)dictionary + error:(NSError *_Nullable *_Nullable)error { + _projectID = [dictionary[@"projectId"] copy]; + id authorizedDomains = dictionary[@"authorizedDomains"]; + if ([authorizedDomains isKindOfClass:[NSString class]]) { + NSData *data = [authorizedDomains dataUsingEncoding:NSUTF8StringEncoding]; + authorizedDomains = [NSJSONSerialization JSONObjectWithData:data + options:NSJSONReadingMutableLeaves + error:nil]; + } + if ([authorizedDomains isKindOfClass:[NSArray class]]) { + _authorizedDomains = [[NSArray alloc] initWithArray:authorizedDomains + copyItems:YES]; + } + return YES; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRResetPasswordRequest.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRResetPasswordRequest.h new file mode 100644 index 0000000..701e305 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRResetPasswordRequest.h @@ -0,0 +1,55 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCRequest.h" +#import "FIRIdentityToolkitRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRResetPasswordRequest : FIRIdentityToolkitRequest + +/** @property oobCode + @brief The oobCode sent in the request. + */ +@property(nonatomic, copy, readonly) NSString *oobCode; + +/** @property updatedPassword + @brief The new password sent in the request. + */ +@property(nonatomic, copy, readonly) NSString *updatedPassword; + +/** @fn initWithEndpoint:requestConfiguration: + @brief Please use initWithOobCode:newPassword:requestConfiguration: instead. + */ +- (nullable instancetype)initWithEndpoint:(NSString *)endpoint + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_UNAVAILABLE; + +/** @fn initWithOobCode:newPassword:requestConfiguration: + @brief Designated initializer. + @param oobCode The OOB Code. + @param newPassword The new password. + @param requestConfiguration An object containing configurations to be added to the request. + */ +- (nullable instancetype)initWithOobCode:(NSString *)oobCode + newPassword:(nullable NSString *)newPassword + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRResetPasswordRequest.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRResetPasswordRequest.m new file mode 100644 index 0000000..1295037 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRResetPasswordRequest.m @@ -0,0 +1,60 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRResetPasswordRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kResetPasswordEndpoint + @brief The "resetPassword" endpoint. + */ +static NSString *const kResetPasswordEndpoint = @"resetPassword"; + +/** @var kOOBCodeKey + @brief The "resetPassword" key. + */ +static NSString *const kOOBCodeKey = @"oobCode"; + +/** @var kCurrentPasswordKey + @brief The "newPassword" key. + */ +static NSString *const kCurrentPasswordKey = @"newPassword"; + +@implementation FIRResetPasswordRequest + +- (nullable instancetype)initWithOobCode:(NSString *)oobCode + newPassword:(nullable NSString *)newPassword + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration { + self = [super initWithEndpoint:kResetPasswordEndpoint requestConfiguration:requestConfiguration]; + if (self) { + _oobCode = oobCode; + _updatedPassword = newPassword; + } + return self; +} + +- (nullable id)unencodedHTTPRequestBodyWithError:(NSError *_Nullable *_Nullable)error { + NSMutableDictionary *postBody = [NSMutableDictionary dictionary]; + postBody[kOOBCodeKey] = _oobCode; + if (_updatedPassword) { + postBody[kCurrentPasswordKey] = _updatedPassword; + } + return postBody; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRResetPasswordResponse.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRResetPasswordResponse.h new file mode 100644 index 0000000..28eb5f4 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRResetPasswordResponse.h @@ -0,0 +1,52 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRAuthResetPasswordResponse + @brief Represents the response from the resetPassword endpoint. + @remarks Possible error codes: + - FIRAuthErrorCodeWeakPassword + - FIRAuthErrorCodeUserDisabled + - FIRAuthErrorCodeOperationNotAllowed + - FIRAuthErrorCodeExpiredActionCode + - FIRAuthErrorCodeInvalidActionCode + @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/resetPassword + */ +@interface FIRResetPasswordResponse : NSObject + +/** @property email + @brief The email address corresponding to the reset password request. + */ +@property(nonatomic, strong, readonly) NSString *email; + +/** @property verifiedEmail + @brief The verified email returned from the backend. + */ +@property(nonatomic, strong, readonly) NSString *verifiedEmail; + +/** @property requestType + @brief The tpye of request as returned by the backend. + */ +@property(nonatomic, strong, readonly) NSString *requestType; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRResetPasswordResponse.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRResetPasswordResponse.m new file mode 100644 index 0000000..4f43cc9 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRResetPasswordResponse.m @@ -0,0 +1,33 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRResetPasswordResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRResetPasswordResponse + +- (BOOL)setWithDictionary:(NSDictionary *)dictionary + error:(NSError *_Nullable *_Nullable)error { + _email = [dictionary[@"email"] copy]; + _requestType = [dictionary[@"requestType"] copy]; + _verifiedEmail = [dictionary[@"newEmail"] copy]; + return YES; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSecureTokenRequest.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSecureTokenRequest.h new file mode 100644 index 0000000..14722fa --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSecureTokenRequest.h @@ -0,0 +1,113 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @enum FIRSecureTokenRequestGrantType + @brief Represents the possible grant types for a token request. + */ +typedef NS_ENUM(NSUInteger, FIRSecureTokenRequestGrantType) { + /** @var FIRSecureTokenRequestGrantTypeAuthorizationCode + @brief Indicates an authorization code request. + @remarks Exchanges a Gitkit "ID Token" for an STS Access Token and Refresh Token. + */ + FIRSecureTokenRequestGrantTypeAuthorizationCode, + + /** @var FIRSecureTokenRequestGrantTypeRefreshToken + @brief Indicates an refresh token request. + @remarks Uses an existing Refresh Token to create a new Access Token. + */ + FIRSecureTokenRequestGrantTypeRefreshToken, +}; + +/** @class FIRSecureTokenRequest + @brief Represents the parameters for the token endpoint. + */ +@interface FIRSecureTokenRequest : NSObject + +/** @property grantType + @brief The type of grant requested. + @see FIRSecureTokenRequestGrantType + */ +@property(nonatomic, assign, readonly) FIRSecureTokenRequestGrantType grantType; + +/** @property scope + @brief The scopes requested (a comma-delimited list of scope strings.) + */ +@property(nonatomic, copy, readonly, nullable) NSString *scope; + +/** @property refreshToken + @brief The client's refresh token. + */ +@property(nonatomic, copy, readonly, nullable) NSString *refreshToken; + +/** @property code + @brief The client's authorization code (legacy Gitkit "ID Token"). + */ +@property(nonatomic, copy, readonly, nullable) NSString *code; + +/** @property APIKey + @brief The client's API Key. + */ +@property(nonatomic, copy, readonly) NSString *APIKey; + +/** @fn authCodeRequestWithCode: + @brief Creates an authorization code request with the given code (legacy Gitkit "ID Token"). + @param code The authorization code (legacy Gitkit "ID Token"). + @param requestConfiguration An object containing configurations to be added to the request. + @return An authorization request. + */ ++ (FIRSecureTokenRequest *)authCodeRequestWithCode:(NSString *)code + requestConfiguration:(FIRAuthRequestConfiguration *) + requestConfiguration; + +/** @fn refreshRequestWithCode: + @brief Creates a refresh request with the given refresh token. + @param refreshToken The refresh token. + @param requestConfiguration An object containing configurations to be added to the request. + @return A refresh request. + */ ++ (FIRSecureTokenRequest *)refreshRequestWithRefreshToken:(NSString *)refreshToken + requestConfiguration:(FIRAuthRequestConfiguration *) + requestConfiguration; + +/** @fn init + @brief Please use initWithGrantType:scope:refreshToken:code: + */ +- (instancetype)init NS_UNAVAILABLE; + +/** @fn initWithGrantType:scope:refreshToken:code:APIKey: + @brief Designated initializer. + @param grantType The type of request. + @param scope The scopes requested. + @param refreshToken The client's refresh token (for refresh requests.) + @param code The client's authorization code (Gitkit ID Token) (for authorization code requests.) + @param requestConfiguration An object containing configurations to be added to the request. + */ +- (nullable instancetype)initWithGrantType:(FIRSecureTokenRequestGrantType)grantType + scope:(nullable NSString *)scope + refreshToken:(nullable NSString *)refreshToken + code:(nullable NSString *)code + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSecureTokenRequest.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSecureTokenRequest.m new file mode 100644 index 0000000..b733a94 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSecureTokenRequest.m @@ -0,0 +1,163 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRSecureTokenRequest.h" +#import "FIRAuthRequestConfiguration.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kFIRSecureTokenServiceGetTokenURLFormat + @brief The format of the secure token service URLs. Requires string format substitution with + the client's API Key. + */ +static NSString *const kFIRSecureTokenServiceGetTokenURLFormat = @"https://%@/v1/token?key=%@"; + +/** @var kFIRSecureTokenServiceGrantTypeRefreshToken + @brief The string value of the @c FIRSecureTokenRequestGrantTypeRefreshToken request type. + */ +static NSString *const kFIRSecureTokenServiceGrantTypeRefreshToken = @"refresh_token"; + +/** @var kFIRSecureTokenServiceGrantTypeAuthorizationCode + @brief The string value of the @c FIRSecureTokenRequestGrantTypeAuthorizationCode request type. + */ +static NSString *const kFIRSecureTokenServiceGrantTypeAuthorizationCode = @"authorization_code"; + +/** @var kGrantTypeKey + @brief The key for the "grantType" parameter in the request. + */ +static NSString *const kGrantTypeKey = @"grantType"; + +/** @var kScopeKey + @brief The key for the "scope" parameter in the request. + */ +static NSString *const kScopeKey = @"scope"; + +/** @var kRefreshTokenKey + @brief The key for the "refreshToken" parameter in the request. + */ +static NSString *const kRefreshTokenKey = @"refreshToken"; + +/** @var kCodeKey + @brief The key for the "code" parameter in the request. + */ +static NSString *const kCodeKey = @"code"; + +/** @var gAPIHost + @brief Host for server API calls. + */ +static NSString *gAPIHost = @"securetoken.googleapis.com"; + +@implementation FIRSecureTokenRequest { + /** @var _requestConfiguration + @brief Contains configuration relevant to the request. + */ + FIRAuthRequestConfiguration *_requestConfiguration; +} + ++ (FIRSecureTokenRequest *)authCodeRequestWithCode:(NSString *)code + requestConfiguration:(FIRAuthRequestConfiguration *) + requestConfiguration { + return [[self alloc] initWithGrantType:FIRSecureTokenRequestGrantTypeAuthorizationCode + scope:nil + refreshToken:nil + code:code + requestConfiguration:requestConfiguration]; +} + ++ (FIRSecureTokenRequest *)refreshRequestWithRefreshToken:(NSString *)refreshToken + requestConfiguration:(FIRAuthRequestConfiguration *) + requestConfiguration { + return [[self alloc] initWithGrantType:FIRSecureTokenRequestGrantTypeRefreshToken + scope:nil + refreshToken:refreshToken + code:nil + requestConfiguration:requestConfiguration]; +} + +/** @fn grantTypeStringWithGrantType: + @brief Converts a @c FIRSecureTokenRequestGrantType to it's @c NSString equivilent. + */ ++ (NSString *)grantTypeStringWithGrantType:(FIRSecureTokenRequestGrantType)grantType { + switch (grantType) { + case FIRSecureTokenRequestGrantTypeAuthorizationCode: + return kFIRSecureTokenServiceGrantTypeAuthorizationCode; + case FIRSecureTokenRequestGrantTypeRefreshToken: + return kFIRSecureTokenServiceGrantTypeRefreshToken; + // No Default case so we will notice if new grant types are added to the enum. + } +} + +- (nullable instancetype)initWithGrantType:(FIRSecureTokenRequestGrantType)grantType + scope:(nullable NSString *)scope + refreshToken:(nullable NSString *)refreshToken + code:(nullable NSString *)code + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration { + self = [super init]; + if (self) { + _grantType = grantType; + _scope = [scope copy]; + _refreshToken = [refreshToken copy]; + _code = [code copy]; + _APIKey = [requestConfiguration.APIKey copy]; + _requestConfiguration = requestConfiguration; + } + return self; +} + +- (FIRAuthRequestConfiguration *)requestConfiguration { + return _requestConfiguration; +} + +- (NSURL *)requestURL { + NSString *URLString = + [NSString stringWithFormat:kFIRSecureTokenServiceGetTokenURLFormat, gAPIHost, _APIKey]; + NSURL *URL = [NSURL URLWithString:URLString]; + return URL; +} + +- (BOOL)containsPostBody { + return YES; +} + +- (nullable id)unencodedHTTPRequestBodyWithError:(NSError *_Nullable *_Nullable)error { + NSMutableDictionary *postBody = [@{ + kGrantTypeKey : [[self class] grantTypeStringWithGrantType:_grantType] + } mutableCopy]; + if (_scope) { + postBody[kScopeKey] = _scope; + } + if (_refreshToken) { + postBody[kRefreshTokenKey] = _refreshToken; + } + if (_code) { + postBody[kCodeKey] = _code; + } + return postBody; +} + +#pragma mark - Internal API for development + ++ (NSString *)host { + return gAPIHost; +} + ++ (void)setHost:(NSString *)host { + gAPIHost = host; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSecureTokenResponse.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSecureTokenResponse.h new file mode 100644 index 0000000..0dd4a20 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSecureTokenResponse.h @@ -0,0 +1,50 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRSecureTokenResponse + @brief Represents the response from the token endpoint. + */ +@interface FIRSecureTokenResponse : NSObject + +/** @property approximateExpirationDate + @brief The approximate expiration date of the access token. + */ +@property(nonatomic, copy, readonly, nullable) NSDate *approximateExpirationDate; + +/** @property refreshToken + @brief The refresh token. (Possibly an updated one for refresh requests.) + */ +@property(nonatomic, copy, readonly, nullable) NSString *refreshToken; + +/** @property accessToken + @brief The new access token. + */ +@property(nonatomic, copy, readonly, nullable) NSString *accessToken; + +/** @property IDToken + @brief The new ID Token. + */ +@property(nonatomic, copy, readonly, nullable) NSString *IDToken; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSecureTokenResponse.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSecureTokenResponse.m new file mode 100644 index 0000000..1b1797b --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSecureTokenResponse.m @@ -0,0 +1,74 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRSecureTokenResponse.h" + +#import "FIRAuthErrorUtils.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kExpiresInKey + @brief The key for the number of seconds till the access token expires. + */ +static NSString *const kExpiresInKey = @"expires_in"; + +/** @var kRefreshTokenKey + @brief The key for the refresh token. + */ +static NSString *const kRefreshTokenKey = @"refresh_token"; + +/** @var kAccessTokenKey + @brief The key for the access token. + */ +static NSString *const kAccessTokenKey = @"access_token"; + +/** @var kIDTokenKey + @brief The key for the "id_token" value in the response. + */ +static NSString *const kIDTokenKey = @"id_token"; + +@implementation FIRSecureTokenResponse + +- (nullable NSString *)expectedKind { + return nil; +} + +- (BOOL)setWithDictionary:(NSDictionary *)dictionary + error:(NSError *_Nullable *_Nullable)error { + _refreshToken = dictionary[kRefreshTokenKey]; + _accessToken = dictionary[kAccessTokenKey]; + _IDToken = dictionary[kIDTokenKey]; + if (!_accessToken.length) { + if (error) { + *error = [FIRAuthErrorUtils unexpectedResponseWithDeserializedResponse:dictionary]; + } + return NO; + } + id expiresIn = dictionary[kExpiresInKey]; + if (![expiresIn isKindOfClass:[NSString class]]) { + if (error) { + *error = [FIRAuthErrorUtils unexpectedResponseWithDeserializedResponse:dictionary]; + } + return NO; + } + + _approximateExpirationDate = [NSDate dateWithTimeIntervalSinceNow:[expiresIn doubleValue]]; + return YES; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSendVerificationCodeRequest.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSendVerificationCodeRequest.h new file mode 100644 index 0000000..af6cc93 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSendVerificationCodeRequest.h @@ -0,0 +1,67 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRIdentityToolkitRequest.h" + +#import "FIRAuthRPCRequest.h" +#import "FIRIdentityToolkitRequest.h" + +@class FIRAuthAppCredential; + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRSendVerificationCodeRequest : FIRIdentityToolkitRequest + +/** @property phoneNumber + @brief The phone number to which the verification code should be sent. + */ +@property(nonatomic, strong, readonly) NSString *phoneNumber; + +/** @property appCredential + @brief The credential to prove the identity of the app in order to send the verification code. + */ +@property(nonatomic, strong, readonly, nullable) FIRAuthAppCredential *appCredential; + +/** @property reCAPTCHAToken + @brief The reCAPTCHA token to prove the identity of the app in order to send the verification + code. + */ +@property(nonatomic, strong, readonly, nullable) NSString *reCAPTCHAToken; + +/** @fn initWithEndpoint:requestConfiguration: + @brief Please use initWithPhoneNumber:appCredentials:requestConfiguration: instead. + */ +- (nullable instancetype)initWithEndpoint:(NSString *)endpoint + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_UNAVAILABLE; + +/** @fn initWithPhoneNumber:appCredentials:requestConfiguration: + @brief Designated initializer. + @param phoneNumber The phone number to which the verification code is to be sent. + @param appCredential The credential that proves the identity of the app. + @param reCAPTCHAToken The reCAPTCHA token that proves the identity of the app. + @param requestConfiguration An object containing configurations to be added to the request. + */ +- (nullable instancetype)initWithPhoneNumber:(NSString *)phoneNumber + appCredential:(nullable FIRAuthAppCredential *)appCredential + reCAPTCHAToken:(nullable NSString *)reCAPTCHAToken + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_DESIGNATED_INITIALIZER; + + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSendVerificationCodeRequest.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSendVerificationCodeRequest.m new file mode 100644 index 0000000..38ad8cf --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSendVerificationCodeRequest.m @@ -0,0 +1,84 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRSendVerificationCodeRequest.h" + +#import "FIRAuthAppCredential.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kSendVerificationCodeEndPoint + @brief The "sendVerificationCodeEnd" endpoint. + */ +static NSString *const kSendVerificationCodeEndPoint = @"sendVerificationCode"; + +/** @var kPhoneNumberKey + @brief The key for the Phone Number parameter in the request. + */ +static NSString *const kPhoneNumberKey = @"phoneNumber"; + +/** @var kReceiptKey + @brief The key for the receipt parameter in the request. + */ +static NSString *const kReceiptKey = @"iosReceipt"; + +/** @var kSecretKey + @brief The key for the Secret parameter in the request. + */ +static NSString *const kSecretKey = @"iosSecret"; + +/** @var kreCAPTCHATokenKey + @brief The key for the reCAPTCHAToken parameter in the request. + */ +static NSString *const kreCAPTCHATokenKey = @"recaptchaToken"; + +@implementation FIRSendVerificationCodeRequest { +} + +- (nullable instancetype)initWithPhoneNumber:(NSString *)phoneNumber + appCredential:(nullable FIRAuthAppCredential *)appCredential + reCAPTCHAToken:(nullable NSString *)reCAPTCHAToken + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration { + self = [super initWithEndpoint:kSendVerificationCodeEndPoint + requestConfiguration:requestConfiguration]; + if (self) { + _phoneNumber = [phoneNumber copy]; + _appCredential = appCredential; + _reCAPTCHAToken = [reCAPTCHAToken copy]; + } + return self; +} + +- (nullable id)unencodedHTTPRequestBodyWithError:(NSError *_Nullable *_Nullable)error { + NSMutableDictionary *postBody = [NSMutableDictionary dictionary]; + if (_phoneNumber) { + postBody[kPhoneNumberKey] = _phoneNumber; + } + if (_appCredential.receipt) { + postBody[kReceiptKey] = _appCredential.receipt; + } + if (_appCredential.secret) { + postBody[kSecretKey] = _appCredential.secret; + } + if (_reCAPTCHAToken) { + postBody[kreCAPTCHATokenKey] = _reCAPTCHAToken; + } + return postBody; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSendVerificationCodeResponse.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSendVerificationCodeResponse.h new file mode 100644 index 0000000..1a49ec2 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSendVerificationCodeResponse.h @@ -0,0 +1,32 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRSendVerificationCodeResponse : NSObject + +/** @property verificationID + @brief Encrypted session information returned by the backend. + */ +@property(nonatomic, readonly) NSString *verificationID; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSendVerificationCodeResponse.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSendVerificationCodeResponse.m new file mode 100644 index 0000000..9e47b6e --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSendVerificationCodeResponse.m @@ -0,0 +1,36 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRSendVerificationCodeResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRSendVerificationCodeResponse + +// TODO: remove when resolving b/37169084 . +- (nullable NSString *)expectedKind { + return nil; +} + +- (BOOL)setWithDictionary:(NSDictionary *)dictionary + error:(NSError *_Nullable *_Nullable)error { + _verificationID = [dictionary[@"sessionInfo"] copy]; + return YES; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSetAccountInfoRequest.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSetAccountInfoRequest.h new file mode 100644 index 0000000..0e0e18f --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSetAccountInfoRequest.h @@ -0,0 +1,151 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCRequest.h" +#import "FIRIdentityToolkitRequest.h" + +@class FIRGetAccountInfoResponse; + +NS_ASSUME_NONNULL_BEGIN + +/** @var FIRSetAccountInfoUserAttributeEmail + @brief Constant for email attribute used in "deleteAttributes". + */ +extern NSString *const FIRSetAccountInfoUserAttributeEmail; + +/** @var FIRSetAccountInfoUserAttributeDisplayName + @brief Constant for displayName attribute used in "deleteAttributes". + */ +extern NSString *const FIRSetAccountInfoUserAttributeDisplayName; + +/** @var FIRSetAccountInfoUserAttributeProvider + @brief Constant for provider attribute used in "deleteAttributes". + */ +extern NSString *const FIRSetAccountInfoUserAttributeProvider; + +/** @var FIRSetAccountInfoUserAttributePhotoURL + @brief Constant for photoURL attribute used in "deleteAttributes". + */ +extern NSString *const FIRSetAccountInfoUserAttributePhotoURL; + +/** @var FIRSetAccountInfoUserAttributePassword + @brief Constant for password attribute used in "deleteAttributes". + */ +extern NSString *const FIRSetAccountInfoUserAttributePassword; + +/** @class FIRSetAccountInfoRequest + @brief Represents the parameters for the setAccountInfo endpoint. + @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/setAccountInfo + */ +@interface FIRSetAccountInfoRequest : FIRIdentityToolkitRequest + +/** @property accessToken + @brief The STS Access Token of the authenticated user. + */ +@property(nonatomic, copy, nullable) NSString *accessToken; + +/** @property displayName + @brief The name of the user. + */ +@property(nonatomic, copy, nullable) NSString *displayName; + +/** @property localID + @brief The local ID of the user. + */ +@property(nonatomic, copy, nullable) NSString *localID; + +/** @property email + @brief The email of the user. + */ +@property(nonatomic, copy, nullable) NSString *email; + +/** @property photoURL + @brief The photoURL of the user. + */ +@property(nonatomic, copy, nullable) NSURL *photoURL; + +/** @property password + @brief The new password of the user. + */ +@property(nonatomic, copy, nullable) NSString *password; + +/** @property providers + @brief The associated identity providers of the user. + */ +@property(nonatomic, copy, nullable) NSArray *providers; + +/** @property OOBCode + @brief The out-of-band code of the change email request. + */ +@property(nonatomic, copy, nullable) NSString *OOBCode; + +/** @property emailVerified + @brief Whether to mark the email as verified or not. + */ +@property(nonatomic, assign) BOOL emailVerified; + +/** @property upgradeToFederatedLogin + @brief Whether to mark the user to upgrade to federated login. + */ +@property(nonatomic, assign) BOOL upgradeToFederatedLogin; + +/** @property captchaChallenge + @brief The captcha challenge. + */ +@property(nonatomic, copy, nullable) NSString *captchaChallenge; + +/** @property captchaResponse + @brief Response to the captcha. + */ +@property(nonatomic, copy, nullable) NSString *captchaResponse; + +/** @property deleteAttributes + @brief The list of user attributes to delete. + @remarks Every element of the list must be one of the predefined constant starts with + "FIRSetAccountInfoUserAttribute". + */ +@property(nonatomic, copy, nullable) NSArray *deleteAttributes; + +/** @property deleteProviders + @brief The list of identity providers to delete. + */ +@property(nonatomic, copy, nullable) NSArray *deleteProviders; + +/** @property returnSecureToken + @brief Whether the response should return access token and refresh token directly. + @remarks The default value is @c YES . + */ +@property(nonatomic, assign) BOOL returnSecureToken; + +/** @fn initWithEndpoint:requestConfiguration: + @brief Please use initWithAPIKey:email:password:displayName:requestConfiguration instead. + */ +- (nullable instancetype)initWithEndpoint:(NSString *)endpoint + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_UNAVAILABLE; + +/** @fn initWithRequestConfiguration: + @brief Designated initializer. + @param requestConfiguration An object containing configurations to be added to the request. + */ +- (nullable instancetype)initWithRequestConfiguration: + (FIRAuthRequestConfiguration *)requestConfiguration NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSetAccountInfoRequest.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSetAccountInfoRequest.m new file mode 100644 index 0000000..ef06d2b --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSetAccountInfoRequest.m @@ -0,0 +1,180 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRSetAccountInfoRequest.h" + +#import "FIRAuthErrorUtils.h" +#import "FIRAuth_Internal.h" +#import "FIRGetAccountInfoResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +NSString *const FIRSetAccountInfoUserAttributeEmail = @"EMAIL"; + +NSString *const FIRSetAccountInfoUserAttributeDisplayName = @"DISPLAY_NAME"; + +NSString *const FIRSetAccountInfoUserAttributeProvider = @"PROVIDER"; + +NSString *const FIRSetAccountInfoUserAttributePhotoURL = @"PHOTO_URL"; + +NSString *const FIRSetAccountInfoUserAttributePassword = @"PASSWORD"; + +/** @var kCreateAuthURIEndpoint + @brief The "setAccountInfo" endpoint. + */ +static NSString *const kSetAccountInfoEndpoint = @"setAccountInfo"; + +/** @var kIDTokenKey + @brief The key for the "idToken" value in the request. This is actually the STS Access Token, + despite it's confusing (backwards compatiable) parameter name. + */ +static NSString *const kIDTokenKey = @"idToken"; + +/** @var kDisplayNameKey + @brief The key for the "displayName" value in the request. + */ +static NSString *const kDisplayNameKey = @"displayName"; + +/** @var kLocalIDKey + @brief The key for the "localID" value in the request. + */ +static NSString *const kLocalIDKey = @"localId"; + +/** @var kEmailKey + @brief The key for the "email" value in the request. + */ +static NSString *const kEmailKey = @"email"; + +/** @var kPasswordKey + @brief The key for the "password" value in the request. + */ +static NSString *const kPasswordKey = @"password"; + +/** @var kPhotoURLKey + @brief The key for the "photoURL" value in the request. + */ +static NSString *const kPhotoURLKey = @"photoUrl"; + +/** @var kProvidersKey + @brief The key for the "providers" value in the request. + */ +static NSString *const kProvidersKey = @"provider"; + +/** @var kOOBCodeKey + @brief The key for the "OOBCode" value in the request. + */ +static NSString *const kOOBCodeKey = @"oobCode"; + +/** @var kEmailVerifiedKey + @brief The key for the "emailVerified" value in the request. + */ +static NSString *const kEmailVerifiedKey = @"emailVerified"; + +/** @var kUpgradeToFederatedLoginKey + @brief The key for the "upgradeToFederatedLogin" value in the request. + */ +static NSString *const kUpgradeToFederatedLoginKey = @"upgradeToFederatedLogin"; + +/** @var kCaptchaChallengeKey + @brief The key for the "captchaChallenge" value in the request. + */ +static NSString *const kCaptchaChallengeKey = @"captchaChallenge"; + +/** @var kCaptchaResponseKey + @brief The key for the "captchaResponse" value in the request. + */ +static NSString *const kCaptchaResponseKey = @"captchaResponse"; + +/** @var kDeleteAttributesKey + @brief The key for the "deleteAttribute" value in the request. + */ +static NSString *const kDeleteAttributesKey = @"deleteAttribute"; + +/** @var kDeleteProvidersKey + @brief The key for the "deleteProvider" value in the request. + */ +static NSString *const kDeleteProvidersKey = @"deleteProvider"; + +/** @var kReturnSecureTokenKey + @brief The key for the "returnSecureToken" value in the request. + */ +static NSString *const kReturnSecureTokenKey = @"returnSecureToken"; + +@implementation FIRSetAccountInfoRequest + +- (nullable instancetype)initWithRequestConfiguration: + (FIRAuthRequestConfiguration *)requestConfiguration { + self = [super initWithEndpoint:kSetAccountInfoEndpoint requestConfiguration:requestConfiguration]; + if (self) { + _returnSecureToken = YES; + } + return self; +} + +- (nullable id)unencodedHTTPRequestBodyWithError:(NSError *_Nullable *_Nullable)error { + NSMutableDictionary *postBody = [NSMutableDictionary dictionary]; + if (_accessToken) { + postBody[kIDTokenKey] = _accessToken; + } + if (_displayName) { + postBody[kDisplayNameKey] = _displayName; + } + if (_localID) { + postBody[kLocalIDKey] = _localID; + } + if (_email) { + postBody[kEmailKey] = _email; + } + if (_password) { + postBody[kPasswordKey] = _password; + } + if (_photoURL) { + postBody[kPhotoURLKey] = _photoURL.absoluteString; + } + if (_providers) { + postBody[kProvidersKey] = _providers; + } + if (_OOBCode) { + postBody[kOOBCodeKey] = _OOBCode; + } + if (_emailVerified) { + postBody[kEmailVerifiedKey] = @YES; + } + if (_upgradeToFederatedLogin) { + postBody[kUpgradeToFederatedLoginKey] = @YES; + } + if (_captchaChallenge) { + postBody[kCaptchaChallengeKey] = _captchaChallenge; + } + if (_captchaResponse) { + postBody[kCaptchaResponseKey] = _captchaResponse; + } + if (_deleteAttributes) { + postBody[kDeleteAttributesKey] = _deleteAttributes; + } + if (_deleteProviders) { + postBody[kDeleteProvidersKey] = _deleteProviders; + } + if (_returnSecureToken) { + postBody[kReturnSecureTokenKey] = @YES; + } + return postBody; +} + +@end + +NS_ASSUME_NONNULL_END + diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSetAccountInfoResponse.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSetAccountInfoResponse.h new file mode 100644 index 0000000..92895c0 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSetAccountInfoResponse.h @@ -0,0 +1,98 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRSetAccountInfoResponseProviderUserInfo + @brief Represents the provider user info part of the response from the setAccountInfo endpoint. + @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/setAccountInfo + */ +@interface FIRSetAccountInfoResponseProviderUserInfo : NSObject + +/** @property providerID + @brief The ID of the identity provider. + */ +@property(nonatomic, strong, readonly, nullable) NSString *providerID; + +/** @property displayName + @brief The user's display name at the identity provider. + */ +@property(nonatomic, strong, readonly, nullable) NSString *displayName; + +/** @property photoURL + @brief The user's photo URL at the identity provider. + */ +@property(nonatomic, strong, readonly, nullable) NSURL *photoURL; + +/** @fn init + @brief Please use initWithDictionary: + */ +- (instancetype)init NS_UNAVAILABLE; + +/** @fn initWithAPIKey: + @brief Designated initializer. + @param dictionary The provider user info data from endpoint. + */ +- (instancetype)initWithDictionary:(NSDictionary *)dictionary NS_DESIGNATED_INITIALIZER; + +@end + +/** @class FIRSetAccountInfoResponse + @brief Represents the response from the setAccountInfo endpoint. + @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/setAccountInfo + */ +@interface FIRSetAccountInfoResponse : NSObject + +/** @property email + @brief The email or the user. + */ +@property(nonatomic, strong, readonly, nullable) NSString *email; + +/** @property displayName + @brief The display name of the user. + */ +@property(nonatomic, strong, readonly, nullable) NSString *displayName; + +/** @property providerUserInfo + @brief The user's profiles at the associated identity providers. + */ +@property(nonatomic, strong, readonly, nullable) + NSArray *providerUserInfo; + +/** @property IDToken + @brief Either an authorization code suitable for performing an STS token exchange, or the + access token from Secure Token Service, depending on whether @c returnSecureToken is set + on the request. + */ +@property(nonatomic, strong, readonly, nullable) NSString *IDToken; + +/** @property approximateExpirationDate + @brief The approximate expiration date of the access token. + */ +@property(nonatomic, copy, readonly, nullable) NSDate *approximateExpirationDate; + +/** @property refreshToken + @brief The refresh token from Secure Token Service. + */ +@property(nonatomic, strong, readonly, nullable) NSString *refreshToken; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSetAccountInfoResponse.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSetAccountInfoResponse.m new file mode 100644 index 0000000..7054a44 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSetAccountInfoResponse.m @@ -0,0 +1,63 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRSetAccountInfoResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRSetAccountInfoResponseProviderUserInfo + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary { + self = [super init]; + if (self) { + _providerID = [dictionary[@"providerId"] copy]; + _displayName = [dictionary[@"displayName"] copy]; + NSString *photoURL = dictionary[@"photoUrl"]; + if (photoURL) { + _photoURL = [NSURL URLWithString:photoURL]; + } + } + return self; +} + +@end + +@implementation FIRSetAccountInfoResponse + +- (BOOL)setWithDictionary:(NSDictionary *)dictionary + error:(NSError *_Nullable *_Nullable)error { + _email = [dictionary[@"email"] copy]; + _displayName = [dictionary[@"displayName"] copy]; + _IDToken = [dictionary[@"idToken"] copy]; + _approximateExpirationDate = [dictionary[@"expiresIn"] isKindOfClass:[NSString class]] ? + [NSDate dateWithTimeIntervalSinceNow:[dictionary[@"expiresIn"] doubleValue]] : nil; + _refreshToken = [dictionary[@"refreshToken"] copy]; + NSArray *providerUserInfoData = dictionary[@"providerUserInfo"]; + if (providerUserInfoData) { + NSMutableArray *providerUserInfoArray = + [NSMutableArray arrayWithCapacity:providerUserInfoData.count]; + for (NSDictionary *dictionary in providerUserInfoData) { + [providerUserInfoArray addObject: + [[FIRSetAccountInfoResponseProviderUserInfo alloc] initWithDictionary:dictionary]]; + } + _providerUserInfo = [providerUserInfoArray copy]; + } + return YES; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSignInWithGameCenterRequest.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSignInWithGameCenterRequest.h new file mode 100644 index 0000000..52720cb --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSignInWithGameCenterRequest.h @@ -0,0 +1,91 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCRequest.h" +#import "FIRIdentityToolkitRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRSignInWithGameCenterRequest + @brief The request to sign in with Game Center account + */ +@interface FIRSignInWithGameCenterRequest : FIRIdentityToolkitRequest + +/** @property playerID + @brief The playerID to verify. + */ +@property(nonatomic, copy) NSString *playerID; + +/** @property publicKeyURL + @brief The URL for the public encryption key. + */ +@property(nonatomic, copy) NSURL *publicKeyURL; + +/** @property signature + @brief The verification signature data generated by Game Center. + */ +@property(nonatomic, copy) NSData *signature; + +/** @property salt + @brief A random strong used to compute the hash and keep it randomized. + */ +@property(nonatomic, copy) NSData *salt; + +/** @property timestamp + @brief The date and time that the signature was created. + */ +@property(nonatomic, assign) uint64_t timestamp; + +/** @property accessToken + @brief The STS Access Token for the authenticated user, only needed for linking the user. + */ +@property(nonatomic, copy, nullable) NSString *accessToken; + +/** @property displayName + @brief The display name of the local Game Center player. + */ +@property(nonatomic, copy, nullable) NSString *displayName; + +/** @fn initWithEndpoint:requestConfiguration: + @brief Please use initWithPlayerID:publicKeyURL:signature:salt:timestamp:requestConfiguration:. + */ +- (nullable instancetype)initWithEndpoint:(NSString *)endpoint + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_UNAVAILABLE; + +/** @fn initWithPlayerID:publicKeyURL:signature:salt:timestamp:displayName:requestConfiguration: + @brief Designated initializer. + @param playerID The ID of the Game Center player. + @param publicKeyURL The URL for the public encryption key. + @param signature The verification signature generated. + @param salt A random string used to compute the hash and keep it randomized. + @param timestamp The date and time that the signature was created. + @param displayName The display name of the Game Center player. + */ +- (nullable instancetype)initWithPlayerID:(NSString *)playerID + publicKeyURL:(NSURL *)publicKeyURL + signature:(NSData *)signature + salt:(NSData *)salt + timestamp:(uint64_t)timestamp + displayName:(NSString *)displayName + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSignInWithGameCenterRequest.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSignInWithGameCenterRequest.m new file mode 100644 index 0000000..35fb754 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSignInWithGameCenterRequest.m @@ -0,0 +1,80 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRSignInWithGameCenterRequest.h" + +#import "NSData+FIRBase64.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kSignInWithGameCenterEndPoint + @brief The "SignInWithGameCenter" endpoint. + */ +static NSString *const kSignInWithGameCenterEndPoint = @"signInWithGameCenter"; + +@implementation FIRSignInWithGameCenterRequest + +- (nullable instancetype)initWithPlayerID:(NSString *)playerID + publicKeyURL:(NSURL *)publicKeyURL + signature:(NSData *)signature + salt:(NSData *)salt + timestamp:(uint64_t)timestamp + displayName:(NSString *)displayName + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration { + self = [super initWithEndpoint:kSignInWithGameCenterEndPoint + requestConfiguration:requestConfiguration]; + if (self) { + _playerID = playerID; + _publicKeyURL = [publicKeyURL copy]; + _signature = [signature copy]; + _salt = [salt copy]; + _timestamp = timestamp; + _displayName = displayName; + } + return self; +} + +#pragma mark - FIRAuthRPCRequest + +- (nullable id)unencodedHTTPRequestBodyWithError:(NSError *__autoreleasing _Nullable *)error { + NSMutableDictionary *postBody = [NSMutableDictionary dictionary]; + if (_playerID) { + postBody[@"playerId"] = _playerID; + } + if (_publicKeyURL) { + postBody[@"publicKeyUrl"] = _publicKeyURL.absoluteString; + } + if (_signature) { + postBody[@"signature"] = [_signature fir_base64URLEncodedStringWithOptions:0]; + } + if (_salt) { + postBody[@"salt"] = [_salt fir_base64URLEncodedStringWithOptions:0]; + } + if (_timestamp != 0) { + postBody[@"timestamp"] = [NSNumber numberWithUnsignedLongLong:_timestamp]; + } + if (_accessToken) { + postBody[@"idToken"] = _accessToken; + } + if (_displayName) { + postBody[@"displayName"] = _displayName; + } + return postBody; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSignInWithGameCenterResponse.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSignInWithGameCenterResponse.h new file mode 100644 index 0000000..75dbd75 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSignInWithGameCenterResponse.h @@ -0,0 +1,64 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRSignInWithGameCenterResponse : NSObject + +/** @property IDToken + @breif Either an authorization code suitable for performing an STS token exchange, or the access + token from Secure Token Service, depending on whether @c returnSecureToken is set on the + request. + */ +@property(nonatomic, copy, readonly, nullable) NSString *IDToken; + +/** @property refreshToken + @breif @breif The refresh token from Secure Token Service. + */ +@property(nonatomic, copy, readonly, nullable) NSString *refreshToken; + +/** @property localID + @breif @breif The Firebase Auth user ID. + */ +@property(nonatomic, copy, readonly, nullable) NSString *localID; + +/** @property playerID + @breif @breif The verified player ID. + */ +@property(nonatomic, copy, readonly, nullable) NSString *playerID; + +/** @property approximateExpirationDate + @breif The approximate expiration date of the access token. + */ +@property(nonatomic, copy, readonly, nullable) NSDate *approximateExpirationDate; + +/** @property isNewUser + @breif Flag indicating that the user signing in is a new user and not a returning user. + */ +@property(nonatomic, assign) BOOL isNewUser; + +/** @property displayName + @breif The user's Game Center display name. + */ +@property(nonatomic, copy, readonly, nullable) NSString *displayName; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSignInWithGameCenterResponse.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSignInWithGameCenterResponse.m new file mode 100644 index 0000000..7cd1b9a --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSignInWithGameCenterResponse.m @@ -0,0 +1,40 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRSignInWithGameCenterResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRSignInWithGameCenterResponse + +- (BOOL)setWithDictionary:(NSDictionary *)dictionary + error:(NSError *_Nullable *_Nullable)error { + _IDToken = [dictionary[@"idToken"] copy]; + _refreshToken = [dictionary[@"refreshToken"] copy]; + _localID = [dictionary[@"localId"] copy]; + _approximateExpirationDate = nil; + if ([dictionary[@"expiresIn"] isKindOfClass:[NSString class]]) { + _approximateExpirationDate = [NSDate dateWithTimeIntervalSinceNow:[dictionary[@"expiresIn"] integerValue]]; + } + _playerID = [dictionary[@"playerId"] copy]; + _isNewUser = [dictionary[@"isNewUser"] boolValue]; + _displayName = [dictionary[@"displayName"] copy]; + return YES; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSignUpNewUserRequest.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSignUpNewUserRequest.h new file mode 100644 index 0000000..06d2cfe --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSignUpNewUserRequest.h @@ -0,0 +1,72 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCRequest.h" +#import "FIRIdentityToolkitRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRSignUpNewUserRequest : FIRIdentityToolkitRequest + +/** @property email + @brief The email of the user. + */ +@property(nonatomic, copy, nullable) NSString *email; + +/** @property password + @brief The password inputed by the user. + */ +@property(nonatomic, copy, nullable) NSString *password; + +/** @property displayName + @brief The password inputed by the user. + */ +@property(nonatomic, copy, nullable) NSString *displayName; + +/** @property returnSecureToken + @brief Whether the response should return access token and refresh token directly. + @remarks The default value is @c YES . + */ +@property(nonatomic, assign) BOOL returnSecureToken; + +/** @fn initWithEndpoint:requestConfiguration: + @brief Please use initWithAPIKey:email:password:displayName:requestConfiguration instead. + */ +- (nullable instancetype)initWithEndpoint:(NSString *)endpoint + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_UNAVAILABLE; + +/** @fn initWithEndpoint:requestConfiguration: + @brief initializer for anonymous sign-in. + */ +- (nullable instancetype)initWithRequestConfiguration: + (FIRAuthRequestConfiguration *)requestConfiguration; + +/** @fn initWithAPIKey:email:password:displayName:requestConfiguration + @brief Designated initializer. + @param requestConfiguration An object containing configurations to be added to the request. + */ +- (nullable instancetype)initWithEmail:(nullable NSString *)email + password:(nullable NSString *)password + displayName:(nullable NSString *)displayName + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSignUpNewUserRequest.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSignUpNewUserRequest.m new file mode 100644 index 0000000..5d50e0a --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSignUpNewUserRequest.m @@ -0,0 +1,90 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRSignUpNewUserRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kSignupNewUserEndpoint + @brief The "SingupNewUserEndpoint" endpoint. + */ +static NSString *const kSignupNewUserEndpoint = @"signupNewUser"; + +/** @var kEmailKey + @brief The key for the "email" value in the request. + */ +static NSString *const kEmailKey = @"email"; + +/** @var kPasswordKey + @brief The key for the "password" value in the request. + */ +static NSString *const kPasswordKey = @"password"; + +/** @var kDisplayNameKey + @brief The key for the "kDisplayName" value in the request. + */ +static NSString *const kDisplayNameKey = @"displayName"; + +/** @var kReturnSecureTokenKey + @brief The key for the "returnSecureToken" value in the request. + */ +static NSString *const kReturnSecureTokenKey = @"returnSecureToken"; + +@implementation FIRSignUpNewUserRequest + +- (nullable instancetype)initWithEmail:(nullable NSString *)email + password:(nullable NSString *)password + displayName:(nullable NSString *)displayName + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration { + self = [super initWithEndpoint:kSignupNewUserEndpoint requestConfiguration:requestConfiguration]; + if (self) { + _email = [email copy]; + _password = [password copy]; + _displayName = [displayName copy]; + _returnSecureToken = YES; + } + return self; +} + +- (nullable instancetype)initWithRequestConfiguration: + (FIRAuthRequestConfiguration *)requestConfiguration { + self = [self initWithEmail:nil + password:nil + displayName:nil + requestConfiguration:requestConfiguration]; + return self; +} + +- (nullable id)unencodedHTTPRequestBodyWithError:(NSError *_Nullable *_Nullable)error { + NSMutableDictionary *postBody = [NSMutableDictionary dictionary]; + if (_email) { + postBody[kEmailKey] = _email; + } + if (_password) { + postBody[kPasswordKey] = _password; + } + if (_displayName) { + postBody[kDisplayNameKey] = _displayName; + } + if (_returnSecureToken) { + postBody[kReturnSecureTokenKey] = @YES; + } + return postBody; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSignUpNewUserResponse.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSignUpNewUserResponse.h new file mode 100644 index 0000000..0d55939 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSignUpNewUserResponse.h @@ -0,0 +1,44 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRSignUpNewUserResponse : NSObject + +/** @property IDToken + @brief Either an authorization code suitable for performing an STS token exchange, or the + access token from Secure Token Service, depending on whether @c returnSecureToken is set + on the request. + */ +@property(nonatomic, strong, readonly, nullable) NSString *IDToken; + +/** @property approximateExpirationDate + @brief The approximate expiration date of the access token. + */ +@property(nonatomic, copy, readonly, nullable) NSDate *approximateExpirationDate; + +/** @property refreshToken + @brief The refresh token from Secure Token Service. + */ +@property(nonatomic, strong, readonly, nullable) NSString *refreshToken; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSignUpNewUserResponse.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSignUpNewUserResponse.m new file mode 100644 index 0000000..03d0616 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRSignUpNewUserResponse.m @@ -0,0 +1,34 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRSignUpNewUserResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRSignUpNewUserResponse + +- (BOOL)setWithDictionary:(NSDictionary *)dictionary + error:(NSError *_Nullable *_Nullable)error { + _IDToken = [dictionary[@"idToken"] copy]; + _approximateExpirationDate = [dictionary[@"expiresIn"] isKindOfClass:[NSString class]] ? + [NSDate dateWithTimeIntervalSinceNow:[dictionary[@"expiresIn"] doubleValue]] : nil; + _refreshToken = [dictionary[@"refreshToken"] copy]; + return YES; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyAssertionRequest.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyAssertionRequest.h new file mode 100644 index 0000000..d031611 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyAssertionRequest.h @@ -0,0 +1,118 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCRequest.h" +#import "FIRIdentityToolkitRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRVerifyAssertionRequest + @brief Represents the parameters for the verifyAssertion endpoint. + @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/verifyAssertion + */ +@interface FIRVerifyAssertionRequest : FIRIdentityToolkitRequest + +/** @property requestURI + @brief The URI to which the IDP redirects the user back. It may contain federated login result + params added by the IDP. + */ +@property(nonatomic, copy, nullable) NSString *requestURI; + +/** @property pendingToken + @brief The Firebase ID Token for the IDP pending to be confirmed by the user. + */ +@property(nonatomic, copy, nullable) NSString *pendingToken; + +/** @property accessToken + @brief The STS Access Token for the authenticated user, only needed for linking the user. + */ +@property(nonatomic, copy, nullable) NSString *accessToken; + +/** @property returnSecureToken + @brief Whether the response should return access token and refresh token directly. + @remarks The default value is @c YES . + */ +@property(nonatomic, assign) BOOL returnSecureToken; + +#pragma mark - Components of "postBody" + +/** @property providerID + @brief The ID of the IDP whose credentials are being presented to the endpoint. + */ +@property(nonatomic, copy, readonly) NSString *providerID; + +/** @property providerAccessToken + @brief An access token from the IDP. + */ +@property(nonatomic, copy, nullable) NSString *providerAccessToken; + +/** @property providerIDToken + @brief An ID Token from the IDP. + */ +@property(nonatomic, copy, nullable) NSString *providerIDToken; + +/** @property providerRawNonce + @brief An raw nonce from the IDP. + */ +@property(nonatomic, copy, nullable) NSString *providerRawNonce; + +/** @property returnIDPCredential + @brief Whether the response should return the IDP credential directly. + */ +@property(nonatomic, assign) BOOL returnIDPCredential; + +/** @property providerOAuthTokenSecret + @brief A session ID used to map this request to a headful-lite flow. + */ +@property(nonatomic, copy, nullable) NSString *sessionID; + +/** @property providerOAuthTokenSecret + @brief An OAuth client secret from the IDP. + */ +@property(nonatomic, copy, nullable) NSString *providerOAuthTokenSecret; + +/** @property inputEmail + @brief The originally entered email in the UI. + */ +@property(nonatomic, copy, nullable) NSString *inputEmail; + +/** @property autoCreate + @brief A flag that indicates whether or not the user should be automatically created. + */ +@property(nonatomic, assign) BOOL autoCreate; + +/** @fn initWithEndpoint:requestConfiguration: + @brief Please use initWithProviderID:requestConfifuration instead. + */ +- (nullable instancetype)initWithEndpoint:(NSString *)endpoint + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_UNAVAILABLE; + +/** @fn initWithProviderID:requestConfifuration + @brief Designated initializer. + @param providerID The auth provider's ID. + @param requestConfiguration An object containing configurations to be added to the request. + + */ +- (nullable instancetype)initWithProviderID:(NSString *)providerID + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyAssertionRequest.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyAssertionRequest.m new file mode 100644 index 0000000..9aa326e --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyAssertionRequest.m @@ -0,0 +1,178 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRVerifyAssertionRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kVerifyAssertionEndpoint + @brief The "verifyAssertion" endpoint. + */ +static NSString *const kVerifyAssertionEndpoint = @"verifyAssertion"; + +/** @var kProviderIDKey + @brief The key for the "providerId" value in the request. + */ +static NSString *const kProviderIDKey = @"providerId"; + +/** @var kProviderIDTokenKey + @brief The key for the "id_token" value in the request. + */ +static NSString *const kProviderIDTokenKey = @"id_token"; + +/** @var kProviderNonceKey + @brief The key for the "nonce" value in the request. + */ +static NSString *const kProviderNonceKey = @"nonce"; + +/** @var kProviderAccessTokenKey + @brief The key for the "access_token" value in the request. + */ +static NSString *const kProviderAccessTokenKey = @"access_token"; + +/** @var kProviderOAuthTokenSecretKey + @brief The key for the "oauth_token_secret" value in the request. + */ +static NSString *const kProviderOAuthTokenSecretKey = @"oauth_token_secret"; + +/** @var kIdentifierKey + @brief The key for the "identifier" value in the request. + */ +static NSString *const kIdentifierKey = @"identifier"; + +/** @var kRequestURIKey + @brief The key for the "requestUri" value in the request. + */ +static NSString *const kRequestURIKey = @"requestUri"; + +/** @var kPostBodyKey + @brief The key for the "postBody" value in the request. + */ +static NSString *const kPostBodyKey = @"postBody"; + +/** @var kPendingTokenKey + @brief The key for the "pendingToken" value in the request. + */ +static NSString *const kPendingTokenKey = @"pendingToken"; + +/** @var kAutoCreateKey + @brief The key for the "autoCreate" value in the request. + */ +static NSString *const kAutoCreateKey = @"autoCreate"; + +/** @var kIDTokenKey + @brief The key for the "idToken" value in the request. This is actually the STS Access Token, + despite it's confusing (backwards compatiable) parameter name. + */ +static NSString *const kIDTokenKey = @"idToken"; + +/** @var kReturnSecureTokenKey + @brief The key for the "returnSecureToken" value in the request. + */ +static NSString *const kReturnSecureTokenKey = @"returnSecureToken"; + +/** @var kReturnIDPCredentialKey + @brief The key for the "returnIdpCredential" value in the request. + */ +static NSString *const kReturnIDPCredentialKey = @"returnIdpCredential"; + +/** @var kSessionIDKey + @brief The key for the "sessionID" value in the request. + */ +static NSString *const kSessionIDKey = @"sessionId"; + +@implementation FIRVerifyAssertionRequest + +- (nullable instancetype)initWithProviderID:(NSString *)providerID + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration { + self = [super initWithEndpoint:kVerifyAssertionEndpoint + requestConfiguration:requestConfiguration]; + if (self) { + _providerID = providerID; + _returnSecureToken = YES; + _autoCreate = YES; + _returnIDPCredential = YES; + } + return self; +} + +- (nullable id)unencodedHTTPRequestBodyWithError:(NSError *_Nullable *_Nullable)error { + NSURLComponents *components = [[NSURLComponents alloc] init]; + NSMutableArray *queryItems = [@[[NSURLQueryItem queryItemWithName:kProviderIDKey + value:_providerID]] + mutableCopy]; + + if (_providerIDToken) { + [queryItems addObject:[NSURLQueryItem queryItemWithName:kProviderIDTokenKey + value:_providerIDToken]]; + } + + if (_providerRawNonce) { + [queryItems addObject:[NSURLQueryItem queryItemWithName:kProviderNonceKey + value:_providerRawNonce]]; + } + + if (_providerAccessToken) { + [queryItems addObject:[NSURLQueryItem queryItemWithName:kProviderAccessTokenKey + value:_providerAccessToken]]; + } + + if (!_providerIDToken && !_providerAccessToken && !_pendingToken && !_requestURI) { + [NSException raise:NSInvalidArgumentException + format:@"One of IDToken, accessToken, pendingToken, or requestURI must be supplied."]; + } + + if (_providerOAuthTokenSecret) { + [queryItems addObject:[NSURLQueryItem queryItemWithName:kProviderOAuthTokenSecretKey + value:_providerOAuthTokenSecret]]; + } + + if (_inputEmail) { + [queryItems addObject:[NSURLQueryItem queryItemWithName:kIdentifierKey + value:_inputEmail]]; + } + [components setQueryItems:queryItems]; + NSMutableDictionary *body = [@{ + kRequestURIKey : _requestURI ?: @"http://localhost", // Unused by server, but required + kPostBodyKey : [components query] + } mutableCopy]; + + if (_pendingToken) { + body[kPendingTokenKey] = _pendingToken; + } + if (_accessToken) { + body[kIDTokenKey] = _accessToken; + } + if (_returnSecureToken) { + body[kReturnSecureTokenKey] = @YES; + } + + if (_returnIDPCredential) { + body[kReturnIDPCredentialKey] = @YES; + } + + if (_sessionID) { + body[kSessionIDKey] = _sessionID; + } + + body[kAutoCreateKey] = @(_autoCreate); + + return body; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyAssertionResponse.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyAssertionResponse.h new file mode 100644 index 0000000..295e2ff --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyAssertionResponse.h @@ -0,0 +1,211 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRVerifyAssertionResponse + @brief Represents the response from the verifyAssertion endpoint. + @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/verifyAssertion + */ +@interface FIRVerifyAssertionResponse : NSObject + +/** @property federatedID + @brief The unique ID identifies the IdP account. + */ +@property(nonatomic, strong, readonly, nullable) NSString *federatedID; + +/** @property providerID + @brief The IdP ID. For white listed IdPs it's a short domain name e.g. google.com, aol.com, + live.net and yahoo.com. If the "providerId" param is set to OpenID OP identifer other than + the whilte listed IdPs the OP identifier is returned. If the "identifier" param is federated + ID in the createAuthUri request. The domain part of the federated ID is returned. + */ +@property(nonatomic, strong, readonly, nullable) NSString *providerID; + +/** @property localID + @brief The RP local ID if it's already been mapped to the IdP account identified by the + federated ID. + */ +@property(nonatomic, strong, readonly, nullable) NSString *localID; + +/** @property email + @brief The email returned by the IdP. NOTE: The federated login user may not own the email. + */ +@property(nonatomic, strong, readonly, nullable) NSString *email; + +/** @property inputEmail + @brief It's the identifier param in the createAuthUri request if the identifier is an email. It + can be used to check whether the user input email is different from the asserted email. + */ +@property(nonatomic, strong, readonly, nullable) NSString *inputEmail; + +/** @property originalEmail + @brief The original email stored in the mapping storage. It's returned when the federated ID is + associated to a different email. + */ +@property(nonatomic, strong, readonly, nullable) NSString *originalEmail; + +/** @property oauthRequestToken + @brief The user approved request token for the OpenID OAuth extension. + */ +@property(nonatomic, strong, readonly, nullable) NSString *oauthRequestToken; + +/** @property oauthScope + @brief The scope for the OpenID OAuth extension. + */ +@property(nonatomic, strong, readonly, nullable) NSString *oauthScope; + +/** @property firstName + @brief The first name of the user. + */ +@property(nonatomic, strong, readonly, nullable) NSString *firstName; + +/** @property lastName + @brief The last name of the user. + */ +@property(nonatomic, strong, readonly, nullable) NSString *lastName; + +/** @property fullName + @brief The full name of the user. + */ +@property(nonatomic, strong, readonly, nullable) NSString *fullName; + +/** @property nickName + @brief The nick name of the user. + */ +@property(nonatomic, strong, readonly, nullable) NSString *nickName; + +/** @property displayName + @brief The display name of the user. + */ +@property(nonatomic, strong, readonly, nullable) NSString *displayName; + +/** @property IDToken + @brief Either an authorization code suitable for performing an STS token exchange, or the + access token from Secure Token Service, depending on whether @c returnSecureToken is set + on the request. + */ +@property(nonatomic, strong, readonly, nullable) NSString *IDToken; + +/** @property approximateExpirationDate + @brief The approximate expiration date of the access token. + */ +@property(nonatomic, copy, readonly, nullable) NSDate *approximateExpirationDate; + +/** @property refreshToken + @brief The refresh token from Secure Token Service. + */ +@property(nonatomic, strong, readonly, nullable) NSString *refreshToken; + +/** @property action + @brief The action code. + */ +@property(nonatomic, strong, readonly, nullable) NSString *action; + +/** @property language + @brief The language preference of the user. + */ +@property(nonatomic, strong, readonly, nullable) NSString *language; + +/** @property timeZone + @brief The timezone of the user. + */ +@property(nonatomic, strong, readonly, nullable) NSString *timeZone; + +/** @property photoURL + @brief The URI of the public accessible profile picture. + */ +@property(nonatomic, strong, readonly, nullable) NSURL *photoURL; + +/** @property dateOfBirth + @brief The birth date of the IdP account. + */ +@property(nonatomic, strong, readonly, nullable) NSString *dateOfBirth; + +/** @property context + @brief The opaque value used by the client to maintain context info between the authentication + request and the IDP callback. + */ +@property(nonatomic, strong, readonly, nullable) NSString *context; + +/** @property verifiedProvider + @brief When action is 'map', contains the idps which can be used for confirmation. + */ +@property(nonatomic, strong, readonly, nullable) NSArray *verifiedProvider; + +/** @property needConfirmation + @brief Whether the assertion is from a non-trusted IDP and need account linking confirmation. + */ +@property(nonatomic, assign) BOOL needConfirmation; + +/** @property emailRecycled + @brief It's true if the email is recycled. + */ +@property(nonatomic, assign) BOOL emailRecycled; + +/** @property emailVerified + @brief The value is true if the IDP is also the email provider. It means the user owns the + email. + */ +@property(nonatomic, assign) BOOL emailVerified; + +/** @property isNewUser + @brief Flag indicating that the user signing in is a new user and not a returning user. + */ +@property(nonatomic, assign) BOOL isNewUser; + +/** @property profile + @brief Dictionary containing the additional IdP specific information. + */ +@property(nonatomic, readonly, nullable) NSDictionary *profile; + +/** @property username + @brief The name of the user. + */ +@property(nonatomic, strong, readonly, nullable) NSString *username; + +/** @property oauthIDToken + @brief The ID token for the OpenID OAuth extension. + */ +@property(nonatomic, strong, readonly, nullable) NSString *oauthIDToken; + +/** @property oauthExpirationDate + @brief The approximate expiration date of the oauth access token. + */ +@property(nonatomic, copy, readonly, nullable) NSDate *oauthExpirationDate; + +/** @property oauthAccessToken + @brief The access token for the OpenID OAuth extension. + */ +@property(nonatomic, strong, readonly, nullable) NSString *oauthAccessToken; + +/** @property oauthSecretToken + @brief The secret for the OpenID OAuth extention. + */ +@property(nonatomic, readonly, nullable) NSString *oauthSecretToken; + +/** @property pendingToken + @brief The pending ID Token string. + */ +@property(nonatomic, copy, nullable) NSString *pendingToken; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyAssertionResponse.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyAssertionResponse.m new file mode 100644 index 0000000..a5f23d5 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyAssertionResponse.m @@ -0,0 +1,86 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRVerifyAssertionResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRVerifyAssertionResponse + +- (BOOL)setWithDictionary:(NSDictionary *)dictionary + error:(NSError *_Nullable *_Nullable)error { + _federatedID = [dictionary[@"federatedId"] copy]; + _providerID = [dictionary[@"providerId"] copy]; + _localID = [dictionary[@"localId"] copy]; + _emailRecycled = [dictionary[@"emailRecycled"] boolValue]; + _emailVerified = [dictionary[@"emailVerified"] boolValue]; + _email = [dictionary[@"email"] copy]; + _inputEmail = [dictionary[@"inputEmail"] copy]; + _originalEmail = [dictionary[@"originalEmail"] copy]; + _oauthRequestToken = [dictionary[@"oauthRequestToken"] copy]; + _oauthScope = [dictionary[@"oauthScope"] copy]; + _firstName = [dictionary[@"firstName"] copy]; + _lastName = [dictionary[@"lastName"] copy]; + _fullName = [dictionary[@"fullName"] copy]; + _nickName = [dictionary[@"nickName"] copy]; + _displayName = [dictionary[@"displayName"] copy]; + _IDToken = [dictionary[@"idToken"] copy]; + _approximateExpirationDate = [dictionary[@"expiresIn"] isKindOfClass:[NSString class]] ? + [NSDate dateWithTimeIntervalSinceNow:[dictionary[@"expiresIn"] doubleValue]] : nil; + _refreshToken = [dictionary[@"refreshToken"] copy]; + _isNewUser = [dictionary[@"isNewUser"] boolValue]; + id rawUserInfo = dictionary[@"rawUserInfo"]; + if ([rawUserInfo isKindOfClass:[NSString class]]) { + NSData *data = [rawUserInfo dataUsingEncoding:NSUTF8StringEncoding]; + rawUserInfo = [NSJSONSerialization JSONObjectWithData:data + options:NSJSONReadingMutableLeaves + error:nil]; + } + if ([rawUserInfo isKindOfClass:[NSDictionary class]]) { + _profile = [[NSDictionary alloc] initWithDictionary:rawUserInfo + copyItems:YES]; + } + _username = [dictionary[@"username"] copy]; + _action = [dictionary[@"action"] copy]; + _language = [dictionary[@"language"] copy]; + _timeZone = [dictionary[@"timeZone"] copy]; + _photoURL = dictionary[@"photoUrl"] ? [NSURL URLWithString:dictionary[@"photoUrl"]] : nil; + _dateOfBirth = [dictionary[@"dateOfBirth"] copy]; + _context = [dictionary[@"context"] copy]; + _needConfirmation = [dictionary[@"needConfirmation"] boolValue]; + id verifiedProvider = dictionary[@"verifiedProvider"]; + if ([verifiedProvider isKindOfClass:[NSString class]]) { + NSData *data = [verifiedProvider dataUsingEncoding:NSUTF8StringEncoding]; + verifiedProvider = [NSJSONSerialization JSONObjectWithData:data + options:NSJSONReadingMutableLeaves + error:nil]; + } + if ([verifiedProvider isKindOfClass:[NSArray class]]) { + _verifiedProvider = [[NSArray alloc] initWithArray:verifiedProvider + copyItems:YES]; + } + _oauthIDToken = [dictionary[@"oauthIdToken"] copy]; + _oauthExpirationDate = [dictionary[@"oauthExpireIn"] isKindOfClass:[NSString class]] ? + [NSDate dateWithTimeIntervalSinceNow:[dictionary[@"oauthExpireIn"] doubleValue]] : nil; + _oauthAccessToken = [dictionary[@"oauthAccessToken"] copy]; + _oauthSecretToken = [dictionary[@"oauthTokenSecret"] copy]; + _pendingToken = [dictionary[@"pendingToken"] copy]; + return YES; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyClientRequest.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyClientRequest.h new file mode 100644 index 0000000..a235788 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyClientRequest.h @@ -0,0 +1,56 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRIdentityToolkitRequest.h" + +#import "FIRAuthRPCRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRVerifyClientRequest : FIRIdentityToolkitRequest + +/** @property appToken + @brief The APNS device token. + */ +@property(nonatomic, readonly, nullable) NSString *appToken; + +/** @property isSandbox + @brief The flag that denotes if the appToken pertains to Sandbox or Production. + */ +@property(nonatomic, assign, readonly) BOOL isSandbox; + +/** @fn initWithEndpoint:requestConfiguration: + @brief Please use initWithToken:requestConfiguration: instead. + */ +- (nullable instancetype)initWithEndpoint:(NSString *)endpoint + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_UNAVAILABLE; + +/** @fn initWithAppToken:isSandbox:requestConfiguration: + @brief Designated initializer. + @param appToken The APNS device token. + @param isSandbox The flag indicating whether or not the app token provided is for Sandbox or + Production. + @param requestConfiguration An object containing configurations to be added to the request. + */ +- (nullable instancetype)initWithAppToken:(nullable NSString *)appToken + isSandbox:(BOOL)isSandbox + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyClientRequest.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyClientRequest.m new file mode 100644 index 0000000..101f4ef --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyClientRequest.m @@ -0,0 +1,63 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRVerifyClientRequest.h" + + +NS_ASSUME_NONNULL_BEGIN + +/** @var kVerifyClientEndpoint + @brief The endpoint for the verifyClient request. + */ +static NSString *const kVerifyClientEndpoint = @"verifyClient"; + +/** @var kAppTokenKey + @brief The key for the appToken request paramenter. + */ +static NSString *const kAPPTokenKey = @"appToken"; + +/** @var kIsSandboxKey + @brief The key for the isSandbox request parameter + */ +static NSString *const kIsSandboxKey = @"isSandbox"; + +@implementation FIRVerifyClientRequest + +- (nullable instancetype)initWithAppToken:(nullable NSString *)appToken + isSandbox:(BOOL)isSandbox + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration { + self = [super initWithEndpoint:kVerifyClientEndpoint requestConfiguration:requestConfiguration]; + if (self) { + _appToken = appToken; + _isSandbox = isSandbox; + } + return self; +} + +- (nullable id)unencodedHTTPRequestBodyWithError:(NSError *__autoreleasing _Nullable *)error { + NSMutableDictionary *postBody = [NSMutableDictionary dictionary]; + if (_appToken) { + postBody[kAPPTokenKey] = _appToken; + } + if (_isSandbox) { + postBody[kIsSandboxKey] = @YES; + } + return postBody; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyClientResponse.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyClientResponse.h new file mode 100644 index 0000000..794256a --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyClientResponse.h @@ -0,0 +1,38 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRVerifyClientResponse : NSObject + +/** @property receipt + @brief Receipt that the APNS token was successfully validated with APNS. + */ +@property(nonatomic, copy, readonly, nullable) NSString *receipt; + +/** @property suggestedTimeOut + @brief The date after which delivery of the silent push notification is considered to have + failed. + */ +@property(nonatomic, copy, readonly, nullable) NSDate *suggestedTimeOutDate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyClientResponse.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyClientResponse.m new file mode 100644 index 0000000..c2477d2 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyClientResponse.m @@ -0,0 +1,33 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRVerifyClientResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRVerifyClientResponse + +- (BOOL)setWithDictionary:(NSDictionary *)dictionary + error:(NSError *_Nullable *_Nullable)error { + _receipt = dictionary[@"receipt"]; + _suggestedTimeOutDate = [dictionary[@"suggestedTimeout"] isKindOfClass:[NSString class]] ? + [NSDate dateWithTimeIntervalSinceNow:[dictionary[@"suggestedTimeout"] doubleValue]] : nil; + return YES; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyCustomTokenRequest.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyCustomTokenRequest.h new file mode 100644 index 0000000..84bad05 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyCustomTokenRequest.h @@ -0,0 +1,57 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCRequest.h" +#import "FIRIdentityToolkitRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRVerifyCustomTokenRequest + @brief Represents the parameters for the verifyCustomToken endpoint. + */ +@interface FIRVerifyCustomTokenRequest : FIRIdentityToolkitRequest + +/** @property token + @brief The self-signed token from the client's BYOAuth server. + */ +@property(nonatomic, copy, readonly) NSString *token; + +/** @property returnSecureToken + @brief Whether the response should return access token and refresh token directly. + @remarks The default value is @c YES . + */ +@property(nonatomic, assign) BOOL returnSecureToken; + +/** @fn initWithEndpoint:requestConfiguration: + @brief Please use initWithToken:requestConfiguration: instead. + */ +- (nullable instancetype)initWithEndpoint:(NSString *)endpoint requestConfiguration: + (FIRAuthRequestConfiguration *)requestConfiguration NS_UNAVAILABLE; + +/** @fn initWithToken:requestConfiguration: + @brief Designated initializer. + @param token The self-signed token from the client's BYOAuth server. + @param requestConfiguration An object containing configurations to be added to the request. + */ +- (nullable instancetype)initWithToken:(NSString *)token + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyCustomTokenRequest.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyCustomTokenRequest.m new file mode 100644 index 0000000..9ad46a0 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyCustomTokenRequest.m @@ -0,0 +1,61 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRVerifyCustomTokenRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kVerifyCustomTokenEndpoint + @brief The "verifyPassword" endpoint. + */ +static NSString *const kVerifyCustomTokenEndpoint = @"verifyCustomToken"; + +/** @var kTokenKey + @brief The key for the "token" value in the request. + */ +static NSString *const kTokenKey = @"token"; + +/** @var kReturnSecureTokenKey + @brief The key for the "returnSecureToken" value in the request. + */ +static NSString *const kReturnSecureTokenKey = @"returnSecureToken"; + +@implementation FIRVerifyCustomTokenRequest + +- (nullable instancetype)initWithToken:(NSString *)token + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration { + self = [super initWithEndpoint:kVerifyCustomTokenEndpoint + requestConfiguration:requestConfiguration]; + if (self) { + _token = [token copy]; + _returnSecureToken = YES; + } + return self; +} + +- (nullable id)unencodedHTTPRequestBodyWithError:(NSError *_Nullable *_Nullable)error { + NSMutableDictionary *body = [@{ + kTokenKey : _token + } mutableCopy]; + if (_returnSecureToken) { + body[kReturnSecureTokenKey] = @YES; + } + return body; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyCustomTokenResponse.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyCustomTokenResponse.h new file mode 100644 index 0000000..6957bf3 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyCustomTokenResponse.h @@ -0,0 +1,52 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRVerifyCustomTokenResponse + @brief Represents the response from the verifyCustomToken endpoint. + */ +@interface FIRVerifyCustomTokenResponse : NSObject + +/** @property IDToken + @brief Either an authorization code suitable for performing an STS token exchange, or the + access token from Secure Token Service, depending on whether @c returnSecureToken is set + on the request. + */ +@property(nonatomic, strong, readonly, nullable) NSString *IDToken; + +/** @property approximateExpirationDate + @brief The approximate expiration date of the access token. + */ +@property(nonatomic, copy, readonly, nullable) NSDate *approximateExpirationDate; + +/** @property refreshToken + @brief The refresh token from Secure Token Service. + */ +@property(nonatomic, strong, readonly, nullable) NSString *refreshToken; + +/** @property isNewUser + @brief Flag indicating that the user signing in is a new user and not a returning user. + */ +@property(nonatomic, assign) BOOL isNewUser; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyCustomTokenResponse.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyCustomTokenResponse.m new file mode 100644 index 0000000..8b67360 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyCustomTokenResponse.m @@ -0,0 +1,35 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRVerifyCustomTokenResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRVerifyCustomTokenResponse + +- (BOOL)setWithDictionary:(NSDictionary *)dictionary + error:(NSError *_Nullable *_Nullable)error { + _IDToken = [dictionary[@"idToken"] copy]; + _approximateExpirationDate = [dictionary[@"expiresIn"] isKindOfClass:[NSString class]] ? + [NSDate dateWithTimeIntervalSinceNow:[dictionary[@"expiresIn"] doubleValue]] : nil; + _refreshToken = [dictionary[@"refreshToken"] copy]; + _isNewUser = [dictionary[@"isNewUser"] boolValue]; + return YES; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyPasswordRequest.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyPasswordRequest.h new file mode 100644 index 0000000..39eb388 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyPasswordRequest.h @@ -0,0 +1,81 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCRequest.h" +#import "FIRIdentityToolkitRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRVerifyPasswordRequest + @brief Represents the parameters for the verifyPassword endpoint. + @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/verifyPassword + */ +@interface FIRVerifyPasswordRequest : FIRIdentityToolkitRequest + +/** @property email + @brief The email of the user. + */ +@property(nonatomic, copy) NSString *email; + +/** @property password + @brief The password inputed by the user. + */ +@property(nonatomic, copy) NSString *password; + +/** @property pendingIDToken + @brief The GITKit token for the non-trusted IDP, which is to be confirmed by the user. + */ +@property(nonatomic, copy, nullable) NSString *pendingIDToken; + +/** @property captchaChallenge + @brief The captcha challenge. + */ +@property(nonatomic, copy, nullable) NSString *captchaChallenge; + +/** @property captchaResponse + @brief Response to the captcha. + */ +@property(nonatomic, copy, nullable) NSString *captchaResponse; + +/** @property returnSecureToken + @brief Whether the response should return access token and refresh token directly. + @remarks The default value is @c YES . + */ +@property(nonatomic, assign) BOOL returnSecureToken; + +/** @fn initWithEndpoint:requestConfiguration: + @brief Please use initWithEmail:password:requestConfiguration: + */ +- (nullable instancetype)initWithEndpoint:(NSString *)endpoint + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_UNAVAILABLE; + +/** @fn initWithEmail:password:requestConfiguration: + @brief Designated initializer. + @param email The email of the user. + @param password The password inputed by the user. + @param requestConfiguration The configu + */ +- (nullable instancetype)initWithEmail:(NSString *)email + password:(NSString *)password + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyPasswordRequest.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyPasswordRequest.m new file mode 100644 index 0000000..5849da6 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyPasswordRequest.m @@ -0,0 +1,96 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRVerifyPasswordRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kVerifyPasswordEndpoint + @brief The "verifyPassword" endpoint. + */ +static NSString *const kVerifyPasswordEndpoint = @"verifyPassword"; + +/** @var kEmailKey + @brief The key for the "email" value in the request. + */ +static NSString *const kEmailKey = @"email"; + +/** @var kPasswordKey + @brief The key for the "password" value in the request. + */ +static NSString *const kPasswordKey = @"password"; + +/** @var kPendingIDTokenKey + @brief The key for the "pendingIdToken" value in the request. + */ +static NSString *const kPendingIDTokenKey = @"pendingIdToken"; + +/** @var kCaptchaChallengeKey + @brief The key for the "captchaChallenge" value in the request. + */ +static NSString *const kCaptchaChallengeKey = @"captchaChallenge"; + +/** @var kCaptchaResponseKey + @brief The key for the "captchaResponse" value in the request. + */ +static NSString *const kCaptchaResponseKey = @"captchaResponse"; + +/** @var kReturnSecureTokenKey + @brief The key for the "returnSecureToken" value in the request. + */ +static NSString *const kReturnSecureTokenKey = @"returnSecureToken"; + +@implementation FIRVerifyPasswordRequest + +- (nullable instancetype)initWithEmail:(NSString *)email + password:(NSString *)password + requestConfiguration:(nonnull FIRAuthRequestConfiguration *)requestConfiguration { + self = [super initWithEndpoint:kVerifyPasswordEndpoint + requestConfiguration:requestConfiguration]; + if (self) { + _email = [email copy]; + _password = [password copy]; + _returnSecureToken = YES; + } + return self; +} + +- (nullable id)unencodedHTTPRequestBodyWithError:(NSError *_Nullable *_Nullable)error { + NSMutableDictionary *postBody = [NSMutableDictionary dictionary]; + if (_email) { + postBody[kEmailKey] = _email; + } + if (_password) { + postBody[kPasswordKey] = _password; + } + if (_pendingIDToken) { + postBody[kPendingIDTokenKey] = _pendingIDToken; + } + if (_captchaChallenge) { + postBody[kCaptchaChallengeKey] = _captchaChallenge; + } + if (_captchaResponse) { + postBody[kCaptchaResponseKey] = _captchaResponse; + } + if (_returnSecureToken) { + postBody[kReturnSecureTokenKey] = @YES; + } + return postBody; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyPasswordResponse.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyPasswordResponse.h new file mode 100644 index 0000000..bed13be --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyPasswordResponse.h @@ -0,0 +1,72 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRVerifyPasswordResponse + @brief Represents the response from the verifyPassword endpoint. + @remarks Possible error codes: + - FIRAuthInternalErrorCodeUserDisabled + - FIRAuthInternalErrorCodeEmailNotFound + @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/verifyPassword + */ +@interface FIRVerifyPasswordResponse : NSObject + +/** @property localID + @brief The RP local ID if it's already been mapped to the IdP account identified by the + federated ID. + */ +@property(nonatomic, strong, readonly, nullable) NSString *localID; + +/** @property email + @brief The email returned by the IdP. NOTE: The federated login user may not own the email. + */ +@property(nonatomic, strong, readonly, nullable) NSString *email; + +/** @property displayName + @brief The display name of the user. + */ +@property(nonatomic, strong, readonly, nullable) NSString *displayName; + +/** @property IDToken + @brief Either an authorization code suitable for performing an STS token exchange, or the + access token from Secure Token Service, depending on whether @c returnSecureToken is set + on the request. + */ +@property(nonatomic, strong, readonly, nullable) NSString *IDToken; + +/** @property approximateExpirationDate + @brief The approximate expiration date of the access token. + */ +@property(nonatomic, copy, readonly, nullable) NSDate *approximateExpirationDate; + +/** @property refreshToken + @brief The refresh token from Secure Token Service. + */ +@property(nonatomic, strong, readonly, nullable) NSString *refreshToken; + +/** @property photoURL + @brief The URI of the public accessible profile picture. + */ +@property(nonatomic, strong, readonly, nullable) NSURL *photoURL; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyPasswordResponse.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyPasswordResponse.m new file mode 100644 index 0000000..b42a371 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyPasswordResponse.m @@ -0,0 +1,38 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRVerifyPasswordResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRVerifyPasswordResponse + +- (BOOL)setWithDictionary:(NSDictionary *)dictionary + error:(NSError *_Nullable *_Nullable)error { + _localID = [dictionary[@"localId"] copy]; + _email = [dictionary[@"email"] copy]; + _displayName = [dictionary[@"displayName"] copy]; + _IDToken = [dictionary[@"idToken"] copy]; + _approximateExpirationDate = [dictionary[@"expiresIn"] isKindOfClass:[NSString class]] ? + [NSDate dateWithTimeIntervalSinceNow:[dictionary[@"expiresIn"] doubleValue]] : nil; + _refreshToken = [dictionary[@"refreshToken"] copy]; + _photoURL = dictionary[@"photoUrl"] ? [NSURL URLWithString:dictionary[@"photoUrl"]] : nil; + return YES; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyPhoneNumberRequest.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyPhoneNumberRequest.h new file mode 100644 index 0000000..07988f1 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyPhoneNumberRequest.h @@ -0,0 +1,91 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRIdentityToolkitRequest.h" + +#import "FIRAuthOperationType.h" +#import "FIRAuthRPCRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRVerifyPhoneNumberRequest : FIRIdentityToolkitRequest + +/** @property verificationID + @brief The verification ID obtained from the response of @c sendVerificationCode. +*/ +@property(nonatomic, readonly, nullable) NSString *verificationID; + +/** @property verificationCode + @brief The verification code provided by the user. +*/ +@property(nonatomic, readonly, nullable) NSString *verificationCode; + +/** @property accessToken + @brief The STS Access Token for the authenticated user. + */ +@property(nonatomic, copy, nullable) NSString *accessToken; + +/** @var temporaryProof + @brief The temporary proof code, previously returned from the backend. + */ +@property(nonatomic, readonly, nonnull) NSString *temporaryProof; + +/** @var phoneNumber + @brief The phone number to be verified in the request. + */ +@property(nonatomic, readonly, nonnull) NSString *phoneNumber; + +/** @var operation + @brief The type of operation triggering this verify phone number request. + */ +@property(nonatomic, assign, readonly) FIRAuthOperationType operation; + +/** @fn initWithEndpoint:requestConfiguration: + @brief Please use initWithVerificationID:verificationCode:requestConfiguration + */ +- (nullable instancetype)initWithEndpoint:(NSString *)endpoint + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_UNAVAILABLE; + +/** @fn initWithTemporaryProof:phoneNumberAPIKey + @brief Designated initializer. + @param temporaryProof The temporary proof sent by the backed. + @param phoneNumber The phone number associated with the credential to be signed in. + @param operation Indicates what operation triggered the verify phone number request. + @param requestConfiguration An object containing configurations to be added to the request. + */ +- (nullable instancetype)initWithTemporaryProof:(NSString *)temporaryProof + phoneNumber:(NSString *)phoneNumber + operation:(FIRAuthOperationType)operation + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_DESIGNATED_INITIALIZER; + +/** @fn initWithVerificationID:verificationCode:requestConfiguration + @brief Designated initializer. + @param verificationID The verification ID obtained from the response of @c sendVerificationCode. + @param verificationCode The verification code provided by the user. + @param operation Indicates what operation triggered the verify phone number request. + @param requestConfiguration An object containing configurations to be added to the request. + */ +- (nullable instancetype)initWithVerificationID:(NSString *)verificationID + verificationCode:(NSString *)verificationCode + operation:(FIRAuthOperationType)operation + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyPhoneNumberRequest.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyPhoneNumberRequest.m new file mode 100644 index 0000000..022ab9e --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyPhoneNumberRequest.m @@ -0,0 +1,133 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRVerifyPhoneNumberRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kVerifyPhoneNumberEndPoint + @brief The "verifyPhoneNumber" endpoint. + */ +static NSString *const kVerifyPhoneNumberEndPoint = @"verifyPhoneNumber"; + +/** @var kVerificationIDKey + @brief The key for the verification ID parameter in the request. + */ +static NSString *const kVerificationIDKey = @"sessionInfo"; + +/** @var kVerificationCodeKey + @brief The key for the verification code parameter in the request. + */ +static NSString *const kVerificationCodeKey = @"code"; + +/** @var kIDTokenKey + @brief The key for the "ID Token" value in the request. + */ +static NSString *const kIDTokenKey = @"idToken"; + +/** @var kTemporaryProofKey + @brief The key for the temporary proof value in the request. + */ +static NSString *const kTemporaryProofKey = @"temporaryProof"; + +/** @var kPhoneNumberKey + @brief The key for the phone number value in the request. + */ +static NSString *const kPhoneNumberKey = @"phoneNumber"; + +/** @var kOperationKey + @brief The key for the operation value in the request. + */ +static NSString *const kOperationKey = @"operation"; + +@implementation FIRVerifyPhoneNumberRequest + +- (nullable instancetype)initWithTemporaryProof:(NSString *)temporaryProof + phoneNumber:(NSString *)phoneNumber + operation:(FIRAuthOperationType)operation + requestConfiguration: + (FIRAuthRequestConfiguration *)requestConfiguration { + self = [super initWithEndpoint:kVerifyPhoneNumberEndPoint + requestConfiguration:requestConfiguration]; + if (self) { + _temporaryProof = [temporaryProof copy]; + _phoneNumber = [phoneNumber copy]; + _operation = operation; + } + return self; +} + +- (nullable instancetype)initWithVerificationID:(NSString *)verificationID + verificationCode:(NSString *)verificationCode + operation:(FIRAuthOperationType)operation + requestConfiguration: + (FIRAuthRequestConfiguration *)requestConfiguration { + self = [super initWithEndpoint:kVerifyPhoneNumberEndPoint + requestConfiguration:requestConfiguration]; + if (self) { + _verificationID = verificationID; + _verificationCode = verificationCode; + _operation = operation; + } + return self; +} + +/** @fn FIRAuthOperationString + @brief Returns a string object corresponding to the provided FIRAuthOperationType value. + @param operationType The value of the FIRAuthOperationType enum which will be translated to its + corresponding string value. + @return The string value corresponding to the FIRAuthOperationType argument. + */ +NSString *const FIRAuthOperationString(FIRAuthOperationType operationType) { + switch(operationType){ + case FIRAuthOperationTypeUnspecified: + return @"VERIFY_OP_UNSPECIFIED"; + case FIRAuthOperationTypeSignUpOrSignIn: + return @"SIGN_UP_OR_IN"; + case FIRAuthOperationTypeReauth: + return @"REAUTH"; + case FIRAuthOperationTypeLink: + return @"LINK"; + case FIRAuthOperationTypeUpdate: + return @"UPDATE"; + } +} + +- (nullable id)unencodedHTTPRequestBodyWithError:(NSError *__autoreleasing _Nullable *)error { + NSMutableDictionary *postBody = [NSMutableDictionary dictionary]; + if (_verificationID) { + postBody[kVerificationIDKey] = _verificationID; + } + if (_verificationCode) { + postBody[kVerificationCodeKey] = _verificationCode; + } + if (_accessToken) { + postBody[kIDTokenKey] = _accessToken; + } + if (_temporaryProof) { + postBody[kTemporaryProofKey] = _temporaryProof; + } + if (_phoneNumber) { + postBody[kPhoneNumberKey] = _phoneNumber; + } + NSString *operation = FIRAuthOperationString(_operation); + postBody[kOperationKey] = operation; + return postBody; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyPhoneNumberResponse.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyPhoneNumberResponse.h new file mode 100644 index 0000000..b0ba5dd --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyPhoneNumberResponse.h @@ -0,0 +1,64 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRVerifyPhoneNumberResponse : NSObject + +/** @property IDToken + @brief Either an authorization code suitable for performing an STS token exchange, or the + access token from Secure Token Service, depending on whether @c returnSecureToken is set + on the request. + */ +@property(nonatomic, strong, readonly, nullable) NSString *IDToken; + +/** @property refreshToken + @brief The refresh token from Secure Token Service. + */ +@property(nonatomic, strong, readonly, nullable) NSString *refreshToken; + +/** @property localID + @brief The Firebase Auth user ID. + */ +@property(nonatomic, strong, readonly, nullable) NSString *localID; + +/** @property phoneNumber + @brief The verified phone number. + */ +@property(nonatomic, strong, readonly, nullable) NSString *phoneNumber; + +/** @property temporaryProof + @brief The temporary proof code returned by the backend. + */ +@property(nonatomic, strong, readonly, nullable) NSString *temporaryProof; + +/** @property isNewUser + @brief Flag indicating that the user signing in is a new user and not a returning user. + */ +@property(nonatomic, assign) BOOL isNewUser; + +/** @property approximateExpirationDate + @brief The approximate expiration date of the access token. + */ +@property(nonatomic, copy, readonly, nullable) NSDate *approximateExpirationDate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyPhoneNumberResponse.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyPhoneNumberResponse.m new file mode 100644 index 0000000..acba2c2 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Backend/RPC/FIRVerifyPhoneNumberResponse.m @@ -0,0 +1,42 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRVerifyPhoneNumberResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRVerifyPhoneNumberResponse + +- (nullable NSString *)expectedKind { + return nil; +} + +- (BOOL)setWithDictionary:(NSDictionary *)dictionary + error:(NSError *_Nullable *_Nullable)error { + _IDToken = [dictionary[@"idToken"] copy]; + _refreshToken = [dictionary[@"refreshToken"] copy]; + _isNewUser = [dictionary[@"isNewUser"] boolValue]; + _localID = [dictionary[@"localId"] copy]; + _phoneNumber = [dictionary[@"phoneNumber"] copy]; + _temporaryProof = [dictionary[@"temporaryProof"] copy]; + _approximateExpirationDate = [dictionary[@"expiresIn"] isKindOfClass:[NSString class]] ? + [NSDate dateWithTimeIntervalSinceNow:[dictionary[@"expiresIn"] doubleValue]] : nil; + return YES; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/FirebaseAuthVersion.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/FirebaseAuthVersion.m new file mode 100644 index 0000000..4893018 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/FirebaseAuthVersion.m @@ -0,0 +1,25 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FirebaseAuthVersion.h" + +// Convert the macro to a string +#define STR(x) STR_EXPAND(x) +#define STR_EXPAND(x) #x + +const double FirebaseAuthVersionNum = FIRAuth_MINOR_VERSION; + +const char *const FirebaseAuthVersionStr = (const char *const)STR(FIRAuth_VERSION); diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRActionCodeSettings.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRActionCodeSettings.h new file mode 100644 index 0000000..b903d8d --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRActionCodeSettings.h @@ -0,0 +1,89 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #import + + NS_ASSUME_NONNULL_BEGIN + + /** @class FIRActionCodeSettings + @brief Used to set and retrieve settings related to handling action codes. + */ + NS_SWIFT_NAME(ActionCodeSettings) + @interface FIRActionCodeSettings : NSObject + + /** @property URL + @brief This URL represents the state/Continue URL in the form of a universal link. + @remarks This URL can should be constructed as a universal link that would either directly open + the app where the action code would be handled or continue to the app after the action code + is handled by Firebase. + */ + @property(nonatomic, copy, nullable) NSURL *URL; + + /** @property handleCodeInApp + @brief Indicates whether the action code link will open the app directly or after being + redirected from a Firebase owned web widget. + */ + @property(assign, nonatomic) BOOL handleCodeInApp; + + /** @property iOSBundleID + @brief The iOS bundle ID, if available. The default value is the current app's bundle ID. + */ + @property(copy, nonatomic, readonly, nullable) NSString *iOSBundleID; + + /** @property androidPackageName + @brief The Android package name, if available. + */ + @property(nonatomic, copy, readonly, nullable) NSString *androidPackageName; + + /** @property androidMinimumVersion + @brief The minimum Android version supported, if available. + */ + @property(nonatomic, copy, readonly, nullable) NSString *androidMinimumVersion; + + /** @property androidInstallIfNotAvailable + @brief Indicates whether the Android app should be installed on a device where it is not + available. + */ + @property(nonatomic, assign, readonly) BOOL androidInstallIfNotAvailable; + + /** @property dynamicLinkDomain + @brief The Firebase Dynamic Link domain used for out of band code flow. + */ + @property(copy, nonatomic, nullable) NSString *dynamicLinkDomain; + + /** @fn setIOSBundleID + @brief Sets the iOS bundle Id. + @param iOSBundleID The iOS bundle ID. + */ + - (void)setIOSBundleID:(NSString *)iOSBundleID; + + /** @fn setAndroidPackageName:installIfNotAvailable:minimumVersion: + @brief Sets the Android package name, the flag to indicate whether or not to install the app + and the minimum Android version supported. + @param androidPackageName The Android package name. + @param installIfNotAvailable Indicates whether or not the app should be installed if not + available. + @param minimumVersion The minimum version of Android supported. + @remarks If installIfNotAvailable is set to YES and the link is opened on an android device, it + will try to install the app if not already available. Otherwise the web URL is used. + */ + - (void)setAndroidPackageName:(NSString *)androidPackageName + installIfNotAvailable:(BOOL)installIfNotAvailable + minimumVersion:(nullable NSString *)minimumVersion; + + @end + + NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRAdditionalUserInfo.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRAdditionalUserInfo.h new file mode 100644 index 0000000..4f6947a --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRAdditionalUserInfo.h @@ -0,0 +1,57 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FIRVerifyAssertionResponse; + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRAdditionalUserInfo + @brief Represents additional user data returned from an identity provider. + */ +NS_SWIFT_NAME(AdditionalUserInfo) +@interface FIRAdditionalUserInfo : NSObject + +/** @fn init + @brief This class should not be initialized manually. `FIRAdditionalUserInfo` can be retrieved + from from an instance of `FIRAuthDataResult`. + */ +- (instancetype)init NS_UNAVAILABLE; + +/** @property providerID + @brief The provider identifier. + */ +@property(nonatomic, readonly) NSString *providerID; + +/** @property profile + @brief Dictionary containing the additional IdP specific information. + */ +@property(nonatomic, readonly, nullable) NSDictionary *profile; + +/** @property username + @brief username The name of the user. + */ +@property(nonatomic, readonly, nullable) NSString *username; + +/** @property newUser + @brief Indicates whether or not the current user was signed in for the first time. + */ +@property(nonatomic, readonly, getter=isNewUser) BOOL newUser; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRAuth.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRAuth.h new file mode 100644 index 0000000..e938751 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRAuth.h @@ -0,0 +1,826 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import +#import + +#import "FIRAuthErrors.h" + +#if TARGET_OS_IOS +#import "FIRAuthAPNSTokenType.h" +#endif + +@class FIRActionCodeSettings; +@class FIRApp; +@class FIRAuth; +@class FIRAuthCredential; +@class FIRAuthDataResult; +@class FIRAuthSettings; +@class FIRUser; +@protocol FIRAuthStateListener; +@protocol FIRAuthUIDelegate; +@protocol FIRFederatedAuthProvider; + +NS_ASSUME_NONNULL_BEGIN + +/** @typedef FIRUserUpdateCallback + @brief The type of block invoked when a request to update the current user is completed. + */ +typedef void (^FIRUserUpdateCallback)(NSError *_Nullable error) + NS_SWIFT_NAME(UserUpdateCallback); + +/** @typedef FIRAuthStateDidChangeListenerHandle + @brief The type of handle returned by `FIRAuth.addAuthStateDidChangeListener:`. + */ +typedef id FIRAuthStateDidChangeListenerHandle + NS_SWIFT_NAME(AuthStateDidChangeListenerHandle); + +/** @typedef FIRAuthStateDidChangeListenerBlock + @brief The type of block which can be registered as a listener for auth state did change events. + + @param auth The FIRAuth object on which state changes occurred. + @param user Optionally; the current signed in user, if any. + */ +typedef void(^FIRAuthStateDidChangeListenerBlock)(FIRAuth *auth, FIRUser *_Nullable user) + NS_SWIFT_NAME(AuthStateDidChangeListenerBlock); + +/** @typedef FIRIDTokenDidChangeListenerHandle + @brief The type of handle returned by `FIRAuth.addIDTokenDidChangeListener:`. + */ +typedef id FIRIDTokenDidChangeListenerHandle + NS_SWIFT_NAME(IDTokenDidChangeListenerHandle); + +/** @typedef FIRIDTokenDidChangeListenerBlock + @brief The type of block which can be registered as a listener for ID token did change events. + + @param auth The FIRAuth object on which ID token changes occurred. + @param user Optionally; the current signed in user, if any. + */ +typedef void(^FIRIDTokenDidChangeListenerBlock)(FIRAuth *auth, FIRUser *_Nullable user) + NS_SWIFT_NAME(IDTokenDidChangeListenerBlock); + +/** @typedef FIRAuthDataResultCallback + @brief The type of block invoked when sign-in related events complete. + + @param authResult Optionally; Result of sign-in request containing both the user and + the additional user info associated with the user. + @param error Optionally; the error which occurred - or nil if the request was successful. + */ +typedef void (^FIRAuthDataResultCallback)(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) + NS_SWIFT_NAME(AuthDataResultCallback); + +#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 +/** + @brief The name of the `NSNotificationCenter` notification which is posted when the auth state + changes (for example, a new token has been produced, a user signs in or signs out). The + object parameter of the notification is the sender `FIRAuth` instance. + */ +extern const NSNotificationName FIRAuthStateDidChangeNotification + NS_SWIFT_NAME(AuthStateDidChange); +#else +/** + @brief The name of the `NSNotificationCenter` notification which is posted when the auth state + changes (for example, a new token has been produced, a user signs in or signs out). The + object parameter of the notification is the sender `FIRAuth` instance. + */ +extern NSString *const FIRAuthStateDidChangeNotification + NS_SWIFT_NAME(AuthStateDidChangeNotification); +#endif // defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 + +/** @typedef FIRAuthResultCallback + @brief The type of block invoked when sign-in related events complete. + + @param user Optionally; the signed in user, if any. + @param error Optionally; if an error occurs, this is the NSError object that describes the + problem. Set to nil otherwise. + */ +typedef void (^FIRAuthResultCallback)(FIRUser *_Nullable user, NSError *_Nullable error) + NS_SWIFT_NAME(AuthResultCallback); + +/** @typedef FIRProviderQueryCallback + @brief The type of block invoked when a list of identity providers for a given email address is + requested. + + @param providers Optionally; a list of provider identifiers, if any. + @see FIRGoogleAuthProviderID etc. + @param error Optionally; if an error occurs, this is the NSError object that describes the + problem. Set to nil otherwise. + */ +typedef void (^FIRProviderQueryCallback)(NSArray *_Nullable providers, + NSError *_Nullable error) + NS_SWIFT_NAME(ProviderQueryCallback); + +/** @typedef FIRSignInMethodQueryCallback + @brief The type of block invoked when a list of sign-in methods for a given email address is + requested. + */ +typedef void (^FIRSignInMethodQueryCallback)(NSArray *_Nullable, + NSError *_Nullable) + NS_SWIFT_NAME(SignInMethodQueryCallback); + +/** @typedef FIRSendPasswordResetCallback + @brief The type of block invoked when sending a password reset email. + + @param error Optionally; if an error occurs, this is the NSError object that describes the + problem. Set to nil otherwise. + */ +typedef void (^FIRSendPasswordResetCallback)(NSError *_Nullable error) + NS_SWIFT_NAME(SendPasswordResetCallback); + +/** @typedef FIRSendSignInLinkToEmailCallback + @brief The type of block invoked when sending an email sign-in link email. + */ +typedef void (^FIRSendSignInLinkToEmailCallback)(NSError *_Nullable error) + NS_SWIFT_NAME(SendSignInLinkToEmailCallback); + +/** @typedef FIRConfirmPasswordResetCallback + @brief The type of block invoked when performing a password reset. + + @param error Optionally; if an error occurs, this is the NSError object that describes the + problem. Set to nil otherwise. + */ +typedef void (^FIRConfirmPasswordResetCallback)(NSError *_Nullable error) + NS_SWIFT_NAME(ConfirmPasswordResetCallback); + +/** @typedef FIRVerifyPasswordResetCodeCallback + @brief The type of block invoked when verifying that an out of band code should be used to + perform password reset. + + @param email Optionally; the email address of the user for which the out of band code applies. + @param error Optionally; if an error occurs, this is the NSError object that describes the + problem. Set to nil otherwise. + */ +typedef void (^FIRVerifyPasswordResetCodeCallback)(NSString *_Nullable email, + NSError *_Nullable error) + NS_SWIFT_NAME(VerifyPasswordResetCodeCallback); + +/** @typedef FIRApplyActionCodeCallback + @brief The type of block invoked when applying an action code. + + @param error Optionally; if an error occurs, this is the NSError object that describes the + problem. Set to nil otherwise. + */ +typedef void (^FIRApplyActionCodeCallback)(NSError *_Nullable error) + NS_SWIFT_NAME(ApplyActionCodeCallback); + +/** + @brief Keys used to retrieve operation data from a `FIRActionCodeInfo` object by the + `dataForKey` method. + */ +typedef NS_ENUM(NSInteger, FIRActionDataKey) { + /** + * The email address to which the code was sent. + * For FIRActionCodeOperationRecoverEmail, the new email address for the account. + */ + FIRActionCodeEmailKey = 0, + + /** For FIRActionCodeOperationRecoverEmail, the current email address for the account. */ + FIRActionCodeFromEmailKey = 1 +} NS_SWIFT_NAME(ActionDataKey); + +/** @class FIRActionCodeInfo + @brief Manages information regarding action codes. + */ +NS_SWIFT_NAME(ActionCodeInfo) +@interface FIRActionCodeInfo : NSObject + +/** + @brief Operations which can be performed with action codes. + */ +typedef NS_ENUM(NSInteger, FIRActionCodeOperation) { + /** Action code for unknown operation. */ + FIRActionCodeOperationUnknown = 0, + + /** Action code for password reset operation. */ + FIRActionCodeOperationPasswordReset = 1, + + /** Action code for verify email operation. */ + FIRActionCodeOperationVerifyEmail = 2, + + /** Action code for recover email operation. */ + FIRActionCodeOperationRecoverEmail = 3, + + /** Action code for email link operation. */ + FIRActionCodeOperationEmailLink = 4, + + +} NS_SWIFT_NAME(ActionCodeOperation); + +/** + @brief The operation being performed. + */ +@property(nonatomic, readonly) FIRActionCodeOperation operation; + +/** @fn dataForKey: + @brief The operation being performed. + + @param key The FIRActionDataKey value used to retrieve the operation data. + + @return The operation data pertaining to the provided action code key. + */ +- (NSString *)dataForKey:(FIRActionDataKey)key; + +/** @fn init + @brief please use initWithOperation: instead. + */ +- (instancetype)init NS_UNAVAILABLE; + +@end + +/** @typedef FIRCheckActionCodeCallBack + @brief The type of block invoked when performing a check action code operation. + + @param info Metadata corresponding to the action code. + @param error Optionally; if an error occurs, this is the NSError object that describes the + problem. Set to nil otherwise. + */ +typedef void (^FIRCheckActionCodeCallBack)(FIRActionCodeInfo *_Nullable info, + NSError *_Nullable error) + NS_SWIFT_NAME(CheckActionCodeCallback); + +/** @class FIRAuth + @brief Manages authentication for Firebase apps. + @remarks This class is thread-safe. + */ +NS_SWIFT_NAME(Auth) +@interface FIRAuth : NSObject + +/** @fn auth + @brief Gets the auth object for the default Firebase app. + @remarks The default Firebase app must have already been configured or an exception will be + raised. + */ ++ (FIRAuth *)auth NS_SWIFT_NAME(auth()); + +/** @fn authWithApp: + @brief Gets the auth object for a `FIRApp`. + + @param app The FIRApp for which to retrieve the associated FIRAuth instance. + @return The FIRAuth instance associated with the given FIRApp. + */ ++ (FIRAuth *)authWithApp:(FIRApp *)app NS_SWIFT_NAME(auth(app:)); + +/** @property app + @brief Gets the `FIRApp` object that this auth object is connected to. + */ +@property(nonatomic, weak, readonly, nullable) FIRApp *app; + +/** @property currentUser + @brief Synchronously gets the cached current user, or null if there is none. + */ +@property(nonatomic, strong, readonly, nullable) FIRUser *currentUser; + +/** @property languageCode + @brief The current user language code. This property can be set to the app's current language by + calling `useAppLanguage`. + + @remarks The string used to set this property must be a language code that follows BCP 47. + */ +@property(nonatomic, copy, nullable) NSString *languageCode; + +/** @property settings + @brief Contains settings related to the auth object. + */ +@property(nonatomic, copy, nullable) FIRAuthSettings *settings; + +/** @property userAccessGroup + @brief The current user access group that the Auth instance is using. Default is nil. + */ +@property(readonly, nonatomic, copy, nullable) NSString *userAccessGroup; + +#if TARGET_OS_IOS +/** @property APNSToken + @brief The APNs token used for phone number authentication. The type of the token (production + or sandbox) will be attempted to be automatcially detected. + @remarks If swizzling is disabled, the APNs Token must be set for phone number auth to work, + by either setting this property or by calling `setAPNSToken:type:` + */ +@property(nonatomic, strong, nullable) NSData *APNSToken; +#endif + +/** @fn init + @brief Please access auth instances using `FIRAuth.auth` and `FIRAuth.authForApp:`. + */ +- (instancetype)init NS_UNAVAILABLE; + +/** @fn updateCurrentUser:completion: + @brief Sets the currentUser on the calling Auth instance to the provided user object. + @param user The user object to be set as the current user of the calling Auth instance. + @param completion Optionally; a block invoked after the user of the calling Auth instance has + been updated or an error was encountered. + */ +- (void)updateCurrentUser:(FIRUser *)user completion:(nullable FIRUserUpdateCallback)completion; + +/** @fn fetchProvidersForEmail:completion: + @brief Please use fetchSignInMethodsForEmail:completion: for Objective-C or + fetchSignInMethods(forEmail:completion:) for Swift instead. + */ +- (void)fetchProvidersForEmail:(NSString *)email + completion:(nullable FIRProviderQueryCallback)completion +DEPRECATED_MSG_ATTRIBUTE("Please use fetchSignInMethodsForEmail:completion: for Objective-C or " + "fetchSignInMethods(forEmail:completion:) for Swift instead."); + +/** @fn fetchSignInMethodsForEmail:completion: + @brief Fetches the list of all sign-in methods previously used for the provided email address. + + @param email The email address for which to obtain a list of sign-in methods. + @param completion Optionally; a block which is invoked when the list of sign in methods for the + specified email address is ready or an error was encountered. Invoked asynchronously on the + main thread in the future. + + @remarks Possible error codes: + + + `FIRAuthErrorCodeInvalidEmail` - Indicates the email address is malformed. + + @remarks See @c FIRAuthErrors for a list of error codes that are common to all API methods. + */ + +- (void)fetchSignInMethodsForEmail:(NSString *)email + completion:(nullable FIRSignInMethodQueryCallback)completion; + +/** @fn signInWithEmail:password:completion: + @brief Signs in using an email address and password. + + @param email The user's email address. + @param password The user's password. + @param completion Optionally; a block which is invoked when the sign in flow finishes, or is + canceled. Invoked asynchronously on the main thread in the future. + + @remarks Possible error codes: + + + `FIRAuthErrorCodeOperationNotAllowed` - Indicates that email and password + accounts are not enabled. Enable them in the Auth section of the + Firebase console. + + `FIRAuthErrorCodeUserDisabled` - Indicates the user's account is disabled. + + `FIRAuthErrorCodeWrongPassword` - Indicates the user attempted + sign in with an incorrect password. + + `FIRAuthErrorCodeInvalidEmail` - Indicates the email address is malformed. + + + @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods. + */ +- (void)signInWithEmail:(NSString *)email + password:(NSString *)password + completion:(nullable FIRAuthDataResultCallback)completion; + +/** @fn signInWithEmail:link:completion: + @brief Signs in using an email address and email sign-in link. + + @param email The user's email address. + @param link The email sign-in link. + @param completion Optionally; a block which is invoked when the sign in flow finishes, or is + canceled. Invoked asynchronously on the main thread in the future. + + @remarks Possible error codes: + + + `FIRAuthErrorCodeOperationNotAllowed` - Indicates that email and email sign-in link + accounts are not enabled. Enable them in the Auth section of the + Firebase console. + + `FIRAuthErrorCodeUserDisabled` - Indicates the user's account is disabled. + + `FIRAuthErrorCodeInvalidEmail` - Indicates the email address is invalid. + + + @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods. + */ + +- (void)signInWithEmail:(NSString *)email + link:(NSString *)link + completion:(nullable FIRAuthDataResultCallback)completion; + +/** @fn signInWithProvider:UIDelegate:completion: + @brief Signs in using the provided auth provider instance. + + @param provider An instance of an auth provider used to initiate the sign-in flow. + @param UIDelegate Optionally an instance of a class conforming to the FIRAuthUIDelegate + protocol, this is used for presenting the web context. If nil, a default FIRAuthUIDelegate + will be used. + @param completion Optionally; a block which is invoked when the sign in flow finishes, or is + canceled. Invoked asynchronously on the main thread in the future. + + @remarks Possible error codes: +
    +
  • @c FIRAuthErrorCodeOperationNotAllowed - Indicates that email and password + accounts are not enabled. Enable them in the Auth section of the + Firebase console. +
  • +
  • @c FIRAuthErrorCodeUserDisabled - Indicates the user's account is disabled. +
  • +
  • @c FIRAuthErrorCodeWebNetworkRequestFailed - Indicates that a network request within a + SFSafariViewController or WKWebView failed. +
  • +
  • @c FIRAuthErrorCodeWebInternalError - Indicates that an internal error occurred within a + SFSafariViewController or WKWebView. +
  • +
  • @c FIRAuthErrorCodeWebSignInUserInteractionFailure - Indicates a general failure during + a web sign-in flow. +
  • +
  • @c FIRAuthErrorCodeWebContextAlreadyPresented - Indicates that an attempt was made to + present a new web context while one was already being presented. +
  • +
  • @c FIRAuthErrorCodeWebContextCancelled - Indicates that the URL presentation was + cancelled prematurely by the user. +
  • +
  • @c FIRAuthErrorCodeAccountExistsWithDifferentCredential - Indicates the email asserted + by the credential (e.g. the email in a Facebook access token) is already in use by an + existing account, that cannot be authenticated with this sign-in method. Call + fetchProvidersForEmail for this user’s email and then prompt them to sign in with any of + the sign-in providers returned. This error will only be thrown if the "One account per + email address" setting is enabled in the Firebase console, under Auth settings. +
  • +
+ + @remarks See @c FIRAuthErrors for a list of error codes that are common to all API methods. + */ +- (void)signInWithProvider:(id)provider + UIDelegate:(nullable id)UIDelegate + completion:(nullable FIRAuthDataResultCallback)completion; + +/** @fn signInAndRetrieveDataWithCredential:completion: + @brief Please use signInWithCredential:completion: for Objective-C or " + "signIn(with:completion:) for Swift instead. + */ +- (void)signInAndRetrieveDataWithCredential:(FIRAuthCredential *)credential + completion:(nullable FIRAuthDataResultCallback)completion +DEPRECATED_MSG_ATTRIBUTE("Please use signInWithCredential:completion: for Objective-C or " + "signIn(with:completion:) for Swift instead."); + +/** @fn signInWithCredential:completion: + @brief Asynchronously signs in to Firebase with the given 3rd-party credentials (e.g. a Facebook + login Access Token, a Google ID Token/Access Token pair, etc.) and returns additional + identity provider data. + + @param credential The credential supplied by the IdP. + @param completion Optionally; a block which is invoked when the sign in flow finishes, or is + canceled. Invoked asynchronously on the main thread in the future. + + @remarks Possible error codes: + + + `FIRAuthErrorCodeInvalidCredential` - Indicates the supplied credential is invalid. + This could happen if it has expired or it is malformed. + + `FIRAuthErrorCodeOperationNotAllowed` - Indicates that accounts + with the identity provider represented by the credential are not enabled. + Enable them in the Auth section of the Firebase console. + + `FIRAuthErrorCodeAccountExistsWithDifferentCredential` - Indicates the email asserted + by the credential (e.g. the email in a Facebook access token) is already in use by an + existing account, that cannot be authenticated with this sign-in method. Call + fetchProvidersForEmail for this user’s email and then prompt them to sign in with any of + the sign-in providers returned. This error will only be thrown if the "One account per + email address" setting is enabled in the Firebase console, under Auth settings. + + `FIRAuthErrorCodeUserDisabled` - Indicates the user's account is disabled. + + `FIRAuthErrorCodeWrongPassword` - Indicates the user attempted sign in with an + incorrect password, if credential is of the type EmailPasswordAuthCredential. + + `FIRAuthErrorCodeInvalidEmail` - Indicates the email address is malformed. + + `FIRAuthErrorCodeMissingVerificationID` - Indicates that the phone auth credential was + created with an empty verification ID. + + `FIRAuthErrorCodeMissingVerificationCode` - Indicates that the phone auth credential + was created with an empty verification code. + + `FIRAuthErrorCodeInvalidVerificationCode` - Indicates that the phone auth credential + was created with an invalid verification Code. + + `FIRAuthErrorCodeInvalidVerificationID` - Indicates that the phone auth credential was + created with an invalid verification ID. + + `FIRAuthErrorCodeSessionExpired` - Indicates that the SMS code has expired. + + @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods +*/ +- (void)signInWithCredential:(FIRAuthCredential *)credential + completion:(nullable FIRAuthDataResultCallback)completion; + +/** @fn signInAnonymouslyWithCompletion: + @brief Asynchronously creates and becomes an anonymous user. + @param completion Optionally; a block which is invoked when the sign in finishes, or is + canceled. Invoked asynchronously on the main thread in the future. + + @remarks If there is already an anonymous user signed in, that user will be returned instead. + If there is any other existing user signed in, that user will be signed out. + + @remarks Possible error codes: + + + `FIRAuthErrorCodeOperationNotAllowed` - Indicates that anonymous accounts are + not enabled. Enable them in the Auth section of the Firebase console. + + @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods. + */ +- (void)signInAnonymouslyWithCompletion:(nullable FIRAuthDataResultCallback)completion; + +/** @fn signInWithCustomToken:completion: + @brief Asynchronously signs in to Firebase with the given Auth token. + + @param token A self-signed custom auth token. + @param completion Optionally; a block which is invoked when the sign in finishes, or is + canceled. Invoked asynchronously on the main thread in the future. + + @remarks Possible error codes: + + + `FIRAuthErrorCodeInvalidCustomToken` - Indicates a validation error with + the custom token. + + `FIRAuthErrorCodeCustomTokenMismatch` - Indicates the service account and the API key + belong to different projects. + + @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods. + */ +- (void)signInWithCustomToken:(NSString *)token + completion:(nullable FIRAuthDataResultCallback)completion; + +/** @fn createUserWithEmail:password:completion: + @brief Creates and, on success, signs in a user with the given email address and password. + + @param email The user's email address. + @param password The user's desired password. + @param completion Optionally; a block which is invoked when the sign up flow finishes, or is + canceled. Invoked asynchronously on the main thread in the future. + + @remarks Possible error codes: + + + `FIRAuthErrorCodeInvalidEmail` - Indicates the email address is malformed. + + `FIRAuthErrorCodeEmailAlreadyInUse` - Indicates the email used to attempt sign up + already exists. Call fetchProvidersForEmail to check which sign-in mechanisms the user + used, and prompt the user to sign in with one of those. + + `FIRAuthErrorCodeOperationNotAllowed` - Indicates that email and password accounts + are not enabled. Enable them in the Auth section of the Firebase console. + + `FIRAuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is + considered too weak. The NSLocalizedFailureReasonErrorKey field in the NSError.userInfo + dictionary object will contain more detailed explanation that can be shown to the user. + + @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods. + */ +- (void)createUserWithEmail:(NSString *)email + password:(NSString *)password + completion:(nullable FIRAuthDataResultCallback)completion; + +/** @fn confirmPasswordResetWithCode:newPassword:completion: + @brief Resets the password given a code sent to the user outside of the app and a new password + for the user. + + @param newPassword The new password. + @param completion Optionally; a block which is invoked when the request finishes. Invoked + asynchronously on the main thread in the future. + + @remarks Possible error codes: + + + `FIRAuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is + considered too weak. + + `FIRAuthErrorCodeOperationNotAllowed` - Indicates the administrator disabled sign + in with the specified identity provider. + + `FIRAuthErrorCodeExpiredActionCode` - Indicates the OOB code is expired. + + `FIRAuthErrorCodeInvalidActionCode` - Indicates the OOB code is invalid. + + @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods. + */ +- (void)confirmPasswordResetWithCode:(NSString *)code + newPassword:(NSString *)newPassword + completion:(FIRConfirmPasswordResetCallback)completion; + +/** @fn checkActionCode:completion: + @brief Checks the validity of an out of band code. + + @param code The out of band code to check validity. + @param completion Optionally; a block which is invoked when the request finishes. Invoked + asynchronously on the main thread in the future. + */ +- (void)checkActionCode:(NSString *)code completion:(FIRCheckActionCodeCallBack)completion; + +/** @fn verifyPasswordResetCode:completion: + @brief Checks the validity of a verify password reset code. + + @param code The password reset code to be verified. + @param completion Optionally; a block which is invoked when the request finishes. Invoked + asynchronously on the main thread in the future. + */ +- (void)verifyPasswordResetCode:(NSString *)code + completion:(FIRVerifyPasswordResetCodeCallback)completion; + +/** @fn applyActionCode:completion: + @brief Applies out of band code. + + @param code The out of band code to be applied. + @param completion Optionally; a block which is invoked when the request finishes. Invoked + asynchronously on the main thread in the future. + + @remarks This method will not work for out of band codes which require an additional parameter, + such as password reset code. + */ +- (void)applyActionCode:(NSString *)code + completion:(FIRApplyActionCodeCallback)completion; + +/** @fn sendPasswordResetWithEmail:completion: + @brief Initiates a password reset for the given email address. + + @param email The email address of the user. + @param completion Optionally; a block which is invoked when the request finishes. Invoked + asynchronously on the main thread in the future. + + @remarks Possible error codes: + + + `FIRAuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was + sent in the request. + + `FIRAuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in + the console for this action. + + `FIRAuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for + sending update email. + + + */ +- (void)sendPasswordResetWithEmail:(NSString *)email + completion:(nullable FIRSendPasswordResetCallback)completion; + +/** @fn sendPasswordResetWithEmail:actionCodeSetting:completion: + @brief Initiates a password reset for the given email address and @FIRActionCodeSettings object. + + @param email The email address of the user. + @param actionCodeSettings An `FIRActionCodeSettings` object containing settings related to + handling action codes. + @param completion Optionally; a block which is invoked when the request finishes. Invoked + asynchronously on the main thread in the future. + + @remarks Possible error codes: + + + `FIRAuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was + sent in the request. + + `FIRAuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in + the console for this action. + + `FIRAuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for + sending update email. + + `FIRAuthErrorCodeMissingIosBundleID` - Indicates that the iOS bundle ID is missing when + `handleCodeInApp` is set to YES. + + `FIRAuthErrorCodeMissingAndroidPackageName` - Indicates that the android package name + is missing when the `androidInstallApp` flag is set to true. + + `FIRAuthErrorCodeUnauthorizedDomain` - Indicates that the domain specified in the + continue URL is not whitelisted in the Firebase console. + + `FIRAuthErrorCodeInvalidContinueURI` - Indicates that the domain specified in the + continue URI is not valid. + + + */ + - (void)sendPasswordResetWithEmail:(NSString *)email + actionCodeSettings:(FIRActionCodeSettings *)actionCodeSettings + completion:(nullable FIRSendPasswordResetCallback)completion; + +/** @fn sendSignInLinkToEmail:actionCodeSettings:completion: + @brief Sends a sign in with email link to provided email address. + + @param email The email address of the user. + @param actionCodeSettings An `FIRActionCodeSettings` object containing settings related to + handling action codes. + @param completion Optionally; a block which is invoked when the request finishes. Invoked + asynchronously on the main thread in the future. + */ +- (void)sendSignInLinkToEmail:(NSString *)email + actionCodeSettings:(FIRActionCodeSettings *)actionCodeSettings + completion:(nullable FIRSendSignInLinkToEmailCallback)completion; + +/** @fn signOut: + @brief Signs out the current user. + + @param error Optionally; if an error occurs, upon return contains an NSError object that + describes the problem; is nil otherwise. + @return @YES when the sign out request was successful. @NO otherwise. + + @remarks Possible error codes: + + + `FIRAuthErrorCodeKeychainError` - Indicates an error occurred when accessing the + keychain. The `NSLocalizedFailureReasonErrorKey` field in the `NSError.userInfo` + dictionary will contain more information about the error encountered. + + + + */ +- (BOOL)signOut:(NSError *_Nullable *_Nullable)error; + +/** @fn isSignInWithEmailLink + @brief Checks if link is an email sign-in link. + + @param link The email sign-in link. + @return @YES when the link passed matches the expected format of an email sign-in link. + */ +- (BOOL)isSignInWithEmailLink:(NSString *)link; + +/** @fn addAuthStateDidChangeListener: + @brief Registers a block as an "auth state did change" listener. To be invoked when: + + + The block is registered as a listener, + + A user with a different UID from the current user has signed in, or + + The current user has signed out. + + @param listener The block to be invoked. The block is always invoked asynchronously on the main + thread, even for it's initial invocation after having been added as a listener. + + @remarks The block is invoked immediately after adding it according to it's standard invocation + semantics, asynchronously on the main thread. Users should pay special attention to + making sure the block does not inadvertently retain objects which should not be retained by + the long-lived block. The block itself will be retained by `FIRAuth` until it is + unregistered or until the `FIRAuth` instance is otherwise deallocated. + + @return A handle useful for manually unregistering the block as a listener. + */ +- (FIRAuthStateDidChangeListenerHandle)addAuthStateDidChangeListener: + (FIRAuthStateDidChangeListenerBlock)listener; + +/** @fn removeAuthStateDidChangeListener: + @brief Unregisters a block as an "auth state did change" listener. + + @param listenerHandle The handle for the listener. + */ +- (void)removeAuthStateDidChangeListener:(FIRAuthStateDidChangeListenerHandle)listenerHandle; + +/** @fn addIDTokenDidChangeListener: + @brief Registers a block as an "ID token did change" listener. To be invoked when: + + + The block is registered as a listener, + + A user with a different UID from the current user has signed in, + + The ID token of the current user has been refreshed, or + + The current user has signed out. + + @param listener The block to be invoked. The block is always invoked asynchronously on the main + thread, even for it's initial invocation after having been added as a listener. + + @remarks The block is invoked immediately after adding it according to it's standard invocation + semantics, asynchronously on the main thread. Users should pay special attention to + making sure the block does not inadvertently retain objects which should not be retained by + the long-lived block. The block itself will be retained by `FIRAuth` until it is + unregistered or until the `FIRAuth` instance is otherwise deallocated. + + @return A handle useful for manually unregistering the block as a listener. + */ +- (FIRIDTokenDidChangeListenerHandle)addIDTokenDidChangeListener: + (FIRIDTokenDidChangeListenerBlock)listener; + +/** @fn removeIDTokenDidChangeListener: + @brief Unregisters a block as an "ID token did change" listener. + + @param listenerHandle The handle for the listener. + */ +- (void)removeIDTokenDidChangeListener:(FIRIDTokenDidChangeListenerHandle)listenerHandle; + +/** @fn useAppLanguage + @brief Sets `languageCode` to the app's current language. + */ +- (void)useAppLanguage; + +#if TARGET_OS_IOS + +/** @fn canHandleURL: + @brief Whether the specific URL is handled by `FIRAuth` . + @param URL The URL received by the application delegate from any of the openURL method. + @return Whether or the URL is handled. YES means the URL is for Firebase Auth + so the caller should ignore the URL from further processing, and NO means the + the URL is for the app (or another libaray) so the caller should continue handling + this URL as usual. + @remarks If swizzling is disabled, URLs received by the application delegate must be forwarded + to this method for phone number auth to work. + */ +- (BOOL)canHandleURL:(nonnull NSURL *)URL; + +/** @fn setAPNSToken:type: + @brief Sets the APNs token along with its type. + @remarks If swizzling is disabled, the APNs Token must be set for phone number auth to work, + by either setting calling this method or by setting the `APNSToken` property. + */ +- (void)setAPNSToken:(NSData *)token type:(FIRAuthAPNSTokenType)type; + +/** @fn canHandleNotification: + @brief Whether the specific remote notification is handled by `FIRAuth` . + @param userInfo A dictionary that contains information related to the + notification in question. + @return Whether or the notification is handled. YES means the notification is for Firebase Auth + so the caller should ignore the notification from further processing, and NO means the + the notification is for the app (or another libaray) so the caller should continue handling + this notification as usual. + @remarks If swizzling is disabled, related remote notifications must be forwarded to this method + for phone number auth to work. + */ +- (BOOL)canHandleNotification:(NSDictionary *)userInfo; + +#endif // TARGET_OS_IOS + +#pragma mark - User sharing + +/** @fn useUserAccessGroup:error: + @brief Switch userAccessGroup and current user to the given accessGroup and the user stored in + it. + */ +- (BOOL)useUserAccessGroup:(NSString *_Nullable)accessGroup + error:(NSError *_Nullable *_Nullable)outError; + +/** @fn getStoredUserForAccessGroup:error: + @brief Get the stored user in the given accessGroup. + */ +- (nullable FIRUser *)getStoredUserForAccessGroup:(NSString *_Nullable)accessGroup + error:(NSError *_Nullable *_Nullable)outError; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRAuthAPNSTokenType.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRAuthAPNSTokenType.h new file mode 100644 index 0000000..5630e21 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRAuthAPNSTokenType.h @@ -0,0 +1,45 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#if !TARGET_OS_OSX + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * @brief The APNs token type for the app. + */ +typedef NS_ENUM(NSInteger, FIRAuthAPNSTokenType) { + + /** Unknown token type. + The actual token type will be detected from the provisioning profile in the app's bundle. + */ + FIRAuthAPNSTokenTypeUnknown, + + /** Sandbox token type. + */ + FIRAuthAPNSTokenTypeSandbox, + + /** Production token type. + */ + FIRAuthAPNSTokenTypeProd, +} NS_SWIFT_NAME(AuthAPNSTokenType); + +NS_ASSUME_NONNULL_END + +#endif diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRAuthCredential.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRAuthCredential.h new file mode 100644 index 0000000..106d844 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRAuthCredential.h @@ -0,0 +1,41 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRAuthCredential + @brief Represents a credential. + */ +NS_SWIFT_NAME(AuthCredential) +@interface FIRAuthCredential : NSObject + +/** @property provider + @brief Gets the name of the identity provider for the credential. + */ +@property(nonatomic, copy, readonly) NSString *provider; + +/** @fn init + @brief This is an abstract base class. Concrete instances should be created via factory + methods available in the various authentication provider libraries (like the Facebook + provider or the Google provider libraries.) + */ +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRAuthDataResult.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRAuthDataResult.h new file mode 100644 index 0000000..93c8b3b --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRAuthDataResult.h @@ -0,0 +1,57 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FIRAdditionalUserInfo; +@class FIRAuthCredential; +@class FIRUser; + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRAuthDataResult + @brief Helper object that contains the result of a successful sign-in, link and reauthenticate + action. It contains references to a FIRUser instance and a FIRAdditionalUserInfo instance. + */ +NS_SWIFT_NAME(AuthDataResult) +@interface FIRAuthDataResult : NSObject + +/** @fn init + @brief This class should not be initialized manually. `FIRAuthDataResult` instance is + returned as part of `FIRAuthDataResultCallback`. + */ +- (instancetype)init NS_UNAVAILABLE; + +/** @property user + @brief The signed in user. + */ +@property(nonatomic, readonly) FIRUser *user; + +/** @property additionalUserInfo + @brief If available contains the additional IdP specific information about signed in user. + */ +@property(nonatomic, readonly, nullable) FIRAdditionalUserInfo *additionalUserInfo; + +/** @property credential + @brief This property will be non-nil after a successful headful-lite sign-in via + signInWithProvider:UIDelegate:. May be used to obtain the accessToken and/or IDToken + pertaining to a recently signed-in user. + */ +@property(nonatomic, readonly, nullable) FIRAuthCredential *credential; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRAuthErrors.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRAuthErrors.h new file mode 100644 index 0000000..73b0b21 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRAuthErrors.h @@ -0,0 +1,370 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRAuthErrors + @remarks Error Codes common to all API Methods: + + + `FIRAuthErrorCodeNetworkError` + + `FIRAuthErrorCodeUserNotFound` + + `FIRAuthErrorCodeUserTokenExpired` + + `FIRAuthErrorCodeTooManyRequests` + + `FIRAuthErrorCodeInvalidAPIKey` + + `FIRAuthErrorCodeAppNotAuthorized` + + `FIRAuthErrorCodeKeychainError` + + `FIRAuthErrorCodeInternalError` + + @remarks Common error codes for `FIRUser` operations: + + + `FIRAuthErrorCodeInvalidUserToken` + + `FIRAuthErrorCodeUserDisabled` + + */ +NS_SWIFT_NAME(AuthErrors) +@interface FIRAuthErrors + +/** + @brief The Firebase Auth error domain. + */ +extern NSString *const FIRAuthErrorDomain NS_SWIFT_NAME(AuthErrorDomain); + +/** + @brief The name of the key for the error short string of an error code. + */ +extern NSString *const FIRAuthErrorUserInfoNameKey NS_SWIFT_NAME(AuthErrorUserInfoNameKey); + +/** + @brief Errors with one of the following three codes: + - `FIRAuthErrorCodeAccountExistsWithDifferentCredential` + - `FIRAuthErrorCodeCredentialAlreadyInUse` + - `FIRAuthErrorCodeEmailAlreadyInUse` + may contain an `NSError.userInfo` dictinary object which contains this key. The value + associated with this key is an NSString of the email address of the account that already + exists. + */ +extern NSString *const FIRAuthErrorUserInfoEmailKey NS_SWIFT_NAME(AuthErrorUserInfoEmailKey); + +/** + @brief The key used to read the updated Auth credential from the userInfo dictionary of the + NSError object returned. This is the updated auth credential the developer should use for + recovery if applicable. + */ +extern NSString *const FIRAuthErrorUserInfoUpdatedCredentialKey + NS_SWIFT_NAME(AuthErrorUserInfoUpdatedCredentialKey); + +/** + @brief Error codes used by Firebase Auth. + */ +typedef NS_ENUM(NSInteger, FIRAuthErrorCode) { + /** Indicates a validation error with the custom token. + */ + FIRAuthErrorCodeInvalidCustomToken = 17000, + + /** Indicates the service account and the API key belong to different projects. + */ + FIRAuthErrorCodeCustomTokenMismatch = 17002, + + /** Indicates the IDP token or requestUri is invalid. + */ + FIRAuthErrorCodeInvalidCredential = 17004, + + /** Indicates the user's account is disabled on the server. + */ + FIRAuthErrorCodeUserDisabled = 17005, + + /** Indicates the administrator disabled sign in with the specified identity provider. + */ + FIRAuthErrorCodeOperationNotAllowed = 17006, + + /** Indicates the email used to attempt a sign up is already in use. + */ + FIRAuthErrorCodeEmailAlreadyInUse = 17007, + + /** Indicates the email is invalid. + */ + FIRAuthErrorCodeInvalidEmail = 17008, + + /** Indicates the user attempted sign in with a wrong password. + */ + FIRAuthErrorCodeWrongPassword = 17009, + + /** Indicates that too many requests were made to a server method. + */ + FIRAuthErrorCodeTooManyRequests = 17010, + + /** Indicates the user account was not found. + */ + FIRAuthErrorCodeUserNotFound = 17011, + + /** Indicates account linking is required. + */ + FIRAuthErrorCodeAccountExistsWithDifferentCredential = 17012, + + /** Indicates the user has attemped to change email or password more than 5 minutes after + signing in. + */ + FIRAuthErrorCodeRequiresRecentLogin = 17014, + + /** Indicates an attempt to link a provider to which the account is already linked. + */ + FIRAuthErrorCodeProviderAlreadyLinked = 17015, + + /** Indicates an attempt to unlink a provider that is not linked. + */ + FIRAuthErrorCodeNoSuchProvider = 17016, + + /** Indicates user's saved auth credential is invalid, the user needs to sign in again. + */ + FIRAuthErrorCodeInvalidUserToken = 17017, + + /** Indicates a network error occurred (such as a timeout, interrupted connection, or + unreachable host). These types of errors are often recoverable with a retry. The + `NSUnderlyingError` field in the `NSError.userInfo` dictionary will contain the error + encountered. + */ + FIRAuthErrorCodeNetworkError = 17020, + + /** Indicates the saved token has expired, for example, the user may have changed account + password on another device. The user needs to sign in again on the device that made this + request. + */ + FIRAuthErrorCodeUserTokenExpired = 17021, + + /** Indicates an invalid API key was supplied in the request. + */ + FIRAuthErrorCodeInvalidAPIKey = 17023, + + /** Indicates that an attempt was made to reauthenticate with a user which is not the current + user. + */ + FIRAuthErrorCodeUserMismatch = 17024, + + /** Indicates an attempt to link with a credential that has already been linked with a + different Firebase account + */ + FIRAuthErrorCodeCredentialAlreadyInUse = 17025, + + /** Indicates an attempt to set a password that is considered too weak. + */ + FIRAuthErrorCodeWeakPassword = 17026, + + /** Indicates the App is not authorized to use Firebase Authentication with the + provided API Key. + */ + FIRAuthErrorCodeAppNotAuthorized = 17028, + + /** Indicates the OOB code is expired. + */ + FIRAuthErrorCodeExpiredActionCode = 17029, + + /** Indicates the OOB code is invalid. + */ + FIRAuthErrorCodeInvalidActionCode = 17030, + + /** Indicates that there are invalid parameters in the payload during a "send password reset + * email" attempt. + */ + FIRAuthErrorCodeInvalidMessagePayload = 17031, + + /** Indicates that the sender email is invalid during a "send password reset email" attempt. + */ + FIRAuthErrorCodeInvalidSender = 17032, + + /** Indicates that the recipient email is invalid. + */ + FIRAuthErrorCodeInvalidRecipientEmail = 17033, + + /** Indicates that an email address was expected but one was not provided. + */ + FIRAuthErrorCodeMissingEmail = 17034, + + // The enum values 17035 is reserved and should NOT be used for new error codes. + + /** Indicates that the iOS bundle ID is missing when a iOS App Store ID is provided. + */ + FIRAuthErrorCodeMissingIosBundleID = 17036, + + /** Indicates that the android package name is missing when the `androidInstallApp` flag is set + to true. + */ + FIRAuthErrorCodeMissingAndroidPackageName = 17037, + + /** Indicates that the domain specified in the continue URL is not whitelisted in the Firebase + console. + */ + FIRAuthErrorCodeUnauthorizedDomain = 17038, + + /** Indicates that the domain specified in the continue URI is not valid. + */ + FIRAuthErrorCodeInvalidContinueURI = 17039, + + /** Indicates that a continue URI was not provided in a request to the backend which requires + one. + */ + FIRAuthErrorCodeMissingContinueURI = 17040, + + /** Indicates that a phone number was not provided in a call to + `verifyPhoneNumber:completion:`. + */ + FIRAuthErrorCodeMissingPhoneNumber = 17041, + + /** Indicates that an invalid phone number was provided in a call to + `verifyPhoneNumber:completion:`. + */ + FIRAuthErrorCodeInvalidPhoneNumber = 17042, + + /** Indicates that the phone auth credential was created with an empty verification code. + */ + FIRAuthErrorCodeMissingVerificationCode = 17043, + + /** Indicates that an invalid verification code was used in the verifyPhoneNumber request. + */ + FIRAuthErrorCodeInvalidVerificationCode = 17044, + + /** Indicates that the phone auth credential was created with an empty verification ID. + */ + FIRAuthErrorCodeMissingVerificationID = 17045, + + /** Indicates that an invalid verification ID was used in the verifyPhoneNumber request. + */ + FIRAuthErrorCodeInvalidVerificationID = 17046, + + /** Indicates that the APNS device token is missing in the verifyClient request. + */ + FIRAuthErrorCodeMissingAppCredential = 17047, + + /** Indicates that an invalid APNS device token was used in the verifyClient request. + */ + FIRAuthErrorCodeInvalidAppCredential = 17048, + + // The enum values between 17048 and 17051 are reserved and should NOT be used for new error + // codes. + + /** Indicates that the SMS code has expired. + */ + FIRAuthErrorCodeSessionExpired = 17051, + + /** Indicates that the quota of SMS messages for a given project has been exceeded. + */ + FIRAuthErrorCodeQuotaExceeded = 17052, + + /** Indicates that the APNs device token could not be obtained. The app may not have set up + remote notification correctly, or may fail to forward the APNs device token to FIRAuth + if app delegate swizzling is disabled. + */ + FIRAuthErrorCodeMissingAppToken = 17053, + + /** Indicates that the app fails to forward remote notification to FIRAuth. + */ + FIRAuthErrorCodeNotificationNotForwarded = 17054, + + /** Indicates that the app could not be verified by Firebase during phone number authentication. + */ + FIRAuthErrorCodeAppNotVerified = 17055, + + /** Indicates that the reCAPTCHA token is not valid. + */ + FIRAuthErrorCodeCaptchaCheckFailed = 17056, + + /** Indicates that an attempt was made to present a new web context while one was already being + presented. + */ + FIRAuthErrorCodeWebContextAlreadyPresented = 17057, + + /** Indicates that the URL presentation was cancelled prematurely by the user. + */ + FIRAuthErrorCodeWebContextCancelled = 17058, + + /** Indicates a general failure during the app verification flow. + */ + FIRAuthErrorCodeAppVerificationUserInteractionFailure = 17059, + + /** Indicates that the clientID used to invoke a web flow is invalid. + */ + FIRAuthErrorCodeInvalidClientID = 17060, + + /** Indicates that a network request within a SFSafariViewController or WKWebView failed. + */ + FIRAuthErrorCodeWebNetworkRequestFailed = 17061, + + /** Indicates that an internal error occurred within a SFSafariViewController or WKWebView. + */ + FIRAuthErrorCodeWebInternalError = 17062, + + /** Indicates a general failure during a web sign-in flow. + */ + FIRAuthErrorCodeWebSignInUserInteractionFailure = 17063, + + /** Indicates that the local player was not authenticated prior to attempting Game Center + signin. + */ + FIRAuthErrorCodeLocalPlayerNotAuthenticated = 17066, + + /** Indicates that a non-null user was expected as an argmument to the operation but a null + user was provided. + */ + FIRAuthErrorCodeNullUser = 17067, + + /** Indicates that a Firebase Dynamic Link is not activated. + */ + FIRAuthErrorCodeDynamicLinkNotActivated = 17068, + + /** + * Represents the error code for when the given provider id for a web operation is invalid. + */ + FIRAuthErrorCodeInvalidProviderID = 17071, + + /** Indicates that the Firebase Dynamic Link domain used is either not configured or is + unauthorized for the current project. + */ + FIRAuthErrorCodeInvalidDynamicLinkDomain = 17074, + + /** Indicates that the credential is rejected because it's misformed or mismatching. + */ + FIRAuthErrorCodeRejectedCredential = 17075, + + /** Indicates that the GameKit framework is not linked prior to attempting Game Center signin. + */ + FIRAuthErrorCodeGameKitNotLinked = 17076, + + /** Indicates that the nonce is missing or invalid. + */ + FIRAuthErrorCodeMissingOrInvalidNonce = 17094, + + /** Indicates an error for when the client identifier is missing. + */ + FIRAuthErrorCodeMissingClientIdentifier = 17993, + + /** Indicates an error occurred while attempting to access the keychain. + */ + FIRAuthErrorCodeKeychainError = 17995, + + /** Indicates an internal error occurred. + */ + FIRAuthErrorCodeInternalError = 17999, + + /** Raised when a JWT fails to parse correctly. May be accompanied by an underlying error + describing which step of the JWT parsing process failed. + */ + FIRAuthErrorCodeMalformedJWT = 18000, +} NS_SWIFT_NAME(AuthErrorCode); + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRAuthSettings.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRAuthSettings.h new file mode 100644 index 0000000..4ac7ce8 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRAuthSettings.h @@ -0,0 +1,35 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRAuthSettings + @brief Determines settings related to an auth object. + */ +NS_SWIFT_NAME(AuthSettings) +@interface FIRAuthSettings : NSObject + +/** @property appVerificationDisabledForTesting + @brief Flag to determine whether app verification should be disabled for testing or not. + */ +@property(nonatomic, assign, getter=isAppVerificationDisabledForTesting) BOOL + appVerificationDisabledForTesting; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRAuthTokenResult.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRAuthTokenResult.h new file mode 100644 index 0000000..515aa60 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRAuthTokenResult.h @@ -0,0 +1,66 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRAuthTokenResult + @brief A data class containing the ID token JWT string and other properties associated with the + token including the decoded payload claims. + */ +NS_SWIFT_NAME(AuthTokenResult) +@interface FIRAuthTokenResult : NSObject + +/** @property token + @brief Stores the JWT string of the ID token. + */ +@property(nonatomic, readonly) NSString *token; + +/** @property expirationDate + @brief Stores the ID token's expiration date. + */ +@property(nonatomic, readonly) NSDate *expirationDate; + +/** @property authDate + @brief Stores the ID token's authentication date. + @remarks This is the date the user was signed in and NOT the date the token was refreshed. + */ +@property(nonatomic, readonly) NSDate *authDate; + +/** @property issuedAtDate + @brief Stores the date that the ID token was issued. + @remarks This is the date last refreshed and NOT the last authentication date. + */ +@property(nonatomic, readonly) NSDate *issuedAtDate; + +/** @property signInProvider + @brief Stores sign-in provider through which the token was obtained. + @remarks This does not necessarily map to provider IDs. + */ +@property(nonatomic, readonly) NSString *signInProvider; + +/** @property claims + @brief Stores the entire payload of claims found on the ID token. This includes the standard + reserved claims as well as custom claims set by the developer via the Admin SDK. + */ +@property(nonatomic, readonly) NSDictionary *claims; + + + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRAuthUIDelegate.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRAuthUIDelegate.h new file mode 100644 index 0000000..2adee33 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRAuthUIDelegate.h @@ -0,0 +1,58 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#if !TARGET_OS_OSX + +#import + +@class UIViewController; + +NS_ASSUME_NONNULL_BEGIN + +/** @protocol FIRAuthUIDelegate + @brief A protocol to handle user interface interactions for Firebase Auth. + */ +NS_SWIFT_NAME(AuthUIDelegate) +@protocol FIRAuthUIDelegate + +/** @fn presentViewController:animated:completion: + @brief If implemented, this method will be invoked when Firebase Auth needs to display a view + controller. + @param viewControllerToPresent The view controller to be presented. + @param flag Decides whether the view controller presentation should be animated or not. + @param completion The block to execute after the presentation finishes. This block has no return + value and takes no parameters. +*/ +- (void)presentViewController:(UIViewController *)viewControllerToPresent + animated:(BOOL)flag + completion:(void (^ _Nullable)(void))completion; + +/** @fn dismissViewControllerAnimated:completion: + @brief If implemented, this method will be invoked when Firebase Auth needs to display a view + controller. + @param flag Decides whether removing the view controller should be animated or not. + @param completion The block to execute after the presentation finishes. This block has no return + value and takes no parameters. +*/ +- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^ _Nullable)(void))completion + NS_SWIFT_NAME(dismiss(animated:completion:)); + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIREmailAuthProvider.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIREmailAuthProvider.h new file mode 100644 index 0000000..aac0bf0 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIREmailAuthProvider.h @@ -0,0 +1,70 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FIRAuthCredential; + +NS_ASSUME_NONNULL_BEGIN + +/** + @brief A string constant identifying the email & password identity provider. + */ +extern NSString *const FIREmailAuthProviderID NS_SWIFT_NAME(EmailAuthProviderID); + +/** + @brief A string constant identifying the email-link sign-in method. + */ +extern NSString *const FIREmailLinkAuthSignInMethod NS_SWIFT_NAME(EmailLinkAuthSignInMethod); + +/** + @brief A string constant identifying the email & password sign-in method. + */ +extern NSString *const FIREmailPasswordAuthSignInMethod + NS_SWIFT_NAME(EmailPasswordAuthSignInMethod); + +/** @class FIREmailAuthProvider + @brief A concrete implementation of `FIRAuthProvider` for Email & Password Sign In. + */ +NS_SWIFT_NAME(EmailAuthProvider) +@interface FIREmailAuthProvider : NSObject + +/** @fn credentialWithEmail:password: + @brief Creates an `FIRAuthCredential` for an email & password sign in. + + @param email The user's email address. + @param password The user's password. + @return A FIRAuthCredential containing the email & password credential. + */ ++ (FIRAuthCredential *)credentialWithEmail:(NSString *)email password:(NSString *)password; + +/** @fn credentialWithEmail:Link: + @brief Creates an `FIRAuthCredential` for an email & link sign in. + + @param email The user's email address. + @param link The email sign-in link. + @return A FIRAuthCredential containing the email & link credential. + */ ++ (FIRAuthCredential *)credentialWithEmail:(NSString *)email link:(NSString *)link; + +/** @fn init + @brief This class is not meant to be initialized. + */ +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRFacebookAuthProvider.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRFacebookAuthProvider.h new file mode 100644 index 0000000..75efe13 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRFacebookAuthProvider.h @@ -0,0 +1,54 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FIRAuthCredential; + +NS_ASSUME_NONNULL_BEGIN + +/** + @brief A string constant identifying the Facebook identity provider. + */ +extern NSString *const FIRFacebookAuthProviderID NS_SWIFT_NAME(FacebookAuthProviderID); + +/** + @brief A string constant identifying the Facebook sign-in method. + */ +extern NSString *const _Nonnull FIRFacebookAuthSignInMethod NS_SWIFT_NAME(FacebookAuthSignInMethod); + +/** @class FIRFacebookAuthProvider + @brief Utility class for constructing Facebook credentials. + */ +NS_SWIFT_NAME(FacebookAuthProvider) +@interface FIRFacebookAuthProvider : NSObject + +/** @fn credentialWithAccessToken: + @brief Creates an `FIRAuthCredential` for a Facebook sign in. + + @param accessToken The Access Token from Facebook. + @return A FIRAuthCredential containing the Facebook credentials. + */ ++ (FIRAuthCredential *)credentialWithAccessToken:(NSString *)accessToken; + +/** @fn init + @brief This class should not be initialized. + */ +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRFederatedAuthProvider.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRFederatedAuthProvider.h new file mode 100644 index 0000000..8f22660 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRFederatedAuthProvider.h @@ -0,0 +1,55 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#if TARGET_OS_IOS +#import "FIRAuthUIDelegate.h" +#endif // TARGET_OS_IOS + +@class FIRAuthCredential; + +NS_ASSUME_NONNULL_BEGIN + +/** + Utility type for constructing federated auth provider credentials. + */ +NS_SWIFT_NAME(FederatedAuthProvider) +@protocol FIRFederatedAuthProvider + +/** @typedef FIRAuthCredentialCallback + @brief The type of block invoked when obtaining an auth credential. + @param credential The credential obtained. + @param error The error that occurred if any. + */ +typedef void(^FIRAuthCredentialCallback)(FIRAuthCredential *_Nullable credential, + NSError *_Nullable error) + NS_SWIFT_NAME(AuthCredentialCallback); + +#if TARGET_OS_IOS +/** @fn getCredentialWithUIDelegate:completion: + @brief Used to obtain an auth credential via a mobile web flow. + @param UIDelegate An optional UI delegate used to presenet the mobile web flow. + @param completion Optionally; a block which is invoked asynchronously on the main thread when + the mobile web flow is completed. + */ +- (void)getCredentialWithUIDelegate:(nullable id)UIDelegate + completion:(nullable FIRAuthCredentialCallback)completion; +#endif // TARGET_OS_IOS + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRGameCenterAuthProvider.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRGameCenterAuthProvider.h new file mode 100644 index 0000000..5e59404 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRGameCenterAuthProvider.h @@ -0,0 +1,62 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FIRAuthCredential; + +NS_ASSUME_NONNULL_BEGIN + +/** + @brief A string constant identifying the Game Center identity provider. + */ +extern NSString *const FIRGameCenterAuthProviderID NS_SWIFT_NAME(GameCenterAuthProviderID); + +/** + @brief A string constant identifying the Game Center sign-in method. + */ +extern NSString *const _Nonnull FIRGameCenterAuthSignInMethod +NS_SWIFT_NAME(GameCenterAuthSignInMethod); + +/** @typedef FIRGameCenterCredentialCallback + @brief The type of block invoked when the Game Center credential code has finished. + @param credential On success, the credential will be provided, nil otherwise. + @param error On error, the error that occurred, nil otherwise. + */ +typedef void (^FIRGameCenterCredentialCallback)(FIRAuthCredential *_Nullable credential, + NSError *_Nullable error) +NS_SWIFT_NAME(GameCenterCredentialCallback); + +/** @class FIRGameCenterAuthProvider + @brief A concrete implementation of @c FIRAuthProvider for Game Center Sign In. + */ +NS_SWIFT_NAME(GameCenterAuthProvider) +@interface FIRGameCenterAuthProvider : NSObject + +/** @fn getCredentialWithCompletion: + @brief Creates a @c FIRAuthCredential for a Game Center sign in. + */ ++ (void)getCredentialWithCompletion:(FIRGameCenterCredentialCallback)completion +NS_SWIFT_NAME(getCredential(completion:)); + +/** @fn init + @brief This class is not meant to be initialized. + */ +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRGitHubAuthProvider.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRGitHubAuthProvider.h new file mode 100644 index 0000000..0610427 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRGitHubAuthProvider.h @@ -0,0 +1,55 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FIRAuthCredential; + +NS_ASSUME_NONNULL_BEGIN + +/** + @brief A string constant identifying the GitHub identity provider. + */ +extern NSString *const FIRGitHubAuthProviderID NS_SWIFT_NAME(GitHubAuthProviderID); + +/** + @brief A string constant identifying the GitHub sign-in method. + */ +extern NSString *const _Nonnull FIRGitHubAuthSignInMethod NS_SWIFT_NAME(GitHubAuthSignInMethod); + + +/** @class FIRGitHubAuthProvider + @brief Utility class for constructing GitHub credentials. + */ +NS_SWIFT_NAME(GitHubAuthProvider) +@interface FIRGitHubAuthProvider : NSObject + +/** @fn credentialWithToken: + @brief Creates an `FIRAuthCredential` for a GitHub sign in. + + @param token The GitHub OAuth access token. + @return A FIRAuthCredential containing the GitHub credential. + */ ++ (FIRAuthCredential *)credentialWithToken:(NSString *)token; + +/** @fn init + @brief This class is not meant to be initialized. + */ +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRGoogleAuthProvider.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRGoogleAuthProvider.h new file mode 100644 index 0000000..7d6fa22 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRGoogleAuthProvider.h @@ -0,0 +1,56 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FIRAuthCredential; + +NS_ASSUME_NONNULL_BEGIN + +/** + @brief A string constant identifying the Google identity provider. + */ +extern NSString *const FIRGoogleAuthProviderID NS_SWIFT_NAME(GoogleAuthProviderID); + +/** + @brief A string constant identifying the Google sign-in method. + */ +extern NSString *const _Nonnull FIRGoogleAuthSignInMethod NS_SWIFT_NAME(GoogleAuthSignInMethod); + +/** @class FIRGoogleAuthProvider + @brief Utility class for constructing Google Sign In credentials. + */ +NS_SWIFT_NAME(GoogleAuthProvider) +@interface FIRGoogleAuthProvider : NSObject + +/** @fn credentialWithIDToken:accessToken: + @brief Creates an `FIRAuthCredential` for a Google sign in. + + @param IDToken The ID Token from Google. + @param accessToken The Access Token from Google. + @return A FIRAuthCredential containing the Google credentials. + */ ++ (FIRAuthCredential *)credentialWithIDToken:(NSString *)IDToken + accessToken:(NSString *)accessToken; + +/** @fn init + @brief This class should not be initialized. + */ +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIROAuthCredential.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIROAuthCredential.h new file mode 100644 index 0000000..94abe4f --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIROAuthCredential.h @@ -0,0 +1,53 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthCredential.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIROAuthCredential + @brief Internal implementation of FIRAuthCredential for generic credentials. + */ +NS_SWIFT_NAME(OAuthCredential) +@interface FIROAuthCredential : FIRAuthCredential + +/** @property IDToken + @brief The ID Token associated with this credential. + */ +@property(nonatomic, readonly, nullable) NSString *IDToken; + +/** @property accessToken + @brief The access token associated with this credential. + */ +@property(nonatomic, readonly, nullable) NSString *accessToken; + +/** @property secret + @brief The secret associated with this credential. This will be nil for OAuth 2.0 providers. + @detail OAuthCredential already exposes a providerId getter. This will help the developer + determine whether an access token/secret pair is needed. + */ +@property(nonatomic, readonly, nullable) NSString *secret; + +/** @fn init + @brief This class is not supposed to be instantiated directly. + */ +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIROAuthProvider.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIROAuthProvider.h new file mode 100644 index 0000000..68a9aa4 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIROAuthProvider.h @@ -0,0 +1,124 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRFederatedAuthProvider.h" + +@class FIRAuth; +@class FIROAuthCredential; + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIROAuthProvider + @brief A concrete implementation of `FIRAuthProvider` for generic OAuth Providers. + */ +NS_SWIFT_NAME(OAuthProvider) +@interface FIROAuthProvider : NSObject + +/** @property scopes + @brief Array used to configure the OAuth scopes. + */ +@property(nonatomic, copy, nullable) NSArray *scopes; + +/** @property customParameters + @brief Dictionary used to configure the OAuth custom parameters. + */ +@property(nonatomic, copy, nullable) NSDictionary *customParameters; + +/** @property providerID + @brief The provider ID indicating the specific OAuth provider this OAuthProvider instance + represents. + */ +@property(nonatomic, copy, readonly) NSString *providerID; + +/** @fn providerWithProviderID: + @param providerID The provider ID of the IDP for which this auth provider instance will be + configured. + @return An instance of FIROAuthProvider corresponding to the specified provider ID. + */ ++ (FIROAuthProvider *)providerWithProviderID:(NSString *)providerID; + +/** @fn providerWithProviderID:auth: + @param providerID The provider ID of the IDP for which this auth provider instance will be + configured. + @param auth The auth instance to be associated with the FIROAuthProvider instance. + @return An instance of FIROAuthProvider corresponding to the specified provider ID. + */ ++ (FIROAuthProvider *)providerWithProviderID:(NSString *)providerID auth:(FIRAuth *)auth; + +/** @fn credentialWithProviderID:IDToken:accessToken: + @brief Creates an `FIRAuthCredential` for that OAuth 2 provider identified by providerID, ID + token and access token. + + @param providerID The provider ID associated with the Auth credential being created. + @param IDToken The IDToken associated with the Auth credential being created. + @param accessToken The accessstoken associated with the Auth credential be created, if + available. + @return A FIRAuthCredential for the specified provider ID, ID token and access token. + */ ++ (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID + IDToken:(NSString *)IDToken + accessToken:(nullable NSString *)accessToken; + +/** @fn credentialWithProviderID:accessToken: + @brief Creates an `FIRAuthCredential` for that OAuth 2 provider identified by providerID using + an ID token. + + @param providerID The provider ID associated with the Auth credential being created. + @param accessToken The accessstoken associated with the Auth credential be created + @return A FIRAuthCredential. + */ ++ (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID + accessToken:(NSString *)accessToken; + +/** @fn credentialWithProviderID:IDToken:rawNonce:accessToken: + @brief Creates an `FIRAuthCredential` for that OAuth 2 provider identified by providerID, ID + token, raw nonce and access token. + + @param providerID The provider ID associated with the Auth credential being created. + @param IDToken The IDToken associated with the Auth credential being created. + @param rawNonce The raw nonce associated with the Auth credential being created. + @param accessToken The accessstoken associated with the Auth credential be created, if + available. + @return A FIRAuthCredential for the specified provider ID, ID token and access token. + */ ++ (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID + IDToken:(NSString *)IDToken + rawNonce:(nullable NSString *)rawNonce + accessToken:(nullable NSString *)accessToken; + +/** @fn credentialWithProviderID:IDToken:rawNonce: + @brief Creates an `FIRAuthCredential` for that OAuth 2 provider identified by providerID using + an ID token and raw nonce. + + @param providerID The provider ID associated with the Auth credential being created. + @param IDToken The IDToken associated with the Auth credential being created. + @param rawNonce The raw nonce associated with the Auth credential being created. + @return A FIRAuthCredential. + */ ++ (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID + IDToken:(NSString *)IDToken + rawNonce:(nullable NSString *)rawNonce; + +/** @fn init + @brief This class is not meant to be initialized. + */ +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRPhoneAuthCredential.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRPhoneAuthCredential.h new file mode 100644 index 0000000..69289fb --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRPhoneAuthCredential.h @@ -0,0 +1,41 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#if !TARGET_OS_OSX && !TARGET_OS_TV + +#import + +#import "FIRAuthCredential.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRPhoneAuthCredential + @brief Implementation of FIRAuthCredential for Phone Auth credentials. + */ +NS_SWIFT_NAME(PhoneAuthCredential) +@interface FIRPhoneAuthCredential : FIRAuthCredential + +/** @fn init + @brief This class is not supposed to be instantiated directly. + */ +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRPhoneAuthProvider.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRPhoneAuthProvider.h new file mode 100644 index 0000000..fce1c23 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRPhoneAuthProvider.h @@ -0,0 +1,110 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#if !TARGET_OS_OSX && !TARGET_OS_TV + +#import + +@class FIRAuth; +@class FIRPhoneAuthCredential; +@protocol FIRAuthUIDelegate; + +NS_ASSUME_NONNULL_BEGIN + +/** @var FIRPhoneAuthProviderID + @brief A string constant identifying the phone identity provider. + */ +extern NSString *const FIRPhoneAuthProviderID NS_SWIFT_NAME(PhoneAuthProviderID); + +/** @var FIRPhoneAuthProviderID + @brief A string constant identifying the phone sign-in method. + */ +extern NSString *const _Nonnull FIRPhoneAuthSignInMethod NS_SWIFT_NAME(PhoneAuthSignInMethod); + +/** @typedef FIRVerificationResultCallback + @brief The type of block invoked when a request to send a verification code has finished. + + @param verificationID On success, the verification ID provided, nil otherwise. + @param error On error, the error that occurred, nil otherwise. + */ +typedef void (^FIRVerificationResultCallback) + (NSString *_Nullable verificationID, NSError *_Nullable error) + NS_SWIFT_NAME(VerificationResultCallback); + +/** @class FIRPhoneAuthProvider + @brief A concrete implementation of `FIRAuthProvider` for phone auth providers. + */ +NS_SWIFT_NAME(PhoneAuthProvider) +@interface FIRPhoneAuthProvider : NSObject + +/** @fn provider + @brief Returns an instance of `FIRPhoneAuthProvider` for the default `FIRAuth` object. + */ ++ (instancetype)provider NS_SWIFT_NAME(provider()); + +/** @fn providerWithAuth: + @brief Returns an instance of `FIRPhoneAuthProvider` for the provided `FIRAuth` object. + + @param auth The auth object to associate with the phone auth provider instance. + */ ++ (instancetype)providerWithAuth:(FIRAuth *)auth NS_SWIFT_NAME(provider(auth:)); + +/** @fn verifyPhoneNumber:UIDelegate:completion: + @brief Starts the phone number authentication flow by sending a verification code to the + specified phone number. + @param phoneNumber The phone number to be verified. + @param UIDelegate An object used to present the SFSafariViewController. The object is retained + by this method until the completion block is executed. + @param completion The callback to be invoked when the verification flow is finished. + @remarks Possible error codes: + + + `FIRAuthErrorCodeCaptchaCheckFailed` - Indicates that the reCAPTCHA token obtained by + the Firebase Auth is invalid or has expired. + + `FIRAuthErrorCodeQuotaExceeded` - Indicates that the phone verification quota for this + project has been exceeded. + + `FIRAuthErrorCodeInvalidPhoneNumber` - Indicates that the phone number provided is + invalid. + + `FIRAuthErrorCodeMissingPhoneNumber` - Indicates that a phone number was not provided. + */ +- (void)verifyPhoneNumber:(NSString *)phoneNumber + UIDelegate:(nullable id)UIDelegate + completion:(nullable FIRVerificationResultCallback)completion; + +/** @fn credentialWithVerificationID:verificationCode: + @brief Creates an `FIRAuthCredential` for the phone number provider identified by the + verification ID and verification code. + + @param verificationID The verification ID obtained from invoking + verifyPhoneNumber:completion: + @param verificationCode The verification code obtained from the user. + @return The corresponding phone auth credential for the verification ID and verification code + provided. + */ +- (FIRPhoneAuthCredential *)credentialWithVerificationID:(NSString *)verificationID + verificationCode:(NSString *)verificationCode; + +/** @fn init + @brief Please use the `provider` or `providerWithAuth:` methods to obtain an instance of + `FIRPhoneAuthProvider`. + */ +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRTwitterAuthProvider.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRTwitterAuthProvider.h new file mode 100644 index 0000000..0f1b28d --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRTwitterAuthProvider.h @@ -0,0 +1,54 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FIRAuthCredential; + +NS_ASSUME_NONNULL_BEGIN + +/** + @brief A string constant identifying the Twitter identity provider. + */ +extern NSString *const FIRTwitterAuthProviderID NS_SWIFT_NAME(TwitterAuthProviderID); +/** + @brief A string constant identifying the Twitter sign-in method. + */ +extern NSString *const _Nonnull FIRTwitterAuthSignInMethod NS_SWIFT_NAME(TwitterAuthSignInMethod); + +/** @class FIRTwitterAuthProvider + @brief Utility class for constructing Twitter credentials. + */ +NS_SWIFT_NAME(TwitterAuthProvider) +@interface FIRTwitterAuthProvider : NSObject + +/** @fn credentialWithToken:secret: + @brief Creates an `FIRAuthCredential` for a Twitter sign in. + + @param token The Twitter OAuth token. + @param secret The Twitter OAuth secret. + @return A FIRAuthCredential containing the Twitter credential. + */ ++ (FIRAuthCredential *)credentialWithToken:(NSString *)token secret:(NSString *)secret; + +/** @fn init + @brief This class is not meant to be initialized. + */ +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRUser.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRUser.h new file mode 100644 index 0000000..3649429 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRUser.h @@ -0,0 +1,523 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuth.h" +#import "FIRAuthDataResult.h" +#import "FIRUserInfo.h" + +@class FIRAuthTokenResult; +@class FIRPhoneAuthCredential; +@class FIRUserProfileChangeRequest; +@class FIRUserMetadata; +@protocol FIRAuthUIDelegate; + +NS_ASSUME_NONNULL_BEGIN + +/** @typedef FIRAuthTokenCallback + @brief The type of block called when a token is ready for use. + @see FIRUser.getIDTokenWithCompletion: + @see FIRUser.getIDTokenForcingRefresh:withCompletion: + + @param token Optionally; an access token if the request was successful. + @param error Optionally; the error which occurred - or nil if the request was successful. + + @remarks One of: `token` or `error` will always be non-nil. + */ +typedef void (^FIRAuthTokenCallback)(NSString *_Nullable token, NSError *_Nullable error) + NS_SWIFT_NAME(AuthTokenCallback); + +/** @typedef FIRAuthTokenResultCallback + @brief The type of block called when a token is ready for use. + @see FIRUser.getIDTokenResultWithCompletion: + @see FIRUser.getIDTokenResultForcingRefresh:withCompletion: + + @param tokenResult Optionally; an object containing the raw access token string as well as other + useful data pertaining to the token. + @param error Optionally; the error which occurred - or nil if the request was successful. + + @remarks One of: `token` or `error` will always be non-nil. + */ +typedef void (^FIRAuthTokenResultCallback)(FIRAuthTokenResult *_Nullable tokenResult, + NSError *_Nullable error) + NS_SWIFT_NAME(AuthTokenResultCallback); + +/** @typedef FIRUserProfileChangeCallback + @brief The type of block called when a user profile change has finished. + + @param error Optionally; the error which occurred - or nil if the request was successful. + */ +typedef void (^FIRUserProfileChangeCallback)(NSError *_Nullable error) + NS_SWIFT_NAME(UserProfileChangeCallback); + +/** @typedef FIRSendEmailVerificationCallback + @brief The type of block called when a request to send an email verification has finished. + + @param error Optionally; the error which occurred - or nil if the request was successful. + */ +typedef void (^FIRSendEmailVerificationCallback)(NSError *_Nullable error) + NS_SWIFT_NAME(SendEmailVerificationCallback); + +/** @class FIRUser + @brief Represents a user. Firebase Auth does not attempt to validate users + when loading them from the keychain. Invalidated users (such as those + whose passwords have been changed on another client) are automatically + logged out when an auth-dependent operation is attempted or when the + ID token is automatically refreshed. + @remarks This class is thread-safe. + */ +NS_SWIFT_NAME(User) +@interface FIRUser : NSObject + +/** @property anonymous + @brief Indicates the user represents an anonymous user. + */ +@property(nonatomic, readonly, getter=isAnonymous) BOOL anonymous; + +/** @property emailVerified + @brief Indicates the email address associated with this user has been verified. + */ +@property(nonatomic, readonly, getter=isEmailVerified) BOOL emailVerified; + +/** @property refreshToken + @brief A refresh token; useful for obtaining new access tokens independently. + @remarks This property should only be used for advanced scenarios, and is not typically needed. + */ +@property(nonatomic, readonly, nullable) NSString *refreshToken; + +/** @property providerData + @brief Profile data for each identity provider, if any. + @remarks This data is cached on sign-in and updated when linking or unlinking. + */ +@property(nonatomic, readonly, nonnull) NSArray> *providerData; + +/** @property metadata + @brief Metadata associated with the Firebase user in question. + */ +@property(nonatomic, readonly, nonnull) FIRUserMetadata *metadata; + +/** @fn init + @brief This class should not be instantiated. + @remarks To retrieve the current user, use `FIRAuth.currentUser`. To sign a user + in or out, use the methods on `FIRAuth`. + */ +- (instancetype)init NS_UNAVAILABLE; + +/** @fn updateEmail:completion: + @brief Updates the email address for the user. On success, the cached user profile data is + updated. + @remarks May fail if there is already an account with this email address that was created using + email and password authentication. + + @param email The email address for the user. + @param completion Optionally; the block invoked when the user profile change has finished. + Invoked asynchronously on the main thread in the future. + + @remarks Possible error codes: + + + `FIRAuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was + sent in the request. + + `FIRAuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in + the console for this action. + + `FIRAuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for + sending update email. + + `FIRAuthErrorCodeEmailAlreadyInUse` - Indicates the email is already in use by another + account. + + `FIRAuthErrorCodeInvalidEmail` - Indicates the email address is malformed. + + `FIRAuthErrorCodeRequiresRecentLogin` - Updating a user’s email is a security + sensitive operation that requires a recent login from the user. This error indicates + the user has not signed in recently enough. To resolve, reauthenticate the user by + invoking reauthenticateWithCredential:completion: on FIRUser. + + @remarks See `FIRAuthErrors` for a list of error codes that are common to all FIRUser methods. + */ +- (void)updateEmail:(NSString *)email completion:(nullable FIRUserProfileChangeCallback)completion + NS_SWIFT_NAME(updateEmail(to:completion:)); + +/** @fn updatePassword:completion: + @brief Updates the password for the user. On success, the cached user profile data is updated. + + @param password The new password for the user. + @param completion Optionally; the block invoked when the user profile change has finished. + Invoked asynchronously on the main thread in the future. + + @remarks Possible error codes: + + + `FIRAuthErrorCodeOperationNotAllowed` - Indicates the administrator disabled + sign in with the specified identity provider. + + `FIRAuthErrorCodeRequiresRecentLogin` - Updating a user’s password is a security + sensitive operation that requires a recent login from the user. This error indicates + the user has not signed in recently enough. To resolve, reauthenticate the user by + invoking reauthenticateWithCredential:completion: on FIRUser. + + `FIRAuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is + considered too weak. The NSLocalizedFailureReasonErrorKey field in the NSError.userInfo + dictionary object will contain more detailed explanation that can be shown to the user. + + @remarks See `FIRAuthErrors` for a list of error codes that are common to all FIRUser methods. + */ +- (void)updatePassword:(NSString *)password + completion:(nullable FIRUserProfileChangeCallback)completion + NS_SWIFT_NAME(updatePassword(to:completion:)); + +#if TARGET_OS_IOS +/** @fn updatePhoneNumberCredential:completion: + @brief Updates the phone number for the user. On success, the cached user profile data is + updated. + + @param phoneNumberCredential The new phone number credential corresponding to the phone number + to be added to the Firebase account, if a phone number is already linked to the account this + new phone number will replace it. + @param completion Optionally; the block invoked when the user profile change has finished. + Invoked asynchronously on the main thread in the future. + + @remarks Possible error codes: + + + `FIRAuthErrorCodeRequiresRecentLogin` - Updating a user’s phone number is a security + sensitive operation that requires a recent login from the user. This error indicates + the user has not signed in recently enough. To resolve, reauthenticate the user by + invoking reauthenticateWithCredential:completion: on FIRUser. + + @remarks See `FIRAuthErrors` for a list of error codes that are common to all FIRUser methods. + */ +- (void)updatePhoneNumberCredential:(FIRPhoneAuthCredential *)phoneNumberCredential + completion:(nullable FIRUserProfileChangeCallback)completion; +#endif + +/** @fn profileChangeRequest + @brief Creates an object which may be used to change the user's profile data. + + @remarks Set the properties of the returned object, then call + `FIRUserProfileChangeRequest.commitChangesWithCallback:` to perform the updates atomically. + + @return An object which may be used to change the user's profile data atomically. + */ +- (FIRUserProfileChangeRequest *)profileChangeRequest NS_SWIFT_NAME(createProfileChangeRequest()); + +/** @fn reloadWithCompletion: + @brief Reloads the user's profile data from the server. + + @param completion Optionally; the block invoked when the reload has finished. Invoked + asynchronously on the main thread in the future. + + @remarks May fail with a `FIRAuthErrorCodeRequiresRecentLogin` error code. In this case + you should call `FIRUser.reauthenticateWithCredential:completion:` before re-invoking + `FIRUser.updateEmail:completion:`. + + @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods. + */ +- (void)reloadWithCompletion:(nullable FIRUserProfileChangeCallback)completion; + +/** @fn reauthenticateWithCredential:completion: + @brief Renews the user's authentication tokens by validating a fresh set of credentials supplied + by the user and returns additional identity provider data. + + @param credential A user-supplied credential, which will be validated by the server. This can be + a successful third-party identity provider sign-in, or an email address and password. + @param completion Optionally; the block invoked when the re-authentication operation has + finished. Invoked asynchronously on the main thread in the future. + + @remarks If the user associated with the supplied credential is different from the current user, + or if the validation of the supplied credentials fails; an error is returned and the current + user remains signed in. + + @remarks Possible error codes: + + + `FIRAuthErrorCodeInvalidCredential` - Indicates the supplied credential is invalid. + This could happen if it has expired or it is malformed. + + `FIRAuthErrorCodeOperationNotAllowed` - Indicates that accounts with the + identity provider represented by the credential are not enabled. Enable them in the + Auth section of the Firebase console. + + `FIRAuthErrorCodeEmailAlreadyInUse` - Indicates the email asserted by the credential + (e.g. the email in a Facebook access token) is already in use by an existing account, + that cannot be authenticated with this method. Call fetchProvidersForEmail for + this user’s email and then prompt them to sign in with any of the sign-in providers + returned. This error will only be thrown if the "One account per email address" + setting is enabled in the Firebase console, under Auth settings. Please note that the + error code raised in this specific situation may not be the same on Web and Android. + + `FIRAuthErrorCodeUserDisabled` - Indicates the user's account is disabled. + + `FIRAuthErrorCodeWrongPassword` - Indicates the user attempted reauthentication with + an incorrect password, if credential is of the type EmailPasswordAuthCredential. + + `FIRAuthErrorCodeUserMismatch` - Indicates that an attempt was made to + reauthenticate with a user which is not the current user. + + `FIRAuthErrorCodeInvalidEmail` - Indicates the email address is malformed. + + @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods. + */ +- (void)reauthenticateWithCredential:(FIRAuthCredential *)credential + completion:(nullable FIRAuthDataResultCallback)completion; + +/** @fn reauthenticateAndRetrieveDataWithCredential:completion: + @brief Please use linkWithCredential:completion: for Objective-C + or link(withCredential:completion:) for Swift instead. + */ +- (void)reauthenticateAndRetrieveDataWithCredential:(FIRAuthCredential *)credential + completion:(nullable FIRAuthDataResultCallback)completion +DEPRECATED_MSG_ATTRIBUTE( "Please use reauthenticateWithCredential:completion: for" + " Objective-C or reauthenticate(withCredential:completion:)" + " for Swift instead."); + +/** @fn reauthenticateWithProvider:UIDelegate:completion: + @brief Renews the user's authentication using the provided auth provider instance. + + @param provider An instance of an auth provider used to initiate the reauthenticate flow. + @param UIDelegate Optionally an instance of a class conforming to the FIRAuthUIDelegate + protocol, this is used for presenting the web context. If nil, a default FIRAuthUIDelegate + will be used. + @param completion Optionally; a block which is invoked when the reauthenticate flow finishes, or + is canceled. Invoked asynchronously on the main thread in the future. + */ +- (void)reauthenticateWithProvider:(id)provider + UIDelegate:(nullable id)UIDelegate + completion:(nullable FIRAuthDataResultCallback)completion + NS_SWIFT_NAME(reauthenticate(with:uiDelegate:completion:)) + API_AVAILABLE(ios(8.0)); + +/** @fn getIDTokenResultWithCompletion: + @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired. + + @param completion Optionally; the block invoked when the token is available. Invoked + asynchronously on the main thread in the future. + + @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods. + */ +- (void)getIDTokenResultWithCompletion:(nullable FIRAuthTokenResultCallback)completion + NS_SWIFT_NAME(getIDTokenResult(completion:)); + +/** @fn getIDTokenResultForcingRefresh:completion: + @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired. + + @param forceRefresh Forces a token refresh. Useful if the token becomes invalid for some reason + other than an expiration. + @param completion Optionally; the block invoked when the token is available. Invoked + asynchronously on the main thread in the future. + + @remarks The authentication token will be refreshed (by making a network request) if it has + expired, or if `forceRefresh` is YES. + + @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods. + */ +- (void)getIDTokenResultForcingRefresh:(BOOL)forceRefresh + completion:(nullable FIRAuthTokenResultCallback)completion + NS_SWIFT_NAME(getIDTokenResult(forcingRefresh:completion:)); + +/** @fn getIDTokenWithCompletion: + @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired. + + @param completion Optionally; the block invoked when the token is available. Invoked + asynchronously on the main thread in the future. + + @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods. + */ +- (void)getIDTokenWithCompletion:(nullable FIRAuthTokenCallback)completion + NS_SWIFT_NAME(getIDToken(completion:)); + +/** @fn getIDTokenForcingRefresh:completion: + @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired. + + @param forceRefresh Forces a token refresh. Useful if the token becomes invalid for some reason + other than an expiration. + @param completion Optionally; the block invoked when the token is available. Invoked + asynchronously on the main thread in the future. + + @remarks The authentication token will be refreshed (by making a network request) if it has + expired, or if `forceRefresh` is YES. + + @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods. + */ +- (void)getIDTokenForcingRefresh:(BOOL)forceRefresh + completion:(nullable FIRAuthTokenCallback)completion; + +/** @fn linkAndRetrieveDataWithCredential:completion: + @brief Please use linkWithCredential:completion: for Objective-C + or link(withCredential:completion:) for Swift instead. + */ +- (void)linkAndRetrieveDataWithCredential:(FIRAuthCredential *)credential + completion:(nullable FIRAuthDataResultCallback)completion +DEPRECATED_MSG_ATTRIBUTE("Please use linkWithCredential:completion: for Objective-C " + "or link(withCredential:completion:) for Swift instead."); + +/** @fn linkWithCredential:completion: + @brief Associates a user account from a third-party identity provider with this user and + returns additional identity provider data. + + @param credential The credential for the identity provider. + @param completion Optionally; the block invoked when the unlinking is complete, or fails. + Invoked asynchronously on the main thread in the future. + + @remarks Possible error codes: + + + `FIRAuthErrorCodeProviderAlreadyLinked` - Indicates an attempt to link a provider of a + type already linked to this account. + + `FIRAuthErrorCodeCredentialAlreadyInUse` - Indicates an attempt to link with a + credential that has already been linked with a different Firebase account. + + `FIRAuthErrorCodeOperationNotAllowed` - Indicates that accounts with the identity + provider represented by the credential are not enabled. Enable them in the Auth section + of the Firebase console. + + @remarks This method may also return error codes associated with updateEmail:completion: and + updatePassword:completion: on FIRUser. + + @remarks See `FIRAuthErrors` for a list of error codes that are common to all FIRUser methods. + */ +- (void)linkWithCredential:(FIRAuthCredential *)credential + completion:(nullable FIRAuthDataResultCallback)completion; + +/** @fn linkWithProvider:UIDelegate:completion: + @brief link the user with the provided auth provider instance. + + @param provider An instance of an auth provider used to initiate the link flow. + @param UIDelegate Optionally an instance of a class conforming to the FIRAuthUIDelegate + protocol, this is used for presenting the web context. If nil, a default FIRAuthUIDelegate + will be used. + @param completion Optionally; a block which is invoked when the link flow finishes, or + is canceled. Invoked asynchronously on the main thread in the future. + */ +- (void)linkWithProvider:(id)provider + UIDelegate:(nullable id)UIDelegate + completion:(nullable FIRAuthDataResultCallback)completion + NS_SWIFT_NAME(link(with:uiDelegate:completion:)) + API_AVAILABLE(ios(8.0)); + +/** @fn unlinkFromProvider:completion: + @brief Disassociates a user account from a third-party identity provider with this user. + + @param provider The provider ID of the provider to unlink. + @param completion Optionally; the block invoked when the unlinking is complete, or fails. + Invoked asynchronously on the main thread in the future. + + @remarks Possible error codes: + + + `FIRAuthErrorCodeNoSuchProvider` - Indicates an attempt to unlink a provider + that is not linked to the account. + + `FIRAuthErrorCodeRequiresRecentLogin` - Updating email is a security sensitive + operation that requires a recent login from the user. This error indicates the user + has not signed in recently enough. To resolve, reauthenticate the user by invoking + reauthenticateWithCredential:completion: on FIRUser. + + @remarks See `FIRAuthErrors` for a list of error codes that are common to all FIRUser methods. + */ +- (void)unlinkFromProvider:(NSString *)provider + completion:(nullable FIRAuthResultCallback)completion; + +/** @fn sendEmailVerificationWithCompletion: + @brief Initiates email verification for the user. + + @param completion Optionally; the block invoked when the request to send an email verification + is complete, or fails. Invoked asynchronously on the main thread in the future. + + @remarks Possible error codes: + + + `FIRAuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was + sent in the request. + + `FIRAuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in + the console for this action. + + `FIRAuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for + sending update email. + + `FIRAuthErrorCodeUserNotFound` - Indicates the user account was not found. + + @remarks See `FIRAuthErrors` for a list of error codes that are common to all FIRUser methods. + */ +- (void)sendEmailVerificationWithCompletion:(nullable FIRSendEmailVerificationCallback)completion; + +/** @fn sendEmailVerificationWithActionCodeSettings:completion: + @brief Initiates email verification for the user. + + @param actionCodeSettings An `FIRActionCodeSettings` object containing settings related to + handling action codes. + + @remarks Possible error codes: + + + `FIRAuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was + sent in the request. + + `FIRAuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in + the console for this action. + + `FIRAuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for + sending update email. + + `FIRAuthErrorCodeUserNotFound` - Indicates the user account was not found. + + `FIRAuthErrorCodeMissingIosBundleID` - Indicates that the iOS bundle ID is missing when + a iOS App Store ID is provided. + + `FIRAuthErrorCodeMissingAndroidPackageName` - Indicates that the android package name + is missing when the `androidInstallApp` flag is set to true. + + `FIRAuthErrorCodeUnauthorizedDomain` - Indicates that the domain specified in the + continue URL is not whitelisted in the Firebase console. + + `FIRAuthErrorCodeInvalidContinueURI` - Indicates that the domain specified in the + continue URI is not valid. + */ +- (void)sendEmailVerificationWithActionCodeSettings:(FIRActionCodeSettings *)actionCodeSettings + completion:(nullable FIRSendEmailVerificationCallback) + completion; + +/** @fn deleteWithCompletion: + @brief Deletes the user account (also signs out the user, if this was the current user). + + @param completion Optionally; the block invoked when the request to delete the account is + complete, or fails. Invoked asynchronously on the main thread in the future. + + @remarks Possible error codes: + + + `FIRAuthErrorCodeRequiresRecentLogin` - Updating email is a security sensitive + operation that requires a recent login from the user. This error indicates the user + has not signed in recently enough. To resolve, reauthenticate the user by invoking + reauthenticateWithCredential:completion: on FIRUser. + + @remarks See `FIRAuthErrors` for a list of error codes that are common to all FIRUser methods. + + */ +- (void)deleteWithCompletion:(nullable FIRUserProfileChangeCallback)completion; + +@end + +/** @class FIRUserProfileChangeRequest + @brief Represents an object capable of updating a user's profile data. + @remarks Properties are marked as being part of a profile update when they are set. Setting a + property value to nil is not the same as leaving the property unassigned. + */ +NS_SWIFT_NAME(UserProfileChangeRequest) +@interface FIRUserProfileChangeRequest : NSObject + +/** @fn init + @brief Please use `FIRUser.profileChangeRequest` + */ +- (instancetype)init NS_UNAVAILABLE; + +/** @property displayName + @brief The user's display name. + @remarks It is an error to set this property after calling + `FIRUserProfileChangeRequest.commitChangesWithCallback:` + */ +@property(nonatomic, copy, nullable) NSString *displayName; + +/** @property photoURL + @brief The user's photo URL. + @remarks It is an error to set this property after calling + `FIRUserProfileChangeRequest.commitChangesWithCallback:` + */ +@property(nonatomic, copy, nullable) NSURL *photoURL; + +/** @fn commitChangesWithCompletion: + @brief Commits any pending changes. + @remarks This method should only be called once. Once called, property values should not be + changed. + + @param completion Optionally; the block invoked when the user profile change has been applied. + Invoked asynchronously on the main thread in the future. + */ +- (void)commitChangesWithCompletion:(nullable FIRUserProfileChangeCallback)completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRUserInfo.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRUserInfo.h new file mode 100644 index 0000000..04eca49 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRUserInfo.h @@ -0,0 +1,60 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + @brief Represents user data returned from an identity provider. + */ +NS_SWIFT_NAME(UserInfo) +@protocol FIRUserInfo + +/** @property providerID + @brief The provider identifier. + */ +@property(nonatomic, copy, readonly) NSString *providerID; + +/** @property uid + @brief The provider's user ID for the user. + */ +@property(nonatomic, copy, readonly) NSString *uid; + +/** @property displayName + @brief The name of the user. + */ +@property(nonatomic, copy, readonly, nullable) NSString *displayName; + +/** @property photoURL + @brief The URL of the user's profile photo. + */ +@property(nonatomic, copy, readonly, nullable) NSURL *photoURL; + +/** @property email + @brief The user's email address. + */ +@property(nonatomic, copy, readonly, nullable) NSString *email; + +/** @property phoneNumber + @brief A phone number associated with the user. + @remarks This property is only available for users authenticated via phone number auth. + */ +@property(nonatomic, readonly, nullable) NSString *phoneNumber; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRUserMetadata.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRUserMetadata.h new file mode 100644 index 0000000..3ceae38 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FIRUserMetadata.h @@ -0,0 +1,45 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRUserMetadata + @brief A data class representing the metadata corresponding to a Firebase user. + */ +NS_SWIFT_NAME(UserMetadata) +@interface FIRUserMetadata : NSObject + +/** @property lastSignInDate + @brief Stores the last sign in date for the corresponding Firebase user. + */ +@property(copy, nonatomic, readonly, nullable) NSDate *lastSignInDate; + +/** @property creationDate + @brief Stores the creation date for the corresponding Firebase user. + */ +@property(copy, nonatomic, readonly, nullable) NSDate *creationDate; + +/** @fn init + @brief This class should not be initialized manually, an instance of this class can be obtained + from a Firebase user object. + */ +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FirebaseAuth.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FirebaseAuth.h new file mode 100644 index 0000000..462d2ec --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FirebaseAuth.h @@ -0,0 +1,46 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRActionCodeSettings.h" +#import "FIRAdditionalUserInfo.h" +#import "FIRAuth.h" +#import "FIRAuthCredential.h" +#import "FIRAuthDataResult.h" +#import "FIRAuthErrors.h" +#import "FIRAuthTokenResult.h" +#import "FirebaseAuthVersion.h" +#import "FIREmailAuthProvider.h" +#import "FIRFacebookAuthProvider.h" +#import "FIRFederatedAuthProvider.h" +#import "FIRGameCenterAuthProvider.h" +#import "FIRGitHubAuthProvider.h" +#import "FIRGoogleAuthProvider.h" +#import "FIROAuthCredential.h" +#import "FIROAuthProvider.h" +#import "FIRTwitterAuthProvider.h" +#import "FIRUser.h" +#import "FIRUserInfo.h" +#import "FIRUserMetadata.h" + +#if TARGET_OS_IOS +#import "FIRAuthUIDelegate.h" +#import "FIRPhoneAuthCredential.h" +#import "FIRPhoneAuthProvider.h" +#import "FIRAuthAPNSTokenType.h" +#import "FIRAuthSettings.h" +#endif diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FirebaseAuthVersion.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FirebaseAuthVersion.h new file mode 100644 index 0000000..7b4b94e --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Public/FirebaseAuthVersion.h @@ -0,0 +1,27 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +/** + Version number for FirebaseAuth. + */ +extern const double FirebaseAuthVersionNum; + +/** + Version string for FirebaseAuth. + */ +extern const char *const FirebaseAuthVersionStr; diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Storage/FIRAuthKeychainServices.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Storage/FIRAuthKeychainServices.h new file mode 100644 index 0000000..2673c36 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Storage/FIRAuthKeychainServices.h @@ -0,0 +1,98 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + @brief The protocol for permanant data storage. + */ +@protocol FIRAuthStorage + +/** @fn initWithService: + @brief Initialize a @c FIRAuthStorage instance. + @param service The name of the storage service to use. + @return An initialized @c FIRAuthStorage instance for the specified service. + */ +- (id)initWithService:(NSString *)service; + +/** @fn dataForKey:error: + @brief Gets the data for @c key in the storage. The key is set for the attribute + @c kSecAttrAccount of a generic password query. + @param key The key to use. + @param error The address to store any error that occurs during the process, if not NULL. + If the operation was successful, its content is set to @c nil . + @return The data stored in the storage for @c key, if any. + */ +- (nullable NSData *)dataForKey:(NSString *)key error:(NSError **_Nullable)error; + +/** @fn setData:forKey:error: + @brief Sets the data for @c key in the storage. The key is set for the attribute + @c kSecAttrAccount of a generic password query. + @param data The data to store. + @param key The key to use. + @param error The address to store any error that occurs during the process, if not NULL. + @return Whether the operation succeeded or not. + */ +- (BOOL)setData:(NSData *)data forKey:(NSString *)key error:(NSError **_Nullable)error; + +/** @fn removeDataForKey:error: + @brief Removes the data for @c key in the storage. The key is set for the attribute + @c kSecAttrAccount of a generic password query. + @param key The key to use. + @param error The address to store any error that occurs during the process, if not NULL. + @return Whether the operation succeeded or not. + */ +- (BOOL)removeDataForKey:(NSString *)key error:(NSError **_Nullable)error; + +@end + +/** @class FIRAuthKeychain + @brief The utility class to manipulate data in iOS Keychain. + */ +@interface FIRAuthKeychainServices : NSObject + +/** @fn getItemWithQuery:error: + @brief Get the item from keychain by given query. + @param query The query to query the keychain. + @param outError The address to store any error that occurs during the process, if not nil. + @return The item of the given query. nil if not exsit. + */ +- (nullable NSData *)getItemWithQuery:(NSDictionary *)query + error:(NSError *_Nullable *_Nullable)outError; + +/** @fn setItem:withQuery:error: + @brief Set the item into keychain with given query. + @param item The item to be added into keychain. + @param query The query to query the keychain. + @param outError The address to store any error that occurs during the process, if not nil. + @return Whether the operation succeed. + */ +- (BOOL)setItem:(NSData *)item withQuery:(NSDictionary *)query + error:(NSError *_Nullable *_Nullable)outError; + +/** @fn getItemWithQuery:error: + @brief Remove the item with given queryfrom keychain. + @param query The query to query the keychain. + @param outError The address to store any error that occurs during the process, if not nil. + @return Whether the operation succeed. + */ +- (BOOL)removeItemWithQuery:(NSDictionary *)query error:(NSError *_Nullable *_Nullable)outError; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Storage/FIRAuthKeychainServices.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Storage/FIRAuthKeychainServices.m new file mode 100644 index 0000000..83b34ae --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Storage/FIRAuthKeychainServices.m @@ -0,0 +1,327 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRAuthKeychainServices.h" + +#import + +#import "FIRAuthErrorUtils.h" +#import "FIRAuthUserDefaults.h" + +/** @var kAccountPrefix + @brief The prefix string for keychain item account attribute before the key. + @remarks A number "1" is encoded in the prefix in case we need to upgrade the scheme in future. + */ +static NSString *const kAccountPrefix = @"firebase_auth_1_"; + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRAuthKeychainServices { + /** @var _service + @brief The name of the keychain service. + */ + NSString *_service; + + /** @var _legacyItemDeletedForKey + @brief Indicates whether or not this class knows that the legacy item for a particular key has + been deleted. + @remarks This dictionary is to avoid unecessary keychain operations against legacy items. + */ + NSMutableDictionary *_legacyEntryDeletedForKey; +} + +- (id)initWithService:(NSString *)service { + + self = [super init]; + if (self) { + _service = [service copy]; + _legacyEntryDeletedForKey = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (nullable NSData *)dataForKey:(NSString *)key error:(NSError **_Nullable)error { + if (!key.length) { + [NSException raise:NSInvalidArgumentException + format:@"%@", @"The key cannot be nil or empty."]; + return nil; + } + NSData *data = [self itemWithQuery:[self genericPasswordQueryWithKey:key] error:error]; + if (error && *error) { + return nil; + } + if (data) { + return data; + } + // Check for legacy form. + if (_legacyEntryDeletedForKey[key]) { + return nil; + } + data = [self itemWithQuery:[self legacyGenericPasswordQueryWithKey:key] error:error]; + if (error && *error) { + return nil; + } + if (!data) { + // Mark legacy data as non-existing so we don't have to query it again. + _legacyEntryDeletedForKey[key] = @YES; + return nil; + } + // Move the data to current form. + if (![self setData:data forKey:key error:error]) { + return nil; + } + [self deleteLegacyItemWithKey:key]; + return data; +} + +- (BOOL)setData:(NSData *)data forKey:(NSString *)key error:(NSError **_Nullable)error { + if (!key.length) { + [NSException raise:NSInvalidArgumentException + format:@"%@", @"The key cannot be nil or empty."]; + return NO; + } + NSDictionary *attributes = @{ + (__bridge id)kSecValueData : data, + (__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, + }; + return [self setItemWithQuery:[self genericPasswordQueryWithKey:key] + attributes:attributes + error:error]; +} + +- (BOOL)removeDataForKey:(NSString *)key error:(NSError **_Nullable)error { + if (!key.length) { + [NSException raise:NSInvalidArgumentException + format:@"%@", @"The key cannot be nil or empty."]; + return NO; + } + if (![self deleteItemWithQuery:[self genericPasswordQueryWithKey:key] error:error]) { + return NO; + } + // Legacy form item, if exists, also needs to be removed, otherwise it will be exposed when + // current form item is removed, leading to incorrect semantics. + [self deleteLegacyItemWithKey:key]; + return YES; +} + +#pragma mark - Private methods for non-sharing keychain operations + +- (nullable NSData *)itemWithQuery:(NSDictionary *)query error:(NSError **_Nullable)error { + NSMutableDictionary *returningQuery = [query mutableCopy]; + returningQuery[(__bridge id)kSecReturnData] = @YES; + returningQuery[(__bridge id)kSecReturnAttributes] = @YES; + // Using a match limit of 2 means that we can check whether there is more than one item. + // If we used a match limit of 1 we would never find out. + returningQuery[(__bridge id)kSecMatchLimit] = @2; + + CFArrayRef result = NULL; + OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)returningQuery, + (CFTypeRef *)&result); + + if (status == noErr && result != NULL) { + NSArray *items = (__bridge_transfer NSArray *)result; + if (items.count != 1) { + if (error) { + *error = [FIRAuthErrorUtils keychainErrorWithFunction:@"SecItemCopyMatching" + status:status]; + } + return nil; + } + + if (error) { + *error = nil; + } + NSDictionary *item = items[0]; + return item[(__bridge id)kSecValueData]; + } + + if (status == errSecItemNotFound) { + if (error) { + *error = nil; + } + } else { + if (error) { + *error = [FIRAuthErrorUtils keychainErrorWithFunction:@"SecItemCopyMatching" status:status]; + } + } + return nil; +} + +- (BOOL)setItemWithQuery:(NSDictionary *)query + attributes:(NSDictionary *)attributes + error:(NSError **_Nullable)error { + NSMutableDictionary *combined = [attributes mutableCopy]; + [combined addEntriesFromDictionary:query]; + BOOL hasItem = NO; + OSStatus status = SecItemAdd((__bridge CFDictionaryRef)combined, NULL); + + if (status == errSecDuplicateItem) { + hasItem = YES; + status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)attributes); + } + + if (status == noErr) { + return YES; + } + if (error) { + NSString *function = hasItem ? @"SecItemUpdate" : @"SecItemAdd"; + *error = [FIRAuthErrorUtils keychainErrorWithFunction:function status:status]; + } + return NO; +} + +- (BOOL)deleteItemWithQuery:(NSDictionary *)query error:(NSError **_Nullable)error { + OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query); + if (status == noErr || status == errSecItemNotFound) { + return YES; + } + if (error) { + *error = [FIRAuthErrorUtils keychainErrorWithFunction:@"SecItemDelete" status:status]; + } + return NO; +} + +/** @fn deleteLegacyItemsWithKey: + @brief Deletes legacy item from the keychain if it is not already known to be deleted. + @param key The key for the item. + */ +- (void)deleteLegacyItemWithKey:(NSString *)key { + if (_legacyEntryDeletedForKey[key]) { + return; + } + NSDictionary *query = [self legacyGenericPasswordQueryWithKey:key]; + SecItemDelete((__bridge CFDictionaryRef)query); + _legacyEntryDeletedForKey[key] = @YES; +} + +/** @fn genericPasswordQueryWithKey: + @brief Returns a keychain query of generic password to be used to manipulate key'ed value. + @param key The key for the value being manipulated, used as the account field in the query. + */ +- (NSDictionary *)genericPasswordQueryWithKey:(NSString *)key { + return @{ + (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, + (__bridge id)kSecAttrAccount : [kAccountPrefix stringByAppendingString:key], + (__bridge id)kSecAttrService : _service, + }; +} + +/** @fn legacyGenericPasswordQueryWithKey: + @brief Returns a keychain query of generic password without service field, which is used by + previous version of this class. + @param key The key for the value being manipulated, used as the account field in the query. + */ +- (NSDictionary *)legacyGenericPasswordQueryWithKey:(NSString *)key { + return @{ + (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, + (__bridge id)kSecAttrAccount : key, + }; +} + +#pragma mark - Private methods for shared keychain operations + +- (nullable NSData *)getItemWithQuery:(NSDictionary *)query + error:(NSError *_Nullable *_Nullable)outError { + NSMutableDictionary *mutableQuery = [query mutableCopy]; + + mutableQuery[(__bridge id)kSecReturnData] = @YES; + mutableQuery[(__bridge id)kSecReturnAttributes] = @YES; + mutableQuery[(__bridge id)kSecMatchLimit] = @2; + + CFArrayRef result = NULL; + OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)mutableQuery, + (CFTypeRef *)&result); + + if (status == noErr && result != NULL) { + NSArray *items = (__bridge_transfer NSArray *)result; + if (items.count != 1) { + if (outError) { + *outError = [FIRAuthErrorUtils keychainErrorWithFunction:@"SecItemCopyMatching" + status:status]; + } + return nil; + } + + if (outError) { + *outError = nil; + } + NSDictionary *item = items[0]; + return item[(__bridge id)kSecValueData]; + } + + if (status == errSecItemNotFound) { + if (outError) { + *outError = nil; + } + } else { + if (outError) { + *outError = [FIRAuthErrorUtils keychainErrorWithFunction:@"SecItemCopyMatching" status:status]; + } + } + return nil; +} + +- (BOOL)setItem:(NSData *)item + withQuery:(NSDictionary *)query + error:(NSError *_Nullable *_Nullable)outError { + NSData *existingItem = [self getItemWithQuery:query error:outError]; + if (outError && *outError) { + return NO; + } + + OSStatus status; + if (!existingItem) { + NSMutableDictionary *queryWithItem = [query mutableCopy]; + [queryWithItem setObject:item forKey:(__bridge id)kSecValueData]; + status = SecItemAdd((__bridge CFDictionaryRef)queryWithItem, NULL); + } else { + NSDictionary *attributes = @{(__bridge id)kSecValueData: item}; + status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)attributes); + } + + if (status == noErr) { + if (outError) { + *outError = nil; + } + return YES; + } + + NSString *function = existingItem ? @"SecItemUpdate" : @"SecItemAdd"; + if (outError) { + *outError = [FIRAuthErrorUtils keychainErrorWithFunction:function status:status]; + } + return NO; +} + +- (BOOL)removeItemWithQuery:(NSDictionary *)query error:(NSError *_Nullable *_Nullable)outError { + OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query); + + if (status == noErr || status == errSecItemNotFound) { + if (outError) { + *outError = nil; + } + return YES; + } + + if (outError) { + *outError = [FIRAuthErrorUtils keychainErrorWithFunction:@"SecItemDelete" status:status]; + } + return NO; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Storage/FIRAuthUserDefaults.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Storage/FIRAuthUserDefaults.h new file mode 100644 index 0000000..34e8a09 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Storage/FIRAuthUserDefaults.h @@ -0,0 +1,36 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthKeychainServices.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRAuthUserDefaults + @brief The utility class to storage data in NSUserDefaults. + */ +@interface FIRAuthUserDefaults : NSObject + +/** @fn clear + @brief Clears all data from the storage. + @remarks This method is only supposed to be called from tests. + */ +- (void)clear; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Storage/FIRAuthUserDefaults.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Storage/FIRAuthUserDefaults.m new file mode 100644 index 0000000..dc59112 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Storage/FIRAuthUserDefaults.m @@ -0,0 +1,74 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRAuthUserDefaults.h" + +NS_ASSUME_NONNULL_BEGIN + +static NSString *const kPersistentDomainNamePrefix = @"com.google.Firebase.Auth."; + +@implementation FIRAuthUserDefaults { + /** @var _persistentDomainName + @brief The name of the persistent domain in user defaults. + */ + NSString *_persistentDomainName; + + /** @var _storage + @brief The backing NSUserDefaults storage for this instance. + */ + NSUserDefaults *_storage; +} + +- (instancetype)initWithService:(NSString *)service { + self = [super init]; + if (self) { + _persistentDomainName = [kPersistentDomainNamePrefix stringByAppendingString:service]; + _storage = [[NSUserDefaults alloc] init]; + } + return self; +} + +- (nullable NSData *)dataForKey:(NSString *)key error:(NSError **_Nullable)error { + if (error) { + *error = nil; + } + NSDictionary *allData = [_storage persistentDomainForName:_persistentDomainName]; + return allData[key]; +} + +- (BOOL)setData:(NSData *)data forKey:(NSString *)key error:(NSError **_Nullable)error { + NSMutableDictionary *allData = + [([_storage persistentDomainForName:_persistentDomainName] ?: @{}) mutableCopy]; + allData[key] = data; + [_storage setPersistentDomain:allData forName:_persistentDomainName]; + return YES; +} + +- (BOOL)removeDataForKey:(NSString *)key error:(NSError **_Nullable)error { + NSMutableDictionary *allData = + [[_storage persistentDomainForName:_persistentDomainName] mutableCopy]; + [allData removeObjectForKey:key]; + [_storage setPersistentDomain:allData forName:_persistentDomainName]; + return YES; +} + +- (void)clear { + [_storage setPersistentDomain:@{} forName:_persistentDomainName]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthAPNSToken.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthAPNSToken.h new file mode 100644 index 0000000..95b8222 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthAPNSToken.h @@ -0,0 +1,64 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#if !TARGET_OS_OSX + +#import + +#import "FIRAuthAPNSTokenType.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRAuthAPNSToken + @brief A data structure for an APNs token. + */ +@interface FIRAuthAPNSToken : NSObject + +/** @property data + @brief The APNs token data. + */ +@property(nonatomic, strong, readonly) NSData *data; + +/** @property string + @brief The uppercase hexadecimal string form of the APNs token data. + */ +@property(nonatomic, strong, readonly) NSString *string; + +/** @property type + @brief The APNs token type. + */ +@property(nonatomic, assign, readonly) FIRAuthAPNSTokenType type; + +/** @fn initWithData:type: + @brief Initializes the instance. + @param data The APNs token data. + @param type The APNs token type. + @return The initialized instance. + */ +- (instancetype)initWithData:(NSData *)data type:(FIRAuthAPNSTokenType)type + NS_DESIGNATED_INITIALIZER; + +/** @fn init + @brief Call @c initWithData:type: to get an instance of this class. + */ +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthAPNSToken.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthAPNSToken.m new file mode 100644 index 0000000..4b89572 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthAPNSToken.m @@ -0,0 +1,57 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#if !TARGET_OS_OSX + +#import "FIRAuthAPNSToken.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRAuthAPNSToken { + /** @var _string + @brief The lazy-initialized string form of the token data. + */ + NSString *_string; +} + +- (instancetype)initWithData:(NSData *)data type:(FIRAuthAPNSTokenType)type { + self = [super init]; + if (self) { + _data = [data copy]; + _type = type; + } + return self; +} + +- (NSString *)string { + if (!_string) { + NSUInteger capacity = _data.length * 2; + NSMutableString *tokenString = [NSMutableString stringWithCapacity:capacity]; + const unsigned char *tokenData = _data.bytes; + for (int idx = 0; idx < _data.length; ++idx) { + [tokenString appendFormat:@"%02X", (int)tokenData[idx]]; + } + _string = tokenString; + } + return _string; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthAPNSTokenManager.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthAPNSTokenManager.h new file mode 100644 index 0000000..5e2acfe --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthAPNSTokenManager.h @@ -0,0 +1,83 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#if !TARGET_OS_OSX + +#import +#import + +@class FIRAuthAPNSToken; + +NS_ASSUME_NONNULL_BEGIN + +/** @typedef FIRAuthAPNSTokenCallback + @brief The type of block to receive an APNs token. + @param token The APNs token if one is available. + @param error The error happened if any. + @remarks Both `token` and `error` being `nil` means the request timed-out. + */ +typedef void (^FIRAuthAPNSTokenCallback)(FIRAuthAPNSToken *_Nullable token, + NSError *_Nullable error); + +/** @class FIRAuthAPNSTokenManager + @brief A class to manage APNs token in memory. + */ +@interface FIRAuthAPNSTokenManager : NSObject + +/** @property token + @brief The APNs token, if one is available. + @remarks Setting a token with FIRAuthAPNSTokenTypeUnknown will automatically converts it to + a token with the automatically detected type. + */ +@property(nonatomic, strong, nullable) FIRAuthAPNSToken *token; + +/** @property timeout + @brief The timeout for registering for remote notification. + @remarks Only tests should access this property. + */ +@property(nonatomic, assign) NSTimeInterval timeout; + +/** @fn init + @brief Call @c initWithApplication: to initialize an instance of this class. + */ +- (instancetype)init NS_UNAVAILABLE; + +/** @fn initWithApplication:bundle + @brief Initializes the instance. + @param application The @c UIApplication to request the token from. + @return The initialized instance. + */ +- (instancetype)initWithApplication:(UIApplication *)application NS_DESIGNATED_INITIALIZER; + +/** @fn getTokenWithCallback: + @brief Attempts to get the APNs token. + @param callback The block to be called either immediately or in future, either when a token + becomes available, or when timeout occurs, whichever happens earlier. + */ +- (void)getTokenWithCallback:(FIRAuthAPNSTokenCallback)callback; + +/** @fn cancelWithError: + @brief Cancels any pending `getTokenWithCallback:` request. + @param error The error to return. + */ +- (void)cancelWithError:(NSError *)error; + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthAPNSTokenManager.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthAPNSTokenManager.m new file mode 100644 index 0000000..1b37363 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthAPNSTokenManager.m @@ -0,0 +1,252 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#if !TARGET_OS_OSX + +#import "FIRAuthAPNSTokenManager.h" + +#import +#import + +#import "FIRAuthAPNSToken.h" +#import "FIRAuthGlobalWorkQueue.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kRegistrationTimeout + @brief Timeout for registration for remote notification. + @remarks Once we start to handle `application:didFailToRegisterForRemoteNotificationsWithError:` + we probably don't have to use timeout at all. + */ +static const NSTimeInterval kRegistrationTimeout = 5; + +/** @var kLegacyRegistrationTimeout + @brief Timeout for registration for remote notification on iOS 7. + */ +static const NSTimeInterval kLegacyRegistrationTimeout = 30; + +@implementation FIRAuthAPNSTokenManager { + /** @var _application + @brief The @c UIApplication to request the token from. + */ + UIApplication *_application; + + /** @var _pendingCallbacks + @brief The list of all pending callbacks for the APNs token. + */ + NSMutableArray *_pendingCallbacks; +} + +- (instancetype)initWithApplication:(UIApplication *)application { + self = [super init]; + if (self) { + _application = application; + _timeout = [_application respondsToSelector:@selector(registerForRemoteNotifications)] ? + kRegistrationTimeout : kLegacyRegistrationTimeout; + } + return self; +} + +- (void)getTokenWithCallback:(FIRAuthAPNSTokenCallback)callback { + if (_token) { + callback(_token, nil); + return; + } + if (_pendingCallbacks) { + [_pendingCallbacks addObject:callback]; + return; + } + _pendingCallbacks = + [[NSMutableArray alloc] initWithObjects:callback, nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + if ([self->_application respondsToSelector:@selector(registerForRemoteNotifications)]) { + [self->_application registerForRemoteNotifications]; + } else { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#if TARGET_OS_IOS + [self->_application registerForRemoteNotificationTypes:UIRemoteNotificationTypeAlert]; +#endif // TARGET_OS_IOS +#pragma clang diagnostic pop + } + }); + NSArray *applicableCallbacks = _pendingCallbacks; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_timeout * NSEC_PER_SEC)), + FIRAuthGlobalWorkQueue(), ^{ + // Only cancel if the pending callbacks remain the same, i.e., not triggered yet. + if (applicableCallbacks == self->_pendingCallbacks) { + [self callBackWithToken:nil error:nil]; + } + }); +} + +- (void)setToken:(nullable FIRAuthAPNSToken *)token { + if (!token) { + _token = nil; + return; + } + if (token.type == FIRAuthAPNSTokenTypeUnknown) { + static FIRAuthAPNSTokenType detectedTokenType = FIRAuthAPNSTokenTypeUnknown; + if (detectedTokenType == FIRAuthAPNSTokenTypeUnknown) { + detectedTokenType = + [[self class] isProductionApp] ? FIRAuthAPNSTokenTypeProd : FIRAuthAPNSTokenTypeSandbox; + } + token = [[FIRAuthAPNSToken alloc] initWithData:token.data type:detectedTokenType]; + } + _token = token; + [self callBackWithToken:token error:nil]; +} + +- (void)cancelWithError:(NSError *)error { + [self callBackWithToken:nil error:error]; +} + +#pragma mark - Internal methods + +/** @fn callBack + @brief Calls back all pending callbacks with APNs token or error. + @param token The APNs token if one is available. + @param error The error occurred, if any. + */ +- (void)callBackWithToken:(nullable FIRAuthAPNSToken *)token error:(nullable NSError *)error { + if (!_pendingCallbacks) { + return; + } + NSArray *allCallbacks = _pendingCallbacks; + _pendingCallbacks = nil; + for (FIRAuthAPNSTokenCallback callback in allCallbacks) { + callback(token, error); + } +}; + +/** @fn isProductionApp + @brief Whether or not the app has production (versus sandbox) provisioning profile. + @remarks This method is adapted from @c FIRInstanceID . + */ ++ (BOOL)isProductionApp { + const BOOL defaultAppTypeProd = YES; + + NSError *error = nil; + + if ([GULAppEnvironmentUtil isSimulator]) { + FIRLogInfo(kFIRLoggerAuth, @"I-AUT000006", @"Assuming prod APNs token type on simulator."); + return defaultAppTypeProd; + } + + // Apps distributed via AppStore or TestFlight use the Production APNS certificates. + if ([GULAppEnvironmentUtil isFromAppStore]) { + return defaultAppTypeProd; + } + NSString *path = [[[NSBundle mainBundle] bundlePath] + stringByAppendingPathComponent:@"embedded.mobileprovision"]; + if ([GULAppEnvironmentUtil isAppStoreReceiptSandbox] && !path.length) { + // Distributed via TestFlight + return defaultAppTypeProd; + } + + NSMutableData *profileData = [NSMutableData dataWithContentsOfFile:path options:0 error:&error]; + + if (!profileData.length || error) { + FIRLogInfo(kFIRLoggerAuth, @"I-AUT000007", + @"Error while reading embedded mobileprovision %@", error); + return defaultAppTypeProd; + } + + // The "embedded.mobileprovision" sometimes contains characters with value 0, which signals the + // end of a c-string and halts the ASCII parser, or with value > 127, which violates strict 7-bit + // ASCII. Replace any 0s or invalid characters in the input. + uint8_t *profileBytes = (uint8_t *)profileData.bytes; + for (int i = 0; i < profileData.length; i++) { + uint8_t currentByte = profileBytes[i]; + if (!currentByte || currentByte > 127) { + profileBytes[i] = '.'; + } + } + + NSString *embeddedProfile = [[NSString alloc] initWithBytesNoCopy:profileBytes + length:profileData.length + encoding:NSASCIIStringEncoding + freeWhenDone:NO]; + + if (error || !embeddedProfile.length) { + FIRLogInfo(kFIRLoggerAuth, @"I-AUT000008", + @"Error while reading embedded mobileprovision %@", error); + return defaultAppTypeProd; + } + + NSScanner *scanner = [NSScanner scannerWithString:embeddedProfile]; + NSString *plistContents; + if ([scanner scanUpToString:@"" intoString:&plistContents]) { + plistContents = [plistContents stringByAppendingString:@""]; + } + } + + if (!plistContents.length) { + return defaultAppTypeProd; + } + + NSData *data = [plistContents dataUsingEncoding:NSUTF8StringEncoding]; + if (!data.length) { + FIRLogInfo(kFIRLoggerAuth, @"I-AUT000009", + @"Couldn't read plist fetched from embedded mobileprovision"); + return defaultAppTypeProd; + } + + NSError *plistMapError; + id plistData = [NSPropertyListSerialization propertyListWithData:data + options:NSPropertyListImmutable + format:nil + error:&plistMapError]; + if (plistMapError || ![plistData isKindOfClass:[NSDictionary class]]) { + FIRLogInfo(kFIRLoggerAuth, @"I-AUT000010", + @"Error while converting assumed plist to dict %@", + plistMapError.localizedDescription); + return defaultAppTypeProd; + } + NSDictionary *plistMap = (NSDictionary *)plistData; + + if ([plistMap valueForKeyPath:@"ProvisionedDevices"]) { + FIRLogInfo(kFIRLoggerAuth, @"I-AUT000011", + @"Provisioning profile has specifically provisioned devices, " + @"most likely a Dev profile."); + } + + NSString *apsEnvironment = [plistMap valueForKeyPath:@"Entitlements.aps-environment"]; + FIRLogDebug(kFIRLoggerAuth, @"I-AUT000012", + @"APNS Environment in profile: %@", apsEnvironment); + + // No aps-environment in the profile. + if (!apsEnvironment.length) { + FIRLogInfo(kFIRLoggerAuth, @"I-AUT000013", + @"No aps-environment set. If testing on a device APNS is not " + @"correctly configured. Please recheck your provisioning profiles."); + return defaultAppTypeProd; + } + + if ([apsEnvironment isEqualToString:@"development"]) { + return NO; + } + + return defaultAppTypeProd; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthAppCredential.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthAppCredential.h new file mode 100644 index 0000000..57fa83a --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthAppCredential.h @@ -0,0 +1,53 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRAuthAppCredential + @brief A class represents a credential that proves the identity of the app. + */ +@interface FIRAuthAppCredential : NSObject + +/** @property receipt + @brief The server acknowledgement of receiving client's claim of identity. + */ +@property(nonatomic, strong, readonly) NSString *receipt; + +/** @property secret + @brief The secret that the client received from server via a trusted channel, if ever. + */ +@property(nonatomic, strong, readonly, nullable) NSString *secret; + +/** @fn initWithReceipt:secret: + @brief Initializes the instance. + @param receipt The server acknowledgement of receiving client's claim of identity. + @param secret The secret that the client received from server via a trusted channel, if ever. + @return The initialized instance. + */ +- (instancetype)initWithReceipt:(NSString *)receipt secret:(nullable NSString *)secret + NS_DESIGNATED_INITIALIZER; + +/** @fn init + @brief Call @c initWithReceipt:secret: to get an instance of this class. + */ +- (instancetype)init NS_UNAVAILABLE; + + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthAppCredential.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthAppCredential.m new file mode 100644 index 0000000..27d4ad2 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthAppCredential.m @@ -0,0 +1,64 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRAuthAppCredential.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kReceiptKey + @brief The key used to encode the receipt property for NSSecureCoding. + */ +static NSString *const kReceiptKey = @"receipt"; + +/** @var kSecretKey + @brief The key used to encode the secret property for NSSecureCoding. + */ +static NSString *const kSecretKey = @"secret"; + +@implementation FIRAuthAppCredential + +- (instancetype)initWithReceipt:(NSString *)receipt secret:(nullable NSString *)secret { + self = [super init]; + if (self) { + _receipt = [receipt copy]; + _secret = [secret copy]; + } + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + NSString *receipt = [aDecoder decodeObjectOfClass:[NSString class] forKey:kReceiptKey]; + if (!receipt) { + return nil; + } + NSString *secret = [aDecoder decodeObjectOfClass:[NSString class] forKey:kSecretKey]; + return [self initWithReceipt:receipt secret:secret]; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_receipt forKey:kReceiptKey]; + [aCoder encodeObject:_secret forKey:kSecretKey]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthAppCredentialManager.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthAppCredentialManager.h new file mode 100644 index 0000000..2a6cc02 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthAppCredentialManager.h @@ -0,0 +1,90 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#if !TARGET_OS_OSX + +#import + +#import "FIRAuthAppCredential.h" +#import "FIRAuthKeychainServices.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @typedef FIRAuthAppCredentialCallback + @brief The type of block to receive an app crdential. + @param credential The best available app credential at the time. + */ +typedef void (^FIRAuthAppCredentialCallback)(FIRAuthAppCredential *credential); + +/** @class FIRAuthAppCredentialManager + @brief A class to manage app credentials backed by iOS Keychain. + */ +@interface FIRAuthAppCredentialManager : NSObject + +/** @property credential + @brief The full credential (which has a secret) to be used by the app, if one is available. + */ +@property(nonatomic, strong, readonly, nullable) FIRAuthAppCredential *credential; + +/** @property maximumNumberOfPendingReceipts + @brief The maximum (but not necessarily the minimum) number of pending receipts to be kept. + @remarks Only tests should access this property. + */ +@property(nonatomic, assign, readonly) NSUInteger maximumNumberOfPendingReceipts; + +/** @fn init + @brief Call @c initWithKeychain: to initialize an instance of this class. + */ +- (instancetype)init NS_UNAVAILABLE; + +/** @fn initWithKeychain: + @brief Initializes the instance. + @param keychain The iOS Keychain storage to back up the app credential with. + @return The initialized instance. + */ +- (instancetype)initWithKeychain:(FIRAuthKeychainServices *)keychain NS_DESIGNATED_INITIALIZER; + +/** @fn didStartVerificationWithReceipt:timeout:callback: + @brief Notifies that the app verification process has started. + @param receipt The receipt for verification. + @param timeout The timeout value for how long the callback is waited to be called. + @param callback The block to be called in future either when the verification finishes, or + when timeout occurs, whichever happens earlier. + */ +- (void)didStartVerificationWithReceipt:(NSString *)receipt + timeout:(NSTimeInterval)timeout + callback:(FIRAuthAppCredentialCallback)callback; + +/** @fn canFinishVerificationWithReceipt: + @brief Attempts to finish verification. + @param receipt The receipt to match the original receipt obtained when verification started. + @param secret The secret to complete the verification. + @return Whether or not the receipt matches a pending verification, and finishes verification + if it does. + */ +- (BOOL)canFinishVerificationWithReceipt:(NSString *)receipt secret:(NSString *)secret; + +/** @fn clearCredential + @brief Clears the saved credential, to be used in the case that it is rejected by the server. + */ +- (void)clearCredential; + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthAppCredentialManager.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthAppCredentialManager.m new file mode 100644 index 0000000..836456d --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthAppCredentialManager.m @@ -0,0 +1,169 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#if !TARGET_OS_OSX + +#import "FIRAuthAppCredentialManager.h" + +#import "FIRAuthAppCredential.h" +#import "FIRAuthGlobalWorkQueue.h" +#import "FIRAuthKeychainServices.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kKeychainDataKey + @brief The keychain key for the data. + */ +static NSString *const kKeychainDataKey = @"app_credentials"; + +/** @var kFullCredentialKey + @brief The data key for the full app credential. + */ +static NSString *const kFullCredentialKey = @"full_credential"; + +/** @var kPendingReceiptsKey + @brief The data key for the array of pending receipts. + */ +static NSString *const kPendingReceiptsKey = @"pending_receipts"; + +/** @var kMaximumNumberOfPendingReceipts + @brief The maximum number of partial credentials kept by this class. + */ +static const NSUInteger kMaximumNumberOfPendingReceipts = 32; + +@implementation FIRAuthAppCredentialManager { + /** @var _keychainServices + @brief The keychain for app credentials to load from and to save to. + */ + FIRAuthKeychainServices *_keychainServices; + + /** @var _pendingReceipts + @brief A list of pending receipts sorted in the order they were recorded. + */ + NSMutableArray *_pendingReceipts; + + /** @var _callbacksByReceipt + @brief A map from pending receipts to callbacks. + */ + NSMutableDictionary *_callbacksByReceipt; +} + +- (instancetype)initWithKeychain:(FIRAuthKeychainServices *)keychain { + self = [super init]; + if (self) { + _keychainServices = keychain; + // Load the credentials from keychain if possible. + NSError *error; + NSData *encodedData = [_keychainServices dataForKey:kKeychainDataKey error:&error]; + if (!error && encodedData) { + NSKeyedUnarchiver *unarchiver = + [[NSKeyedUnarchiver alloc] initForReadingWithData:encodedData]; + FIRAuthAppCredential *credential = + [unarchiver decodeObjectOfClass:[FIRAuthAppCredential class] + forKey:kFullCredentialKey]; + if ([credential isKindOfClass:[FIRAuthAppCredential class]]) { + _credential = credential; + } + NSSet *allowedClasses = + [NSSet setWithObjects:[NSArray class], [NSString class], nil]; + NSArray *pendingReceipts = + [unarchiver decodeObjectOfClasses:allowedClasses forKey:kPendingReceiptsKey]; + if ([pendingReceipts isKindOfClass:[NSArray class]]) { + _pendingReceipts = [pendingReceipts mutableCopy]; + } + } + if (!_pendingReceipts) { + _pendingReceipts = [[NSMutableArray alloc] init]; + } + _callbacksByReceipt = + [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (NSUInteger)maximumNumberOfPendingReceipts { + return kMaximumNumberOfPendingReceipts; +} + +- (void)didStartVerificationWithReceipt:(NSString *)receipt + timeout:(NSTimeInterval)timeout + callback:(FIRAuthAppCredentialCallback)callback { + [_pendingReceipts removeObject:receipt]; + if (_pendingReceipts.count >= kMaximumNumberOfPendingReceipts) { + [_pendingReceipts removeObjectAtIndex:0]; + } + [_pendingReceipts addObject:receipt]; + _callbacksByReceipt[receipt] = callback; + [self saveData]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)), + FIRAuthGlobalWorkQueue(), ^{ + [self callBackWithReceipt:receipt]; + }); +} + +- (BOOL)canFinishVerificationWithReceipt:(NSString *)receipt secret:(NSString *)secret { + if (![_pendingReceipts containsObject:receipt]) { + return NO; + } + [_pendingReceipts removeObject:receipt]; + _credential = [[FIRAuthAppCredential alloc] initWithReceipt:receipt secret:secret]; + [self saveData]; + [self callBackWithReceipt:receipt]; + return YES; +} + +- (void)clearCredential { + _credential = nil; + [self saveData]; +} + +#pragma mark - Internal methods + +/** @fn saveData + @brief Save the data in memory to the keychain ignoring any errors. + */ +- (void)saveData { + NSMutableData *archiveData = [NSMutableData data]; + NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:archiveData]; + [archiver encodeObject:_credential forKey:kFullCredentialKey]; + [archiver encodeObject:_pendingReceipts forKey:kPendingReceiptsKey]; + [archiver finishEncoding]; + [_keychainServices setData:archiveData forKey:kKeychainDataKey error:NULL]; +} + +/** @fn callBackWithReceipt: + @brief Calls the saved callback for the specifc receipt. + @param receipt The receipt associated with the callback. + */ +- (void)callBackWithReceipt:(NSString *)receipt { + FIRAuthAppCredentialCallback callback = _callbacksByReceipt[receipt]; + if (!callback) { + return; + } + [_callbacksByReceipt removeObjectForKey:receipt]; + if (_credential) { + callback(_credential); + } else { + callback([[FIRAuthAppCredential alloc] initWithReceipt:receipt secret:nil]); + } +} + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthNotificationManager.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthNotificationManager.h new file mode 100644 index 0000000..3de2e32 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthNotificationManager.h @@ -0,0 +1,76 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#if !TARGET_OS_OSX + +#import +#import + +@class FIRAuthAppCredentialManager; + +NS_ASSUME_NONNULL_BEGIN + +/** @typedef FIRAuthNotificationForwardingCallback + @brief The type of block to receive whether or not remote notifications are being forwarded. + @param isNotificationBeingForwarded Whether or not remote notifications are being forwarded. + */ +typedef void (^FIRAuthNotificationForwardingCallback)(BOOL isNotificationBeingForwarded); + +/** @class FIRAuthNotificationManager + */ +@interface FIRAuthNotificationManager : NSObject + +/** @property timeout + @brief The timeout for checking for notification forwarding. + @remarks Only tests should access this property. + */ +@property(nonatomic, assign) NSTimeInterval timeout; + +/** @fn initWithApplication:appCredentialManager: + @brief Initializes the instance. + @param application The application. + @param appCredentialManager The object to handle app credentials delivered via notification. + @return The initialized instance. + */ +- (instancetype)initWithApplication:(UIApplication *)application + appCredentialManager:(FIRAuthAppCredentialManager *)appCredentialManager + NS_DESIGNATED_INITIALIZER; + +/** @fn init + @brief please use initWithAppCredentialManager: instead. + */ +- (instancetype)init NS_UNAVAILABLE; + +/** @fn checkNotificationForwardingWithCallback: + @brief Checks whether or not remote notifications are being forwarded to this class. + @param callback The block to be called either immediately or in future once a result + is available. + */ +- (void)checkNotificationForwardingWithCallback:(FIRAuthNotificationForwardingCallback)callback; + +/** @fn canHandleNotification: + @brief Attempts to handle the remote notification. + @param notification The notification in question. + @return Whether or the notification has been handled. + */ +- (BOOL)canHandleNotification:(NSDictionary *)notification; + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthNotificationManager.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthNotificationManager.m new file mode 100644 index 0000000..5348886 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthNotificationManager.m @@ -0,0 +1,182 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#if !TARGET_OS_OSX + +#import "FIRAuthNotificationManager.h" + +#import +#import "FIRAuthAppCredential.h" +#import "FIRAuthAppCredentialManager.h" +#import "FIRAuthGlobalWorkQueue.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kNotificationKey + @brief The key to locate payload data in the remote notification. + */ +static NSString *const kNotificationDataKey = @"com.google.firebase.auth"; + +/** @var kNotificationReceiptKey + @brief The key for the receipt in the remote notification payload data. + */ +static NSString *const kNotificationReceiptKey = @"receipt"; + +/** @var kNotificationSecretKey + @brief The key for the secret in the remote notification payload data. + */ +static NSString *const kNotificationSecretKey = @"secret"; + +/** @var kNotificationProberKey + @brief The key for marking the prober in the remote notification payload data. + */ +static NSString *const kNotificationProberKey = @"warning"; + +/** @var kProbingTimeout + @brief Timeout for probing whether the app delegate forwards the remote notification to us. + */ +static const NSTimeInterval kProbingTimeout = 1; + +@implementation FIRAuthNotificationManager { + /** @var _application + @brief The application. + */ + UIApplication *_application; + + /** @var _appCredentialManager + @brief The object to handle app credentials delivered via notification. + */ + FIRAuthAppCredentialManager *_appCredentialManager; + + /** @var _hasCheckedNotificationForwarding + @brief Whether notification forwarding has been checked or not. + */ + BOOL _hasCheckedNotificationForwarding; + + /** @var _isNotificationBeingForwarded + @brief Whether or not notification is being forwarded + */ + BOOL _isNotificationBeingForwarded; + + /** @var _pendingCallbacks + @brief All pending callbacks while a check is being performed. + */ + NSMutableArray *_pendingCallbacks; +} + +- (instancetype)initWithApplication:(UIApplication *)application + appCredentialManager:(FIRAuthAppCredentialManager *)appCredentialManager { + self = [super init]; + if (self) { + _application = application; + _appCredentialManager = appCredentialManager; + _timeout = kProbingTimeout; + } + return self; +} + +- (void)checkNotificationForwardingWithCallback:(FIRAuthNotificationForwardingCallback)callback { + if (_pendingCallbacks) { + [_pendingCallbacks addObject:callback]; + return; + } + if (_hasCheckedNotificationForwarding) { + callback(_isNotificationBeingForwarded); + return; + } + _hasCheckedNotificationForwarding = YES; + _pendingCallbacks = + [[NSMutableArray alloc] initWithObjects:callback, nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + NSDictionary *proberNotification = @{ + kNotificationDataKey : @{ + kNotificationProberKey : @"This fake notification should be forwarded to Firebase Auth." + } + }; + if ([self->_application.delegate respondsToSelector: + @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)]) { + [self->_application.delegate application:self->_application + didReceiveRemoteNotification:proberNotification + fetchCompletionHandler:^(UIBackgroundFetchResult result) {}]; +#if !TARGET_OS_TV + } else if ([self->_application.delegate respondsToSelector: + @selector(application:didReceiveRemoteNotification:)]) { + [self->_application.delegate application:self->_application + didReceiveRemoteNotification:proberNotification]; +#endif + } else { + FIRLogWarning(kFIRLoggerAuth, @"I-AUT000015", + @"The UIApplicationDelegate must handle remote notification for phone number " + @"authentication to work."); + } + }); + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_timeout * NSEC_PER_SEC)), + FIRAuthGlobalWorkQueue(), ^{ + [self callBack]; + }); +} + +- (BOOL)canHandleNotification:(NSDictionary *)notification { + NSDictionary *data = notification[kNotificationDataKey]; + if ([data isKindOfClass:[NSString class]]) { + // Deserialize in case the data is a JSON string. + NSData *JSONData = [((NSString *)data) dataUsingEncoding:NSUTF8StringEncoding]; + data = [NSJSONSerialization JSONObjectWithData:JSONData options:0 error:NULL]; + } + if (![data isKindOfClass:[NSDictionary class]]) { + return NO; + } + if (data[kNotificationProberKey]) { + if (!_pendingCallbacks) { + // The prober notification probably comes from another instance, so pass it along. + return NO; + } + _isNotificationBeingForwarded = YES; + [self callBack]; + return YES; + } + NSString *receipt = data[kNotificationReceiptKey]; + if (![receipt isKindOfClass:[NSString class]]) { + return NO; + } + NSString *secret = data[kNotificationSecretKey]; + if (![receipt isKindOfClass:[NSString class]]) { + return NO; + } + return [_appCredentialManager canFinishVerificationWithReceipt:receipt secret:secret]; +} + +#pragma mark - Internal methods + +/** @fn callBack + @brief Calls back all pending callbacks with the result of notification forwarding check. + */ +- (void)callBack { + if (!_pendingCallbacks) { + return; + } + NSArray *allCallbacks = _pendingCallbacks; + _pendingCallbacks = nil; + for (FIRAuthNotificationForwardingCallback callback in allCallbacks) { + callback(_isNotificationBeingForwarded); + } +}; + +@end +NS_ASSUME_NONNULL_END + +#endif diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthStoredUserManager.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthStoredUserManager.h new file mode 100644 index 0000000..e3c0701 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthStoredUserManager.h @@ -0,0 +1,99 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRUser.h" +#import "FIRAuthKeychainServices.h" +#import "FIRAuthUserDefaults.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRAuthStoredUserManager : NSObject + +/** @property keychain + @brief The mediator object to access to the system Keychain services. + */ +@property (readonly, nonatomic, strong) FIRAuthKeychainServices *keychainServices; + +/** @property userDefaults + @brief The mediator object to access to the system User Defaults services. + */ +@property (readonly, nonatomic, strong) FIRAuthUserDefaults *userDefaults; + +/** @fn init + @brief The default initializer is disabled. + */ +- (instancetype)init NS_UNAVAILABLE; + +/** @fn initWithServiceName: + @brief The designated initializer. + @param serviceName The service name to initialize with. + */ +- (instancetype)initWithServiceName:(NSString *)serviceName NS_DESIGNATED_INITIALIZER; + +/** @fn getStoredUserAccessGroupWithError: + @brief Get the user access group stored locally. + @param outError Return value for any error which occurs. + */ +- (NSString *_Nullable)getStoredUserAccessGroupWithError:(NSError *_Nullable *_Nullable)outError; + +/** @fn setStoredUserAccessGroup:error: + @brief The setter of the user access group stored locally. + @param accessGroup The access group to be set. + @param outError Return value for any error which occurs. + */ +- (BOOL)setStoredUserAccessGroup:(NSString *_Nullable)accessGroup + error:(NSError *_Nullable *_Nullable)outError; + +/** @fn getStoredUserForAccessGroup:projectID:error: + @brief The getter of the user stored locally. + @param accessGroup The access group to retrieve the user from. + @param projectIdentifier An identifier of the project that the user associates with. Currently, + we use API KEY. + @param outError Return value for any error which occurs. + */ +- (FIRUser *)getStoredUserForAccessGroup:(NSString *)accessGroup + projectIdentifier:(NSString *)projectIdentifier + error:(NSError *_Nullable *_Nullable)outError; + +/** @fn setStoredUser:forAccessGroup:projectID:error: + @brief The setter of the user stored locally. + @param user The user to be stored. + @param accessGroup The access group to store the user in. + @param projectIdentifier An identifier of the project that the user associates with. Currently, + we use API KEY. + @param outError Return value for any error which occurs. + */ +- (BOOL)setStoredUser:(FIRUser *)user + forAccessGroup:(NSString *)accessGroup + projectIdentifier:(NSString *)projectIdentifier + error:(NSError *_Nullable *_Nullable)outError; + +/** @fn removeStoredUserForAccessGroup:projectID:error: + @brief Remove the user that stored locally. + @param accessGroup The access group to remove the user from. + @param projectIdentifier An identifier of the project that the user associates with. Currently, + we use API KEY. + @param outError Return value for any error which occurs. + */ +- (BOOL)removeStoredUserForAccessGroup:(NSString *)accessGroup + projectIdentifier:(NSString *)projectIdentifier + error:(NSError *_Nullable *_Nullable)outError; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthStoredUserManager.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthStoredUserManager.m new file mode 100644 index 0000000..42f40d6 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRAuthStoredUserManager.m @@ -0,0 +1,125 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRAuthStoredUserManager.h" + +/** @var kUserAccessGroupKey + @brief Key of user access group stored in user defaults. Used for retrieve the user access + group at launch. + */ +static NSString *kStoredUserAccessGroupKey = @"firebase_auth_stored_user_access_group"; + +/** @var kSharedKeychainAccountValue + @brief Default value for kSecAttrAccount of shared keychain items. + */ +static NSString *kSharedKeychainAccountValue = @"firebase_auth_firebase_user"; + +/** @var kStoredUserCoderKey + @brief The key to encode and decode the stored user. + */ +static NSString *kStoredUserCoderKey = @"firebase_auth_stored_user_coder_key"; + +@implementation FIRAuthStoredUserManager + +#pragma mark - Initializers + +- (instancetype)initWithServiceName:(NSString *)serviceName { + self = [super init]; + if (self) { + _keychainServices = [[FIRAuthKeychainServices alloc] initWithService:serviceName]; + _userDefaults = [[FIRAuthUserDefaults alloc] initWithService:serviceName]; + } + return self; +} + +#pragma mark - User Access Group + +- (NSString *_Nullable)getStoredUserAccessGroupWithError:(NSError *_Nullable *_Nullable)outError { + NSData *data = [self.userDefaults dataForKey:kStoredUserAccessGroupKey error:outError]; + if (data) { + NSString *userAccessGroup = [NSString stringWithUTF8String:data.bytes]; + return userAccessGroup; + } else { + return nil; + } +} + +- (BOOL)setStoredUserAccessGroup:(NSString *_Nullable)accessGroup + error:(NSError *_Nullable *_Nullable)outError { + NSData *data = [accessGroup dataUsingEncoding:NSUTF8StringEncoding]; + if (!data) { + return [self.userDefaults removeDataForKey:kStoredUserAccessGroupKey error:outError]; + } else { + return [self.userDefaults setData:data forKey:kStoredUserAccessGroupKey error:outError]; + } +} + +#pragma mark - User for Access Group + +- (FIRUser *)getStoredUserForAccessGroup:(NSString *)accessGroup + projectIdentifier:(NSString *)projectIdentifier + error:(NSError *_Nullable *_Nullable)outError { + + + NSMutableDictionary *query = [[NSMutableDictionary alloc] init]; + query[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword; + + query[(__bridge id)kSecAttrAccessGroup] = accessGroup; + query[(__bridge id)kSecAttrService] = projectIdentifier; + query[(__bridge id)kSecAttrAccount] = kSharedKeychainAccountValue; + + NSData *data = [self.keychainServices getItemWithQuery:query error:outError]; + NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; + FIRUser *user = [unarchiver decodeObjectOfClass:[FIRUser class] forKey:kStoredUserCoderKey]; + + return user; +} + +- (BOOL)setStoredUser:(FIRUser *)user + forAccessGroup:(NSString *)accessGroup + projectIdentifier:(NSString *)projectIdentifier + error:(NSError *_Nullable *_Nullable)outError { + NSMutableDictionary *query = [[NSMutableDictionary alloc] init]; + query[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword; + query[(__bridge id)kSecAttrAccessible] = (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly; + + query[(__bridge id)kSecAttrAccessGroup] = accessGroup; + query[(__bridge id)kSecAttrService] = projectIdentifier; + query[(__bridge id)kSecAttrAccount] = kSharedKeychainAccountValue; + + NSMutableData *data = [NSMutableData data]; + NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; + [archiver encodeObject:user forKey:kStoredUserCoderKey]; + [archiver finishEncoding]; + + return [self.keychainServices setItem:data withQuery:query error:outError]; +} + +- (BOOL)removeStoredUserForAccessGroup:(NSString *)accessGroup + projectIdentifier:(NSString *)projectIdentifier + error:(NSError *_Nullable *_Nullable)outError { + NSMutableDictionary *query = [[NSMutableDictionary alloc] init]; + query[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword; + query[(__bridge id)kSecAttrAccessible] = (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly; + + query[(__bridge id)kSecAttrAccessGroup] = accessGroup; + query[(__bridge id)kSecAttrService] = projectIdentifier; + query[(__bridge id)kSecAttrAccount] = kSharedKeychainAccountValue; + + return [self.keychainServices removeItemWithQuery:query error:outError]; +} + +@end diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRSecureTokenService.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRSecureTokenService.h new file mode 100644 index 0000000..989e786 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRSecureTokenService.h @@ -0,0 +1,99 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FIRAuthRequestConfiguration; + +NS_ASSUME_NONNULL_BEGIN + +/** @typedef FIRFetchAccessTokenCallback + @brief The callback used to return the value of attempting to fetch an access token. + + In the event the operation was successful @c token will be set and @c error will be @c nil. + In the event of failure @c token will be @c nil and @c error will be set. + @c tokenUpdated indicates whether either the access or the refresh token has been updated. + + The token returned should be considered ephemeral and not cached. It should be used immediately + and discarded. All operations that need this token should call fetchAccessToken and do their + work from the callback. + */ +typedef void(^FIRFetchAccessTokenCallback)(NSString *_Nullable token, + NSError *_Nullable error, + BOOL tokenUpdated); + +/** @class FIRSecureTokenService + @brief Provides services for token exchanges and refreshes. + */ +@interface FIRSecureTokenService : NSObject + +/** @property requestConfiguration + @brief The configuration for making requests to server. + */ +@property(nonatomic, strong) FIRAuthRequestConfiguration *requestConfiguration; + +/** @property rawAccessToken + @brief The cached access token. + @remarks This method is specifically for providing the access token to internal clients during + deserialization and sign-in events, and should not be used to retrieve the access token by + anyone else. + */ +@property(nonatomic, copy, readonly) NSString *rawAccessToken; + +/** @property refreshToken + @brief The refresh token for the user, or @c nil if the user has yet completed sign-in flow. + @remarks This property needs to be set manually after the instance is decoded from archive. + */ +@property(nonatomic, copy, readonly, nullable) NSString *refreshToken; + +/** @property accessTokenExpirationDate + @brief The expiration date of the cached access token. + */ +@property(nonatomic, copy, readonly, nullable) NSDate *accessTokenExpirationDate; + +/** @fn initWithRequestConfiguration:authorizationCode: + @brief Creates a @c FIRSecureTokenService with an authroization code. + @param requestConfiguration The configuration for making requests to server. + @param authorizationCode An authorization code which needs to be exchanged for STS tokens. + */ +- (instancetype)initWithRequestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + authorizationCode:(NSString *)authorizationCode; + +/** @fn initWithRequestConfiguration:accessToken:accessTokenExpirationDate:refreshToken + @brief Creates a @c FIRSecureTokenService with access and refresh tokens. + @param requestConfiguration The configuration for making requests to server. + @param accessToken The STS access token. + @param accessTokenExpirationDate The approximate expiration date of the access token. + @param refreshToken The STS refresh token. + */ +- (instancetype)initWithRequestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + accessToken:(nullable NSString *)accessToken + accessTokenExpirationDate:(nullable NSDate *)accessTokenExpirationDate + refreshToken:(NSString *)refreshToken; + +/** @fn fetchAccessTokenForcingRefresh:callback: + @brief Fetch a fresh ephemeral access token for the ID associated with this instance. The token + received in the callback should be considered short lived and not cached. + @param forceRefresh Forces the token to be refreshed. + @param callback Callback block that will be called to return either the token or an error. + Invoked asyncronously on the auth global work queue in the future. + */ +- (void)fetchAccessTokenForcingRefresh:(BOOL)forceRefresh + callback:(FIRFetchAccessTokenCallback)callback; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRSecureTokenService.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRSecureTokenService.m new file mode 100644 index 0000000..6289f1e --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/SystemService/FIRSecureTokenService.m @@ -0,0 +1,209 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRSecureTokenService.h" + +#import "FIRAuth.h" +#import "FIRAuthSerialTaskQueue.h" +#import "FIRAuthBackend.h" +#import "FIRAuthRequestConfiguration.h" +#import "FIRSecureTokenRequest.h" +#import "FIRSecureTokenResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kAPIKeyCodingKey + @brief The key used to encode the APIKey for NSSecureCoding. + */ +static NSString *const kAPIKeyCodingKey = @"APIKey"; + +/** @var kRefreshTokenKey + @brief The key used to encode the refresh token for NSSecureCoding. + */ +static NSString *const kRefreshTokenKey = @"refreshToken"; + +/** @var kAccessTokenKey + @brief The key used to encode the access token for NSSecureCoding. + */ +static NSString *const kAccessTokenKey = @"accessToken"; + +/** @var kAccessTokenExpirationDateKey + @brief The key used to encode the access token expiration date for NSSecureCoding. + */ +static NSString *const kAccessTokenExpirationDateKey = @"accessTokenExpirationDate"; + +/** @var kFiveMinutes + @brief Five minutes (in seconds.) + */ +static const NSTimeInterval kFiveMinutes = 5 * 60; + +@interface FIRSecureTokenService () +- (instancetype)init NS_DESIGNATED_INITIALIZER; +@end + +@implementation FIRSecureTokenService { + /** @var _taskQueue + @brief Used to serialize all requests for access tokens. + */ + FIRAuthSerialTaskQueue *_taskQueue; + + /** @var _authorizationCode + @brief An authorization code which needs to be exchanged for Secure Token Service tokens. + */ + NSString *_Nullable _authorizationCode; + + /** @var _accessToken + @brief The currently cached access token. Or |nil| if no token is currently cached. + */ + NSString *_Nullable _accessToken; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _taskQueue = [[FIRAuthSerialTaskQueue alloc] init]; + } + return self; +} + +- (instancetype)initWithRequestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + authorizationCode:(NSString *)authorizationCode { + self = [self init]; + if (self) { + _requestConfiguration = requestConfiguration; + _authorizationCode = [authorizationCode copy]; + } + return self; +} + +- (instancetype)initWithRequestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + accessToken:(nullable NSString *)accessToken + accessTokenExpirationDate:(nullable NSDate *)accessTokenExpirationDate + refreshToken:(NSString *)refreshToken { + self = [self init]; + if (self) { + _requestConfiguration = requestConfiguration; + _accessToken = [accessToken copy]; + _accessTokenExpirationDate = [accessTokenExpirationDate copy]; + _refreshToken = [refreshToken copy]; + } + return self; +} + +- (void)fetchAccessTokenForcingRefresh:(BOOL)forceRefresh + callback:(FIRFetchAccessTokenCallback)callback { + [_taskQueue enqueueTask:^(FIRAuthSerialTaskCompletionBlock complete) { + if (!forceRefresh && [self hasValidAccessToken]) { + complete(); + callback(self->_accessToken, nil, NO); + } else { + [self requestAccessToken:^(NSString *_Nullable token, + NSError *_Nullable error, + BOOL tokenUpdated) { + complete(); + callback(token, error, tokenUpdated); + }]; + } + }]; +} + +- (NSString *)rawAccessToken { + return _accessToken; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + NSString *refreshToken = [aDecoder decodeObjectOfClass:[NSString class] forKey:kRefreshTokenKey]; + NSString *accessToken = [aDecoder decodeObjectOfClass:[NSString class] forKey:kAccessTokenKey]; + NSDate *accessTokenExpirationDate = + [aDecoder decodeObjectOfClass:[NSDate class] forKey:kAccessTokenExpirationDateKey]; + if (!refreshToken) { + return nil; + } + self = [self init]; + if (self) { + _refreshToken = refreshToken; + _accessToken = accessToken; + _accessTokenExpirationDate = accessTokenExpirationDate; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + // The API key is encoded even it is not used in decoding to be compatible with previous versions + // of the library. + [aCoder encodeObject:_requestConfiguration.APIKey forKey:kAPIKeyCodingKey]; + // Authorization code is not encoded because it is not long-lived. + [aCoder encodeObject:_refreshToken forKey:kRefreshTokenKey]; + [aCoder encodeObject:_accessToken forKey:kAccessTokenKey]; + [aCoder encodeObject:_accessTokenExpirationDate forKey:kAccessTokenExpirationDateKey]; +} + +#pragma mark - Private methods + +/** @fn requestAccessToken: + @brief Makes a request to STS for an access token. + @details This handles both the case that the token has not been granted yet and that it just + needs to be refreshed. The caller is responsible for making sure that this is occurring in + a @c _taskQueue task. + @param callback Called when the fetch is complete. Invoked asynchronously on the main thread in + the future. + @remarks Because this method is guaranteed to only be called from tasks enqueued in + @c _taskQueue, we do not need any @synchronized guards around access to _accessToken/etc. + since only one of those tasks is ever running at a time, and those tasks are the only + access to and mutation of these instance variables. + */ +- (void)requestAccessToken:(FIRFetchAccessTokenCallback)callback { + FIRSecureTokenRequest *request; + if (_refreshToken.length) { + request = [FIRSecureTokenRequest refreshRequestWithRefreshToken:_refreshToken + requestConfiguration:_requestConfiguration]; + } else { + request = [FIRSecureTokenRequest authCodeRequestWithCode:_authorizationCode + requestConfiguration:_requestConfiguration]; + } + [FIRAuthBackend secureToken:request + callback:^(FIRSecureTokenResponse *_Nullable response, + NSError *_Nullable error) { + BOOL tokenUpdated = NO; + NSString *newAccessToken = response.accessToken; + if (newAccessToken.length && ![newAccessToken isEqualToString:self->_accessToken]) { + self->_accessToken = [newAccessToken copy]; + self->_accessTokenExpirationDate = response.approximateExpirationDate; + tokenUpdated = YES; + } + NSString *newRefreshToken = response.refreshToken; + if (newRefreshToken.length && + ![newRefreshToken isEqualToString:self->_refreshToken]) { + self->_refreshToken = [newRefreshToken copy]; + tokenUpdated = YES; + } + callback(newAccessToken, error, tokenUpdated); + }]; +} + +- (BOOL)hasValidAccessToken { + return _accessToken && [_accessTokenExpirationDate timeIntervalSinceNow] > kFiveMinutes; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/User/FIRAdditionalUserInfo.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/User/FIRAdditionalUserInfo.m new file mode 100644 index 0000000..c6ba37c --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/User/FIRAdditionalUserInfo.m @@ -0,0 +1,98 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRAdditionalUserInfo_Internal.h" + +#import "FIRVerifyAssertionResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRAdditionalUserInfo + +/** @var kProviderIDCodingKey + @brief The key used to encode the providerID property for NSSecureCoding. + */ +static NSString *const kProviderIDCodingKey = @"providerID"; + +/** @var kProfileCodingKey + @brief The key used to encode the profile property for NSSecureCoding. + */ +static NSString *const kProfileCodingKey = @"profile"; + +/** @var kUsernameCodingKey + @brief The key used to encode the username property for NSSecureCoding. + */ +static NSString *const kUsernameCodingKey = @"username"; + +/** @var kNewUserKey + @brief The key used to encode the newUser property for NSSecureCoding. + */ +static NSString *const kNewUserKey = @"newUser"; + ++ (nullable instancetype)userInfoWithVerifyAssertionResponse: + (FIRVerifyAssertionResponse *)verifyAssertionResponse { + return [[self alloc] initWithProviderID:verifyAssertionResponse.providerID + profile:verifyAssertionResponse.profile + username:verifyAssertionResponse.username + isNewUser:verifyAssertionResponse.isNewUser]; +} + +- (nullable instancetype)initWithProviderID:(nullable NSString *)providerID + profile:(nullable NSDictionary *)profile + username:(nullable NSString *)username + isNewUser:(BOOL)isNewUser { + self = [super init]; + if (self) { + _providerID = [providerID copy]; + if (profile) { + _profile = [[NSDictionary alloc] initWithDictionary:profile copyItems:YES]; + } + _username = [username copy]; + _newUser = isNewUser; + } + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + NSString *providerID = + [aDecoder decodeObjectOfClass:[NSString class] forKey:kProviderIDCodingKey]; + NSDictionary *profile = + [aDecoder decodeObjectOfClass:[NSDictionary class] forKey:kProfileCodingKey]; + NSString *username = [aDecoder decodeObjectOfClass:[NSString class] forKey:kUsernameCodingKey]; + NSNumber *isNewUser = [aDecoder decodeObjectOfClass:[NSNumber class] forKey:kNewUserKey]; + + return [self initWithProviderID:providerID + profile:profile + username:username + isNewUser:isNewUser.boolValue]; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_providerID forKey:kProviderIDCodingKey]; + [aCoder encodeObject:_profile forKey:kProfileCodingKey]; + [aCoder encodeObject:_username forKey:kUsernameCodingKey]; + [aCoder encodeObject:[NSNumber numberWithBool:_newUser] forKey:kNewUserKey]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/User/FIRAdditionalUserInfo_Internal.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/User/FIRAdditionalUserInfo_Internal.h new file mode 100644 index 0000000..c8e345d --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/User/FIRAdditionalUserInfo_Internal.h @@ -0,0 +1,46 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRAdditionalUserInfo.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRAdditionalUserInfo () + +/** @fn userInfoWithVerifyAssertionResponse: + @brief A convenience factory method for constructing a @c FIRAdditionalUserInfo instance from + data returned by the verifyAssertion endpoint. + @param verifyAssertionResponse Data returned by the verifyAssertion endpoint. + @return A new instance of @c FIRAdditionalUserInfo using data from the verifyAssertion endpoint. + */ ++ (nullable instancetype)userInfoWithVerifyAssertionResponse: + (FIRVerifyAssertionResponse *)verifyAssertionResponse; + +/** @fn initWithProviderID:profile:username: + @brief Designated initializer. + @param providerID The provider identifier. + @param profile Dictionary containing the additional IdP specific information. + @param username The name of the user. + @param isNewUser Indicates whether or not the current user was signed in for the first time. + */ +- (nullable instancetype)initWithProviderID:(nullable NSString *)providerID + profile:(nullable NSDictionary *)profile + username:(nullable NSString *)username + isNewUser:(BOOL)isNewUser NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/User/FIRUser.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/User/FIRUser.m new file mode 100644 index 0000000..686763b --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/User/FIRUser.m @@ -0,0 +1,1559 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRUser_Internal.h" + +#import + +#import "FIRAdditionalUserInfo_Internal.h" +#import "FIRAuth.h" +#import "FIRAuthCredential_Internal.h" +#import "FIRAuthDataResult_Internal.h" +#import "FIRAuthErrorUtils.h" +#import "FIRAuthGlobalWorkQueue.h" +#import "FIRAuthSerialTaskQueue.h" +#import "FIRAuthOperationType.h" +#import "FIRAuth_Internal.h" +#import "FIRAuthBackend.h" +#import "FIRAuthRequestConfiguration.h" +#import "FIRAuthTokenResult_Internal.h" +#import "FIRAuthWebUtils.h" +#import "FIRDeleteAccountRequest.h" +#import "FIRDeleteAccountResponse.h" +#import "FIREmailAuthProvider.h" +#import "FIREmailPasswordAuthCredential.h" +#import "FIREmailLinkSignInRequest.h" +#import "FIRFederatedAuthProvider.h" +#import "FIRGameCenterAuthCredential.h" +#import "FIRGetAccountInfoRequest.h" +#import "FIRGetAccountInfoResponse.h" +#import "FIRGetOOBConfirmationCodeRequest.h" +#import "FIRGetOOBConfirmationCodeResponse.h" +#import "FIROAuthCredential_Internal.h" +#import "FIRSecureTokenService.h" +#import "FIRSetAccountInfoRequest.h" +#import "FIRSetAccountInfoResponse.h" +#import "FIRSignInWithGameCenterRequest.h" +#import "FIRSignInWithGameCenterResponse.h" +#import "FIRUserInfoImpl.h" +#import "FIRUserMetadata_Internal.h" +#import "FIRVerifyAssertionRequest.h" +#import "FIRVerifyAssertionResponse.h" +#import "FIRVerifyCustomTokenRequest.h" +#import "FIRVerifyCustomTokenResponse.h" +#import "FIRVerifyPasswordRequest.h" +#import "FIRVerifyPasswordResponse.h" +#import "FIRVerifyPhoneNumberRequest.h" +#import "FIRVerifyPhoneNumberResponse.h" + +#if TARGET_OS_IOS +#import "FIRPhoneAuthProvider.h" +#import "FIRPhoneAuthCredential_Internal.h" +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** @var kUserIDCodingKey + @brief The key used to encode the user ID for NSSecureCoding. + */ +static NSString *const kUserIDCodingKey = @"userID"; + +/** @var kHasEmailPasswordCredentialCodingKey + @brief The key used to encode the hasEmailPasswordCredential property for NSSecureCoding. + */ +static NSString *const kHasEmailPasswordCredentialCodingKey = @"hasEmailPassword"; + +/** @var kAnonymousCodingKey + @brief The key used to encode the anonymous property for NSSecureCoding. + */ +static NSString *const kAnonymousCodingKey = @"anonymous"; + +/** @var kEmailCodingKey + @brief The key used to encode the email property for NSSecureCoding. + */ +static NSString *const kEmailCodingKey = @"email"; + +/** @var kPhoneNumberCodingKey + @brief The key used to encode the phoneNumber property for NSSecureCoding. + */ +static NSString *const kPhoneNumberCodingKey = @"phoneNumber"; + +/** @var kEmailVerifiedCodingKey + @brief The key used to encode the isEmailVerified property for NSSecureCoding. + */ +static NSString *const kEmailVerifiedCodingKey = @"emailVerified"; + +/** @var kDisplayNameCodingKey + @brief The key used to encode the displayName property for NSSecureCoding. + */ +static NSString *const kDisplayNameCodingKey = @"displayName"; + +/** @var kPhotoURLCodingKey + @brief The key used to encode the photoURL property for NSSecureCoding. + */ +static NSString *const kPhotoURLCodingKey = @"photoURL"; + +/** @var kProviderDataKey + @brief The key used to encode the providerData instance variable for NSSecureCoding. + */ +static NSString *const kProviderDataKey = @"providerData"; + +/** @var kAPIKeyCodingKey + @brief The key used to encode the APIKey instance variable for NSSecureCoding. + */ +static NSString *const kAPIKeyCodingKey = @"APIKey"; + +/** @var kTokenServiceCodingKey + @brief The key used to encode the tokenService instance variable for NSSecureCoding. + */ +static NSString *const kTokenServiceCodingKey = @"tokenService"; + +/** @var kMetadataCodingKey + @brief The key used to encode the metadata instance variable for NSSecureCoding. + */ +static NSString *const kMetadataCodingKey = @"metadata"; + +/** @var kMissingUsersErrorMessage + @brief The error message when there is no users array in the getAccountInfo response. + */ +static NSString *const kMissingUsersErrorMessage = @"users"; + +/** @typedef CallbackWithError + @brief The type for a callback block that only takes an error parameter. + */ +typedef void (^CallbackWithError)(NSError *_Nullable); + +/** @typedef CallbackWithUserAndError + @brief The type for a callback block that takes a user parameter and an error parameter. + */ +typedef void (^CallbackWithUserAndError)(FIRUser *_Nullable, NSError *_Nullable); + +/** @typedef CallbackWithUserAndError + @brief The type for a callback block that takes a user parameter and an error parameter. + */ +typedef void (^CallbackWithAuthDataResultAndError)(FIRAuthDataResult *_Nullable, + NSError *_Nullable); + +/** @var kMissingPasswordReason + @brief The reason why the @c FIRAuthErrorCodeWeakPassword error is thrown. + @remarks This error message will be localized in the future. + */ +static NSString *const kMissingPasswordReason = @"Missing Password"; + +/** @fn callInMainThreadWithError + @brief Calls a callback in main thread with error. + @param callback The callback to be called in main thread. + @param error The error to pass to callback. + */ +static void callInMainThreadWithError(_Nullable CallbackWithError callback, + NSError *_Nullable error) { + if (callback) { + dispatch_async(dispatch_get_main_queue(), ^{ + callback(error); + }); + } +} + +/** @fn callInMainThreadWithUserAndError + @brief Calls a callback in main thread with user and error. + @param callback The callback to be called in main thread. + @param user The user to pass to callback if there is no error. + @param error The error to pass to callback. + */ +static void callInMainThreadWithUserAndError(_Nullable CallbackWithUserAndError callback, + FIRUser *_Nonnull user, + NSError *_Nullable error) { + if (callback) { + dispatch_async(dispatch_get_main_queue(), ^{ + callback(error ? nil : user, error); + }); + } +} + +/** @fn callInMainThreadWithUserAndError + @brief Calls a callback in main thread with user and error. + @param callback The callback to be called in main thread. + @param result The result to pass to callback if there is no error. + @param error The error to pass to callback. + */ +static void callInMainThreadWithAuthDataResultAndError( + _Nullable CallbackWithAuthDataResultAndError callback, + FIRAuthDataResult *_Nullable result, + NSError *_Nullable error) { + if (callback) { + dispatch_async(dispatch_get_main_queue(), ^{ + callback(result, error); + }); + } +} + +@interface FIRUserProfileChangeRequest () + +/** @fn initWithUser: + @brief Designated initializer. + @param user The user for which we are updating profile information. + */ +- (nullable instancetype)initWithUser:(FIRUser *)user NS_DESIGNATED_INITIALIZER; + +@end + +@interface FIRUser () + +/** @property anonymous + @brief Whether the current user is anonymous. + */ +@property(nonatomic, readwrite) BOOL anonymous; + +@end + +@implementation FIRUser { + /** @var _hasEmailPasswordCredential + @brief Whether or not the user can be authenticated by using Firebase email and password. + */ + BOOL _hasEmailPasswordCredential; + + /** @var _providerData + @brief Provider specific user data. + */ + NSDictionary *_providerData; + + /** @var _taskQueue + @brief Used to serialize the update profile calls. + */ + FIRAuthSerialTaskQueue *_taskQueue; + + /** @var _tokenService + @brief A secure token service associated with this user. For performing token exchanges and + refreshing access tokens. + */ + FIRSecureTokenService *_tokenService; +} + +#pragma mark - Properties + +// Explicitly @synthesize because these properties are defined in FIRUserInfo protocol. +@synthesize uid = _userID; +@synthesize displayName = _displayName; +@synthesize photoURL = _photoURL; +@synthesize email = _email; +@synthesize phoneNumber = _phoneNumber; + +#pragma mark - + ++ (void)retrieveUserWithAuth:(FIRAuth *)auth + accessToken:(nullable NSString *)accessToken + accessTokenExpirationDate:(nullable NSDate *)accessTokenExpirationDate + refreshToken:(nullable NSString *)refreshToken + anonymous:(BOOL)anonymous + callback:(FIRRetrieveUserCallback)callback { + FIRSecureTokenService *tokenService = + [[FIRSecureTokenService alloc] initWithRequestConfiguration:auth.requestConfiguration + accessToken:accessToken + accessTokenExpirationDate:accessTokenExpirationDate + refreshToken:refreshToken]; + FIRUser *user = [[self alloc] initWithTokenService:tokenService]; + user.auth = auth; + user.requestConfiguration = auth.requestConfiguration; + [user internalGetTokenWithCallback:^(NSString *_Nullable accessToken, NSError *_Nullable error) { + if (error) { + callback(nil, error); + return; + } + FIRGetAccountInfoRequest *getAccountInfoRequest = + [[FIRGetAccountInfoRequest alloc] initWithAccessToken:accessToken + requestConfiguration:auth.requestConfiguration]; + [FIRAuthBackend getAccountInfo:getAccountInfoRequest + callback:^(FIRGetAccountInfoResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + // No need to sign out user here for errors because the user hasn't been signed in yet. + callback(nil, error); + return; + } + user.anonymous = anonymous; + [user updateWithGetAccountInfoResponse:response]; + callback(user, nil); + }]; + }]; +} + +- (instancetype)initWithTokenService:(FIRSecureTokenService *)tokenService { + self = [super init]; + if (self) { + _providerData = @{ }; + _taskQueue = [[FIRAuthSerialTaskQueue alloc] init]; + _tokenService = tokenService; + } + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + NSString *userID = [aDecoder decodeObjectOfClass:[NSString class] forKey:kUserIDCodingKey]; + BOOL hasAnonymousKey = [aDecoder containsValueForKey:kAnonymousCodingKey]; + BOOL anonymous = [aDecoder decodeBoolForKey:kAnonymousCodingKey]; + BOOL hasEmailPasswordCredential = + [aDecoder decodeBoolForKey:kHasEmailPasswordCredentialCodingKey]; + NSString *displayName = + [aDecoder decodeObjectOfClass:[NSString class] forKey:kDisplayNameCodingKey]; + NSURL *photoURL = + [aDecoder decodeObjectOfClass:[NSURL class] forKey:kPhotoURLCodingKey]; + NSString *email = + [aDecoder decodeObjectOfClass:[NSString class] forKey:kEmailCodingKey]; + NSString *phoneNumber = + [aDecoder decodeObjectOfClass:[NSString class] forKey:kPhoneNumberCodingKey]; + BOOL emailVerified = [aDecoder decodeBoolForKey:kEmailVerifiedCodingKey]; + NSSet *providerDataClasses = [NSSet setWithArray:@[ + [NSDictionary class], + [NSString class], + [FIRUserInfoImpl class] + ]]; + NSDictionary *providerData = + [aDecoder decodeObjectOfClasses:providerDataClasses forKey:kProviderDataKey]; + FIRSecureTokenService *tokenService = + [aDecoder decodeObjectOfClass:[FIRSecureTokenService class] forKey:kTokenServiceCodingKey]; + FIRUserMetadata *metadata = + [aDecoder decodeObjectOfClass:[FIRUserMetadata class] forKey:kMetadataCodingKey]; + NSString *APIKey = + [aDecoder decodeObjectOfClass:[NSString class] forKey:kAPIKeyCodingKey]; + if (!userID || !tokenService) { + return nil; + } + self = [self initWithTokenService:tokenService]; + if (self) { + _userID = userID; + // Previous version of this code didn't save 'anonymous' bit directly but deduced it from + // 'hasEmailPasswordCredential' and 'providerData' instead, so here backward compatibility is + // provided to read old format data. + _anonymous = hasAnonymousKey ? anonymous : (!hasEmailPasswordCredential && !providerData.count); + _hasEmailPasswordCredential = hasEmailPasswordCredential; + _email = email; + _emailVerified = emailVerified; + _displayName = displayName; + _photoURL = photoURL; + _providerData = providerData; + _phoneNumber = phoneNumber; + _metadata = metadata ?: [[FIRUserMetadata alloc] initWithCreationDate:nil lastSignInDate:nil]; + _requestConfiguration = [[FIRAuthRequestConfiguration alloc] initWithAPIKey:APIKey]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_userID forKey:kUserIDCodingKey]; + [aCoder encodeBool:self.anonymous forKey:kAnonymousCodingKey]; + [aCoder encodeBool:_hasEmailPasswordCredential forKey:kHasEmailPasswordCredentialCodingKey]; + [aCoder encodeObject:_providerData forKey:kProviderDataKey]; + [aCoder encodeObject:_email forKey:kEmailCodingKey]; + [aCoder encodeObject:_phoneNumber forKey:kPhoneNumberCodingKey]; + [aCoder encodeBool:_emailVerified forKey:kEmailVerifiedCodingKey]; + [aCoder encodeObject:_photoURL forKey:kPhotoURLCodingKey]; + [aCoder encodeObject:_displayName forKey:kDisplayNameCodingKey]; + [aCoder encodeObject:_metadata forKey:kMetadataCodingKey]; + [aCoder encodeObject:_auth.requestConfiguration.APIKey forKey:kAPIKeyCodingKey]; + [aCoder encodeObject:_tokenService forKey:kTokenServiceCodingKey]; +} + +#pragma mark - + +- (void)setAuth:(nullable FIRAuth *)auth { + _auth = auth; + _tokenService.requestConfiguration = auth.requestConfiguration; +} + +- (NSString *)providerID { + return @"Firebase"; +} + +- (NSArray> *)providerData { + return _providerData.allValues; +} + +/** @fn getAccountInfoRefreshingCache: + @brief Gets the users's account data from the server, updating our local values. + @param callback Invoked when the request to getAccountInfo has completed, or when an error has + been detected. Invoked asynchronously on the auth global work queue in the future. + */ +- (void)getAccountInfoRefreshingCache:(void(^)(FIRGetAccountInfoResponseUser *_Nullable user, + NSError *_Nullable error))callback { + [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken, NSError *_Nullable error) { + if (error) { + callback(nil, error); + return; + } + FIRGetAccountInfoRequest *getAccountInfoRequest = + [[FIRGetAccountInfoRequest alloc] initWithAccessToken:accessToken + requestConfiguration:self->_auth.requestConfiguration]; + [FIRAuthBackend getAccountInfo:getAccountInfoRequest + callback:^(FIRGetAccountInfoResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + [self signOutIfTokenIsInvalidWithError:error]; + callback(nil, error); + return; + } + [self updateWithGetAccountInfoResponse:response]; + if (![self updateKeychain:&error]) { + callback(nil, error); + return; + } + callback(response.users.firstObject, nil); + }]; + }]; +} + +- (void)updateWithGetAccountInfoResponse:(FIRGetAccountInfoResponse *)response { + FIRGetAccountInfoResponseUser *user = response.users.firstObject; + _userID = user.localID; + _email = user.email; + _emailVerified = user.emailVerified; + _displayName = user.displayName; + _photoURL = user.photoURL; + _phoneNumber = user.phoneNumber; + _hasEmailPasswordCredential = user.passwordHash.length > 0; + _metadata = + [[FIRUserMetadata alloc]initWithCreationDate:user.creationDate + lastSignInDate:user.lastLoginDate]; + NSMutableDictionary *providerData = + [NSMutableDictionary dictionary]; + for (FIRGetAccountInfoResponseProviderUserInfo *providerUserInfo in user.providerUserInfo) { + FIRUserInfoImpl *userInfo = + [FIRUserInfoImpl userInfoWithGetAccountInfoResponseProviderUserInfo:providerUserInfo]; + if (userInfo) { + providerData[providerUserInfo.providerID] = userInfo; + } + } + _providerData = [providerData copy]; +} + +/** @fn executeUserUpdateWithChanges:callback: + @brief Performs a setAccountInfo request by mutating the results of a getAccountInfo response, + atomically in regards to other calls to this method. + @param changeBlock A block responsible for mutating a template @c FIRSetAccountInfoRequest + @param callback A block to invoke when the change is complete. Invoked asynchronously on the + auth global work queue in the future. + */ +- (void)executeUserUpdateWithChanges:(void(^)(FIRGetAccountInfoResponseUser *, + FIRSetAccountInfoRequest *))changeBlock + callback:(nonnull FIRUserProfileChangeCallback)callback { + [_taskQueue enqueueTask:^(FIRAuthSerialTaskCompletionBlock _Nonnull complete) { + [self getAccountInfoRefreshingCache:^(FIRGetAccountInfoResponseUser *_Nullable user, + NSError *_Nullable error) { + if (error) { + complete(); + callback(error); + return; + } + [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken, + NSError *_Nullable error) { + if (error) { + complete(); + callback(error); + return; + } + FIRAuthRequestConfiguration *configuration = self->_auth.requestConfiguration; + // Mutate setAccountInfoRequest in block: + FIRSetAccountInfoRequest *setAccountInfoRequest = + [[FIRSetAccountInfoRequest alloc] initWithRequestConfiguration:configuration]; + setAccountInfoRequest.accessToken = accessToken; + changeBlock(user, setAccountInfoRequest); + // Execute request: + [FIRAuthBackend setAccountInfo:setAccountInfoRequest + callback:^(FIRSetAccountInfoResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + [self signOutIfTokenIsInvalidWithError:error]; + complete(); + callback(error); + return; + } + if (response.IDToken && response.refreshToken) { + FIRSecureTokenService *tokenService = [[FIRSecureTokenService alloc] + initWithRequestConfiguration:configuration + accessToken:response.IDToken + accessTokenExpirationDate:response.approximateExpirationDate + refreshToken:response.refreshToken]; + [self setTokenService:tokenService callback:^(NSError *_Nullable error) { + complete(); + callback(error); + }]; + return; + } + complete(); + callback(nil); + }]; + }]; + }]; + }]; +} + +/** @fn updateKeychain: + @brief Updates the keychain for user token or info changes. + @param error The error if NO is returned. + @return Whether the operation is successful. + */ +- (BOOL)updateKeychain:(NSError *_Nullable *_Nullable)error { + return [_auth updateKeychainWithUser:self error:error]; +} + +/** @fn setTokenService:callback: + @brief Sets a new token service for the @c FIRUser instance. + @param tokenService The new token service object. + @param callback The block to be called in the global auth working queue once finished. + @remarks The method makes sure the token service has access and refresh token and the new tokens + are saved in the keychain before calling back. + */ +- (void)setTokenService:(FIRSecureTokenService *)tokenService + callback:(nonnull CallbackWithError)callback { + [tokenService fetchAccessTokenForcingRefresh:NO + callback:^(NSString *_Nullable token, + NSError *_Nullable error, + BOOL tokenUpdated) { + if (error) { + callback(error); + return; + } + self->_tokenService = tokenService; + if (![self updateKeychain:&error]) { + callback(error); + return; + } + callback(nil); + }]; +} + +#pragma mark - + +/** @fn updateEmail:password:callback: + @brief Updates email address and/or password for the current user. + @remarks May fail if there is already an email/password-based account for the same email + address. + @param email The email address for the user, if to be updated. + @param password The new password for the user, if to be updated. + @param callback The block called when the user profile change has finished. Invoked + asynchronously on the auth global work queue in the future. + @remarks May fail with a @c FIRAuthErrorCodeRequiresRecentLogin error code. + Call @c reauthentateWithCredential:completion: beforehand to avoid this error case. + */ +- (void)updateEmail:(nullable NSString *)email + password:(nullable NSString *)password + callback:(nonnull FIRUserProfileChangeCallback)callback { + if (password && ![password length]) { + callback([FIRAuthErrorUtils weakPasswordErrorWithServerResponseReason:kMissingPasswordReason]); + return; + } + BOOL hadEmailPasswordCredential = _hasEmailPasswordCredential; + [self executeUserUpdateWithChanges:^(FIRGetAccountInfoResponseUser *user, + FIRSetAccountInfoRequest *request) { + if (email) { + request.email = email; + } + if (password) { + request.password = password; + } + } + callback:^(NSError *error) { + if (error) { + callback(error); + return; + } + if (email) { + self->_email = [email copy]; + } + if (self->_email) { + if (!hadEmailPasswordCredential) { + // The list of providers need to be updated for the newly added email-password provider. + [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken, + NSError *_Nullable error) { + if (error) { + callback(error); + return; + } + FIRAuthRequestConfiguration *requestConfiguration = self->_auth.requestConfiguration; + FIRGetAccountInfoRequest *getAccountInfoRequest = + [[FIRGetAccountInfoRequest alloc] initWithAccessToken:accessToken + requestConfiguration:requestConfiguration]; + [FIRAuthBackend getAccountInfo:getAccountInfoRequest + callback:^(FIRGetAccountInfoResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + [self signOutIfTokenIsInvalidWithError:error]; + callback(error); + return; + } + for (FIRGetAccountInfoResponseUser *userAccountInfo in response.users) { + // Set the account to non-anonymous if there are any providers, even if + // they're not email/password ones. + if (userAccountInfo.providerUserInfo.count > 0) { + self.anonymous = NO; + } + for (FIRGetAccountInfoResponseProviderUserInfo *providerUserInfo in + userAccountInfo.providerUserInfo) { + if ([providerUserInfo.providerID isEqualToString:FIREmailAuthProviderID]) { + self->_hasEmailPasswordCredential = YES; + break; + } + } + } + [self updateWithGetAccountInfoResponse:response]; + if (![self updateKeychain:&error]) { + callback(error); + return; + } + callback(nil); + }]; + }]; + return; + } + } + if (![self updateKeychain:&error]) { + callback(error); + return; + } + callback(nil); + }]; +} + +- (void)updateEmail:(NSString *)email completion:(nullable FIRUserProfileChangeCallback)completion { + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + [self updateEmail:email password:nil callback:^(NSError *_Nullable error) { + callInMainThreadWithError(completion, error); + }]; + }); +} + +- (void)updatePassword:(NSString *)password + completion:(nullable FIRUserProfileChangeCallback)completion { + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + [self updateEmail:nil password:password callback:^(NSError *_Nullable error){ + callInMainThreadWithError(completion, error); + }]; + }); +} + +#if TARGET_OS_IOS +/** @fn internalUpdateOrLinkPhoneNumberCredential:completion: + @brief Updates the phone number for the user. On success, the cached user profile data is + updated. + + @param phoneAuthCredential The new phone number credential corresponding to the phone number + to be added to the Firebase account, if a phone number is already linked to the account this + new phone number will replace it. + @param isLinkOperation Boolean value indicating whether or not this is a link operation. + @param completion Optionally; the block invoked when the user profile change has finished. + Invoked asynchronously on the global work queue in the future. + */ +- (void)internalUpdateOrLinkPhoneNumberCredential:(FIRPhoneAuthCredential *)phoneAuthCredential + isLinkOperation:(BOOL)isLinkOperation + completion:(FIRUserProfileChangeCallback)completion { + [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken, + NSError *_Nullable error) { + if (error) { + completion(error); + return; + } + FIRAuthOperationType operation = + isLinkOperation ? FIRAuthOperationTypeLink : FIRAuthOperationTypeUpdate; + FIRVerifyPhoneNumberRequest *request = [[FIRVerifyPhoneNumberRequest alloc] + initWithVerificationID:phoneAuthCredential.verificationID + verificationCode:phoneAuthCredential.verificationCode + operation:operation + requestConfiguration:self->_auth.requestConfiguration]; + request.accessToken = accessToken; + [FIRAuthBackend verifyPhoneNumber:request + callback:^(FIRVerifyPhoneNumberResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + [self signOutIfTokenIsInvalidWithError:error]; + completion(error); + return; + } + // Get account info to update cached user info. + [self getAccountInfoRefreshingCache:^(FIRGetAccountInfoResponseUser *_Nullable user, + NSError *_Nullable error) { + if (error) { + [self signOutIfTokenIsInvalidWithError:error]; + completion(error); + return; + } + self.anonymous = NO; + if (![self updateKeychain:&error]) { + completion(error); + return; + } + completion(nil); + }]; + }]; + }]; +} + +- (void)updatePhoneNumberCredential:(FIRPhoneAuthCredential *)phoneAuthCredential + completion:(nullable FIRUserProfileChangeCallback)completion { + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + [self internalUpdateOrLinkPhoneNumberCredential:phoneAuthCredential + isLinkOperation:NO + completion:^(NSError *_Nullable error) { + callInMainThreadWithError(completion, error); + }]; + }); +} +#endif + +- (FIRUserProfileChangeRequest *)profileChangeRequest { + __block FIRUserProfileChangeRequest *result; + dispatch_sync(FIRAuthGlobalWorkQueue(), ^{ + result = [[FIRUserProfileChangeRequest alloc] initWithUser:self]; + }); + return result; +} + +- (void)setDisplayName:(NSString *)displayName { + _displayName = [displayName copy]; +} + +- (void)setPhotoURL:(NSURL *)photoURL { + _photoURL = [photoURL copy]; +} + +- (NSString *)rawAccessToken { + return _tokenService.rawAccessToken; +} + +- (NSDate *)accessTokenExpirationDate { + return _tokenService.accessTokenExpirationDate; +} + +#pragma mark - + +- (void)reloadWithCompletion:(nullable FIRUserProfileChangeCallback)completion { + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + [self getAccountInfoRefreshingCache:^(FIRGetAccountInfoResponseUser *_Nullable user, + NSError *_Nullable error) { + callInMainThreadWithError(completion, error); + }]; + }); +} + +#pragma mark - + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +- (void)reauthenticateWithCredential:(FIRAuthCredential *) credential + completion:(nullable FIRAuthDataResultCallback) completion { + [self reauthenticateAndRetrieveDataWithCredential:credential completion:completion]; +} +#pragma clang diagnostic pop + +- (void)reauthenticateAndRetrieveDataWithCredential:(FIRAuthCredential *) credential + completion:(nullable FIRAuthDataResultCallback) completion { + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + [self->_auth internalSignInAndRetrieveDataWithCredential:credential + isReauthentication:YES + callback:^(FIRAuthDataResult *_Nullable + authResult, + NSError *_Nullable error) { + if (error) { + // If "user not found" error returned by backend, translate to user mismatch error which is + // more accurate. + if (error.code == FIRAuthErrorCodeUserNotFound) { + error = [FIRAuthErrorUtils userMismatchError]; + } + callInMainThreadWithAuthDataResultAndError(completion, authResult, error); + return; + } + if (![authResult.user.uid isEqual:[self->_auth getUserID]]) { + callInMainThreadWithAuthDataResultAndError(completion, authResult, + [FIRAuthErrorUtils userMismatchError]); + return; + } + // Successful reauthenticate + [self setTokenService:authResult.user->_tokenService callback:^(NSError *_Nullable error) { + callInMainThreadWithAuthDataResultAndError(completion, authResult, error); + }]; + }]; + }); +} + +- (void)reauthenticateWithProvider:(id)provider + UIDelegate:(nullable id)UIDelegate + completion:(nullable FIRAuthDataResultCallback)completion { +#if TARGET_OS_IOS + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + [provider getCredentialWithUIDelegate:UIDelegate + completion:^(FIRAuthCredential *_Nullable credential, + NSError *_Nullable error) { + [self reauthenticateWithCredential:credential + completion:completion]; + }]; + }); +#endif // TARGET_OS_IOS +} + +- (nullable NSString *)refreshToken { + __block NSString *result; + dispatch_sync(FIRAuthGlobalWorkQueue(), ^{ + result = self->_tokenService.refreshToken; + }); + return result; +} + +- (void)getIDTokenWithCompletion:(nullable FIRAuthTokenCallback)completion { + // |getIDTokenForcingRefresh:completion:| is also a public API so there is no need to dispatch to + // global work queue here. + [self getIDTokenForcingRefresh:NO completion:completion]; +} + +- (void)getIDTokenForcingRefresh:(BOOL)forceRefresh + completion:(nullable FIRAuthTokenCallback)completion { + [self getIDTokenResultForcingRefresh:forceRefresh + completion:^(FIRAuthTokenResult *_Nullable tokenResult, + NSError *_Nullable error) { + + if (completion) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(tokenResult.token, error); + }); + } + }]; +} + +- (void)getIDTokenResultWithCompletion:(nullable FIRAuthTokenResultCallback)completion { + [self getIDTokenResultForcingRefresh:NO + completion:^(FIRAuthTokenResult *_Nullable tokenResult, + NSError *_Nullable error) { + if (completion) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(tokenResult, error); + }); + } + }]; +} + +- (void)getIDTokenResultForcingRefresh:(BOOL)forceRefresh + completion:(nullable FIRAuthTokenResultCallback)completion { + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + [self internalGetTokenForcingRefresh:forceRefresh + callback:^(NSString *_Nullable token, NSError *_Nullable error) { + FIRAuthTokenResult *tokenResult; + if (token) { + tokenResult = [self parseIDToken:token error:&error]; + } + if (completion) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(tokenResult, error); + }); + } + }]; + }); +} + +/** @fn parseIDToken:error: + @brief Parses the provided IDToken and returns an instance of FIRAuthTokenResult containing + claims obtained from the IDToken. + + @param token The raw text of the Firebase IDToken encoded in base64. + @param error An out parameter which would contain any error that occurs during parsing. + @return An instance of FIRAuthTokenResult containing claims obtained from the IDToken. + + @remarks IDToken returned from the backend in some cases is of a length that is not a multiple + of 4. In these cases this function pads the token with as many "=" characters as needed and + then attempts to parse the token. If the token cannot be parsed an error is returned via the + "error" out parameter. + */ +- (nullable FIRAuthTokenResult *)parseIDToken:(NSString *)token error:(NSError **)error { + // Though this is an internal method, errors returned here are surfaced in user-visible + // callbacks. + if (error) { + *error = nil; + } + NSArray *tokenStringArray = [token componentsSeparatedByString:@"."]; + + // The JWT should have three parts, though we only use the second in this method. + if (tokenStringArray.count != 3) { + if (error) { + *error = [FIRAuthErrorUtils malformedJWTErrorWithToken:token underlyingError:nil]; + } + return nil; + } + + // The token payload is always the second index of the array. + NSString *idToken = tokenStringArray[1]; + + // Convert the base64URL encoded string to a base64 encoded string. + // Replace "_" with "/" + NSMutableString *tokenPayload = + [[idToken stringByReplacingOccurrencesOfString:@"_" withString:@"/"] mutableCopy]; + + // Replace "-" with "+" + [tokenPayload replaceOccurrencesOfString:@"-" + withString:@"+" + options:kNilOptions + range:NSMakeRange(0, tokenPayload.length)]; + + // Pad the token payload with "=" signs if the payload's length is not a multiple of 4. + while ((tokenPayload.length % 4) != 0) { + [tokenPayload appendFormat:@"="]; + } + NSData *decodedTokenPayloadData = + [[NSData alloc] initWithBase64EncodedString:tokenPayload + options:NSDataBase64DecodingIgnoreUnknownCharacters]; + if (!decodedTokenPayloadData) { + if (error) { + *error = [FIRAuthErrorUtils malformedJWTErrorWithToken:token underlyingError:nil]; + } + return nil; + } + NSError *jsonError = nil; + NSJSONReadingOptions options = NSJSONReadingMutableContainers|NSJSONReadingAllowFragments; + NSDictionary *tokenPayloadDictionary = + [NSJSONSerialization JSONObjectWithData:decodedTokenPayloadData + options:options + error:&jsonError]; + if (jsonError != nil) { + if (error) { + *error = [FIRAuthErrorUtils malformedJWTErrorWithToken:token underlyingError:jsonError]; + } + return nil; + } + + if (!tokenPayloadDictionary) { + if (error) { + *error = [FIRAuthErrorUtils malformedJWTErrorWithToken:token underlyingError:nil]; + } + return nil; + } + + // These are dates since 00:00:00 January 1 1970, as described by the Terminology section in + // the JWT spec. https://tools.ietf.org/html/rfc7519 + NSDate *expDate = + [NSDate dateWithTimeIntervalSince1970:[tokenPayloadDictionary[@"exp"] doubleValue]]; + NSDate *authDate = + [NSDate dateWithTimeIntervalSince1970:[tokenPayloadDictionary[@"auth_time"] doubleValue]]; + NSDate *issuedDate = + [NSDate dateWithTimeIntervalSince1970:[tokenPayloadDictionary[@"iat"] doubleValue]]; + FIRAuthTokenResult *result = + [[FIRAuthTokenResult alloc] initWithToken:token + expirationDate:expDate + authDate:authDate + issuedAtDate:issuedDate + signInProvider:tokenPayloadDictionary[@"firebase"][@"sign_in_provider"] + claims:tokenPayloadDictionary]; + return result; +} + +/** @fn internalGetTokenForcingRefresh:callback: + @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired. + @param callback The block to invoke when the token is available. Invoked asynchronously on the + global work thread in the future. + */ +- (void)internalGetTokenWithCallback:(nonnull FIRAuthTokenCallback)callback { + [self internalGetTokenForcingRefresh:NO callback:callback]; +} + +- (void)internalGetTokenForcingRefresh:(BOOL)forceRefresh + callback:(nonnull FIRAuthTokenCallback)callback { + [_tokenService fetchAccessTokenForcingRefresh:forceRefresh + callback:^(NSString *_Nullable token, + NSError *_Nullable error, + BOOL tokenUpdated) { + if (error) { + [self signOutIfTokenIsInvalidWithError:error]; + callback(nil, error); + return; + } + if (tokenUpdated) { + if (![self updateKeychain:&error]) { + callback(nil, error); + return; + } + } + callback(token, nil); + }]; +} + +- (void)internalVerifyBeforeUpdateEmailWithNewEmail:(NSString *)newEmail + actionCodeSettings:(nullable FIRActionCodeSettings *)actionCodeSettings + completion:(FIRVerifyBeforeUpdateEmailCallback)completion { + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken, + NSError *_Nullable error) { + if (error) { + callInMainThreadWithError(completion, error); + return; + } + FIRAuthRequestConfiguration *configuration = self->_auth.requestConfiguration; + FIRActionCodeSettings *settings = actionCodeSettings; + FIRGetOOBConfirmationCodeRequest *request = + [FIRGetOOBConfirmationCodeRequest verifyBeforeUpdateEmailWithAccessToken:accessToken + newEmail:newEmail + actionCodeSettings:settings + requestConfiguration:configuration]; + [FIRAuthBackend getOOBConfirmationCode:request + callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable + response, + NSError *_Nullable error) { + callInMainThreadWithError(completion, error); + }]; + }]; + }); +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +- (void)linkWithCredential:(FIRAuthCredential *)credential + completion:(nullable FIRAuthDataResultCallback)completion { + [self linkAndRetrieveDataWithCredential:credential completion:completion]; +} +#pragma clang diagnostic pop + +- (void)linkAndRetrieveDataWithCredential:(FIRAuthCredential *)credential + completion:(nullable FIRAuthDataResultCallback)completion { + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + if (self->_providerData[credential.provider]) { + callInMainThreadWithAuthDataResultAndError(completion, + nil, + [FIRAuthErrorUtils providerAlreadyLinkedError]); + return; + } + FIRAuthDataResult *result = + [[FIRAuthDataResult alloc] initWithUser:self additionalUserInfo:nil]; + if ([credential isKindOfClass:[FIREmailPasswordAuthCredential class]]) { + if (self->_hasEmailPasswordCredential) { + callInMainThreadWithAuthDataResultAndError(completion, + nil, + [FIRAuthErrorUtils providerAlreadyLinkedError]); + return; + } + FIREmailPasswordAuthCredential *emailPasswordCredential = + (FIREmailPasswordAuthCredential *)credential; + if (emailPasswordCredential.password) { + [self updateEmail:emailPasswordCredential.email + password:emailPasswordCredential.password + callback:^(NSError *error) { + if (error) { + callInMainThreadWithAuthDataResultAndError(completion, nil, error); + } else { + callInMainThreadWithAuthDataResultAndError(completion, result, nil); + } + }]; + } else { + [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken, + NSError *_Nullable error) { + NSDictionary *queryItems = [FIRAuthWebUtils parseURL:emailPasswordCredential.link]; + if (![queryItems count]) { + NSURLComponents *urlComponents = [NSURLComponents componentsWithString:emailPasswordCredential.link]; + queryItems = [FIRAuthWebUtils parseURL:urlComponents.query]; + } + NSString *actionCode = queryItems[@"oobCode"]; + FIRAuthRequestConfiguration *requestConfiguration = self.auth.requestConfiguration; + FIREmailLinkSignInRequest *request = + [[FIREmailLinkSignInRequest alloc] initWithEmail:emailPasswordCredential.email + oobCode:actionCode + requestConfiguration:requestConfiguration]; + request.IDToken = accessToken; + [FIRAuthBackend emailLinkSignin:request + callback:^(FIREmailLinkSignInResponse *_Nullable response, + NSError *_Nullable error) { + if (error){ + callInMainThreadWithAuthDataResultAndError(completion, nil, error); + } else { + [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken, + NSError *_Nullable error) { + if (error) { + callInMainThreadWithAuthDataResultAndError(completion, nil, error); + return; + } + + FIRGetAccountInfoRequest *getAccountInfoRequest = + [[FIRGetAccountInfoRequest alloc] initWithAccessToken:accessToken + requestConfiguration:requestConfiguration]; + [FIRAuthBackend getAccountInfo:getAccountInfoRequest + callback:^(FIRGetAccountInfoResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + [self signOutIfTokenIsInvalidWithError:error]; + callInMainThreadWithAuthDataResultAndError(completion, nil, error); + return; + } + self.anonymous = NO; + [self updateWithGetAccountInfoResponse:response]; + if (![self updateKeychain:&error]) { + callInMainThreadWithAuthDataResultAndError(completion, nil, error); + return; + } + callInMainThreadWithAuthDataResultAndError(completion, result, nil); + }]; + }]; + } + }]; + }]; + } + return; + } + + if ([credential isKindOfClass:[FIRGameCenterAuthCredential class]]) { + FIRGameCenterAuthCredential *gameCenterCredential = (FIRGameCenterAuthCredential *)credential; + [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken, + NSError *_Nullable error) { + FIRAuthRequestConfiguration *requestConfiguration = self.auth.requestConfiguration; + FIRSignInWithGameCenterRequest *gameCenterRequest = + [[FIRSignInWithGameCenterRequest alloc] initWithPlayerID:gameCenterCredential.playerID + publicKeyURL:gameCenterCredential.publicKeyURL + signature:gameCenterCredential.signature + salt:gameCenterCredential.salt + timestamp:gameCenterCredential.timestamp + displayName:gameCenterCredential.displayName + requestConfiguration:requestConfiguration]; + gameCenterRequest.accessToken = accessToken; + + [FIRAuthBackend signInWithGameCenter:gameCenterRequest + callback:^(FIRSignInWithGameCenterResponse *_Nullable response, + NSError *_Nullable error) { + if (error){ + callInMainThreadWithAuthDataResultAndError(completion, nil, error); + } else { + [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken, + NSError *_Nullable error) { + if (error) { + callInMainThreadWithAuthDataResultAndError(completion, nil, error); + return; + } + + FIRGetAccountInfoRequest *getAccountInfoRequest = + [[FIRGetAccountInfoRequest alloc] initWithAccessToken:accessToken + requestConfiguration:requestConfiguration]; + [FIRAuthBackend getAccountInfo:getAccountInfoRequest + callback:^(FIRGetAccountInfoResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + [self signOutIfTokenIsInvalidWithError:error]; + callInMainThreadWithAuthDataResultAndError(completion, nil, error); + return; + } + self.anonymous = NO; + [self updateWithGetAccountInfoResponse:response]; + if (![self updateKeychain:&error]) { + callInMainThreadWithAuthDataResultAndError(completion, nil, error); + return; + } + callInMainThreadWithAuthDataResultAndError(completion, result, nil); + }]; + }]; + } + }]; + }]; + return; + } + + #if TARGET_OS_IOS + if ([credential isKindOfClass:[FIRPhoneAuthCredential class]]) { + FIRPhoneAuthCredential *phoneAuthCredential = (FIRPhoneAuthCredential *)credential; + [self internalUpdateOrLinkPhoneNumberCredential:phoneAuthCredential + isLinkOperation:YES + completion:^(NSError *_Nullable error) { + if (error){ + callInMainThreadWithAuthDataResultAndError(completion, nil, error); + } else { + callInMainThreadWithAuthDataResultAndError(completion, result, nil); + } + }]; + return; + } + #endif + + [self->_taskQueue enqueueTask:^(FIRAuthSerialTaskCompletionBlock _Nonnull complete) { + CallbackWithAuthDataResultAndError completeWithError = + ^(FIRAuthDataResult *result, NSError *error) { + complete(); + callInMainThreadWithAuthDataResultAndError(completion, result, error); + }; + [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken, + NSError *_Nullable error) { + if (error) { + completeWithError(nil, error); + return; + } + FIRAuthRequestConfiguration *requestConfiguration = self->_auth.requestConfiguration; + FIRVerifyAssertionRequest *request = + [[FIRVerifyAssertionRequest alloc] initWithProviderID:credential.provider + requestConfiguration:requestConfiguration]; + [credential prepareVerifyAssertionRequest:request]; + request.accessToken = accessToken; + [FIRAuthBackend verifyAssertion:request + callback:^(FIRVerifyAssertionResponse *response, NSError *error) { + if (error) { + [self signOutIfTokenIsInvalidWithError:error]; + completeWithError(nil, error); + return; + } + FIRAdditionalUserInfo *additionalUserInfo = + [FIRAdditionalUserInfo userInfoWithVerifyAssertionResponse:response]; + FIROAuthCredential *updatedOAuthCredential = + [[FIROAuthCredential alloc] initWithVerifyAssertionResponse:response]; + FIRAuthDataResult *result = + [[FIRAuthDataResult alloc] initWithUser:self + additionalUserInfo:additionalUserInfo + credential:updatedOAuthCredential]; + // Update the new token and refresh user info again. + self->_tokenService = [[FIRSecureTokenService alloc] + initWithRequestConfiguration:requestConfiguration + accessToken:response.IDToken + accessTokenExpirationDate:response.approximateExpirationDate + refreshToken:response.refreshToken]; + [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken, + NSError *_Nullable error) { + if (error) { + completeWithError(nil, error); + return; + } + FIRGetAccountInfoRequest *getAccountInfoRequest = + [[FIRGetAccountInfoRequest alloc] initWithAccessToken:accessToken + requestConfiguration:requestConfiguration]; + [FIRAuthBackend getAccountInfo:getAccountInfoRequest + callback:^(FIRGetAccountInfoResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + [self signOutIfTokenIsInvalidWithError:error]; + completeWithError(nil, error); + return; + } + self.anonymous = NO; + [self updateWithGetAccountInfoResponse:response]; + if (![self updateKeychain:&error]) { + completeWithError(nil, error); + return; + } + completeWithError(result, nil); + }]; + }]; + }]; + }]; + }]; + }); +} + +- (void)linkWithProvider:(id)provider + UIDelegate:(nullable id)UIDelegate + completion:(nullable FIRAuthDataResultCallback)completion { +#if TARGET_OS_IOS + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + [provider getCredentialWithUIDelegate:UIDelegate + completion:^(FIRAuthCredential *_Nullable credential, + NSError *_Nullable error) { + [self linkWithCredential:credential + completion:completion]; + }]; + }); +#endif // TARGET_OS_IOS +} + +- (void)unlinkFromProvider:(NSString *)provider + completion:(nullable FIRAuthResultCallback)completion { + [_taskQueue enqueueTask:^(FIRAuthSerialTaskCompletionBlock _Nonnull complete) { + CallbackWithError completeAndCallbackWithError = ^(NSError *error) { + complete(); + callInMainThreadWithUserAndError(completion, self, error); + }; + [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken, + NSError *_Nullable error) { + if (error) { + completeAndCallbackWithError(error); + return; + } + FIRAuthRequestConfiguration *requestConfiguration = self->_auth.requestConfiguration; + FIRSetAccountInfoRequest *setAccountInfoRequest = + [[FIRSetAccountInfoRequest alloc] initWithRequestConfiguration:requestConfiguration]; + setAccountInfoRequest.accessToken = accessToken; + + if (!self->_providerData[provider]) { + completeAndCallbackWithError([FIRAuthErrorUtils noSuchProviderError]); + return; + } + setAccountInfoRequest.deleteProviders = @[ provider ]; + + [FIRAuthBackend setAccountInfo:setAccountInfoRequest + callback:^(FIRSetAccountInfoResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + [self signOutIfTokenIsInvalidWithError:error]; + completeAndCallbackWithError(error); + return; + } + + // We can't just use the provider info objects in FIRSetAccountInfoResponse because they + // don't have localID and email fields. Remove the specific provider manually. + NSMutableDictionary *mutableProviderData = [self->_providerData mutableCopy]; + [mutableProviderData removeObjectForKey:provider]; + self->_providerData = [mutableProviderData copy]; + + if ([provider isEqualToString:FIREmailAuthProviderID]) { + self->_hasEmailPasswordCredential = NO; + } + #if TARGET_OS_IOS + // After successfully unlinking a phone auth provider, remove the phone number from the + // cached user info. + if ([provider isEqualToString:FIRPhoneAuthProviderID]) { + self->_phoneNumber = nil; + } + #endif + + if (response.IDToken && response.refreshToken) { + FIRSecureTokenService *tokenService = [[FIRSecureTokenService alloc] + initWithRequestConfiguration:requestConfiguration + accessToken:response.IDToken + accessTokenExpirationDate:response.approximateExpirationDate + refreshToken:response.refreshToken]; + [self setTokenService:tokenService callback:^(NSError *_Nullable error) { + completeAndCallbackWithError(error); + }]; + return; + } + if (![self updateKeychain:&error]) { + completeAndCallbackWithError(error); + return; + } + completeAndCallbackWithError(nil); + }]; + }]; + }]; +} + +- (void)sendEmailVerificationWithCompletion:(nullable FIRSendEmailVerificationCallback)completion { + [self sendEmailVerificationWithNullableActionCodeSettings:nil completion:completion]; +} + +- (void)sendEmailVerificationWithActionCodeSettings:(FIRActionCodeSettings *)actionCodeSettings + completion:(nullable FIRSendEmailVerificationCallback) + completion { + [self sendEmailVerificationWithNullableActionCodeSettings:actionCodeSettings + completion:completion]; +} + +/** @fn sendEmailVerificationWithNullableActionCodeSettings:completion: + @brief Initiates email verification for the user. + + @param actionCodeSettings Optionally, a @c FIRActionCodeSettings object containing settings + related to the handling action codes. + */ +- (void)sendEmailVerificationWithNullableActionCodeSettings:(nullable FIRActionCodeSettings *) + actionCodeSettings + completion: + (nullable FIRSendEmailVerificationCallback) + completion { + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken, + NSError *_Nullable error) { + if (error) { + callInMainThreadWithError(completion, error); + return; + } + FIRAuthRequestConfiguration *configuration = self->_auth.requestConfiguration; + FIRGetOOBConfirmationCodeRequest *request = + [FIRGetOOBConfirmationCodeRequest verifyEmailRequestWithAccessToken:accessToken + actionCodeSettings:actionCodeSettings + requestConfiguration:configuration]; + [FIRAuthBackend getOOBConfirmationCode:request + callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable + response, + NSError *_Nullable error) { + [self signOutIfTokenIsInvalidWithError:error]; + callInMainThreadWithError(completion, error); + }]; + }]; + }); +} + +- (void)deleteWithCompletion:(nullable FIRUserProfileChangeCallback)completion { + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken, + NSError *_Nullable error) { + if (error) { + callInMainThreadWithError(completion, error); + return; + } + FIRDeleteAccountRequest *deleteUserRequest = + [[FIRDeleteAccountRequest alloc] initWitLocalID:self->_userID + accessToken:accessToken + requestConfiguration:self->_auth.requestConfiguration]; + [FIRAuthBackend deleteAccount:deleteUserRequest callback:^(NSError *_Nullable error) { + if (error) { + callInMainThreadWithError(completion, error); + return; + } + if (![self->_auth signOutByForceWithUserID:self->_userID error:&error]) { + callInMainThreadWithError(completion, error); + return; + } + callInMainThreadWithError(completion, error); + }]; + }]; + }); +} + +/** @fn signOutIfTokenIsInvalidWithError: + @brief Signs out this user if the user or the token is invalid. + @param error The error from the server. + */ +- (void)signOutIfTokenIsInvalidWithError:(nullable NSError *)error { + NSInteger errorCode = error.code; + if (errorCode == FIRAuthErrorCodeUserNotFound || + errorCode == FIRAuthErrorCodeUserDisabled || + errorCode == FIRAuthErrorCodeInvalidUserToken || + errorCode == FIRAuthErrorCodeUserTokenExpired) { + FIRLogNotice(kFIRLoggerAuth, @"I-AUT000016", + @"Invalid user token detected, user is automatically signed out."); + [_auth signOutByForceWithUserID:_userID error:NULL]; + } +} + +@end + +@implementation FIRUserProfileChangeRequest { + /** @var _user + @brief The user associated with the change request. + */ + FIRUser *_user; + + /** @var _displayName + @brief The display name value to set if @c _displayNameSet is YES. + */ + NSString *_displayName; + + /** @var _displayNameSet + @brief Indicates the display name should be part of the change request. + */ + BOOL _displayNameSet; + + /** @var _photoURL + @brief The photo URL value to set if @c _displayNameSet is YES. + */ + NSURL *_photoURL; + + /** @var _photoURLSet + @brief Indicates the photo URL should be part of the change request. + */ + BOOL _photoURLSet; + + /** @var _consumed + @brief Indicates the @c commitChangesWithCallback: method has already been invoked. + */ + BOOL _consumed; +} + +- (nullable instancetype)initWithUser:(FIRUser *)user { + self = [super init]; + if (self) { + _user = user; + } + return self; +} + +- (nullable NSString *)displayName { + return _displayName; +} + +- (void)setDisplayName:(nullable NSString *)displayName { + dispatch_sync(FIRAuthGlobalWorkQueue(), ^{ + if (self->_consumed) { + [NSException raise:NSInternalInconsistencyException + format:@"%@", + @"Invalid call to setDisplayName: after commitChangesWithCallback:."]; + return; + } + self->_displayNameSet = YES; + self->_displayName = [displayName copy]; + }); +} + +- (nullable NSURL *)photoURL { + return _photoURL; +} + +- (void)setPhotoURL:(nullable NSURL *)photoURL { + dispatch_sync(FIRAuthGlobalWorkQueue(), ^{ + if (self->_consumed) { + [NSException raise:NSInternalInconsistencyException + format:@"%@", + @"Invalid call to setPhotoURL: after commitChangesWithCallback:."]; + return; + } + self->_photoURLSet = YES; + self->_photoURL = [photoURL copy]; + }); +} + +/** @fn hasUpdates + @brief Indicates at least one field has a value which needs to be committed. + */ +- (BOOL)hasUpdates { + return _displayNameSet || _photoURLSet; +} + +- (void)commitChangesWithCompletion:(nullable FIRUserProfileChangeCallback)completion { + dispatch_sync(FIRAuthGlobalWorkQueue(), ^{ + if (self->_consumed) { + [NSException raise:NSInternalInconsistencyException + format:@"%@", + @"commitChangesWithCallback: should only be called once."]; + return; + } + self->_consumed = YES; + // Return fast if there is nothing to update: + if (![self hasUpdates]) { + callInMainThreadWithError(completion, nil); + return; + } + NSString *displayName = [self->_displayName copy]; + BOOL displayNameWasSet = self->_displayNameSet; + NSURL *photoURL = [self->_photoURL copy]; + BOOL photoURLWasSet = self->_photoURLSet; + [self->_user executeUserUpdateWithChanges:^(FIRGetAccountInfoResponseUser *user, + FIRSetAccountInfoRequest *request) { + if (photoURLWasSet) { + request.photoURL = photoURL; + } + if (displayNameWasSet) { + request.displayName = displayName; + } + } + callback:^(NSError *_Nullable error) { + if (error) { + callInMainThreadWithError(completion, error); + return; + } + if (displayNameWasSet) { + [self->_user setDisplayName:displayName]; + } + if (photoURLWasSet) { + [self->_user setPhotoURL:photoURL]; + } + if (![self->_user updateKeychain:&error]) { + callInMainThreadWithError(completion, error); + return; + } + callInMainThreadWithError(completion, nil); + }]; + }); +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/User/FIRUserInfoImpl.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/User/FIRUserInfoImpl.h new file mode 100644 index 0000000..0022a68 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/User/FIRUserInfoImpl.h @@ -0,0 +1,61 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRUserInfo.h" + +@class FIRGetAccountInfoResponseProviderUserInfo; + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRUserInfoImpl : NSObject + +/** @fn userInfoWithGetAccountInfoResponseProviderUserInfo: + @brief A convenience factory method for constructing a @c FIRUserInfo instance from data + returned by the getAccountInfo endpoint. + @param providerUserInfo Data returned by the getAccountInfo endpoint. + @return A new instance of @c FIRUserInfo using data from the getAccountInfo endpoint. + */ ++ (nullable instancetype)userInfoWithGetAccountInfoResponseProviderUserInfo: + (FIRGetAccountInfoResponseProviderUserInfo *)providerUserInfo; + +/** @fn init + @brief This class should not be initialized manually. + @see FIRUser.providerData + */ +- (instancetype)init NS_UNAVAILABLE; + +/** @fn initWithProviderID:userID:displayName:photoURL:email: + @brief Designated initializer. + @param providerID The provider identifier. + @param userID The unique user ID for the user (the value of the @c uid field in the token.) + @param displayName The name of the user. + @param photoURL The URL of the user's profile photo. + @param email The user's email address. + @param phoneNumber The user's phone number. + */ +- (nullable instancetype)initWithProviderID:(NSString *)providerID + userID:(NSString *)userID + displayName:(nullable NSString *)displayName + photoURL:(nullable NSURL *)photoURL + email:(nullable NSString *)email + phoneNumber:(nullable NSString *)phoneNumber + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/User/FIRUserInfoImpl.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/User/FIRUserInfoImpl.m new file mode 100644 index 0000000..2e804ab --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/User/FIRUserInfoImpl.m @@ -0,0 +1,131 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRUserInfoImpl.h" + +#import "FIRGetAccountInfoResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kProviderIDCodingKey + @brief The key used to encode the providerID property for NSSecureCoding. + */ +static NSString *const kProviderIDCodingKey = @"providerID"; + +/** @var kUserIDCodingKey + @brief The key used to encode the userID property for NSSecureCoding. + */ +static NSString *const kUserIDCodingKey = @"userID"; + +/** @var kDisplayNameCodingKey + @brief The key used to encode the displayName property for NSSecureCoding. + */ +static NSString *const kDisplayNameCodingKey = @"displayName"; + +/** @var kProfileURLCodingKey + @brief The key used to encode the profileURL property for NSSecureCoding. + */ +static NSString *const kProfileURLCodingKey = @"profileURL"; + +/** @var kPhotoURLCodingKey + @brief The key used to encode the photoURL property for NSSecureCoding. + */ +static NSString *const kPhotoURLCodingKey = @"photoURL"; + +/** @var kEmailCodingKey + @brief The key used to encode the email property for NSSecureCoding. + */ +static NSString *const kEmailCodingKey = @"email"; + +/** @var kPhoneNumberCodingKey + @brief The key used to encode the phoneNumber property for NSSecureCoding. + */ +static NSString *const kPhoneNumberCodingKey = @"phoneNumber"; + +@implementation FIRUserInfoImpl + +@synthesize providerID = _providerID; +@synthesize uid = _userID; +@synthesize displayName = _displayName; +@synthesize photoURL = _photoURL; +@synthesize email = _email; +@synthesize phoneNumber = _phoneNumber; + ++ (nullable instancetype)userInfoWithGetAccountInfoResponseProviderUserInfo: + (FIRGetAccountInfoResponseProviderUserInfo *)providerUserInfo { + return [[self alloc] initWithProviderID:providerUserInfo.providerID + userID:providerUserInfo.federatedID + displayName:providerUserInfo.displayName + photoURL:providerUserInfo.photoURL + email:providerUserInfo.email + phoneNumber:providerUserInfo.phoneNumber]; +} + +- (nullable instancetype)initWithProviderID:(NSString *)providerID + userID:(NSString *)userID + displayName:(nullable NSString *)displayName + photoURL:(nullable NSURL *)photoURL + email:(nullable NSString *)email + phoneNumber:(nullable NSString *)phoneNumber { + self = [super init]; + if (self) { + _providerID = [providerID copy]; + _userID = [userID copy]; + _displayName = [displayName copy]; + _photoURL = [photoURL copy]; + _email = [email copy]; + _phoneNumber = [phoneNumber copy]; + } + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + NSString *providerID = + [aDecoder decodeObjectOfClass:[NSString class] forKey:kProviderIDCodingKey]; + NSString *userID = [aDecoder decodeObjectOfClass:[NSString class] forKey:kUserIDCodingKey]; + NSString *displayName = + [aDecoder decodeObjectOfClass:[NSString class] forKey:kDisplayNameCodingKey]; + NSURL *photoURL = [aDecoder decodeObjectOfClass:[NSURL class] forKey:kPhotoURLCodingKey]; + NSString *email = [aDecoder decodeObjectOfClass:[NSString class] forKey:kEmailCodingKey]; + NSString *phoneNumber = + [aDecoder decodeObjectOfClass:[NSString class] forKey:kPhoneNumberCodingKey]; + + return [self initWithProviderID:providerID + userID:userID + displayName:displayName + photoURL:photoURL + email:email + phoneNumber:phoneNumber]; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_providerID forKey:kProviderIDCodingKey]; + [aCoder encodeObject:_userID forKey:kUserIDCodingKey]; + [aCoder encodeObject:_displayName forKey:kDisplayNameCodingKey]; + [aCoder encodeObject:_photoURL forKey:kPhotoURLCodingKey]; + [aCoder encodeObject:_email forKey:kEmailCodingKey]; + [aCoder encodeObject:_phoneNumber forKey:kPhoneNumberCodingKey]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/User/FIRUserMetadata.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/User/FIRUserMetadata.m new file mode 100644 index 0000000..8fe6509 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/User/FIRUserMetadata.m @@ -0,0 +1,64 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRUserMetadata_Internal.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRUserMetadata + +/** @var kCreationDateCodingKey + @brief The key used to encode the creationDate property for NSSecureCoding. + */ +static NSString *const kCreationDateCodingKey = @"creationDate"; + +/** @var kLastSignInDateCodingKey + @brief The key used to encode the lastSignInDate property for NSSecureCoding. + */ +static NSString *const kLastSignInDateCodingKey = @"lastSignInDate"; + +- (instancetype)initWithCreationDate:(nullable NSDate *)creationDate + lastSignInDate:(nullable NSDate *)lastSignInDate { + self = [super init]; + if (self) { + _creationDate = [creationDate copy]; + _lastSignInDate = [lastSignInDate copy]; + } + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + NSDate *creationDate = + [aDecoder decodeObjectOfClass:[NSDate class] forKey:kCreationDateCodingKey]; + NSDate *lastSignInDate = + [aDecoder decodeObjectOfClass:[NSDate class] forKey:kLastSignInDateCodingKey]; + return [self initWithCreationDate:creationDate lastSignInDate:lastSignInDate]; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_creationDate forKey:kCreationDateCodingKey]; + [aCoder encodeObject:_lastSignInDate forKey:kLastSignInDateCodingKey]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/User/FIRUserMetadata_Internal.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/User/FIRUserMetadata_Internal.h new file mode 100644 index 0000000..0b01a03 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/User/FIRUserMetadata_Internal.h @@ -0,0 +1,38 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRUserMetadata.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @extension FIRUserMetadata + @brief An internal class used to expose internal methods of FIRUserMetadata. + */ +@interface FIRUserMetadata () + +/** @fn initWithCreationDate:lastSignInDate: + @brief Designated initializer. + @param creationDate The creation date of the corresponding user. + @param lastSignInDate The date of the last recorded sign-in of the corresponding user. + */ +- (instancetype)initWithCreationDate:(nullable NSDate *)creationDate + lastSignInDate:(nullable NSDate *)lastSignInDate NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/User/FIRUser_Internal.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/User/FIRUser_Internal.h new file mode 100644 index 0000000..a049dde --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/User/FIRUser_Internal.h @@ -0,0 +1,108 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRUser.h" + +@class FIRAuth; +@class FIRAuthRequestConfiguration; + +NS_ASSUME_NONNULL_BEGIN + +/** @typedef FIRRetrieveUserCallback + @brief The type of block that is invoked when the construction of a user succeeds or fails. + @param user The user that was constructed, or nil if user construction failed. + @param error The error which occurred, or nil if the request was successful. + */ +typedef void(^FIRRetrieveUserCallback)(FIRUser *_Nullable user, NSError *_Nullable error); + +/** @typedef FIRVerifyBeforeUpdateEmailCallback + @brief The type of block called when a request to verify before update email has finished. + @param error Optionally; the error which occurred - or nil if the request was successful. + */ +typedef void (^FIRVerifyBeforeUpdateEmailCallback)(NSError *_Nullable error); + +@interface FIRUser () + +/** @property rawAccessToken + @brief The cached access token. + @remarks This method is specifically for providing the access token to internal clients during + deserialization and sign-in events, and should not be used to retrieve the access token by + anyone else. + */ +@property(nonatomic, copy, readonly) NSString *rawAccessToken; + +/** @property auth + @brief A weak reference to a FIRAuth instance associated with this instance. + */ +@property(nonatomic, weak) FIRAuth *auth; + +/** @property auth + @brief A strong reference to a requestConfiguration instance associated with this user instance. + */ +@property(nonatomic, strong) FIRAuthRequestConfiguration *requestConfiguration; + +/** @var accessTokenExpirationDate + @brief The expiration date of the cached access token. + */ +@property(nonatomic, copy, readonly) NSDate *accessTokenExpirationDate; + +/** @fn retrieveUserWithAuth:accessToken:accessTokenExpirationDate:refreshToken:callback: + @brief Constructs a user with Secure Token Service tokens, and obtains user details from the + getAccountInfo endpoint. + @param auth The associated FIRAuth instance. + @param accessToken The Secure Token Service access token. + @param accessTokenExpirationDate The approximate expiration date of the access token. + @param refreshToken The Secure Token Service refresh token. + @param anonymous Whether or not the user is anonymous. + @param callback A block which is invoked when the construction succeeds or fails. Invoked + asynchronously on the auth global work queue in the future. + */ ++ (void)retrieveUserWithAuth:(FIRAuth *)auth + accessToken:(nullable NSString *)accessToken + accessTokenExpirationDate:(nullable NSDate *)accessTokenExpirationDate + refreshToken:(nullable NSString *)refreshToken + anonymous:(BOOL)anonymous + callback:(FIRRetrieveUserCallback)callback; + +/** @fn internalGetTokenForcingRefresh:callback: + @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired. + @param forceRefresh Forces a token refresh. Useful if the token becomes invalid for some reason + other than an expiration. + @param callback The block to invoke when the token is available. Invoked asynchronously on the + global work thread in the future. + */ +- (void)internalGetTokenForcingRefresh:(BOOL)forceRefresh + callback:(nonnull FIRAuthTokenCallback)callback; + + +/** @fn internalVerifyBeforeUpdateEmailWithNewEmail:actionCodeSettings:callback: + @brief Sends a verification email to newEmail. Upon redemption of the link in the email, + this user's email will be changed to newEmail and that email will be marked verified. + @param newEmail the user's new email. + @param actionCodeSettings the optional FIRActionCodeSettings object to allow linking back + to your app in the email. + @param completion The block to invoke when the call succeeds or fails. Invoked asynchronously on + the global work thread in the future. + + */ +- (void)internalVerifyBeforeUpdateEmailWithNewEmail:(NSString *)newEmail + actionCodeSettings:(nullable FIRActionCodeSettings *)actionCodeSettings + completion:(FIRVerifyBeforeUpdateEmailCallback)completion; + + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthDefaultUIDelegate.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthDefaultUIDelegate.h new file mode 100644 index 0000000..a2bb2c4 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthDefaultUIDelegate.h @@ -0,0 +1,48 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#if !TARGET_OS_OSX + +#import + +#import "FIRAuthUIDelegate.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRAuthDefaultUIDelegate + @brief Class responsible for providing a default FIRAuthUIDelegte. + @remarks This class should be used in the case that a UIDelegate was expected and necessary to + continue a given flow, but none was provided. + */ +@interface FIRAuthDefaultUIDelegate : NSObject + +/** @fn defaultUIDelegate + @brief Unavailable. Please use @c +defaultUIDelegate: + */ +- (instancetype)init NS_UNAVAILABLE; + +/** @fn defaultUIDelegate + @brief Returns a default FIRAuthUIDelegate object. + @return The default FIRAuthUIDelegate object. + */ ++ (id)defaultUIDelegate; + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthDefaultUIDelegate.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthDefaultUIDelegate.m new file mode 100644 index 0000000..f0447fe --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthDefaultUIDelegate.m @@ -0,0 +1,121 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#if !TARGET_OS_OSX + +#import "FIRAuthDefaultUIDelegate.h" + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRAuthDefaultUIDelegate () + +/** @fn initWithViewController: + @brief Initializes the instance with a view controller. + @param viewController The view controller as the presenting view controller in @c + FIRAuthUIDelegate. + @return The initialized instance. + */ +- (instancetype)initWithViewController:(nullable UIViewController *)viewController NS_DESIGNATED_INITIALIZER; + +@end + +@implementation FIRAuthDefaultUIDelegate { + /** @var _viewController + @brief The presenting view controller. + */ + UIViewController *_viewController; +} + +- (instancetype)initWithViewController:(nullable UIViewController *)viewController { + self = [super init]; + if (self) { + _viewController = viewController; + } + return self; +} + +- (void)presentViewController:(UIViewController *)viewControllerToPresent + animated:(BOOL)flag + completion:(nullable void (^)(void))completion { + [_viewController presentViewController:viewControllerToPresent + animated:flag + completion:completion]; +} + +- (void)dismissViewControllerAnimated:(BOOL)flag completion:(nullable void (^)(void))completion { + [_viewController dismissViewControllerAnimated:flag completion:completion]; +} + ++ (id)defaultUIDelegate { + // iOS App extensions should not call [UIApplication sharedApplication], even if UIApplication + // responds to it. + static Class applicationClass = nil; + if (![GULAppEnvironmentUtil isAppExtension]) { + Class cls = NSClassFromString(@"UIApplication"); + if (cls && [cls respondsToSelector:NSSelectorFromString(@"sharedApplication")]) { + applicationClass = cls; + } + } + + UIViewController *topViewController; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 + if (@available(iOS 13.0, tvOS 13.0, *)) { + UIApplication *application = [applicationClass sharedApplication]; + NSSet * connectedScenes = application.connectedScenes; + for (UIScene *scene in connectedScenes) { + if ([scene isKindOfClass:[UIWindowScene class]]) { + UIWindowScene *windowScene = (UIWindowScene *)scene; + for (UIWindow *window in windowScene.windows) { + if (window.isKeyWindow) { + topViewController = window.rootViewController; + } + } + } + } + } else { + UIApplication *application = [applicationClass sharedApplication]; + topViewController = application.keyWindow.rootViewController; + } +#else + UIApplication *application = [applicationClass sharedApplication]; + topViewController = application.keyWindow.rootViewController; +#endif + + while (true){ + if (topViewController.presentedViewController) { + topViewController = topViewController.presentedViewController; + } else if ([topViewController isKindOfClass:[UINavigationController class]]) { + UINavigationController *nav = (UINavigationController *)topViewController; + topViewController = nav.topViewController; + } else if ([topViewController isKindOfClass:[UITabBarController class]]) { + UITabBarController *tab = (UITabBarController *)topViewController; + topViewController = tab.selectedViewController; + } else { + break; + } + } + return [[FIRAuthDefaultUIDelegate alloc] initWithViewController:topViewController]; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthErrorUtils.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthErrorUtils.h new file mode 100644 index 0000000..31bbf5b --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthErrorUtils.h @@ -0,0 +1,568 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FIRAuthCredential; + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRAuthErrorUtils + @brief Utility class used to construct @c NSError instances. + */ +@interface FIRAuthErrorUtils : NSObject + +/** @fn RPCRequestEncodingErrorWithUnderlyingError + @brief Constructs an @c NSError with the @c FIRAuthInternalErrorCodeRPCRequestEncodingError + code and a populated @c NSUnderlyingErrorKey in the @c NSError.userInfo dictionary. + @param underlyingError The value of the @c NSUnderlyingErrorKey key. + @remarks This error is used when an @c FIRAuthRPCRequest.unencodedHTTPRequestBodyWithError: + invocation returns an error. The error returned is wrapped in this internal error code. + */ ++ (NSError *)RPCRequestEncodingErrorWithUnderlyingError:(NSError *)underlyingError; + +/** @fn JSONSerializationErrorForUnencodableType + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeJSONSerializationError code. + @remarks This error is used when an @c NSJSONSerialization.isValidJSONObject: check fails, not + for when an error is returned from @c NSJSONSerialization.dataWithJSONObject:options:error:. + */ ++ (NSError *)JSONSerializationErrorForUnencodableType; + +/** @fn JSONSerializationErrorWithUnderlyingError: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeJSONSerializationError code, and the + @c underlyingError as the @c NSUnderlyingErrorKey value in the @c NSError.userInfo + dictionary. + @param underlyingError The value of the @c NSUnderlyingErrorKey key. + @remarks This error is used when an invocation of + @c NSJSONSerialization.dataWithJSONObject:options:error: returns an error. + */ ++ (NSError *)JSONSerializationErrorWithUnderlyingError:(NSError *)underlyingError; + +/** @fn networkErrorWithUnderlyingError: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeNetworkError code, and the + @c underlyingError as the @c NSUnderlyingErrorKey value in the @c NSError.userInfo + dictionary. + @param underlyingError The value of the @c NSUnderlyingErrorKey key. Should be the error from + GTM. + @remarks This error is used when a network request results in an error, and no body data was + returned. + */ ++ (NSError *)networkErrorWithUnderlyingError:(NSError *)underlyingError; + +/** @fn unexpectedErrorResponseWithUnderlyingError: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeNetworkError code, and the + @c underlyingError as the @c NSUnderlyingErrorKey value. + @param data The value of the @c FIRAuthErrorUserInfoDataKey key in the @c NSError.userInfo + dictionary. + @param underlyingError The value of the @c NSUnderlyingErrorKey key in the @c NSError.userInfo + dictionary. + @remarks This error is used when a network request results in an error, and unserializable body + data was returned. + */ ++ (NSError *)unexpectedErrorResponseWithData:(NSData *)data + underlyingError:(NSError *)underlyingError; + +/** @fn unexpectedErrorResponseWithDeserializedResponse: + @brief Constructs an @c NSError with the @c FIRAuthInternalErrorCodeUnexpectedErrorResponse + code, and a populated @c FIRAuthErrorUserInfoDeserializedResponseKey key in the + @c NSError.userInfo dictionary. + @param deserializedResponse The value of the @c FIRAuthErrorUserInfoDeserializedResponseKey key. + @remarks This error is used when a network request results in an error, and the body data was + deserializable as JSON, but couldn't be decoded as an error. + */ ++ (NSError *)unexpectedErrorResponseWithDeserializedResponse:(id)deserializedResponse; + +/** @fn malformedJWTErrorWithToken:underlyingError: + @brief Constructs an @c NSError with the code set to @c FIRAuthErrorCodeMalformedJWT and + populates the userInfo dictionary with an error message, the bad token, and an underlying + error that may have occurred when parsing. + @param token The token that failed to parse. + @param underlyingError The error that caused this error. If this parameter is nil, the + NSUnderlyingErrorKey value will not be set. + @remarks This error is returned when JWT parsing fails. + @returns An @c FIRAuthErrorCodeMalformedJWT error wrapping an underlying error, if available. + */ ++ (NSError *)malformedJWTErrorWithToken:(NSString *)token + underlyingError:(NSError *_Nullable)underlyingError; + +/** @fn unexpectedResponseWithData:underlyingError: + @brief Constructs an @c NSError with the @c FIRAuthInternalErrorCodeUnexpectedResponse + code, and a populated @c FIRAuthErrorUserInfoDataKey key in the @c NSError.userInfo + dictionary. + @param data The value of the @c FIRAuthErrorUserInfoDataKey key in the @c NSError.userInfo + dictionary. + @param underlyingError The value of the @c NSUnderlyingErrorKey key in the @c NSError.userInfo + dictionary. + @remarks This error is used when a network request is apparently successful, but the body data + couldn't be deserialized as JSON. + */ ++ (NSError *)unexpectedResponseWithData:(NSData *)data + underlyingError:(NSError *)underlyingError;; + +/** @fn unexpectedResponseWithDeserializedResponse: + @brief Constructs an @c NSError with the @c FIRAuthInternalErrorCodeUnexpectedResponse + code, and a populated @c FIRAuthErrorUserInfoDeserializedResponseKey key in the + @c NSError.userInfo dictionary. + @param deserializedResponse The value of the @c FIRAuthErrorUserInfoDeserializedResponseKey key. + @remarks This error is used when a network request is apparently successful, the body data was + successfully deserialized as JSON, but the JSON wasn't a dictionary. + */ ++ (NSError *)unexpectedResponseWithDeserializedResponse:(id)deserializedResponse; + +/** @fn unexpectedResponseWithDeserializedResponse:underlyingError: + @brief Constructs an @c NSError with the @c FIRAuthInternalErrorCodeUnexpectedResponse + code, and populated @c FIRAuthErrorUserInfoDeserializedResponseKey and + @c NSUnderlyingErrorKey keys in the @c NSError.userInfo dictionary. + @param deserializedResponse The value of the @c FIRAuthErrorUserInfoDeserializedResponseKey key. + @param underlyingError The value of the @c NSUnderlyingErrorKey key. + @remarks This error is used when a network request was apparently successful, the body data was + successfully deserialized as JSON, but the data type of the response was unexpected. + */ ++ (NSError *)unexpectedResponseWithDeserializedResponse:(nullable id)deserializedResponse + underlyingError:(NSError *)underlyingError; + +/** @fn RPCResponseDecodingErrorWithDeserializedResponse:underlyingError: + @brief Constructs an @c NSError with the @c FIRAuthInternalErrorCodeRPCResponseDecodingError + code, and populated @c FIRAuthErrorUserInfoDeserializedResponseKey and + @c NSUnderlyingErrorKey keys in the @c NSError.userInfo dictionary. + @param deserializedResponse The value of the @c FIRAuthErrorUserInfoDeserializedResponseKey key. + @param underlyingError The value of the @c NSUnderlyingErrorKey key. + @remarks This error is used when an invocation of @c FIRAuthRPCResponse.setWithDictionary:error: + resulted in an error. + */ ++ (NSError *)RPCResponseDecodingErrorWithDeserializedResponse:(id)deserializedResponse + underlyingError:(NSError *)underlyingError; + +/** @fn emailAlreadyInUseErrorWithEmail: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeEmailExists code. + @param email The email address that is already in use. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)emailAlreadyInUseErrorWithEmail:(nullable NSString *)email; + +/** @fn userDisabledErrorWithMessageWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeUserDisabled code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)userDisabledErrorWithMessage:(nullable NSString *)message; + +/** @fn wrongPasswordErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeWrongPassword code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)wrongPasswordErrorWithMessage:(nullable NSString *)message; + +/** @fn tooManyRequestsErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeTooManyRequests Code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)tooManyRequestsErrorWithMessage:(nullable NSString *)message; + +/** @fn invalidCustomTokenErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeInvalidCustomToken code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)invalidCustomTokenErrorWithMessage:(nullable NSString *)message; + +/** @fn customTokenMistmatchErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeCustomTokenMismatch code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)customTokenMistmatchErrorWithMessage:(nullable NSString *)message; + +/** @fn invalidCredentialErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeInvalidCredential code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)invalidCredentialErrorWithMessage:(nullable NSString *)message; + +/** @fn requiresRecentLoginError + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeRequiresRecentLogin code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)requiresRecentLoginErrorWithMessage:(nullable NSString *)message; + +/** @fn invalidUserTokenErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeInvalidUserToken code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)invalidUserTokenErrorWithMessage:(nullable NSString *)message; + +/** @fn invalidEmailErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeInvalidEmail code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)invalidEmailErrorWithMessage:(nullable NSString *)message; + +/** @fn accountExistsWithDifferentCredentialErrorWithEmail: + @brief Constructs an @c NSError with the @c FIRAuthErrorAccountExistsWithDifferentCredential + code. + @param email The email address that is already associated with an existing account + @param updatedCredential The updated credential for the existing account + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)accountExistsWithDifferentCredentialErrorWithEmail:(nullable NSString *)email + updatedCredential:(nullable FIRAuthCredential *)updatedCredential; + +/** @fn providerAlreadyLinkedErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeProviderAlreadyLinked code. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)providerAlreadyLinkedError; + +/** @fn noSuchProviderError + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeNoSuchProvider code. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)noSuchProviderError; + +/** @fn userTokenExpiredErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeUserTokenExpired code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)userTokenExpiredErrorWithMessage:(nullable NSString *)message; + +/** @fn userNotFoundErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeUserNotFound code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)userNotFoundErrorWithMessage:(nullable NSString *)message; + +/** @fn invalidLocalAPIKeyErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeInvalidAPIKey code. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)invalidAPIKeyError; + +/** @fn userMismatchError + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeUserMismatch code. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)userMismatchError; + +/** @fn credentialAlreadyInUseErrorWithMessage:email: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeCredentialAlreadyInUse code. + @param message Error message from the backend, if any. + @param credential Auth credential to be added to the Error User Info dictionary. + @param email Email to be added to the Error User Info dictionary. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)credentialAlreadyInUseErrorWithMessage:(nullable NSString *)message + credential:(nullable FIRAuthCredential *)credential + email:(nullable NSString *)email; +/** @fn operationNotAllowedErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeOperationNotAllowed code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)operationNotAllowedErrorWithMessage:(nullable NSString *)message; + +/** @fn weakPasswordErrorWithServerResponseReason: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeWeakPassword code. + @param serverResponseReason A more detailed explanation string from server response. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)weakPasswordErrorWithServerResponseReason:(nullable NSString *)serverResponseReason; + +/** @fn appNotAuthorizedError + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeAppNotAuthorized code. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)appNotAuthorizedError; + +/** @fn expiredActionCodeErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeExpiredActionCode code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)expiredActionCodeErrorWithMessage:(nullable NSString *)message; + +/** @fn invalidActionCodeErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeInvalidActionCode code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)invalidActionCodeErrorWithMessage:(nullable NSString *)message; + +/** @fn invalidMessagePayloadError: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeInvalidMessagePayload code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)invalidMessagePayloadErrorWithMessage:(nullable NSString *)message; + +/** @fn invalidSenderErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeInvalidSender code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)invalidSenderErrorWithMessage:(nullable NSString *)message; + +/** @fn invalidRecipientEmailError: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeInvalidRecipientEmail code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)invalidRecipientEmailErrorWithMessage:(nullable NSString *)message; + +/** @fn missingIosBundleIDErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeMissingIosBundleID code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)missingIosBundleIDErrorWithMessage:(nullable NSString *)message; + +/** @fn missingAndroidPackageNameErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeMissingAndroidPackageName code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)missingAndroidPackageNameErrorWithMessage:(nullable NSString *)message; + +/** @fn unauthorizedDomainErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeUnauthorizedDomain code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)unauthorizedDomainErrorWithMessage:(nullable NSString *)message; + +/** @fn invalidContinueURIErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeInvalidContinueURI code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)invalidContinueURIErrorWithMessage:(nullable NSString *)message; + +/** @fn missingContinueURIErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeMissingContinueURI code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)missingContinueURIErrorWithMessage:(nullable NSString *)message; + +/** @fn missingEmailErrorWithMessage + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeMissingEmail code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)missingEmailErrorWithMessage:(nullable NSString *)message; + +/** @fn missingPhoneNumberErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeMissingPhoneNumber code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)missingPhoneNumberErrorWithMessage:(nullable NSString *)message; + +/** @fn invalidPhoneNumberErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeInvalidPhoneNumber code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)invalidPhoneNumberErrorWithMessage:(nullable NSString *)message; + +/** @fn missingVerificationCodeErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeMissingVerificationCode code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)missingVerificationCodeErrorWithMessage:(nullable NSString *)message; + +/** @fn invalidVerificationCodeErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeInvalidVerificationCode code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)invalidVerificationCodeErrorWithMessage:(nullable NSString *)message; + +/** @fn missingVerificationIDErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeMissingVerificationID code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)missingVerificationIDErrorWithMessage:(nullable NSString *)message; + +/** @fn invalidVerificationIDErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeInvalidVerificationID code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)invalidVerificationIDErrorWithMessage:(nullable NSString *)message; + +/** @fn sessionExpiredErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeSessionExpired code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)sessionExpiredErrorWithMessage:(nullable NSString *)message; + +/** @fn missingAppCredentialWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorMissingCredential code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)missingAppCredentialWithMessage:(nullable NSString *)message; + +/** @fn invalidAppCredentialWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorInvalidCredential code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)invalidAppCredentialWithMessage:(nullable NSString *)message; + +/** @fn quotaExceededErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeQuotaExceeded code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)quotaExceededErrorWithMessage:(nullable NSString *)message; + +/** @fn missingAppTokenErrorWithUnderlyingError + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeMissingAppToken code. + @param underlyingError The underlying error, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)missingAppTokenErrorWithUnderlyingError:(nullable NSError *)underlyingError; + +/** @fn localPlayerNotAuthenticatedError + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeLocalPlayerNotAuthenticated code. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)localPlayerNotAuthenticatedError; + +/** @fn gameKitNotLinkedError + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeGameKitNotLinked code. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)gameKitNotLinkedError; + +/** @fn notificationNotForwardedError + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeNotificationNotForwarded code. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)notificationNotForwardedError; + +/** @fn appNotVerifiedErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeAppNotVerified code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)appNotVerifiedErrorWithMessage:(nullable NSString *)message; + +/** @fn missingClientIdentifierErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeMissingClientIdentifier code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)missingClientIdentifierErrorWithMessage:(nullable NSString *)message; + +/** @fn captchaCheckFailedErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCaptchaCheckFailed code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)captchaCheckFailedErrorWithMessage:(nullable NSString *)message; + +/** @fn webContextAlreadyPresentedErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeWebContextAlreadyPresented code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)webContextAlreadyPresentedErrorWithMessage:(nullable NSString *)message; + +/** @fn webContextCancelledErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeWebContextCancelled code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)webContextCancelledErrorWithMessage:(nullable NSString *)message; + +/** @fn appVerificationUserInteractionFailureWithReason: + @brief Constructs an @c NSError with the @c + FIRAuthErrorCodeAppVerificationUserInteractionFailure code. + @param reason Reason for error, returned via URL response. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)appVerificationUserInteractionFailureWithReason:(NSString *)reason; + +/** @fn webSignInUserInteractionFailureWithReason: + @brief Constructs an @c NSError with the @c + FIRAuthErrorCodeWebSignInUserInteractionFailure code. + @param reason Reason for error, returned via URL response. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)webSignInUserInteractionFailureWithReason:(nullable NSString *)reason; + +/** @fn URLResponseErrorWithCode:message: + @brief Constructs an @c NSError with the code and message provided. + @param message Error message from the backend, if any. + @return The nullable NSError instance associated with the given error message, if one is found. + */ ++ (nullable NSError *)URLResponseErrorWithCode:(NSString *)code message:(nullable NSString *)message; + +/** @fn nullUserErrorWithMessage: + @brief Constructs an @c NSError with the code and message provided. + @param message Error message from the backend, if any. + @return The nullable NSError instance associated with the given error message, if one is found. + */ ++ (NSError *)nullUserErrorWithMessage:(nullable NSString *)message; + +/** @fn invalidProviderIDErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeInvalidProviderID code. + @param message Error message from the backend, if any. + @remarks This error indicates that the provider id given for the web operation is invalid. + */ ++ (NSError *)invalidProviderIDErrorWithMessage:(nullable NSString *)message; + +/** @fn invalidDynamicLinkDomainErrorWithMessage: + @brief Constructs an @c NSError with the code and message provided. + @param message Error message from the backend, if any. + @return The nullable NSError instance associated with the given error message, if one is found. + */ ++ (NSError *)invalidDynamicLinkDomainErrorWithMessage:(nullable NSString *)message; + +/** @fn keychainErrorWithFunction:status: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeKeychainError code. + @param keychainFunction The keychain function which was invoked and yielded an unexpected + response. The @c NSLocalizedFailureReasonErrorKey field in the @c NSError.userInfo + dictionary will contain a string partially comprised of this value. + @param status The response status from the invoked keychain function. The + @c NSLocalizedFailureReasonErrorKey field in the @c NSError.userInfo dictionary will contain + a string partially comprised of this value. + */ ++ (NSError *)keychainErrorWithFunction:(NSString *)keychainFunction status:(OSStatus)status; + +/** @fn missingOrInvalidNonceErrorWithMessage: + @brief Constructs an @c NSError with the code and message provided. + @param message Error message from the backend, if any. + @return The nullable NSError instance associated with the given error message, if one is found. +*/ ++ (NSError *)missingOrInvalidNonceErrorWithMessage:(nullable NSString *)message; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthErrorUtils.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthErrorUtils.m new file mode 100644 index 0000000..146c14f --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthErrorUtils.m @@ -0,0 +1,1205 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRAuthErrorUtils.h" + +#import "FIRAuthCredential.h" +#import "FIRAuthInternalErrors.h" + +NS_ASSUME_NONNULL_BEGIN + +NSString *const FIRAuthErrorDomain = @"FIRAuthErrorDomain"; + +NSString *const FIRAuthInternalErrorDomain = @"FIRAuthInternalErrorDomain"; + +NSString *const FIRAuthErrorUserInfoDeserializedResponseKey = + @"FIRAuthErrorUserInfoDeserializedResponseKey"; + +NSString *const FIRAuthErrorUserInfoDataKey = @"FIRAuthErrorUserInfoDataKey"; + +NSString *const FIRAuthErrorUserInfoEmailKey = @"FIRAuthErrorUserInfoEmailKey"; + +NSString *const FIRAuthErrorUserInfoUpdatedCredentialKey = + @"FIRAuthErrorUserInfoUpdatedCredentialKey"; + +NSString *const FIRAuthErrorUserInfoNameKey = @"FIRAuthErrorUserInfoNameKey"; + +/** @var kServerErrorDetailMarker + @brief This marker indicates that the server error message contains a detail error message which + should be used instead of the hardcoded client error message. + */ +static NSString *const kServerErrorDetailMarker = @" : "; + +#pragma mark - URL response error codes + +/** @var kURLResponseErrorCodeInvalidClientID + @brief Error code that indicates that the client ID provided was invalid. + */ +static NSString *const kURLResponseErrorCodeInvalidClientID = @"auth/invalid-oauth-client-id"; + +/** @var kURLResponseErrorCodeNetworkRequestFailed + @brief Error code that indicates that a network request within the SFSafariViewController or + WKWebView failed. + */ +static NSString *const kURLResponseErrorCodeNetworkRequestFailed = @"auth/network-request-failed"; + +/** @var kURLResponseErrorCodeInternalError + @brief Error code that indicates that an internal error occurred within the + SFSafariViewController or WKWebView failed. + */ +static NSString *const kURLResponseErrorCodeInternalError = @"auth/internal-error"; + +#pragma mark - Standard Error Messages + +/** @var kFIRAuthErrorMessageInvalidCustomToken + @brief Message for @c FIRAuthErrorCodeInvalidCustomToken error code. + */ +static NSString *const kFIRAuthErrorMessageInvalidCustomToken = @"The custom token format is " + "incorrect. Please check the documentation."; + +/** @var kFIRAuthErrorMessageCustomTokenMismatch + @brief Message for @c FIRAuthErrorCodeCustomTokenMismatch error code. + */ +static NSString *const kFIRAuthErrorMessageCustomTokenMismatch = @"The custom token corresponds to " + "a different audience."; + +/** @var kFIRAuthErrorMessageInvalidEmail + @brief Message for @c FIRAuthErrorCodeInvalidEmail error code. + */ +static NSString *const kFIRAuthErrorMessageInvalidEmail = @"The email address is badly formatted."; + +/** @var kFIRAuthErrorMessageInvalidCredential + @brief Message for @c FIRAuthErrorCodeInvalidCredential error code. + */ +static NSString *const kFIRAuthErrorMessageInvalidCredential = @"The supplied auth credential is " + "malformed or has expired."; + +/** @var kFIRAuthErrorMessageUserDisabled + @brief Message for @c FIRAuthErrorCodeUserDisabled error code. + */ +static NSString *const kFIRAuthErrorMessageUserDisabled = @"The user account has been disabled by " + "an administrator."; + +/** @var kFIRAuthErrorMessageEmailAlreadyInUse + @brief Message for @c FIRAuthErrorCodeEmailAlreadyInUse error code. + */ +static NSString *const kFIRAuthErrorMessageEmailAlreadyInUse = @"The email address is already in " + "use by another account."; + +/** @var kFIRAuthErrorMessageWrongPassword + @brief Message for @c FIRAuthErrorCodeWrongPassword error code. + */ +static NSString *const kFIRAuthErrorMessageWrongPassword = @"The password is invalid or the user " + "does not have a password."; + +/** @var kFIRAuthErrorMessageTooManyRequests + @brief Message for @c FIRAuthErrorCodeTooManyRequests error code. + */ +static NSString *const kFIRAuthErrorMessageTooManyRequests = @"We have blocked all requests from " + "this device due to unusual activity. Try again later."; + +/** @var kFIRAuthErrorMessageAccountExistsWithDifferentCredential + @brief Message for @c FIRAuthErrorCodeAccountExistsWithDifferentCredential error code. + */ +static NSString *const kFIRAuthErrorMessageAccountExistsWithDifferentCredential = @"An account " + "already exists with the same email address but different sign-in credentials. Sign in using a " + "provider associated with this email address."; + +/** @var kFIRAuthErrorMessageRequiresRecentLogin + @brief Message for @c FIRAuthErrorCodeRequiresRecentLogin error code. + */ +static NSString *const kFIRAuthErrorMessageRequiresRecentLogin= @"This operation is sensitive and " + "requires recent authentication. Log in again before retrying this request."; + +/** @var kFIRAuthErrorMessageProviderAlreadyLinked + @brief Message for @c FIRAuthErrorCodeProviderAlreadyExists error code. + */ +static NSString *const kFIRAuthErrorMessageProviderAlreadyLinked = + @"[ERROR_PROVIDER_ALREADY_LINKED] - User can only be linked to one identity for the given " + "provider."; + +/** @var kFIRAuthErrorMessageNoSuchProvider + @brief Message for @c FIRAuthErrorCodeNoSuchProvider error code. + */ +static NSString *const kFIRAuthErrorMessageNoSuchProvider = @"User was not linked to an account " + "with the given provider."; + +/** @var kFIRAuthErrorMessageInvalidUserToken + @brief Message for @c FIRAuthErrorCodeInvalidUserToken error code. + */ +static NSString *const kFIRAuthErrorMessageInvalidUserToken = @"This user's credential isn't valid " + "for this project. This can happen if the user's token has been tampered with, or if the user " + "doesn’t belong to the project associated with the API key used in your request."; + +/** @var kFIRAuthErrorMessageNetworkError + @brief Message for @c FIRAuthErrorCodeNetworkError error code. + */ +static NSString *const kFIRAuthErrorMessageNetworkError = @"Network error (such as timeout, " + "interrupted connection or unreachable host) has occurred."; + +/** @var kFIRAuthErrorMessageKeychainError + @brief Message for @c FIRAuthErrorCodeKeychainError error code. + */ +static NSString *const kFIRAuthErrorMessageKeychainError = @"An error occurred when accessing the " + "keychain. The @c NSLocalizedFailureReasonErrorKey field in the @c NSError.userInfo dictionary " + "will contain more information about the error encountered"; + +/** @var kFIRAuthErrorMessageMissingClientIdentifier + @brief Message for @c FIRAuthErrorCodeMissingClientIdentifier error code. + */ +static NSString *const kFIRAuthErrorMessageMissingClientIdentifier = @"The request does not contain " + "any client identifier."; + +/** @var kFIRAuthErrorMessageUserTokenExpired + @brief Message for @c FIRAuthErrorCodeTokenExpired error code. + */ +static NSString *const kFIRAuthErrorMessageUserTokenExpired = @"The user's credential is no longer " + "valid. The user must sign in again."; + +/** @var kFIRAuthErrorMessageUserNotFound + @brief Message for @c FIRAuthErrorCodeUserNotFound error code. + */ +static NSString *const kFIRAuthErrorMessageUserNotFound = @"There is no user record corresponding " + "to this identifier. The user may have been deleted."; + +/** @var kFIRAuthErrorMessageInvalidAPIKey + @brief Message for @c FIRAuthErrorCodeInvalidAPIKey error code. + @remarks This error is not thrown by the server. + */ +static NSString *const kFIRAuthErrorMessageInvalidAPIKey = @"An invalid API Key was supplied in " + "the request."; + +/** @var kFIRAuthErrorMessageUserMismatch. + @brief Message for @c FIRAuthErrorCodeInvalidAPIKey error code. + */ +static NSString *const FIRAuthErrorMessageUserMismatch = @"The supplied credentials do not " + "correspond to the previously signed in user."; + +/** @var kFIRAuthErrorMessageCredentialAlreadyInUse + @brief Message for @c FIRAuthErrorCodeCredentialAlreadyInUse error code. + */ +static NSString *const kFIRAuthErrorMessageCredentialAlreadyInUse = @"This credential is already " + "associated with a different user account."; + +/** @var kFIRAuthErrorMessageOperationNotAllowed + @brief Message for @c FIRAuthErrorCodeOperationNotAllowed error code. + */ +static NSString *const kFIRAuthErrorMessageOperationNotAllowed = @"The given sign-in provider is " + "disabled for this Firebase project. Enable it in the Firebase console, under the sign-in " + "method tab of the Auth section."; + +/** @var kFIRAuthErrorMessageWeakPassword + @brief Message for @c FIRAuthErrorCodeWeakPassword error code. + */ +static NSString *const kFIRAuthErrorMessageWeakPassword = @"The password must be 6 characters long " + "or more."; + +/** @var kFIRAuthErrorMessageAppNotAuthorized + @brief Message for @c FIRAuthErrorCodeAppNotAuthorized error code. + */ +static NSString *const kFIRAuthErrorMessageAppNotAuthorized = @"This app is not authorized to use " + "Firebase Authentication with the provided API key. Review your key configuration in the " + "Google API console and ensure that it accepts requests from your app's bundle ID."; + +/** @var kFIRAuthErrorMessageExpiredActionCode + @brief Message for @c FIRAuthErrorCodeExpiredActionCode error code. + */ +static NSString *const kFIRAuthErrorMessageExpiredActionCode = @"The action code has expired."; + +/** @var kFIRAuthErrorMessageInvalidActionCode + @brief Message for @c FIRAuthErrorCodeInvalidActionCode error code. + */ +static NSString *const kFIRAuthErrorMessageInvalidActionCode = @"The action code is invalid. This " + "can happen if the code is malformed, expired, or has already been used."; + +/** @var kFIRAuthErrorMessageInvalidMessagePayload + @brief Message for @c FIRAuthErrorCodeInvalidMessagePayload error code. + */ +static NSString *const kFIRAuthErrorMessageInvalidMessagePayload = @"The action code is invalid. " + "This can happen if the code is malformed, expired, or has already been used."; + +/** @var kFIRAuthErrorMessageInvalidSender + @brief Message for @c FIRAuthErrorCodeInvalidSender error code. + */ +static NSString *const kFIRAuthErrorMessageInvalidSender = @"The email template corresponding to " + "this action contains invalid characters in its message. Please fix by going to the Auth email " + "templates section in the Firebase Console."; + +/** @var kFIRAuthErrorMessageInvalidRecipientEmail + @brief Message for @c FIRAuthErrorCodeInvalidRecipient error code. + */ +static NSString *const kFIRAuthErrorMessageInvalidRecipientEmail = @"The action code is invalid. " + "This can happen if the code is malformed, expired, or has already been used."; + +/** @var kFIRAuthErrorMessageMissingIosBundleID + @brief Message for @c FIRAuthErrorCodeMissingIosbundleID error code. + */ +static NSString *const kFIRAuthErrorMessageMissingIosBundleID = + @"An iOS Bundle ID must be provided if an App Store ID is provided."; + +/** @var kFIRAuthErrorMessageMissingAndroidPackageName + @brief Message for @c FIRAuthErrorCodeMissingAndroidPackageName error code. + */ +static NSString *const kFIRAuthErrorMessageMissingAndroidPackageName = + @"An Android Package Name must be provided if the Android App is required to be installed."; + +/** @var kFIRAuthErrorMessageUnauthorizedDomain + @brief Message for @c FIRAuthErrorCodeUnauthorizedDomain error code. + */ +static NSString *const kFIRAuthErrorMessageUnauthorizedDomain = @"The domain of the continue URL " + "is not whitelisted. Please whitelist the domain in the Firebase console."; + +/** @var kFIRAuthErrorMessageInvalidContinueURI + @brief Message for @c FIRAuthErrorCodeInvalidContinueURI error code. + */ +static NSString *const kFIRAuthErrorMessageInvalidContinueURI = + @"The continue URL provided in the request is invalid."; + +/** @var kFIRAuthErrorMessageMissingEmail + @brief Message for @c FIRAuthErrorCodeMissingEmail error code. + */ +static NSString *const kFIRAuthErrorMessageMissingEmail = @"An email address must be provided."; + +/** @var kFIRAuthErrorMessageMissingContinueURI + @brief Message for @c FIRAuthErrorCodeMissingContinueURI error code. + */ +static NSString *const kFIRAuthErrorMessageMissingContinueURI = + @"A continue URL must be provided in the request."; + +/** @var kFIRAuthErrorMessageMissingPhoneNumber + @brief Message for @c FIRAuthErrorCodeMissingPhoneNumber error code. + */ +static NSString *const kFIRAuthErrorMessageMissingPhoneNumber = + @"To send verification codes, provide a phone number for the recipient."; + +/** @var kFIRAuthErrorMessageInvalidPhoneNumber + @brief Message for @c FIRAuthErrorCodeInvalidPhoneNumber error code. + */ +static NSString *const kFIRAuthErrorMessageInvalidPhoneNumber = + @"The format of the phone number provided is incorrect. Please enter the phone number in a " + "format that can be parsed into E.164 format. E.164 phone numbers are written in the format " + "[+][country code][subscriber number including area code]."; + +/** @var kFIRAuthErrorMessageMissingVerificationCode + @brief Message for @c FIRAuthErrorCodeMissingVerificationCode error code. + */ +static NSString *const kFIRAuthErrorMessageMissingVerificationCode = + @"The phone auth credential was created with an empty SMS verification Code."; + +/** @var kFIRAuthErrorMessageInvalidVerificationCode + @brief Message for @c FIRAuthErrorCodeInvalidVerificationCode error code. + */ +static NSString *const kFIRAuthErrorMessageInvalidVerificationCode = + @"The SMS verification code used to create the phone auth credential is invalid. Please resend " + "the verification code SMS and be sure to use the verification code provided by the user."; + +/** @var kFIRAuthErrorMessageMissingVerificationID + @brief Message for @c FIRAuthErrorCodeInvalidVerificationID error code. + */ +static NSString *const kFIRAuthErrorMessageMissingVerificationID = + @"The phone auth credential was created with an empty verification ID."; + +/** @var kFIRAuthErrorMessageInvalidVerificationID + @brief Message for @c FIRAuthErrorCodeInvalidVerificationID error code. + */ +static NSString *const kFIRAuthErrorMessageInvalidVerificationID = + @"The verification ID used to create the phone auth credential is invalid."; + +/** @var kFIRAuthErrorMessageLocalPlayerNotAuthenticated + @brief Message for @c FIRAuthErrorCodeLocalPlayerNotAuthenticated error code. + */ +static NSString *const kFIRAuthErrorMessageLocalPlayerNotAuthenticated = + @"The local player is not authenticated. Please log the local player in to Game Center."; + +/** @var kFIRAuthErrorMessageGameKitNotLinked + @brief Message for @c kFIRAuthErrorMessageGameKitNotLinked error code. + */ +static NSString *const kFIRAuthErrorMessageGameKitNotLinked = + @"The GameKit framework is not linked. Please turn on the Game Center capability."; + +/** @var kFIRAuthErrorMessageSessionExpired + @brief Message for @c FIRAuthErrorCodeSessionExpired error code. + */ +static NSString *const kFIRAuthErrorMessageSessionExpired = @"The SMS code has expired. Please " + @"re-send the verification code to try again."; + +/** @var kFIRAuthErrorMessageMissingAppCredential + @brief Message for @c FIRAuthErrorCodeMissingAppCredential error code. + */ +static NSString *const kFIRAuthErrorMessageMissingAppCredential = @"The phone verification request " + "is missing an APNs Device token. Firebase Auth automatically detects APNs Device Tokens, " + "however, if method swizzling is disabled, the APNs token must be set via the APNSToken " + "property on FIRAuth or by calling setAPNSToken:type on FIRAuth."; + +/** @var kFIRAuthErrorMessageInvalidAppCredential + @brief Message for @c FIRAuthErrorCodeInvalidAppCredential error code. + */ +static NSString *const kFIRAuthErrorMessageInvalidAppCredential = @"The APNs device token provided " + "is either incorrect or does not match the private certificate uploaded to the Firebase " + "Console."; + +/** @var kFIRAuthErrorMessageQuotaExceeded + @brief Message for @c FIRAuthErrorCodeQuotaExceeded error code. + */ +static NSString *const kFIRAuthErrorMessageQuotaExceeded = @"The phone verification quota for this " + "project has been exceeded."; + +/** @var kFIRAuthErrorMessageMissingAppToken + @brief Message for @c FIRAuthErrorCodeMissingAppToken error code. + */ +static NSString *const kFIRAuthErrorMessageMissingAppToken = @"There seems to be a problem with " + "your project's Firebase phone number authentication set-up, please make sure to follow the " + "instructions found at https://firebase.google.com/docs/auth/ios/phone-auth"; + +/** @var kFIRAuthErrorMessageMissingAppToken + @brief Message for @c FIRAuthErrorCodeMissingAppToken error code. + */ +static NSString *const kFIRAuthErrorMessageNotificationNotForwarded = @"If app delegate swizzling " + "is disabled, remote notifications received by UIApplicationDelegate need to be forwarded to " + "FIRAuth's canHandleNotificaton: method."; + +/** @var kFIRAuthErrorMessageAppNotVerified + @brief Message for @c FIRAuthErrorCodeMissingAppToken error code. + */ +static NSString *const kFIRAuthErrorMessageAppNotVerified = @"Firebase could not retrieve the " + "silent push notification and therefore could not verify your app. Ensure that you configured " + "your app correctly to receive push notifications."; + +/** @var kFIRAuthErrorMessageCaptchaCheckFailed + @brief Message for @c FIRAuthErrorCodeCaptchaCheckFailed error code. + */ +static NSString *const kFIRAuthErrorMessageCaptchaCheckFailed = @"The reCAPTCHA response token " + "provided is either invalid, expired or already"; + +/** @var kFIRAuthErrorMessageWebContextAlreadyPresented + @brief Message for @c FIRAuthErrorCodeWebContextAlreadyPresented error code. + */ +static NSString *const kFIRAuthErrorMessageWebContextAlreadyPresented = @"User interaction is " + "still ongoing, another view cannot be presented."; + +/** @var kFIRAuthErrorMessageWebContextCancelled + @brief Message for @c FIRAuthErrorCodeWebContextCancelled error code. + */ +static NSString *const kFIRAuthErrorMessageWebContextCancelled = @"The interaction was cancelled " + "by the user."; + +/** @var kFIRAuthErrorMessageInvalidClientID + @brief Message for @c FIRAuthErrorCodeInvalidClientID error code. + */ +static NSString *const kFIRAuthErrorMessageInvalidClientID = @"The OAuth client ID provided is " + "either invalid or does not match the specified API key."; + +/** @var kFIRAuthErrorMessageWebRequestFailed + @brief Message for @c FIRAuthErrorCodeWebRequestFailed error code. + */ +static NSString *const kFIRAuthErrorMessageWebRequestFailed = @"A network error (such as timeout, " + "interrupted connection, or unreachable host) has occurred within the web context."; + +/** @var kFIRAuthErrorMessageWebInternalError + @brief Message for @c FIRAuthErrorCodeWebInternalError error code. + */ +static NSString *const kFIRAuthErrorMessageWebInternalError = @"An internal error has occurred " + "within the SFSafariViewController or WKWebView."; + +/** @var kFIRAuthErrorMessageAppVerificationUserInteractionFailure + @brief Message for @c FIRAuthErrorCodeInvalidClientID error code. + */ +static NSString *const kFIRAuthErrorMessageAppVerificationUserInteractionFailure = @"The app " + "verification process has failed, print and inspect the error details for more information"; + +/** @var kFIRAuthErrorMessageNullUser + @brief Message for @c FIRAuthErrorCodeNullUser error code. + */ +static NSString *const kFIRAuthErrorMessageNullUser = @"A null user object was provided as the " + "argument for an operation which requires a non-null user object."; + +/** @var kFIRAuthErrorMessageInvalidProviderID + @brief Message for @c FIRAuthErrorCodeInvalidProviderID error code. + */ +static NSString *const kFIRAuthErrorMessageInvalidProviderID = @"The provider ID provided for the " + "attempted web operation is invalid."; + +/** @var kFIRAuthErrorMessageInvalidDynamicLinkDomain + @brief Message for @c kFIRAuthErrorMessageInvalidDynamicLinkDomain error code. + */ +static NSString *const kFIRAuthErrorMessageInvalidDynamicLinkDomain = @"The " + "Firebase Dynamic Link domain used is either not configured or is unauthorized " + "for the current project."; + +/** @var kFIRAuthErrorMessageInternalError + @brief Message for @c FIRAuthErrorCodeInternalError error code. + */ +static NSString *const kFIRAuthErrorMessageInternalError = @"An internal error has occurred, " + "print and inspect the error details for more information."; + +/** @var kFIRAuthErrorMessageMalformedJWT + @brief Error message constant describing @c FIRAuthErrorCodeMalformedJWT errors. + */ +static NSString *const kFIRAuthErrorMessageMalformedJWT = + @"Failed to parse JWT. Check the userInfo dictionary for the full token."; + +/** @var kFIRAuthErrorMessageDynamicLinkNotActivated + @brief Error message constant describing @c FIRAuthErrorCodeDynamicLinkNotActivated errors. + */ +static NSString *const kFIRAuthErrorMessageDynamicLinkNotActivated = + @"Please activate Dynamic Links in the Firebase Console and agree to the terms and conditions."; + +/** @var kFIRAuthErrorMessageRejectedCredential + @brief Error message constant describing @c FIRAuthErrorCodeRejectedCredential errors. + */ +static NSString *const kFIRAuthErrorMessageRejectedCredential = + @"The request contains malformed or mismatching credentials."; + +/** @var kFIRAuthErrorMessageMissingOrInvalidNonce + @brief Error message constant describing @c FIRAuthErrorCodeMissingOrInvalidNonce errors. + */ +static NSString *const kFIRAuthErrorMessageMissingOrInvalidNonce = + @"The request contains malformed or mismatched credentials."; + +/** @var FIRAuthErrorDescription + @brief The error descrioption, based on the error code. + @remarks No default case so that we get a compiler warning if a new value was added to the enum. + */ +static NSString *FIRAuthErrorDescription(FIRAuthErrorCode code) { + switch (code) { + case FIRAuthErrorCodeInvalidCustomToken: + return kFIRAuthErrorMessageInvalidCustomToken; + case FIRAuthErrorCodeCustomTokenMismatch: + return kFIRAuthErrorMessageCustomTokenMismatch; + case FIRAuthErrorCodeInvalidEmail: + return kFIRAuthErrorMessageInvalidEmail; + case FIRAuthErrorCodeInvalidCredential: + return kFIRAuthErrorMessageInvalidCredential; + case FIRAuthErrorCodeUserDisabled: + return kFIRAuthErrorMessageUserDisabled; + case FIRAuthErrorCodeEmailAlreadyInUse: + return kFIRAuthErrorMessageEmailAlreadyInUse; + case FIRAuthErrorCodeWrongPassword: + return kFIRAuthErrorMessageWrongPassword; + case FIRAuthErrorCodeTooManyRequests: + return kFIRAuthErrorMessageTooManyRequests; + case FIRAuthErrorCodeAccountExistsWithDifferentCredential: + return kFIRAuthErrorMessageAccountExistsWithDifferentCredential; + case FIRAuthErrorCodeRequiresRecentLogin: + return kFIRAuthErrorMessageRequiresRecentLogin; + case FIRAuthErrorCodeProviderAlreadyLinked: + return kFIRAuthErrorMessageProviderAlreadyLinked; + case FIRAuthErrorCodeNoSuchProvider: + return kFIRAuthErrorMessageNoSuchProvider; + case FIRAuthErrorCodeInvalidUserToken: + return kFIRAuthErrorMessageInvalidUserToken; + case FIRAuthErrorCodeNetworkError: + return kFIRAuthErrorMessageNetworkError; + case FIRAuthErrorCodeKeychainError: + return kFIRAuthErrorMessageKeychainError; + case FIRAuthErrorCodeMissingClientIdentifier: + return kFIRAuthErrorMessageMissingClientIdentifier; + case FIRAuthErrorCodeUserTokenExpired: + return kFIRAuthErrorMessageUserTokenExpired; + case FIRAuthErrorCodeUserNotFound: + return kFIRAuthErrorMessageUserNotFound; + case FIRAuthErrorCodeInvalidAPIKey: + return kFIRAuthErrorMessageInvalidAPIKey; + case FIRAuthErrorCodeCredentialAlreadyInUse: + return kFIRAuthErrorMessageCredentialAlreadyInUse; + case FIRAuthErrorCodeInternalError: + return kFIRAuthErrorMessageInternalError; + case FIRAuthErrorCodeUserMismatch: + return FIRAuthErrorMessageUserMismatch; + case FIRAuthErrorCodeOperationNotAllowed: + return kFIRAuthErrorMessageOperationNotAllowed; + case FIRAuthErrorCodeWeakPassword: + return kFIRAuthErrorMessageWeakPassword; + case FIRAuthErrorCodeAppNotAuthorized: + return kFIRAuthErrorMessageAppNotAuthorized; + case FIRAuthErrorCodeExpiredActionCode: + return kFIRAuthErrorMessageExpiredActionCode; + case FIRAuthErrorCodeInvalidActionCode: + return kFIRAuthErrorMessageInvalidActionCode; + case FIRAuthErrorCodeInvalidSender: + return kFIRAuthErrorMessageInvalidSender; + case FIRAuthErrorCodeInvalidMessagePayload: + return kFIRAuthErrorMessageInvalidMessagePayload; + case FIRAuthErrorCodeInvalidRecipientEmail: + return kFIRAuthErrorMessageInvalidRecipientEmail; + case FIRAuthErrorCodeMissingIosBundleID: + return kFIRAuthErrorMessageMissingIosBundleID; + case FIRAuthErrorCodeMissingAndroidPackageName: + return kFIRAuthErrorMessageMissingAndroidPackageName; + case FIRAuthErrorCodeUnauthorizedDomain: + return kFIRAuthErrorMessageUnauthorizedDomain; + case FIRAuthErrorCodeInvalidContinueURI: + return kFIRAuthErrorMessageInvalidContinueURI; + case FIRAuthErrorCodeMissingContinueURI: + return kFIRAuthErrorMessageMissingContinueURI; + case FIRAuthErrorCodeMissingEmail: + return kFIRAuthErrorMessageMissingEmail; + case FIRAuthErrorCodeMissingPhoneNumber: + return kFIRAuthErrorMessageMissingPhoneNumber; + case FIRAuthErrorCodeInvalidPhoneNumber: + return kFIRAuthErrorMessageInvalidPhoneNumber; + case FIRAuthErrorCodeMissingVerificationCode: + return kFIRAuthErrorMessageMissingVerificationCode; + case FIRAuthErrorCodeInvalidVerificationCode: + return kFIRAuthErrorMessageInvalidVerificationCode; + case FIRAuthErrorCodeMissingVerificationID: + return kFIRAuthErrorMessageMissingVerificationID; + case FIRAuthErrorCodeInvalidVerificationID: + return kFIRAuthErrorMessageInvalidVerificationID; + case FIRAuthErrorCodeSessionExpired: + return kFIRAuthErrorMessageSessionExpired; + case FIRAuthErrorCodeMissingAppCredential: + return kFIRAuthErrorMessageMissingAppCredential; + case FIRAuthErrorCodeInvalidAppCredential: + return kFIRAuthErrorMessageInvalidAppCredential; + case FIRAuthErrorCodeQuotaExceeded: + return kFIRAuthErrorMessageQuotaExceeded; + case FIRAuthErrorCodeMissingAppToken: + return kFIRAuthErrorMessageMissingAppToken; + case FIRAuthErrorCodeNotificationNotForwarded: + return kFIRAuthErrorMessageNotificationNotForwarded; + case FIRAuthErrorCodeAppNotVerified: + return kFIRAuthErrorMessageAppNotVerified; + case FIRAuthErrorCodeCaptchaCheckFailed: + return kFIRAuthErrorMessageCaptchaCheckFailed; + case FIRAuthErrorCodeWebContextAlreadyPresented: + return kFIRAuthErrorMessageWebContextAlreadyPresented; + case FIRAuthErrorCodeWebContextCancelled: + return kFIRAuthErrorMessageWebContextCancelled; + case FIRAuthErrorCodeInvalidClientID: + return kFIRAuthErrorMessageInvalidClientID; + case FIRAuthErrorCodeAppVerificationUserInteractionFailure: + return kFIRAuthErrorMessageAppVerificationUserInteractionFailure; + case FIRAuthErrorCodeWebNetworkRequestFailed: + return kFIRAuthErrorMessageWebRequestFailed; + case FIRAuthErrorCodeNullUser: + return kFIRAuthErrorMessageNullUser; + case FIRAuthErrorCodeInvalidProviderID: + return kFIRAuthErrorMessageInvalidProviderID; + case FIRAuthErrorCodeInvalidDynamicLinkDomain: + return kFIRAuthErrorMessageInvalidDynamicLinkDomain; + case FIRAuthErrorCodeWebInternalError: + return kFIRAuthErrorMessageWebInternalError; + case FIRAuthErrorCodeWebSignInUserInteractionFailure: + return kFIRAuthErrorMessageAppVerificationUserInteractionFailure; + case FIRAuthErrorCodeMalformedJWT: + return kFIRAuthErrorMessageMalformedJWT; + case FIRAuthErrorCodeLocalPlayerNotAuthenticated: + return kFIRAuthErrorMessageLocalPlayerNotAuthenticated; + case FIRAuthErrorCodeGameKitNotLinked: + return kFIRAuthErrorMessageGameKitNotLinked; + case FIRAuthErrorCodeDynamicLinkNotActivated: + return kFIRAuthErrorMessageDynamicLinkNotActivated; + case FIRAuthErrorCodeRejectedCredential: + return kFIRAuthErrorMessageRejectedCredential; + case FIRAuthErrorCodeMissingOrInvalidNonce: + return kFIRAuthErrorMessageMissingOrInvalidNonce; + } +} + +/** @var FIRAuthErrorCodeString + @brief The the error short string, based on the error code. + @remarks No default case so that we get a compiler warning if a new value was added to the enum. + */ +static NSString *const FIRAuthErrorCodeString(FIRAuthErrorCode code) { + switch (code) { + case FIRAuthErrorCodeInvalidCustomToken: + return @"ERROR_INVALID_CUSTOM_TOKEN"; + case FIRAuthErrorCodeCustomTokenMismatch: + return @"ERROR_CUSTOM_TOKEN_MISMATCH"; + case FIRAuthErrorCodeInvalidEmail: + return @"ERROR_INVALID_EMAIL"; + case FIRAuthErrorCodeInvalidCredential: + return @"ERROR_INVALID_CREDENTIAL"; + case FIRAuthErrorCodeUserDisabled: + return @"ERROR_USER_DISABLED"; + case FIRAuthErrorCodeEmailAlreadyInUse: + return @"ERROR_EMAIL_ALREADY_IN_USE"; + case FIRAuthErrorCodeWrongPassword: + return @"ERROR_WRONG_PASSWORD"; + case FIRAuthErrorCodeTooManyRequests: + return @"ERROR_TOO_MANY_REQUESTS"; + case FIRAuthErrorCodeAccountExistsWithDifferentCredential: + return @"ERROR_ACCOUNT_EXISTS_WITH_DIFFERENT_CREDENTIAL"; + case FIRAuthErrorCodeRequiresRecentLogin: + return @"ERROR_REQUIRES_RECENT_LOGIN"; + case FIRAuthErrorCodeProviderAlreadyLinked: + return @"ERROR_PROVIDER_ALREADY_LINKED"; + case FIRAuthErrorCodeNoSuchProvider: + return @"ERROR_NO_SUCH_PROVIDER"; + case FIRAuthErrorCodeInvalidUserToken: + return @"ERROR_INVALID_USER_TOKEN"; + case FIRAuthErrorCodeNetworkError: + return @"ERROR_NETWORK_REQUEST_FAILED"; + case FIRAuthErrorCodeKeychainError: + return @"ERROR_KEYCHAIN_ERROR"; + case FIRAuthErrorCodeMissingClientIdentifier: + return @"ERROR_MISSING_CLIENT_IDENTIFIER"; + case FIRAuthErrorCodeUserTokenExpired: + return @"ERROR_USER_TOKEN_EXPIRED"; + case FIRAuthErrorCodeUserNotFound: + return @"ERROR_USER_NOT_FOUND"; + case FIRAuthErrorCodeInvalidAPIKey: + return @"ERROR_INVALID_API_KEY"; + case FIRAuthErrorCodeCredentialAlreadyInUse: + return @"ERROR_CREDENTIAL_ALREADY_IN_USE"; + case FIRAuthErrorCodeInternalError: + return @"ERROR_INTERNAL_ERROR"; + case FIRAuthErrorCodeUserMismatch: + return @"ERROR_USER_MISMATCH"; + case FIRAuthErrorCodeOperationNotAllowed: + return @"ERROR_OPERATION_NOT_ALLOWED"; + case FIRAuthErrorCodeWeakPassword: + return @"ERROR_WEAK_PASSWORD"; + case FIRAuthErrorCodeAppNotAuthorized: + return @"ERROR_APP_NOT_AUTHORIZED"; + case FIRAuthErrorCodeExpiredActionCode: + return @"ERROR_EXPIRED_ACTION_CODE"; + case FIRAuthErrorCodeInvalidActionCode: + return @"ERROR_INVALID_ACTION_CODE"; + case FIRAuthErrorCodeInvalidMessagePayload: + return @"ERROR_INVALID_MESSAGE_PAYLOAD"; + case FIRAuthErrorCodeInvalidSender: + return @"ERROR_INVALID_SENDER"; + case FIRAuthErrorCodeInvalidRecipientEmail: + return @"ERROR_INVALID_RECIPIENT_EMAIL"; + case FIRAuthErrorCodeMissingIosBundleID: + return @"ERROR_MISSING_IOS_BUNDLE_ID"; + case FIRAuthErrorCodeMissingAndroidPackageName: + return @"ERROR_MISSING_ANDROID_PKG_NAME"; + case FIRAuthErrorCodeUnauthorizedDomain: + return @"ERROR_UNAUTHORIZED_DOMAIN"; + case FIRAuthErrorCodeInvalidContinueURI: + return @"ERROR_INVALID_CONTINUE_URI"; + case FIRAuthErrorCodeMissingContinueURI: + return @"ERROR_MISSING_CONTINUE_URI"; + case FIRAuthErrorCodeMissingEmail: + return @"ERROR_MISSING_EMAIL"; + case FIRAuthErrorCodeMissingPhoneNumber: + return @"ERROR_MISSING_PHONE_NUMBER"; + case FIRAuthErrorCodeInvalidPhoneNumber: + return @"ERROR_INVALID_PHONE_NUMBER"; + case FIRAuthErrorCodeMissingVerificationCode: + return @"ERROR_MISSING_VERIFICATION_CODE"; + case FIRAuthErrorCodeInvalidVerificationCode: + return @"ERROR_INVALID_VERIFICATION_CODE"; + case FIRAuthErrorCodeMissingVerificationID: + return @"ERROR_MISSING_VERIFICATION_ID"; + case FIRAuthErrorCodeInvalidVerificationID: + return @"ERROR_INVALID_VERIFICATION_ID"; + case FIRAuthErrorCodeSessionExpired: + return @"ERROR_SESSION_EXPIRED"; + case FIRAuthErrorCodeMissingAppCredential: + return @"MISSING_APP_CREDENTIAL"; + case FIRAuthErrorCodeInvalidAppCredential: + return @"INVALID_APP_CREDENTIAL"; + case FIRAuthErrorCodeQuotaExceeded: + return @"ERROR_QUOTA_EXCEEDED"; + case FIRAuthErrorCodeMissingAppToken: + return @"ERROR_MISSING_APP_TOKEN"; + case FIRAuthErrorCodeNotificationNotForwarded: + return @"ERROR_NOTIFICATION_NOT_FORWARDED"; + case FIRAuthErrorCodeAppNotVerified: + return @"ERROR_APP_NOT_VERIFIED"; + case FIRAuthErrorCodeCaptchaCheckFailed: + return @"ERROR_CAPTCHA_CHECK_FAILED"; + case FIRAuthErrorCodeWebContextAlreadyPresented: + return @"ERROR_WEB_CONTEXT_ALREADY_PRESENTED"; + case FIRAuthErrorCodeWebContextCancelled: + return @"ERROR_WEB_CONTEXT_CANCELLED"; + case FIRAuthErrorCodeInvalidClientID: + return @"ERROR_INVALID_CLIENT_ID"; + case FIRAuthErrorCodeAppVerificationUserInteractionFailure: + return @"ERROR_APP_VERIFICATION_FAILED"; + case FIRAuthErrorCodeWebNetworkRequestFailed: + return @"ERROR_WEB_NETWORK_REQUEST_FAILED"; + case FIRAuthErrorCodeNullUser: + return @"ERROR_NULL_USER"; + case FIRAuthErrorCodeInvalidProviderID: + return @"ERROR_INVALID_PROVIDER_ID"; + case FIRAuthErrorCodeInvalidDynamicLinkDomain: + return @"ERROR_INVALID_DYNAMIC_LINK_DOMAIN"; + case FIRAuthErrorCodeWebInternalError: + return @"ERROR_WEB_INTERNAL_ERROR"; + case FIRAuthErrorCodeWebSignInUserInteractionFailure: + return @"ERROR_WEB_USER_INTERACTION_FAILURE"; + case FIRAuthErrorCodeMalformedJWT: + return @"ERROR_MALFORMED_JWT"; + case FIRAuthErrorCodeLocalPlayerNotAuthenticated: + return @"ERROR_LOCAL_PLAYER_NOT_AUTHENTICATED"; + case FIRAuthErrorCodeGameKitNotLinked: + return @"ERROR_GAME_KIT_NOT_LINKED"; + case FIRAuthErrorCodeDynamicLinkNotActivated: + return @"ERROR_DYNAMIC_LINK_NOT_ACTIVATED"; + case FIRAuthErrorCodeRejectedCredential: + return @"ERROR_REJECTED_CREDENTIAL"; + case FIRAuthErrorCodeMissingOrInvalidNonce: + return @"ERROR_MISSING_OR_INVALID_NONCE"; + } +} + +@implementation FIRAuthErrorUtils + ++ (NSError *)errorWithCode:(FIRAuthInternalErrorCode)code { + return [self errorWithCode:code message:nil]; +} + ++ (NSError *)errorWithCode:(FIRAuthInternalErrorCode)code + message:(nullable NSString *)message { + NSDictionary *userInfo = nil; + if (message.length) { + userInfo = @{ + NSLocalizedDescriptionKey : message + }; + } + return [self errorWithCode:code userInfo:userInfo]; +} + ++ (NSError *)errorWithCode:(FIRAuthInternalErrorCode)code + underlyingError:(nullable NSError *)underlyingError { + NSDictionary *errorUserInfo; + if (underlyingError) { + errorUserInfo = @{ + NSUnderlyingErrorKey : underlyingError + }; + } + return [self errorWithCode:code userInfo:errorUserInfo]; +} + ++ (NSError *)errorWithCode:(FIRAuthInternalErrorCode)code + userInfo:(nullable NSDictionary *)userInfo { + BOOL isPublic = (code & FIRAuthPublicErrorCodeFlag) == FIRAuthPublicErrorCodeFlag; + if (isPublic) { + // This is a public error. Return it as a public error and add a description. + NSInteger errorCode = code & ~FIRAuthPublicErrorCodeFlag; + NSMutableDictionary *errorUserInfo = [NSMutableDictionary dictionary]; + if (userInfo) { + [errorUserInfo addEntriesFromDictionary:userInfo]; + } + if (!errorUserInfo[NSLocalizedDescriptionKey]) { + errorUserInfo[NSLocalizedDescriptionKey] = FIRAuthErrorDescription(errorCode); + } + errorUserInfo[FIRAuthErrorUserInfoNameKey] = FIRAuthErrorCodeString(errorCode); + return [NSError errorWithDomain:FIRAuthErrorDomain code:errorCode userInfo:errorUserInfo]; + } else { + // This is an internal error. Wrap it in an internal error. + NSError *error = + [NSError errorWithDomain:FIRAuthInternalErrorDomain code:code userInfo:userInfo]; + return [self errorWithCode:FIRAuthInternalErrorCodeInternalError underlyingError:error]; + } +} + ++ (NSError *)RPCRequestEncodingErrorWithUnderlyingError:(NSError *)underlyingError { + return [self errorWithCode:FIRAuthInternalErrorCodeRPCRequestEncodingError + underlyingError:underlyingError]; +} + ++ (NSError *)JSONSerializationErrorForUnencodableType { + return [self errorWithCode:FIRAuthInternalErrorCodeJSONSerializationError]; +} + ++ (NSError *)JSONSerializationErrorWithUnderlyingError:(NSError *)underlyingError { + return [self errorWithCode:FIRAuthInternalErrorCodeJSONSerializationError + underlyingError:underlyingError]; +} + ++ (NSError *)networkErrorWithUnderlyingError:(NSError *)underlyingError { + return [self errorWithCode:FIRAuthInternalErrorCodeNetworkError + underlyingError:underlyingError]; +} + ++ (NSError *)unexpectedErrorResponseWithData:(NSData *)data + underlyingError:(NSError *)underlyingError { + NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; + if (data) { + userInfo[FIRAuthErrorUserInfoDataKey] = data; + } + if (underlyingError) { + userInfo[NSUnderlyingErrorKey] = underlyingError; + } + return [self errorWithCode:FIRAuthInternalErrorCodeUnexpectedErrorResponse + userInfo:[userInfo copy]]; +} + ++ (NSError *)unexpectedErrorResponseWithDeserializedResponse:(id)deserializedResponse { + NSDictionary *userInfo; + if (deserializedResponse) { + userInfo = @{ + FIRAuthErrorUserInfoDeserializedResponseKey : deserializedResponse, + }; + } + return [self errorWithCode:FIRAuthInternalErrorCodeUnexpectedErrorResponse userInfo:userInfo]; +} + ++ (NSError *)malformedJWTErrorWithToken:(NSString *)token + underlyingError:(NSError *_Nullable)underlyingError { + NSMutableDictionary *userInfo = + [NSMutableDictionary dictionaryWithObject:kFIRAuthErrorMessageMalformedJWT + forKey:NSLocalizedDescriptionKey]; + [userInfo setObject:token forKey:FIRAuthErrorUserInfoDataKey]; + if (underlyingError != nil) { + [userInfo setObject:underlyingError forKey:NSUnderlyingErrorKey]; + } + return [self errorWithCode:FIRAuthInternalErrorCodeMalformedJWT userInfo:[userInfo copy]]; +} + ++ (NSError *)unexpectedResponseWithData:(NSData *)data + underlyingError:(NSError *)underlyingError { + NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; + if (data) { + userInfo[FIRAuthErrorUserInfoDataKey] = data; + } + if (underlyingError) { + userInfo[NSUnderlyingErrorKey] = underlyingError; + } + return [self errorWithCode:FIRAuthInternalErrorCodeUnexpectedResponse userInfo:[userInfo copy]]; +} + ++ (NSError *)unexpectedResponseWithDeserializedResponse:(id)deserializedResponse { + NSDictionary *userInfo; + if (deserializedResponse) { + userInfo = @{ + FIRAuthErrorUserInfoDeserializedResponseKey : deserializedResponse, + }; + } + return [self errorWithCode:FIRAuthInternalErrorCodeUnexpectedResponse userInfo:userInfo]; +} + ++ (NSError *)unexpectedResponseWithDeserializedResponse:(nullable id)deserializedResponse + underlyingError:(NSError *)underlyingError { + NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; + if (deserializedResponse) { + userInfo[FIRAuthErrorUserInfoDeserializedResponseKey] = deserializedResponse; + } + if (underlyingError) { + userInfo[NSUnderlyingErrorKey] = underlyingError; + } + return [self errorWithCode:FIRAuthInternalErrorCodeUnexpectedResponse userInfo:[userInfo copy]]; +} + ++ (NSError *)RPCResponseDecodingErrorWithDeserializedResponse:(id)deserializedResponse + underlyingError:(NSError *)underlyingError { + NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; + if (deserializedResponse) { + userInfo[FIRAuthErrorUserInfoDeserializedResponseKey] = deserializedResponse; + } + if (underlyingError) { + userInfo[NSUnderlyingErrorKey] = underlyingError; + } + return [self errorWithCode:FIRAuthInternalErrorCodeRPCResponseDecodingError + userInfo:[userInfo copy]]; +} + ++ (NSError *)emailAlreadyInUseErrorWithEmail:(nullable NSString *)email { + NSDictionary *userInfo; + if (email.length) { + userInfo = @{ + FIRAuthErrorUserInfoEmailKey : email, + }; + } + return [self errorWithCode:FIRAuthInternalErrorCodeEmailAlreadyInUse userInfo:userInfo]; +} + ++ (NSError *)userDisabledErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeUserDisabled message:message]; +} + ++ (NSError *)wrongPasswordErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeWrongPassword message:message]; +} + ++ (NSError *)tooManyRequestsErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeTooManyRequests message:message]; +} + ++ (NSError *)invalidCustomTokenErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeInvalidCustomToken message:message]; +} + ++ (NSError *)customTokenMistmatchErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeCustomTokenMismatch message:message]; +} + ++ (NSError *)invalidCredentialErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeInvalidCredential message:message]; +} + ++ (NSError *)requiresRecentLoginErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeRequiresRecentLogin message:message]; +} + ++ (NSError *)invalidUserTokenErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeInvalidUserToken message:message]; +} + ++ (NSError *)invalidEmailErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeInvalidEmail message:message]; +} + ++ (NSError *)accountExistsWithDifferentCredentialErrorWithEmail:(nullable NSString *)email + updatedCredential:(nullable FIRAuthCredential *)updatedCredential { + NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; + if (email) { + userInfo[FIRAuthErrorUserInfoEmailKey] = email; + } + if (updatedCredential) { + userInfo[FIRAuthErrorUserInfoUpdatedCredentialKey] = updatedCredential; + } + return [self errorWithCode:FIRAuthInternalErrorCodeAccountExistsWithDifferentCredential + userInfo:userInfo]; +} + ++ (NSError *)providerAlreadyLinkedError { + return [self errorWithCode:FIRAuthInternalErrorCodeProviderAlreadyLinked]; +} + ++ (NSError *)noSuchProviderError { + return [self errorWithCode:FIRAuthInternalErrorCodeNoSuchProvider]; +} + ++ (NSError *)userTokenExpiredErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeUserTokenExpired message:message]; +} + ++ (NSError *)userNotFoundErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeUserNotFound message:message]; +} + ++ (NSError *)invalidAPIKeyError { + return [self errorWithCode:FIRAuthInternalErrorCodeInvalidAPIKey]; +} + ++ (NSError *)userMismatchError { + return [self errorWithCode:FIRAuthInternalErrorCodeUserMismatch]; +} + ++ (NSError *)credentialAlreadyInUseErrorWithMessage:(nullable NSString *)message + credential:(nullable FIRAuthCredential *)credential + email:(nullable NSString *)email { + NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; + if (credential) { + userInfo[FIRAuthErrorUserInfoUpdatedCredentialKey] = credential; + } + if (email.length) { + userInfo[FIRAuthErrorUserInfoEmailKey] = email; + } + if (userInfo.count) { + return [self errorWithCode:FIRAuthInternalErrorCodeCredentialAlreadyInUse + userInfo:userInfo]; + } + return [self errorWithCode:FIRAuthInternalErrorCodeCredentialAlreadyInUse message:message]; +} + ++ (NSError *)operationNotAllowedErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeOperationNotAllowed message:message]; +} + ++ (NSError *)weakPasswordErrorWithServerResponseReason:(nullable NSString *)reason { + NSDictionary *userInfo; + if (reason.length) { + userInfo = @{ + NSLocalizedFailureReasonErrorKey : reason, + }; + } + return [self errorWithCode:FIRAuthInternalErrorCodeWeakPassword userInfo:userInfo]; +} + ++ (NSError *)appNotAuthorizedError { + return [self errorWithCode:FIRAuthInternalErrorCodeAppNotAuthorized]; +} + ++ (NSError *)expiredActionCodeErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeExpiredActionCode message:message]; +} + ++ (NSError *)invalidActionCodeErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeInvalidActionCode message:message]; +} + ++ (NSError *)invalidMessagePayloadErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeInvalidMessagePayload message:message]; +} + ++ (NSError *)invalidSenderErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeInvalidSender message:message]; +} + ++ (NSError *)invalidRecipientEmailErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeInvalidRecipientEmail message:message]; +} + ++ (NSError *)missingIosBundleIDErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthinternalErrorCodeMissingIosBundleID message:message]; +} + ++ (NSError *)missingAndroidPackageNameErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeMissingAndroidPackageName message:message]; +} + ++ (NSError *)unauthorizedDomainErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeUnauthorizedDomain message:message]; +} + ++ (NSError *)invalidContinueURIErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeInvalidContinueURI message:message]; +} + ++ (NSError *)missingContinueURIErrorWithMessage:(nullable NSString *)message { + return[self errorWithCode:FIRAuthInternalErrorCodeMissingContinueURI message:message]; +} + ++ (NSError *)missingEmailErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeMissingEmail message:message]; +} + ++ (NSError *)missingPhoneNumberErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeMissingPhoneNumber message:message]; +} + ++ (NSError *)invalidPhoneNumberErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeInvalidPhoneNumber message:message]; +} + ++ (NSError *)missingVerificationCodeErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeMissingVerificationCode message:message]; +} + ++ (NSError *)invalidVerificationCodeErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeInvalidVerificationCode message:message]; +} + ++ (NSError *)missingVerificationIDErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeMissingVerificationID message:message]; +} + ++ (NSError *)invalidVerificationIDErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeInvalidVerificationID message:message]; +} + ++ (NSError *)sessionExpiredErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeSessionExpired message:message]; +} + ++ (NSError *)missingAppCredentialWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeMissingAppCredential message:message]; +} + ++ (NSError *)invalidAppCredentialWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeInvalidAppCredential message:message]; +} + ++ (NSError *)quotaExceededErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeQuotaExceeded message:message]; +} + ++ (NSError *)missingAppTokenErrorWithUnderlyingError:(nullable NSError *)underlyingError { + return [self errorWithCode:FIRAuthInternalErrorCodeMissingAppToken + underlyingError:underlyingError]; +} + ++ (NSError *)localPlayerNotAuthenticatedError { + return [self errorWithCode:FIRAuthInternalErrorCodeLocalPlayerNotAuthenticated]; +} + ++ (NSError *)gameKitNotLinkedError { + return [self errorWithCode:FIRAuthInternalErrorCodeGameKitNotLinked]; +} + ++ (NSError *)notificationNotForwardedError { + return [self errorWithCode:FIRAuthInternalErrorCodeNotificationNotForwarded]; +} + ++ (NSError *)appNotVerifiedErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeAppNotVerified message:message]; +} + ++ (NSError *)missingClientIdentifierErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeMissingClientIdentifier message:message]; +} + ++ (NSError *)captchaCheckFailedErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeCaptchaCheckFailed message:message]; +} + ++ (NSError *)webContextAlreadyPresentedErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeWebContextAlreadyPresented message:message]; +} + ++ (NSError *)webContextCancelledErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeWebContextCancelled message:message]; +} + ++ (NSError *)appVerificationUserInteractionFailureWithReason:(NSString *)reason { + NSDictionary *userInfo; + if (reason.length) { + userInfo = @{ + NSLocalizedFailureReasonErrorKey : reason, + }; + } + return [self errorWithCode:FIRAuthInternalErrorCodeAppVerificationUserInteractionFailure + userInfo:userInfo]; +} + ++ (NSError *)webSignInUserInteractionFailureWithReason:(nullable NSString *)reason { + NSDictionary *userInfo; + if (reason.length) { + userInfo = @{ + NSLocalizedFailureReasonErrorKey : reason, + }; + } + return [self errorWithCode:FIRAuthInternalErrorCodeWebSignInUserInteractionFailure + userInfo:userInfo]; +} + ++ (nullable NSError *)URLResponseErrorWithCode:(NSString *)code message:(nullable NSString *)message { + if ([code isEqualToString:kURLResponseErrorCodeInvalidClientID]) { + return [self errorWithCode:FIRAuthInternalErrorCodeInvalidClientID message:message]; + } + if ([code isEqualToString:kURLResponseErrorCodeNetworkRequestFailed]) { + return [self errorWithCode:FIRAuthInternalErrorCodeWebNetworkRequestFailed message:message]; + } + if ([code isEqualToString:kURLResponseErrorCodeInternalError]) { + return [self errorWithCode:FIRAuthInternalErrorCodeWebInternalError message:message]; + } + return nil; +} + ++ (NSError *)nullUserErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeNullUser message:message]; +} + ++ (NSError *)invalidProviderIDErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeInvalidProviderID message:message]; +} + ++ (NSError *)invalidDynamicLinkDomainErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeInvalidDynamicLinkDomain message:message]; +} + ++ (NSError *)missingOrInvalidNonceErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeMissingOrInvalidNonce message:message]; +} + ++ (NSError *)keychainErrorWithFunction:(NSString *)keychainFunction status:(OSStatus)status { + NSString *failureReason = [NSString stringWithFormat:@"%@ (%li)", keychainFunction, (long)status]; + return [self errorWithCode:FIRAuthInternalErrorCodeKeychainError userInfo:@{ + NSLocalizedFailureReasonErrorKey : failureReason, + }]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthExceptionUtils.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthExceptionUtils.h new file mode 100644 index 0000000..3ae9159 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthExceptionUtils.h @@ -0,0 +1,41 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRAuthExceptionUtils + @brief Utility class used to raise standardized Auth related exceptions. +*/ +@interface FIRAuthExceptionUtils : NSObject + +/** @fn raiseInvalidParameterExceptionWithReason: + @brief raises the "invalid parameter" exception + @param reason string will contain a description of the error. + */ ++ (void)raiseInvalidParameterExceptionWithReason:(nullable NSString *)reason; + +/** @fn raiseMethodNotImplementedExceptionWithReason: + @brief raises the "method not implemented" exception + @param reason string will contain a description of the error. + @see FIRMethodNotImplementedException + */ ++ (void)raiseMethodNotImplementedExceptionWithReason:(nullable NSString *)reason; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthExceptionUtils.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthExceptionUtils.m new file mode 100644 index 0000000..3da858f --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthExceptionUtils.m @@ -0,0 +1,40 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRAuthExceptionUtils.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var FIRMethodNotImplementedException + @brief The name of the "Method Not Implemented" exception. + */ +static NSString *const FIRMethodNotImplementedException = @"FIRMethodNotImplementedException"; + +@implementation FIRAuthExceptionUtils + ++ (void)raiseInvalidParameterExceptionWithReason:(nullable NSString *)reason { + [NSException raise:NSInvalidArgumentException format:@"%@", reason]; +} + ++ (void)raiseMethodNotImplementedExceptionWithReason:(nullable NSString *)reason { + NSException *exception = + [NSException exceptionWithName:FIRMethodNotImplementedException reason:reason userInfo:nil]; + [exception raise]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthInternalErrors.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthInternalErrors.h new file mode 100644 index 0000000..8f34bdf --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthInternalErrors.h @@ -0,0 +1,484 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthErrors.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var FIRAuthPublicErrorCodeFlag + @brief Bitmask value indicating the error represents a public error code when this bit is + zeroed. Error codes which don't contain this flag will be wrapped in an @c NSError whose + code is @c FIRAuthErrorCodeInternalError. + */ +static const NSInteger FIRAuthPublicErrorCodeFlag = 1 << 20; + +/** @var FIRAuthInternalErrorDomain + @brief The Firebase Auth error domain for internal errors. + */ +extern NSString *const FIRAuthInternalErrorDomain; + +/** @var FIRAuthErrorUserInfoDeserializedResponseKey + @brief Errors with the code @c FIRAuthErrorCodeUnexpectedResponseError, + @c FIRAuthErrorCodeUnexpectedErrorResponseError, and + @c FIRAuthInternalErrorCodeRPCResponseDecodingError may contain an @c NSError.userInfo + dictionary which contains this key. The value associated with this key is an object of + unspecified contents containing the deserialized server response. + */ +extern NSString *const FIRAuthErrorUserInfoDeserializedResponseKey; + +/** @var FIRAuthErrorUserInfoDataKey + @brief Errors with the code @c FIRAuthErrorCodeUnexpectedResponseError or + @c FIRAuthErrorCodeUnexpectedErrorResponseError may contain an @c NSError.userInfo + dictionary which contains this key. The value associated with this key is an @c NSString + which represents the response from a server to an RPC which could not be deserialized. + */ +extern NSString *const FIRAuthErrorUserInfoDataKey; + + +/** @var FIRAuthInternalErrorCode + @brief Error codes used internally by Firebase Auth. + @remarks All errors are generated using an internal error code. These errors are automatically + converted to the appropriate public version of the @c NSError by the methods in + @c FIRAuthErrorUtils + */ +typedef NS_ENUM(NSInteger, FIRAuthInternalErrorCode) { + /** @var FIRAuthInternalErrorCodeNetworkError + @brief Indicates a network error occurred (such as a timeout, interrupted connection, or + unreachable host.) + @remarks These types of errors are often recoverable with a retry. + + See the @c NSUnderlyingError value in the @c NSError.userInfo dictionary for details about + the network error which occurred. + */ + FIRAuthInternalErrorCodeNetworkError = FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeNetworkError, + + /** @var FIRAuthInternalErrorCodeEmailAlreadyInUse + @brief The email used to attempt a sign-up already exists. + */ + FIRAuthInternalErrorCodeEmailAlreadyInUse = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeEmailAlreadyInUse, + + /** @var FIRAuthInternalErrorCodeUserDisabled + @brief Indicates the user's account is disabled on the server side. + */ + FIRAuthInternalErrorCodeUserDisabled = FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeUserDisabled, + + /** @var FIRAuthInternalErrorCodeWrongPassword + @brief Indicates the user attempted sign in with a wrong password + */ + FIRAuthInternalErrorCodeWrongPassword = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeWrongPassword, + + /** @var FIRAuthInternalErrorCodeKeychainError + @brief Indicates an error occurred accessing the keychain. + @remarks The @c NSLocalizedFailureReasonErrorKey field in the @c NSError.userInfo dictionary + will contain more information about the error encountered. + */ + FIRAuthInternalErrorCodeKeychainError = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeKeychainError, + + /** @var FIRAuthInternalErrorCodeMissingClientIdentifier + @brief Indicates an error for when the client identifier is missing. + */ + FIRAuthInternalErrorCodeMissingClientIdentifier = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeMissingClientIdentifier, + + /** @var FIRAuthInternalErrorCodeInternalError + @brief An internal error occurred. + @remarks This value is here for consistency. It's also used to make the implementation of + wrapping internal errors simpler. + */ + FIRAuthInternalErrorCodeInternalError = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeInternalError, + + /** @var FIRAuthInternalErrorCodeTooManyRequests + @brief Indicates that too many requests were made to a server method. + */ + FIRAuthInternalErrorCodeTooManyRequests = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeTooManyRequests, + + /** @var FIRAuthInternalErrorCodeInvalidCustomToken + @brief Indicates a validation error with the custom token. + */ + FIRAuthInternalErrorCodeInvalidCustomToken = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeInvalidCustomToken, + + /** @var FIRAuthInternalErrorCodeCredentialMismatch + @brief Indicates the service account and the API key belong to different projects. + */ + FIRAuthInternalErrorCodeCustomTokenMismatch = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeCustomTokenMismatch, + + /** @var FIRAuthInternalErrorCodeInvalidCredential + @brief Indicates the IDP token or requestUri is invalid. + */ + FIRAuthInternalErrorCodeInvalidCredential = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeInvalidCredential, + + /** @var FIRAuthInternalErrorCodeRequiresRecentLogin + @brief Indicates the user has attemped to change email or password more than 5 minutes after + signing in. + */ + FIRAuthInternalErrorCodeRequiresRecentLogin = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeRequiresRecentLogin, + + /** @var FIRAuthInternalErrorCodeInvalidUserToken + @brief Indicates user's saved auth credential is invalid, the user needs to sign in again. + */ + FIRAuthInternalErrorCodeInvalidUserToken = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeInvalidUserToken, + + /** @var FIRAuthInternalErrorCodeInvalidEmail + @brief Indicates the email identifier is invalid. + */ + FIRAuthInternalErrorCodeInvalidEmail = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeInvalidEmail, + + /** @var FIRAuthInternalErrorCodeAccountExistsWithDifferentCredential + @brief Indicates account linking is needed. + */ + FIRAuthInternalErrorCodeAccountExistsWithDifferentCredential = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeAccountExistsWithDifferentCredential, + + /** @var FIRAuthInternalErrorCodeProviderAlreadyLinked + @brief Indicates an attempt to link a provider to which we are already linked. + */ + FIRAuthInternalErrorCodeProviderAlreadyLinked = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeProviderAlreadyLinked, + + /** @var FIRAuthInternalErrorCodeNoSuchProvider + @brief Indicates an attempt to unlink a provider that is not is not linked. + */ + FIRAuthInternalErrorCodeNoSuchProvider = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeNoSuchProvider, + + /** @var FIRAuthInternalErrorCodeUserTokenExpired + @brief Indicates the token issue time is older than account's valid_since time. + */ + FIRAuthInternalErrorCodeUserTokenExpired = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeUserTokenExpired, + + /** @var FIRAuthInternalErrorCodeUserNotFound + @brief Indicates the user account was been found. + */ + FIRAuthInternalErrorCodeUserNotFound = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeUserNotFound, + + /** @var FIRAuthInternalErrorCodeInvalidAPIKey + @brief Indicates an invalid API Key was supplied in the request. + */ + FIRAuthInternalErrorCodeInvalidAPIKey = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeInvalidAPIKey, + + /** @var FIRAuthInternalErrorCodeOperationNotAllowed + @brief Indicates that admin disabled sign-in with the specified IDP. + */ + FIRAuthInternalErrorCodeOperationNotAllowed = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeOperationNotAllowed, + + /** @var FIRAuthInternalErrorCodeUserMismatch + @brief Indicates that user attempted to reauthenticate with a user other than the current + user. + */ + FIRAuthInternalErrorCodeUserMismatch = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeUserMismatch, + + /** @var FIRAuthInternalErrorCodeCredentialAlreadyInUse + @brief Indicates an attempt to link with a credential that has already been linked with a + different Firebase account. + */ + FIRAuthInternalErrorCodeCredentialAlreadyInUse = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeCredentialAlreadyInUse, + + /** @var FIRAuthInternalErrorCodeWeakPassword + @brief Indicates an attempt to set a password that is considered too weak. + */ + FIRAuthInternalErrorCodeWeakPassword = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeWeakPassword, + + /** @var FIRAuthInternalErrorCodeAppNotAuthorized + @brief Indicates the App is not authorized to use Firebase Authentication with the + provided API Key. + */ + FIRAuthInternalErrorCodeAppNotAuthorized = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeAppNotAuthorized, + + /** @var FIRAuthInternalErrorCodeExpiredActionCode + @brief Indicates the OOB code is expired. + */ + FIRAuthInternalErrorCodeExpiredActionCode = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeExpiredActionCode, + + /** @var FIRAuthInternalErrorCodeInvalidActionCode + @brief Indicates the OOB code is invalid. + */ + FIRAuthInternalErrorCodeInvalidActionCode = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeInvalidActionCode, + + /** Indicates that there are invalid parameters in the payload during a "send password reset email + * " attempt. + */ + FIRAuthInternalErrorCodeInvalidMessagePayload = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeInvalidMessagePayload, + + /** Indicates that the sender email is invalid during a "send password reset email" attempt. + */ + FIRAuthInternalErrorCodeInvalidSender = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeInvalidSender, + + /** Indicates that the recipient email is invalid. + */ + FIRAuthInternalErrorCodeInvalidRecipientEmail = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeInvalidRecipientEmail, + + /** Indicates that the iOS bundle ID is missing when a iOS App Store ID is provided. + */ + FIRAuthinternalErrorCodeMissingIosBundleID = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeMissingIosBundleID, + + /** Indicates that the android package name is missing when the @c androidInstallApp flag is set + to true. + */ + FIRAuthInternalErrorCodeMissingAndroidPackageName = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeMissingAndroidPackageName, + + /** Indicates that the domain specified in the continue URL is not whitelisted in the Firebase + console. + */ + FIRAuthInternalErrorCodeUnauthorizedDomain = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeUnauthorizedDomain, + + /** Indicates that the domain specified in the continue URI is not valid. + */ + FIRAuthInternalErrorCodeInvalidContinueURI = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeInvalidContinueURI, + + /** Indicates that a continue URI was not provided in a request to the backend which requires + one. + */ + FIRAuthInternalErrorCodeMissingContinueURI = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeMissingContinueURI, + + /** Indicates that an email address was expected but one was not provided. + */ + FIRAuthInternalErrorCodeMissingEmail = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeMissingEmail, + + /** Indicates that a phone number was not provided in a call to @c verifyPhoneNumber:completion:. + */ + FIRAuthInternalErrorCodeMissingPhoneNumber = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeMissingPhoneNumber, + + /** Indicates that an invalid phone number was provided in a call to @c + verifyPhoneNumber:completion:. + */ + FIRAuthInternalErrorCodeInvalidPhoneNumber = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeInvalidPhoneNumber, + + /** Indicates that the phone auth credential was created with an empty verification code. + */ + FIRAuthInternalErrorCodeMissingVerificationCode = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeMissingVerificationCode, + + /** Indicates that an invalid verification code was used in the verifyPhoneNumber request. + */ + FIRAuthInternalErrorCodeInvalidVerificationCode = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeInvalidVerificationCode, + + /** Indicates that the phone auth credential was created with an empty verification ID. + */ + FIRAuthInternalErrorCodeMissingVerificationID = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeMissingVerificationID, + + /** Indicates that the APNS device token is missing in the verifyClient request. + */ + FIRAuthInternalErrorCodeMissingAppCredential = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeMissingAppCredential, + + /** Indicates that an invalid APNS device token was used in the verifyClient request. + */ + FIRAuthInternalErrorCodeInvalidAppCredential = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeInvalidAppCredential, + + /** Indicates that the reCAPTCHA token is not valid. + */ + FIRAuthInternalErrorCodeCaptchaCheckFailed = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeCaptchaCheckFailed, + + /** Indicates that an invalid verification ID was used in the verifyPhoneNumber request. + */ + FIRAuthInternalErrorCodeInvalidVerificationID = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeInvalidVerificationID, + + /** Indicates that the quota of SMS messages for a given project has been exceeded. + */ + FIRAuthInternalErrorCodeQuotaExceeded = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeQuotaExceeded, + + /** Indicates that an attempt was made to present a new web context while one was already being + presented. + */ + FIRAuthInternalErrorCodeWebContextAlreadyPresented = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeWebContextAlreadyPresented, + + /** Indicates that the URL presentation was cancelled prematurely by the user. + */ + FIRAuthInternalErrorCodeWebContextCancelled = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeWebContextCancelled, + + /** Indicates a general failure during the app verification flow. + */ + FIRAuthInternalErrorCodeAppVerificationUserInteractionFailure = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeAppVerificationUserInteractionFailure, + + /** Indicates that the clientID used to invoke a web flow is invalid. + */ + FIRAuthInternalErrorCodeInvalidClientID = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeInvalidClientID, + + /** Indicates that a network request within a SFSafariViewController or WKWebView failed. + */ + FIRAuthInternalErrorCodeWebNetworkRequestFailed = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeWebNetworkRequestFailed, + + /** Indicates that an internal error occurred within a SFSafariViewController or WKWebView. + */ + FIRAuthInternalErrorCodeWebInternalError = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeWebInternalError, + + /** Indicates that an internal error occurred within a SFSafariViewController or WKWebView. + */ + FIRAuthInternalErrorCodeWebSignInUserInteractionFailure = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeWebSignInUserInteractionFailure, + + // The enum values between 17046 and 17051 are reserved and should NOT be used for new error + // codes. + + /** Indicates that the SMS code has expired + */ + FIRAuthInternalErrorCodeSessionExpired = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeSessionExpired, + + FIRAuthInternalErrorCodeMissingAppToken = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeMissingAppToken, + + FIRAuthInternalErrorCodeNotificationNotForwarded = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeNotificationNotForwarded, + + FIRAuthInternalErrorCodeAppNotVerified = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeAppNotVerified, + + /** Indicates that the Game Center local player was not authenticated. + */ + FIRAuthInternalErrorCodeLocalPlayerNotAuthenticated = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeLocalPlayerNotAuthenticated, + + /** Indicates that the Game Center local player was not authenticated. + */ + FIRAuthInternalErrorCodeGameKitNotLinked = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeGameKitNotLinked, + + /** Indicates that the nonce is missing or invalid. + */ + FIRAuthInternalErrorCodeMissingOrInvalidNonce = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeMissingOrInvalidNonce, + + /** Indicates that a non-null user was expected as an argmument to the operation but a null + user was provided. + */ + FIRAuthInternalErrorCodeNullUser = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeNullUser, + + /** Indicates that the provider id given for the web operation is invalid. + */ + FIRAuthInternalErrorCodeInvalidProviderID = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeInvalidProviderID, + + /** Indicates that the Firebase Dynamic Link domain used is either not configured or is unauthorized + for the current project. + */ + FIRAuthInternalErrorCodeInvalidDynamicLinkDomain = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeInvalidDynamicLinkDomain, + + FIRAuthInternalErrorCodeMalformedJWT = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeMalformedJWT, + + /** @var FIRAuthInternalErrorCodeRPCRequestEncodingError + @brief Indicates an error encoding the RPC request. + @remarks This is typically due to some sort of unexpected input value. + + See the @c NSUnderlyingError value in the @c NSError.userInfo dictionary for details. + */ + FIRAuthInternalErrorCodeRPCRequestEncodingError = 1, + + /** @var FIRAuthInternalErrorCodeJSONSerializationError + @brief Indicates an error serializing an RPC request. + @remarks This is typically due to some sort of unexpected input value. + + If an @c NSJSONSerialization.isValidJSONObject: check fails, the error will contain no + @c NSUnderlyingError key in the @c NSError.userInfo dictionary. If an error was + encountered calling @c NSJSONSerialization.dataWithJSONObject:options:error:, the + resulting error will be associated with the @c NSUnderlyingError key in the + @c NSError.userInfo dictionary. + */ + FIRAuthInternalErrorCodeJSONSerializationError = 2, + + /** @var FIRAuthInternalErrorCodeUnexpectedErrorResponse + @brief Indicates an HTTP error occurred and the data returned either couldn't be deserialized + or couldn't be decoded. + @remarks See the @c NSUnderlyingError value in the @c NSError.userInfo dictionary for details + about the HTTP error which occurred. + + If the response could be deserialized as JSON then the @c NSError.userInfo dictionary will + contain a value for the key @c FIRAuthErrorUserInfoDeserializedResponseKey which is the + deserialized response value. + + If the response could not be deserialized as JSON then the @c NSError.userInfo dictionary + will contain values for the @c NSUnderlyingErrorKey and @c FIRAuthErrorUserInfoDataKey + keys. + */ + FIRAuthInternalErrorCodeUnexpectedErrorResponse = 3, + + /** @var FIRAuthInternalErrorCodeUnexpectedResponse + @brief Indicates the HTTP response indicated the request was a successes, but the response + contains something other than a JSON-encoded dictionary, or the data type of the response + indicated it is different from the type of response we expected. + @remarks See the @c NSUnderlyingError value in the @c NSError.userInfo dictionary. + If this key is present in the dictionary, it may contain an error from + @c NSJSONSerialization error (indicating the response received was of the wrong data + type). + + See the @c FIRAuthErrorUserInfoDeserializedResponseKey value in the @c NSError.userInfo + dictionary. If the response could be deserialized, it's deserialized representation will + be associated with this key. If the @c NSUnderlyingError value in the @c NSError.userInfo + dictionary is @c nil, this indicates the JSON didn't represent a dictionary. + */ + FIRAuthInternalErrorCodeUnexpectedResponse = 4, + + /** @var FIRAuthInternalErrorCodeRPCResponseDecodingError + @brief Indicates an error decoding the RPC response. + This is typically due to some sort of unexpected response value from the server. + @remarks See the @c NSUnderlyingError value in the @c NSError.userInfo dictionary for details. + + See the @c FIRErrorUserInfoDecodedResponseKey value in the @c NSError.userInfo dictionary. + The deserialized representation of the response will be associated with this key. + */ + FIRAuthInternalErrorCodeRPCResponseDecodingError = 5, +}; + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthURLPresenter.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthURLPresenter.h new file mode 100644 index 0000000..e5341f5 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthURLPresenter.h @@ -0,0 +1,69 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#if !TARGET_OS_OSX && !TARGET_OS_TV + +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol FIRAuthUIDelegate; + +/** @typedef FIRAuthURLPresentationCompletion + @brief The type of block invoked when the URLPresentation completes. + @param callbackURL The callback URL if the presentation ends with a matching callback. + @param error The error if the presentation fails to start or ends with an error. + */ +typedef void (^FIRAuthURLPresentationCompletion)(NSURL *_Nullable callbackURL, + NSError *_Nullable error); + +/** @typedef FIRAuthCallbackMatcher + @brief The type of block invoked for checking whether a callback URL matches. + @param callbackURL The callback URL to check for match. + @return Whether or not the specific callback URL matches or not. + */ +typedef BOOL (^FIRAuthURLCallbackMatcher)(NSURL * _Nullable callbackURL); + +/** @class FIRAuthURLPresenter + @brief A Class responsible for presenting URL via SFSafariViewController or WKWebView. + */ +@interface FIRAuthURLPresenter : NSObject + +/** @fn presentURL:UIDelegate:callbackMatcher:completion: + @brief Presents an URL to interact with user. + @param URL The URL to present. + @param UIDelegate The UI delegate to present view controller. + @param completion A block to be called either synchronously if the presentation fails to start, + or asynchronously in future on an unspecified thread once the presentation finishes. + */ +- (void)presentURL:(NSURL *)URL + UIDelegate:(nullable id)UIDelegate + callbackMatcher:(FIRAuthURLCallbackMatcher)callbackMatcher + completion:(FIRAuthURLPresentationCompletion)completion; + +/** @fn canHandleURL: + @brief Determines if a URL was produced by the currently presented URL. + @param URL The URL to handle. + @return Whether the URL could be handled or not. + */ +- (BOOL)canHandleURL:(NSURL *)URL; + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthURLPresenter.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthURLPresenter.m new file mode 100644 index 0000000..2a43292 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthURLPresenter.m @@ -0,0 +1,195 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#if !TARGET_OS_OSX && !TARGET_OS_TV + +#import "FIRAuthURLPresenter.h" + +#import + +#import "FIRAuthDefaultUIDelegate.h" +#import "FIRAuthErrorUtils.h" +#import "FIRAuthGlobalWorkQueue.h" +#import "FIRAuthUIDelegate.h" +#import "FIRAuthWebViewController.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRAuthURLPresenter () +@end + +// Disable unguarded availability warnings because SFSafariViewController is been used throughout +// the code, including as an iVar, which cannot be simply excluded by @available check. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability" + +@implementation FIRAuthURLPresenter { + /** @var _isPresenting + @brief Whether or not some web-based content is being presented. + */ + BOOL _isPresenting; + + /** @var _callbackMatcher + @brief The callback URL matcher for the current presentation, if one is active. + */ + FIRAuthURLCallbackMatcher _Nullable _callbackMatcher; + + /** @var _safariViewController + @brief The SFSafariViewController used for the current presentation, if any. + */ + SFSafariViewController *_Nullable _safariViewController; + + /** @var _webViewController + @brief The FIRAuthWebViewController used for the current presentation, if any. + */ + FIRAuthWebViewController *_Nullable _webViewController; + + /** @var _UIDelegate + @brief The UIDelegate used to present the SFSafariViewController. + */ + id _UIDelegate; + + /** @var _completion + @brief The completion handler for the current presentaion, if one is active. + @remarks This variable is also used as a flag to indicate a presentation is active. + */ + FIRAuthURLPresentationCompletion _Nullable _completion; +} + +- (void)presentURL:(NSURL *)URL + UIDelegate:(nullable id)UIDelegate + callbackMatcher:(FIRAuthURLCallbackMatcher)callbackMatcher + completion:(FIRAuthURLPresentationCompletion)completion { + if (_isPresenting) { + // Unable to start a new presentation on top of another. + _completion(nil, [FIRAuthErrorUtils webContextAlreadyPresentedErrorWithMessage:nil]); + return; + } + _isPresenting = YES; + _callbackMatcher = callbackMatcher; + _completion = completion; + dispatch_async(dispatch_get_main_queue(), ^() { + self->_UIDelegate = UIDelegate ?: [FIRAuthDefaultUIDelegate defaultUIDelegate]; + if ([SFSafariViewController class]) { + self->_safariViewController = [[SFSafariViewController alloc] initWithURL:URL]; + self->_safariViewController.delegate = self; + [self->_UIDelegate presentViewController:self->_safariViewController + animated:YES + completion:nil]; + return; + } else { + self->_webViewController = [[FIRAuthWebViewController alloc] initWithURL:URL delegate:self]; + UINavigationController *navController = + [[UINavigationController alloc] initWithRootViewController:self->_webViewController]; + [self->_UIDelegate presentViewController:navController animated:YES completion:nil]; + } + }); +} + +- (BOOL)canHandleURL:(NSURL *)URL { + if (_isPresenting && _callbackMatcher && _callbackMatcher(URL)) { + [self finishPresentationWithURL:URL error:nil]; + return YES; + } + return NO; +} + +#pragma mark - SFSafariViewControllerDelegate + +- (void)safariViewControllerDidFinish:(SFSafariViewController *)controller { + dispatch_async(FIRAuthGlobalWorkQueue(), ^() { + if (controller == self->_safariViewController) { + self->_safariViewController = nil; + //TODO:Ensure that the SFSafariViewController is actually removed from the screen before + //invoking finishPresentationWithURL:error: + [self finishPresentationWithURL:nil + error:[FIRAuthErrorUtils webContextCancelledErrorWithMessage:nil]]; + } + }); +} + +#pragma mark - FIRAuthwebViewControllerDelegate + +- (BOOL)webViewController:(FIRAuthWebViewController *)webViewController canHandleURL:(NSURL *)URL { + __block BOOL result = NO; + dispatch_sync(FIRAuthGlobalWorkQueue(), ^() { + if (webViewController == self->_webViewController) { + result = [self canHandleURL:URL]; + } + }); + return result; +} + +- (void)webViewControllerDidCancel:(FIRAuthWebViewController *)webViewController { + dispatch_async(FIRAuthGlobalWorkQueue(), ^() { + if (webViewController == self->_webViewController) { + [self finishPresentationWithURL:nil + error:[FIRAuthErrorUtils webContextCancelledErrorWithMessage:nil]]; + } + }); +} + +- (void)webViewController:(FIRAuthWebViewController *)webViewController + didFailWithError:(NSError *)error { + dispatch_async(FIRAuthGlobalWorkQueue(), ^() { + if (webViewController == self->_webViewController) { + [self finishPresentationWithURL:nil error:error]; + } + }); +} + +#pragma mark - Private methods + +/** @fn finishPresentationWithURL:error: + @brief Finishes the presentation for a given URL, if any. + @param URL The URL to finish presenting. + @param error The error with which to finish presenting, if any. + */ +- (void)finishPresentationWithURL:(nullable NSURL *)URL + error:(nullable NSError *)error { + _callbackMatcher = nil; + id UIDelegate = _UIDelegate; + _UIDelegate = nil; + FIRAuthURLPresentationCompletion completion = _completion; + _completion = nil; + void (^finishBlock)(void) = ^() { + self->_isPresenting = NO; + completion(URL, error); + }; + SFSafariViewController *safariViewController = _safariViewController; + _safariViewController = nil; + FIRAuthWebViewController *webViewController = _webViewController; + _webViewController = nil; + if (safariViewController || webViewController) { + dispatch_async(dispatch_get_main_queue(), ^() { + [UIDelegate dismissViewControllerAnimated:YES completion:^() { + dispatch_async(FIRAuthGlobalWorkQueue(), finishBlock); + }]; + }); + } else { + finishBlock(); + } +} + +#pragma clang diagnostic pop // ignored "-Wunguarded-availability" + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthWebUtils.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthWebUtils.h new file mode 100644 index 0000000..ebf464d --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthWebUtils.h @@ -0,0 +1,101 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FIRAuthRequestConfiguration; + +NS_ASSUME_NONNULL_BEGIN + +/** @typedef FIRFetchAuthDomainCallback + @brief The callback invoked at the end of the flow to fetch the Auth domain. + @param authDomain The Auth domain. + @param error The error that occurred while fetching the auth domain, if any. + */ +typedef void (^FIRFetchAuthDomainCallback)(NSString *_Nullable authDomain, + NSError *_Nullable error); + +/** @class FIRAuthURLUtils + @brief A utility class used to facilitate the creation of auth related URLs. + */ +@interface FIRAuthWebUtils : NSObject + +/** @fn randomStringWithLength: + @brief Generates a random string of a specified length. + */ ++ (NSString *)randomStringWithLength:(NSUInteger)length; + +/** @fn isCallbackSchemeRegisteredForCustomURLScheme: + @brief Checks whether or not the provided custom URL scheme has been registered by the app. + @param URLScheme The custom URL scheme to be checked against all custom URL schemes registered by the app. + @return whether or not the provided custom URL scheme has been registered by the app. + */ ++ (BOOL)isCallbackSchemeRegisteredForCustomURLScheme:(NSString *)URLScheme; + +/** @fn isExpectedCallbackURL:eventID:authType + @brief Parses a URL into all available query items. + @param URL The actual callback URL. + @param eventID The expected event ID. + @param authType The expected auth type. + @param callbackScheme The expected callback custom scheme. + @return Whether or not the actual callback URL matches the expected callback URL. + */ ++ (BOOL)isExpectedCallbackURL:(nullable NSURL *)URL + eventID:(NSString *)eventID + authType:(NSString *)authType + callbackScheme:(NSString *)callbackScheme; + +/** @fn fetchAuthDomainWithCompletion:completion: + @brief Fetches the auth domain associated with the Firebase Project. + @param completion The callback invoked after the auth domain has been constructed or an error + has been encountered. + */ ++ (void)fetchAuthDomainWithRequestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + completion:(FIRFetchAuthDomainCallback)completion; + +/** @fn queryItemValue:from: + @brief Utility function to get a value from a NSURLQueryItem array. + @param name The key. + @param queryList The NSURLQueryItem array. + @return The value for the key. + */ + ++ (nullable NSString *)queryItemValue:(NSString *)name from:(NSArray *)queryList; + +/** @fn dictionaryWithHttpArgumentsString: + @brief Utility function to get a dictionary from a http argument string. + @param argString The http argument string. + @return The resulting dictionary of query arguments. + */ ++ (NSDictionary *)dictionaryWithHttpArgumentsString:(NSString *)argString; + +/** @fn stringByUnescapingFromURLArgument:from: + @brief Utility function to get a string by unescapting URL arguments. + @param argument The argument string. + @return The resulting string after unescaping URL argument. + */ ++ (NSString *)stringByUnescapingFromURLArgument:(NSString *)argument; + +/** @fn parseURL: + @brief Parses an incoming URL into all available query items. + @param urlString The url to be parsed. + @return A dictionary of available query items in the target URL. + */ ++ (NSDictionary *)parseURL:(NSString *)urlString; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthWebUtils.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthWebUtils.m new file mode 100644 index 0000000..f5f50da --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthWebUtils.m @@ -0,0 +1,203 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRAuthWebUtils.h" + +#import "FIRAuthBackend.h" +#import "FIRAuthErrorUtils.h" +#import "FIRGetProjectConfigRequest.h" +#import "FIRGetProjectConfigResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRAuthWebUtils + ++ (NSArray *)supportedAuthDomains { + return @[@"firebaseapp.com", @"web.app"]; +} + ++ (NSString *)randomStringWithLength:(NSUInteger)length { + NSMutableString *randomString = [[NSMutableString alloc] init]; + for (int i=0; i < length; i++) { + [randomString appendString: + [NSString stringWithFormat:@"%c", 'a' + arc4random_uniform('z' - 'a' + 1)]]; + } + return randomString; +} + ++ (BOOL)isCallbackSchemeRegisteredForCustomURLScheme:(NSString *)URLScheme { + NSString *expectedCustomScheme = [URLScheme lowercaseString]; + NSArray *urlTypes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleURLTypes"]; + for (NSDictionary *urlType in urlTypes) { + NSArray *urlTypeSchemes = urlType[@"CFBundleURLSchemes"]; + for (NSString *urlTypeScheme in urlTypeSchemes) { + if ([urlTypeScheme.lowercaseString isEqualToString:expectedCustomScheme]) { + return YES; + } + } + } + return NO; +} + ++ (BOOL)isExpectedCallbackURL:(nullable NSURL *)URL + eventID:(NSString *)eventID + authType:(NSString *)authType + callbackScheme:(NSString *)callbackScheme { + if (!URL) { + return NO; + } + NSURLComponents *actualURLComponents = + [NSURLComponents componentsWithURL:URL resolvingAgainstBaseURL:NO]; + actualURLComponents.query = nil; + actualURLComponents.fragment = nil; + + NSURLComponents *expectedURLComponents = [[NSURLComponents alloc] init]; + expectedURLComponents.scheme = callbackScheme; + expectedURLComponents.host = @"firebaseauth"; + expectedURLComponents.path = @"/link"; + + if (![expectedURLComponents.URL isEqual:actualURLComponents.URL]) { + return NO; + } + NSDictionary *URLQueryItems = + [self dictionaryWithHttpArgumentsString:URL.query]; + NSURL *deeplinkURL = [NSURL URLWithString:URLQueryItems[@"deep_link_id"]]; + NSDictionary *deeplinkQueryItems = + [self dictionaryWithHttpArgumentsString:deeplinkURL.query]; + if ([deeplinkQueryItems[@"authType"] isEqualToString:authType] && + [deeplinkQueryItems[@"eventId"] isEqualToString:eventID]) { + return YES; + } + return NO; +} + ++ (void)fetchAuthDomainWithRequestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + completion:(FIRFetchAuthDomainCallback)completion { + FIRGetProjectConfigRequest *request = + [[FIRGetProjectConfigRequest alloc] initWithRequestConfiguration:requestConfiguration]; + + [FIRAuthBackend getProjectConfig:request + callback:^(FIRGetProjectConfigResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + completion(nil, error); + return; + } + // Look up an authorized domain ends with one of the supportedAuthDomains. + // The sequence of supportedAuthDomains matters. ("firebaseapp.com", "web.app") + // The searching ends once the first valid suportedAuthDomain is found. + NSString *authDomain; + for (NSString *domain in response.authorizedDomains) { + for (NSString *suportedAuthDomain in [self supportedAuthDomains]) { + NSInteger index = domain.length - suportedAuthDomain.length; + if (index >= 2) { + if ([domain hasSuffix:suportedAuthDomain] && domain.length >= suportedAuthDomain.length + 2) { + authDomain = domain; + break; + } + } + } + if (authDomain != nil) { + break; + } + } + if (!authDomain.length) { + completion(nil, [FIRAuthErrorUtils unexpectedErrorResponseWithDeserializedResponse:response]); + return; + } + completion(authDomain, nil); + }]; +} + +/** @fn queryItemValue:from: + @brief Utility function to get a value from a NSURLQueryItem array. + @param name The key. + @param queryList The NSURLQueryItem array. + @return The value for the key. + */ ++ (nullable NSString *)queryItemValue:(NSString *)name from:(NSArray *)queryList { + for (NSURLQueryItem *item in queryList) { + if ([item.name isEqualToString:name]) { + return item.value; + } + } + return nil; +} + ++ (NSDictionary *)dictionaryWithHttpArgumentsString:(NSString *)argString { + NSMutableDictionary* ret = [NSMutableDictionary dictionary]; + NSArray* components = [argString componentsSeparatedByString:@"&"]; + NSString* component; + // Use reverse order so that the first occurrence of a key replaces + // those subsequent. + for (component in [components reverseObjectEnumerator]) { + if (component.length == 0) + continue; + NSRange pos = [component rangeOfString:@"="]; + NSString *key; + NSString *val; + if (pos.location == NSNotFound) { + key = [self stringByUnescapingFromURLArgument:component]; + val = @""; + } else { + key = [self stringByUnescapingFromURLArgument:[component substringToIndex:pos.location]]; + val = [self stringByUnescapingFromURLArgument: + [component substringFromIndex:pos.location + pos.length]]; + } + // returns nil on invalid UTF8 and NSMutableDictionary raises an exception when passed nil + // values. + if (!key) key = @""; + if (!val) val = @""; + [ret setObject:val forKey:key]; + } + return ret; +} + ++ (NSString *)stringByUnescapingFromURLArgument:(NSString *)argument { + NSMutableString *resultString = [NSMutableString stringWithString:argument]; + [resultString replaceOccurrencesOfString:@"+" + withString:@" " + options:NSLiteralSearch + range:NSMakeRange(0, [resultString length])]; + return [resultString stringByRemovingPercentEncoding]; +} + ++ (NSDictionary *)parseURL:(NSString *)urlString { + NSString *linkURL = [NSURLComponents componentsWithString:urlString].query; + if (!linkURL) { + return @{}; + } + NSArray *URLComponents = [linkURL componentsSeparatedByString:@"&"]; + NSMutableDictionary *queryItems = + [[NSMutableDictionary alloc] initWithCapacity:URLComponents.count]; + for (NSString *component in URLComponents) { + NSRange equalRange = [component rangeOfString:@"="]; + if (equalRange.location != NSNotFound) { + NSString *queryItemKey = + [[component substringToIndex:equalRange.location] stringByRemovingPercentEncoding]; + NSString *queryItemValue = + [[component substringFromIndex:equalRange.location + 1] stringByRemovingPercentEncoding]; + if (queryItemKey && queryItemValue) { + queryItems[queryItemKey] = queryItemValue; + } + } + } + return queryItems; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthWebView.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthWebView.h new file mode 100644 index 0000000..4b7a583 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthWebView.h @@ -0,0 +1,44 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#if !TARGET_OS_OSX && !TARGET_OS_TV + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRAuthWebView + @brief A class reponsible for creating a WKWebView for use within Firebase Auth. + */ +@interface FIRAuthWebView : UIView + +/** @property webView + * @brief The web view. + */ +@property(nonatomic, weak) WKWebView *webView; + +/** @property spinner + * @brief The spinner that indicates web view loading. + */ +@property(nonatomic, weak) UIActivityIndicatorView *spinner; + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthWebView.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthWebView.m new file mode 100644 index 0000000..7b3a9ed --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthWebView.m @@ -0,0 +1,100 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#if !TARGET_OS_OSX && !TARGET_OS_TV + +#import "FIRAuthWebView.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRAuthWebView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor whiteColor]; + [self initializeSubviews]; + } + return self; +} + +/** @fn initializeSubviews + @brief Initializes the subviews of this view. + */ +- (void)initializeSubviews { + WKWebView *webView = [self createWebView]; + UIActivityIndicatorView *spinner = [self createSpinner]; + + // The order of the following controls z-order. + [self addSubview:webView]; + [self addSubview:spinner]; + + [self layoutSubviews]; + _webView = webView; + _spinner = spinner; +} + +- (void)layoutSubviews { + CGFloat height = self.bounds.size.height; + CGFloat width = self.bounds.size.width; + _webView.frame = CGRectMake(0, 0, width, height); + _spinner.center = _webView.center; +} + +/** @fn createWebView + @brief Creates a web view to be used by this view. + @return The newly created web view. + */ +- (WKWebView *)createWebView { + WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectZero]; + // Trickery to make the web view not do weird things (like showing a black background when + // the prompt in the navigation bar animates changes.) + webView.opaque = NO; + webView.backgroundColor = [UIColor clearColor]; + webView.scrollView.opaque = NO; + webView.scrollView.backgroundColor = [UIColor clearColor]; + webView.scrollView.bounces = NO; + webView.scrollView.alwaysBounceVertical = NO; + webView.scrollView.alwaysBounceHorizontal = NO; + return webView; +} + +/** @fn createSpinner + @brief Creates a spinner to be used by this view. + @return The newly created spinner. + */ +- (UIActivityIndicatorView *)createSpinner { + UIActivityIndicatorViewStyle spinnerStyle; +#if defined(TARGET_OS_MACCATALYST) + if (@available(iOS 13.0, *)) { + spinnerStyle = UIActivityIndicatorViewStyleMedium; + } else { + spinnerStyle = UIActivityIndicatorViewStyleGray; + } +#else + spinnerStyle = UIActivityIndicatorViewStyleGray; +#endif + UIActivityIndicatorView *spinner = + [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:spinnerStyle]; + return spinner; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthWebViewController.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthWebViewController.h new file mode 100644 index 0000000..25926c5 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthWebViewController.h @@ -0,0 +1,78 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#if !TARGET_OS_OSX && !TARGET_OS_TV + +#import + +@class FIRAuthWebViewController; + +NS_ASSUME_NONNULL_BEGIN + +/** @protocol FIRAuthWebViewControllerDelegate + @brief Defines a delegate for FIRAuthWebViewController + */ +@protocol FIRAuthWebViewControllerDelegate + +/** @fn webViewController:canHandleURL: + @brief Determines if a URL should be handled by the delegate. + @param URL The URL to handle. + @return Whether the URL could be handled or not. + */ +- (BOOL)webViewController:(FIRAuthWebViewController *)webViewController canHandleURL:(NSURL *)URL; + +/** @fn webViewControllerDidCancel: + @brief Notifies the delegate that the web view controller is being cancelled by the user. + @param webViewController The web view controller in question. + */ +- (void)webViewControllerDidCancel:(FIRAuthWebViewController *)webViewController; + +/** @fn webViewController:didFailWithError: + @brief Notifies the delegate that the web view controller failed to load a page. + @param webViewController The web view controller in question. + @param error The error that has occurred. + */ +- (void)webViewController:(FIRAuthWebViewController *)webViewController + didFailWithError:(NSError *)error; + +@end + +/** @class FIRAuthWebViewController + @brief Reponsible for creating a UIViewController for presenting a FIRAutWebView. + */ +@interface FIRAuthWebViewController : UIViewController + +/** @fn initWithNibName:bundle: + * @brief Please call initWithURL:delegate: + */ +- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil + bundle:(nullable NSBundle *)nibBundleOrNil NS_UNAVAILABLE; + +/** @fn initWithCoder: + * @brief Please call initWithURL:delegate: + */ +- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE; + +- (instancetype)initWithURL:(NSURL *)URL + delegate:(__weak id)delegate + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthWebViewController.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthWebViewController.m new file mode 100644 index 0000000..163bfe8 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthWebViewController.m @@ -0,0 +1,119 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#if !TARGET_OS_OSX && !TARGET_OS_TV + +#import "FIRAuthWebViewController.h" + +#import "FIRAuthWebView.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRAuthWebViewController () +@end + +@implementation FIRAuthWebViewController { + /** @var _URL + @brief The initial URL to display. + */ + NSURL *_URL; + + /** @var _delegate + @brief The delegate to call. + */ + __weak id _delegate; + + /** @var _webView; + @brief The web view instance for easier access. + */ + __weak FIRAuthWebView *_webView; +} + +- (instancetype)initWithURL:(NSURL *)URL + delegate:(__weak id)delegate { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _URL = URL; + _delegate = delegate; + } + return self; +} + +#pragma mark - Lifecycle + +- (void)loadView { + FIRAuthWebView *webView = [[FIRAuthWebView alloc] initWithFrame:[UIScreen mainScreen].bounds]; + webView.webView.navigationDelegate = self; + self.view = webView; + _webView = webView; + self.navigationItem.leftBarButtonItem = + [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel + target:self + action:@selector(cancel)]; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + // Loads the requested URL in the web view. + [_webView.webView loadRequest:[NSURLRequest requestWithURL:_URL]]; +} + +#pragma mark - UI Targets + +- (void)cancel { + [_delegate webViewControllerDidCancel:self]; +} + +#pragma mark - WKNavigationDelegate + +- (void)webView:(WKWebView *)webView + decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction + decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { + [_delegate webViewController:self canHandleURL:navigationAction.request.URL]; + decisionHandler(WKNavigationActionPolicyAllow); +} + +- (void)webView:(WKWebView *)webView +didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation { + _webView.spinner.hidden = NO; + [_webView.spinner startAnimating]; +} + +- (void)webView:(WKWebView *)webView +didFinishNavigation:(null_unspecified WKNavigation *)navigation { + _webView.spinner.hidden = YES; + [_webView.spinner stopAnimating]; +} + +- (void)webView:(WKWebView *)webView +didFailNavigation:(null_unspecified WKNavigation *)navigation + withError:(NSError *)error { + if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) { + // It's okay for the page to be redirected before it is completely loaded. See b/32028062 . + return; + } + // Forward notification to our delegate. + [self webView:webView didFinishNavigation:navigation]; + [_delegate webViewController:self didFailWithError:error]; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/NSData+FIRBase64.h b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/NSData+FIRBase64.h new file mode 100644 index 0000000..114cbfd --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/NSData+FIRBase64.h @@ -0,0 +1,31 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSData (FIRBase64) + +/** @fn fir_base64URLEncodedStringWithOptions: + @brief Get a web safe base64 encoded string + @param options The base64 encoding options + */ +- (NSString *)fir_base64URLEncodedStringWithOptions:(NSDataBase64EncodingOptions)options; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/NSData+FIRBase64.m b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/NSData+FIRBase64.m new file mode 100644 index 0000000..b53f053 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/NSData+FIRBase64.m @@ -0,0 +1,33 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "NSData+FIRBase64.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation NSData (FIRBase64) + +- (NSString *)fir_base64URLEncodedStringWithOptions:(NSDataBase64EncodingOptions)options { + NSString *string = [self base64EncodedStringWithOptions:options]; + string = [string stringByReplacingOccurrencesOfString:@"/" withString:@"_"]; + string = [string stringByReplacingOccurrencesOfString:@"+" withString:@"-"]; + string = [string stringByReplacingOccurrencesOfString:@"=" withString:@""]; + return string; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseAuth/LICENSE b/!main project/Pods/FirebaseAuth/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/!main project/Pods/FirebaseAuth/README.md b/!main project/Pods/FirebaseAuth/README.md new file mode 100644 index 0000000..5097a89 --- /dev/null +++ b/!main project/Pods/FirebaseAuth/README.md @@ -0,0 +1,254 @@ +# Firebase iOS Open Source Development [![Build Status](https://travis-ci.org/firebase/firebase-ios-sdk.svg?branch=master)](https://travis-ci.org/firebase/firebase-ios-sdk) + +This repository contains a subset of the Firebase iOS SDK source. It currently +includes FirebaseCore, FirebaseABTesting, FirebaseAuth, FirebaseDatabase, +FirebaseFirestore, FirebaseFunctions, FirebaseInstanceID, FirebaseInAppMessaging, +FirebaseInAppMessagingDisplay, FirebaseMessaging, FirebaseRemoteConfig, and +FirebaseStorage. + +The repository also includes GoogleUtilities source. The +[GoogleUtilities](GoogleUtilities/README.md) pod is +a set of utilities used by Firebase and other Google products. + +Firebase is an app development platform with tools to help you build, grow and +monetize your app. More information about Firebase can be found at +[https://firebase.google.com](https://firebase.google.com). + +## Installation + +See the three subsections for details about three different installation methods. +1. [Standard pod install](README.md#standard-pod-install) +1. [Installing from the GitHub repo](README.md#installing-from-github) +1. [Experimental Carthage](README.md#carthage-ios-only) + +### Standard pod install + +Go to +[https://firebase.google.com/docs/ios/setup](https://firebase.google.com/docs/ios/setup). + +### Installing from GitHub + +For releases starting with 5.0.0, the source for each release is also deployed +to CocoaPods master and available via standard +[CocoaPods Podfile syntax](https://guides.cocoapods.org/syntax/podfile.html#pod). + +These instructions can be used to access the Firebase repo at other branches, +tags, or commits. + +#### Background + +See +[the Podfile Syntax Reference](https://guides.cocoapods.org/syntax/podfile.html#pod) +for instructions and options about overriding pod source locations. + +#### Accessing Firebase Source Snapshots + +All of the official releases are tagged in this repo and available via CocoaPods. To access a local +source snapshot or unreleased branch, use Podfile directives like the following: + +To access FirebaseFirestore via a branch: +``` +pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +``` + +To access FirebaseMessaging via a checked out version of the firebase-ios-sdk repo do: + +``` +pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk' +pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk' +``` + +### Carthage (iOS only) + +Instructions for the experimental Carthage distribution are at +[Carthage](Carthage.md). + +### Rome + +Instructions for installing binary frameworks via +[Rome](https://github.com/CocoaPods/Rome) are at [Rome](Rome.md). + +## Development + +To develop Firebase software in this repository, ensure that you have at least +the following software: + + * Xcode 10.1 (or later) + * CocoaPods 1.7.2 (or later) + * [CocoaPods generate](https://github.com/square/cocoapods-generate) + +For the pod that you want to develop: + +`pod gen Firebase{name here}.podspec --local-sources=./ --auto-open --platforms=ios` + +Note: If the CocoaPods cache is out of date, you may need to run +`pod repo update` before the `pod gen` command. + +Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for +those platforms. Since 10.2, Xcode does not properly handle multi-platform +CocoaPods workspaces. + +Firestore has a self contained Xcode project. See +[Firestore/README.md](Firestore/README.md). + +### Development for Catalyst +* `pod gen {name here}.podspec --local-sources=./ --auto-open --platforms=ios` +* Check the Mac box in the App-iOS Build Settings +* Sign the App in the Settings Signing & Capabilities tab +* Click Pods in the Project Manager +* Add Signing to the iOS host app and unit test targets +* Select the Unit-unit scheme +* Run it to build and test + +### Adding a New Firebase Pod + +See [AddNewPod.md](AddNewPod.md). + +### Code Formatting + +To ensure that the code is formatted consistently, run the script +[./scripts/style.sh](https://github.com/firebase/firebase-ios-sdk/blob/master/scripts/style.sh) +before creating a PR. + +Travis will verify that any code changes are done in a style compliant way. Install +`clang-format` and `swiftformat`. +These commands will get the right versions: + +``` +brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/e3496d9/Formula/clang-format.rb +brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/7963c3d/Formula/swiftformat.rb +``` + +Note: if you already have a newer version of these installed you may need to +`brew switch` to this version. + +To update this section, find the versions of clang-format and swiftformat.rb to +match the versions in the CI failure logs +[here](https://github.com/Homebrew/homebrew-core/tree/master/Formula). + +### Running Unit Tests + +Select a scheme and press Command-u to build a component and run its unit tests. + +#### Viewing Code Coverage + +First, make sure that [xcov](https://github.com/nakiostudio/xcov) is installed with `gem install xcov`. + +After running the `AllUnitTests_iOS` scheme in Xcode, execute +`xcov --workspace Firebase.xcworkspace --scheme AllUnitTests_iOS --output_directory xcov_output` +at Example/ in the terminal. This will aggregate the coverage, and you can run `open xcov_output/index.html` to see the results. + +### Running Sample Apps +In order to run the sample apps and integration tests, you'll need valid +`GoogleService-Info.plist` files for those samples. The Firebase Xcode project contains dummy plist +files without real values, but can be replaced with real plist files. To get your own +`GoogleService-Info.plist` files: + +1. Go to the [Firebase Console](https://console.firebase.google.com/) +2. Create a new Firebase project, if you don't already have one +3. For each sample app you want to test, create a new Firebase app with the sample app's bundle +identifier (e.g. `com.google.Database-Example`) +4. Download the resulting `GoogleService-Info.plist` and replace the appropriate dummy plist file +(e.g. in [Example/Database/App/](Example/Database/App/)); + +Some sample apps like Firebase Messaging ([Example/Messaging/App](Example/Messaging/App)) require +special Apple capabilities, and you will have to change the sample app to use a unique bundle +identifier that you can control in your own Apple Developer account. + +## Specific Component Instructions +See the sections below for any special instructions for those components. + +### Firebase Auth + +If you're doing specific Firebase Auth development, see +[the Auth Sample README](Example/Auth/README.md) for instructions about +building and running the FirebaseAuth pod along with various samples and tests. + +### Firebase Database + +To run the Database Integration tests, make your database authentication rules +[public](https://firebase.google.com/docs/database/security/quickstart). + +### Firebase Storage + +To run the Storage Integration tests, follow the instructions in +[FIRStorageIntegrationTests.m](Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m). + +#### Push Notifications + +Push notifications can only be delivered to specially provisioned App IDs in the developer portal. +In order to actually test receiving push notifications, you will need to: + +1. Change the bundle identifier of the sample app to something you own in your Apple Developer +account, and enable that App ID for push notifications. +2. You'll also need to +[upload your APNs Provider Authentication Key or certificate to the Firebase Console](https://firebase.google.com/docs/cloud-messaging/ios/certs) +at **Project Settings > Cloud Messaging > [Your Firebase App]**. +3. Ensure your iOS device is added to your Apple Developer portal as a test device. + +#### iOS Simulator + +The iOS Simulator cannot register for remote notifications, and will not receive push notifications. +In order to receive push notifications, you'll have to follow the steps above and run the app on a +physical device. + +## Community Supported Efforts + +We've seen an amazing amount of interest and contributions to improve the Firebase SDKs, and we are +very grateful! We'd like to empower as many developers as we can to be able to use Firebase and +participate in the Firebase community. + +### tvOS, macOS, and Catalyst +Thanks to contributions from the community, many of Firebase SDKs now compile, run unit tests, and work on +tvOS, macOS, and Catalyst. + +For tvOS, checkout the [Sample](Example/tvOSSample). + +Keep in mind that macOS, Catalyst and tvOS are not officially supported by Firebase, and this +repository is actively developed primarily for iOS. While we can catch basic unit test issues with +Travis, there may be some changes where the SDK no longer works as expected on macOS or tvOS. If you +encounter this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues). + +During app setup in the console, you may get to a step that mentions something like "Checking if the app +has communicated with our servers". This relies on Analytics and will not work on macOS/tvOS/Catalyst. +**It's safe to ignore the message and continue**, the rest of the SDKs will work as expected. + +To install, add a subset of the following to the Podfile: + +``` +pod 'Firebase/ABTesting' +pod 'Firebase/Auth' +pod 'Firebase/Crashlytics' +pod 'Firebase/Database' +pod 'Firebase/Firestore' +pod 'Firebase/Functions' +pod 'Firebase/Messaging' +pod 'Firebase/RemoteConfig' +pod 'Firebase/Storage' +``` + +#### Additional Catalyst Notes + +* FirebaseAuth and FirebaseMessaging require adding `Keychain Sharing Capability` +to Build Settings. +* FirebaseFirestore requires signing the +[gRPC Resource target](https://github.com/firebase/firebase-ios-sdk/issues/3500#issuecomment-518741681). + +## Roadmap + +See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source +plans and directions. + +## Contributing + +See [Contributing](CONTRIBUTING.md) for more information on contributing to the Firebase +iOS SDK. + +## License + +The contents of this repository is licensed under the +[Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0). + +Your use of Firebase is governed by the +[Terms of Service for Firebase Services](https://firebase.google.com/terms/). diff --git a/!main project/Pods/FirebaseAuthInterop/Interop/Auth/Public/FIRAuthInterop.h b/!main project/Pods/FirebaseAuthInterop/Interop/Auth/Public/FIRAuthInterop.h new file mode 100644 index 0000000..5c365a3 --- /dev/null +++ b/!main project/Pods/FirebaseAuthInterop/Interop/Auth/Public/FIRAuthInterop.h @@ -0,0 +1,42 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRAuthInterop_h +#define FIRAuthInterop_h + +NS_ASSUME_NONNULL_BEGIN + +/** @typedef FIRTokenCallback + @brief The type of block which gets called when a token is ready. + */ +typedef void (^FIRTokenCallback)(NSString *_Nullable token, NSError *_Nullable error) + NS_SWIFT_NAME(TokenCallback); + +/// Common methods for Auth interoperability. +NS_SWIFT_NAME(AuthInterop) +@protocol FIRAuthInterop + +/// Retrieves the Firebase authentication token, possibly refreshing it if it has expired. +- (void)getTokenForcingRefresh:(BOOL)forceRefresh withCallback:(FIRTokenCallback)callback; + +/// Get the current Auth user's UID. Returns nil if there is no user signed in. +- (nullable NSString *)getUserID; + +@end + +NS_ASSUME_NONNULL_END + +#endif /* FIRAuthInterop_h */ diff --git a/!main project/Pods/FirebaseAuthInterop/LICENSE b/!main project/Pods/FirebaseAuthInterop/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/!main project/Pods/FirebaseAuthInterop/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/!main project/Pods/FirebaseAuthInterop/README.md b/!main project/Pods/FirebaseAuthInterop/README.md new file mode 100644 index 0000000..4414b3e --- /dev/null +++ b/!main project/Pods/FirebaseAuthInterop/README.md @@ -0,0 +1,179 @@ +# Firebase iOS Open Source Development [![Build Status](https://travis-ci.org/firebase/firebase-ios-sdk.svg?branch=master)](https://travis-ci.org/firebase/firebase-ios-sdk) + +This repository contains a subset of the Firebase iOS SDK source. It currently +includes FirebaseCore, FirebaseAuth, FirebaseDatabase, FirebaseFirestore, +FirebaseFunctions, FirebaseMessaging and FirebaseStorage. + +The repository also includes GoogleUtilities source. The +[GoogleUtilities](GoogleUtilities/README.md) pod is +a set of utilities used by Firebase and other Google products. + +Firebase is an app development platform with tools to help you build, grow and +monetize your app. More information about Firebase can be found at +[https://firebase.google.com](https://firebase.google.com). + +## Installation + +See the three subsections for details about three different installation methods. +1. [Standard pod install](README.md#standard-pod-install) +1. [Installing from the GitHub repo](README.md#installing-from-github) +1. [Experimental Carthage](README.md#carthage-ios-only) + +### Standard pod install + +Go to +[https://firebase.google.com/docs/ios/setup](https://firebase.google.com/docs/ios/setup). + +### Installing from GitHub + +For releases starting with 5.0.0, the source for each release is also deployed +to CocoaPods master and available via standard +[CocoaPods Podfile syntax](https://guides.cocoapods.org/syntax/podfile.html#pod). + +These instructions can be used to access the Firebase repo at other branches, +tags, or commits. + +#### Background + +See +[the Podfile Syntax Reference](https://guides.cocoapods.org/syntax/podfile.html#pod) +for instructions and options about overriding pod source locations. + +#### Accessing Firebase Source Snapshots + +All of the official releases are tagged in this repo and available via CocoaPods. To access a local +source snapshot or unreleased branch, use Podfile directives like the following: + +To access FirebaseFirestore via a branch: +``` +pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +``` + +To access FirebaseMessaging via a checked out version of the firebase-ios-sdk repo do: + +``` +pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk' +pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk' +``` + +### Carthage (iOS only) + +An experimental Carthage distribution is now available. See +[Carthage](Carthage.md). + +## Development + +Follow the subsequent instructions to develop, debug, unit test, run integration +tests, and try out reference samples: + +``` +$ git clone git@github.com:firebase/firebase-ios-sdk.git +$ cd firebase-ios-sdk/Example +$ pod update +$ open Firebase.xcworkspace +``` + +Firestore and Functions have self contained Xcode projects. See +[Firestore/README.md](Firestore/README.md) and +[Functions/README.md](Functions/README.md). + +### Running Unit Tests + +Select a scheme and press Command-u to build a component and run its unit tests. + +### Running Sample Apps +In order to run the sample apps and integration tests, you'll need valid +`GoogleService-Info.plist` files for those samples. The Firebase Xcode project contains dummy plist +files without real values, but can be replaced with real plist files. To get your own +`GoogleService-Info.plist` files: + +1. Go to the [Firebase Console](https://console.firebase.google.com/) +2. Create a new Firebase project, if you don't already have one +3. For each sample app you want to test, create a new Firebase app with the sample app's bundle +identifier (e.g. `com.google.Database-Example`) +4. Download the resulting `GoogleService-Info.plist` and replace the appropriate dummy plist file +(e.g. in [Example/Database/App/](Example/Database/App/)); + +Some sample apps like Firebase Messaging ([Example/Messaging/App](Example/Messaging/App)) require +special Apple capabilities, and you will have to change the sample app to use a unique bundle +identifier that you can control in your own Apple Developer account. + +## Specific Component Instructions +See the sections below for any special instructions for those components. + +### Firebase Auth + +If you're doing specific Firebase Auth development, see +[AuthSamples/README.md](AuthSamples/README.md) for instructions about +building and running the FirebaseAuth pod along with various samples and tests. + +### Firebase Database + +To run the Database Integration tests, make your database authentication rules +[public](https://firebase.google.com/docs/database/security/quickstart). + +### Firebase Storage + +To run the Storage Integration tests, follow the instructions in +[FIRStorageIntegrationTests.m](Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m). + +#### Push Notifications + +Push notifications can only be delivered to specially provisioned App IDs in the developer portal. +In order to actually test receiving push notifications, you will need to: + +1. Change the bundle identifier of the sample app to something you own in your Apple Developer +account, and enable that App ID for push notifications. +2. You'll also need to +[upload your APNs Provider Authentication Key or certificate to the Firebase Console](https://firebase.google.com/docs/cloud-messaging/ios/certs) +at **Project Settings > Cloud Messaging > [Your Firebase App]**. +3. Ensure your iOS device is added to your Apple Developer portal as a test device. + +#### iOS Simulator + +The iOS Simulator cannot register for remote notifications, and will not receive push notifications. +In order to receive push notifications, you'll have to follow the steps above and run the app on a +physical device. + +## Community Supported Efforts + +We've seen an amazing amount of interest and contributions to improve the Firebase SDKs, and we are +very grateful! We'd like to empower as many developers as we can to be able to use Firebase and +participate in the Firebase community. + +### macOS and tvOS +FirebaseAuth, FirebaseCore, FirebaseDatabase and FirebaseStorage now compile, run unit tests, and +work on macOS and tvOS, thanks to contributions from the community. There are a few tweaks needed, +like ensuring iOS-only, macOS-only, or tvOS-only code is correctly guarded with checks for +`TARGET_OS_IOS`, `TARGET_OS_OSX` and `TARGET_OS_TV`. + +For tvOS, checkout the [Sample](Example/tvOSSample). + +Keep in mind that macOS and tvOS are not officially supported by Firebase, and this repository is +actively developed primarily for iOS. While we can catch basic unit test issues with Travis, there +may be some changes where the SDK no longer works as expected on macOS or tvOS. If you encounter +this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues). + +For installation instructions, see [above](README.md#accessing-firebase-source-snapshots). + +Note that the Firebase pod is not available for macOS and tvOS. Install a selection of the +`FirebaseAuth`, `FirebaseCore`, `FirebaseDatabase` and `FirebaseStorage` CocoaPods. + +## Roadmap + +See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source +plans and directions. + +## Contributing + +See [Contributing](CONTRIBUTING.md) for more information on contributing to the Firebase +iOS SDK. + +## License + +The contents of this repository is licensed under the +[Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0). + +Your use of Firebase is governed by the +[Terms of Service for Firebase Services](https://firebase.google.com/terms/). diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRAnalyticsConfiguration.m b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRAnalyticsConfiguration.m new file mode 100644 index 0000000..3a7d6de --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRAnalyticsConfiguration.m @@ -0,0 +1,62 @@ +// Copyright 2017 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +#import "FirebaseCore/Sources/Private/FIRAnalyticsConfiguration.h" + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" +@implementation FIRAnalyticsConfiguration +#pragma clang diagnostic pop + ++ (FIRAnalyticsConfiguration *)sharedInstance { + static FIRAnalyticsConfiguration *sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[FIRAnalyticsConfiguration alloc] init]; + }); + return sharedInstance; +} + +- (void)postNotificationName:(NSString *)name value:(id)value { + if (!name.length || !value) { + return; + } + [[NSNotificationCenter defaultCenter] postNotificationName:name + object:self + userInfo:@{name : value}]; +} + +- (void)setAnalyticsCollectionEnabled:(BOOL)analyticsCollectionEnabled { + [self setAnalyticsCollectionEnabled:analyticsCollectionEnabled persistSetting:YES]; +} + +- (void)setAnalyticsCollectionEnabled:(BOOL)analyticsCollectionEnabled + persistSetting:(BOOL)shouldPersist { + // Persist the measurementEnabledState. Use FIRAnalyticsEnabledState values instead of YES/NO. + FIRAnalyticsEnabledState analyticsEnabledState = + analyticsCollectionEnabled ? kFIRAnalyticsEnabledStateSetYes : kFIRAnalyticsEnabledStateSetNo; + if (shouldPersist) { + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + [userDefaults setObject:@(analyticsEnabledState) + forKey:kFIRAPersistedConfigMeasurementEnabledStateKey]; + [userDefaults synchronize]; + } + + [self postNotificationName:kFIRAnalyticsConfigurationSetEnabledNotification + value:@(analyticsCollectionEnabled)]; +} + +@end diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRApp.m b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRApp.m new file mode 100644 index 0000000..02f3ac3 --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRApp.m @@ -0,0 +1,912 @@ +// Copyright 2017 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#if __has_include() +#import +#endif + +#if __has_include() +#import +#endif + +#import + +#import "FirebaseCore/Sources/FIRBundleUtil.h" +#import "FirebaseCore/Sources/FIRVersion.h" +#import "FirebaseCore/Sources/Private/FIRAnalyticsConfiguration.h" +#import "FirebaseCore/Sources/Private/FIRAppInternal.h" +#import "FirebaseCore/Sources/Private/FIRComponentContainerInternal.h" +#import "FirebaseCore/Sources/Private/FIRConfigurationInternal.h" +#import "FirebaseCore/Sources/Private/FIRCoreDiagnosticsConnector.h" +#import "FirebaseCore/Sources/Private/FIRLibrary.h" +#import "FirebaseCore/Sources/Private/FIRLogger.h" +#import "FirebaseCore/Sources/Private/FIROptionsInternal.h" + +#import + +#import + +// The kFIRService strings are only here while transitioning CoreDiagnostics from the Analytics +// pod to a Core dependency. These symbols are not used and should be deleted after the transition. +NSString *const kFIRServiceAdMob; +NSString *const kFIRServiceAuth; +NSString *const kFIRServiceAuthUI; +NSString *const kFIRServiceCrash; +NSString *const kFIRServiceDatabase; +NSString *const kFIRServiceDynamicLinks; +NSString *const kFIRServiceFirestore; +NSString *const kFIRServiceFunctions; +NSString *const kFIRServiceInstanceID; +NSString *const kFIRServiceInvites; +NSString *const kFIRServiceMessaging; +NSString *const kFIRServiceMeasurement; +NSString *const kFIRServicePerformance; +NSString *const kFIRServiceRemoteConfig; +NSString *const kFIRServiceStorage; +NSString *const kGGLServiceAnalytics; +NSString *const kGGLServiceSignIn; + +NSString *const kFIRDefaultAppName = @"__FIRAPP_DEFAULT"; +NSString *const kFIRAppReadyToConfigureSDKNotification = @"FIRAppReadyToConfigureSDKNotification"; +NSString *const kFIRAppDeleteNotification = @"FIRAppDeleteNotification"; +NSString *const kFIRAppIsDefaultAppKey = @"FIRAppIsDefaultAppKey"; +NSString *const kFIRAppNameKey = @"FIRAppNameKey"; +NSString *const kFIRGoogleAppIDKey = @"FIRGoogleAppIDKey"; + +NSString *const kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat = + @"/google/firebase/global_data_collection_enabled:%@"; +NSString *const kFIRGlobalAppDataCollectionEnabledPlistKey = + @"FirebaseDataCollectionDefaultEnabled"; + +NSString *const kFIRAppDiagnosticsNotification = @"FIRAppDiagnosticsNotification"; + +NSString *const kFIRAppDiagnosticsConfigurationTypeKey = @"ConfigType"; +NSString *const kFIRAppDiagnosticsErrorKey = @"Error"; +NSString *const kFIRAppDiagnosticsFIRAppKey = @"FIRApp"; +NSString *const kFIRAppDiagnosticsSDKNameKey = @"SDKName"; +NSString *const kFIRAppDiagnosticsSDKVersionKey = @"SDKVersion"; + +// Auth internal notification notification and key. +NSString *const FIRAuthStateDidChangeInternalNotification = + @"FIRAuthStateDidChangeInternalNotification"; +NSString *const FIRAuthStateDidChangeInternalNotificationAppKey = + @"FIRAuthStateDidChangeInternalNotificationAppKey"; +NSString *const FIRAuthStateDidChangeInternalNotificationTokenKey = + @"FIRAuthStateDidChangeInternalNotificationTokenKey"; +NSString *const FIRAuthStateDidChangeInternalNotificationUIDKey = + @"FIRAuthStateDidChangeInternalNotificationUIDKey"; + +/** + * The URL to download plist files. + */ +static NSString *const kPlistURL = @"https://console.firebase.google.com/"; + +/** + * An array of all classes that registered as `FIRCoreConfigurable` in order to receive lifecycle + * events from Core. + */ +static NSMutableArray> *sRegisteredAsConfigurable; + +@interface FIRApp () + +#ifdef DEBUG +@property(nonatomic) BOOL alreadyOutputDataCollectionFlag; +#endif // DEBUG + +@end + +@implementation FIRApp + +// This is necessary since our custom getter prevents `_options` from being created. +@synthesize options = _options; + +static NSMutableDictionary *sAllApps; +static FIRApp *sDefaultApp; +static NSMutableDictionary *sLibraryVersions; +static dispatch_once_t sFirebaseUserAgentOnceToken; + ++ (void)configure { + FIROptions *options = [FIROptions defaultOptions]; + if (!options) { + [NSException raise:kFirebaseCoreErrorDomain + format:@"`[FIRApp configure];` (`FirebaseApp.configure()` in Swift) could not find " + @"a valid GoogleService-Info.plist in your project. Please download one " + @"from %@.", + kPlistURL]; + } + [FIRApp configureWithOptions:options]; +#if TARGET_OS_OSX || TARGET_OS_TV + FIRLogNotice(kFIRLoggerCore, @"I-COR000028", + @"tvOS and macOS SDK support is not part of the official Firebase product. " + @"Instead they are community supported. Details at " + @"https://github.com/firebase/firebase-ios-sdk/blob/master/README.md."); +#endif +} + ++ (void)configureWithOptions:(FIROptions *)options { + if (!options) { + [NSException raise:kFirebaseCoreErrorDomain + format:@"Options is nil. Please pass a valid options."]; + } + [FIRApp configureWithName:kFIRDefaultAppName options:options]; +} + ++ (NSCharacterSet *)applicationNameAllowedCharacters { + static NSCharacterSet *applicationNameAllowedCharacters; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSMutableCharacterSet *allowedNameCharacters = [NSMutableCharacterSet alphanumericCharacterSet]; + [allowedNameCharacters addCharactersInString:@"-_"]; + applicationNameAllowedCharacters = [allowedNameCharacters copy]; + }); + return applicationNameAllowedCharacters; +} + ++ (void)configureWithName:(NSString *)name options:(FIROptions *)options { + if (!name || !options) { + [NSException raise:kFirebaseCoreErrorDomain format:@"Neither name nor options can be nil."]; + } + if (name.length == 0) { + [NSException raise:kFirebaseCoreErrorDomain format:@"Name cannot be empty."]; + } + + if ([name isEqualToString:kFIRDefaultAppName]) { + if (sDefaultApp) { + // The default app already exixts. Handle duplicate `configure` calls and return. + [self appWasConfiguredTwice:sDefaultApp usingOptions:options]; + return; + } + + FIRLogDebug(kFIRLoggerCore, @"I-COR000001", @"Configuring the default app."); + } else { + // Validate the app name and ensure it hasn't been configured already. + NSCharacterSet *nameCharacters = [NSCharacterSet characterSetWithCharactersInString:name]; + + if (![[self applicationNameAllowedCharacters] isSupersetOfSet:nameCharacters]) { + [NSException raise:kFirebaseCoreErrorDomain + format:@"App name can only contain alphanumeric, " + @"hyphen (-), and underscore (_) characters"]; + } + + @synchronized(self) { + if (sAllApps && sAllApps[name]) { + // The app already exists. Handle a duplicate `configure` call and return. + [self appWasConfiguredTwice:sAllApps[name] usingOptions:options]; + return; + } + } + + FIRLogDebug(kFIRLoggerCore, @"I-COR000002", @"Configuring app named %@", name); + } + + @synchronized(self) { + FIRApp *app = [[FIRApp alloc] initInstanceWithName:name options:options]; + if (app.isDefaultApp) { + sDefaultApp = app; + } + + [FIRApp addAppToAppDictionary:app]; + + // The FIRApp instance is ready to go, `sDefaultApp` is assigned, other SDKs are now ready to be + // instantiated. + [app.container instantiateEagerComponents]; + [FIRApp sendNotificationsToSDKs:app]; + } +} + +/// Called when `configure` has been called multiple times for the same app. This can either throw +/// an exception (most cases) or ignore the duplicate configuration in situations where it's allowed +/// like an extension. ++ (void)appWasConfiguredTwice:(FIRApp *)app usingOptions:(FIROptions *)options { + // Only extensions should potentially be able to call `configure` more than once. + if (![GULAppEnvironmentUtil isAppExtension]) { + // Throw an exception since this is now an invalid state. + if (app.isDefaultApp) { + [NSException raise:kFirebaseCoreErrorDomain + format:@"Default app has already been configured."]; + } else { + [NSException raise:kFirebaseCoreErrorDomain + format:@"App named %@ has already been configured.", app.name]; + } + } + + // In an extension, the entry point could be called multiple times. As long as the options are + // identical we should allow multiple `configure` calls. + if ([options isEqual:app.options]) { + // Everything is identical but the extension's lifecycle triggered `configure` twice. + // Ignore duplicate calls and return since everything should still be in a valid state. + FIRLogDebug(kFIRLoggerCore, @"I-COR000035", + @"Ignoring second `configure` call in an extension."); + return; + } else { + [NSException raise:kFirebaseCoreErrorDomain + format:@"App named %@ has already been configured.", app.name]; + } +} + ++ (FIRApp *)defaultApp { + if (sDefaultApp) { + return sDefaultApp; + } + FIRLogError(kFIRLoggerCore, @"I-COR000003", + @"The default Firebase app has not yet been " + @"configured. Add `[FIRApp configure];` (`FirebaseApp.configure()` in Swift) to your " + @"application initialization. Read more: https://goo.gl/ctyzm8."); + return nil; +} + ++ (FIRApp *)appNamed:(NSString *)name { + @synchronized(self) { + if (sAllApps) { + FIRApp *app = sAllApps[name]; + if (app) { + return app; + } + } + FIRLogError(kFIRLoggerCore, @"I-COR000004", @"App with name %@ does not exist.", name); + return nil; + } +} + ++ (NSDictionary *)allApps { + @synchronized(self) { + if (!sAllApps) { + FIRLogError(kFIRLoggerCore, @"I-COR000005", @"No app has been configured yet."); + } + return [sAllApps copy]; + } +} + +// Public only for tests ++ (void)resetApps { + @synchronized(self) { + sDefaultApp = nil; + [sAllApps removeAllObjects]; + sAllApps = nil; + [sLibraryVersions removeAllObjects]; + sLibraryVersions = nil; + sFirebaseUserAgentOnceToken = 0; + } +} + +- (void)deleteApp:(FIRAppVoidBoolCallback)completion { + @synchronized([self class]) { + if (sAllApps && sAllApps[self.name]) { + FIRLogDebug(kFIRLoggerCore, @"I-COR000006", @"Deleting app named %@", self.name); + + // Remove all cached instances from the container before deleting the app. + [self.container removeAllCachedInstances]; + + [sAllApps removeObjectForKey:self.name]; + [self clearDataCollectionSwitchFromUserDefaults]; + if ([self.name isEqualToString:kFIRDefaultAppName]) { + sDefaultApp = nil; + } + NSDictionary *appInfoDict = @{kFIRAppNameKey : self.name}; + [[NSNotificationCenter defaultCenter] postNotificationName:kFIRAppDeleteNotification + object:[self class] + userInfo:appInfoDict]; + completion(YES); + } else { + FIRLogError(kFIRLoggerCore, @"I-COR000007", @"App does not exist."); + completion(NO); + } + } +} + ++ (void)addAppToAppDictionary:(FIRApp *)app { + if (!sAllApps) { + sAllApps = [NSMutableDictionary dictionary]; + } + if ([app configureCore]) { + sAllApps[app.name] = app; + } else { + [NSException raise:kFirebaseCoreErrorDomain + format:@"Configuration fails. It may be caused by an invalid GOOGLE_APP_ID in " + @"GoogleService-Info.plist or set in the customized options."]; + } +} + +- (instancetype)initInstanceWithName:(NSString *)name options:(FIROptions *)options { + self = [super init]; + if (self) { + _name = [name copy]; + _options = [options copy]; + _options.editingLocked = YES; + _isDefaultApp = [name isEqualToString:kFIRDefaultAppName]; + _container = [[FIRComponentContainer alloc] initWithApp:self]; + } + return self; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (BOOL)configureCore { + [self checkExpectedBundleID]; + if (![self isAppIDValid]) { + return NO; + } + + [self logCoreTelemetryIfEnabled]; + +#if TARGET_OS_IOS + // Initialize the Analytics once there is a valid options under default app. Analytics should + // always initialize first by itself before the other SDKs. + if ([self.name isEqualToString:kFIRDefaultAppName]) { + Class firAnalyticsClass = NSClassFromString(@"FIRAnalytics"); + if (firAnalyticsClass) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundeclared-selector" + SEL startWithConfigurationSelector = @selector(startWithConfiguration:options:); +#pragma clang diagnostic pop + if ([firAnalyticsClass respondsToSelector:startWithConfigurationSelector]) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [firAnalyticsClass performSelector:startWithConfigurationSelector + withObject:[FIRConfiguration sharedInstance].analyticsConfiguration + withObject:_options]; +#pragma clang diagnostic pop + } + } + } +#endif + + [self subscribeForAppDidBecomeActiveNotifications]; + + return YES; +} + +- (FIROptions *)options { + return [_options copy]; +} + +- (void)setDataCollectionDefaultEnabled:(BOOL)dataCollectionDefaultEnabled { +#ifdef DEBUG + FIRLogDebug(kFIRLoggerCore, @"I-COR000034", @"Explicitly %@ data collection flag.", + dataCollectionDefaultEnabled ? @"enabled" : @"disabled"); + self.alreadyOutputDataCollectionFlag = YES; +#endif // DEBUG + + NSString *key = + [NSString stringWithFormat:kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat, self.name]; + [[NSUserDefaults standardUserDefaults] setBool:dataCollectionDefaultEnabled forKey:key]; + + // Core also controls the FirebaseAnalytics flag, so check if the Analytics flags are set + // within FIROptions and change the Analytics value if necessary. Analytics only works with the + // default app, so return if this isn't the default app. + if (!self.isDefaultApp) { + return; + } + + // Check if the Analytics flag is explicitly set. If so, no further actions are necessary. + if ([self.options isAnalyticsCollectionExplicitlySet]) { + return; + } + + // The Analytics flag has not been explicitly set, so update with the value being set. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [[FIRAnalyticsConfiguration sharedInstance] + setAnalyticsCollectionEnabled:dataCollectionDefaultEnabled + persistSetting:NO]; +#pragma clang diagnostic pop +} + +- (BOOL)isDataCollectionDefaultEnabled { + // Check if it's been manually set before in code, and use that as the higher priority value. + NSNumber *defaultsObject = [[self class] readDataCollectionSwitchFromUserDefaultsForApp:self]; + if (defaultsObject != nil) { +#ifdef DEBUG + if (!self.alreadyOutputDataCollectionFlag) { + FIRLogDebug(kFIRLoggerCore, @"I-COR000031", @"Data Collection flag is %@ in user defaults.", + [defaultsObject boolValue] ? @"enabled" : @"disabled"); + self.alreadyOutputDataCollectionFlag = YES; + } +#endif // DEBUG + return [defaultsObject boolValue]; + } + + // Read the Info.plist to see if the flag is set. If it's not set, it should default to `YES`. + // As per the implementation of `readDataCollectionSwitchFromPlist`, it's a cached value and has + // no performance impact calling multiple times. + NSNumber *collectionEnabledPlistValue = [[self class] readDataCollectionSwitchFromPlist]; + if (collectionEnabledPlistValue != nil) { +#ifdef DEBUG + if (!self.alreadyOutputDataCollectionFlag) { + FIRLogDebug(kFIRLoggerCore, @"I-COR000032", @"Data Collection flag is %@ in plist.", + [collectionEnabledPlistValue boolValue] ? @"enabled" : @"disabled"); + self.alreadyOutputDataCollectionFlag = YES; + } +#endif // DEBUG + return [collectionEnabledPlistValue boolValue]; + } + +#ifdef DEBUG + if (!self.alreadyOutputDataCollectionFlag) { + FIRLogDebug(kFIRLoggerCore, @"I-COR000033", @"Data Collection flag is not set."); + self.alreadyOutputDataCollectionFlag = YES; + } +#endif // DEBUG + return YES; +} + +#pragma mark - private + ++ (void)sendNotificationsToSDKs:(FIRApp *)app { + // TODO: Remove this notification once all SDKs are registered with `FIRCoreConfigurable`. + NSNumber *isDefaultApp = [NSNumber numberWithBool:app.isDefaultApp]; + NSDictionary *appInfoDict = @{ + kFIRAppNameKey : app.name, + kFIRAppIsDefaultAppKey : isDefaultApp, + kFIRGoogleAppIDKey : app.options.googleAppID + }; + [[NSNotificationCenter defaultCenter] postNotificationName:kFIRAppReadyToConfigureSDKNotification + object:self + userInfo:appInfoDict]; + + // This is the new way of sending information to SDKs. + // TODO: Do we want this on a background thread, maybe? + @synchronized(self) { + for (Class library in sRegisteredAsConfigurable) { + [library configureWithApp:app]; + } + } +} + ++ (NSError *)errorForMissingOptions { + NSDictionary *errorDict = @{ + NSLocalizedDescriptionKey : + @"Unable to parse GoogleService-Info.plist in order to configure services.", + NSLocalizedRecoverySuggestionErrorKey : + @"Check formatting and location of GoogleService-Info.plist." + }; + return [NSError errorWithDomain:kFirebaseCoreErrorDomain + code:FIRErrorCodeInvalidPlistFile + userInfo:errorDict]; +} + ++ (NSError *)errorForSubspecConfigurationFailureWithDomain:(NSString *)domain + errorCode:(FIRErrorCode)code + service:(NSString *)service + reason:(NSString *)reason { + NSString *description = + [NSString stringWithFormat:@"Configuration failed for service %@.", service]; + NSDictionary *errorDict = + @{NSLocalizedDescriptionKey : description, NSLocalizedFailureReasonErrorKey : reason}; + return [NSError errorWithDomain:domain code:code userInfo:errorDict]; +} + ++ (NSError *)errorForInvalidAppID { + NSDictionary *errorDict = @{ + NSLocalizedDescriptionKey : @"Unable to validate Google App ID", + NSLocalizedRecoverySuggestionErrorKey : + @"Check formatting and location of GoogleService-Info.plist or GoogleAppID set in the " + @"customized options." + }; + return [NSError errorWithDomain:kFirebaseCoreErrorDomain + code:FIRErrorCodeInvalidAppID + userInfo:errorDict]; +} + ++ (BOOL)isDefaultAppConfigured { + return (sDefaultApp != nil); +} + ++ (void)registerLibrary:(nonnull NSString *)name withVersion:(nonnull NSString *)version { + // Create the set of characters which aren't allowed, only if this feature is used. + NSMutableCharacterSet *allowedSet = [NSMutableCharacterSet alphanumericCharacterSet]; + [allowedSet addCharactersInString:@"-_."]; + NSCharacterSet *disallowedSet = [allowedSet invertedSet]; + // Make sure the library name and version strings do not contain unexpected characters, and + // add the name/version pair to the dictionary. + if ([name rangeOfCharacterFromSet:disallowedSet].location == NSNotFound && + [version rangeOfCharacterFromSet:disallowedSet].location == NSNotFound) { + @synchronized(self) { + if (!sLibraryVersions) { + sLibraryVersions = [[NSMutableDictionary alloc] init]; + } + sLibraryVersions[name] = version; + } + } else { + FIRLogError(kFIRLoggerCore, @"I-COR000027", + @"The library name (%@) or version number (%@) contain invalid characters. " + @"Only alphanumeric, dash, underscore and period characters are allowed.", + name, version); + } +} + ++ (void)registerInternalLibrary:(nonnull Class)library + withName:(nonnull NSString *)name + withVersion:(nonnull NSString *)version { + // This is called at +load time, keep the work to a minimum. + + // Ensure the class given conforms to the proper protocol. + if (![(Class)library conformsToProtocol:@protocol(FIRLibrary)] || + ![(Class)library respondsToSelector:@selector(componentsToRegister)]) { + [NSException raise:NSInvalidArgumentException + format:@"Class %@ attempted to register components, but it does not conform to " + @"`FIRLibrary or provide a `componentsToRegister:` method.", + library]; + } + + [FIRComponentContainer registerAsComponentRegistrant:library]; + if ([(Class)library respondsToSelector:@selector(configureWithApp:)]) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sRegisteredAsConfigurable = [[NSMutableArray alloc] init]; + }); + @synchronized(self) { + [sRegisteredAsConfigurable addObject:library]; + } + } + [self registerLibrary:name withVersion:version]; +} + ++ (NSString *)firebaseUserAgent { + @synchronized(self) { + dispatch_once(&sFirebaseUserAgentOnceToken, ^{ + // Report FirebaseCore version for useragent string + [FIRApp registerLibrary:@"fire-ios" + withVersion:[NSString stringWithUTF8String:FIRCoreVersionString]]; + + NSDictionary *info = [[NSBundle mainBundle] infoDictionary]; + NSString *xcodeVersion = info[@"DTXcodeBuild"]; + NSString *sdkVersion = info[@"DTSDKBuild"]; + if (xcodeVersion) { + [FIRApp registerLibrary:@"xcode" withVersion:xcodeVersion]; + } + if (sdkVersion) { + [FIRApp registerLibrary:@"apple-sdk" withVersion:sdkVersion]; + } + + NSString *swiftFlagValue = [self hasSwiftRuntime] ? @"true" : @"false"; + [FIRApp registerLibrary:@"swift" withVersion:swiftFlagValue]; + }); + + NSMutableArray *libraries = + [[NSMutableArray alloc] initWithCapacity:sLibraryVersions.count]; + for (NSString *libraryName in sLibraryVersions) { + [libraries addObject:[NSString stringWithFormat:@"%@/%@", libraryName, + sLibraryVersions[libraryName]]]; + } + [libraries sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; + return [libraries componentsJoinedByString:@" "]; + } +} + ++ (BOOL)hasSwiftRuntime { + // The class + // [Swift._SwiftObject](https://github.com/apple/swift/blob/5eac3e2818eb340b11232aff83edfbd1c307fa03/stdlib/public/runtime/SwiftObject.h#L35) + // is a part of Swift runtime, so it should be present if Swift runtime is available. + + BOOL hasSwiftRuntime = + objc_lookUpClass("Swift._SwiftObject") != nil || + // Swift object class name before + // https://github.com/apple/swift/commit/9637b4a6e11ddca72f5f6dbe528efc7c92f14d01 + objc_getClass("_TtCs12_SwiftObject") != nil; + + return hasSwiftRuntime; +} + +- (void)checkExpectedBundleID { + NSArray *bundles = [FIRBundleUtil relevantBundles]; + NSString *expectedBundleID = [self expectedBundleID]; + // The checking is only done when the bundle ID is provided in the serviceInfo dictionary for + // backward compatibility. + if (expectedBundleID != nil && ![FIRBundleUtil hasBundleIdentifierPrefix:expectedBundleID + inBundles:bundles]) { + FIRLogError(kFIRLoggerCore, @"I-COR000008", + @"The project's Bundle ID is inconsistent with " + @"either the Bundle ID in '%@.%@', or the Bundle ID in the options if you are " + @"using a customized options. To ensure that everything can be configured " + @"correctly, you may need to make the Bundle IDs consistent. To continue with this " + @"plist file, you may change your app's bundle identifier to '%@'. Or you can " + @"download a new configuration file that matches your bundle identifier from %@ " + @"and replace the current one.", + kServiceInfoFileName, kServiceInfoFileType, expectedBundleID, kPlistURL); + } +} + +#pragma mark - private - App ID Validation + +/** + * Validates the format and fingerprint of the app ID contained in GOOGLE_APP_ID in the plist file. + * This is the main method for validating app ID. + * + * @return YES if the app ID fulfills the expected format and fingerprint, NO otherwise. + */ +- (BOOL)isAppIDValid { + NSString *appID = _options.googleAppID; + BOOL isValid = [FIRApp validateAppID:appID]; + if (!isValid) { + NSString *expectedBundleID = [self expectedBundleID]; + FIRLogError(kFIRLoggerCore, @"I-COR000009", + @"The GOOGLE_APP_ID either in the plist file " + @"'%@.%@' or the one set in the customized options is invalid. If you are using " + @"the plist file, use the iOS version of bundle identifier to download the file, " + @"and do not manually edit the GOOGLE_APP_ID. You may change your app's bundle " + @"identifier to '%@'. Or you can download a new configuration file that matches " + @"your bundle identifier from %@ and replace the current one.", + kServiceInfoFileName, kServiceInfoFileType, expectedBundleID, kPlistURL); + }; + return isValid; +} + ++ (BOOL)validateAppID:(NSString *)appID { + // Failing validation only occurs when we are sure we are looking at a V2 app ID and it does not + // have a valid fingerprint, otherwise we just warn about the potential issue. + if (!appID.length) { + return NO; + } + + NSScanner *stringScanner = [NSScanner scannerWithString:appID]; + stringScanner.charactersToBeSkipped = nil; + + NSString *appIDVersion; + if (![stringScanner scanCharactersFromSet:[NSCharacterSet decimalDigitCharacterSet] + intoString:&appIDVersion]) { + return NO; + } + + if (![stringScanner scanString:@":" intoString:NULL]) { + // appIDVersion must be separated by ":" + return NO; + } + + NSArray *knownVersions = @[ @"1" ]; + if (![knownVersions containsObject:appIDVersion]) { + // Permit unknown yet properly formatted app ID versions. + FIRLogInfo(kFIRLoggerCore, @"I-COR000010", @"Unknown GOOGLE_APP_ID version: %@", appIDVersion); + return YES; + } + + if (![self validateAppIDFormat:appID withVersion:appIDVersion]) { + return NO; + } + + if (![self validateAppIDFingerprint:appID withVersion:appIDVersion]) { + return NO; + } + + return YES; +} + ++ (NSString *)actualBundleID { + return [[NSBundle mainBundle] bundleIdentifier]; +} + +/** + * Validates that the format of the app ID string is what is expected based on the supplied version. + * The version must end in ":". + * + * For v1 app ids the format is expected to be + * '::ios:'. + * + * This method does not verify that the contents of the app id are correct, just that they fulfill + * the expected format. + * + * @param appID Contents of GOOGLE_APP_ID from the plist file. + * @param version Indicates what version of the app id format this string should be. + * @return YES if provided string fufills the expected format, NO otherwise. + */ ++ (BOOL)validateAppIDFormat:(NSString *)appID withVersion:(NSString *)version { + if (!appID.length || !version.length) { + return NO; + } + + NSScanner *stringScanner = [NSScanner scannerWithString:appID]; + stringScanner.charactersToBeSkipped = nil; + + // Skip version part + // '**::ios:' + if (![stringScanner scanString:version intoString:NULL]) { + // The version part is missing or mismatched + return NO; + } + + // Validate version part (see part between '*' symbols below) + // '*:*:ios:' + if (![stringScanner scanString:@":" intoString:NULL]) { + // appIDVersion must be separated by ":" + return NO; + } + + // Validate version part (see part between '*' symbols below) + // ':**:ios:'. + NSInteger projectNumber = NSNotFound; + if (![stringScanner scanInteger:&projectNumber]) { + // NO project number found. + return NO; + } + + // Validate version part (see part between '*' symbols below) + // ':*:*ios:'. + if (![stringScanner scanString:@":" intoString:NULL]) { + // The project number must be separated by ":" + return NO; + } + + // Validate version part (see part between '*' symbols below) + // '::*ios*:'. + NSString *platform; + if (![stringScanner scanUpToString:@":" intoString:&platform]) { + return NO; + } + + if (![platform isEqualToString:@"ios"]) { + // The platform must be @"ios" + return NO; + } + + // Validate version part (see part between '*' symbols below) + // '::ios*:*'. + if (![stringScanner scanString:@":" intoString:NULL]) { + // The platform must be separated by ":" + return NO; + } + + // Validate version part (see part between '*' symbols below) + // '::ios:**'. + unsigned long long fingerprint = NSNotFound; + if (![stringScanner scanHexLongLong:&fingerprint]) { + // Fingerprint part is missing + return NO; + } + + if (!stringScanner.isAtEnd) { + // There are not allowed characters in the fingerprint part + return NO; + } + + return YES; +} + +/** + * Validates that the fingerprint of the app ID string is what is expected based on the supplied + * version. + * + * Note that the v1 hash algorithm is not permitted on the client and cannot be fully validated. + * + * @param appID Contents of GOOGLE_APP_ID from the plist file. + * @param version Indicates what version of the app id format this string should be. + * @return YES if provided string fufills the expected fingerprint and the version is known, NO + * otherwise. + */ ++ (BOOL)validateAppIDFingerprint:(NSString *)appID withVersion:(NSString *)version { + // Extract the supplied fingerprint from the supplied app ID. + // This assumes the app ID format is the same for all known versions below. If the app ID format + // changes in future versions, the tokenizing of the app ID format will need to take into account + // the version of the app ID. + NSArray *components = [appID componentsSeparatedByString:@":"]; + if (components.count != 4) { + return NO; + } + + NSString *suppliedFingerprintString = components[3]; + if (!suppliedFingerprintString.length) { + return NO; + } + + uint64_t suppliedFingerprint; + NSScanner *scanner = [NSScanner scannerWithString:suppliedFingerprintString]; + if (![scanner scanHexLongLong:&suppliedFingerprint]) { + return NO; + } + + if ([version isEqual:@"1"]) { + // The v1 hash algorithm is not permitted on the client so the actual hash cannot be validated. + return YES; + } + + // Unknown version. + return NO; +} + +- (NSString *)expectedBundleID { + return _options.bundleID; +} + +// end App ID validation + +#pragma mark - Reading From Plist & User Defaults + +/** + * Clears the data collection switch from the standard NSUserDefaults for easier testing and + * readability. + */ +- (void)clearDataCollectionSwitchFromUserDefaults { + NSString *key = + [NSString stringWithFormat:kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat, self.name]; + [[NSUserDefaults standardUserDefaults] removeObjectForKey:key]; +} + +/** + * Reads the data collection switch from the standard NSUserDefaults for easier testing and + * readability. + */ ++ (nullable NSNumber *)readDataCollectionSwitchFromUserDefaultsForApp:(FIRApp *)app { + // Read the object in user defaults, and only return if it's an NSNumber. + NSString *key = + [NSString stringWithFormat:kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat, app.name]; + id collectionEnabledDefaultsObject = [[NSUserDefaults standardUserDefaults] objectForKey:key]; + if ([collectionEnabledDefaultsObject isKindOfClass:[NSNumber class]]) { + return collectionEnabledDefaultsObject; + } + + return nil; +} + +/** + * Reads the data collection switch from the Info.plist for easier testing and readability. Will + * only read once from the plist and return the cached value. + */ ++ (nullable NSNumber *)readDataCollectionSwitchFromPlist { + static NSNumber *collectionEnabledPlistObject; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + // Read the data from the `Info.plist`, only assign it if it's there and an NSNumber. + id plistValue = [[NSBundle mainBundle] + objectForInfoDictionaryKey:kFIRGlobalAppDataCollectionEnabledPlistKey]; + if (plistValue && [plistValue isKindOfClass:[NSNumber class]]) { + collectionEnabledPlistObject = (NSNumber *)plistValue; + } + }); + + return collectionEnabledPlistObject; +} + +#pragma mark - Sending Logs + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-parameter" +- (void)sendLogsWithServiceName:(NSString *)serviceName + version:(NSString *)version + error:(NSError *)error { + // Do nothing. Please remove calls to this method. +} +#pragma clang diagnostic pop + +#pragma mark - App Life Cycle + +- (void)subscribeForAppDidBecomeActiveNotifications { +#if TARGET_OS_IOS || TARGET_OS_TV + NSNotificationName notificationName = UIApplicationDidBecomeActiveNotification; +#elif TARGET_OS_OSX + NSNotificationName notificationName = NSApplicationDidBecomeActiveNotification; +#endif + +#if !TARGET_OS_WATCH + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(appDidBecomeActive:) + name:notificationName + object:nil]; +#endif +} + +- (void)appDidBecomeActive:(NSNotification *)notification { + [self logCoreTelemetryIfEnabled]; +} + +- (void)logCoreTelemetryIfEnabled { + if ([self isDataCollectionDefaultEnabled]) { + [FIRCoreDiagnosticsConnector logCoreTelemetryWithOptions:_options]; + } +} + +@end diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRAppAssociationRegistration.m b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRAppAssociationRegistration.m new file mode 100644 index 0000000..e4125cd --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRAppAssociationRegistration.m @@ -0,0 +1,47 @@ +// Copyright 2017 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FirebaseCore/Sources/Private/FIRAppAssociationRegistration.h" + +#import + +@implementation FIRAppAssociationRegistration + ++ (nullable id)registeredObjectWithHost:(id)host + key:(NSString *)key + creationBlock:(id _Nullable (^)(void))creationBlock { + @synchronized(self) { + SEL dictKey = @selector(registeredObjectWithHost:key:creationBlock:); + NSMutableDictionary *objectsByKey = objc_getAssociatedObject(host, dictKey); + if (!objectsByKey) { + objectsByKey = [[NSMutableDictionary alloc] init]; + objc_setAssociatedObject(host, dictKey, objectsByKey, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + id obj = objectsByKey[key]; + NSValue *creationBlockBeingCalled = [NSValue valueWithPointer:dictKey]; + if (obj) { + if ([creationBlockBeingCalled isEqual:obj]) { + [NSException raise:@"Reentering registeredObjectWithHost:key:creationBlock: not allowed" + format:@"host: %@ key: %@", host, key]; + } + return obj; + } + objectsByKey[key] = creationBlockBeingCalled; + obj = creationBlock(); + objectsByKey[key] = obj; + return obj; + } +} + +@end diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRBundleUtil.h b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRBundleUtil.h new file mode 100644 index 0000000..d9475dd --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRBundleUtil.h @@ -0,0 +1,53 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +/** + * This class provides utilities for accessing resources in bundles. + */ +@interface FIRBundleUtil : NSObject + +/** + * Finds all relevant bundles, starting with [NSBundle mainBundle]. + */ ++ (NSArray *)relevantBundles; + +/** + * Reads the options dictionary from one of the provided bundles. + * + * @param resourceName The resource name, e.g. @"GoogleService-Info". + * @param fileType The file type (extension), e.g. @"plist". + * @param bundles The bundles to expect, in priority order. See also + * +[FIRBundleUtil relevantBundles]. + */ ++ (NSString *)optionsDictionaryPathWithResourceName:(NSString *)resourceName + andFileType:(NSString *)fileType + inBundles:(NSArray *)bundles; + +/** + * Finds URL schemes defined in all relevant bundles, starting with those from + * [NSBundle mainBundle]. + */ ++ (NSArray *)relevantURLSchemes; + +/** + * Checks if any of the given bundles have a matching bundle identifier prefix (removing extension + * suffixes). + */ ++ (BOOL)hasBundleIdentifierPrefix:(NSString *)bundleIdentifier inBundles:(NSArray *)bundles; + +@end diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRBundleUtil.m b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRBundleUtil.m new file mode 100644 index 0000000..b858f14 --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRBundleUtil.m @@ -0,0 +1,75 @@ +// Copyright 2017 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FirebaseCore/Sources/FIRBundleUtil.h" + +#import + +@implementation FIRBundleUtil + ++ (NSArray *)relevantBundles { + return @[ [NSBundle mainBundle], [NSBundle bundleForClass:[self class]] ]; +} + ++ (NSString *)optionsDictionaryPathWithResourceName:(NSString *)resourceName + andFileType:(NSString *)fileType + inBundles:(NSArray *)bundles { + // Loop through all bundles to find the config dict. + for (NSBundle *bundle in bundles) { + NSString *path = [bundle pathForResource:resourceName ofType:fileType]; + // Use the first one we find. + if (path) { + return path; + } + } + return nil; +} + ++ (NSArray *)relevantURLSchemes { + NSMutableArray *result = [[NSMutableArray alloc] init]; + for (NSBundle *bundle in [[self class] relevantBundles]) { + NSArray *urlTypes = [bundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]; + for (NSDictionary *urlType in urlTypes) { + [result addObjectsFromArray:urlType[@"CFBundleURLSchemes"]]; + } + } + return result; +} + ++ (BOOL)hasBundleIdentifierPrefix:(NSString *)bundleIdentifier inBundles:(NSArray *)bundles { + for (NSBundle *bundle in bundles) { + // This allows app extensions that have the app's bundle as their prefix to pass this test. + NSString *applicationBundleIdentifier = + [GULAppEnvironmentUtil isAppExtension] + ? [self bundleIdentifierByRemovingLastPartFrom:bundle.bundleIdentifier] + : bundle.bundleIdentifier; + + if ([applicationBundleIdentifier isEqualToString:bundleIdentifier]) { + return YES; + } + } + return NO; +} + ++ (NSString *)bundleIdentifierByRemovingLastPartFrom:(NSString *)bundleIdentifier { + NSString *bundleIDComponentsSeparator = @"."; + + NSMutableArray *bundleIDComponents = + [[bundleIdentifier componentsSeparatedByString:bundleIDComponentsSeparator] mutableCopy]; + [bundleIDComponents removeLastObject]; + + return [bundleIDComponents componentsJoinedByString:bundleIDComponentsSeparator]; +} + +@end diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRComponent.m b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRComponent.m new file mode 100644 index 0000000..9c1fbed --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRComponent.m @@ -0,0 +1,65 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FirebaseCore/Sources/Private/FIRComponent.h" + +#import "FirebaseCore/Sources/Private/FIRComponentContainer.h" +#import "FirebaseCore/Sources/Private/FIRDependency.h" + +@interface FIRComponent () + +- (instancetype)initWithProtocol:(Protocol *)protocol + instantiationTiming:(FIRInstantiationTiming)instantiationTiming + dependencies:(NSArray *)dependencies + creationBlock:(FIRComponentCreationBlock)creationBlock; + +@end + +@implementation FIRComponent + ++ (instancetype)componentWithProtocol:(Protocol *)protocol + creationBlock:(FIRComponentCreationBlock)creationBlock { + return [[FIRComponent alloc] initWithProtocol:protocol + instantiationTiming:FIRInstantiationTimingLazy + dependencies:@[] + creationBlock:creationBlock]; +} + ++ (instancetype)componentWithProtocol:(Protocol *)protocol + instantiationTiming:(FIRInstantiationTiming)instantiationTiming + dependencies:(NSArray *)dependencies + creationBlock:(FIRComponentCreationBlock)creationBlock { + return [[FIRComponent alloc] initWithProtocol:protocol + instantiationTiming:instantiationTiming + dependencies:dependencies + creationBlock:creationBlock]; +} + +- (instancetype)initWithProtocol:(Protocol *)protocol + instantiationTiming:(FIRInstantiationTiming)instantiationTiming + dependencies:(NSArray *)dependencies + creationBlock:(FIRComponentCreationBlock)creationBlock { + self = [super init]; + if (self) { + _protocol = protocol; + _instantiationTiming = instantiationTiming; + _dependencies = [dependencies copy]; + _creationBlock = creationBlock; + } + return self; +} + +@end diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRComponentContainer.m b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRComponentContainer.m new file mode 100644 index 0000000..9f91786 --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRComponentContainer.m @@ -0,0 +1,208 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FirebaseCore/Sources/Private/FIRComponentContainer.h" + +#import "FirebaseCore/Sources/Private/FIRAppInternal.h" +#import "FirebaseCore/Sources/Private/FIRComponent.h" +#import "FirebaseCore/Sources/Private/FIRLibrary.h" +#import "FirebaseCore/Sources/Private/FIRLogger.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRComponentContainer () + +/// The dictionary of components that are registered for a particular app. The key is an `NSString` +/// of the protocol. +@property(nonatomic, strong) NSMutableDictionary *components; + +/// Cached instances of components that requested to be cached. +@property(nonatomic, strong) NSMutableDictionary *cachedInstances; + +/// Protocols of components that have requested to be eagerly instantiated. +@property(nonatomic, strong, nullable) NSMutableArray *eagerProtocolsToInstantiate; + +@end + +@implementation FIRComponentContainer + +// Collection of all classes that register to provide components. +static NSMutableSet *sFIRComponentRegistrants; + +#pragma mark - Public Registration + ++ (void)registerAsComponentRegistrant:(Class)klass { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sFIRComponentRegistrants = [[NSMutableSet alloc] init]; + }); + + [self registerAsComponentRegistrant:klass inSet:sFIRComponentRegistrants]; +} + ++ (void)registerAsComponentRegistrant:(Class)klass + inSet:(NSMutableSet *)allRegistrants { + [allRegistrants addObject:klass]; +} + +#pragma mark - Internal Initialization + +- (instancetype)initWithApp:(FIRApp *)app { + return [self initWithApp:app registrants:sFIRComponentRegistrants]; +} + +- (instancetype)initWithApp:(FIRApp *)app registrants:(NSMutableSet *)allRegistrants { + self = [super init]; + if (self) { + _app = app; + _cachedInstances = [NSMutableDictionary dictionary]; + _components = [NSMutableDictionary dictionary]; + + [self populateComponentsFromRegisteredClasses:allRegistrants forApp:app]; + } + return self; +} + +- (void)populateComponentsFromRegisteredClasses:(NSSet *)classes forApp:(FIRApp *)app { + // Keep track of any components that need to eagerly instantiate after all components are added. + self.eagerProtocolsToInstantiate = [[NSMutableArray alloc] init]; + + // Loop through the verified component registrants and populate the components array. + for (Class klass in classes) { + // Loop through all the components being registered and store them as appropriate. + // Classes which do not provide functionality should use a dummy FIRComponentRegistrant + // protocol. + for (FIRComponent *component in [klass componentsToRegister]) { + // Check if the component has been registered before, and error out if so. + NSString *protocolName = NSStringFromProtocol(component.protocol); + if (self.components[protocolName]) { + FIRLogError(kFIRLoggerCore, @"I-COR000029", + @"Attempted to register protocol %@, but it already has an implementation.", + protocolName); + continue; + } + + // Store the creation block for later usage. + self.components[protocolName] = component.creationBlock; + + // Queue any protocols that should be eagerly instantiated. Don't instantiate them yet + // because they could depend on other components that haven't been added to the components + // array yet. + BOOL shouldInstantiateEager = + (component.instantiationTiming == FIRInstantiationTimingAlwaysEager); + BOOL shouldInstantiateDefaultEager = + (component.instantiationTiming == FIRInstantiationTimingEagerInDefaultApp && + [app isDefaultApp]); + if (shouldInstantiateEager || shouldInstantiateDefaultEager) { + [self.eagerProtocolsToInstantiate addObject:component.protocol]; + } + } + } +} + +#pragma mark - Instance Creation + +- (void)instantiateEagerComponents { + // After all components are registered, instantiate the ones that are requesting eager + // instantiation. + @synchronized(self) { + for (Protocol *protocol in self.eagerProtocolsToInstantiate) { + // Get an instance for the protocol, which will instantiate it since it couldn't have been + // cached yet. Ignore the instance coming back since we don't need it. + __unused id unusedInstance = [self instanceForProtocol:protocol]; + } + + // All eager instantiation is complete, clear the stored property now. + self.eagerProtocolsToInstantiate = nil; + } +} + +/// Instantiate an instance of a class that conforms to the specified protocol. +/// This will: +/// - Call the block to create an instance if possible, +/// - Validate that the instance returned conforms to the protocol it claims to, +/// - Cache the instance if the block requests it +/// +/// Note that this method assumes the caller already has @sychronized on self. +- (nullable id)instantiateInstanceForProtocol:(Protocol *)protocol + withBlock:(FIRComponentCreationBlock)creationBlock { + if (!creationBlock) { + return nil; + } + + // Create an instance using the creation block. + BOOL shouldCache = NO; + id instance = creationBlock(self, &shouldCache); + if (!instance) { + return nil; + } + + // An instance was created, validate that it conforms to the protocol it claims to. + NSString *protocolName = NSStringFromProtocol(protocol); + if (![instance conformsToProtocol:protocol]) { + FIRLogError(kFIRLoggerCore, @"I-COR000030", + @"An instance conforming to %@ was requested, but the instance provided does not " + @"conform to the protocol", + protocolName); + } + + // The instance is ready to be returned, but check if it should be cached first before returning. + if (shouldCache) { + self.cachedInstances[protocolName] = instance; + } + + return instance; +} + +#pragma mark - Internal Retrieval + +- (nullable id)instanceForProtocol:(Protocol *)protocol { + // Check if there is a cached instance, and return it if so. + NSString *protocolName = NSStringFromProtocol(protocol); + + id cachedInstance; + @synchronized(self) { + cachedInstance = self.cachedInstances[protocolName]; + if (!cachedInstance) { + // Use the creation block to instantiate an instance and return it. + FIRComponentCreationBlock creationBlock = self.components[protocolName]; + cachedInstance = [self instantiateInstanceForProtocol:protocol withBlock:creationBlock]; + } + } + return cachedInstance; +} + +#pragma mark - Lifecycle + +- (void)removeAllCachedInstances { + @synchronized(self) { + // Loop through the cache and notify each instance that is a maintainer to clean up after + // itself. + for (id instance in self.cachedInstances.allValues) { + if ([instance conformsToProtocol:@protocol(FIRComponentLifecycleMaintainer)] && + [instance respondsToSelector:@selector(appWillBeDeleted:)]) { + [instance appWillBeDeleted:self.app]; + } + } + + // Empty the cache. + [self.cachedInstances removeAllObjects]; + } +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRComponentType.m b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRComponentType.m new file mode 100644 index 0000000..6410f2e --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRComponentType.m @@ -0,0 +1,28 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FirebaseCore/Sources/Private/FIRComponentType.h" + +#import "FirebaseCore/Sources/Private/FIRComponentContainerInternal.h" + +@implementation FIRComponentType + ++ (id)instanceForProtocol:(Protocol *)protocol inContainer:(FIRComponentContainer *)container { + // Forward the call to the container. + return [container instanceForProtocol:protocol]; +} + +@end diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRConfiguration.m b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRConfiguration.m new file mode 100644 index 0000000..a1c9f4a --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRConfiguration.m @@ -0,0 +1,46 @@ +// Copyright 2017 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FirebaseCore/Sources/Private/FIRConfigurationInternal.h" + +#import "FirebaseCore/Sources/Private/FIRAnalyticsConfiguration.h" + +extern void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel); + +@implementation FIRConfiguration + ++ (instancetype)sharedInstance { + static FIRConfiguration *sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[FIRConfiguration alloc] init]; + }); + return sharedInstance; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _analyticsConfiguration = [FIRAnalyticsConfiguration sharedInstance]; + } + return self; +} + +- (void)setLoggerLevel:(FIRLoggerLevel)loggerLevel { + NSAssert(loggerLevel <= FIRLoggerLevelMax && loggerLevel >= FIRLoggerLevelMin, + @"Invalid logger level, %ld", (long)loggerLevel); + FIRSetLoggerLevel(loggerLevel); +} + +@end diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRCoreDiagnosticsConnector.m b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRCoreDiagnosticsConnector.m new file mode 100644 index 0000000..4981ca1 --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRCoreDiagnosticsConnector.m @@ -0,0 +1,61 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FirebaseCore/Sources/Private/FIRCoreDiagnosticsConnector.h" + +#import + +#import + +#import "FirebaseCore/Sources/Private/FIRAppInternal.h" +#import "FirebaseCore/Sources/Private/FIRDiagnosticsData.h" +#import "FirebaseCore/Sources/Private/FIROptionsInternal.h" + +// Define the interop class symbol declared as an extern in FIRCoreDiagnosticsInterop. +Class FIRCoreDiagnosticsImplementation; + +@implementation FIRCoreDiagnosticsConnector + ++ (void)initialize { + if (!FIRCoreDiagnosticsImplementation) { + FIRCoreDiagnosticsImplementation = NSClassFromString(@"FIRCoreDiagnostics"); + if (FIRCoreDiagnosticsImplementation) { + NSAssert([FIRCoreDiagnosticsImplementation + conformsToProtocol:@protocol(FIRCoreDiagnosticsInterop)], + @"If FIRCoreDiagnostics is implemented, it must conform to the interop protocol."); + NSAssert( + [FIRCoreDiagnosticsImplementation respondsToSelector:@selector(sendDiagnosticsData:)], + @"If FIRCoreDiagnostics is implemented, it must implement +sendDiagnosticsData."); + } + } +} + ++ (void)logCoreTelemetryWithOptions:(FIROptions *)options { + if (FIRCoreDiagnosticsImplementation) { + FIRDiagnosticsData *diagnosticsData = [[FIRDiagnosticsData alloc] init]; + [diagnosticsData insertValue:@(YES) forKey:kFIRCDIsDataCollectionDefaultEnabledKey]; + [diagnosticsData insertValue:[FIRApp firebaseUserAgent] forKey:kFIRCDFirebaseUserAgentKey]; + [diagnosticsData insertValue:@(FIRConfigTypeCore) forKey:kFIRCDConfigurationTypeKey]; + [diagnosticsData insertValue:options.googleAppID forKey:kFIRCDGoogleAppIDKey]; + [diagnosticsData insertValue:options.bundleID forKey:kFIRCDBundleIDKey]; + [diagnosticsData insertValue:@(options.usingOptionsFromDefaultPlist) + forKey:kFIRCDUsingOptionsFromDefaultPlistKey]; + [diagnosticsData insertValue:options.libraryVersionID forKey:kFIRCDLibraryVersionIDKey]; + [FIRCoreDiagnosticsImplementation sendDiagnosticsData:diagnosticsData]; + } +} + +@end diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRDependency.m b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRDependency.m new file mode 100644 index 0000000..e1e2578 --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRDependency.m @@ -0,0 +1,44 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FirebaseCore/Sources/Private/FIRDependency.h" + +@interface FIRDependency () + +- (instancetype)initWithProtocol:(Protocol *)protocol isRequired:(BOOL)required; + +@end + +@implementation FIRDependency + ++ (instancetype)dependencyWithProtocol:(Protocol *)protocol { + return [[self alloc] initWithProtocol:protocol isRequired:YES]; +} + ++ (instancetype)dependencyWithProtocol:(Protocol *)protocol isRequired:(BOOL)required { + return [[self alloc] initWithProtocol:protocol isRequired:required]; +} + +- (instancetype)initWithProtocol:(Protocol *)protocol isRequired:(BOOL)required { + self = [super init]; + if (self) { + _protocol = protocol; + _isRequired = required; + } + return self; +} + +@end diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRDiagnosticsData.m b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRDiagnosticsData.m new file mode 100644 index 0000000..bbe0561 --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRDiagnosticsData.m @@ -0,0 +1,66 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FirebaseCore/Sources/Private/FIRDiagnosticsData.h" + +#import + +#import "FirebaseCore/Sources/Private/FIRAppInternal.h" +#import "FirebaseCore/Sources/Private/FIROptionsInternal.h" + +@implementation FIRDiagnosticsData { + /** Backing ivar for the diagnosticObjects property. */ + NSMutableDictionary *_diagnosticObjects; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _diagnosticObjects = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (void)insertValue:(nullable id)value forKey:(NSString *)key { + if (key) { + _diagnosticObjects[key] = value; + } +} + +#pragma mark - FIRCoreDiagnosticsData + +- (NSDictionary *)diagnosticObjects { + if (!_diagnosticObjects[kFIRCDllAppsCountKey]) { + _diagnosticObjects[kFIRCDllAppsCountKey] = @([FIRApp allApps].count); + } + if (!_diagnosticObjects[kFIRCDIsDataCollectionDefaultEnabledKey]) { + _diagnosticObjects[kFIRCDIsDataCollectionDefaultEnabledKey] = + @([[FIRApp defaultApp] isDataCollectionDefaultEnabled]); + } + if (!_diagnosticObjects[kFIRCDFirebaseUserAgentKey]) { + _diagnosticObjects[kFIRCDFirebaseUserAgentKey] = [FIRApp firebaseUserAgent]; + } + return _diagnosticObjects; +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-parameter" +- (void)setDiagnosticObjects:(NSDictionary *)diagnosticObjects { + NSAssert(NO, @"Please use -insertValue:forKey:"); +} +#pragma clang diagnostic pop + +@end diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRErrors.m b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRErrors.m new file mode 100644 index 0000000..104eeb8 --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRErrors.m @@ -0,0 +1,21 @@ +// Copyright 2017 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FirebaseCore/Sources/Private/FIRErrors.h" + +NSString *const kFirebaseErrorDomain = @"com.firebase"; +NSString *const kFirebaseConfigErrorDomain = @"com.firebase.config"; +NSString *const kFirebaseCoreErrorDomain = @"com.firebase.core"; +NSString *const kFirebasePerfErrorDomain = @"com.firebase.perf"; +NSString *const kFirebaseStorageErrorDomain = @"com.firebase.storage"; diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRHeartbeatInfo.m b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRHeartbeatInfo.m new file mode 100644 index 0000000..f1359f5 --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRHeartbeatInfo.m @@ -0,0 +1,61 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FirebaseCore/Sources/Private/FIRHeartbeatInfo.h" +#import +#import + +const static long secondsInDay = 864000; +@implementation FIRHeartbeatInfo : NSObject + +/** Updates the storage with the heartbeat information corresponding to this tag. + * @param heartbeatTag Tag which could either be sdk specific tag or the global tag. + * @return Boolean representing whether the heartbeat needs to be sent for this tag or not. + */ ++ (BOOL)updateIfNeededHeartbeatDateForTag:(NSString *)heartbeatTag { + @synchronized(self) { + NSString *const kHeartbeatStorageFile = @"HEARTBEAT_INFO_STORAGE"; + GULHeartbeatDateStorage *dataStorage = + [[GULHeartbeatDateStorage alloc] initWithFileName:kHeartbeatStorageFile]; + NSDate *heartbeatTime = [dataStorage heartbeatDateForTag:heartbeatTag]; + NSDate *currentDate = [NSDate date]; + if (heartbeatTime != nil) { + NSTimeInterval secondsBetween = [currentDate timeIntervalSinceDate:heartbeatTime]; + if (secondsBetween < secondsInDay) { + return false; + } + } + return [dataStorage setHearbeatDate:currentDate forTag:heartbeatTag]; + } +} + ++ (FIRHeartbeatInfoCode)heartbeatCodeForTag:(NSString *)heartbeatTag { + NSString *globalTag = @"GLOBAL"; + BOOL isSdkHeartbeatNeeded = [FIRHeartbeatInfo updateIfNeededHeartbeatDateForTag:heartbeatTag]; + BOOL isGlobalHeartbeatNeeded = [FIRHeartbeatInfo updateIfNeededHeartbeatDateForTag:globalTag]; + if (!isSdkHeartbeatNeeded && !isGlobalHeartbeatNeeded) { + // Both sdk and global heartbeat not needed. + return FIRHeartbeatInfoCodeNone; + } else if (isSdkHeartbeatNeeded && !isGlobalHeartbeatNeeded) { + // Only SDK heartbeat needed. + return FIRHeartbeatInfoCodeSDK; + } else if (!isSdkHeartbeatNeeded && isGlobalHeartbeatNeeded) { + // Only global heartbeat needed. + return FIRHeartbeatInfoCodeGlobal; + } else { + // Both sdk and global heartbeat are needed. + return FIRHeartbeatInfoCodeCombined; + } +} +@end diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRLogger.m b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRLogger.m new file mode 100644 index 0000000..ba2ee1f --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRLogger.m @@ -0,0 +1,179 @@ +// Copyright 2017 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FirebaseCore/Sources/Private/FIRLogger.h" + +#import +#import +#import + +#import "FirebaseCore/Sources/FIRVersion.h" + +FIRLoggerService kFIRLoggerCore = @"[Firebase/Core]"; + +// All the FIRLoggerService definitions should be migrated to clients. Do not add new ones! +FIRLoggerService kFIRLoggerABTesting = @"[Firebase/ABTesting]"; +FIRLoggerService kFIRLoggerAdMob = @"[Firebase/AdMob]"; +FIRLoggerService kFIRLoggerAnalytics = @"[Firebase/Analytics]"; +FIRLoggerService kFIRLoggerAuth = @"[Firebase/Auth]"; +FIRLoggerService kFIRLoggerCrash = @"[Firebase/Crash]"; +FIRLoggerService kFIRLoggerMLKit = @"[Firebase/MLKit]"; +FIRLoggerService kFIRLoggerPerf = @"[Firebase/Performance]"; +FIRLoggerService kFIRLoggerRemoteConfig = @"[Firebase/RemoteConfig]"; + +/// Arguments passed on launch. +NSString *const kFIRDisableDebugModeApplicationArgument = @"-FIRDebugDisabled"; +NSString *const kFIREnableDebugModeApplicationArgument = @"-FIRDebugEnabled"; +NSString *const kFIRLoggerForceSDTERRApplicationArgument = @"-FIRLoggerForceSTDERR"; + +/// Key for the debug mode bit in NSUserDefaults. +NSString *const kFIRPersistedDebugModeKey = @"/google/firebase/debug_mode"; + +/// NSUserDefaults that should be used to store and read variables. If nil, `standardUserDefaults` +/// will be used. +static NSUserDefaults *sFIRLoggerUserDefaults; + +static dispatch_once_t sFIRLoggerOnceToken; + +// The sFIRAnalyticsDebugMode flag is here to support the -FIRDebugEnabled/-FIRDebugDisabled +// flags used by Analytics. Users who use those flags expect Analytics to log verbosely, +// while the rest of Firebase logs at the default level. This flag is introduced to support +// that behavior. +static BOOL sFIRAnalyticsDebugMode; + +#ifdef DEBUG +/// The regex pattern for the message code. +static NSString *const kMessageCodePattern = @"^I-[A-Z]{3}[0-9]{6}$"; +static NSRegularExpression *sMessageCodeRegex; +#endif + +void FIRLoggerInitializeASL() { + dispatch_once(&sFIRLoggerOnceToken, ^{ + // Register Firebase Version with GULLogger. + GULLoggerRegisterVersion(FIRVersionString); + + // Override the aslOptions to ASL_OPT_STDERR if the override argument is passed in. + NSArray *arguments = [NSProcessInfo processInfo].arguments; + BOOL overrideSTDERR = [arguments containsObject:kFIRLoggerForceSDTERRApplicationArgument]; + + // Use the standard NSUserDefaults if it hasn't been explicitly set. + if (sFIRLoggerUserDefaults == nil) { + sFIRLoggerUserDefaults = [NSUserDefaults standardUserDefaults]; + } + + BOOL forceDebugMode = NO; + BOOL debugMode = [sFIRLoggerUserDefaults boolForKey:kFIRPersistedDebugModeKey]; + if ([arguments containsObject:kFIRDisableDebugModeApplicationArgument]) { // Default mode + [sFIRLoggerUserDefaults removeObjectForKey:kFIRPersistedDebugModeKey]; + } else if ([arguments containsObject:kFIREnableDebugModeApplicationArgument] || + debugMode) { // Debug mode + [sFIRLoggerUserDefaults setBool:YES forKey:kFIRPersistedDebugModeKey]; + forceDebugMode = YES; + } + GULLoggerInitializeASL(); + if (overrideSTDERR) { + GULLoggerEnableSTDERR(); + } + if (forceDebugMode) { + GULLoggerForceDebug(); + } + }); +} + +__attribute__((no_sanitize("thread"))) void FIRSetAnalyticsDebugMode(BOOL analyticsDebugMode) { + sFIRAnalyticsDebugMode = analyticsDebugMode; +} + +void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel) { + FIRLoggerInitializeASL(); + GULSetLoggerLevel((GULLoggerLevel)loggerLevel); +} + +#ifdef DEBUG +void FIRResetLogger() { + extern void GULResetLogger(void); + sFIRLoggerOnceToken = 0; + [sFIRLoggerUserDefaults removeObjectForKey:kFIRPersistedDebugModeKey]; + sFIRLoggerUserDefaults = nil; + GULResetLogger(); +} + +void FIRSetLoggerUserDefaults(NSUserDefaults *defaults) { + sFIRLoggerUserDefaults = defaults; +} +#endif + +/** + * Check if the level is high enough to be loggable. + * + * Analytics can override the log level with an intentional race condition. + * Add the attribute to get a clean thread sanitizer run. + */ +__attribute__((no_sanitize("thread"))) BOOL FIRIsLoggableLevel(FIRLoggerLevel loggerLevel, + BOOL analyticsComponent) { + FIRLoggerInitializeASL(); + if (sFIRAnalyticsDebugMode && analyticsComponent) { + return YES; + } + return GULIsLoggableLevel((GULLoggerLevel)loggerLevel); +} + +void FIRLogBasic(FIRLoggerLevel level, + FIRLoggerService service, + NSString *messageCode, + NSString *message, + va_list args_ptr) { + FIRLoggerInitializeASL(); + GULLogBasic((GULLoggerLevel)level, service, + sFIRAnalyticsDebugMode && [kFIRLoggerAnalytics isEqualToString:service], messageCode, + message, args_ptr); +} + +/** + * Generates the logging functions using macros. + * + * Calling FIRLogError(kFIRLoggerCore, @"I-COR000001", @"Configure %@ failed.", @"blah") shows: + * yyyy-mm-dd hh:mm:ss.SSS sender[PID] [Firebase/Core][I-COR000001] Configure blah failed. + * Calling FIRLogDebug(kFIRLoggerCore, @"I-COR000001", @"Configure succeed.") shows: + * yyyy-mm-dd hh:mm:ss.SSS sender[PID] [Firebase/Core][I-COR000001] Configure succeed. + */ +#define FIR_LOGGING_FUNCTION(level) \ + void FIRLog##level(FIRLoggerService service, NSString *messageCode, NSString *message, ...) { \ + va_list args_ptr; \ + va_start(args_ptr, message); \ + FIRLogBasic(FIRLoggerLevel##level, service, messageCode, message, args_ptr); \ + va_end(args_ptr); \ + } + +FIR_LOGGING_FUNCTION(Error) +FIR_LOGGING_FUNCTION(Warning) +FIR_LOGGING_FUNCTION(Notice) +FIR_LOGGING_FUNCTION(Info) +FIR_LOGGING_FUNCTION(Debug) + +#undef FIR_MAKE_LOGGER + +#pragma mark - FIRLoggerWrapper + +@implementation FIRLoggerWrapper + ++ (void)logWithLevel:(FIRLoggerLevel)level + withService:(FIRLoggerService)service + withCode:(NSString *)messageCode + withMessage:(NSString *)message + withArgs:(va_list)args { + FIRLogBasic(level, service, messageCode, message, args); +} + +@end diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIROptions.m b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIROptions.m new file mode 100644 index 0000000..d185330 --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIROptions.m @@ -0,0 +1,490 @@ +// Copyright 2017 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FirebaseCore/Sources/FIRBundleUtil.h" +#import "FirebaseCore/Sources/FIRVersion.h" +#import "FirebaseCore/Sources/Private/FIRAppInternal.h" +#import "FirebaseCore/Sources/Private/FIRLogger.h" +#import "FirebaseCore/Sources/Private/FIROptionsInternal.h" + +// Keys for the strings in the plist file. +NSString *const kFIRAPIKey = @"API_KEY"; +NSString *const kFIRTrackingID = @"TRACKING_ID"; +NSString *const kFIRGoogleAppID = @"GOOGLE_APP_ID"; +NSString *const kFIRClientID = @"CLIENT_ID"; +NSString *const kFIRGCMSenderID = @"GCM_SENDER_ID"; +NSString *const kFIRAndroidClientID = @"ANDROID_CLIENT_ID"; +NSString *const kFIRDatabaseURL = @"DATABASE_URL"; +NSString *const kFIRStorageBucket = @"STORAGE_BUCKET"; +// The key to locate the expected bundle identifier in the plist file. +NSString *const kFIRBundleID = @"BUNDLE_ID"; +// The key to locate the project identifier in the plist file. +NSString *const kFIRProjectID = @"PROJECT_ID"; + +NSString *const kFIRIsMeasurementEnabled = @"IS_MEASUREMENT_ENABLED"; +NSString *const kFIRIsAnalyticsCollectionEnabled = @"FIREBASE_ANALYTICS_COLLECTION_ENABLED"; +NSString *const kFIRIsAnalyticsCollectionDeactivated = @"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED"; + +NSString *const kFIRIsAnalyticsEnabled = @"IS_ANALYTICS_ENABLED"; +NSString *const kFIRIsSignInEnabled = @"IS_SIGNIN_ENABLED"; + +// Library version ID formatted like: +// @"5" // Major version (one or more digits) +// @"04" // Minor version (exactly 2 digits) +// @"01" // Build number (exactly 2 digits) +// @"000"; // Fixed "000" +NSString *kFIRLibraryVersionID; + +// Plist file name. +NSString *const kServiceInfoFileName = @"GoogleService-Info"; +// Plist file type. +NSString *const kServiceInfoFileType = @"plist"; + +// Exception raised from attempting to modify a FIROptions after it's been copied to a FIRApp. +NSString *const kFIRExceptionBadModification = + @"Attempted to modify options after it's set on FIRApp. Please modify all properties before " + @"initializing FIRApp."; + +@interface FIROptions () + +/** + * This property maintains the actual configuration key-value pairs. + */ +@property(nonatomic, readwrite) NSMutableDictionary *optionsDictionary; + +/** + * Calls `analyticsOptionsDictionaryWithInfoDictionary:` using [NSBundle mainBundle].infoDictionary. + * It combines analytics options from both the infoDictionary and the GoogleService-Info.plist. + * Values which are present in the main plist override values from the GoogleService-Info.plist. + */ +@property(nonatomic, readonly) NSDictionary *analyticsOptionsDictionary; + +/** + * Combination of analytics options from both the infoDictionary and the GoogleService-Info.plist. + * Values which are present in the infoDictionary override values from the GoogleService-Info.plist. + */ +- (NSDictionary *)analyticsOptionsDictionaryWithInfoDictionary:(NSDictionary *)infoDictionary; + +/** + * Throw exception if editing is locked when attempting to modify an option. + */ +- (void)checkEditingLocked; + +@end + +@implementation FIROptions { + /// Backing variable for self.analyticsOptionsDictionary. + NSDictionary *_analyticsOptionsDictionary; +} + +static FIROptions *sDefaultOptions = nil; +static NSDictionary *sDefaultOptionsDictionary = nil; + +#pragma mark - Public only for internal class methods + ++ (FIROptions *)defaultOptions { + if (sDefaultOptions != nil) { + return sDefaultOptions; + } + + NSDictionary *defaultOptionsDictionary = [self defaultOptionsDictionary]; + if (defaultOptionsDictionary == nil) { + return nil; + } + + sDefaultOptions = [[FIROptions alloc] initInternalWithOptionsDictionary:defaultOptionsDictionary]; + return sDefaultOptions; +} + +#pragma mark - Private class methods + ++ (NSDictionary *)defaultOptionsDictionary { + if (sDefaultOptionsDictionary != nil) { + return sDefaultOptionsDictionary; + } + NSString *plistFilePath = [FIROptions plistFilePathWithName:kServiceInfoFileName]; + if (plistFilePath == nil) { + return nil; + } + sDefaultOptionsDictionary = [NSDictionary dictionaryWithContentsOfFile:plistFilePath]; + if (sDefaultOptionsDictionary == nil) { + FIRLogError(kFIRLoggerCore, @"I-COR000011", + @"The configuration file is not a dictionary: " + @"'%@.%@'.", + kServiceInfoFileName, kServiceInfoFileType); + } + return sDefaultOptionsDictionary; +} + +// Returns the path of the plist file with a given file name. ++ (NSString *)plistFilePathWithName:(NSString *)fileName { + NSArray *bundles = [FIRBundleUtil relevantBundles]; + NSString *plistFilePath = + [FIRBundleUtil optionsDictionaryPathWithResourceName:fileName + andFileType:kServiceInfoFileType + inBundles:bundles]; + if (plistFilePath == nil) { + FIRLogError(kFIRLoggerCore, @"I-COR000012", @"Could not locate configuration file: '%@.%@'.", + fileName, kServiceInfoFileType); + } + return plistFilePath; +} + ++ (void)resetDefaultOptions { + sDefaultOptions = nil; + sDefaultOptionsDictionary = nil; +} + +#pragma mark - Private instance methods + +- (instancetype)initInternalWithOptionsDictionary:(NSDictionary *)optionsDictionary { + self = [super init]; + if (self) { + _optionsDictionary = [optionsDictionary mutableCopy]; + _usingOptionsFromDefaultPlist = YES; + } + return self; +} + +- (id)copyWithZone:(NSZone *)zone { + FIROptions *newOptions = [[[self class] allocWithZone:zone] init]; + if (newOptions) { + newOptions.optionsDictionary = self.optionsDictionary; + newOptions.deepLinkURLScheme = self.deepLinkURLScheme; + newOptions.appGroupID = self.appGroupID; + newOptions.editingLocked = self.isEditingLocked; + newOptions.usingOptionsFromDefaultPlist = self.usingOptionsFromDefaultPlist; + } + return newOptions; +} + +#pragma mark - Public instance methods + +- (instancetype)initWithContentsOfFile:(NSString *)plistPath { + self = [super init]; + if (self) { + if (plistPath == nil) { + FIRLogError(kFIRLoggerCore, @"I-COR000013", @"The plist file path is nil."); + return nil; + } + _optionsDictionary = [[NSDictionary dictionaryWithContentsOfFile:plistPath] mutableCopy]; + if (_optionsDictionary == nil) { + FIRLogError(kFIRLoggerCore, @"I-COR000014", + @"The configuration file at %@ does not exist or " + @"is not a well-formed plist file.", + plistPath); + return nil; + } + // TODO: Do we want to validate the dictionary here? It says we do that already in + // the public header. + } + return self; +} + +- (instancetype)initWithGoogleAppID:(NSString *)googleAppID GCMSenderID:(NSString *)GCMSenderID { + self = [super init]; + if (self) { + NSMutableDictionary *mutableOptionsDict = [NSMutableDictionary dictionary]; + [mutableOptionsDict setValue:googleAppID forKey:kFIRGoogleAppID]; + [mutableOptionsDict setValue:GCMSenderID forKey:kFIRGCMSenderID]; + [mutableOptionsDict setValue:[[NSBundle mainBundle] bundleIdentifier] forKey:kFIRBundleID]; + self.optionsDictionary = mutableOptionsDict; + } + return self; +} + +- (NSString *)APIKey { + return self.optionsDictionary[kFIRAPIKey]; +} + +- (void)checkEditingLocked { + if (self.isEditingLocked) { + [NSException raise:kFirebaseCoreErrorDomain format:kFIRExceptionBadModification]; + } +} + +- (void)setAPIKey:(NSString *)APIKey { + [self checkEditingLocked]; + _optionsDictionary[kFIRAPIKey] = [APIKey copy]; +} + +- (NSString *)clientID { + return self.optionsDictionary[kFIRClientID]; +} + +- (void)setClientID:(NSString *)clientID { + [self checkEditingLocked]; + _optionsDictionary[kFIRClientID] = [clientID copy]; +} + +- (NSString *)trackingID { + return self.optionsDictionary[kFIRTrackingID]; +} + +- (void)setTrackingID:(NSString *)trackingID { + [self checkEditingLocked]; + _optionsDictionary[kFIRTrackingID] = [trackingID copy]; +} + +- (NSString *)GCMSenderID { + return self.optionsDictionary[kFIRGCMSenderID]; +} + +- (void)setGCMSenderID:(NSString *)GCMSenderID { + [self checkEditingLocked]; + _optionsDictionary[kFIRGCMSenderID] = [GCMSenderID copy]; +} + +- (NSString *)projectID { + return self.optionsDictionary[kFIRProjectID]; +} + +- (void)setProjectID:(NSString *)projectID { + [self checkEditingLocked]; + _optionsDictionary[kFIRProjectID] = [projectID copy]; +} + +- (NSString *)androidClientID { + return self.optionsDictionary[kFIRAndroidClientID]; +} + +- (void)setAndroidClientID:(NSString *)androidClientID { + [self checkEditingLocked]; + _optionsDictionary[kFIRAndroidClientID] = [androidClientID copy]; +} + +- (NSString *)googleAppID { + return self.optionsDictionary[kFIRGoogleAppID]; +} + +- (void)setGoogleAppID:(NSString *)googleAppID { + [self checkEditingLocked]; + _optionsDictionary[kFIRGoogleAppID] = [googleAppID copy]; +} + +- (NSString *)libraryVersionID { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + // The unit tests are set up to catch anything that does not properly convert. + NSString *version = [NSString stringWithUTF8String:FIRCoreVersionString]; + NSArray *components = [version componentsSeparatedByString:@"."]; + NSString *major = [components objectAtIndex:0]; + NSString *minor = [NSString stringWithFormat:@"%02d", [[components objectAtIndex:1] intValue]]; + NSString *patch = [NSString stringWithFormat:@"%02d", [[components objectAtIndex:2] intValue]]; + kFIRLibraryVersionID = [NSString stringWithFormat:@"%@%@%@000", major, minor, patch]; + }); + return kFIRLibraryVersionID; +} + +- (void)setLibraryVersionID:(NSString *)libraryVersionID { + _optionsDictionary[kFIRLibraryVersionID] = [libraryVersionID copy]; +} + +- (NSString *)databaseURL { + return self.optionsDictionary[kFIRDatabaseURL]; +} + +- (void)setDatabaseURL:(NSString *)databaseURL { + [self checkEditingLocked]; + + _optionsDictionary[kFIRDatabaseURL] = [databaseURL copy]; +} + +- (NSString *)storageBucket { + return self.optionsDictionary[kFIRStorageBucket]; +} + +- (void)setStorageBucket:(NSString *)storageBucket { + [self checkEditingLocked]; + _optionsDictionary[kFIRStorageBucket] = [storageBucket copy]; +} + +- (void)setDeepLinkURLScheme:(NSString *)deepLinkURLScheme { + [self checkEditingLocked]; + _deepLinkURLScheme = [deepLinkURLScheme copy]; +} + +- (NSString *)bundleID { + return self.optionsDictionary[kFIRBundleID]; +} + +- (void)setBundleID:(NSString *)bundleID { + [self checkEditingLocked]; + _optionsDictionary[kFIRBundleID] = [bundleID copy]; +} + +- (void)setAppGroupID:(NSString *)appGroupID { + [self checkEditingLocked]; + _appGroupID = [appGroupID copy]; +} + +#pragma mark - Equality + +- (BOOL)isEqual:(id)object { + if (!object || ![object isKindOfClass:[FIROptions class]]) { + return NO; + } + + return [self isEqualToOptions:(FIROptions *)object]; +} + +- (BOOL)isEqualToOptions:(FIROptions *)options { + // Skip any non-FIROptions classes. + if (![options isKindOfClass:[FIROptions class]]) { + return NO; + } + + // Check the internal dictionary and custom properties for differences. + if (![options.optionsDictionary isEqualToDictionary:self.optionsDictionary]) { + return NO; + } + + // Validate extra properties not contained in the dictionary. Only validate it if one of the + // objects has the property set. + if ((options.deepLinkURLScheme != nil || self.deepLinkURLScheme != nil) && + ![options.deepLinkURLScheme isEqualToString:self.deepLinkURLScheme]) { + return NO; + } + + if ((options.appGroupID != nil || self.appGroupID != nil) && + ![options.appGroupID isEqualToString:self.appGroupID]) { + return NO; + } + + // Validate the Analytics options haven't changed with the Info.plist. + if (![options.analyticsOptionsDictionary isEqualToDictionary:self.analyticsOptionsDictionary]) { + return NO; + } + + // We don't care about the `editingLocked` or `usingOptionsFromDefaultPlist` properties since + // those relate to lifecycle and construction, we only care if the contents of the options + // themselves are equal. + return YES; +} + +- (NSUInteger)hash { + // This is strongly recommended for any object that implements a custom `isEqual:` method to + // ensure that dictionary and set behavior matches other `isEqual:` checks. + // Note: `self.analyticsOptionsDictionary` was left out here since it solely relies on the + // contents of the main bundle's `Info.plist`. We should avoid reading that file and the contents + // should be identical. + return self.optionsDictionary.hash ^ self.deepLinkURLScheme.hash ^ self.appGroupID.hash; +} + +#pragma mark - Internal instance methods + +- (NSDictionary *)analyticsOptionsDictionaryWithInfoDictionary:(NSDictionary *)infoDictionary { + if (_analyticsOptionsDictionary == nil) { + NSMutableDictionary *tempAnalyticsOptions = [[NSMutableDictionary alloc] init]; + NSArray *measurementKeys = @[ + kFIRIsMeasurementEnabled, kFIRIsAnalyticsCollectionEnabled, + kFIRIsAnalyticsCollectionDeactivated + ]; + for (NSString *key in measurementKeys) { + id value = infoDictionary[key] ?: self.optionsDictionary[key] ?: nil; + if (!value) { + continue; + } + tempAnalyticsOptions[key] = value; + } + _analyticsOptionsDictionary = tempAnalyticsOptions; + } + return _analyticsOptionsDictionary; +} + +- (NSDictionary *)analyticsOptionsDictionary { + return [self analyticsOptionsDictionaryWithInfoDictionary:[NSBundle mainBundle].infoDictionary]; +} + +/** + * Whether or not Measurement was enabled. Measurement is enabled unless explicitly disabled in + * GoogleService-Info.plist. This uses the old plist flag IS_MEASUREMENT_ENABLED, which should still + * be supported. + */ +- (BOOL)isMeasurementEnabled { + if (self.isAnalyticsCollectionDeactivated) { + return NO; + } + NSNumber *value = self.analyticsOptionsDictionary[kFIRIsMeasurementEnabled]; + if (value == nil) { + // TODO: This could probably be cleaned up since FIROptions shouldn't know about FIRApp or have + // to check if it's the default app. The FIROptions instance can't be modified after + // `+configure` is called, so it's not a good place to copy it either in case the flag is + // changed at runtime. + + // If no values are set for Analytics, fall back to the global collection switch in FIRApp. + // Analytics only supports the default FIRApp, so check that first. + if (![FIRApp isDefaultAppConfigured]) { + return NO; + } + + // Fall back to the default app's collection switch when the key is not in the dictionary. + return [FIRApp defaultApp].isDataCollectionDefaultEnabled; + } + return [value boolValue]; +} + +- (BOOL)isAnalyticsCollectionExplicitlySet { + // If it's de-activated, it classifies as explicity set. If not, it's not a good enough indication + // that the developer wants FirebaseAnalytics enabled so continue checking. + if (self.isAnalyticsCollectionDeactivated) { + return YES; + } + + // Check if the current Analytics flag is set. + id collectionEnabledObject = self.analyticsOptionsDictionary[kFIRIsAnalyticsCollectionEnabled]; + if (collectionEnabledObject && [collectionEnabledObject isKindOfClass:[NSNumber class]]) { + // It doesn't matter what the value is, it's explicitly set. + return YES; + } + + // Check if the old measurement flag is set. + id measurementEnabledObject = self.analyticsOptionsDictionary[kFIRIsMeasurementEnabled]; + if (measurementEnabledObject && [measurementEnabledObject isKindOfClass:[NSNumber class]]) { + // It doesn't matter what the value is, it's explicitly set. + return YES; + } + + // No flags are set to explicitly enable or disable FirebaseAnalytics. + return NO; +} + +- (BOOL)isAnalyticsCollectionEnabled { + if (self.isAnalyticsCollectionDeactivated) { + return NO; + } + NSNumber *value = self.analyticsOptionsDictionary[kFIRIsAnalyticsCollectionEnabled]; + if (value == nil) { + return self.isMeasurementEnabled; // Fall back to older plist flag. + } + return [value boolValue]; +} + +- (BOOL)isAnalyticsCollectionDeactivated { + NSNumber *value = self.analyticsOptionsDictionary[kFIRIsAnalyticsCollectionDeactivated]; + if (value == nil) { + return NO; // Analytics Collection is not deactivated when the key is not in the dictionary. + } + return [value boolValue]; +} + +- (BOOL)isAnalyticsEnabled { + return [self.optionsDictionary[kFIRIsAnalyticsEnabled] boolValue]; +} + +- (BOOL)isSignInEnabled { + return [self.optionsDictionary[kFIRIsSignInEnabled] boolValue]; +} + +@end diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRVersion.h b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRVersion.h new file mode 100644 index 0000000..226efb1 --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRVersion.h @@ -0,0 +1,23 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +/** The version of the Firebase SDK. */ +FOUNDATION_EXPORT const char *const FIRVersionString; + +/** The version of the FirebaseCore Component. */ +FOUNDATION_EXPORT const char *const FIRCoreVersionString; diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRVersion.m b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRVersion.m new file mode 100644 index 0000000..ec0f6ba --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/FIRVersion.m @@ -0,0 +1,33 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef Firebase_VERSION +#error "Firebase_VERSION is not defined: add -DFirebase_VERSION=... to the build invocation" +#endif + +#ifndef FIRCore_VERSION +#error "FIRCore_VERSION is not defined: add -DFIRCore_VERSION=... to the build invocation" +#endif + +// The following two macros supply the incantation so that the C +// preprocessor does not try to parse the version as a floating +// point number. See +// https://www.guyrutenberg.com/2008/12/20/expanding-macros-into-string-constants-in-c/ +#define STR(x) STR_EXPAND(x) +#define STR_EXPAND(x) #x + +const char *const FIRVersionString = (const char *const)STR(Firebase_VERSION); +const char *const FIRCoreVersionString = (const char *const)STR(FIRCore_VERSION); diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRAnalyticsConfiguration.h b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRAnalyticsConfiguration.h new file mode 100644 index 0000000..6429ac7 --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRAnalyticsConfiguration.h @@ -0,0 +1,56 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +/// Values stored in analyticsEnabledState. Never alter these constants since they must match with +/// values persisted to disk. +typedef NS_ENUM(int64_t, FIRAnalyticsEnabledState) { + // 0 is the default value for keys not found stored in persisted config, so it cannot represent + // kFIRAnalyticsEnabledStateSetNo. It must represent kFIRAnalyticsEnabledStateNotSet. + kFIRAnalyticsEnabledStateNotSet = 0, + kFIRAnalyticsEnabledStateSetYes = 1, + kFIRAnalyticsEnabledStateSetNo = 2, +}; + +/// The user defaults key for the persisted measurementEnabledState value. FIRAPersistedConfig reads +/// measurementEnabledState using this same key. +static NSString *const kFIRAPersistedConfigMeasurementEnabledStateKey = + @"/google/measurement/measurement_enabled_state"; + +static NSString *const kFIRAnalyticsConfigurationSetEnabledNotification = + @"FIRAnalyticsConfigurationSetEnabledNotification"; +static NSString *const kFIRAnalyticsConfigurationSetMinimumSessionIntervalNotification = + @"FIRAnalyticsConfigurationSetMinimumSessionIntervalNotification"; +static NSString *const kFIRAnalyticsConfigurationSetSessionTimeoutIntervalNotification = + @"FIRAnalyticsConfigurationSetSessionTimeoutIntervalNotification"; + +@interface FIRAnalyticsConfiguration : NSObject + +/// Returns the shared instance of FIRAnalyticsConfiguration. ++ (FIRAnalyticsConfiguration *)sharedInstance; + +// Sets whether analytics collection is enabled for this app on this device. This setting is +// persisted across app sessions. By default it is enabled. +- (void)setAnalyticsCollectionEnabled:(BOOL)analyticsCollectionEnabled; + +/// Sets whether analytics collection is enabled for this app on this device, and a flag to persist +/// the value or not. The setting should not be persisted if being set by the global data collection +/// flag. +- (void)setAnalyticsCollectionEnabled:(BOOL)analyticsCollectionEnabled + persistSetting:(BOOL)shouldPersist; + +@end diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRAppAssociationRegistration.h b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRAppAssociationRegistration.h new file mode 100644 index 0000000..3fc69c6 --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRAppAssociationRegistration.h @@ -0,0 +1,49 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +// TODO: Remove this once Auth moves over to Core's instance registration system. +/** @class FIRAppAssociationRegistration + @brief Manages object associations as a singleton-dependent: At most one object is + registered for any given host/key pair, and the object shall be created on-the-fly when + asked for. + */ +@interface FIRAppAssociationRegistration : NSObject + +/** @fn registeredObjectWithHost:key:creationBlock: + @brief Retrieves the registered object with a particular host and key. + @param host The host object. + @param key The key to specify the registered object on the host. + @param creationBlock The block to return the object to be registered if not already. + The block is executed immediately before this method returns if it is executed at all. + It can also be executed multiple times across different method invocations if previous + execution of the block returns @c nil. + @return The registered object for the host/key pair, or @c nil if no object is registered + and @c creationBlock returns @c nil. + @remarks The method is thread-safe but non-reentrant in the sense that attempting to call this + method again within the @c creationBlock with the same host/key pair raises an exception. + The registered object is retained by the host. + */ ++ (nullable ObjectType)registeredObjectWithHost:(id)host + key:(NSString *)key + creationBlock:(ObjectType _Nullable (^)(void))creationBlock; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRAppInternal.h b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRAppInternal.h new file mode 100644 index 0000000..ad1a186 --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRAppInternal.h @@ -0,0 +1,162 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import +#import + +@class FIRComponentContainer; +@protocol FIRLibrary; + +/** + * The internal interface to FIRApp. This is meant for first-party integrators, who need to receive + * FIRApp notifications, log info about the success or failure of their configuration, and access + * other internal functionality of FIRApp. + * + * TODO(b/28296561): Restructure this header. + */ +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSInteger, FIRConfigType) { + FIRConfigTypeCore = 1, + FIRConfigTypeSDK = 2, +}; + +extern NSString *const kFIRDefaultAppName; +extern NSString *const kFIRAppReadyToConfigureSDKNotification; +extern NSString *const kFIRAppDeleteNotification; +extern NSString *const kFIRAppIsDefaultAppKey; +extern NSString *const kFIRAppNameKey; +extern NSString *const kFIRGoogleAppIDKey; + +/** + * The format string for the User Defaults key used for storing the data collection enabled flag. + * This includes formatting to append the Firebase App's name. + */ +extern NSString *const kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat; + +/** + * The plist key used for storing the data collection enabled flag. + */ +extern NSString *const kFIRGlobalAppDataCollectionEnabledPlistKey; + +/** + * A notification fired containing diagnostic information when SDK errors occur. + */ +extern NSString *const kFIRAppDiagnosticsNotification; + +/** @var FIRAuthStateDidChangeInternalNotification + @brief The name of the @c NSNotificationCenter notification which is posted when the auth state + changes (e.g. a new token has been produced, a user logs in or out). The object parameter of + the notification is a dictionary possibly containing the key: + @c FIRAuthStateDidChangeInternalNotificationTokenKey (the new access token.) If it does not + contain this key it indicates a sign-out event took place. + */ +extern NSString *const FIRAuthStateDidChangeInternalNotification; + +/** @var FIRAuthStateDidChangeInternalNotificationTokenKey + @brief A key present in the dictionary object parameter of the + @c FIRAuthStateDidChangeInternalNotification notification. The value associated with this + key will contain the new access token. + */ +extern NSString *const FIRAuthStateDidChangeInternalNotificationTokenKey; + +/** @var FIRAuthStateDidChangeInternalNotificationAppKey + @brief A key present in the dictionary object parameter of the + @c FIRAuthStateDidChangeInternalNotification notification. The value associated with this + key will contain the FIRApp associated with the auth instance. + */ +extern NSString *const FIRAuthStateDidChangeInternalNotificationAppKey; + +/** @var FIRAuthStateDidChangeInternalNotificationUIDKey + @brief A key present in the dictionary object parameter of the + @c FIRAuthStateDidChangeInternalNotification notification. The value associated with this + key will contain the new user's UID (or nil if there is no longer a user signed in). + */ +extern NSString *const FIRAuthStateDidChangeInternalNotificationUIDKey; + +@interface FIRApp () + +/** + * A flag indicating if this is the default app (has the default app name). + */ +@property(nonatomic, readonly) BOOL isDefaultApp; + +/* + * The container of interop SDKs for this app. + */ +@property(nonatomic) FIRComponentContainer *container; + +/** + * Creates an error for failing to configure a subspec service. This method is called by each + * FIRApp notification listener. + */ ++ (NSError *)errorForSubspecConfigurationFailureWithDomain:(NSString *)domain + errorCode:(FIRErrorCode)code + service:(NSString *)service + reason:(NSString *)reason; +/** + * Checks if the default app is configured without trying to configure it. + */ ++ (BOOL)isDefaultAppConfigured; + +/** + * Registers a given third-party library with the given version number to be reported for + * analytics. + * + * @param name Name of the library. + * @param version Version of the library. + */ ++ (void)registerLibrary:(nonnull NSString *)name withVersion:(nonnull NSString *)version; + +/** + * Registers a given internal library with the given version number to be reported for + * analytics. + * + * @param library Optional parameter for component registration. + * @param name Name of the library. + * @param version Version of the library. + */ ++ (void)registerInternalLibrary:(nonnull Class)library + withName:(nonnull NSString *)name + withVersion:(nonnull NSString *)version; + +/** + * A concatenated string representing all the third-party libraries and version numbers. + */ ++ (NSString *)firebaseUserAgent; + +/** + * Used by each SDK to send logs about SDK configuration status to Clearcut. + * + * @note This API is a no-op, please remove calls to it. + */ +- (void)sendLogsWithServiceName:(NSString *)serviceName + version:(NSString *)version + error:(NSError *)error; + +/** + * Can be used by the unit tests in eack SDK to reset FIRApp. This method is thread unsafe. + */ ++ (void)resetApps; + +/** + * Can be used by the unit tests in each SDK to set customized options. + */ +- (instancetype)initInstanceWithName:(NSString *)name options:(FIROptions *)options; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRComponent.h b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRComponent.h new file mode 100644 index 0000000..cb51ee7 --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRComponent.h @@ -0,0 +1,91 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FIRApp; +@class FIRComponentContainer; + +NS_ASSUME_NONNULL_BEGIN + +/// Provides a system to clean up cached instances returned from the component system. +NS_SWIFT_NAME(ComponentLifecycleMaintainer) +@protocol FIRComponentLifecycleMaintainer +/// The associated app will be deleted, clean up any resources as they are about to be deallocated. +- (void)appWillBeDeleted:(FIRApp *)app; +@end + +typedef _Nullable id (^FIRComponentCreationBlock)(FIRComponentContainer *container, + BOOL *isCacheable) + NS_SWIFT_NAME(ComponentCreationBlock); + +@class FIRDependency; + +/// Describes the timing of instantiation. Note: new components should default to lazy unless there +/// is a strong reason to be eager. +typedef NS_ENUM(NSInteger, FIRInstantiationTiming) { + FIRInstantiationTimingLazy, + FIRInstantiationTimingAlwaysEager, + FIRInstantiationTimingEagerInDefaultApp +} NS_SWIFT_NAME(InstantiationTiming); + +/// A component that can be used from other Firebase SDKs. +NS_SWIFT_NAME(Component) +@interface FIRComponent : NSObject + +/// The protocol describing functionality provided from the Component. +@property(nonatomic, strong, readonly) Protocol *protocol; + +/// The timing of instantiation. +@property(nonatomic, readonly) FIRInstantiationTiming instantiationTiming; + +/// An array of dependencies for the component. +@property(nonatomic, copy, readonly) NSArray *dependencies; + +/// A block to instantiate an instance of the component with the appropriate dependencies. +@property(nonatomic, copy, readonly) FIRComponentCreationBlock creationBlock; + +// There's an issue with long NS_SWIFT_NAMES that causes compilation to fail, disable clang-format +// for the next two methods. +// clang-format off + +/// Creates a component with no dependencies that will be lazily initialized. ++ (instancetype)componentWithProtocol:(Protocol *)protocol + creationBlock:(FIRComponentCreationBlock)creationBlock +NS_SWIFT_NAME(init(_:creationBlock:)); + +/// Creates a component to be registered with the component container. +/// +/// @param protocol - The protocol describing functionality provided by the component. +/// @param instantiationTiming - When the component should be initialized. Use .lazy unless there's +/// a good reason to be instantiated earlier. +/// @param dependencies - Any dependencies the `implementingClass` has, optional or required. +/// @param creationBlock - A block to instantiate the component with a container, and if +/// @return A component that can be registered with the component container. ++ (instancetype)componentWithProtocol:(Protocol *)protocol + instantiationTiming:(FIRInstantiationTiming)instantiationTiming + dependencies:(NSArray *)dependencies + creationBlock:(FIRComponentCreationBlock)creationBlock +NS_SWIFT_NAME(init(_:instantiationTiming:dependencies:creationBlock:)); + +// clang-format on + +/// Unavailable. +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRComponentContainer.h b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRComponentContainer.h new file mode 100644 index 0000000..8dfab9c --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRComponentContainer.h @@ -0,0 +1,44 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/// A type-safe macro to retrieve a component from a container. This should be used to retrieve +/// components instead of using the container directly. +#define FIR_COMPONENT(type, container) \ + [FIRComponentType> instanceForProtocol:@protocol(type) inContainer:container] + +@class FIRApp; + +/// A container that holds different components that are registered via the +/// `registerAsComponentRegistrant:` call. These classes should conform to `FIRComponentRegistrant` +/// in order to properly register components for Core. +NS_SWIFT_NAME(FirebaseComponentContainer) +@interface FIRComponentContainer : NSObject + +/// A weak reference to the app that an instance of the container belongs to. +@property(nonatomic, weak, readonly) FIRApp *app; + +/// Unavailable. Use the `container` property on `FIRApp`. +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRComponentContainerInternal.h b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRComponentContainerInternal.h new file mode 100644 index 0000000..2b77981 --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRComponentContainerInternal.h @@ -0,0 +1,46 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#import + +#import +#import + +@class FIRApp; + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRComponentContainer (Private) + +/// Initializes a container for a given app. This should only be called by the app itself. +- (instancetype)initWithApp:(FIRApp *)app; + +/// Retrieves an instance that conforms to the specified protocol. This will return `nil` if the +/// protocol wasn't registered, or if the instance couldn't be instantiated for the provided app. +- (nullable id)instanceForProtocol:(Protocol *)protocol NS_SWIFT_NAME(instance(for:)); + +/// Instantiates all the components that have registered as "eager" after initialization. +- (void)instantiateEagerComponents; + +/// Remove all of the cached instances stored and allow them to clean up after themselves. +- (void)removeAllCachedInstances; + +/// Register a class to provide components for the interoperability system. The class should conform +/// to `FIRComponentRegistrant` and provide an array of `FIRComponent` objects. ++ (void)registerAsComponentRegistrant:(Class)klass; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRComponentType.h b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRComponentType.h new file mode 100644 index 0000000..6f2aca7 --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRComponentType.h @@ -0,0 +1,34 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FIRComponentContainer; + +NS_ASSUME_NONNULL_BEGIN + +/// Do not use directly. A placeholder type in order to provide a macro that will warn users of +/// mis-matched protocols. +NS_SWIFT_NAME(ComponentType) +@interface FIRComponentType<__covariant T> : NSObject + +/// Do not use directly. A factory method to retrieve an instance that provides a specific +/// functionality. ++ (T)instanceForProtocol:(Protocol *)protocol inContainer:(FIRComponentContainer *)container; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRConfigurationInternal.h b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRConfigurationInternal.h new file mode 100644 index 0000000..0d1a36f --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRConfigurationInternal.h @@ -0,0 +1,29 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FIRAnalyticsConfiguration; + +@interface FIRConfiguration () + +/** + * The configuration class for Firebase Analytics. This should be removed once the logic for + * enabling and disabling Analytics is moved to Analytics. + */ +@property(nonatomic, readwrite) FIRAnalyticsConfiguration *analyticsConfiguration; + +@end diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRCoreDiagnosticsConnector.h b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRCoreDiagnosticsConnector.h new file mode 100644 index 0000000..76c0c05 --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRCoreDiagnosticsConnector.h @@ -0,0 +1,35 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FIRDiagnosticsData; +@class FIROptions; + +NS_ASSUME_NONNULL_BEGIN + +/** Connects FIRCore with the CoreDiagnostics library. */ +@interface FIRCoreDiagnosticsConnector : NSObject + +/** Logs FirebaseCore related data. + * + * @param options The options object containing data to log. + */ ++ (void)logCoreTelemetryWithOptions:(FIROptions *)options; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRDependency.h b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRDependency.h new file mode 100644 index 0000000..46e9b7e --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRDependency.h @@ -0,0 +1,45 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// A dependency on a specific protocol's functionality. +NS_SWIFT_NAME(Dependency) +@interface FIRDependency : NSObject + +/// The protocol describing functionality being depended on. +@property(nonatomic, strong, readonly) Protocol *protocol; + +/// A flag to specify if the dependency is required or not. +@property(nonatomic, readonly) BOOL isRequired; + +/// Initializes a dependency that is required. Calls `initWithProtocol:isRequired` with `YES` for +/// the required parameter. +/// Creates a required dependency on the specified protocol's functionality. ++ (instancetype)dependencyWithProtocol:(Protocol *)protocol; + +/// Creates a dependency on the specified protocol's functionality and specify if it's required for +/// the class's functionality. ++ (instancetype)dependencyWithProtocol:(Protocol *)protocol isRequired:(BOOL)required; + +/// Use `dependencyWithProtocol:isRequired:` instead. +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRDiagnosticsData.h b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRDiagnosticsData.h new file mode 100644 index 0000000..ac5ef2c --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRDiagnosticsData.h @@ -0,0 +1,35 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Implements the FIRCoreDiagnosticsData protocol to log diagnostics data. */ +@interface FIRDiagnosticsData : NSObject + +/** Inserts values into the diagnosticObjects dictionary if the value isn't nil. + * + * @param value The value to insert if it's not nil. + * @param key The key to associate it with. + */ +- (void)insertValue:(nullable id)value forKey:(NSString *)key; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRErrorCode.h b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRErrorCode.h new file mode 100644 index 0000000..f77b3d0 --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRErrorCode.h @@ -0,0 +1,38 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** Error codes in Firebase error domain. */ +typedef NS_ENUM(NSInteger, FIRErrorCode) { + /** + * Unknown error. + */ + FIRErrorCodeUnknown = 0, + /** + * Loading data from the GoogleService-Info.plist file failed. This is a fatal error and should + * not be ignored. Further calls to the API will fail and/or possibly cause crashes. + */ + FIRErrorCodeInvalidPlistFile = -100, + + /** + * Validating the Google App ID format failed. + */ + FIRErrorCodeInvalidAppID = -101, + + /** + * Error code for failing to configure a specific service. + */ + FIRErrorCodeConfigFailed = -114, +}; diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRErrors.h b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRErrors.h new file mode 100644 index 0000000..19e4732 --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRErrors.h @@ -0,0 +1,24 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#include "FIRErrorCode.h" + +extern NSString *const kFirebaseErrorDomain; +extern NSString *const kFirebaseConfigErrorDomain; +extern NSString *const kFirebaseCoreErrorDomain; +extern NSString *const kFirebasePerfErrorDomain; diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRHeartbeatInfo.h b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRHeartbeatInfo.h new file mode 100644 index 0000000..bfff73e --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRHeartbeatInfo.h @@ -0,0 +1,39 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRHeartbeatInfo : NSObject + +// Enum representing the different heartbeat codes. +typedef NS_ENUM(NSInteger, FIRHeartbeatInfoCode) { + FIRHeartbeatInfoCodeNone = 0, + FIRHeartbeatInfoCodeSDK = 1, + FIRHeartbeatInfoCodeGlobal = 2, + FIRHeartbeatInfoCodeCombined = 3, +}; + +/** + * Get heartbeat code requred for the sdk. + * @param heartbeatTag String representing the sdk heartbeat tag. + * @return Heartbeat code indicating whether or not an sdk/global heartbeat + * needs to be sent + */ ++ (FIRHeartbeatInfoCode)heartbeatCodeForTag:(NSString *)heartbeatTag; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRLibrary.h b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRLibrary.h new file mode 100644 index 0000000..af9d968 --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRLibrary.h @@ -0,0 +1,45 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRLibrary_h +#define FIRLibrary_h + +#import + +#import + +@class FIRApp; + +NS_ASSUME_NONNULL_BEGIN + +/// Provide an interface to register a library for userAgent logging and availability to others. +NS_SWIFT_NAME(Library) +@protocol FIRLibrary + +/// Returns one or more FIRComponents that will be registered in +/// FIRApp and participate in dependency resolution and injection. ++ (NSArray *)componentsToRegister; + +@optional +/// Implement this method if the library needs notifications for lifecycle events. This method is +/// called when the developer calls `FirebaseApp.configure()`. ++ (void)configureWithApp:(FIRApp *)app; + +@end + +NS_ASSUME_NONNULL_END + +#endif /* FIRLibrary_h */ diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRLogger.h b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRLogger.h new file mode 100644 index 0000000..548e389 --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRLogger.h @@ -0,0 +1,151 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * The Firebase services used in Firebase logger. + */ +typedef NSString *const FIRLoggerService; + +extern FIRLoggerService kFIRLoggerABTesting; +extern FIRLoggerService kFIRLoggerAdMob; +extern FIRLoggerService kFIRLoggerAnalytics; +extern FIRLoggerService kFIRLoggerAuth; +extern FIRLoggerService kFIRLoggerCrash; +extern FIRLoggerService kFIRLoggerCore; +extern FIRLoggerService kFIRLoggerMLKit; +extern FIRLoggerService kFIRLoggerPerf; +extern FIRLoggerService kFIRLoggerRemoteConfig; + +/** + * The key used to store the logger's error count. + */ +extern NSString *const kFIRLoggerErrorCountKey; + +/** + * The key used to store the logger's warning count. + */ +extern NSString *const kFIRLoggerWarningCountKey; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * Enables or disables Analytics debug mode. + * If set to YES, the logging level for Analytics will be set to FIRLoggerLevelDebug. + * Enabling the debug mode has no effect if the app is running from App Store. + * (required) analytics debug mode flag. + */ +void FIRSetAnalyticsDebugMode(BOOL analyticsDebugMode); + +/** + * Changes the default logging level of FIRLoggerLevelNotice to a user-specified level. + * The default level cannot be set above FIRLoggerLevelNotice if the app is running from App Store. + * (required) log level (one of the FIRLoggerLevel enum values). + */ +void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel); + +/** + * Checks if the specified logger level is loggable given the current settings. + * (required) log level (one of the FIRLoggerLevel enum values). + * (required) whether or not this function is called from the Analytics component. + */ +BOOL FIRIsLoggableLevel(FIRLoggerLevel loggerLevel, BOOL analyticsComponent); + +/** + * Logs a message to the Xcode console and the device log. If running from AppStore, will + * not log any messages with a level higher than FIRLoggerLevelNotice to avoid log spamming. + * (required) log level (one of the FIRLoggerLevel enum values). + * (required) service name of type FIRLoggerService. + * (required) message code starting with "I-" which means iOS, followed by a capitalized + * three-character service identifier and a six digit integer message ID that is unique + * within the service. + * An example of the message code is @"I-COR000001". + * (required) message string which can be a format string. + * (optional) variable arguments list obtained from calling va_start, used when message is a format + * string. + */ +extern void FIRLogBasic(FIRLoggerLevel level, + FIRLoggerService service, + NSString *messageCode, + NSString *message, +// On 64-bit simulators, va_list is not a pointer, so cannot be marked nullable +// See: http://stackoverflow.com/q/29095469 +#if __LP64__ && TARGET_OS_SIMULATOR || TARGET_OS_OSX + va_list args_ptr +#else + va_list _Nullable args_ptr +#endif +); + +/** + * The following functions accept the following parameters in order: + * (required) service name of type FIRLoggerService. + * (required) message code starting from "I-" which means iOS, followed by a capitalized + * three-character service identifier and a six digit integer message ID that is unique + * within the service. + * An example of the message code is @"I-COR000001". + * See go/firebase-log-proposal for details. + * (required) message string which can be a format string. + * (optional) the list of arguments to substitute into the format string. + * Example usage: + * FIRLogError(kFIRLoggerCore, @"I-COR000001", @"Configuration of %@ failed.", app.name); + */ +extern void FIRLogError(FIRLoggerService service, NSString *messageCode, NSString *message, ...) + NS_FORMAT_FUNCTION(3, 4); +extern void FIRLogWarning(FIRLoggerService service, NSString *messageCode, NSString *message, ...) + NS_FORMAT_FUNCTION(3, 4); +extern void FIRLogNotice(FIRLoggerService service, NSString *messageCode, NSString *message, ...) + NS_FORMAT_FUNCTION(3, 4); +extern void FIRLogInfo(FIRLoggerService service, NSString *messageCode, NSString *message, ...) + NS_FORMAT_FUNCTION(3, 4); +extern void FIRLogDebug(FIRLoggerService service, NSString *messageCode, NSString *message, ...) + NS_FORMAT_FUNCTION(3, 4); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +@interface FIRLoggerWrapper : NSObject + +/** + * Objective-C wrapper for FIRLogBasic to allow weak linking to FIRLogger + * (required) log level (one of the FIRLoggerLevel enum values). + * (required) service name of type FIRLoggerService. + * (required) message code starting with "I-" which means iOS, followed by a capitalized + * three-character service identifier and a six digit integer message ID that is unique + * within the service. + * An example of the message code is @"I-COR000001". + * (required) message string which can be a format string. + * (optional) variable arguments list obtained from calling va_start, used when message is a format + * string. + */ + ++ (void)logWithLevel:(FIRLoggerLevel)level + withService:(FIRLoggerService)service + withCode:(NSString *)messageCode + withMessage:(NSString *)message + withArgs:(va_list)args; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIROptionsInternal.h b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIROptionsInternal.h new file mode 100644 index 0000000..0660a3c --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Private/FIROptionsInternal.h @@ -0,0 +1,114 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +/** + * Keys for the strings in the plist file. + */ +extern NSString *const kFIRAPIKey; +extern NSString *const kFIRTrackingID; +extern NSString *const kFIRGoogleAppID; +extern NSString *const kFIRClientID; +extern NSString *const kFIRGCMSenderID; +extern NSString *const kFIRAndroidClientID; +extern NSString *const kFIRDatabaseURL; +extern NSString *const kFIRStorageBucket; +extern NSString *const kFIRBundleID; +extern NSString *const kFIRProjectID; + +/** + * Keys for the plist file name + */ +extern NSString *const kServiceInfoFileName; + +extern NSString *const kServiceInfoFileType; + +/** + * This header file exposes the initialization of FIROptions to internal use. + */ +@interface FIROptions () + +/** + * resetDefaultOptions and initInternalWithOptionsDictionary: are exposed only for unit tests. + */ ++ (void)resetDefaultOptions; + +/** + * Initializes the options with dictionary. The above strings are the keys of the dictionary. + * This is the designated initializer. + */ +- (instancetype)initInternalWithOptionsDictionary:(NSDictionary *)serviceInfoDictionary; + +/** + * defaultOptions and defaultOptionsDictionary are exposed in order to be used in FIRApp and + * other first party services. + */ ++ (FIROptions *)defaultOptions; + ++ (NSDictionary *)defaultOptionsDictionary; + +/** + * Indicates whether or not Analytics collection was explicitly enabled via a plist flag or at + * runtime. + */ +@property(nonatomic, readonly) BOOL isAnalyticsCollectionExplicitlySet; + +/** + * Whether or not Analytics Collection was enabled. Analytics Collection is enabled unless + * explicitly disabled in GoogleService-Info.plist. + */ +@property(nonatomic, readonly) BOOL isAnalyticsCollectionEnabled; + +/** + * Whether or not Analytics Collection was completely disabled. If YES, then + * isAnalyticsCollectionEnabled will be NO. + */ +@property(nonatomic, readonly) BOOL isAnalyticsCollectionDeactivated; + +/** + * The version ID of the client library, e.g. @"1100000". + */ +@property(nonatomic, readonly, copy) NSString *libraryVersionID; + +/** + * The flag indicating whether this object was constructed with the values in the default plist + * file. + */ +@property(nonatomic) BOOL usingOptionsFromDefaultPlist; + +/** + * Whether or not Measurement was enabled. Measurement is enabled unless explicitly disabled in + * GoogleService-Info.plist. + */ +@property(nonatomic, readonly) BOOL isMeasurementEnabled; + +/** + * Whether or not Analytics was enabled in the developer console. + */ +@property(nonatomic, readonly) BOOL isAnalyticsEnabled; + +/** + * Whether or not SignIn was enabled in the developer console. + */ +@property(nonatomic, readonly) BOOL isSignInEnabled; + +/** + * Whether or not editing is locked. This should occur after FIROptions has been set on a FIRApp. + */ +@property(nonatomic, getter=isEditingLocked) BOOL editingLocked; + +@end diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Public/FIRApp.h b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Public/FIRApp.h new file mode 100644 index 0000000..e0dd6d6 --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Public/FIRApp.h @@ -0,0 +1,127 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FIROptions; + +NS_ASSUME_NONNULL_BEGIN + +/** A block that takes a BOOL and has no return value. */ +typedef void (^FIRAppVoidBoolCallback)(BOOL success) NS_SWIFT_NAME(FirebaseAppVoidBoolCallback); + +/** + * The entry point of Firebase SDKs. + * + * Initialize and configure FIRApp using +[FIRApp configure] + * or other customized ways as shown below. + * + * The logging system has two modes: default mode and debug mode. In default mode, only logs with + * log level Notice, Warning and Error will be sent to device. In debug mode, all logs will be sent + * to device. The log levels that Firebase uses are consistent with the ASL log levels. + * + * Enable debug mode by passing the -FIRDebugEnabled argument to the application. You can add this + * argument in the application's Xcode scheme. When debug mode is enabled via -FIRDebugEnabled, + * further executions of the application will also be in debug mode. In order to return to default + * mode, you must explicitly disable the debug mode with the application argument -FIRDebugDisabled. + * + * It is also possible to change the default logging level in code by calling setLoggerLevel: on + * the FIRConfiguration interface. + */ +NS_SWIFT_NAME(FirebaseApp) +@interface FIRApp : NSObject + +/** + * Configures a default Firebase app. Raises an exception if any configuration step fails. The + * default app is named "__FIRAPP_DEFAULT". This method should be called after the app is launched + * and before using Firebase services. This method is thread safe and contains synchronous file I/O + * (reading GoogleService-Info.plist from disk). + */ ++ (void)configure; + +/** + * Configures the default Firebase app with the provided options. The default app is named + * "__FIRAPP_DEFAULT". Raises an exception if any configuration step fails. This method is thread + * safe. + * + * @param options The Firebase application options used to configure the service. + */ ++ (void)configureWithOptions:(FIROptions *)options NS_SWIFT_NAME(configure(options:)); + +/** + * Configures a Firebase app with the given name and options. Raises an exception if any + * configuration step fails. This method is thread safe. + * + * @param name The application's name given by the developer. The name should should only contain + Letters, Numbers and Underscore. + * @param options The Firebase application options used to configure the services. + */ +// clang-format off ++ (void)configureWithName:(NSString *)name + options:(FIROptions *)options NS_SWIFT_NAME(configure(name:options:)); +// clang-format on + +/** + * Returns the default app, or nil if the default app does not exist. + */ ++ (nullable FIRApp *)defaultApp NS_SWIFT_NAME(app()); + +/** + * Returns a previously created FIRApp instance with the given name, or nil if no such app exists. + * This method is thread safe. + */ ++ (nullable FIRApp *)appNamed:(NSString *)name NS_SWIFT_NAME(app(name:)); + +/** + * Returns the set of all extant FIRApp instances, or nil if there are no FIRApp instances. This + * method is thread safe. + */ +@property(class, readonly, nullable) NSDictionary *allApps; + +/** + * Cleans up the current FIRApp, freeing associated data and returning its name to the pool for + * future use. This method is thread safe. + */ +- (void)deleteApp:(FIRAppVoidBoolCallback)completion; + +/** + * FIRApp instances should not be initialized directly. Call +[FIRApp configure], + * +[FIRApp configureWithOptions:], or +[FIRApp configureWithNames:options:] directly. + */ +- (instancetype)init NS_UNAVAILABLE; + +/** + * Gets the name of this app. + */ +@property(nonatomic, copy, readonly) NSString *name; + +/** + * Gets a copy of the options for this app. These are non-modifiable. + */ +@property(nonatomic, copy, readonly) FIROptions *options; + +/** + * Gets or sets whether automatic data collection is enabled for all products. Defaults to `YES` + * unless `FirebaseDataCollectionDefaultEnabled` is set to `NO` in your app's Info.plist. This value + * is persisted across runs of the app so that it can be set once when users have consented to + * collection. + */ +@property(nonatomic, readwrite, getter=isDataCollectionDefaultEnabled) + BOOL dataCollectionDefaultEnabled; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Public/FIRConfiguration.h b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Public/FIRConfiguration.h new file mode 100644 index 0000000..8de3b07 --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Public/FIRConfiguration.h @@ -0,0 +1,45 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * This interface provides global level properties that the developer can tweak. + */ +NS_SWIFT_NAME(FirebaseConfiguration) +@interface FIRConfiguration : NSObject + +/** Returns the shared configuration object. */ +@property(class, nonatomic, readonly) FIRConfiguration *sharedInstance NS_SWIFT_NAME(shared); + +/** + * Sets the logging level for internal Firebase logging. Firebase will only log messages + * that are logged at or below loggerLevel. The messages are logged both to the Xcode + * console and to the device's log. Note that if an app is running from AppStore, it will + * never log above FIRLoggerLevelNotice even if loggerLevel is set to a higher (more verbose) + * setting. + * + * @param loggerLevel The maximum logging level. The default level is set to FIRLoggerLevelNotice. + */ +- (void)setLoggerLevel:(FIRLoggerLevel)loggerLevel; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Public/FIRLoggerLevel.h b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Public/FIRLoggerLevel.h new file mode 100644 index 0000000..dca3aa0 --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Public/FIRLoggerLevel.h @@ -0,0 +1,38 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Note that importing GULLoggerLevel.h will lead to a non-modular header +// import error. + +/** + * The log levels used by internal logging. + */ +typedef NS_ENUM(NSInteger, FIRLoggerLevel) { + /** Error level, matches ASL_LEVEL_ERR. */ + FIRLoggerLevelError = 3, + /** Warning level, matches ASL_LEVEL_WARNING. */ + FIRLoggerLevelWarning = 4, + /** Notice level, matches ASL_LEVEL_NOTICE. */ + FIRLoggerLevelNotice = 5, + /** Info level, matches ASL_LEVEL_INFO. */ + FIRLoggerLevelInfo = 6, + /** Debug level, matches ASL_LEVEL_DEBUG. */ + FIRLoggerLevelDebug = 7, + /** Minimum log level. */ + FIRLoggerLevelMin = FIRLoggerLevelError, + /** Maximum log level. */ + FIRLoggerLevelMax = FIRLoggerLevelDebug +} NS_SWIFT_NAME(FirebaseLoggerLevel); diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Public/FIROptions.h b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Public/FIROptions.h new file mode 100644 index 0000000..67fbe5b --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Public/FIROptions.h @@ -0,0 +1,123 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * This class provides constant fields of Google APIs. + */ +NS_SWIFT_NAME(FirebaseOptions) +@interface FIROptions : NSObject + +/** + * Returns the default options. The first time this is called it synchronously reads + * GoogleService-Info.plist from disk. + */ ++ (nullable FIROptions *)defaultOptions NS_SWIFT_NAME(defaultOptions()); + +/** + * An iOS API key used for authenticating requests from your app, e.g. + * @"AIzaSyDdVgKwhZl0sTTTLZ7iTmt1r3N2cJLnaDk", used to identify your app to Google servers. + */ +@property(nonatomic, copy, nullable) NSString *APIKey NS_SWIFT_NAME(apiKey); + +/** + * The bundle ID for the application. Defaults to `[[NSBundle mainBundle] bundleID]` when not set + * manually or in a plist. + */ +@property(nonatomic, copy) NSString *bundleID; + +/** + * The OAuth2 client ID for iOS application used to authenticate Google users, for example + * @"12345.apps.googleusercontent.com", used for signing in with Google. + */ +@property(nonatomic, copy, nullable) NSString *clientID; + +/** + * The tracking ID for Google Analytics, e.g. @"UA-12345678-1", used to configure Google Analytics. + */ +@property(nonatomic, copy, nullable) NSString *trackingID; + +/** + * The Project Number from the Google Developer's console, for example @"012345678901", used to + * configure Google Cloud Messaging. + */ +@property(nonatomic, copy) NSString *GCMSenderID NS_SWIFT_NAME(gcmSenderID); + +/** + * The Project ID from the Firebase console, for example @"abc-xyz-123". + */ +@property(nonatomic, copy, nullable) NSString *projectID; + +/** + * The Android client ID used in Google AppInvite when an iOS app has its Android version, for + * example @"12345.apps.googleusercontent.com". + */ +@property(nonatomic, copy, nullable) NSString *androidClientID; + +/** + * The Google App ID that is used to uniquely identify an instance of an app. + */ +@property(nonatomic, copy) NSString *googleAppID; + +/** + * The database root URL, e.g. @"http://abc-xyz-123.firebaseio.com". + */ +@property(nonatomic, copy, nullable) NSString *databaseURL; + +/** + * The URL scheme used to set up Durable Deep Link service. + */ +@property(nonatomic, copy, nullable) NSString *deepLinkURLScheme; + +/** + * The Google Cloud Storage bucket name, e.g. @"abc-xyz-123.storage.firebase.com". + */ +@property(nonatomic, copy, nullable) NSString *storageBucket; + +/** + * The App Group identifier to share data between the application and the application extensions. + * The App Group must be configured in the application and on the Apple Developer Portal. Default + * value `nil`. + */ +@property(nonatomic, copy, nullable) NSString *appGroupID; + +/** + * Initializes a customized instance of FIROptions from the file at the given plist file path. This + * will read the file synchronously from disk. + * For example, + * NSString *filePath = + * [[NSBundle mainBundle] pathForResource:@"GoogleService-Info" ofType:@"plist"]; + * FIROptions *options = [[FIROptions alloc] initWithContentsOfFile:filePath]; + * Returns nil if the plist file does not exist or is invalid. + */ +- (nullable instancetype)initWithContentsOfFile:(NSString *)plistPath; + +/** + * Initializes a customized instance of FIROptions with required fields. Use the mutable properties + * to modify fields for configuring specific services. + */ +// clang-format off +- (instancetype)initWithGoogleAppID:(NSString *)googleAppID + GCMSenderID:(NSString *)GCMSenderID + NS_SWIFT_NAME(init(googleAppID:gcmSenderID:)); +// clang-format on + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Public/FirebaseCore.h b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Public/FirebaseCore.h new file mode 100644 index 0000000..95119ae --- /dev/null +++ b/!main project/Pods/FirebaseCore/FirebaseCore/Sources/Public/FirebaseCore.h @@ -0,0 +1,20 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRApp.h" +#import "FIRConfiguration.h" +#import "FIRLoggerLevel.h" +#import "FIROptions.h" diff --git a/!main project/Pods/FirebaseCore/LICENSE b/!main project/Pods/FirebaseCore/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/!main project/Pods/FirebaseCore/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/!main project/Pods/FirebaseCore/README.md b/!main project/Pods/FirebaseCore/README.md new file mode 100644 index 0000000..5097a89 --- /dev/null +++ b/!main project/Pods/FirebaseCore/README.md @@ -0,0 +1,254 @@ +# Firebase iOS Open Source Development [![Build Status](https://travis-ci.org/firebase/firebase-ios-sdk.svg?branch=master)](https://travis-ci.org/firebase/firebase-ios-sdk) + +This repository contains a subset of the Firebase iOS SDK source. It currently +includes FirebaseCore, FirebaseABTesting, FirebaseAuth, FirebaseDatabase, +FirebaseFirestore, FirebaseFunctions, FirebaseInstanceID, FirebaseInAppMessaging, +FirebaseInAppMessagingDisplay, FirebaseMessaging, FirebaseRemoteConfig, and +FirebaseStorage. + +The repository also includes GoogleUtilities source. The +[GoogleUtilities](GoogleUtilities/README.md) pod is +a set of utilities used by Firebase and other Google products. + +Firebase is an app development platform with tools to help you build, grow and +monetize your app. More information about Firebase can be found at +[https://firebase.google.com](https://firebase.google.com). + +## Installation + +See the three subsections for details about three different installation methods. +1. [Standard pod install](README.md#standard-pod-install) +1. [Installing from the GitHub repo](README.md#installing-from-github) +1. [Experimental Carthage](README.md#carthage-ios-only) + +### Standard pod install + +Go to +[https://firebase.google.com/docs/ios/setup](https://firebase.google.com/docs/ios/setup). + +### Installing from GitHub + +For releases starting with 5.0.0, the source for each release is also deployed +to CocoaPods master and available via standard +[CocoaPods Podfile syntax](https://guides.cocoapods.org/syntax/podfile.html#pod). + +These instructions can be used to access the Firebase repo at other branches, +tags, or commits. + +#### Background + +See +[the Podfile Syntax Reference](https://guides.cocoapods.org/syntax/podfile.html#pod) +for instructions and options about overriding pod source locations. + +#### Accessing Firebase Source Snapshots + +All of the official releases are tagged in this repo and available via CocoaPods. To access a local +source snapshot or unreleased branch, use Podfile directives like the following: + +To access FirebaseFirestore via a branch: +``` +pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +``` + +To access FirebaseMessaging via a checked out version of the firebase-ios-sdk repo do: + +``` +pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk' +pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk' +``` + +### Carthage (iOS only) + +Instructions for the experimental Carthage distribution are at +[Carthage](Carthage.md). + +### Rome + +Instructions for installing binary frameworks via +[Rome](https://github.com/CocoaPods/Rome) are at [Rome](Rome.md). + +## Development + +To develop Firebase software in this repository, ensure that you have at least +the following software: + + * Xcode 10.1 (or later) + * CocoaPods 1.7.2 (or later) + * [CocoaPods generate](https://github.com/square/cocoapods-generate) + +For the pod that you want to develop: + +`pod gen Firebase{name here}.podspec --local-sources=./ --auto-open --platforms=ios` + +Note: If the CocoaPods cache is out of date, you may need to run +`pod repo update` before the `pod gen` command. + +Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for +those platforms. Since 10.2, Xcode does not properly handle multi-platform +CocoaPods workspaces. + +Firestore has a self contained Xcode project. See +[Firestore/README.md](Firestore/README.md). + +### Development for Catalyst +* `pod gen {name here}.podspec --local-sources=./ --auto-open --platforms=ios` +* Check the Mac box in the App-iOS Build Settings +* Sign the App in the Settings Signing & Capabilities tab +* Click Pods in the Project Manager +* Add Signing to the iOS host app and unit test targets +* Select the Unit-unit scheme +* Run it to build and test + +### Adding a New Firebase Pod + +See [AddNewPod.md](AddNewPod.md). + +### Code Formatting + +To ensure that the code is formatted consistently, run the script +[./scripts/style.sh](https://github.com/firebase/firebase-ios-sdk/blob/master/scripts/style.sh) +before creating a PR. + +Travis will verify that any code changes are done in a style compliant way. Install +`clang-format` and `swiftformat`. +These commands will get the right versions: + +``` +brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/e3496d9/Formula/clang-format.rb +brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/7963c3d/Formula/swiftformat.rb +``` + +Note: if you already have a newer version of these installed you may need to +`brew switch` to this version. + +To update this section, find the versions of clang-format and swiftformat.rb to +match the versions in the CI failure logs +[here](https://github.com/Homebrew/homebrew-core/tree/master/Formula). + +### Running Unit Tests + +Select a scheme and press Command-u to build a component and run its unit tests. + +#### Viewing Code Coverage + +First, make sure that [xcov](https://github.com/nakiostudio/xcov) is installed with `gem install xcov`. + +After running the `AllUnitTests_iOS` scheme in Xcode, execute +`xcov --workspace Firebase.xcworkspace --scheme AllUnitTests_iOS --output_directory xcov_output` +at Example/ in the terminal. This will aggregate the coverage, and you can run `open xcov_output/index.html` to see the results. + +### Running Sample Apps +In order to run the sample apps and integration tests, you'll need valid +`GoogleService-Info.plist` files for those samples. The Firebase Xcode project contains dummy plist +files without real values, but can be replaced with real plist files. To get your own +`GoogleService-Info.plist` files: + +1. Go to the [Firebase Console](https://console.firebase.google.com/) +2. Create a new Firebase project, if you don't already have one +3. For each sample app you want to test, create a new Firebase app with the sample app's bundle +identifier (e.g. `com.google.Database-Example`) +4. Download the resulting `GoogleService-Info.plist` and replace the appropriate dummy plist file +(e.g. in [Example/Database/App/](Example/Database/App/)); + +Some sample apps like Firebase Messaging ([Example/Messaging/App](Example/Messaging/App)) require +special Apple capabilities, and you will have to change the sample app to use a unique bundle +identifier that you can control in your own Apple Developer account. + +## Specific Component Instructions +See the sections below for any special instructions for those components. + +### Firebase Auth + +If you're doing specific Firebase Auth development, see +[the Auth Sample README](Example/Auth/README.md) for instructions about +building and running the FirebaseAuth pod along with various samples and tests. + +### Firebase Database + +To run the Database Integration tests, make your database authentication rules +[public](https://firebase.google.com/docs/database/security/quickstart). + +### Firebase Storage + +To run the Storage Integration tests, follow the instructions in +[FIRStorageIntegrationTests.m](Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m). + +#### Push Notifications + +Push notifications can only be delivered to specially provisioned App IDs in the developer portal. +In order to actually test receiving push notifications, you will need to: + +1. Change the bundle identifier of the sample app to something you own in your Apple Developer +account, and enable that App ID for push notifications. +2. You'll also need to +[upload your APNs Provider Authentication Key or certificate to the Firebase Console](https://firebase.google.com/docs/cloud-messaging/ios/certs) +at **Project Settings > Cloud Messaging > [Your Firebase App]**. +3. Ensure your iOS device is added to your Apple Developer portal as a test device. + +#### iOS Simulator + +The iOS Simulator cannot register for remote notifications, and will not receive push notifications. +In order to receive push notifications, you'll have to follow the steps above and run the app on a +physical device. + +## Community Supported Efforts + +We've seen an amazing amount of interest and contributions to improve the Firebase SDKs, and we are +very grateful! We'd like to empower as many developers as we can to be able to use Firebase and +participate in the Firebase community. + +### tvOS, macOS, and Catalyst +Thanks to contributions from the community, many of Firebase SDKs now compile, run unit tests, and work on +tvOS, macOS, and Catalyst. + +For tvOS, checkout the [Sample](Example/tvOSSample). + +Keep in mind that macOS, Catalyst and tvOS are not officially supported by Firebase, and this +repository is actively developed primarily for iOS. While we can catch basic unit test issues with +Travis, there may be some changes where the SDK no longer works as expected on macOS or tvOS. If you +encounter this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues). + +During app setup in the console, you may get to a step that mentions something like "Checking if the app +has communicated with our servers". This relies on Analytics and will not work on macOS/tvOS/Catalyst. +**It's safe to ignore the message and continue**, the rest of the SDKs will work as expected. + +To install, add a subset of the following to the Podfile: + +``` +pod 'Firebase/ABTesting' +pod 'Firebase/Auth' +pod 'Firebase/Crashlytics' +pod 'Firebase/Database' +pod 'Firebase/Firestore' +pod 'Firebase/Functions' +pod 'Firebase/Messaging' +pod 'Firebase/RemoteConfig' +pod 'Firebase/Storage' +``` + +#### Additional Catalyst Notes + +* FirebaseAuth and FirebaseMessaging require adding `Keychain Sharing Capability` +to Build Settings. +* FirebaseFirestore requires signing the +[gRPC Resource target](https://github.com/firebase/firebase-ios-sdk/issues/3500#issuecomment-518741681). + +## Roadmap + +See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source +plans and directions. + +## Contributing + +See [Contributing](CONTRIBUTING.md) for more information on contributing to the Firebase +iOS SDK. + +## License + +The contents of this repository is licensed under the +[Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0). + +Your use of Firebase is governed by the +[Terms of Service for Firebase Services](https://firebase.google.com/terms/). diff --git a/!main project/Pods/FirebaseCoreDiagnostics/Firebase/CoreDiagnostics/FIRCDLibrary/FIRCoreDiagnostics.m b/!main project/Pods/FirebaseCoreDiagnostics/Firebase/CoreDiagnostics/FIRCDLibrary/FIRCoreDiagnostics.m new file mode 100644 index 0000000..bb0326b --- /dev/null +++ b/!main project/Pods/FirebaseCoreDiagnostics/Firebase/CoreDiagnostics/FIRCDLibrary/FIRCoreDiagnostics.m @@ -0,0 +1,641 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import +#include + +#import +#import +#import +#import + +#import +#import +#import + +#import +#import + +#import +#import +#import + +#import "FIRCDLibrary/Protogen/nanopb/firebasecore.nanopb.h" + +/** The logger service string to use when printing to the console. */ +static GULLoggerService kFIRCoreDiagnostics = @"[FirebaseCoreDiagnostics/FIRCoreDiagnostics]"; + +#ifdef FIREBASE_BUILD_ZIP_FILE +static BOOL kUsingZipFile = YES; +#else // FIREBASE_BUILD_ZIP_FILE +static BOOL kUsingZipFile = NO; +#endif // FIREBASE_BUILD_ZIP_FILE + +#ifdef FIREBASE_BUILD_CARTHAGE +#define kDeploymentType logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_CARTHAGE +#elif FIREBASE_BUILD_ZIP_FILE +#define kDeploymentType logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_ZIP_FILE +#else +#define kDeploymentType logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_COCOAPODS +#endif + +static NSString *const kFIRServiceMLVisionOnDeviceAutoML = @"MLVisionOnDeviceAutoML"; +static NSString *const kFIRServiceMLVisionOnDeviceFace = @"MLVisionOnDeviceFace"; +static NSString *const kFIRServiceMLVisionOnDeviceBarcode = @"MLVisionOnDeviceBarcode"; +static NSString *const kFIRServiceMLVisionOnDeviceText = @"MLVisionOnDeviceText"; +static NSString *const kFIRServiceMLVisionOnDeviceLabel = @"MLVisionOnDeviceLabel"; +static NSString *const kFIRServiceMLVisionOnDeviceObjectDetection = + @"MLVisionOnDeviceObjectDetection"; +static NSString *const kFIRServiceMLModelInterpreter = @"MLModelInterpreter"; + +static NSString *const kFIRServiceAdMob = @"AdMob"; +static NSString *const kFIRServiceAuth = @"Auth"; +static NSString *const kFIRServiceAuthUI = @"AuthUI"; +static NSString *const kFIRServiceCrash = @"Crash"; +static NSString *const kFIRServiceDatabase = @"Database"; +static NSString *const kFIRServiceDynamicLinks = @"DynamicLinks"; +static NSString *const kFIRServiceFirestore = @"Firestore"; +static NSString *const kFIRServiceFunctions = @"Functions"; +static NSString *const kFIRServiceIAM = @"InAppMessaging"; +static NSString *const kFIRServiceInstanceID = @"InstanceID"; +static NSString *const kFIRServiceInvites = @"Invites"; +static NSString *const kFIRServiceMessaging = @"Messaging"; +static NSString *const kFIRServiceMeasurement = @"Measurement"; +static NSString *const kFIRServicePerformance = @"Performance"; +static NSString *const kFIRServiceRemoteConfig = @"RemoteConfig"; +static NSString *const kFIRServiceStorage = @"Storage"; +static NSString *const kGGLServiceAnalytics = @"Analytics"; +static NSString *const kGGLServiceSignIn = @"SignIn"; +static NSString *const kFIRAppDiagnosticsConfigurationTypeKey = + @"FIRAppDiagnosticsConfigurationTypeKey"; +static NSString *const kFIRAppDiagnosticsFIRAppKey = @"FIRAppDiagnosticsFIRAppKey"; +static NSString *const kFIRAppDiagnosticsSDKNameKey = @"FIRAppDiagnosticsSDKNameKey"; +static NSString *const kFIRAppDiagnosticsSDKVersionKey = @"FIRAppDiagnosticsSDKVersionKey"; +static NSString *const kFIRCoreDiagnosticsHeartbeatTag = @"FIRCoreDiagnostics"; + +/** + * The file name to the recent heartbeat date. + */ +NSString *const kFIRCoreDiagnosticsHeartbeatDateFileName = @"FIREBASE_DIAGNOSTICS_HEARTBEAT_DATE"; + +/** + * @note This should implement the GDTCOREventDataObject protocol, but can't because of + * weak-linking. + */ +@interface FIRCoreDiagnosticsLog : NSObject + +/** The config that will be converted to proto bytes. */ +@property(nonatomic) logs_proto_mobilesdk_ios_ICoreConfiguration config; + +@end + +@implementation FIRCoreDiagnosticsLog + +- (instancetype)initWithConfig:(logs_proto_mobilesdk_ios_ICoreConfiguration)config { + self = [super init]; + if (self) { + _config = config; + } + return self; +} + +// Provided and required by the GDTCOREventDataObject protocol. +- (NSData *)transportBytes { + pb_ostream_t sizestream = PB_OSTREAM_SIZING; + + // Encode 1 time to determine the size. + if (!pb_encode(&sizestream, logs_proto_mobilesdk_ios_ICoreConfiguration_fields, &_config)) { + GDTCORLogError(GDTCORMCETransportBytesError, @"Error in nanopb encoding for size: %s", + PB_GET_ERROR(&sizestream)); + } + + // Encode a 2nd time to actually get the bytes from it. + size_t bufferSize = sizestream.bytes_written; + CFMutableDataRef dataRef = CFDataCreateMutable(CFAllocatorGetDefault(), bufferSize); + pb_ostream_t ostream = pb_ostream_from_buffer((void *)CFDataGetBytePtr(dataRef), bufferSize); + if (!pb_encode(&ostream, logs_proto_mobilesdk_ios_ICoreConfiguration_fields, &_config)) { + GDTCORLogError(GDTCORMCETransportBytesError, @"Error in nanopb encoding for bytes: %s", + PB_GET_ERROR(&ostream)); + } + CFDataSetLength(dataRef, ostream.bytes_written); + + return CFBridgingRelease(dataRef); +} + +- (void)dealloc { + pb_release(logs_proto_mobilesdk_ios_ICoreConfiguration_fields, &_config); +} + +@end + +NS_ASSUME_NONNULL_BEGIN + +/** This class produces a protobuf containing diagnostics and usage data to be logged. */ +@interface FIRCoreDiagnostics : NSObject + +/** The queue on which all diagnostics collection will occur. */ +@property(nonatomic, readonly) dispatch_queue_t diagnosticsQueue; + +/** The transport object used to send data. */ +@property(nonatomic, readonly) GDTCORTransport *transport; + +/** The storage to store the date of the last sent heartbeat. */ +@property(nonatomic, readonly) GULHeartbeatDateStorage *heartbeatDateStorage; + +@end + +NS_ASSUME_NONNULL_END + +@implementation FIRCoreDiagnostics + ++ (instancetype)sharedInstance { + static FIRCoreDiagnostics *sharedInstance; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[FIRCoreDiagnostics alloc] init]; + }); + return sharedInstance; +} + +- (instancetype)init { + GDTCORTransport *transport = [[GDTCORTransport alloc] initWithMappingID:@"137" + transformers:nil + target:kGDTCORTargetFLL]; + + GULHeartbeatDateStorage *dateStorage = + [[GULHeartbeatDateStorage alloc] initWithFileName:kFIRCoreDiagnosticsHeartbeatDateFileName]; + + return [self initWithTransport:transport heartbeatDateStorage:dateStorage]; +} + +/** Initializer for unit tests. + * + * @param transport A `GDTCORTransport` instance which that be used to send event. + * @param heartbeatDateStorage An instanse of date storage to track heartbeat sending. + * @return Returns the initialized `FIRCoreDiagnostics` instance. + */ +- (instancetype)initWithTransport:(GDTCORTransport *)transport + heartbeatDateStorage:(GULHeartbeatDateStorage *)heartbeatDateStorage { + self = [super init]; + if (self) { + _diagnosticsQueue = + dispatch_queue_create("com.google.FIRCoreDiagnostics", DISPATCH_QUEUE_SERIAL); + _transport = transport; + _heartbeatDateStorage = heartbeatDateStorage; + } + return self; +} + +#pragma mark - Metadata helpers + +/** Returns the model of iOS device. Sample platform strings are @"iPhone7,1" for iPhone 6 Plus, + * @"iPhone7,2" for iPhone 6, etc. Refer to the Hardware strings at + * https://en.wikipedia.org/wiki/List_of_iOS_devices + * + * @return The device model as an NSString. + */ ++ (NSString *)deviceModel { + static NSString *deviceModel = nil; + if (deviceModel == nil) { + struct utsname systemInfo; + uname(&systemInfo); + deviceModel = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding]; + } + return deviceModel; +} + +#pragma mark - nanopb helper functions + +/** Mallocs a pb_bytes_array and copies the given NSString's bytes into the bytes array. + * + * @note Memory needs to be free manually, through pb_free or pb_release. + * @param string The string to encode as pb_bytes. + */ +pb_bytes_array_t *FIREncodeString(NSString *string) { + NSData *stringBytes = [string dataUsingEncoding:NSUTF8StringEncoding]; + return FIREncodeData(stringBytes); +} + +/** Mallocs a pb_bytes_array and copies the given NSData bytes into the bytes array. + * + * @note Memory needs to be free manually, through pb_free or pb_release. + * @param data The data to copy into the new bytes array. + */ +pb_bytes_array_t *FIREncodeData(NSData *data) { + pb_bytes_array_t *pbBytes = malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(data.length)); + memcpy(pbBytes->bytes, [data bytes], data.length); + pbBytes->size = (pb_size_t)data.length; + return pbBytes; +} + +/** Maps a service string to the representative nanopb enum. + * + * @param serviceString The SDK service string to convert. + * @return The representative nanopb enum. + */ +logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType FIRMapFromServiceStringToTypeEnum( + NSString *serviceString) { + static NSDictionary *serviceStringToTypeEnum; + if (serviceStringToTypeEnum == nil) { + serviceStringToTypeEnum = @{ + kFIRServiceAdMob : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ADMOB), + kFIRServiceMessaging : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_MESSAGING), + kFIRServiceMeasurement : + @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_MEASUREMENT), + kFIRServiceRemoteConfig : + @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_REMOTE_CONFIG), + kFIRServiceDatabase : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_DATABASE), + kFIRServiceDynamicLinks : + @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_DYNAMIC_LINKS), + kFIRServiceAuth : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_AUTH), + kFIRServiceAuthUI : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_AUTH_UI), + kFIRServiceFirestore : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_FIRESTORE), + kFIRServiceFunctions : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_FUNCTIONS), + kFIRServicePerformance : + @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_PERFORMANCE), + kFIRServiceStorage : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_STORAGE), + kFIRServiceMLVisionOnDeviceAutoML : + @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_VISION_ON_DEVICE_AUTOML), + kFIRServiceMLVisionOnDeviceFace : + @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_VISION_ON_DEVICE_FACE), + kFIRServiceMLVisionOnDeviceBarcode : + @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_VISION_ON_DEVICE_BARCODE), + kFIRServiceMLVisionOnDeviceText : + @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_VISION_ON_DEVICE_TEXT), + kFIRServiceMLVisionOnDeviceLabel : + @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_VISION_ON_DEVICE_LABEL), + kFIRServiceMLVisionOnDeviceObjectDetection : @( + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_VISION_ON_DEVICE_OBJECT_DETECTION), + kFIRServiceMLModelInterpreter : + @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_MODEL_INTERPRETER), + kGGLServiceAnalytics : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ANALYTICS), + kGGLServiceSignIn : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_SIGN_IN), + kFIRServiceIAM : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_IN_APP_MESSAGING), + }; + } + if (serviceStringToTypeEnum[serviceString] != nil) { + return (int32_t)serviceStringToTypeEnum[serviceString].longLongValue; + } + return logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_UNKNOWN_SDK_SERVICE; +} + +#pragma mark - Proto population functions + +/** Populates the given proto with data related to an SDK logDiagnostics call from the + * diagnosticObjects dictionary. + * + * @param config The proto to populate + * @param diagnosticObjects The dictionary of diagnostics objects. + */ +void FIRPopulateProtoWithInfoFromUserInfoParams(logs_proto_mobilesdk_ios_ICoreConfiguration *config, + NSDictionary *diagnosticObjects) { + NSNumber *configurationType = diagnosticObjects[kFIRCDConfigurationTypeKey]; + if (configurationType != nil) { + switch (configurationType.integerValue) { + case logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_CORE: + config->configuration_type = + logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_CORE; + config->has_configuration_type = 1; + break; + case logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_SDK: + config->configuration_type = + logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_SDK; + config->has_configuration_type = 1; + break; + default: + break; + } + } + + NSString *sdkName = diagnosticObjects[kFIRCDSdkNameKey]; + if (sdkName) { + config->sdk_name = FIRMapFromServiceStringToTypeEnum(sdkName); + config->has_sdk_name = 1; + } + + NSString *version = diagnosticObjects[kFIRCDSdkVersionKey]; + if (version) { + config->sdk_version = FIREncodeString(version); + } +} + +/** Populates the given proto with data from the calling FIRApp using the given + * diagnosticObjects dictionary. + * + * @param config The proto to populate + * @param diagnosticObjects The dictionary of diagnostics objects. + */ +void FIRPopulateProtoWithCommonInfoFromApp(logs_proto_mobilesdk_ios_ICoreConfiguration *config, + NSDictionary *diagnosticObjects) { + config->pod_name = logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_FIREBASE; + config->has_pod_name = 1; + + if (!diagnosticObjects[kFIRCDllAppsCountKey]) { + GDTCORLogError(GDTCORMCEGeneralError, @"%@", + @"App count is a required value in the data dict."); + } + config->app_count = (int32_t)[diagnosticObjects[kFIRCDllAppsCountKey] integerValue]; + config->has_app_count = 1; + + NSString *googleAppID = diagnosticObjects[kFIRCDGoogleAppIDKey]; + if (googleAppID.length) { + config->app_id = FIREncodeString(googleAppID); + } + + NSString *bundleID = diagnosticObjects[kFIRCDBundleIDKey]; + if (bundleID.length) { + config->bundle_id = FIREncodeString(bundleID); + } + + NSString *firebaseUserAgent = diagnosticObjects[kFIRCDFirebaseUserAgentKey]; + if (firebaseUserAgent.length) { + config->platform_info = FIREncodeString(firebaseUserAgent); + } + + NSNumber *usingOptionsFromDefaultPlist = diagnosticObjects[kFIRCDUsingOptionsFromDefaultPlistKey]; + if (usingOptionsFromDefaultPlist != nil) { + config->use_default_app = [usingOptionsFromDefaultPlist boolValue]; + config->has_use_default_app = 1; + } + + NSString *libraryVersionID = diagnosticObjects[kFIRCDLibraryVersionIDKey]; + if (libraryVersionID) { + config->icore_version = FIREncodeString(libraryVersionID); + } + + NSString *deviceModel = [FIRCoreDiagnostics deviceModel]; + if (deviceModel.length) { + config->device_model = FIREncodeString(deviceModel); + } + + NSString *osVersion = [GULAppEnvironmentUtil systemVersion]; + if (osVersion.length) { + config->os_version = FIREncodeString(osVersion); + } + + config->using_zip_file = kUsingZipFile; + config->has_using_zip_file = 1; + config->deployment_type = kDeploymentType; + config->has_deployment_type = 1; + config->deployed_in_app_store = [GULAppEnvironmentUtil isFromAppStore]; + config->has_deployed_in_app_store = 1; +} + +/** Populates the given proto with installed services data. + * + * @param config The proto to populate + */ +void FIRPopulateProtoWithInstalledServices(logs_proto_mobilesdk_ios_ICoreConfiguration *config) { + NSMutableArray *sdkServiceInstalledArray = [NSMutableArray array]; + + // AdMob + if (NSClassFromString(@"GADBannerView") != nil) { + [sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceAdMob))]; + } + // CloudMessaging + if (NSClassFromString(@"FIRMessaging") != nil) { + [sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceMessaging))]; + } + // RemoteConfig + if (NSClassFromString(@"FIRRemoteConfig") != nil) { + [sdkServiceInstalledArray + addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceRemoteConfig))]; + } + // Measurement/Analtyics + if (NSClassFromString(@"FIRAnalytics") != nil) { + [sdkServiceInstalledArray + addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceMeasurement))]; + } + // ML Vision On Device AutoML. + if (NSClassFromString(@"FIRVisionOnDeviceAutoMLImageLabelerOptions") != nil) { + [sdkServiceInstalledArray + addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceMLVisionOnDeviceAutoML))]; + } + // ML Vision On Device Face. + if (NSClassFromString(@"FIRVisionFaceDetector") != nil && + NSClassFromString(@"GMVFaceDetector") != nil) { + [sdkServiceInstalledArray + addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceMLVisionOnDeviceFace))]; + } + // ML Vision On Device Barcode. + if (NSClassFromString(@"FIRVisionBarcodeDetector") != nil && + NSClassFromString(@"GMVBarcodeDetector") != nil) { + [sdkServiceInstalledArray + addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceMLVisionOnDeviceBarcode))]; + } + // ML Vision On Device Text. + if (NSClassFromString(@"FIRVisionTextDetector") != nil && + NSClassFromString(@"GMVTextDetector") != nil) { + [sdkServiceInstalledArray + addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceMLVisionOnDeviceText))]; + } + // ML Vision On Device Image Label. + if (NSClassFromString(@"FIRVisionLabelDetector") != nil && + NSClassFromString(@"GMVLabelDetector") != nil) { + [sdkServiceInstalledArray + addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceMLVisionOnDeviceLabel))]; + } + // ML Vision On Device Object. + if (NSClassFromString(@"FIRVisionObjectDetector") != nil) { + [sdkServiceInstalledArray + addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceMLVisionOnDeviceObjectDetection))]; + } + // ML Model Interpreter + if (NSClassFromString(@"FIRCustomModelInterpreter") != nil) { + [sdkServiceInstalledArray + addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceMLModelInterpreter))]; + } + // Database + if (NSClassFromString(@"FIRDatabase") != nil) { + [sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceDatabase))]; + } + // DynamicDeepLink + if (NSClassFromString(@"FIRDynamicLinks") != nil) { + [sdkServiceInstalledArray + addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceDynamicLinks))]; + } + // Auth + if (NSClassFromString(@"FIRAuth") != nil) { + [sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceAuth))]; + } + // AuthUI + if (NSClassFromString(@"FUIAuth") != nil) { + [sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceAuthUI))]; + } + // Firestore + if (NSClassFromString(@"FIRFirestore") != nil) { + [sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceFirestore))]; + } + // Functions + if (NSClassFromString(@"FIRFunctions") != nil) { + [sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceFunctions))]; + } + // Performance + if (NSClassFromString(@"FIRPerformance") != nil) { + [sdkServiceInstalledArray + addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServicePerformance))]; + } + // Storage + if (NSClassFromString(@"FIRStorage") != nil) { + [sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceStorage))]; + } + // SignIn via Google pod + if (NSClassFromString(@"GIDSignIn") != nil && NSClassFromString(@"GGLContext") != nil) { + [sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kGGLServiceSignIn))]; + } + // Analytics via Google pod + if (NSClassFromString(@"GAI") != nil && NSClassFromString(@"GGLContext") != nil) { + [sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kGGLServiceAnalytics))]; + } + + // In-App Messaging + if (NSClassFromString(@"FIRInAppMessaging") != nil) { + [sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceIAM))]; + } + + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType *servicesInstalled = + malloc(sizeof(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType) * + sdkServiceInstalledArray.count); + for (NSUInteger i = 0; i < sdkServiceInstalledArray.count; i++) { + NSNumber *typeEnum = sdkServiceInstalledArray[i]; + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType serviceType = + (int32_t)typeEnum.integerValue; + servicesInstalled[i] = serviceType; + } + + config->sdk_service_installed = servicesInstalled; + config->sdk_service_installed_count = (int32_t)sdkServiceInstalledArray.count; +} + +/** Populates the proto with the number of linked frameworks. + * + * @param config The proto to populate. + */ +void FIRPopulateProtoWithNumberOfLinkedFrameworks( + logs_proto_mobilesdk_ios_ICoreConfiguration *config) { + int numFrameworks = -1; // Subtract the app binary itself. + unsigned int numImages; + const char **imageNames = objc_copyImageNames(&numImages); + for (unsigned int i = 0; i < numImages; i++) { + NSString *imageName = [NSString stringWithUTF8String:imageNames[i]]; + if ([imageName rangeOfString:@"System/Library"].length != 0 // Apple .frameworks + || [imageName rangeOfString:@"Developer/Library"].length != 0 // Xcode debug .frameworks + || [imageName rangeOfString:@"usr/lib"].length != 0) { // Public .dylibs + continue; + } + numFrameworks++; + } + free(imageNames); + config->dynamic_framework_count = numFrameworks; + config->has_dynamic_framework_count = 1; +} + +/** Populates the proto with Info.plist values. + * + * @param config The proto to populate. + */ +void FIRPopulateProtoWithInfoPlistValues(logs_proto_mobilesdk_ios_ICoreConfiguration *config) { + NSDictionary *info = [[NSBundle mainBundle] infoDictionary]; + + NSString *xcodeVersion = info[@"DTXcodeBuild"] ?: @""; + NSString *sdkVersion = info[@"DTSDKBuild"] ?: @""; + NSString *combinedVersions = [NSString stringWithFormat:@"%@-%@", xcodeVersion, sdkVersion]; + config->apple_framework_version = FIREncodeString(combinedVersions); + + NSString *minVersion = info[@"MinimumOSVersion"]; + if (minVersion) { + config->min_supported_ios_version = FIREncodeString(minVersion); + } + + // Apps can turn off swizzling in the Info.plist, check if they've explicitly set the value and + // report it. It's enabled by default. + NSNumber *appDelegateSwizzledNum = info[@"FirebaseAppDelegateProxyEnabled"]; + BOOL appDelegateSwizzled = YES; + if ([appDelegateSwizzledNum isKindOfClass:[NSNumber class]]) { + appDelegateSwizzled = [appDelegateSwizzledNum boolValue]; + } + config->swizzling_enabled = appDelegateSwizzled; + config->has_swizzling_enabled = 1; +} + +#pragma mark - FIRCoreDiagnosticsInterop + ++ (void)sendDiagnosticsData:(nonnull id)diagnosticsData { + FIRCoreDiagnostics *diagnostics = [FIRCoreDiagnostics sharedInstance]; + [diagnostics sendDiagnosticsData:diagnosticsData]; +} + +- (void)sendDiagnosticsData:(nonnull id)diagnosticsData { + dispatch_async(self.diagnosticsQueue, ^{ + NSDictionary *diagnosticObjects = diagnosticsData.diagnosticObjects; + NSNumber *isDataCollectionDefaultEnabled = + diagnosticObjects[kFIRCDIsDataCollectionDefaultEnabledKey]; + if (isDataCollectionDefaultEnabled && ![isDataCollectionDefaultEnabled boolValue]) { + return; + } + + // Create the proto. + logs_proto_mobilesdk_ios_ICoreConfiguration icore_config = + logs_proto_mobilesdk_ios_ICoreConfiguration_init_default; + + icore_config.using_gdt = 1; + icore_config.has_using_gdt = 1; + + // Populate the proto with information. + FIRPopulateProtoWithInfoFromUserInfoParams(&icore_config, diagnosticObjects); + FIRPopulateProtoWithCommonInfoFromApp(&icore_config, diagnosticObjects); + FIRPopulateProtoWithInstalledServices(&icore_config); + FIRPopulateProtoWithNumberOfLinkedFrameworks(&icore_config); + FIRPopulateProtoWithInfoPlistValues(&icore_config); + [self setHeartbeatFlagIfNeededToConfig:&icore_config]; + + // This log object is capable of converting the proto to bytes. + FIRCoreDiagnosticsLog *log = [[FIRCoreDiagnosticsLog alloc] initWithConfig:icore_config]; + + // Send the log as a telemetry event. + GDTCOREvent *event = [self.transport eventForTransport]; + event.dataObject = (id)log; + [self.transport sendTelemetryEvent:event]; + }); +} + +#pragma mark - Heartbeat + +- (void)setHeartbeatFlagIfNeededToConfig:(logs_proto_mobilesdk_ios_ICoreConfiguration *)config { + // Check if need to send a heartbeat. + NSDate *currentDate = [NSDate date]; + NSDate *lastCheckin = + [self.heartbeatDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag]; + if (lastCheckin) { + // Ensure the previous checkin was on a different date in the past. + if ([self isDate:currentDate inSameDayOrBeforeThan:lastCheckin]) { + return; + } + } + + // Update heartbeat sent date. + [self.heartbeatDateStorage setHearbeatDate:currentDate forTag:kFIRCoreDiagnosticsHeartbeatTag]; + // Set the flag. + config->sdk_name = logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ICORE; + config->has_sdk_name = 1; +} + +- (BOOL)isDate:(NSDate *)date1 inSameDayOrBeforeThan:(NSDate *)date2 { + return [[NSCalendar currentCalendar] isDate:date1 inSameDayAsDate:date2] || + [date1 compare:date2] == NSOrderedAscending; +} + +@end diff --git a/!main project/Pods/FirebaseCoreDiagnostics/Firebase/CoreDiagnostics/FIRCDLibrary/Protogen/nanopb/firebasecore.nanopb.c b/!main project/Pods/FirebaseCoreDiagnostics/Firebase/CoreDiagnostics/FIRCDLibrary/Protogen/nanopb/firebasecore.nanopb.c new file mode 100644 index 0000000..4b2ac2f --- /dev/null +++ b/!main project/Pods/FirebaseCoreDiagnostics/Firebase/CoreDiagnostics/FIRCDLibrary/Protogen/nanopb/firebasecore.nanopb.c @@ -0,0 +1,60 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.3.9.3 */ + +#include "firebasecore.nanopb.h" + +/* @@protoc_insertion_point(includes) */ +#if PB_PROTO_HEADER_VERSION != 30 +#error Regenerate this file with the current version of nanopb generator. +#endif + + + +const pb_field_t logs_proto_mobilesdk_ios_ICoreConfiguration_fields[22] = { + PB_FIELD( 1, UENUM , OPTIONAL, STATIC , FIRST, logs_proto_mobilesdk_ios_ICoreConfiguration, configuration_type, configuration_type, 0), + PB_FIELD( 7, UENUM , REPEATED, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, sdk_service_installed, configuration_type, 0), + PB_FIELD( 9, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, device_model, sdk_service_installed, 0), + PB_FIELD( 10, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, app_id, device_model, 0), + PB_FIELD( 12, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, bundle_id, app_id, 0), + PB_FIELD( 16, UENUM , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, pod_name, bundle_id, 0), + PB_FIELD( 18, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, icore_version, pod_name, 0), + PB_FIELD( 19, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, sdk_version, icore_version, 0), + PB_FIELD( 20, UENUM , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, sdk_name, sdk_version, 0), + PB_FIELD( 21, INT32 , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, app_count, sdk_name, 0), + PB_FIELD( 22, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, os_version, app_count, 0), + PB_FIELD( 24, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, min_supported_ios_version, os_version, 0), + PB_FIELD( 25, BOOL , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, use_default_app, min_supported_ios_version, 0), + PB_FIELD( 26, BOOL , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, deployed_in_app_store, use_default_app, 0), + PB_FIELD( 27, INT32 , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, dynamic_framework_count, deployed_in_app_store, 0), + PB_FIELD( 28, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, apple_framework_version, dynamic_framework_count, 0), + PB_FIELD( 29, BOOL , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, using_zip_file, apple_framework_version, 0), + PB_FIELD( 30, UENUM , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, deployment_type, using_zip_file, 0), + PB_FIELD( 31, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, platform_info, deployment_type, 0), + PB_FIELD( 33, BOOL , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, swizzling_enabled, platform_info, 0), + PB_FIELD( 36, BOOL , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, using_gdt, swizzling_enabled, 0), + PB_LAST_FIELD +}; + + + + + + + +/* @@protoc_insertion_point(eof) */ diff --git a/!main project/Pods/FirebaseCoreDiagnostics/Firebase/CoreDiagnostics/FIRCDLibrary/Protogen/nanopb/firebasecore.nanopb.h b/!main project/Pods/FirebaseCoreDiagnostics/Firebase/CoreDiagnostics/FIRCDLibrary/Protogen/nanopb/firebasecore.nanopb.h new file mode 100644 index 0000000..3e4c195 --- /dev/null +++ b/!main project/Pods/FirebaseCoreDiagnostics/Firebase/CoreDiagnostics/FIRCDLibrary/Protogen/nanopb/firebasecore.nanopb.h @@ -0,0 +1,193 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.3.9.3 */ + +#ifndef PB_LOGS_PROTO_MOBILESDK_IOS_FIREBASECORE_NANOPB_H_INCLUDED +#define PB_LOGS_PROTO_MOBILESDK_IOS_FIREBASECORE_NANOPB_H_INCLUDED +#include + +/* @@protoc_insertion_point(includes) */ +#if PB_PROTO_HEADER_VERSION != 30 +#error Regenerate this file with the current version of nanopb generator. +#endif + + +/* Enum definitions */ +typedef enum _logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType { + logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_UNKNOWN_CONFIGURATION_TYPE = 0, + logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_CORE = 1, + logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_SDK = 2 +} logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType; +#define _logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_MIN logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_UNKNOWN_CONFIGURATION_TYPE +#define _logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_MAX logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_SDK +#define _logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_ARRAYSIZE ((logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType)(logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_SDK+1)) + +typedef enum _logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType { + logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType_UNKNOWN_BUILD_TYPE = 0, + logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType_INTERNAL = 1, + logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType_EAP = 2, + logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType_PROD = 3 +} logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType; +#define _logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType_MIN logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType_UNKNOWN_BUILD_TYPE +#define _logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType_MAX logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType_PROD +#define _logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType_ARRAYSIZE ((logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType)(logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType_PROD+1)) + +typedef enum _logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType { + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_UNKNOWN_SDK_SERVICE = 0, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ICORE = 1, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ADMOB = 2, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_APP_INVITE = 3, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_SIGN_IN = 5, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_GCM = 6, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_MAPS = 7, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_SCION = 8, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ANALYTICS = 9, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_APP_INDEXING = 10, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_CONFIG = 11, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_DURABLE_DEEP_LINKS = 12, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_CRASH = 13, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_AUTH = 14, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_DATABASE = 15, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_STORAGE = 16, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_MESSAGING = 17, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_MEASUREMENT = 18, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_REMOTE_CONFIG = 19, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_DYNAMIC_LINKS = 20, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_INVITES = 21, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_AUTH_UI = 22, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_FIRESTORE = 23, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_PERFORMANCE = 24, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_VISION_ON_DEVICE_FACE = 26, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_VISION_ON_DEVICE_BARCODE = 27, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_VISION_ON_DEVICE_TEXT = 28, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_VISION_ON_DEVICE_LABEL = 29, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_MODEL_INTERPRETER = 30, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_IN_APP_MESSAGING = 31, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_FUNCTIONS = 32, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_NATURAL_LANGUAGE = 33, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_VISION_ON_DEVICE_AUTOML = 34, + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_VISION_ON_DEVICE_OBJECT_DETECTION = 35 +} logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType; +#define _logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_MIN logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_UNKNOWN_SDK_SERVICE +#define _logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_MAX logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_VISION_ON_DEVICE_OBJECT_DETECTION +#define _logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ARRAYSIZE ((logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType)(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_VISION_ON_DEVICE_OBJECT_DETECTION+1)) + +typedef enum _logs_proto_mobilesdk_ios_ICoreConfiguration_PodName { + logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_UNKNOWN_POD_NAME = 0, + logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_GOOGLE = 1, + logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_FIREBASE = 2 +} logs_proto_mobilesdk_ios_ICoreConfiguration_PodName; +#define _logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_MIN logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_UNKNOWN_POD_NAME +#define _logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_MAX logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_FIREBASE +#define _logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_ARRAYSIZE ((logs_proto_mobilesdk_ios_ICoreConfiguration_PodName)(logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_FIREBASE+1)) + +typedef enum _logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType { + logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_UNKNOWN = 0, + logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_COCOAPODS = 1, + logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_ZIP_FILE = 2, + logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_CARTHAGE = 3, + logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_SPM = 4 +} logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType; +#define _logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_MIN logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_UNKNOWN +#define _logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_MAX logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_SPM +#define _logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_ARRAYSIZE ((logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType)(logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_SPM+1)) + +/* Struct definitions */ +typedef struct _logs_proto_mobilesdk_ios_ICoreConfiguration { + bool has_configuration_type; + logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType configuration_type; + pb_size_t sdk_service_installed_count; + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType *sdk_service_installed; + pb_bytes_array_t *device_model; + pb_bytes_array_t *app_id; + pb_bytes_array_t *bundle_id; + bool has_pod_name; + logs_proto_mobilesdk_ios_ICoreConfiguration_PodName pod_name; + pb_bytes_array_t *icore_version; + pb_bytes_array_t *sdk_version; + bool has_sdk_name; + logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType sdk_name; + bool has_app_count; + int32_t app_count; + pb_bytes_array_t *os_version; + pb_bytes_array_t *min_supported_ios_version; + bool has_use_default_app; + bool use_default_app; + bool has_deployed_in_app_store; + bool deployed_in_app_store; + bool has_dynamic_framework_count; + int32_t dynamic_framework_count; + pb_bytes_array_t *apple_framework_version; + bool has_using_zip_file; + bool using_zip_file; + bool has_deployment_type; + logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType deployment_type; + pb_bytes_array_t *platform_info; + bool has_swizzling_enabled; + bool swizzling_enabled; + bool has_using_gdt; + bool using_gdt; +/* @@protoc_insertion_point(struct:logs_proto_mobilesdk_ios_ICoreConfiguration) */ +} logs_proto_mobilesdk_ios_ICoreConfiguration; + +/* Default values for struct fields */ + +/* Initializer values for message structs */ +#define logs_proto_mobilesdk_ios_ICoreConfiguration_init_default {false, _logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_MIN, 0, NULL, NULL, NULL, NULL, false, _logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_MIN, NULL, NULL, false, _logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_MIN, false, 0, NULL, NULL, false, 0, false, 0, false, 0, NULL, false, 0, false, _logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_MIN, NULL, false, 0, false, 0} +#define logs_proto_mobilesdk_ios_ICoreConfiguration_init_zero {false, _logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_MIN, 0, NULL, NULL, NULL, NULL, false, _logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_MIN, NULL, NULL, false, _logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_MIN, false, 0, NULL, NULL, false, 0, false, 0, false, 0, NULL, false, 0, false, _logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_MIN, NULL, false, 0, false, 0} + +/* Field tags (for use in manual encoding/decoding) */ +#define logs_proto_mobilesdk_ios_ICoreConfiguration_pod_name_tag 16 +#define logs_proto_mobilesdk_ios_ICoreConfiguration_configuration_type_tag 1 +#define logs_proto_mobilesdk_ios_ICoreConfiguration_icore_version_tag 18 +#define logs_proto_mobilesdk_ios_ICoreConfiguration_sdk_version_tag 19 +#define logs_proto_mobilesdk_ios_ICoreConfiguration_sdk_service_installed_tag 7 +#define logs_proto_mobilesdk_ios_ICoreConfiguration_sdk_name_tag 20 +#define logs_proto_mobilesdk_ios_ICoreConfiguration_device_model_tag 9 +#define logs_proto_mobilesdk_ios_ICoreConfiguration_os_version_tag 22 +#define logs_proto_mobilesdk_ios_ICoreConfiguration_app_id_tag 10 +#define logs_proto_mobilesdk_ios_ICoreConfiguration_bundle_id_tag 12 +#define logs_proto_mobilesdk_ios_ICoreConfiguration_min_supported_ios_version_tag 24 +#define logs_proto_mobilesdk_ios_ICoreConfiguration_use_default_app_tag 25 +#define logs_proto_mobilesdk_ios_ICoreConfiguration_app_count_tag 21 +#define logs_proto_mobilesdk_ios_ICoreConfiguration_deployed_in_app_store_tag 26 +#define logs_proto_mobilesdk_ios_ICoreConfiguration_dynamic_framework_count_tag 27 +#define logs_proto_mobilesdk_ios_ICoreConfiguration_apple_framework_version_tag 28 +#define logs_proto_mobilesdk_ios_ICoreConfiguration_using_zip_file_tag 29 +#define logs_proto_mobilesdk_ios_ICoreConfiguration_deployment_type_tag 30 +#define logs_proto_mobilesdk_ios_ICoreConfiguration_platform_info_tag 31 +#define logs_proto_mobilesdk_ios_ICoreConfiguration_swizzling_enabled_tag 33 +#define logs_proto_mobilesdk_ios_ICoreConfiguration_using_gdt_tag 36 + +/* Struct field encoding specification for nanopb */ +extern const pb_field_t logs_proto_mobilesdk_ios_ICoreConfiguration_fields[22]; + +/* Maximum encoded size of messages (where known) */ +/* logs_proto_mobilesdk_ios_ICoreConfiguration_size depends on runtime parameters */ + +/* Message IDs (where set with "msgid" option) */ +#ifdef PB_MSGID + +#define FIREBASECORE_MESSAGES \ + + +#endif + +/* @@protoc_insertion_point(eof) */ + +#endif diff --git a/!main project/Pods/FirebaseCoreDiagnostics/LICENSE b/!main project/Pods/FirebaseCoreDiagnostics/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/!main project/Pods/FirebaseCoreDiagnostics/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/!main project/Pods/FirebaseCoreDiagnostics/README.md b/!main project/Pods/FirebaseCoreDiagnostics/README.md new file mode 100644 index 0000000..3ddc8fb --- /dev/null +++ b/!main project/Pods/FirebaseCoreDiagnostics/README.md @@ -0,0 +1,251 @@ +# Firebase iOS Open Source Development [![Build Status](https://travis-ci.org/firebase/firebase-ios-sdk.svg?branch=master)](https://travis-ci.org/firebase/firebase-ios-sdk) + +This repository contains a subset of the Firebase iOS SDK source. It currently +includes FirebaseCore, FirebaseABTesting, FirebaseAuth, FirebaseDatabase, +FirebaseFirestore, FirebaseFunctions, FirebaseInstanceID, FirebaseInAppMessaging, +FirebaseInAppMessagingDisplay, FirebaseMessaging, FirebaseRemoteConfig, and +FirebaseStorage. + +The repository also includes GoogleUtilities source. The +[GoogleUtilities](GoogleUtilities/README.md) pod is +a set of utilities used by Firebase and other Google products. + +Firebase is an app development platform with tools to help you build, grow and +monetize your app. More information about Firebase can be found at +[https://firebase.google.com](https://firebase.google.com). + +## Installation + +See the three subsections for details about three different installation methods. +1. [Standard pod install](README.md#standard-pod-install) +1. [Installing from the GitHub repo](README.md#installing-from-github) +1. [Experimental Carthage](README.md#carthage-ios-only) + +### Standard pod install + +Go to +[https://firebase.google.com/docs/ios/setup](https://firebase.google.com/docs/ios/setup). + +### Installing from GitHub + +For releases starting with 5.0.0, the source for each release is also deployed +to CocoaPods master and available via standard +[CocoaPods Podfile syntax](https://guides.cocoapods.org/syntax/podfile.html#pod). + +These instructions can be used to access the Firebase repo at other branches, +tags, or commits. + +#### Background + +See +[the Podfile Syntax Reference](https://guides.cocoapods.org/syntax/podfile.html#pod) +for instructions and options about overriding pod source locations. + +#### Accessing Firebase Source Snapshots + +All of the official releases are tagged in this repo and available via CocoaPods. To access a local +source snapshot or unreleased branch, use Podfile directives like the following: + +To access FirebaseFirestore via a branch: +``` +pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +``` + +To access FirebaseMessaging via a checked out version of the firebase-ios-sdk repo do: + +``` +pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk' +pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk' +``` + +### Carthage (iOS only) + +Instructions for the experimental Carthage distribution are at +[Carthage](Carthage.md). + +### Rome + +Instructions for installing binary frameworks via +[Rome](https://github.com/CocoaPods/Rome) are at [Rome](Rome.md). + +## Development + +To develop Firebase software in this repository, ensure that you have at least +the following software: + + * Xcode 10.1 (or later) + * CocoaPods 1.7.2 (or later) + * [CocoaPods generate](https://github.com/square/cocoapods-generate) + +For the pod that you want to develop: + +`pod gen Firebase{name here}.podspec --local-sources=./ --auto-open --platforms=ios` + +Note: If the CocoaPods cache is out of date, you may need to run +`pod repo update` before the `pod gen` command. + +Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for +those platforms. Since 10.2, Xcode does not properly handle multi-platform +CocoaPods workspaces. + +Firestore has a self contained Xcode project. See +[Firestore/README.md](Firestore/README.md). + +### Development for Catalyst +* `pod gen {name here}.podspec --local-sources=./ --auto-open --platforms=ios` +* Check the Mac box in the App-iOS Build Settings +* Sign the App in the Settings Signing & Capabilities tab +* Click Pods in the Project Manager +* Add Signing to the iOS host app and unit test targets +* Select the Unit-unit scheme +* Run it to build and test + +### Adding a New Firebase Pod + +See [AddNewPod.md](AddNewPod.md). + +### Code Formatting + +To ensure that the code is formatted consistently, run the script +[./scripts/style.sh](https://github.com/firebase/firebase-ios-sdk/blob/master/scripts/style.sh) +before creating a PR. + +Travis will verify that any code changes are done in a style compliant way. Install +`clang-format` and `swiftformat`. +These commands will get the right versions: + +``` +brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/e3496d9/Formula/clang-format.rb +brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/7963c3d/Formula/swiftformat.rb +``` + +Note: if you already have a newer version of these installed you may need to +`brew switch` to this version. + +To update this section, find the versions of clang-format and swiftformat.rb to +match the versions in the CI failure logs +[here](https://github.com/Homebrew/homebrew-core/tree/master/Formula). + +### Running Unit Tests + +Select a scheme and press Command-u to build a component and run its unit tests. + +#### Viewing Code Coverage + +First, make sure that [xcov](https://github.com/nakiostudio/xcov) is installed with `gem install xcov`. + +After running the `AllUnitTests_iOS` scheme in Xcode, execute +`xcov --workspace Firebase.xcworkspace --scheme AllUnitTests_iOS --output_directory xcov_output` +at Example/ in the terminal. This will aggregate the coverage, and you can run `open xcov_output/index.html` to see the results. + +### Running Sample Apps +In order to run the sample apps and integration tests, you'll need valid +`GoogleService-Info.plist` files for those samples. The Firebase Xcode project contains dummy plist +files without real values, but can be replaced with real plist files. To get your own +`GoogleService-Info.plist` files: + +1. Go to the [Firebase Console](https://console.firebase.google.com/) +2. Create a new Firebase project, if you don't already have one +3. For each sample app you want to test, create a new Firebase app with the sample app's bundle +identifier (e.g. `com.google.Database-Example`) +4. Download the resulting `GoogleService-Info.plist` and replace the appropriate dummy plist file +(e.g. in [Example/Database/App/](Example/Database/App/)); + +Some sample apps like Firebase Messaging ([Example/Messaging/App](Example/Messaging/App)) require +special Apple capabilities, and you will have to change the sample app to use a unique bundle +identifier that you can control in your own Apple Developer account. + +## Specific Component Instructions +See the sections below for any special instructions for those components. + +### Firebase Auth + +If you're doing specific Firebase Auth development, see +[the Auth Sample README](Example/Auth/README.md) for instructions about +building and running the FirebaseAuth pod along with various samples and tests. + +### Firebase Database + +To run the Database Integration tests, make your database authentication rules +[public](https://firebase.google.com/docs/database/security/quickstart). + +### Firebase Storage + +To run the Storage Integration tests, follow the instructions in +[FIRStorageIntegrationTests.m](Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m). + +#### Push Notifications + +Push notifications can only be delivered to specially provisioned App IDs in the developer portal. +In order to actually test receiving push notifications, you will need to: + +1. Change the bundle identifier of the sample app to something you own in your Apple Developer +account, and enable that App ID for push notifications. +2. You'll also need to +[upload your APNs Provider Authentication Key or certificate to the Firebase Console](https://firebase.google.com/docs/cloud-messaging/ios/certs) +at **Project Settings > Cloud Messaging > [Your Firebase App]**. +3. Ensure your iOS device is added to your Apple Developer portal as a test device. + +#### iOS Simulator + +The iOS Simulator cannot register for remote notifications, and will not receive push notifications. +In order to receive push notifications, you'll have to follow the steps above and run the app on a +physical device. + +## Community Supported Efforts + +We've seen an amazing amount of interest and contributions to improve the Firebase SDKs, and we are +very grateful! We'd like to empower as many developers as we can to be able to use Firebase and +participate in the Firebase community. + +### tvOS, macOS, and Catalyst +Thanks to contributions from the community, FirebaseABTesting, FirebaseAuth, FirebaseCore, +FirebaseDatabase, FirebaseMessaging, FirebaseFirestore, +FirebaseFunctions, FirebaseRemoteConfig, and FirebaseStorage now compile, run unit tests, and work on +tvOS, macOS, and Catalyst. + +For tvOS, checkout the [Sample](Example/tvOSSample). + +Keep in mind that macOS, Catalyst and tvOS are not officially supported by Firebase, and this +repository is actively developed primarily for iOS. While we can catch basic unit test issues with +Travis, there may be some changes where the SDK no longer works as expected on macOS or tvOS. If you +encounter this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues). + +To install, add a subset of the following to the Podfile: + +``` +pod 'Firebase/ABTesting' +pod 'Firebase/Auth' +pod 'Firebase/Database' +pod 'Firebase/Firestore' +pod 'Firebase/Functions' +pod 'Firebase/Messaging' +pod 'Firebase/RemoteConfig' +pod 'Firebase/Storage' +``` + +#### Additional Catalyst Notes + +* FirebaseAuth and FirebaseMessaging require adding `Keychain Sharing Capability` +to Build Settings. +* FirebaseFirestore requires signing the +[gRPC Resource target](https://github.com/firebase/firebase-ios-sdk/issues/3500#issuecomment-518741681). + +## Roadmap + +See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source +plans and directions. + +## Contributing + +See [Contributing](CONTRIBUTING.md) for more information on contributing to the Firebase +iOS SDK. + +## License + +The contents of this repository is licensed under the +[Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0). + +Your use of Firebase is governed by the +[Terms of Service for Firebase Services](https://firebase.google.com/terms/). diff --git a/!main project/Pods/FirebaseCoreDiagnosticsInterop/Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsData.h b/!main project/Pods/FirebaseCoreDiagnosticsInterop/Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsData.h new file mode 100644 index 0000000..69c4072 --- /dev/null +++ b/!main project/Pods/FirebaseCoreDiagnosticsInterop/Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsData.h @@ -0,0 +1,61 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** If present, is a BOOL wrapped in an NSNumber. */ +#define kFIRCDIsDataCollectionDefaultEnabledKey @"FIRCDIsDataCollectionDefaultEnabledKey" + +/** If present, is an int32_t wrapped in an NSNumber. */ +#define kFIRCDConfigurationTypeKey @"FIRCDConfigurationTypeKey" + +/** If present, is an NSString. */ +#define kFIRCDSdkNameKey @"FIRCDSdkNameKey" + +/** If present, is an NSString. */ +#define kFIRCDSdkVersionKey @"FIRCDSdkVersionKey" + +/** If present, is an int32_t wrapped in an NSNumber. */ +#define kFIRCDllAppsCountKey @"FIRCDllAppsCountKey" + +/** If present, is an NSString. */ +#define kFIRCDGoogleAppIDKey @"FIRCDGoogleAppIDKey" + +/** If present, is an NSString. */ +#define kFIRCDBundleIDKey @"FIRCDBundleID" + +/** If present, is a BOOL wrapped in an NSNumber. */ +#define kFIRCDUsingOptionsFromDefaultPlistKey @"FIRCDUsingOptionsFromDefaultPlistKey" + +/** If present, is an NSString. */ +#define kFIRCDLibraryVersionIDKey @"FIRCDLibraryVersionIDKey" + +/** If present, is an NSString. */ +#define kFIRCDFirebaseUserAgentKey @"FIRCDFirebaseUserAgentKey" + +/** Defines the interface of a data object needed to log diagnostics data. */ +@protocol FIRCoreDiagnosticsData + +@required + +/** A dictionary containing data (non-exhaustive) to be logged in diagnostics. */ +@property(nonatomic) NSDictionary *diagnosticObjects; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCoreDiagnosticsInterop/Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsInterop.h b/!main project/Pods/FirebaseCoreDiagnosticsInterop/Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsInterop.h new file mode 100644 index 0000000..2b0eb71 --- /dev/null +++ b/!main project/Pods/FirebaseCoreDiagnosticsInterop/Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsInterop.h @@ -0,0 +1,34 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRCoreDiagnosticsData.h" + +NS_ASSUME_NONNULL_BEGIN + +/** Allows the interoperation of FirebaseCore and FirebaseCoreDiagnostics. */ +@protocol FIRCoreDiagnosticsInterop + +/** Sends the given diagnostics data. + * + * @param diagnosticsData The diagnostics data object to send. + */ ++ (void)sendDiagnosticsData:(id)diagnosticsData; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCoreDiagnosticsInterop/LICENSE b/!main project/Pods/FirebaseCoreDiagnosticsInterop/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/!main project/Pods/FirebaseCoreDiagnosticsInterop/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/!main project/Pods/FirebaseCoreDiagnosticsInterop/README.md b/!main project/Pods/FirebaseCoreDiagnosticsInterop/README.md new file mode 100644 index 0000000..3ddc8fb --- /dev/null +++ b/!main project/Pods/FirebaseCoreDiagnosticsInterop/README.md @@ -0,0 +1,251 @@ +# Firebase iOS Open Source Development [![Build Status](https://travis-ci.org/firebase/firebase-ios-sdk.svg?branch=master)](https://travis-ci.org/firebase/firebase-ios-sdk) + +This repository contains a subset of the Firebase iOS SDK source. It currently +includes FirebaseCore, FirebaseABTesting, FirebaseAuth, FirebaseDatabase, +FirebaseFirestore, FirebaseFunctions, FirebaseInstanceID, FirebaseInAppMessaging, +FirebaseInAppMessagingDisplay, FirebaseMessaging, FirebaseRemoteConfig, and +FirebaseStorage. + +The repository also includes GoogleUtilities source. The +[GoogleUtilities](GoogleUtilities/README.md) pod is +a set of utilities used by Firebase and other Google products. + +Firebase is an app development platform with tools to help you build, grow and +monetize your app. More information about Firebase can be found at +[https://firebase.google.com](https://firebase.google.com). + +## Installation + +See the three subsections for details about three different installation methods. +1. [Standard pod install](README.md#standard-pod-install) +1. [Installing from the GitHub repo](README.md#installing-from-github) +1. [Experimental Carthage](README.md#carthage-ios-only) + +### Standard pod install + +Go to +[https://firebase.google.com/docs/ios/setup](https://firebase.google.com/docs/ios/setup). + +### Installing from GitHub + +For releases starting with 5.0.0, the source for each release is also deployed +to CocoaPods master and available via standard +[CocoaPods Podfile syntax](https://guides.cocoapods.org/syntax/podfile.html#pod). + +These instructions can be used to access the Firebase repo at other branches, +tags, or commits. + +#### Background + +See +[the Podfile Syntax Reference](https://guides.cocoapods.org/syntax/podfile.html#pod) +for instructions and options about overriding pod source locations. + +#### Accessing Firebase Source Snapshots + +All of the official releases are tagged in this repo and available via CocoaPods. To access a local +source snapshot or unreleased branch, use Podfile directives like the following: + +To access FirebaseFirestore via a branch: +``` +pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +``` + +To access FirebaseMessaging via a checked out version of the firebase-ios-sdk repo do: + +``` +pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk' +pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk' +``` + +### Carthage (iOS only) + +Instructions for the experimental Carthage distribution are at +[Carthage](Carthage.md). + +### Rome + +Instructions for installing binary frameworks via +[Rome](https://github.com/CocoaPods/Rome) are at [Rome](Rome.md). + +## Development + +To develop Firebase software in this repository, ensure that you have at least +the following software: + + * Xcode 10.1 (or later) + * CocoaPods 1.7.2 (or later) + * [CocoaPods generate](https://github.com/square/cocoapods-generate) + +For the pod that you want to develop: + +`pod gen Firebase{name here}.podspec --local-sources=./ --auto-open --platforms=ios` + +Note: If the CocoaPods cache is out of date, you may need to run +`pod repo update` before the `pod gen` command. + +Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for +those platforms. Since 10.2, Xcode does not properly handle multi-platform +CocoaPods workspaces. + +Firestore has a self contained Xcode project. See +[Firestore/README.md](Firestore/README.md). + +### Development for Catalyst +* `pod gen {name here}.podspec --local-sources=./ --auto-open --platforms=ios` +* Check the Mac box in the App-iOS Build Settings +* Sign the App in the Settings Signing & Capabilities tab +* Click Pods in the Project Manager +* Add Signing to the iOS host app and unit test targets +* Select the Unit-unit scheme +* Run it to build and test + +### Adding a New Firebase Pod + +See [AddNewPod.md](AddNewPod.md). + +### Code Formatting + +To ensure that the code is formatted consistently, run the script +[./scripts/style.sh](https://github.com/firebase/firebase-ios-sdk/blob/master/scripts/style.sh) +before creating a PR. + +Travis will verify that any code changes are done in a style compliant way. Install +`clang-format` and `swiftformat`. +These commands will get the right versions: + +``` +brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/e3496d9/Formula/clang-format.rb +brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/7963c3d/Formula/swiftformat.rb +``` + +Note: if you already have a newer version of these installed you may need to +`brew switch` to this version. + +To update this section, find the versions of clang-format and swiftformat.rb to +match the versions in the CI failure logs +[here](https://github.com/Homebrew/homebrew-core/tree/master/Formula). + +### Running Unit Tests + +Select a scheme and press Command-u to build a component and run its unit tests. + +#### Viewing Code Coverage + +First, make sure that [xcov](https://github.com/nakiostudio/xcov) is installed with `gem install xcov`. + +After running the `AllUnitTests_iOS` scheme in Xcode, execute +`xcov --workspace Firebase.xcworkspace --scheme AllUnitTests_iOS --output_directory xcov_output` +at Example/ in the terminal. This will aggregate the coverage, and you can run `open xcov_output/index.html` to see the results. + +### Running Sample Apps +In order to run the sample apps and integration tests, you'll need valid +`GoogleService-Info.plist` files for those samples. The Firebase Xcode project contains dummy plist +files without real values, but can be replaced with real plist files. To get your own +`GoogleService-Info.plist` files: + +1. Go to the [Firebase Console](https://console.firebase.google.com/) +2. Create a new Firebase project, if you don't already have one +3. For each sample app you want to test, create a new Firebase app with the sample app's bundle +identifier (e.g. `com.google.Database-Example`) +4. Download the resulting `GoogleService-Info.plist` and replace the appropriate dummy plist file +(e.g. in [Example/Database/App/](Example/Database/App/)); + +Some sample apps like Firebase Messaging ([Example/Messaging/App](Example/Messaging/App)) require +special Apple capabilities, and you will have to change the sample app to use a unique bundle +identifier that you can control in your own Apple Developer account. + +## Specific Component Instructions +See the sections below for any special instructions for those components. + +### Firebase Auth + +If you're doing specific Firebase Auth development, see +[the Auth Sample README](Example/Auth/README.md) for instructions about +building and running the FirebaseAuth pod along with various samples and tests. + +### Firebase Database + +To run the Database Integration tests, make your database authentication rules +[public](https://firebase.google.com/docs/database/security/quickstart). + +### Firebase Storage + +To run the Storage Integration tests, follow the instructions in +[FIRStorageIntegrationTests.m](Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m). + +#### Push Notifications + +Push notifications can only be delivered to specially provisioned App IDs in the developer portal. +In order to actually test receiving push notifications, you will need to: + +1. Change the bundle identifier of the sample app to something you own in your Apple Developer +account, and enable that App ID for push notifications. +2. You'll also need to +[upload your APNs Provider Authentication Key or certificate to the Firebase Console](https://firebase.google.com/docs/cloud-messaging/ios/certs) +at **Project Settings > Cloud Messaging > [Your Firebase App]**. +3. Ensure your iOS device is added to your Apple Developer portal as a test device. + +#### iOS Simulator + +The iOS Simulator cannot register for remote notifications, and will not receive push notifications. +In order to receive push notifications, you'll have to follow the steps above and run the app on a +physical device. + +## Community Supported Efforts + +We've seen an amazing amount of interest and contributions to improve the Firebase SDKs, and we are +very grateful! We'd like to empower as many developers as we can to be able to use Firebase and +participate in the Firebase community. + +### tvOS, macOS, and Catalyst +Thanks to contributions from the community, FirebaseABTesting, FirebaseAuth, FirebaseCore, +FirebaseDatabase, FirebaseMessaging, FirebaseFirestore, +FirebaseFunctions, FirebaseRemoteConfig, and FirebaseStorage now compile, run unit tests, and work on +tvOS, macOS, and Catalyst. + +For tvOS, checkout the [Sample](Example/tvOSSample). + +Keep in mind that macOS, Catalyst and tvOS are not officially supported by Firebase, and this +repository is actively developed primarily for iOS. While we can catch basic unit test issues with +Travis, there may be some changes where the SDK no longer works as expected on macOS or tvOS. If you +encounter this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues). + +To install, add a subset of the following to the Podfile: + +``` +pod 'Firebase/ABTesting' +pod 'Firebase/Auth' +pod 'Firebase/Database' +pod 'Firebase/Firestore' +pod 'Firebase/Functions' +pod 'Firebase/Messaging' +pod 'Firebase/RemoteConfig' +pod 'Firebase/Storage' +``` + +#### Additional Catalyst Notes + +* FirebaseAuth and FirebaseMessaging require adding `Keychain Sharing Capability` +to Build Settings. +* FirebaseFirestore requires signing the +[gRPC Resource target](https://github.com/firebase/firebase-ios-sdk/issues/3500#issuecomment-518741681). + +## Roadmap + +See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source +plans and directions. + +## Contributing + +See [Contributing](CONTRIBUTING.md) for more information on contributing to the Firebase +iOS SDK. + +## License + +The contents of this repository is licensed under the +[Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0). + +Your use of Firebase is governed by the +[Terms of Service for Firebase Services](https://firebase.google.com/terms/). diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSApplication.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSApplication.h new file mode 100644 index 0000000..75536f7 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSApplication.h @@ -0,0 +1,88 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import +#if CLS_TARGET_OS_HAS_UIKIT +#import +#endif + +__BEGIN_DECLS + +#define FIRCLSApplicationActivityDefault \ + (NSActivitySuddenTerminationDisabled | NSActivityAutomaticTerminationDisabled) + +/** + * Type to indicate application installation source + */ +typedef NS_ENUM(NSInteger, FIRCLSApplicationInstallationSourceType) { + FIRCLSApplicationInstallationSourceTypeDeveloperInstall = 1, + // 2 and 3 are reserved for legacy values. + FIRCLSApplicationInstallationSourceTypeAppStore = 4 +}; + +/** + * Returns the application bundle identifier with occurences of "/" replaced by "_" + */ +NSString* FIRCLSApplicationGetBundleIdentifier(void); + +/** + * Returns the SDK's bundle ID + */ +NSString* FIRCLSApplicationGetSDKBundleID(void); + +/** + * Returns the platform identifier, either: ios, mac, or tvos. + * Catalyst apps are treated as mac. + */ +NSString* FIRCLSApplicationGetPlatform(void); + +/** + * Returns the user-facing app name + */ +NSString* FIRCLSApplicationGetName(void); + +/** + * Returns the build number + */ +NSString* FIRCLSApplicationGetBundleVersion(void); + +/** + * Returns the human-readable build version + */ +NSString* FIRCLSApplicationGetShortBundleVersion(void); + +/** + * Returns a number to indicate how the app has been installed: Developer / App Store + */ +FIRCLSApplicationInstallationSourceType FIRCLSApplicationInstallationSource(void); + +BOOL FIRCLSApplicationIsExtension(void); +NSString* FIRCLSApplicationExtensionPointIdentifier(void); + +#if CLS_TARGET_OS_HAS_UIKIT +UIApplication* FIRCLSApplicationSharedInstance(void); +#else +id FIRCLSApplicationSharedInstance(void); +#endif + +void FIRCLSApplicationOpenURL(NSURL* url, + NSExtensionContext* extensionContext, + void (^completionBlock)(BOOL success)); + +id FIRCLSApplicationBeginActivity(NSActivityOptions options, NSString* reason); +void FIRCLSApplicationEndActivity(id activity); + +void FIRCLSApplicationActivity(NSActivityOptions options, NSString* reason, void (^block)(void)); + +__END_DECLS diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSApplication.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSApplication.m new file mode 100644 index 0000000..219b4bb --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSApplication.m @@ -0,0 +1,211 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSApplication.h" + +#import "FIRCLSHost.h" +#import "FIRCLSUtility.h" + +#if CLS_TARGET_OS_OSX +#import +#endif + +#if CLS_TARGET_OS_HAS_UIKIT +#import +#endif + +NSString* FIRCLSApplicationGetBundleIdentifier(void) { + return [[[NSBundle mainBundle] bundleIdentifier] stringByReplacingOccurrencesOfString:@"/" + withString:@"_"]; +} + +NSString* FIRCLSApplicationGetSDKBundleID(void) { + return + [@"com.google.firebase.crashlytics." stringByAppendingString:FIRCLSApplicationGetPlatform()]; +} + +NSString* FIRCLSApplicationGetPlatform(void) { +#if defined(TARGET_OS_MACCATALYST) && TARGET_OS_MACCATALYST + return @"mac"; +#elif TARGET_OS_IOS + return @"ios"; +#elif TARGET_OS_OSX + return @"mac"; +#elif TARGET_OS_TV + return @"tvos"; +#endif +} + +// these defaults match the FIRCLSInfoPlist helper in FIRCLSIDEFoundation +NSString* FIRCLSApplicationGetBundleVersion(void) { + return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]; +} + +NSString* FIRCLSApplicationGetShortBundleVersion(void) { + return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; +} + +NSString* FIRCLSApplicationGetName(void) { + NSString* name; + NSBundle* mainBundle; + + mainBundle = [NSBundle mainBundle]; + + name = [mainBundle objectForInfoDictionaryKey:@"CFBundleDisplayName"]; + if (name) { + return name; + } + + name = [mainBundle objectForInfoDictionaryKey:@"CFBundleName"]; + if (name) { + return name; + } + + return FIRCLSApplicationGetBundleVersion(); +} + +BOOL FIRCLSApplicationHasAppStoreReceipt(void) { + NSURL* url = NSBundle.mainBundle.appStoreReceiptURL; + return [NSFileManager.defaultManager fileExistsAtPath:[url path]]; +} + +FIRCLSApplicationInstallationSourceType FIRCLSApplicationInstallationSource(void) { + if (FIRCLSApplicationHasAppStoreReceipt()) { + return FIRCLSApplicationInstallationSourceTypeAppStore; + } + + return FIRCLSApplicationInstallationSourceTypeDeveloperInstall; +} + +BOOL FIRCLSApplicationIsExtension(void) { + return FIRCLSApplicationExtensionPointIdentifier() != nil; +} + +NSString* FIRCLSApplicationExtensionPointIdentifier(void) { + id extensionDict = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"NSExtension"]; + + if (!extensionDict) { + return nil; + } + + if (![extensionDict isKindOfClass:[NSDictionary class]]) { + FIRCLSSDKLog("Error: NSExtension Info.plist entry is mal-formed\n"); + return nil; + } + + id typeValue = [(NSDictionary*)extensionDict objectForKey:@"NSExtensionPointIdentifier"]; + + if (![typeValue isKindOfClass:[NSString class]]) { + FIRCLSSDKLog("Error: NSExtensionPointIdentifier Info.plist entry is mal-formed\n"); + return nil; + } + + return typeValue; +} + +#if CLS_TARGET_OS_HAS_UIKIT +UIApplication* FIRCLSApplicationSharedInstance(void) { + if (FIRCLSApplicationIsExtension()) { + return nil; + } + + return [[UIApplication class] performSelector:@selector(sharedApplication)]; +} +#elif CLS_TARGET_OS_OSX +id FIRCLSApplicationSharedInstance(void) { + return [NSClassFromString(@"NSApplication") sharedApplication]; +} +#else +id FIRCLSApplicationSharedInstance(void) { + return nil; // FIXME: what do we actually return for watch? +} +#endif + +void FIRCLSApplicationOpenURL(NSURL* url, + NSExtensionContext* extensionContext, + void (^completionBlock)(BOOL success)) { + if (extensionContext) { + [extensionContext openURL:url completionHandler:completionBlock]; + return; + } + + BOOL result = NO; + +#if TARGET_OS_IOS + // What's going on here is the value returned is a scalar, but we really need an object to + // call this dynamically. Hoops must be jumped. + NSInvocationOperation* op = + [[NSInvocationOperation alloc] initWithTarget:FIRCLSApplicationSharedInstance() + selector:@selector(openURL:) + object:url]; + [op start]; + [op.result getValue:&result]; +#elif CLS_TARGET_OS_OSX + result = [[NSClassFromString(@"NSWorkspace") sharedWorkspace] openURL:url]; +#endif + + completionBlock(result); +} + +id FIRCLSApplicationBeginActivity(NSActivityOptions options, NSString* reason) { + if ([[NSProcessInfo processInfo] respondsToSelector:@selector(beginActivityWithOptions: + reason:)]) { + return [[NSProcessInfo processInfo] beginActivityWithOptions:options reason:reason]; + } + +#if CLS_TARGET_OS_OSX + if (options & NSActivitySuddenTerminationDisabled) { + [[NSProcessInfo processInfo] disableSuddenTermination]; + } + + if (options & NSActivityAutomaticTerminationDisabled) { + [[NSProcessInfo processInfo] disableAutomaticTermination:reason]; + } +#endif + + // encode the options, so we can undo our work later + return @{@"options" : @(options), @"reason" : reason}; +} + +void FIRCLSApplicationEndActivity(id activity) { + if (!activity) { + return; + } + + if ([[NSProcessInfo processInfo] respondsToSelector:@selector(endActivity:)]) { + [[NSProcessInfo processInfo] endActivity:activity]; + return; + } + +#if CLS_TARGET_OS_OSX + NSInteger options = [[(NSDictionary*)activity objectForKey:@"options"] integerValue]; + + if (options & NSActivitySuddenTerminationDisabled) { + [[NSProcessInfo processInfo] enableSuddenTermination]; + } + + if (options & NSActivityAutomaticTerminationDisabled) { + [[NSProcessInfo processInfo] + enableAutomaticTermination:[(NSDictionary*)activity objectForKey:@"reason"]]; + } +#endif +} + +void FIRCLSApplicationActivity(NSActivityOptions options, NSString* reason, void (^block)(void)) { + id activity = FIRCLSApplicationBeginActivity(options, reason); + + block(); + + FIRCLSApplicationEndActivity(activity); +} diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSBinaryImage.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSBinaryImage.h new file mode 100644 index 0000000..75929df --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSBinaryImage.h @@ -0,0 +1,81 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "FIRCLSFeatures.h" +#include "FIRCLSFile.h" +#include "FIRCLSMachO.h" + +#include +#include + +__BEGIN_DECLS + +// Typically, apps seem to have ~300 binary images loaded +#define CLS_BINARY_IMAGE_RUNTIME_NODE_COUNT (512) +#define CLS_BINARY_IMAGE_RUNTIME_NODE_NAME_SIZE (32) +#define CLS_BINARY_IMAGE_RUNTIME_NODE_RECORD_NAME 0 + +#define FIRCLSUUIDStringLength (33) + +typedef struct { + _Atomic(void*) volatile baseAddress; + uint64_t size; +#if CLS_DWARF_UNWINDING_SUPPORTED + const void* ehFrame; +#endif +#if CLS_COMPACT_UNWINDING_SUPPORTED + const void* unwindInfo; +#endif + const void* crashInfo; +#if CLS_BINARY_IMAGE_RUNTIME_NODE_RECORD_NAME + char name[CLS_BINARY_IMAGE_RUNTIME_NODE_NAME_SIZE]; +#endif +} FIRCLSBinaryImageRuntimeNode; + +typedef struct { + char uuidString[FIRCLSUUIDStringLength]; + bool encrypted; + FIRCLSMachOVersion builtSDK; + FIRCLSMachOVersion minSDK; + FIRCLSBinaryImageRuntimeNode node; + struct FIRCLSMachOSlice slice; + intptr_t vmaddr_slide; +} FIRCLSBinaryImageDetails; + +typedef struct { + const char* path; +} FIRCLSBinaryImageReadOnlyContext; + +typedef struct { + FIRCLSFile file; + FIRCLSBinaryImageRuntimeNode nodes[CLS_BINARY_IMAGE_RUNTIME_NODE_COUNT]; +} FIRCLSBinaryImageReadWriteContext; + +void FIRCLSBinaryImageInit(FIRCLSBinaryImageReadOnlyContext* roContext, + FIRCLSBinaryImageReadWriteContext* rwContext); + +#if CLS_COMPACT_UNWINDING_SUPPORTED +bool FIRCLSBinaryImageSafeFindImageForAddress(uintptr_t address, + FIRCLSBinaryImageRuntimeNode* image); +bool FIRCLSBinaryImageSafeHasUnwindInfo(FIRCLSBinaryImageRuntimeNode* image); +#endif + +bool FIRCLSBinaryImageFindImageForUUID(const char* uuidString, + FIRCLSBinaryImageDetails* imageDetails); + +bool FIRCLSBinaryImageRecordMainExecutable(FIRCLSFile* file); + +__END_DECLS diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSBinaryImage.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSBinaryImage.m new file mode 100644 index 0000000..e262297 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSBinaryImage.m @@ -0,0 +1,571 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FIRCLSBinaryImage.h" + +#include +#include + +#include + +#include + +#include "FIRCLSByteUtility.h" +#include "FIRCLSFeatures.h" +#include "FIRCLSFile.h" +#include "FIRCLSGlobals.h" +#include "FIRCLSHost.h" +#include "FIRCLSMachO.h" +#include "FIRCLSUtility.h" + +#include + +// this is defined only if __OPEN_SOURCE__ is *not* defined in the TVOS SDK's mach-o/loader.h +// also, it has not yet made it back to the OSX SDKs, for example +#ifndef LC_VERSION_MIN_TVOS +#define LC_VERSION_MIN_TVOS 0x2F +#endif + +#pragma mark Prototypes +static bool FIRCLSBinaryImageOpenIfNeeded(bool* needsClosing); + +static void FIRCLSBinaryImageAddedCallback(const struct mach_header* mh, intptr_t vmaddr_slide); +static void FIRCLSBinaryImageRemovedCallback(const struct mach_header* mh, intptr_t vmaddr_slide); +static void FIRCLSBinaryImageChanged(bool added, + const struct mach_header* mh, + intptr_t vmaddr_slide); +static bool FIRCLSBinaryImageFillInImageDetails(FIRCLSBinaryImageDetails* details); + +static void FIRCLSBinaryImageStoreNode(bool added, FIRCLSBinaryImageDetails imageDetails); +static void FIRCLSBinaryImageRecordSlice(bool added, const FIRCLSBinaryImageDetails imageDetails); + +#pragma mark - Core API +void FIRCLSBinaryImageInit(FIRCLSBinaryImageReadOnlyContext* roContext, + FIRCLSBinaryImageReadWriteContext* rwContext) { + // initialize our node array to all zeros + memset(&_clsContext.writable->binaryImage, 0, sizeof(_clsContext.writable->binaryImage)); + _clsContext.writable->binaryImage.file.fd = -1; + + dispatch_async(FIRCLSGetBinaryImageQueue(), ^{ + if (!FIRCLSUnlinkIfExists(_clsContext.readonly->binaryimage.path)) { + FIRCLSSDKLog("Unable to reset the binary image log file %s\n", strerror(errno)); + } + + bool needsClosing; // unneeded + if (!FIRCLSBinaryImageOpenIfNeeded(&needsClosing)) { + FIRCLSSDKLog("Error: Unable to open the binary image log file during init\n"); + } + }); + + _dyld_register_func_for_add_image(FIRCLSBinaryImageAddedCallback); + _dyld_register_func_for_remove_image(FIRCLSBinaryImageRemovedCallback); + + dispatch_async(FIRCLSGetBinaryImageQueue(), ^{ + FIRCLSFileClose(&_clsContext.writable->binaryImage.file); + }); +} + +static bool FIRCLSBinaryImageOpenIfNeeded(bool* needsClosing) { + if (!FIRCLSIsValidPointer(_clsContext.writable)) { + return false; + } + + if (!FIRCLSIsValidPointer(_clsContext.readonly)) { + return false; + } + + if (!FIRCLSIsValidPointer(needsClosing)) { + return false; + } + + *needsClosing = false; + + if (FIRCLSFileIsOpen(&_clsContext.writable->binaryImage.file)) { + return true; + } + + if (!FIRCLSFileInitWithPath(&_clsContext.writable->binaryImage.file, + _clsContext.readonly->binaryimage.path, false)) { + FIRCLSSDKLog("Error: unable to open binary image log file\n"); + return false; + } + + *needsClosing = true; + + return true; +} + +#if CLS_COMPACT_UNWINDING_SUPPORTED +bool FIRCLSBinaryImageSafeFindImageForAddress(uintptr_t address, + FIRCLSBinaryImageRuntimeNode* image) { + if (!FIRCLSContextIsInitialized()) { + return false; + } + + if (address == 0) { + return false; + } + + if (!FIRCLSIsValidPointer(image)) { + return false; + } + + FIRCLSBinaryImageRuntimeNode* nodes = _clsContext.writable->binaryImage.nodes; + if (!nodes) { + FIRCLSSDKLogError("The node structure is NULL\n"); + return false; + } + + for (uint32_t i = 0; i < CLS_BINARY_IMAGE_RUNTIME_NODE_COUNT; ++i) { + FIRCLSBinaryImageRuntimeNode* node = &nodes[i]; + if (!FIRCLSIsValidPointer(node)) { + FIRCLSSDKLog( + "Invalid node pointer encountered in context's writable binary image at index %i", i); + continue; + } + + if ((address >= (uintptr_t)node->baseAddress) && + (address < (uintptr_t)node->baseAddress + node->size)) { + *image = *node; // copy the image + return true; + } + } + + return false; +} + +bool FIRCLSBinaryImageSafeHasUnwindInfo(FIRCLSBinaryImageRuntimeNode* image) { + return FIRCLSIsValidPointer(image->unwindInfo); +} +#endif + +bool FIRCLSBinaryImageFindImageForUUID(const char* uuidString, + FIRCLSBinaryImageDetails* imageDetails) { + if (!imageDetails || !uuidString) { + FIRCLSSDKLog("null input\n"); + return false; + } + + uint32_t imageCount = _dyld_image_count(); + + for (uint32_t i = 0; i < imageCount; ++i) { + const struct mach_header* mh = _dyld_get_image_header(i); + + FIRCLSBinaryImageDetails image; + + image.slice = FIRCLSMachOSliceWithHeader((void*)mh); + FIRCLSBinaryImageFillInImageDetails(&image); + + if (strncmp(uuidString, image.uuidString, FIRCLSUUIDStringLength) == 0) { + *imageDetails = image; + return true; + } + } + + return false; +} + +#pragma mark - DYLD callback handlers +static void FIRCLSBinaryImageAddedCallback(const struct mach_header* mh, intptr_t vmaddr_slide) { + FIRCLSBinaryImageChanged(true, mh, vmaddr_slide); +} + +static void FIRCLSBinaryImageRemovedCallback(const struct mach_header* mh, intptr_t vmaddr_slide) { + FIRCLSBinaryImageChanged(false, mh, vmaddr_slide); +} + +#if CLS_BINARY_IMAGE_RUNTIME_NODE_RECORD_NAME +static bool FIRCLSBinaryImagePopulateRuntimeNodeName(FIRCLSBinaryImageDetails* details) { + if (!FIRCLSIsValidPointer(details)) { + return false; + } + + memset(details->node.name, 0, CLS_BINARY_IMAGE_RUNTIME_NODE_NAME_SIZE); + + // We have limited storage space for the name. And, we really want to store + // "CoreFoundation", not "/System/Library/Fram", so we have to play tricks + // to make sure we get the right side of the string. + const char* imageName = FIRCLSMachOSliceGetExecutablePath(&details->slice); + if (!imageName) { + return false; + } + + const size_t imageNameLength = strlen(imageName); + + // Remember to leave one character for null-termination. + if (imageNameLength > CLS_BINARY_IMAGE_RUNTIME_NODE_NAME_SIZE - 1) { + imageName = imageName + (imageNameLength - (CLS_BINARY_IMAGE_RUNTIME_NODE_NAME_SIZE - 1)); + } + + // subtract one to make sure the string is always null-terminated + strncpy(details->node.name, imageName, CLS_BINARY_IMAGE_RUNTIME_NODE_NAME_SIZE - 1); + + return true; +} +#endif + +// There were plans later to replace this with FIRCLSMachO +static FIRCLSMachOSegmentCommand FIRCLSBinaryImageMachOGetSegmentCommand( + const struct load_command* cmd) { + FIRCLSMachOSegmentCommand segmentCommand; + + memset(&segmentCommand, 0, sizeof(FIRCLSMachOSegmentCommand)); + + if (!cmd) { + return segmentCommand; + } + + if (cmd->cmd == LC_SEGMENT) { + struct segment_command* segCmd = (struct segment_command*)cmd; + + memcpy(segmentCommand.segname, segCmd->segname, 16); + segmentCommand.vmaddr = segCmd->vmaddr; + segmentCommand.vmsize = segCmd->vmsize; + } else if (cmd->cmd == LC_SEGMENT_64) { + struct segment_command_64* segCmd = (struct segment_command_64*)cmd; + + memcpy(segmentCommand.segname, segCmd->segname, 16); + segmentCommand.vmaddr = segCmd->vmaddr; + segmentCommand.vmsize = segCmd->vmsize; + } + + return segmentCommand; +} + +static bool FIRCLSBinaryImageMachOSliceInitSectionByName(FIRCLSMachOSliceRef slice, + const char* segName, + const char* sectionName, + FIRCLSMachOSection* section) { + if (!FIRCLSIsValidPointer(slice)) { + return false; + } + + if (!section) { + return false; + } + + memset(section, 0, sizeof(FIRCLSMachOSection)); + + if (FIRCLSMachOSliceIs64Bit(slice)) { + const struct section_64* sect = + getsectbynamefromheader_64(slice->startAddress, segName, sectionName); + if (!sect) { + return false; + } + + section->addr = sect->addr; + section->size = sect->size; + section->offset = sect->offset; + } else { + const struct section* sect = getsectbynamefromheader(slice->startAddress, segName, sectionName); + if (!sect) { + return false; + } + + section->addr = sect->addr; + section->size = sect->size; + section->offset = sect->offset; + } + + return true; +} + +static bool FIRCLSBinaryImageFillInImageDetails(FIRCLSBinaryImageDetails* details) { + if (!FIRCLSIsValidPointer(details)) { + return false; + } + + if (!FIRCLSIsValidPointer(details->slice.startAddress)) { + return false; + } + +#if CLS_BINARY_IMAGE_RUNTIME_NODE_RECORD_NAME + // this is done for debugging purposes, so if it fails, its ok to continue + FIRCLSBinaryImagePopulateRuntimeNodeName(details); +#endif + + // This cast might look a little dubious, but its just because we're using the same + // struct types in a few different places. + details->node.baseAddress = (void* volatile)details->slice.startAddress; + + FIRCLSMachOSliceEnumerateLoadCommands( + &details->slice, ^(uint32_t type, uint32_t size, const struct load_command* cmd) { + switch (type) { + case LC_UUID: { + const uint8_t* uuid = FIRCLSMachOGetUUID(cmd); + FIRCLSSafeHexToString(uuid, 16, details->uuidString); + } break; + case LC_ENCRYPTION_INFO: + details->encrypted = FIRCLSMachOGetEncrypted(cmd); + break; + case LC_SEGMENT: + case LC_SEGMENT_64: { + FIRCLSMachOSegmentCommand segmentCommand = FIRCLSBinaryImageMachOGetSegmentCommand(cmd); + + if (strncmp(segmentCommand.segname, SEG_TEXT, sizeof(SEG_TEXT)) == 0) { + details->node.size = segmentCommand.vmsize; + } + } break; + case LC_VERSION_MIN_MACOSX: + case LC_VERSION_MIN_IPHONEOS: + case LC_VERSION_MIN_TVOS: + case LC_VERSION_MIN_WATCHOS: + details->minSDK = FIRCLSMachOGetMinimumOSVersion(cmd); + details->builtSDK = FIRCLSMachOGetLinkedSDKVersion(cmd); + break; + } + }); + + // We look up the section we want, and we *should* be able to use: + // + // address of data we want = start address + section.offset + // + // However, the offset value is coming back funky in iOS 9. So, instead we look up the address + // the section should be loaded at, and compute the offset by looking up the address of the + // segment itself. + + FIRCLSMachOSection section; + +#if CLS_COMPACT_UNWINDING_SUPPORTED + if (FIRCLSBinaryImageMachOSliceInitSectionByName(&details->slice, SEG_TEXT, "__unwind_info", + §ion)) { + details->node.unwindInfo = (void*)(section.addr + details->vmaddr_slide); + } +#endif + +#if CLS_DWARF_UNWINDING_SUPPORTED + if (FIRCLSBinaryImageMachOSliceInitSectionByName(&details->slice, SEG_TEXT, "__eh_frame", + §ion)) { + details->node.ehFrame = (void*)(section.addr + details->vmaddr_slide); + } +#endif + + if (FIRCLSBinaryImageMachOSliceInitSectionByName(&details->slice, SEG_DATA, "__crash_info", + §ion)) { + details->node.crashInfo = (void*)(section.addr + details->vmaddr_slide); + } + + return true; +} + +static void FIRCLSBinaryImageChanged(bool added, + const struct mach_header* mh, + intptr_t vmaddr_slide) { + // FIRCLSSDKLog("Binary image %s %p\n", added ? "loaded" : "unloaded", mh); + + FIRCLSBinaryImageDetails imageDetails; + + memset(&imageDetails, 0, sizeof(FIRCLSBinaryImageDetails)); + + imageDetails.slice = FIRCLSMachOSliceWithHeader((void*)mh); + imageDetails.vmaddr_slide = vmaddr_slide; + FIRCLSBinaryImageFillInImageDetails(&imageDetails); + + // this is an atomic operation + FIRCLSBinaryImageStoreNode(added, imageDetails); + + // this isn't, so do it on a serial queue + dispatch_async(FIRCLSGetBinaryImageQueue(), ^{ + FIRCLSBinaryImageRecordSlice(added, imageDetails); + }); +} + +#pragma mark - In-Memory Storage +static void FIRCLSBinaryImageStoreNode(bool added, FIRCLSBinaryImageDetails imageDetails) { + // This function is mutating a structure that needs to be accessed at crash time. We + // need to make sure the structure is always in as valid a state as possible. + // FIRCLSSDKLog("Storing %s node %p\n", added ? "loaded" : "unloaded", + // (void*)imageDetails.node.baseAddress); + + if (!_clsContext.writable) { + FIRCLSSDKLog("Error: Writable context is NULL\n"); + return; + } + + void* searchAddress = NULL; + bool success = false; + FIRCLSBinaryImageRuntimeNode* nodes = _clsContext.writable->binaryImage.nodes; + + if (!added) { + // capture the search address first + searchAddress = imageDetails.node.baseAddress; + + // If we are removing a node, we need to set its entries to zero. By clearing all of + // these values, we can just copy in imageDetails.node. Using memset here is slightly + // weird, since we have to restore one field. But, this way, if/when the structure changes, + // we still do the right thing. + memset(&imageDetails.node, 0, sizeof(FIRCLSBinaryImageRuntimeNode)); + + // restore the baseAddress, which just got zeroed, and is used for indexing + imageDetails.node.baseAddress = searchAddress; + } + + for (uint32_t i = 0; i < CLS_BINARY_IMAGE_RUNTIME_NODE_COUNT; ++i) { + FIRCLSBinaryImageRuntimeNode* node = &nodes[i]; + + if (!node) { + FIRCLSSDKLog("Error: Binary image storage is NULL\n"); + break; + } + + // navigate through the array, looking for our matching address + if (node->baseAddress != searchAddress) { + continue; + } + + // Attempt to swap the base address with whatever we are searching for. Success means that + // entry has been claims/cleared. Failure means some other thread beat us to it. + if (atomic_compare_exchange_strong(&node->baseAddress, &searchAddress, + imageDetails.node.baseAddress)) { + *node = imageDetails.node; + success = true; + + break; + } + + // If this is an unload, getting here means two threads unloaded at the same time. I think + // that's highly unlikely, and possibly even impossible. So, I'm choosing to abort the process + // at this point. + if (!added) { + FIRCLSSDKLog("Error: Failed to swap during image unload\n"); + break; + } + } + + if (!success) { + FIRCLSSDKLog("Error: Unable to track a %s node %p\n", added ? "loaded" : "unloaded", + (void*)imageDetails.node.baseAddress); + } +} + +#pragma mark - On-Disk Storage +static void FIRCLSBinaryImageRecordDetails(FIRCLSFile* file, + const FIRCLSBinaryImageDetails imageDetails) { + if (!file) { + FIRCLSSDKLog("Error: file is invalid\n"); + return; + } + + FIRCLSFileWriteHashEntryString(file, "uuid", imageDetails.uuidString); + FIRCLSFileWriteHashEntryUint64(file, "base", (uintptr_t)imageDetails.slice.startAddress); + FIRCLSFileWriteHashEntryUint64(file, "size", imageDetails.node.size); +} + +static void FIRCLSBinaryImageRecordLibraryFrameworkInfo(FIRCLSFile* file, const char* path) { + if (!file) { + FIRCLSSDKLog("Error: file is invalid\n"); + return; + } + + if (!path) { + return; + } + + // Because this function is so expensive, we've decided to omit this info for all Apple-supplied + // frameworks. This really isn't that bad, because we can know their info ahead of time (within a + // small margin of error). With this implemenation, we will still record this info for any + // user-built framework, which in the end is the most important thing. + if (strncmp(path, "/System", 7) == 0) { + return; + } + + // check to see if this is a potential framework bundle + if (!strstr(path, ".framework")) { + return; + } + + // My.framework/Versions/A/My for OS X + // My.framework/My for iOS + + NSString* frameworkPath = [NSString stringWithUTF8String:path]; +#if TARGET_OS_IPHONE + frameworkPath = [frameworkPath stringByDeletingLastPathComponent]; +#else + frameworkPath = [frameworkPath stringByDeletingLastPathComponent]; // My.framework/Versions/A + frameworkPath = [frameworkPath stringByDeletingLastPathComponent]; // My.framework/Versions + frameworkPath = [frameworkPath stringByDeletingLastPathComponent]; // My.framework +#endif + + NSBundle* const bundle = [NSBundle bundleWithPath:frameworkPath]; + + if (!bundle) { + return; + } + + FIRCLSFileWriteHashEntryNSStringUnlessNilOrEmpty(file, "bundle_id", [bundle bundleIdentifier]); + FIRCLSFileWriteHashEntryNSStringUnlessNilOrEmpty( + file, "build_version", [bundle objectForInfoDictionaryKey:@"CFBundleVersion"]); + FIRCLSFileWriteHashEntryNSStringUnlessNilOrEmpty( + file, "display_version", [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]); +} + +static void FIRCLSBinaryImageRecordSlice(bool added, const FIRCLSBinaryImageDetails imageDetails) { + bool needsClosing = false; + if (!FIRCLSBinaryImageOpenIfNeeded(&needsClosing)) { + FIRCLSSDKLog("Error: unable to open binary image log file\n"); + return; + } + + FIRCLSFile* file = &_clsContext.writable->binaryImage.file; + + FIRCLSFileWriteSectionStart(file, added ? "load" : "unload"); + + FIRCLSFileWriteHashStart(file); + + const char* path = FIRCLSMachOSliceGetExecutablePath((FIRCLSMachOSliceRef)&imageDetails.slice); + + FIRCLSFileWriteHashEntryString(file, "path", path); + + if (added) { + // this won't work if the binary has been unloaded + FIRCLSBinaryImageRecordLibraryFrameworkInfo(file, path); + } + + FIRCLSBinaryImageRecordDetails(file, imageDetails); + + FIRCLSFileWriteHashEnd(file); + + FIRCLSFileWriteSectionEnd(file); + + if (needsClosing) { + FIRCLSFileClose(file); + } +} + +bool FIRCLSBinaryImageRecordMainExecutable(FIRCLSFile* file) { + FIRCLSBinaryImageDetails imageDetails; + + memset(&imageDetails, 0, sizeof(FIRCLSBinaryImageDetails)); + + imageDetails.slice = FIRCLSMachOSliceGetCurrent(); + FIRCLSBinaryImageFillInImageDetails(&imageDetails); + + FIRCLSFileWriteSectionStart(file, "executable"); + FIRCLSFileWriteHashStart(file); + + FIRCLSFileWriteHashEntryString(file, "architecture", + FIRCLSMachOSliceGetArchitectureName(&imageDetails.slice)); + + FIRCLSBinaryImageRecordDetails(file, imageDetails); + FIRCLSFileWriteHashEntryBoolean(file, "encrypted", imageDetails.encrypted); + FIRCLSFileWriteHashEntryString(file, "minimum_sdk_version", + [FIRCLSMachOFormatVersion(&imageDetails.minSDK) UTF8String]); + FIRCLSFileWriteHashEntryString(file, "built_sdk_version", + [FIRCLSMachOFormatVersion(&imageDetails.builtSDK) UTF8String]); + + FIRCLSFileWriteHashEnd(file); + FIRCLSFileWriteSectionEnd(file); + + return true; +} diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSContext.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSContext.h new file mode 100644 index 0000000..f365608 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSContext.h @@ -0,0 +1,106 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "FIRCLSAllocate.h" +#include "FIRCLSBinaryImage.h" +#include "FIRCLSException.h" +#include "FIRCLSFeatures.h" +#include "FIRCLSHost.h" +#include "FIRCLSInternalLogging.h" +#include "FIRCLSMachException.h" +#include "FIRCLSSignal.h" +#include "FIRCLSUserLogging.h" + +#include +#include + +// The purpose of the crash context is to hold values that absolutely must be read and/or written at +// crash time. For robustness against memory corruption, they are protected with guard pages. +// Further, the context is seperated into read-only and read-write sections. + +__BEGIN_DECLS + +typedef struct { + volatile bool initialized; + volatile bool debuggerAttached; + const char* previouslyCrashedFileFullPath; + const char* logPath; +#if CLS_USE_SIGALTSTACK + void* signalStack; +#endif +#if CLS_MACH_EXCEPTION_SUPPORTED + void* machStack; +#endif + void* delegate; + void* callbackDelegate; + + FIRCLSBinaryImageReadOnlyContext binaryimage; + FIRCLSExceptionReadOnlyContext exception; + FIRCLSHostReadOnlyContext host; + FIRCLSSignalReadContext signal; +#if CLS_MACH_EXCEPTION_SUPPORTED + FIRCLSMachExceptionReadContext machException; +#endif + FIRCLSUserLoggingReadOnlyContext logging; +} FIRCLSReadOnlyContext; + +typedef struct { + FIRCLSInternalLoggingWritableContext internalLogging; + volatile bool crashOccurred; + FIRCLSBinaryImageReadWriteContext binaryImage; + FIRCLSUserLoggingWritableContext logging; + FIRCLSExceptionWritableContext exception; +} FIRCLSReadWriteContext; + +typedef struct { + FIRCLSReadOnlyContext* readonly; + FIRCLSReadWriteContext* writable; + FIRCLSAllocatorRef allocator; +} FIRCLSContext; + +typedef struct { + void* delegate; + const char* customBundleId; + const char* rootPath; + const char* previouslyCrashedFileRootPath; + const char* sessionId; + const char* installId; + const char* betaToken; +#if CLS_MACH_EXCEPTION_SUPPORTED + exception_mask_t machExceptionMask; +#endif + bool errorsEnabled; + bool customExceptionsEnabled; + uint32_t maxCustomExceptions; + uint32_t maxErrorLogSize; + uint32_t maxLogSize; + uint32_t maxKeyValues; +} FIRCLSContextInitData; + +bool FIRCLSContextInitialize(const FIRCLSContextInitData* initData); + +// Re-writes the metadata file on the current thread +void FIRCLSContextUpdateMetadata(const FIRCLSContextInitData* initData); + +void FIRCLSContextBaseInit(void); +void FIRCLSContextBaseDeinit(void); + +bool FIRCLSContextIsInitialized(void); +bool FIRCLSContextHasCrashed(void); +void FIRCLSContextMarkHasCrashed(void); +bool FIRCLSContextMarkAndCheckIfCrashed(void); + +__END_DECLS diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSContext.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSContext.m new file mode 100644 index 0000000..1438bf3 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSContext.m @@ -0,0 +1,400 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FIRCLSContext.h" + +#include +#include + +#include "FIRCLSApplication.h" +#include "FIRCLSCrashedMarkerFile.h" +#include "FIRCLSDefines.h" +#include "FIRCLSFeatures.h" +#include "FIRCLSGlobals.h" +#include "FIRCLSInternalReport.h" +#include "FIRCLSProcess.h" +#include "FIRCLSUtility.h" + +// The writable size is our handler stack plus whatever scratch we need. We have to use this space +// extremely carefully, however, because thread stacks always needs to be page-aligned. Only the +// first allocation is gauranteed to be page-aligned. +// +// CLS_SIGNAL_HANDLER_STACK_SIZE and CLS_MACH_EXCEPTION_HANDLER_STACK_SIZE are platform dependant, +// defined as 0 for tv/watch. +#define CLS_MINIMUM_READWRITE_SIZE \ + (CLS_SIGNAL_HANDLER_STACK_SIZE + CLS_MACH_EXCEPTION_HANDLER_STACK_SIZE + \ + sizeof(FIRCLSReadWriteContext)) + +// We need enough space here for the context, plus storage for strings. +#define CLS_MINIMUM_READABLE_SIZE (sizeof(FIRCLSReadOnlyContext) + 4096 * 4) + +static const int64_t FIRCLSContextInitWaitTime = 5LL * NSEC_PER_SEC; + +static bool FIRCLSContextRecordMetadata(const char* path, const FIRCLSContextInitData* initData); +static const char* FIRCLSContextAppendToRoot(NSString* root, NSString* component); +static void FIRCLSContextAllocate(FIRCLSContext* context); + +bool FIRCLSContextInitialize(const FIRCLSContextInitData* initData) { + if (!initData) { + return false; + } + + FIRCLSContextBaseInit(); + + dispatch_group_t group = dispatch_group_create(); + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + + if (!FIRCLSIsValidPointer(initData->rootPath)) { + return false; + } + + NSString* rootPath = [NSString stringWithUTF8String:initData->rootPath]; + + // setup our SDK log file synchronously, because other calls may depend on it + _clsContext.readonly->logPath = FIRCLSContextAppendToRoot(rootPath, @"sdk.log"); + if (!FIRCLSUnlinkIfExists(_clsContext.readonly->logPath)) { + FIRCLSErrorLog(@"Unable to write initialize SDK write paths %s", strerror(errno)); + } + + // some values that aren't tied to particular subsystem + _clsContext.readonly->debuggerAttached = FIRCLSProcessDebuggerAttached(); + _clsContext.readonly->delegate = initData->delegate; + + dispatch_group_async(group, queue, ^{ + FIRCLSHostInitialize(&_clsContext.readonly->host); + }); + + dispatch_group_async(group, queue, ^{ + _clsContext.readonly->logging.errorStorage.maxSize = 0; + _clsContext.readonly->logging.errorStorage.maxEntries = + initData->errorsEnabled ? initData->maxCustomExceptions : 0; + _clsContext.readonly->logging.errorStorage.restrictBySize = false; + _clsContext.readonly->logging.errorStorage.entryCount = + &_clsContext.writable->logging.errorsCount; + _clsContext.readonly->logging.errorStorage.aPath = + FIRCLSContextAppendToRoot(rootPath, CLSReportErrorAFile); + _clsContext.readonly->logging.errorStorage.bPath = + FIRCLSContextAppendToRoot(rootPath, CLSReportErrorBFile); + + _clsContext.readonly->logging.logStorage.maxSize = initData->maxLogSize; + _clsContext.readonly->logging.logStorage.maxEntries = 0; + _clsContext.readonly->logging.logStorage.restrictBySize = true; + _clsContext.readonly->logging.logStorage.entryCount = NULL; + _clsContext.readonly->logging.logStorage.aPath = + FIRCLSContextAppendToRoot(rootPath, @"log_a.clsrecord"); + _clsContext.readonly->logging.logStorage.bPath = + FIRCLSContextAppendToRoot(rootPath, @"log_b.clsrecord"); + _clsContext.readonly->logging.customExceptionStorage.aPath = + FIRCLSContextAppendToRoot(rootPath, CLSReportCustomExceptionAFile); + _clsContext.readonly->logging.customExceptionStorage.bPath = + FIRCLSContextAppendToRoot(rootPath, CLSReportCustomExceptionBFile); + _clsContext.readonly->logging.customExceptionStorage.maxSize = 0; + _clsContext.readonly->logging.customExceptionStorage.restrictBySize = false; + _clsContext.readonly->logging.customExceptionStorage.maxEntries = initData->maxCustomExceptions; + _clsContext.readonly->logging.customExceptionStorage.entryCount = + &_clsContext.writable->exception.customExceptionCount; + + _clsContext.readonly->logging.userKVStorage.maxCount = initData->maxKeyValues; + _clsContext.readonly->logging.userKVStorage.incrementalPath = + FIRCLSContextAppendToRoot(rootPath, CLSReportUserIncrementalKVFile); + _clsContext.readonly->logging.userKVStorage.compactedPath = + FIRCLSContextAppendToRoot(rootPath, CLSReportUserCompactedKVFile); + + _clsContext.readonly->logging.internalKVStorage.maxCount = 32; // Hardcode = bad + _clsContext.readonly->logging.internalKVStorage.incrementalPath = + FIRCLSContextAppendToRoot(rootPath, CLSReportInternalIncrementalKVFile); + _clsContext.readonly->logging.internalKVStorage.compactedPath = + FIRCLSContextAppendToRoot(rootPath, CLSReportInternalCompactedKVFile); + + FIRCLSUserLoggingInit(&_clsContext.readonly->logging, &_clsContext.writable->logging); + }); + + dispatch_group_async(group, queue, ^{ + _clsContext.readonly->binaryimage.path = + FIRCLSContextAppendToRoot(rootPath, CLSReportBinaryImageFile); + + FIRCLSBinaryImageInit(&_clsContext.readonly->binaryimage, &_clsContext.writable->binaryImage); + }); + + dispatch_group_async(group, queue, ^{ + NSString* rootPath = [NSString stringWithUTF8String:initData->previouslyCrashedFileRootPath]; + NSString* fileName = [NSString stringWithUTF8String:FIRCLSCrashedMarkerFileName]; + _clsContext.readonly->previouslyCrashedFileFullPath = + FIRCLSContextAppendToRoot(rootPath, fileName); + }); + + if (!_clsContext.readonly->debuggerAttached) { + dispatch_group_async(group, queue, ^{ + _clsContext.readonly->signal.path = FIRCLSContextAppendToRoot(rootPath, CLSReportSignalFile); + + FIRCLSSignalInitialize(&_clsContext.readonly->signal); + }); + +#if CLS_MACH_EXCEPTION_SUPPORTED + dispatch_group_async(group, queue, ^{ + _clsContext.readonly->machException.path = + FIRCLSContextAppendToRoot(rootPath, CLSReportMachExceptionFile); + + FIRCLSMachExceptionInit(&_clsContext.readonly->machException, initData->machExceptionMask); + }); +#endif + + dispatch_group_async(group, queue, ^{ + _clsContext.readonly->exception.path = + FIRCLSContextAppendToRoot(rootPath, CLSReportExceptionFile); + _clsContext.readonly->exception.maxCustomExceptions = + initData->customExceptionsEnabled ? initData->maxCustomExceptions : 0; + + FIRCLSExceptionInitialize(&_clsContext.readonly->exception, &_clsContext.writable->exception, + initData->delegate); + }); + } else { + FIRCLSSDKLog("Debugger present - not installing handlers\n"); + } + + dispatch_group_async(group, queue, ^{ + const char* metaDataPath = + [[rootPath stringByAppendingPathComponent:CLSReportMetadataFile] fileSystemRepresentation]; + if (!FIRCLSContextRecordMetadata(metaDataPath, initData)) { + FIRCLSSDKLog("Unable to record context metadata\n"); + } + }); + + // At this point we need to do two things. First, we need to do our memory protection *only* after + // all of these initialization steps are really done. But, we also want to wait as long as + // possible for these to be complete. If we do not, there's a chance that we will not be able to + // correctly report a crash shortly after start. + + // Note at this will retain the group, so its totally fine to release the group here. + dispatch_group_notify(group, queue, ^{ + _clsContext.readonly->initialized = true; + __sync_synchronize(); + + if (!FIRCLSAllocatorProtect(_clsContext.allocator)) { + FIRCLSSDKLog("Error: Memory protection failed\n"); + } + }); + + if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, FIRCLSContextInitWaitTime)) != + 0) { + FIRCLSSDKLog("Error: Delayed initialization\n"); + } + + return true; +} + +void FIRCLSContextUpdateMetadata(const FIRCLSContextInitData* initData) { + NSString* rootPath = [NSString stringWithUTF8String:initData->rootPath]; + + const char* metaDataPath = + [[rootPath stringByAppendingPathComponent:CLSReportMetadataFile] fileSystemRepresentation]; + + if (!FIRCLSContextRecordMetadata(metaDataPath, initData)) { + FIRCLSErrorLog(@"Unable to update context metadata"); + } +} + +void FIRCLSContextBaseInit(void) { + NSString* sdkBundleID = FIRCLSApplicationGetSDKBundleID(); + + NSString* loggingQueueName = [sdkBundleID stringByAppendingString:@".logging"]; + NSString* binaryImagesQueueName = [sdkBundleID stringByAppendingString:@".binary-images"]; + NSString* exceptionQueueName = [sdkBundleID stringByAppendingString:@".exception"]; + + _clsLoggingQueue = dispatch_queue_create([loggingQueueName UTF8String], DISPATCH_QUEUE_SERIAL); + _clsBinaryImageQueue = + dispatch_queue_create([binaryImagesQueueName UTF8String], DISPATCH_QUEUE_SERIAL); + _clsExceptionQueue = + dispatch_queue_create([exceptionQueueName UTF8String], DISPATCH_QUEUE_SERIAL); + + FIRCLSContextAllocate(&_clsContext); + + _clsContext.writable->internalLogging.logFd = -1; + _clsContext.writable->internalLogging.logLevel = FIRCLSInternalLogLevelDebug; + _clsContext.writable->crashOccurred = false; + + _clsContext.readonly->initialized = false; + + __sync_synchronize(); +} + +static void FIRCLSContextAllocate(FIRCLSContext* context) { + // create the allocator, and the contexts + // The ordering here is really important, because the "stack" variable must be + // page-aligned. There's no mechanism to ask the allocator to do alignment, but we + // do know the very first allocation in a region is aligned to a page boundary. + + context->allocator = FIRCLSAllocatorCreate(CLS_MINIMUM_READWRITE_SIZE, CLS_MINIMUM_READABLE_SIZE); + + context->readonly = + FIRCLSAllocatorSafeAllocate(context->allocator, sizeof(FIRCLSReadOnlyContext), CLS_READONLY); + memset(context->readonly, 0, sizeof(FIRCLSReadOnlyContext)); + +#if CLS_MEMORY_PROTECTION_ENABLED +#if CLS_MACH_EXCEPTION_SUPPORTED + context->readonly->machStack = FIRCLSAllocatorSafeAllocate( + context->allocator, CLS_MACH_EXCEPTION_HANDLER_STACK_SIZE, CLS_READWRITE); +#endif +#if CLS_USE_SIGALTSTACK + context->readonly->signalStack = + FIRCLSAllocatorSafeAllocate(context->allocator, CLS_SIGNAL_HANDLER_STACK_SIZE, CLS_READWRITE); +#endif +#else +#if CLS_MACH_EXCEPTION_SUPPORTED + context->readonly->machStack = valloc(CLS_MACH_EXCEPTION_HANDLER_STACK_SIZE); +#endif +#if CLS_USE_SIGALTSTACK + context->readonly->signalStack = valloc(CLS_SIGNAL_HANDLER_STACK_SIZE); +#endif +#endif + +#if CLS_MACH_EXCEPTION_SUPPORTED + memset(_clsContext.readonly->machStack, 0, CLS_MACH_EXCEPTION_HANDLER_STACK_SIZE); +#endif +#if CLS_USE_SIGALTSTACK + memset(_clsContext.readonly->signalStack, 0, CLS_SIGNAL_HANDLER_STACK_SIZE); +#endif + + context->writable = FIRCLSAllocatorSafeAllocate(context->allocator, + sizeof(FIRCLSReadWriteContext), CLS_READWRITE); + memset(context->writable, 0, sizeof(FIRCLSReadWriteContext)); +} + +void FIRCLSContextBaseDeinit(void) { + _clsContext.readonly->initialized = false; + + FIRCLSAllocatorDestroy(_clsContext.allocator); +} + +bool FIRCLSContextIsInitialized(void) { + __sync_synchronize(); + if (!FIRCLSIsValidPointer(_clsContext.readonly)) { + return false; + } + + return _clsContext.readonly->initialized; +} + +bool FIRCLSContextHasCrashed(void) { + if (!FIRCLSContextIsInitialized()) { + return false; + } + + // we've already run a full barrier above, so this read is ok + return _clsContext.writable->crashOccurred; +} + +void FIRCLSContextMarkHasCrashed(void) { + if (!FIRCLSContextIsInitialized()) { + return; + } + + _clsContext.writable->crashOccurred = true; + __sync_synchronize(); +} + +bool FIRCLSContextMarkAndCheckIfCrashed(void) { + if (!FIRCLSContextIsInitialized()) { + return false; + } + + if (_clsContext.writable->crashOccurred) { + return true; + } + + _clsContext.writable->crashOccurred = true; + __sync_synchronize(); + + return false; +} + +static const char* FIRCLSContextAppendToRoot(NSString* root, NSString* component) { + return FIRCLSDupString( + [[root stringByAppendingPathComponent:component] fileSystemRepresentation]); +} + +static bool FIRCLSContextRecordIdentity(FIRCLSFile* file, const FIRCLSContextInitData* initData) { + FIRCLSFileWriteSectionStart(file, "identity"); + + FIRCLSFileWriteHashStart(file); + + FIRCLSFileWriteHashEntryString(file, "generator", CLS_SDK_GENERATOR_NAME); + FIRCLSFileWriteHashEntryString(file, "display_version", CLS_SDK_DISPLAY_VERSION); + FIRCLSFileWriteHashEntryString(file, "build_version", CLS_SDK_DISPLAY_VERSION); + FIRCLSFileWriteHashEntryUint64(file, "started_at", time(NULL)); + + FIRCLSFileWriteHashEntryString(file, "session_id", initData->sessionId); + FIRCLSFileWriteHashEntryString(file, "install_id", initData->installId); + FIRCLSFileWriteHashEntryString(file, "beta_token", initData->betaToken); + FIRCLSFileWriteHashEntryBoolean(file, "absolute_log_timestamps", true); + + FIRCLSFileWriteHashEnd(file); + FIRCLSFileWriteSectionEnd(file); + + return true; +} + +static bool FIRCLSContextRecordApplication(FIRCLSFile* file, const char* customBundleId) { + FIRCLSFileWriteSectionStart(file, "application"); + + FIRCLSFileWriteHashStart(file); + + FIRCLSFileWriteHashEntryString(file, "bundle_id", + [FIRCLSApplicationGetBundleIdentifier() UTF8String]); + FIRCLSFileWriteHashEntryString(file, "custom_bundle_id", customBundleId); + FIRCLSFileWriteHashEntryString(file, "build_version", + [FIRCLSApplicationGetBundleVersion() UTF8String]); + FIRCLSFileWriteHashEntryString(file, "display_version", + [FIRCLSApplicationGetShortBundleVersion() UTF8String]); + FIRCLSFileWriteHashEntryString(file, "extension_id", + [FIRCLSApplicationExtensionPointIdentifier() UTF8String]); + + FIRCLSFileWriteHashEnd(file); + FIRCLSFileWriteSectionEnd(file); + + return true; +} + +static bool FIRCLSContextRecordMetadata(const char* path, const FIRCLSContextInitData* initData) { + if (!FIRCLSUnlinkIfExists(path)) { + FIRCLSSDKLog("Unable to unlink existing metadata file %s\n", strerror(errno)); + } + + FIRCLSFile file; + + if (!FIRCLSFileInitWithPath(&file, path, false)) { + FIRCLSSDKLog("Unable to open metadata file %s\n", strerror(errno)); + return false; + } + + if (!FIRCLSContextRecordIdentity(&file, initData)) { + FIRCLSSDKLog("Unable to write out identity metadata\n"); + } + + if (!FIRCLSHostRecord(&file)) { + FIRCLSSDKLog("Unable to write out host metadata\n"); + } + + if (!FIRCLSContextRecordApplication(&file, initData->customBundleId)) { + FIRCLSSDKLog("Unable to write out application metadata\n"); + } + + if (!FIRCLSBinaryImageRecordMainExecutable(&file)) { + FIRCLSSDKLog("Unable to write out executable metadata\n"); + } + + FIRCLSFileClose(&file); + + return true; +} diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSCrashedMarkerFile.c b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSCrashedMarkerFile.c new file mode 100644 index 0000000..fbb1faa --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSCrashedMarkerFile.c @@ -0,0 +1,31 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FIRCLSCrashedMarkerFile.h" +#include "FIRCLSFile.h" +#include "FIRCLSUtility.h" + +const char *FIRCLSCrashedMarkerFileName = "previously-crashed"; + +void FIRCLSCreateCrashedMarkerFile() { + FIRCLSFile file; + + if (!FIRCLSFileInitWithPath(&file, _clsContext.readonly->previouslyCrashedFileFullPath, false)) { + FIRCLSSDKLog("Unable to create the crashed marker file\n"); + return; + } + + FIRCLSFileClose(&file); + FIRCLSSDKLog("Created the crashed marker file\n"); +} diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSCrashedMarkerFile.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSCrashedMarkerFile.h new file mode 100644 index 0000000..ccf4276 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSCrashedMarkerFile.h @@ -0,0 +1,19 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +extern const char *FIRCLSCrashedMarkerFileName; + +void FIRCLSCreateCrashedMarkerFile(void); diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSGlobals.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSGlobals.h new file mode 100644 index 0000000..191b24d --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSGlobals.h @@ -0,0 +1,28 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FIRCLSContext.h" + +__BEGIN_DECLS + +extern FIRCLSContext _clsContext; +extern dispatch_queue_t _clsLoggingQueue; +extern dispatch_queue_t _clsBinaryImageQueue; +extern dispatch_queue_t _clsExceptionQueue; + +#define FIRCLSGetLoggingQueue() (_clsLoggingQueue) +#define FIRCLSGetBinaryImageQueue() (_clsBinaryImageQueue) +#define FIRCLSGetExceptionQueue() (_clsExceptionQueue) + +__END_DECLS diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSHost.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSHost.h new file mode 100644 index 0000000..31dfc24 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSHost.h @@ -0,0 +1,37 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "FIRCLSFile.h" + +#include +#include + +typedef struct { + const char* documentDirectoryPath; + vm_size_t pageSize; +} FIRCLSHostReadOnlyContext; + +__BEGIN_DECLS + +void FIRCLSHostInitialize(FIRCLSHostReadOnlyContext* roContext); + +vm_size_t FIRCLSHostGetPageSize(void); + +bool FIRCLSHostRecord(FIRCLSFile* file); + +void FIRCLSHostWriteDiskUsage(FIRCLSFile* file); + +__END_DECLS diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSHost.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSHost.m new file mode 100644 index 0000000..a2900bf --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSHost.m @@ -0,0 +1,161 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FIRCLSHost.h" + +#include +#include +#include + +#import "FIRCLSApplication.h" +#include "FIRCLSDefines.h" +#import "FIRCLSFABHost.h" +#include "FIRCLSFile.h" +#include "FIRCLSGlobals.h" +#include "FIRCLSUtility.h" + +#if TARGET_OS_IPHONE +#import +#else +#import +#endif + +#define CLS_HOST_SYSCTL_BUFFER_SIZE (128) + +#if CLS_CPU_ARM64 +#define CLS_MAX_NATIVE_PAGE_SIZE (1024 * 16) +#else +// return 4K, which is correct for all platforms except arm64, currently +#define CLS_MAX_NATIVE_PAGE_SIZE (1024 * 4) +#endif +#define CLS_MIN_NATIVE_PAGE_SIZE (1024 * 4) + +#pragma mark Prototypes +static void FIRCLSHostWriteSysctlEntry( + FIRCLSFile* file, const char* key, const char* sysctlKey, void* buffer, size_t bufferSize); +static void FIRCLSHostWriteModelInfo(FIRCLSFile* file); +static void FIRCLSHostWriteOSVersionInfo(FIRCLSFile* file); + +#pragma mark - API +void FIRCLSHostInitialize(FIRCLSHostReadOnlyContext* roContext) { + _clsContext.readonly->host.pageSize = FIRCLSHostGetPageSize(); + _clsContext.readonly->host.documentDirectoryPath = NULL; + + // determine where the document directory is mounted, so we can get file system statistics later + NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + if ([paths count]) { + _clsContext.readonly->host.documentDirectoryPath = + FIRCLSDupString([[paths objectAtIndex:0] fileSystemRepresentation]); + } +} + +vm_size_t FIRCLSHostGetPageSize(void) { + size_t size; + int pageSize; + + // hw.pagesize is defined as HW_PAGESIZE, which is an int. It's important to match + // these types. Turns out that sysctl will not init the data to zero, but it appears + // that sysctlbyname does. This API is nicer, but that's important to keep in mind. + + pageSize = 0; + size = sizeof(pageSize); + if (sysctlbyname("hw.pagesize", &pageSize, &size, NULL, 0) != 0) { + FIRCLSSDKLog("sysctlbyname failed while trying to get hw.pagesize\n"); + + return CLS_MAX_NATIVE_PAGE_SIZE; + } + + // if the returned size is not the expected value, abort + if (size != sizeof(pageSize)) { + return CLS_MAX_NATIVE_PAGE_SIZE; + } + + // put in some guards to make sure our size is reasonable + if (pageSize > CLS_MAX_NATIVE_PAGE_SIZE) { + return CLS_MAX_NATIVE_PAGE_SIZE; + } + + if (pageSize < CLS_MIN_NATIVE_PAGE_SIZE) { + return CLS_MIN_NATIVE_PAGE_SIZE; + } + + return pageSize; +} + +static void FIRCLSHostWriteSysctlEntry( + FIRCLSFile* file, const char* key, const char* sysctlKey, void* buffer, size_t bufferSize) { + if (sysctlbyname(sysctlKey, buffer, &bufferSize, NULL, 0) != 0) { + FIRCLSFileWriteHashEntryString(file, key, "(failed)"); + return; + } + + FIRCLSFileWriteHashEntryString(file, key, buffer); +} + +static void FIRCLSHostWriteModelInfo(FIRCLSFile* file) { + FIRCLSFileWriteHashEntryString(file, "model", [FIRCLSHostModelInfo() UTF8String]); + + // allocate a static buffer for the sysctl values, which are typically + // quite short + char buffer[CLS_HOST_SYSCTL_BUFFER_SIZE]; + +#if TARGET_OS_EMBEDDED + FIRCLSHostWriteSysctlEntry(file, "machine", "hw.model", buffer, CLS_HOST_SYSCTL_BUFFER_SIZE); +#else + FIRCLSHostWriteSysctlEntry(file, "machine", "hw.machine", buffer, CLS_HOST_SYSCTL_BUFFER_SIZE); + FIRCLSHostWriteSysctlEntry(file, "cpu", "machdep.cpu.brand_string", buffer, + CLS_HOST_SYSCTL_BUFFER_SIZE); +#endif +} + +static void FIRCLSHostWriteOSVersionInfo(FIRCLSFile* file) { + FIRCLSFileWriteHashEntryString(file, "os_build_version", [FIRCLSHostOSBuildVersion() UTF8String]); + FIRCLSFileWriteHashEntryString(file, "os_display_version", + [FIRCLSHostOSDisplayVersion() UTF8String]); + FIRCLSFileWriteHashEntryString(file, "platform", [FIRCLSApplicationGetPlatform() UTF8String]); +} + +bool FIRCLSHostRecord(FIRCLSFile* file) { + FIRCLSFileWriteSectionStart(file, "host"); + + FIRCLSFileWriteHashStart(file); + + FIRCLSHostWriteModelInfo(file); + FIRCLSHostWriteOSVersionInfo(file); + FIRCLSFileWriteHashEntryString(file, "locale", + [[[NSLocale currentLocale] localeIdentifier] UTF8String]); + + FIRCLSFileWriteHashEnd(file); + + FIRCLSFileWriteSectionEnd(file); + + return true; +} + +void FIRCLSHostWriteDiskUsage(FIRCLSFile* file) { + struct statfs tStats; + + FIRCLSFileWriteSectionStart(file, "storage"); + + FIRCLSFileWriteHashStart(file); + + if (statfs(_clsContext.readonly->host.documentDirectoryPath, &tStats) == 0) { + FIRCLSFileWriteHashEntryUint64(file, "free", tStats.f_bavail * tStats.f_bsize); + FIRCLSFileWriteHashEntryUint64(file, "total", tStats.f_blocks * tStats.f_bsize); + } + + FIRCLSFileWriteHashEnd(file); + + FIRCLSFileWriteSectionEnd(file); +} diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSProcess.c b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSProcess.c new file mode 100644 index 0000000..2a71756 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSProcess.c @@ -0,0 +1,824 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FIRCLSProcess.h" +#include "FIRCLSDefines.h" +#include "FIRCLSFeatures.h" +#include "FIRCLSGlobals.h" +#include "FIRCLSProfiling.h" +#include "FIRCLSThreadState.h" +#include "FIRCLSUnwind.h" +#include "FIRCLSUtility.h" + +#include +#include +#include +#include + +#define THREAD_NAME_BUFFER_SIZE (64) + +#pragma mark Prototypes +static bool FIRCLSProcessGetThreadName(FIRCLSProcess *process, + thread_t thread, + char *buffer, + size_t length); +static const char *FIRCLSProcessGetThreadDispatchQueueName(FIRCLSProcess *process, thread_t thread); + +#pragma mark - API +bool FIRCLSProcessInit(FIRCLSProcess *process, thread_t crashedThread, void *uapVoid) { + if (!process) { + return false; + } + + process->task = mach_task_self(); + process->thisThread = mach_thread_self(); + process->crashedThread = crashedThread; + process->uapVoid = uapVoid; + + if (task_threads(process->task, &process->threads, &process->threadCount) != KERN_SUCCESS) { + // failed to get all threads + process->threadCount = 0; + FIRCLSSDKLog("Error: unable to get task threads\n"); + + return false; + } + + return true; +} + +bool FIRCLSProcessDestroy(FIRCLSProcess *process) { + return false; +} + +// https://developer.apple.com/library/mac/#qa/qa2004/qa1361.html +bool FIRCLSProcessDebuggerAttached(void) { + int junk; + int mib[4]; + struct kinfo_proc info; + size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + size = sizeof(info); + junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); + if (junk != 0) { + FIRCLSSDKLog("sysctl failed while trying to get kinfo_proc\n"); + return false; + } + + // We're being debugged if the P_TRACED flag is set. + return (info.kp_proc.p_flag & P_TRACED) != 0; +} + +#pragma mark - Thread Support +static bool FIRCLSProcessIsCurrentThread(FIRCLSProcess *process, thread_t thread) { + return MACH_PORT_INDEX(process->thisThread) == MACH_PORT_INDEX(thread); +} + +static bool FIRCLSProcessIsCrashedThread(FIRCLSProcess *process, thread_t thread) { + return MACH_PORT_INDEX(process->crashedThread) == MACH_PORT_INDEX(thread); +} + +static uint32_t FIRCLSProcessGetThreadCount(FIRCLSProcess *process) { + return process->threadCount; +} + +static thread_t FIRCLSProcessGetThread(FIRCLSProcess *process, uint32_t index) { + if (index >= process->threadCount) { + return MACH_PORT_NULL; + } + + return process->threads[index]; +} + +bool FIRCLSProcessSuspendAllOtherThreads(FIRCLSProcess *process) { + mach_msg_type_number_t i; + bool success; + + success = true; + for (i = 0; i < process->threadCount; ++i) { + thread_t thread; + + thread = FIRCLSProcessGetThread(process, i); + + if (FIRCLSProcessIsCurrentThread(process, thread)) { + continue; + } + + // FIXME: workaround to get this building on watch, but we need to suspend/resume threads! +#if CLS_CAN_SUSPEND_THREADS + success = success && (thread_suspend(thread) == KERN_SUCCESS); +#endif + } + + return success; +} + +bool FIRCLSProcessResumeAllOtherThreads(FIRCLSProcess *process) { + mach_msg_type_number_t i; + bool success; + + success = true; + for (i = 0; i < process->threadCount; ++i) { + thread_t thread; + + thread = FIRCLSProcessGetThread(process, i); + + if (FIRCLSProcessIsCurrentThread(process, thread)) { + continue; + } + + // FIXME: workaround to get this building on watch, but we need to suspend/resume threads! +#if CLS_CAN_SUSPEND_THREADS + success = success && (thread_resume(thread) == KERN_SUCCESS); +#endif + } + + return success; +} + +#pragma mark - Thread Properties +void *FIRCLSThreadGetCurrentPC(void) { + return __builtin_return_address(0); +} + +static bool FIRCLSProcessGetThreadState(FIRCLSProcess *process, + thread_t thread, + FIRCLSThreadContext *context) { + if (!FIRCLSIsValidPointer(context)) { + FIRCLSSDKLogError("invalid context supplied"); + return false; + } + + // If the thread context we should use is non-NULL, then just assign it here. Otherwise, + // query the thread state + if (FIRCLSProcessIsCrashedThread(process, thread) && FIRCLSIsValidPointer(process->uapVoid)) { + *context = *((_STRUCT_UCONTEXT *)process->uapVoid)->uc_mcontext; + return true; + } + + // Here's a wild trick: emulate what thread_get_state would do. It apppears that + // we cannot reliably unwind out of thread_get_state. So, instead of trying, setup + // a thread context that resembles what the real thing would look like + if (FIRCLSProcessIsCurrentThread(process, thread)) { + FIRCLSSDKLog("Faking current thread\n"); + memset(context, 0, sizeof(FIRCLSThreadContext)); + + // Compute the frame address, and then base the stack value off of that. A frame pushes + // two pointers onto the stack, so we have to offset. + const uintptr_t frameAddress = (uintptr_t)__builtin_frame_address(0); + const uintptr_t stackAddress = FIRCLSUnwindStackPointerFromFramePointer(frameAddress); + +#if CLS_CPU_X86_64 + context->__ss.__rip = (uintptr_t)FIRCLSThreadGetCurrentPC(); + context->__ss.__rbp = frameAddress; + context->__ss.__rsp = stackAddress; +#elif CLS_CPU_I386 + context->__ss.__eip = (uintptr_t)FIRCLSThreadGetCurrentPC(); + context->__ss.__ebp = frameAddress; + context->__ss.__esp = stackAddress; +#elif CLS_CPU_ARM64 + FIRCLSThreadContextSetPC(context, (uintptr_t)FIRCLSThreadGetCurrentPC()); + FIRCLSThreadContextSetFramePointer(context, frameAddress); + FIRCLSThreadContextSetLinkRegister(context, (uintptr_t)__builtin_return_address(0)); + FIRCLSThreadContextSetStackPointer(context, stackAddress); +#elif CLS_CPU_ARM + context->__ss.__pc = (uintptr_t)FIRCLSThreadGetCurrentPC(); + context->__ss.__r[7] = frameAddress; + context->__ss.__lr = (uintptr_t)__builtin_return_address(0); + context->__ss.__sp = stackAddress; +#endif + + return true; + } + +#if !TARGET_OS_WATCH + // try to get the value by querying the thread state + mach_msg_type_number_t stateCount = FIRCLSThreadStateCount; + if (thread_get_state(thread, FIRCLSThreadState, (thread_state_t)(&(context->__ss)), + &stateCount) != KERN_SUCCESS) { + FIRCLSSDKLogError("failed to get thread state\n"); + return false; + } + + return true; +#else + return false; +#endif +} + +static bool FIRCLSProcessGetThreadName(FIRCLSProcess *process, + thread_t thread, + char *buffer, + size_t length) { + pthread_t pthread; + + if (!buffer || length <= 0) { + return false; + } + + pthread = pthread_from_mach_thread_np(thread); + + return pthread_getname_np(pthread, buffer, length) == 0; +} + +static const char *FIRCLSProcessGetThreadDispatchQueueName(FIRCLSProcess *process, + thread_t thread) { + thread_identifier_info_data_t info; + mach_msg_type_number_t infoCount; + dispatch_queue_t *queueAddress; + dispatch_queue_t queue; + const char *string; + + infoCount = THREAD_IDENTIFIER_INFO_COUNT; + if (thread_info(thread, THREAD_IDENTIFIER_INFO, (thread_info_t)&info, &infoCount) != + KERN_SUCCESS) { + FIRCLSSDKLog("unable to get thread info\n"); + return NULL; + } + + queueAddress = (dispatch_queue_t *)info.dispatch_qaddr; + if (queueAddress == NULL) { + return ""; + } + + // Sometimes a queue address is invalid. I cannot explain why this is, but + // it can cause a crash. + if (!FIRCLSReadMemory((vm_address_t)queueAddress, &queue, sizeof(void *))) { + return ""; + } + + // here, we know it is safe to de-reference this address, so attempt to get the queue name + if (!queue) { + return ""; + } + + string = dispatch_queue_get_label(queue); + + // but, we still don't if the entire string is valid, so check that too + if (!FIRCLSReadString((vm_address_t)string, (char **)&string, 128)) { + return ""; + } + + return string; +} + +#pragma mark - Data Recording +static bool FIRCLSProcessRecordThreadRegisters(FIRCLSThreadContext context, FIRCLSFile *file) { +#if CLS_CPU_ARM + FIRCLSFileWriteHashEntryUint64(file, "r0", context.__ss.__r[0]); + FIRCLSFileWriteHashEntryUint64(file, "r1", context.__ss.__r[1]); + FIRCLSFileWriteHashEntryUint64(file, "r2", context.__ss.__r[2]); + FIRCLSFileWriteHashEntryUint64(file, "r3", context.__ss.__r[3]); + FIRCLSFileWriteHashEntryUint64(file, "r4", context.__ss.__r[4]); + FIRCLSFileWriteHashEntryUint64(file, "r5", context.__ss.__r[5]); + FIRCLSFileWriteHashEntryUint64(file, "r6", context.__ss.__r[6]); + FIRCLSFileWriteHashEntryUint64(file, "r7", context.__ss.__r[7]); + FIRCLSFileWriteHashEntryUint64(file, "r8", context.__ss.__r[8]); + FIRCLSFileWriteHashEntryUint64(file, "r9", context.__ss.__r[9]); + FIRCLSFileWriteHashEntryUint64(file, "r10", context.__ss.__r[10]); + FIRCLSFileWriteHashEntryUint64(file, "r11", context.__ss.__r[11]); + FIRCLSFileWriteHashEntryUint64(file, "ip", context.__ss.__r[12]); + FIRCLSFileWriteHashEntryUint64(file, "sp", context.__ss.__sp); + FIRCLSFileWriteHashEntryUint64(file, "lr", context.__ss.__lr); + FIRCLSFileWriteHashEntryUint64(file, "pc", context.__ss.__pc); + FIRCLSFileWriteHashEntryUint64(file, "cpsr", context.__ss.__cpsr); +#elif CLS_CPU_ARM64 + FIRCLSFileWriteHashEntryUint64(file, "x0", context.__ss.__x[0]); + FIRCLSFileWriteHashEntryUint64(file, "x1", context.__ss.__x[1]); + FIRCLSFileWriteHashEntryUint64(file, "x2", context.__ss.__x[2]); + FIRCLSFileWriteHashEntryUint64(file, "x3", context.__ss.__x[3]); + FIRCLSFileWriteHashEntryUint64(file, "x4", context.__ss.__x[4]); + FIRCLSFileWriteHashEntryUint64(file, "x5", context.__ss.__x[5]); + FIRCLSFileWriteHashEntryUint64(file, "x6", context.__ss.__x[6]); + FIRCLSFileWriteHashEntryUint64(file, "x7", context.__ss.__x[7]); + FIRCLSFileWriteHashEntryUint64(file, "x8", context.__ss.__x[8]); + FIRCLSFileWriteHashEntryUint64(file, "x9", context.__ss.__x[9]); + FIRCLSFileWriteHashEntryUint64(file, "x10", context.__ss.__x[10]); + FIRCLSFileWriteHashEntryUint64(file, "x11", context.__ss.__x[11]); + FIRCLSFileWriteHashEntryUint64(file, "x12", context.__ss.__x[12]); + FIRCLSFileWriteHashEntryUint64(file, "x13", context.__ss.__x[13]); + FIRCLSFileWriteHashEntryUint64(file, "x14", context.__ss.__x[14]); + FIRCLSFileWriteHashEntryUint64(file, "x15", context.__ss.__x[15]); + FIRCLSFileWriteHashEntryUint64(file, "x16", context.__ss.__x[16]); + FIRCLSFileWriteHashEntryUint64(file, "x17", context.__ss.__x[17]); + FIRCLSFileWriteHashEntryUint64(file, "x18", context.__ss.__x[18]); + FIRCLSFileWriteHashEntryUint64(file, "x19", context.__ss.__x[19]); + FIRCLSFileWriteHashEntryUint64(file, "x20", context.__ss.__x[20]); + FIRCLSFileWriteHashEntryUint64(file, "x21", context.__ss.__x[21]); + FIRCLSFileWriteHashEntryUint64(file, "x22", context.__ss.__x[22]); + FIRCLSFileWriteHashEntryUint64(file, "x23", context.__ss.__x[23]); + FIRCLSFileWriteHashEntryUint64(file, "x24", context.__ss.__x[24]); + FIRCLSFileWriteHashEntryUint64(file, "x25", context.__ss.__x[25]); + FIRCLSFileWriteHashEntryUint64(file, "x26", context.__ss.__x[26]); + FIRCLSFileWriteHashEntryUint64(file, "x27", context.__ss.__x[27]); + FIRCLSFileWriteHashEntryUint64(file, "x28", context.__ss.__x[28]); + FIRCLSFileWriteHashEntryUint64(file, "fp", FIRCLSThreadContextGetFramePointer(&context)); + FIRCLSFileWriteHashEntryUint64(file, "sp", FIRCLSThreadContextGetStackPointer(&context)); + FIRCLSFileWriteHashEntryUint64(file, "lr", FIRCLSThreadContextGetLinkRegister(&context)); + FIRCLSFileWriteHashEntryUint64(file, "pc", FIRCLSThreadContextGetPC(&context)); + FIRCLSFileWriteHashEntryUint64(file, "cpsr", context.__ss.__cpsr); +#elif CLS_CPU_I386 + FIRCLSFileWriteHashEntryUint64(file, "eax", context.__ss.__eax); + FIRCLSFileWriteHashEntryUint64(file, "ebx", context.__ss.__ebx); + FIRCLSFileWriteHashEntryUint64(file, "ecx", context.__ss.__ecx); + FIRCLSFileWriteHashEntryUint64(file, "edx", context.__ss.__edx); + FIRCLSFileWriteHashEntryUint64(file, "edi", context.__ss.__edi); + FIRCLSFileWriteHashEntryUint64(file, "esi", context.__ss.__esi); + FIRCLSFileWriteHashEntryUint64(file, "ebp", context.__ss.__ebp); + FIRCLSFileWriteHashEntryUint64(file, "esp", context.__ss.__esp); + FIRCLSFileWriteHashEntryUint64(file, "ss", context.__ss.__ss); + FIRCLSFileWriteHashEntryUint64(file, "eflags", context.__ss.__eflags); + FIRCLSFileWriteHashEntryUint64(file, "eip", context.__ss.__eip); + FIRCLSFileWriteHashEntryUint64(file, "cs", context.__ss.__cs); + FIRCLSFileWriteHashEntryUint64(file, "ds", context.__ss.__ds); + FIRCLSFileWriteHashEntryUint64(file, "es", context.__ss.__es); + FIRCLSFileWriteHashEntryUint64(file, "fs", context.__ss.__fs); + FIRCLSFileWriteHashEntryUint64(file, "gs", context.__ss.__gs); + + // how do we get the cr2 register? +#elif CLS_CPU_X86_64 + FIRCLSFileWriteHashEntryUint64(file, "rax", context.__ss.__rax); + FIRCLSFileWriteHashEntryUint64(file, "rbx", context.__ss.__rbx); + FIRCLSFileWriteHashEntryUint64(file, "rcx", context.__ss.__rcx); + FIRCLSFileWriteHashEntryUint64(file, "rdx", context.__ss.__rdx); + FIRCLSFileWriteHashEntryUint64(file, "rdi", context.__ss.__rdi); + FIRCLSFileWriteHashEntryUint64(file, "rsi", context.__ss.__rsi); + FIRCLSFileWriteHashEntryUint64(file, "rbp", context.__ss.__rbp); + FIRCLSFileWriteHashEntryUint64(file, "rsp", context.__ss.__rsp); + FIRCLSFileWriteHashEntryUint64(file, "r8", context.__ss.__r8); + FIRCLSFileWriteHashEntryUint64(file, "r9", context.__ss.__r9); + FIRCLSFileWriteHashEntryUint64(file, "r10", context.__ss.__r10); + FIRCLSFileWriteHashEntryUint64(file, "r11", context.__ss.__r11); + FIRCLSFileWriteHashEntryUint64(file, "r12", context.__ss.__r12); + FIRCLSFileWriteHashEntryUint64(file, "r13", context.__ss.__r13); + FIRCLSFileWriteHashEntryUint64(file, "r14", context.__ss.__r14); + FIRCLSFileWriteHashEntryUint64(file, "r15", context.__ss.__r15); + FIRCLSFileWriteHashEntryUint64(file, "rip", context.__ss.__rip); + FIRCLSFileWriteHashEntryUint64(file, "rflags", context.__ss.__rflags); + FIRCLSFileWriteHashEntryUint64(file, "cs", context.__ss.__cs); + FIRCLSFileWriteHashEntryUint64(file, "fs", context.__ss.__fs); + FIRCLSFileWriteHashEntryUint64(file, "gs", context.__ss.__gs); +#endif + + return true; +} + +static bool FIRCLSProcessRecordThread(FIRCLSProcess *process, thread_t thread, FIRCLSFile *file) { + FIRCLSUnwindContext unwindContext; + FIRCLSThreadContext context; + + if (!FIRCLSProcessGetThreadState(process, thread, &context)) { + FIRCLSSDKLogError("unable to get thread state"); + return false; + } + + if (!FIRCLSUnwindInit(&unwindContext, context)) { + FIRCLSSDKLog("unable to init unwind context\n"); + + return false; + } + + FIRCLSFileWriteHashStart(file); + + // registers + FIRCLSFileWriteHashKey(file, "registers"); + FIRCLSFileWriteHashStart(file); + + FIRCLSProcessRecordThreadRegisters(context, file); + + FIRCLSFileWriteHashEnd(file); + + // stacktrace + FIRCLSFileWriteHashKey(file, "stacktrace"); + + // stacktrace is an array of integers + FIRCLSFileWriteArrayStart(file); + + uint32_t repeatedPCCount = 0; + uint64_t repeatedPC = 0; + const FIRCLSInternalLogLevel level = _clsContext.writable->internalLogging.logLevel; + + while (FIRCLSUnwindNextFrame(&unwindContext)) { + const uintptr_t pc = FIRCLSUnwindGetPC(&unwindContext); + const uint32_t frameCount = FIRCLSUnwindGetFrameRepeatCount(&unwindContext); + + if (repeatedPC == pc && repeatedPC != 0) { + // actively counting a recursion + repeatedPCCount = frameCount; + continue; + } + + if (frameCount >= FIRCLSUnwindInfiniteRecursionCountThreshold && repeatedPC == 0) { + repeatedPC = pc; + FIRCLSSDKLogWarn("Possible infinite recursion - suppressing logging\n"); + _clsContext.writable->internalLogging.logLevel = FIRCLSInternalLogLevelWarn; + continue; + } + + if (repeatedPC != 0) { + // at this point, we've recorded a repeated PC, but it is now no longer + // repeating, so we can restore the logging + _clsContext.writable->internalLogging.logLevel = level; + } + + FIRCLSFileWriteArrayEntryUint64(file, pc); + } + + FIRCLSFileWriteArrayEnd(file); + + // crashed? + if (FIRCLSProcessIsCrashedThread(process, thread)) { + FIRCLSFileWriteHashEntryBoolean(file, "crashed", true); + } + + if (repeatedPC != 0) { + FIRCLSFileWriteHashEntryUint64(file, "repeated_pc", repeatedPC); + FIRCLSFileWriteHashEntryUint64(file, "repeat_count", repeatedPCCount); + } + + // Just for extra safety, restore the logging level again. The logic + // above is fairly tricky, this is cheap, and no logging is a real pain. + _clsContext.writable->internalLogging.logLevel = level; + + // end thread info + FIRCLSFileWriteHashEnd(file); + + return true; +} + +bool FIRCLSProcessRecordAllThreads(FIRCLSProcess *process, FIRCLSFile *file) { + uint32_t threadCount; + uint32_t i; + + threadCount = FIRCLSProcessGetThreadCount(process); + + FIRCLSFileWriteSectionStart(file, "threads"); + + FIRCLSFileWriteArrayStart(file); + + for (i = 0; i < threadCount; ++i) { + thread_t thread; + + thread = FIRCLSProcessGetThread(process, i); + + FIRCLSSDKLogInfo("recording thread %d data\n", i); + if (!FIRCLSProcessRecordThread(process, thread, file)) { + return false; + } + } + + FIRCLSFileWriteArrayEnd(file); + + FIRCLSFileWriteSectionEnd(file); + + FIRCLSSDKLogInfo("completed recording all thread data\n"); + + return true; +} + +void FIRCLSProcessRecordThreadNames(FIRCLSProcess *process, FIRCLSFile *file) { + uint32_t threadCount; + uint32_t i; + + FIRCLSFileWriteSectionStart(file, "thread_names"); + + FIRCLSFileWriteArrayStart(file); + + threadCount = FIRCLSProcessGetThreadCount(process); + for (i = 0; i < threadCount; ++i) { + thread_t thread; + char name[THREAD_NAME_BUFFER_SIZE]; + + thread = FIRCLSProcessGetThread(process, i); + + name[0] = 0; // null-terminate, just in case nothing is written + + FIRCLSProcessGetThreadName(process, thread, name, THREAD_NAME_BUFFER_SIZE); + + FIRCLSFileWriteArrayEntryString(file, name); + } + + FIRCLSFileWriteArrayEnd(file); + FIRCLSFileWriteSectionEnd(file); +} + +void FIRCLSProcessRecordDispatchQueueNames(FIRCLSProcess *process, FIRCLSFile *file) { + uint32_t threadCount; + uint32_t i; + + FIRCLSFileWriteSectionStart(file, "dispatch_queue_names"); + + FIRCLSFileWriteArrayStart(file); + + threadCount = FIRCLSProcessGetThreadCount(process); + for (i = 0; i < threadCount; ++i) { + thread_t thread; + const char *name; + + thread = FIRCLSProcessGetThread(process, i); + + name = FIRCLSProcessGetThreadDispatchQueueName(process, thread); + + FIRCLSFileWriteArrayEntryString(file, name); + } + + FIRCLSFileWriteArrayEnd(file); + FIRCLSFileWriteSectionEnd(file); +} + +#pragma mark - Othe Process Info +bool FIRCLSProcessGetMemoryUsage(uint64_t *active, + uint64_t *inactive, + uint64_t *wired, + uint64_t *freeMem) { + mach_port_t hostPort; + mach_msg_type_number_t hostSize; + vm_size_t pageSize; + vm_statistics_data_t vmStat; + + hostPort = mach_host_self(); + + hostSize = sizeof(vm_statistics_data_t) / sizeof(integer_t); + + pageSize = _clsContext.readonly->host.pageSize; + + if (host_statistics(hostPort, HOST_VM_INFO, (host_info_t)&vmStat, &hostSize) != KERN_SUCCESS) { + FIRCLSSDKLog("Failed to get vm statistics\n"); + return false; + } + + if (!(active && inactive && wired && freeMem)) { + FIRCLSSDKLog("Invalid pointers\n"); + return false; + } + + // compute the sizes in bytes and return the values + *active = vmStat.active_count * pageSize; + *inactive = vmStat.inactive_count * pageSize; + *wired = vmStat.wire_count * pageSize; + *freeMem = vmStat.free_count * pageSize; + + return true; +} + +bool FIRCLSProcessGetInfo(FIRCLSProcess *process, + uint64_t *virtualSize, + uint64_t *residentSize, + time_value_t *userTime, + time_value_t *systemTime) { + struct task_basic_info_64 taskInfo; + mach_msg_type_number_t count; + + count = TASK_BASIC_INFO_64_COUNT; + if (task_info(process->task, TASK_BASIC_INFO_64, (task_info_t)&taskInfo, &count) != + KERN_SUCCESS) { + FIRCLSSDKLog("Failed to get task info\n"); + return false; + } + + if (!(virtualSize && residentSize && userTime && systemTime)) { + FIRCLSSDKLog("Invalid pointers\n"); + return false; + } + + *virtualSize = taskInfo.virtual_size; + *residentSize = taskInfo.resident_size; + *userTime = taskInfo.user_time; + *systemTime = taskInfo.system_time; + + return true; +} + +void FIRCLSProcessRecordStats(FIRCLSProcess *process, FIRCLSFile *file) { + uint64_t active; + uint64_t inactive; + uint64_t virtualSize; + uint64_t residentSize; + uint64_t wired; + uint64_t freeMem; + time_value_t userTime; + time_value_t systemTime; + + if (!FIRCLSProcessGetMemoryUsage(&active, &inactive, &wired, &freeMem)) { + FIRCLSSDKLog("Unable to get process memory usage\n"); + return; + } + + if (!FIRCLSProcessGetInfo(process, &virtualSize, &residentSize, &userTime, &systemTime)) { + FIRCLSSDKLog("Unable to get process stats\n"); + return; + } + + FIRCLSFileWriteSectionStart(file, "process_stats"); + + FIRCLSFileWriteHashStart(file); + + FIRCLSFileWriteHashEntryUint64(file, "active", active); + FIRCLSFileWriteHashEntryUint64(file, "inactive", inactive); + FIRCLSFileWriteHashEntryUint64(file, "wired", wired); + FIRCLSFileWriteHashEntryUint64(file, "freeMem", freeMem); // Intentionally left in, for now. Arg. + FIRCLSFileWriteHashEntryUint64(file, "free_mem", freeMem); + FIRCLSFileWriteHashEntryUint64(file, "virtual", virtualSize); + FIRCLSFileWriteHashEntryUint64(file, "resident", active); + FIRCLSFileWriteHashEntryUint64(file, "user_time", + (userTime.seconds * 1000 * 1000) + userTime.microseconds); + FIRCLSFileWriteHashEntryUint64(file, "sys_time", + (systemTime.seconds * 1000 * 1000) + systemTime.microseconds); + + FIRCLSFileWriteHashEnd(file); + + FIRCLSFileWriteSectionEnd(file); +} + +#pragma mark - Runtime Info +#define OBJC_MSG_SEND_START ((vm_address_t)objc_msgSend) +#define OBJC_MSG_SEND_SUPER_START ((vm_address_t)objc_msgSendSuper) +#define OBJC_MSG_SEND_END (OBJC_MSG_SEND_START + 66) +#define OBJC_MSG_SEND_SUPER_END (OBJC_MSG_SEND_SUPER_START + 66) + +#if !CLS_CPU_ARM64 +#define OBJC_MSG_SEND_STRET_START ((vm_address_t)objc_msgSend_stret) +#define OBJC_MSG_SEND_SUPER_STRET_START ((vm_address_t)objc_msgSendSuper_stret) +#define OBJC_MSG_SEND_STRET_END (OBJC_MSG_SEND_STRET_START + 66) +#define OBJC_MSG_SEND_SUPER_STRET_END (OBJC_MSG_SEND_SUPER_STRET_START + 66) +#endif + +#if CLS_CPU_X86 +#define OBJC_MSG_SEND_FPRET_START ((vm_address_t)objc_msgSend_fpret) +#define OBJC_MSG_SEND_FPRET_END (OBJC_MSG_SEND_FPRET_START + 66) +#endif + +static const char *FIRCLSProcessGetObjCSelectorName(FIRCLSThreadContext registers) { + void *selectorAddress; + void *selRegister; +#if !CLS_CPU_ARM64 + void *stretSelRegister; +#endif + vm_address_t pc; + + // First, did we crash in objc_msgSend? The two ways I can think + // of doing this are to use dladdr, and then comparing the strings to + // objc_msg*, or looking up the symbols, and guessing if we are "close enough". + + selectorAddress = NULL; + +#if CLS_CPU_ARM + pc = registers.__ss.__pc; + selRegister = (void *)registers.__ss.__r[1]; + stretSelRegister = (void *)registers.__ss.__r[2]; +#elif CLS_CPU_ARM64 + pc = FIRCLSThreadContextGetPC(®isters); + selRegister = (void *)registers.__ss.__x[1]; +#elif CLS_CPU_I386 + pc = registers.__ss.__eip; + selRegister = (void *)registers.__ss.__ecx; + stretSelRegister = (void *)registers.__ss.__ecx; +#elif CLS_CPU_X86_64 + pc = registers.__ss.__rip; + selRegister = (void *)registers.__ss.__rsi; + stretSelRegister = (void *)registers.__ss.__rdx; +#endif + + if ((pc >= OBJC_MSG_SEND_START) && (pc <= OBJC_MSG_SEND_END)) { + selectorAddress = selRegister; + } + +#if !CLS_CPU_ARM64 + if ((pc >= OBJC_MSG_SEND_SUPER_START) && (pc <= OBJC_MSG_SEND_SUPER_END)) { + selectorAddress = selRegister; + } + + if ((pc >= OBJC_MSG_SEND_STRET_START) && (pc <= OBJC_MSG_SEND_STRET_END)) { + selectorAddress = stretSelRegister; + } + + if ((pc >= OBJC_MSG_SEND_SUPER_STRET_START) && (pc <= OBJC_MSG_SEND_SUPER_STRET_END)) { + selectorAddress = stretSelRegister; + } + +#if CLS_CPU_X86 + if ((pc >= OBJC_MSG_SEND_FPRET_START) && (pc <= OBJC_MSG_SEND_FPRET_END)) { + selectorAddress = selRegister; + } +#endif +#endif + + if (!selectorAddress) { + return ""; + } + + if (!FIRCLSReadString((vm_address_t)selectorAddress, (char **)&selectorAddress, 128)) { + FIRCLSSDKLog("Unable to read the selector string\n"); + return ""; + } + + return selectorAddress; +} + +#define CRASH_ALIGN __attribute__((aligned(8))) +typedef struct { + unsigned version CRASH_ALIGN; + const char *message CRASH_ALIGN; + const char *signature CRASH_ALIGN; + const char *backtrace CRASH_ALIGN; + const char *message2 CRASH_ALIGN; + void *reserved CRASH_ALIGN; + void *reserved2 CRASH_ALIGN; +} crash_info_t; + +static void FIRCLSProcessRecordCrashInfo(FIRCLSFile *file) { + // TODO: this should be abstracted into binary images, if possible + FIRCLSBinaryImageRuntimeNode *nodes = _clsContext.writable->binaryImage.nodes; + if (!nodes) { + FIRCLSSDKLogError("The node structure is NULL\n"); + return; + } + + for (uint32_t i = 0; i < CLS_BINARY_IMAGE_RUNTIME_NODE_COUNT; ++i) { + FIRCLSBinaryImageRuntimeNode *node = &nodes[i]; + + if (!node->crashInfo) { + continue; + } + + crash_info_t info; + + if (!FIRCLSReadMemory((vm_address_t)node->crashInfo, &info, sizeof(crash_info_t))) { + continue; + } + + FIRCLSSDKLogDebug("Found crash info with version %d\n", info.version); + + // Currently support versions 0 through 5. + // 4 was in use for a long time, but it appears that with iOS 9 / swift 2.0, the verison has + // been bumped. + if (info.version > 5) { + continue; + } + + if (!info.message) { + continue; + } + +#if CLS_BINARY_IMAGE_RUNTIME_NODE_RECORD_NAME + FIRCLSSDKLogInfo("Found crash info for %s\n", node->name); +#endif + + FIRCLSSDKLogDebug("attempting to read crash info string\n"); + + char *string = NULL; + + if (!FIRCLSReadString((vm_address_t)info.message, &string, 256)) { + FIRCLSSDKLogError("Failed to copy crash info string\n"); + continue; + } + + FIRCLSFileWriteArrayEntryHexEncodedString(file, string); + } +} + +void FIRCLSProcessRecordRuntimeInfo(FIRCLSProcess *process, FIRCLSFile *file) { + FIRCLSThreadContext mcontext; + + if (!FIRCLSProcessGetThreadState(process, process->crashedThread, &mcontext)) { + FIRCLSSDKLogError("unable to get crashed thread state"); + } + + FIRCLSFileWriteSectionStart(file, "runtime"); + + FIRCLSFileWriteHashStart(file); + + FIRCLSFileWriteHashEntryString(file, "objc_selector", FIRCLSProcessGetObjCSelectorName(mcontext)); + + FIRCLSFileWriteHashKey(file, "crash_info_entries"); + + FIRCLSFileWriteArrayStart(file); + FIRCLSProcessRecordCrashInfo(file); + FIRCLSFileWriteArrayEnd(file); + + FIRCLSFileWriteHashEnd(file); + + FIRCLSFileWriteSectionEnd(file); +} diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSProcess.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSProcess.h new file mode 100644 index 0000000..c12fd73 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSProcess.h @@ -0,0 +1,45 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "FIRCLSFile.h" + +#include +#include + +typedef struct { + // task info + mach_port_t task; + + // thread stuff + thread_t thisThread; + thread_t crashedThread; + thread_act_array_t threads; + mach_msg_type_number_t threadCount; + void *uapVoid; // current thread state +} FIRCLSProcess; + +bool FIRCLSProcessInit(FIRCLSProcess *process, thread_t crashedThread, void *uapVoid); +bool FIRCLSProcessDestroy(FIRCLSProcess *process); +bool FIRCLSProcessDebuggerAttached(void); + +bool FIRCLSProcessSuspendAllOtherThreads(FIRCLSProcess *process); +bool FIRCLSProcessResumeAllOtherThreads(FIRCLSProcess *process); + +void FIRCLSProcessRecordThreadNames(FIRCLSProcess *process, FIRCLSFile *file); +void FIRCLSProcessRecordDispatchQueueNames(FIRCLSProcess *process, FIRCLSFile *file); +bool FIRCLSProcessRecordAllThreads(FIRCLSProcess *process, FIRCLSFile *file); +void FIRCLSProcessRecordStats(FIRCLSProcess *process, FIRCLSFile *file); +void FIRCLSProcessRecordRuntimeInfo(FIRCLSProcess *process, FIRCLSFile *file); diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSUserLogging.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSUserLogging.h new file mode 100644 index 0000000..737d240 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSUserLogging.h @@ -0,0 +1,103 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "FIRCLSFile.h" + +__BEGIN_DECLS + +#ifdef __OBJC__ +extern NSString* const FIRCLSStartTimeKey; +extern NSString* const FIRCLSFirstRunloopTurnTimeKey; +extern NSString* const FIRCLSInBackgroundKey; +#if TARGET_OS_IPHONE +extern NSString* const FIRCLSDeviceOrientationKey; +extern NSString* const FIRCLSUIOrientationKey; +#endif +extern NSString* const FIRCLSUserIdentifierKey; +extern NSString* const FIRCLSUserNameKey; +extern NSString* const FIRCLSUserEmailKey; +extern NSString* const FIRCLSDevelopmentPlatformNameKey; +extern NSString* const FIRCLSDevelopmentPlatformVersionKey; +#endif + +extern const uint32_t FIRCLSUserLoggingMaxKVEntries; + +typedef struct { + const char* incrementalPath; + const char* compactedPath; + + uint32_t maxIncrementalCount; + uint32_t maxCount; +} FIRCLSUserLoggingKVStorage; + +typedef struct { + const char* aPath; + const char* bPath; + uint32_t maxSize; + uint32_t maxEntries; + bool restrictBySize; + uint32_t* entryCount; +} FIRCLSUserLoggingABStorage; + +typedef struct { + FIRCLSUserLoggingKVStorage userKVStorage; + FIRCLSUserLoggingKVStorage internalKVStorage; + + FIRCLSUserLoggingABStorage logStorage; + FIRCLSUserLoggingABStorage errorStorage; + FIRCLSUserLoggingABStorage customExceptionStorage; +} FIRCLSUserLoggingReadOnlyContext; + +typedef struct { + const char* activeUserLogPath; + const char* activeErrorLogPath; + const char* activeCustomExceptionPath; + uint32_t userKVCount; + uint32_t internalKVCount; + uint32_t errorsCount; +} FIRCLSUserLoggingWritableContext; + +void FIRCLSUserLoggingInit(FIRCLSUserLoggingReadOnlyContext* roContext, + FIRCLSUserLoggingWritableContext* rwContext); + +void CLSLogDisableUninitializedKitMessaging(void); + +#ifdef __OBJC__ +void FIRCLSUserLoggingRecordUserKeyValue(NSString* key, id value); +void FIRCLSUserLoggingRecordInternalKeyValue(NSString* key, id value); +void FIRCLSUserLoggingWriteInternalKeyValue(NSString* key, NSString* value); + +void FIRCLSUserLoggingRecordError(NSError* error, NSDictionary* additionalUserInfo); + +NSDictionary* FIRCLSUserLoggingGetCompactedKVEntries(FIRCLSUserLoggingKVStorage* storage, + bool decodeHex); +void FIRCLSUserLoggingCompactKVEntries(FIRCLSUserLoggingKVStorage* storage); + +void FIRCLSUserLoggingRecordKeyValue(NSString* key, + id value, + FIRCLSUserLoggingKVStorage* storage, + uint32_t* counter); + +void FIRCLSUserLoggingWriteAndCheckABFiles(FIRCLSUserLoggingABStorage* storage, + const char** activePath, + void (^openedFileBlock)(FIRCLSFile* file)); + +NSArray* FIRCLSUserLoggingStoredKeyValues(const char* path); + +OBJC_EXTERN void FIRCLSLog(NSString* format, ...) NS_FORMAT_FUNCTION(1, 2); +#endif + +__END_DECLS diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSUserLogging.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSUserLogging.m new file mode 100644 index 0000000..587d923 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Components/FIRCLSUserLogging.m @@ -0,0 +1,531 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FIRCLSUserLogging.h" + +#include + +#include "FIRCLSGlobals.h" +#include "FIRCLSUtility.h" + +#import "FIRCLSReportManager_Private.h" + +NSString *const FIRCLSStartTimeKey = @"com.crashlytics.kit-start-time"; +NSString *const FIRCLSFirstRunloopTurnTimeKey = @"com.crashlytics.first-run-loop-time"; +NSString *const FIRCLSInBackgroundKey = @"com.crashlytics.in-background"; +#if TARGET_OS_IPHONE +NSString *const FIRCLSDeviceOrientationKey = @"com.crashlytics.device-orientation"; +NSString *const FIRCLSUIOrientationKey = @"com.crashlytics.ui-orientation"; +#endif +NSString *const FIRCLSUserIdentifierKey = @"com.crashlytics.user-id"; +NSString *const FIRCLSDevelopmentPlatformNameKey = @"com.crashlytics.development-platform-name"; +NSString *const FIRCLSDevelopmentPlatformVersionKey = + @"com.crashlytics.development-platform-version"; + +const uint32_t FIRCLSUserLoggingMaxKVEntries = 64; + +static bool clsDisableUninitializedKitMessaging = false; + +#pragma mark - Prototypes +static void FIRCLSUserLoggingWriteKeyValue(NSString *key, + NSString *value, + FIRCLSUserLoggingKVStorage *storage, + uint32_t *counter); +static void FIRCLSUserLoggingCheckAndSwapABFiles(FIRCLSUserLoggingABStorage *storage, + const char **activePath, + off_t fileSize); +void FIRCLSLogInternal(NSString *message); + +#pragma mark - Setup +void FIRCLSUserLoggingInit(FIRCLSUserLoggingReadOnlyContext *roContext, + FIRCLSUserLoggingWritableContext *rwContext) { + rwContext->activeUserLogPath = roContext->logStorage.aPath; + rwContext->activeErrorLogPath = roContext->errorStorage.aPath; + rwContext->activeCustomExceptionPath = roContext->customExceptionStorage.aPath; + + rwContext->userKVCount = 0; + rwContext->internalKVCount = 0; + rwContext->errorsCount = 0; + + roContext->userKVStorage.maxIncrementalCount = FIRCLSUserLoggingMaxKVEntries; + roContext->internalKVStorage.maxIncrementalCount = roContext->userKVStorage.maxIncrementalCount; +} + +void CLSLogDisableUninitializedKitMessaging(void) { + clsDisableUninitializedKitMessaging = true; +} + +#pragma mark - KV Logging +void FIRCLSUserLoggingRecordInternalKeyValue(NSString *key, id value) { + FIRCLSUserLoggingRecordKeyValue(key, value, &_clsContext.readonly->logging.internalKVStorage, + &_clsContext.writable->logging.internalKVCount); +} + +void FIRCLSUserLoggingWriteInternalKeyValue(NSString *key, NSString *value) { + // Unsynchronized - must be run on the correct queue + FIRCLSUserLoggingWriteKeyValue(key, value, &_clsContext.readonly->logging.internalKVStorage, + &_clsContext.writable->logging.internalKVCount); +} + +void FIRCLSUserLoggingRecordUserKeyValue(NSString *key, id value) { + FIRCLSUserLoggingRecordKeyValue(key, value, &_clsContext.readonly->logging.userKVStorage, + &_clsContext.writable->logging.userKVCount); +} + +static id FIRCLSUserLoggingGetComponent(NSDictionary *entry, + NSString *componentName, + bool decodeHex) { + id value = [entry objectForKey:componentName]; + + return (decodeHex && value != [NSNull null]) ? FIRCLSFileHexDecodeString([value UTF8String]) + : value; +} + +static NSString *FIRCLSUserLoggingGetKey(NSDictionary *entry, bool decodeHex) { + return FIRCLSUserLoggingGetComponent(entry, @"key", decodeHex); +} + +static id FIRCLSUserLoggingGetValue(NSDictionary *entry, bool decodeHex) { + return FIRCLSUserLoggingGetComponent(entry, @"value", decodeHex); +} + +NSDictionary *FIRCLSUserLoggingGetCompactedKVEntries(FIRCLSUserLoggingKVStorage *storage, + bool decodeHex) { + if (!FIRCLSIsValidPointer(storage)) { + FIRCLSSDKLogError("storage invalid\n"); + return nil; + } + + NSArray *incrementalKVs = FIRCLSUserLoggingStoredKeyValues(storage->incrementalPath); + NSArray *compactedKVs = FIRCLSUserLoggingStoredKeyValues(storage->compactedPath); + + NSMutableDictionary *finalKVSet = [NSMutableDictionary new]; + + // These should all be unique, so there might be a more efficient way to + // do this + for (NSDictionary *entry in compactedKVs) { + NSString *key = FIRCLSUserLoggingGetKey(entry, decodeHex); + NSString *value = FIRCLSUserLoggingGetValue(entry, decodeHex); + + if (!key || !value) { + FIRCLSSDKLogError("compacted key/value contains a nil and must be dropped\n"); + continue; + } + + [finalKVSet setObject:value forKey:key]; + } + + // Now, assign the incremental values, in file order, so we overwrite any older values. + for (NSDictionary *entry in incrementalKVs) { + NSString *key = FIRCLSUserLoggingGetKey(entry, decodeHex); + NSString *value = FIRCLSUserLoggingGetValue(entry, decodeHex); + + if (!key || !value) { + FIRCLSSDKLogError("incremental key/value contains a nil and must be dropped\n"); + continue; + } + + if ([value isEqual:[NSNull null]]) { + [finalKVSet removeObjectForKey:key]; + } else { + [finalKVSet setObject:value forKey:key]; + } + } + + return finalKVSet; +} + +void FIRCLSUserLoggingCompactKVEntries(FIRCLSUserLoggingKVStorage *storage) { + if (!FIRCLSIsValidPointer(storage)) { + FIRCLSSDKLogError("Error: storage invalid\n"); + return; + } + + NSDictionary *finalKVs = FIRCLSUserLoggingGetCompactedKVEntries(storage, false); + + if (unlink(storage->compactedPath) != 0) { + FIRCLSSDKLog("Error: Unable to remove compacted KV store before compaction %s\n", + strerror(errno)); + } + + FIRCLSFile file; + + if (!FIRCLSFileInitWithPath(&file, storage->compactedPath, true)) { + FIRCLSSDKLog("Error: Unable to open compacted k-v file\n"); + return; + } + + uint32_t maxCount = storage->maxCount; + if ([finalKVs count] > maxCount) { + // We need to remove keys, to avoid going over the max. + // This is just about the worst way to go about doing this. There are lots of smarter ways, + // but it's very uncommon to go down this path. + NSArray *keys = [finalKVs allKeys]; + + FIRCLSSDKLogInfo("Truncating KV set, which is above max %d\n", maxCount); + + finalKVs = + [finalKVs dictionaryWithValuesForKeys:[keys subarrayWithRange:NSMakeRange(0, maxCount)]]; + } + + for (NSString *key in finalKVs) { + NSString *value = [finalKVs objectForKey:key]; + + FIRCLSFileWriteSectionStart(&file, "kv"); + FIRCLSFileWriteHashStart(&file); + // tricky - the values stored incrementally have already been hex-encoded + FIRCLSFileWriteHashEntryString(&file, "key", [key UTF8String]); + FIRCLSFileWriteHashEntryString(&file, "value", [value UTF8String]); + FIRCLSFileWriteHashEnd(&file); + FIRCLSFileWriteSectionEnd(&file); + } + + FIRCLSFileClose(&file); + + if (unlink(storage->incrementalPath) != 0) { + FIRCLSSDKLog("Error: Unable to remove incremental KV store after compaction %s\n", + strerror(errno)); + } +} + +void FIRCLSUserLoggingRecordKeyValue(NSString *key, + id value, + FIRCLSUserLoggingKVStorage *storage, + uint32_t *counter) { + if (!FIRCLSIsValidPointer(key)) { + FIRCLSSDKLogWarn("User provided bad key\n"); + return; + } + + // ensure that any invalid pointer is actually set to nil + if (!FIRCLSIsValidPointer(value) && value != nil) { + FIRCLSSDKLogWarn("Bad value pointer being clamped to nil\n"); + value = nil; + } + + if (!FIRCLSContextIsInitialized()) { + return; + } + + if ([value respondsToSelector:@selector(description)]) { + value = [value description]; + } else { + // passing nil will result in a JSON null being written, which is deserialized as [NSNull null], + // signaling to remove the key during compaction + value = nil; + } + + dispatch_sync(FIRCLSGetLoggingQueue(), ^{ + FIRCLSUserLoggingWriteKeyValue(key, value, storage, counter); + }); +} + +static void FIRCLSUserLoggingWriteKeyValue(NSString *key, + NSString *value, + FIRCLSUserLoggingKVStorage *storage, + uint32_t *counter) { + FIRCLSFile file; + + if (!FIRCLSIsValidPointer(storage) || !FIRCLSIsValidPointer(counter)) { + FIRCLSSDKLogError("Bad parameters\n"); + return; + } + + if (!FIRCLSFileInitWithPath(&file, storage->incrementalPath, true)) { + FIRCLSSDKLogError("Unable to open k-v file\n"); + return; + } + + FIRCLSFileWriteSectionStart(&file, "kv"); + FIRCLSFileWriteHashStart(&file); + FIRCLSFileWriteHashEntryHexEncodedString(&file, "key", [key UTF8String]); + FIRCLSFileWriteHashEntryHexEncodedString(&file, "value", [value UTF8String]); + FIRCLSFileWriteHashEnd(&file); + FIRCLSFileWriteSectionEnd(&file); + + FIRCLSFileClose(&file); + + *counter += 1; + if (*counter >= storage->maxIncrementalCount) { + dispatch_async(FIRCLSGetLoggingQueue(), ^{ + FIRCLSUserLoggingCompactKVEntries(storage); + *counter = 0; + }); + } +} + +NSArray *FIRCLSUserLoggingStoredKeyValues(const char *path) { + if (!FIRCLSContextIsInitialized()) { + return nil; + } + + return FIRCLSFileReadSections(path, true, ^NSObject *(id obj) { + return [obj objectForKey:@"kv"]; + }); +} + +#pragma mark - NSError Logging +static void FIRCLSUserLoggingRecordErrorUserInfo(FIRCLSFile *file, + const char *fileKey, + NSDictionary *userInfo) { + if ([userInfo count] == 0) { + return; + } + + FIRCLSFileWriteHashKey(file, fileKey); + FIRCLSFileWriteArrayStart(file); + + for (id key in userInfo) { + id value = [userInfo objectForKey:key]; + if (![value respondsToSelector:@selector(description)]) { + continue; + } + + FIRCLSFileWriteArrayStart(file); + FIRCLSFileWriteArrayEntryHexEncodedString(file, [key UTF8String]); + FIRCLSFileWriteArrayEntryHexEncodedString(file, [[value description] UTF8String]); + FIRCLSFileWriteArrayEnd(file); + } + + FIRCLSFileWriteArrayEnd(file); +} + +static void FIRCLSUserLoggingWriteError(FIRCLSFile *file, + NSError *error, + NSDictionary *additionalUserInfo, + NSArray *addresses, + uint64_t timestamp) { + FIRCLSFileWriteSectionStart(file, "error"); + FIRCLSFileWriteHashStart(file); + FIRCLSFileWriteHashEntryHexEncodedString(file, "domain", [[error domain] UTF8String]); + FIRCLSFileWriteHashEntryInt64(file, "code", [error code]); + FIRCLSFileWriteHashEntryUint64(file, "time", timestamp); + + // addresses + FIRCLSFileWriteHashKey(file, "stacktrace"); + FIRCLSFileWriteArrayStart(file); + for (NSNumber *address in addresses) { + FIRCLSFileWriteArrayEntryUint64(file, [address unsignedLongLongValue]); + } + FIRCLSFileWriteArrayEnd(file); + + // user-info + FIRCLSUserLoggingRecordErrorUserInfo(file, "info", [error userInfo]); + FIRCLSUserLoggingRecordErrorUserInfo(file, "extra_info", additionalUserInfo); + + FIRCLSFileWriteHashEnd(file); + FIRCLSFileWriteSectionEnd(file); +} + +void FIRCLSUserLoggingRecordError(NSError *error, + NSDictionary *additionalUserInfo) { + if (!error) { + return; + } + + if (!FIRCLSContextIsInitialized()) { + return; + } + + // record the stacktrace and timestamp here, so we + // are as close as possible to the user's log statement + NSArray *addresses = [NSThread callStackReturnAddresses]; + uint64_t timestamp = time(NULL); + + FIRCLSUserLoggingWriteAndCheckABFiles( + &_clsContext.readonly->logging.errorStorage, + &_clsContext.writable->logging.activeErrorLogPath, ^(FIRCLSFile *file) { + FIRCLSUserLoggingWriteError(file, error, additionalUserInfo, addresses, timestamp); + }); +} + +#pragma mark - CLSLog Support +void FIRCLSLog(NSString *format, ...) { + // If the format is nil do nothing just like NSLog. + if (!format) { + return; + } + + va_list args; + va_start(args, format); + NSString *msg = [[NSString alloc] initWithFormat:format arguments:args]; + va_end(args); + + FIRCLSLogInternal(msg); +} + +#pragma mark - Properties +uint32_t FIRCLSUserLoggingMaxLogSize(void) { + // don't forget that the message encoding overhead is 2x, and we + // wrap everything in a json structure with time. So, there is + // quite a penalty + + uint32_t size = 1024 * 64; + + return size * 2; +} + +uint32_t FIRCLSUserLoggingMaxErrorSize(void) { + return FIRCLSUserLoggingMaxLogSize(); +} + +#pragma mark - AB Logging +void FIRCLSUserLoggingCheckAndSwapABFiles(FIRCLSUserLoggingABStorage *storage, + const char **activePath, + off_t fileSize) { + if (!activePath || !storage) { + return; + } + + if (!*activePath) { + return; + } + + if (storage->restrictBySize) { + if (fileSize <= storage->maxSize) { + return; + } + } else { + if (!FIRCLSIsValidPointer(storage->entryCount)) { + FIRCLSSDKLogError("Error: storage has invalid pointer, but is restricted by entry count\n"); + return; + } + + if (*storage->entryCount < storage->maxEntries) { + return; + } + + // Here we have rolled over, so we have to reset our counter. + *storage->entryCount = 0; + } + + // if it is too big: + // - reset the other log + // - make it active + const char *otherPath = NULL; + + if (*activePath == storage->aPath) { + otherPath = storage->bPath; + } else { + // take this path if the pointer is invalid as well, to reset + otherPath = storage->aPath; + } + + // guard here against path being nil or empty + NSString *pathString = [NSString stringWithUTF8String:otherPath]; + + if ([pathString length] > 0) { + // ignore the error, because there is nothing we can do to recover here, and its likely + // any failures would be intermittent + + [[NSFileManager defaultManager] removeItemAtPath:pathString error:nil]; + } + + *activePath = otherPath; +} + +void FIRCLSUserLoggingWriteAndCheckABFiles(FIRCLSUserLoggingABStorage *storage, + const char **activePath, + void (^openedFileBlock)(FIRCLSFile *file)) { + if (!storage || !activePath || !openedFileBlock) { + return; + } + + if (!*activePath) { + return; + } + + if (storage->restrictBySize) { + if (storage->maxSize == 0) { + return; + } + } else { + if (storage->maxEntries == 0) { + return; + } + } + + dispatch_sync(FIRCLSGetLoggingQueue(), ^{ + FIRCLSFile file; + + if (!FIRCLSFileInitWithPath(&file, *activePath, true)) { + FIRCLSSDKLog("Unable to open log file\n"); + return; + } + + openedFileBlock(&file); + + off_t fileSize = 0; + FIRCLSFileCloseWithOffset(&file, &fileSize); + + // increment the count before calling FIRCLSUserLoggingCheckAndSwapABFiles, so the value + // reflects the actual amount of stuff written + if (!storage->restrictBySize && FIRCLSIsValidPointer(storage->entryCount)) { + *storage->entryCount += 1; + } + + dispatch_async(FIRCLSGetLoggingQueue(), ^{ + FIRCLSUserLoggingCheckAndSwapABFiles(storage, activePath, fileSize); + }); + }); +} + +void FIRCLSLogInternalWrite(FIRCLSFile *file, NSString *message, uint64_t time) { + FIRCLSFileWriteSectionStart(file, "log"); + FIRCLSFileWriteHashStart(file); + FIRCLSFileWriteHashEntryHexEncodedString(file, "msg", [message UTF8String]); + FIRCLSFileWriteHashEntryUint64(file, "time", time); + FIRCLSFileWriteHashEnd(file); + FIRCLSFileWriteSectionEnd(file); +} + +void FIRCLSLogInternal(NSString *message) { + if (!message) { + return; + } + + if (!FIRCLSContextIsInitialized()) { + if (!clsDisableUninitializedKitMessaging) { + FIRCLSWarningLog(@"WARNING: FIRCLSLog has been used before (or concurrently with) " + @"Crashlytics initialization and cannot be recorded. The message was: \n%@", + message); + } + return; + } + struct timeval te; + + NSUInteger messageLength = [message length]; + int maxLogSize = _clsContext.readonly->logging.logStorage.maxSize; + + if (messageLength > maxLogSize) { + FIRCLSWarningLog( + @"WARNING: Attempted to write %zd bytes, but %d is the maximum size of the log. " + @"Truncating to %d bytes.\n", + messageLength, maxLogSize, maxLogSize); + message = [message substringToIndex:maxLogSize]; + } + + // unable to get time - abort + if (gettimeofday(&te, NULL) != 0) { + return; + } + + const uint64_t time = te.tv_sec * 1000LL + te.tv_usec / 1000; + + FIRCLSUserLoggingWriteAndCheckABFiles(&_clsContext.readonly->logging.logStorage, + &_clsContext.writable->logging.activeUserLogPath, + ^(FIRCLSFile *file) { + FIRCLSLogInternalWrite(file, message, time); + }); +} diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Controllers/FIRCLSNetworkClient.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Controllers/FIRCLSNetworkClient.h new file mode 100644 index 0000000..2c259c2 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Controllers/FIRCLSNetworkClient.h @@ -0,0 +1,61 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +typedef NS_ENUM(NSInteger, FIRCLSNetworkClientErrorType) { + FIRCLSNetworkClientErrorTypeUnknown = -1, + FIRCLSNetworkClientErrorTypeFileUnreadable = -2 +}; + +extern NSString *const FIRCLSNetworkClientErrorDomain; + +@protocol FIRCLSNetworkClientDelegate; +@class FIRCLSDataCollectionToken; +@class FIRCLSFileManager; + +@interface FIRCLSNetworkClient : NSObject + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; +- (instancetype)initWithQueue:(NSOperationQueue *)queue + fileManager:(FIRCLSFileManager *)fileManager + delegate:(id)delegate; + +@property(nonatomic, weak) id delegate; + +@property(nonatomic, readonly) NSOperationQueue *operationQueue; +@property(nonatomic, readonly) BOOL supportsBackgroundRequests; + +- (void)startUploadRequest:(NSURLRequest *)request + filePath:(NSString *)path + dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken + immediately:(BOOL)immediate; + +- (void)attemptToReconnectBackgroundSessionWithCompletionBlock:(void (^)(void))completionBlock; + +@end + +@class FIRCLSNetworkClient; + +@protocol FIRCLSNetworkClientDelegate +@required +- (BOOL)networkClientCanUseBackgroundSessions:(FIRCLSNetworkClient *)client; + +@optional +- (void)networkClient:(FIRCLSNetworkClient *)client + didFinishUploadWithPath:(NSString *)path + error:(NSError *)error; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Controllers/FIRCLSNetworkClient.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Controllers/FIRCLSNetworkClient.m new file mode 100644 index 0000000..8618348 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Controllers/FIRCLSNetworkClient.m @@ -0,0 +1,362 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSNetworkClient.h" + +#import "FIRCLSApplication.h" +#import "FIRCLSByteUtility.h" +#import "FIRCLSDataCollectionToken.h" +#import "FIRCLSDefines.h" +#import "FIRCLSFileManager.h" +#import "FIRCLSNetworkResponseHandler.h" +#import "FIRCLSURLSession.h" +#import "FIRCLSURLSessionConfiguration.h" + +#import "FIRCLSUtility.h" + +NSString *const FIRCLSNetworkClientErrorDomain = @"FIRCLSNetworkError"; + +NSString *const FIRCLSNetworkClientBackgroundIdentifierSuffix = @".crash.background-session"; + +@interface FIRCLSNetworkClient () { + NSURLSession *_session; +} + +@property(nonatomic, strong) void (^backgroundCompletionHandler)(void); +@property(nonatomic, strong, readonly) NSURLSession *session; +@property(nonatomic, assign) BOOL canUseBackgroundSession; +@property(nonatomic, strong) FIRCLSFileManager *fileManager; + +@end + +@implementation FIRCLSNetworkClient + +- (instancetype)initWithQueue:(NSOperationQueue *)queue + fileManager:(FIRCLSFileManager *)fileManager + delegate:(id)delegate { + self = [super init]; + if (!self) { + return nil; + } + + _operationQueue = queue; + _delegate = delegate; + _fileManager = fileManager; + + self.canUseBackgroundSession = [_delegate networkClientCanUseBackgroundSessions:self]; + + if (!self.supportsBackgroundRequests) { + FIRCLSDeveloperLog( + "Crashlytics:Crash:Client", + @"Background session uploading not supported, asynchronous uploading will be used"); + } + + return self; +} + +#pragma mark - Background Support +- (NSURLSession *)session { + // Creating a NSURLSession takes some time. Doing it lazily saves us time in the normal case. + if (_session) { + return _session; + } + + NSURLSessionConfiguration *config = nil; + + Class urlSessionClass; + Class urlSessionConfigurationClass; +#if FIRCLSURLSESSION_REQUIRED + urlSessionClass = [FIRCLSURLSession class]; + urlSessionConfigurationClass = [FIRCLSURLSessionConfiguration class]; +#else + urlSessionClass = [NSURLSession class]; + urlSessionConfigurationClass = [NSURLSessionConfiguration class]; +#endif + + if (self.supportsBackgroundRequests) { + NSString *sdkBundleID = FIRCLSApplicationGetSDKBundleID(); + NSString *backgroundConfigName = + [sdkBundleID stringByAppendingString:FIRCLSNetworkClientBackgroundIdentifierSuffix]; + + config = [urlSessionConfigurationClass backgroundSessionConfiguration:backgroundConfigName]; +#if TARGET_OS_IPHONE + [config setSessionSendsLaunchEvents:NO]; +#endif + } + + if (!config) { + // take this code path if we don't support background requests OR if we failed to create a + // background configuration + config = [urlSessionConfigurationClass defaultSessionConfiguration]; + } + + _session = [urlSessionClass sessionWithConfiguration:config + delegate:self + delegateQueue:self.operationQueue]; + + if (!_session || !config) { + FIRCLSErrorLog(@"Failed to initialize"); + } + + return _session; +} + +#if FIRCLSURLSESSION_REQUIRED +- (BOOL)NSURLSessionAvailable { + if ([[FIRCLSURLSession class] respondsToSelector:@selector(NSURLSessionShouldBeUsed)]) { + return [FIRCLSURLSession NSURLSessionShouldBeUsed]; + } + + return NSClassFromString(@"NSURLSession") != nil; +} +#endif + +- (BOOL)supportsBackgroundRequests { + return !FIRCLSApplicationIsExtension() +#if FIRCLSURLSESSION_REQUIRED + && [self NSURLSessionAvailable] +#endif + && self.canUseBackgroundSession; +} + +- (void)attemptToReconnectBackgroundSessionWithCompletionBlock:(void (^)(void))completionBlock { + if (!self.supportsBackgroundRequests) { + completionBlock(); + return; + } + + // This is the absolute minimum necessary. Perhaps we can do better? + [[NSOperationQueue mainQueue] addOperationWithBlock:completionBlock]; +} + +#pragma mark - API +- (void)startUploadRequest:(NSURLRequest *)request + filePath:(NSString *)path + dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken + immediately:(BOOL)immediate { + if (![dataCollectionToken isValid]) { + FIRCLSErrorLog(@"An upload was requested with an invalid data collection token."); + return; + } + + if (immediate) { + [self startImmediateUploadRequest:request filePath:path]; + return; + } + + NSString *description = [self relativeTaskPathForAbsolutePath:path]; + [self checkForExistingTaskMatchingDescription:description + completionBlock:^(BOOL found) { + if (found) { + FIRCLSDeveloperLog( + "Crashlytics:Crash:Client", + @"A task currently exists for this upload, skipping"); + return; + } + + [self startNewUploadRequest:request filePath:path]; + }]; +} + +#pragma mark - Support API +- (void)startImmediateUploadRequest:(NSURLRequest *)request filePath:(NSString *)path { + // check the ivar directly, to avoid going back to the delegate + if (self.supportsBackgroundRequests) { + // this can be done here, because the request will be started synchronously. + [self startNewUploadRequest:request filePath:path]; + return; + } + + if (![[NSFileManager defaultManager] isReadableFileAtPath:path]) { + FIRCLSSDKLog("Error: file unreadable\n"); + // Following the same logic as below, do not try to inform the delegate + return; + } + + NSMutableURLRequest *mutableRequest = [request mutableCopy]; + + [mutableRequest setHTTPBodyStream:[NSInputStream inputStreamWithFileAtPath:path]]; + + NSURLResponse *requestResponse = nil; + + [[NSURLSession sharedSession] + dataTaskWithRequest:mutableRequest + completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, + NSError *_Nullable error) { + [FIRCLSNetworkResponseHandler + clientResponseType:requestResponse + handler:^(FIRCLSNetworkClientResponseType type, NSInteger statusCode) { + if (type != FIRCLSNetworkClientResponseSuccess) { + // don't even inform the delegate of a failure here, because we don't + // want any action to be taken synchronously + return; + } + + [[self delegate] networkClient:self + didFinishUploadWithPath:path + error:error]; + }]; + }]; +} + +- (void)startNewUploadRequest:(NSURLRequest *)request filePath:(NSString *)path { + if (![[NSFileManager defaultManager] isReadableFileAtPath:path]) { + [self.operationQueue addOperationWithBlock:^{ + [self + handleTaskDescription:path + completedWithError:[NSError errorWithDomain:FIRCLSNetworkClientErrorDomain + code:FIRCLSNetworkClientErrorTypeFileUnreadable + userInfo:@{@"path" : path}]]; + }]; + + return; + } + + NSURLSessionUploadTask *task = [self.session uploadTaskWithRequest:request + fromFile:[NSURL fileURLWithPath:path]]; + + // set the description, so we can determine what file was successfully uploaded later on + [task setTaskDescription:[self relativeTaskPathForAbsolutePath:path]]; + + [task resume]; +} + +- (NSString *)rootPath { + return self.fileManager.rootPath; +} + +- (NSString *)absolutePathForRelativeTaskPath:(NSString *)path { + return [self.rootPath stringByAppendingPathComponent:path]; +} + +- (NSString *)relativeTaskPathForAbsolutePath:(NSString *)path { + // make sure this has a tailing slash, so the path looks relative + NSString *root = [self.rootPath stringByAppendingString:@"/"]; + + if (![path hasPrefix:root]) { + FIRCLSSDKLog("Error: path '%s' is not at the root '%s'", [path UTF8String], [root UTF8String]); + return nil; + } + + return [path stringByReplacingOccurrencesOfString:root withString:@""]; +} + +#pragma mark - Task Management +- (BOOL)taskArray:(NSArray *)array hasTaskMatchingDescription:(NSString *)description { + NSUInteger idx = [array indexOfObjectPassingTest:^BOOL(id obj, NSUInteger arrayIdx, BOOL *stop) { + return [[obj taskDescription] isEqualToString:description]; + }]; + + return idx != NSNotFound; +} + +- (void)checkSession:(NSURLSession *)session + forTasksMatchingDescription:(NSString *)description + completionBlock:(void (^)(BOOL found))block { + if (!session) { + block(NO); + return; + } + + [session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, + NSArray *downloadTasks) { + if ([self taskArray:uploadTasks hasTaskMatchingDescription:description]) { + block(YES); + return; + } + + if ([self taskArray:dataTasks hasTaskMatchingDescription:description]) { + block(YES); + return; + } + + if ([self taskArray:downloadTasks hasTaskMatchingDescription:description]) { + block(YES); + return; + } + + block(NO); + }]; +} + +- (void)checkForExistingTaskMatchingDescription:(NSString *)description + completionBlock:(void (^)(BOOL found))block { + // Do not instantiate the normal session, because if it doesn't exist yet, it cannot possibly have + // existing tasks + [_operationQueue addOperationWithBlock:^{ + [self checkSession:self.session + forTasksMatchingDescription:description + completionBlock:^(BOOL found) { + block(found); + }]; + }]; +} + +#pragma mark - Result Handling +// This method is duplicated from FIRCLSFABNetworkClient. Sharing it is a little weird - I didn't +// feel like it fit into FIRCLSNetworkResponseHandler. +- (void)runAfterRetryValueFromResponse:(NSURLResponse *)response block:(void (^)(void))block { + NSTimeInterval delay = [FIRCLSNetworkResponseHandler retryValueForResponse:response]; + + // FIRCLSDeveloperLog("Network", @"Restarting request after %f", delay); + + FIRCLSAddOperationAfter(delay, _operationQueue, block); +} + +- (void)restartTask:(NSURLSessionTask *)task { + NSURLRequest *request = [task originalRequest]; + + [self runAfterRetryValueFromResponse:[task response] + block:^{ + NSString *path = [self + absolutePathForRelativeTaskPath:[task taskDescription]]; + + [self startNewUploadRequest:request filePath:path]; + }]; +} + +- (void)handleTask:(NSURLSessionTask *)task completedWithError:(NSError *)error { + [self handleTaskDescription:[task taskDescription] completedWithError:error]; +} + +- (void)handleTaskDescription:(NSString *)taskDescription completedWithError:(NSError *)error { + NSString *path = [self absolutePathForRelativeTaskPath:taskDescription]; + + [[self delegate] networkClient:self didFinishUploadWithPath:path error:error]; +} + +#pragma mark - NSURLSessionDelegate +- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error { + FIRCLSDeveloperLog("Crashlytics:Crash:Client", @"session became invalid: %@", error); +} + +// Careful! Not implementing this method appears to cause a crash when using a background task +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + didCompleteWithError:(NSError *)error { + [FIRCLSNetworkResponseHandler handleCompletedResponse:task.response + forOriginalRequest:task.originalRequest + error:error + block:^(BOOL restart, NSError *taskError) { + if (restart) { + [self restartTask:task]; + return; + } + + [self handleTask:task + completedWithError:taskError]; + }]; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Controllers/FIRCLSReportManager.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Controllers/FIRCLSReportManager.h new file mode 100644 index 0000000..6673748 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Controllers/FIRCLSReportManager.h @@ -0,0 +1,53 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +#include "FIRCLSProfiling.h" +#include "FIRCrashlytics.h" + +@class FBLPromise; + +NS_ASSUME_NONNULL_BEGIN + +@class FIRCLSDataCollectionArbiter; +@class FIRCLSFileManager; +@class FIRCLSInternalReport; +@class FIRCLSSettings; +@class FIRInstanceID; +@protocol FIRAnalyticsInterop; + +@interface FIRCLSReportManager : NSObject + +- (instancetype)initWithFileManager:(FIRCLSFileManager *)fileManager + instanceID:(FIRInstanceID *)instanceID + analytics:(nullable id)analytics + googleAppID:(NSString *)googleAppID + dataArbiter:(FIRCLSDataCollectionArbiter *)dataArbiter + NS_DESIGNATED_INITIALIZER; +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +- (FBLPromise *)startWithProfilingMark:(FIRCLSProfileMark)mark; + +- (FBLPromise *)checkForUnsentReports; +- (FBLPromise *)sendUnsentReports; +- (FBLPromise *)deleteUnsentReports; + +@end + +extern NSString *const FIRCLSConfigSubmitReportsKey; +extern NSString *const FIRCLSConfigPackageReportsKey; + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Controllers/FIRCLSReportManager.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Controllers/FIRCLSReportManager.m new file mode 100644 index 0000000..8132570 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Controllers/FIRCLSReportManager.m @@ -0,0 +1,906 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#if __has_include() +#import +#else +#import "FBLPromises.h" +#endif + +#import "FIRCLSApplication.h" +#import "FIRCLSDataCollectionArbiter.h" +#import "FIRCLSDataCollectionToken.h" +#import "FIRCLSDefines.h" +#import "FIRCLSFeatures.h" +#import "FIRCLSFileManager.h" +#import "FIRCLSInternalReport.h" +#import "FIRCLSLogger.h" +#import "FIRCLSNetworkClient.h" +#import "FIRCLSPackageReportOperation.h" +#import "FIRCLSProcessReportOperation.h" +#import "FIRCLSReportUploader.h" +#import "FIRCLSSettings.h" +#import "FIRCLSSymbolResolver.h" +#import "FIRCLSUserLogging.h" + +#include "FIRCLSGlobals.h" +#include "FIRCLSUtility.h" + +#import "FIRCLSApplicationIdentifierModel.h" +#import "FIRCLSConstants.h" +#import "FIRCLSExecutionIdentifierModel.h" +#import "FIRCLSInstallIdentifierModel.h" +#import "FIRCLSSettingsOnboardingManager.h" + +#import "FIRCLSReportManager_Private.h" + +#include +#include +#include "FIRAEvent+Internal.h" +#include "FIRCLSFCRAnalytics.h" + +#if TARGET_OS_IPHONE +#import +#else +#import +#endif + +static NSTimeInterval const CLSReportRetryInterval = 10 * 60; + +static NSString *FIRCLSFirebaseAnalyticsEventLogFormat = @"$A$:%@"; + +@interface FIRCLSAnalyticsInteropListener : NSObject { +} +@end + +@implementation FIRCLSAnalyticsInteropListener + +- (void)messageTriggered:(NSString *)name parameters:(NSDictionary *)parameters { + NSDictionary *event = @{ + @"name" : name, + @"parameters" : parameters, + }; + NSString *json = FIRCLSFIRAEventDictionaryToJSON(event); + if (json != nil) { + FIRCLSLog(FIRCLSFirebaseAnalyticsEventLogFormat, json); + } +} + +@end + +/** + * A FIRReportAction is used to indicate how to handle unsent reports. + */ +typedef NS_ENUM(NSInteger, FIRCLSReportAction) { + /** Upload the reports to Crashlytics. */ + FIRCLSReportActionSend, + /** Delete the reports without uploading them. */ + FIRCLSReportActionDelete, +}; + +/** + * This is just a helper to make code using FIRReportAction more readable. + */ +typedef NSNumber FIRCLSWrappedReportAction; +@implementation NSNumber (FIRCLSWrappedReportAction) +- (FIRCLSReportAction)reportActionValue { + return [self intValue]; +} +@end + +/** + * This is a helper to make code using NSNumber for bools more readable. + */ +typedef NSNumber FIRCLSWrappedBool; + +@interface FIRCLSReportManager () { + FIRCLSFileManager *_fileManager; + FIRCLSNetworkClient *_networkClient; + FIRCLSReportUploader *_uploader; + dispatch_queue_t _dispatchQueue; + NSOperationQueue *_operationQueue; + id _analytics; + + // A promise that will be resolved when unsent reports are found on the device, and + // processReports: can be called to decide how to deal with them. + FBLPromise *_unsentReportsAvailable; + + // A promise that will be resolved when the user has provided an action that they want to perform + // for all the unsent reports. + FBLPromise *_reportActionProvided; + + // A promise that will be resolved when all unsent reports have been "handled". They won't + // necessarily have been uploaded, but we will know whether they should be sent or deleted, and + // the initial work to make that happen will have been processed on the work queue. + // + // Currently only used for testing + FBLPromise *_unsentReportsHandled; + + // A token to make sure that checkForUnsentReports only gets called once. + atomic_bool _checkForUnsentReportsCalled; + + BOOL _registeredAnalyticsEventListener; +} + +@property(nonatomic, readonly) NSString *googleAppID; + +@property(nonatomic, strong) FIRCLSDataCollectionArbiter *dataArbiter; + +// Uniquely identifies a build / binary of the app +@property(nonatomic, strong) FIRCLSApplicationIdentifierModel *appIDModel; + +// Uniquely identifies an install of the app +@property(nonatomic, strong) FIRCLSInstallIdentifierModel *installIDModel; + +// Uniquely identifies a run of the app +@property(nonatomic, strong) FIRCLSExecutionIdentifierModel *executionIDModel; + +// Settings fetched from the server +@property(nonatomic, strong) FIRCLSSettings *settings; + +// Runs the operations that fetch settings and call onboarding endpoints +@property(nonatomic, strong) FIRCLSSettingsOnboardingManager *settingsAndOnboardingManager; + +@end + +@implementation FIRCLSReportManager + +// Used only for internal data collection E2E testing +static void (^reportSentCallback)(void); + +- (instancetype)initWithFileManager:(FIRCLSFileManager *)fileManager + instanceID:(FIRInstanceID *)instanceID + analytics:(id)analytics + googleAppID:(NSString *)googleAppID + dataArbiter:(FIRCLSDataCollectionArbiter *)dataArbiter { + self = [super init]; + if (!self) { + return nil; + } + + _fileManager = fileManager; + _analytics = analytics; + _googleAppID = [googleAppID copy]; + _dataArbiter = dataArbiter; + + NSString *sdkBundleID = FIRCLSApplicationGetSDKBundleID(); + + _operationQueue = [NSOperationQueue new]; + [_operationQueue setMaxConcurrentOperationCount:1]; + [_operationQueue setName:[sdkBundleID stringByAppendingString:@".work-queue"]]; + + _dispatchQueue = dispatch_queue_create("com.google.firebase.crashlytics.startup", 0); + _operationQueue.underlyingQueue = _dispatchQueue; + + _networkClient = [self clientWithOperationQueue:_operationQueue]; + + _unsentReportsAvailable = [FBLPromise pendingPromise]; + _reportActionProvided = [FBLPromise pendingPromise]; + _unsentReportsHandled = [FBLPromise pendingPromise]; + + _checkForUnsentReportsCalled = NO; + + _appIDModel = [[FIRCLSApplicationIdentifierModel alloc] init]; + _installIDModel = [[FIRCLSInstallIdentifierModel alloc] initWithInstanceID:instanceID]; + _executionIDModel = [[FIRCLSExecutionIdentifierModel alloc] init]; + + _settings = [[FIRCLSSettings alloc] initWithFileManager:_fileManager appIDModel:_appIDModel]; + + _settingsAndOnboardingManager = + [[FIRCLSSettingsOnboardingManager alloc] initWithAppIDModel:self.appIDModel + installIDModel:self.installIDModel + settings:self.settings + fileManager:self.fileManager + googleAppID:self.googleAppID]; + + return self; +} + +- (FIRCLSNetworkClient *)clientWithOperationQueue:(NSOperationQueue *)queue { + return [[FIRCLSNetworkClient alloc] initWithQueue:queue fileManager:_fileManager delegate:self]; +} + +/** + * Returns the number of unsent reports on the device, including the ones passed in. + */ +- (int)unsentReportsCountWithPreexisting:(NSArray *)paths { + int count = [self countSubmittableAndDeleteUnsubmittableReportPaths:paths]; + count += _fileManager.processingPathContents.count; + count += _fileManager.preparedPathContents.count; + return count; +} + +// This method returns a promise that is resolved with a wrapped FIRReportAction once the user has +// indicated whether they want to upload currently cached reports. +// This method should only be called when we have determined there is at least 1 unsent report. +// This method waits until either: +// 1. Data collection becomes enabled, in which case, the promise will be resolved with Send. +// 2. The developer uses the processCrashReports API to indicate whether the report +// should be sent or deleted, at which point the promise will be resolved with the action. +- (FBLPromise *)waitForReportAction { + FIRCLSDebugLog(@"[Crashlytics:Crash] Notifying that unsent reports are available."); + [_unsentReportsAvailable fulfill:@YES]; + + // If data collection gets enabled while we are waiting for an action, go ahead and send the + // reports, and any subsequent explicit response will be ignored. + FBLPromise *collectionEnabled = + [[self.dataArbiter waitForCrashlyticsCollectionEnabled] + then:^id _Nullable(NSNumber *_Nullable value) { + return @(FIRCLSReportActionSend); + }]; + + FIRCLSDebugLog(@"[Crashlytics:Crash] Waiting for send/deleteUnsentReports to be called."); + // Wait for either the processReports callback to be called, or data collection to be enabled. + return [FBLPromise race:@[ collectionEnabled, _reportActionProvided ]]; +} + +- (FBLPromise *)checkForUnsentReports { + bool expectedCalled = NO; + if (!atomic_compare_exchange_strong(&_checkForUnsentReportsCalled, &expectedCalled, YES)) { + FIRCLSErrorLog(@"checkForUnsentReports should only be called once per execution."); + return [FBLPromise resolvedWith:@NO]; + } + return _unsentReportsAvailable; +} + +- (FBLPromise *)sendUnsentReports { + [_reportActionProvided fulfill:@(FIRCLSReportActionSend)]; + return _unsentReportsHandled; +} + +- (FBLPromise *)deleteUnsentReports { + [_reportActionProvided fulfill:@(FIRCLSReportActionDelete)]; + return _unsentReportsHandled; +} + +- (FBLPromise *)startWithProfilingMark:(FIRCLSProfileMark)mark { + NSString *executionIdentifier = self.executionIDModel.executionID; + + // This needs to be called before any values are read from settings + NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate]; + [self.settings reloadFromCacheWithGoogleAppID:self.googleAppID currentTimestamp:currentTimestamp]; + + if (![self checkBundleIDExists]) { + return [FBLPromise resolvedWith:@NO]; + } + +#if DEBUG + FIRCLSDebugLog(@"Root: %@", [_fileManager rootPath]); +#endif + + if ([self.dataArbiter isLegacyDataCollectionKeyInPlist]) { + FIRCLSErrorLog(@"Found legacy data collection key in app's Info.plist: " + @"firebase_crashlytics_collection_enabled"); + FIRCLSErrorLog(@"Please update your Info.plist to use the new data collection key: " + @"FirebaseCrashlyticsCollectionEnabled"); + FIRCLSErrorLog(@"The legacy data collection Info.plist value could be overridden by " + @"calling: [Fabric with:...]"); + FIRCLSErrorLog(@"The new value can be overridden by calling: [[FIRCrashlytics " + @"crashlytics] setCrashlyticsCollectionEnabled:]"); + + return [FBLPromise resolvedWith:@NO]; + } + + if (![self.settings crashReportingEnabled]) { + FIRCLSInfoLog(@"Reporting is disabled"); + [_fileManager removeContentsOfAllPaths]; + return [FBLPromise resolvedWith:@NO]; + } + + if (![_fileManager createReportDirectories]) { + return [FBLPromise resolvedWith:@NO]; + } + + // Grab existing reports + BOOL launchFailure = [self checkForAndCreateLaunchMarker]; + NSArray *preexistingReportPaths = _fileManager.activePathContents; + + FIRCLSInternalReport *report = [self setupCurrentReport:executionIdentifier]; + if (!report) { + FIRCLSErrorLog(@"Unable to setup a new report"); + } + + if (![self startCrashReporterWithProfilingMark:mark report:report]) { + FIRCLSErrorLog(@"Unable to start crash reporter"); + report = nil; + } + + // Regenerate the Install ID on a background thread if it needs to rotate because + // fetching the Firebase Install ID can be slow on some devices. This should happen after we + // create the session on disk so that we can update the Install ID in the written crash report + // metadata. + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ + [self checkAndRotateInstallUUIDIfNeededWithReport:report]; + }); + + FBLPromise *promise = [FBLPromise resolvedWith:@(report != nil)]; + + if ([self.dataArbiter isCrashlyticsCollectionEnabled]) { + FIRCLSDebugLog(@"Automatic data collection is enabled."); + FIRCLSDebugLog(@"Unsent reports will be uploaded at startup"); + FIRCLSDataCollectionToken *dataCollectionToken = [FIRCLSDataCollectionToken validToken]; + [self startNetworkRequestsWithToken:dataCollectionToken + preexistingReportPaths:preexistingReportPaths + blockingSend:launchFailure + report:report]; + + // If data collection is enabled, the SDK will not notify the user + // when unsent reports are available, or respect Send / DeleteUnsentReports + [_unsentReportsAvailable fulfill:@NO]; + + } else { + FIRCLSDebugLog(@"Automatic data collection is disabled."); + + // TODO: This counting of the file system happens on the main thread. Now that some of the other + // work below has been made async and moved to the dispatch queue, maybe we can move this code + // to the dispatch queue as well. + int unsentReportsCount = [self unsentReportsCountWithPreexisting:preexistingReportPaths]; + if (unsentReportsCount > 0) { + FIRCLSDebugLog( + @"[Crashlytics:Crash] %d unsent reports are available. Checking for upload permission.", + unsentReportsCount); + // Wait for an action to get sent, either from processReports: or automatic data collection. + promise = [[self waitForReportAction] + onQueue:_dispatchQueue + then:^id _Nullable(FIRCLSWrappedReportAction *_Nullable wrappedAction) { + // Process the actions for the reports on disk. + FIRCLSReportAction action = [wrappedAction reportActionValue]; + if (action == FIRCLSReportActionSend) { + FIRCLSDebugLog(@"Sending unsent reports."); + FIRCLSDataCollectionToken *dataCollectionToken = + [FIRCLSDataCollectionToken validToken]; + [self startNetworkRequestsWithToken:dataCollectionToken + preexistingReportPaths:preexistingReportPaths + blockingSend:NO + report:report]; + + } else if (action == FIRCLSReportActionDelete) { + FIRCLSDebugLog(@"Deleting unsent reports."); + [self removeExistingReportPaths:preexistingReportPaths]; + [self removeContentsInOtherReportingDirectories]; + } else { + FIRCLSErrorLog(@"Unknown report action: %d", action); + } + return @(report != nil); + }]; + } else { + FIRCLSDebugLog(@"[Crashlytics:Crash] There are no unsent reports."); + [_unsentReportsAvailable fulfill:@NO]; + } + } + + if (report != nil) { + // capture the start-up time here, but record it asynchronously + double endMark = FIRCLSProfileEnd(mark); + + dispatch_async(FIRCLSGetLoggingQueue(), ^{ + FIRCLSUserLoggingWriteInternalKeyValue(FIRCLSStartTimeKey, [@(endMark) description]); + }); + } + + // To make the code more predictable and therefore testable, don't resolve the startup promise + // until the operations that got queued up for processing reports have been processed through the + // work queue. + NSOperationQueue *__weak queue = _operationQueue; + FBLPromise *__weak unsentReportsHandled = _unsentReportsHandled; + promise = [promise then:^id _Nullable(NSNumber *_Nullable value) { + [queue waitUntilAllOperationsAreFinished]; + // Signal that to callers of processReports that everything is finished. + [unsentReportsHandled fulfill:nil]; + return value; + }]; + + return promise; +} + +- (void)checkAndRotateInstallUUIDIfNeededWithReport:(FIRCLSInternalReport *)report { + [self.installIDModel regenerateInstallIDIfNeededWithBlock:^(BOOL didRotate) { + if (!didRotate) { + return; + } + + FIRCLSContextInitData initData = [self initializeContextInitData:report]; + + FIRCLSContextUpdateMetadata(&initData); + }]; +} + +- (void)startNetworkRequestsWithToken:(FIRCLSDataCollectionToken *)token + preexistingReportPaths:(NSArray *)preexistingReportPaths + blockingSend:(BOOL)blockingSend + report:(FIRCLSInternalReport *)report { + if (self.settings.isCacheExpired) { + // This method can be called more than once if the user calls + // SendUnsentReports again, so don't repeat the settings fetch + static dispatch_once_t settingsFetchOnceToken; + dispatch_once(&settingsFetchOnceToken, ^{ + [self.settingsAndOnboardingManager beginSettingsAndOnboardingWithGoogleAppId:self.googleAppID + token:token]; + }); + } + + [self processExistingReportPaths:preexistingReportPaths + dataCollectionToken:token + asUrgent:blockingSend]; + [self handleContentsInOtherReportingDirectoriesWithToken:token]; +} + +- (FIRCLSContextInitData)initializeContextInitData:(FIRCLSInternalReport *)report { + FIRCLSContextInitData initData; + + memset(&initData, 0, sizeof(FIRCLSContextInitData)); + + // Because we need to start the crash reporter right away, + // it starts up either with default settings, or cached settings + // from the last time they were fetched + FIRCLSSettings *settings = self.settings; + + initData.customBundleId = NULL; + initData.installId = [self.installIDModel.installID UTF8String]; + initData.sessionId = [[report identifier] UTF8String]; + initData.rootPath = [[report path] UTF8String]; + initData.previouslyCrashedFileRootPath = [[_fileManager rootPath] UTF8String]; +#if CLS_MACH_EXCEPTION_SUPPORTED + initData.machExceptionMask = [self machExceptionMask]; +#endif + initData.errorsEnabled = [settings errorReportingEnabled]; + initData.customExceptionsEnabled = [settings customExceptionsEnabled]; + initData.maxCustomExceptions = [settings maxCustomExceptions]; + initData.maxErrorLogSize = [settings errorLogBufferSize]; + initData.maxLogSize = [settings logBufferSize]; + initData.maxKeyValues = [settings maxCustomKeys]; + + return initData; +} + +- (BOOL)startCrashReporterWithProfilingMark:(FIRCLSProfileMark)mark + report:(FIRCLSInternalReport *)report { + if (!report) { + return NO; + } + + FIRCLSContextInitData initData = [self initializeContextInitData:report]; + + // If this is set, then we could attempt to do a synchronous submission for certain kinds of + // events (exceptions). This is a very cool feature, but adds complexity to the backend. For now, + // we're going to leave this disabled. It does work in the exception case, but will ultimtely + // result in the following crash to be discared. Usually that crash isn't interesting. But, if it + // was, we'd never have a chance to see it. + initData.delegate = NULL; + + if (![self installCrashReportingHandlers:&initData]) { + return NO; + } + + [self registerAnalyticsEventListener]; + + [self crashReportingSetupCompleted:mark]; + + return YES; +} + +- (void)crashReportingSetupCompleted:(FIRCLSProfileMark)mark { + // check our handlers + FIRCLSDispatchAfter(2.0, dispatch_get_main_queue(), ^{ + FIRCLSExceptionCheckHandlers((__bridge void *)(self)); + FIRCLSSignalCheckHandlers(); +#if CLS_MACH_EXCEPTION_SUPPORTED + FIRCLSMachExceptionCheckHandlers(); +#endif + }); + + // remove the launch failure marker and record the startup time + dispatch_async(dispatch_get_main_queue(), ^{ + [self removeLaunchFailureMarker]; + dispatch_async(FIRCLSGetLoggingQueue(), ^{ + FIRCLSUserLoggingWriteInternalKeyValue(FIRCLSFirstRunloopTurnTimeKey, + [@(FIRCLSProfileEnd(mark)) description]); + }); + }); +} + +- (BOOL)checkBundleIDExists { + if (self.appIDModel.bundleID.length == 0) { + FIRCLSErrorLog(@"An application must have a valid bundle identifier in its Info.plist"); + return NO; + } + + return YES; +} + +- (FIRCLSReportUploader *)uploader { + if (!_uploader) { + _uploader = [[FIRCLSReportUploader alloc] initWithQueue:self.operationQueue + delegate:self + dataSource:self + client:self.networkClient + fileManager:_fileManager + analytics:_analytics]; + } + + return _uploader; +} + +#if CLS_MACH_EXCEPTION_SUPPORTED +- (exception_mask_t)machExceptionMask { + __block exception_mask_t mask = 0; + + // TODO(b/141241224) This if statement was hardcoded to no, so this block was never run + // FIRCLSSignalEnumerateHandledSignals(^(int idx, int signal) { + // if ([self.delegate ensureDeliveryOfUnixSignal:signal]) { + // mask |= FIRCLSMachExceptionMaskForSignal(signal); + // } + // }); + + return mask; +} +#endif + +#pragma mark - Reporting Lifecycle + +- (BOOL)installCrashReportingHandlers:(FIRCLSContextInitData *)initData { + if (!FIRCLSContextInitialize(initData)) { + return NO; + } + + [self setupStateNotifications]; + + return YES; +} + +- (FIRCLSInternalReport *)setupCurrentReport:(NSString *)executionIdentifier { + [self createLaunchFailureMarker]; + + NSString *reportPath = [_fileManager setupNewPathForExecutionIdentifier:executionIdentifier]; + + return [[FIRCLSInternalReport alloc] initWithPath:reportPath + executionIdentifier:executionIdentifier]; +} + +- (void)removeExistingReportPaths:(NSArray *)reportPaths { + [self.operationQueue addOperationWithBlock:^{ + for (NSString *path in reportPaths) { + [self.fileManager removeItemAtPath:path]; + } + }]; +} + +- (int)countSubmittableAndDeleteUnsubmittableReportPaths:(NSArray *)reportPaths { + int count = 0; + for (NSString *path in reportPaths) { + FIRCLSInternalReport *report = [FIRCLSInternalReport reportWithPath:path]; + if ([report needsToBeSubmitted]) { + count++; + } else { + [self.operationQueue addOperationWithBlock:^{ + [self->_fileManager removeItemAtPath:path]; + }]; + } + } + return count; +} + +- (void)processExistingReportPaths:(NSArray *)reportPaths + dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken + asUrgent:(BOOL)urgent { + for (NSString *path in reportPaths) { + [self processExistingActiveReportPath:path + dataCollectionToken:dataCollectionToken + asUrgent:urgent]; + } +} + +- (void)processExistingActiveReportPath:(NSString *)path + dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken + asUrgent:(BOOL)urgent { + FIRCLSInternalReport *report = [FIRCLSInternalReport reportWithPath:path]; + + // TODO: needsToBeSubmitted should really be called on the background queue. + if (![report needsToBeSubmitted]) { + [self.operationQueue addOperationWithBlock:^{ + [self->_fileManager removeItemAtPath:path]; + }]; + + return; + } + + if (urgent && [dataCollectionToken isValid]) { + // We can proceed without the delegate. + [[self uploader] prepareAndSubmitReport:report + dataCollectionToken:dataCollectionToken + asUrgent:urgent + withProcessing:YES]; + return; + } + + [self submitReport:report dataCollectionToken:dataCollectionToken]; +} + +- (void)submitReport:(FIRCLSInternalReport *)report + dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken { + [self.operationQueue addOperationWithBlock:^{ + [[self uploader] prepareAndSubmitReport:report + dataCollectionToken:dataCollectionToken + asUrgent:NO + withProcessing:YES]; + }]; + + [self didSubmitReport]; +} + +- (void)removeReport:(FIRCLSInternalReport *)report { + [_fileManager removeItemAtPath:report.path]; +} + +- (void)removeContentsInOtherReportingDirectories { + [self removeExistingReportPaths:self.fileManager.processingPathContents]; + [self removeExistingReportPaths:self.fileManager.preparedPathContents]; +} + +- (void)handleContentsInOtherReportingDirectoriesWithToken:(FIRCLSDataCollectionToken *)token { + [self handleExistingFilesInProcessingWithToken:token]; + [self handleExistingFilesInPreparedWithToken:token]; +} + +- (void)handleExistingFilesInProcessingWithToken:(FIRCLSDataCollectionToken *)token { + NSArray *processingPaths = _fileManager.processingPathContents; + + // deal with stuff in processing more carefully - do not process again + [self.operationQueue addOperationWithBlock:^{ + for (NSString *path in processingPaths) { + FIRCLSInternalReport *report = [FIRCLSInternalReport reportWithPath:path]; + [[self uploader] prepareAndSubmitReport:report + dataCollectionToken:token + asUrgent:NO + withProcessing:NO]; + } + }]; +} + +- (void)handleExistingFilesInPreparedWithToken:(FIRCLSDataCollectionToken *)token { + NSArray *preparedPaths = _fileManager.preparedPathContents; + + // Give our network client a chance to reconnect here, if needed. This attempts to avoid + // trying to re-submit a prepared file that is already in flight. + [self.networkClient attemptToReconnectBackgroundSessionWithCompletionBlock:^{ + [self.operationQueue addOperationWithBlock:^{ + [self uploadPreexistingFiles:preparedPaths withToken:token]; + }]; + }]; +} + +- (void)uploadPreexistingFiles:(NSArray *)files withToken:(FIRCLSDataCollectionToken *)token { + // Because this could happen quite a bit after the inital set of files was + // captured, some could be completed (deleted). So, just double-check to make sure + // the file still exists. + + for (NSString *path in files) { + if (![[_fileManager underlyingFileManager] fileExistsAtPath:path]) { + continue; + } + + [[self uploader] uploadPackagedReportAtPath:path dataCollectionToken:token asUrgent:NO]; + } +} + +- (void)retryUploadForReportAtPath:(NSString *)path + dataCollectionToken:(FIRCLSDataCollectionToken *)token { + FIRCLSAddOperationAfter(CLSReportRetryInterval, self.operationQueue, ^{ + FIRCLSDeveloperLog("Crashlytics:Crash", @"re-attempting report submission"); + [[self uploader] uploadPackagedReportAtPath:path dataCollectionToken:token asUrgent:NO]; + }); +} + +#pragma mark - Launch Failure Detection +- (NSString *)launchFailureMarkerPath { + return [[_fileManager structurePath] stringByAppendingPathComponent:@"launchmarker"]; +} + +- (BOOL)createLaunchFailureMarker { + // It's tempting to use - [NSFileManger createFileAtPath:contents:attributes:] here. But that + // operation, even with empty/nil contents does a ton of work to write out nothing via a + // temporarly file. This is a much faster implemenation. + const char *path = [[self launchFailureMarkerPath] fileSystemRepresentation]; + +#if TARGET_OS_IPHONE + /* + * data-protected non-portable open(2) : + * int open_dprotected_np(user_addr_t path, int flags, int class, int dpflags, int mode) + */ + int fd = open_dprotected_np(path, O_WRONLY | O_CREAT | O_TRUNC, 4, 0, 0644); +#else + int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); +#endif + if (fd == -1) { + return NO; + } + + return close(fd) == 0; +} + +- (BOOL)launchFailureMarkerPresent { + return [[_fileManager underlyingFileManager] fileExistsAtPath:[self launchFailureMarkerPath]]; +} + +- (BOOL)removeLaunchFailureMarker { + return [_fileManager removeItemAtPath:[self launchFailureMarkerPath]]; +} + +- (BOOL)checkForAndCreateLaunchMarker { + BOOL launchFailure = [self launchFailureMarkerPresent]; + if (launchFailure) { + FIRCLSDeveloperLog("Crashlytics:Crash", + @"Last launch failed: this may indicate a crash shortly after app launch."); + } else { + [self createLaunchFailureMarker]; + } + + return launchFailure; +} + +#pragma mark - + +- (void)registerAnalyticsEventListener { + if (_registeredAnalyticsEventListener) { + return; + } + FIRCLSAnalyticsInteropListener *listener = [[FIRCLSAnalyticsInteropListener alloc] init]; + [FIRCLSFCRAnalytics registerEventListener:listener toAnalytics:_analytics]; + _registeredAnalyticsEventListener = YES; +} + +#pragma mark - Notifications +- (void)setupStateNotifications { + [self captureInitialNotificationStates]; + +#if TARGET_OS_IOS + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(willBecomeActive:) + name:UIApplicationWillEnterForegroundNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(didBecomeInactive:) + name:UIApplicationDidEnterBackgroundNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(didChangeOrientation:) + name:UIDeviceOrientationDidChangeNotification + object:nil]; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(didChangeUIOrientation:) + name:UIApplicationDidChangeStatusBarOrientationNotification + object:nil]; +#pragma clang diagnostic pop + +#elif CLS_TARGET_OS_OSX + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(willBecomeActive:) + name:@"NSApplicationWillBecomeActiveNotification" + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(didBecomeInactive:) + name:@"NSApplicationDidResignActiveNotification" + object:nil]; +#endif +} + +- (void)captureInitialNotificationStates { +#if TARGET_OS_IOS + UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation]; + UIInterfaceOrientation statusBarOrientation = + [FIRCLSApplicationSharedInstance() statusBarOrientation]; +#endif + + // It's nice to do this async, so we don't hold up the main thread while doing three + // consecutive IOs here. + dispatch_async(FIRCLSGetLoggingQueue(), ^{ + FIRCLSUserLoggingWriteInternalKeyValue(FIRCLSInBackgroundKey, @"0"); +#if TARGET_OS_IOS + FIRCLSUserLoggingWriteInternalKeyValue(FIRCLSDeviceOrientationKey, + [@(orientation) description]); + FIRCLSUserLoggingWriteInternalKeyValue(FIRCLSUIOrientationKey, + [@(statusBarOrientation) description]); +#endif + }); +} + +- (void)willBecomeActive:(NSNotification *)notification { + FIRCLSUserLoggingRecordInternalKeyValue(FIRCLSInBackgroundKey, @NO); +} + +- (void)didBecomeInactive:(NSNotification *)notification { + FIRCLSUserLoggingRecordInternalKeyValue(FIRCLSInBackgroundKey, @YES); +} + +#if TARGET_OS_IOS +- (void)didChangeOrientation:(NSNotification *)notification { + UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation]; + + FIRCLSUserLoggingRecordInternalKeyValue(FIRCLSDeviceOrientationKey, @(orientation)); +} + +- (void)didChangeUIOrientation:(NSNotification *)notification { + UIInterfaceOrientation statusBarOrientation = + [FIRCLSApplicationSharedInstance() statusBarOrientation]; + + FIRCLSUserLoggingRecordInternalKeyValue(FIRCLSUIOrientationKey, @(statusBarOrientation)); +} +#endif + +#pragma mark - FIRCLSNetworkClientDelegate +- (BOOL)networkClientCanUseBackgroundSessions:(FIRCLSNetworkClient *)client { + return !FIRCLSApplicationIsExtension(); +} + +- (void)networkClient:(FIRCLSNetworkClient *)client + didFinishUploadWithPath:(NSString *)path + error:(NSError *)error { + // Route this through to the reports uploader. + // Since this callback happens after an upload finished, then we can assume that the original data + // collection was authorized. This isn't ideal, but it's better than trying to plumb the data + // collection token through all the system networking callbacks. + FIRCLSDataCollectionToken *token = [FIRCLSDataCollectionToken validToken]; + [[self uploader] reportUploadAtPath:path dataCollectionToken:token completedWithError:error]; +} + +#pragma mark - FIRCLSReportUploaderDelegate + +- (void)didCompletePackageSubmission:(NSString *)path + dataCollectionToken:(FIRCLSDataCollectionToken *)token + error:(NSError *)error { + if (!error) { + FIRCLSDeveloperLog("Crashlytics:Crash", @"report submission successful"); + return; + } + + FIRCLSDeveloperLog("Crashlytics:Crash", @"report submission failed with error %@", error); + FIRCLSSDKLog("Error: failed to submit report '%s'\n", error.description.UTF8String); + + [self retryUploadForReportAtPath:path dataCollectionToken:token]; +} + +- (void)didCompleteAllSubmissions { + [self.operationQueue addOperationWithBlock:^{ + // Dealloc the reports uploader. If we need it again (if we re-enqueued submissions from + // didCompletePackageSubmission:, we can just create it again + self->_uploader = nil; + + FIRCLSDeveloperLog("Crashlytics:Crash", @"report submission complete"); + }]; +} + +#pragma mark - UITest Helpers + +// Used only for internal data collection E2E testing +- (void)didSubmitReport { + if (reportSentCallback) { + dispatch_async(dispatch_get_main_queue(), ^{ + reportSentCallback(); + }); + } +} + ++ (void)setReportSentCallback:(void (^)(void))callback { + reportSentCallback = callback; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Controllers/FIRCLSReportManager_Private.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Controllers/FIRCLSReportManager_Private.h new file mode 100644 index 0000000..4940855 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Controllers/FIRCLSReportManager_Private.h @@ -0,0 +1,34 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSReportManager.h" +#import "FIRCLSReportUploader.h" + +@interface FIRCLSReportManager () + +@property(nonatomic, strong) NSOperationQueue *operationQueue; +@property(nonatomic, strong) FIRCLSNetworkClient *networkClient; +@property(nonatomic, readonly) FIRCLSReportUploader *uploader; +@property(nonatomic, strong) FIRCLSFileManager *fileManager; + +@end + +@interface FIRCLSReportManager (PrivateMethods) + +- (BOOL)createLaunchFailureMarker; +- (BOOL)launchFailureMarkerPresent; + +- (BOOL)potentiallySubmittableCrashOccurred; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader.h new file mode 100644 index 0000000..d1d9e96 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader.h @@ -0,0 +1,77 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +@class FIRCLSDataCollectionToken; +@class FIRCLSInternalReport; +@class FIRCLSSettings; +@class FIRCLSFileManager; +@class FIRCLSNetworkClient; +@class FIRCLSReportUploader; + +@protocol FIRCLSReportUploaderDelegate; +@protocol FIRCLSReportUploaderDataSource; +@protocol FIRAnalyticsInterop; + +@interface FIRCLSReportUploader : NSObject + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; +- (instancetype)initWithQueue:(NSOperationQueue *)queue + delegate:(id)delegate + dataSource:(id)dataSource + client:(FIRCLSNetworkClient *)client + fileManager:(FIRCLSFileManager *)fileManager + analytics:(id)analytics NS_DESIGNATED_INITIALIZER; + +@property(nonatomic, weak) id delegate; +@property(nonatomic, weak) id dataSource; + +@property(nonatomic, readonly) NSOperationQueue *operationQueue; +@property(nonatomic, readonly) FIRCLSNetworkClient *networkClient; +@property(nonatomic, readonly) FIRCLSFileManager *fileManager; + +- (BOOL)prepareAndSubmitReport:(FIRCLSInternalReport *)report + dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken + asUrgent:(BOOL)urgent + withProcessing:(BOOL)shouldProcess; + +- (BOOL)uploadPackagedReportAtPath:(NSString *)path + dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken + asUrgent:(BOOL)urgent; + +- (void)reportUploadAtPath:(NSString *)path + dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken + completedWithError:(NSError *)error; + +@end + +@protocol FIRCLSReportUploaderDelegate +@required + +- (void)didCompletePackageSubmission:(NSString *)path + dataCollectionToken:(FIRCLSDataCollectionToken *)token + error:(NSError *)error; +- (void)didCompleteAllSubmissions; + +@end + +@protocol FIRCLSReportUploaderDataSource +@required + +- (NSString *)googleAppID; +- (FIRCLSSettings *)settings; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader.m new file mode 100644 index 0000000..aa837b2 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader.m @@ -0,0 +1,274 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +#import "FIRCLSApplication.h" +#import "FIRCLSDataCollectionArbiter.h" +#import "FIRCLSDataCollectionToken.h" +#import "FIRCLSDefines.h" +#import "FIRCLSFCRAnalytics.h" +#import "FIRCLSFileManager.h" +#import "FIRCLSInstallIdentifierModel.h" +#import "FIRCLSInternalReport.h" +#import "FIRCLSNetworkClient.h" +#import "FIRCLSPackageReportOperation.h" +#import "FIRCLSProcessReportOperation.h" +#import "FIRCLSReportUploader_Private.h" +#import "FIRCLSSettings.h" +#import "FIRCLSSymbolResolver.h" + +#include "FIRCLSUtility.h" + +#import "FIRCLSConstants.h" +#import "FIRCLSMultipartMimeStreamEncoder.h" +#import "FIRCLSURLBuilder.h" + +@interface FIRCLSReportUploader () { + id _analytics; +} +@end + +@implementation FIRCLSReportUploader + +- (instancetype)initWithQueue:(NSOperationQueue *)queue + delegate:(id)delegate + dataSource:(id)dataSource + client:(FIRCLSNetworkClient *)client + fileManager:(FIRCLSFileManager *)fileManager + analytics:(id)analytics { + self = [super init]; + if (!self) { + return nil; + } + + _operationQueue = queue; + _delegate = delegate; + _dataSource = dataSource; + _networkClient = client; + _fileManager = fileManager; + _analytics = analytics; + + return self; +} + +#pragma mark - Properties + +- (NSURL *)reportURL { + FIRCLSURLBuilder *url = [FIRCLSURLBuilder URLWithBase:FIRCLSReportsEndpoint]; + + [url appendComponent:@"/sdk-api/v1/platforms/"]; + [url appendComponent:FIRCLSApplicationGetPlatform()]; + [url appendComponent:@"/apps/"]; + [url appendComponent:self.dataSource.settings.fetchedBundleID]; + [url appendComponent:@"/reports"]; + + return [url URL]; +} + +- (NSString *)localeIdentifier { + return [[NSLocale currentLocale] localeIdentifier]; +} + +#pragma mark - URL Requests +- (NSMutableURLRequest *)mutableRequestWithURL:(NSURL *)url timeout:(NSTimeInterval)timeout { + NSMutableURLRequest *request = + [NSMutableURLRequest requestWithURL:url + cachePolicy:NSURLRequestReloadIgnoringLocalCacheData + timeoutInterval:timeout]; + + NSString *localeId = [self localeIdentifier]; + + [request setValue:@CLS_SDK_GENERATOR_NAME forHTTPHeaderField:FIRCLSNetworkUserAgent]; + [request setValue:FIRCLSNetworkApplicationJson forHTTPHeaderField:FIRCLSNetworkAccept]; + [request setValue:FIRCLSNetworkUTF8 forHTTPHeaderField:FIRCLSNetworkAcceptCharset]; + [request setValue:localeId forHTTPHeaderField:FIRCLSNetworkAcceptLanguage]; + [request setValue:localeId forHTTPHeaderField:FIRCLSNetworkContentLanguage]; + [request setValue:FIRCLSDeveloperToken forHTTPHeaderField:FIRCLSNetworkCrashlyticsDeveloperToken]; + [request setValue:FIRCLSApplicationGetSDKBundleID() + forHTTPHeaderField:FIRCLSNetworkCrashlyticsAPIClientId]; + [request setValue:@CLS_SDK_DISPLAY_VERSION + forHTTPHeaderField:FIRCLSNetworkCrashlyticsAPIClientDisplayVersion]; + [request setValue:[[self dataSource] googleAppID] + forHTTPHeaderField:FIRCLSNetworkCrashlyticsGoogleAppId]; + + return request; +} + +- (BOOL)fillInRequest:(NSMutableURLRequest *)request forMultipartMimeDataAtPath:(NSString *)path { + NSString *boundary = [[path lastPathComponent] stringByDeletingPathExtension]; + + [request setValue:[FIRCLSMultipartMimeStreamEncoder + contentTypeHTTPHeaderValueWithBoundary:boundary] + forHTTPHeaderField:@"Content-Type"]; + + NSNumber *fileSize = [[self fileManager] fileSizeAtPath:path]; + if (fileSize == nil) { + FIRCLSErrorLog(@"Could not determine size of multipart mime file"); + return NO; + } + + [request setValue:[fileSize stringValue] forHTTPHeaderField:@"Content-Length"]; + + return YES; +} + +#pragma mark - Packaging and Submission +- (BOOL)prepareAndSubmitReport:(FIRCLSInternalReport *)report + dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken + asUrgent:(BOOL)urgent + withProcessing:(BOOL)shouldProcess { + __block BOOL success = NO; + + if (![dataCollectionToken isValid]) { + FIRCLSErrorLog(@"Data collection disabled and report will not be submitted"); + return NO; + } + + NSString *reportOrgID = self.dataSource.settings.orgID; + if (!reportOrgID) { + FIRCLSDebugLog(@"Skipping report with id '%@' this run of the app because Organization ID was " + @"nil. Report will upload once settings are download successfully", + report.identifier); + return YES; + } + + FIRCLSApplicationActivity( + FIRCLSApplicationActivityDefault, @"Crashlytics Crash Report Processing", ^{ + if (shouldProcess) { + if (![self.fileManager moveItemAtPath:[report path] + toDirectory:self.fileManager.processingPath]) { + FIRCLSErrorLog(@"Unable to move report for processing"); + return; + } + + // adjust the report's path, and process it + [report setPath:[self.fileManager.processingPath + stringByAppendingPathComponent:report.directoryName]]; + + FIRCLSSymbolResolver *resolver = [[FIRCLSSymbolResolver alloc] init]; + + FIRCLSProcessReportOperation *processOperation = + [[FIRCLSProcessReportOperation alloc] initWithReport:report resolver:resolver]; + + [processOperation start]; + } + + FIRCLSPackageReportOperation *packageOperation = + [[FIRCLSPackageReportOperation alloc] initWithReport:report + fileManager:self.fileManager + settings:self.dataSource.settings]; + + [packageOperation start]; + + NSString *packagedPath = [packageOperation finalPath]; + if (!packagedPath) { + FIRCLSErrorLog(@"Unable to package report"); + return; + } + + // Save the crashed on date for potential scion event sending. + NSTimeInterval crashedOnDate = report.crashedOnDate.timeIntervalSince1970; + // We only want to forward crash events to scion, storing this for later use. + BOOL isCrash = report.isCrash; + + if (![[self fileManager] removeItemAtPath:[report path]]) { + FIRCLSErrorLog(@"Unable to remove a processing item"); + } + + NSLog(@"[Firebase/Crashlytics] Packaged report with id '%@' for submission", + report.identifier); + + success = [self uploadPackagedReportAtPath:packagedPath + dataCollectionToken:dataCollectionToken + asUrgent:urgent]; + + // If the upload was successful and the report contained a crash forward it to scion. + if (success && isCrash) { + [FIRCLSFCRAnalytics logCrashWithTimeStamp:crashedOnDate toAnalytics:self->_analytics]; + } + }); + + return success; +} + +- (BOOL)submitPackageMultipartMimeAtPath:(NSString *)multipartmimePath + dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken + synchronously:(BOOL)synchronous { + FIRCLSDeveloperLog(@"Crashlytics:Crash:Reports", "Submitting %@ %@", + synchronous ? @"sync" : @"async", multipartmimePath); + + if ([[[self fileManager] fileSizeAtPath:multipartmimePath] unsignedIntegerValue] == 0) { + FIRCLSDeveloperLog("Crashlytics:Crash:Reports", @"Already-submitted report being ignored"); + return NO; + } + + NSTimeInterval timeout = 10.0; + + // If we are submitting synchronously, be more aggressive with the timeout. However, + // we only need this if the client does not support background requests. + if (synchronous && ![[self networkClient] supportsBackgroundRequests]) { + timeout = 2.0; + } + + NSMutableURLRequest *request = [self mutableRequestWithURL:[self reportURL] timeout:timeout]; + + [request setHTTPMethod:@"POST"]; + + if (![self fillInRequest:request forMultipartMimeDataAtPath:multipartmimePath]) { + return NO; + } + + [[self networkClient] startUploadRequest:request + filePath:multipartmimePath + dataCollectionToken:dataCollectionToken + immediately:synchronous]; + + return YES; +} + +- (BOOL)uploadPackagedReportAtPath:(NSString *)path + dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken + asUrgent:(BOOL)urgent { + FIRCLSDeveloperLog("Crashlytics:Crash:Reports", @"Submitting report%@", + urgent ? @" as urgent" : @""); + return [self submitPackageMultipartMimeAtPath:path + dataCollectionToken:dataCollectionToken + synchronously:urgent]; +} + +- (BOOL)cleanUpSubmittedReportAtPath:(NSString *)path { + if (![[self fileManager] removeItemAtPath:path]) { + FIRCLSErrorLog(@"Unable to remove packaged submission"); + return NO; + } + + return YES; +} + +- (void)reportUploadAtPath:(NSString *)path + dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken + completedWithError:(NSError *)error { + FIRCLSDeveloperLog("Crashlytics:Crash:Reports", @"completed submission of %@", path); + + if (!error) { + [self cleanUpSubmittedReportAtPath:path]; + } + + [[self delegate] didCompletePackageSubmission:path + dataCollectionToken:dataCollectionToken + error:error]; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader_Private.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader_Private.h new file mode 100644 index 0000000..f54dc16 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader_Private.h @@ -0,0 +1,23 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSReportUploader.h" + +@interface FIRCLSReportUploader (PrivateMethods) + +@property(nonatomic, readonly) NSURL *reportURL; + +- (NSMutableURLRequest *)mutableRequestWithURL:(NSURL *)url timeout:(NSTimeInterval)timeout; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionArbiter.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionArbiter.h new file mode 100644 index 0000000..147bc7b --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionArbiter.h @@ -0,0 +1,39 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +@class FIRApp; +@class FBLPromise; + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRCLSDataCollectionArbiter : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithApp:(FIRApp *)app withAppInfo:(NSDictionary *)dict; + +- (BOOL)isLegacyDataCollectionKeyInPlist; + +- (BOOL)isCrashlyticsCollectionEnabled; + +- (void)setCrashlyticsCollectionEnabled:(BOOL)enabled; + +// Returns a promise that is fulfilled once data collection is enabled. +- (FBLPromise *)waitForCrashlyticsCollectionEnabled; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionArbiter.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionArbiter.m new file mode 100644 index 0000000..3d40af2 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionArbiter.m @@ -0,0 +1,148 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSDataCollectionArbiter.h" + +#if __has_include() +#import +#else +#import "FBLPromises.h" +#endif + +#import + +#import "FIRCLSUserDefaults.h" + +// The legacy data collection setting allows Fabric customers to turn off auto- +// initialization, but can be overridden by calling [Fabric with:]. +// +// While we support Fabric, we must have two different versions, because +// they require these slightly different semantics. +NSString *const FIRCLSLegacyCrashlyticsCollectionKey = @"firebase_crashlytics_collection_enabled"; + +// The new data collection setting can be set by an API that is stored in FIRCLSUserDefaults +NSString *const FIRCLSDataCollectionEnabledKey = @"com.crashlytics.data_collection"; + +// The new data collection setting also allows Firebase customers to turn off data +// collection in their Info.plist, and can be overridden by setting it to true using +// the setCrashlyticsCollectionEnabled API. +NSString *const FIRCLSCrashlyticsCollectionKey = @"FirebaseCrashlyticsCollectionEnabled"; + +typedef NS_ENUM(NSInteger, FIRCLSDataCollectionSetting) { + FIRCLSDataCollectionSettingNotSet = 0, + FIRCLSDataCollectionSettingEnabled = 1, + FIRCLSDataCollectionSettingDisabled = 2, +}; + +@interface FIRCLSDataCollectionArbiter () { + NSLock *_mutex; + FBLPromise *_dataCollectionEnabled; + BOOL _promiseResolved; + FIRApp *_app; + NSDictionary *_appInfo; +} +@end + +@implementation FIRCLSDataCollectionArbiter + +- (instancetype)initWithApp:(FIRApp *)app withAppInfo:(NSDictionary *)dict { + self = [super init]; + if (self) { + _mutex = [[NSLock alloc] init]; + _appInfo = dict; + _app = app; + if ([FIRCLSDataCollectionArbiter isCrashlyticsCollectionEnabledWithApp:app withAppInfo:dict]) { + _dataCollectionEnabled = [FBLPromise resolvedWith:nil]; + _promiseResolved = YES; + } else { + _dataCollectionEnabled = [FBLPromise pendingPromise]; + _promiseResolved = NO; + } + } + + return self; +} + +/* + * Legacy collection key that we provide for customers to disable Crash reporting. + * Customers can later turn on Crashlytics using Fabric.with if they choose to do so. + * + * This flag is unsupported for the "New SDK" + */ +- (BOOL)isLegacyDataCollectionKeyInPlist { + if ([_appInfo objectForKey:FIRCLSLegacyCrashlyticsCollectionKey]) { + return true; + } + + return false; +} + +// This functionality is called in the initializer before self is fully initialized, +// so a class method is used. The instance method below allows for a consistent clean API. ++ (BOOL)isCrashlyticsCollectionEnabledWithApp:(FIRApp *)app withAppInfo:(NSDictionary *)dict { + FIRCLSDataCollectionSetting stickySetting = [FIRCLSDataCollectionArbiter stickySetting]; + if (stickySetting != FIRCLSDataCollectionSettingNotSet) { + return stickySetting == FIRCLSDataCollectionSettingEnabled; + } + + id firebaseCrashlyticsCollectionEnabled = [dict objectForKey:FIRCLSCrashlyticsCollectionKey]; + if ([firebaseCrashlyticsCollectionEnabled isKindOfClass:[NSString class]] || + [firebaseCrashlyticsCollectionEnabled isKindOfClass:[NSNumber class]]) { + return [firebaseCrashlyticsCollectionEnabled boolValue]; + } + + return [app isDataCollectionDefaultEnabled]; +} + +- (BOOL)isCrashlyticsCollectionEnabled { + return [FIRCLSDataCollectionArbiter isCrashlyticsCollectionEnabledWithApp:_app + withAppInfo:_appInfo]; +} + +- (void)setCrashlyticsCollectionEnabled:(BOOL)enabled { + FIRCLSUserDefaults *userDefaults = [FIRCLSUserDefaults standardUserDefaults]; + FIRCLSDataCollectionSetting setting = + enabled ? FIRCLSDataCollectionSettingEnabled : FIRCLSDataCollectionSettingDisabled; + [userDefaults setInteger:setting forKey:FIRCLSDataCollectionEnabledKey]; + [userDefaults synchronize]; + + [_mutex lock]; + if (enabled) { + if (!_promiseResolved) { + [_dataCollectionEnabled fulfill:nil]; + _promiseResolved = YES; + } + } else { + if (_promiseResolved) { + _dataCollectionEnabled = [FBLPromise pendingPromise]; + _promiseResolved = NO; + } + } + [_mutex unlock]; +} + ++ (FIRCLSDataCollectionSetting)stickySetting { + FIRCLSUserDefaults *userDefaults = [FIRCLSUserDefaults standardUserDefaults]; + return [userDefaults integerForKey:FIRCLSDataCollectionEnabledKey]; +} + +- (FBLPromise *)waitForCrashlyticsCollectionEnabled { + FBLPromise *result = nil; + [_mutex lock]; + result = _dataCollectionEnabled; + [_mutex unlock]; + return result; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionToken.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionToken.h new file mode 100644 index 0000000..4ab2bb6 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionToken.h @@ -0,0 +1,45 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * A FIRCLSDataCollectionToken represents having permission to upload data. A data collection token + * is either valid or nil. Every function that directly initiates a network operation that will + * result in data collection must check to make sure it has been passed a valid token. Tokens should + * only be created when either (1) automatic data collection is enabled, or (2) the user has + * explicitly given permission to collect data for a particular purpose, using the API. For all the + * functions in between, the data collection token getting passed as an argument helps to document + * and enforce the flow of data collection permission through the SDK. + */ +@interface FIRCLSDataCollectionToken : NSObject + +/** + * Creates a valid token. Only call this method when either (1) automatic data collection is + * enabled, or (2) the user has explicitly given permission to collect data for a particular + * purpose, using the API. + */ ++ (instancetype)validToken; + +/** + * Use this to verify that a token is valid. If this is called on a nil instance, it will return NO. + * @return YES. + */ +- (BOOL)isValid; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionToken.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionToken.m new file mode 100644 index 0000000..1a41ee1 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionToken.m @@ -0,0 +1,27 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSDataCollectionToken.h" + +@implementation FIRCLSDataCollectionToken + ++ (instancetype)validToken { + return [[FIRCLSDataCollectionToken alloc] init]; +} + +- (BOOL)isValid { + return YES; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/FIRCLSURLSession.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/FIRCLSURLSession.h new file mode 100644 index 0000000..fa53468 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/FIRCLSURLSession.h @@ -0,0 +1,84 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSURLSessionAvailability.h" + +#if FIRCLSURLSESSION_REQUIRED + +#import "FIRCLSURLSessionConfiguration.h" + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRCLSURLSession : NSObject { + id _delegate; + NSOperationQueue *_delegateQueue; + NSURLSessionConfiguration *_configuration; + NSMutableSet *_taskSet; + dispatch_queue_t _queue; + + NSString *_sessionDescription; +} + ++ (BOOL)NSURLSessionShouldBeUsed; + ++ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration; ++ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration + delegate:(nullable id)delegate + delegateQueue:(nullable NSOperationQueue *)queue; + +@property(nonatomic, readonly, retain) NSOperationQueue *delegateQueue; +@property(nonatomic, readonly, retain) id delegate; +@property(nonatomic, readonly, copy) NSURLSessionConfiguration *configuration; + +@property(nonatomic, copy) NSString *sessionDescription; + +- (void)getTasksWithCompletionHandler: + (void (^)(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks))completionHandler; + +// task creation - suitable for background operations +- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL; + +- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request; +- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url; + +// convenience methods (that are not available for background sessions +- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request + completionHandler:(nullable void (^)(NSData *data, + NSURLResponse *response, + NSError *error))completionHandler; +- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request; + +- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request + completionHandler: + (nullable void (^)(NSURL *targetPath, + NSURLResponse *response, + NSError *error))completionHandler; + +- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request + fromFile:(NSURL *)fileURL + completionHandler: + (nullable void (^)(NSData *data, + NSURLResponse *response, + NSError *error))completionHandler; + +- (void)invalidateAndCancel; +- (void)finishTasksAndInvalidate; + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/FIRCLSURLSession.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/FIRCLSURLSession.m new file mode 100644 index 0000000..9815423 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/FIRCLSURLSession.m @@ -0,0 +1,346 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSURLSessionAvailability.h" + +#if FIRCLSURLSESSION_REQUIRED +#import "FIRCLSURLSession.h" + +#import "FIRCLSURLSessionDataTask.h" +#import "FIRCLSURLSessionDataTask_PrivateMethods.h" +#import "FIRCLSURLSessionDownloadTask.h" +#import "FIRCLSURLSessionDownloadTask_PrivateMethods.h" +#import "FIRCLSURLSessionTask_PrivateMethods.h" +#import "FIRCLSURLSessionUploadTask.h" + +#define DELEGATE ((id)self->_delegate) + +@interface FIRCLSURLSession () + +@property(nonatomic, retain) NSOperationQueue *delegateQueue; +@property(nonatomic, retain) id delegate; +@property(nonatomic, copy) NSURLSessionConfiguration *configuration; + +@end + +@implementation FIRCLSURLSession + +@synthesize delegate = _delegate; +@synthesize delegateQueue = _delegateQueue; +@synthesize configuration = _configuration; +@synthesize sessionDescription = _sessionDescription; + ++ (BOOL)NSURLSessionShouldBeUsed { + if (!NSClassFromString(@"NSURLSession")) { + return NO; + } + + // We use this as a proxy to verify that we are on at least iOS 8 or 10.10. The first OSes that + // has NSURLSession were fairly unstable. + return [[NSURLSessionConfiguration class] + respondsToSelector:@selector(backgroundSessionConfigurationWithIdentifier:)]; +} + ++ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration { + return [self sessionWithConfiguration:configuration delegate:nil delegateQueue:nil]; +} + ++ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration + delegate:(nullable id)delegate + delegateQueue:(nullable NSOperationQueue *)queue { + if ([self NSURLSessionShouldBeUsed]) { + return [NSURLSession sessionWithConfiguration:configuration + delegate:delegate + delegateQueue:queue]; + } + + if (!configuration) { + return nil; + } + +#if __has_feature(objc_arc) + FIRCLSURLSession *session = [self new]; +#else + FIRCLSURLSession *session = [[self new] autorelease]; +#endif + [session setDelegate:delegate]; + // When delegate exists, but delegateQueue is nil, create a serial queue like NSURLSession + // documents. + if (delegate && !queue) { + queue = [self newDefaultDelegateQueue]; + } + session.delegateQueue = queue; + session.configuration = configuration; + return (NSURLSession *)session; +} + ++ (NSOperationQueue *)newDefaultDelegateQueue { + NSOperationQueue *delegateQueue = [[NSOperationQueue alloc] init]; + delegateQueue.name = [NSString stringWithFormat:@"%@ %p", NSStringFromClass(self), self]; + delegateQueue.maxConcurrentOperationCount = 1; + return delegateQueue; +} + +- (instancetype)init { + self = [super init]; + if (!self) { + return nil; + } + + _queue = dispatch_queue_create("com.crashlytics.URLSession", 0); + + return self; +} + +#if !__has_feature(objc_arc) +- (void)dealloc { + [_taskSet release]; + [_delegate release]; + [_delegateQueue release]; + [_configuration release]; + +#if !OS_OBJECT_USE_OBJC + dispatch_release(_queue); +#endif + + [super dealloc]; +} +#endif + +#pragma mark - Managing the Session + +- (void)invalidateAndCancel { + dispatch_sync(_queue, ^{ + for (FIRCLSURLSessionTask *task in self->_taskSet) { + [task cancel]; + } + }); + + self.delegate = nil; +} + +- (void)finishTasksAndInvalidate { + self.delegate = nil; +} + +#pragma mark - + +- (void)getTasksWithCompletionHandler: + (void (^)(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks))completionHandler { + [[self delegateQueue] addOperationWithBlock:^{ + // TODO - this is totally wrong, but better than not calling back at all + completionHandler(@[], @[], @[]); + }]; +} + +- (void)removeTaskFromSet:(FIRCLSURLSessionTask *)task { + dispatch_async(_queue, ^{ + [self->_taskSet removeObject:task]; + }); +} + +- (void)configureTask:(FIRCLSURLSessionTask *)task + withRequest:(NSURLRequest *)request + block:(void (^)(NSMutableURLRequest *mutableRequest))block { + NSMutableURLRequest *modifiedRequest = [request mutableCopy]; + + dispatch_sync(_queue, ^{ + [self->_taskSet addObject:task]; + + // TODO: this isn't allowed to overwrite existing headers + for (NSString *key in [self->_configuration HTTPAdditionalHeaders]) { + [modifiedRequest addValue:[[self->_configuration HTTPAdditionalHeaders] objectForKey:key] + forHTTPHeaderField:key]; + } + }); + + if (block) { + block(modifiedRequest); + } + + [task setOriginalRequest:modifiedRequest]; + [task setDelegate:self]; + +#if !__has_feature(objc_arc) + [modifiedRequest release]; +#endif +} + +- (BOOL)shouldInvokeDelegateSelector:(SEL)selector forTask:(FIRCLSURLSessionTask *)task { + return [task invokesDelegate] && [_delegate respondsToSelector:selector]; +} + +#pragma mark Task Creation +- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request + fromFile:(NSURL *)fileURL { + return [self uploadTaskWithRequest:request fromFile:fileURL completionHandler:nil]; +} + +- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request { + return [self downloadTaskWithRequest:request completionHandler:nil]; +} + +- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url { + return [self downloadTaskWithRequest:[NSURLRequest requestWithURL:url]]; +} + +#pragma mark Async Convenience Methods +- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request + completionHandler:(nullable void (^)(NSData *data, + NSURLResponse *response, + NSError *error))completionHandler { + FIRCLSURLSessionDataTask *task = [FIRCLSURLSessionDataTask task]; + + if (completionHandler) { + [task setCompletionHandler:completionHandler]; + [task setInvokesDelegate:NO]; + } + + [self configureTask:task withRequest:request block:nil]; + + return (NSURLSessionDataTask *)task; +} + +- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request { + return [self dataTaskWithRequest:request completionHandler:nil]; +} + +- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request + fromFile:(NSURL *)fileURL + completionHandler: + (nullable void (^)(NSData *data, + NSURLResponse *response, + NSError *error))completionHandler { + FIRCLSURLSessionUploadTask *task = [FIRCLSURLSessionUploadTask task]; + + if (completionHandler) { + [task setCompletionHandler:completionHandler]; + [task setInvokesDelegate:NO]; + } + + [self configureTask:task + withRequest:request + block:^(NSMutableURLRequest *mutableRequest) { + // you cannot set up both of these, and we'll be using the stream here + [mutableRequest setHTTPBody:nil]; + [mutableRequest setHTTPBodyStream:[NSInputStream inputStreamWithURL:fileURL]]; + }]; + + return (NSURLSessionUploadTask *)task; +} + +- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request + completionHandler: + (nullable void (^)(NSURL *targetPath, + NSURLResponse *response, + NSError *error))completionHandler { + FIRCLSURLSessionDownloadTask *task = [FIRCLSURLSessionDownloadTask task]; + + if (completionHandler) { + [task setDownloadCompletionHandler:completionHandler]; + [task setInvokesDelegate:NO]; + } + + [self configureTask:task withRequest:request block:nil]; + + return (NSURLSessionDownloadTask *)task; +} + +#pragma mark FIRCLSURLSessionTaskDelegate +- (NSURLRequest *)task:(FIRCLSURLSessionTask *)task + willPerformHTTPRedirection:(NSHTTPURLResponse *)response + newRequest:(NSURLRequest *)request { + // just accept the proposed redirection + return request; +} + +- (void)task:(FIRCLSURLSessionTask *)task didCompleteWithError:(NSError *)error { + if (![self shouldInvokeDelegateSelector:@selector(URLSession:task:didCompleteWithError:) + forTask:task]) { + [self removeTaskFromSet:task]; + return; + } + + [_delegateQueue addOperationWithBlock:^{ + [DELEGATE URLSession:(NSURLSession *)self + task:(NSURLSessionTask *)task + didCompleteWithError:error]; + + // Note that you *cannot* clean up here, because this method could be run asynchronously with + // the delegate methods that care about the state of the task + [self removeTaskFromSet:task]; + }]; +} + +#pragma mark FIRCLSURLSessionDataTask +- (void)task:(FIRCLSURLSessionDataTask *)task didReceiveResponse:(NSURLResponse *)response { + if (![self shouldInvokeDelegateSelector:@selector + (URLSession:dataTask:didReceiveResponse:completionHandler:) + forTask:task]) { + return; + } + + [_delegateQueue addOperationWithBlock:^{ + [DELEGATE URLSession:(NSURLSession *)self + dataTask:(NSURLSessionDataTask *)task + didReceiveResponse:response + completionHandler:^(NSURLSessionResponseDisposition disposition){ + // nothing to do here + }]; + }]; +} + +- (void)task:(FIRCLSURLSessionDataTask *)task didReceiveData:(NSData *)data { + if (![self shouldInvokeDelegateSelector:@selector(URLSession:dataTask:didReceiveData:) + forTask:task]) { + return; + } + + [_delegateQueue addOperationWithBlock:^{ + [DELEGATE URLSession:(NSURLSession *)self + dataTask:(NSURLSessionDataTask *)task + didReceiveData:data]; + }]; +} + +#pragma mark FIRCLSURLSessionDownloadDelegate +- (void)downloadTask:(FIRCLSURLSessionDownloadTask *)task didFinishDownloadingToURL:(NSURL *)url { + if (![self shouldInvokeDelegateSelector:@selector(URLSession: + downloadTask:didFinishDownloadingToURL:) + forTask:task]) { + // We have to be certain that we cleanup only once the delegate no longer cares about the state + // of the task being changed. In the case of download, this is either after the delegate method + // has been invoked, or here, if the delegate doesn't care. + [task cleanup]; + return; + } + + [_delegateQueue addOperationWithBlock:^{ + [DELEGATE URLSession:(NSURLSession *)self + downloadTask:(NSURLSessionDownloadTask *)task + didFinishDownloadingToURL:url]; + + // Cleanup for the download tasks is a little complex. As long as we do it only after + // the delegate has been informed of the completed download, we are ok. + [task cleanup]; + }]; +} + +@end + +#else + +INJECT_STRIP_SYMBOL(clsurlsession) + +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/FIRCLSURLSessionAvailability.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/FIRCLSURLSessionAvailability.h new file mode 100644 index 0000000..0565948 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/FIRCLSURLSessionAvailability.h @@ -0,0 +1,28 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +#define FIRCLSURLSESSION_REQUIRED (!TARGET_OS_WATCH && !TARGET_OS_TV) + +// These macros generate a function to force a symbol for the containing .o, to work around an issue +// where strip will not strip debug information without a symbol to strip. +#define CONCAT_EXPANDED(a, b) a##b +#define CONCAT(a, b) CONCAT_EXPANDED(a, b) +#define DUMMY_FUNCTION_NAME(x) CONCAT(strip_this_, x) +#define INJECT_STRIP_SYMBOL(x) \ + void DUMMY_FUNCTION_NAME(x)(void) { \ + } diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/FIRCLSURLSessionConfiguration.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/FIRCLSURLSessionConfiguration.h new file mode 100644 index 0000000..bda0f70 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/FIRCLSURLSessionConfiguration.h @@ -0,0 +1,42 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSURLSessionAvailability.h" + +#if FIRCLSURLSESSION_REQUIRED + +#import + +@interface FIRCLSURLSessionConfiguration : NSObject { + NSDictionary *_additionalHeaders; + NSURLCache *_URLCache; + NSHTTPCookieAcceptPolicy _cookiePolicy; +} + ++ (NSURLSessionConfiguration *)defaultSessionConfiguration; ++ (NSURLSessionConfiguration *)ephemeralSessionConfiguration; ++ (NSURLSessionConfiguration *)backgroundSessionConfiguration:(NSString *)identifier; ++ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier; + +@property(nonatomic, copy) NSDictionary *HTTPAdditionalHeaders; +@property(nonatomic, retain) NSURLCache *URLCache; +@property(nonatomic, assign) NSHTTPCookieAcceptPolicy HTTPCookieAcceptPolicy; +@property(nonatomic, assign) BOOL sessionSendsLaunchEvents; +@property(nonatomic, assign) NSTimeInterval timeoutIntervalForRequest; +@property(nonatomic, assign) NSTimeInterval timeoutIntervalForResource; +@property(nonatomic, assign) BOOL allowsCellularAccess; + +@end + +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/FIRCLSURLSessionConfiguration.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/FIRCLSURLSessionConfiguration.m new file mode 100644 index 0000000..177e7a6 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/FIRCLSURLSessionConfiguration.m @@ -0,0 +1,92 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSURLSessionAvailability.h" + +#import "FIRCLSURLSession.h" + +#if FIRCLSURLSESSION_REQUIRED +#import "FIRCLSURLSessionConfiguration.h" + +@implementation FIRCLSURLSessionConfiguration + +@synthesize URLCache = _URLCache; +@synthesize HTTPAdditionalHeaders = _additionalHeaders; +@synthesize HTTPCookieAcceptPolicy = _cookiePolicy; + ++ (NSURLSessionConfiguration *)defaultSessionConfiguration { + if ([FIRCLSURLSession NSURLSessionShouldBeUsed]) { + return [NSURLSessionConfiguration defaultSessionConfiguration]; + } + +#if __has_feature(objc_arc) + return [self new]; +#else + return [[self new] autorelease]; +#endif +} + ++ (NSURLSessionConfiguration *)ephemeralSessionConfiguration { + if ([FIRCLSURLSession NSURLSessionShouldBeUsed]) { + return [NSURLSessionConfiguration ephemeralSessionConfiguration]; + } + +#if __has_feature(objc_arc) + return [self new]; +#else + return [[self new] autorelease]; +#endif +} + ++ (NSURLSessionConfiguration *)backgroundSessionConfiguration:(NSString *)identifier { + return [self backgroundSessionConfigurationWithIdentifier:identifier]; +} + ++ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier { + if (![FIRCLSURLSession NSURLSessionShouldBeUsed]) { + return nil; + } + + if ([[NSURLSessionConfiguration class] + respondsToSelector:@selector(backgroundSessionConfigurationWithIdentifier:)]) { + return [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifier]; + } + + return [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifier]; +} + +- (id)copyWithZone:(NSZone *)zone { + FIRCLSURLSessionConfiguration *configuration; + + configuration = [FIRCLSURLSessionConfiguration new]; + [configuration setHTTPAdditionalHeaders:[self HTTPAdditionalHeaders]]; + + return configuration; +} + +// This functionality is not supported by the wrapper, so we just stub it out +- (BOOL)sessionSendsLaunchEvents { + return NO; +} + +- (void)setSessionSendsLaunchEvents:(BOOL)sessionSendsLaunchEvents { +} + +@end + +#else + +INJECT_STRIP_SYMBOL(clsurlsessionconfiguration) + +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/FIRCLSURLSession_PrivateMethods.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/FIRCLSURLSession_PrivateMethods.h new file mode 100644 index 0000000..84885e1 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/FIRCLSURLSession_PrivateMethods.h @@ -0,0 +1,27 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSURLSessionAvailability.h" + +#if FIRCLSURLSESSION_REQUIRED + +#import + +@interface FIRCLSURLSession (PrivateMethods) + +- (void)runOnDelegateQueue:(void (^)(void))block; + +@end + +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionDataTask.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionDataTask.h new file mode 100644 index 0000000..2209958 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionDataTask.h @@ -0,0 +1,32 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSURLSessionAvailability.h" + +#if FIRCLSURLSESSION_REQUIRED + +#import "FIRCLSURLSessionTask.h" + +@interface FIRCLSURLSessionDataTask : FIRCLSURLSessionTask { + void (^_completionHandler)(NSData *data, NSURLResponse *response, NSError *error); + NSURLConnection *_connection; + NSMutableData *_data; + NSString *_taskDescription; +} + +@property(nonatomic, copy) NSString *taskDescription; + +@end + +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionDataTask.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionDataTask.m new file mode 100644 index 0000000..be40ab7 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionDataTask.m @@ -0,0 +1,124 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSURLSessionAvailability.h" + +#if FIRCLSURLSESSION_REQUIRED +#import "FIRCLSURLSessionDataTask.h" + +#import "FIRCLSURLSessionDataTask_PrivateMethods.h" + +#define DELEGATE ((id)[self delegate]) + +@interface FIRCLSURLSessionDataTask () +@end + +@implementation FIRCLSURLSessionDataTask + +@synthesize connection = _connection; +@synthesize completionHandler = _completionHandler; +@synthesize taskDescription = _taskDescription; + +#if !__has_feature(objc_arc) +- (void)dealloc { + [_connection release]; + [_completionHandler release]; + [_taskDescription release]; + [_data release]; + + [super dealloc]; +} +#endif + +- (void)resume { + dispatch_async([self queue], ^{ + NSURLConnection *connection; + + if ([self connection]) { + return; + } + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + connection = [[NSURLConnection alloc] initWithRequest:[self originalRequest] + delegate:self + startImmediately:NO]; +#pragma clang diagnostic pop + + [self setConnection:connection]; + + // bummer we have to do this on a runloop, but other mechanisms require iOS 5 or 10.7 + [connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; +#if !__has_feature(objc_arc) + [connection release]; +#endif + [connection start]; + }); +} + +- (void)complete { + // call completion handler first + if (_completionHandler) { + // this should go to another queue + _completionHandler(_data, [self response], [self error]); + } + + // and then finally, call the session delegate completion + [DELEGATE task:self didCompleteWithError:[self error]]; +} + +- (void)cancel { + [self.connection cancel]; +} + +#pragma mark NSURLConnectionDelegate +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { + dispatch_async([self queue], ^{ + [DELEGATE task:self didReceiveResponse:response]; + + [self setResponse:response]; + }); +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { + dispatch_async([self queue], ^{ + if (!self->_data) { + self->_data = [NSMutableData new]; + } + + [self->_data appendData:data]; + [DELEGATE task:self didReceiveData:data]; + }); +} + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { + dispatch_async([self queue], ^{ + [self setError:error]; + [self complete]; + }); +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection { + dispatch_async([self queue], ^{ + [self complete]; + }); +} + +@end + +#else + +INJECT_STRIP_SYMBOL(clsurlsessiondatatask) + +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionDataTask_PrivateMethods.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionDataTask_PrivateMethods.h new file mode 100644 index 0000000..f85a377 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionDataTask_PrivateMethods.h @@ -0,0 +1,43 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSURLSessionAvailability.h" + +#if FIRCLSURLSESSION_REQUIRED + +#import + +#import "FIRCLSURLSessionTask_PrivateMethods.h" + +@protocol FIRCLSURLSessionDataDelegate; + +@interface FIRCLSURLSessionDataTask () + +@property(nonatomic, retain) NSURLConnection *connection; +@property(nonatomic, copy) void (^completionHandler) + (NSData *data, NSURLResponse *response, NSError *error); + +- (void)complete; + +@end + +@protocol FIRCLSURLSessionDataDelegate +@required + +- (void)task:(FIRCLSURLSessionDataTask *)task didReceiveResponse:(NSURLResponse *)response; +- (void)task:(FIRCLSURLSessionDataTask *)task didReceiveData:(NSData *)data; + +@end + +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionDownloadTask.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionDownloadTask.h new file mode 100644 index 0000000..314180f --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionDownloadTask.h @@ -0,0 +1,31 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSURLSessionAvailability.h" + +#if FIRCLSURLSESSION_REQUIRED + +#import "FIRCLSURLSessionDataTask.h" + +@protocol FIRCLSURLSessionDownloadDelegate; + +@interface FIRCLSURLSessionDownloadTask : FIRCLSURLSessionDataTask { + void (^_downloadCompletionHandler)(NSURL *targetPath, NSURLResponse *response, NSError *error); + NSOutputStream *_outputStream; + NSURL *_targetURL; +} + +@end + +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionDownloadTask.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionDownloadTask.m new file mode 100644 index 0000000..a0c9b2a --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionDownloadTask.m @@ -0,0 +1,157 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSURLSessionAvailability.h" + +#if FIRCLSURLSESSION_REQUIRED +#import "FIRCLSURLSessionDownloadTask.h" + +#import "FIRCLSURLSessionDownloadTask_PrivateMethods.h" + +#define DELEGATE ((id)[self delegate]) + +@interface FIRCLSURLSessionDownloadTask () +@end + +@implementation FIRCLSURLSessionDownloadTask + +@synthesize downloadCompletionHandler = _downloadCompletionHandler; + +- (id)init { + self = [super init]; + if (!self) { + return nil; + } + +#if __has_feature(objc_arc) + _targetURL = [self temporaryFileURL]; + _outputStream = [NSOutputStream outputStreamWithURL:_targetURL append:NO]; +#else + _targetURL = [[self temporaryFileURL] retain]; + _outputStream = [[NSOutputStream outputStreamWithURL:_targetURL append:NO] retain]; +#endif + + [_outputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; + [_outputStream setDelegate:self]; + + return self; +} + +#if !__has_feature(objc_arc) +- (void)dealloc { + [_downloadCompletionHandler release]; + [_targetURL release]; + [_outputStream release]; + + [super dealloc]; +} +#else +- (void)dealloc { + [_outputStream close]; + _outputStream.delegate = nil; +} +#endif + +- (NSURL *)temporaryFileURL { + NSString *tmpPath; + + tmpPath = [NSTemporaryDirectory() + stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]; + + // TODO: make this actually unique + return [NSURL fileURLWithPath:tmpPath isDirectory:NO]; +} + +- (void)cleanup { + // now, remove the temporary file + [[NSFileManager defaultManager] removeItemAtURL:_targetURL error:nil]; +} + +- (void)complete { + // This is an override of FIRCLSURLSessionDataTask's cleanup method + + // call completion handler first + if (_downloadCompletionHandler) { + _downloadCompletionHandler(_targetURL, [self response], [self error]); + } + + // followed by the session delegate, if there was no error + if (![self error]) { + [DELEGATE downloadTask:self didFinishDownloadingToURL:_targetURL]; + } + + // and then finally, call the session delegate completion + [DELEGATE task:self didCompleteWithError:[self error]]; +} + +- (void)writeDataToStream:(NSData *)data { + // open the stream first + if ([_outputStream streamStatus] == NSStreamStatusNotOpen) { + [_outputStream open]; + } + + if ([data respondsToSelector:@selector(enumerateByteRangesUsingBlock:)]) { + [data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) { + [self->_outputStream write:bytes maxLength:byteRange.length]; + }]; + + return; + } + + // fall back to the less-efficient mechanism for older OSes + [_outputStream write:[data bytes] maxLength:[data length]]; +} + +#pragma mark NSURLConnectionDelegate +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { + dispatch_async([self queue], ^{ + [self writeDataToStream:data]; + }); +} + +- (void)completeForError { + dispatch_async([self queue], ^{ + [self->_outputStream close]; + [self->_connection cancel]; + if (![self error]) { + [self setError:[NSError errorWithDomain:@"FIRCLSURLSessionDownloadTaskError" + code:-1 + userInfo:nil]]; + } + [self complete]; + }); +} + +#pragma mark NSStreamDelegate +- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode { + switch (eventCode) { + case NSStreamEventHasSpaceAvailable: + break; + case NSStreamEventErrorOccurred: + [self completeForError]; + break; + case NSStreamEventEndEncountered: + break; + default: + break; + } +} + +@end + +#else + +INJECT_STRIP_SYMBOL(clsurlsessiondownloadtask) + +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionDownloadTask_PrivateMethods.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionDownloadTask_PrivateMethods.h new file mode 100644 index 0000000..7e8ee9d --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionDownloadTask_PrivateMethods.h @@ -0,0 +1,39 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSURLSessionAvailability.h" + +#if FIRCLSURLSESSION_REQUIRED + +#import + +#import "FIRCLSURLSessionDataTask_PrivateMethods.h" + +@protocol FIRCLSURLSessionDownloadDelegate; + +@interface FIRCLSURLSessionDownloadTask () + +@property(nonatomic, copy) void (^downloadCompletionHandler) + (NSURL *targetPath, NSURLResponse *response, NSError *error); + +@end + +@protocol FIRCLSURLSessionDownloadDelegate +@required + +- (void)downloadTask:(FIRCLSURLSessionDownloadTask *)task didFinishDownloadingToURL:(NSURL *)url; + +@end + +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionTask.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionTask.h new file mode 100644 index 0000000..9231646 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionTask.h @@ -0,0 +1,38 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +@protocol FIRCLSURLSessionTaskDelegate; + +@interface FIRCLSURLSessionTask : NSObject { + __unsafe_unretained id _delegate; + + NSURLRequest* _originalRequest; + NSURLRequest* _currentRequest; + NSURLResponse* _response; + NSError* _error; + dispatch_queue_t _queue; + BOOL _invokesDelegate; +} + +@property(nonatomic, readonly, copy) NSURLRequest* originalRequest; +@property(nonatomic, readonly, copy) NSURLRequest* currentRequest; +@property(nonatomic, readonly, copy) NSURLResponse* response; + +@property(nonatomic, readonly, copy) NSError* error; + +- (void)resume; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionTask.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionTask.m new file mode 100644 index 0000000..8eba2c1 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionTask.m @@ -0,0 +1,95 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSURLSessionAvailability.h" + +#if FIRCLSURLSESSION_REQUIRED +#import "FIRCLSURLSession.h" + +#import "FIRCLSURLSessionTask.h" + +#import "FIRCLSURLSessionTask_PrivateMethods.h" +#import "FIRCLSURLSession_PrivateMethods.h" + +@implementation FIRCLSURLSessionTask + ++ (instancetype)task { +#if __has_feature(objc_arc) + return [[self class] new]; + +#else + return [[[self class] new] autorelease]; +#endif +} + +@synthesize currentRequest = _currentRequest; +@synthesize originalRequest = _originalRequest; +@synthesize response = _response; +@synthesize error = _error; +@synthesize queue = _queue; +@synthesize invokesDelegate = _invokesDelegate; + +- (instancetype)init { + self = [super init]; + if (!self) { + return self; + } + + _queue = dispatch_queue_create("com.crashlytics.URLSessionTask", 0); + + _invokesDelegate = YES; + + return self; +} + +#if !__has_feature(objc_arc) +- (void)dealloc { + [_originalRequest release]; + [_currentRequest release]; + [_response release]; + [_error release]; + +#if !OS_OBJECT_USE_OBJC + dispatch_release(_queue); +#endif + + [super dealloc]; +} +#endif + +- (void)start { +#if DEBUG + assert(0 && "Must be implemented by FIRCLSURLSessionTask subclasses"); +#endif +} + +- (void)cancel { +#if DEBUG + assert(0 && "Must be implemented by FIRCLSURLSessionTask subclasses"); +#endif +} + +- (void)resume { +} + +- (void)cleanup { +} + +@end + +#else + +INJECT_STRIP_SYMBOL(clsurlsessiontask) + +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionTask_PrivateMethods.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionTask_PrivateMethods.h new file mode 100644 index 0000000..784b3a3 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionTask_PrivateMethods.h @@ -0,0 +1,55 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSURLSessionAvailability.h" + +#if FIRCLSURLSESSION_REQUIRED + +#import + +@protocol FIRCLSURLSessionTaskDelegate; + +@interface FIRCLSURLSessionTask () + ++ (instancetype)task; + +@property(nonatomic, assign) id delegate; + +@property(nonatomic, copy) NSURLRequest *originalRequest; +@property(nonatomic, copy) NSURLRequest *currentRequest; +@property(nonatomic, copy) NSURLResponse *response; + +@property(nonatomic, readonly) dispatch_queue_t queue; +@property(nonatomic, assign) BOOL invokesDelegate; + +- (void)cancel; + +@property(nonatomic, copy) NSError *error; + +- (void)cleanup; + +@end + +@protocol FIRCLSURLSessionTaskDelegate +@required + +- (NSURLRequest *)task:(FIRCLSURLSessionTask *)task + willPerformHTTPRedirection:(NSHTTPURLResponse *)response + newRequest:(NSURLRequest *)request; + +- (void)task:(FIRCLSURLSessionTask *)task didCompleteWithError:(NSError *)error; + +@end + +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionUploadTask.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionUploadTask.h new file mode 100644 index 0000000..6c5ed8b --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionUploadTask.h @@ -0,0 +1,25 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSURLSessionAvailability.h" + +#if FIRCLSURLSESSION_REQUIRED + +#import "FIRCLSURLSessionDataTask.h" + +@interface FIRCLSURLSessionUploadTask : FIRCLSURLSessionDataTask + +@end + +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionUploadTask.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionUploadTask.m new file mode 100644 index 0000000..df3df83 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionUploadTask.m @@ -0,0 +1,28 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSURLSessionAvailability.h" + +#if FIRCLSURLSESSION_REQUIRED +#import "FIRCLSURLSessionUploadTask.h" + +@implementation FIRCLSURLSessionUploadTask + +@end + +#else + +INJECT_STRIP_SYMBOL(clsurlsessionuploadtask) + +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSUserDefaults/FIRCLSUserDefaults.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSUserDefaults/FIRCLSUserDefaults.h new file mode 100644 index 0000000..6df1532 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSUserDefaults/FIRCLSUserDefaults.h @@ -0,0 +1,42 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +extern NSString *const FIRCLSUserDefaultsPathComponent; + +@interface FIRCLSUserDefaults : NSObject + ++ (instancetype)standardUserDefaults; + +- (id)objectForKey:(NSString *)key; +- (NSString *)stringForKey:(NSString *)key; +- (BOOL)boolForKey:(NSString *)key; +- (NSInteger)integerForKey:(NSString *)key; + +- (void)setObject:(id)object forKey:(NSString *)key; +- (void)setString:(NSString *)string forKey:(NSString *)key; +- (void)setBool:(BOOL)boolean forKey:(NSString *)key; +- (void)setInteger:(NSInteger)integer forKey:(NSString *)key; + +- (void)removeObjectForKey:(NSString *)key; +- (void)removeAllObjects; + +- (NSDictionary *)dictionaryRepresentation; + +- (void)migrateFromNSUserDefaults:(NSArray *)keysToMigrate; +- (id)objectForKeyByMigratingFromNSUserDefaults:(NSString *)keyToMigrateOrNil; +- (void)synchronize; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSUserDefaults/FIRCLSUserDefaults.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSUserDefaults/FIRCLSUserDefaults.m new file mode 100644 index 0000000..1c7257a --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSUserDefaults/FIRCLSUserDefaults.m @@ -0,0 +1,372 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSUserDefaults.h" + +#import "FIRCLSApplication.h" +#import "FIRCLSLogger.h" + +#define CLS_USER_DEFAULTS_SERIAL_DISPATCH_QUEUE "com.crashlytics.CLSUserDefaults.access" +#define CLS_USER_DEFAULTS_SYNC_QUEUE "com.crashlytics.CLSUserDefaults.io" + +#define CLS_TARGET_CAN_WRITE_TO_DISK !TARGET_OS_TV + +// These values are required to stay the same between versions of the SDK so +// that when end users upgrade, their crashlytics data is still saved on disk. +#if !CLS_TARGET_CAN_WRITE_TO_DISK +static NSString *const FIRCLSNSUserDefaultsDataDictionaryKey = + @"com.crashlytics.CLSUserDefaults.user-default-key.data-dictionary"; +#endif + +NSString *const FIRCLSUserDefaultsPathComponent = @"CLSUserDefaults"; + +/** + * This class is an isolated re-implementation of NSUserDefaults which isolates our storage + * from that of our customers. This solves a number of issues we have seen in production, firstly + * that customers often delete or clear NSUserDefaults, unintentionally deleting our data. + * Further, we have seen thread safety issues in production with NSUserDefaults, as well as a number + * of bugs related to accessing NSUserDefaults before the device has been unlocked due to the + * NSFileProtection of NSUserDefaults. + */ +@interface FIRCLSUserDefaults () +@property(nonatomic, readwrite) BOOL synchronizeWroteToDisk; +#if CLS_TARGET_CAN_WRITE_TO_DISK +@property(nonatomic, copy, readonly) NSURL *directoryURL; +@property(nonatomic, copy, readonly) NSURL *fileURL; +#endif +@property(nonatomic, copy, readonly) + NSDictionary *persistedDataDictionary; // May only be safely accessed on the DictionaryQueue +@property(nonatomic, copy, readonly) + NSMutableDictionary *dataDictionary; // May only be safely accessed on the DictionaryQueue +@property(nonatomic, readonly) dispatch_queue_t + serialDictionaryQueue; // The queue on which all access to the dataDictionary occurs. +@property(nonatomic, readonly) + dispatch_queue_t synchronizationQueue; // The queue on which all disk access occurs. + +@end + +@implementation FIRCLSUserDefaults + +#pragma mark - singleton + ++ (instancetype)standardUserDefaults { + static FIRCLSUserDefaults *standardUserDefaults = nil; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + standardUserDefaults = [[super allocWithZone:NULL] init]; + }); + + return standardUserDefaults; +} + +- (id)copyWithZone:(NSZone *)zone { + return self; +} + +- (id)init { + if (self = [super init]) { + _serialDictionaryQueue = + dispatch_queue_create(CLS_USER_DEFAULTS_SERIAL_DISPATCH_QUEUE, DISPATCH_QUEUE_SERIAL); + _synchronizationQueue = + dispatch_queue_create(CLS_USER_DEFAULTS_SYNC_QUEUE, DISPATCH_QUEUE_SERIAL); + + dispatch_sync(self.serialDictionaryQueue, ^{ +#if CLS_TARGET_CAN_WRITE_TO_DISK + self->_directoryURL = [self generateDirectoryURL]; + self->_fileURL = [[self->_directoryURL + URLByAppendingPathComponent:FIRCLSUserDefaultsPathComponent + isDirectory:NO] URLByAppendingPathExtension:@"plist"]; +#endif + self->_persistedDataDictionary = [self loadDefaults]; + if (!self->_persistedDataDictionary) { + self->_persistedDataDictionary = [NSDictionary dictionary]; + } + self->_dataDictionary = [self->_persistedDataDictionary mutableCopy]; + }); + } + return self; +} + +- (NSURL *)generateDirectoryURL { + NSURL *directoryBaseURL = + [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory + inDomains:NSUserDomainMask] lastObject]; + NSString *hostAppBundleIdentifier = [self getEscapedAppBundleIdentifier]; + return [self generateDirectoryURLForBaseURL:directoryBaseURL + hostAppBundleIdentifier:hostAppBundleIdentifier]; +} + +- (NSURL *)generateDirectoryURLForBaseURL:(NSURL *)directoryBaseURL + hostAppBundleIdentifier:(NSString *)hostAppBundleIdentifier { + NSURL *directoryURL = directoryBaseURL; + // On iOS NSApplicationSupportDirectory is contained in the app's bundle. On OSX, it is not (it is + // ~/Library/Application Support/). On OSX we create a directory + // ~/Library/Application Support//com.crashlytics/ for storing files. + // Mac App Store review process requires files to be written to + // ~/Library/Application Support//, + // so ~/Library/Application Support/com.crashlytics// cannot be used. +#if !TARGET_OS_SIMULATOR && !TARGET_OS_EMBEDDED + if (hostAppBundleIdentifier) { + directoryURL = [directoryURL URLByAppendingPathComponent:hostAppBundleIdentifier]; + } +#endif + directoryURL = [directoryURL URLByAppendingPathComponent:@"com.crashlytics"]; + return directoryURL; +} + +- (NSString *)getEscapedAppBundleIdentifier { + return FIRCLSApplicationGetBundleIdentifier(); +} + +#pragma mark - fetch object + +- (id)objectForKey:(NSString *)key { + __block id result; + + dispatch_sync(self.serialDictionaryQueue, ^{ + result = [self->_dataDictionary objectForKey:key]; + }); + + return result; +} + +- (NSString *)stringForKey:(NSString *)key { + id result = [self objectForKey:key]; + + if (result != nil && [result isKindOfClass:[NSString class]]) { + return (NSString *)result; + } else { + return nil; + } +} + +- (BOOL)boolForKey:(NSString *)key { + id result = [self objectForKey:key]; + if (result != nil && [result isKindOfClass:[NSNumber class]]) { + return [(NSNumber *)result boolValue]; + } else { + return NO; + } +} + +// Defaults to 0 +- (NSInteger)integerForKey:(NSString *)key { + id result = [self objectForKey:key]; + if (result && [result isKindOfClass:[NSNumber class]]) { + return [(NSNumber *)result integerValue]; + } else { + return 0; + } +} + +#pragma mark - set object + +- (void)setObject:(id)object forKey:(NSString *)key { + dispatch_sync(self.serialDictionaryQueue, ^{ + [self->_dataDictionary setValue:object forKey:key]; + }); +} + +- (void)setString:(NSString *)string forKey:(NSString *)key { + [self setObject:string forKey:key]; +} + +- (void)setBool:(BOOL)boolean forKey:(NSString *)key { + [self setObject:[NSNumber numberWithBool:boolean] forKey:key]; +} + +- (void)setInteger:(NSInteger)integer forKey:(NSString *)key { + [self setObject:[NSNumber numberWithInteger:integer] forKey:key]; +} + +#pragma mark - removing objects + +- (void)removeObjectForKey:(NSString *)key { + dispatch_sync(self.serialDictionaryQueue, ^{ + [self->_dataDictionary removeObjectForKey:key]; + }); +} + +- (void)removeAllObjects { + dispatch_sync(self.serialDictionaryQueue, ^{ + [self->_dataDictionary removeAllObjects]; + }); +} + +#pragma mark - dictionary representation + +- (NSDictionary *)dictionaryRepresentation { + __block NSDictionary *result; + + dispatch_sync(self.serialDictionaryQueue, ^{ + result = [self->_dataDictionary copy]; + }); + + return result; +} + +#pragma mark - synchronization + +- (void)synchronize { + __block BOOL dirty = NO; + + // only write to the disk if the dictionaries have changed + dispatch_sync(self.serialDictionaryQueue, ^{ + dirty = ![self->_persistedDataDictionary isEqualToDictionary:self->_dataDictionary]; + }); + + _synchronizeWroteToDisk = dirty; + if (!dirty) { + return; + } + + NSDictionary *state = [self dictionaryRepresentation]; + dispatch_sync(self.synchronizationQueue, ^{ +#if CLS_TARGET_CAN_WRITE_TO_DISK + BOOL isDirectory; + BOOL pathExists = [[NSFileManager defaultManager] fileExistsAtPath:[self->_directoryURL path] + isDirectory:&isDirectory]; + + if (!pathExists) { + NSError *error; + if (![[NSFileManager defaultManager] createDirectoryAtURL:self->_directoryURL + withIntermediateDirectories:YES + attributes:nil + error:&error]) { + FIRCLSErrorLog(@"Failed to create directory with error: %@", error); + } + } + + if (![state writeToURL:self->_fileURL atomically:YES]) { + FIRCLSErrorLog(@"Unable to open file for writing at path %@", [self->_fileURL path]); + } else { +#if TARGET_OS_IOS + // We disable NSFileProtection on our file in order to allow us to access it even if the + // device is locked. + NSError *error; + if (![[NSFileManager defaultManager] + setAttributes:@{NSFileProtectionKey : NSFileProtectionNone} + ofItemAtPath:[self->_fileURL path] + error:&error]) { + FIRCLSErrorLog(@"Error setting NSFileProtection: %@", error); + } +#endif + } +#else + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + [defaults setObject:state forKey:FIRCLSNSUserDefaultsDataDictionaryKey]; + [defaults synchronize]; +#endif + }); + + dispatch_sync(self.serialDictionaryQueue, ^{ + self->_persistedDataDictionary = [self->_dataDictionary copy]; + }); +} + +- (NSDictionary *)loadDefaults { + __block NSDictionary *state = nil; + dispatch_sync(self.synchronizationQueue, ^{ +#if CLS_TARGET_CAN_WRITE_TO_DISK + BOOL isDirectory; + BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:[self->_fileURL path] + isDirectory:&isDirectory]; + + if (fileExists && !isDirectory) { + state = [NSDictionary dictionaryWithContentsOfURL:self->_fileURL]; + if (nil == state) { + FIRCLSErrorLog(@"Failed to read existing UserDefaults file"); + } + } else if (!fileExists && !isDirectory) { + // No file found. This is expected on first launch. + } else if (fileExists && isDirectory) { + FIRCLSErrorLog(@"Found directory where file expected. Removing conflicting directory"); + + NSError *error; + if (![[NSFileManager defaultManager] removeItemAtURL:self->_fileURL error:&error]) { + FIRCLSErrorLog(@"Error removing conflicting directory: %@", error); + } + } +#else + state = [[NSUserDefaults standardUserDefaults] dictionaryForKey:FIRCLSNSUserDefaultsDataDictionaryKey]; +#endif + }); + return state; +} + +#pragma mark - migration + +// This method migrates all keys specified from NSUserDefaults to FIRCLSUserDefaults +// To do so, we copy all known key-value pairs into FIRCLSUserDefaults, synchronize it, then +// remove the keys from NSUserDefaults and synchronize it. +- (void)migrateFromNSUserDefaults:(NSArray *)keysToMigrate { + BOOL didFindKeys = NO; + + // First, copy all of the keysToMigrate which are stored NSUserDefaults + for (NSString *key in keysToMigrate) { + id oldValue = [[NSUserDefaults standardUserDefaults] objectForKey:(NSString *)key]; + if (nil != oldValue) { + didFindKeys = YES; + [self setObject:oldValue forKey:key]; + } + } + + if (didFindKeys) { + // First synchronize FIRCLSUserDefaults such that all keysToMigrate in NSUserDefaults are stored + // in FIRCLSUserDefaults. At this point, data is duplicated. + [[FIRCLSUserDefaults standardUserDefaults] synchronize]; + + for (NSString *key in keysToMigrate) { + [[NSUserDefaults standardUserDefaults] removeObjectForKey:(NSString *)key]; + } + + // This should be our last interaction with NSUserDefaults. All data is migrated into + // FIRCLSUserDefaults + [[NSUserDefaults standardUserDefaults] synchronize]; + } +} + +// This method first queries FIRCLSUserDefaults to see if the key exist, and upon failure, +// searches for the key in NSUserDefaults, and migrates it if found. +- (id)objectForKeyByMigratingFromNSUserDefaults:(NSString *)keyToMigrateOrNil { + if (!keyToMigrateOrNil) { + return nil; + } + + id clsUserDefaultsValue = [self objectForKey:keyToMigrateOrNil]; + if (clsUserDefaultsValue != nil) { + return clsUserDefaultsValue; // if the value exists in FIRCLSUserDefaults, return it. + } + + id oldNSUserDefaultsValue = + [[NSUserDefaults standardUserDefaults] objectForKey:keyToMigrateOrNil]; + if (!oldNSUserDefaultsValue) { + return nil; // if the value also does not exist in NSUserDefaults, return nil. + } + + // Otherwise, the key exists in NSUserDefaults. Migrate it to FIRCLSUserDefaults + // and then return the associated value. + + // First store it in FIRCLSUserDefaults so in the event of a crash, data is not lost. + [self setObject:oldNSUserDefaultsValue forKey:keyToMigrateOrNil]; + [[FIRCLSUserDefaults standardUserDefaults] synchronize]; + + [[NSUserDefaults standardUserDefaults] removeObjectForKey:keyToMigrateOrNil]; + [[NSUserDefaults standardUserDefaults] synchronize]; + + return oldNSUserDefaultsValue; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSUserDefaults/FIRCLSUserDefaults_private.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSUserDefaults/FIRCLSUserDefaults_private.h new file mode 100644 index 0000000..775bca2 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCLSUserDefaults/FIRCLSUserDefaults_private.h @@ -0,0 +1,23 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import +#import "FIRCLSUserDefaults.h" + +@interface FIRCLSUserDefaults (Private) +- (BOOL)synchronizeWroteToDisk; +- (NSDictionary *)loadDefaults; +- (NSURL *)generateDirectoryURLForBaseURL:(NSURL *)directoryBaseURL + hostAppBundleIdentifier:(NSString *)hostAppBundleIdentifer; +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCrashlytics.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCrashlytics.m new file mode 100644 index 0000000..f2b0246 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/FIRCrashlytics.m @@ -0,0 +1,306 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#if __has_include() +#import +#else +#import "FBLPromises.h" +#endif + +#include "FIRCLSCrashedMarkerFile.h" +#import "FIRCLSDataCollectionArbiter.h" +#import "FIRCLSDefines.h" +#include "FIRCLSException.h" +#import "FIRCLSFileManager.h" +#include "FIRCLSGlobals.h" +#import "FIRCLSHost.h" +#include "FIRCLSProfiling.h" +#import "FIRCLSReport_Private.h" +#import "FIRCLSUserDefaults.h" +#include "FIRCLSUserLogging.h" +#include "FIRCLSUtility.h" + +#import "FIRCLSByteUtility.h" +#import "FIRCLSFABHost.h" +#import "FIRCLSLogger.h" + +#import "FIRCLSReportManager.h" + +#import +#import +#import +#import +#import +#import +#import +#import + +#if TARGET_OS_IPHONE +#import +#endif + +FIRCLSContext _clsContext; +dispatch_queue_t _clsLoggingQueue; +dispatch_queue_t _clsBinaryImageQueue; +dispatch_queue_t _clsExceptionQueue; + +static atomic_bool _hasInitializedInstance; + +/// Empty protocol to register with FirebaseCore's component system. +@protocol FIRCrashlyticsInstanceProvider +@end + +@interface FIRCrashlytics () + +@property(nonatomic) BOOL didPreviouslyCrash; +@property(nonatomic, copy) NSString *googleAppID; +@property(nonatomic) FIRCLSDataCollectionArbiter *dataArbiter; +@property(nonatomic) FIRCLSFileManager *fileManager; +@property(nonatomic) FIRCLSReportManager *reportManager; + +@end + +@implementation FIRCrashlytics + +#pragma mark - Singleton Support + +- (instancetype)initWithApp:(FIRApp *)app + appInfo:(NSDictionary *)appInfo + instanceID:(FIRInstanceID *)instanceID + analytics:(id)analytics { + self = [super init]; + + if (self) { + bool expectedCalled = NO; + if (!atomic_compare_exchange_strong(&_hasInitializedInstance, &expectedCalled, YES)) { + FIRCLSErrorLog(@"Cannot instantiate more than one instance of Crashlytics."); + return nil; + } + + FIRCLSProfileMark mark = FIRCLSProfilingStart(); + + NSLog(@"[Firebase/Crashlytics] Version %@", @CLS_SDK_DISPLAY_VERSION); + + FIRCLSDeveloperLog("Crashlytics", @"Running on %@, %@ (%@)", FIRCLSHostModelInfo(), + FIRCLSHostOSDisplayVersion(), FIRCLSHostOSBuildVersion()); + + _fileManager = [[FIRCLSFileManager alloc] init]; + _googleAppID = app.options.googleAppID; + _dataArbiter = [[FIRCLSDataCollectionArbiter alloc] initWithApp:app withAppInfo:appInfo]; + _reportManager = [[FIRCLSReportManager alloc] initWithFileManager:_fileManager + instanceID:instanceID + analytics:analytics + googleAppID:_googleAppID + dataArbiter:_dataArbiter]; + + // Process did crash during previous execution + NSString *crashedMarkerFileName = [NSString stringWithUTF8String:FIRCLSCrashedMarkerFileName]; + NSString *crashedMarkerFileFullPath = + [[_fileManager rootPath] stringByAppendingPathComponent:crashedMarkerFileName]; + _didPreviouslyCrash = [_fileManager fileExistsAtPath:crashedMarkerFileFullPath]; + + if (_didPreviouslyCrash) { + // Delete the crash file marker in the background ensure start up is as fast as possible + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ + [self.fileManager removeItemAtPath:crashedMarkerFileFullPath]; + }); + } + + [[[_reportManager startWithProfilingMark:mark] then:^id _Nullable(NSNumber *_Nullable value) { + if (![value boolValue]) { + FIRCLSErrorLog(@"Crash reporting could not be initialized"); + } + return value; + }] catch:^void(NSError *error) { + FIRCLSErrorLog(@"Crash reporting failed to initialize with error: %@", error); + }]; + } + + return self; +} + ++ (void)load { + [FIRApp registerInternalLibrary:(Class)self + withName:@"firebase-crashlytics" + withVersion:@CLS_SDK_DISPLAY_VERSION]; +} + ++ (NSArray *)componentsToRegister { + FIRDependency *analyticsDep = + [FIRDependency dependencyWithProtocol:@protocol(FIRAnalyticsInterop)]; + + FIRComponentCreationBlock creationBlock = + ^id _Nullable(FIRComponentContainer *container, BOOL *isCacheable) { + if (!container.app.isDefaultApp) { + FIRCLSErrorLog(@"Crashlytics must be used with the default Firebase app."); + return nil; + } + + id analytics = FIR_COMPONENT(FIRAnalyticsInterop, container); + + FIRInstanceID *instanceID = FIRInstanceID.instanceID; + + *isCacheable = YES; + + return [[FIRCrashlytics alloc] initWithApp:container.app + appInfo:NSBundle.mainBundle.infoDictionary + instanceID:instanceID + analytics:analytics]; + }; + + FIRComponent *component = + [FIRComponent componentWithProtocol:@protocol(FIRCrashlyticsInstanceProvider) + instantiationTiming:FIRInstantiationTimingEagerInDefaultApp + dependencies:@[ analyticsDep ] + creationBlock:creationBlock]; + return @[ component ]; +} + ++ (instancetype)crashlytics { + // The container will return the same instance since isCacheable is set + + FIRApp *defaultApp = [FIRApp defaultApp]; // Missing configure will be logged here. + + // Get the instance from the `FIRApp`'s container. This will create a new instance the + // first time it is called, and since `isCacheable` is set in the component creation + // block, it will return the existing instance on subsequent calls. + id instance = + FIR_COMPONENT(FIRCrashlyticsInstanceProvider, defaultApp.container); + + // In the component creation block, we return an instance of `FIRCrashlytics`. Cast it and + // return it. + return (FIRCrashlytics *)instance; +} + +- (void)setCrashlyticsCollectionEnabled:(BOOL)enabled { + [self.dataArbiter setCrashlyticsCollectionEnabled:enabled]; +} + +- (BOOL)isCrashlyticsCollectionEnabled { + return [self.dataArbiter isCrashlyticsCollectionEnabled]; +} + +#pragma mark - API: didCrashDuringPreviousExecution + +- (BOOL)didCrashDuringPreviousExecution { + return self.didPreviouslyCrash; +} + +- (void)processDidCrashDuringPreviousExecution { + NSString *crashedMarkerFileName = [NSString stringWithUTF8String:FIRCLSCrashedMarkerFileName]; + NSString *crashedMarkerFileFullPath = + [[self.fileManager rootPath] stringByAppendingPathComponent:crashedMarkerFileName]; + self.didPreviouslyCrash = [self.fileManager fileExistsAtPath:crashedMarkerFileFullPath]; + + if (self.didPreviouslyCrash) { + // Delete the crash file marker in the background ensure start up is as fast as possible + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ + [self.fileManager removeItemAtPath:crashedMarkerFileFullPath]; + }); + } +} + +#pragma mark - API: Logging +- (void)log:(NSString *)msg { + FIRCLSLog(@"%@", msg); +} + +- (void)logWithFormat:(NSString *)format, ... { + va_list args; + va_start(args, format); + [self logWithFormat:format arguments:args]; + va_end(args); +} + +- (void)logWithFormat:(NSString *)format arguments:(va_list)args { + [self log:[[NSString alloc] initWithFormat:format arguments:args]]; +} + +#pragma mark - API: Accessors + +- (void)checkForUnsentReportsWithCompletion:(void (^)(BOOL))completion { + [[self.reportManager checkForUnsentReports] then:^id _Nullable(NSNumber *_Nullable value) { + completion([value boolValue]); + return nil; + }]; +} + +- (void)sendUnsentReports { + [self.reportManager sendUnsentReports]; +} + +- (void)deleteUnsentReports { + [self.reportManager deleteUnsentReports]; +} + +#pragma mark - API: setUserID +- (void)setUserID:(NSString *)userID { + FIRCLSUserLoggingRecordInternalKeyValue(FIRCLSUserIdentifierKey, userID); +} + +#pragma mark - API: setCustomValue + +- (void)setCustomValue:(id)value forKey:(NSString *)key { + FIRCLSUserLoggingRecordUserKeyValue(key, value); +} + +#pragma mark - API: Development Platform +// These two methods are depercated by our own API, so +// its ok to implement them +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" ++ (void)setDevelopmentPlatformName:(NSString *)name { + [[self crashlytics] setDevelopmentPlatformName:name]; +} + ++ (void)setDevelopmentPlatformVersion:(NSString *)version { + [[self crashlytics] setDevelopmentPlatformVersion:version]; +} +#pragma clang diagnostic pop + +- (NSString *)developmentPlatformName { + FIRCLSErrorLog(@"developmentPlatformName is write-only"); + return nil; +} + +- (void)setDevelopmentPlatformName:(NSString *)developmentPlatformName { + FIRCLSUserLoggingRecordInternalKeyValue(FIRCLSDevelopmentPlatformNameKey, + developmentPlatformName); +} + +- (NSString *)developmentPlatformVersion { + FIRCLSErrorLog(@"developmentPlatformVersion is write-only"); + return nil; +} + +- (void)setDevelopmentPlatformVersion:(NSString *)developmentPlatformVersion { + FIRCLSUserLoggingRecordInternalKeyValue(FIRCLSDevelopmentPlatformVersionKey, + developmentPlatformVersion); +} + +#pragma mark - API: Errors and Exceptions +- (void)recordError:(NSError *)error { + FIRCLSUserLoggingRecordError(error, nil); +} + +- (void)recordCustomExceptionName:(NSString *)name + reason:(NSString *)reason + frameArray:(NSArray *)frameArray { + FIRCLSExceptionRecord(FIRCLSExceptionTypeCustom, [[name copy] UTF8String], + [[reason copy] UTF8String], [frameArray copy], NO); +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Handlers/FIRCLSException.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Handlers/FIRCLSException.h new file mode 100644 index 0000000..7287d9e --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Handlers/FIRCLSException.h @@ -0,0 +1,71 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include + +#ifdef __OBJC__ +#import +@class FIRCLSStackFrame; +#endif + +#define CLS_EXCEPTION_STRING_LENGTH_MAX (1024 * 16) + +typedef enum { + FIRCLSExceptionTypeUnknown = 0, + FIRCLSExceptionTypeObjectiveC = 1, + FIRCLSExceptionTypeCpp = 2, + // 3 was FIRCLSExceptionTypeJavascript + // Keeping these numbers the same just to be safe + FIRCLSExceptionTypeCustom = 4 +} FIRCLSExceptionType; + +typedef struct { + const char* path; + + void (*originalTerminateHandler)(void); + +#if !TARGET_OS_IPHONE + void* originalNSApplicationReportException; +#endif + + uint32_t maxCustomExceptions; +} FIRCLSExceptionReadOnlyContext; + +typedef struct { + uint32_t customExceptionCount; +} FIRCLSExceptionWritableContext; + +__BEGIN_DECLS + +void FIRCLSExceptionInitialize(FIRCLSExceptionReadOnlyContext* roContext, + FIRCLSExceptionWritableContext* rwContext, + void* delegate); +void FIRCLSExceptionCheckHandlers(void* delegate); + +void FIRCLSExceptionRaiseTestObjCException(void) __attribute((noreturn)); +void FIRCLSExceptionRaiseTestCppException(void) __attribute((noreturn)); + +#ifdef __OBJC__ +void FIRCLSExceptionRecordNSException(NSException* exception); +void FIRCLSExceptionRecord(FIRCLSExceptionType type, + const char* name, + const char* reason, + NSArray* frames, + BOOL attemptDelivery); +#endif + +__END_DECLS diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Handlers/FIRCLSException.mm b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Handlers/FIRCLSException.mm new file mode 100644 index 0000000..cb48cbd --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Handlers/FIRCLSException.mm @@ -0,0 +1,413 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +#include "FIRCLSException.h" + +#include "FIRCLSApplication.h" +#include "FIRCLSFile.h" +#include "FIRCLSGlobals.h" +#include "FIRCLSHandler.h" +#import "FIRCLSLogger.h" +#include "FIRCLSProcess.h" +#import "FIRCLSStackFrame.h" +#import "FIRCLSUserLogging.h" +#import "FIRCLSUtility.h" + +#include "FIRCLSDemangleOperation.h" +#import "FIRCLSReportManager_Private.h" + +// C++/Objective-C exception handling +#include +#include +#include +#include + +#if !TARGET_OS_IPHONE +#import +#import +#endif + +#pragma mark Prototypes +static void FIRCLSTerminateHandler(void); +#if !TARGET_OS_IPHONE +void FIRCLSNSApplicationReportException(id self, SEL cmd, NSException *exception); + +typedef void (*NSApplicationReportExceptionFunction)(id, SEL, NSException *); + +static BOOL FIRCLSIsNSApplicationCrashOnExceptionsEnabled(void); +static NSApplicationReportExceptionFunction FIRCLSOriginalNSExceptionReportExceptionFunction(void); +static Method FIRCLSGetNSApplicationReportExceptionMethod(void); + +#endif + +#pragma mark - API +void FIRCLSExceptionInitialize(FIRCLSExceptionReadOnlyContext *roContext, + FIRCLSExceptionWritableContext *rwContext, + void *delegate) { + if (!FIRCLSUnlinkIfExists(roContext->path)) { + FIRCLSSDKLog("Unable to reset the exception file %s\n", strerror(errno)); + } + + roContext->originalTerminateHandler = std::set_terminate(FIRCLSTerminateHandler); + +#if !TARGET_OS_IPHONE + // If FIRCLSApplicationSharedInstance is null, we don't need this + if (FIRCLSIsNSApplicationCrashOnExceptionsEnabled() && FIRCLSApplicationSharedInstance()) { + Method m = FIRCLSGetNSApplicationReportExceptionMethod(); + + roContext->originalNSApplicationReportException = + (void *)method_setImplementation(m, (IMP)FIRCLSNSApplicationReportException); + } +#endif + + rwContext->customExceptionCount = 0; +} + +#if 0 +// This bit of code might be useful at some point. But, currently it isn't needed. +static void FIRCLSExceptionUninstallHandlers(void) { + std::set_terminate(_clsContext.readonly->exception.originalTerminateHandler); + +#if !TARGET_OS_IPHONE + Method m = FIRCLSGetNSApplicationReportExceptionMethod(); + + method_setImplementation(m, (IMP)FIRCLSOriginalNSExceptionReportExceptionFunction()); +#endif +} +#endif + +void FIRCLSExceptionRecordNSException(NSException *exception) { + FIRCLSSDKLog("Recording an NSException\n"); + + NSArray *returnAddresses = [exception callStackReturnAddresses]; + + NSString *name = [exception name]; + NSString *reason = [exception reason]; + + // It's tempting to try to make use of callStackSymbols here. But, the output + // of that function is not intended to be machine-readible. We could parse it, + // but that isn't really worthwhile, considering that address-based symbolication + // needs to work anyways. + + // package our frames up into the appropriate format + NSMutableArray *frames = [NSMutableArray new]; + + for (NSNumber *address in returnAddresses) { + [frames addObject:[FIRCLSStackFrame stackFrameWithAddress:[address unsignedIntegerValue]]]; + } + + FIRCLSExceptionRecord(FIRCLSExceptionTypeObjectiveC, [name UTF8String], [reason UTF8String], + frames, YES); +} + +static void FIRCLSExceptionRecordFrame(FIRCLSFile *file, FIRCLSStackFrame *frame) { + FIRCLSFileWriteHashStart(file); + + FIRCLSFileWriteHashEntryUint64(file, "pc", [frame address]); + + NSString *string = [frame symbol]; + if (string) { + FIRCLSFileWriteHashEntryHexEncodedString(file, "symbol", [string UTF8String]); + } + + FIRCLSFileWriteHashEntryUint64(file, "offset", [frame offset]); + + string = [frame library]; + if (string) { + FIRCLSFileWriteHashEntryHexEncodedString(file, "library", [string UTF8String]); + } + + string = [frame fileName]; + if (string) { + FIRCLSFileWriteHashEntryHexEncodedString(file, "file", [string UTF8String]); + } + + FIRCLSFileWriteHashEntryUint64(file, "line", [frame lineNumber]); + + FIRCLSFileWriteHashEnd(file); +} + +static bool FIRCLSExceptionIsNative(FIRCLSExceptionType type) { + return type == FIRCLSExceptionTypeObjectiveC || type == FIRCLSExceptionTypeCpp; +} + +static const char *FIRCLSExceptionNameForType(FIRCLSExceptionType type) { + switch (type) { + case FIRCLSExceptionTypeObjectiveC: + return "objective-c"; + case FIRCLSExceptionTypeCpp: + return "c++"; + case FIRCLSExceptionTypeCustom: + return "custom"; + default: + break; + } + + return "unknown"; +} + +void FIRCLSExceptionWrite(FIRCLSFile *file, + FIRCLSExceptionType type, + const char *name, + const char *reason, + NSArray *frames) { + FIRCLSFileWriteSectionStart(file, "exception"); + + FIRCLSFileWriteHashStart(file); + + FIRCLSFileWriteHashEntryString(file, "type", FIRCLSExceptionNameForType(type)); + FIRCLSFileWriteHashEntryHexEncodedString(file, "name", name); + FIRCLSFileWriteHashEntryHexEncodedString(file, "reason", reason); + FIRCLSFileWriteHashEntryUint64(file, "time", time(NULL)); + + if ([frames count]) { + FIRCLSFileWriteHashKey(file, "frames"); + FIRCLSFileWriteArrayStart(file); + + for (FIRCLSStackFrame *frame in frames) { + FIRCLSExceptionRecordFrame(file, frame); + } + + FIRCLSFileWriteArrayEnd(file); + } + + FIRCLSFileWriteHashEnd(file); + + FIRCLSFileWriteSectionEnd(file); +} + +void FIRCLSExceptionRecord(FIRCLSExceptionType type, + const char *name, + const char *reason, + NSArray *frames, + BOOL attemptDelivery) { + if (!FIRCLSContextIsInitialized()) { + return; + } + + bool native = FIRCLSExceptionIsNative(type); + + FIRCLSSDKLog("Recording an exception structure (%d, %d)\n", attemptDelivery, native); + + // exceptions can happen on multiple threads at the same time + if (native) { + dispatch_sync(_clsExceptionQueue, ^{ + const char *path = _clsContext.readonly->exception.path; + FIRCLSFile file; + + if (!FIRCLSFileInitWithPath(&file, path, false)) { + FIRCLSSDKLog("Unable to open exception file\n"); + return; + } + + FIRCLSExceptionWrite(&file, type, name, reason, frames); + + // We only want to do this work if we have the expectation that we'll actually crash + FIRCLSHandler(&file, mach_thread_self(), NULL); + + FIRCLSFileClose(&file); + + // disallow immediate delivery for non-native exceptions + if (attemptDelivery) { + FIRCLSHandlerAttemptImmediateDelivery(); + } + }); + } else { + FIRCLSUserLoggingWriteAndCheckABFiles(&_clsContext.readonly->logging.customExceptionStorage, + &_clsContext.writable->logging.activeCustomExceptionPath, + ^(FIRCLSFile *file) { + FIRCLSExceptionWrite(file, type, name, reason, frames); + }); + } + + FIRCLSSDKLog("Finished recording an exception structure\n"); +} + +// Ignore this message here, because we know that this call will not leak. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Winvalid-noreturn" +void FIRCLSExceptionRaiseTestObjCException(void) { + [NSException raise:@"CrashlyticsTestException" + format:@"This is an Objective-C exception using for testing."]; +} + +void FIRCLSExceptionRaiseTestCppException(void) { + throw "Crashlytics C++ Test Exception"; +} +#pragma clang diagnostic pop + +static const char *FIRCLSExceptionDemangle(const char *symbol) { + return [[FIRCLSDemangleOperation demangleCppSymbol:symbol] UTF8String]; +} + +static void FIRCLSCatchAndRecordActiveException(std::type_info *typeInfo) { + if (!FIRCLSIsValidPointer(typeInfo)) { + FIRCLSSDKLog("Error: invalid parameter\n"); + return; + } + + const char *name = typeInfo->name(); + FIRCLSSDKLog("Recording exception of type '%s'\n", name); + + // This is a funny technique to get the exception object. The inner @try + // has the ability to capture NSException-derived objects. It seems that + // c++ trys can do that in some cases, but I was warned by the WWDC labs + // that there are cases where that will not work (like for NSException subclasses). + try { + @try { + // This could potentially cause a call to std::terminate() if there is actually no active + // exception. + throw; + } @catch (NSException *exception) { +#if TARGET_OS_IPHONE + FIRCLSExceptionRecordNSException(exception); +#else + // There's no need to record this here, because we're going to get + // the value forward to us by AppKit + FIRCLSSDKLog("Skipping ObjC exception at this point\n"); +#endif + } + } catch (const char *exc) { + FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, "const char *", exc, nil, YES); + } catch (const std::string &exc) { + FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, "std::string", exc.c_str(), nil, YES); + } catch (const std::exception &exc) { + FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, FIRCLSExceptionDemangle(name), exc.what(), nil, + YES); + } catch (const std::exception *exc) { + FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, FIRCLSExceptionDemangle(name), exc->what(), nil, + YES); + } catch (const std::bad_alloc &exc) { + // it is especially important to avoid demangling in this case, because the expetation at this + // point is that all allocations could fail + FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, "std::bad_alloc", exc.what(), nil, YES); + } catch (...) { + FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, FIRCLSExceptionDemangle(name), "", nil, YES); + } +} + +#pragma mark - Handlers +static void FIRCLSTerminateHandler(void) { + FIRCLSSDKLog("C++ terminate handler invoked\n"); + + void (*handler)(void) = _clsContext.readonly->exception.originalTerminateHandler; + if (handler == FIRCLSTerminateHandler) { + FIRCLSSDKLog("Error: original handler was set recursively\n"); + handler = NULL; + } + + // Restore pre-existing handler, if any. Do this early, so that + // if std::terminate is called while we are executing here, we do not recurse. + if (handler) { + FIRCLSSDKLog("restoring pre-existing handler\n"); + + // To prevent infinite recursion in this function, check that we aren't resetting the terminate + // handler to the same function again, which would be this function in the event that we can't + // actually change the handler during a terminate. + if (std::set_terminate(handler) == handler) { + FIRCLSSDKLog("handler has already been restored, aborting\n"); + abort(); + } + } + + // we can use typeInfo to record the type of the exception, + // but we must use a catch to get the value + std::type_info *typeInfo = __cxxabiv1::__cxa_current_exception_type(); + if (typeInfo) { + FIRCLSCatchAndRecordActiveException(typeInfo); + } else { + FIRCLSSDKLog("no active exception\n"); + } + + // only do this if there was a pre-existing handler + if (handler) { + FIRCLSSDKLog("invoking pre-existing handler\n"); + handler(); + } + + FIRCLSSDKLog("aborting\n"); + abort(); +} + +void FIRCLSExceptionCheckHandlers(void *delegate) { +#if !TARGET_OS_IPHONE + // Check this on OS X all the time, even if the debugger is attached. This is a common + // source of errors, so we want to be extra verbose in this case. + if (FIRCLSApplicationSharedInstance()) { + if (!FIRCLSIsNSApplicationCrashOnExceptionsEnabled()) { + FIRCLSWarningLog(@"Warning: NSApplicationCrashOnExceptions is not set. This will " + @"result in poor top-level uncaught exception reporting."); + } + } +#endif + + if (_clsContext.readonly->debuggerAttached) { + return; + } + + void *ptr = NULL; + + ptr = (void *)std::get_terminate(); + if (ptr != FIRCLSTerminateHandler) { + FIRCLSLookupFunctionPointer(ptr, ^(const char *name, const char *lib) { + FIRCLSWarningLog(@"Warning: std::get_terminate is '%s' in '%s'", name, lib); + }); + } + +#if TARGET_OS_IPHONE + ptr = (void *)NSGetUncaughtExceptionHandler(); + if (ptr) { + FIRCLSLookupFunctionPointer(ptr, ^(const char *name, const char *lib) { + FIRCLSWarningLog(@"Warning: NSUncaughtExceptionHandler is '%s' in '%s'", name, lib); + }); + } +#else + if (FIRCLSApplicationSharedInstance() && FIRCLSIsNSApplicationCrashOnExceptionsEnabled()) { + // In this case, we *might* be able to intercept exceptions. But, verify we've still + // swizzled the method. + Method m = FIRCLSGetNSApplicationReportExceptionMethod(); + + if (method_getImplementation(m) != (IMP)FIRCLSNSApplicationReportException) { + FIRCLSWarningLog( + @"Warning: top-level NSApplication-reported exceptions cannot be intercepted"); + } + } +#endif +} + +#pragma mark - AppKit Handling +#if !TARGET_OS_IPHONE +static BOOL FIRCLSIsNSApplicationCrashOnExceptionsEnabled(void) { + return [[NSUserDefaults standardUserDefaults] boolForKey:@"NSApplicationCrashOnExceptions"]; +} + +static Method FIRCLSGetNSApplicationReportExceptionMethod(void) { + return class_getInstanceMethod(NSClassFromString(@"NSApplication"), @selector(reportException:)); +} + +static NSApplicationReportExceptionFunction FIRCLSOriginalNSExceptionReportExceptionFunction(void) { + return (NSApplicationReportExceptionFunction) + _clsContext.readonly->exception.originalNSApplicationReportException; +} + +void FIRCLSNSApplicationReportException(id self, SEL cmd, NSException *exception) { + FIRCLSExceptionRecordNSException(exception); + + // Call the original implementation + FIRCLSOriginalNSExceptionReportExceptionFunction()(self, cmd, exception); +} + +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Handlers/FIRCLSHandler.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Handlers/FIRCLSHandler.h new file mode 100644 index 0000000..2940de2 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Handlers/FIRCLSHandler.h @@ -0,0 +1,26 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "FIRCLSFile.h" + +#include + +__BEGIN_DECLS + +void FIRCLSHandler(FIRCLSFile* file, thread_t crashedThread, void* uapVoid); +void FIRCLSHandlerAttemptImmediateDelivery(void); + +__END_DECLS diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Handlers/FIRCLSHandler.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Handlers/FIRCLSHandler.m new file mode 100644 index 0000000..9cfaa04 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Handlers/FIRCLSHandler.m @@ -0,0 +1,61 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FIRCLSHandler.h" + +#include "FIRCLSCrashedMarkerFile.h" +#include "FIRCLSGlobals.h" +#include "FIRCLSHost.h" +#include "FIRCLSProcess.h" +#include "FIRCLSUtility.h" + +#import "FIRCLSReportManager_Private.h" + +void FIRCLSHandler(FIRCLSFile* file, thread_t crashedThread, void* uapVoid) { + FIRCLSProcess process; + + FIRCLSProcessInit(&process, crashedThread, uapVoid); + + FIRCLSProcessSuspendAllOtherThreads(&process); + + FIRCLSProcessRecordAllThreads(&process, file); + + FIRCLSProcessRecordRuntimeInfo(&process, file); + // Get dispatch queue and thread names. Note that getting the thread names + // can hang, so let's do that last + FIRCLSProcessRecordDispatchQueueNames(&process, file); + FIRCLSProcessRecordThreadNames(&process, file); + + // this stuff isn't super important, but we can try + FIRCLSProcessRecordStats(&process, file); + FIRCLSHostWriteDiskUsage(file); + + // This is the first common point where various crash handlers call into + // Store a crash file marker to indicate that a crash has occured + FIRCLSCreateCrashedMarkerFile(); + + FIRCLSProcessResumeAllOtherThreads(&process); + + // clean up after ourselves + FIRCLSProcessDestroy(&process); +} + +void FIRCLSHandlerAttemptImmediateDelivery(void) { + // now, attempt to relay the event to the delegate + FIRCLSReportManager* manager = (__bridge id)_clsContext.readonly->delegate; + + if ([manager respondsToSelector:@selector(potentiallySubmittableCrashOccurred)]) { + [manager potentiallySubmittableCrashOccurred]; + } +} diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Handlers/FIRCLSMachException.c b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Handlers/FIRCLSMachException.c new file mode 100644 index 0000000..c4d7f8a --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Handlers/FIRCLSMachException.c @@ -0,0 +1,533 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FIRCLSDefines.h" +#include "FIRCLSFeatures.h" + +#if CLS_MACH_EXCEPTION_SUPPORTED + +#include "FIRCLSGlobals.h" +#include "FIRCLSHandler.h" +#include "FIRCLSMachException.h" +#include "FIRCLSProcess.h" +#include "FIRCLSSignal.h" +#include "FIRCLSUtility.h" + +#include +#include +#include +#include + +#pragma mark Prototypes +static exception_mask_t FIRCLSMachExceptionMask(void); +static void* FIRCLSMachExceptionServer(void* argument); +static bool FIRCLSMachExceptionThreadStart(FIRCLSMachExceptionReadContext* context); +static bool FIRCLSMachExceptionReadMessage(FIRCLSMachExceptionReadContext* context, + MachExceptionMessage* message); +static kern_return_t FIRCLSMachExceptionDispatchMessage(FIRCLSMachExceptionReadContext* context, + MachExceptionMessage* message); +static bool FIRCLSMachExceptionReply(FIRCLSMachExceptionReadContext* context, + MachExceptionMessage* message, + kern_return_t result); +static bool FIRCLSMachExceptionRegister(FIRCLSMachExceptionReadContext* context, + exception_mask_t ignoreMask); +static bool FIRCLSMachExceptionUnregister(FIRCLSMachExceptionOriginalPorts* originalPorts, + exception_mask_t mask); +static bool FIRCLSMachExceptionRecord(FIRCLSMachExceptionReadContext* context, + MachExceptionMessage* message); + +#pragma mark - Initialization +void FIRCLSMachExceptionInit(FIRCLSMachExceptionReadContext* context, exception_mask_t ignoreMask) { + if (!FIRCLSUnlinkIfExists(context->path)) { + FIRCLSSDKLog("Unable to reset the mach exception file %s\n", strerror(errno)); + } + + if (!FIRCLSMachExceptionRegister(context, ignoreMask)) { + FIRCLSSDKLog("Unable to register mach exception handler\n"); + return; + } + + if (!FIRCLSMachExceptionThreadStart(context)) { + FIRCLSSDKLog("Unable to start thread\n"); + FIRCLSMachExceptionUnregister(&context->originalPorts, context->mask); + } +} + +void FIRCLSMachExceptionCheckHandlers(void) { + if (_clsContext.readonly->debuggerAttached) { + return; + } + + // It isn't really critical that this be done, as its extremely uncommon to run into + // preexisting handlers. + // Can use task_get_exception_ports for this. +} + +static exception_mask_t FIRCLSMachExceptionMask(void) { + exception_mask_t mask; + + // EXC_BAD_ACCESS + // EXC_BAD_INSTRUCTION + // EXC_ARITHMETIC + // EXC_EMULATION - non-failure + // EXC_SOFTWARE - non-failure + // EXC_BREAKPOINT - trap instructions, from the debugger and code. Needs special treatment. + // EXC_SYSCALL - non-failure + // EXC_MACH_SYSCALL - non-failure + // EXC_RPC_ALERT - non-failure + // EXC_CRASH - see below + // EXC_RESOURCE - non-failure, happens when a process exceeds a resource limit + // EXC_GUARD - see below + // + // EXC_CRASH is a special kind of exception. It is handled by launchd, and treated special by + // the kernel. Seems that we cannot safely catch it - our handler will never be called. This + // is a confirmed kernel bug. Lacking access to EXC_CRASH means we must use signal handlers to + // cover all types of crashes. + // EXC_GUARD is relatively new, and isn't available on all OS versions. You have to be careful, + // becuase you cannot succesfully register hanlders if there are any unrecognized masks. We've + // dropped support for old OS versions that didn't have EXC_GUARD (iOS 5 and below, macOS 10.6 and + // below) so we always add it now + + mask = EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC | + EXC_MASK_BREAKPOINT | EXC_MASK_GUARD; + + return mask; +} + +static bool FIRCLSMachExceptionThreadStart(FIRCLSMachExceptionReadContext* context) { + pthread_attr_t attr; + + if (pthread_attr_init(&attr) != 0) { + FIRCLSSDKLog("pthread_attr_init %s\n", strerror(errno)); + return false; + } + + if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) != 0) { + FIRCLSSDKLog("pthread_attr_setdetachstate %s\n", strerror(errno)); + return false; + } + + // Use to pre-allocate a stack for this thread + // The stack must be page-aligned + if (pthread_attr_setstack(&attr, _clsContext.readonly->machStack, + CLS_MACH_EXCEPTION_HANDLER_STACK_SIZE) != 0) { + FIRCLSSDKLog("pthread_attr_setstack %s\n", strerror(errno)); + return false; + } + + if (pthread_create(&context->thread, &attr, FIRCLSMachExceptionServer, context) != 0) { + FIRCLSSDKLog("pthread_create %s\n", strerror(errno)); + return false; + } + + pthread_attr_destroy(&attr); + + return true; +} + +exception_mask_t FIRCLSMachExceptionMaskForSignal(int signal) { + switch (signal) { + case SIGTRAP: + return EXC_MASK_BREAKPOINT; + case SIGSEGV: + return EXC_MASK_BAD_ACCESS; + case SIGBUS: + return EXC_MASK_BAD_ACCESS; + case SIGILL: + return EXC_MASK_BAD_INSTRUCTION; + case SIGABRT: + return EXC_MASK_CRASH; + case SIGSYS: + return EXC_MASK_CRASH; + case SIGFPE: + return EXC_MASK_ARITHMETIC; + } + + return 0; +} + +#pragma mark - Message Handling +static void* FIRCLSMachExceptionServer(void* argument) { + FIRCLSMachExceptionReadContext* context = argument; + + pthread_setname_np("com.google.firebase.crashlytics.MachExceptionServer"); + + while (1) { + MachExceptionMessage message; + + // read the exception message + if (!FIRCLSMachExceptionReadMessage(context, &message)) { + break; + } + + // handle it, and possibly forward + kern_return_t result = FIRCLSMachExceptionDispatchMessage(context, &message); + + // and now, reply + if (!FIRCLSMachExceptionReply(context, &message, result)) { + break; + } + } + + FIRCLSSDKLog("Mach exception server thread exiting\n"); + + return NULL; +} + +static bool FIRCLSMachExceptionReadMessage(FIRCLSMachExceptionReadContext* context, + MachExceptionMessage* message) { + mach_msg_return_t r; + + memset(message, 0, sizeof(MachExceptionMessage)); + + r = mach_msg(&message->head, MACH_RCV_MSG | MACH_RCV_LARGE, 0, sizeof(MachExceptionMessage), + context->port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + if (r != MACH_MSG_SUCCESS) { + FIRCLSSDKLog("Error receving mach_msg (%d)\n", r); + return false; + } + + FIRCLSSDKLog("Accepted mach exception message\n"); + + return true; +} + +static kern_return_t FIRCLSMachExceptionDispatchMessage(FIRCLSMachExceptionReadContext* context, + MachExceptionMessage* message) { + FIRCLSSDKLog("Mach exception: 0x%x, count: %d, code: 0x%llx 0x%llx\n", message->exception, + message->codeCnt, message->codeCnt > 0 ? message->code[0] : -1, + message->codeCnt > 1 ? message->code[1] : -1); + + // This will happen if a child process raises an exception, as the exception ports are + // inherited. + if (message->task.name != mach_task_self()) { + FIRCLSSDKLog("Mach exception task mis-match, returning failure\n"); + return KERN_FAILURE; + } + + FIRCLSSDKLog("Unregistering handler\n"); + if (!FIRCLSMachExceptionUnregister(&context->originalPorts, context->mask)) { + FIRCLSSDKLog("Failed to unregister\n"); + return KERN_FAILURE; + } + + FIRCLSSDKLog("Restoring original signal handlers\n"); + if (!FIRCLSSignalSafeInstallPreexistingHandlers(&_clsContext.readonly->signal)) { + FIRCLSSDKLog("Failed to restore signal handlers\n"); + return KERN_FAILURE; + } + + FIRCLSSDKLog("Recording mach exception\n"); + if (!FIRCLSMachExceptionRecord(context, message)) { + FIRCLSSDKLog("Failed to record mach exception\n"); + return KERN_FAILURE; + } + + return KERN_SUCCESS; +} + +static bool FIRCLSMachExceptionReply(FIRCLSMachExceptionReadContext* context, + MachExceptionMessage* message, + kern_return_t result) { + MachExceptionReply reply; + mach_msg_return_t r; + + // prepare the reply + reply.head.msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(message->head.msgh_bits), 0); + reply.head.msgh_remote_port = message->head.msgh_remote_port; + reply.head.msgh_size = (mach_msg_size_t)sizeof(MachExceptionReply); + reply.head.msgh_local_port = MACH_PORT_NULL; + reply.head.msgh_id = message->head.msgh_id + 100; + + reply.NDR = NDR_record; + + reply.retCode = result; + + FIRCLSSDKLog("Sending exception reply\n"); + + // send it + r = mach_msg(&reply.head, MACH_SEND_MSG, reply.head.msgh_size, 0, MACH_PORT_NULL, + MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + if (r != MACH_MSG_SUCCESS) { + FIRCLSSDKLog("mach_msg reply failed (%d)\n", r); + return false; + } + + FIRCLSSDKLog("Exception reply delivered\n"); + + return true; +} + +#pragma mark - Registration +static bool FIRCLSMachExceptionRegister(FIRCLSMachExceptionReadContext* context, + exception_mask_t ignoreMask) { + mach_port_t task = mach_task_self(); + + kern_return_t kr = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &context->port); + if (kr != KERN_SUCCESS) { + FIRCLSSDKLog("Error: mach_port_allocate failed %d\n", kr); + return false; + } + + kr = mach_port_insert_right(task, context->port, context->port, MACH_MSG_TYPE_MAKE_SEND); + if (kr != KERN_SUCCESS) { + FIRCLSSDKLog("Error: mach_port_insert_right failed %d\n", kr); + mach_port_deallocate(task, context->port); + return false; + } + + // Get the desired mask, which covers all the mach exceptions we are capable of handling, + // but clear out any that are in our ignore list. We do this by ANDing with the bitwise + // negation. Because we are only clearing bits, there's no way to set an incorrect mask + // using ignoreMask. + context->mask = FIRCLSMachExceptionMask() & ~ignoreMask; + + // ORing with MACH_EXCEPTION_CODES will produce 64-bit exception data + kr = task_swap_exception_ports(task, context->mask, context->port, + EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE, + context->originalPorts.masks, &context->originalPorts.count, + context->originalPorts.ports, context->originalPorts.behaviors, + context->originalPorts.flavors); + if (kr != KERN_SUCCESS) { + FIRCLSSDKLog("Error: task_swap_exception_ports %d\n", kr); + return false; + } + + for (int i = 0; i < context->originalPorts.count; ++i) { + FIRCLSSDKLog("original 0x%x 0x%x 0x%x 0x%x\n", context->originalPorts.ports[i], + context->originalPorts.masks[i], context->originalPorts.behaviors[i], + context->originalPorts.flavors[i]); + } + + return true; +} + +static bool FIRCLSMachExceptionUnregister(FIRCLSMachExceptionOriginalPorts* originalPorts, + exception_mask_t mask) { + kern_return_t kr; + + // Re-register all the old ports. + for (mach_msg_type_number_t i = 0; i < originalPorts->count; ++i) { + // clear the bits from this original mask + mask &= ~originalPorts->masks[i]; + + kr = + task_set_exception_ports(mach_task_self(), originalPorts->masks[i], originalPorts->ports[i], + originalPorts->behaviors[i], originalPorts->flavors[i]); + if (kr != KERN_SUCCESS) { + FIRCLSSDKLog("unable to restore original port: %d", originalPorts->ports[i]); + } + } + + // Finally, mark any masks we registered for that do not have an original port as unused. + kr = task_set_exception_ports(mach_task_self(), mask, MACH_PORT_NULL, + EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE); + if (kr != KERN_SUCCESS) { + FIRCLSSDKLog("unable to unset unregistered mask: 0x%x", mask); + return false; + } + + return true; +} + +#pragma mark - Recording +static void FIRCLSMachExceptionNameLookup(exception_type_t number, + mach_exception_data_type_t code, + const char** name, + const char** codeName) { + if (!name || !codeName) { + return; + } + + *name = NULL; + *codeName = NULL; + + switch (number) { + case EXC_BAD_ACCESS: + *name = "EXC_BAD_ACCESS"; + switch (code) { + case KERN_INVALID_ADDRESS: + *codeName = "KERN_INVALID_ADDRESS"; + break; + case KERN_PROTECTION_FAILURE: + *codeName = "KERN_PROTECTION_FAILURE"; + break; + } + + break; + case EXC_BAD_INSTRUCTION: + *name = "EXC_BAD_INSTRUCTION"; +#if CLS_CPU_X86 + *codeName = "EXC_I386_INVOP"; +#endif + break; + case EXC_ARITHMETIC: + *name = "EXC_ARITHMETIC"; +#if CLS_CPU_X86 + switch (code) { + case EXC_I386_DIV: + *codeName = "EXC_I386_DIV"; + break; + case EXC_I386_INTO: + *codeName = "EXC_I386_INTO"; + break; + case EXC_I386_NOEXT: + *codeName = "EXC_I386_NOEXT"; + break; + case EXC_I386_EXTOVR: + *codeName = "EXC_I386_EXTOVR"; + break; + case EXC_I386_EXTERR: + *codeName = "EXC_I386_EXTERR"; + break; + case EXC_I386_EMERR: + *codeName = "EXC_I386_EMERR"; + break; + case EXC_I386_BOUND: + *codeName = "EXC_I386_BOUND"; + break; + case EXC_I386_SSEEXTERR: + *codeName = "EXC_I386_SSEEXTERR"; + break; + } +#endif + break; + case EXC_BREAKPOINT: + *name = "EXC_BREAKPOINT"; +#if CLS_CPU_X86 + switch (code) { + case EXC_I386_DIVERR: + *codeName = "EXC_I386_DIVERR"; + break; + case EXC_I386_SGLSTP: + *codeName = "EXC_I386_SGLSTP"; + break; + case EXC_I386_NMIFLT: + *codeName = "EXC_I386_NMIFLT"; + break; + case EXC_I386_BPTFLT: + *codeName = "EXC_I386_BPTFLT"; + break; + case EXC_I386_INTOFLT: + *codeName = "EXC_I386_INTOFLT"; + break; + case EXC_I386_BOUNDFLT: + *codeName = "EXC_I386_BOUNDFLT"; + break; + case EXC_I386_INVOPFLT: + *codeName = "EXC_I386_INVOPFLT"; + break; + case EXC_I386_NOEXTFLT: + *codeName = "EXC_I386_NOEXTFLT"; + break; + case EXC_I386_EXTOVRFLT: + *codeName = "EXC_I386_EXTOVRFLT"; + break; + case EXC_I386_INVTSSFLT: + *codeName = "EXC_I386_INVTSSFLT"; + break; + case EXC_I386_SEGNPFLT: + *codeName = "EXC_I386_SEGNPFLT"; + break; + case EXC_I386_STKFLT: + *codeName = "EXC_I386_STKFLT"; + break; + case EXC_I386_GPFLT: + *codeName = "EXC_I386_GPFLT"; + break; + case EXC_I386_PGFLT: + *codeName = "EXC_I386_PGFLT"; + break; + case EXC_I386_EXTERRFLT: + *codeName = "EXC_I386_EXTERRFLT"; + break; + case EXC_I386_ALIGNFLT: + *codeName = "EXC_I386_ALIGNFLT"; + break; + case EXC_I386_ENDPERR: + *codeName = "EXC_I386_ENDPERR"; + break; + case EXC_I386_ENOEXTFLT: + *codeName = "EXC_I386_ENOEXTFLT"; + break; + } +#endif + break; + case EXC_GUARD: + *name = "EXC_GUARD"; + break; + } +} + +static bool FIRCLSMachExceptionRecord(FIRCLSMachExceptionReadContext* context, + MachExceptionMessage* message) { + if (!context || !message) { + return false; + } + + if (FIRCLSContextMarkAndCheckIfCrashed()) { + FIRCLSSDKLog("Error: aborting mach exception handler because crash has already occurred\n"); + exit(1); + return false; + } + + FIRCLSFile file; + + if (!FIRCLSFileInitWithPath(&file, context->path, false)) { + FIRCLSSDKLog("Unable to open mach exception file\n"); + return false; + } + + FIRCLSFileWriteSectionStart(&file, "mach_exception"); + + FIRCLSFileWriteHashStart(&file); + + FIRCLSFileWriteHashEntryUint64(&file, "exception", message->exception); + + // record the codes + FIRCLSFileWriteHashKey(&file, "codes"); + FIRCLSFileWriteArrayStart(&file); + for (mach_msg_type_number_t i = 0; i < message->codeCnt; ++i) { + FIRCLSFileWriteArrayEntryUint64(&file, message->code[i]); + } + FIRCLSFileWriteArrayEnd(&file); + + const char* name = NULL; + const char* codeName = NULL; + + FIRCLSMachExceptionNameLookup(message->exception, message->codeCnt > 0 ? message->code[0] : 0, + &name, &codeName); + + FIRCLSFileWriteHashEntryString(&file, "name", name); + FIRCLSFileWriteHashEntryString(&file, "code_name", codeName); + + FIRCLSFileWriteHashEntryUint64(&file, "original_ports", context->originalPorts.count); + FIRCLSFileWriteHashEntryUint64(&file, "time", time(NULL)); + + FIRCLSFileWriteHashEnd(&file); + + FIRCLSFileWriteSectionEnd(&file); + + FIRCLSHandler(&file, message->thread.name, NULL); + + FIRCLSFileClose(&file); + + return true; +} + +#else + +INJECT_STRIP_SYMBOL(cls_mach_exception) + +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Handlers/FIRCLSMachException.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Handlers/FIRCLSMachException.h new file mode 100644 index 0000000..b19881a --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Handlers/FIRCLSMachException.h @@ -0,0 +1,78 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FIRCLSFeatures.h" + +#pragma once + +#if CLS_MACH_EXCEPTION_SUPPORTED + +#include +#include +#include + +// must be at least PTHREAD_STACK_MIN size +#define CLS_MACH_EXCEPTION_HANDLER_STACK_SIZE (256 * 1024) + +#pragma mark Structures +#pragma pack(push, 4) +typedef struct { + mach_msg_header_t head; + /* start of the kernel processed data */ + mach_msg_body_t msgh_body; + mach_msg_port_descriptor_t thread; + mach_msg_port_descriptor_t task; + /* end of the kernel processed data */ + NDR_record_t NDR; + exception_type_t exception; + mach_msg_type_number_t codeCnt; + mach_exception_data_type_t code[EXCEPTION_CODE_MAX]; + mach_msg_trailer_t trailer; +} MachExceptionMessage; + +typedef struct { + mach_msg_header_t head; + NDR_record_t NDR; + kern_return_t retCode; +} MachExceptionReply; +#pragma pack(pop) + +typedef struct { + mach_msg_type_number_t count; + exception_mask_t masks[EXC_TYPES_COUNT]; + exception_handler_t ports[EXC_TYPES_COUNT]; + exception_behavior_t behaviors[EXC_TYPES_COUNT]; + thread_state_flavor_t flavors[EXC_TYPES_COUNT]; +} FIRCLSMachExceptionOriginalPorts; + +typedef struct { + mach_port_t port; + pthread_t thread; + const char* path; + + exception_mask_t mask; + FIRCLSMachExceptionOriginalPorts originalPorts; +} FIRCLSMachExceptionReadContext; + +#pragma mark - API +void FIRCLSMachExceptionInit(FIRCLSMachExceptionReadContext* context, exception_mask_t ignoreMask); +exception_mask_t FIRCLSMachExceptionMaskForSignal(int signal); + +void FIRCLSMachExceptionCheckHandlers(void); + +#else + +#define CLS_MACH_EXCEPTION_HANDLER_STACK_SIZE 0 + +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Handlers/FIRCLSSignal.c b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Handlers/FIRCLSSignal.c new file mode 100644 index 0000000..d5daba7 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Handlers/FIRCLSSignal.c @@ -0,0 +1,318 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FIRCLSSignal.h" +#include "FIRCLSGlobals.h" +#include "FIRCLSHandler.h" +#include "FIRCLSUtility.h" + +#include +#include + +static const int FIRCLSFatalSignals[FIRCLSSignalCount] = {SIGABRT, SIGBUS, SIGFPE, SIGILL, + SIGSEGV, SIGSYS, SIGTRAP}; + +#if CLS_USE_SIGALTSTACK +static void FIRCLSSignalInstallAltStack(FIRCLSSignalReadContext *roContext); +#endif +static void FIRCLSSignalInstallHandlers(FIRCLSSignalReadContext *roContext); +static void FIRCLSSignalHandler(int signal, siginfo_t *info, void *uapVoid); + +void FIRCLSSignalInitialize(FIRCLSSignalReadContext *roContext) { + if (!FIRCLSUnlinkIfExists(roContext->path)) { + FIRCLSSDKLog("Unable to reset the signal log file %s\n", strerror(errno)); + } + +#if CLS_USE_SIGALTSTACK + FIRCLSSignalInstallAltStack(roContext); +#endif + FIRCLSSignalInstallHandlers(roContext); +#if TARGET_IPHONE_SIMULATOR + // prevent the OpenGL stack (by way of OpenGLES.framework/libLLVMContainer.dylib) from installing + // signal handlers that do not chain back + // TODO: I don't believe this is necessary as of recent iOS releases + bool *ptr = dlsym(RTLD_DEFAULT, "_ZN4llvm23DisablePrettyStackTraceE"); + if (ptr) { + *ptr = true; + } +#endif +} + +void FIRCLSSignalEnumerateHandledSignals(void (^block)(int idx, int signal)) { + for (int i = 0; i < FIRCLSSignalCount; ++i) { + block(i, FIRCLSFatalSignals[i]); + } +} + +#if CLS_USE_SIGALTSTACK + +static void FIRCLSSignalInstallAltStack(FIRCLSSignalReadContext *roContext) { + stack_t signalStack; + stack_t originalStack; + + signalStack.ss_sp = _clsContext.readonly->signalStack; + signalStack.ss_size = CLS_SIGNAL_HANDLER_STACK_SIZE; + signalStack.ss_flags = 0; + + if (sigaltstack(&signalStack, &originalStack) != 0) { + FIRCLSSDKLog("Unable to setup stack %s\n", strerror(errno)); + + return; + } + + roContext->originalStack.ss_sp = NULL; + roContext->originalStack = originalStack; +} + +#endif + +static void FIRCLSSignalInstallHandlers(FIRCLSSignalReadContext *roContext) { + FIRCLSSignalEnumerateHandledSignals(^(int idx, int signal) { + struct sigaction action; + struct sigaction previousAction; + + action.sa_sigaction = FIRCLSSignalHandler; + // SA_RESETHAND seems like it would be great, but it doesn't appear to + // work correctly. After taking a signal, causing another identical signal in + // the handler will *not* cause the default handler to be invokved (which should + // terminate the process). I've found some evidence that others have seen this + // behavior on MAC OS X. + action.sa_flags = SA_SIGINFO | SA_ONSTACK; + + sigemptyset(&action.sa_mask); + + previousAction.sa_sigaction = NULL; + if (sigaction(signal, &action, &previousAction) != 0) { + FIRCLSSDKLog("Unable to install handler for %d (%s)\n", signal, strerror(errno)); + } + + // store the last action, so it can be recalled + roContext->originalActions[idx].sa_sigaction = NULL; + + if (previousAction.sa_sigaction) { + roContext->originalActions[idx] = previousAction; + } + }); +} + +void FIRCLSSignalCheckHandlers(void) { + if (_clsContext.readonly->debuggerAttached) { + return; + } + + FIRCLSSignalEnumerateHandledSignals(^(int idx, int signal) { + struct sigaction previousAction; + Dl_info info; + void *ptr; + + if (sigaction(signal, 0, &previousAction) != 0) { + fprintf(stderr, "Unable to read signal handler\n"); + return; + } + + ptr = previousAction.__sigaction_u.__sa_handler; + const char *signalName = NULL; + const char *codeName = NULL; + + FIRCLSSignalNameLookup(signal, 0, &signalName, &codeName); + + if (ptr == FIRCLSSignalHandler) { + return; + } + + const char *name = NULL; + if (dladdr(ptr, &info) != 0) { + name = info.dli_sname; + } + + fprintf(stderr, + "[Crashlytics] The signal %s has a non-Crashlytics handler (%s). This will interfere " + "with reporting.\n", + signalName, name); + }); +} + +void FIRCLSSignalSafeRemoveHandlers(bool includingAbort) { + for (int i = 0; i < FIRCLSSignalCount; ++i) { + struct sigaction sa; + + if (!includingAbort && (FIRCLSFatalSignals[i] == SIGABRT)) { + continue; + } + + sa.sa_handler = SIG_DFL; + sigemptyset(&sa.sa_mask); + + if (sigaction(FIRCLSFatalSignals[i], &sa, NULL) != 0) + FIRCLSSDKLog("Unable to set default handler for %d (%s)\n", i, strerror(errno)); + } +} + +bool FIRCLSSignalSafeInstallPreexistingHandlers(FIRCLSSignalReadContext *roContext) { + bool success; + + FIRCLSSignalSafeRemoveHandlers(true); + +#if CLS_USE_SIGALTSTACK + + // re-install the original stack, if needed + if (roContext->originalStack.ss_sp) { + if (sigaltstack(&roContext->originalStack, 0) != 0) { + FIRCLSSDKLog("Unable to setup stack %s\n", strerror(errno)); + + return false; + } + } + +#endif + + // re-install the original handlers, if any + success = true; + for (int i = 0; i < FIRCLSSignalCount; ++i) { + if (roContext->originalActions[i].sa_sigaction == NULL) { + continue; + } + + if (sigaction(FIRCLSFatalSignals[i], &roContext->originalActions[i], 0) != 0) { + FIRCLSSDKLog("Unable to install handler for %d (%s)\n", i, strerror(errno)); + success = false; + } + } + + return success; +} + +void FIRCLSSignalNameLookup(int number, int code, const char **name, const char **codeName) { + if (!name || !codeName) { + return; + } + + *codeName = NULL; + + switch (number) { + case SIGABRT: + *name = "SIGABRT"; + *codeName = "ABORT"; + break; + case SIGBUS: + *name = "SIGBUS"; + break; + case SIGFPE: + *name = "SIGFPE"; + break; + case SIGILL: + *name = "SIGILL"; + break; + case SIGSEGV: + *name = "SIGSEGV"; + break; + case SIGSYS: + *name = "SIGSYS"; + break; + case SIGTRAP: + *name = "SIGTRAP"; + break; + default: + *name = "UNKNOWN"; + break; + } +} + +static void FIRCLSSignalRecordSignal(int savedErrno, siginfo_t *info, void *uapVoid) { + if (!_clsContext.readonly) { + return; + } + + if (FIRCLSContextMarkAndCheckIfCrashed()) { + FIRCLSSDKLog("Error: aborting signal handler because crash has already occurred"); + exit(1); + return; + } + + FIRCLSFile file; + + if (!FIRCLSFileInitWithPath(&file, _clsContext.readonly->signal.path, false)) { + FIRCLSSDKLog("Unable to open signal file\n"); + return; + } + + FIRCLSFileWriteSectionStart(&file, "signal"); + + FIRCLSFileWriteHashStart(&file); + + if (FIRCLSIsValidPointer(info)) { + FIRCLSFileWriteHashEntryUint64(&file, "number", info->si_signo); + FIRCLSFileWriteHashEntryUint64(&file, "code", info->si_code); + FIRCLSFileWriteHashEntryUint64(&file, "address", (uint64_t)info->si_addr); + + const char *name = NULL; + const char *codeName = NULL; + + FIRCLSSignalNameLookup(info->si_signo, info->si_code, &name, &codeName); + + FIRCLSFileWriteHashEntryString(&file, "name", name); + FIRCLSFileWriteHashEntryString(&file, "code_name", codeName); + } + + FIRCLSFileWriteHashEntryUint64(&file, "errno", savedErrno); + FIRCLSFileWriteHashEntryUint64(&file, "time", time(NULL)); + + FIRCLSFileWriteHashEnd(&file); + + FIRCLSFileWriteSectionEnd(&file); + + FIRCLSHandler(&file, mach_thread_self(), uapVoid); + + FIRCLSFileClose(&file); +} + +static void FIRCLSSignalHandler(int signal, siginfo_t *info, void *uapVoid) { + int savedErrno; + sigset_t set; + + // save errno, both because it is interesting, and so we can restore it afterwards + savedErrno = errno; + errno = 0; + + FIRCLSSDKLog("Signal: %d\n", signal); + + // it is important to do this before unmasking signals, otherwise we can get + // called in a loop + FIRCLSSignalSafeRemoveHandlers(true); + + sigfillset(&set); + if (sigprocmask(SIG_UNBLOCK, &set, NULL) != 0) { + FIRCLSSDKLog("Unable to unmask signals - we risk infinite recursion here\n"); + } + + // check info and uapVoid, and set them to appropriate values if invalid. This can happen + // if we have been called without the SA_SIGINFO flag set + if (!FIRCLSIsValidPointer(info)) { + info = NULL; + } + + if (!FIRCLSIsValidPointer(uapVoid)) { + uapVoid = NULL; + } + + FIRCLSSignalRecordSignal(savedErrno, info, uapVoid); + + // re-install original handlers + if (_clsContext.readonly) { + FIRCLSSignalSafeInstallPreexistingHandlers(&_clsContext.readonly->signal); + } + + // restore errno + errno = savedErrno; +} diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Handlers/FIRCLSSignal.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Handlers/FIRCLSSignal.h new file mode 100644 index 0000000..3b6b1b4 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Handlers/FIRCLSSignal.h @@ -0,0 +1,51 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "FIRCLSFeatures.h" +#include "FIRCLSFile.h" + +#include +#include + +#define FIRCLSSignalCount (7) + +// per man sigaltstack, MINSIGSTKSZ is the minimum *overhead* needed to support +// a signal stack. The actual stack size must be larger. Let's pick the recommended +// size. +#if CLS_USE_SIGALTSTACK +#define CLS_SIGNAL_HANDLER_STACK_SIZE (SIGSTKSZ * 2) +#else +#define CLS_SIGNAL_HANDLER_STACK_SIZE 0 +#endif + +typedef struct { + const char* path; + struct sigaction originalActions[FIRCLSSignalCount]; + +#if CLS_USE_SIGALTSTACK + stack_t originalStack; +#endif +} FIRCLSSignalReadContext; + +void FIRCLSSignalInitialize(FIRCLSSignalReadContext* roContext); +void FIRCLSSignalCheckHandlers(void); + +void FIRCLSSignalSafeRemoveHandlers(bool includingAbort); +bool FIRCLSSignalSafeInstallPreexistingHandlers(FIRCLSSignalReadContext* roContext); + +void FIRCLSSignalNameLookup(int number, int code, const char** name, const char** codeName); + +void FIRCLSSignalEnumerateHandledSignals(void (^block)(int idx, int signal)); diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRAEvent+Internal.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRAEvent+Internal.h new file mode 100644 index 0000000..f0de9fe --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRAEvent+Internal.h @@ -0,0 +1,23 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef FIRAEvent_Internal_h +#define FIRAEvent_Internal_h + +#import "FIRAEvent.h" +#import "FIRAValue.h" + +NSString* FIRCLSFIRAEventDictionaryToJSON(NSDictionary* eventAsDictionary); + +#endif /* FIRAEvent_Internal_h */ diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRAEvent+Internal.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRAEvent+Internal.m new file mode 100644 index 0000000..4591e29 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRAEvent+Internal.m @@ -0,0 +1,42 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRAEvent+Internal.h" + +#import "FIRCLSUtility.h" + +NSString* FIRCLSFIRAEventDictionaryToJSON(NSDictionary* eventAsDictionary) { + NSError* error = nil; + + if (eventAsDictionary == nil) { + return nil; + } + + if (![NSJSONSerialization isValidJSONObject:eventAsDictionary]) { + FIRCLSSDKLog("Firebase Analytics event is not valid JSON"); + return nil; + } + + NSData* jsonData = [NSJSONSerialization dataWithJSONObject:eventAsDictionary + options:0 + error:&error]; + + if (error == nil) { + NSString* json = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + return json; + } else { + FIRCLSSDKLog("Unable to convert Firebase Analytics event to json"); + return nil; + } +} diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRAEvent.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRAEvent.h new file mode 100644 index 0000000..9fcbd66 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRAEvent.h @@ -0,0 +1,79 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +@class FIRAPBEvent; + +/// An application event. +@interface FIRAEvent : NSObject + +/// Origin of this event (eg "app" or "auto"). +@property(nonatomic, readonly) NSString *origin; + +/// Name of this event. +@property(nonatomic, readonly) NSString *name; + +/// Timestamp of when this event was fired. +@property(nonatomic, readonly) NSTimeInterval timestamp; + +/// Timestamp of the previous time an event with this name was fired, if any. +@property(nonatomic, readonly) NSTimeInterval previousTimestamp; + +/// The event's parameters as {NSString : NSString} or {NSString : NSNumber}. +@property(nonatomic, readonly) NSDictionary *parameters; + +/// Indicates whether the event has the conversion parameter. Setting to YES adds the conversion +/// parameter if not already present. Setting to NO removes the conversion parameter and adds an +/// error. +@property(nonatomic, getter=isConversion) BOOL conversion; + +/// Indicates whether the event has the real-time parameter. Setting to YES adds the real-time +/// parameter if not already present. Setting to NO removes the real-time parameter. +@property(nonatomic, getter=isRealtime) BOOL realtime; + +/// Indicates whether the event has debug parameter. Setting to YES adds the debug parameter if +/// not already present. Setting to NO removes the debug parameter. +@property(nonatomic, getter=isDebug) BOOL debug; + +/// The populated FIRAPBEvent for proto. +@property(nonatomic, readonly) FIRAPBEvent *protoEvent; + +/// Creates an event with the given parameters. Parameters will be copied and normalized. Returns +/// nil if the name does not meet length requirements. +/// If |parameters| contains the "_o" parameter, its value will be overwritten with the value of +/// |origin|. +- (instancetype)initWithOrigin:(NSString *)origin + isPublic:(BOOL)isPublic + name:(NSString *)name + timestamp:(NSTimeInterval)timestamp + previousTimestamp:(NSTimeInterval)previousTimestamp + parameters:(NSDictionary *)parameters NS_DESIGNATED_INITIALIZER; + +- (instancetype)init NS_UNAVAILABLE; + +/// Returns a new event object with the given previousTimestamp. +- (instancetype)copyWithPreviousTimestamp:(NSTimeInterval)previousTimestamp; + +/// Returns a new event object with the new parameters. +- (instancetype)copyWithParameters:(NSDictionary *)parameters; + +/// Returns YES if all parameters in screenParameters were added to the event object. Returns NO if +/// screenParameters is nil/empty or the event already contains any of the screen parameter keys. +/// Performs internal validation on the screen parameter values and converts them to FIRAValue +/// objects if they aren't already. screenParameters should be a dictionary of +/// { NSString : NSString | NSNumber } or { NSString : FIRAValue }. +- (BOOL)addScreenParameters:(NSDictionary *)screenParameters; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRAValue.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRAValue.h new file mode 100644 index 0000000..7d10ec3 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRAValue.h @@ -0,0 +1,69 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +typedef NS_ENUM(NSInteger, FIRAValueType) { + kFIRAValueTypeDouble = 0, + kFIRAValueTypeInteger, + kFIRAValueTypeString, +}; + +@interface FIRAValue : NSObject + +/// The type of the value. +@property(nonatomic, readonly) FIRAValueType valueType; + +#pragma mark - Double type. + +/// Indicates whether the FIRAValue instance is a floating point. +@property(nonatomic, readonly) BOOL isDouble; + +/// Float value. Check valueType to see if this attribute has float value. +@property(nonatomic, readonly) double doubleValue; + +#pragma mark - Integer type. + +/// Indicates whether the FIRAValue instance is an integer. +@property(nonatomic, readonly) BOOL isInt64; + +/// Int64 value. Check valueType to see if this attribute has int64 value. +@property(nonatomic, readonly) int64_t int64Value; + +#pragma mark - String type. + +/// Indicates whether the FIRAValue instance is a string. +@property(nonatomic, readonly) BOOL isString; + +/// String value. Check valueType to see if this attribute has string value. +@property(nonatomic, readonly) NSString *stringValue; + +#pragma mark - Initializers. + +/// Creates a @c FIRAValue if |object| is of type NSString or NSNumber. Returns |object| if it's +/// already a FIRAValue. Returns nil otherwise. ++ (instancetype)valueFromObject:(id)object; + +/// Creates a @c FIRAValue with double value. +- (instancetype)initWithDouble:(double)value; + +/// Creates a @c FIRAValue with int64 value. +- (instancetype)initWithInt64:(int64_t)value; + +/// Creates a @c FIRAValue with string value. +- (instancetype)initWithString:(NSString *)stringValue; + +- (instancetype)init NS_UNAVAILABLE; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSAllocate.c b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSAllocate.c new file mode 100644 index 0000000..febafd2 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSAllocate.c @@ -0,0 +1,238 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "FIRCLSAllocate.h" +#include "FIRCLSHost.h" +#include "FIRCLSUtility.h" + +#include +#include +#include +#include +#include +#include +#include + +void* FIRCLSAllocatorSafeAllocateFromRegion(FIRCLSAllocationRegion* region, size_t size); + +FIRCLSAllocatorRef FIRCLSAllocatorCreate(size_t writableSpace, size_t readableSpace) { + FIRCLSAllocatorRef allocator; + FIRCLSAllocationRegion writableRegion; + FIRCLSAllocationRegion readableRegion; + size_t allocationSize; + vm_size_t pageSize; + void* buffer; + + // | GUARD | WRITABLE_REGION | GUARD | READABLE_REGION | GUARD | + + pageSize = FIRCLSHostGetPageSize(); + + readableSpace += sizeof(FIRCLSAllocator); // add the space for our allocator itself + + // we can only protect at the page level, so we need all of our regions to be + // exact multples of pages. But, we don't need anything in the special-case of zero. + + writableRegion.size = 0; + if (writableSpace > 0) { + writableRegion.size = ((writableSpace / pageSize) + 1) * pageSize; + } + + readableRegion.size = 0; + if (readableSpace > 0) { + readableRegion.size = ((readableSpace / pageSize) + 1) * pageSize; + } + + // Make one big, continous allocation, adding additional pages for our guards. Note + // that we cannot use malloc (or valloc) in this case, because we need to assert full + // ownership over these allocations. mmap is a much better choice. We also mark these + // pages as MAP_NOCACHE. + allocationSize = writableRegion.size + readableRegion.size + pageSize * 3; + buffer = + mmap(0, allocationSize, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_NOCACHE, -1, 0); + if (buffer == MAP_FAILED) { + FIRCLSSDKLogError("Mapping failed %s\n", strerror(errno)); + return NULL; + } + + // move our cursors into position + writableRegion.cursor = (void*)((uintptr_t)buffer + pageSize); + readableRegion.cursor = (void*)((uintptr_t)buffer + pageSize + writableRegion.size + pageSize); + writableRegion.start = writableRegion.cursor; + readableRegion.start = readableRegion.cursor; + + FIRCLSSDKLogInfo("Mapping: %p %p %p, total: %zu K\n", buffer, writableRegion.start, + readableRegion.start, allocationSize / 1024); + + // protect first guard page + if (mprotect(buffer, pageSize, PROT_NONE) != 0) { + FIRCLSSDKLogError("First guard protection failed %s\n", strerror(errno)); + return NULL; + } + + // middle guard + if (mprotect((void*)((uintptr_t)buffer + pageSize + writableRegion.size), pageSize, PROT_NONE) != + 0) { + FIRCLSSDKLogError("Middle guard protection failed %s\n", strerror(errno)); + return NULL; + } + + // end guard + if (mprotect((void*)((uintptr_t)buffer + pageSize + writableRegion.size + pageSize + + readableRegion.size), + pageSize, PROT_NONE) != 0) { + FIRCLSSDKLogError("Last guard protection failed %s\n", strerror(errno)); + return NULL; + } + + // now, perform our first "allocation", which is to place our allocator into the read-only region + allocator = FIRCLSAllocatorSafeAllocateFromRegion(&readableRegion, sizeof(FIRCLSAllocator)); + + // set up its data structure + allocator->buffer = buffer; + allocator->protectionEnabled = false; + allocator->readableRegion = readableRegion; + allocator->writeableRegion = writableRegion; + + FIRCLSSDKLogDebug("Allocator successfully created %p", allocator); + + return allocator; +} + +void FIRCLSAllocatorDestroy(FIRCLSAllocatorRef allocator) { + if (allocator) { + } +} + +bool FIRCLSAllocatorProtect(FIRCLSAllocatorRef allocator) { + void* address; + + if (!FIRCLSIsValidPointer(allocator)) { + FIRCLSSDKLogError("Invalid allocator"); + return false; + } + + if (allocator->protectionEnabled) { + FIRCLSSDKLogWarn("Write protection already enabled"); + return true; + } + + // This has to be done first + allocator->protectionEnabled = true; + + vm_size_t pageSize = FIRCLSHostGetPageSize(); + + // readable region + address = + (void*)((uintptr_t)allocator->buffer + pageSize + allocator->writeableRegion.size + pageSize); + + return mprotect(address, allocator->readableRegion.size, PROT_READ) == 0; +} + +bool FIRCLSAllocatorUnprotect(FIRCLSAllocatorRef allocator) { + size_t bufferSize; + + if (!allocator) { + return false; + } + + vm_size_t pageSize = FIRCLSHostGetPageSize(); + + bufferSize = (uintptr_t)allocator->buffer + pageSize + allocator->writeableRegion.size + + pageSize + allocator->readableRegion.size + pageSize; + + allocator->protectionEnabled = + !(mprotect(allocator->buffer, bufferSize, PROT_READ | PROT_WRITE) == 0); + + return allocator->protectionEnabled; +} + +void* FIRCLSAllocatorSafeAllocateFromRegion(FIRCLSAllocationRegion* region, size_t size) { + void* newCursor; + void* originalCursor; + + // Here's the idea + // - read the current cursor + // - compute what our new cursor should be + // - attempt a swap + // if the swap fails, some other thread has modified stuff, and we have to start again + // if the swap works, everything has been updated correctly and we are done + do { + originalCursor = region->cursor; + + // this shouldn't happen unless we make a mistake with our size pre-computations + if ((uintptr_t)originalCursor - (uintptr_t)region->start + size > region->size) { + FIRCLSSDKLog("Unable to allocate sufficient memory, falling back to malloc\n"); + void* ptr = malloc(size); + if (!ptr) { + FIRCLSSDKLog("Unable to malloc in FIRCLSAllocatorSafeAllocateFromRegion\n"); + return NULL; + } + return ptr; + } + + newCursor = (void*)((uintptr_t)originalCursor + size); + } while (!atomic_compare_exchange_strong(®ion->cursor, &originalCursor, newCursor)); + + return originalCursor; +} + +void* FIRCLSAllocatorSafeAllocate(FIRCLSAllocatorRef allocator, + size_t size, + FIRCLSAllocationType type) { + FIRCLSAllocationRegion* region; + + if (!allocator) { + // fall back to malloc in this case + FIRCLSSDKLog("Allocator invalid, falling back to malloc\n"); + void* ptr = malloc(size); + if (!ptr) { + FIRCLSSDKLog("Unable to malloc in FIRCLSAllocatorSafeAllocate\n"); + return NULL; + } + return ptr; + } + + if (allocator->protectionEnabled) { + FIRCLSSDKLog("Allocator already protected, falling back to malloc\n"); + void* ptr = malloc(size); + if (!ptr) { + FIRCLSSDKLog("Unable to malloc in FIRCLSAllocatorSafeAllocate\n"); + return NULL; + } + return ptr; + } + + switch (type) { + case CLS_READONLY: + region = &allocator->readableRegion; + break; + case CLS_READWRITE: + region = &allocator->writeableRegion; + break; + default: + return NULL; + } + + return FIRCLSAllocatorSafeAllocateFromRegion(region, size); +} + +void FIRCLSAllocatorFree(FIRCLSAllocatorRef allocator, void* ptr) { + if (!allocator) { + free(ptr); + } + + // how do we do deallocations? +} diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSAllocate.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSAllocate.h new file mode 100644 index 0000000..66fcda4 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSAllocate.h @@ -0,0 +1,54 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FIRCLSFeatures.h" + +#pragma once + +#include +#include + +typedef enum { CLS_READONLY = 0, CLS_READWRITE = 1 } FIRCLSAllocationType; + +typedef struct { + size_t size; + void* start; + _Atomic(void*) volatile cursor; +} FIRCLSAllocationRegion; + +typedef struct { + void* buffer; + bool protectionEnabled; + FIRCLSAllocationRegion writeableRegion; + FIRCLSAllocationRegion readableRegion; +} FIRCLSAllocator; +typedef FIRCLSAllocator* FIRCLSAllocatorRef; + +FIRCLSAllocatorRef FIRCLSAllocatorCreate(size_t writableSpace, size_t readableSpace); +void FIRCLSAllocatorDestroy(FIRCLSAllocatorRef allocator); + +bool FIRCLSAllocatorProtect(FIRCLSAllocatorRef allocator); +bool FIRCLSAllocatorUnprotect(FIRCLSAllocatorRef allocator); + +void* FIRCLSAllocatorSafeAllocate(FIRCLSAllocatorRef allocator, + size_t size, + FIRCLSAllocationType type); +const char* FIRCLSAllocatorSafeStrdup(FIRCLSAllocatorRef allocator, const char* string); +void FIRCLSAllocatorFree(FIRCLSAllocatorRef allocator, void* ptr); + +#if CLS_MEMORY_PROTECTION_ENABLED +#define FIRCLSFree(x) FIRCLSAllocatorFree(_clsContext.allocator, (void*)(x)); +#else +#define FIRCLSFree(x) free((void*)(x)) +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSDefines.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSDefines.h new file mode 100644 index 0000000..27def6e --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSDefines.h @@ -0,0 +1,81 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "TargetConditionals.h" + +// macro trickiness +#define STR_HELPER(x) #x +#define STR(x) STR_HELPER(x) +#define CONCAT_EXPANDED(a, b) a##b +#define CONCAT(a, b) CONCAT_EXPANDED(a, b) + +// These macros generate a function to force a symbol for the containing .o, to work around an issue +// where strip will not strip debug information without a symbol to strip. +#define DUMMY_FUNCTION_NAME(x) CONCAT(strip_this_, x) +#define INJECT_STRIP_SYMBOL(x) \ + void DUMMY_FUNCTION_NAME(x)(void) { \ + } + +// These make some target os types available to previous versions of xcode that do not yet have them +// in their SDKs +#ifndef TARGET_OS_IOS +#define TARGET_OS_IOS TARGET_OS_IPHONE +#endif + +#ifndef TARGET_OS_WATCH +#define TARGET_OS_WATCH 0 +#endif + +#ifndef TARGET_OS_TV +#define TARGET_OS_TV 0 +#endif + +// These help compile based on availability of technologies/frameworks. +#define CLS_TARGET_OS_OSX (TARGET_OS_MAC && !TARGET_OS_IPHONE) +#define CLS_TARGET_OS_HAS_UIKIT (TARGET_OS_IOS || TARGET_OS_TV) + +#define CLS_SDK_DISPLAY_VERSION STR(DISPLAY_VERSION) + +#define CLS_SDK_GENERATOR_NAME (STR(CLS_SDK_NAME) "/" CLS_SDK_DISPLAY_VERSION) + +// arch definitions +#if defined(__arm__) || defined(__arm64__) || defined(__arm64e__) +#include +#endif + +#if defined(__arm__) +#define CLS_CPU_ARM 1 +#endif +#if defined(__arm64__) || defined(__arm64e__) +#define CLS_CPU_ARM64 1 +#endif +#if defined(__ARM_ARCH_7S__) +#define CLS_CPU_ARMV7S 1 +#endif +#if defined(_ARM_ARCH_7) +#define CLS_CPU_ARMV7 1 +#endif +#if defined(_ARM_ARCH_6) +#define CLS_CPU_ARMV6 1 +#endif +#if defined(__i386__) +#define CLS_CPU_I386 1 +#endif +#if defined(__x86_64__) +#define CLS_CPU_X86_64 1 +#endif +#define CLS_CPU_X86 (CLS_CPU_I386 || CLS_CPU_X86_64) +#define CLS_CPU_64BIT (CLS_CPU_X86_64 || CLS_CPU_ARM64) diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSFCRAnalytics.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSFCRAnalytics.h new file mode 100644 index 0000000..098833f --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSFCRAnalytics.h @@ -0,0 +1,32 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +@class FIRCLSSettings; +@protocol FIRAnalyticsInterop; +@protocol FIRAnalyticsInteropListener; + +@interface FIRCLSFCRAnalytics : NSObject + +/** Logs a Crashlytics crash session in Firebase Analytics. + * @param crashTimeStamp The time stamp of the crash to be logged. + */ ++ (void)logCrashWithTimeStamp:(NSTimeInterval)crashTimeStamp + toAnalytics:(id)analytics; + ++ (void)registerEventListener:(id)eventListener + toAnalytics:(id)analytics; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSFCRAnalytics.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSFCRAnalytics.m new file mode 100644 index 0000000..6901290 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSFCRAnalytics.m @@ -0,0 +1,90 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSFCRAnalytics.h" + +#import "FIRCLSInternalLogging.h" +#import "FIRCLSSettings.h" + +#import + +// Origin for events and user properties generated by Crashlytics. +static NSString *const kFIREventOriginCrash = @"clx"; + +// Origin for events and user properties generated by Crash Reporting. +// We're using this for now because it's whitelisted, and "clx" is not. +// TODO(b/144163610) add a separate parameter for Crashlytics +static NSString *const kFIREventListenerOriginCrash = @"crash"; + +// App exception event name. +static NSString *const kFIREventAppException = @"_ae"; + +// Timestamp key for the event payload. +static NSString *const kFIRParameterTimestamp = @"timestamp"; + +// Fatal key for the event payload. +static NSString *const kFIRParameterFatal = @"fatal"; + +// Realtime flag +// TODO(b/144114243) We had to remove this because it's blacklisted. +// Previously, the value was set as: +// static NSString *const kFIRParameterRealtime = @"_r"; +// and used in buildLogParamsFromCrash as: +// kFIRParameterRealtime : @(INT64_C(1)), + +FOUNDATION_STATIC_INLINE NSNumber *timeIntervalInMillis(NSTimeInterval timeInterval) { + return @(llrint(timeInterval * 1000.0)); +} + +@implementation FIRCLSFCRAnalytics + ++ (void)logCrashWithTimeStamp:(NSTimeInterval)crashTimeStamp + toAnalytics:(id)analytics { + if (analytics == nil) { + return; + } + + FIRCLSDeveloperLog(@"Crashlytics:Crash:Reports:Event", "Sending event."); + NSDictionary *params = [self buildLogParamsFromCrash:crashTimeStamp]; + [analytics logEventWithOrigin:kFIREventOriginCrash name:kFIREventAppException parameters:params]; +} + ++ (void)registerEventListener:(id)eventListener + toAnalytics:(id)analytics { + if (analytics == nil) { + return; + } + + [analytics registerAnalyticsListener:eventListener withOrigin:kFIREventListenerOriginCrash]; + + FIRCLSDeveloperLog(@"Crashlytics:Crash:Reports:Event", + "Registered Firebase Analytics event listener"); +} + +/** + * Builds a dictionary of params to be sent to Analytics using the crash object. + * + * @param crashTimeStamp The time stamp of the crash to be logged. + * + * @return An NSDictionary containing the time the crash occured and the fatal + * flag to be fed into Firebase Analytics. + */ ++ (NSDictionary *)buildLogParamsFromCrash:(NSTimeInterval)crashTimeStamp { + return @{ + kFIRParameterTimestamp : timeIntervalInMillis(crashTimeStamp), + kFIRParameterFatal : @(INT64_C(1)) + }; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSFeatures.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSFeatures.h new file mode 100644 index 0000000..4810a5d --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSFeatures.h @@ -0,0 +1,31 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "FIRCLSDefines.h" + +#define CLS_MEMORY_PROTECTION_ENABLED 1 +#define CLS_COMPACT_UNWINDED_ENABLED 1 +#define CLS_DWARF_UNWINDING_ENABLED 1 + +#define CLS_USE_SIGALTSTACK (!TARGET_OS_WATCH && !TARGET_OS_TV) +#define CLS_CAN_SUSPEND_THREADS !TARGET_OS_WATCH +#define CLS_MACH_EXCEPTION_SUPPORTED (!TARGET_OS_WATCH && !TARGET_OS_TV) + +#define CLS_COMPACT_UNWINDING_SUPPORTED \ + ((CLS_CPU_I386 || CLS_CPU_X86_64 || CLS_CPU_ARM64) && CLS_COMPACT_UNWINDED_ENABLED) + +#define CLS_DWARF_UNWINDING_SUPPORTED \ + (CLS_COMPACT_UNWINDING_SUPPORTED && CLS_DWARF_UNWINDING_ENABLED) diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSFile.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSFile.h new file mode 100644 index 0000000..ad2e54d --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSFile.h @@ -0,0 +1,104 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include + +#if defined(__OBJC__) +#import +#endif + +__BEGIN_DECLS + +typedef struct { + int fd; + int collectionDepth; + bool needComma; + + bool bufferWrites; + char* writeBuffer; + size_t writeBufferLength; + + off_t writtenLength; +} FIRCLSFile; +typedef FIRCLSFile* FIRCLSFileRef; + +#define CLS_FILE_MAX_STRING_LENGTH (10240) +#define CLS_FILE_HEX_BUFFER \ + (32) // must be at least 2, and should be even (to account for 2 chars per hex value) +#define CLS_FILE_MAX_WRITE_ATTEMPTS (50) + +extern const size_t FIRCLSWriteBufferLength; + +// make sure to stop work if either FIRCLSFileInit... method returns false, because the FIRCLSFile +// struct will contain garbage data! +bool FIRCLSFileInitWithPath(FIRCLSFile* file, const char* path, bool bufferWrites); +bool FIRCLSFileInitWithPathMode(FIRCLSFile* file, + const char* path, + bool appendMode, + bool bufferWrites); + +void FIRCLSFileFlushWriteBuffer(FIRCLSFile* file); +bool FIRCLSFileClose(FIRCLSFile* file); +bool FIRCLSFileCloseWithOffset(FIRCLSFile* file, off_t* finalSize); +bool FIRCLSFileIsOpen(FIRCLSFile* file); + +bool FIRCLSFileLoopWithWriteBlock(const void* buffer, + size_t length, + ssize_t (^writeBlock)(const void* partialBuffer, + size_t partialLength)); +bool FIRCLSFileWriteWithRetries(int fd, const void* buffer, size_t length); + +// writing +void FIRCLSFileWriteSectionStart(FIRCLSFile* file, const char* name); +void FIRCLSFileWriteSectionEnd(FIRCLSFile* file); + +void FIRCLSFileWriteHashStart(FIRCLSFile* file); +void FIRCLSFileWriteHashEnd(FIRCLSFile* file); +void FIRCLSFileWriteHashKey(FIRCLSFile* file, const char* key); +void FIRCLSFileWriteHashEntryUint64(FIRCLSFile* file, const char* key, uint64_t value); +void FIRCLSFileWriteHashEntryInt64(FIRCLSFile* file, const char* key, int64_t value); +void FIRCLSFileWriteHashEntryString(FIRCLSFile* file, const char* key, const char* value); +#if defined(__OBJC__) +void FIRCLSFileWriteHashEntryNSString(FIRCLSFile* file, const char* key, NSString* string); +void FIRCLSFileWriteHashEntryNSStringUnlessNilOrEmpty(FIRCLSFile* file, + const char* key, + NSString* string); +#endif +void FIRCLSFileWriteHashEntryHexEncodedString(FIRCLSFile* file, const char* key, const char* value); +void FIRCLSFileWriteHashEntryBoolean(FIRCLSFile* file, const char* key, bool value); + +void FIRCLSFileWriteArrayStart(FIRCLSFile* file); +void FIRCLSFileWriteArrayEnd(FIRCLSFile* file); +void FIRCLSFileWriteArrayEntryUint64(FIRCLSFile* file, uint64_t value); +void FIRCLSFileWriteArrayEntryString(FIRCLSFile* file, const char* value); +void FIRCLSFileWriteArrayEntryHexEncodedString(FIRCLSFile* file, const char* value); + +void FIRCLSFileFDWriteUInt64(int fd, uint64_t number, bool hex); +void FIRCLSFileFDWriteInt64(int fd, int64_t number); +void FIRCLSFileWriteUInt64(FIRCLSFile* file, uint64_t number, bool hex); +void FIRCLSFileWriteInt64(FIRCLSFile* file, int64_t number); + +#if defined(__OBJC__) && TARGET_OS_MAC +NSArray* FIRCLSFileReadSections(const char* path, + bool deleteOnFailure, + NSObject* (^transformer)(id obj)); +NSString* FIRCLSFileHexEncodeString(const char* string); +NSString* FIRCLSFileHexDecodeString(const char* string); +#endif + +__END_DECLS diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSFile.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSFile.m new file mode 100644 index 0000000..10b96d3 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSFile.m @@ -0,0 +1,702 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FIRCLSFile.h" + +#include "FIRCLSByteUtility.h" +#include "FIRCLSUtility.h" + +#if TARGET_OS_MAC +#include +#endif + +#include + +#include +#include + +#include + +// uint64_t should only have max 19 chars in base 10, and less in base 16 +static const size_t FIRCLSUInt64StringBufferLength = 21; +static const size_t FIRCLSStringBufferLength = 16; +const size_t FIRCLSWriteBufferLength = 1000; + +static bool FIRCLSFileInit(FIRCLSFile* file, int fdm, bool appendMode, bool bufferWrites); + +static void FIRCLSFileWriteToFileDescriptorOrBuffer(FIRCLSFile* file, + const char* string, + size_t length); +static void FIRCLSFileWriteToBuffer(FIRCLSFile* file, const char* string, size_t length); +static void FIRCLSFileWriteToFileDescriptor(FIRCLSFile* file, const char* string, size_t length); + +short FIRCLSFilePrepareUInt64(char* buffer, uint64_t number, bool hex); + +static void FIRCLSFileWriteString(FIRCLSFile* file, const char* string); +static void FIRCLSFileWriteHexEncodedString(FIRCLSFile* file, const char* string); +static void FIRCLSFileWriteBool(FIRCLSFile* file, bool value); + +static void FIRCLSFileWriteCollectionStart(FIRCLSFile* file, const char openingChar); +static void FIRCLSFileWriteCollectionEnd(FIRCLSFile* file, const char closingChar); +static void FIRCLSFileWriteColletionEntryProlog(FIRCLSFile* file); +static void FIRCLSFileWriteColletionEntryEpilog(FIRCLSFile* file); + +#define CLS_FILE_DEBUG_LOGGING 0 + +#pragma mark - File Structure +static bool FIRCLSFileInit(FIRCLSFile* file, int fd, bool appendMode, bool bufferWrites) { + if (!file) { + FIRCLSSDKLog("Error: file is null\n"); + return false; + } + + if (fd < 0) { + FIRCLSSDKLog("Error: file descriptor invalid\n"); + return false; + } + + memset(file, 0, sizeof(FIRCLSFile)); + + file->fd = fd; + + file->bufferWrites = bufferWrites; + if (bufferWrites) { + file->writeBuffer = malloc(FIRCLSWriteBufferLength * sizeof(char)); + if (!file->writeBuffer) { + FIRCLSErrorLog(@"Unable to malloc in FIRCLSFileInit"); + return false; + } + + file->writeBufferLength = 0; + } + + file->writtenLength = 0; + if (appendMode) { + struct stat fileStats; + fstat(fd, &fileStats); + off_t currentFileSize = fileStats.st_size; + if (currentFileSize > 0) { + file->writtenLength += currentFileSize; + } + } + + return true; +} + +bool FIRCLSFileInitWithPath(FIRCLSFile* file, const char* path, bool bufferWrites) { + return FIRCLSFileInitWithPathMode(file, path, true, bufferWrites); +} + +bool FIRCLSFileInitWithPathMode(FIRCLSFile* file, + const char* path, + bool appendMode, + bool bufferWrites) { + if (!file) { + FIRCLSSDKLog("Error: file is null\n"); + return false; + } + + int mask = O_WRONLY | O_CREAT; + + if (appendMode) { + mask |= O_APPEND; + } else { + mask |= O_TRUNC; + } + + // make sure to call FIRCLSFileInit no matter what + int fd = -1; + if (path) { +#if TARGET_OS_IPHONE + /* + * data-protected non-portable open(2) : + * int open_dprotected_np(user_addr_t path, int flags, int class, int dpflags, int mode) + */ + fd = open_dprotected_np(path, mask, 4, 0, 0644); +#else + fd = open(path, mask, 0644); +#endif + + if (fd < 0) { + FIRCLSSDKLog("Error: Unable to open file %s\n", strerror(errno)); + } + } + + return FIRCLSFileInit(file, fd, appendMode, bufferWrites); +} + +bool FIRCLSFileClose(FIRCLSFile* file) { + return FIRCLSFileCloseWithOffset(file, NULL); +} + +bool FIRCLSFileCloseWithOffset(FIRCLSFile* file, off_t* finalSize) { + if (!FIRCLSIsValidPointer(file)) { + return false; + } + + if (file->bufferWrites && FIRCLSIsValidPointer(file->writeBuffer)) { + if (file->writeBufferLength > 0) { + FIRCLSFileFlushWriteBuffer(file); + } + free(file->writeBuffer); + } + + if (FIRCLSIsValidPointer(finalSize)) { + *finalSize = file->writtenLength; + } + + if (close(file->fd) != 0) { + FIRCLSSDKLog("Error: Unable to close file %s\n", strerror(errno)); + return false; + } + + memset(file, 0, sizeof(FIRCLSFile)); + file->fd = -1; + + return true; +} + +bool FIRCLSFileIsOpen(FIRCLSFile* file) { + if (!FIRCLSIsValidPointer(file)) { + return false; + } + + return file->fd > -1; +} + +#pragma mark - Core Writing API +void FIRCLSFileFlushWriteBuffer(FIRCLSFile* file) { + if (!FIRCLSIsValidPointer(file)) { + return; + } + + if (!file->bufferWrites) { + return; + } + + FIRCLSFileWriteToFileDescriptor(file, file->writeBuffer, file->writeBufferLength); + file->writeBufferLength = 0; +} + +static void FIRCLSFileWriteToFileDescriptorOrBuffer(FIRCLSFile* file, + const char* string, + size_t length) { + if (file->bufferWrites) { + if (file->writeBufferLength + length > FIRCLSWriteBufferLength - 1) { + // fill remaining space in buffer + size_t remainingSpace = FIRCLSWriteBufferLength - file->writeBufferLength - 1; + FIRCLSFileWriteToBuffer(file, string, remainingSpace); + FIRCLSFileFlushWriteBuffer(file); + + // write remainder of string to newly-emptied buffer + size_t remainingLength = length - remainingSpace; + FIRCLSFileWriteToFileDescriptorOrBuffer(file, string + remainingSpace, remainingLength); + } else { + FIRCLSFileWriteToBuffer(file, string, length); + } + } else { + FIRCLSFileWriteToFileDescriptor(file, string, length); + } +} + +static void FIRCLSFileWriteToFileDescriptor(FIRCLSFile* file, const char* string, size_t length) { + if (!FIRCLSFileWriteWithRetries(file->fd, string, length)) { + return; + } + + file->writtenLength += length; +} + +// Beware calling this method directly: it will truncate the input string if it's longer +// than the remaining space in the buffer. It's safer to call through +// FIRCLSFileWriteToFileDescriptorOrBuffer. +static void FIRCLSFileWriteToBuffer(FIRCLSFile* file, const char* string, size_t length) { + size_t writeLength = length; + if (file->writeBufferLength + writeLength > FIRCLSWriteBufferLength - 1) { + writeLength = FIRCLSWriteBufferLength - file->writeBufferLength - 1; + } + strncpy(file->writeBuffer + file->writeBufferLength, string, writeLength); + file->writeBufferLength += writeLength; + file->writeBuffer[file->writeBufferLength] = '\0'; +} + +bool FIRCLSFileLoopWithWriteBlock(const void* buffer, + size_t length, + ssize_t (^writeBlock)(const void* buf, size_t len)) { + for (size_t count = 0; length > 0 && count < CLS_FILE_MAX_WRITE_ATTEMPTS; ++count) { + // try to write all that is left + ssize_t ret = writeBlock(buffer, length); + if (ret >= 0 && ret == length) { + return true; + } + + // Write was unsuccessful (out of space, etc) + if (ret < 0) { + return false; + } + + // We wrote more bytes than we expected, abort + if (ret > length) { + return false; + } + + // wrote a portion of the data, adjust and keep trying + if (ret > 0) { + length -= ret; + buffer += ret; + continue; + } + + // return value is <= 0, which is an error + break; + } + + return false; +} + +bool FIRCLSFileWriteWithRetries(int fd, const void* buffer, size_t length) { + return FIRCLSFileLoopWithWriteBlock(buffer, length, + ^ssize_t(const void* partialBuffer, size_t partialLength) { + return write(fd, partialBuffer, partialLength); + }); +} + +#pragma mark - Strings + +static void FIRCLSFileWriteUnbufferedStringWithSuffix(FIRCLSFile* file, + const char* string, + size_t length, + char suffix) { + char suffixBuffer[2]; + + // collaspe the quote + suffix into one single write call, for a small performance win + suffixBuffer[0] = '"'; + suffixBuffer[1] = suffix; + + FIRCLSFileWriteToFileDescriptorOrBuffer(file, "\"", 1); + FIRCLSFileWriteToFileDescriptorOrBuffer(file, string, length); + FIRCLSFileWriteToFileDescriptorOrBuffer(file, suffixBuffer, suffix == 0 ? 1 : 2); +} + +static void FIRCLSFileWriteStringWithSuffix(FIRCLSFile* file, + const char* string, + size_t length, + char suffix) { + // 2 for quotes, 1 for suffix (if present) and 1 more for null character + const size_t maxStringSize = FIRCLSStringBufferLength - (suffix == 0 ? 3 : 4); + + if (length >= maxStringSize) { + FIRCLSFileWriteUnbufferedStringWithSuffix(file, string, length, suffix); + return; + } + + // we are trying to achieve this in one write call + // <"><"> + + char buffer[FIRCLSStringBufferLength]; + + buffer[0] = '"'; + + strncpy(buffer + 1, string, length); + + buffer[length + 1] = '"'; + length += 2; + + if (suffix) { + buffer[length] = suffix; + length += 1; + } + + // Always add the terminator. strncpy above would copy the terminator, if we supplied length + 1, + // but since we do this suffix adjustment here, it's easier to just fix it up in both cases. + buffer[length + 1] = 0; + + FIRCLSFileWriteToFileDescriptorOrBuffer(file, buffer, length); +} + +void FIRCLSFileWriteString(FIRCLSFile* file, const char* string) { + if (!string) { + FIRCLSFileWriteToFileDescriptorOrBuffer(file, "null", 4); + return; + } + + FIRCLSFileWriteStringWithSuffix(file, string, strlen(string), 0); +} + +void FIRCLSFileWriteHexEncodedString(FIRCLSFile* file, const char* string) { + if (!file) { + return; + } + + if (!string) { + FIRCLSFileWriteToFileDescriptorOrBuffer(file, "null", 4); + return; + } + + char buffer[CLS_FILE_HEX_BUFFER]; + + memset(buffer, 0, sizeof(buffer)); + + size_t length = strlen(string); + + FIRCLSFileWriteToFileDescriptorOrBuffer(file, "\"", 1); + + int bufferIndex = 0; + for (int i = 0; i < length; ++i) { + FIRCLSHexFromByte(string[i], &buffer[bufferIndex]); + + bufferIndex += 2; // 1 char => 2 hex values at a time + + // we can continue only if we have enough space for two more hex + // characters *and* a terminator. So, we need three total chars + // of space + if (bufferIndex >= CLS_FILE_HEX_BUFFER) { + FIRCLSFileWriteToFileDescriptorOrBuffer(file, buffer, CLS_FILE_HEX_BUFFER); + bufferIndex = 0; + } + } + + // Copy the remainder, which could even be the entire string, if it + // fit into the buffer completely. Be careful with bounds checking here. + // The string needs to be non-empty, and we have to have copied at least + // one pair of hex characters in. + if (bufferIndex > 0 && length > 0) { + FIRCLSFileWriteToFileDescriptorOrBuffer(file, buffer, bufferIndex); + } + + FIRCLSFileWriteToFileDescriptorOrBuffer(file, "\"", 1); +} + +#pragma mark - Integers +void FIRCLSFileWriteUInt64(FIRCLSFile* file, uint64_t number, bool hex) { + char buffer[FIRCLSUInt64StringBufferLength]; + short i = FIRCLSFilePrepareUInt64(buffer, number, hex); + char* beginning = &buffer[i]; // Write from a pointer to the begining of the string. + FIRCLSFileWriteToFileDescriptorOrBuffer(file, beginning, strlen(beginning)); +} + +void FIRCLSFileFDWriteUInt64(int fd, uint64_t number, bool hex) { + char buffer[FIRCLSUInt64StringBufferLength]; + short i = FIRCLSFilePrepareUInt64(buffer, number, hex); + char* beginning = &buffer[i]; // Write from a pointer to the begining of the string. + FIRCLSFileWriteWithRetries(fd, beginning, strlen(beginning)); +} + +void FIRCLSFileWriteInt64(FIRCLSFile* file, int64_t number) { + if (number < 0) { + FIRCLSFileWriteToFileDescriptorOrBuffer(file, "-", 1); + number *= -1; // make it positive + } + + FIRCLSFileWriteUInt64(file, number, false); +} + +void FIRCLSFileFDWriteInt64(int fd, int64_t number) { + if (number < 0) { + FIRCLSFileWriteWithRetries(fd, "-", 1); + number *= -1; // make it positive + } + + FIRCLSFileFDWriteUInt64(fd, number, false); +} + +short FIRCLSFilePrepareUInt64(char* buffer, uint64_t number, bool hex) { + uint32_t base = hex ? 16 : 10; + + // zero it out, which will add a terminator + memset(buffer, 0, FIRCLSUInt64StringBufferLength); + + // TODO: look at this closer + // I'm pretty sure there is a bug in this code that + // can result in numbers with leading zeros. Technically, + // those are not valid json. + + // Set current index. + short i = FIRCLSUInt64StringBufferLength - 1; + + // Loop through filling in the chars from the end. + do { + char value = number % base + '0'; + if (value > '9') { + value += 'a' - '9' - 1; + } + + buffer[--i] = value; + } while ((number /= base) > 0 && i > 0); + + // returns index pointing to the beginning of the string. + return i; +} + +void FIRCLSFileWriteBool(FIRCLSFile* file, bool value) { + if (value) { + FIRCLSFileWriteToFileDescriptorOrBuffer(file, "true", 4); + } else { + FIRCLSFileWriteToFileDescriptorOrBuffer(file, "false", 5); + } +} + +void FIRCLSFileWriteSectionStart(FIRCLSFile* file, const char* name) { + FIRCLSFileWriteHashStart(file); + FIRCLSFileWriteHashKey(file, name); +} + +void FIRCLSFileWriteSectionEnd(FIRCLSFile* file) { + FIRCLSFileWriteHashEnd(file); + FIRCLSFileWriteToFileDescriptorOrBuffer(file, "\n", 1); +} + +void FIRCLSFileWriteCollectionStart(FIRCLSFile* file, const char openingChar) { + char string[2]; + + string[0] = ','; + string[1] = openingChar; + + if (file->needComma) { + FIRCLSFileWriteToFileDescriptorOrBuffer(file, string, 2); // write the seperator + opening char + } else { + FIRCLSFileWriteToFileDescriptorOrBuffer(file, &string[1], 1); // write only the opening char + } + + file->collectionDepth++; + + file->needComma = false; +} + +void FIRCLSFileWriteCollectionEnd(FIRCLSFile* file, const char closingChar) { + FIRCLSFileWriteToFileDescriptorOrBuffer(file, &closingChar, 1); + + if (file->collectionDepth <= 0) { + // FIRCLSSafeLog("Collection depth invariant violated\n"); + return; + } + + file->collectionDepth--; + + file->needComma = file->collectionDepth > 0; +} + +void FIRCLSFileWriteColletionEntryProlog(FIRCLSFile* file) { + if (file->needComma) { + FIRCLSFileWriteToFileDescriptorOrBuffer(file, ",", 1); + } +} + +void FIRCLSFileWriteColletionEntryEpilog(FIRCLSFile* file) { + file->needComma = true; +} + +void FIRCLSFileWriteHashStart(FIRCLSFile* file) { + FIRCLSFileWriteCollectionStart(file, '{'); +} + +void FIRCLSFileWriteHashEnd(FIRCLSFile* file) { + FIRCLSFileWriteCollectionEnd(file, '}'); +} + +void FIRCLSFileWriteHashKey(FIRCLSFile* file, const char* key) { + FIRCLSFileWriteColletionEntryProlog(file); + + FIRCLSFileWriteStringWithSuffix(file, key, strlen(key), ':'); + + file->needComma = false; +} + +void FIRCLSFileWriteHashEntryUint64(FIRCLSFile* file, const char* key, uint64_t value) { + // no prolog needed because it comes from the key + + FIRCLSFileWriteHashKey(file, key); + FIRCLSFileWriteUInt64(file, value, false); + + FIRCLSFileWriteColletionEntryEpilog(file); +} + +void FIRCLSFileWriteHashEntryInt64(FIRCLSFile* file, const char* key, int64_t value) { + // prolog from key + FIRCLSFileWriteHashKey(file, key); + FIRCLSFileWriteInt64(file, value); + + FIRCLSFileWriteColletionEntryEpilog(file); +} + +void FIRCLSFileWriteHashEntryString(FIRCLSFile* file, const char* key, const char* value) { + FIRCLSFileWriteHashKey(file, key); + FIRCLSFileWriteString(file, value); + + FIRCLSFileWriteColletionEntryEpilog(file); +} + +void FIRCLSFileWriteHashEntryNSString(FIRCLSFile* file, const char* key, NSString* string) { + FIRCLSFileWriteHashEntryString(file, key, [string UTF8String]); +} + +void FIRCLSFileWriteHashEntryNSStringUnlessNilOrEmpty(FIRCLSFile* file, + const char* key, + NSString* string) { + if ([string length] > 0) { + FIRCLSFileWriteHashEntryString(file, key, [string UTF8String]); + } +} + +void FIRCLSFileWriteHashEntryHexEncodedString(FIRCLSFile* file, + const char* key, + const char* value) { + FIRCLSFileWriteHashKey(file, key); + FIRCLSFileWriteHexEncodedString(file, value); + + FIRCLSFileWriteColletionEntryEpilog(file); +} + +void FIRCLSFileWriteHashEntryBoolean(FIRCLSFile* file, const char* key, bool value) { + FIRCLSFileWriteHashKey(file, key); + FIRCLSFileWriteBool(file, value); + + FIRCLSFileWriteColletionEntryEpilog(file); +} + +void FIRCLSFileWriteArrayStart(FIRCLSFile* file) { + FIRCLSFileWriteCollectionStart(file, '['); +} + +void FIRCLSFileWriteArrayEnd(FIRCLSFile* file) { + FIRCLSFileWriteCollectionEnd(file, ']'); +} + +void FIRCLSFileWriteArrayEntryUint64(FIRCLSFile* file, uint64_t value) { + FIRCLSFileWriteColletionEntryProlog(file); + + FIRCLSFileWriteUInt64(file, value, false); + + FIRCLSFileWriteColletionEntryEpilog(file); +} + +void FIRCLSFileWriteArrayEntryString(FIRCLSFile* file, const char* value) { + FIRCLSFileWriteColletionEntryProlog(file); + + FIRCLSFileWriteString(file, value); + + FIRCLSFileWriteColletionEntryEpilog(file); +} + +void FIRCLSFileWriteArrayEntryHexEncodedString(FIRCLSFile* file, const char* value) { + FIRCLSFileWriteColletionEntryProlog(file); + + FIRCLSFileWriteHexEncodedString(file, value); + + FIRCLSFileWriteColletionEntryEpilog(file); +} + +NSArray* FIRCLSFileReadSections(const char* path, + bool deleteOnFailure, + NSObject* (^transformer)(id obj)) { + if (!FIRCLSIsValidPointer(path)) { + FIRCLSSDKLogError("Error: input path is invalid\n"); + return nil; + } + + NSString* pathString = [NSString stringWithUTF8String:path]; + NSString* contents = [NSString stringWithContentsOfFile:pathString + encoding:NSUTF8StringEncoding + error:nil]; + NSArray* components = [contents componentsSeparatedByString:@"\n"]; + + if (!components) { + if (deleteOnFailure) { + unlink(path); + } + + FIRCLSSDKLog("Unable to read file %s\n", path); + return nil; + } + + NSMutableArray* array = [NSMutableArray array]; + + // loop through all the entires, and + for (NSString* component in components) { + NSData* data = [component dataUsingEncoding:NSUTF8StringEncoding]; + + id obj = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; + if (!obj) { + continue; + } + + if (transformer) { + obj = transformer(obj); + } + + if (!obj) { + continue; + } + + [array addObject:obj]; + } + + return array; +} + +NSString* FIRCLSFileHexEncodeString(const char* string) { + size_t length = strlen(string); + char* encodedBuffer = malloc(length * 2 + 1); + + if (!encodedBuffer) { + FIRCLSErrorLog(@"Unable to malloc in FIRCLSFileHexEncodeString"); + return nil; + } + + memset(encodedBuffer, 0, length * 2 + 1); + + int bufferIndex = 0; + for (int i = 0; i < length; ++i) { + FIRCLSHexFromByte(string[i], &encodedBuffer[bufferIndex]); + + bufferIndex += 2; // 1 char => 2 hex values at a time + } + + NSString* stringObject = [NSString stringWithUTF8String:encodedBuffer]; + + free(encodedBuffer); + + return stringObject; +} + +NSString* FIRCLSFileHexDecodeString(const char* string) { + size_t length = strlen(string); + char* decodedBuffer = malloc(length); // too long, but safe + if (!decodedBuffer) { + FIRCLSErrorLog(@"Unable to malloc in FIRCLSFileHexDecodeString"); + return nil; + } + + memset(decodedBuffer, 0, length); + + for (int i = 0; i < length / 2; ++i) { + size_t index = i * 2; + + uint8_t hiNybble = FIRCLSNybbleFromChar(string[index]); + uint8_t lowNybble = FIRCLSNybbleFromChar(string[index + 1]); + + if (hiNybble == FIRCLSInvalidCharNybble || lowNybble == FIRCLSInvalidCharNybble) { + // char is invalid, abort loop + break; + } + + decodedBuffer[i] = (hiNybble << 4) | lowNybble; + } + + NSString* strObject = [NSString stringWithUTF8String:decodedBuffer]; + + free(decodedBuffer); + + return strObject; +} diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSInternalLogging.c b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSInternalLogging.c new file mode 100644 index 0000000..13d4ea5 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSInternalLogging.c @@ -0,0 +1,96 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FIRCLSInternalLogging.h" +#include "FIRCLSContext.h" +#include "FIRCLSGlobals.h" +#include "FIRCLSUtility.h" + +void FIRCLSSDKFileLog(FIRCLSInternalLogLevel level, const char* format, ...) { + if (!_clsContext.readonly || !_clsContext.writable) { + return; + } + + const char* path = _clsContext.readonly->logPath; + if (!FIRCLSIsValidPointer(path)) { + return; + } + + if (_clsContext.writable->internalLogging.logLevel > level) { + return; + } + + if (_clsContext.writable->internalLogging.logFd == -1) { + _clsContext.writable->internalLogging.logFd = open(path, O_WRONLY | O_CREAT | O_APPEND, 0644); + } + + const int fd = _clsContext.writable->internalLogging.logFd; + if (fd < 0) { + return; + } + + va_list args; + va_start(args, format); + +#if DEBUG && 0 + // It's nice to use printf here, so all the formatting works. However, its possible to hit a + // deadlock if you call vfprintf in a crash handler. So, this code is handy to keep, just in case, + // if there's a really tough thing to debug. + FILE* file = fopen(path, "a+"); + vfprintf(file, format, args); + fclose(file); +#else + size_t formatLength = strlen(format); + for (size_t idx = 0; idx < formatLength; ++idx) { + if (format[idx] != '%') { + write(fd, &format[idx], 1); + continue; + } + + idx++; // move to the format char + switch (format[idx]) { + case 'd': { + int value = va_arg(args, int); + FIRCLSFileFDWriteInt64(fd, value); + } break; + case 'u': { + uint32_t value = va_arg(args, uint32_t); + FIRCLSFileFDWriteUInt64(fd, value, false); + } break; + case 'p': { + uintptr_t value = va_arg(args, uintptr_t); + write(fd, "0x", 2); + FIRCLSFileFDWriteUInt64(fd, value, true); + } break; + case 's': { + const char* string = va_arg(args, const char*); + if (!string) { + string = "(null)"; + } + + write(fd, string, strlen(string)); + } break; + case 'x': { + unsigned int value = va_arg(args, unsigned int); + FIRCLSFileFDWriteUInt64(fd, value, true); + } break; + default: + // unhandled, back up to write out the percent + the format char + write(fd, &format[idx - 1], 2); + break; + } + } +#endif + va_end(args); +} diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSInternalLogging.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSInternalLogging.h new file mode 100644 index 0000000..4ff1644 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSInternalLogging.h @@ -0,0 +1,57 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +#if __OBJC__ +#import "FIRCLSLogger.h" +#define FIRCLSDeveloperLog(label, __FORMAT__, ...) \ + FIRCLSDebugLog(@"[" label "] " __FORMAT__, ##__VA_ARGS__); +#endif + +typedef enum { + FIRCLSInternalLogLevelUnknown = 0, + FIRCLSInternalLogLevelDebug = 1, + FIRCLSInternalLogLevelInfo = 2, + FIRCLSInternalLogLevelWarn = 3, + FIRCLSInternalLogLevelError = 4 +} FIRCLSInternalLogLevel; + +typedef struct { + int logFd; + FIRCLSInternalLogLevel logLevel; +} FIRCLSInternalLoggingWritableContext; + +#define FIRCLSSDKLogDebug(__FORMAT__, ...) \ + FIRCLSSDKFileLog(FIRCLSInternalLogLevelDebug, "DEBUG [%s:%d] " __FORMAT__, __FUNCTION__, \ + __LINE__, ##__VA_ARGS__) +#define FIRCLSSDKLogInfo(__FORMAT__, ...) \ + FIRCLSSDKFileLog(FIRCLSInternalLogLevelInfo, "INFO [%s:%d] " __FORMAT__, __FUNCTION__, \ + __LINE__, ##__VA_ARGS__) +#define FIRCLSSDKLogWarn(__FORMAT__, ...) \ + FIRCLSSDKFileLog(FIRCLSInternalLogLevelWarn, "WARN [%s:%d] " __FORMAT__, __FUNCTION__, \ + __LINE__, ##__VA_ARGS__) +#define FIRCLSSDKLogError(__FORMAT__, ...) \ + FIRCLSSDKFileLog(FIRCLSInternalLogLevelError, "ERROR [%s:%d] " __FORMAT__, __FUNCTION__, \ + __LINE__, ##__VA_ARGS__) + +#define FIRCLSSDKLog FIRCLSSDKLogWarn + +__BEGIN_DECLS + +void FIRCLSSDKFileLog(FIRCLSInternalLogLevel level, const char* format, ...) __printflike(2, 3); + +__END_DECLS diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h new file mode 100644 index 0000000..e03d99a --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h @@ -0,0 +1,24 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +__BEGIN_DECLS + +void FIRCLSDebugLog(NSString *message, ...); +void FIRCLSInfoLog(NSString *message, ...); +void FIRCLSWarningLog(NSString *message, ...); +void FIRCLSErrorLog(NSString *message, ...); + +__END_DECLS diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSLogger.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSLogger.m new file mode 100644 index 0000000..0bef608 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSLogger.m @@ -0,0 +1,52 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSLogger.h" + +#import + +FIRLoggerService kFIRLoggerCrashlytics = @"[Firebase/Crashlytics]"; + +NSString *const CrashlyticsMessageCode = @"I-CLS000000"; + +void FIRCLSDebugLog(NSString *message, ...) { + va_list args_ptr; + va_start(args_ptr, message); + FIRLogBasic(FIRLoggerLevelDebug, kFIRLoggerCrashlytics, CrashlyticsMessageCode, message, + args_ptr); + va_end(args_ptr); +} + +void FIRCLSInfoLog(NSString *message, ...) { + va_list args_ptr; + va_start(args_ptr, message); + FIRLogBasic(FIRLoggerLevelInfo, kFIRLoggerCrashlytics, CrashlyticsMessageCode, message, args_ptr); + va_end(args_ptr); +} + +void FIRCLSWarningLog(NSString *message, ...) { + va_list args_ptr; + va_start(args_ptr, message); + FIRLogBasic(FIRLoggerLevelWarning, kFIRLoggerCrashlytics, CrashlyticsMessageCode, message, + args_ptr); + va_end(args_ptr); +} + +void FIRCLSErrorLog(NSString *message, ...) { + va_list args_ptr; + va_start(args_ptr, message); + FIRLogBasic(FIRLoggerLevelError, kFIRLoggerCrashlytics, CrashlyticsMessageCode, message, + args_ptr); + va_end(args_ptr); +} diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSProfiling.c b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSProfiling.c new file mode 100644 index 0000000..df50080 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSProfiling.c @@ -0,0 +1,47 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FIRCLSProfiling.h" + +#include +#include + +FIRCLSProfileMark FIRCLSProfilingStart(void) { + return mach_absolute_time(); +} + +double FIRCLSProfileEnd(FIRCLSProfileMark mark) { + uint64_t duration = mach_absolute_time() - mark; + + mach_timebase_info_data_t info; + mach_timebase_info(&info); + + if (info.denom == 0) { + return 0.0; + } + + // Convert to nanoseconds + duration *= info.numer; + duration /= info.denom; + + return (double)duration / (double)NSEC_PER_MSEC; // return time in milliseconds +} + +void FIRCLSProfileBlock(const char* label, void (^block)(void)) { + FIRCLSProfileMark mark = FIRCLSProfilingStart(); + + block(); + + fprintf(stderr, "[Profile] %s: %f ms\n", label, FIRCLSProfileEnd(mark)); +} diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSProfiling.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSProfiling.h new file mode 100644 index 0000000..5cc312f --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSProfiling.h @@ -0,0 +1,29 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +typedef uint64_t FIRCLSProfileMark; + +__BEGIN_DECLS + +// high-resolution timing, returning the results in seconds +FIRCLSProfileMark FIRCLSProfilingStart(void); +double FIRCLSProfileEnd(FIRCLSProfileMark mark); + +void FIRCLSProfileBlock(const char* label, void (^block)(void)); + +__END_DECLS diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSThreadState.c b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSThreadState.c new file mode 100644 index 0000000..44a3002 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSThreadState.c @@ -0,0 +1,147 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FIRCLSThreadState.h" +#include "FIRCLSDefines.h" +#include "FIRCLSUtility.h" + +#if defined(__arm__) || defined(__arm64__) +#include +#include +#endif + +#if CLS_CPU_X86_64 +#define GET_IP_REGISTER(r) (r->__ss.__rip) +#define GET_FP_REGISTER(r) (r->__ss.__rbp) +#define GET_SP_REGISTER(r) (r->__ss.__rsp) +#define GET_LR_REGISTER(r) 0 +#define SET_IP_REGISTER(r, v) (r->__ss.__rip = v) +#define SET_FP_REGISTER(r, v) (r->__ss.__rbp = v) +#define SET_SP_REGISTER(r, v) (r->__ss.__rsp = v) +#define SET_LR_REGISTER(r, v) +#elif CLS_CPU_I386 +#define GET_IP_REGISTER(r) (r->__ss.__eip) +#define GET_FP_REGISTER(r) (r->__ss.__ebp) +#define GET_SP_REGISTER(r) (r->__ss.__esp) +#define GET_LR_REGISTER(r) 0 +#define SET_IP_REGISTER(r, v) (r->__ss.__eip = v) +#define SET_FP_REGISTER(r, v) (r->__ss.__ebp = v) +#define SET_SP_REGISTER(r, v) (r->__ss.__esp = v) +#define SET_LR_REGISTER(r, v) +#elif CLS_CPU_ARM64 +// The arm_thread_state64_get_* macros translate down to the AUTIA and AUTIB instructions which +// authenticate the address, but don't clear the upper bits. From the docs: +// "If the authentication passes, the upper bits of the address are restored to enable +// subsequent use of the address. the authentication fails, the upper bits are corrupted and +// any subsequent use of the address results in a Translation fault." +// Since we only want the address (with the metadata in the upper bits masked out), we used the +// ptrauth_strip macro to clear the upper bits. +// +// We found later that ptrauth_strip doesn't seem to do anything. In many cases, the upper bits were +// already stripped, so for most non-system-library code, Crashlytics would still symbolicate. But +// for system libraries, the upper bits were being left in even when we called ptrauth_strip. +// Instead, we're bit masking and only allowing the latter 36 bits. +#define CLS_PTRAUTH_STRIP(pointer) ((uintptr_t)pointer & 0x0000000FFFFFFFFF) +#define GET_IP_REGISTER(r) (CLS_PTRAUTH_STRIP(arm_thread_state64_get_pc(r->__ss))) +#define GET_FP_REGISTER(r) (CLS_PTRAUTH_STRIP(arm_thread_state64_get_fp(r->__ss))) +#define GET_SP_REGISTER(r) (CLS_PTRAUTH_STRIP(arm_thread_state64_get_sp(r->__ss))) +#define GET_LR_REGISTER(r) (CLS_PTRAUTH_STRIP(arm_thread_state64_get_lr(r->__ss))) +#define SET_IP_REGISTER(r, v) arm_thread_state64_set_pc_fptr(r->__ss, (void*)v) +#define SET_FP_REGISTER(r, v) arm_thread_state64_set_fp(r->__ss, v) +#define SET_SP_REGISTER(r, v) arm_thread_state64_set_sp(r->__ss, v) +#define SET_LR_REGISTER(r, v) arm_thread_state64_set_lr_fptr(r->__ss, (void*)v) +#elif CLS_CPU_ARM +#define GET_IP_REGISTER(r) (r->__ss.__pc) +#define GET_FP_REGISTER(r) (r->__ss.__r[7]) +#define GET_SP_REGISTER(r) (r->__ss.__sp) +#define GET_LR_REGISTER(r) (r->__ss.__lr) +#define SET_IP_REGISTER(r, v) (r->__ss.__pc = v) +#define SET_FP_REGISTER(r, v) (r->__ss.__r[7] = v) +#define SET_SP_REGISTER(r, v) (r->__ss.__sp = v) +#define SET_LR_REGISTER(r, v) (r->__ss.__lr = v) +#else +#error "Architecture Unsupported" +#endif + +uintptr_t FIRCLSThreadContextGetPC(FIRCLSThreadContext* registers) { + if (!registers) { + return 0; + } + + return GET_IP_REGISTER(registers); +} + +uintptr_t FIRCLSThreadContextGetStackPointer(const FIRCLSThreadContext* registers) { + if (!registers) { + return 0; + } + + return GET_SP_REGISTER(registers); +} + +bool FIRCLSThreadContextSetStackPointer(FIRCLSThreadContext* registers, uintptr_t value) { + if (!FIRCLSIsValidPointer(registers)) { + return false; + } + + SET_SP_REGISTER(registers, value); + + return true; +} + +uintptr_t FIRCLSThreadContextGetLinkRegister(const FIRCLSThreadContext* registers) { + if (!FIRCLSIsValidPointer(registers)) { + return 0; + } + + return GET_LR_REGISTER(registers); +} + +bool FIRCLSThreadContextSetLinkRegister(FIRCLSThreadContext* registers, uintptr_t value) { + if (!FIRCLSIsValidPointer(registers)) { + return false; + } + + SET_LR_REGISTER(registers, value); + + return true; +} + +bool FIRCLSThreadContextSetPC(FIRCLSThreadContext* registers, uintptr_t value) { + if (!registers) { + return false; + } + + SET_IP_REGISTER(registers, value); + + return true; +} + +uintptr_t FIRCLSThreadContextGetFramePointer(const FIRCLSThreadContext* registers) { + if (!FIRCLSIsValidPointer(registers)) { + return 0; + } + + return GET_FP_REGISTER(registers); +} + +bool FIRCLSThreadContextSetFramePointer(FIRCLSThreadContext* registers, uintptr_t value) { + if (!FIRCLSIsValidPointer(registers)) { + return false; + } + + SET_FP_REGISTER(registers, value); + + return true; +} diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSThreadState.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSThreadState.h new file mode 100644 index 0000000..f281f66 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSThreadState.h @@ -0,0 +1,57 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include + +#if CLS_CPU_ARM +#define FIRCLSThreadStateCount ARM_THREAD_STATE_COUNT +#define FIRCLSThreadState ARM_THREAD_STATE +#elif CLS_CPU_ARM64 +#define FIRCLSThreadStateCount ARM_THREAD_STATE64_COUNT +#define FIRCLSThreadState ARM_THREAD_STATE64 +#elif CLS_CPU_I386 +#define FIRCLSThreadStateCount x86_THREAD_STATE32_COUNT +#define FIRCLSThreadState x86_THREAD_STATE32 +#elif CLS_CPU_X86_64 +#define FIRCLSThreadStateCount x86_THREAD_STATE64_COUNT +#define FIRCLSThreadState x86_THREAD_STATE64 +#endif + +// _STRUCT_MCONTEXT was fixed to point to the right thing on ARM in the iOS 7.1 SDK +typedef _STRUCT_MCONTEXT FIRCLSThreadContext; + +// I'm not entirely sure what happened when, but this appears to have disappeared from +// the SDKs... +#if !defined(_STRUCT_UCONTEXT64) +typedef _STRUCT_UCONTEXT _STRUCT_UCONTEXT64; +#endif + +#pragma mark Register Access + +uintptr_t FIRCLSThreadContextGetPC(FIRCLSThreadContext* registers); +uintptr_t FIRCLSThreadContextGetStackPointer(const FIRCLSThreadContext* registers); +uintptr_t FIRCLSThreadContextGetFramePointer(const FIRCLSThreadContext* registers); + +bool FIRCLSThreadContextSetPC(FIRCLSThreadContext* registers, uintptr_t value); +bool FIRCLSThreadContextSetStackPointer(FIRCLSThreadContext* registers, uintptr_t value); +bool FIRCLSThreadContextSetFramePointer(FIRCLSThreadContext* registers, uintptr_t value); + +// The link register only exists on ARM platforms. +#if CLS_CPU_ARM || CLS_CPU_ARM64 +uintptr_t FIRCLSThreadContextGetLinkRegister(const FIRCLSThreadContext* registers); +bool FIRCLSThreadContextSetLinkRegister(FIRCLSThreadContext* registers, uintptr_t value); +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h new file mode 100644 index 0000000..5a50f0f --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h @@ -0,0 +1,54 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include +#include "FIRCLSGlobals.h" + +#define FIRCLSIsValidPointer(x) ((uintptr_t)x >= 4096) +#define FIRCLSInvalidCharNybble (255) + +__BEGIN_DECLS + +void FIRCLSLookupFunctionPointer(void* ptr, void (^block)(const char* name, const char* lib)); + +void FIRCLSHexFromByte(uint8_t c, char output[]); +uint8_t FIRCLSNybbleFromChar(char c); + +bool FIRCLSReadMemory(vm_address_t src, void* dest, size_t len); +bool FIRCLSReadString(vm_address_t src, char** dest, size_t maxlen); + +const char* FIRCLSDupString(const char* string); + +bool FIRCLSUnlinkIfExists(const char* path); + +#if __OBJC__ +void FIRCLSDispatchAfter(float timeInSeconds, dispatch_queue_t queue, dispatch_block_t block); + +NSString* FIRCLSNormalizeUUID(NSString* value); +NSString* FIRCLSGenerateNormalizedUUID(void); + +NSString* FIRCLSNSDataToNSString(NSData* data); + +void FIRCLSAddOperationAfter(float timeInSeconds, NSOperationQueue* queue, void (^block)(void)); +#endif + +#if DEBUG +void FIRCLSPrintAUUID(const uint8_t* value); +#endif + +__END_DECLS diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSUtility.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSUtility.m new file mode 100644 index 0000000..0d6dafc --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSUtility.m @@ -0,0 +1,218 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FIRCLSUtility.h" + +#include + +#include + +#include "FIRCLSFeatures.h" +#include "FIRCLSFile.h" +#include "FIRCLSGlobals.h" + +#import "FIRCLSByteUtility.h" +#import "FIRCLSUUID.h" + +#import + +void FIRCLSLookupFunctionPointer(void* ptr, void (^block)(const char* name, const char* lib)) { + Dl_info info; + + if (dladdr(ptr, &info) == 0) { + block(NULL, NULL); + return; + } + + const char* name = "unknown"; + const char* lib = "unknown"; + + if (info.dli_sname) { + name = info.dli_sname; + } + + if (info.dli_fname) { + lib = info.dli_fname; + } + + block(name, lib); +} + +uint8_t FIRCLSNybbleFromChar(char c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } + + if (c >= 'a' && c <= 'f') { + return c - 'a' + 10; + } + + if (c >= 'A' && c <= 'F') { + return c - 'A' + 10; + } + + return FIRCLSInvalidCharNybble; +} + +bool FIRCLSReadMemory(vm_address_t src, void* dest, size_t len) { + if (!FIRCLSIsValidPointer(src)) { + return false; + } + + vm_size_t readSize = len; + + return vm_read_overwrite(mach_task_self(), src, len, (pointer_t)dest, &readSize) == KERN_SUCCESS; +} + +bool FIRCLSReadString(vm_address_t src, char** dest, size_t maxlen) { + char c; + vm_address_t address; + + if (!dest) { + return false; + } + + // Walk the entire string. Not certain this is perfect... + for (address = src; address < src + maxlen; ++address) { + if (!FIRCLSReadMemory(address, &c, 1)) { + return false; + } + + if (c == 0) { + break; + } + } + + *dest = (char*)src; + + return true; +} + +const char* FIRCLSDupString(const char* string) { +#if CLS_MEMORY_PROTECTION_ENABLED + char* buffer; + size_t length; + + if (!string) { + return NULL; + } + + length = strlen(string); + buffer = FIRCLSAllocatorSafeAllocate(_clsContext.allocator, length + 1, CLS_READONLY); + + memcpy(buffer, string, length); + + buffer[length] = 0; // null-terminate + + return buffer; +#else + return strdup(string); +#endif +} + +void FIRCLSDispatchAfter(float timeInSeconds, dispatch_queue_t queue, dispatch_block_t block) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeInSeconds * NSEC_PER_SEC)), queue, + block); +} + +bool FIRCLSUnlinkIfExists(const char* path) { + if (unlink(path) != 0) { + if (errno != ENOENT) { + return false; + } + } + + return true; +} + +/* +NSString* FIRCLSGenerateUUID(void) { + NSString* string; + + CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault); + string = CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuid)); + CFRelease(uuid); + + return string; +} +*/ + +NSString* FIRCLSNormalizeUUID(NSString* value) { + return [[value stringByReplacingOccurrencesOfString:@"-" withString:@""] lowercaseString]; +} + +NSString* FIRCLSGenerateNormalizedUUID(void) { + return FIRCLSNormalizeUUID(FIRCLSGenerateUUID()); +} + +NSString* FIRCLSNSDataToNSString(NSData* data) { + NSString* string; + char* buffer; + size_t size; + NSUInteger length; + + // we need 2 hex char for every byte of data, plus one more spot for a + // null terminator + length = [data length]; + size = (length * 2) + 1; + buffer = malloc(sizeof(char) * size); + + if (!buffer) { + FIRCLSErrorLog(@"Unable to malloc in FIRCLSNSDataToNSString"); + return nil; + } + + FIRCLSSafeHexToString([data bytes], length, buffer); + + string = [NSString stringWithUTF8String:buffer]; + + free(buffer); + + return string; +} + +/* +NSString* FIRCLSHashBytes(const void* bytes, size_t length) { + uint8_t digest[CC_SHA1_DIGEST_LENGTH] = {0}; + CC_SHA1(bytes, (CC_LONG)length, digest); + + NSData* result = [NSData dataWithBytes:digest length:CC_SHA1_DIGEST_LENGTH]; + + return FIRCLSNSDataToNSString(result); +} + +NSString* FIRCLSHashNSData(NSData* data) { + return FIRCLSHashBytes([data bytes], [data length]); +} +*/ + +void FIRCLSAddOperationAfter(float timeInSeconds, NSOperationQueue* queue, void (^block)(void)) { + dispatch_queue_t afterQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + FIRCLSDispatchAfter(timeInSeconds, afterQueue, ^{ + [queue addOperationWithBlock:block]; + }); +} + +#if DEBUG +void FIRCLSPrintAUUID(const uint8_t* value) { + CFUUIDRef uuid = CFUUIDCreateFromUUIDBytes(kCFAllocatorDefault, *(CFUUIDBytes*)value); + + NSString* string = CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuid)); + + CFRelease(uuid); + + FIRCLSDebugLog(@"%@", [[string stringByReplacingOccurrencesOfString:@"-" + withString:@""] lowercaseString]); +} +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSExecutionIdentifierModel.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSExecutionIdentifierModel.h new file mode 100644 index 0000000..41a4896 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSExecutionIdentifierModel.h @@ -0,0 +1,33 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * This class is a model to identify a single execution of the app + */ +@interface FIRCLSExecutionIdentifierModel : NSObject + +/** + * Returns the launch identifier. This is a unique id that will remain constant until this process + * is relaunched. This value is useful for correlating events across kits and/or across reports at + * the process-lifecycle level. + */ +@property(nonatomic, readonly) NSString *executionID; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSExecutionIdentifierModel.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSExecutionIdentifierModel.m new file mode 100644 index 0000000..e312d46 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSExecutionIdentifierModel.m @@ -0,0 +1,33 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSExecutionIdentifierModel.h" + +#import "FIRCLSUUID.h" + +@implementation FIRCLSExecutionIdentifierModel + +- (instancetype)init { + self = [super init]; + if (!self) { + return nil; + } + + _executionID = [[FIRCLSGenerateUUID() stringByReplacingOccurrencesOfString:@"-" + withString:@""] lowercaseString]; + + return self; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSFileManager.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSFileManager.h new file mode 100644 index 0000000..cb788f7 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSFileManager.h @@ -0,0 +1,82 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +@class FIRCLSInternalReport; + +@interface FIRCLSFileManager : NSObject + +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +@property(nonatomic, readonly) NSFileManager *underlyingFileManager; + +/** + * Returns the folder containing the settings file + */ +@property(nonatomic, readonly) NSString *settingsDirectoryPath; + +/** + * Returns the path to the settings file + */ +@property(nonatomic, readonly) NSString *settingsFilePath; + +/** + * Path to the file that holds the ttl and keys that invalidate settings + */ +@property(nonatomic, readonly) NSString *settingsCacheKeyPath; + +@property(nonatomic, readonly) NSString *rootPath; +@property(nonatomic, readonly) NSString *structurePath; +@property(nonatomic, readonly) NSString *activePath; +@property(nonatomic, readonly) NSString *processingPath; +@property(nonatomic, readonly) NSString *pendingPath; +@property(nonatomic, readonly) NSString *preparedPath; +@property(nonatomic, readonly) NSArray *activePathContents; +@property(nonatomic, readonly) NSArray *preparedPathContents; +@property(nonatomic, readonly) NSArray *processingPathContents; + +- (BOOL)fileExistsAtPath:(NSString *)path; +- (BOOL)createFileAtPath:(NSString *)path + contents:(NSData *)data + attributes:(NSDictionary *)attr; +- (BOOL)createDirectoryAtPath:(NSString *)path; +- (BOOL)removeItemAtPath:(NSString *)path; +- (BOOL)removeContentsOfDirectoryAtPath:(NSString *)path; +- (BOOL)moveItemAtPath:(NSString *)path toDirectory:(NSString *)destDir; +- (void)enumerateFilesInDirectory:(NSString *)directory + usingBlock:(void (^)(NSString *filePath, NSString *extension))block; +- (BOOL)moveItemsFromDirectory:(NSString *)srcDir toDirectory:(NSString *)destDir; +- (NSNumber *)fileSizeAtPath:(NSString *)path; +- (NSArray *)contentsOfDirectory:(NSString *)path; + +// logic of managing files/directories +- (BOOL)createReportDirectories; +- (NSString *)setupNewPathForExecutionIdentifier:(NSString *)identifier; + +- (void)enumerateFilesInActiveDirectoryUsingBlock:(void (^)(NSString *path, + NSString *extension))block; +- (void)enumerateReportsInProcessingDirectoryUsingBlock:(void (^)(FIRCLSInternalReport *report, + NSString *path))block; +- (void)enumerateFilesInPreparedDirectoryUsingBlock:(void (^)(NSString *path, + NSString *extension))block; + +- (BOOL)moveProcessingContentsToPrepared; +- (BOOL)movePendingToProcessing; + +- (BOOL)removeContentsOfProcessingPath; +- (BOOL)removeContentsOfPendingPath; +- (BOOL)removeContentsOfAllPaths; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSFileManager.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSFileManager.m new file mode 100644 index 0000000..69fd8b0 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSFileManager.m @@ -0,0 +1,331 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSFileManager.h" + +#import "FIRCLSApplication.h" +#import "FIRCLSInternalReport.h" +#import "FIRCLSLogger.h" + +NSString *const FIRCLSCacheDirectoryName = @"com.crashlytics.data"; +NSString *const FIRCLSCacheVersion = @"v4"; + +@interface FIRCLSFileManager () { + NSString *_rootPath; +} + +@end + +@implementation FIRCLSFileManager + +- (instancetype)init { + self = [super init]; + if (!self) { + return nil; + } + + _underlyingFileManager = [NSFileManager defaultManager]; + + NSString *path = + [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; + path = [path stringByAppendingPathComponent:FIRCLSCacheDirectoryName]; + path = [path stringByAppendingPathComponent:[self pathNamespace]]; + _rootPath = [path copy]; + + return self; +} + +#pragma mark - Core API + +- (BOOL)fileExistsAtPath:(NSString *)path { + return [_underlyingFileManager fileExistsAtPath:path]; +} + +- (BOOL)createFileAtPath:(NSString *)path + contents:(nullable NSData *)data + attributes:(nullable NSDictionary *)attr { + return [_underlyingFileManager createFileAtPath:path contents:data attributes:attr]; +} + +- (BOOL)createDirectoryAtPath:(NSString *)path { + NSDictionary *attributes; + NSError *error; + + attributes = @{NSFilePosixPermissions : [NSNumber numberWithShort:0755]}; + error = nil; + + if (![[self underlyingFileManager] createDirectoryAtPath:path + withIntermediateDirectories:YES + attributes:attributes + error:&error]) { + FIRCLSErrorLog(@"Unable to create directory %@", error); + return NO; + } + + return YES; +} + +- (BOOL)removeItemAtPath:(NSString *)path { + NSError *error; + + error = nil; + if (![[self underlyingFileManager] removeItemAtPath:path error:&error] || !path) { + FIRCLSErrorLog(@"Failed to remove file %@: %@", path, error); + + return NO; + } + + return YES; +} + +- (BOOL)removeContentsOfDirectoryAtPath:(NSString *)path { + __block BOOL success = YES; + + // only return true if we were able to remove every item in the directory (or it was empty) + + [self enumerateFilesInDirectory:path + usingBlock:^(NSString *filePath, NSString *extension) { + success = [self removeItemAtPath:filePath] && success; + }]; + + return success; +} + +- (BOOL)moveItemAtPath:(NSString *)path toDirectory:(NSString *)destDir { + NSString *destPath; + NSError *error; + + destPath = [destDir stringByAppendingPathComponent:[path lastPathComponent]]; + error = nil; + + if (!path || !destPath) { + FIRCLSErrorLog(@"Failed to move file, inputs invalid"); + + return NO; + } + + if (![[self underlyingFileManager] moveItemAtPath:path toPath:destPath error:&error]) { + FIRCLSErrorLog(@"Failed to move file: %@", error); + + return NO; + } + + return YES; +} + +- (void)enumerateFilesInDirectory:(NSString *)directory + usingBlock:(void (^)(NSString *filePath, NSString *extension))block { + for (NSString *path in [[self underlyingFileManager] contentsOfDirectoryAtPath:directory + error:nil]) { + NSString *extension; + NSString *fullPath; + + // Skip files that start with a dot. This is important, because if you try to move a .DS_Store + // file, it will fail if the target directory also has a .DS_Store file in it. Plus, its + // wasteful, because we don't care about dot files. + if ([path hasPrefix:@"."]) { + continue; + } + + extension = [path pathExtension]; + fullPath = [directory stringByAppendingPathComponent:path]; + + block(fullPath, extension); + } +} + +- (BOOL)moveItemsFromDirectory:(NSString *)srcDir toDirectory:(NSString *)destDir { + __block BOOL success = YES; + + [self enumerateFilesInDirectory:srcDir + usingBlock:^(NSString *filePath, NSString *extension) { + success = success && [self moveItemAtPath:filePath toDirectory:destDir]; + }]; + + return success; +} + +- (NSNumber *)fileSizeAtPath:(NSString *)path { + NSError *error = nil; + NSDictionary *attrs = [[self underlyingFileManager] attributesOfItemAtPath:path error:&error]; + + if (!attrs) { + FIRCLSErrorLog(@"Unable to read file size: %@", error); + return nil; + } + + return [attrs objectForKey:NSFileSize]; +} + +- (NSArray *)contentsOfDirectory:(NSString *)path { + NSMutableArray *array = [NSMutableArray array]; + + [self enumerateFilesInDirectory:path + usingBlock:^(NSString *filePath, NSString *extension) { + [array addObject:filePath]; + }]; + + return array; +} + +#pragma - Properties +- (NSString *)pathNamespace { + return FIRCLSApplicationGetBundleIdentifier(); +} + +- (NSString *)versionedPath { + return [[self rootPath] stringByAppendingPathComponent:FIRCLSCacheVersion]; +} + +#pragma - Settings Paths + +// This path should be different than the structurePath because the +// settings download operations will delete the settings directory, +// which would delete crash reports if these were the same +- (NSString *)settingsDirectoryPath { + return [[self versionedPath] stringByAppendingPathComponent:@"settings"]; +} + +- (NSString *)settingsFilePath { + return [[self settingsDirectoryPath] stringByAppendingPathComponent:@"settings.json"]; +} + +- (NSString *)settingsCacheKeyPath { + return [[self settingsDirectoryPath] stringByAppendingPathComponent:@"cache-key.json"]; +} + +#pragma - Report Paths +- (NSString *)structurePath { + return [[self versionedPath] stringByAppendingPathComponent:@"reports"]; +} + +- (NSString *)activePath { + return [[self structurePath] stringByAppendingPathComponent:@"active"]; +} + +- (NSString *)pendingPath { + return [[self structurePath] stringByAppendingPathComponent:@"pending"]; +} + +- (NSString *)processingPath { + return [[self structurePath] stringByAppendingPathComponent:@"processing"]; +} + +- (NSString *)preparedPath { + return [[self structurePath] stringByAppendingPathComponent:@"prepared"]; +} + +- (NSArray *)activePathContents { + return [self contentsOfDirectory:[self activePath]]; +} + +- (NSArray *)preparedPathContents { + return [self contentsOfDirectory:[self preparedPath]]; +} + +- (NSArray *)processingPathContents { + return [self contentsOfDirectory:[self processingPath]]; +} + +#pragma mark - Logic +- (BOOL)createReportDirectories { + if (![self createDirectoryAtPath:[self activePath]]) { + return NO; + } + + if (![self createDirectoryAtPath:[self processingPath]]) { + return NO; + } + + if (![self createDirectoryAtPath:[self preparedPath]]) { + return NO; + } + + return YES; +} + +- (NSString *)setupNewPathForExecutionIdentifier:(NSString *)identifier { + NSString *path = [[self activePath] stringByAppendingPathComponent:identifier]; + + if (![self createDirectoryAtPath:path]) { + return nil; + } + + return path; +} + +- (void)enumerateReportsInProcessingDirectoryUsingBlock:(void (^)(FIRCLSInternalReport *report, + NSString *path))block { + [self enumerateFilesInDirectory:[self processingPath] + usingBlock:^(NSString *filePath, NSString *extension) { + FIRCLSInternalReport *report = + [FIRCLSInternalReport reportWithPath:filePath]; + + block(report, filePath); + }]; +} + +- (void)enumerateFilesInActiveDirectoryUsingBlock:(void (^)(NSString *path, + NSString *extension))block { + [self enumerateFilesInDirectory:[self activePath] usingBlock:block]; +} + +- (void)enumerateFilesInPreparedDirectoryUsingBlock:(void (^)(NSString *path, + NSString *extension))block { + [self enumerateFilesInDirectory:[self preparedPath] usingBlock:block]; +} + +- (BOOL)moveProcessingContentsToPrepared { + return [self moveItemsFromDirectory:[self processingPath] toDirectory:[self preparedPath]]; +} + +- (BOOL)movePendingToProcessing { + return [self moveItemsFromDirectory:[self pendingPath] toDirectory:[self processingPath]]; +} + +- (BOOL)removeContentsOfProcessingPath { + return [self removeContentsOfDirectoryAtPath:[self processingPath]]; +} + +- (BOOL)removeContentsOfPendingPath { + return [self removeContentsOfDirectoryAtPath:[self pendingPath]]; +} + +- (BOOL)removeContentsOfAllPaths { + BOOL success = YES; + + // We want to call all of these methods, even if some fail, and return + // NO if any fail. This turned out to be slightly tricky to do in more + // compact forms, so I did it the simple but verbose way. + + if (![self removeContentsOfProcessingPath]) { + success = NO; + } + + if (![self removeContentsOfPendingPath]) { + success = NO; + } + + if (![self removeContentsOfDirectoryAtPath:self.preparedPath]) { + success = NO; + } + + if (![self removeContentsOfDirectoryAtPath:self.activePath]) { + success = NO; + } + + return success; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSInstallIdentifierModel.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSInstallIdentifierModel.h new file mode 100644 index 0000000..9cfd999 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSInstallIdentifierModel.h @@ -0,0 +1,46 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +@class FIRInstanceID; + +NS_ASSUME_NONNULL_BEGIN + +/** + * This class is a model for identifying an installation of an app + */ +@interface FIRCLSInstallIdentifierModel : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithInstanceID:(FIRInstanceID *)instanceID NS_DESIGNATED_INITIALIZER; + +/** + * Returns the backwards compatible Crashlytics Installation UUID + */ +@property(nonatomic, readonly) NSString *installID; + +/** + * To support end-users rotating Install IDs, this will check and rotate the Install ID, + * which is a costly operation performance-wise. To keep the startup time impact down, call this in + * a background thread. + * + * The block will be called on a background thread. + */ +- (void)regenerateInstallIDIfNeededWithBlock:(void (^)(BOOL didRotate))callback; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSInstallIdentifierModel.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSInstallIdentifierModel.m new file mode 100644 index 0000000..1eaf184 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSInstallIdentifierModel.m @@ -0,0 +1,160 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSInstallIdentifierModel.h" + +#import + +#import "FIRCLSByteUtility.h" +#import "FIRCLSLogger.h" +#import "FIRCLSUUID.h" +#import "FIRCLSUserDefaults.h" + +static NSString *const FIRCLSInstallationUUIDKey = @"com.crashlytics.iuuid"; +static NSString *const FIRCLSInstallationIIDHashKey = @"com.crashlytics.install.iid"; + +// Legacy key that is automatically removed +static NSString *const FIRCLSInstallationADIDKey = @"com.crashlytics.install.adid"; + +@interface FIRCLSInstallIdentifierModel () + +@property(nonatomic, copy) NSString *installID; + +@property(nonatomic, readonly) FIRInstanceID *instanceID; + +@end + +@implementation FIRCLSInstallIdentifierModel + +// This needs to be synthesized so we can set without using the setter in the constructor and +// overridden setters and getters +@synthesize installID = _installID; + +- (instancetype)initWithInstanceID:(FIRInstanceID *)instanceID { + self = [super init]; + if (!self) { + return nil; + } + + // capture the install ID information + _installID = [self readInstallationUUID].copy; + _instanceID = instanceID; + + if (!_installID) { + FIRCLSDebugLog(@"Generating Install ID"); + _installID = [self generateInstallationUUID].copy; + + FIRCLSUserDefaults *defaults = [FIRCLSUserDefaults standardUserDefaults]; + [defaults synchronize]; + } + + return self; +} + +- (NSString *)installID { + @synchronized(self) { + return _installID; + } +} + +- (void)setInstallID:(NSString *)installID { + @synchronized(self) { + _installID = installID; + } +} + +/** + * Reads installation UUID stored in persistent storage. + * If the installation UUID is stored in legacy key, migrates it over to the new key. + */ +- (NSString *)readInstallationUUID { + return [[FIRCLSUserDefaults standardUserDefaults] objectForKey:FIRCLSInstallationUUIDKey]; +} + +/** + * Generates a new UUID and saves it in persistent storage. + * Does not sychronize the user defaults (to allow optimized + * batching of user default synchronizing) + */ +- (NSString *)generateInstallationUUID { + NSString *UUID = FIRCLSGenerateUUID(); + FIRCLSUserDefaults *userDefaults = [FIRCLSUserDefaults standardUserDefaults]; + [userDefaults setObject:UUID forKey:FIRCLSInstallationUUIDKey]; + return UUID; +} + +#pragma mark Privacy Shield + +/** + * To support privacy shield we need to regenerate the install id when the IID changes. + * + * This is a blocking, slow call that must be called on a background thread. + */ +- (void)regenerateInstallIDIfNeededWithBlock:(void (^)(BOOL didRotate))callback { + // This callback is on the main thread + [self.instanceID getIDWithHandler:^(NSString *_Nullable currentIID, NSError *_Nullable error) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ + BOOL didRotate = [self rotateCrashlyticsInstallUUIDWithIID:currentIID error:error]; + callback(didRotate); + }); + }]; +} + +- (BOOL)rotateCrashlyticsInstallUUIDWithIID:(NSString *_Nullable)currentIID + error:(NSError *_Nullable)error { + BOOL didRotate = NO; + + FIRCLSUserDefaults *defaults = [FIRCLSUserDefaults standardUserDefaults]; + + // Remove the legacy ID + NSString *adID = [defaults objectForKey:FIRCLSInstallationADIDKey]; + if (adID.length != 0) { + [defaults removeObjectForKey:FIRCLSInstallationADIDKey]; + [defaults synchronize]; + } + + if (error != nil) { + FIRCLSErrorLog(@"Failed to get Firebase Instance ID: %@", error); + return didRotate; + } + + if (currentIID.length == 0) { + FIRCLSErrorLog(@"Firebase Instance ID was empty when checked for changes"); + return didRotate; + } + + NSString *currentIIDHash = + FIRCLS256HashNSData([currentIID dataUsingEncoding:NSUTF8StringEncoding]); + NSString *lastIIDHash = [defaults objectForKey:FIRCLSInstallationIIDHashKey]; + + // If the IDs are the same, we never regenerate + if ([lastIIDHash isEqualToString:currentIIDHash]) { + return didRotate; + } + + // If we had an FIID saved, we know it's not an upgrade scenario, so we can regenerate + if (lastIIDHash.length != 0) { + FIRCLSDebugLog(@"Regenerating Install ID"); + self.installID = [self generateInstallationUUID].copy; + didRotate = YES; + } + + // Write the new FIID to UserDefaults + [defaults setObject:currentIIDHash forKey:FIRCLSInstallationIIDHashKey]; + [defaults synchronize]; + + return didRotate; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h new file mode 100644 index 0000000..bf9caa7 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h @@ -0,0 +1,113 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +#include "FIRCLSFeatures.h" + +extern NSString *const CLSReportBinaryImageFile; +extern NSString *const CLSReportExceptionFile; +extern NSString *const CLSReportCustomExceptionAFile; +extern NSString *const CLSReportCustomExceptionBFile; +extern NSString *const CLSReportSignalFile; +#if CLS_MACH_EXCEPTION_SUPPORTED +extern NSString *const CLSReportMachExceptionFile; +#endif +extern NSString *const CLSReportErrorAFile; +extern NSString *const CLSReportErrorBFile; +extern NSString *const CLSReportMetadataFile; +extern NSString *const CLSReportInternalIncrementalKVFile; +extern NSString *const CLSReportInternalCompactedKVFile; +extern NSString *const CLSReportUserIncrementalKVFile; +extern NSString *const CLSReportUserCompactedKVFile; + +@class FIRCLSFileManager; + +@interface FIRCLSInternalReport : NSObject + ++ (instancetype)reportWithPath:(NSString *)path; +- (instancetype)initWithPath:(NSString *)path + executionIdentifier:(NSString *)identifier NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithPath:(NSString *)path; +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@property(nonatomic, copy, readonly) NSString *directoryName; +@property(nonatomic, copy) NSString *path; +@property(nonatomic, assign, readonly) BOOL needsToBeSubmitted; + +// content paths +@property(nonatomic, copy, readonly) NSString *binaryImagePath; +@property(nonatomic, copy, readonly) NSString *metadataPath; + +- (void)enumerateSymbolicatableFilesInContent:(void (^)(NSString *path))block; + +- (NSString *)pathForContentFile:(NSString *)name; + +// Metadata Helpers + +/** + * Returns the org id for the report. + **/ +@property(nonatomic, copy, readonly) NSString *orgID; + +/** + * Returns the Install UUID for the report. + **/ +@property(nonatomic, copy, readonly) NSString *installID; + +/** + * Returns YES if report contains a signal, mach exception or unhandled exception record, NO + * otherwise. + **/ +@property(nonatomic, assign, readonly) BOOL isCrash; + +/** + * Returns the session identifier for the report. + **/ +@property(nonatomic, copy, readonly) NSString *identifier; + +/** + * Returns the custom key value data for the report. + **/ +@property(nonatomic, copy, readonly) NSDictionary *customKeys; + +/** + * Returns the CFBundleVersion of the application that generated the report. + **/ +@property(nonatomic, copy, readonly) NSString *bundleVersion; + +/** + * Returns the CFBundleShortVersionString of the application that generated the report. + **/ +@property(nonatomic, copy, readonly) NSString *bundleShortVersionString; + +/** + * Returns the date that the report was created. + **/ +@property(nonatomic, copy, readonly) NSDate *dateCreated; + +@property(nonatomic, copy, readonly) NSDate *crashedOnDate; + +/** + * Returns the os version that the application crashed on. + **/ +@property(nonatomic, copy, readonly) NSString *OSVersion; + +/** + * Returns the os build version that the application crashed on. + **/ +@property(nonatomic, copy, readonly) NSString *OSBuildVersion; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSInternalReport.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSInternalReport.m new file mode 100644 index 0000000..97a8bfa --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSInternalReport.m @@ -0,0 +1,242 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSInternalReport.h" + +#import "FIRCLSFile.h" +#import "FIRCLSFileManager.h" +#import "FIRCLSLogger.h" + +NSString *const CLSReportBinaryImageFile = @"binary_images.clsrecord"; +NSString *const CLSReportExceptionFile = @"exception.clsrecord"; +NSString *const CLSReportCustomExceptionAFile = @"custom_exception_a.clsrecord"; +NSString *const CLSReportCustomExceptionBFile = @"custom_exception_b.clsrecord"; +NSString *const CLSReportSignalFile = @"signal.clsrecord"; +#if CLS_MACH_EXCEPTION_SUPPORTED +NSString *const CLSReportMachExceptionFile = @"mach_exception.clsrecord"; +#endif +NSString *const CLSReportMetadataFile = @"metadata.clsrecord"; +NSString *const CLSReportErrorAFile = @"errors_a.clsrecord"; +NSString *const CLSReportErrorBFile = @"errors_b.clsrecord"; +NSString *const CLSReportInternalIncrementalKVFile = @"internal_incremental_kv.clsrecord"; +NSString *const CLSReportInternalCompactedKVFile = @"internal_compacted_kv.clsrecord"; +NSString *const CLSReportUserIncrementalKVFile = @"user_incremental_kv.clsrecord"; +NSString *const CLSReportUserCompactedKVFile = @"user_compacted_kv.clsrecord"; + +@interface FIRCLSInternalReport () { + NSString *_identifier; + NSString *_path; + NSArray *_metadataSections; +} + +@end + +@implementation FIRCLSInternalReport + ++ (instancetype)reportWithPath:(NSString *)path { + return [[self alloc] initWithPath:path]; +} + +#pragma mark - Initialization +/** + * Initializes a new report, i.e. one without metadata on the file system yet. + */ +- (instancetype)initWithPath:(NSString *)path executionIdentifier:(NSString *)identifier { + self = [super init]; + if (!self) { + return self; + } + + if (!path || !identifier) { + return nil; + } + + [self setPath:path]; + + _identifier = [identifier copy]; + + return self; +} + +/** + * Initializes a pre-existing report, i.e. one with metadata on the file system. + */ +- (instancetype)initWithPath:(NSString *)path { + NSString *metadataPath = [path stringByAppendingPathComponent:CLSReportMetadataFile]; + NSString *identifier = [[[[self.class readFIRCLSFileAtPath:metadataPath] objectAtIndex:0] + objectForKey:@"identity"] objectForKey:@"session_id"]; + if (!identifier) { + FIRCLSErrorLog(@"Unable to read identifier at path %@", path); + } + return [self initWithPath:path executionIdentifier:identifier]; +} + +#pragma mark - Path Helpers +- (NSString *)directoryName { + return self.path.lastPathComponent; +} + +- (NSString *)pathForContentFile:(NSString *)name { + return [[self path] stringByAppendingPathComponent:name]; +} + +- (NSString *)metadataPath { + return [[self path] stringByAppendingPathComponent:CLSReportMetadataFile]; +} + +- (NSString *)binaryImagePath { + return [self pathForContentFile:CLSReportBinaryImageFile]; +} + +#pragma mark - Processing Methods +- (BOOL)needsToBeSubmitted { + NSArray *reportFiles = @[ + CLSReportExceptionFile, CLSReportSignalFile, CLSReportCustomExceptionAFile, + CLSReportCustomExceptionBFile, +#if CLS_MACH_EXCEPTION_SUPPORTED + CLSReportMachExceptionFile, +#endif + CLSReportErrorAFile, CLSReportErrorBFile + ]; + return [self checkExistenceOfAtLeastOnceFileInArray:reportFiles]; +} + +- (NSArray *)crashFilenames { + static NSArray *files; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + files = @[ + CLSReportExceptionFile, +#if CLS_MACH_EXCEPTION_SUPPORTED + CLSReportMachExceptionFile, +#endif + CLSReportSignalFile + ]; + }); + return files; +} + +- (BOOL)isCrash { + NSArray *crashFiles = [self crashFilenames]; + return [self checkExistenceOfAtLeastOnceFileInArray:crashFiles]; +} + +- (BOOL)checkExistenceOfAtLeastOnceFileInArray:(NSArray *)files { + NSFileManager *manager = [NSFileManager defaultManager]; + + for (NSString *fileName in files) { + NSString *path = [self pathForContentFile:fileName]; + + if ([manager fileExistsAtPath:path]) { + return YES; + } + } + + return NO; +} + +- (void)enumerateSymbolicatableFilesInContent:(void (^)(NSString *path))block { + for (NSString *fileName in [self crashFilenames]) { + NSString *path = [self pathForContentFile:fileName]; + + block(path); + } +} + +#pragma mark - Metadata helpers ++ (NSArray *)readFIRCLSFileAtPath:(NSString *)path { + NSArray *sections = FIRCLSFileReadSections([path fileSystemRepresentation], false, nil); + + if ([sections count] == 0) { + return nil; + } + + return sections; +} + +- (NSArray *)metadataSections { + if (!_metadataSections) { + _metadataSections = [self.class readFIRCLSFileAtPath:self.metadataPath]; + } + return _metadataSections; +} + +- (NSString *)orgID { + return + [[[self.metadataSections objectAtIndex:0] objectForKey:@"identity"] objectForKey:@"org_id"]; +} + +- (NSDictionary *)customKeys { + return nil; +} + +- (NSString *)bundleVersion { + return [[[self.metadataSections objectAtIndex:2] objectForKey:@"application"] + objectForKey:@"build_version"]; +} + +- (NSString *)bundleShortVersionString { + return [[[self.metadataSections objectAtIndex:2] objectForKey:@"application"] + objectForKey:@"display_version"]; +} + +- (NSDate *)dateCreated { + NSUInteger unixtime = [[[[self.metadataSections objectAtIndex:0] objectForKey:@"identity"] + objectForKey:@"started_at"] unsignedIntegerValue]; + + return [NSDate dateWithTimeIntervalSince1970:unixtime]; +} + +- (NSDate *)crashedOnDate { + if (!self.isCrash) { + return nil; + } + +#if CLS_MACH_EXCEPTION_SUPPORTED + // try the mach exception first, because it is more common + NSDate *date = [self timeFromCrashContentFile:CLSReportMachExceptionFile + sectionName:@"mach_exception"]; + if (date) { + return date; + } +#endif + + return [self timeFromCrashContentFile:CLSReportSignalFile sectionName:@"signal"]; +} + +- (NSDate *)timeFromCrashContentFile:(NSString *)fileName sectionName:(NSString *)sectionName { + // This works because both signal and mach exception files have the same structure to extract + // the "time" component + NSString *path = [self pathForContentFile:fileName]; + + NSNumber *timeValue = [[[[self.class readFIRCLSFileAtPath:path] objectAtIndex:0] + objectForKey:sectionName] objectForKey:@"time"]; + if (timeValue == nil) { + return nil; + } + + return [NSDate dateWithTimeIntervalSince1970:[timeValue unsignedIntegerValue]]; +} + +- (NSString *)OSVersion { + return [[[self.metadataSections objectAtIndex:1] objectForKey:@"host"] + objectForKey:@"os_display_version"]; +} + +- (NSString *)OSBuildVersion { + return [[[self.metadataSections objectAtIndex:1] objectForKey:@"host"] + objectForKey:@"os_build_version"]; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSReport.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSReport.h new file mode 100644 index 0000000..464dff7 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSReport.h @@ -0,0 +1,110 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * The CLSCrashReport protocol is deprecated. See the CLSReport class and the CrashyticsDelegate + * changes for details. + **/ +@protocol FIRCLSCrashReport + +@property(nonatomic, copy, readonly) NSString *identifier; +@property(nonatomic, copy, readonly) NSDictionary *customKeys; +@property(nonatomic, copy, readonly) NSString *bundleVersion; +@property(nonatomic, copy, readonly) NSString *bundleShortVersionString; +@property(nonatomic, readonly, nullable) NSDate *crashedOnDate; +@property(nonatomic, copy, readonly) NSString *OSVersion; +@property(nonatomic, copy, readonly) NSString *OSBuildVersion; + +@end + +/** + * The CLSReport exposes an interface to the phsyical report that Crashlytics has created. You can + * use this class to get information about the event, and can also set some values after the + * event has occurred. + **/ +@interface FIRCLSReport : NSObject + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +/** + * Returns the session identifier for the report. + **/ +@property(nonatomic, copy, readonly) NSString *identifier; + +/** + * Returns the custom key value data for the report. + **/ +@property(nonatomic, copy, readonly) NSDictionary *customKeys; + +/** + * Returns the CFBundleVersion of the application that generated the report. + **/ +@property(nonatomic, copy, readonly) NSString *bundleVersion; + +/** + * Returns the CFBundleShortVersionString of the application that generated the report. + **/ +@property(nonatomic, copy, readonly) NSString *bundleShortVersionString; + +/** + * Returns the date that the report was created. + **/ +@property(nonatomic, copy, readonly) NSDate *dateCreated; + +/** + * Returns the os version that the application crashed on. + **/ +@property(nonatomic, copy, readonly) NSString *OSVersion; + +/** + * Returns the os build version that the application crashed on. + **/ +@property(nonatomic, copy, readonly) NSString *OSBuildVersion; + +/** + * Returns YES if the report contains any crash information, otherwise returns NO. + **/ +@property(nonatomic, assign, readonly) BOOL isCrash; + +/** + * You can use this method to set, after the event, additional custom keys. The rules + * and semantics for this method are the same as those documented in FIRCrashlytics.h. Be aware + * that the maximum size and count of custom keys is still enforced, and you can overwrite keys + * and/or cause excess keys to be deleted by using this method. + **/ +- (void)setObjectValue:(nullable id)value forKey:(NSString *)key; + +/** + * Record an application-specific user identifier. See FIRCrashlytics.h for details. + **/ +@property(nonatomic, copy, nullable) NSString *userIdentifier; + +/** + * Record a user name. See FIRCrashlytics.h for details. + **/ +@property(nonatomic, copy, nullable) NSString *userName; + +/** + * Record a user email. See FIRCrashlytics.h for details. + **/ +@property(nonatomic, copy, nullable) NSString *userEmail; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSReport.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSReport.m new file mode 100644 index 0000000..18b1b6f --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSReport.m @@ -0,0 +1,239 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSContext.h" +#import "FIRCLSFile.h" +#import "FIRCLSGlobals.h" +#import "FIRCLSInternalReport.h" +#import "FIRCLSReport_Private.h" +#import "FIRCLSUserLogging.h" + +@interface FIRCLSReport () { + FIRCLSInternalReport *_internalReport; + uint32_t _internalKVCounter; + uint32_t _userKVCounter; + + NSString *_internalCompactedKVFile; + NSString *_internalIncrementalKVFile; + NSString *_userCompactedKVFile; + NSString *_userIncrementalKVFile; + + BOOL _readOnly; + + // cached values, to ensure that their contents remain valid + // even if the report is deleted + NSString *_identifer; + NSString *_bundleVersion; + NSString *_bundleShortVersionString; + NSDate *_dateCreated; + NSDate *_crashedOnDate; + NSString *_OSVersion; + NSString *_OSBuildVersion; + NSNumber *_isCrash; + NSDictionary *_customKeys; +} + +@end + +@implementation FIRCLSReport + +- (instancetype)initWithInternalReport:(FIRCLSInternalReport *)report + prefetchData:(BOOL)shouldPrefetch { + self = [super init]; + if (!self) { + return nil; + } + + _internalReport = report; + + // TODO: correct kv accounting + // The internal report will have non-zero compacted and incremental keys. The right thing to do + // is count them, so we can kick off compactions/pruning at the right times. By + // setting this value to zero, we're allowing more entries to be made than there really + // should be. Not the end of the world, but we should do better eventually. + _internalKVCounter = 0; + _userKVCounter = 0; + + _internalCompactedKVFile = + [self.internalReport pathForContentFile:CLSReportInternalCompactedKVFile]; + _internalIncrementalKVFile = + [self.internalReport pathForContentFile:CLSReportInternalIncrementalKVFile]; + _userCompactedKVFile = [self.internalReport pathForContentFile:CLSReportUserCompactedKVFile]; + _userIncrementalKVFile = [self.internalReport pathForContentFile:CLSReportUserIncrementalKVFile]; + + _readOnly = shouldPrefetch; + + if (shouldPrefetch) { + _identifer = report.identifier; + _bundleVersion = report.bundleVersion; + _bundleShortVersionString = report.bundleShortVersionString; + _dateCreated = report.dateCreated; + _crashedOnDate = report.crashedOnDate; + _OSVersion = report.OSVersion; + _OSBuildVersion = report.OSBuildVersion; + _isCrash = [NSNumber numberWithBool:report.isCrash]; + + _customKeys = [self readCustomKeys]; + } + + return self; +} + +- (instancetype)initWithInternalReport:(FIRCLSInternalReport *)report { + return [self initWithInternalReport:report prefetchData:NO]; +} + +#pragma mark - Helpers +- (FIRCLSUserLoggingKVStorage)internalKVStorage { + FIRCLSUserLoggingKVStorage storage; + + storage.maxCount = _clsContext.readonly->logging.internalKVStorage.maxCount; + storage.maxIncrementalCount = _clsContext.readonly->logging.internalKVStorage.maxIncrementalCount; + storage.compactedPath = [_internalCompactedKVFile fileSystemRepresentation]; + storage.incrementalPath = [_internalIncrementalKVFile fileSystemRepresentation]; + + return storage; +} + +- (FIRCLSUserLoggingKVStorage)userKVStorage { + FIRCLSUserLoggingKVStorage storage; + + storage.maxCount = _clsContext.readonly->logging.userKVStorage.maxCount; + storage.maxIncrementalCount = _clsContext.readonly->logging.userKVStorage.maxIncrementalCount; + storage.compactedPath = [_userCompactedKVFile fileSystemRepresentation]; + storage.incrementalPath = [_userIncrementalKVFile fileSystemRepresentation]; + + return storage; +} + +- (BOOL)canRecordNewValues { + return !_readOnly && FIRCLSContextIsInitialized(); +} + +- (void)recordValue:(id)value forInternalKey:(NSString *)key { + if (!self.canRecordNewValues) { + return; + } + + FIRCLSUserLoggingKVStorage storage = [self internalKVStorage]; + + FIRCLSUserLoggingRecordKeyValue(key, value, &storage, &_internalKVCounter); +} + +- (void)recordValue:(id)value forUserKey:(NSString *)key { + if (!self.canRecordNewValues) { + return; + } + + FIRCLSUserLoggingKVStorage storage = [self userKVStorage]; + + FIRCLSUserLoggingRecordKeyValue(key, value, &storage, &_userKVCounter); +} + +- (NSDictionary *)readCustomKeys { + FIRCLSUserLoggingKVStorage storage = [self userKVStorage]; + + // return decoded entries + return FIRCLSUserLoggingGetCompactedKVEntries(&storage, true); +} + +#pragma mark - Metadata helpers + +- (NSString *)identifier { + if (!_identifer) { + _identifer = self.internalReport.identifier; + } + + return _identifer; +} + +- (NSDictionary *)customKeys { + if (!_customKeys) { + _customKeys = [self readCustomKeys]; + } + + return _customKeys; +} + +- (NSString *)bundleVersion { + if (!_bundleVersion) { + _bundleVersion = self.internalReport.bundleVersion; + } + + return _bundleVersion; +} + +- (NSString *)bundleShortVersionString { + if (!_bundleShortVersionString) { + _bundleShortVersionString = self.internalReport.bundleShortVersionString; + } + + return _bundleShortVersionString; +} + +- (NSDate *)dateCreated { + if (!_dateCreated) { + _dateCreated = self.internalReport.dateCreated; + } + + return _dateCreated; +} + +// for compatibility with the CLSCrashReport Protocol +- (NSDate *)crashedOnDate { + if (!_crashedOnDate) { + _crashedOnDate = self.internalReport.crashedOnDate; + } + + return _crashedOnDate; +} + +- (NSString *)OSVersion { + if (!_OSVersion) { + _OSVersion = self.internalReport.OSVersion; + } + + return _OSVersion; +} + +- (NSString *)OSBuildVersion { + if (!_OSBuildVersion) { + _OSBuildVersion = self.internalReport.OSBuildVersion; + } + + return _OSBuildVersion; +} + +- (BOOL)isCrash { + if (_isCrash == nil) { + _isCrash = [NSNumber numberWithBool:self.internalReport.isCrash]; + } + + return [_isCrash boolValue]; +} + +#pragma mark - Public Read/Write Methods +- (void)setObjectValue:(id)value forKey:(NSString *)key { + [self recordValue:value forUserKey:key]; +} + +- (NSString *)userIdentifier { + return nil; +} + +- (void)setUserIdentifier:(NSString *)userIdentifier { + [self recordValue:userIdentifier forInternalKey:FIRCLSUserIdentifierKey]; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSReport_Private.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSReport_Private.h new file mode 100644 index 0000000..0d8b67c --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSReport_Private.h @@ -0,0 +1,27 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSReport.h" + +@class FIRCLSInternalReport; + +@interface FIRCLSReport () + +- (instancetype)initWithInternalReport:(FIRCLSInternalReport *)report + prefetchData:(BOOL)shouldPrefetch NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithInternalReport:(FIRCLSInternalReport *)report; + +@property(nonatomic, strong, readonly) FIRCLSInternalReport *internalReport; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSSettings.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSSettings.h new file mode 100644 index 0000000..5e7dcae --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSSettings.h @@ -0,0 +1,129 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +#if __has_include() +#import +#else +#import "FBLPromises.h" +#endif + +@class FIRCLSApplicationIdentifierModel; +@class FIRCLSFileManager; + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRCLSSettings : NSObject + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; +- (instancetype)initWithFileManager:(FIRCLSFileManager *)fileManager + appIDModel:(FIRCLSApplicationIdentifierModel *)appIDModel + NS_DESIGNATED_INITIALIZER; + +/** + * Recreates the settings dictionary by re-reading the settings file from persistent storage. This + * should be called before any settings values are read, as it will populate the underlying + * settingsDictionary. If the Google App ID has changed or there is an error, delete the cache file + * and settingsDictionary. If the cache has expired, set `isCacheExpired` to true so that settings + * are re-fetched, but do not delete any values. + */ +- (void)reloadFromCacheWithGoogleAppID:(NSString *)googleAppID + currentTimestamp:(NSTimeInterval)currentTimestamp; + +/** + * Stores a separate file with the settings expiration and Google App ID it was saved with + * so that we can later determine that the settings have expired. + * + * This should be called in a background thread right after the settings.json file has been + * downloaded. + */ +- (void)cacheSettingsWithGoogleAppID:(NSString *)googleAppID + currentTimestamp:(NSTimeInterval)currentTimestamp; + +/** + * Returns true when Settings should be fetched from the server again + */ +@property(nonatomic, readonly) BOOL isCacheExpired; + +/** + * Determines how long these Settings should be respected until the SDK should fetch again + */ +@property(nonatomic, readonly) uint32_t cacheDurationSeconds; + +/** + * The Crashlytics Organization identifier of the app. Allows data continuity between + * old and new Crashlytics SDKs. + */ +@property(nonatomic, nullable, readonly) NSString *orgID; + +/** + * The backend bundle identifier of the app. Crashlytics can in some cases have + * a different bundle identifier than the app itself (eg. Crashlytics will always downcase + * the bundle ID). + */ +@property(nonatomic, nullable, readonly) NSString *fetchedBundleID; + +/** + * Indicates whether the app needs onboarding + */ +@property(nonatomic, readonly) BOOL appNeedsOnboarding; + +/** + * Indicates whether the app needs an update + */ +@property(nonatomic, readonly) BOOL appUpdateRequired; + +/** + * When this is false, Crashlytics will not start up + */ +@property(nonatomic, readonly) BOOL crashReportingEnabled; + +/** + * When this is false, Crashlytics will not collect non-fatal errors and errors + * from the custom exception / record error APIs + */ +@property(nonatomic, readonly) BOOL errorReportingEnabled; + +/** + * When this is false, Crashlytics will not collect custom exceptions from the API + */ +@property(nonatomic, readonly) BOOL customExceptionsEnabled; + +/** + * Returns the maximum number of custom exception events that will be + * recorded in a session. + */ +@property(nonatomic, readonly) uint32_t errorLogBufferSize; + +/** + * Returns the maximum size of the log buffer in bytes + */ +@property(nonatomic, readonly) uint32_t logBufferSize; + +/** + * Returns the maximum number of custom exceptions that will be collected + * in a session. + */ +@property(nonatomic, readonly) uint32_t maxCustomExceptions; + +/** + * Returns the maximum number of custom key-value pair keys (not bytes). + */ +@property(nonatomic, readonly) uint32_t maxCustomKeys; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSSettings.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSSettings.m new file mode 100644 index 0000000..f7ee006 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSSettings.m @@ -0,0 +1,329 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSSettings.h" + +#if __has_include() +#import +#else +#import "FBLPromises.h" +#endif + +#import "FIRCLSApplicationIdentifierModel.h" +#import "FIRCLSConstants.h" +#import "FIRCLSFileManager.h" +#import "FIRCLSLogger.h" +#import "FIRCLSURLBuilder.h" + +NSString *const CreatedAtKey = @"created_at"; +NSString *const GoogleAppIDKey = @"google_app_id"; +NSString *const BuildInstanceID = @"build_instance_id"; + +@interface FIRCLSSettings () + +@property(nonatomic, strong) FIRCLSFileManager *fileManager; +@property(nonatomic, strong) FIRCLSApplicationIdentifierModel *appIDModel; + +@property(nonatomic, strong) NSDictionary *settingsDictionary; + +@property(nonatomic) BOOL isCacheKeyExpired; + +@end + +@implementation FIRCLSSettings + +- (instancetype)initWithFileManager:(FIRCLSFileManager *)fileManager + appIDModel:(FIRCLSApplicationIdentifierModel *)appIDModel { + self = [super init]; + if (!self) { + return nil; + } + + _fileManager = fileManager; + _appIDModel = appIDModel; + + _settingsDictionary = nil; + _isCacheKeyExpired = NO; + + return self; +} + +#pragma mark - Public Methods + +- (void)reloadFromCacheWithGoogleAppID:(NSString *)googleAppID + currentTimestamp:(NSTimeInterval)currentTimestamp { + NSString *settingsFilePath = self.fileManager.settingsFilePath; + + NSData *data = [NSData dataWithContentsOfFile:settingsFilePath]; + if (!data) { + FIRCLSDebugLog(@"[Crashlytics:Settings] No settings were cached"); + + return; + } + + NSError *error = nil; + @synchronized(self) { + _settingsDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; + } + + if (!_settingsDictionary) { + FIRCLSErrorLog(@"Could not load settings file data with error: %@", error.localizedDescription); + + // Attempt to remove it, in case it's messed up + [self deleteCachedSettings]; + return; + } + + NSDictionary *cacheKey = [self loadCacheKey]; + if (!cacheKey) { + FIRCLSErrorLog(@"Could not load settings cache key"); + + [self deleteCachedSettings]; + return; + } + + NSString *cachedGoogleAppID = cacheKey[GoogleAppIDKey]; + if (![cachedGoogleAppID isEqualToString:googleAppID]) { + FIRCLSDebugLog( + @"[Crashlytics:Settings] Invalidating settings cache because Google App ID changed"); + + [self deleteCachedSettings]; + return; + } + + NSTimeInterval cacheCreatedAt = [cacheKey[CreatedAtKey] unsignedIntValue]; + NSTimeInterval cacheDurationSeconds = self.cacheDurationSeconds; + if (currentTimestamp > (cacheCreatedAt + cacheDurationSeconds)) { + FIRCLSDebugLog(@"[Crashlytics:Settings] Settings TTL expired"); + + @synchronized(self) { + self.isCacheKeyExpired = YES; + } + } + + NSString *cacheBuildInstanceID = cacheKey[BuildInstanceID]; + if (![cacheBuildInstanceID isEqualToString:self.appIDModel.buildInstanceID]) { + FIRCLSDebugLog(@"[Crashlytics:Settings] Settings expired because build instance changed"); + + @synchronized(self) { + self.isCacheKeyExpired = YES; + } + } +} + +- (void)cacheSettingsWithGoogleAppID:(NSString *)googleAppID + currentTimestamp:(NSTimeInterval)currentTimestamp { + NSNumber *createdAtTimestamp = [NSNumber numberWithDouble:currentTimestamp]; + NSDictionary *cacheKey = @{ + CreatedAtKey : createdAtTimestamp, + GoogleAppIDKey : googleAppID, + BuildInstanceID : self.appIDModel.buildInstanceID, + }; + + NSError *error = nil; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:cacheKey + options:kNilOptions + error:&error]; + + if (!jsonData) { + FIRCLSErrorLog(@"Could not create settings cache key with error: %@", + error.localizedDescription); + + return; + } + + if ([self.fileManager fileExistsAtPath:self.fileManager.settingsCacheKeyPath]) { + [self.fileManager removeItemAtPath:self.fileManager.settingsCacheKeyPath]; + } + [self.fileManager createFileAtPath:self.fileManager.settingsCacheKeyPath + contents:jsonData + attributes:nil]; + + // If Settings were expired before, they should no longer be expired after this. + // This may be set back to YES if reloading from the cache fails + @synchronized(self) { + self.isCacheKeyExpired = NO; + } + + [self reloadFromCacheWithGoogleAppID:googleAppID currentTimestamp:currentTimestamp]; +} + +#pragma mark - Convenience Methods + +- (NSDictionary *)loadCacheKey { + NSData *cacheKeyData = [NSData dataWithContentsOfFile:self.fileManager.settingsCacheKeyPath]; + + if (!cacheKeyData) { + return nil; + } + + NSError *error = nil; + NSDictionary *cacheKey = [NSJSONSerialization JSONObjectWithData:cacheKeyData + options:NSJSONReadingAllowFragments + error:&error]; + return cacheKey; +} + +- (void)deleteCachedSettings { + __weak FIRCLSSettings *weakSelf = self; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ + __strong FIRCLSSettings *strongSelf = weakSelf; + if ([strongSelf.fileManager fileExistsAtPath:strongSelf.fileManager.settingsFilePath]) { + [strongSelf.fileManager removeItemAtPath:strongSelf.fileManager.settingsFilePath]; + } + if ([strongSelf.fileManager fileExistsAtPath:strongSelf.fileManager.settingsCacheKeyPath]) { + [strongSelf.fileManager removeItemAtPath:strongSelf.fileManager.settingsCacheKeyPath]; + } + }); + + @synchronized(self) { + self.isCacheKeyExpired = YES; + _settingsDictionary = nil; + } +} + +- (NSDictionary *)settingsDictionary { + @synchronized(self) { + return _settingsDictionary; + } +} + +#pragma mark - Settings Groups + +- (NSDictionary *)appSettings { + return self.settingsDictionary[@"app"]; +} + +- (NSDictionary *)sessionSettings { + return self.settingsDictionary[@"session"]; +} + +- (NSDictionary *)featuresSettings { + return self.settingsDictionary[@"features"]; +} + +- (NSDictionary *)fabricSettings { + return self.settingsDictionary[@"fabric"]; +} + +#pragma mark - Caching + +- (BOOL)isCacheExpired { + if (!self.settingsDictionary) { + return YES; + } + + @synchronized(self) { + return self.isCacheKeyExpired; + } +} + +- (uint32_t)cacheDurationSeconds { + id fetchedCacheDuration = self.settingsDictionary[@"cache_duration"]; + if (fetchedCacheDuration) { + return [fetchedCacheDuration unsignedIntValue]; + } + + return 60 * 60; +} + +#pragma mark - Identifiers + +- (nullable NSString *)orgID { + return self.fabricSettings[@"org_id"]; +} + +- (nullable NSString *)fetchedBundleID { + return self.fabricSettings[@"bundle_id"]; +} + +#pragma mark - Onboarding / Update + +- (NSString *)appStatus { + return self.appSettings[@"status"]; +} + +- (BOOL)appNeedsOnboarding { + return [self.appStatus isEqualToString:@"new"]; +} + +- (BOOL)appUpdateRequired { + return [[self.appSettings objectForKey:@"update_required"] boolValue]; +} + +#pragma mark - On / Off Switches + +- (BOOL)errorReportingEnabled { + NSNumber *value = [self featuresSettings][@"collect_logged_exceptions"]; + + if (value != nil) { + return [value boolValue]; + } + + return YES; +} + +- (BOOL)customExceptionsEnabled { + // Right now, recording custom exceptions from the API and + // automatically capturing non-fatal errors go hand in hand + return [self errorReportingEnabled]; +} + +- (BOOL)crashReportingEnabled { + NSNumber *value = [self featuresSettings][@"collect_reports"]; + + if (value != nil) { + return value.boolValue; + } + + return YES; +} + +#pragma mark - Optional Limit Overrides + +- (uint32_t)errorLogBufferSize { + return [self logBufferSize]; +} + +- (uint32_t)logBufferSize { + NSNumber *value = [self sessionSettings][@"log_buffer_size"]; + + if (value != nil) { + return value.unsignedIntValue; + } + + return 64 * 1000; +} + +- (uint32_t)maxCustomExceptions { + NSNumber *value = [self sessionSettings][@"max_custom_exception_events"]; + + if (value != nil) { + return value.unsignedIntValue; + } + + return 8; +} + +- (uint32_t)maxCustomKeys { + NSNumber *value = [self sessionSettings][@"max_custom_key_value_pairs"]; + + if (value != nil) { + return value.unsignedIntValue; + } + + return 64; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSStackFrame.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSStackFrame.h new file mode 100644 index 0000000..24ef1cc --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSStackFrame.h @@ -0,0 +1,44 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * + * This class is used in conjunction with -[Crashlytics + * recordCustomExceptionName:reason:frameArray:] to record information about non-ObjC/C++ + * exceptions. All information included here will be displayed in the Crashlytics UI, and can + * influence crash grouping. Be particularly careful with the use of the address property. If set, + * Crashlytics will attempt symbolication and could overwrite other properities in the process. + * + **/ +@interface FIRCLSStackFrame : NSObject + ++ (instancetype)stackFrame; ++ (instancetype)stackFrameWithAddress:(NSUInteger)address; ++ (instancetype)stackFrameWithSymbol:(NSString *)symbol; + +@property(nonatomic, copy, nullable) NSString *symbol; +@property(nonatomic, copy, nullable) NSString *rawSymbol; +@property(nonatomic, copy, nullable) NSString *library; +@property(nonatomic, copy, nullable) NSString *fileName; +@property(nonatomic, assign) uint32_t lineNumber; +@property(nonatomic, assign) uint64_t offset; +@property(nonatomic, assign) uint64_t address; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSStackFrame.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSStackFrame.m new file mode 100644 index 0000000..8227e4f --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSStackFrame.m @@ -0,0 +1,50 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSStackFrame.h" + +@implementation FIRCLSStackFrame + ++ (instancetype)stackFrame { + return [[self alloc] init]; +} + ++ (instancetype)stackFrameWithAddress:(NSUInteger)address { + FIRCLSStackFrame* frame = [self stackFrame]; + + [frame setAddress:address]; + + return frame; +} + ++ (instancetype)stackFrameWithSymbol:(NSString*)symbol { + FIRCLSStackFrame* frame = [self stackFrame]; + + frame.symbol = symbol; + frame.rawSymbol = symbol; + + return frame; +} + +- (NSString*)description { + if ([self fileName]) { + return [NSString stringWithFormat:@"{[0x%llx] %@ - %@:%u}", [self address], [self fileName], + [self symbol], [self lineNumber]]; + } + + return [NSString + stringWithFormat:@"{[0x%llx + %u] %@}", [self address], [self lineNumber], [self symbol]]; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSSymbolResolver.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSSymbolResolver.h new file mode 100644 index 0000000..85adc85 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSSymbolResolver.h @@ -0,0 +1,26 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +@class FIRCLSStackFrame; + +@interface FIRCLSSymbolResolver : NSObject + +- (BOOL)loadBinaryImagesFromFile:(NSString *)path; + +- (FIRCLSStackFrame *)frameForAddress:(uint64_t)address; +- (BOOL)updateStackFrame:(FIRCLSStackFrame *)frame; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSSymbolResolver.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSSymbolResolver.m new file mode 100644 index 0000000..a96dd70 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Models/FIRCLSSymbolResolver.m @@ -0,0 +1,175 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSSymbolResolver.h" + +#include + +#include "FIRCLSBinaryImage.h" +#include "FIRCLSFile.h" +#import "FIRCLSLogger.h" +#import "FIRCLSStackFrame.h" + +@interface FIRCLSSymbolResolver () { + NSMutableArray* _binaryImages; +} + +@end + +@implementation FIRCLSSymbolResolver + +- (instancetype)init { + self = [super init]; + if (!self) { + return nil; + } + + _binaryImages = [NSMutableArray array]; + + return self; +} + +- (BOOL)loadBinaryImagesFromFile:(NSString*)path { + if ([path length] == 0) { + return NO; + } + + NSArray* sections = FIRCLSFileReadSections([path fileSystemRepresentation], false, nil); + + if ([sections count] == 0) { + FIRCLSErrorLog(@"Failed to read binary image file %@", path); + return NO; + } + + // filter out unloads, as well as loads with invalid entries + for (NSDictionary* entry in sections) { + NSDictionary* details = [entry objectForKey:@"load"]; + if (!details) { + continue; + } + + // This does happen occationally and causes a crash. I'm really not sure there + // is anything sane we can do in this case. + if (![details objectForKey:@"base"] || ![details objectForKey:@"size"]) { + continue; + } + + if ([details objectForKey:@"base"] == (id)[NSNull null] || + [details objectForKey:@"size"] == (id)[NSNull null]) { + continue; + } + + [_binaryImages addObject:details]; + } + + [_binaryImages sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { + NSNumber* base1 = [obj1 objectForKey:@"base"]; + NSNumber* base2 = [obj2 objectForKey:@"base"]; + + return [base1 compare:base2]; + }]; + + return YES; +} + +- (NSDictionary*)loadedBinaryImageForPC:(uintptr_t)pc { + NSUInteger index = + [_binaryImages indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL* stop) { + uintptr_t base = [[obj objectForKey:@"base"] unsignedIntegerValue]; + uintptr_t size = [[obj objectForKey:@"size"] unsignedIntegerValue]; + + return pc >= base && pc < (base + size); + }]; + + if (index == NSNotFound) { + return nil; + } + + return [_binaryImages objectAtIndex:index]; +} + +- (BOOL)fillInImageDetails:(FIRCLSBinaryImageDetails*)details forUUID:(NSString*)uuid { + if (!details || !uuid) { + return NO; + } + + return FIRCLSBinaryImageFindImageForUUID([uuid UTF8String], details); +} + +- (FIRCLSStackFrame*)frameForAddress:(uint64_t)address { + FIRCLSStackFrame* frame = [FIRCLSStackFrame stackFrameWithAddress:(NSUInteger)address]; + + if (![self updateStackFrame:frame]) { + return nil; + } + + return frame; +} + +- (BOOL)updateStackFrame:(FIRCLSStackFrame*)frame { + uint64_t address = [frame address]; + if (address == 0) { + return NO; + } + + NSDictionary* binaryImage = [self loadedBinaryImageForPC:(uintptr_t)address]; + + FIRCLSBinaryImageDetails imageDetails; + + if (![self fillInImageDetails:&imageDetails forUUID:[binaryImage objectForKey:@"uuid"]]) { +#if DEBUG + FIRCLSDebugLog(@"Image not found"); +#endif + return NO; + } + + uintptr_t addr = (uintptr_t)address - + (uintptr_t)[[binaryImage objectForKey:@"base"] unsignedIntegerValue] + + (uintptr_t)imageDetails.node.baseAddress; + Dl_info dlInfo; + + if (dladdr((void*)addr, &dlInfo) == 0) { +#if DEBUG + FIRCLSDebugLog(@"Could not look up address"); +#endif + return NO; + } + + if (addr - (uintptr_t)dlInfo.dli_saddr == 0) { + addr -= 2; + if (dladdr((void*)addr, &dlInfo) == 0) { +#if DEBUG + FIRCLSDebugLog(@"Could not look up address"); +#endif + return NO; + } + } + + if (dlInfo.dli_sname) { + NSString* symbol = [NSString stringWithUTF8String:dlInfo.dli_sname]; + + frame.symbol = symbol; + frame.rawSymbol = symbol; + } + + if (addr > (uintptr_t)dlInfo.dli_saddr) { + [frame setOffset:addr - (uintptr_t)dlInfo.dli_saddr]; + } + + [frame setLibrary:[[binaryImage objectForKey:@"path"] lastPathComponent]]; + + return YES; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/FIRCLSAsyncOperation.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/FIRCLSAsyncOperation.h new file mode 100644 index 0000000..5636d3d --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/FIRCLSAsyncOperation.h @@ -0,0 +1,23 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +typedef void (^FIRCLSAsyncOperationCompletionBlock)(NSError* error); + +@interface FIRCLSAsyncOperation : NSOperation + +@property(copy, nonatomic) FIRCLSAsyncOperationCompletionBlock asyncCompletion; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/FIRCLSAsyncOperation.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/FIRCLSAsyncOperation.m new file mode 100644 index 0000000..94415f1 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/FIRCLSAsyncOperation.m @@ -0,0 +1,135 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSAsyncOperation.h" + +@interface FIRCLSAsyncOperation () { + BOOL _internalExecuting; + BOOL _internalFinished; +} + +@property(nonatomic, strong) NSRecursiveLock *lock; + +@end + +@implementation FIRCLSAsyncOperation + +- (instancetype)init { + self = [super init]; + if (!self) { + return nil; + } + + _internalExecuting = NO; + _internalFinished = NO; + + self.lock = [[NSRecursiveLock alloc] init]; + self.lock.name = @"com.crashlytics.async-operation-lock"; + + return self; +} + +#pragma mark - NSOperation Overrides +- (BOOL)isConcurrent { + return YES; +} + +- (BOOL)isAsynchronous { + return YES; +} + +- (BOOL)isExecuting { + [self.lock lock]; + BOOL result = _internalExecuting; + [self.lock unlock]; + + return result; +} + +- (BOOL)isFinished { + [self.lock lock]; + BOOL result = _internalFinished; + [self.lock unlock]; + + return result; +} + +- (void)start { + if ([self checkForCancellation]) { + return; + } + + [self markStarted]; + + [self main]; +} + +#pragma mark - Utilities +- (void)changeValueForKey:(NSString *)key inBlock:(void (^)(void))block { + [self willChangeValueForKey:key]; + block(); + [self didChangeValueForKey:key]; +} + +- (void)lock:(void (^)(void))block { + [self.lock lock]; + block(); + [self.lock unlock]; +} + +- (BOOL)checkForCancellation { + if ([self isCancelled]) { + [self markDone]; + return YES; + } + + return NO; +} + +#pragma mark - State Management +- (void)unlockedMarkFinished { + [self changeValueForKey:@"isFinished" + inBlock:^{ + self->_internalFinished = YES; + }]; +} + +- (void)unlockedMarkStarted { + [self changeValueForKey:@"isExecuting" + inBlock:^{ + self->_internalExecuting = YES; + }]; +} + +- (void)unlockedMarkComplete { + [self changeValueForKey:@"isExecuting" + inBlock:^{ + self->_internalExecuting = NO; + }]; +} + +- (void)markStarted { + [self lock:^{ + [self unlockedMarkStarted]; + }]; +} + +- (void)markDone { + [self lock:^{ + [self unlockedMarkComplete]; + [self unlockedMarkFinished]; + }]; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/FIRCLSAsyncOperation_Private.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/FIRCLSAsyncOperation_Private.h new file mode 100644 index 0000000..1135ed7 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/FIRCLSAsyncOperation_Private.h @@ -0,0 +1,24 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSAsyncOperation.h" + +@interface FIRCLSAsyncOperation (Private) + +- (void)markStarted; +- (void)markDone; + +- (BOOL)checkForCancellation; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Reports/FIRCLSPackageReportOperation.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Reports/FIRCLSPackageReportOperation.h new file mode 100644 index 0000000..5822e75 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Reports/FIRCLSPackageReportOperation.h @@ -0,0 +1,35 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +@class FIRCLSInternalReport; +@class FIRCLSFileManager; +@class FIRCLSSettings; + +@interface FIRCLSPackageReportOperation : NSOperation + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; +- (instancetype)initWithReport:(FIRCLSInternalReport *)report + fileManager:(FIRCLSFileManager *)fileManager + settings:(FIRCLSSettings *)settings NS_DESIGNATED_INITIALIZER; + +@property(nonatomic, readonly) FIRCLSInternalReport *report; +@property(nonatomic, readonly) FIRCLSFileManager *fileManager; +@property(nonatomic, readonly) FIRCLSSettings *settings; + +@property(nonatomic, copy, readonly) NSString *finalPath; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Reports/FIRCLSPackageReportOperation.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Reports/FIRCLSPackageReportOperation.m new file mode 100644 index 0000000..d67ca56 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Reports/FIRCLSPackageReportOperation.m @@ -0,0 +1,210 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSPackageReportOperation.h" + +#include +#include + +#import "FIRCLSFileManager.h" +#import "FIRCLSInternalReport.h" + +#import "FIRCLSUtility.h" + +#import "FIRCLSByteUtility.h" +#import "FIRCLSMultipartMimeStreamEncoder.h" + +#import "FIRCLSSettings.h" + +@interface FIRCLSPackageReportOperation () + +@property(nonatomic, copy) NSString *finalPath; + +@end + +@implementation FIRCLSPackageReportOperation + +- (instancetype)initWithReport:(FIRCLSInternalReport *)report + fileManager:(FIRCLSFileManager *)fileManager + settings:(FIRCLSSettings *)settings { + self = [super init]; + if (!self) { + return nil; + } + + _report = report; + _fileManager = fileManager; + _settings = settings; + + return self; +} + +- (BOOL)compressData:(NSData *)data toPath:(NSString *)path { + gzFile file = gzopen([path fileSystemRepresentation], "w"); + if (file == Z_NULL) { + FIRCLSSDKLogError("Error: unable to open file for compression %s\n", strerror(errno)); + return NO; + } + + __block BOOL success = [data length] > 0; + + FIRCLSEnumerateByteRangesOfNSDataUsingBlock( + data, ^(const void *bytes, NSRange byteRange, BOOL *stop) { + size_t length = byteRange.length; + + if (![self writeBytes:bytes length:length toGZFile:file]) { + *stop = YES; + success = NO; + } + }); + + gzclose(file); + + return success; +} + +- (BOOL)writeBytes:(const void *)buffer length:(size_t)length toGZFile:(gzFile)file { + return FIRCLSFileLoopWithWriteBlock( + buffer, length, ^ssize_t(const void *partialBuffer, size_t partialLength) { + errno = 0; + int ret = gzwrite(file, buffer, (unsigned int)length); + + if (ret == 0) { + int zerror = 0; + const char *errorString = gzerror(file, &zerror); + + FIRCLSSDKLogError("Error: failed to write compressed bytes %d, %s, %s \n", zerror, + errorString, strerror(errno)); + } + + return ret; + }); +} + +- (NSString *)reportPath { + return [self.report path]; +} + +- (NSString *)destinationDirectory { + return [self.fileManager preparedPath]; +} + +- (NSString *)packagedPathWithName:(NSString *)name { + // the output file will use the boundary as the filename, and "multipartmime" as the extension + return [[self.destinationDirectory stringByAppendingPathComponent:name] + stringByAppendingPathExtension:@"multipartmime"]; +} + +- (void)main { + NSString *reportOrgID = self.settings.orgID; + if (!reportOrgID) { + FIRCLSDebugLog( + @"[Crashlytics:PackageReport] Skipping packaging of report with id '%@' this run of the " + @"app because Organization ID was nil. Report will upload once settings are download " + @"successfully", + self.report.identifier); + + return; + } + + self.finalPath = nil; + + NSString *boundary = [FIRCLSMultipartMimeStreamEncoder generateBoundary]; + NSString *destPath = [self packagedPathWithName:boundary]; + + // try to read the metadata file, which could always fail + NSString *reportSessionId = self.report.identifier; + + NSOutputStream *stream = [NSOutputStream outputStreamToFileAtPath:destPath append:NO]; + + FIRCLSMultipartMimeStreamEncoder *encoder = + [FIRCLSMultipartMimeStreamEncoder encoderWithStream:stream andBoundary:boundary]; + if (!encoder) { + return; + } + + [encoder encode:^{ + [encoder addValue:reportOrgID fieldName:@"org_id"]; + + if (reportSessionId) { + [encoder addValue:reportSessionId fieldName:@"report_id"]; + } + + [self.fileManager + enumerateFilesInDirectory:self.reportPath + usingBlock:^(NSString *filePath, NSString *extension) { + if (self.cancelled) { + return; + } + + // Do not package or include already gz'ed files. These can get left over + // from previously-submitted reports. There's an opportinity here to avoid + // compressed certain types of files that cannot be changed. + if ([extension isEqualToString:@"gz"]) { + return; + } + + NSData *data = [NSData dataWithContentsOfFile:filePath + options:0 + error:nil]; + if ([data length] == 0) { + const char *filename = [[filePath lastPathComponent] UTF8String]; + + FIRCLSSDKLogError("Error: unable to read data for compression: %s\n", + filename); + return; + } + + [self encode:encoder data:data fromPath:filePath]; + }]; + }]; + + if (self.cancelled) { + [self.fileManager removeItemAtPath:destPath]; + return; + } + + self.finalPath = destPath; +} + +- (void)encode:(FIRCLSMultipartMimeStreamEncoder *)encoder + data:(NSData *)data + fromPath:(NSString *)path { + // must be non-nil and > 0 length + if ([path length] == 0) { + FIRCLSSDKLogError("Error: path is invalid\n"); + return; + } + + NSString *uploadPath = [path stringByAppendingPathExtension:@"gz"]; + NSString *fieldname = [path lastPathComponent]; + NSString *filename = [uploadPath lastPathComponent]; + NSString *mimeType = @"application/x-gzip"; + + // first, attempt to compress + if (![self compressData:data toPath:uploadPath]) { + FIRCLSSDKLogError("Error: compression failed for %s\n", [filename UTF8String]); + + // attempt the upload without compression + mimeType = @"text/plain"; + uploadPath = path; + } + + [encoder addFile:[NSURL fileURLWithPath:uploadPath] + fileName:filename + mimeType:mimeType + fieldName:fieldname]; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Reports/FIRCLSProcessReportOperation.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Reports/FIRCLSProcessReportOperation.h new file mode 100644 index 0000000..1e90286 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Reports/FIRCLSProcessReportOperation.h @@ -0,0 +1,30 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +@class FIRCLSInternalReport; +@class FIRCLSSymbolResolver; + +@interface FIRCLSProcessReportOperation : NSOperation + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; +- (instancetype)initWithReport:(FIRCLSInternalReport *)report + resolver:(FIRCLSSymbolResolver *)resolver NS_DESIGNATED_INITIALIZER; + +@property(nonatomic, readonly) FIRCLSSymbolResolver *symbolResolver; +@property(nonatomic, readonly) FIRCLSInternalReport *report; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Reports/FIRCLSProcessReportOperation.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Reports/FIRCLSProcessReportOperation.m new file mode 100644 index 0000000..3c5ab8a --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Reports/FIRCLSProcessReportOperation.m @@ -0,0 +1,113 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSProcessReportOperation.h" + +#import "FIRCLSDemangleOperation.h" +#import "FIRCLSFile.h" +#import "FIRCLSInternalReport.h" +#import "FIRCLSSerializeSymbolicatedFramesOperation.h" +#import "FIRCLSStackFrame.h" +#import "FIRCLSSymbolResolver.h" +#import "FIRCLSSymbolicationOperation.h" + +@implementation FIRCLSProcessReportOperation + +- (instancetype)initWithReport:(FIRCLSInternalReport *)report + resolver:(FIRCLSSymbolResolver *)resolver { + self = [super init]; + if (!self) { + return nil; + } + + _report = report; + _symbolResolver = resolver; + + return self; +} + +- (NSString *)binaryImagePath { + return self.report.binaryImagePath; +} + +- (NSArray *)threadArrayFromFile:(NSString *)path { + NSArray *threads = + FIRCLSFileReadSections([path fileSystemRepresentation], false, ^NSObject *(id obj) { + // use this to select out the one entry that has a "threads" top-level entry + return [obj objectForKey:@"threads"]; + }); + + if ([threads count] == 0) { + return nil; + } + + // threads is actually an array of arrays + threads = [threads objectAtIndex:0]; + if (!threads) { + return nil; + } + + NSMutableArray *threadArray = [NSMutableArray array]; + + for (NSDictionary *threadDetails in threads) { + NSMutableArray *frameArray = [NSMutableArray array]; + + for (NSNumber *pc in [threadDetails objectForKey:@"stacktrace"]) { + FIRCLSStackFrame *frame = [FIRCLSStackFrame stackFrameWithAddress:[pc unsignedIntegerValue]]; + + [frameArray addObject:frame]; + } + + [threadArray addObject:frameArray]; + } + + return threadArray; +} + +- (BOOL)symbolicateFile:(NSString *)path withResolver:(FIRCLSSymbolResolver *)resolver { + NSArray *threadArray = [self threadArrayFromFile:path]; + if (!threadArray) { + return NO; + } + + FIRCLSSymbolicationOperation *symbolicationOp = [[FIRCLSSymbolicationOperation alloc] init]; + [symbolicationOp setThreadArray:threadArray]; + [symbolicationOp setSymbolResolver:resolver]; + + FIRCLSDemangleOperation *demangleOp = [[FIRCLSDemangleOperation alloc] init]; + [demangleOp setThreadArray:threadArray]; + + FIRCLSSerializeSymbolicatedFramesOperation *serializeOp = + [[FIRCLSSerializeSymbolicatedFramesOperation alloc] init]; + [serializeOp setThreadArray:threadArray]; + [serializeOp setOutputPath:[path stringByAppendingPathExtension:@"symbolicated"]]; + + [symbolicationOp start]; + [demangleOp start]; + [serializeOp start]; + + return YES; +} + +- (void)main { + if (![self.symbolResolver loadBinaryImagesFromFile:self.binaryImagePath]) { + return; + } + + [self.report enumerateSymbolicatableFilesInContent:^(NSString *path) { + [self symbolicateFile:path withResolver:self.symbolResolver]; + }]; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSDemangleOperation.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSDemangleOperation.h new file mode 100644 index 0000000..b26b87c --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSDemangleOperation.h @@ -0,0 +1,24 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSThreadArrayOperation.h" + +@interface FIRCLSDemangleOperation : FIRCLSThreadArrayOperation + ++ (NSString *)demangleSymbol:(const char *)symbol; ++ (NSString *)demangleCppSymbol:(const char *)symbol; + +- (NSString *)demangleSymbol:(const char *)symbol; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSDemangleOperation.mm b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSDemangleOperation.mm new file mode 100644 index 0000000..182f7a0 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSDemangleOperation.mm @@ -0,0 +1,96 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FIRCLSDemangleOperation.h" +#include "FIRCLSStackFrame.h" + +#import + +@implementation FIRCLSDemangleOperation + ++ (NSString *)demangleSymbol:(const char *)symbol { + if (!symbol) { + return nil; + } + + if (strncmp(symbol, "_Z", 2) == 0) { + return [self demangleCppSymbol:symbol]; + } else if (strncmp(symbol, "__Z", 3) == 0) { + return [self demangleBlockInvokeCppSymbol:symbol]; + } + + return nil; +} + ++ (NSString *)demangleBlockInvokeCppSymbol:(const char *)symbol { + NSString *string = [NSString stringWithUTF8String:symbol]; + + // search backwards, because this string should be at the end + NSRange range = [string rangeOfString:@"_block_invoke" options:NSBackwardsSearch]; + + if (range.location == NSNotFound) { + return nil; + } + + // we need at least a "_Z..." for a valid C++ symbol, so make sure of that + if (range.location < 5) { + return nil; + } + + // extract the mangled C++ symbol from the string + NSString *cppSymbol = [string substringWithRange:NSMakeRange(1, range.location - 1)]; + cppSymbol = [self demangleSymbol:[cppSymbol UTF8String]]; + if (!cppSymbol) { + return nil; + } + + // extract out just the "_block_invoke..." part + string = + [string substringWithRange:NSMakeRange(range.location, [string length] - range.location)]; + + // and glue that onto the end + return [cppSymbol stringByAppendingString:string]; +} + ++ (NSString *)demangleCppSymbol:(const char *)symbol { + int status; + char *buffer = NULL; + + buffer = __cxxabiv1::__cxa_demangle(symbol, buffer, NULL, &status); + if (!buffer) { + return nil; + } + + NSString *result = [NSString stringWithUTF8String:buffer]; + + free(buffer); + + return result; +} + +- (NSString *)demangleSymbol:(const char *)symbol { + return [[self class] demangleSymbol:symbol]; +} + +- (void)main { + [self enumerateFramesWithBlock:^(FIRCLSStackFrame *frame) { + NSString *demangedSymbol = [self demangleSymbol:[[frame rawSymbol] UTF8String]]; + + if (demangedSymbol) { + [frame setSymbol:demangedSymbol]; + } + }]; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSSerializeSymbolicatedFramesOperation.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSSerializeSymbolicatedFramesOperation.h new file mode 100644 index 0000000..b73c67c --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSSerializeSymbolicatedFramesOperation.h @@ -0,0 +1,21 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSThreadArrayOperation.h" + +@interface FIRCLSSerializeSymbolicatedFramesOperation : FIRCLSThreadArrayOperation + +@property(nonatomic, copy) NSString *outputPath; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSSerializeSymbolicatedFramesOperation.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSSerializeSymbolicatedFramesOperation.m new file mode 100644 index 0000000..6b2b79b --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSSerializeSymbolicatedFramesOperation.m @@ -0,0 +1,63 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSSerializeSymbolicatedFramesOperation.h" + +#import "FIRCLSFile.h" +#import "FIRCLSLogger.h" +#import "FIRCLSStackFrame.h" + +@implementation FIRCLSSerializeSymbolicatedFramesOperation + +- (void)main { + FIRCLSFile file; + + // Make sure not to open in append mode, so we can overwrite any pre-existing symbolication + // files. + if (!FIRCLSFileInitWithPathMode(&file, [self.outputPath fileSystemRepresentation], false, + false)) { + FIRCLSErrorLog(@"Failed to create output file"); + return; + } + + FIRCLSFileWriteSectionStart(&file, "threads"); + FIRCLSFileWriteArrayStart(&file); + + for (NSArray *frameArray in self.threadArray) { + FIRCLSFileWriteArrayStart(&file); + + for (FIRCLSStackFrame *frame in frameArray) { + FIRCLSFileWriteHashStart(&file); + FIRCLSFileWriteHashEntryString(&file, "symbol", [[frame symbol] UTF8String]); + + // only include this field if it is present and different + if (![[frame rawSymbol] isEqualToString:[frame symbol]]) { + FIRCLSFileWriteHashEntryString(&file, "raw_symbol", [[frame rawSymbol] UTF8String]); + } + + FIRCLSFileWriteHashEntryUint64(&file, "offset", [frame offset]); + FIRCLSFileWriteHashEntryString(&file, "library", [[frame library] UTF8String]); + + FIRCLSFileWriteHashEnd(&file); + } + + FIRCLSFileWriteArrayEnd(&file); + } + + FIRCLSFileWriteArrayEnd(&file); + FIRCLSFileWriteSectionEnd(&file); + FIRCLSFileClose(&file); +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSSymbolicationOperation.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSSymbolicationOperation.h new file mode 100644 index 0000000..7c63e20 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSSymbolicationOperation.h @@ -0,0 +1,23 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSThreadArrayOperation.h" + +@class FIRCLSSymbolResolver; + +@interface FIRCLSSymbolicationOperation : FIRCLSThreadArrayOperation + +@property(nonatomic, strong) FIRCLSSymbolResolver *symbolResolver; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSSymbolicationOperation.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSSymbolicationOperation.m new file mode 100644 index 0000000..e2988b2 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSSymbolicationOperation.m @@ -0,0 +1,28 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSSymbolicationOperation.h" + +#import "FIRCLSStackFrame.h" +#import "FIRCLSSymbolResolver.h" + +@implementation FIRCLSSymbolicationOperation + +- (void)main { + [self enumerateFramesWithBlock:^(FIRCLSStackFrame *frame) { + [self.symbolResolver updateStackFrame:frame]; + }]; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSThreadArrayOperation.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSThreadArrayOperation.h new file mode 100644 index 0000000..e223523 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSThreadArrayOperation.h @@ -0,0 +1,25 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +@class FIRCLSStackFrame; + +@interface FIRCLSThreadArrayOperation : NSOperation + +@property(nonatomic, strong) NSArray *threadArray; + +- (void)enumerateFramesWithBlock:(void (^)(FIRCLSStackFrame *frame))block; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSThreadArrayOperation.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSThreadArrayOperation.m new file mode 100644 index 0000000..96c7327 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSThreadArrayOperation.m @@ -0,0 +1,33 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSThreadArrayOperation.h" + +#import "FIRCLSStackFrame.h" + +@implementation FIRCLSThreadArrayOperation + +- (void)enumerateFramesWithBlock:(void (^)(FIRCLSStackFrame *frame))block { + for (NSArray *frameArray in self.threadArray) { + for (FIRCLSStackFrame *frame in frameArray) { + block(frame); + + if ([self isCancelled]) { + break; + } + } + } +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Public/FIRCrashlytics.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Public/FIRCrashlytics.h new file mode 100644 index 0000000..16a4424 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Public/FIRCrashlytics.h @@ -0,0 +1,173 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * The Firebase Crashlytics API provides methods to annotate and manage fatal and + * non-fatal reports captured and reported to Firebase Crashlytics. + * + * By default, Firebase Crashlytics is initialized with `[FIRApp configure]`. + * + * Note: The Crashlytics class cannot be subclassed. If this makes testing difficult, + * we suggest using a wrapper class or a protocol extension. + */ +NS_SWIFT_NAME(Crashlytics) +@interface FIRCrashlytics : NSObject + +/** :nodoc: */ +- (instancetype)init NS_UNAVAILABLE; + +/** + * Accesses the singleton Crashlytics instance. + * + * @return The singleton Crashlytics instance. + */ ++ (instancetype)crashlytics NS_SWIFT_NAME(crashlytics()); + +/** + * Adds logging that is sent with your crash data. The logging does not appear in the + * system.log and is only visible in the Crashlytics dashboard. + * + * @param msg Message to log + */ +- (void)log:(NSString *)msg; + +/** + * Adds logging that is sent with your crash data. The logging does not appear in the + * system.log and is only visible in the Crashlytics dashboard. + * + * @param format Format of string + * @param ... A comma-separated list of arguments to substitute into format + */ +- (void)logWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1, 2); + +/** + * Adds logging that is sent with your crash data. The logging does not appear in the + * system.log and is only visible in the Crashlytics dashboard. + * + * @param format Format of string + * @param args Arguments to substitute into format + */ +- (void)logWithFormat:(NSString *)format + arguments:(va_list)args NS_SWIFT_NAME(log(format:arguments:)); + +/** + * Sets a custom key and value to be associated with subsequent fatal and non-fatal reports. + * When setting an object value, the object is converted to a string. This is + * typically done by calling "-[NSObject description]". + * + * @param value The value to be associated with the key + * @param key A unique key + */ +- (void)setCustomValue:(id)value forKey:(NSString *)key; + +/** + * Records a user ID (identifier) that's associated with subsequent fatal and non-fatal reports. + * + * If you want to associate a crash with a specific user, we recommend specifying an arbitrary + * string (e.g., a database, ID, hash, or other value that you can index and query, but is + * meaningless to a third-party observer). This allows you to facilitate responses for support + * requests and reach out to users for more information. + * + * @param userID An arbitrary user identifier string that associates a user to a record in your + * system. + */ +- (void)setUserID:(NSString *)userID; + +/** + * + * Records a non-fatal event described by an NSError object. The events are + * grouped and displayed similarly to crashes. Keep in mind that this method can be expensive. + * The total number of NSErrors that can be recorded during your app's life-cycle is limited by a + * fixed-size circular buffer. If the buffer is overrun, the oldest data is dropped. Errors are + * relayed to Crashlytics on a subsequent launch of your application. + * + * @param error Non-fatal error to be recorded + */ +- (void)recordError:(NSError *)error NS_SWIFT_NAME(record(error:)); + +/** + * Returns whether the app crashed during the previous execution. + */ +- (BOOL)didCrashDuringPreviousExecution; + +/** + * Enables/disables automatic data collection. + * + * Calling this method overrides both the FirebaseCrashlyticsCollectionEnabled flag in your + * App's Info.plist and FIRApp's isDataCollectionDefaultEnabled flag. + * + * When you set a value for this method, it persists across runs of the app. + * + * The value does not apply until the next run of the app. If you want to disable data + * collection without rebooting, add the FirebaseCrashlyticsCollectionEnabled flag to your app's + * Info.plist. + * * + * @param enabled Determines whether automatic data collection is enabled + */ +- (void)setCrashlyticsCollectionEnabled:(BOOL)enabled; + +/** + * Indicates whether or not automatic data collection is enabled + * + * This method uses three ways to decide whether automatic data collection is enabled, + * in order of priority: + * - If setCrashlyticsCollectionEnabled iscalled with a value, use it + * - If the FirebaseCrashlyticsCollectionEnabled key is in your app's Info.plist, use it + * - Otherwise, use the default isDataCollectionDefaultEnabled in FIRApp + */ +- (BOOL)isCrashlyticsCollectionEnabled; + +/** + * Determines whether there are any unsent crash reports cached on the device, then calls the given + * callback. + * + * The callback only executes if automatic data collection is disabled. You can use + * the callback to get one-time consent from a user upon a crash, and then call + * sendUnsentReports or deleteUnsentReports, depending on whether or not the user gives consent. + * + * Disable automatic collection by: + * - Adding the FirebaseCrashlyticsCollectionEnabled: NO key to your App's Info.plist + * - Calling [[FIRCrashlytics crashlytics] setCrashlyticsCollectionEnabled:NO] in your app + * - Setting FIRApp's isDataCollectionDefaultEnabled to NO + * + * @param completion The callback that's executed once Crashlytics finishes checking for unsent + * reports. The callback is set to YES if there are unsent reports on disk. + */ +- (void)checkForUnsentReportsWithCompletion:(void (^)(BOOL))completion + NS_SWIFT_NAME(checkForUnsentReports(completion:)); + +/** + * Enqueues any unsent reports on the device to upload to Crashlytics. + * + * This method only applies if automatic data collection is disabled. + * + * When automatic data collection is enabled, Crashlytics automatically uploads and deletes reports + * at startup, so this method is ignored. + */ +- (void)sendUnsentReports; + +/** + * Deletes any unsent reports on the device. + * + * This method only applies if automatic data collection is disabled. + */ +- (void)deleteUnsentReports; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Public/FirebaseCrashlytics.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Public/FirebaseCrashlytics.h new file mode 100644 index 0000000..d85680b --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Public/FirebaseCrashlytics.h @@ -0,0 +1,17 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRCrashlytics.h" diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/FIRCLSSettingsOnboardingManager.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/FIRCLSSettingsOnboardingManager.h new file mode 100644 index 0000000..252414f --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/FIRCLSSettingsOnboardingManager.h @@ -0,0 +1,58 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +#if __has_include() +#import +#else +#import "FBLPromises.h" +#endif + +@class FIRCLSApplicationIdentifierModel; +@class FIRCLSDataCollectionToken; +@class FIRCLSFileManager; +@class FIRCLSInstallIdentifierModel; +@class FIRCLSSettings; + +NS_ASSUME_NONNULL_BEGIN + +/** + * Use this class to retrieve remote settings for the application from crashlytics backend, and + * onboard the application on the server. + */ +@interface FIRCLSSettingsOnboardingManager : NSObject + +/** + * Designated Initializer. + */ +- (instancetype)initWithAppIDModel:(FIRCLSApplicationIdentifierModel *)appIDModel + installIDModel:(FIRCLSInstallIdentifierModel *)installIDModel + settings:(FIRCLSSettings *)settings + fileManager:(FIRCLSFileManager *)fileManager + googleAppID:(NSString *)googleAppID NS_DESIGNATED_INITIALIZER; +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +/** + * This method kicks off downloading settings and onboarding for the app. + * @param googleAppID (required) GMP id for the app. + * @param token (required) Data collection token signifying we can make network calls + */ +- (void)beginSettingsAndOnboardingWithGoogleAppId:(NSString *)googleAppID + token:(FIRCLSDataCollectionToken *)token; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/FIRCLSSettingsOnboardingManager.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/FIRCLSSettingsOnboardingManager.m new file mode 100644 index 0000000..d0f017e --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/FIRCLSSettingsOnboardingManager.m @@ -0,0 +1,229 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSSettingsOnboardingManager.h" + +#import "FIRCLSApplicationIdentifierModel.h" +#import "FIRCLSConstants.h" +#import "FIRCLSDataCollectionToken.h" +#import "FIRCLSDefines.h" +#import "FIRCLSDownloadAndSaveSettingsOperation.h" +#import "FIRCLSFABNetworkClient.h" +#import "FIRCLSFileManager.h" +#import "FIRCLSInstallIdentifierModel.h" +#import "FIRCLSLogger.h" +#import "FIRCLSOnboardingOperation.h" +#import "FIRCLSSettings.h" +#import "FIRCLSURLBuilder.h" + +@interface FIRCLSSettingsOnboardingManager () + +@property(nonatomic, strong) FIRCLSApplicationIdentifierModel *appIDModel; +@property(nonatomic, strong) FIRCLSInstallIdentifierModel *installIDModel; + +@property(nonatomic, strong) FIRCLSSettings *settings; + +@property(nonatomic, nullable, strong) FIRCLSOnboardingOperation *onboardingOperation; +@property(nonatomic, strong) FIRCLSFileManager *fileManager; + +// set to YES once onboarding call has been made. +@property(nonatomic) BOOL hasAttemptedAppConfigure; + +@property(nonatomic) NSDictionary *configuration; +@property(nonatomic) NSDictionary *defaultConfiguration; +@property(nonatomic, copy) NSString *googleAppID; +@property(nonatomic, copy) NSDictionary *kitVersionsByKitBundleIdentifier; +@property(nonatomic, readonly) FIRCLSFABNetworkClient *networkClient; + +@end + +@implementation FIRCLSSettingsOnboardingManager + +- (instancetype)initWithAppIDModel:(FIRCLSApplicationIdentifierModel *)appIDModel + installIDModel:(FIRCLSInstallIdentifierModel *)installIDModel + settings:(FIRCLSSettings *)settings + fileManager:(FIRCLSFileManager *)fileManager + googleAppID:(NSString *)googleAppID { + self = [super init]; + if (!self) { + return nil; + } + + _appIDModel = appIDModel; + _installIDModel = installIDModel; + _settings = settings; + _fileManager = fileManager; + _googleAppID = googleAppID; + + _networkClient = [[FIRCLSFABNetworkClient alloc] initWithQueue:nil]; + + return self; +} + +- (void)beginSettingsAndOnboardingWithGoogleAppId:(NSString *)googleAppID + token:(FIRCLSDataCollectionToken *)token { + NSParameterAssert(googleAppID); + + self.googleAppID = googleAppID; + + // This map helps us determine what versions of the SDK + // are out there. We're keeping the Fabric value in there for + // backwards compatibility + // TODO(b/141747635) + self.kitVersionsByKitBundleIdentifier = @{ + FIRCLSApplicationGetSDKBundleID() : @CLS_SDK_DISPLAY_VERSION, + }; + + [self beginSettingsDownload:token]; +} + +#pragma mark Helper methods + +/** + * Makes a settings download request. If the request fails, the error is handled silently(with a log + * statement). If the server response indicates onboarding is needed, an onboarding request is sent + * to the server. If the onboarding request fails, the error is handled silently(with a log + * statement). + */ +- (void)beginSettingsDownload:(FIRCLSDataCollectionToken *)token { + FIRCLSDownloadAndSaveSettingsOperation *operation = nil; + operation = [[FIRCLSDownloadAndSaveSettingsOperation alloc] + initWithGoogleAppID:self.googleAppID + delegate:self + settingsURL:self.settingsURL + settingsDirectoryPath:self.fileManager.settingsDirectoryPath + settingsFilePath:self.fileManager.settingsFilePath + installIDModel:self.installIDModel + networkClient:self.networkClient + token:token]; + + [operation startWithToken:token]; +} + +- (void)beginOnboarding:(BOOL)appCreate + endpointString:(NSString *)endpoint + token:(FIRCLSDataCollectionToken *)token { + [self.onboardingOperation cancel]; + + self.onboardingOperation = + [[FIRCLSOnboardingOperation alloc] initWithDelegate:self + shouldCreate:appCreate + googleAppID:self.googleAppID + kitVersionsByKitBundleIdentifier:self.kitVersionsByKitBundleIdentifier + appIdentifierModel:self.appIDModel + endpointString:endpoint + networkClient:self.networkClient + token:token + settings:self.settings]; + + [self.onboardingOperation startWithToken:token]; +} + +- (void)finishNetworkingSession { + [self.networkClient invalidateAndCancel]; +} + +#pragma mark FIRCLSOnboardingOperationDelegate methods + +- (void)onboardingOperation:(FIRCLSOnboardingOperation *)operation + didCompleteAppCreationWithError:(nullable NSError *)error { + if (error) { + FIRCLSErrorLog(@"Unable to complete application configure: %@", error); + [self finishNetworkingSession]; + return; + } + self.onboardingOperation = nil; + FIRCLSDebugLog(@"Completed configure"); + + // now, go get settings, as they can change (and it completes the onboarding process) + [self beginSettingsDownload:operation.token]; +} + +- (void)onboardingOperation:(FIRCLSOnboardingOperation *)operation + didCompleteAppUpdateWithError:(nullable NSError *)error { + [self finishNetworkingSession]; + if (error) { + FIRCLSErrorLog(@"Unable to complete application update: %@", error); + return; + } + self.onboardingOperation = nil; + FIRCLSDebugLog(@"Completed application update"); +} + +#pragma mark FIRCLSDownloadAndSaveSettingsOperationDelegate methods + +- (void)operation:(FIRCLSDownloadAndSaveSettingsOperation *)operation + didDownloadAndSaveSettingsWithError:(nullable NSError *)error { + if (error) { + FIRCLSErrorLog(@"Failed to download settings %@", error); + [self finishNetworkingSession]; + return; + } + + FIRCLSDebugLog(@"Settings downloaded successfully"); + + NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate]; + [self.settings cacheSettingsWithGoogleAppID:self.googleAppID currentTimestamp:currentTimestamp]; + + // only try this once + if (self.hasAttemptedAppConfigure) { + FIRCLSDebugLog(@"App already onboarded in this run of the app"); + [self finishNetworkingSession]; + return; + } + + // Onboarding is still needed in Firebase, here are the backend app states - + // 1. When the app is created in the Firebase console, app state: built (client settings call + // returns app status: new) + // 2. After onboarding call is made, app state: build_configured + // 3. Another settings call is triggered after onboarding, app state: activated + if ([self.settings appNeedsOnboarding]) { + FIRCLSDebugLog(@"Starting onboarding with app create"); + self.hasAttemptedAppConfigure = YES; + [self beginOnboarding:YES endpointString:FIRCLSConfigureEndpoint token:operation.token]; + return; + } + + if ([self.settings appUpdateRequired]) { + FIRCLSDebugLog(@"Starting onboarding with app update"); + self.hasAttemptedAppConfigure = YES; + [self beginOnboarding:NO endpointString:FIRCLSConfigureEndpoint token:operation.token]; + return; + } + + // we're all set! + [self finishNetworkingSession]; +} + +- (NSURL *)settingsURL { + // GET + // /spi/v2/platforms/:platform/apps/:identifier/settings?build_version=1234&display_version=abc&instance=xyz&source=1 + FIRCLSURLBuilder *url = [FIRCLSURLBuilder URLWithBase:FIRCLSSettingsEndpoint]; + + [url appendComponent:@"/spi/v2/platforms/"]; + [url escapeAndAppendComponent:self.appIDModel.platform]; + [url appendComponent:@"/gmp/"]; + [url escapeAndAppendComponent:self.googleAppID]; + [url appendComponent:@"/settings"]; + + [url appendValue:self.appIDModel.buildVersion forQueryParam:@"build_version"]; + [url appendValue:self.appIDModel.displayVersion forQueryParam:@"display_version"]; + [url appendValue:self.appIDModel.buildInstanceID forQueryParam:@"instance"]; + [url appendValue:@(self.appIDModel.installSource) forQueryParam:@"source"]; + // TODO: find the right param name for KitVersions and add them here + return url.URL; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/Models/FIRCLSApplicationIdentifierModel.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/Models/FIRCLSApplicationIdentifierModel.h new file mode 100644 index 0000000..b229e69 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/Models/FIRCLSApplicationIdentifierModel.h @@ -0,0 +1,64 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +#import "FIRCLSApplication.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * This class is a model for identifiers related to the application binary. + * It is thread-safe. + */ +@interface FIRCLSApplicationIdentifierModel : NSObject + +@property(nonatomic, readonly, nullable) NSString* bundleID; + +/** + * Returns the user-facing app name + */ +@property(nonatomic, readonly, nullable) NSString* displayName; + +@property(nonatomic, readonly, nullable) NSString* platform; +@property(nonatomic, readonly, nullable) NSString* buildVersion; +@property(nonatomic, readonly, nullable) NSString* displayVersion; +@property(nonatomic, readonly) FIRCLSApplicationInstallationSourceType installSource; + +/** + * A mapping between all supported architectures and their UUIDs + */ +@property(nonatomic, readonly) NSDictionary* architectureUUIDMap; + +/** + * Returns the linked OS SDK + */ +@property(nonatomic, readonly) NSString* builtSDKString; + +/** + * Returns the min supported OS + */ +@property(nonatomic, readonly) NSString* minimumSDKString; + +/** + * The unique identifier for this instance of the version of app running Crashlytics. This is + * computed by hashing the app itself. + * + * On Android, this is called the Build ID + */ +@property(nonatomic, readonly) NSString* buildInstanceID; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/Models/FIRCLSApplicationIdentifierModel.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/Models/FIRCLSApplicationIdentifierModel.m new file mode 100644 index 0000000..81a8631 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/Models/FIRCLSApplicationIdentifierModel.m @@ -0,0 +1,134 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSApplicationIdentifierModel.h" + +#import "FIRCLSApplication.h" +#import "FIRCLSByteUtility.h" +#import "FIRCLSDefines.h" +#import "FIRCLSMachO.h" +#import "FIRCLSUUID.h" + +@interface FIRCLSApplicationIdentifierModel () + +@property(nonatomic, copy, readwrite) NSDictionary *architectureUUIDMap; +@property(nonatomic, copy, readwrite) NSString *buildInstanceID; +@property(nonatomic, readonly) FIRCLSMachOVersion builtSDK; +@property(nonatomic, readonly) FIRCLSMachOVersion minimumSDK; + +@end + +@implementation FIRCLSApplicationIdentifierModel + +- (nullable instancetype)init { + self = [super init]; + if (!self) { + return nil; + } + + if (![self computeExecutableInfo]) { + return nil; + } + + [self computeInstanceIdentifier]; + + return self; +} + +- (NSString *)bundleID { + return FIRCLSApplicationGetBundleIdentifier(); +} + +- (NSString *)displayName { + return FIRCLSApplicationGetName(); +} + +- (NSString *)platform { + return FIRCLSApplicationGetPlatform(); +} + +- (NSString *)buildVersion { + return FIRCLSApplicationGetBundleVersion(); +} + +- (NSString *)displayVersion { + return FIRCLSApplicationGetShortBundleVersion(); +} + +- (FIRCLSApplicationInstallationSourceType)installSource { + return FIRCLSApplicationInstallationSource(); +} + +- (NSString *)builtSDKString { + return FIRCLSMachOFormatVersion(&_builtSDK); +} + +- (NSString *)minimumSDKString { + return FIRCLSMachOFormatVersion(&_minimumSDK); +} + +- (BOOL)computeExecutableInfo { + struct FIRCLSMachOFile file; + + if (!FIRCLSMachOFileInitWithCurrent(&file)) { + return NO; + } + + NSMutableDictionary *executables = [NSMutableDictionary dictionary]; + + FIRCLSMachOFileEnumerateSlices(&file, ^(FIRCLSMachOSliceRef fileSlice) { + NSString *arch; + + arch = [NSString stringWithUTF8String:FIRCLSMachOSliceGetArchitectureName(fileSlice)]; + + FIRCLSMachOSliceEnumerateLoadCommands( + fileSlice, ^(uint32_t type, uint32_t size, const struct load_command *cmd) { + if (type == LC_UUID) { + const uint8_t *uuid; + + uuid = FIRCLSMachOGetUUID(cmd); + + [executables setObject:FIRCLSUUIDToNSString(uuid) forKey:arch]; + } else if (type == LC_VERSION_MIN_MACOSX || type == LC_VERSION_MIN_IPHONEOS) { + self->_minimumSDK = FIRCLSMachOGetMinimumOSVersion(cmd); + self->_builtSDK = FIRCLSMachOGetLinkedSDKVersion(cmd); + } + }); + }); + + FIRCLSMachOFileDestroy(&file); + + _architectureUUIDMap = executables; + + return YES; +} + +- (void)computeInstanceIdentifier { + // build up the components of the instance identifier + NSMutableString *string = [NSMutableString string]; + + // first, the uuids, sorted by architecture name + for (NSString *key in + [[_architectureUUIDMap allKeys] sortedArrayUsingSelector:@selector(compare:)]) { + [string appendString:[self.architectureUUIDMap objectForKey:key]]; + } + + // TODO: the instance identifier calculation needs to match Beta's expectation. So, we have to + // continue generating a less-correct value for now. One day, we should encorporate a hash of the + // Info.plist and icon data. + + _buildInstanceID = FIRCLSHashNSData([string dataUsingEncoding:NSUTF8StringEncoding]); +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/Operations/FIRCLSDownloadAndSaveSettingsOperation.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/Operations/FIRCLSDownloadAndSaveSettingsOperation.h new file mode 100644 index 0000000..318089f --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/Operations/FIRCLSDownloadAndSaveSettingsOperation.h @@ -0,0 +1,80 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import +#import "FIRCLSNetworkOperation.h" + +@class FIRCLSDownloadAndSaveSettingsOperation; +@class FIRCLSFABNetworkClient; +@class FIRCLSInstallIdentifierModel; + +NS_ASSUME_NONNULL_BEGIN + +/** + * This is the protocol that a delegate of FIRCLSDownloadAndSaveSettingsOperation needs to follow. + */ +@protocol FIRCLSDownloadAndSaveSettingsOperationDelegate + +@required + +/** + * Method that is called when settings have been downloaded and saved, or an error has occurred + * during the operation. This method may be called on an arbitrary background thread. + */ +- (void)operation:(FIRCLSDownloadAndSaveSettingsOperation *)operation + didDownloadAndSaveSettingsWithError:(nullable NSError *)error; + +@end + +/** + * This operation downloads settings from the backend servers, and saves them in file on disk. + */ +@interface FIRCLSDownloadAndSaveSettingsOperation : FIRCLSNetworkOperation + +- (instancetype)initWithGoogleAppID:(NSString *)googleAppID + token:(FIRCLSDataCollectionToken *)token NS_UNAVAILABLE; + +/** + * @param googleAppID must NOT be nil + * @param delegate gets a callback after settings have been downloaded or an error occurs. + * @param settingsURL must NOT be nil. This is the URL to which a download request is made. + * @param settingsDirectoryPath must NOT be nil. This is the directory on disk where the settings + * are persisted. + * @param settingsFilePath must NOT be nil. It is the full file path(including file name) in which + * settings will be persisted on disk. + * @param installIDModel must NOT be nil. This value is sent back to the backend to uniquely + * identify the app install. + */ +- (instancetype)initWithGoogleAppID:(NSString *)googleAppID + delegate:(id)delegate + settingsURL:(NSURL *)settingsURL + settingsDirectoryPath:(NSString *)settingsDirectoryPath + settingsFilePath:(NSString *)settingsFilePath + installIDModel:(FIRCLSInstallIdentifierModel *)installIDModel + networkClient:(FIRCLSFABNetworkClient *)networkClient + token:(FIRCLSDataCollectionToken *)token NS_DESIGNATED_INITIALIZER; + +/** + * Delegate of this operation. + */ +@property(nonatomic, readonly, weak) id delegate; + +/** + * When an error occurs during this operation, it is made available in this property. + */ +@property(nonatomic, readonly) NSError *error; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/Operations/FIRCLSDownloadAndSaveSettingsOperation.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/Operations/FIRCLSDownloadAndSaveSettingsOperation.m new file mode 100644 index 0000000..9d32f91 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/Operations/FIRCLSDownloadAndSaveSettingsOperation.m @@ -0,0 +1,132 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSDownloadAndSaveSettingsOperation.h" + +#import "FIRCLSConstants.h" +#import "FIRCLSFABHost.h" +#import "FIRCLSFABNetworkClient.h" +#import "FIRCLSInstallIdentifierModel.h" +#import "FIRCLSLogger.h" + +@interface FIRCLSDownloadAndSaveSettingsOperation () + +/** + * Method called to fetch the URL from where settings have to be downloaded. + */ +@property(readonly, nonatomic) NSURL *settingsURL; +/** + * File manager which will be used to save settings on disk. + */ +@property(readonly, nonatomic) NSFileManager *fileManager; + +/** + * Directory path on which settings file will be saved + */ +@property(readonly, nonatomic) NSString *settingsDirectoryPath; +/** + * Complete file path on which settings file will be saved + */ +@property(readonly, nonatomic) NSString *settingsFilePath; +/** + * App install identifier. + */ +@property(strong, readonly, nonatomic) FIRCLSInstallIdentifierModel *installIDModel; + +@property(weak, readonly, nonatomic) FIRCLSFABNetworkClient *networkClient; + +@end + +@implementation FIRCLSDownloadAndSaveSettingsOperation + +- (instancetype)initWithGoogleAppID:(NSString *)googleAppID + delegate:(id)delegate + settingsURL:(NSURL *)settingsURL + settingsDirectoryPath:(NSString *)settingsDirectoryPath + settingsFilePath:(NSString *)settingsFilePath + installIDModel:(FIRCLSInstallIdentifierModel *)installIDModel + networkClient:(FIRCLSFABNetworkClient *)networkClient + token:(FIRCLSDataCollectionToken *)token { + NSParameterAssert(settingsURL); + NSParameterAssert(settingsDirectoryPath); + NSParameterAssert(settingsFilePath); + NSParameterAssert(installIDModel); + + self = [super initWithGoogleAppID:googleAppID token:token]; + if (self) { + _delegate = delegate; + _settingsURL = settingsURL.copy; + _settingsDirectoryPath = settingsDirectoryPath.copy; + _settingsFilePath = settingsFilePath.copy; + _fileManager = [[NSFileManager alloc] init]; + _installIDModel = installIDModel; + _networkClient = networkClient; + } + return self; +} + +- (NSMutableURLRequest *)mutableRequestWithDefaultHTTPHeaderFieldsAndTimeoutForURL:(NSURL *)url { + NSMutableURLRequest *request = + [super mutableRequestWithDefaultHTTPHeaderFieldsAndTimeoutForURL:url]; + request.HTTPMethod = @"GET"; + [request setValue:@"application/json" forHTTPHeaderField:@"Accept"]; + [request setValue:self.installIDModel.installID + forHTTPHeaderField:@"X-Crashlytics-Installation-ID"]; + [request setValue:FIRCLSHostModelInfo() forHTTPHeaderField:@"X-Crashlytics-Device-Model"]; + [request setValue:FIRCLSHostOSBuildVersion() + forHTTPHeaderField:@"X-Crashlytics-OS-Build-Version"]; + [request setValue:FIRCLSHostOSDisplayVersion() + forHTTPHeaderField:@"X-Crashlytics-OS-Display-Version"]; + [request setValue:FIRCLSVersion forHTTPHeaderField:@"X-Crashlytics-API-Client-Version"]; + + return request; +} + +- (void)main { + NSMutableURLRequest *request = + [self mutableRequestWithDefaultHTTPHeaderFieldsAndTimeoutForURL:self.settingsURL]; + + [self.networkClient + startDownloadTaskWithRequest:request + retryLimit:1 + completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) { + if (error) { + self->_error = error; + [self.delegate operation:self didDownloadAndSaveSettingsWithError:self.error]; + [self finishWithError:error]; + return; + } + // This move needs to happen synchronously, because after this method completes, + // the file will not be available. + NSError *moveError = nil; + + // this removal will frequently fail, and we don't want the warning + [self.fileManager removeItemAtPath:self.settingsDirectoryPath error:nil]; + + [self.fileManager createDirectoryAtPath:self.settingsDirectoryPath + withIntermediateDirectories:YES + attributes:nil + error:nil]; + if (![self.fileManager moveItemAtPath:location.path + toPath:self.settingsFilePath + error:&moveError]) { + FIRCLSErrorLog(@"Unable to complete settings download %@", moveError); + self->_error = moveError; + } + [self.delegate operation:self didDownloadAndSaveSettingsWithError:self.error]; + [self finishWithError:self.error]; + }]; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/Operations/FIRCLSNetworkOperation.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/Operations/FIRCLSNetworkOperation.h new file mode 100644 index 0000000..a449903 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/Operations/FIRCLSNetworkOperation.h @@ -0,0 +1,55 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import +#import "FIRCLSOperation.h" + +NS_ASSUME_NONNULL_BEGIN + +@class FIRCLSDataCollectionToken; +@class FIRCLSSettings; + +/** + * This is a base class for network based operations. + */ +@interface FIRCLSNetworkOperation : FIRCLSFABAsyncOperation + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +/** + * Designated initializer. All parameters are mandatory and must not be nil. + */ +- (instancetype)initWithGoogleAppID:(NSString *)googleAppID + token:(FIRCLSDataCollectionToken *)token NS_DESIGNATED_INITIALIZER; + +- (void)start NS_UNAVAILABLE; +- (void)startWithToken:(FIRCLSDataCollectionToken *)token; + +/** + * Creates a mutable request for posting to Crashlytics backend with a default timeout. + */ +- (NSMutableURLRequest *)mutableRequestWithDefaultHTTPHeaderFieldsAndTimeoutForURL:(NSURL *)url; + +/** + * Creates a mutable request for posting to Crashlytics backend with given timeout. + */ +- (NSMutableURLRequest *)mutableRequestWithDefaultHTTPHeadersForURL:(NSURL *)url + timeout:(NSTimeInterval)timeout; + +@property(nonatomic, strong, readonly) FIRCLSDataCollectionToken *token; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/Operations/FIRCLSNetworkOperation.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/Operations/FIRCLSNetworkOperation.m new file mode 100644 index 0000000..52b77c1 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/Operations/FIRCLSNetworkOperation.m @@ -0,0 +1,92 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSNetworkOperation.h" + +#import "FIRCLSApplication.h" +#import "FIRCLSConstants.h" +#import "FIRCLSDataCollectionToken.h" +#import "FIRCLSDefines.h" +#import "FIRCLSLogger.h" + +@interface FIRCLSNetworkOperation () + +@property(nonatomic, strong, readonly) NSString *googleAppID; + +@end + +@implementation FIRCLSNetworkOperation + +- (instancetype)initWithGoogleAppID:(NSString *)googleAppID + token:(FIRCLSDataCollectionToken *)token { + NSParameterAssert(googleAppID); + if (!googleAppID) { + return nil; + } + + self = [super init]; + if (self) { + _googleAppID = googleAppID; + _token = token; + } + return self; +} + +- (void)startWithToken:(FIRCLSDataCollectionToken *)token { + // Settings and Onboarding are considered data collection, so we must only + // call this with a valid token + if (![token isValid]) { + FIRCLSErrorLog(@"Skipping network operation with invalid data collection token"); + return; + } + + [super start]; +} + +- (NSMutableURLRequest *)mutableRequestWithDefaultHTTPHeaderFieldsAndTimeoutForURL:(NSURL *)url { + return [self mutableRequestWithDefaultHTTPHeadersForURL:url timeout:10.0]; +} + +- (NSMutableURLRequest *)mutableRequestWithDefaultHTTPHeadersForURL:(NSURL *)url + timeout:(NSTimeInterval)timeout { + NSMutableURLRequest *request = + [NSMutableURLRequest requestWithURL:url + cachePolicy:NSURLRequestReloadIgnoringLocalCacheData + timeoutInterval:timeout]; + + NSString *localeId = self.localeIdentifier; + + [request setValue:self.userAgentString forHTTPHeaderField:FIRCLSNetworkUserAgent]; + [request setValue:FIRCLSNetworkUTF8 forHTTPHeaderField:FIRCLSNetworkAcceptCharset]; + [request setValue:localeId forHTTPHeaderField:FIRCLSNetworkAcceptLanguage]; + [request setValue:localeId forHTTPHeaderField:FIRCLSNetworkContentLanguage]; + [request setValue:FIRCLSDeveloperToken forHTTPHeaderField:FIRCLSNetworkCrashlyticsDeveloperToken]; + [request setValue:FIRCLSApplicationGetSDKBundleID() + forHTTPHeaderField:FIRCLSNetworkCrashlyticsAPIClientId]; + [request setValue:FIRCLSVersion + forHTTPHeaderField:FIRCLSNetworkCrashlyticsAPIClientDisplayVersion]; + [request setValue:self.googleAppID forHTTPHeaderField:FIRCLSNetworkCrashlyticsGoogleAppId]; + + return request; +} + +- (NSString *)userAgentString { + return [NSString stringWithFormat:@"%@/%@", FIRCLSApplicationGetSDKBundleID(), FIRCLSVersion]; +} + +- (NSString *)localeIdentifier { + return NSLocale.currentLocale.localeIdentifier; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/Operations/FIRCLSOnboardingOperation.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/Operations/FIRCLSOnboardingOperation.h new file mode 100644 index 0000000..14d56ed --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/Operations/FIRCLSOnboardingOperation.h @@ -0,0 +1,84 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import +#import "FIRCLSApplicationIdentifierModel.h" +#import "FIRCLSDataCollectionToken.h" +#import "FIRCLSNetworkOperation.h" + +NS_ASSUME_NONNULL_BEGIN + +extern NSString *const FIRCLSOnboardingErrorDomain; + +@class FIRCLSOnboardingOperation; +@class FIRCLSFABNetworkClient; +@class FIRCLSSettings; + +/** + * This is the protocol that a delegate of FIRCLSOnboardingOperation should follow. + */ +@protocol FIRCLSOnboardingOperationDelegate +@required + +/** + * This callback is for the delegate to know that app update has completed with/without an error. + */ +- (void)onboardingOperation:(FIRCLSOnboardingOperation *)operation + didCompleteAppUpdateWithError:(nullable NSError *)error; +/** + * This callback is for the delegate to know that app creation has completed with/without an error. + */ +- (void)onboardingOperation:(FIRCLSOnboardingOperation *)operation + didCompleteAppCreationWithError:(nullable NSError *)error; + +@end + +/** + * This class onboards the app, by making request to the backend servers. + */ +@interface FIRCLSOnboardingOperation : FIRCLSNetworkOperation + +/** + * When an error occurs during this operation, it is made available in this property. + */ +@property(nonatomic, readonly) NSError *error; + +- (instancetype)initWithGoogleAppID:(NSString *)googleAppID + token:(FIRCLSDataCollectionToken *)token NS_UNAVAILABLE; + +/** + * Designated initializer. + * @param delegate may be nil. Gets callbacks when app creation or updation succeeds or gets errored + * out. + * @param googleAppID must NOT be nil. + * @param kitVersionsByKitBundleIdentifier may be nil. Maps Kit bundle identifier to kit version + * being used in the app. + * @param appIdentifierModel must NOT be nil. Used to get information required in the onboarding + * network call. + * @param appEndPoint must NOT be nil. Endpoint which needs to be hit with the onboarding request. + * @param settings which are used to fetch the organization identifier. + */ +- (instancetype)initWithDelegate:(id)delegate + shouldCreate:(BOOL)shouldCreate + googleAppID:(NSString *)googleAppID + kitVersionsByKitBundleIdentifier:(NSDictionary *)kitVersionsByKitBundleIdentifier + appIdentifierModel:(FIRCLSApplicationIdentifierModel *)appIdentifierModel + endpointString:(NSString *)appEndPoint + networkClient:(FIRCLSFABNetworkClient *)networkClient + token:(FIRCLSDataCollectionToken *)token + settings:(FIRCLSSettings *)settings NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/Operations/FIRCLSOnboardingOperation.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/Operations/FIRCLSOnboardingOperation.m new file mode 100644 index 0000000..5f556b7 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Settings/Operations/FIRCLSOnboardingOperation.m @@ -0,0 +1,208 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSOnboardingOperation.h" + +#import "FIRCLSByteUtility.h" +#import "FIRCLSConstants.h" +#import "FIRCLSFABNetworkClient.h" +#import "FIRCLSLogger.h" +#import "FIRCLSMachO.h" +#import "FIRCLSMultipartMimeStreamEncoder.h" +#import "FIRCLSSettings.h" +#import "FIRCLSURLBuilder.h" + +// The SPIv1/v2 onboarding flow looks something like this: +// - get settings +// - settings says we're good, nothing to do +// - settings says update +// - do an update +// - settings says new +// - do a create +// - get settings again (and do *not* take action after that) + +NSString *const FIRCLSOnboardingErrorDomain = @"FIRCLSOnboardingErrorDomain"; + +typedef NS_ENUM(NSInteger, FIRCLSOnboardingError) { + FIRCLSOnboardingErrorMultipartMimeConfiguration +}; + +@interface FIRCLSOnboardingOperation () + +@property(nonatomic) BOOL shouldCreate; +@property(nonatomic, readonly) FIRCLSApplicationIdentifierModel *appIdentifierModel; +@property(nonatomic, readonly) NSString *appEndpoint; +@property(nonatomic, readonly, unsafe_unretained) id delegate; +@property(nonatomic, weak, readonly) FIRCLSFABNetworkClient *networkClient; +@property(nonatomic, readonly) NSDictionary *kitVersionsByKitBundleIdentifier; +@property(nonatomic, readonly) FIRCLSSettings *settings; +@end + +@implementation FIRCLSOnboardingOperation + +#pragma mark lifecycle methods + +- (instancetype)initWithDelegate:(id)delegate + shouldCreate:(BOOL)shouldCreate + googleAppID:(NSString *)googleAppID + kitVersionsByKitBundleIdentifier:(NSDictionary *)kitVersionsByKitBundleIdentifier + appIdentifierModel:(FIRCLSApplicationIdentifierModel *)appIdentifierModel + endpointString:(NSString *)appEndPoint + networkClient:(FIRCLSFABNetworkClient *)networkClient + token:(FIRCLSDataCollectionToken *)token + settings:(FIRCLSSettings *)settings { + NSParameterAssert(appIdentifierModel); + NSParameterAssert(appEndPoint); + + self = [super initWithGoogleAppID:googleAppID token:token]; + if (self) { + _shouldCreate = shouldCreate; + _delegate = delegate; + _appIdentifierModel = appIdentifierModel; + _appEndpoint = appEndPoint; + _networkClient = networkClient; + _kitVersionsByKitBundleIdentifier = kitVersionsByKitBundleIdentifier.copy; + _settings = settings; + } + return self; +} + +- (void)main { + [self beginAppConfigure]; +} + +- (void)beginAppConfigure { + NSOutputStream *stream = [[NSOutputStream alloc] initToMemory]; + NSString *boundary = [FIRCLSMultipartMimeStreamEncoder generateBoundary]; + + FIRCLSMultipartMimeStreamEncoder *encoder = + [FIRCLSMultipartMimeStreamEncoder encoderWithStream:stream andBoundary:boundary]; + if (!encoder) { + FIRCLSErrorLog(@"Configure failed during onboarding"); + [self finishWithError:[self errorForCode:FIRCLSOnboardingErrorMultipartMimeConfiguration + userInfo:@{ + NSLocalizedDescriptionKey : @"Multipart mime encoder was nil" + }]]; + return; + } + + NSString *orgID = [self.settings orgID]; + if (!orgID) { + FIRCLSErrorLog(@"Could not onboard app with missing Organization ID"); + [self finishWithError:[self errorForCode:FIRCLSOnboardingErrorMultipartMimeConfiguration + userInfo:@{ + NSLocalizedDescriptionKey : @"Organization ID was nil" + }]]; + return; + } + + [encoder encode:^{ + [encoder addValue:orgID fieldName:@"org_id"]; + + [encoder addValue:self.settings.fetchedBundleID fieldName:@"app[identifier]"]; + [encoder addValue:self.appIdentifierModel.buildInstanceID + fieldName:@"app[instance_identifier]"]; + [encoder addValue:self.appIdentifierModel.displayName fieldName:@"app[name]"]; + [encoder addValue:self.appIdentifierModel.buildVersion fieldName:@"app[build_version]"]; + [encoder addValue:self.appIdentifierModel.displayVersion fieldName:@"app[display_version]"]; + [encoder addValue:@(self.appIdentifierModel.installSource) fieldName:@"app[source]"]; + [encoder addValue:self.appIdentifierModel.minimumSDKString + fieldName:@"app[minimum_sdk_version]"]; + [encoder addValue:self.appIdentifierModel.builtSDKString fieldName:@"app[built_sdk_version]"]; + [self.kitVersionsByKitBundleIdentifier + enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + NSString *formKey = [NSString stringWithFormat:@"%@[%@]", @"app[build][libraries]", key]; + [encoder addValue:obj fieldName:formKey]; + }]; + + [self.appIdentifierModel.architectureUUIDMap + enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + [encoder addValue:key fieldName:@"app[slices][][arch]"]; + [encoder addValue:obj fieldName:@"app[slices][][uuid]"]; + }]; + }]; + + NSMutableURLRequest *request = [self onboardingRequestForAppCreate:self.shouldCreate]; + [request setValue:orgID forHTTPHeaderField:FIRCLSNetworkCrashlyticsOrgId]; + + [request setValue:encoder.contentTypeHTTPHeaderValue forHTTPHeaderField:@"Content-Type"]; + [request setValue:encoder.contentLengthHTTPHeaderValue forHTTPHeaderField:@"Content-Length"]; + [request setHTTPBody:[stream propertyForKey:NSStreamDataWrittenToMemoryStreamKey]]; + + // Retry only when onboarding an app for the first time, otherwise it'll overwhelm our servers + NSUInteger retryLimit = self.shouldCreate ? 10 : 1; + + [self.networkClient + startDataTaskWithRequest:request + retryLimit:retryLimit + completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + self->_error = error; + if (!self.shouldCreate) { + [self.delegate onboardingOperation:self didCompleteAppUpdateWithError:error]; + } else { + [self.delegate onboardingOperation:self didCompleteAppCreationWithError:error]; + } + [self finishWithError:error]; + }]; +} + +#pragma mark private methods + +- (NSError *)errorForCode:(NSUInteger)code userInfo:(NSDictionary *)userInfo { + return [NSError errorWithDomain:FIRCLSOnboardingErrorDomain code:code userInfo:userInfo]; +} + +- (NSURL *)appCreateURL { + // https://api.crashlytics.com/spi/v1/platforms/mac/apps/com.crashlytics.mac + + FIRCLSURLBuilder *url = [FIRCLSURLBuilder URLWithBase:self.appEndpoint]; + + [url appendComponent:@"/spi/v1/platforms/"]; + [url escapeAndAppendComponent:self.appIdentifierModel.platform]; + [url appendComponent:@"/apps"]; + + return url.URL; +} + +- (NSURL *)appUpdateURL { + // https://api.crashlytics.com/spi/v1/platforms/mac/apps/com.crashlytics.mac + + FIRCLSURLBuilder *url = [FIRCLSURLBuilder URLWithBase:[self appEndpoint]]; + + [url appendComponent:@"/spi/v1/platforms/"]; + [url escapeAndAppendComponent:self.appIdentifierModel.platform]; + [url appendComponent:@"/apps/"]; + [url escapeAndAppendComponent:self.settings.fetchedBundleID]; + + return url.URL; +} + +- (NSMutableURLRequest *)onboardingRequestForAppCreate:(BOOL)shouldCreate { + const NSTimeInterval timeout = 10.0; + NSURL *url = nil; + NSString *httpVerb = nil; + if (shouldCreate) { + httpVerb = @"POST"; + url = self.appCreateURL; + } else { + httpVerb = @"PUT"; + url = self.appUpdateURL; + } + NSMutableURLRequest *request = [self mutableRequestWithDefaultHTTPHeadersForURL:url + timeout:timeout]; + request.HTTPMethod = httpVerb; + return request; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Compact/FIRCLSCompactUnwind.c b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Compact/FIRCLSCompactUnwind.c new file mode 100644 index 0000000..1875f98 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Compact/FIRCLSCompactUnwind.c @@ -0,0 +1,404 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FIRCLSCompactUnwind_Private.h" +#include "FIRCLSDataParsing.h" +#include "FIRCLSDefines.h" +#include "FIRCLSDwarfUnwind.h" +#include "FIRCLSFeatures.h" +#include "FIRCLSUnwind.h" +#include "FIRCLSUtility.h" + +#include + +#if CLS_COMPACT_UNWINDING_SUPPORTED + +#pragma mark Parsing +bool FIRCLSCompactUnwindInit(FIRCLSCompactUnwindContext* context, + const void* unwindInfo, + const void* ehFrame, + uintptr_t loadAddress) { + if (!FIRCLSIsValidPointer(context)) { + FIRCLSSDKLog("Error: invalid context passed to compact unwind init"); + return false; + } + if (!FIRCLSIsValidPointer(unwindInfo)) { + FIRCLSSDKLog("Error: invalid unwind info passed to compact unwind init"); + return false; + } + if (!FIRCLSIsValidPointer(loadAddress)) { + FIRCLSSDKLog("Error: invalid load address passed to compact unwind init"); + return false; + } + + memset(context, 0, sizeof(FIRCLSCompactUnwindContext)); + + if (!FIRCLSReadMemory((vm_address_t)unwindInfo, &context->unwindHeader, + sizeof(struct unwind_info_section_header))) { + FIRCLSSDKLog("Error: could not read memory contents of unwindInfo\n"); + return false; + } + + if (context->unwindHeader.version != UNWIND_SECTION_VERSION) { + FIRCLSSDKLog("Error: bad unwind_info structure version (%d != %d)\n", + context->unwindHeader.version, UNWIND_SECTION_VERSION); + return false; + } + + // copy in the values + context->unwindInfo = unwindInfo; + context->ehFrame = ehFrame; + context->loadAddress = loadAddress; + + return true; +} + +void* FIRCLSCompactUnwindGetIndexData(FIRCLSCompactUnwindContext* context) { + return (void*)((uintptr_t)context->unwindInfo + + (uintptr_t)context->unwindHeader.indexSectionOffset); +} + +compact_unwind_encoding_t* FIRCLSCompactUnwindGetCommonEncodings( + FIRCLSCompactUnwindContext* context) { + return (compact_unwind_encoding_t*)((uintptr_t)context->unwindInfo + + (uintptr_t) + context->unwindHeader.commonEncodingsArraySectionOffset); +} + +void* FIRCLSCompactUnwindGetSecondLevelData(FIRCLSCompactUnwindContext* context) { + return (void*)((uintptr_t)context->unwindInfo + + context->indexHeader.secondLevelPagesSectionOffset); +} + +uintptr_t FIRCLSCompactUnwindGetIndexFunctionOffset(FIRCLSCompactUnwindContext* context) { + return context->loadAddress + context->indexHeader.functionOffset; +} +uintptr_t FIRCLSCompactUnwindGetTargetAddress(FIRCLSCompactUnwindContext* context, uintptr_t pc) { + uintptr_t offset = FIRCLSCompactUnwindGetIndexFunctionOffset(context); + + if (pc <= offset) { + FIRCLSSDKLog("Error: PC is invalid\n"); + return 0; + } + + return pc - offset; +} + +#pragma mark - Parsing and Lookup +bool FIRCLSCompactUnwindLookupFirstLevel(FIRCLSCompactUnwindContext* context, uintptr_t address) { + if (!context) { + return false; + } + + // In practice, it appears that there always one more first level entry + // than required. This actually makes sense, since we have to use this + // info to check if we are in range. This implies there must be + // at least 2 indices at a minimum. + + uint32_t indexCount = context->unwindHeader.indexCount; + if (indexCount < 2) { + return false; + } + + // make sure our address is valid + if (address < context->loadAddress) { + return false; + } + + struct unwind_info_section_header_index_entry* indexEntries = + FIRCLSCompactUnwindGetIndexData(context); + if (!indexEntries) { + return false; + } + + address -= context->loadAddress; // search relative to zero + + // minus one because of the extra entry - see comment above + for (uint32_t index = 0; index < indexCount - 1; ++index) { + uint32_t value = indexEntries[index].functionOffset; + uint32_t nextValue = indexEntries[index + 1].functionOffset; + + if (address >= value && address < nextValue) { + context->firstLevelNextFunctionOffset = nextValue; + context->indexHeader = indexEntries[index]; + return true; + } + } + + return false; +} + +uint32_t FIRCLSCompactUnwindGetSecondLevelPageKind(FIRCLSCompactUnwindContext* context) { + if (!context) { + return 0; + } + + return *(uint32_t*)FIRCLSCompactUnwindGetSecondLevelData(context); +} + +bool FIRCLSCompactUnwindLookupSecondLevelRegular(FIRCLSCompactUnwindContext* context, + uintptr_t pc, + FIRCLSCompactUnwindResult* result) { + FIRCLSSDKLog("Encountered a regular second-level page\n"); + return false; +} + +// this only works for compressed entries right now +bool FIRCLSCompactUnwindBinarySearchSecondLevel(uintptr_t address, + uint32_t* index, + uint16_t entryCount, + uint32_t* entryArray) { + if (!index || !entryArray) { + return false; + } + + if (entryCount == 0) { + return false; + } + + if (address == 0) { + return false; + } + + uint32_t highIndex = entryCount; + *index = 0; + + while (*index < highIndex) { + uint32_t midIndex = (*index + highIndex) / 2; + + // FIRCLSSDKLog("%u %u %u\n", *index, midIndex, highIndex); + + uintptr_t value = UNWIND_INFO_COMPRESSED_ENTRY_FUNC_OFFSET(entryArray[midIndex]); + + if (value > address) { + if (highIndex == midIndex) { + return false; + } + + highIndex = midIndex; + continue; + } + + *index = midIndex; + + // are we at the end of the array? + if (midIndex == entryCount - 1) { + return false; + } + + uintptr_t nextValue = UNWIND_INFO_COMPRESSED_ENTRY_FUNC_OFFSET(entryArray[midIndex + 1]); + if (nextValue > address) { + // we've found it + break; + } + + *index += 1; + } + + // check to make sure we're still within bounds + return *index < entryCount; +} + +bool FIRCLSCompactUnwindLookupSecondLevelCompressed(FIRCLSCompactUnwindContext* context, + uintptr_t pc, + FIRCLSCompactUnwindResult* result) { + if (!context || !result) { + return false; + } + + void* ptr = FIRCLSCompactUnwindGetSecondLevelData(context); + + if (!ptr) { + return false; + } + + memset(result, 0, sizeof(FIRCLSCompactUnwindResult)); + + struct unwind_info_compressed_second_level_page_header* header = + (struct unwind_info_compressed_second_level_page_header*)ptr; + + // adjust address + uintptr_t targetAddress = FIRCLSCompactUnwindGetTargetAddress(context, pc); + + uint32_t* entryArray = ptr + header->entryPageOffset; + + uint32_t index = 0; + + if (!FIRCLSCompactUnwindBinarySearchSecondLevel(targetAddress, &index, header->entryCount, + entryArray)) { + FIRCLSSDKLogInfo("Unable to find PC in second level\n"); + return false; + } + + uint32_t entry = entryArray[index]; + + // Computing the fuction start address is easy + result->functionStart = UNWIND_INFO_COMPRESSED_ENTRY_FUNC_OFFSET(entry) + + FIRCLSCompactUnwindGetIndexFunctionOffset(context); + + // Computing the end is more complex, because we could be on the last entry. In that case, we + // cannot use the next value as the end. + result->functionEnd = context->loadAddress; + if (index < header->entryCount - 1) { + result->functionEnd += UNWIND_INFO_COMPRESSED_ENTRY_FUNC_OFFSET(entryArray[index + 1]) + + context->indexHeader.functionOffset; + } else { + result->functionEnd += context->firstLevelNextFunctionOffset; + } + + // FIRCLSSDKLog("Located %lx => %lx %lx\n", pc, result->functionStart, result->functionEnd); + + if ((pc < result->functionStart) || (pc >= result->functionEnd)) { + FIRCLSSDKLog("PC does not match computed function range\n"); + return false; + } + + uint32_t encodingIndex = UNWIND_INFO_COMPRESSED_ENTRY_ENCODING_INDEX(entry); + + // encoding could be in the common array + if (encodingIndex < context->unwindHeader.commonEncodingsArrayCount) { + result->encoding = FIRCLSCompactUnwindGetCommonEncodings(context)[encodingIndex]; + + // FIRCLSSDKLog("Entry has common encoding: 0x%x\n", result->encoding); + } else { + encodingIndex = encodingIndex - context->unwindHeader.commonEncodingsArrayCount; + + compact_unwind_encoding_t* encodings = ptr + header->encodingsPageOffset; + + result->encoding = encodings[encodingIndex]; + + // FIRCLSSDKLog("Entry has compressed encoding: 0x%x\n", result->encoding); + } + + if (result->encoding == 0) { + FIRCLSSDKLogInfo("Entry has has no unwind info\n"); + return false; + } + + return true; +} + +bool FIRCLSCompactUnwindLookupSecondLevel(FIRCLSCompactUnwindContext* context, + uintptr_t pc, + FIRCLSCompactUnwindResult* result) { + switch (FIRCLSCompactUnwindGetSecondLevelPageKind(context)) { + case UNWIND_SECOND_LEVEL_REGULAR: + FIRCLSSDKLogInfo("Found a second level regular header\n"); + if (FIRCLSCompactUnwindLookupSecondLevelRegular(context, pc, result)) { + return true; + } + break; + case UNWIND_SECOND_LEVEL_COMPRESSED: + FIRCLSSDKLogInfo("Found a second level compressed header\n"); + if (FIRCLSCompactUnwindLookupSecondLevelCompressed(context, pc, result)) { + return true; + } + break; + default: + FIRCLSSDKLogError("Unrecognized header kind - unable to continue\n"); + break; + } + + return false; +} + +bool FIRCLSCompactUnwindLookup(FIRCLSCompactUnwindContext* context, + uintptr_t pc, + FIRCLSCompactUnwindResult* result) { + if (!context || !result) { + return false; + } + + // step 1 - find the pc in the first-level index + if (!FIRCLSCompactUnwindLookupFirstLevel(context, pc)) { + FIRCLSSDKLogWarn("Unable to find pc in first level\n"); + return false; + } + + FIRCLSSDKLogDebug("Found first level (second => %u)\n", + context->indexHeader.secondLevelPagesSectionOffset); + + // step 2 - use that info to find the second-level information + // that second actually has the encoding info we're looking for. + if (!FIRCLSCompactUnwindLookupSecondLevel(context, pc, result)) { + FIRCLSSDKLogInfo("Second-level PC lookup failed\n"); + return false; + } + + return true; +} + +#pragma mark - Unwinding +bool FIRCLSCompactUnwindLookupAndCompute(FIRCLSCompactUnwindContext* context, + FIRCLSThreadContext* registers) { + if (!context || !registers) { + return false; + } + + uintptr_t pc = FIRCLSThreadContextGetPC(registers); + + // little sanity check + if (pc < context->loadAddress) { + return false; + } + + FIRCLSCompactUnwindResult result; + + memset(&result, 0, sizeof(result)); + + if (!FIRCLSCompactUnwindLookup(context, pc, &result)) { + FIRCLSSDKLogInfo("Unable to lookup compact unwind for pc %p\n", (void*)pc); + return false; + } + + // Ok, armed with the encoding, we can actually attempt to modify the registers. Because + // the encoding is arch-specific, this function has to be defined per-arch. + if (!FIRCLSCompactUnwindComputeRegisters(context, &result, registers)) { + FIRCLSSDKLogError("Failed to compute registers\n"); + return false; + } + + return true; +} + +#if CLS_DWARF_UNWINDING_SUPPORTED +bool FIRCLSCompactUnwindDwarfFrame(FIRCLSCompactUnwindContext* context, + uintptr_t dwarfOffset, + FIRCLSThreadContext* registers) { + if (!context || !registers) { + return false; + } + + // Everyone's favorite! Dwarf unwinding! + FIRCLSSDKLogInfo("Trying to read dwarf data with offset %lx\n", dwarfOffset); + + FIRCLSDwarfCFIRecord record; + + if (!FIRCLSDwarfParseCFIFromFDERecordOffset(&record, context->ehFrame, dwarfOffset)) { + FIRCLSSDKLogError("Unable to init FDE\n"); + return false; + } + + if (!FIRCLSDwarfUnwindComputeRegisters(&record, registers)) { + FIRCLSSDKLogError("Failed to compute DWARF registers\n"); + return false; + } + + return true; +} +#endif + +#else +INJECT_STRIP_SYMBOL(compact_unwind) +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Compact/FIRCLSCompactUnwind.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Compact/FIRCLSCompactUnwind.h new file mode 100644 index 0000000..3701e35 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Compact/FIRCLSCompactUnwind.h @@ -0,0 +1,68 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "FIRCLSFeatures.h" +#include "FIRCLSThreadState.h" + +#include +#include + +// We have to pack the arrays defined in this header, so +// we can reason about pointer math. +#pragma pack(push) +#pragma pack(1) +#include +#pragma pack(pop) + +// First masks out the value, and then shifts the value by the number +// of zeros in the mask. __builtin_ctz returns the number of trailing zeros. +// Its output is undefined if the input is zero. +#define GET_BITS_WITH_MASK(value, mask) ((value & mask) >> (mask == 0 ? 0 : __builtin_ctz(mask))) + +typedef struct { + const void* unwindInfo; + const void* ehFrame; + uintptr_t loadAddress; + + struct unwind_info_section_header unwindHeader; + struct unwind_info_section_header_index_entry indexHeader; + uint32_t firstLevelNextFunctionOffset; +} FIRCLSCompactUnwindContext; + +typedef struct { + compact_unwind_encoding_t encoding; + uintptr_t functionStart; + uintptr_t functionEnd; + uintptr_t lsda; + uintptr_t personality; + +} FIRCLSCompactUnwindResult; + +bool FIRCLSCompactUnwindInit(FIRCLSCompactUnwindContext* context, + const void* unwindInfo, + const void* ehFrame, + uintptr_t loadAddress); +void* FIRCLSCompactUnwindGetIndexData(FIRCLSCompactUnwindContext* context); +void* FIRCLSCompactUnwindGetSecondLevelData(FIRCLSCompactUnwindContext* context); +bool FIRCLSCompactUnwindFindFirstLevelIndex(FIRCLSCompactUnwindContext* context, + uintptr_t pc, + uint32_t* index); + +bool FIRCLSCompactUnwindDwarfFrame(FIRCLSCompactUnwindContext* context, + uintptr_t dwarfOffset, + FIRCLSThreadContext* registers); +bool FIRCLSCompactUnwindLookupAndCompute(FIRCLSCompactUnwindContext* context, + FIRCLSThreadContext* registers); diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Compact/FIRCLSCompactUnwind_Private.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Compact/FIRCLSCompactUnwind_Private.h new file mode 100644 index 0000000..1dd0156 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Compact/FIRCLSCompactUnwind_Private.h @@ -0,0 +1,28 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "FIRCLSCompactUnwind.h" +#pragma pack(push, 1) +#include +#pragma pack(pop) + +bool FIRCLSCompactUnwindLookup(FIRCLSCompactUnwindContext* context, + uintptr_t pc, + FIRCLSCompactUnwindResult* result); + +bool FIRCLSCompactUnwindComputeRegisters(FIRCLSCompactUnwindContext* context, + FIRCLSCompactUnwindResult* result, + FIRCLSThreadContext* registers); diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDataParsing.c b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDataParsing.c new file mode 100644 index 0000000..871fd30 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDataParsing.c @@ -0,0 +1,238 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FIRCLSDataParsing.h" +#include "FIRCLSDefines.h" +#include "FIRCLSUtility.h" +#include "dwarf.h" + +#include + +#if CLS_DWARF_UNWINDING_SUPPORTED + +uint8_t FIRCLSParseUint8AndAdvance(const void** cursor) { + uint8_t tmp = **(uint8_t**)cursor; + + *cursor += sizeof(uint8_t); + + return tmp; +} + +uint16_t FIRCLSParseUint16AndAdvance(const void** cursor) { + uint16_t tmp = **(uint16_t**)cursor; + + *cursor += sizeof(uint16_t); + + return tmp; +} + +int16_t FIRCLSParseInt16AndAdvance(const void** cursor) { + int16_t tmp = **(int16_t**)cursor; + + *cursor += sizeof(int16_t); + + return tmp; +} + +uint32_t FIRCLSParseUint32AndAdvance(const void** cursor) { + uint32_t tmp = **(uint32_t**)cursor; + + *cursor += sizeof(uint32_t); + + return tmp; +} + +int32_t FIRCLSParseInt32AndAdvance(const void** cursor) { + int32_t tmp = **(int32_t**)cursor; + + *cursor += sizeof(int32_t); + + return tmp; +} + +uint64_t FIRCLSParseUint64AndAdvance(const void** cursor) { + uint64_t tmp = **(uint64_t**)cursor; + + *cursor += sizeof(uint64_t); + + return tmp; +} + +int64_t FIRCLSParseInt64AndAdvance(const void** cursor) { + int64_t tmp = **(int64_t**)cursor; + + *cursor += sizeof(int64_t); + + return tmp; +} + +uintptr_t FIRCLSParsePointerAndAdvance(const void** cursor) { + uintptr_t tmp = **(uintptr_t**)cursor; + + *cursor += sizeof(uintptr_t); + + return tmp; +} + +// Signed and Unsigned LEB128 decoding algorithms taken from Wikipedia - +// http://en.wikipedia.org/wiki/LEB128 +uint64_t FIRCLSParseULEB128AndAdvance(const void** cursor) { + uint64_t result = 0; + char shift = 0; + + for (int i = 0; i < sizeof(uint64_t); ++i) { + char byte; + + byte = **(uint8_t**)cursor; + + *cursor += 1; + + result |= ((0x7F & byte) << shift); + if ((0x80 & byte) == 0) { + break; + } + + shift += 7; + } + + return result; +} + +int64_t FIRCLSParseLEB128AndAdvance(const void** cursor) { + uint64_t result = 0; + char shift = 0; + char size = sizeof(int64_t) * 8; + char byte = 0; + + for (int i = 0; i < sizeof(uint64_t); ++i) { + byte = **(uint8_t**)cursor; + + *cursor += 1; + + result |= ((0x7F & byte) << shift); + shift += 7; + + /* sign bit of byte is second high order bit (0x40) */ + if ((0x80 & byte) == 0) { + break; + } + } + + if ((shift < size) && (0x40 & byte)) { + // sign extend + result |= -(1 << shift); + } + + return result; +} + +const char* FIRCLSParseStringAndAdvance(const void** cursor) { + const char* string; + + string = (const char*)(*cursor); + + // strlen doesn't include the null character, which we need to advance past + *cursor += strlen(string) + 1; + + return string; +} + +uint64_t FIRCLSParseRecordLengthAndAdvance(const void** cursor) { + uint64_t length; + + length = FIRCLSParseUint32AndAdvance(cursor); + if (length == DWARF_EXTENDED_LENGTH_FLAG) { + length = FIRCLSParseUint64AndAdvance(cursor); + } + + return length; +} + +uintptr_t FIRCLSParseAddressWithEncodingAndAdvance(const void** cursor, uint8_t encoding) { + if (encoding == DW_EH_PE_omit) { + return 0; + } + + if (!cursor) { + return CLS_INVALID_ADDRESS; + } + + if (!*cursor) { + return CLS_INVALID_ADDRESS; + } + + intptr_t inputAddr = (intptr_t)*cursor; + intptr_t addr; + + switch (encoding & DW_EH_PE_VALUE_MASK) { + case DW_EH_PE_ptr: + // 32 or 64 bits + addr = FIRCLSParsePointerAndAdvance(cursor); + break; + case DW_EH_PE_uleb128: + addr = (intptr_t)FIRCLSParseULEB128AndAdvance(cursor); + break; + case DW_EH_PE_udata2: + addr = FIRCLSParseUint16AndAdvance(cursor); + break; + case DW_EH_PE_udata4: + addr = FIRCLSParseUint32AndAdvance(cursor); + break; + case DW_EH_PE_udata8: + addr = (intptr_t)FIRCLSParseUint64AndAdvance(cursor); + break; + case DW_EH_PE_sleb128: + addr = (intptr_t)FIRCLSParseLEB128AndAdvance(cursor); + break; + case DW_EH_PE_sdata2: + addr = FIRCLSParseInt16AndAdvance(cursor); + break; + case DW_EH_PE_sdata4: + addr = FIRCLSParseInt32AndAdvance(cursor); + break; + case DW_EH_PE_sdata8: + addr = (intptr_t)FIRCLSParseInt64AndAdvance(cursor); + break; + default: + FIRCLSSDKLog("Unhandled: encoding 0x%02x\n", encoding); + return CLS_INVALID_ADDRESS; + } + + // and now apply the relative offset + switch (encoding & DW_EH_PE_RELATIVE_OFFSET_MASK) { + case DW_EH_PE_absptr: + break; + case DW_EH_PE_pcrel: + addr += inputAddr; + break; + default: + FIRCLSSDKLog("Unhandled: relative encoding 0x%02x\n", encoding); + return CLS_INVALID_ADDRESS; + } + + // Here's a crazy one. It seems this encoding means you actually look up + // the value of the address using the result address itself + if (encoding & DW_EH_PE_indirect) { + if (!addr) { + return CLS_INVALID_ADDRESS; + } + + addr = *(uintptr_t*)addr; + } + + return addr; +} +#else +INJECT_STRIP_SYMBOL(data_parsing) +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDataParsing.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDataParsing.h new file mode 100644 index 0000000..44c8a28 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDataParsing.h @@ -0,0 +1,46 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "FIRCLSFeatures.h" + +#include + +#if CLS_DWARF_UNWINDING_SUPPORTED + +#if CLS_CPU_64BIT +#define CLS_INVALID_ADDRESS (0xffffffffffffffff) +#else +#define CLS_INVALID_ADDRESS (0xffffffff) +#endif + +// basic data types +uint8_t FIRCLSParseUint8AndAdvance(const void** cursor); +uint16_t FIRCLSParseUint16AndAdvance(const void** cursor); +int16_t FIRCLSParseInt16AndAdvance(const void** cursor); +uint32_t FIRCLSParseUint32AndAdvance(const void** cursor); +int32_t FIRCLSParseInt32AndAdvance(const void** cursor); +uint64_t FIRCLSParseUint64AndAdvance(const void** cursor); +int64_t FIRCLSParseInt64AndAdvance(const void** cursor); +uintptr_t FIRCLSParsePointerAndAdvance(const void** cursor); +uint64_t FIRCLSParseULEB128AndAdvance(const void** cursor); +int64_t FIRCLSParseLEB128AndAdvance(const void** cursor); +const char* FIRCLSParseStringAndAdvance(const void** cursor); + +// FDE/CIE-specifc structures +uint64_t FIRCLSParseRecordLengthAndAdvance(const void** cursor); +uintptr_t FIRCLSParseAddressWithEncodingAndAdvance(const void** cursor, uint8_t encoding); + +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDwarfExpressionMachine.c b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDwarfExpressionMachine.c new file mode 100644 index 0000000..ea308f1 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDwarfExpressionMachine.c @@ -0,0 +1,453 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FIRCLSDwarfExpressionMachine.h" +#include "FIRCLSDataParsing.h" +#include "FIRCLSDefines.h" +#include "FIRCLSDwarfUnwindRegisters.h" +#include "FIRCLSUnwind_arch.h" +#include "FIRCLSUtility.h" +#include "dwarf.h" + +#if CLS_DWARF_UNWINDING_SUPPORTED + +static bool FIRCLSDwarfExpressionMachineExecute_bregN(FIRCLSDwarfExpressionMachine *machine, + uint8_t opcode); +static bool FIRCLSDwarfExpressionMachineExecute_deref(FIRCLSDwarfExpressionMachine *machine); +static bool FIRCLSDwarfExpressionMachineExecute_plus_uconst(FIRCLSDwarfExpressionMachine *machine); +static bool FIRCLSDwarfExpressionMachineExecute_and(FIRCLSDwarfExpressionMachine *machine); +static bool FIRCLSDwarfExpressionMachineExecute_plus(FIRCLSDwarfExpressionMachine *machine); +static bool FIRCLSDwarfExpressionMachineExecute_dup(FIRCLSDwarfExpressionMachine *machine); +static bool FIRCLSDwarfExpressionMachineExecute_swap(FIRCLSDwarfExpressionMachine *machine); +static bool FIRCLSDwarfExpressionMachineExecute_deref_size(FIRCLSDwarfExpressionMachine *machine); +static bool FIRCLSDwarfExpressionMachineExecute_ne(FIRCLSDwarfExpressionMachine *machine); +static bool FIRCLSDwarfExpressionMachineExecute_litN(FIRCLSDwarfExpressionMachine *machine, + uint8_t opcode); + +#pragma mark - +#pragma mark Stack Implementation +void FIRCLSDwarfExpressionStackInit(FIRCLSDwarfExpressionStack *stack) { + if (!FIRCLSIsValidPointer(stack)) { + return; + } + + memset(stack, 0, sizeof(FIRCLSDwarfExpressionStack)); + + stack->pointer = stack->buffer; +} + +bool FIRCLSDwarfExpressionStackIsValid(FIRCLSDwarfExpressionStack *stack) { + if (!FIRCLSIsValidPointer(stack)) { + return false; + } + + // check for valid stack pointer + if (stack->pointer < stack->buffer) { + return false; + } + + if (stack->pointer > stack->buffer + CLS_DWARF_EXPRESSION_STACK_SIZE) { + return false; + } + + return true; +} + +bool FIRCLSDwarfExpressionStackPush(FIRCLSDwarfExpressionStack *stack, intptr_t value) { + if (!FIRCLSDwarfExpressionStackIsValid(stack)) { + return false; + } + + if (stack->pointer == stack->buffer + CLS_DWARF_EXPRESSION_STACK_SIZE) { + // overflow + stack->pointer = NULL; + return false; + } + + *(stack->pointer) = value; + stack->pointer += 1; + + return true; +} + +intptr_t FIRCLSDwarfExpressionStackPeek(FIRCLSDwarfExpressionStack *stack) { + if (!FIRCLSDwarfExpressionStackIsValid(stack)) { + return 0; + } + + if (stack->pointer == stack->buffer) { + // underflow + stack->pointer = NULL; + return 0; + } + + return *(stack->pointer - 1); +} + +intptr_t FIRCLSDwarfExpressionStackPop(FIRCLSDwarfExpressionStack *stack) { + if (!FIRCLSDwarfExpressionStackIsValid(stack)) { + return 0; + } + + if (stack->pointer == stack->buffer) { + // underflow + stack->pointer = NULL; + return 0; + } + + stack->pointer -= 1; + + return *(stack->pointer); +} + +#pragma mark - +#pragma mark Machine API +bool FIRCLSDwarfExpressionMachineInit(FIRCLSDwarfExpressionMachine *machine, + const void *cursor, + const FIRCLSThreadContext *registers, + intptr_t stackValue) { + if (!FIRCLSIsValidPointer(machine)) { + return false; + } + + memset(machine, 0, sizeof(FIRCLSDwarfExpressionMachine)); + + if (!FIRCLSIsValidPointer(cursor)) { + return false; + } + + machine->dataCursor = cursor; + machine->registers = registers; + + FIRCLSDwarfExpressionStackInit(&machine->stack); + + return FIRCLSDwarfExpressionStackPush(&machine->stack, stackValue); +} + +bool FIRCLSDwarfExpressionMachinePrepareForExecution(FIRCLSDwarfExpressionMachine *machine) { + if (!FIRCLSIsValidPointer(machine)) { + FIRCLSSDKLog("Error: invalid inputs\n"); + return false; + } + + uint64_t expressionLength = FIRCLSParseULEB128AndAdvance(&machine->dataCursor); + + if (expressionLength == 0) { + FIRCLSSDKLog("Error: DWARF expression length is zero\n"); + return false; + } + + machine->endAddress = machine->dataCursor + expressionLength; + + return true; +} + +bool FIRCLSDwarfExpressionMachineIsFinished(FIRCLSDwarfExpressionMachine *machine) { + if (!FIRCLSIsValidPointer(machine)) { + FIRCLSSDKLog("Error: invalid inputs\n"); + return true; + } + + if (!FIRCLSIsValidPointer(machine->endAddress) || !FIRCLSIsValidPointer(machine->dataCursor)) { + FIRCLSSDKLog("Error: DWARF machine pointers invalid\n"); + return true; + } + + if (!FIRCLSDwarfExpressionStackIsValid(&machine->stack)) { + FIRCLSSDKLog("Error: DWARF machine stack invalid\n"); + return true; + } + + return machine->dataCursor >= machine->endAddress; +} + +bool FIRCLSDwarfExpressionMachineGetResult(FIRCLSDwarfExpressionMachine *machine, + intptr_t *result) { + if (!FIRCLSIsValidPointer(machine) || !FIRCLSIsValidPointer(result)) { + return false; + } + + if (machine->dataCursor != machine->endAddress) { + FIRCLSSDKLog("Error: DWARF expression hasn't completed execution\n"); + return false; + } + + *result = FIRCLSDwarfExpressionStackPeek(&machine->stack); + + return FIRCLSDwarfExpressionStackIsValid(&machine->stack); +} + +bool FIRCLSDwarfExpressionMachineExecuteNextOpcode(FIRCLSDwarfExpressionMachine *machine) { + if (!FIRCLSIsValidPointer(machine)) { + return false; + } + + const uint8_t opcode = FIRCLSParseUint8AndAdvance(&machine->dataCursor); + + bool success = false; + + switch (opcode) { + case DW_OP_deref: + success = FIRCLSDwarfExpressionMachineExecute_deref(machine); + break; + case DW_OP_dup: + success = FIRCLSDwarfExpressionMachineExecute_dup(machine); + break; + case DW_OP_and: + success = FIRCLSDwarfExpressionMachineExecute_and(machine); + break; + case DW_OP_plus: + success = FIRCLSDwarfExpressionMachineExecute_plus(machine); + break; + case DW_OP_swap: + success = FIRCLSDwarfExpressionMachineExecute_swap(machine); + break; + case DW_OP_plus_uconst: + success = FIRCLSDwarfExpressionMachineExecute_plus_uconst(machine); + break; + case DW_OP_ne: + success = FIRCLSDwarfExpressionMachineExecute_ne(machine); + break; + case DW_OP_lit0: + case DW_OP_lit1: + case DW_OP_lit2: + case DW_OP_lit3: + case DW_OP_lit4: + case DW_OP_lit5: + case DW_OP_lit6: + case DW_OP_lit7: + case DW_OP_lit8: + case DW_OP_lit9: + case DW_OP_lit10: + case DW_OP_lit11: + case DW_OP_lit12: + case DW_OP_lit13: + case DW_OP_lit14: + case DW_OP_lit15: + case DW_OP_lit16: + case DW_OP_lit17: + case DW_OP_lit18: + case DW_OP_lit19: + case DW_OP_lit20: + case DW_OP_lit21: + case DW_OP_lit22: + case DW_OP_lit23: + case DW_OP_lit24: + case DW_OP_lit25: + case DW_OP_lit26: + case DW_OP_lit27: + case DW_OP_lit28: + case DW_OP_lit29: + case DW_OP_lit30: + case DW_OP_lit31: + success = FIRCLSDwarfExpressionMachineExecute_litN(machine, opcode); + break; + case DW_OP_breg0: + case DW_OP_breg1: + case DW_OP_breg2: + case DW_OP_breg3: + case DW_OP_breg4: + case DW_OP_breg5: + case DW_OP_breg6: + case DW_OP_breg7: + case DW_OP_breg8: + case DW_OP_breg9: + case DW_OP_breg10: + case DW_OP_breg11: + case DW_OP_breg12: + case DW_OP_breg13: + case DW_OP_breg14: + case DW_OP_breg15: + case DW_OP_breg16: + case DW_OP_breg17: + case DW_OP_breg18: + case DW_OP_breg19: + case DW_OP_breg20: + case DW_OP_breg21: + case DW_OP_breg22: + case DW_OP_breg23: + case DW_OP_breg24: + case DW_OP_breg25: + case DW_OP_breg26: + case DW_OP_breg27: + case DW_OP_breg28: + case DW_OP_breg29: + case DW_OP_breg30: + case DW_OP_breg31: + success = FIRCLSDwarfExpressionMachineExecute_bregN(machine, opcode); + break; + case DW_OP_deref_size: + success = FIRCLSDwarfExpressionMachineExecute_deref_size(machine); + break; + default: + FIRCLSSDKLog("Error: Unrecognized DWARF expression opcode 0x%x\n", opcode); + return false; + } + + return success; +} + +#pragma mark - +#pragma mark Helpers +static intptr_t FIRCLSDwarfExpressionMachineStackPop(FIRCLSDwarfExpressionMachine *machine) { + return FIRCLSDwarfExpressionStackPop(&machine->stack); +} + +static bool FIRCLSDwarfExpressionMachineStackPush(FIRCLSDwarfExpressionMachine *machine, + intptr_t value) { + return FIRCLSDwarfExpressionStackPush(&machine->stack, value); +} + +#pragma mark - +#pragma mark Opcode Implementations +static bool FIRCLSDwarfExpressionMachineExecute_bregN(FIRCLSDwarfExpressionMachine *machine, + uint8_t opcode) { + // find the register number, compute offset value, push + const uint8_t regNum = opcode - DW_OP_breg0; + + if (regNum > CLS_DWARF_MAX_REGISTER_NUM) { + FIRCLSSDKLog("Error: DW_OP_breg invalid register number\n"); + return false; + } + + int64_t offset = FIRCLSParseLEB128AndAdvance(&machine->dataCursor); + + FIRCLSSDKLog("DW_OP_breg %d value %d\n", regNum, (int)offset); + + const intptr_t value = + FIRCLSDwarfUnwindGetRegisterValue(machine->registers, regNum) + (intptr_t)offset; + + return FIRCLSDwarfExpressionMachineStackPush(machine, value); +} + +static bool FIRCLSDwarfExpressionMachineExecute_deref(FIRCLSDwarfExpressionMachine *machine) { + // pop stack, dereference, push result + intptr_t value = FIRCLSDwarfExpressionMachineStackPop(machine); + + FIRCLSSDKLog("DW_OP_deref value %p\n", (void *)value); + + if (!FIRCLSReadMemory(value, &value, sizeof(value))) { + FIRCLSSDKLog("Error: DW_OP_deref failed to read memory\n"); + return false; + } + + return FIRCLSDwarfExpressionMachineStackPush(machine, value); +} + +static bool FIRCLSDwarfExpressionMachineExecute_plus_uconst(FIRCLSDwarfExpressionMachine *machine) { + // pop stack, add constant, push result + intptr_t value = FIRCLSDwarfExpressionMachineStackPop(machine); + + value += FIRCLSParseULEB128AndAdvance(&machine->dataCursor); + + FIRCLSSDKLog("DW_OP_plus_uconst value %lu\n", value); + + return FIRCLSDwarfExpressionMachineStackPush(machine, value); +} + +static bool FIRCLSDwarfExpressionMachineExecute_and(FIRCLSDwarfExpressionMachine *machine) { + FIRCLSSDKLog("DW_OP_plus_and\n"); + + intptr_t value = FIRCLSDwarfExpressionMachineStackPop(machine); + + value = value & FIRCLSDwarfExpressionMachineStackPop(machine); + + return FIRCLSDwarfExpressionMachineStackPush(machine, value); +} + +static bool FIRCLSDwarfExpressionMachineExecute_plus(FIRCLSDwarfExpressionMachine *machine) { + FIRCLSSDKLog("DW_OP_plus\n"); + + intptr_t value = FIRCLSDwarfExpressionMachineStackPop(machine); + + value = value + FIRCLSDwarfExpressionMachineStackPop(machine); + + return FIRCLSDwarfExpressionMachineStackPush(machine, value); +} + +static bool FIRCLSDwarfExpressionMachineExecute_dup(FIRCLSDwarfExpressionMachine *machine) { + // duplicate top of stack + intptr_t value = FIRCLSDwarfExpressionStackPeek(&machine->stack); + + FIRCLSSDKLog("DW_OP_dup value %lu\n", value); + + return FIRCLSDwarfExpressionMachineStackPush(machine, value); +} + +static bool FIRCLSDwarfExpressionMachineExecute_swap(FIRCLSDwarfExpressionMachine *machine) { + // swap top two values on the stack + intptr_t valueA = FIRCLSDwarfExpressionMachineStackPop(machine); + intptr_t valueB = FIRCLSDwarfExpressionMachineStackPop(machine); + + FIRCLSSDKLog("DW_OP_swap\n"); + + if (!FIRCLSDwarfExpressionMachineStackPush(machine, valueA)) { + return false; + } + + return FIRCLSDwarfExpressionMachineStackPush(machine, valueB); +} + +static bool FIRCLSDwarfExpressionMachineExecute_deref_size(FIRCLSDwarfExpressionMachine *machine) { + // pop stack, dereference variable sized value, push result + const void *address = (const void *)FIRCLSDwarfExpressionMachineStackPop(machine); + const uint8_t readSize = FIRCLSParseUint8AndAdvance(&machine->dataCursor); + intptr_t value = 0; + + FIRCLSSDKLog("DW_OP_deref_size %p size %u\n", address, readSize); + + switch (readSize) { + case 1: + value = FIRCLSParseUint8AndAdvance(&address); + break; + case 2: + value = FIRCLSParseUint16AndAdvance(&address); + break; + case 4: + value = FIRCLSParseUint32AndAdvance(&address); + break; + case 8: + // this is a little funky, as an 8 here really doesn't make sense for 32-bit platforms + value = (intptr_t)FIRCLSParseUint64AndAdvance(&address); + break; + default: + FIRCLSSDKLog("Error: unrecognized DW_OP_deref_size argument %x\n", readSize); + return false; + } + + return FIRCLSDwarfExpressionMachineStackPush(machine, value); +} + +static bool FIRCLSDwarfExpressionMachineExecute_ne(FIRCLSDwarfExpressionMachine *machine) { + FIRCLSSDKLog("DW_OP_ne\n"); + + intptr_t value = FIRCLSDwarfExpressionMachineStackPop(machine); + + value = value != FIRCLSDwarfExpressionMachineStackPop(machine); + + return FIRCLSDwarfExpressionMachineStackPush(machine, value); +} + +static bool FIRCLSDwarfExpressionMachineExecute_litN(FIRCLSDwarfExpressionMachine *machine, + uint8_t opcode) { + const uint8_t value = opcode - DW_OP_lit0; + + FIRCLSSDKLog("DW_OP_lit %u\n", value); + + return FIRCLSDwarfExpressionMachineStackPush(machine, value); +} + +#else +INJECT_STRIP_SYMBOL(dwarf_expression_machine) +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDwarfExpressionMachine.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDwarfExpressionMachine.h new file mode 100644 index 0000000..7dd70f8 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDwarfExpressionMachine.h @@ -0,0 +1,55 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include + +#include "FIRCLSFeatures.h" +#include "FIRCLSThreadState.h" + +#define CLS_DWARF_EXPRESSION_STACK_SIZE (100) + +#if CLS_DWARF_UNWINDING_SUPPORTED + +typedef struct { + intptr_t buffer[CLS_DWARF_EXPRESSION_STACK_SIZE]; + intptr_t *pointer; +} FIRCLSDwarfExpressionStack; + +typedef struct { + FIRCLSDwarfExpressionStack stack; + const void *dataCursor; + const void *endAddress; + const FIRCLSThreadContext *registers; +} FIRCLSDwarfExpressionMachine; + +void FIRCLSDwarfExpressionStackInit(FIRCLSDwarfExpressionStack *stack); +bool FIRCLSDwarfExpressionStackIsValid(FIRCLSDwarfExpressionStack *stack); +bool FIRCLSDwarfExpressionStackPush(FIRCLSDwarfExpressionStack *stack, intptr_t value); +intptr_t FIRCLSDwarfExpressionStackPeek(FIRCLSDwarfExpressionStack *stack); +intptr_t FIRCLSDwarfExpressionStackPop(FIRCLSDwarfExpressionStack *stack); + +bool FIRCLSDwarfExpressionMachineInit(FIRCLSDwarfExpressionMachine *machine, + const void *cursor, + const FIRCLSThreadContext *registers, + intptr_t stackValue); +bool FIRCLSDwarfExpressionMachinePrepareForExecution(FIRCLSDwarfExpressionMachine *machine); +bool FIRCLSDwarfExpressionMachineIsFinished(FIRCLSDwarfExpressionMachine *machine); +bool FIRCLSDwarfExpressionMachineGetResult(FIRCLSDwarfExpressionMachine *machine, intptr_t *result); + +bool FIRCLSDwarfExpressionMachineExecuteNextOpcode(FIRCLSDwarfExpressionMachine *machine); + +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDwarfUnwind.c b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDwarfUnwind.c new file mode 100644 index 0000000..665e8aa --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDwarfUnwind.c @@ -0,0 +1,1002 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FIRCLSDwarfUnwind.h" +#include "FIRCLSDataParsing.h" +#include "FIRCLSDefines.h" +#include "FIRCLSDwarfExpressionMachine.h" +#include "FIRCLSFeatures.h" +#include "FIRCLSUnwind_arch.h" +#include "FIRCLSUtility.h" +#include "dwarf.h" + +#include + +#if CLS_DWARF_UNWINDING_SUPPORTED + +#define FIRCLSDwarfLog(__FORMAT__, ...) FIRCLSSDKLog(__FORMAT__, ##__VA_ARGS__) + +#define CLS_DWARF_EXPRESSION_STACK_SIZE (100) + +#pragma mark Prototypes +static bool FIRCLSDwarfParseAndProcessAugmentation(DWARFCIERecord* record, const void** ptr); + +#pragma mark - Record Parsing +bool FIRCLSDwarfParseCIERecord(DWARFCIERecord* cie, const void* ptr) { + if (!cie || !ptr) { + return false; + } + + memset(cie, 0, sizeof(DWARFCIERecord)); + + cie->length = FIRCLSParseRecordLengthAndAdvance(&ptr); + if (cie->length == 0) { + FIRCLSSDKLog("Error: CIE length invalid\n"); + return false; + } + + // the length does not include the length field(s) themselves + const void* endAddress = ptr + cie->length; + + if (FIRCLSParseUint32AndAdvance(&ptr) != DWARF_CIE_ID_CIE_FLAG) { + FIRCLSSDKLog("Error: CIE flag not found\n"); + } + + cie->version = FIRCLSParseUint8AndAdvance(&ptr); + if (cie->version != 1 && cie->version != 3) { + FIRCLSSDKLog("Error: CIE version %u unsupported\n", cie->version); + } + + cie->pointerEncoding = DW_EH_PE_absptr; + cie->lsdaEncoding = DW_EH_PE_absptr; + + cie->augmentation = FIRCLSParseStringAndAdvance(&ptr); + cie->codeAlignFactor = FIRCLSParseULEB128AndAdvance(&ptr); + cie->dataAlignFactor = FIRCLSParseLEB128AndAdvance(&ptr); + + switch (cie->version) { + case 1: + cie->returnAddressRegister = FIRCLSParseUint8AndAdvance(&ptr); + break; + case 3: + cie->returnAddressRegister = FIRCLSParseULEB128AndAdvance(&ptr); + break; + default: + FIRCLSSDKLog("Error: CIE version %u unsupported\n", cie->version); + return false; + } + + if (!FIRCLSDwarfParseAndProcessAugmentation(cie, &ptr)) { + return false; + } + + cie->instructions.data = ptr; + cie->instructions.length = (uint32_t)(endAddress - ptr); + + return true; +} + +static bool FIRCLSDwarfParseAndProcessAugmentation(DWARFCIERecord* record, const void** ptr) { + if (!record || !ptr) { + return false; + } + + if (!record->augmentation) { + return false; + } + + if (record->augmentation[0] == 0) { + return true; + } + + if (record->augmentation[0] != 'z') { + FIRCLSSDKLog("Error: Unimplemented: augmentation string %s\n", record->augmentation); + return false; + } + + size_t stringLength = strlen(record->augmentation); + + uint64_t dataLength = FIRCLSParseULEB128AndAdvance(ptr); + const void* ending = *ptr + dataLength; + + // start at 1 because we know the first character is a 'z' + for (size_t i = 1; i < stringLength; ++i) { + switch (record->augmentation[i]) { + case 'L': + // There is an LSDA pointer encoding present. The actual address of the LSDA + // is in the FDE + record->lsdaEncoding = FIRCLSParseUint8AndAdvance(ptr); + break; + case 'R': + // There is a pointer encoding present, used for all addresses in an FDE. + record->pointerEncoding = FIRCLSParseUint8AndAdvance(ptr); + break; + case 'P': + // Two arguments. A pointer encoding, and a pointer to a personality function encoded + // with that value. + record->personalityEncoding = FIRCLSParseUint8AndAdvance(ptr); + record->personalityFunction = + FIRCLSParseAddressWithEncodingAndAdvance(ptr, record->personalityEncoding); + if (record->personalityFunction == CLS_INVALID_ADDRESS) { + FIRCLSSDKLog("Error: Found an invalid start address\n"); + return false; + } + break; + case 'S': + record->signalFrame = true; + break; + default: + FIRCLSSDKLog("Error: Unhandled augmentation string entry %c\n", record->augmentation[i]); + return false; + } + + // small sanity check + if (*ptr > ending) { + return false; + } + } + + return true; +} + +bool FIRCLSDwarfParseFDERecord(DWARFFDERecord* fdeRecord, + bool parseCIE, + DWARFCIERecord* cieRecord, + const void* ptr) { + if (!fdeRecord || !cieRecord || !ptr) { + return false; + } + + fdeRecord->length = FIRCLSParseRecordLengthAndAdvance(&ptr); + if (fdeRecord->length == 0) { + FIRCLSSDKLog("Error: FDE has zero length\n"); + return false; + } + + // length does not include length field + const void* endAddress = ptr + fdeRecord->length; + + // According to the spec, this is 32/64 bit value, but libunwind always + // parses this as a 32bit value. + fdeRecord->cieOffset = FIRCLSParseUint32AndAdvance(&ptr); + if (fdeRecord->cieOffset == 0) { + FIRCLSSDKLog("Error: CIE offset invalid\n"); + return false; + } + + if (parseCIE) { + // The CIE offset is really weird. It appears to be an offset from the + // beginning of its field. This isn't what the documentation says, but it is + // a little ambigious. This is what DwarfParser.hpp does. + // Note that we have to back up one sizeof(uint32_t), because we've advanced + // by parsing the offset + const void* ciePointer = ptr - fdeRecord->cieOffset - sizeof(uint32_t); + if (!FIRCLSDwarfParseCIERecord(cieRecord, ciePointer)) { + FIRCLSSDKLog("Error: Unable to parse CIE record\n"); + return false; + } + } + + if (!FIRCLSDwarfCIEIsValid(cieRecord)) { + FIRCLSSDKLog("Error: CIE invalid\n"); + return false; + } + + // the next field depends on the pointer encoding style used + fdeRecord->startAddress = + FIRCLSParseAddressWithEncodingAndAdvance(&ptr, cieRecord->pointerEncoding); + if (fdeRecord->startAddress == CLS_INVALID_ADDRESS) { + FIRCLSSDKLog("Error: Found an invalid start address\n"); + return false; + } + + // Here's something weird too. The range is encoded as a "special" address, where only the value + // is used, regardless of other pointer-encoding schemes. + fdeRecord->rangeSize = FIRCLSParseAddressWithEncodingAndAdvance( + &ptr, cieRecord->pointerEncoding & DW_EH_PE_VALUE_MASK); + if (fdeRecord->rangeSize == CLS_INVALID_ADDRESS) { + FIRCLSSDKLog("Error: Found an invalid address range\n"); + return false; + } + + // Just skip over the section for now. The data here is only needed for personality functions, + // which we don't need + if (FIRCLSDwarfCIEHasAugmentationData(cieRecord)) { + uintptr_t augmentationLength = (uintptr_t)FIRCLSParseULEB128AndAdvance(&ptr); + + ptr += augmentationLength; + } + + fdeRecord->instructions.data = ptr; + fdeRecord->instructions.length = (uint32_t)(endAddress - ptr); + + return true; +} + +bool FIRCLSDwarfParseCFIFromFDERecord(FIRCLSDwarfCFIRecord* record, const void* ptr) { + if (!record || !ptr) { + return false; + } + + return FIRCLSDwarfParseFDERecord(&record->fde, true, &record->cie, ptr); +} + +bool FIRCLSDwarfParseCFIFromFDERecordOffset(FIRCLSDwarfCFIRecord* record, + const void* ehFrame, + uintptr_t fdeOffset) { + if (!record || !ehFrame || (fdeOffset == 0)) { + return false; + } + + const void* ptr = ehFrame + fdeOffset; + + return FIRCLSDwarfParseCFIFromFDERecord(record, ptr); +} + +#pragma mark - Properties +bool FIRCLSDwarfCIEIsValid(DWARFCIERecord* cie) { + if (!cie) { + return false; + } + + if (cie->length == 0) { + return false; + } + + if (cie->version != 1 && cie->version != 3) { + return false; + } + + return true; +} + +bool FIRCLSDwarfCIEHasAugmentationData(DWARFCIERecord* cie) { + if (!cie) { + return false; + } + + if (!cie->augmentation) { + return false; + } + + return cie->augmentation[0] == 'z'; +} + +#pragma mark - Instructions + +static bool FIRCLSDwarfParseAndExecute_set_loc(const void** cursor, + DWARFCIERecord* cieRecord, + intptr_t* codeOffset) { + uintptr_t operand = FIRCLSParseAddressWithEncodingAndAdvance(cursor, cieRecord->pointerEncoding); + + *codeOffset = operand; + + FIRCLSDwarfLog("DW_CFA_set_loc %lu\n", operand); + + return true; +} + +static bool FIRCLSDwarfParseAndExecute_advance_loc1(const void** cursor, + DWARFCIERecord* cieRecord, + intptr_t* codeOffset) { + int64_t offset = FIRCLSParseUint8AndAdvance(cursor) * cieRecord->codeAlignFactor; + + *codeOffset += offset; + + FIRCLSDwarfLog("DW_CFA_advance_loc1 %lld\n", offset); + + return true; +} + +static bool FIRCLSDwarfParseAndExecute_advance_loc2(const void** cursor, + DWARFCIERecord* cieRecord, + intptr_t* codeOffset) { + int64_t offset = FIRCLSParseUint16AndAdvance(cursor) * cieRecord->codeAlignFactor; + + *codeOffset += offset; + + FIRCLSDwarfLog("DW_CFA_advance_loc2 %lld\n", offset); + + return true; +} + +static bool FIRCLSDwarfParseAndExecute_advance_loc4(const void** cursor, + DWARFCIERecord* cieRecord, + intptr_t* codeOffset) { + int64_t offset = FIRCLSParseUint32AndAdvance(cursor) * cieRecord->codeAlignFactor; + + *codeOffset += offset; + + FIRCLSDwarfLog("DW_CFA_advance_loc4 %lld\n", offset); + + return true; +} + +static bool FIRCLSDwarfParseAndExecute_def_cfa(const void** cursor, + DWARFCIERecord* cieRecord, + FIRCLSDwarfState* state) { + uint64_t regNum = FIRCLSParseULEB128AndAdvance(cursor); + + if (regNum > CLS_DWARF_MAX_REGISTER_NUM) { + FIRCLSSDKLog("Error: Found an invalid DW_CFA_def_cfa register number\n"); + return false; + } + + int64_t offset = FIRCLSParseULEB128AndAdvance(cursor); + + state->cfaRegister = regNum; + state->cfaRegisterOffset = offset; + + FIRCLSDwarfLog("DW_CFA_def_cfa %llu, %lld\n", regNum, offset); + + return true; +} + +static bool FIRCLSDwarfParseAndExecute_def_cfa_register(const void** cursor, + DWARFCIERecord* cieRecord, + FIRCLSDwarfState* state) { + uint64_t regNum = FIRCLSParseULEB128AndAdvance(cursor); + + if (regNum > CLS_DWARF_MAX_REGISTER_NUM) { + FIRCLSSDKLog("Error: Found an invalid DW_CFA_def_cfa_register register number\n"); + return false; + } + + state->cfaRegister = regNum; + + FIRCLSDwarfLog("DW_CFA_def_cfa_register %llu\n", regNum); + + return true; +} + +static bool FIRCLSDwarfParseAndExecute_def_cfa_offset(const void** cursor, + DWARFCIERecord* cieRecord, + FIRCLSDwarfState* state) { + uint64_t offset = FIRCLSParseULEB128AndAdvance(cursor); + + state->cfaRegisterOffset = offset; + + FIRCLSDwarfLog("DW_CFA_def_cfa_offset %lld\n", offset); + + return true; +} + +static bool FIRCLSDwarfParseAndExecute_same_value(const void** cursor, + DWARFCIERecord* cieRecord, + FIRCLSDwarfState* state) { + uint64_t regNum = FIRCLSParseULEB128AndAdvance(cursor); + + if (regNum > CLS_DWARF_MAX_REGISTER_NUM) { + FIRCLSSDKLog("Error: Found an invalid DW_CFA_same_value register number\n"); + return false; + } + + state->registers[regNum].location = FIRCLSDwarfRegisterUnused; + + FIRCLSDwarfLog("DW_CFA_same_value %llu\n", regNum); + + return true; +} + +static bool FIRCLSDwarfParseAndExecute_register(const void** cursor, + DWARFCIERecord* cieRecord, + FIRCLSDwarfState* state) { + uint64_t regNum = FIRCLSParseULEB128AndAdvance(cursor); + + if (regNum > CLS_DWARF_MAX_REGISTER_NUM) { + FIRCLSSDKLog("Error: Found an invalid DW_CFA_register number\n"); + return false; + } + + uint64_t regValue = FIRCLSParseULEB128AndAdvance(cursor); + + if (regValue > CLS_DWARF_MAX_REGISTER_NUM) { + FIRCLSSDKLog("Error: Found an invalid DW_CFA_register value\n"); + return false; + } + + state->registers[regNum].location = FIRCLSDwarfRegisterInRegister; + state->registers[regNum].value = regValue; + + FIRCLSDwarfLog("DW_CFA_register %llu %llu\n", regNum, regValue); + + return true; +} + +static bool FIRCLSDwarfParseAndExecute_expression(const void** cursor, + DWARFCIERecord* cieRecord, + FIRCLSDwarfState* state) { + uint64_t regNum = FIRCLSParseULEB128AndAdvance(cursor); + + if (regNum > CLS_DWARF_MAX_REGISTER_NUM) { + FIRCLSSDKLog("Error: Found an invalid DW_CFA_expression register number\n"); + return false; + } + + state->registers[regNum].location = FIRCLSDwarfRegisterAtExpression; + state->registers[regNum].value = (uintptr_t)*cursor; + + // read the length of the expression, and advance past it + uint64_t length = FIRCLSParseULEB128AndAdvance(cursor); + *cursor += length; + + FIRCLSDwarfLog("DW_CFA_expression %llu %llu\n", regNum, length); + + return true; +} + +static bool FIRCLSDwarfParseAndExecute_val_expression(const void** cursor, + DWARFCIERecord* cieRecord, + FIRCLSDwarfState* state) { + uint64_t regNum = FIRCLSParseULEB128AndAdvance(cursor); + + if (regNum > CLS_DWARF_MAX_REGISTER_NUM) { + FIRCLSSDKLog("Error: Found an invalid DW_CFA_val_expression register number\n"); + return false; + } + + state->registers[regNum].location = FIRCLSDwarfRegisterIsExpression; + state->registers[regNum].value = (uintptr_t)*cursor; + + // read the length of the expression, and advance past it + uint64_t length = FIRCLSParseULEB128AndAdvance(cursor); + *cursor += length; + + FIRCLSDwarfLog("DW_CFA_val_expression %llu %llu\n", regNum, length); + + return true; +} + +static bool FIRCLSDwarfParseAndExecute_def_cfa_expression(const void** cursor, + DWARFCIERecord* cieRecord, + FIRCLSDwarfState* state) { + state->cfaRegister = CLS_DWARF_INVALID_REGISTER_NUM; + state->cfaExpression = *cursor; + + // read the length of the expression, and advance past it + uint64_t length = FIRCLSParseULEB128AndAdvance(cursor); + *cursor += length; + + FIRCLSDwarfLog("DW_CFA_def_cfa_expression %llu\n", length); + + return true; +} + +static bool FIRCLSDwarfParseAndExecute_offset(const void** cursor, + DWARFCIERecord* cieRecord, + FIRCLSDwarfState* state, + uint8_t regNum) { + if (regNum > CLS_DWARF_MAX_REGISTER_NUM) { + FIRCLSSDKLog("Error: Found an invalid DW_CFA_offset register number\n"); + return false; + } + + int64_t offset = FIRCLSParseULEB128AndAdvance(cursor) * cieRecord->dataAlignFactor; + + state->registers[regNum].location = FIRCLSDwarfRegisterInCFA; + state->registers[regNum].value = offset; + + FIRCLSDwarfLog("DW_CFA_offset %u, %lld\n", regNum, offset); + + return true; +} + +static bool FIRCLSDwarfParseAndExecute_advance_loc(const void** cursor, + DWARFCIERecord* cieRecord, + FIRCLSDwarfState* state, + uint8_t delta, + intptr_t* codeOffset) { + if (!FIRCLSIsValidPointer(codeOffset) || !FIRCLSIsValidPointer(cieRecord)) { + FIRCLSSDKLog("Error: invalid inputs\n"); + return false; + } + + *codeOffset = delta * (intptr_t)cieRecord->codeAlignFactor; + + FIRCLSDwarfLog("DW_CFA_advance_loc %u\n", delta); + + return true; +} + +static bool FIRCLSDwarfParseAndExecuteInstructionWithOperand(const void** cursor, + uint8_t instruction, + DWARFCIERecord* cieRecord, + FIRCLSDwarfState* state, + intptr_t* codeOffset) { + uint8_t operand = instruction & DW_CFA_OPERAND_MASK; + bool success = false; + + switch (instruction & DW_CFA_OPCODE_MASK) { + case DW_CFA_offset: + success = FIRCLSDwarfParseAndExecute_offset(cursor, cieRecord, state, operand); + break; + case DW_CFA_advance_loc: + success = + FIRCLSDwarfParseAndExecute_advance_loc(cursor, cieRecord, state, operand, codeOffset); + break; + case DW_CFA_restore: + FIRCLSSDKLog("Error: Unimplemented DWARF instruction with operand 0x%x\n", instruction); + break; + default: + FIRCLSSDKLog("Error: Unrecognized DWARF instruction 0x%x\n", instruction); + break; + } + + return success; +} + +#pragma mark - Expressions +static bool FIRCLSDwarfEvalulateExpression(const void* cursor, + const FIRCLSThreadContext* registers, + intptr_t stackValue, + intptr_t* result) { + FIRCLSDwarfLog("starting at %p with initial value %lx\n", cursor, stackValue); + + if (!FIRCLSIsValidPointer(cursor) || !FIRCLSIsValidPointer(result)) { + FIRCLSSDKLog("Error: inputs invalid\n"); + return false; + } + + FIRCLSDwarfExpressionMachine machine; + + if (!FIRCLSDwarfExpressionMachineInit(&machine, cursor, registers, stackValue)) { + FIRCLSSDKLog("Error: unable to init DWARF expression machine\n"); + return false; + } + + if (!FIRCLSDwarfExpressionMachinePrepareForExecution(&machine)) { + FIRCLSSDKLog("Error: unable to prepare for execution\n"); + return false; + } + + while (!FIRCLSDwarfExpressionMachineIsFinished(&machine)) { + if (!FIRCLSDwarfExpressionMachineExecuteNextOpcode(&machine)) { + FIRCLSSDKLog("Error: failed to execute DWARF machine opcode\n"); + return false; + } + } + + if (!FIRCLSDwarfExpressionMachineGetResult(&machine, result)) { + FIRCLSSDKLog("Error: failed to get DWARF expression result\n"); + return false; + } + + FIRCLSDwarfLog("successfully computed expression result\n"); + + return true; +} + +#pragma mark - Execution +bool FIRCLSDwarfInstructionsEnumerate(DWARFInstructions* instructions, + DWARFCIERecord* cieRecord, + FIRCLSDwarfState* state, + intptr_t pcOffset) { + if (!instructions || !cieRecord || !state) { + FIRCLSSDKLog("Error: inputs invalid\n"); + return false; + } + + // This is a little bit of state that can't be put into the state structure, because + // it is possible for instructions to push/pop state that does not affect this value. + intptr_t codeOffset = 0; + + const void* cursor = instructions->data; + const void* endAddress = cursor + instructions->length; + + FIRCLSDwarfLog("Running instructions from %p to %p\n", cursor, endAddress); + + // parse the instructions, as long as: + // - our data pointer is still in range + // - the pc offset is within the range of instructions that apply + + while ((cursor < endAddress) && (codeOffset < pcOffset)) { + uint8_t instruction = FIRCLSParseUint8AndAdvance(&cursor); + bool success = false; + + switch (instruction) { + case DW_CFA_nop: + FIRCLSDwarfLog("DW_CFA_nop\n"); + continue; + case DW_CFA_set_loc: + success = FIRCLSDwarfParseAndExecute_set_loc(&cursor, cieRecord, &codeOffset); + break; + case DW_CFA_advance_loc1: + success = FIRCLSDwarfParseAndExecute_advance_loc1(&cursor, cieRecord, &codeOffset); + break; + case DW_CFA_advance_loc2: + success = FIRCLSDwarfParseAndExecute_advance_loc2(&cursor, cieRecord, &codeOffset); + break; + case DW_CFA_advance_loc4: + success = FIRCLSDwarfParseAndExecute_advance_loc4(&cursor, cieRecord, &codeOffset); + break; + case DW_CFA_def_cfa: + success = FIRCLSDwarfParseAndExecute_def_cfa(&cursor, cieRecord, state); + break; + case DW_CFA_def_cfa_register: + success = FIRCLSDwarfParseAndExecute_def_cfa_register(&cursor, cieRecord, state); + break; + case DW_CFA_def_cfa_offset: + success = FIRCLSDwarfParseAndExecute_def_cfa_offset(&cursor, cieRecord, state); + break; + case DW_CFA_same_value: + success = FIRCLSDwarfParseAndExecute_same_value(&cursor, cieRecord, state); + break; + case DW_CFA_register: + success = FIRCLSDwarfParseAndExecute_register(&cursor, cieRecord, state); + break; + case DW_CFA_def_cfa_expression: + success = FIRCLSDwarfParseAndExecute_def_cfa_expression(&cursor, cieRecord, state); + break; + case DW_CFA_expression: + success = FIRCLSDwarfParseAndExecute_expression(&cursor, cieRecord, state); + break; + case DW_CFA_val_expression: + success = FIRCLSDwarfParseAndExecute_val_expression(&cursor, cieRecord, state); + break; + case DW_CFA_offset_extended: + case DW_CFA_restore_extended: + case DW_CFA_undefined: + case DW_CFA_remember_state: + case DW_CFA_restore_state: + case DW_CFA_offset_extended_sf: + case DW_CFA_def_cfa_sf: + case DW_CFA_def_cfa_offset_sf: + case DW_CFA_val_offset: + case DW_CFA_val_offset_sf: + case DW_CFA_GNU_window_save: + case DW_CFA_GNU_args_size: + case DW_CFA_GNU_negative_offset_extended: + FIRCLSSDKLog("Error: Unimplemented DWARF instruction 0x%x\n", instruction); + return false; + default: + success = FIRCLSDwarfParseAndExecuteInstructionWithOperand(&cursor, instruction, cieRecord, + state, &codeOffset); + break; + } + + if (!success) { + FIRCLSSDKLog("Error: Failed to execute dwarf instruction 0x%x\n", instruction); + return false; + } + } + + return true; +} + +bool FIRCLSDwarfUnwindComputeRegisters(FIRCLSDwarfCFIRecord* record, + FIRCLSThreadContext* registers) { + if (!record || !registers) { + return false; + } + + // We need to run the dwarf instructions to compute our register values. + // - initialize state + // - run the CIE instructions + // - run the FDE instructions + // - grab the values + + FIRCLSDwarfState state; + + memset(&state, 0, sizeof(FIRCLSDwarfState)); + + // We need to run all the instructions in the CIE record. So, pass in a large value for the pc + // offset so we don't stop early. + if (!FIRCLSDwarfInstructionsEnumerate(&record->cie.instructions, &record->cie, &state, + INTPTR_MAX)) { + FIRCLSSDKLog("Error: Unable to run CIE instructions\n"); + return false; + } + + intptr_t pcOffset = FIRCLSThreadContextGetPC(registers) - record->fde.startAddress; + if (pcOffset < 0) { + FIRCLSSDKLog("Error: The FDE pcOffset value cannot be negative\n"); + return false; + } + + if (!FIRCLSDwarfInstructionsEnumerate(&record->fde.instructions, &record->cie, &state, + pcOffset)) { + FIRCLSSDKLog("Error: Unable to run FDE instructions\n"); + return false; + } + + uintptr_t cfaRegister = 0; + + if (!FIRCLSDwarfGetCFA(&state, registers, &cfaRegister)) { + FIRCLSSDKLog("Error: failed to get CFA\n"); + return false; + } + + if (!FIRCLSDwarfUnwindAssignRegisters(&state, registers, cfaRegister, registers)) { + FIRCLSSDKLogError("Error: Unable to assign DWARF registers\n"); + return false; + } + + return true; +} + +bool FIRCLSDwarfUnwindAssignRegisters(const FIRCLSDwarfState* state, + const FIRCLSThreadContext* registers, + uintptr_t cfaRegister, + FIRCLSThreadContext* outputRegisters) { + if (!FIRCLSIsValidPointer(state) || !FIRCLSIsValidPointer(registers)) { + FIRCLSSDKLogError("Error: input invalid\n"); + return false; + } + + // make a copy, which we'll be changing + FIRCLSThreadContext newThreadState = *registers; + + // loop through all the registers, so we can set their values + for (size_t i = 0; i <= CLS_DWARF_MAX_REGISTER_NUM; ++i) { + if (state->registers[i].location == FIRCLSDwarfRegisterUnused) { + continue; + } + + const uintptr_t value = + FIRCLSDwarfGetSavedRegister(registers, cfaRegister, state->registers[i]); + + if (!FIRCLSDwarfUnwindSetRegisterValue(&newThreadState, i, value)) { + FIRCLSSDKLog("Error: Unable to restore register value\n"); + return false; + } + } + + if (!FIRCLSDwarfUnwindSetRegisterValue(&newThreadState, CLS_DWARF_REG_SP, cfaRegister)) { + FIRCLSSDKLog("Error: Unable to restore SP value\n"); + return false; + } + + // sanity-check that things have changed + if (FIRCLSDwarfCompareRegisters(registers, &newThreadState, CLS_DWARF_REG_SP)) { + FIRCLSSDKLog("Error: Stack pointer hasn't changed\n"); + return false; + } + + if (FIRCLSDwarfCompareRegisters(registers, &newThreadState, CLS_DWARF_REG_RETURN)) { + FIRCLSSDKLog("Error: PC hasn't changed\n"); + return false; + } + + // set our new value + *outputRegisters = newThreadState; + + return true; +} + +#pragma mark - Register Operations +bool FIRCLSDwarfCompareRegisters(const FIRCLSThreadContext* a, + const FIRCLSThreadContext* b, + uint64_t registerNum) { + return FIRCLSDwarfUnwindGetRegisterValue(a, registerNum) == + FIRCLSDwarfUnwindGetRegisterValue(b, registerNum); +} + +bool FIRCLSDwarfGetCFA(FIRCLSDwarfState* state, + const FIRCLSThreadContext* registers, + uintptr_t* cfa) { + if (!FIRCLSIsValidPointer(state) || !FIRCLSIsValidPointer(registers) || + !FIRCLSIsValidPointer(cfa)) { + FIRCLSSDKLog("Error: invalid input\n"); + return false; + } + + if (state->cfaExpression) { + if (!FIRCLSDwarfEvalulateExpression(state->cfaExpression, registers, 0, (intptr_t*)cfa)) { + FIRCLSSDKLog("Error: failed to compute CFA expression\n"); + return false; + } + + return true; + } + + // libunwind checks that cfaRegister is not zero. This seems like a potential bug - why couldn't + // it be zero? + + *cfa = FIRCLSDwarfUnwindGetRegisterValue(registers, state->cfaRegister) + + (uintptr_t)state->cfaRegisterOffset; + + return true; +} + +uintptr_t FIRCLSDwarfGetSavedRegister(const FIRCLSThreadContext* registers, + uintptr_t cfaRegister, + FIRCLSDwarfRegister dRegister) { + intptr_t result = 0; + + FIRCLSDwarfLog("Getting register %x\n", dRegister.location); + + switch (dRegister.location) { + case FIRCLSDwarfRegisterInCFA: { + const uintptr_t address = cfaRegister + (uintptr_t)dRegister.value; + + if (!FIRCLSReadMemory(address, &result, sizeof(result))) { + FIRCLSSDKLog("Error: Unable to read CFA value\n"); + return 0; + } + } + return result; + case FIRCLSDwarfRegisterInRegister: + return FIRCLSDwarfUnwindGetRegisterValue(registers, dRegister.value); + case FIRCLSDwarfRegisterOffsetFromCFA: + FIRCLSSDKLog("Error: OffsetFromCFA unhandled\n"); + break; + case FIRCLSDwarfRegisterAtExpression: + if (!FIRCLSDwarfEvalulateExpression((void*)dRegister.value, registers, cfaRegister, + &result)) { + FIRCLSSDKLog("Error: unable to evaluate expression\n"); + return 0; + } + + if (!FIRCLSReadMemory(result, &result, sizeof(result))) { + FIRCLSSDKLog("Error: Unable to read memory computed from expression\n"); + return 0; + } + + return result; + case FIRCLSDwarfRegisterIsExpression: + if (!FIRCLSDwarfEvalulateExpression((void*)dRegister.value, registers, cfaRegister, + &result)) { + FIRCLSSDKLog("Error: unable to evaluate expression\n"); + return 0; + } + + return result; + default: + FIRCLSSDKLog("Error: Unrecognized register save location 0x%x\n", dRegister.location); + break; + } + + return 0; +} + +#if DEBUG +#pragma mark - Debugging +void FIRCLSCFIRecordShow(FIRCLSDwarfCFIRecord* record) { + if (!record) { + FIRCLSSDKLog("Error: CFI record: null\n"); + return; + } + + FIRCLSCIERecordShow(&record->cie); + FIRCLSFDERecordShow(&record->fde, &record->cie); +} + +void FIRCLSCIERecordShow(DWARFCIERecord* record) { + if (!record) { + FIRCLSSDKLog("Error: CIE: null\n"); + return; + } + + FIRCLSSDKLog("CIE:\n"); + FIRCLSSDKLog(" length: %llu\n", record->length); + FIRCLSSDKLog(" version: %u\n", record->version); + FIRCLSSDKLog(" augmentation: %s\n", record->augmentation); + FIRCLSSDKLog(" EH Data: 0x%04lx\n", record->ehData); + FIRCLSSDKLog("LSDA encoding: 0x%02x\n", record->lsdaEncoding); + FIRCLSSDKLog(" personality: 0x%lx\n", record->personalityFunction); + + FIRCLSDwarfPointerEncodingShow(" encoding", record->pointerEncoding); + FIRCLSDwarfPointerEncodingShow(" P encoding", record->personalityEncoding); + + FIRCLSSDKLog(" code align: %llu\n", record->codeAlignFactor); + FIRCLSSDKLog(" data align: %lld\n", record->dataAlignFactor); + FIRCLSSDKLog(" RA register: %llu\n", record->returnAddressRegister); + + FIRCLSDwarfInstructionsShow(&record->instructions, record); +} + +void FIRCLSFDERecordShow(DWARFFDERecord* record, DWARFCIERecord* cie) { + if (!record) { + FIRCLSSDKLog("FDE: null\n"); + return; + } + + FIRCLSSDKLog("FDE:\n"); + FIRCLSSDKLog(" length: %llu\n", record->length); + FIRCLSSDKLog(" CIE offset: %llu\n", record->cieOffset); + FIRCLSSDKLog(" start addr: 0x%lx\n", record->startAddress); + FIRCLSSDKLog(" range: %lu\n", record->rangeSize); + + FIRCLSDwarfInstructionsShow(&record->instructions, cie); +} + +void FIRCLSDwarfPointerEncodingShow(const char* leadString, uint8_t encoding) { + if (encoding == DW_EH_PE_omit) { + FIRCLSSDKLog("%s: 0x%02x (omit)\n", leadString, encoding); + } else { + const char* peValue = ""; + const char* peOffset = ""; + + switch (encoding & DW_EH_PE_VALUE_MASK) { + case DW_EH_PE_absptr: + peValue = "DW_EH_PE_absptr"; + break; + case DW_EH_PE_uleb128: + peValue = "DW_EH_PE_uleb128"; + break; + case DW_EH_PE_udata2: + peValue = "DW_EH_PE_udata2"; + break; + case DW_EH_PE_udata4: + peValue = "DW_EH_PE_udata4"; + break; + case DW_EH_PE_udata8: + peValue = "DW_EH_PE_udata8"; + break; + case DW_EH_PE_signed: + peValue = "DW_EH_PE_signed"; + break; + case DW_EH_PE_sleb128: + peValue = "DW_EH_PE_sleb128"; + break; + case DW_EH_PE_sdata2: + peValue = "DW_EH_PE_sdata2"; + break; + case DW_EH_PE_sdata4: + peValue = "DW_EH_PE_sdata4"; + break; + case DW_EH_PE_sdata8: + peValue = "DW_EH_PE_sdata8"; + break; + default: + peValue = "unknown"; + break; + } + + switch (encoding & DW_EH_PE_RELATIVE_OFFSET_MASK) { + case DW_EH_PE_absptr: + break; + case DW_EH_PE_pcrel: + peOffset = " + DW_EH_PE_pcrel"; + break; + case DW_EH_PE_textrel: + peOffset = " + DW_EH_PE_textrel"; + break; + case DW_EH_PE_datarel: + peOffset = " + DW_EH_PE_datarel"; + break; + case DW_EH_PE_funcrel: + peOffset = " + DW_EH_PE_funcrel"; + break; + case DW_EH_PE_aligned: + peOffset = " + DW_EH_PE_aligned"; + break; + case DW_EH_PE_indirect: + peOffset = " + DW_EH_PE_indirect"; + break; + default: + break; + } + + FIRCLSSDKLog("%s: 0x%02x (%s%s)\n", leadString, encoding, peValue, peOffset); + } +} + +void FIRCLSDwarfInstructionsShow(DWARFInstructions* instructions, DWARFCIERecord* cie) { + if (!instructions) { + FIRCLSSDKLog("Error: Instructions null\n"); + } + + FIRCLSDwarfState state; + + memset(&state, 0, sizeof(FIRCLSDwarfState)); + + FIRCLSDwarfInstructionsEnumerate(instructions, cie, &state, -1); +} + +#endif + +#else +INJECT_STRIP_SYMBOL(dwarf_unwind) +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDwarfUnwind.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDwarfUnwind.h new file mode 100644 index 0000000..c7307fd --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDwarfUnwind.h @@ -0,0 +1,138 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "FIRCLSDwarfUnwindRegisters.h" +#include "FIRCLSThreadState.h" + +#include +#include +#include + +#if CLS_DWARF_UNWINDING_SUPPORTED + +#pragma mark Structures +typedef struct { + uint32_t length; + const void* data; +} DWARFInstructions; + +typedef struct { + uint64_t length; + uint8_t version; + uintptr_t ehData; // 8 bytes for 64-bit architectures, 4 bytes for 32 + const char* augmentation; + uint8_t pointerEncoding; + uint8_t lsdaEncoding; + uint8_t personalityEncoding; + uintptr_t personalityFunction; + uint64_t codeAlignFactor; + int64_t dataAlignFactor; + uint64_t returnAddressRegister; // is 64 bits enough for this value? + bool signalFrame; + + DWARFInstructions instructions; +} DWARFCIERecord; + +typedef struct { + uint64_t length; + uint64_t cieOffset; // also an arch-specific size + uintptr_t startAddress; + uintptr_t rangeSize; + + DWARFInstructions instructions; +} DWARFFDERecord; + +typedef struct { + DWARFCIERecord cie; + DWARFFDERecord fde; +} FIRCLSDwarfCFIRecord; + +typedef enum { + FIRCLSDwarfRegisterUnused = 0, + FIRCLSDwarfRegisterInCFA, + FIRCLSDwarfRegisterOffsetFromCFA, + FIRCLSDwarfRegisterInRegister, + FIRCLSDwarfRegisterAtExpression, + FIRCLSDwarfRegisterIsExpression +} FIRCLSDwarfRegisterLocation; + +typedef struct { + FIRCLSDwarfRegisterLocation location; + uint64_t value; +} FIRCLSDwarfRegister; + +typedef struct { + uint64_t cfaRegister; + int64_t cfaRegisterOffset; + const void* cfaExpression; + uint32_t spArgSize; + + FIRCLSDwarfRegister registers[CLS_DWARF_MAX_REGISTER_NUM + 1]; +} FIRCLSDwarfState; + +__BEGIN_DECLS + +#pragma mark - Parsing +bool FIRCLSDwarfParseCIERecord(DWARFCIERecord* cie, const void* ptr); +bool FIRCLSDwarfParseFDERecord(DWARFFDERecord* fdeRecord, + bool parseCIE, + DWARFCIERecord* cieRecord, + const void* ptr); +bool FIRCLSDwarfParseCFIFromFDERecord(FIRCLSDwarfCFIRecord* record, const void* ptr); +bool FIRCLSDwarfParseCFIFromFDERecordOffset(FIRCLSDwarfCFIRecord* record, + const void* ehFrame, + uintptr_t fdeOffset); + +#pragma mark - Properties +bool FIRCLSDwarfCIEIsValid(DWARFCIERecord* cie); +bool FIRCLSDwarfCIEHasAugmentationData(DWARFCIERecord* cie); + +#pragma mark - Execution +bool FIRCLSDwarfInstructionsEnumerate(DWARFInstructions* instructions, + DWARFCIERecord* cieRecord, + FIRCLSDwarfState* state, + intptr_t pcOffset); +bool FIRCLSDwarfUnwindComputeRegisters(FIRCLSDwarfCFIRecord* record, + FIRCLSThreadContext* registers); +bool FIRCLSDwarfUnwindAssignRegisters(const FIRCLSDwarfState* state, + const FIRCLSThreadContext* registers, + uintptr_t cfaRegister, + FIRCLSThreadContext* outputRegisters); + +#pragma mark - Register Operations +bool FIRCLSDwarfCompareRegisters(const FIRCLSThreadContext* a, + const FIRCLSThreadContext* b, + uint64_t registerNum); + +bool FIRCLSDwarfGetCFA(FIRCLSDwarfState* state, + const FIRCLSThreadContext* registers, + uintptr_t* cfa); +uintptr_t FIRCLSDwarfGetSavedRegister(const FIRCLSThreadContext* registers, + uintptr_t cfaRegister, + FIRCLSDwarfRegister dRegister); + +#if DEBUG +#pragma mark - Debugging +void FIRCLSCFIRecordShow(FIRCLSDwarfCFIRecord* record); +void FIRCLSCIERecordShow(DWARFCIERecord* record); +void FIRCLSFDERecordShow(DWARFFDERecord* record, DWARFCIERecord* cie); +void FIRCLSDwarfPointerEncodingShow(const char* leadString, uint8_t encoding); +void FIRCLSDwarfInstructionsShow(DWARFInstructions* instructions, DWARFCIERecord* cie); +#endif + +__END_DECLS + +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDwarfUnwindRegisters.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDwarfUnwindRegisters.h new file mode 100644 index 0000000..1e37396 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDwarfUnwindRegisters.h @@ -0,0 +1,152 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +#include "FIRCLSFeatures.h" + +#if CLS_CPU_X86_64 +enum { + CLS_DWARF_X86_64_RAX = 0, + CLS_DWARF_X86_64_RDX = 1, + CLS_DWARF_X86_64_RCX = 2, + CLS_DWARF_X86_64_RBX = 3, + CLS_DWARF_X86_64_RSI = 4, + CLS_DWARF_X86_64_RDI = 5, + CLS_DWARF_X86_64_RBP = 6, + CLS_DWARF_X86_64_RSP = 7, + CLS_DWARF_X86_64_R8 = 8, + CLS_DWARF_X86_64_R9 = 9, + CLS_DWARF_X86_64_R10 = 10, + CLS_DWARF_X86_64_R11 = 11, + CLS_DWARF_X86_64_R12 = 12, + CLS_DWARF_X86_64_R13 = 13, + CLS_DWARF_X86_64_R14 = 14, + CLS_DWARF_X86_64_R15 = 15, + + CLS_DWARF_X86_64_RET_ADDR = 16 +}; + +#define CLS_DWARF_REG_RETURN CLS_DWARF_X86_64_RET_ADDR +#define CLS_DWARF_REG_SP CLS_DWARF_X86_64_RSP +#define CLS_DWARF_REG_FP CLS_DWARF_X86_64_RBP + +#define CLS_DWARF_MAX_REGISTER_NUM (CLS_DWARF_X86_64_RET_ADDR) + +#elif CLS_CPU_I386 + +enum { + CLS_DWARF_X86_EAX = 0, + CLS_DWARF_X86_ECX = 1, + CLS_DWARF_X86_EDX = 2, + CLS_DWARF_X86_EBX = 3, + CLS_DWARF_X86_EBP = 4, + CLS_DWARF_X86_ESP = 5, + CLS_DWARF_X86_ESI = 6, + CLS_DWARF_X86_EDI = 7, + + CLS_DWARF_X86_RET_ADDR = 8 +}; + +#define CLS_DWARF_REG_RETURN CLS_DWARF_X86_RET_ADDR +#define CLS_DWARF_REG_SP CLS_DWARF_X86_ESP +#define CLS_DWARF_REG_FP CLS_DWARF_X86_EBP + +#define CLS_DWARF_MAX_REGISTER_NUM (CLS_DWARF_X86_RET_ADDR) + +#elif CLS_CPU_ARM64 + +// 64-bit ARM64 registers +enum { + CLS_DWARF_ARM64_X0 = 0, + CLS_DWARF_ARM64_X1 = 1, + CLS_DWARF_ARM64_X2 = 2, + CLS_DWARF_ARM64_X3 = 3, + CLS_DWARF_ARM64_X4 = 4, + CLS_DWARF_ARM64_X5 = 5, + CLS_DWARF_ARM64_X6 = 6, + CLS_DWARF_ARM64_X7 = 7, + CLS_DWARF_ARM64_X8 = 8, + CLS_DWARF_ARM64_X9 = 9, + CLS_DWARF_ARM64_X10 = 10, + CLS_DWARF_ARM64_X11 = 11, + CLS_DWARF_ARM64_X12 = 12, + CLS_DWARF_ARM64_X13 = 13, + CLS_DWARF_ARM64_X14 = 14, + CLS_DWARF_ARM64_X15 = 15, + CLS_DWARF_ARM64_X16 = 16, + CLS_DWARF_ARM64_X17 = 17, + CLS_DWARF_ARM64_X18 = 18, + CLS_DWARF_ARM64_X19 = 19, + CLS_DWARF_ARM64_X20 = 20, + CLS_DWARF_ARM64_X21 = 21, + CLS_DWARF_ARM64_X22 = 22, + CLS_DWARF_ARM64_X23 = 23, + CLS_DWARF_ARM64_X24 = 24, + CLS_DWARF_ARM64_X25 = 25, + CLS_DWARF_ARM64_X26 = 26, + CLS_DWARF_ARM64_X27 = 27, + CLS_DWARF_ARM64_X28 = 28, + CLS_DWARF_ARM64_X29 = 29, + CLS_DWARF_ARM64_FP = 29, + CLS_DWARF_ARM64_X30 = 30, + CLS_DWARF_ARM64_LR = 30, + CLS_DWARF_ARM64_X31 = 31, + CLS_DWARF_ARM64_SP = 31, + // reserved block + CLS_DWARF_ARM64_D0 = 64, + CLS_DWARF_ARM64_D1 = 65, + CLS_DWARF_ARM64_D2 = 66, + CLS_DWARF_ARM64_D3 = 67, + CLS_DWARF_ARM64_D4 = 68, + CLS_DWARF_ARM64_D5 = 69, + CLS_DWARF_ARM64_D6 = 70, + CLS_DWARF_ARM64_D7 = 71, + CLS_DWARF_ARM64_D8 = 72, + CLS_DWARF_ARM64_D9 = 73, + CLS_DWARF_ARM64_D10 = 74, + CLS_DWARF_ARM64_D11 = 75, + CLS_DWARF_ARM64_D12 = 76, + CLS_DWARF_ARM64_D13 = 77, + CLS_DWARF_ARM64_D14 = 78, + CLS_DWARF_ARM64_D15 = 79, + CLS_DWARF_ARM64_D16 = 80, + CLS_DWARF_ARM64_D17 = 81, + CLS_DWARF_ARM64_D18 = 82, + CLS_DWARF_ARM64_D19 = 83, + CLS_DWARF_ARM64_D20 = 84, + CLS_DWARF_ARM64_D21 = 85, + CLS_DWARF_ARM64_D22 = 86, + CLS_DWARF_ARM64_D23 = 87, + CLS_DWARF_ARM64_D24 = 88, + CLS_DWARF_ARM64_D25 = 89, + CLS_DWARF_ARM64_D26 = 90, + CLS_DWARF_ARM64_D27 = 91, + CLS_DWARF_ARM64_D28 = 92, + CLS_DWARF_ARM64_D29 = 93, + CLS_DWARF_ARM64_D30 = 94, + CLS_DWARF_ARM64_D31 = 95 +}; + +#define CLS_DWARF_MAX_REGISTER_NUM (CLS_DWARF_ARM64_SP) + +#define CLS_DWARF_REG_RETURN CLS_DWARF_ARM64_LR +#define CLS_DWARF_REG_SP CLS_DWARF_ARM64_SP +#define CLS_DWARF_REG_FP CLS_DWARF_ARM64_FP + +#endif + +#define CLS_DWARF_INVALID_REGISTER_NUM (CLS_DWARF_MAX_REGISTER_NUM + 1) diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind.c b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind.c new file mode 100644 index 0000000..7961071 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind.c @@ -0,0 +1,319 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FIRCLSUnwind.h" +#include "FIRCLSBinaryImage.h" +#include "FIRCLSCompactUnwind.h" +#include "FIRCLSFeatures.h" +#include "FIRCLSGlobals.h" +#include "FIRCLSUtility.h" + +#include +#include +#include + +// Without a limit on the number of frames we unwind, there's a real possibility +// we'll get stuck in an infinite loop. But, we still need pretty big limits, +// because stacks can get quite big. Also, the stacks are different on the platforms. +// These values were empirically determined (~525000 on OS X, ~65000 on iOS). +#if TARGET_OS_EMBEDDED +const uint32_t FIRCLSUnwindMaxFrames = 100000; +#else +const uint32_t FIRCLSUnwindMaxFrames = 600000; +#endif + +const uint32_t FIRCLSUnwindInfiniteRecursionCountThreshold = 10; + +#pragma mark Prototypes +static bool FIRCLSUnwindNextFrameUsingAllStrategies(FIRCLSUnwindContext* context); +#if CLS_COMPACT_UNWINDING_SUPPORTED +static bool FIRCLSUnwindWithCompactUnwindInfo(FIRCLSUnwindContext* context); +#endif +bool FIRCLSUnwindContextHasValidPCAndSP(FIRCLSUnwindContext* context); + +#pragma mark - API +bool FIRCLSUnwindInit(FIRCLSUnwindContext* context, FIRCLSThreadContext threadContext) { + if (!context) { + return false; + } + + memset(context, 0, sizeof(FIRCLSUnwindContext)); + + context->registers = threadContext; + + return true; +} + +bool FIRCLSUnwindNextFrame(FIRCLSUnwindContext* context) { + if (!FIRCLSIsValidPointer(context)) { + FIRCLSSDKLog("Error: invalid inputs\n"); + return false; + } + + if (!FIRCLSUnwindContextHasValidPCAndSP(context)) { + // This is a special-case. It is possible to try to unwind a thread that has no stack (ie, is + // executing zero functions. I believe this happens when a thread has exited, but before the + // kernel has actually cleaned it up. This situation can only apply to the first frame. So, in + // that case, we don't count it as an error. But, if it happens mid-unwind, it's a problem. + + if (context->frameCount == 0) { + FIRCLSSDKLog("Cancelling unwind for thread with invalid PC/SP\n"); + } else { + FIRCLSSDKLog("Error: thread PC/SP invalid before unwind\n"); + } + + return false; + } + + if (!FIRCLSUnwindNextFrameUsingAllStrategies(context)) { + FIRCLSSDKLogError("Failed to advance to the next frame\n"); + return false; + } + + uintptr_t pc = FIRCLSUnwindGetPC(context); + uintptr_t sp = FIRCLSUnwindGetStackPointer(context); + + // Unwinding will complete when this is no longer a valid value + if (!FIRCLSIsValidPointer(pc)) { + return false; + } + + // after unwinding, validate that we have a sane register value + if (!FIRCLSIsValidPointer(sp)) { + FIRCLSSDKLog("Error: SP (%p) isn't a valid pointer\n", (void*)sp); + return false; + } + + // track repeating frames + if (context->lastFramePC == pc) { + context->repeatCount += 1; + } else { + context->repeatCount = 0; + } + + context->frameCount += 1; + context->lastFramePC = pc; + + return true; +} + +#pragma mark - Register Accessors +uintptr_t FIRCLSUnwindGetPC(FIRCLSUnwindContext* context) { + if (!FIRCLSIsValidPointer(context)) { + return 0; + } + + return FIRCLSThreadContextGetPC(&context->registers); +} + +uintptr_t FIRCLSUnwindGetStackPointer(FIRCLSUnwindContext* context) { + if (!FIRCLSIsValidPointer(context)) { + return 0; + } + + return FIRCLSThreadContextGetStackPointer(&context->registers); +} + +static uintptr_t FIRCLSUnwindGetFramePointer(FIRCLSUnwindContext* context) { + if (!FIRCLSIsValidPointer(context)) { + return 0; + } + + return FIRCLSThreadContextGetFramePointer(&context->registers); +} + +uint32_t FIRCLSUnwindGetFrameRepeatCount(FIRCLSUnwindContext* context) { + if (!FIRCLSIsValidPointer(context)) { + return 0; + } + + return context->repeatCount; +} + +#pragma mark - Unwind Strategies +static bool FIRCLSUnwindNextFrameUsingAllStrategies(FIRCLSUnwindContext* context) { + if (!FIRCLSIsValidPointer(context)) { + FIRCLSSDKLogError("Arguments invalid\n"); + return false; + } + + if (context->frameCount >= FIRCLSUnwindMaxFrames) { + FIRCLSSDKLogWarn("Exceeded maximum number of frames\n"); + return false; + } + + uintptr_t pc = FIRCLSUnwindGetPC(context); + + // Ok, what's going on here? libunwind's UnwindCursor::setInfoBasedOnIPRegister has a + // parameter that, if true, does this subtraction. Despite the comments in the code + // (of 35.1), I found that the parameter was almost always set to true. + // + // I then ran into a problem when unwinding from _pthread_start -> thread_start. This + // is a common transition, which happens in pretty much every report. An extra frame + // was being generated, because the PC we get for _pthread_start was mapping to exactly + // one greater than the function's last byte, according to the compact unwind info. This + // resulted in using the wrong compact encoding, and picking the next function, which + // turned out to be dwarf instead of a frame pointer. + + // So, the moral is - do the subtraction for all frames except the first. I haven't found + // a case where it produces an incorrect result. Also note that at first, I thought this would + // subtract one from the final addresses too. But, the end of this function will *compute* PC, + // so this value is used only to look up unwinding data. + + if (context->frameCount > 0) { + --pc; + if (!FIRCLSThreadContextSetPC(&context->registers, pc)) { + FIRCLSSDKLogError("Unable to set PC\n"); + return false; + } + } + + if (!FIRCLSIsValidPointer(pc)) { + FIRCLSSDKLogError("PC is invalid\n"); + return false; + } + + // the first frame is special - as the registers we need + // are already loaded by definition + if (context->frameCount == 0) { + return true; + } + +#if CLS_COMPACT_UNWINDING_SUPPORTED + // attempt to advance to the next frame using compact unwinding, and + // only fall back to the frame pointer if that fails + if (FIRCLSUnwindWithCompactUnwindInfo(context)) { + return true; + } +#endif + + // If the frame pointer is zero, we cannot use an FP-based unwind and we can reasonably + // assume that we've just gotten to the end of the stack. + if (FIRCLSUnwindGetFramePointer(context) == 0) { + FIRCLSSDKLogWarn("FP is zero, aborting unwind\n"); + // make sure to set the PC to zero, to indicate the unwind is complete + return FIRCLSThreadContextSetPC(&context->registers, 0); + } + + // Only allow stack scanning (as a last resort) if we're on the first frame. All others + // are too likely to screw up. + if (FIRCLSUnwindWithFramePointer(&context->registers, context->frameCount == 1)) { + return true; + } + + FIRCLSSDKLogError("Unable to use frame pointer\n"); + + return false; +} + +#if CLS_COMPACT_UNWINDING_SUPPORTED +static bool FIRCLSUnwindWithCompactUnwindInfo(FIRCLSUnwindContext* context) { + if (!context) { + return false; + } + + // step one - find the image the current pc is within + FIRCLSBinaryImageRuntimeNode image; + + uintptr_t pc = FIRCLSUnwindGetPC(context); + + if (!FIRCLSBinaryImageSafeFindImageForAddress(pc, &image)) { + FIRCLSSDKLogWarn("Unable to find binary for %p\n", (void*)pc); + return false; + } + +#if CLS_BINARY_IMAGE_RUNTIME_NODE_RECORD_NAME + FIRCLSSDKLogDebug("Binary image for %p at %p => %s\n", (void*)pc, image.baseAddress, image.name); +#else + FIRCLSSDKLogDebug("Binary image for %p at %p\n", (void*)pc, image.baseAddress); +#endif + + if (!FIRCLSBinaryImageSafeHasUnwindInfo(&image)) { + FIRCLSSDKLogInfo("Binary image at %p has no unwind info\n", image.baseAddress); + return false; + } + + if (!FIRCLSCompactUnwindInit(&context->compactUnwindState, image.unwindInfo, image.ehFrame, + (uintptr_t)image.baseAddress)) { + FIRCLSSDKLogError("Unable to read unwind info\n"); + return false; + } + + // this function will actually attempt to find compact unwind info for the current PC, + // and use it to mutate the context register state + return FIRCLSCompactUnwindLookupAndCompute(&context->compactUnwindState, &context->registers); +} +#endif + +#pragma mark - Utility Functions +bool FIRCLSUnwindContextHasValidPCAndSP(FIRCLSUnwindContext* context) { + return FIRCLSIsValidPointer(FIRCLSUnwindGetPC(context)) && + FIRCLSIsValidPointer(FIRCLSUnwindGetStackPointer(context)); +} + +#if CLS_CPU_64BIT +#define BASIC_INFO_TYPE vm_region_basic_info_64_t +#define BASIC_INFO VM_REGION_BASIC_INFO_64 +#define BASIC_INFO_COUNT VM_REGION_BASIC_INFO_COUNT_64 +#define vm_region_query_fn vm_region_64 +#else +#define BASIC_INFO_TYPE vm_region_basic_info_t +#define BASIC_INFO VM_REGION_BASIC_INFO +#define BASIC_INFO_COUNT VM_REGION_BASIC_INFO_COUNT +#define vm_region_query_fn vm_region +#endif +bool FIRCLSUnwindIsAddressExecutable(vm_address_t address) { +#if CLS_COMPACT_UNWINDING_SUPPORTED + FIRCLSBinaryImageRuntimeNode unusedNode; + + return FIRCLSBinaryImageSafeFindImageForAddress(address, &unusedNode); +#else + return true; +#endif +} + +bool FIRCLSUnwindFirstExecutableAddress(vm_address_t start, + vm_address_t end, + vm_address_t* foundAddress) { + // This function walks up the data on the stack, looking for the first value that is an address on + // an exectuable page. This is a heurestic, and can hit false positives. + + *foundAddress = 0; // write in a 0 + + do { + vm_address_t address; + + FIRCLSSDKLogDebug("Checking address %p => %p\n", (void*)start, (void*)*(uintptr_t*)start); + + // if start isn't a valid pointer, don't even bother trying + if (FIRCLSIsValidPointer(start)) { + if (!FIRCLSReadMemory(start, &address, sizeof(void*))) { + // if we fail to read from the stack, we're done + return false; + } + + FIRCLSSDKLogDebug("Checking for executable %p\n", (void*)address); + // when we find an exectuable address, we're finished + if (FIRCLSUnwindIsAddressExecutable(address)) { + *foundAddress = address; + return true; + } + } + + start += sizeof(void*); // move back up the stack + + } while (start < end); + + return false; +} diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind.h new file mode 100644 index 0000000..08b23ea --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind.h @@ -0,0 +1,53 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "FIRCLSThreadState.h" +#include "FIRCLSUtility.h" +#if CLS_COMPACT_UNWINDING_SUPPORTED +#include "FIRCLSCompactUnwind.h" +#endif +#include "FIRCLSUnwind_arch.h" + +#include +#include + +extern const uint32_t FIRCLSUnwindMaxFrames; + +extern const uint32_t FIRCLSUnwindInfiniteRecursionCountThreshold; + +typedef struct { + FIRCLSThreadContext registers; + uint32_t frameCount; +#if CLS_COMPACT_UNWINDING_SUPPORTED + FIRCLSCompactUnwindContext compactUnwindState; +#endif + uintptr_t lastFramePC; + uint32_t repeatCount; +} FIRCLSUnwindContext; + +// API +bool FIRCLSUnwindInit(FIRCLSUnwindContext* context, FIRCLSThreadContext threadContext); + +bool FIRCLSUnwindNextFrame(FIRCLSUnwindContext* context); +uintptr_t FIRCLSUnwindGetPC(FIRCLSUnwindContext* context); +uintptr_t FIRCLSUnwindGetStackPointer(FIRCLSUnwindContext* context); +uint32_t FIRCLSUnwindGetFrameRepeatCount(FIRCLSUnwindContext* context); + +// utility functions +bool FIRCLSUnwindIsAddressExecutable(vm_address_t address); +bool FIRCLSUnwindFirstExecutableAddress(vm_address_t start, + vm_address_t end, + vm_address_t* foundAddress); diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind_arch.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind_arch.h new file mode 100644 index 0000000..dc5e296 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind_arch.h @@ -0,0 +1,33 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "FIRCLSFeatures.h" + +#include "FIRCLSThreadState.h" +#if CLS_COMPACT_UNWINDING_SUPPORTED +#include "FIRCLSCompactUnwind.h" +#endif + +bool FIRCLSUnwindWithFramePointer(FIRCLSThreadContext *registers, bool allowScanning); +uintptr_t FIRCLSUnwindStackPointerFromFramePointer(uintptr_t framePtr); + +#if CLS_DWARF_UNWINDING_SUPPORTED +uintptr_t FIRCLSCompactUnwindDwarfOffset(compact_unwind_encoding_t encoding); +bool FIRCLSDwarfUnwindSetRegisterValue(FIRCLSThreadContext *registers, + uint64_t num, + uintptr_t value); +uintptr_t FIRCLSDwarfUnwindGetRegisterValue(const FIRCLSThreadContext *registers, uint64_t num); +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind_arm.c b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind_arm.c new file mode 100644 index 0000000..ead1a29 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind_arm.c @@ -0,0 +1,313 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FIRCLSCompactUnwind.h" +#include "FIRCLSCompactUnwind_Private.h" +#include "FIRCLSDefines.h" +#include "FIRCLSDwarfUnwind.h" +#include "FIRCLSFeatures.h" +#include "FIRCLSUnwind.h" +#include "FIRCLSUnwind_arch.h" +#include "FIRCLSUtility.h" + +#if CLS_CPU_ARM || CLS_CPU_ARM64 + +static bool FIRCLSUnwindWithLRRegister(FIRCLSThreadContext* registers) { + if (!FIRCLSIsValidPointer(registers)) { + return false; + } + + // Return address is in LR, SP is pointing to the next frame. + uintptr_t value = FIRCLSThreadContextGetLinkRegister(registers); + + if (!FIRCLSIsValidPointer(value)) { + FIRCLSSDKLog("Error: LR value is invalid\n"); + return false; + } + + return FIRCLSThreadContextSetPC(registers, value); +} + +bool FIRCLSUnwindWithFramePointer(FIRCLSThreadContext* registers, bool allowScanning) { + if (allowScanning) { + // The LR register does have the return address here, but there are situations where + // this can produce false matches. Better backend rules can fix this up in many cases. + if (FIRCLSUnwindWithLRRegister(registers)) { + return true; + } else { + // In this case, we're unable to use the LR. We don't want to just stop unwinding, so + // proceed with the normal, non-scanning path + FIRCLSSDKLog("Unable to use LR, skipping\n"); + } + } + + // read the values from the stack + const uintptr_t framePointer = FIRCLSThreadContextGetFramePointer(registers); + uintptr_t stack[2]; + + if (!FIRCLSReadMemory((vm_address_t)framePointer, stack, sizeof(stack))) { + // unable to read the first stack frame + FIRCLSSDKLog("Error: failed to read memory at address %p\n", (void*)framePointer); + return false; + } + + if (!FIRCLSThreadContextSetPC(registers, stack[1])) { + return false; + } + + if (!FIRCLSThreadContextSetFramePointer(registers, stack[0])) { + return false; + } + + if (!FIRCLSThreadContextSetStackPointer(registers, + FIRCLSUnwindStackPointerFromFramePointer(framePointer))) { + return false; + } + + return true; +} + +uintptr_t FIRCLSUnwindStackPointerFromFramePointer(uintptr_t framePtr) { + // the stack pointer is the frame pointer plus the two saved pointers for the frame + return framePtr + 2 * sizeof(void*); +} + +#if CLS_COMPACT_UNWINDING_SUPPORTED +bool FIRCLSCompactUnwindComputeRegisters(FIRCLSCompactUnwindContext* context, + FIRCLSCompactUnwindResult* result, + FIRCLSThreadContext* registers) { + if (!context || !result || !registers) { + return false; + } + + // Note that compact_uwnind_encoding.h has a few bugs in it prior to iOS 8.0. + // Only refer to the >= 8.0 header. + switch (result->encoding & UNWIND_ARM64_MODE_MASK) { + case UNWIND_ARM64_MODE_FRAMELESS: + // Interestingly, we also know the size of the stack frame, by + // using UNWIND_ARM64_FRAMELESS_STACK_SIZE_MASK. Is that useful? + return FIRCLSUnwindWithLRRegister(registers); + break; + case UNWIND_ARM64_MODE_DWARF: + return FIRCLSCompactUnwindDwarfFrame( + context, result->encoding & UNWIND_ARM64_DWARF_SECTION_OFFSET, registers); + break; + case UNWIND_ARM64_MODE_FRAME: + return FIRCLSUnwindWithFramePointer(registers, false); + default: + FIRCLSSDKLog("Invalid encoding 0x%x\n", result->encoding); + break; + } + + return false; +} +#endif + +#if CLS_DWARF_UNWINDING_SUPPORTED +uintptr_t FIRCLSDwarfUnwindGetRegisterValue(const FIRCLSThreadContext* registers, uint64_t num) { + switch (num) { + case CLS_DWARF_ARM64_X0: + return registers->__ss.__x[0]; + case CLS_DWARF_ARM64_X1: + return registers->__ss.__x[1]; + case CLS_DWARF_ARM64_X2: + return registers->__ss.__x[2]; + case CLS_DWARF_ARM64_X3: + return registers->__ss.__x[3]; + case CLS_DWARF_ARM64_X4: + return registers->__ss.__x[4]; + case CLS_DWARF_ARM64_X5: + return registers->__ss.__x[5]; + case CLS_DWARF_ARM64_X6: + return registers->__ss.__x[6]; + case CLS_DWARF_ARM64_X7: + return registers->__ss.__x[7]; + case CLS_DWARF_ARM64_X8: + return registers->__ss.__x[8]; + case CLS_DWARF_ARM64_X9: + return registers->__ss.__x[9]; + case CLS_DWARF_ARM64_X10: + return registers->__ss.__x[10]; + case CLS_DWARF_ARM64_X11: + return registers->__ss.__x[11]; + case CLS_DWARF_ARM64_X12: + return registers->__ss.__x[12]; + case CLS_DWARF_ARM64_X13: + return registers->__ss.__x[13]; + case CLS_DWARF_ARM64_X14: + return registers->__ss.__x[14]; + case CLS_DWARF_ARM64_X15: + return registers->__ss.__x[15]; + case CLS_DWARF_ARM64_X16: + return registers->__ss.__x[16]; + case CLS_DWARF_ARM64_X17: + return registers->__ss.__x[17]; + case CLS_DWARF_ARM64_X18: + return registers->__ss.__x[18]; + case CLS_DWARF_ARM64_X19: + return registers->__ss.__x[19]; + case CLS_DWARF_ARM64_X20: + return registers->__ss.__x[20]; + case CLS_DWARF_ARM64_X21: + return registers->__ss.__x[21]; + case CLS_DWARF_ARM64_X22: + return registers->__ss.__x[22]; + case CLS_DWARF_ARM64_X23: + return registers->__ss.__x[23]; + case CLS_DWARF_ARM64_X24: + return registers->__ss.__x[24]; + case CLS_DWARF_ARM64_X25: + return registers->__ss.__x[25]; + case CLS_DWARF_ARM64_X26: + return registers->__ss.__x[26]; + case CLS_DWARF_ARM64_X27: + return registers->__ss.__x[27]; + case CLS_DWARF_ARM64_X28: + return registers->__ss.__x[28]; + case CLS_DWARF_ARM64_FP: + return FIRCLSThreadContextGetFramePointer(registers); + case CLS_DWARF_ARM64_LR: + return FIRCLSThreadContextGetLinkRegister(registers); + case CLS_DWARF_ARM64_SP: + return FIRCLSThreadContextGetStackPointer(registers); + default: + break; + } + + FIRCLSSDKLog("Error: Unrecognized get register number %llu\n", num); + + return 0; +} + +bool FIRCLSDwarfUnwindSetRegisterValue(FIRCLSThreadContext* registers, + uint64_t num, + uintptr_t value) { + switch (num) { + case CLS_DWARF_ARM64_X0: + registers->__ss.__x[0] = value; + return true; + case CLS_DWARF_ARM64_X1: + registers->__ss.__x[1] = value; + return true; + case CLS_DWARF_ARM64_X2: + registers->__ss.__x[2] = value; + return true; + case CLS_DWARF_ARM64_X3: + registers->__ss.__x[3] = value; + return true; + case CLS_DWARF_ARM64_X4: + registers->__ss.__x[4] = value; + return true; + case CLS_DWARF_ARM64_X5: + registers->__ss.__x[5] = value; + return true; + case CLS_DWARF_ARM64_X6: + registers->__ss.__x[6] = value; + return true; + case CLS_DWARF_ARM64_X7: + registers->__ss.__x[7] = value; + return true; + case CLS_DWARF_ARM64_X8: + registers->__ss.__x[8] = value; + return true; + case CLS_DWARF_ARM64_X9: + registers->__ss.__x[9] = value; + return true; + case CLS_DWARF_ARM64_X10: + registers->__ss.__x[10] = value; + return true; + case CLS_DWARF_ARM64_X11: + registers->__ss.__x[11] = value; + return true; + case CLS_DWARF_ARM64_X12: + registers->__ss.__x[12] = value; + return true; + case CLS_DWARF_ARM64_X13: + registers->__ss.__x[13] = value; + return true; + case CLS_DWARF_ARM64_X14: + registers->__ss.__x[14] = value; + return true; + case CLS_DWARF_ARM64_X15: + registers->__ss.__x[15] = value; + return true; + case CLS_DWARF_ARM64_X16: + registers->__ss.__x[16] = value; + return true; + case CLS_DWARF_ARM64_X17: + registers->__ss.__x[17] = value; + return true; + case CLS_DWARF_ARM64_X18: + registers->__ss.__x[18] = value; + return true; + case CLS_DWARF_ARM64_X19: + registers->__ss.__x[19] = value; + return true; + case CLS_DWARF_ARM64_X20: + registers->__ss.__x[20] = value; + return true; + case CLS_DWARF_ARM64_X21: + registers->__ss.__x[21] = value; + return true; + case CLS_DWARF_ARM64_X22: + registers->__ss.__x[22] = value; + return true; + case CLS_DWARF_ARM64_X23: + registers->__ss.__x[23] = value; + return true; + case CLS_DWARF_ARM64_X24: + registers->__ss.__x[24] = value; + return true; + case CLS_DWARF_ARM64_X25: + registers->__ss.__x[25] = value; + return true; + case CLS_DWARF_ARM64_X26: + registers->__ss.__x[26] = value; + return true; + case CLS_DWARF_ARM64_X27: + registers->__ss.__x[27] = value; + return true; + case CLS_DWARF_ARM64_X28: + registers->__ss.__x[28] = value; + return true; + case CLS_DWARF_ARM64_FP: + FIRCLSThreadContextSetFramePointer(registers, value); + return true; + case CLS_DWARF_ARM64_SP: + FIRCLSThreadContextSetStackPointer(registers, value); + return true; + case CLS_DWARF_ARM64_LR: + // Here's what's going on. For x86, the "return register" is virtual. The architecture + // doesn't actually have one, but DWARF does have the concept. So, when the system + // tries to set the return register, we set the PC. You can see this behavior + // in the FIRCLSDwarfUnwindSetRegisterValue implemenation for that architecture. In the + // case of ARM64, the register is real. So, we have to be extra careful to make sure + // we update the PC here. Otherwise, when a DWARF unwind completes, it won't have + // changed the PC to the right value. + FIRCLSThreadContextSetLinkRegister(registers, value); + FIRCLSThreadContextSetPC(registers, value); + return true; + default: + break; + } + + FIRCLSSDKLog("Unrecognized set register number %llu\n", num); + + return false; +} +#endif + +#else +INJECT_STRIP_SYMBOL(unwind_arm) +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind_x86.c b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind_x86.c new file mode 100644 index 0000000..cc0655a --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind_x86.c @@ -0,0 +1,537 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FIRCLSUnwind_x86.h" +#include "FIRCLSCompactUnwind_Private.h" +#include "FIRCLSDefines.h" +#include "FIRCLSDwarfUnwind.h" +#include "FIRCLSFeatures.h" +#include "FIRCLSUnwind.h" +#include "FIRCLSUnwind_arch.h" +#include "FIRCLSUtility.h" + +#if CLS_CPU_X86 + +static bool FIRCLSCompactUnwindBPFrame(compact_unwind_encoding_t encoding, + FIRCLSThreadContext* registers); +static bool FIRCLSCompactUnwindFrameless(compact_unwind_encoding_t encoding, + FIRCLSThreadContext* registers, + uintptr_t functionStart, + bool indirect); + +#if CLS_COMPACT_UNWINDING_SUPPORTED +bool FIRCLSCompactUnwindComputeRegisters(FIRCLSCompactUnwindContext* context, + FIRCLSCompactUnwindResult* result, + FIRCLSThreadContext* registers) { + if (!FIRCLSIsValidPointer(context) || !FIRCLSIsValidPointer(result) || + !FIRCLSIsValidPointer(registers)) { + FIRCLSSDKLogError("invalid inputs\n"); + return false; + } + + FIRCLSSDKLogDebug("Computing registers for encoding %x\n", result->encoding); + + switch (result->encoding & CLS_X86_MODE_MASK) { + case CLS_X86_MODE_BP_FRAME: + return FIRCLSCompactUnwindBPFrame(result->encoding, registers); + case CLS_X86_MODE_STACK_IMMD: + return FIRCLSCompactUnwindFrameless(result->encoding, registers, result->functionStart, + false); + case CLS_X86_MODE_STACK_IND: + return FIRCLSCompactUnwindFrameless(result->encoding, registers, result->functionStart, true); + case CLS_X86_MODE_DWARF: + return FIRCLSCompactUnwindDwarfFrame(context, result->encoding & CLS_X86_DWARF_SECTION_OFFSET, + registers); + default: + FIRCLSSDKLogError("Invalid encoding %x\n", result->encoding); + break; + } + + return false; +} +#endif + +static bool FIRCLSCompactUnwindBPFrame(compact_unwind_encoding_t encoding, + FIRCLSThreadContext* registers) { + // this is the plain-vanilla frame pointer process + + // uint32_t offset = GET_BITS_WITH_MASK(encoding, UNWIND_X86_EBP_FRAME_OFFSET); + // uint32_t locations = GET_BITS_WITH_MASK(encoding, UNWIND_X86_64_RBP_FRAME_REGISTERS); + + // TODO: pretty sure we do need to restore registers here, so that if a subsequent frame needs + // these results, they will be correct + + // Checkout CompactUnwinder.hpp in libunwind for how to do this. Since we don't make use of any of + // those registers for a stacktrace only, there's nothing we need do with them. + + // read the values from the stack + const uintptr_t framePointer = FIRCLSThreadContextGetFramePointer(registers); + uintptr_t stack[2]; + + if (!FIRCLSReadMemory((vm_address_t)framePointer, stack, sizeof(stack))) { + // unable to read the first stack frame + FIRCLSSDKLog("Error: failed to read memory at address %p\n", (void*)framePointer); + return false; + } + + if (!FIRCLSThreadContextSetPC(registers, stack[1])) { + return false; + } + + if (!FIRCLSThreadContextSetFramePointer(registers, stack[0])) { + return false; + } + + if (!FIRCLSThreadContextSetStackPointer(registers, + FIRCLSUnwindStackPointerFromFramePointer(framePointer))) { + return false; + } + + return true; +} + +bool FIRCLSUnwindWithStackScanning(FIRCLSThreadContext* registers) { + vm_address_t start = (vm_address_t)FIRCLSThreadContextGetStackPointer(registers); + vm_address_t end = (vm_address_t)FIRCLSThreadContextGetFramePointer(registers); + + uintptr_t newPC = 0; + + if (!FIRCLSUnwindFirstExecutableAddress(start, end, (vm_address_t*)&newPC)) { + return false; + } + + return FIRCLSThreadContextSetPC(registers, newPC); +} + +bool FIRCLSUnwindWithFramePointer(FIRCLSThreadContext* registers, bool allowScanning) { + // Here's an interesting case. We've just processed the first frame, and it did + // not have any unwind info. If that first function did not allocate + // a stack frame, we'll "skip" the caller. This might sound unlikely, but it actually + // happens a lot in practice. + + // Sooo, one thing we can do is try to stack the stack for things that look like return + // addresses. Normally, this technique will hit many false positives. But, if we do it + // only for the second frame, and only when we don't have other unwind info available. + + if (allowScanning) { + FIRCLSSDKLogInfo("Attempting stack scan\n"); + if (FIRCLSUnwindWithStackScanning(registers)) { + FIRCLSSDKLogInfo("Stack scan successful\n"); + return true; + } + } + + // If we ever do anything else with the encoding, we need to be sure + // to set it up right. + return FIRCLSCompactUnwindBPFrame(CLS_X86_MODE_BP_FRAME, registers); +} + +uintptr_t FIRCLSUnwindStackPointerFromFramePointer(uintptr_t framePtr) { + // the stack pointer is the frame pointer plus the two saved pointers for the frame + return framePtr + 2 * sizeof(void*); +} + +#if CLS_COMPACT_UNWINDING_SUPPORTED || CLS_DWARF_UNWINDING_SUPPORTED +uintptr_t FIRCLSDwarfUnwindGetRegisterValue(const FIRCLSThreadContext* registers, uint64_t num) { + switch (num) { +#if CLS_CPU_X86_64 + case CLS_DWARF_X86_64_RAX: + return registers->__ss.__rax; + case CLS_DWARF_X86_64_RDX: + return registers->__ss.__rdx; + case CLS_DWARF_X86_64_RCX: + return registers->__ss.__rcx; + case CLS_DWARF_X86_64_RBX: + return registers->__ss.__rbx; + case CLS_DWARF_X86_64_RSI: + return registers->__ss.__rsi; + case CLS_DWARF_X86_64_RDI: + return registers->__ss.__rdi; + case CLS_DWARF_X86_64_RBP: + return registers->__ss.__rbp; + case CLS_DWARF_X86_64_RSP: + return registers->__ss.__rsp; + case CLS_DWARF_X86_64_R8: + return registers->__ss.__r8; + case CLS_DWARF_X86_64_R9: + return registers->__ss.__r9; + case CLS_DWARF_X86_64_R10: + return registers->__ss.__r10; + case CLS_DWARF_X86_64_R11: + return registers->__ss.__r11; + case CLS_DWARF_X86_64_R12: + return registers->__ss.__r12; + case CLS_DWARF_X86_64_R13: + return registers->__ss.__r13; + case CLS_DWARF_X86_64_R14: + return registers->__ss.__r14; + case CLS_DWARF_X86_64_R15: + return registers->__ss.__r15; + case CLS_DWARF_X86_64_RET_ADDR: + return registers->__ss.__rip; +#elif CLS_CPU_I386 + case CLS_DWARF_X86_EAX: + return registers->__ss.__eax; + case CLS_DWARF_X86_ECX: + return registers->__ss.__ecx; + case CLS_DWARF_X86_EDX: + return registers->__ss.__edx; + case CLS_DWARF_X86_EBX: + return registers->__ss.__ebx; + case CLS_DWARF_X86_EBP: + return registers->__ss.__ebp; + case CLS_DWARF_X86_ESP: + return registers->__ss.__esp; + case CLS_DWARF_X86_ESI: + return registers->__ss.__esi; + case CLS_DWARF_X86_EDI: + return registers->__ss.__edi; + case CLS_DWARF_X86_RET_ADDR: + return registers->__ss.__eip; +#endif + default: + break; + } + + FIRCLSSDKLog("Error: Unrecognized get register number %llu\n", num); + + return 0; +} + +bool FIRCLSDwarfUnwindSetRegisterValue(FIRCLSThreadContext* registers, + uint64_t num, + uintptr_t value) { + switch (num) { +#if CLS_CPU_X86_64 + case CLS_DWARF_X86_64_RAX: + registers->__ss.__rax = value; + return true; + case CLS_DWARF_X86_64_RDX: + registers->__ss.__rdx = value; + return true; + case CLS_DWARF_X86_64_RCX: + registers->__ss.__rcx = value; + return true; + case CLS_DWARF_X86_64_RBX: + registers->__ss.__rbx = value; + return true; + case CLS_DWARF_X86_64_RSI: + registers->__ss.__rsi = value; + return true; + case CLS_DWARF_X86_64_RDI: + registers->__ss.__rdi = value; + return true; + case CLS_DWARF_X86_64_RBP: + registers->__ss.__rbp = value; + return true; + case CLS_DWARF_X86_64_RSP: + registers->__ss.__rsp = value; + return true; + case CLS_DWARF_X86_64_R8: + registers->__ss.__r8 = value; + return true; + case CLS_DWARF_X86_64_R9: + registers->__ss.__r9 = value; + return true; + case CLS_DWARF_X86_64_R10: + registers->__ss.__r10 = value; + return true; + case CLS_DWARF_X86_64_R11: + registers->__ss.__r11 = value; + return true; + case CLS_DWARF_X86_64_R12: + registers->__ss.__r12 = value; + return true; + case CLS_DWARF_X86_64_R13: + registers->__ss.__r13 = value; + return true; + case CLS_DWARF_X86_64_R14: + registers->__ss.__r14 = value; + return true; + case CLS_DWARF_X86_64_R15: + registers->__ss.__r15 = value; + return true; + case CLS_DWARF_X86_64_RET_ADDR: + registers->__ss.__rip = value; + return true; +#elif CLS_CPU_I386 + case CLS_DWARF_X86_EAX: + registers->__ss.__eax = value; + return true; + case CLS_DWARF_X86_ECX: + registers->__ss.__ecx = value; + return true; + case CLS_DWARF_X86_EDX: + registers->__ss.__edx = value; + return true; + case CLS_DWARF_X86_EBX: + registers->__ss.__ebx = value; + return true; + case CLS_DWARF_X86_EBP: + registers->__ss.__ebp = value; + return true; + case CLS_DWARF_X86_ESP: + registers->__ss.__esp = value; + return true; + case CLS_DWARF_X86_ESI: + registers->__ss.__esi = value; + return true; + case CLS_DWARF_X86_EDI: + registers->__ss.__edi = value; + return true; + case CLS_DWARF_X86_RET_ADDR: + registers->__ss.__eip = value; + return true; +#endif + default: + break; + } + + FIRCLSSDKLog("Unrecognized set register number %llu\n", num); + + return false; +} +#endif + +#if CLS_COMPACT_UNWINDING_SUPPORTED +bool FIRCLSCompactUnwindComputeStackSize(const compact_unwind_encoding_t encoding, + const uintptr_t functionStart, + const bool indirect, + uint32_t* const stackSize) { + if (!FIRCLSIsValidPointer(stackSize)) { + FIRCLSSDKLog("Error: invalid inputs\n"); + return false; + } + + const uint32_t stackSizeEncoded = GET_BITS_WITH_MASK(encoding, CLS_X86_FRAMELESS_STACK_SIZE); + + if (!indirect) { + *stackSize = stackSizeEncoded * sizeof(void*); + return true; + } + + const vm_address_t sublAddress = functionStart + stackSizeEncoded; + uint32_t sublValue = 0; + + if (!FIRCLSReadMemory(sublAddress, &sublValue, sizeof(uint32_t))) { + FIRCLSSDKLog("Error: unable to read subl value\n"); + return false; + } + + const uint32_t stackAdjust = GET_BITS_WITH_MASK(encoding, CLS_X86_FRAMELESS_STACK_ADJUST); + + *stackSize = sublValue + stackAdjust * sizeof(void*); + + return true; +} + +bool FIRCLSCompactUnwindDecompressPermutation(const compact_unwind_encoding_t encoding, + uintptr_t permutatedRegisters[const static 6]) { + const uint32_t regCount = GET_BITS_WITH_MASK(encoding, CLS_X86_FRAMELESS_STACK_REG_COUNT); + uint32_t permutation = GET_BITS_WITH_MASK(encoding, CLS_X86_FRAMELESS_STACK_REG_PERMUTATION); + + switch (regCount) { + case 6: + permutatedRegisters[0] = permutation / 120; + permutation -= (permutatedRegisters[0] * 120); + permutatedRegisters[1] = permutation / 24; + permutation -= (permutatedRegisters[1] * 24); + permutatedRegisters[2] = permutation / 6; + permutation -= (permutatedRegisters[2] * 6); + permutatedRegisters[3] = permutation / 2; + permutation -= (permutatedRegisters[3] * 2); + permutatedRegisters[4] = permutation; + permutatedRegisters[5] = 0; + break; + case 5: + permutatedRegisters[0] = permutation / 120; + permutation -= (permutatedRegisters[0] * 120); + permutatedRegisters[1] = permutation / 24; + permutation -= (permutatedRegisters[1] * 24); + permutatedRegisters[2] = permutation / 6; + permutation -= (permutatedRegisters[2] * 6); + permutatedRegisters[3] = permutation / 2; + permutation -= (permutatedRegisters[3] * 2); + permutatedRegisters[4] = permutation; + break; + case 4: + permutatedRegisters[0] = permutation / 60; + permutation -= (permutatedRegisters[0] * 60); + permutatedRegisters[1] = permutation / 12; + permutation -= (permutatedRegisters[1] * 12); + permutatedRegisters[2] = permutation / 3; + permutation -= (permutatedRegisters[2] * 3); + permutatedRegisters[3] = permutation; + break; + case 3: + permutatedRegisters[0] = permutation / 20; + permutation -= (permutatedRegisters[0] * 20); + permutatedRegisters[1] = permutation / 4; + permutation -= (permutatedRegisters[1] * 4); + permutatedRegisters[2] = permutation; + break; + case 2: + permutatedRegisters[0] = permutation / 5; + permutation -= (permutatedRegisters[0] * 5); + permutatedRegisters[1] = permutation; + break; + case 1: + permutatedRegisters[0] = permutation; + break; + case 0: + break; + default: + FIRCLSSDKLog("Error: unhandled number of register permutations for encoding %x\n", encoding); + return false; + } + + return true; +} + +bool FIRCLSCompactUnwindRemapRegisters(const compact_unwind_encoding_t encoding, + uintptr_t permutatedRegisters[const static 6], + uintptr_t savedRegisters[const static 6]) { + const uint32_t regCount = GET_BITS_WITH_MASK(encoding, CLS_X86_FRAMELESS_STACK_REG_COUNT); + + if (regCount > 6) { + FIRCLSSDKLog("Error: invalid register number count %d\n", regCount); + return false; + } + + // Re-number the registers + + // You are probably wondering, what the hell is this algorithm even doing? It is + // taken from libunwind's implemenation that does the same thing. + bool used[7] = {false, false, false, false, false, false, false}; + for (uint32_t i = 0; i < regCount; ++i) { + int renum = 0; + for (int u = 1; u < 7; ++u) { + if (!used[u]) { + if (renum == permutatedRegisters[i]) { + savedRegisters[i] = u; + used[u] = true; + break; + } + ++renum; + } + } + } + + return true; +} + +bool FIRCLSCompactUnwindRestoreRegisters(compact_unwind_encoding_t encoding, + FIRCLSThreadContext* registers, + uint32_t stackSize, + const uintptr_t savedRegisters[const static 6], + uintptr_t* address) { + if (!FIRCLSIsValidPointer(registers) || !FIRCLSIsValidPointer(address)) { + FIRCLSSDKLog("Error: invalid inputs\n"); + return false; + } + + const uint32_t regCount = GET_BITS_WITH_MASK(encoding, CLS_X86_FRAMELESS_STACK_REG_COUNT); + + // compute initial address of saved registers + *address = FIRCLSThreadContextGetStackPointer(registers) + stackSize - sizeof(void*) - + sizeof(void*) * regCount; + uintptr_t value = 0; + + for (uint32_t i = 0; i < regCount; ++i) { + value = 0; + + switch (savedRegisters[i]) { + case CLS_X86_REG_RBP: + if (!FIRCLSReadMemory((vm_address_t)*address, (void*)&value, sizeof(uintptr_t))) { + FIRCLSSDKLog("Error: unable to read memory to set register\n"); + return false; + } + + if (!FIRCLSThreadContextSetFramePointer(registers, value)) { + FIRCLSSDKLog("Error: unable to set FP\n"); + return false; + } + break; + default: + // here, we are restoring a register we don't need for unwinding + FIRCLSSDKLog("Error: skipping a restore of register %d at %p\n", (int)savedRegisters[i], + (void*)*address); + break; + } + + *address += sizeof(void*); + } + + return true; +} + +static bool FIRCLSCompactUnwindFrameless(compact_unwind_encoding_t encoding, + FIRCLSThreadContext* registers, + uintptr_t functionStart, + bool indirect) { + FIRCLSSDKLog("Frameless unwind encountered with encoding %x\n", encoding); + + uint32_t stackSize = 0; + if (!FIRCLSCompactUnwindComputeStackSize(encoding, functionStart, indirect, &stackSize)) { + FIRCLSSDKLog("Error: unable to compute stack size for encoding %x\n", encoding); + return false; + } + + uintptr_t permutatedRegisters[6]; + + memset(permutatedRegisters, 0, sizeof(permutatedRegisters)); + if (!FIRCLSCompactUnwindDecompressPermutation(encoding, permutatedRegisters)) { + FIRCLSSDKLog("Error: unable to decompress registers %x\n", encoding); + return false; + } + + uintptr_t savedRegisters[6]; + + memset(savedRegisters, 0, sizeof(savedRegisters)); + if (!FIRCLSCompactUnwindRemapRegisters(encoding, permutatedRegisters, savedRegisters)) { + FIRCLSSDKLog("Error: unable to remap registers %x\n", encoding); + return false; + } + + uintptr_t address = 0; + + if (!FIRCLSCompactUnwindRestoreRegisters(encoding, registers, stackSize, savedRegisters, + &address)) { + FIRCLSSDKLog("Error: unable to restore registers\n"); + return false; + } + + FIRCLSSDKLog("SP is %p and we are reading %p\n", + (void*)FIRCLSThreadContextGetStackPointer(registers), (void*)address); + // read the value from the stack, now that we know the address to read + uintptr_t value = 0; + if (!FIRCLSReadMemory((vm_address_t)address, (void*)&value, sizeof(uintptr_t))) { + FIRCLSSDKLog("Error: unable to read memory to set register\n"); + return false; + } + + FIRCLSSDKLog("Read PC to be %p\n", (void*)value); + if (!FIRCLSIsValidPointer(value)) { + FIRCLSSDKLog("Error: computed PC is invalid\n"); + return false; + } + + return FIRCLSThreadContextSetPC(registers, value) && + FIRCLSThreadContextSetStackPointer(registers, address + sizeof(void*)); +} +#endif + +#else +INJECT_STRIP_SYMBOL(unwind_x86) +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind_x86.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind_x86.h new file mode 100644 index 0000000..7c8010e --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind_x86.h @@ -0,0 +1,76 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "FIRCLSCompactUnwind.h" +#include "FIRCLSFeatures.h" + +// Add some abstraction to compact unwinding, because compact +// unwinding is nearly identical between 32 and 64 bit +#if CLS_CPU_X86_64 + +#define CLS_X86_MODE_MASK UNWIND_X86_64_MODE_MASK +#define CLS_X86_MODE_BP_FRAME UNWIND_X86_64_MODE_RBP_FRAME +#define CLS_X86_MODE_STACK_IMMD UNWIND_X86_64_MODE_STACK_IMMD +#define CLS_X86_MODE_STACK_IND UNWIND_X86_64_MODE_STACK_IND +#define CLS_X86_MODE_DWARF UNWIND_X86_64_MODE_DWARF + +#define CLS_X86_BP_FRAME_REGISTERS UNWIND_X86_64_RBP_FRAME_REGISTERS +#define CLS_X86_BP_FRAME_OFFSET UNWIND_X86_64_RBP_FRAME_OFFSET + +#define CLS_X86_FRAMELESS_STACK_SIZE UNWIND_X86_64_FRAMELESS_STACK_SIZE +#define CLS_X86_FRAMELESS_STACK_ADJUST UNWIND_X86_64_FRAMELESS_STACK_ADJUST +#define CLS_X86_FRAMELESS_STACK_REG_COUNT UNWIND_X86_64_FRAMELESS_STACK_REG_COUNT +#define CLS_X86_FRAMELESS_STACK_REG_PERMUTATION UNWIND_X86_64_FRAMELESS_STACK_REG_PERMUTATION + +#define CLS_X86_DWARF_SECTION_OFFSET UNWIND_X86_64_DWARF_SECTION_OFFSET + +#define CLS_X86_REG_RBP UNWIND_X86_64_REG_RBP + +#else + +#define CLS_X86_MODE_MASK UNWIND_X86_MODE_MASK +#define CLS_X86_MODE_BP_FRAME UNWIND_X86_MODE_EBP_FRAME +#define CLS_X86_MODE_STACK_IMMD UNWIND_X86_MODE_STACK_IMMD +#define CLS_X86_MODE_STACK_IND UNWIND_X86_MODE_STACK_IND +#define CLS_X86_MODE_DWARF UNWIND_X86_MODE_DWARF + +#define CLS_X86_BP_FRAME_REGISTERS UNWIND_X86_RBP_FRAME_REGISTERS +#define CLS_X86_BP_FRAME_OFFSET UNWIND_X86_RBP_FRAME_OFFSET + +#define CLS_X86_FRAMELESS_STACK_SIZE UNWIND_X86_FRAMELESS_STACK_SIZE +#define CLS_X86_FRAMELESS_STACK_ADJUST UNWIND_X86_FRAMELESS_STACK_ADJUST +#define CLS_X86_FRAMELESS_STACK_REG_COUNT UNWIND_X86_FRAMELESS_STACK_REG_COUNT +#define CLS_X86_FRAMELESS_STACK_REG_PERMUTATION UNWIND_X86_FRAMELESS_STACK_REG_PERMUTATION + +#define CLS_X86_DWARF_SECTION_OFFSET UNWIND_X86_DWARF_SECTION_OFFSET + +#define CLS_X86_REG_RBP UNWIND_X86_REG_EBP + +#endif + +#if CLS_COMPACT_UNWINDING_SUPPORTED +bool FIRCLSCompactUnwindComputeStackSize(const compact_unwind_encoding_t encoding, + const uintptr_t functionStart, + const bool indirect, + uint32_t* const stackSize); +bool FIRCLSCompactUnwindDecompressPermutation(const compact_unwind_encoding_t encoding, + uintptr_t permutatedRegisters[const static 6]); +bool FIRCLSCompactUnwindRestoreRegisters(compact_unwind_encoding_t encoding, + FIRCLSThreadContext* registers, + uint32_t stackSize, + const uintptr_t savedRegisters[const static 6], + uintptr_t* address); +#endif diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/LICENSE b/!main project/Pods/FirebaseCrashlytics/Crashlytics/LICENSE new file mode 100644 index 0000000..925bc57 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/LICENSE @@ -0,0 +1,230 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +================================================================================ + +The following copyright from Hewlett-Packard Development Company, L.P. +applies to the dwarf.h file in third_party/libunwind + + libunwind - a platform-independent unwind library + Copyright (c) 2003-2005 Hewlett-Packard Development Company, L.P. + Contributed by David Mosberger-Tang + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/README.md b/!main project/Pods/FirebaseCrashlytics/Crashlytics/README.md new file mode 100644 index 0000000..a3c0c46 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/README.md @@ -0,0 +1,27 @@ +# Firebase Crashlytics SDK + +## Development + +Follow the subsequent instructions to develop, debug, unit test, and +integration test FirebaseCrashlytics: + +### Prereqs + +- At least CocoaPods 1.6.0 +- Install [cocoapods-generate](https://github.com/square/cocoapods-generate) + +### To Develop + +- Run `pod gen FirebaseCrashlytics.podspec` +- `open gen/FirebaseCrashlytics/FirebaseCrashlytics.xcworkspace` + +OR these two commands can be combined with + +- `pod gen FirebaseCrashlytics.podspec --auto-open --gen-directory="gen" --clean` + +You're now in an Xcode workspace generate for building, debugging and +testing the FirebaseCrashlytics CocoaPod. + +### Running Unit Tests + +Open the generated workspace, choose the FirebaseCrashlytics-Unit-unit scheme and press Command-u. diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSByteUtility.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSByteUtility.h new file mode 100644 index 0000000..ca2abb8 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSByteUtility.h @@ -0,0 +1,41 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +/** + * Returns a SHA1 Hash of the input NSData + */ +NSString *FIRCLSHashNSData(NSData *data); +/** + * Returns a SHA256 Hash of the input NSData + */ +NSString *FIRCLS256HashNSData(NSData *data); +/** + * Returns a SHA1 Hash of the input bytes + */ +NSString *FIRCLSHashBytes(const void *bytes, size_t length); +/** + * Populates a Hex value conversion of value into outputBuffer. + * If value is nil, then outputBuffer is not modified. + */ +void FIRCLSSafeHexToString(const uint8_t *value, size_t length, char *outputBuffer); + +/** + * Iterates through the raw bytes of NSData in a way that is similar to + * -[NSData enumerateByteRangesUsingBlock:], but is safe to call from older + * OSes that do not support it. + */ +void FIRCLSEnumerateByteRangesOfNSDataUsingBlock( + NSData *data, void (^block)(const void *bytes, NSRange byteRange, BOOL *stop)); diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSByteUtility.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSByteUtility.m new file mode 100644 index 0000000..79f46f3 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSByteUtility.m @@ -0,0 +1,120 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSByteUtility.h" + +#import +#import + +#pragma mark Private functions + +static const char FIRCLSHexMap[] = "0123456789abcdef"; + +void FIRCLSHexFromByte(uint8_t c, char output[]) { + if (!output) { + return; + } + + output[0] = FIRCLSHexMap[c >> 4]; + output[1] = FIRCLSHexMap[c & 0x0f]; +} + +void FIRCLSSafeHexToString(const uint8_t *value, size_t length, char *outputBuffer) { + if (!outputBuffer) { + return; + } + + memset(outputBuffer, 0, (length * 2) + 1); + + if (!value) { + return; + } + + for (size_t i = 0; i < length; ++i) { + uint8_t c = value[i]; + + FIRCLSHexFromByte(c, &outputBuffer[i * 2]); + } +} + +NSString *FIRCLSNSDataPrettyDescription(NSData *data) { + NSString *string; + char *buffer; + size_t size; + NSUInteger length; + + // we need 2 hex char for every byte of data, plus one more spot for a + // null terminator + length = data.length; + size = (length * 2) + 1; + buffer = malloc(sizeof(char) * size); + + if (!buffer) { + return nil; + } + + FIRCLSSafeHexToString(data.bytes, length, buffer); + + string = [NSString stringWithUTF8String:buffer]; + + free(buffer); + + return string; +} + +#pragma mark Public functions + +NSString *FIRCLSHashBytes(const void *bytes, size_t length) { + uint8_t digest[CC_SHA1_DIGEST_LENGTH] = {0}; + CC_SHA1(bytes, (CC_LONG)length, digest); + + NSData *result = [NSData dataWithBytes:digest length:CC_SHA1_DIGEST_LENGTH]; + + return FIRCLSNSDataPrettyDescription(result); +} + +NSString *FIRCLSHashNSData(NSData *data) { + return FIRCLSHashBytes(data.bytes, data.length); +} + +NSString *FIRCLS256HashBytes(const void *bytes, size_t length) { + uint8_t digest[CC_SHA256_DIGEST_LENGTH] = {0}; + CC_SHA256(bytes, (CC_LONG)length, digest); + + NSData *result = [NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH]; + + return FIRCLSNSDataPrettyDescription(result); +} + +NSString *FIRCLS256HashNSData(NSData *data) { + return FIRCLS256HashBytes(data.bytes, data.length); +} + +void FIRCLSEnumerateByteRangesOfNSDataUsingBlock( + NSData *data, void (^block)(const void *bytes, NSRange byteRange, BOOL *stop)) { + if ([data respondsToSelector:@selector(enumerateByteRangesUsingBlock:)]) { + [data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) { + block(bytes, byteRange, stop); + }]; + + return; + } + + // Fall back to the less-efficient mechanism for older OSes. Safe + // to ignore the return value of stop, since we'll only ever + // call this once anyways + BOOL stop = NO; + + block(data.bytes, NSMakeRange(0, data.length), &stop); +} diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSConstants.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSConstants.h new file mode 100644 index 0000000..c17ee02 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSConstants.h @@ -0,0 +1,45 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +FOUNDATION_EXPORT NSString *const FIRCLSDeveloperToken; + +FOUNDATION_EXPORT NSString *const FIRCLSVersion; + +// User Messages +FOUNDATION_EXPORT NSString *const FIRCLSMissingConsumerKeyMsg; +FOUNDATION_EXPORT NSString *const FIRCLSMissingConsumerSecretMsg; + +// Exceptions +FOUNDATION_EXPORT NSString *const FIRCLSException; + +// Endpoints +FOUNDATION_EXPORT NSString *const FIRCLSSettingsEndpoint; +FOUNDATION_EXPORT NSString *const FIRCLSConfigureEndpoint; +FOUNDATION_EXPORT NSString *const FIRCLSReportsEndpoint; + +// Network requests +FOUNDATION_EXPORT NSString *const FIRCLSNetworkAccept; +FOUNDATION_EXPORT NSString *const FIRCLSNetworkAcceptCharset; +FOUNDATION_EXPORT NSString *const FIRCLSNetworkApplicationJson; +FOUNDATION_EXPORT NSString *const FIRCLSNetworkAcceptLanguage; +FOUNDATION_EXPORT NSString *const FIRCLSNetworkContentLanguage; +FOUNDATION_EXPORT NSString *const FIRCLSNetworkCrashlyticsAPIClientDisplayVersion; +FOUNDATION_EXPORT NSString *const FIRCLSNetworkCrashlyticsAPIClientId; +FOUNDATION_EXPORT NSString *const FIRCLSNetworkCrashlyticsDeveloperToken; +FOUNDATION_EXPORT NSString *const FIRCLSNetworkCrashlyticsGoogleAppId; +FOUNDATION_EXPORT NSString *const FIRCLSNetworkCrashlyticsOrgId; +FOUNDATION_EXPORT NSString *const FIRCLSNetworkUserAgent; +FOUNDATION_EXPORT NSString *const FIRCLSNetworkUTF8; diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSConstants.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSConstants.m new file mode 100644 index 0000000..7dac625 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSConstants.m @@ -0,0 +1,49 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSConstants.h" + +#define STR_HELPER(x) #x +#define STR(x) STR_HELPER(x) + +NSString* const FIRCLSDeveloperToken = @"77f0789d8e230eccdb4b99b82dccd78d47f9b604"; + +NSString* const FIRCLSVersion = @STR(DISPLAY_VERSION); + +// User Messages +NSString* const FIRCLSMissingConsumerKeyMsg = @"consumer key is nil or zero length"; +NSString* const FIRCLSMissingConsumerSecretMsg = @"consumer secret is nil or zero length"; + +// Exceptions +NSString* const FIRCLSException = @"FIRCLSException"; + +// Endpoints +NSString* const FIRCLSSettingsEndpoint = @"https://firebase-settings.crashlytics.com"; +NSString* const FIRCLSConfigureEndpoint = @"https://api.crashlytics.com"; +NSString* const FIRCLSReportsEndpoint = @"https://reports.crashlytics.com"; + +// Network requests +NSString* const FIRCLSNetworkAccept = @"Accept"; +NSString* const FIRCLSNetworkAcceptCharset = @"Accept-Charset"; +NSString* const FIRCLSNetworkApplicationJson = @"application/json"; +NSString* const FIRCLSNetworkAcceptLanguage = @"Accept-Language"; +NSString* const FIRCLSNetworkContentLanguage = @"Content-Language"; +NSString* const FIRCLSNetworkCrashlyticsAPIClientDisplayVersion = + @"X-Crashlytics-API-Client-Display-Version"; +NSString* const FIRCLSNetworkCrashlyticsAPIClientId = @"X-Crashlytics-API-Client-Id"; +NSString* const FIRCLSNetworkCrashlyticsDeveloperToken = @"X-Crashlytics-Developer-Token"; +NSString* const FIRCLSNetworkCrashlyticsGoogleAppId = @"X-Crashlytics-Google-App-Id"; +NSString* const FIRCLSNetworkCrashlyticsOrgId = @"X-Crashlytics-Org-Id"; +NSString* const FIRCLSNetworkUserAgent = @"User-Agent"; +NSString* const FIRCLSNetworkUTF8 = @"utf-8"; diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSFABHost.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSFABHost.h new file mode 100644 index 0000000..82c8fcc --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSFABHost.h @@ -0,0 +1,35 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +/** + * Returns the OS version of the host device + */ +NSOperatingSystemVersion FIRCLSHostGetOSVersion(void); + +/** + * Returns model info for the device on which app is running + */ +NSString *FIRCLSHostModelInfo(void); + +/** + * Returns a string representing the OS build + */ +NSString *FIRCLSHostOSBuildVersion(void); + +/** + * Returns a concatenated string of the OS version(majorVersion.minorVersion.patchVersion) + */ +NSString *FIRCLSHostOSDisplayVersion(void); diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSFABHost.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSFABHost.m new file mode 100644 index 0000000..4c3206c --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSFABHost.m @@ -0,0 +1,119 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FIRCLSFABHost.h" + +#if TARGET_OS_WATCH +#import +#elif TARGET_OS_IPHONE +#import +#endif + +#include + +#define FIRCLS_HOST_SYSCTL_BUFFER_SIZE (128) + +#pragma mark - OS Versions + +#pragma mark Private + +static NSString *FIRCLSHostSysctlEntry(const char *sysctlKey) { + char buffer[FIRCLS_HOST_SYSCTL_BUFFER_SIZE]; + size_t bufferSize = FIRCLS_HOST_SYSCTL_BUFFER_SIZE; + if (sysctlbyname(sysctlKey, buffer, &bufferSize, NULL, 0) != 0) { + return nil; + } + return [NSString stringWithUTF8String:buffer]; +} + +#pragma mark Public + +NSOperatingSystemVersion FIRCLSHostGetOSVersion(void) { + // works on macos(10.10), ios(8.0), watchos(2.0), tvos(9.0) + if ([NSProcessInfo.processInfo respondsToSelector:@selector(operatingSystemVersion)]) { + return [NSProcessInfo.processInfo operatingSystemVersion]; + } + + NSOperatingSystemVersion version = {0, 0, 0}; + +#if TARGET_OS_IPHONE + +#if TARGET_OS_WATCH + NSString *versionString = [[WKInterfaceDevice currentDevice] systemVersion]; +#else + NSString *versionString = [[UIDevice currentDevice] systemVersion]; +#endif + + NSArray *parts = [versionString componentsSeparatedByString:@"."]; + + if (parts.count > 0) { + version.majorVersion = [[parts objectAtIndex:0] integerValue]; + } + + if ([parts count] > 1) { + version.minorVersion = [[parts objectAtIndex:1] integerValue]; + } + + if ([parts count] > 2) { + version.patchVersion = [[parts objectAtIndex:2] integerValue]; + } + +#endif + + return version; +} + +NSString *FIRCLSHostOSBuildVersion(void) { + return FIRCLSHostSysctlEntry("kern.osversion"); +} + +NSString *FIRCLSHostOSDisplayVersion(void) { + NSOperatingSystemVersion version = FIRCLSHostGetOSVersion(); + return [NSString stringWithFormat:@"%ld.%ld.%ld", (long)version.majorVersion, + (long)version.minorVersion, (long)version.patchVersion]; +} + +#pragma mark - Host Models + +#pragma mark Public + +NSString *FIRCLSHostModelInfo(void) { + NSString *model = nil; + +#if TARGET_OS_SIMULATOR +#if TARGET_OS_WATCH + model = @"watchOS Simulator"; +#elif TARGET_OS_TV + model = @"tvOS Simulator"; +#elif TARGET_OS_IPHONE + switch (UI_USER_INTERFACE_IDIOM()) { + case UIUserInterfaceIdiomPhone: + model = @"iOS Simulator (iPhone)"; + break; + case UIUserInterfaceIdiomPad: + model = @"iOS Simulator (iPad)"; + break; + default: + model = @"iOS Simulator (Unknown)"; + break; + } +#endif +#elif TARGET_OS_EMBEDDED + model = FIRCLSHostSysctlEntry("hw.machine"); +#else + model = FIRCLSHostSysctlEntry("hw.model"); +#endif + + return model; +} diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSCodeMapping.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSCodeMapping.h new file mode 100644 index 0000000..ae80c46 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSCodeMapping.h @@ -0,0 +1,34 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +typedef enum { + FIRCLSCodeMappingSourceUnknown, + FIRCLSCodeMappingSourceBuild, + FIRCLSCodeSourceCache, + FIRCLSCodeSourceSpotlight +} FIRCLSCodeMappingSource; + +@interface FIRCLSCodeMapping : NSObject + ++ (instancetype)mappingWithURL:(NSURL*)URL; + +- (instancetype)initWithURL:(NSURL*)URL; + +@property(nonatomic, copy, readonly) NSURL* URL; +@property(nonatomic, assign) FIRCLSCodeMappingSource source; +@property(nonatomic, copy, readonly) NSString* sourceName; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSCodeMapping.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSCodeMapping.m new file mode 100644 index 0000000..c212ce7 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSCodeMapping.m @@ -0,0 +1,40 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSCodeMapping.h" + +@interface FIRCLSCodeMapping () { + FIRCLSCodeMappingSource _source; +} + +@end + +@implementation FIRCLSCodeMapping + ++ (instancetype)mappingWithURL:(NSURL*)URL { + return [[self alloc] initWithURL:URL]; +} + +- (instancetype)initWithURL:(NSURL*)URL { + self = [super init]; + if (!self) { + return nil; + } + + _URL = [URL copy]; + + return self; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSMachO.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSMachO.h new file mode 100644 index 0000000..59b4605 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSMachO.h @@ -0,0 +1,110 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include + +#include + +struct FIRCLSMachOFile { + int fd; + size_t mappedSize; + void* mappedFile; +}; +typedef struct FIRCLSMachOFile* FIRCLSMachOFileRef; + +struct FIRCLSMachOSlice { + const void* startAddress; + cpu_type_t cputype; + cpu_subtype_t cpusubtype; +}; +typedef struct FIRCLSMachOSlice* FIRCLSMachOSliceRef; + +typedef struct { + uint32_t major; + uint32_t minor; + uint32_t bugfix; +} FIRCLSMachOVersion; + +typedef struct { + uint64_t addr; + uint64_t size; + uint32_t offset; +} FIRCLSMachOSection; + +typedef struct { + char segname[16]; + uint64_t vmaddr; + uint64_t vmsize; +} FIRCLSMachOSegmentCommand; + +typedef void (^FIRCLSMachOSliceIterator)(FIRCLSMachOSliceRef slice); +typedef void (^FIRCLSMachOLoadCommandIterator)(uint32_t type, + uint32_t size, + const struct load_command* cmd); + +__BEGIN_DECLS + +bool FIRCLSMachOFileInitWithPath(FIRCLSMachOFileRef file, const char* path); +bool FIRCLSMachOFileInitWithCurrent(FIRCLSMachOFileRef file); +void FIRCLSMachOFileDestroy(FIRCLSMachOFileRef file); +void FIRCLSMachOFileEnumerateSlices(FIRCLSMachOFileRef file, FIRCLSMachOSliceIterator block); +struct FIRCLSMachOSlice FIRCLSMachOFileSliceWithArchitectureName(FIRCLSMachOFileRef file, + const char* name); + +void FIRCLSMachOEnumerateSlicesAtAddress(void* executableData, FIRCLSMachOSliceIterator block); +void FIRCLSMachOSliceEnumerateLoadCommands(FIRCLSMachOSliceRef slice, + FIRCLSMachOLoadCommandIterator block); +struct FIRCLSMachOSlice FIRCLSMachOSliceGetCurrent(void); +struct FIRCLSMachOSlice FIRCLSMachOSliceWithHeader(void* machHeader); + +const char* FIRCLSMachOSliceGetExecutablePath(FIRCLSMachOSliceRef slice); +const char* FIRCLSMachOSliceGetArchitectureName(FIRCLSMachOSliceRef slice); +bool FIRCLSMachOSliceIs64Bit(FIRCLSMachOSliceRef slice); +bool FIRCLSMachOSliceGetSectionByName(FIRCLSMachOSliceRef slice, + const char* segName, + const char* sectionName, + const void** ptr); +bool FIRCLSMachOSliceInitSectionByName(FIRCLSMachOSliceRef slice, + const char* segName, + const char* sectionName, + FIRCLSMachOSection* section); +void FIRCLSMachOSliceGetUnwindInformation(FIRCLSMachOSliceRef slice, + const void** ehFrame, + const void** unwindInfo); + +// load-command-specific calls for convenience + +// returns a pointer to the 16-byte UUID +uint8_t const* FIRCLSMachOGetUUID(const struct load_command* cmd); +const char* FIRCLSMachOGetDylibPath(const struct load_command* cmd); + +// return true if the header indicates the binary is encrypted +bool FIRCLSMachOGetEncrypted(const struct load_command* cmd); + +// SDK minimums +FIRCLSMachOVersion FIRCLSMachOGetMinimumOSVersion(const struct load_command* cmd); +FIRCLSMachOVersion FIRCLSMachOGetLinkedSDKVersion(const struct load_command* cmd); + +// Helpers +FIRCLSMachOSegmentCommand FIRCLSMachOGetSegmentCommand(const struct load_command* cmd); + +#ifdef __OBJC__ +NSString* FIRCLSMachONormalizeUUID(CFUUIDBytes* uuidBytes); +NSString* FIRCLSMachOFormatVersion(FIRCLSMachOVersion* version); +#endif +__END_DECLS diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSMachO.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSMachO.m new file mode 100644 index 0000000..47f707c --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSMachO.m @@ -0,0 +1,506 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FIRCLSMachO.h" + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + +#include + +// This is defined in newer versions of iOS/macOS in usr/include/mach/machine.h +#define CLS_CPU_SUBTYPE_ARM64E ((cpu_subtype_t)2) + +static void FIRCLSMachOHeaderValues(FIRCLSMachOSliceRef slice, + const struct load_command** cmds, + uint32_t* cmdCount); +static bool FIRCLSMachOSliceIsValid(FIRCLSMachOSliceRef slice); + +bool FIRCLSMachOFileInitWithPath(FIRCLSMachOFileRef file, const char* path) { + struct stat statBuffer; + + if (!file || !path) { + return false; + } + + file->fd = 0; + file->mappedFile = NULL; + file->mappedSize = 0; + + file->fd = open(path, O_RDONLY); + if (file->fd < 0) { + // unable to open mach-o file + return false; + } + + if (fstat(file->fd, &statBuffer) == -1) { + close(file->fd); + return false; + } + + // We need some minimum size for this to even be a possible mach-o file. I believe + // its probably quite a bit bigger than this, but this at least covers something. + // We also need it to be a regular file. + file->mappedSize = (size_t)statBuffer.st_size; + if (statBuffer.st_size < 16 || !(statBuffer.st_mode & S_IFREG)) { + close(file->fd); + return false; + } + + // Map the file to memory. MAP_SHARED can potentially reduce the amount of actual private + // memory needed to do this mapping. Also, be sure to check for the correct failure result. + file->mappedFile = mmap(0, file->mappedSize, PROT_READ, MAP_FILE | MAP_SHARED, file->fd, 0); + if (!file->mappedFile || (file->mappedFile == MAP_FAILED)) { + close(file->fd); + return false; + } + + return true; +} + +bool FIRCLSMachOFileInitWithCurrent(FIRCLSMachOFileRef file) { + struct FIRCLSMachOSlice slice = FIRCLSMachOSliceGetCurrent(); + + const char* imagePath = FIRCLSMachOSliceGetExecutablePath(&slice); + + return FIRCLSMachOFileInitWithPath(file, imagePath); +} + +void FIRCLSMachOFileDestroy(FIRCLSMachOFileRef file) { + if (!file) { + return; + } + + if (file->mappedFile && file->mappedSize > 0) { + munmap(file->mappedFile, file->mappedSize); + } + + close(file->fd); +} + +void FIRCLSMachOFileEnumerateSlices(FIRCLSMachOFileRef file, FIRCLSMachOSliceIterator block) { + FIRCLSMachOEnumerateSlicesAtAddress(file->mappedFile, block); +} + +void FIRCLSMachOEnumerateSlicesAtAddress(void* executableData, FIRCLSMachOSliceIterator block) { + // check the magic value, to determine if we have a fat header or not + uint32_t magicValue; + uint32_t archCount; + const struct fat_arch* fatArch; + struct FIRCLSMachOSlice slice; + + memset(&slice, 0, sizeof(struct FIRCLSMachOSlice)); + + magicValue = ((struct fat_header*)executableData)->magic; + if ((magicValue != FAT_MAGIC) && (magicValue != FAT_CIGAM)) { + slice.startAddress = executableData; + + // use this to fill in the values + FIRCLSMachOHeaderValues(&slice, NULL, NULL); + + block(&slice); + + return; + } + + archCount = OSSwapBigToHostInt32(((struct fat_header*)executableData)->nfat_arch); + fatArch = executableData + sizeof(struct fat_header); + + for (uint32_t i = 0; i < archCount; ++i) { + slice.cputype = OSSwapBigToHostInt32(fatArch->cputype); + slice.cpusubtype = OSSwapBigToHostInt32(fatArch->cpusubtype); + slice.startAddress = executableData + OSSwapBigToHostInt32(fatArch->offset); + + block(&slice); + + // advance to the next fat_arch structure + fatArch = (struct fat_arch*)((uintptr_t)fatArch + sizeof(struct fat_arch)); + } +} + +struct FIRCLSMachOSlice FIRCLSMachOFileSliceWithArchitectureName(FIRCLSMachOFileRef file, + const char* name) { + __block struct FIRCLSMachOSlice value; + + memset(&value, 0, sizeof(struct FIRCLSMachOSlice)); + + FIRCLSMachOFileEnumerateSlices(file, ^(FIRCLSMachOSliceRef slice) { + if (strcmp(FIRCLSMachOSliceGetArchitectureName(slice), name) == 0) { + value = *slice; + } + }); + + return value; +} + +static void FIRCLSMachOHeaderValues(FIRCLSMachOSliceRef slice, + const struct load_command** cmds, + uint32_t* cmdCount) { + const struct mach_header* header32 = (const struct mach_header*)slice->startAddress; + const struct mach_header_64* header64 = (const struct mach_header_64*)slice->startAddress; + uint32_t commandCount; + const void* commandsAddress; + + if (cmds) { + *cmds = NULL; + } + + if (cmdCount) { + *cmdCount = 0; + } + + if (!slice->startAddress) { + return; + } + + // the 32 and 64 bit versions have an identical structures, so this will work + switch (header32->magic) { + case MH_MAGIC: // 32-bit + case MH_CIGAM: + slice->cputype = header32->cputype; + slice->cpusubtype = header32->cpusubtype; + commandCount = header32->ncmds; + commandsAddress = slice->startAddress + sizeof(struct mach_header); + break; + case MH_MAGIC_64: // 64-bit + case MH_CIGAM_64: + slice->cputype = header64->cputype; + slice->cpusubtype = header64->cpusubtype; + commandCount = header64->ncmds; + commandsAddress = slice->startAddress + sizeof(struct mach_header_64); + break; + default: + // not a valid header + return; + } + + // assign everything back by reference + if (cmds) { + *cmds = commandsAddress; + } + + if (cmdCount) { + *cmdCount = commandCount; + } +} + +static bool FIRCLSMachOSliceIsValid(FIRCLSMachOSliceRef slice) { + if (!slice) { + return false; + } + + if (!slice->startAddress) { + return false; + } + + return true; +} + +void FIRCLSMachOSliceEnumerateLoadCommands(FIRCLSMachOSliceRef slice, + FIRCLSMachOLoadCommandIterator block) { + const struct load_command* cmd; + uint32_t cmdCount; + + if (!block) { + return; + } + + if (!FIRCLSMachOSliceIsValid(slice)) { + return; + } + + FIRCLSMachOHeaderValues(slice, &cmd, &cmdCount); + + for (uint32_t i = 0; cmd != NULL && i < cmdCount; ++i) { + block(cmd->cmd, cmd->cmdsize, cmd); + + cmd = (struct load_command*)((uintptr_t)cmd + cmd->cmdsize); + } +} + +struct FIRCLSMachOSlice FIRCLSMachOSliceGetCurrent(void) { + const NXArchInfo* archInfo; + struct FIRCLSMachOSlice slice; + void* executableSymbol; + Dl_info dlinfo; + + archInfo = NXGetLocalArchInfo(); + if (archInfo) { + slice.cputype = archInfo->cputype; + slice.cpusubtype = archInfo->cpusubtype; + } + + slice.startAddress = NULL; + + executableSymbol = dlsym(RTLD_MAIN_ONLY, MH_EXECUTE_SYM); + + // get the address of the main function + if (dladdr(executableSymbol, &dlinfo) != 0) { + slice.startAddress = dlinfo.dli_fbase; + } + + return slice; +} + +struct FIRCLSMachOSlice FIRCLSMachOSliceWithHeader(void* machHeader) { + struct FIRCLSMachOSlice slice; + + slice.startAddress = machHeader; + + return slice; +} + +const char* FIRCLSMachOSliceGetExecutablePath(FIRCLSMachOSliceRef slice) { + Dl_info info; + + if (!FIRCLSMachOSliceIsValid(slice)) { + return NULL; + } + + // use dladdr here to look up the information we need for a binary image + if (dladdr(slice->startAddress, &info) == 0) { + return NULL; + } + + return info.dli_fname; +} + +const char* FIRCLSMachOSliceGetArchitectureName(FIRCLSMachOSliceRef slice) { + const NXArchInfo* archInfo; + + // there are some special cases here for types not handled by earlier OSes + if (slice->cputype == CPU_TYPE_ARM && slice->cpusubtype == CPU_SUBTYPE_ARM_V7S) { + return "armv7s"; + } + + if (slice->cputype == (CPU_TYPE_ARM | CPU_ARCH_ABI64)) { + if (slice->cpusubtype == CLS_CPU_SUBTYPE_ARM64E) { + return "arm64e"; + } else if (slice->cpusubtype == CPU_SUBTYPE_ARM64_ALL) { + return "arm64"; + } + } + + if (slice->cputype == (CPU_TYPE_ARM) && slice->cpusubtype == CPU_SUBTYPE_ARM_V7K) { + return "armv7k"; + } + + archInfo = NXGetArchInfoFromCpuType(slice->cputype, slice->cpusubtype); + if (!archInfo) { + return "unknown"; + } + + return archInfo->name; +} + +bool FIRCLSMachOSliceIs64Bit(FIRCLSMachOSliceRef slice) { + // I'm pretty sure this is sufficient... + return (slice->cputype & CPU_ARCH_ABI64) == CPU_ARCH_ABI64; +} + +bool FIRCLSMachOSliceGetSectionByName(FIRCLSMachOSliceRef slice, + const char* segName, + const char* sectionName, + const void** ptr) { + if (!ptr) { + return false; + } + + *ptr = NULL; // make sure this is set before returning + + FIRCLSMachOSection section; + + if (!FIRCLSMachOSliceInitSectionByName(slice, segName, sectionName, §ion)) { + return false; + } + + // WARNING: this calculation isn't correct, but is here to maintain backwards + // compatibility for now with callers of FIRCLSMachOSliceGetSectionByName. All new + // users should be calling FIRCLSMachOSliceInitSectionByName + *ptr = (const void*)((uintptr_t)slice->startAddress + section.offset); + + return true; +} + +bool FIRCLSMachOSliceInitSectionByName(FIRCLSMachOSliceRef slice, + const char* segName, + const char* sectionName, + FIRCLSMachOSection* section) { + if (!FIRCLSMachOSliceIsValid(slice)) { + return false; + } + + if (!section) { + return false; + } + + memset(section, 0, sizeof(FIRCLSMachOSection)); + + if (FIRCLSMachOSliceIs64Bit(slice)) { + const struct section_64* sect = + getsectbynamefromheader_64(slice->startAddress, segName, sectionName); + if (!sect) { + return false; + } + + section->addr = sect->addr; + section->size = sect->size; + section->offset = sect->offset; + } else { + const struct section* sect = getsectbynamefromheader(slice->startAddress, segName, sectionName); + if (!sect) { + return false; + } + + section->addr = sect->addr; + section->size = sect->size; + section->offset = sect->offset; + } + + return true; +} + +// TODO: this is left in-place just to ensure that old crashltyics + new fabric are still compatible +// with each other. As a happy bonus, if that situation does come up, this will also fix the bug +// that was preventing compact unwind on arm64 + iOS 9 from working correctly. +void FIRCLSMachOSliceGetUnwindInformation(FIRCLSMachOSliceRef slice, + const void** ehFrame, + const void** unwindInfo) { + if (!unwindInfo && !ehFrame) { + return; + } + + bool found = false; + intptr_t slide = 0; + + // This is inefficient, but we have no other safe way to do this correctly. Modifying the + // FIRCLSMachOSlice structure is tempting, but could introduce weird binary-compatibility issues + // with version mis-matches. + for (uint32_t i = 0; i < _dyld_image_count(); ++i) { + const struct mach_header* header = _dyld_get_image_header(i); + + if (header == slice->startAddress) { + found = true; + slide = _dyld_get_image_vmaddr_slide(i); + break; + } + } + + // make sure we were able to find a matching value + if (!found) { + return; + } + + FIRCLSMachOSection section; + + if (unwindInfo) { + if (FIRCLSMachOSliceInitSectionByName(slice, SEG_TEXT, "__unwind_info", §ion)) { + *unwindInfo = (void*)(section.addr + slide); + } + } + + if (ehFrame) { + if (FIRCLSMachOSliceInitSectionByName(slice, SEG_TEXT, "__eh_frame", §ion)) { + *ehFrame = (void*)(section.addr + slide); + } + } +} + +uint8_t const* FIRCLSMachOGetUUID(const struct load_command* cmd) { + return ((const struct uuid_command*)cmd)->uuid; +} + +const char* FIRCLSMachOGetDylibPath(const struct load_command* cmd) { + const struct dylib_command* dylibcmd = (const struct dylib_command*)cmd; + + return (const char*)((uintptr_t)cmd + dylibcmd->dylib.name.offset); +} + +bool FIRCLSMachOGetEncrypted(const struct load_command* cmd) { + return ((struct encryption_info_command*)cmd)->cryptid > 0; +} + +static FIRCLSMachOVersion FIRCLSMachOVersionFromEncoded(uint32_t encoded) { + FIRCLSMachOVersion version; + + version.major = (encoded & 0xffff0000) >> 16; + version.minor = (encoded & 0x0000ff00) >> 8; + version.bugfix = encoded & 0x000000ff; + + return version; +} + +FIRCLSMachOVersion FIRCLSMachOGetMinimumOSVersion(const struct load_command* cmd) { + return FIRCLSMachOVersionFromEncoded(((const struct version_min_command*)cmd)->version); +} + +FIRCLSMachOVersion FIRCLSMachOGetLinkedSDKVersion(const struct load_command* cmd) { + return FIRCLSMachOVersionFromEncoded(((const struct version_min_command*)cmd)->sdk); +} + +FIRCLSMachOSegmentCommand FIRCLSMachOGetSegmentCommand(const struct load_command* cmd) { + FIRCLSMachOSegmentCommand segmentCommand; + + memset(&segmentCommand, 0, sizeof(FIRCLSMachOSegmentCommand)); + + if (!cmd) { + return segmentCommand; + } + + if (cmd->cmd == LC_SEGMENT) { + struct segment_command* segCmd = (struct segment_command*)cmd; + + memcpy(segmentCommand.segname, segCmd->segname, 16); + segmentCommand.vmaddr = segCmd->vmaddr; + segmentCommand.vmsize = segCmd->vmsize; + } else if (cmd->cmd == LC_SEGMENT_64) { + struct segment_command_64* segCmd = (struct segment_command_64*)cmd; + + memcpy(segmentCommand.segname, segCmd->segname, 16); + segmentCommand.vmaddr = segCmd->vmaddr; + segmentCommand.vmsize = segCmd->vmsize; + } + + return segmentCommand; +} + +NSString* FIRCLSMachONormalizeUUID(CFUUIDBytes* uuidBytes) { + CFUUIDRef uuid = CFUUIDCreateFromUUIDBytes(kCFAllocatorDefault, *uuidBytes); + + NSString* string = CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuid)); + + CFRelease(uuid); + + return [[string stringByReplacingOccurrencesOfString:@"-" withString:@""] lowercaseString]; +} + +NSString* FIRCLSMachOFormatVersion(FIRCLSMachOVersion* version) { + if (!version) { + return nil; + } + + return [NSString stringWithFormat:@"%d.%d.%d", version->major, version->minor, version->bugfix]; +} diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSMachOBinary.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSMachOBinary.h new file mode 100644 index 0000000..57d5498 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSMachOBinary.h @@ -0,0 +1,41 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import +#import "FIRCLSMachO.h" + +@class FIRCLSMachOSlice; + +@interface FIRCLSMachOBinary : NSObject { + NSURL* _url; + + struct FIRCLSMachOFile _file; + NSMutableArray* _slices; + NSString* _instanceIdentifier; +} + ++ (id)MachOBinaryWithPath:(NSString*)path; + +- (id)initWithURL:(NSURL*)url; + +@property(nonatomic, copy, readonly) NSURL* URL; +@property(nonatomic, copy, readonly) NSString* path; +@property(nonatomic, strong, readonly) NSArray* slices; +@property(nonatomic, copy, readonly) NSString* instanceIdentifier; + +- (void)enumerateUUIDs:(void (^)(NSString* uuid, NSString* architecture))block; + +- (FIRCLSMachOSlice*)sliceForArchitecture:(NSString*)architecture; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSMachOBinary.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSMachOBinary.m new file mode 100644 index 0000000..12598e3 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSMachOBinary.m @@ -0,0 +1,175 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSMachOBinary.h" + +#import "FIRCLSMachOSlice.h" + +#import + +static void FIRCLSSafeHexToString(const uint8_t* value, size_t length, char* outputBuffer); +static NSString* FIRCLSNSDataToNSString(NSData* data); +static NSString* FIRCLSHashBytes(const void* bytes, size_t length); +static NSString* FIRCLSHashNSString(NSString* value); + +@interface FIRCLSMachOBinary () + ++ (NSString*)hashNSString:(NSString*)value; + +@end + +@implementation FIRCLSMachOBinary + ++ (id)MachOBinaryWithPath:(NSString*)path { + return [[self alloc] initWithURL:[NSURL fileURLWithPath:path]]; +} + +@synthesize slices = _slices; + +- (id)initWithURL:(NSURL*)url { + self = [super init]; + if (self) { + _url = [url copy]; + + if (!FIRCLSMachOFileInitWithPath(&_file, [[_url path] fileSystemRepresentation])) { + return nil; + } + + _slices = [NSMutableArray new]; + FIRCLSMachOFileEnumerateSlices(&_file, ^(FIRCLSMachOSliceRef slice) { + FIRCLSMachOSlice* sliceObject; + + sliceObject = [[FIRCLSMachOSlice alloc] initWithSlice:slice]; + + [self->_slices addObject:sliceObject]; + }); + } + + return self; +} + +- (void)dealloc { + FIRCLSMachOFileDestroy(&_file); +} + +- (NSURL*)URL { + return _url; +} + +- (NSString*)path { + return [_url path]; +} + +- (NSString*)instanceIdentifier { + if (_instanceIdentifier) { + return _instanceIdentifier; + } + + NSMutableString* prehashedString = [NSMutableString new]; + + // sort the slices by architecture + NSArray* sortedSlices = + [_slices sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { + return [[obj1 architectureName] compare:[obj2 architectureName]]; + }]; + + // append them all into a big string + for (FIRCLSMachOSlice* slice in sortedSlices) { + [prehashedString appendString:[slice uuid]]; + } + + _instanceIdentifier = [FIRCLSHashNSString(prehashedString) copy]; + + return _instanceIdentifier; +} + +- (void)enumerateUUIDs:(void (^)(NSString* uuid, NSString* architecture))block { + for (FIRCLSMachOSlice* slice in _slices) { + block([slice uuid], [slice architectureName]); + } +} + +- (FIRCLSMachOSlice*)sliceForArchitecture:(NSString*)architecture { + for (FIRCLSMachOSlice* slice in [self slices]) { + if ([[slice architectureName] isEqualToString:architecture]) { + return slice; + } + } + + return nil; +} + ++ (NSString*)hashNSString:(NSString*)value { + return FIRCLSHashNSString(value); +} + +@end + +// TODO: Functions copied from the SDK. We should figure out a way to share this. +static void FIRCLSSafeHexToString(const uint8_t* value, size_t length, char* outputBuffer) { + const char hex[] = "0123456789abcdef"; + + if (!value) { + outputBuffer[0] = '\0'; + return; + } + + for (size_t i = 0; i < length; ++i) { + unsigned char c = value[i]; + outputBuffer[i * 2] = hex[c >> 4]; + outputBuffer[i * 2 + 1] = hex[c & 0x0F]; + } + + outputBuffer[length * 2] = '\0'; // null terminate +} + +static NSString* FIRCLSNSDataToNSString(NSData* data) { + NSString* string; + char* buffer; + size_t size; + NSUInteger length; + + // we need 2 hex char for every byte of data, plus one more spot for a + // null terminator + length = [data length]; + size = (length * 2) + 1; + buffer = malloc(sizeof(char) * size); + + if (!buffer) { + return nil; + } + + FIRCLSSafeHexToString([data bytes], length, buffer); + + string = [NSString stringWithUTF8String:buffer]; + + free(buffer); + + return string; +} + +static NSString* FIRCLSHashBytes(const void* bytes, size_t length) { + uint8_t digest[CC_SHA1_DIGEST_LENGTH] = {0}; + CC_SHA1(bytes, (CC_LONG)length, digest); + + NSData* result = [NSData dataWithBytes:digest length:CC_SHA1_DIGEST_LENGTH]; + + return FIRCLSNSDataToNSString(result); +} + +static NSString* FIRCLSHashNSString(NSString* value) { + const char* s = [value cStringUsingEncoding:NSUTF8StringEncoding]; + + return FIRCLSHashBytes(s, strlen(s)); +} diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSMachOSlice.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSMachOSlice.h new file mode 100644 index 0000000..9f7bcb4 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSMachOSlice.h @@ -0,0 +1,37 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import +#import "FIRCLSMachO.h" + +@interface FIRCLSMachOSlice : NSObject { + struct FIRCLSMachOSlice _slice; + + NSString* _uuidString; + NSArray* _linkedDylibs; + FIRCLSMachOVersion _minimumOSVersion; + FIRCLSMachOVersion _linkedSDKVersion; +} + ++ (id)runningSlice; + +- (id)initWithSlice:(FIRCLSMachOSliceRef)sliceRef; + +@property(nonatomic, copy, readonly) NSString* uuid; +@property(nonatomic, copy, readonly) NSString* architectureName; +@property(nonatomic, strong, readonly) NSArray* linkedDylibs; +@property(nonatomic, assign, readonly) FIRCLSMachOVersion minimumOSVersion; +@property(nonatomic, assign, readonly) FIRCLSMachOVersion linkedSDKVersion; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSMachOSlice.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSMachOSlice.m new file mode 100644 index 0000000..961e144 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSMachOSlice.m @@ -0,0 +1,93 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSMachOSlice.h" + +#include + +// this is defined only if __OPEN_SOURCE__ is *not* defined in the TVOS SDK's mach-o/loader.h +// also, it has not yet made it back to the OSX SDKs, for example +#ifndef LC_VERSION_MIN_TVOS +#define LC_VERSION_MIN_TVOS 0x2F +#endif + +@implementation FIRCLSMachOSlice + ++ (id)runningSlice { + struct FIRCLSMachOSlice slice; + + slice = FIRCLSMachOSliceGetCurrent(); + + return [[self alloc] initWithSlice:&slice]; +} + +@synthesize minimumOSVersion = _minimumOSVersion; +@synthesize linkedSDKVersion = _linkedSDKVersion; + +- (id)initWithSlice:(FIRCLSMachOSliceRef)sliceRef { + self = [super init]; + if (self) { + NSMutableArray* dylibs; + + _slice = *sliceRef; + + _minimumOSVersion.major = 0; + _minimumOSVersion.minor = 0; + _minimumOSVersion.bugfix = 0; + + _linkedSDKVersion.major = 0; + _linkedSDKVersion.minor = 0; + _linkedSDKVersion.bugfix = 0; + + dylibs = [NSMutableArray array]; + + FIRCLSMachOSliceEnumerateLoadCommands( + &_slice, ^(uint32_t type, uint32_t size, const struct load_command* cmd) { + switch (type) { + case LC_UUID: + self->_uuidString = + [FIRCLSMachONormalizeUUID((CFUUIDBytes*)FIRCLSMachOGetUUID(cmd)) copy]; + break; + case LC_LOAD_DYLIB: + [dylibs addObject:[NSString stringWithUTF8String:FIRCLSMachOGetDylibPath(cmd)]]; + break; + case LC_VERSION_MIN_IPHONEOS: + case LC_VERSION_MIN_MACOSX: + case LC_VERSION_MIN_WATCHOS: + case LC_VERSION_MIN_TVOS: + self->_minimumOSVersion = FIRCLSMachOGetMinimumOSVersion(cmd); + self->_linkedSDKVersion = FIRCLSMachOGetLinkedSDKVersion(cmd); + break; + } + }); + + _linkedDylibs = [dylibs copy]; + } + + return self; +} + +- (NSString*)architectureName { + return [NSString stringWithUTF8String:FIRCLSMachOSliceGetArchitectureName(&_slice)]; +} + +- (NSString*)uuid { + return _uuidString; +} + +- (NSArray*)linkedDylibs { + return _linkedDylibs; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSdSYM.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSdSYM.h new file mode 100644 index 0000000..c80ac74 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSdSYM.h @@ -0,0 +1,38 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +@class FIRCLSMachOBinary; + +@interface FIRCLSdSYM : NSObject + +NS_ASSUME_NONNULL_BEGIN + ++ (id)dSYMWithURL:(NSURL*)url; + +- (id)initWithURL:(NSURL*)url; + +@property(nonatomic, readonly) FIRCLSMachOBinary* binary; +@property(nonatomic, copy, readonly, nullable) NSString* bundleIdentifier; +@property(nonatomic, copy, readonly) NSURL* executableURL; +@property(nonatomic, copy, readonly) NSString* executablePath; +@property(nonatomic, copy, readonly) NSString* bundleVersion; +@property(nonatomic, copy, readonly) NSString* shortBundleVersion; + +- (void)enumerateUUIDs:(void (^)(NSString* uuid, NSString* architecture))block; + +NS_ASSUME_NONNULL_END + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSdSYM.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSdSYM.m new file mode 100644 index 0000000..cda7879 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSMachO/FIRCLSdSYM.m @@ -0,0 +1,109 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSdSYM.h" + +#import "FIRCLSMachOBinary.h" + +#define CLS_XCODE_DSYM_PREFIX (@"com.apple.xcode.dsym.") + +@interface FIRCLSdSYM () + +@property(nonatomic, readonly) NSBundle* bundle; + +@end + +@implementation FIRCLSdSYM + ++ (id)dSYMWithURL:(NSURL*)url { + return [[self alloc] initWithURL:url]; +} + +- (id)initWithURL:(NSURL*)url { + self = [super init]; + if (self) { + NSDirectoryEnumerator* enumerator; + NSString* path; + NSFileManager* fileManager; + BOOL isDirectory; + BOOL fileExistsAtPath; + NSArray* itemsInDWARFDir; + + fileManager = [NSFileManager defaultManager]; + + // Is there a file at this path? + if (![fileManager fileExistsAtPath:[url path]]) { + return nil; + } + + _bundle = [NSBundle bundleWithURL:url]; + if (!_bundle) { + return nil; + } + + path = [[url path] stringByAppendingPathComponent:@"Contents/Resources/DWARF"]; + + // Does this path exist and is it a directory? + fileExistsAtPath = [fileManager fileExistsAtPath:path isDirectory:&isDirectory]; + if (!fileExistsAtPath || !isDirectory) { + return nil; + } + + enumerator = [fileManager enumeratorAtPath:path]; + itemsInDWARFDir = [enumerator allObjects]; + // Do we have a Contents/Resources/DWARF dir but no contents? + if ([itemsInDWARFDir count] == 0) { + return nil; + } + + path = [path stringByAppendingPathComponent:[itemsInDWARFDir objectAtIndex:0]]; + + _binary = [[FIRCLSMachOBinary alloc] initWithURL:[NSURL fileURLWithPath:path]]; + } + + return self; +} + +- (NSString*)bundleIdentifier { + NSString* identifier; + + identifier = [_bundle bundleIdentifier]; + if ([identifier hasPrefix:CLS_XCODE_DSYM_PREFIX]) { + return [identifier substringFromIndex:[CLS_XCODE_DSYM_PREFIX length]]; + } + + return identifier; +} + +- (NSURL*)executableURL { + return [_binary URL]; +} + +- (NSString*)executablePath { + return [_binary path]; +} + +- (NSString*)bundleVersion { + return [[_bundle infoDictionary] objectForKey:@"CFBundleVersion"]; +} + +- (NSString*)shortBundleVersion { + return [[_bundle infoDictionary] objectForKey:@"CFBundleShortVersionString"]; +} + +- (void)enumerateUUIDs:(void (^)(NSString* uuid, NSString* architecture))block { + [_binary enumerateUUIDs:block]; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSNetworking/FIRCLSFABNetworkClient.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSNetworking/FIRCLSFABNetworkClient.h new file mode 100644 index 0000000..ebbd26c --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSNetworking/FIRCLSFABNetworkClient.h @@ -0,0 +1,56 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +OBJC_EXTERN const NSUInteger FIRCLSNetworkMaximumRetryCount; + +NS_ASSUME_NONNULL_BEGIN + +typedef void (^FIRCLSNetworkDataTaskCompletionHandlerBlock)(NSData *__nullable data, + NSURLResponse *__nullable response, + NSError *__nullable error); +typedef void (^FIRCLSNetworkDownloadTaskCompletionHandlerBlock)(NSURL *__nullable location, + NSURLResponse *__nullable response, + NSError *__nullable error); + +@interface FIRCLSFABNetworkClient : NSObject + +- (instancetype)init; +- (instancetype)initWithQueue:(nullable NSOperationQueue *)operationQueue; +- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)config + queue:(nullable NSOperationQueue *)operationQueue + NS_DESIGNATED_INITIALIZER; + +- (void)startDataTaskWithRequest:(NSURLRequest *)request + retryLimit:(NSUInteger)retryLimit + completionHandler:(FIRCLSNetworkDataTaskCompletionHandlerBlock)completionHandler; +- (void)startDownloadTaskWithRequest:(NSURLRequest *)request + retryLimit:(NSUInteger)retryLimit + completionHandler: + (FIRCLSNetworkDownloadTaskCompletionHandlerBlock)completionHandler; + +- (void)invalidateAndCancel; + +// Backwards compatibility (we cannot change an interface in Fabric Base that other kits rely on, +// since we have no control of versioning dependencies) +- (void)startDataTaskWithRequest:(NSURLRequest *)request + completionHandler:(FIRCLSNetworkDataTaskCompletionHandlerBlock)completionHandler; +- (void)startDownloadTaskWithRequest:(NSURLRequest *)request + completionHandler: + (FIRCLSNetworkDownloadTaskCompletionHandlerBlock)completionHandler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSNetworking/FIRCLSFABNetworkClient.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSNetworking/FIRCLSFABNetworkClient.m new file mode 100644 index 0000000..d11b3b6 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSNetworking/FIRCLSFABNetworkClient.m @@ -0,0 +1,280 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSFABNetworkClient.h" + +#if FIRCLSURLSESSION_REQUIRED +#import "FIRCLSURLSession.h" +#endif + +#import "FIRCLSNetworkResponseHandler.h" + +static const float FIRCLSNetworkMinimumRetryJitter = 0.90f; +static const float FIRCLSNetworkMaximumRetryJitter = 1.10f; +const NSUInteger FIRCLSNetworkMaximumRetryCount = 10; + +@interface FIRCLSFABNetworkClient () + +@property(nonatomic, strong, readonly) NSURLSession *session; + +@end + +@implementation FIRCLSFABNetworkClient + +- (instancetype)init { + return [self initWithQueue:nil]; +} + +- (instancetype)initWithQueue:(nullable NSOperationQueue *)operationQueue { +#if !FIRCLSURLSESSION_REQUIRED + NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; +#else + NSURLSessionConfiguration *config = [FIRCLSURLSessionConfiguration defaultSessionConfiguration]; +#endif + return [self initWithSessionConfiguration:config queue:operationQueue]; +} + +- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)config + queue:(nullable NSOperationQueue *)operationQueue { + self = [super init]; + if (!self) { + return nil; + } + +#if !FIRCLSURLSESSION_REQUIRED + _session = [NSURLSession sessionWithConfiguration:config + delegate:self + delegateQueue:operationQueue]; +#else + _session = [FIRCLSURLSession sessionWithConfiguration:config + delegate:self + delegateQueue:operationQueue]; +#endif + if (!_session) { + return nil; + } + + return self; +} + +- (void)dealloc { + [_session finishTasksAndInvalidate]; +} + +#pragma mark - Delay Handling +- (double)randomDoubleWithMin:(double)min max:(double)max { + return min + ((max - min) * drand48()); +} + +- (double)generateRandomJitter { + return [self randomDoubleWithMin:FIRCLSNetworkMinimumRetryJitter + max:FIRCLSNetworkMaximumRetryJitter]; +} + +- (NSTimeInterval)computeDelayForResponse:(NSURLResponse *)response + withRetryCount:(NSUInteger)count { + NSTimeInterval initialValue = [FIRCLSNetworkResponseHandler retryValueForResponse:response]; + + // make sure count is > 0 + count = MAX(count, 1); + // make sure initialValue is >2 for exponential backoff to work reasonably with low count numbers + initialValue = MAX(initialValue, 2.0); + + const double jitter = [self generateRandomJitter]; + + return pow(initialValue, count) * jitter; // exponential backoff +} + +- (void)runAfterRetryValueFromResponse:(NSURLResponse *)response + attempts:(NSUInteger)count + onQueue:(dispatch_queue_t)queue + block:(void (^)(void))block { + const NSTimeInterval delay = [self computeDelayForResponse:response withRetryCount:count]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (uint64_t)(delay * NSEC_PER_SEC)), queue, block); +} + +- (void)runAfterRetryValueFromResponse:(NSURLResponse *)response + attempts:(NSUInteger)count + block:(void (^)(void))block { + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + + [self runAfterRetryValueFromResponse:response attempts:count onQueue:queue block:block]; +} + +#pragma mark - Tasks + +- (void)startDataTaskWithRequest:(NSURLRequest *)request + retryLimit:(NSUInteger)retryLimit + tries:(NSUInteger)tries + completionHandler:(FIRCLSNetworkDataTaskCompletionHandlerBlock)completionHandler { + NSURLSessionTask *task = [self.session + dataTaskWithRequest:request + completionHandler:^(NSData *data, NSURLResponse *response, NSError *taskError) { + [FIRCLSNetworkResponseHandler + handleCompletedResponse:response + forOriginalRequest:request + error:taskError + block:^(BOOL retry, NSError *error) { + if (!retry) { + completionHandler(data, response, error); + return; + } + + if (tries >= retryLimit) { + NSDictionary *userInfo = @{ + @"retryLimit" : @(retryLimit), + NSURLErrorFailingURLStringErrorKey : request.URL + }; + completionHandler( + nil, nil, + [NSError + errorWithDomain:FIRCLSNetworkErrorDomain + code:FIRCLSNetworkErrorMaximumAttemptsReached + userInfo:userInfo]); + return; + } + + [self + runAfterRetryValueFromResponse:response + attempts:tries + block:^{ + [self + startDataTaskWithRequest: + request + retryLimit: + retryLimit + tries: + (tries + + 1) + completionHandler: + completionHandler]; + }]; + }]; + }]; + + [task resume]; + + if (!task) { + completionHandler(nil, nil, + [NSError errorWithDomain:FIRCLSNetworkErrorDomain + code:FIRCLSNetworkErrorFailedToStartOperation + userInfo:nil]); + } +} + +- (void)startDataTaskWithRequest:(NSURLRequest *)request + retryLimit:(NSUInteger)retryLimit + completionHandler:(FIRCLSNetworkDataTaskCompletionHandlerBlock)completionHandler { + [self startDataTaskWithRequest:request + retryLimit:retryLimit + tries:0 + completionHandler:completionHandler]; +} + +- (void)startDataTaskWithRequest:(NSURLRequest *)request + completionHandler:(FIRCLSNetworkDataTaskCompletionHandlerBlock)completionHandler { + [self startDataTaskWithRequest:request + retryLimit:FIRCLSNetworkMaximumRetryCount + completionHandler:completionHandler]; +} + +- (void)startDownloadTaskWithRequest:(NSURLRequest *)request + retryLimit:(NSUInteger)retryLimit + tries:(NSUInteger)tries + completionHandler: + (FIRCLSNetworkDownloadTaskCompletionHandlerBlock)completionHandler { + NSURLSessionTask *task = [self.session + downloadTaskWithRequest:request + completionHandler:^(NSURL *location, NSURLResponse *response, NSError *taskError) { + [FIRCLSNetworkResponseHandler + handleCompletedResponse:response + forOriginalRequest:request + error:taskError + block:^(BOOL retry, NSError *error) { + if (!retry) { + completionHandler(location, response, error); + return; + } + + if (tries >= retryLimit) { + NSDictionary *userInfo = @{ + @"retryLimit" : @(retryLimit), + NSURLErrorFailingURLStringErrorKey : request.URL + }; + completionHandler( + nil, nil, + [NSError + errorWithDomain:FIRCLSNetworkErrorDomain + code: + FIRCLSNetworkErrorMaximumAttemptsReached + userInfo:userInfo]); + return; + } + + [self + runAfterRetryValueFromResponse:response + attempts:tries + block:^{ + [self + startDownloadTaskWithRequest: + request + retryLimit: + retryLimit + tries: + (tries + + 1) + completionHandler: + completionHandler]; + }]; + }]; + }]; + + [task resume]; + + if (!task) { + completionHandler(nil, nil, + [NSError errorWithDomain:FIRCLSNetworkErrorDomain + code:FIRCLSNetworkErrorFailedToStartOperation + userInfo:nil]); + } +} + +- (void)startDownloadTaskWithRequest:(NSURLRequest *)request + retryLimit:(NSUInteger)retryLimit + completionHandler: + (FIRCLSNetworkDownloadTaskCompletionHandlerBlock)completionHandler { + [self startDownloadTaskWithRequest:request + retryLimit:retryLimit + tries:0 + completionHandler:completionHandler]; +} + +- (void)startDownloadTaskWithRequest:(NSURLRequest *)request + completionHandler: + (FIRCLSNetworkDownloadTaskCompletionHandlerBlock)completionHandler { + [self startDownloadTaskWithRequest:request + retryLimit:FIRCLSNetworkMaximumRetryCount + completionHandler:completionHandler]; +} + +- (void)invalidateAndCancel { + [self.session invalidateAndCancel]; +} + +#pragma mark - NSURLSession Delegate +- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error { +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSNetworking/FIRCLSMultipartMimeStreamEncoder.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSNetworking/FIRCLSMultipartMimeStreamEncoder.h new file mode 100644 index 0000000..c3630a5 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSNetworking/FIRCLSMultipartMimeStreamEncoder.h @@ -0,0 +1,88 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +/** + * This class is a helper class for generating Multipart requests, as described in + * http://www.w3.org/Protocols/rfc1341/7_2_Multipart.html. In the case of multiple part messages, in + * which one or more different sets of data are combined in a single body, a "multipart" + * Content-Type field must appear in the entity's header. The body must then contain one or more + * "body parts," each preceded by an encapsulation boundary, and the last one followed by a closing + * boundary. Each part starts with an encapsulation boundary, and then contains a body part + * consisting of header area, a blank line, and a body area. + */ +@interface FIRCLSMultipartMimeStreamEncoder : NSObject + +/** + * Convenience class method to populate a NSMutableURLRequest with data from a block that takes an + * instance of this class as input. + */ ++ (void)populateRequest:(NSMutableURLRequest *)request + withDataFromEncoder:(void (^)(FIRCLSMultipartMimeStreamEncoder *encoder))block; + +/** + * Returns a NSString instance with multipart/form-data appended to the boundary. + */ ++ (NSString *)contentTypeHTTPHeaderValueWithBoundary:(NSString *)boundary; +/** + * Convenience class method that returns an instance of this class + */ ++ (instancetype)encoderWithStream:(NSOutputStream *)stream andBoundary:(NSString *)boundary; +/** + * Returns a unique boundary string. + */ ++ (NSString *)generateBoundary; +/** + * Designated initializer + * @param stream NSOutputStream associated with the Multipart request + * @param boundary the unique Boundary string to be used + */ +- (instancetype)initWithStream:(NSOutputStream *)stream + andBoundary:(NSString *)boundary NS_DESIGNATED_INITIALIZER; +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; +/** + * Encodes this block within the boundary on the output stream + */ +- (void)encode:(void (^)(void))block; +/** + * Adds the contents of the file data with given Mime type anf fileName within the boundary in + * stream + */ +- (void)addFileData:(NSData *)data + fileName:(NSString *)fileName + mimeType:(NSString *)mimeType + fieldName:(NSString *)name; +/** + * Convenience method for the method above. Converts fileURL to data and calls the above method. + */ +- (void)addFile:(NSURL *)fileURL + fileName:(NSString *)fileName + mimeType:(NSString *)mimeType + fieldName:(NSString *)name; +/** + * Adds this field and value in the stream + */ +- (void)addValue:(id)value fieldName:(NSString *)name; +/** + * String referring to the multipart MIME type with boundary + */ +@property(nonatomic, copy, readonly) NSString *contentTypeHTTPHeaderValue; +/** + * Length of the data written to stream + */ +@property(nonatomic, copy, readonly) NSString *contentLengthHTTPHeaderValue; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSNetworking/FIRCLSMultipartMimeStreamEncoder.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSNetworking/FIRCLSMultipartMimeStreamEncoder.m new file mode 100644 index 0000000..134b1ce --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSNetworking/FIRCLSMultipartMimeStreamEncoder.m @@ -0,0 +1,208 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSMultipartMimeStreamEncoder.h" + +#import "FIRCLSByteUtility.h" +#import "FIRCLSLogger.h" +#import "FIRCLSUUID.h" + +@interface FIRCLSMultipartMimeStreamEncoder () + +@property(nonatomic) NSUInteger length; +@property(nonatomic, copy) NSString *boundary; +@property(nonatomic, copy, readonly) NSData *headerData; +@property(nonatomic, copy, readonly) NSData *footerData; +@property(nonatomic, strong) NSOutputStream *outputStream; + +@end + +@implementation FIRCLSMultipartMimeStreamEncoder + ++ (void)populateRequest:(NSMutableURLRequest *)request + withDataFromEncoder:(void (^)(FIRCLSMultipartMimeStreamEncoder *encoder))block { + NSString *boundary = [self generateBoundary]; + + NSOutputStream *stream = [NSOutputStream outputStreamToMemory]; + + FIRCLSMultipartMimeStreamEncoder *encoder = + [[FIRCLSMultipartMimeStreamEncoder alloc] initWithStream:stream andBoundary:boundary]; + + [encoder encode:^{ + block(encoder); + }]; + + [request setValue:encoder.contentTypeHTTPHeaderValue forHTTPHeaderField:@"Content-Type"]; + [request setValue:encoder.contentLengthHTTPHeaderValue forHTTPHeaderField:@"Content-Length"]; + + NSData *data = [stream propertyForKey:NSStreamDataWrittenToMemoryStreamKey]; + request.HTTPBody = data; +} + ++ (NSString *)contentTypeHTTPHeaderValueWithBoundary:(NSString *)boundary { + return [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]; +} + ++ (instancetype)encoderWithStream:(NSOutputStream *)stream andBoundary:(NSString *)boundary { + return [[self alloc] initWithStream:stream andBoundary:boundary]; +} + ++ (NSString *)generateBoundary { + return FIRCLSGenerateUUID(); +} + +- (instancetype)initWithStream:(NSOutputStream *)stream andBoundary:(NSString *)boundary { + self = [super init]; + if (!self) { + return nil; + } + + self.outputStream = stream; + + if (!boundary) { + boundary = [FIRCLSMultipartMimeStreamEncoder generateBoundary]; + } + + _boundary = boundary; + + return self; +} + +- (void)encode:(void (^)(void))block { + [self beginEncoding]; + + block(); + + [self endEncoding]; +} + +- (NSString *)contentTypeHTTPHeaderValue { + return [[self class] contentTypeHTTPHeaderValueWithBoundary:self.boundary]; +} + +- (NSString *)contentLengthHTTPHeaderValue { + return [NSString stringWithFormat:@"%lu", (unsigned long)_length]; +} + +#pragma - mark MIME part API +- (void)beginEncoding { + _length = 0; + + [self.outputStream open]; + + [self writeData:self.headerData]; +} + +- (void)endEncoding { + [self writeData:self.footerData]; + + [self.outputStream close]; +} + +- (NSData *)headerData { + return [@"MIME-Version: 1.0\r\n" dataUsingEncoding:NSUTF8StringEncoding]; +} + +- (NSData *)footerData { + return [[NSString stringWithFormat:@"--%@--\r\n", self.boundary] + dataUsingEncoding:NSUTF8StringEncoding]; +} + +- (void)addFileData:(NSData *)data + fileName:(NSString *)fileName + mimeType:(NSString *)mimeType + fieldName:(NSString *)name { + if ([data length] == 0) { + FIRCLSErrorLog(@"Unable to MIME encode data with zero length (%@)", name); + return; + } + + if ([name length] == 0 || [fileName length] == 0) { + FIRCLSErrorLog(@"name (%@) or fieldname (%@) is invalid", name, fileName); + return; + } + + NSMutableString *string; + + string = [NSMutableString + stringWithFormat:@"--%@\r\nContent-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", + self.boundary, name, fileName]; + + if (mimeType) { + [string appendFormat:@"Content-Type: %@\r\n", mimeType]; + [string appendString:@"Content-Transfer-Encoding: binary\r\n\r\n"]; + } else { + [string appendString:@"Content-Type: application/octet-stream\r\n\r\n"]; + } + + [self writeData:[string dataUsingEncoding:NSUTF8StringEncoding]]; + + [self writeData:data]; + + [self writeData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; +} + +- (void)addValue:(id)value fieldName:(NSString *)name { + if ([name length] == 0 || !value || value == NSNull.null) { + FIRCLSErrorLog(@"name (%@) or value (%@) is invalid", name, value); + return; + } + + NSMutableString *string; + + string = + [NSMutableString stringWithFormat:@"--%@\r\nContent-Disposition: form-data; name=\"%@\"\r\n", + self.boundary, name]; + [string appendString:@"Content-Type: text/plain\r\n\r\n"]; + [string appendFormat:@"%@\r\n", value]; + + [self writeData:[string dataUsingEncoding:NSUTF8StringEncoding]]; +} + +- (void)addFile:(NSURL *)fileURL + fileName:(NSString *)fileName + mimeType:(NSString *)mimeType + fieldName:(NSString *)name { + NSData *data = [NSData dataWithContentsOfURL:fileURL]; + + [self addFileData:data fileName:fileName mimeType:mimeType fieldName:name]; +} + +- (BOOL)writeBytes:(const void *)bytes ofLength:(NSUInteger)length { + if ([self.outputStream write:bytes maxLength:length] != length) { + FIRCLSErrorLog(@"Failed to write bytes to stream"); + return NO; + } + + _length += length; + + return YES; +} + +- (void)writeData:(NSData *)data { + FIRCLSEnumerateByteRangesOfNSDataUsingBlock( + data, ^(const void *bytes, NSRange byteRange, BOOL *stop) { + NSUInteger length = byteRange.length; + + if ([self.outputStream write:bytes maxLength:length] != length) { + FIRCLSErrorLog(@"Failed to write data to stream"); + *stop = YES; + return; + } + + self->_length += length; + }); +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSNetworking/FIRCLSNetworkResponseHandler.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSNetworking/FIRCLSNetworkResponseHandler.h new file mode 100644 index 0000000..42f0bb4 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSNetworking/FIRCLSNetworkResponseHandler.h @@ -0,0 +1,87 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +/** + * Type to indicate response status + */ +typedef NS_ENUM(NSInteger, FIRCLSNetworkClientResponseType) { + FIRCLSNetworkClientResponseSuccess, + FIRCLSNetworkClientResponseInvalid, + FIRCLSNetworkClientResponseFailure, + FIRCLSNetworkClientResponseRetry, + FIRCLSNetworkClientResponseBackOff +}; + +typedef NS_ENUM(NSInteger, FIRCLSNetworkErrorType) { + FIRCLSNetworkErrorUnknown = -1, + FIRCLSNetworkErrorFailedToStartOperation = -3, + FIRCLSNetworkErrorResponseInvalid = -4, + FIRCLSNetworkErrorRequestFailed = -5, + FIRCLSNetworkErrorMaximumAttemptsReached = -6, +}; + +extern NSInteger const FIRCLSNetworkErrorUnknownURLCancelReason; + +/** + * This block is an input parameter to handleCompletedResponse: and handleCompletedTask: methods of + * this class. + * @param retryMightSucceed is YES if the request should be retried. + * @param error is the error received back in response. + */ +typedef void (^FIRCLSNetworkResponseCompletionHandlerBlock)(BOOL retryMightSucceed, NSError *error); + +/** + * Error domain for Crashlytics network errors + */ +extern NSString *const FIRCLSNetworkErrorDomain; +/** + * This class handles network responses. + */ +@interface FIRCLSNetworkResponseHandler : NSObject +/** + * Returns the header in the given NSURLResponse with name as key + */ ++ (NSString *)headerForResponse:(NSURLResponse *)response withKey:(NSString *)key; +/** + * Returns Retry-After header value in response, and if absent returns a default retry value + */ ++ (NSTimeInterval)retryValueForResponse:(NSURLResponse *)response; +/** + * Checks if the content type for response matches the request + */ ++ (BOOL)contentTypeForResponse:(NSURLResponse *)response matchesRequest:(NSURLRequest *)request; + ++ (NSInteger)cancelReasonFromURLError:(NSError *)error; + ++ (BOOL)retryableURLError:(NSError *)error; + +/** + * Convenience method that calls back the input block with FIRCLSNetworkClientResponseType after + * checking the response code in response + */ ++ (void)clientResponseType:(NSURLResponse *)response + handler:(void (^)(FIRCLSNetworkClientResponseType type, + NSInteger statusCode))responseTypeAndStatusCodeHandlerBlock; +/** + * Handles a completed response for request and calls back input block. Populates error even if + * error was nil, but response code indicated an error. + */ ++ (void)handleCompletedResponse:(NSURLResponse *)response + forOriginalRequest:(NSURLRequest *)originalRequest + error:(NSError *)error + block:(FIRCLSNetworkResponseCompletionHandlerBlock)completionHandlerBlock; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSNetworking/FIRCLSNetworkResponseHandler.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSNetworking/FIRCLSNetworkResponseHandler.m new file mode 100644 index 0000000..d82cdf6 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSNetworking/FIRCLSNetworkResponseHandler.m @@ -0,0 +1,290 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSNetworkResponseHandler.h" + +@implementation FIRCLSNetworkResponseHandler + +static const NSTimeInterval kFIRCLSNetworkResponseHandlerDefaultRetryInterval = 2.0; +static NSString *const kFIRCLSNetworkResponseHandlerContentType = @"Content-Type"; +NSString *const FIRCLSNetworkErrorDomain = @"FIRCLSNetworkError"; + +NSInteger const FIRCLSNetworkErrorUnknownURLCancelReason = -1; + +#pragma mark - Header Handling ++ (NSString *)headerForResponse:(NSURLResponse *)response withKey:(NSString *)key { + if (![response respondsToSelector:@selector(allHeaderFields)]) { + return nil; + } + + return [((NSHTTPURLResponse *)response).allHeaderFields objectForKey:key]; +} + ++ (NSTimeInterval)retryValueForResponse:(NSURLResponse *)response { + NSString *retryValueString = [self headerForResponse:response withKey:@"Retry-After"]; + if (!retryValueString) { + return kFIRCLSNetworkResponseHandlerDefaultRetryInterval; + } + + NSTimeInterval value = retryValueString.doubleValue; + if (value < 0.0) { + return kFIRCLSNetworkResponseHandlerDefaultRetryInterval; + } + + return value; +} + ++ (NSString *)requestIdForResponse:(NSURLResponse *)response { + return [self headerForResponse:response withKey:@"X-Request-Id"]; +} + ++ (BOOL)contentTypeForResponse:(NSURLResponse *)response matchesRequest:(NSURLRequest *)request { + NSString *accept = [request.allHTTPHeaderFields objectForKey:@"Accept"]; + if (!accept) { + // An omitted accept header is defined to match everything + return YES; + } + + NSString *contentHeader = [self.class headerForResponse:response + withKey:kFIRCLSNetworkResponseHandlerContentType]; + if (!contentHeader) { + // FIRCLSDeveloperLog("Network", @"Content-Type not present in response"); + return NO; + } + + NSString *acceptCharset = request.allHTTPHeaderFields[@"Accept-Charset"]; + + NSArray *parts = [contentHeader componentsSeparatedByString:@"; charset="]; + if (!parts) { + parts = @[ contentHeader ]; + } + + if ([[parts objectAtIndex:0] caseInsensitiveCompare:accept] != NSOrderedSame) { + // FIRCLSDeveloperLog("Network", @"Content-Type does not match Accept"); + return NO; + } + + if (!acceptCharset) { + return YES; + } + + if (parts.count < 2) { + return YES; + } + + return [[parts objectAtIndex:1] caseInsensitiveCompare:acceptCharset] == NSOrderedSame; +} + ++ (NSInteger)cancelReasonFromURLError:(NSError *)error { + if (![[error domain] isEqualToString:NSURLErrorDomain]) { + return FIRCLSNetworkErrorUnknownURLCancelReason; + } + + if ([error code] != NSURLErrorCancelled) { + return FIRCLSNetworkErrorUnknownURLCancelReason; + } + + NSNumber *reason = [[error userInfo] objectForKey:NSURLErrorBackgroundTaskCancelledReasonKey]; + if (reason == nil) { + return FIRCLSNetworkErrorUnknownURLCancelReason; + } + + return [reason integerValue]; +} + ++ (BOOL)retryableURLError:(NSError *)error { + // So far, the only task errors seen are NSURLErrorDomain. For others, we're not + // sure what to do. + if (![[error domain] isEqualToString:NSURLErrorDomain]) { + return NO; + } + + // cases that we know are definitely not retryable + switch ([error code]) { + case NSURLErrorBadURL: + case NSURLErrorUnsupportedURL: + case NSURLErrorHTTPTooManyRedirects: + case NSURLErrorRedirectToNonExistentLocation: + case NSURLErrorUserCancelledAuthentication: + case NSURLErrorUserAuthenticationRequired: + case NSURLErrorAppTransportSecurityRequiresSecureConnection: + case NSURLErrorFileDoesNotExist: + case NSURLErrorFileIsDirectory: + case NSURLErrorDataLengthExceedsMaximum: + case NSURLErrorSecureConnectionFailed: + case NSURLErrorServerCertificateHasBadDate: + case NSURLErrorServerCertificateUntrusted: + case NSURLErrorServerCertificateHasUnknownRoot: + case NSURLErrorServerCertificateNotYetValid: + case NSURLErrorClientCertificateRejected: + case NSURLErrorClientCertificateRequired: + case NSURLErrorBackgroundSessionRequiresSharedContainer: + return NO; + } + + // All other errors, as far as I can tell, are things that could clear up + // without action on the part of the client. + + // NSURLErrorCancelled is a potential special-case. I believe there are + // situations where a cancelled request cannot be successfully restarted. But, + // until I can prove it, we'll retry. There are defnitely many cases where + // a cancelled request definitely can be restarted and will work. + + return YES; +} + +#pragma mark - Error Creation ++ (NSError *)errorForCode:(NSInteger)code userInfo:(NSDictionary *)userInfo { + return [NSError errorWithDomain:FIRCLSNetworkErrorDomain code:code userInfo:userInfo]; +} + ++ (NSError *)errorForResponse:(NSURLResponse *)response + ofType:(FIRCLSNetworkClientResponseType)type + status:(NSInteger)status { + if (type == FIRCLSNetworkClientResponseSuccess) { + return nil; + } + + NSString *requestId = [self requestIdForResponse:response]; + NSString *contentType = [self headerForResponse:response + withKey:kFIRCLSNetworkResponseHandlerContentType]; + + // this could be nil, so be careful + requestId = requestId ? requestId : @""; + contentType = contentType ? contentType : @""; + + NSDictionary *userInfo = @{ + @"type" : @(type), + @"status_code" : @(status), + @"request_id" : requestId, + @"content_type" : contentType + }; + + // compute a reasonable error code type + NSInteger errorCode = FIRCLSNetworkErrorUnknown; + switch (type) { + case FIRCLSNetworkClientResponseFailure: + errorCode = FIRCLSNetworkErrorRequestFailed; + break; + case FIRCLSNetworkClientResponseInvalid: + errorCode = FIRCLSNetworkErrorResponseInvalid; + break; + default: + break; + } + + return [self errorForCode:errorCode userInfo:userInfo]; +} + ++ (void)clientResponseType:(NSURLResponse *)response + handler:(void (^)(FIRCLSNetworkClientResponseType type, + NSInteger statusCode))responseTypeAndStatusCodeHandlerBlock { + if (![response respondsToSelector:@selector(statusCode)]) { + responseTypeAndStatusCodeHandlerBlock(FIRCLSNetworkClientResponseInvalid, 0); + return; + } + + NSInteger code = ((NSHTTPURLResponse *)response).statusCode; + + switch (code) { + case 200: + case 201: + case 202: + case 204: + case 304: + responseTypeAndStatusCodeHandlerBlock(FIRCLSNetworkClientResponseSuccess, code); + return; + case 420: + case 429: + responseTypeAndStatusCodeHandlerBlock(FIRCLSNetworkClientResponseBackOff, code); + return; + case 408: + responseTypeAndStatusCodeHandlerBlock(FIRCLSNetworkClientResponseRetry, code); + return; + case 400: + case 401: + case 403: + case 404: + case 406: + case 410: + case 411: + case 413: + case 419: + case 422: + case 431: + responseTypeAndStatusCodeHandlerBlock(FIRCLSNetworkClientResponseFailure, code); + return; + } + + // check for a 5xx + if (code >= 500 && code <= 599) { + responseTypeAndStatusCodeHandlerBlock(FIRCLSNetworkClientResponseRetry, code); + return; + } + + responseTypeAndStatusCodeHandlerBlock(FIRCLSNetworkClientResponseInvalid, code); +} + ++ (void)handleCompletedResponse:(NSURLResponse *)response + forOriginalRequest:(NSURLRequest *)originalRequest + error:(NSError *)originalError + block: + (FIRCLSNetworkResponseCompletionHandlerBlock)completionHandlerBlock { + // if we have an error, we can just continue + if (originalError) { + BOOL retryable = [self retryableURLError:originalError]; + + completionHandlerBlock(retryable, originalError); + return; + } + + [self.class clientResponseType:response + handler:^(FIRCLSNetworkClientResponseType type, NSInteger statusCode) { + NSError *error = nil; + + switch (type) { + case FIRCLSNetworkClientResponseInvalid: + error = [self errorForResponse:response + ofType:type + status:statusCode]; + break; + case FIRCLSNetworkClientResponseBackOff: + case FIRCLSNetworkClientResponseRetry: + error = [self errorForResponse:response + ofType:type + status:statusCode]; + completionHandlerBlock(YES, error); + return; + case FIRCLSNetworkClientResponseFailure: + error = [self errorForResponse:response + ofType:type + status:statusCode]; + break; + case FIRCLSNetworkClientResponseSuccess: + if (![self contentTypeForResponse:response + matchesRequest:originalRequest]) { + error = [self errorForResponse:response + ofType:FIRCLSNetworkClientResponseInvalid + status:statusCode]; + break; + } + + break; + } + + completionHandlerBlock(NO, error); + }]; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSNetworking/FIRCLSURLBuilder.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSNetworking/FIRCLSURLBuilder.h new file mode 100644 index 0000000..c8fbaa9 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSNetworking/FIRCLSURLBuilder.h @@ -0,0 +1,44 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +/** + * This is a convenience class to ease constructing NSURLs. + */ +@interface FIRCLSURLBuilder : NSObject + +/** + * Convenience method that returns a FIRCLSURLBuilder instance with the input base URL appended to + * it. + */ ++ (instancetype)URLWithBase:(NSString *)base; +/** + * Appends the component to the URL being built by FIRCLSURLBuilder instance + */ +- (void)appendComponent:(NSString *)component; +/** + * Escapes and appends the component to the URL being built by FIRCLSURLBuilder instance + */ +- (void)escapeAndAppendComponent:(NSString *)component; +/** + * Adds a query and value to the URL being built + */ +- (void)appendValue:(id)value forQueryParam:(NSString *)param; +/** + * Returns the built URL + */ +- (NSURL *)URL; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSNetworking/FIRCLSURLBuilder.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSNetworking/FIRCLSURLBuilder.m new file mode 100644 index 0000000..e832c89 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSNetworking/FIRCLSURLBuilder.m @@ -0,0 +1,103 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSURLBuilder.h" + +#import "FIRCLSLogger.h" + +@interface FIRCLSURLBuilder () + +@property(nonatomic) NSMutableString *URLString; +@property(nonatomic) NSUInteger queryParams; + +- (NSString *)escapeString:(NSString *)string; + +@end + +@implementation FIRCLSURLBuilder + ++ (instancetype)URLWithBase:(NSString *)base { + FIRCLSURLBuilder *url = [[FIRCLSURLBuilder alloc] init]; + + [url appendComponent:base]; + + return url; +} + +- (instancetype)init { + self = [super init]; + if (!self) { + return nil; + } + + _URLString = [[NSMutableString alloc] init]; + _queryParams = 0; + + return self; +} + +- (NSString *)escapeString:(NSString *)string { +#if TARGET_OS_WATCH + // TODO: Question - Why does watchOS use a different encoding from the other platforms and the + // Android SDK? + return + [string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet + URLPathAllowedCharacterSet]]; +#else + return + [string stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet + .URLQueryAllowedCharacterSet]; +#endif +} + +- (void)appendComponent:(NSString *)component { + if (component.length == 0) { + FIRCLSErrorLog(@"URLBuilder parameter component must not be empty"); + return; + } + + [self.URLString appendString:component]; +} + +- (void)escapeAndAppendComponent:(NSString *)component { + [self appendComponent:[self escapeString:component]]; +} + +- (void)appendValue:(id)value forQueryParam:(NSString *)param { + if (!value) { + return; + } + + if (self.queryParams == 0) { + [self appendComponent:@"?"]; + } else { + [self appendComponent:@"&"]; + } + + self.queryParams += 1; + + [self appendComponent:param]; + [self appendComponent:@"="]; + if ([value isKindOfClass:NSString.class]) { + [self escapeAndAppendComponent:value]; + } else { + [self escapeAndAppendComponent:[value description]]; + } +} + +- (NSURL *)URL { + return [NSURL URLWithString:self.URLString]; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSOperation/FIRCLSCompoundOperation.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSOperation/FIRCLSCompoundOperation.h new file mode 100644 index 0000000..63b9362 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSOperation/FIRCLSCompoundOperation.h @@ -0,0 +1,58 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSFABAsyncOperation.h" + +/** + * If the compound operation is sent a @c -[cancel] message while executing, it will attempt to + * cancel all operations on its internal queue, and will return an error in its @c asyncCompletion + * block with this value as its code. + */ +FOUNDATION_EXPORT const NSUInteger FIRCLSCompoundOperationErrorCodeCancelled; + +/** + * If one or more of the operations on the @c compoundQueue fail, this operation returns an error + * in its @c asyncCompletion block with this code, and an array of @c NSErrors keyed on @c + * FIRCLSCompoundOperationErrorUserInfoKeyUnderlyingErrors in the @c userInfo dictionary. + */ +FOUNDATION_EXPORT const NSUInteger FIRCLSCompoundOperationErrorCodeSuboperationFailed; + +/** + * When all the operations complete, this @c FIRCLSCompoundOperation instance's @c asyncCompletion + * block is called. If any errors were passed by the suboperations' @c asyncCompletion blocks, they + * are put in an array which can be accessed in the @c userInfo dictionary in the error parameter + * for this instance's @c asyncCompletion block. + */ +FOUNDATION_EXPORT NSString *const FIRCLSCompoundOperationErrorUserInfoKeyUnderlyingErrors; + +/** + * An operation that executes a collection of suboperations on an internal private queue. Any + * instance of @c FIRCLSFABAsyncOperation passed into this instance's @c operations property has the + * potential to return an @c NSError in its @c asyncCompletion block. This instance's @c + * asyncCompletion block will put all such errors in an @c NSArray and return an @c NSError whose @c + * userInfo contains that array keyed by @c FIRCLSCompoundOperationErrorUserInfoKeyUnderlyingErrors. + */ +@interface FIRCLSCompoundOperation : FIRCLSFABAsyncOperation + +/** + * An array of @c NSOperations to execute, which can include instances of @c FIRCLSFABAsyncOperation + * or + * @c FIRCLSCompoundOperation. This operation will not be marked as finished until all suboperations + * are marked as finished. + */ +@property(copy, nonatomic) NSArray *operations; + +@property(strong, nonatomic, readonly) NSOperationQueue *compoundQueue; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSOperation/FIRCLSCompoundOperation.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSOperation/FIRCLSCompoundOperation.m new file mode 100644 index 0000000..5dcf428 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSOperation/FIRCLSCompoundOperation.m @@ -0,0 +1,165 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSCompoundOperation.h" + +#import "FIRCLSFABAsyncOperation_Private.h" + +#define FIRCLS_DISPATCH_QUEUES_AS_OBJECTS OS_OBJECT_USE_OBJC_RETAIN_RELEASE + +const NSUInteger FIRCLSCompoundOperationErrorCodeCancelled = UINT_MAX - 1; +const NSUInteger FIRCLSCompoundOperationErrorCodeSuboperationFailed = UINT_MAX - 2; + +NSString *const FIRCLSCompoundOperationErrorUserInfoKeyUnderlyingErrors = + @"com.google.firebase.crashlytics.FIRCLSCompoundOperation.error.user-info-key.underlying-" + @"errors"; + +static NSString *const FIRCLSCompoundOperationErrorDomain = + @"com.google.firebase.crashlytics.FIRCLSCompoundOperation.error"; +static char *const FIRCLSCompoundOperationCountingQueueLabel = + "com.google.firebase.crashlytics.FIRCLSCompoundOperation.dispatch-queue.counting-queue"; + +@interface FIRCLSCompoundOperation () + +@property(strong, nonatomic, readwrite) NSOperationQueue *compoundQueue; +@property(assign, nonatomic) NSUInteger completedOperations; +@property(strong, nonatomic) NSMutableArray *errors; +#if FIRCLS_DISPATCH_QUEUES_AS_OBJECTS +@property(strong, nonatomic) dispatch_queue_t countingQueue; +#else +@property(assign, nonatomic) dispatch_queue_t countingQueue; +#endif + +@end + +@implementation FIRCLSCompoundOperation + +- (instancetype)init { + self = [super init]; + if (!self) { + return nil; + } + + _compoundQueue = [[NSOperationQueue alloc] init]; + _completedOperations = 0; + _errors = [NSMutableArray array]; + _countingQueue = + dispatch_queue_create(FIRCLSCompoundOperationCountingQueueLabel, DISPATCH_QUEUE_SERIAL); + + return self; +} + +#if !FIRCLS_DISPATCH_QUEUES_AS_OBJECTS +- (void)dealloc { + if (_countingQueue) { + dispatch_release(_countingQueue); + } +} +#endif + +- (void)main { + for (FIRCLSFABAsyncOperation *operation in self.operations) { + [self injectCompoundAsyncCompletionInOperation:operation]; + [self injectCompoundSyncCompletionInOperation:operation]; + + [self.compoundQueue addOperation:operation]; + } +} + +- (void)cancel { + if (self.compoundQueue.operations.count > 0) { + [self.compoundQueue cancelAllOperations]; + dispatch_sync(self.countingQueue, ^{ + [self attemptCompoundCompletion]; + }); + } else { + for (NSOperation *operation in self.operations) { + [operation cancel]; + } + + // we have to add the operations to the queue in order for their isFinished property to be set + // to true. + [self.compoundQueue addOperations:self.operations waitUntilFinished:NO]; + } + [super cancel]; +} + +- (void)injectCompoundAsyncCompletionInOperation:(FIRCLSFABAsyncOperation *)operation { + __weak FIRCLSCompoundOperation *weakSelf = self; + FIRCLSFABAsyncOperationCompletionBlock originalAsyncCompletion = [operation.asyncCompletion copy]; + FIRCLSFABAsyncOperationCompletionBlock completion = ^(NSError *error) { + __strong FIRCLSCompoundOperation *strongSelf = weakSelf; + + if (originalAsyncCompletion) { + dispatch_sync(strongSelf.countingQueue, ^{ + originalAsyncCompletion(error); + }); + } + + [strongSelf updateCompletionCountsWithError:error]; + }; + operation.asyncCompletion = completion; +} + +- (void)injectCompoundSyncCompletionInOperation:(FIRCLSFABAsyncOperation *)operation { + __weak FIRCLSCompoundOperation *weakSelf = self; + void (^originalSyncCompletion)(void) = [operation.completionBlock copy]; + void (^completion)(void) = ^{ + __strong FIRCLSCompoundOperation *strongSelf = weakSelf; + + if (originalSyncCompletion) { + dispatch_sync(strongSelf.countingQueue, ^{ + originalSyncCompletion(); + }); + } + + dispatch_sync(strongSelf.countingQueue, ^{ + [strongSelf attemptCompoundCompletion]; + }); + }; + operation.completionBlock = completion; +} + +- (void)updateCompletionCountsWithError:(NSError *)error { + dispatch_sync(self.countingQueue, ^{ + if (!error) { + self.completedOperations += 1; + } else { + [self.errors addObject:error]; + } + }); +} + +- (void)attemptCompoundCompletion { + if (self.isCancelled) { + [self finishWithError:[NSError errorWithDomain:FIRCLSCompoundOperationErrorDomain + code:FIRCLSCompoundOperationErrorCodeCancelled + userInfo:@{ + NSLocalizedDescriptionKey : [NSString + stringWithFormat:@"%@ cancelled", self.name] + }]]; + self.asyncCompletion = nil; + } else if (self.completedOperations + self.errors.count == self.operations.count) { + NSError *error = nil; + if (self.errors.count > 0) { + error = [NSError + errorWithDomain:FIRCLSCompoundOperationErrorDomain + code:FIRCLSCompoundOperationErrorCodeSuboperationFailed + userInfo:@{FIRCLSCompoundOperationErrorUserInfoKeyUnderlyingErrors : self.errors}]; + } + [self finishWithError:error]; + } +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSOperation/FIRCLSFABAsyncOperation.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSOperation/FIRCLSFABAsyncOperation.h new file mode 100644 index 0000000..e5d2c7e --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSOperation/FIRCLSFABAsyncOperation.h @@ -0,0 +1,39 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +/** + * Completion block that can be called in your subclass implementation. It is up to you when you + * want to call it. + */ +typedef void (^FIRCLSFABAsyncOperationCompletionBlock)(NSError *__nullable error); + +/** + * FIRCLSFABAsyncOperation is a subclass of NSOperation that allows for asynchronous work to be + * performed, for things like networking, IPC or UI-driven logic. Create your own subclasses to + * encapsulate custom logic. + * @warning When subclassing to create your own operations, be sure to call -[finishWithError:] at + * some point, or program execution will hang. + * @see -[finishWithError:] in FIRCLSFABAsyncOperation_Private.h + */ +@interface FIRCLSFABAsyncOperation : NSOperation + +/** + * Add a callback method for consumers of your subclasses to set when the asynchronous work is + * marked as complete with -[finishWithError:]. + */ +@property(copy, nonatomic, nullable) FIRCLSFABAsyncOperationCompletionBlock asyncCompletion; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSOperation/FIRCLSFABAsyncOperation.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSOperation/FIRCLSFABAsyncOperation.m new file mode 100644 index 0000000..dcad16a --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSOperation/FIRCLSFABAsyncOperation.m @@ -0,0 +1,146 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSFABAsyncOperation.h" + +#import "FIRCLSFABAsyncOperation_Private.h" + +@interface FIRCLSFABAsyncOperation () { + BOOL _internalExecuting; + BOOL _internalFinished; +} + +@property(nonatomic, strong) NSRecursiveLock *lock; + +@end + +@implementation FIRCLSFABAsyncOperation + +- (instancetype)init { + self = [super init]; + if (!self) { + return nil; + } + + _internalExecuting = NO; + _internalFinished = NO; + + _lock = [[NSRecursiveLock alloc] init]; + _lock.name = [NSString stringWithFormat:@"com.google.firebase.crashlytics.%@-lock", [self class]]; + ; + + return self; +} + +#pragma mark - NSOperation Overrides +- (BOOL)isConcurrent { + return YES; +} + +- (BOOL)isAsynchronous { + return YES; +} + +- (BOOL)isExecuting { + [self.lock lock]; + BOOL result = _internalExecuting; + [self.lock unlock]; + + return result; +} + +- (BOOL)isFinished { + [self.lock lock]; + BOOL result = _internalFinished; + [self.lock unlock]; + + return result; +} + +- (void)start { + if ([self checkForCancellation]) { + return; + } + + [self markStarted]; + + [self main]; +} + +#pragma mark - Utilities +- (void)changeValueForKey:(NSString *)key inBlock:(void (^)(void))block { + [self willChangeValueForKey:key]; + block(); + [self didChangeValueForKey:key]; +} + +- (void)lock:(void (^)(void))block { + [self.lock lock]; + block(); + [self.lock unlock]; +} + +- (BOOL)checkForCancellation { + if ([self isCancelled]) { + [self markDone]; + return YES; + } + + return NO; +} + +#pragma mark - State Management +- (void)unlockedMarkFinished { + [self changeValueForKey:@"isFinished" + inBlock:^{ + self->_internalFinished = YES; + }]; +} + +- (void)unlockedMarkStarted { + [self changeValueForKey:@"isExecuting" + inBlock:^{ + self->_internalExecuting = YES; + }]; +} + +- (void)unlockedMarkComplete { + [self changeValueForKey:@"isExecuting" + inBlock:^{ + self->_internalExecuting = NO; + }]; +} + +- (void)markStarted { + [self lock:^{ + [self unlockedMarkStarted]; + }]; +} + +- (void)markDone { + [self lock:^{ + [self unlockedMarkComplete]; + [self unlockedMarkFinished]; + }]; +} + +#pragma mark - Protected +- (void)finishWithError:(NSError *)error { + if (self.asyncCompletion) { + self.asyncCompletion(error); + } + [self markDone]; +} + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSOperation/FIRCLSFABAsyncOperation_Private.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSOperation/FIRCLSFABAsyncOperation_Private.h new file mode 100644 index 0000000..d1e5797 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSOperation/FIRCLSFABAsyncOperation_Private.h @@ -0,0 +1,32 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSFABAsyncOperation.h" + +@interface FIRCLSFABAsyncOperation (Private) + +/** + * Subclasses must call this method when they are done performing work. When it is called is up to + * you; it can be directly after kicking of a network request, say, or in the callback for its + * response. Once this method is called, the operation queue it is on will begin executing the next + * waiting operation. If you directly invoked -[start] on the instance, execution will proceed to + * the next code statement. + * @note as soon as this method is called, @c NSOperation's standard @c completionBlock will be + * executed if one exists, as a result of setting the operation's isFinished property to YES, and + * the asyncCompletion block is called. + * @param error Any error to pass to asyncCompletion, or nil if there is none. + */ +- (void)finishWithError:(NSError *__nullable)error; + +@end diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSOperation/FIRCLSOperation.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSOperation/FIRCLSOperation.h new file mode 100644 index 0000000..83fc694 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSOperation/FIRCLSOperation.h @@ -0,0 +1,19 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +#import "FIRCLSCompoundOperation.h" +#import "FIRCLSFABAsyncOperation.h" +#import "FIRCLSFABAsyncOperation_Private.h" diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSUUID.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSUUID.h new file mode 100644 index 0000000..dc3aeda --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSUUID.h @@ -0,0 +1,27 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import +#import "FIRCLSConstants.h" + +/** + * Generates and returns a UUID + * This is also used by used by Answers to generate UUIDs. + */ +NSString *FIRCLSGenerateUUID(void); + +/** + * Converts the input uint8_t UUID to NSString + */ +NSString *FIRCLSUUIDToNSString(const uint8_t *uuid); diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSUUID.m b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSUUID.m new file mode 100644 index 0000000..6534d41 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/Shared/FIRCLSUUID.m @@ -0,0 +1,39 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRCLSUUID.h" + +#import "FIRCLSByteUtility.h" + +static NSInteger const FIRCLSUUIDStringLength = 33; + +#pragma mark Public methods + +NSString *FIRCLSGenerateUUID(void) { + NSString *string; + + CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault); + string = CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuid)); + CFRelease(uuid); + + return string; +} + +NSString *FIRCLSUUIDToNSString(const uint8_t *uuid) { + char uuidString[FIRCLSUUIDStringLength]; + + FIRCLSSafeHexToString(uuid, 16, uuidString); + + return [NSString stringWithUTF8String:uuidString]; +} diff --git a/!main project/Pods/FirebaseCrashlytics/Crashlytics/third_party/libunwind/dwarf.h b/!main project/Pods/FirebaseCrashlytics/Crashlytics/third_party/libunwind/dwarf.h new file mode 100644 index 0000000..9c81868 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/Crashlytics/third_party/libunwind/dwarf.h @@ -0,0 +1,256 @@ +/* libunwind - a platform-independent unwind library + Copyright (c) 2003-2005 Hewlett-Packard Development Company, L.P. + Contributed by David Mosberger-Tang + +This file is part of libunwind. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + +#pragma once + +// +#define DWARF_EXTENDED_LENGTH_FLAG (0xffffffff) +#define DWARF_CIE_ID_CIE_FLAG (0) + +// Exception Handling Pointer Encoding constants +#define DW_EH_PE_VALUE_MASK (0x0F) +#define DW_EH_PE_RELATIVE_OFFSET_MASK (0x70) + +// Register Definitions +#define DW_EN_MAX_REGISTER_NUMBER (120) + +enum { + DW_EH_PE_ptr = 0x00, + DW_EH_PE_uleb128 = 0x01, + DW_EH_PE_udata2 = 0x02, + DW_EH_PE_udata4 = 0x03, + DW_EH_PE_udata8 = 0x04, + DW_EH_PE_signed = 0x08, + DW_EH_PE_sleb128 = 0x09, + DW_EH_PE_sdata2 = 0x0A, + DW_EH_PE_sdata4 = 0x0B, + DW_EH_PE_sdata8 = 0x0C, + + DW_EH_PE_absptr = 0x00, + DW_EH_PE_pcrel = 0x10, + DW_EH_PE_textrel = 0x20, + DW_EH_PE_datarel = 0x30, + DW_EH_PE_funcrel = 0x40, + DW_EH_PE_aligned = 0x50, + DW_EH_PE_indirect = 0x80, + DW_EH_PE_omit = 0xFF +}; + +// Unwind Instructions + +#define DW_CFA_OPCODE_MASK (0xC0) +#define DW_CFA_OPERAND_MASK (0x3F) + +enum { + DW_CFA_nop = 0x0, + DW_CFA_set_loc = 0x1, + DW_CFA_advance_loc1 = 0x2, + DW_CFA_advance_loc2 = 0x3, + DW_CFA_advance_loc4 = 0x4, + DW_CFA_offset_extended = 0x5, + DW_CFA_restore_extended = 0x6, + DW_CFA_undefined = 0x7, + DW_CFA_same_value = 0x8, + DW_CFA_register = 0x9, + DW_CFA_remember_state = 0xA, + DW_CFA_restore_state = 0xB, + DW_CFA_def_cfa = 0xC, + DW_CFA_def_cfa_register = 0xD, + DW_CFA_def_cfa_offset = 0xE, + DW_CFA_def_cfa_expression = 0xF, + DW_CFA_expression = 0x10, + DW_CFA_offset_extended_sf = 0x11, + DW_CFA_def_cfa_sf = 0x12, + DW_CFA_def_cfa_offset_sf = 0x13, + DW_CFA_val_offset = 0x14, + DW_CFA_val_offset_sf = 0x15, + DW_CFA_val_expression = 0x16, + + // opcode is in high 2 bits, operand in is lower 6 bits + DW_CFA_advance_loc = 0x40, // operand is delta + DW_CFA_offset = 0x80, // operand is register + DW_CFA_restore = 0xC0, // operand is register + + // GNU extensions + DW_CFA_GNU_window_save = 0x2D, + DW_CFA_GNU_args_size = 0x2E, + DW_CFA_GNU_negative_offset_extended = 0x2F +}; + +// Expression Instructions +enum { + DW_OP_addr = 0x03, + DW_OP_deref = 0x06, + DW_OP_const1u = 0x08, + DW_OP_const1s = 0x09, + DW_OP_const2u = 0x0A, + DW_OP_const2s = 0x0B, + DW_OP_const4u = 0x0C, + DW_OP_const4s = 0x0D, + DW_OP_const8u = 0x0E, + DW_OP_const8s = 0x0F, + DW_OP_constu = 0x10, + DW_OP_consts = 0x11, + DW_OP_dup = 0x12, + DW_OP_drop = 0x13, + DW_OP_over = 0x14, + DW_OP_pick = 0x15, + DW_OP_swap = 0x16, + DW_OP_rot = 0x17, + DW_OP_xderef = 0x18, + DW_OP_abs = 0x19, + DW_OP_and = 0x1A, + DW_OP_div = 0x1B, + DW_OP_minus = 0x1C, + DW_OP_mod = 0x1D, + DW_OP_mul = 0x1E, + DW_OP_neg = 0x1F, + DW_OP_not = 0x20, + DW_OP_or = 0x21, + DW_OP_plus = 0x22, + DW_OP_plus_uconst = 0x23, + DW_OP_shl = 0x24, + DW_OP_shr = 0x25, + DW_OP_shra = 0x26, + DW_OP_xor = 0x27, + DW_OP_skip = 0x2F, + DW_OP_bra = 0x28, + DW_OP_eq = 0x29, + DW_OP_ge = 0x2A, + DW_OP_gt = 0x2B, + DW_OP_le = 0x2C, + DW_OP_lt = 0x2D, + DW_OP_ne = 0x2E, + DW_OP_lit0 = 0x30, + DW_OP_lit1 = 0x31, + DW_OP_lit2 = 0x32, + DW_OP_lit3 = 0x33, + DW_OP_lit4 = 0x34, + DW_OP_lit5 = 0x35, + DW_OP_lit6 = 0x36, + DW_OP_lit7 = 0x37, + DW_OP_lit8 = 0x38, + DW_OP_lit9 = 0x39, + DW_OP_lit10 = 0x3A, + DW_OP_lit11 = 0x3B, + DW_OP_lit12 = 0x3C, + DW_OP_lit13 = 0x3D, + DW_OP_lit14 = 0x3E, + DW_OP_lit15 = 0x3F, + DW_OP_lit16 = 0x40, + DW_OP_lit17 = 0x41, + DW_OP_lit18 = 0x42, + DW_OP_lit19 = 0x43, + DW_OP_lit20 = 0x44, + DW_OP_lit21 = 0x45, + DW_OP_lit22 = 0x46, + DW_OP_lit23 = 0x47, + DW_OP_lit24 = 0x48, + DW_OP_lit25 = 0x49, + DW_OP_lit26 = 0x4A, + DW_OP_lit27 = 0x4B, + DW_OP_lit28 = 0x4C, + DW_OP_lit29 = 0x4D, + DW_OP_lit30 = 0x4E, + DW_OP_lit31 = 0x4F, + DW_OP_reg0 = 0x50, + DW_OP_reg1 = 0x51, + DW_OP_reg2 = 0x52, + DW_OP_reg3 = 0x53, + DW_OP_reg4 = 0x54, + DW_OP_reg5 = 0x55, + DW_OP_reg6 = 0x56, + DW_OP_reg7 = 0x57, + DW_OP_reg8 = 0x58, + DW_OP_reg9 = 0x59, + DW_OP_reg10 = 0x5A, + DW_OP_reg11 = 0x5B, + DW_OP_reg12 = 0x5C, + DW_OP_reg13 = 0x5D, + DW_OP_reg14 = 0x5E, + DW_OP_reg15 = 0x5F, + DW_OP_reg16 = 0x60, + DW_OP_reg17 = 0x61, + DW_OP_reg18 = 0x62, + DW_OP_reg19 = 0x63, + DW_OP_reg20 = 0x64, + DW_OP_reg21 = 0x65, + DW_OP_reg22 = 0x66, + DW_OP_reg23 = 0x67, + DW_OP_reg24 = 0x68, + DW_OP_reg25 = 0x69, + DW_OP_reg26 = 0x6A, + DW_OP_reg27 = 0x6B, + DW_OP_reg28 = 0x6C, + DW_OP_reg29 = 0x6D, + DW_OP_reg30 = 0x6E, + DW_OP_reg31 = 0x6F, + DW_OP_breg0 = 0x70, + DW_OP_breg1 = 0x71, + DW_OP_breg2 = 0x72, + DW_OP_breg3 = 0x73, + DW_OP_breg4 = 0x74, + DW_OP_breg5 = 0x75, + DW_OP_breg6 = 0x76, + DW_OP_breg7 = 0x77, + DW_OP_breg8 = 0x78, + DW_OP_breg9 = 0x79, + DW_OP_breg10 = 0x7A, + DW_OP_breg11 = 0x7B, + DW_OP_breg12 = 0x7C, + DW_OP_breg13 = 0x7D, + DW_OP_breg14 = 0x7E, + DW_OP_breg15 = 0x7F, + DW_OP_breg16 = 0x80, + DW_OP_breg17 = 0x81, + DW_OP_breg18 = 0x82, + DW_OP_breg19 = 0x83, + DW_OP_breg20 = 0x84, + DW_OP_breg21 = 0x85, + DW_OP_breg22 = 0x86, + DW_OP_breg23 = 0x87, + DW_OP_breg24 = 0x88, + DW_OP_breg25 = 0x89, + DW_OP_breg26 = 0x8A, + DW_OP_breg27 = 0x8B, + DW_OP_breg28 = 0x8C, + DW_OP_breg29 = 0x8D, + DW_OP_breg30 = 0x8E, + DW_OP_breg31 = 0x8F, + DW_OP_regx = 0x90, + DW_OP_fbreg = 0x91, + DW_OP_bregx = 0x92, + DW_OP_piece = 0x93, + DW_OP_deref_size = 0x94, + DW_OP_xderef_size = 0x95, + DW_OP_nop = 0x96, + DW_OP_push_object_addres = 0x97, + DW_OP_call2 = 0x98, + DW_OP_call4 = 0x99, + DW_OP_call_ref = 0x9A, + DW_OP_lo_user = 0xE0, + DW_OP_APPLE_uninit = 0xF0, + DW_OP_hi_user = 0xFF +}; diff --git a/!main project/Pods/FirebaseCrashlytics/README.md b/!main project/Pods/FirebaseCrashlytics/README.md new file mode 100644 index 0000000..5097a89 --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/README.md @@ -0,0 +1,254 @@ +# Firebase iOS Open Source Development [![Build Status](https://travis-ci.org/firebase/firebase-ios-sdk.svg?branch=master)](https://travis-ci.org/firebase/firebase-ios-sdk) + +This repository contains a subset of the Firebase iOS SDK source. It currently +includes FirebaseCore, FirebaseABTesting, FirebaseAuth, FirebaseDatabase, +FirebaseFirestore, FirebaseFunctions, FirebaseInstanceID, FirebaseInAppMessaging, +FirebaseInAppMessagingDisplay, FirebaseMessaging, FirebaseRemoteConfig, and +FirebaseStorage. + +The repository also includes GoogleUtilities source. The +[GoogleUtilities](GoogleUtilities/README.md) pod is +a set of utilities used by Firebase and other Google products. + +Firebase is an app development platform with tools to help you build, grow and +monetize your app. More information about Firebase can be found at +[https://firebase.google.com](https://firebase.google.com). + +## Installation + +See the three subsections for details about three different installation methods. +1. [Standard pod install](README.md#standard-pod-install) +1. [Installing from the GitHub repo](README.md#installing-from-github) +1. [Experimental Carthage](README.md#carthage-ios-only) + +### Standard pod install + +Go to +[https://firebase.google.com/docs/ios/setup](https://firebase.google.com/docs/ios/setup). + +### Installing from GitHub + +For releases starting with 5.0.0, the source for each release is also deployed +to CocoaPods master and available via standard +[CocoaPods Podfile syntax](https://guides.cocoapods.org/syntax/podfile.html#pod). + +These instructions can be used to access the Firebase repo at other branches, +tags, or commits. + +#### Background + +See +[the Podfile Syntax Reference](https://guides.cocoapods.org/syntax/podfile.html#pod) +for instructions and options about overriding pod source locations. + +#### Accessing Firebase Source Snapshots + +All of the official releases are tagged in this repo and available via CocoaPods. To access a local +source snapshot or unreleased branch, use Podfile directives like the following: + +To access FirebaseFirestore via a branch: +``` +pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +``` + +To access FirebaseMessaging via a checked out version of the firebase-ios-sdk repo do: + +``` +pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk' +pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk' +``` + +### Carthage (iOS only) + +Instructions for the experimental Carthage distribution are at +[Carthage](Carthage.md). + +### Rome + +Instructions for installing binary frameworks via +[Rome](https://github.com/CocoaPods/Rome) are at [Rome](Rome.md). + +## Development + +To develop Firebase software in this repository, ensure that you have at least +the following software: + + * Xcode 10.1 (or later) + * CocoaPods 1.7.2 (or later) + * [CocoaPods generate](https://github.com/square/cocoapods-generate) + +For the pod that you want to develop: + +`pod gen Firebase{name here}.podspec --local-sources=./ --auto-open --platforms=ios` + +Note: If the CocoaPods cache is out of date, you may need to run +`pod repo update` before the `pod gen` command. + +Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for +those platforms. Since 10.2, Xcode does not properly handle multi-platform +CocoaPods workspaces. + +Firestore has a self contained Xcode project. See +[Firestore/README.md](Firestore/README.md). + +### Development for Catalyst +* `pod gen {name here}.podspec --local-sources=./ --auto-open --platforms=ios` +* Check the Mac box in the App-iOS Build Settings +* Sign the App in the Settings Signing & Capabilities tab +* Click Pods in the Project Manager +* Add Signing to the iOS host app and unit test targets +* Select the Unit-unit scheme +* Run it to build and test + +### Adding a New Firebase Pod + +See [AddNewPod.md](AddNewPod.md). + +### Code Formatting + +To ensure that the code is formatted consistently, run the script +[./scripts/style.sh](https://github.com/firebase/firebase-ios-sdk/blob/master/scripts/style.sh) +before creating a PR. + +Travis will verify that any code changes are done in a style compliant way. Install +`clang-format` and `swiftformat`. +These commands will get the right versions: + +``` +brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/e3496d9/Formula/clang-format.rb +brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/7963c3d/Formula/swiftformat.rb +``` + +Note: if you already have a newer version of these installed you may need to +`brew switch` to this version. + +To update this section, find the versions of clang-format and swiftformat.rb to +match the versions in the CI failure logs +[here](https://github.com/Homebrew/homebrew-core/tree/master/Formula). + +### Running Unit Tests + +Select a scheme and press Command-u to build a component and run its unit tests. + +#### Viewing Code Coverage + +First, make sure that [xcov](https://github.com/nakiostudio/xcov) is installed with `gem install xcov`. + +After running the `AllUnitTests_iOS` scheme in Xcode, execute +`xcov --workspace Firebase.xcworkspace --scheme AllUnitTests_iOS --output_directory xcov_output` +at Example/ in the terminal. This will aggregate the coverage, and you can run `open xcov_output/index.html` to see the results. + +### Running Sample Apps +In order to run the sample apps and integration tests, you'll need valid +`GoogleService-Info.plist` files for those samples. The Firebase Xcode project contains dummy plist +files without real values, but can be replaced with real plist files. To get your own +`GoogleService-Info.plist` files: + +1. Go to the [Firebase Console](https://console.firebase.google.com/) +2. Create a new Firebase project, if you don't already have one +3. For each sample app you want to test, create a new Firebase app with the sample app's bundle +identifier (e.g. `com.google.Database-Example`) +4. Download the resulting `GoogleService-Info.plist` and replace the appropriate dummy plist file +(e.g. in [Example/Database/App/](Example/Database/App/)); + +Some sample apps like Firebase Messaging ([Example/Messaging/App](Example/Messaging/App)) require +special Apple capabilities, and you will have to change the sample app to use a unique bundle +identifier that you can control in your own Apple Developer account. + +## Specific Component Instructions +See the sections below for any special instructions for those components. + +### Firebase Auth + +If you're doing specific Firebase Auth development, see +[the Auth Sample README](Example/Auth/README.md) for instructions about +building and running the FirebaseAuth pod along with various samples and tests. + +### Firebase Database + +To run the Database Integration tests, make your database authentication rules +[public](https://firebase.google.com/docs/database/security/quickstart). + +### Firebase Storage + +To run the Storage Integration tests, follow the instructions in +[FIRStorageIntegrationTests.m](Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m). + +#### Push Notifications + +Push notifications can only be delivered to specially provisioned App IDs in the developer portal. +In order to actually test receiving push notifications, you will need to: + +1. Change the bundle identifier of the sample app to something you own in your Apple Developer +account, and enable that App ID for push notifications. +2. You'll also need to +[upload your APNs Provider Authentication Key or certificate to the Firebase Console](https://firebase.google.com/docs/cloud-messaging/ios/certs) +at **Project Settings > Cloud Messaging > [Your Firebase App]**. +3. Ensure your iOS device is added to your Apple Developer portal as a test device. + +#### iOS Simulator + +The iOS Simulator cannot register for remote notifications, and will not receive push notifications. +In order to receive push notifications, you'll have to follow the steps above and run the app on a +physical device. + +## Community Supported Efforts + +We've seen an amazing amount of interest and contributions to improve the Firebase SDKs, and we are +very grateful! We'd like to empower as many developers as we can to be able to use Firebase and +participate in the Firebase community. + +### tvOS, macOS, and Catalyst +Thanks to contributions from the community, many of Firebase SDKs now compile, run unit tests, and work on +tvOS, macOS, and Catalyst. + +For tvOS, checkout the [Sample](Example/tvOSSample). + +Keep in mind that macOS, Catalyst and tvOS are not officially supported by Firebase, and this +repository is actively developed primarily for iOS. While we can catch basic unit test issues with +Travis, there may be some changes where the SDK no longer works as expected on macOS or tvOS. If you +encounter this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues). + +During app setup in the console, you may get to a step that mentions something like "Checking if the app +has communicated with our servers". This relies on Analytics and will not work on macOS/tvOS/Catalyst. +**It's safe to ignore the message and continue**, the rest of the SDKs will work as expected. + +To install, add a subset of the following to the Podfile: + +``` +pod 'Firebase/ABTesting' +pod 'Firebase/Auth' +pod 'Firebase/Crashlytics' +pod 'Firebase/Database' +pod 'Firebase/Firestore' +pod 'Firebase/Functions' +pod 'Firebase/Messaging' +pod 'Firebase/RemoteConfig' +pod 'Firebase/Storage' +``` + +#### Additional Catalyst Notes + +* FirebaseAuth and FirebaseMessaging require adding `Keychain Sharing Capability` +to Build Settings. +* FirebaseFirestore requires signing the +[gRPC Resource target](https://github.com/firebase/firebase-ios-sdk/issues/3500#issuecomment-518741681). + +## Roadmap + +See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source +plans and directions. + +## Contributing + +See [Contributing](CONTRIBUTING.md) for more information on contributing to the Firebase +iOS SDK. + +## License + +The contents of this repository is licensed under the +[Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0). + +Your use of Firebase is governed by the +[Terms of Service for Firebase Services](https://firebase.google.com/terms/). diff --git a/!main project/Pods/FirebaseCrashlytics/run b/!main project/Pods/FirebaseCrashlytics/run new file mode 100755 index 0000000..9316eea --- /dev/null +++ b/!main project/Pods/FirebaseCrashlytics/run @@ -0,0 +1,76 @@ +#!/bin/sh + +# Copyright 2019 Google +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# run +# +# This script is meant to be run as a Run Script in the "Build Phases" section +# of your Xcode project. It sends debug symbols to symbolicate stacktraces, +# sends build events to track versions, and onboards apps for Crashlytics. +# +# This script calls upload-symbols twice: +# +# 1) First it calls upload-symbols synchronously in "validation" mode. If the +# script finds issues with the build environment, it will report errors to Xcode. +# In validation mode it exits before doing any time consuming work. +# +# 2) Then it calls upload-symbols in the background to actually send the build +# event and upload symbols. It does this in the background so that it doesn't +# slow down your builds. If an error happens here, you won't see it in Xcode. +# +# You can find the output for the background execution in Console.app, by +# searching for "upload-symbols". +# +# If you want verbose output, you can pass the --debug flag to this script +# + +# Figure out where we're being called from +DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) + +# If the first argument is specified without a dash, treat it as the Fabric API +# Key and add it as an argument. +if [ -z "$1" ] || [[ $1 == -* ]]; then + API_KEY_ARG="" +else + API_KEY_ARG="-a $1"; shift +fi + +# Build up the arguments list, passing through any flags added after the +# API Key +ARGUMENTS="$API_KEY_ARG $@" +VALIDATE_ARGUMENTS="$ARGUMENTS --build-phase --validate" +UPLOAD_ARGUMENTS="$ARGUMENTS --build-phase" + +# Quote the path to handle folders with special characters +COMMAND_PATH="\"$DIR/upload-symbols\" " + +# Ensure params are as expected, run in sync mode to validate, +# and cause a build error if validation fails +eval $COMMAND_PATH$VALIDATE_ARGUMENTS +return_code=$? + +if [[ $return_code != 0 ]]; then + exit $return_code +fi + +# Verification passed, convert and upload cSYMs in the background to prevent +# build delays +# +# Note: Validation is performed again at this step before upload +# +# Note: Output can still be found in Console.app, by searching for +# "upload-symbols" +# +eval $COMMAND_PATH$UPLOAD_ARGUMENTS > /dev/null 2>&1 & diff --git a/!main project/Pods/FirebaseCrashlytics/upload-symbols b/!main project/Pods/FirebaseCrashlytics/upload-symbols new file mode 100755 index 0000000..f76a77c Binary files /dev/null and b/!main project/Pods/FirebaseCrashlytics/upload-symbols differ diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDataSnapshot.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDataSnapshot.m new file mode 100644 index 0000000..c1d48ec --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDataSnapshot.m @@ -0,0 +1,105 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRDataSnapshot.h" +#import "FChildrenNode.h" +#import "FIRDataSnapshot_Private.h" +#import "FIRDatabaseReference.h" +#import "FTransformedEnumerator.h" +#import "FValidation.h" + +@interface FIRDataSnapshot () +@property(nonatomic, strong) FIRDatabaseReference *ref; +@end + +@implementation FIRDataSnapshot + +- (id)initWithRef:(FIRDatabaseReference *)ref indexedNode:(FIndexedNode *)node { + self = [super init]; + if (self != nil) { + self->_ref = ref; + self->_node = node; + } + return self; +} + +- (id)value { + return [self.node.node val]; +} + +- (id)valueInExportFormat { + return [self.node.node valForExport:YES]; +} + +- (FIRDataSnapshot *)childSnapshotForPath:(NSString *)childPathString { + [FValidation validateFrom:@"child:" validPathString:childPathString]; + FPath *childPath = [[FPath alloc] initWith:childPathString]; + FIRDatabaseReference *childRef = [self.ref child:childPathString]; + + id childNode = [self.node.node getChild:childPath]; + return [[FIRDataSnapshot alloc] + initWithRef:childRef + indexedNode:[FIndexedNode indexedNodeWithNode:childNode]]; +} + +- (BOOL)hasChild:(NSString *)childPathString { + [FValidation validateFrom:@"hasChild:" validPathString:childPathString]; + FPath *childPath = [[FPath alloc] initWith:childPathString]; + return ![[self.node.node getChild:childPath] isEmpty]; +} + +- (id)priority { + id priority = [self.node.node getPriority]; + return priority.val; +} + +- (BOOL)hasChildren { + if ([self.node.node isLeafNode]) { + return false; + } else { + return ![self.node.node isEmpty]; + } +} + +- (BOOL)exists { + return ![self.node.node isEmpty]; +} + +- (NSString *)key { + return [self.ref key]; +} + +- (NSUInteger)childrenCount { + return [self.node.node numChildren]; +} + +- (NSEnumerator *)children { + return [[FTransformedEnumerator alloc] + initWithEnumerator:self.node.childEnumerator + andTransform:^id(FNamedNode *node) { + FIRDatabaseReference *childRef = [self.ref child:node.name]; + return [[FIRDataSnapshot alloc] + initWithRef:childRef + indexedNode:[FIndexedNode indexedNodeWithNode:node.node]]; + }]; +} + +- (NSString *)description { + return + [NSString stringWithFormat:@"Snap (%@) %@", self.key, self.node.node]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabase.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabase.m new file mode 100644 index 0000000..8957336 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabase.m @@ -0,0 +1,234 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import +#import +#import +#import +#import +#import +#import + +#import "FIRDatabase.h" +#import "FIRDatabaseComponent.h" +#import "FIRDatabaseConfig_Private.h" +#import "FIRDatabaseQuery_Private.h" +#import "FIRDatabaseReference_Private.h" +#import "FIRDatabase_Private.h" +#import "FRepoInfo.h" +#import "FValidation.h" + +@implementation FIRDatabase + +// The STR and STR_EXPAND macro allow a numeric version passed to he compiler +// driver with a -D to be treated as a string instead of an invalid floating +// point value. +#define STR(x) STR_EXPAND(x) +#define STR_EXPAND(x) #x +static const char *FIREBASE_SEMVER = (const char *)STR(FIRDatabase_VERSION); + ++ (FIRDatabase *)database { + if (![FIRApp isDefaultAppConfigured]) { + [NSException raise:@"FIRAppNotConfigured" + format:@"Failed to get default Firebase Database instance. " + @"Must call `[FIRApp " + @"configure]` (`FirebaseApp.configure()` in Swift) " + @"before using " + @"Firebase Database."]; + } + return [FIRDatabase databaseForApp:[FIRApp defaultApp]]; +} + ++ (FIRDatabase *)databaseWithURL:(NSString *)url { + FIRApp *app = [FIRApp defaultApp]; + if (app == nil) { + [NSException + raise:@"FIRAppNotConfigured" + format: + @"Failed to get default Firebase Database instance. " + @"Must call `[FIRApp configure]` (`FirebaseApp.configure()` in " + @"Swift) before using Firebase Database."]; + } + return [FIRDatabase databaseForApp:app URL:url]; +} + ++ (FIRDatabase *)databaseForApp:(FIRApp *)app { + if (app == nil) { + [NSException raise:@"InvalidFIRApp" + format:@"nil FIRApp instance passed to databaseForApp."]; + } + return [FIRDatabase databaseForApp:app URL:app.options.databaseURL]; +} + ++ (FIRDatabase *)databaseForApp:(FIRApp *)app URL:(NSString *)url { + if (app == nil) { + [NSException raise:@"InvalidFIRApp" + format:@"nil FIRApp instance passed to databaseForApp."]; + } + if (url == nil) { + [NSException raise:@"MissingDatabaseURL" + format:@"Failed to get FirebaseDatabase instance: " + @"Specify DatabaseURL within FIRApp or from your " + @"databaseForApp:URL: call."]; + } + id provider = + FIR_COMPONENT(FIRDatabaseProvider, app.container); + return [provider databaseForApp:app URL:url]; +} + ++ (NSString *)buildVersion { + // TODO: Restore git hash when build moves back to git + return [NSString stringWithFormat:@"%s_%s", FIREBASE_SEMVER, __DATE__]; +} + ++ (FIRDatabase *)createDatabaseForTests:(FRepoInfo *)repoInfo + config:(FIRDatabaseConfig *)config { + FIRDatabase *db = [[FIRDatabase alloc] initWithApp:nil + repoInfo:repoInfo + config:config]; + [db ensureRepo]; + return db; +} + ++ (NSString *)sdkVersion { + return [NSString stringWithUTF8String:FIREBASE_SEMVER]; +} + ++ (void)setLoggingEnabled:(BOOL)enabled { + [FUtilities setLoggingEnabled:enabled]; + FFLog(@"I-RDB024001", @"BUILD Version: %@", [FIRDatabase buildVersion]); +} + +- (id)initWithApp:(FIRApp *)app + repoInfo:(FRepoInfo *)info + config:(FIRDatabaseConfig *)config { + self = [super init]; + if (self != nil) { + self->_repoInfo = info; + self->_config = config; + self->_app = app; + } + return self; +} + +- (FIRDatabaseReference *)reference { + [self ensureRepo]; + + return [[FIRDatabaseReference alloc] initWithRepo:self.repo + path:[FPath empty]]; +} + +- (FIRDatabaseReference *)referenceWithPath:(NSString *)path { + [self ensureRepo]; + + [FValidation validateFrom:@"referenceWithPath" validRootPathString:path]; + FPath *childPath = [[FPath alloc] initWith:path]; + return [[FIRDatabaseReference alloc] initWithRepo:self.repo path:childPath]; +} + +- (FIRDatabaseReference *)referenceFromURL:(NSString *)databaseUrl { + [self ensureRepo]; + + if (databaseUrl == nil) { + [NSException raise:@"InvalidDatabaseURL" + format:@"Invalid nil url passed to referenceFromURL:"]; + } + FParsedUrl *parsedUrl = [FUtilities parseUrl:databaseUrl]; + [FValidation validateFrom:@"referenceFromURL:" validURL:parsedUrl]; + if (![parsedUrl.repoInfo.host isEqualToString:_repoInfo.host]) { + [NSException + raise:@"InvalidDatabaseURL" + format: + @"Invalid URL (%@) passed to getReference(). URL was expected " + "to match configured Database URL: %@", + databaseUrl, [self reference].URL]; + } + return [[FIRDatabaseReference alloc] initWithRepo:self.repo + path:parsedUrl.path]; +} + +- (void)purgeOutstandingWrites { + [self ensureRepo]; + + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + [self.repo purgeOutstandingWrites]; + }); +} + +- (void)goOnline { + [self ensureRepo]; + + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + [self.repo resume]; + }); +} + +- (void)goOffline { + [self ensureRepo]; + + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + [self.repo interrupt]; + }); +} + +- (void)setPersistenceEnabled:(BOOL)persistenceEnabled { + [self assertUnfrozen:@"setPersistenceEnabled"]; + self->_config.persistenceEnabled = persistenceEnabled; +} + +- (BOOL)persistenceEnabled { + return self->_config.persistenceEnabled; +} + +- (void)setPersistenceCacheSizeBytes:(NSUInteger)persistenceCacheSizeBytes { + [self assertUnfrozen:@"setPersistenceCacheSizeBytes"]; + self->_config.persistenceCacheSizeBytes = persistenceCacheSizeBytes; +} + +- (NSUInteger)persistenceCacheSizeBytes { + return self->_config.persistenceCacheSizeBytes; +} + +- (void)setCallbackQueue:(dispatch_queue_t)callbackQueue { + [self assertUnfrozen:@"setCallbackQueue"]; + self->_config.callbackQueue = callbackQueue; +} + +- (dispatch_queue_t)callbackQueue { + return self->_config.callbackQueue; +} + +- (void)assertUnfrozen:(NSString *)methodName { + if (self.repo != nil) { + [NSException + raise:@"FIRDatabaseAlreadyInUse" + format:@"Calls to %@ must be made before any other usage of " + "FIRDatabase instance.", + methodName]; + } +} + +- (void)ensureRepo { + if (self.repo == nil) { + self.repo = [FRepoManager createRepo:self.repoInfo + config:self.config + database:self]; + } +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseComponent.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseComponent.h new file mode 100644 index 0000000..9d8bdb2 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseComponent.h @@ -0,0 +1,46 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FIRApp; +@class FIRDatabase; + +NS_ASSUME_NONNULL_BEGIN + +/// This protocol is used in the interop registration process to register an +/// instance provider for individual FIRApps. +@protocol FIRDatabaseProvider + +/// Gets a FirebaseDatabase instance for the specified URL, using the specified +/// FirebaseApp. +- (FIRDatabase *)databaseForApp:(FIRApp *)app URL:(NSString *)url; + +@end + +/// A concrete implementation for FIRDatabaseProvider to create Database +/// instances. +@interface FIRDatabaseComponent : NSObject + +/// The FIRApp that instances will be set up with. +@property(nonatomic, weak, readonly) FIRApp *app; + +/// Unavailable, use `databaseForApp:URL:` instead. +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseComponent.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseComponent.m new file mode 100644 index 0000000..8e44778 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseComponent.m @@ -0,0 +1,173 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRDatabaseComponent.h" + +#import "FIRDatabaseConfig_Private.h" +#import "FIRDatabase_Private.h" +#import "FRepoManager.h" + +#import +#import +#import +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** A NSMutableDictionary of FirebaseApp name and FRepoInfo to FirebaseDatabase + * instance. */ +typedef NSMutableDictionary FIRDatabaseDictionary; + +@interface FIRDatabaseComponent () +@property(nonatomic) FIRDatabaseDictionary *instances; +/// Internal intializer. +- (instancetype)initWithApp:(FIRApp *)app; +@end + +@implementation FIRDatabaseComponent + +#pragma mark - Initialization + +- (instancetype)initWithApp:(FIRApp *)app { + self = [super init]; + if (self) { + _app = app; + _instances = [NSMutableDictionary dictionary]; + } + return self; +} + +#pragma mark - Lifecycle + ++ (void)load { + [FIRApp registerInternalLibrary:(Class)self + withName:@"fire-db" + withVersion:[FIRDatabase sdkVersion]]; +} + +#pragma mark - FIRComponentRegistrant + ++ (NSArray *)componentsToRegister { + FIRDependency *authDep = + [FIRDependency dependencyWithProtocol:@protocol(FIRAuthInterop) + isRequired:NO]; + FIRComponentCreationBlock creationBlock = + ^id _Nullable(FIRComponentContainer *container, BOOL *isCacheable) { + *isCacheable = YES; + return [[FIRDatabaseComponent alloc] initWithApp:container.app]; + }; + FIRComponent *databaseProvider = + [FIRComponent componentWithProtocol:@protocol(FIRDatabaseProvider) + instantiationTiming:FIRInstantiationTimingLazy + dependencies:@[ authDep ] + creationBlock:creationBlock]; + return @[ databaseProvider ]; +} + +#pragma mark - Instance management. + +- (void)appWillBeDeleted:(FIRApp *)app { + NSString *appName = app.name; + if (appName == nil) { + return; + } + FIRDatabaseDictionary *instances = [self instances]; + @synchronized(instances) { + // Clean up the deleted instance in an effort to remove any resources + // still in use. Note: Any leftover instances of this exact database + // will be invalid. + for (FIRDatabase *database in [instances allValues]) { + [FRepoManager disposeRepos:database.config]; + } + [instances removeAllObjects]; + } +} + +#pragma mark - FIRDatabaseProvider Conformance + +- (FIRDatabase *)databaseForApp:(FIRApp *)app URL:(NSString *)url { + if (app == nil) { + [NSException raise:@"InvalidFIRApp" + format:@"nil FIRApp instance passed to databaseForApp."]; + } + + if (url == nil) { + [NSException raise:@"MissingDatabaseURL" + format:@"Failed to get FirebaseDatabase instance: " + "Specify DatabaseURL within FIRApp or from your " + "databaseForApp:URL: call."]; + } + + NSURL *databaseUrl = [NSURL URLWithString:url]; + + if (databaseUrl == nil) { + [NSException raise:@"InvalidDatabaseURL" + format:@"The Database URL '%@' cannot be parsed. " + "Specify a valid DatabaseURL within FIRApp or from " + "your databaseForApp:URL: call.", + databaseUrl]; + } else if (![databaseUrl.path isEqualToString:@""] && + ![databaseUrl.path isEqualToString:@"/"]) { + [NSException + raise:@"InvalidDatabaseURL" + format:@"Configured Database URL '%@' is invalid. It should point " + "to the root of a Firebase Database but it includes a " + "path: %@", + databaseUrl, databaseUrl.path]; + } + + FIRDatabaseDictionary *instances = [self instances]; + @synchronized(instances) { + FParsedUrl *parsedUrl = + [FUtilities parseUrl:databaseUrl.absoluteString]; + NSString *urlIndex = + [NSString stringWithFormat:@"%@:%@", parsedUrl.repoInfo.host, + [parsedUrl.path toString]]; + FIRDatabase *database = instances[urlIndex]; + if (!database) { + id authTokenProvider = [FAuthTokenProvider + authTokenProviderWithAuth:FIR_COMPONENT(FIRAuthInterop, + app.container)]; + + // If this is the default app, don't set the session persistence key + // so that we use our default ("default") instead of the FIRApp + // default ("[DEFAULT]") so that we preserve the default location + // used by the legacy Firebase SDK. + NSString *sessionIdentifier = @"default"; + if (![FIRApp isDefaultAppConfigured] || + app != [FIRApp defaultApp]) { + sessionIdentifier = app.name; + } + + FIRDatabaseConfig *config = [[FIRDatabaseConfig alloc] + initWithSessionIdentifier:sessionIdentifier + authTokenProvider:authTokenProvider]; + database = [[FIRDatabase alloc] initWithApp:app + repoInfo:parsedUrl.repoInfo + config:config]; + instances[urlIndex] = database; + } + + return database; + } +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseConfig.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseConfig.h new file mode 100644 index 0000000..85399f9 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseConfig.h @@ -0,0 +1,70 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@protocol FAuthTokenProvider; + +NS_ASSUME_NONNULL_BEGIN + +/** + * TODO: Merge FIRDatabaseConfig into FIRDatabase. + */ +@interface FIRDatabaseConfig : NSObject + +- (id)initWithSessionIdentifier:(NSString *)identifier + authTokenProvider:(id)authTokenProvider; + +/** + * By default the Firebase Database client will keep data in memory while your + * application is running, but not when it is restarted. By setting this value + * to YES, the data will be persisted to on-device (disk) storage and will thus + * be available again when the app is restarted (even when there is no network + * connectivity at that time). Note that this property must be set before + * creating your first FIRDatabaseReference and only needs to be called once per + * application. + * + * If your app uses Firebase Authentication, the client will automatically + * persist the user's authentication token across restarts, even without + * persistence enabled. But if the auth token expired while offline and you've + * enabled persistence, the client will pause write operations until you + * successfully re-authenticate (or explicitly unauthenticate) to prevent your + * writes from being sent unauthenticated and failing due to security rules. + */ +@property(nonatomic) BOOL persistenceEnabled; + +/** + * By default the Firebase Database client will use up to 10MB of disk space to + * cache data. If the cache grows beyond this size, the client will start + * removing data that hasn't been recently used. If you find that your + * application caches too little or too much data, call this method to change + * the cache size. This property must be set before creating your first + * FIRDatabaseReference and only needs to be called once per application. + * + * Note that the specified cache size is only an approximation and the size on + * disk may temporarily exceed it at times. + */ +@property(nonatomic) NSUInteger persistenceCacheSizeBytes; + +/** + * Sets the dispatch queue on which all events are raised. The default queue is + * the main queue. + */ +@property(nonatomic, strong) dispatch_queue_t callbackQueue; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseConfig.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseConfig.m new file mode 100644 index 0000000..9e9f8b5 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseConfig.m @@ -0,0 +1,92 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRDatabaseConfig.h" + +#import "FAuthTokenProvider.h" +#import "FIRDatabaseConfig_Private.h" +#import "FIRNoopAuthTokenProvider.h" + +@interface FIRDatabaseConfig (Private) + +@property(nonatomic, strong, readwrite) NSString *sessionIdentifier; + +@end + +@implementation FIRDatabaseConfig + +- (id)init { + [NSException raise:NSInvalidArgumentException + format:@"Can't create config objects!"]; + return nil; +} + +- (id)initWithSessionIdentifier:(NSString *)identifier + authTokenProvider:(id)authTokenProvider { + self = [super init]; + if (self != nil) { + self->_sessionIdentifier = identifier; + self->_callbackQueue = dispatch_get_main_queue(); + self->_persistenceCacheSizeBytes = + 10 * 1024 * 1024; // Default cache size is 10MB + self->_authTokenProvider = authTokenProvider; + } + return self; +} + +- (void)assertUnfrozen { + if (self.isFrozen) { + [NSException raise:NSGenericException + format:@"Can't modify config objects after they are in use " + @"for FIRDatabaseReferences."]; + } +} + +- (void)setAuthTokenProvider:(id)authTokenProvider { + [self assertUnfrozen]; + self->_authTokenProvider = authTokenProvider; +} + +- (void)setPersistenceEnabled:(BOOL)persistenceEnabled { + [self assertUnfrozen]; + self->_persistenceEnabled = persistenceEnabled; +} + +- (void)setPersistenceCacheSizeBytes:(NSUInteger)persistenceCacheSizeBytes { + [self assertUnfrozen]; + // Can't be less than 1MB + if (persistenceCacheSizeBytes < 1024 * 1024) { + [NSException raise:NSInvalidArgumentException + format:@"The minimum cache size must be at least 1MB"]; + } + if (persistenceCacheSizeBytes > 100 * 1024 * 1024) { + [NSException raise:NSInvalidArgumentException + format:@"Firebase Database currently doesn't support a " + @"cache size larger than 100MB"]; + } + self->_persistenceCacheSizeBytes = persistenceCacheSizeBytes; +} + +- (void)setCallbackQueue:(dispatch_queue_t)callbackQueue { + [self assertUnfrozen]; + self->_callbackQueue = callbackQueue; +} + +- (void)freeze { + self->_isFrozen = YES; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseQuery.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseQuery.m new file mode 100644 index 0000000..cd62979 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseQuery.m @@ -0,0 +1,677 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRDatabaseQuery.h" +#import "FChildEventRegistration.h" +#import "FConstants.h" +#import "FIRDatabaseQuery_Private.h" +#import "FKeyIndex.h" +#import "FLeafNode.h" +#import "FPath.h" +#import "FPathIndex.h" +#import "FPriorityIndex.h" +#import "FQueryParams.h" +#import "FQuerySpec.h" +#import "FSnapshotUtilities.h" +#import "FValidation.h" +#import "FValueEventRegistration.h" +#import "FValueIndex.h" + +@implementation FIRDatabaseQuery + +@synthesize repo; +@synthesize path; +@synthesize queryParams; + +#define INVALID_QUERY_PARAM_ERROR @"InvalidQueryParameter" + ++ (dispatch_queue_t)sharedQueue { + // We use this shared queue across all of the FQueries so things happen FIFO + // (as opposed to dispatch_get_global_queue(0, 0) which is concurrent) + static dispatch_once_t pred; + static dispatch_queue_t sharedDispatchQueue; + + dispatch_once(&pred, ^{ + sharedDispatchQueue = dispatch_queue_create("FirebaseWorker", NULL); + }); + + return sharedDispatchQueue; +} + +- (id)initWithRepo:(FRepo *)theRepo path:(FPath *)thePath { + return [self initWithRepo:theRepo + path:thePath + params:nil + orderByCalled:NO + priorityMethodCalled:NO]; +} + +- (id)initWithRepo:(FRepo *)theRepo + path:(FPath *)thePath + params:(FQueryParams *)theParams + orderByCalled:(BOOL)orderByCalled + priorityMethodCalled:(BOOL)priorityMethodCalled { + self = [super init]; + if (self) { + self.repo = theRepo; + self.path = thePath; + if (!theParams) { + theParams = [FQueryParams defaultInstance]; + } + if (![theParams isValid]) { + @throw [[NSException alloc] + initWithName:@"InvalidArgumentError" + reason:@"Queries are limited to two constraints" + userInfo:nil]; + } + self.queryParams = theParams; + self.orderByCalled = orderByCalled; + self.priorityMethodCalled = priorityMethodCalled; + } + return self; +} + +- (FQuerySpec *)querySpec { + return [[FQuerySpec alloc] initWithPath:self.path params:self.queryParams]; +} + +- (void)validateQueryEndpointsForParams:(FQueryParams *)params { + if ([params.index isEqual:[FKeyIndex keyIndex]]) { + if ([params hasStart]) { + if (params.indexStartKey != [FUtilities minName]) { + [NSException raise:INVALID_QUERY_PARAM_ERROR + format:@"Can't use queryStartingAtValue:childKey: " + @"or queryEqualTo:andChildKey: in " + @"combination with queryOrderedByKey"]; + } + if (![params.indexStartValue.val isKindOfClass:[NSString class]]) { + [NSException + raise:INVALID_QUERY_PARAM_ERROR + format: + @"Can't use queryStartingAtValue: with other types " + @"than string in combination with queryOrderedByKey"]; + } + } + if ([params hasEnd]) { + if (params.indexEndKey != [FUtilities maxName]) { + [NSException raise:INVALID_QUERY_PARAM_ERROR + format:@"Can't use queryEndingAtValue:childKey: or " + @"queryEqualToValue:childKey: in " + @"combination with queryOrderedByKey"]; + } + if (![params.indexEndValue.val isKindOfClass:[NSString class]]) { + [NSException + raise:INVALID_QUERY_PARAM_ERROR + format: + @"Can't use queryEndingAtValue: with other types than " + @"string in combination with queryOrderedByKey"]; + } + } + } else if ([params.index isEqual:[FPriorityIndex priorityIndex]]) { + if (([params hasStart] && + ![FValidation validatePriorityValue:params.indexStartValue.val]) || + ([params hasEnd] && + ![FValidation validatePriorityValue:params.indexEndValue.val])) { + [NSException + raise:INVALID_QUERY_PARAM_ERROR + format:@"When using queryOrderedByPriority, values provided to " + @"queryStartingAtValue:, queryEndingAtValue:, or " + @"queryEqualToValue: must be valid priorities."]; + } + } +} + +- (void)validateEqualToCall { + if ([self.queryParams hasStart]) { + [NSException + raise:INVALID_QUERY_PARAM_ERROR + format: + @"Cannot combine queryEqualToValue: and queryStartingAtValue:"]; + } + if ([self.queryParams hasEnd]) { + [NSException + raise:INVALID_QUERY_PARAM_ERROR + format: + @"Cannot combine queryEqualToValue: and queryEndingAtValue:"]; + } +} + +- (void)validateNoPreviousOrderByCalled { + if (self.orderByCalled) { + [NSException raise:INVALID_QUERY_PARAM_ERROR + format:@"Cannot use multiple queryOrderedBy calls!"]; + } +} + +- (void)validateIndexValueType:(id)type fromMethod:(NSString *)method { + if (type != nil && ![type isKindOfClass:[NSNumber class]] && + ![type isKindOfClass:[NSString class]] && + ![type isKindOfClass:[NSNull class]]) { + [NSException raise:INVALID_QUERY_PARAM_ERROR + format:@"You can only pass nil, NSString or NSNumber to %@", + method]; + } +} + +- (FIRDatabaseQuery *)queryStartingAtValue:(id)startValue { + return [self queryStartingAtInternal:startValue + childKey:nil + from:@"queryStartingAtValue:" + priorityMethod:NO]; +} + +- (FIRDatabaseQuery *)queryStartingAtValue:(id)startValue + childKey:(NSString *)childKey { + if ([self.queryParams.index isEqual:[FKeyIndex keyIndex]]) { + @throw [[NSException alloc] + initWithName:INVALID_QUERY_PARAM_ERROR + reason:@"You must use queryStartingAtValue: instead of " + @"queryStartingAtValue:childKey: when using " + @"queryOrderedByKey:" + userInfo:nil]; + } + return [self queryStartingAtInternal:startValue + childKey:childKey + from:@"queryStartingAtValue:childKey:" + priorityMethod:NO]; +} + +- (FIRDatabaseQuery *)queryStartingAtInternal:(id)startValue + childKey:(NSString *)childKey + from:(NSString *)methodName + priorityMethod:(BOOL)priorityMethod { + [self validateIndexValueType:startValue fromMethod:methodName]; + if (childKey != nil) { + [FValidation validateFrom:methodName validKey:childKey]; + } + if ([self.queryParams hasStart]) { + [NSException raise:INVALID_QUERY_PARAM_ERROR + format:@"Can't call %@ after queryStartingAtValue or " + @"queryEqualToValue was previously called", + methodName]; + } + id startNode = [FSnapshotUtilities nodeFrom:startValue]; + FQueryParams *params = [self.queryParams startAt:startNode + childKey:childKey]; + [self validateQueryEndpointsForParams:params]; + return [[FIRDatabaseQuery alloc] + initWithRepo:self.repo + path:self.path + params:params + orderByCalled:self.orderByCalled + priorityMethodCalled:priorityMethod || self.priorityMethodCalled]; +} + +- (FIRDatabaseQuery *)queryEndingAtValue:(id)endValue { + return [self queryEndingAtInternal:endValue + childKey:nil + from:@"queryEndingAtValue:" + priorityMethod:NO]; +} + +- (FIRDatabaseQuery *)queryEndingAtValue:(id)endValue + childKey:(NSString *)childKey { + if ([self.queryParams.index isEqual:[FKeyIndex keyIndex]]) { + @throw [[NSException alloc] + initWithName:INVALID_QUERY_PARAM_ERROR + reason:@"You must use queryEndingAtValue: instead of " + @"queryEndingAtValue:childKey: when using " + @"queryOrderedByKey:" + userInfo:nil]; + } + + return [self queryEndingAtInternal:endValue + childKey:childKey + from:@"queryEndingAtValue:childKey:" + priorityMethod:NO]; +} + +- (FIRDatabaseQuery *)queryEndingAtInternal:(id)endValue + childKey:(NSString *)childKey + from:(NSString *)methodName + priorityMethod:(BOOL)priorityMethod { + [self validateIndexValueType:endValue fromMethod:methodName]; + if (childKey != nil) { + [FValidation validateFrom:methodName validKey:childKey]; + } + if ([self.queryParams hasEnd]) { + [NSException raise:INVALID_QUERY_PARAM_ERROR + format:@"Can't call %@ after queryEndingAtValue or " + @"queryEqualToValue was previously called", + methodName]; + } + id endNode = [FSnapshotUtilities nodeFrom:endValue]; + FQueryParams *params = [self.queryParams endAt:endNode childKey:childKey]; + [self validateQueryEndpointsForParams:params]; + return [[FIRDatabaseQuery alloc] + initWithRepo:self.repo + path:self.path + params:params + orderByCalled:self.orderByCalled + priorityMethodCalled:priorityMethod || self.priorityMethodCalled]; +} + +- (FIRDatabaseQuery *)queryEqualToValue:(id)value { + return [self queryEqualToInternal:value + childKey:nil + from:@"queryEqualToValue:" + priorityMethod:NO]; +} + +- (FIRDatabaseQuery *)queryEqualToValue:(id)value + childKey:(NSString *)childKey { + if ([self.queryParams.index isEqual:[FKeyIndex keyIndex]]) { + @throw [[NSException alloc] + initWithName:INVALID_QUERY_PARAM_ERROR + reason:@"You must use queryEqualToValue: instead of " + @"queryEqualTo:childKey: when using queryOrderedByKey:" + userInfo:nil]; + } + return [self queryEqualToInternal:value + childKey:childKey + from:@"queryEqualToValue:childKey:" + priorityMethod:NO]; +} + +- (FIRDatabaseQuery *)queryEqualToInternal:(id)value + childKey:(NSString *)childKey + from:(NSString *)methodName + priorityMethod:(BOOL)priorityMethod { + [self validateIndexValueType:value fromMethod:methodName]; + if (childKey != nil) { + [FValidation validateFrom:methodName validKey:childKey]; + } + if ([self.queryParams hasEnd] || [self.queryParams hasStart]) { + [NSException + raise:INVALID_QUERY_PARAM_ERROR + format: + @"Can't call %@ after queryStartingAtValue, queryEndingAtValue " + @"or queryEqualToValue was previously called", + methodName]; + } + id node = [FSnapshotUtilities nodeFrom:value]; + FQueryParams *params = [[self.queryParams startAt:node + childKey:childKey] endAt:node + childKey:childKey]; + [self validateQueryEndpointsForParams:params]; + return [[FIRDatabaseQuery alloc] + initWithRepo:self.repo + path:self.path + params:params + orderByCalled:self.orderByCalled + priorityMethodCalled:priorityMethod || self.priorityMethodCalled]; +} + +- (void)validateLimitRange:(NSUInteger)limit { + // No need to check for negative ranges, since limit is unsigned + if (limit == 0) { + [NSException raise:INVALID_QUERY_PARAM_ERROR + format:@"Limit can't be zero"]; + } + if (limit >= 1ul << 31) { + [NSException raise:INVALID_QUERY_PARAM_ERROR + format:@"Limit must be less than 2,147,483,648"]; + } +} + +- (FIRDatabaseQuery *)queryLimitedToFirst:(NSUInteger)limit { + if (self.queryParams.limitSet) { + [NSException raise:INVALID_QUERY_PARAM_ERROR + format:@"Can't call queryLimitedToFirst: if a limit was " + @"previously set"]; + } + [self validateLimitRange:limit]; + FQueryParams *params = [self.queryParams limitToFirst:limit]; + return [[FIRDatabaseQuery alloc] initWithRepo:self.repo + path:self.path + params:params + orderByCalled:self.orderByCalled + priorityMethodCalled:self.priorityMethodCalled]; +} + +- (FIRDatabaseQuery *)queryLimitedToLast:(NSUInteger)limit { + if (self.queryParams.limitSet) { + [NSException raise:INVALID_QUERY_PARAM_ERROR + format:@"Can't call queryLimitedToLast: if a limit was " + @"previously set"]; + } + [self validateLimitRange:limit]; + FQueryParams *params = [self.queryParams limitToLast:limit]; + return [[FIRDatabaseQuery alloc] initWithRepo:self.repo + path:self.path + params:params + orderByCalled:self.orderByCalled + priorityMethodCalled:self.priorityMethodCalled]; +} + +- (FIRDatabaseQuery *)queryOrderedByChild:(NSString *)indexPathString { + if ([indexPathString isEqualToString:@"$key"] || + [indexPathString isEqualToString:@".key"]) { + @throw [[NSException alloc] + initWithName:INVALID_QUERY_PARAM_ERROR + reason:[NSString stringWithFormat: + @"(queryOrderedByChild:) %@ is invalid. " + @" Use queryOrderedByKey: instead.", + indexPathString] + userInfo:nil]; + } else if ([indexPathString isEqualToString:@"$priority"] || + [indexPathString isEqualToString:@".priority"]) { + @throw [[NSException alloc] + initWithName:INVALID_QUERY_PARAM_ERROR + reason:[NSString stringWithFormat: + @"(queryOrderedByChild:) %@ is invalid. " + @" Use queryOrderedByPriority: instead.", + indexPathString] + userInfo:nil]; + } else if ([indexPathString isEqualToString:@"$value"] || + [indexPathString isEqualToString:@".value"]) { + @throw [[NSException alloc] + initWithName:INVALID_QUERY_PARAM_ERROR + reason:[NSString stringWithFormat: + @"(queryOrderedByChild:) %@ is invalid. " + @" Use queryOrderedByValue: instead.", + indexPathString] + userInfo:nil]; + } + [self validateNoPreviousOrderByCalled]; + + [FValidation validateFrom:@"queryOrderedByChild:" + validPathString:indexPathString]; + FPath *indexPath = [FPath pathWithString:indexPathString]; + if (indexPath.isEmpty) { + @throw [[NSException alloc] + initWithName:INVALID_QUERY_PARAM_ERROR + reason:[NSString + stringWithFormat:@"(queryOrderedByChild:) with an " + @"empty path is invalid. Use " + @"queryOrderedByValue: instead."] + userInfo:nil]; + } + id index = [[FPathIndex alloc] initWithPath:indexPath]; + + FQueryParams *params = [self.queryParams orderBy:index]; + [self validateQueryEndpointsForParams:params]; + return [[FIRDatabaseQuery alloc] initWithRepo:self.repo + path:self.path + params:params + orderByCalled:YES + priorityMethodCalled:self.priorityMethodCalled]; +} + +- (FIRDatabaseQuery *)queryOrderedByKey { + [self validateNoPreviousOrderByCalled]; + FQueryParams *params = [self.queryParams orderBy:[FKeyIndex keyIndex]]; + [self validateQueryEndpointsForParams:params]; + return [[FIRDatabaseQuery alloc] initWithRepo:self.repo + path:self.path + params:params + orderByCalled:YES + priorityMethodCalled:self.priorityMethodCalled]; +} + +- (FIRDatabaseQuery *)queryOrderedByValue { + [self validateNoPreviousOrderByCalled]; + FQueryParams *params = [self.queryParams orderBy:[FValueIndex valueIndex]]; + return [[FIRDatabaseQuery alloc] initWithRepo:self.repo + path:self.path + params:params + orderByCalled:YES + priorityMethodCalled:self.priorityMethodCalled]; +} + +- (FIRDatabaseQuery *)queryOrderedByPriority { + [self validateNoPreviousOrderByCalled]; + FQueryParams *params = + [self.queryParams orderBy:[FPriorityIndex priorityIndex]]; + return [[FIRDatabaseQuery alloc] initWithRepo:self.repo + path:self.path + params:params + orderByCalled:YES + priorityMethodCalled:self.priorityMethodCalled]; +} + +- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType + withBlock:(void (^)(FIRDataSnapshot *))block { + [FValidation validateFrom:@"observeEventType:withBlock:" + knownEventType:eventType]; + return [self observeEventType:eventType + withBlock:block + withCancelBlock:nil]; +} + +- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType + andPreviousSiblingKeyWithBlock:(fbt_void_datasnapshot_nsstring)block { + [FValidation + validateFrom:@"observeEventType:andPreviousSiblingKeyWithBlock:" + knownEventType:eventType]; + return [self observeEventType:eventType + andPreviousSiblingKeyWithBlock:block + withCancelBlock:nil]; +} + +- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType + withBlock:(fbt_void_datasnapshot)block + withCancelBlock:(fbt_void_nserror)cancelBlock { + [FValidation validateFrom:@"observeEventType:withBlock:withCancelBlock:" + knownEventType:eventType]; + + if (eventType == FIRDataEventTypeValue) { + // Handle FIRDataEventTypeValue specially because they shouldn't have + // prevName callbacks + NSUInteger handle = [[FUtilities LUIDGenerator] integerValue]; + [self observeValueEventWithHandle:handle + withBlock:block + cancelCallback:cancelBlock]; + return handle; + } else { + // Wrap up the userCallback so we can treat everything as a callback + // that has a prevName + fbt_void_datasnapshot userCallback = [block copy]; + return [self observeEventType:eventType + andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, + NSString *prevName) { + if (userCallback != nil) { + userCallback(snapshot); + } + } + withCancelBlock:cancelBlock]; + } +} + +- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType + andPreviousSiblingKeyWithBlock:(fbt_void_datasnapshot_nsstring)block + withCancelBlock:(fbt_void_nserror)cancelBlock { + [FValidation validateFrom:@"observeEventType:" + @"andPreviousSiblingKeyWithBlock:withCancelBlock:" + knownEventType:eventType]; + + if (eventType == FIRDataEventTypeValue) { + // TODO: This gets hit by observeSingleEventOfType. Need to fix. + /* + @throw [[NSException alloc] initWithName:@"InvalidEventTypeForObserver" + reason:@"(observeEventType:andPreviousSiblingKeyWithBlock:withCancelBlock:) + Cannot use + observeEventType:andPreviousSiblingKeyWithBlock:withCancelBlock: with + FIRDataEventTypeValue. Use observeEventType:withBlock:withCancelBlock: + instead." userInfo:nil]; + */ + } + + NSUInteger handle = [[FUtilities LUIDGenerator] integerValue]; + NSDictionary *callbacks = + @{[NSNumber numberWithInteger:eventType] : [block copy]}; + [self observeChildEventWithHandle:handle + withCallbacks:callbacks + cancelCallback:cancelBlock]; + + return handle; +} + +// If we want to distinguish between value event listeners and child event +// listeners, like in the Java client, we can consider exporting this. If we do, +// add argument validation. Otherwise, arguments are validated in the +// public-facing portions of the API. Also, move the FIRDatabaseHandle logic. +- (void)observeValueEventWithHandle:(FIRDatabaseHandle)handle + withBlock:(fbt_void_datasnapshot)block + cancelCallback:(fbt_void_nserror)cancelBlock { + // Note that we don't need to copy the callbacks here, FEventRegistration + // callback properties set to copy + FValueEventRegistration *registration = + [[FValueEventRegistration alloc] initWithRepo:self.repo + handle:handle + callback:block + cancelCallback:cancelBlock]; + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + [self.repo addEventRegistration:registration forQuery:self.querySpec]; + }); +} + +// Note: as with the above method, we may wish to expose this at some point. +- (void)observeChildEventWithHandle:(FIRDatabaseHandle)handle + withCallbacks:(NSDictionary *)callbacks + cancelCallback:(fbt_void_nserror)cancelBlock { + // Note that we don't need to copy the callbacks here, FEventRegistration + // callback properties set to copy + FChildEventRegistration *registration = + [[FChildEventRegistration alloc] initWithRepo:self.repo + handle:handle + callbacks:callbacks + cancelCallback:cancelBlock]; + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + [self.repo addEventRegistration:registration forQuery:self.querySpec]; + }); +} + +- (void)removeObserverWithHandle:(FIRDatabaseHandle)handle { + FValueEventRegistration *event = + [[FValueEventRegistration alloc] initWithRepo:self.repo + handle:handle + callback:nil + cancelCallback:nil]; + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + [self.repo removeEventRegistration:event forQuery:self.querySpec]; + }); +} + +- (void)removeAllObservers { + [self removeObserverWithHandle:NSNotFound]; +} + +- (void)keepSynced:(BOOL)keepSynced { + if ([self.path.getFront isEqualToString:kDotInfoPrefix]) { + [NSException raise:NSInvalidArgumentException + format:@"Can't keep query on .info tree synced (this " + @"already is the case)."]; + } + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + [self.repo keepQuery:self.querySpec synced:keepSynced]; + }); +} + +- (void)observeSingleEventOfType:(FIRDataEventType)eventType + withBlock:(fbt_void_datasnapshot)block { + + [self observeSingleEventOfType:eventType + withBlock:block + withCancelBlock:nil]; +} + +- (void)observeSingleEventOfType:(FIRDataEventType)eventType + andPreviousSiblingKeyWithBlock:(fbt_void_datasnapshot_nsstring)block { + + [self observeSingleEventOfType:eventType + andPreviousSiblingKeyWithBlock:block + withCancelBlock:nil]; +} + +- (void)observeSingleEventOfType:(FIRDataEventType)eventType + withBlock:(fbt_void_datasnapshot)block + withCancelBlock:(fbt_void_nserror)cancelBlock { + + // XXX: user reported memory leak in method + + // "When you copy a block, any references to other blocks from within that + // block are copied if necessary—an entire tree may be copied (from the + // top). If you have block variables and you reference a block from within + // the block, that block will be copied." + // http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/Blocks/Articles/bxVariables.html#//apple_ref/doc/uid/TP40007502-CH6-SW1 + // So... we don't need to do this since inside the on: we copy this block + // off the stack to the heap. + // __block fbt_void_datasnapshot userCallback = [callback copy]; + + [self observeSingleEventOfType:eventType + andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, + NSString *prevName) { + if (block != nil) { + block(snapshot); + } + } + withCancelBlock:cancelBlock]; +} + +/** + * Attaches a listener, waits for the first event, and then removes the listener + */ +- (void)observeSingleEventOfType:(FIRDataEventType)eventType + andPreviousSiblingKeyWithBlock:(fbt_void_datasnapshot_nsstring)block + withCancelBlock:(fbt_void_nserror)cancelBlock { + + // XXX: user reported memory leak in method + + // "When you copy a block, any references to other blocks from within that + // block are copied if necessary—an entire tree may be copied (from the + // top). If you have block variables and you reference a block from within + // the block, that block will be copied." + // http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/Blocks/Articles/bxVariables.html#//apple_ref/doc/uid/TP40007502-CH6-SW1 + // So... we don't need to do this since inside the on: we copy this block + // off the stack to the heap. + // __block fbt_void_datasnapshot userCallback = [callback copy]; + + __block FIRDatabaseHandle handle; + __block BOOL firstCall = YES; + + fbt_void_datasnapshot_nsstring callback = [block copy]; + fbt_void_datasnapshot_nsstring wrappedCallback = + ^(FIRDataSnapshot *snap, NSString *prevName) { + if (firstCall) { + firstCall = NO; + [self removeObserverWithHandle:handle]; + callback(snap, prevName); + } + }; + + fbt_void_nserror cancelCallback = [cancelBlock copy]; + handle = [self observeEventType:eventType + andPreviousSiblingKeyWithBlock:wrappedCallback + withCancelBlock:^(NSError *error) { + [self removeObserverWithHandle:handle]; + + if (cancelCallback) { + cancelCallback(error); + } + }]; +} + +- (NSString *)description { + return [NSString + stringWithFormat:@"(%@ %@)", self.path, self.queryParams.description]; +} + +- (FIRDatabaseReference *)ref { + return [[FIRDatabaseReference alloc] initWithRepo:self.repo path:self.path]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRMutableData.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRMutableData.m new file mode 100644 index 0000000..5da7fae --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRMutableData.m @@ -0,0 +1,149 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRMutableData.h" +#import "FChildrenNode.h" +#import "FIRMutableData_Private.h" +#import "FIndexedNode.h" +#import "FNamedNode.h" +#import "FSnapshotHolder.h" +#import "FSnapshotUtilities.h" +#import "FTransformedEnumerator.h" + +@interface FIRMutableData () + +- (id)initWithPrefixPath:(FPath *)path + andSnapshotHolder:(FSnapshotHolder *)snapshotHolder; + +@property(strong, nonatomic) FSnapshotHolder *data; +@property(strong, nonatomic) FPath *prefixPath; + +@end + +@implementation FIRMutableData + +@synthesize data; +@synthesize prefixPath; + +- (id)initWithNode:(id)node { + FSnapshotHolder *holder = [[FSnapshotHolder alloc] init]; + FPath *path = [FPath empty]; + [holder updateSnapshot:path withNewSnapshot:node]; + return [self initWithPrefixPath:path andSnapshotHolder:holder]; +} + +- (id)initWithPrefixPath:(FPath *)path + andSnapshotHolder:(FSnapshotHolder *)snapshotHolder { + self = [super init]; + if (self) { + self.prefixPath = path; + self.data = snapshotHolder; + } + return self; +} + +- (FIRMutableData *)childDataByAppendingPath:(NSString *)path { + FPath *wholePath = [self.prefixPath childFromString:path]; + return [[FIRMutableData alloc] initWithPrefixPath:wholePath + andSnapshotHolder:self.data]; +} + +- (FIRMutableData *)parent { + if ([self.prefixPath isEmpty]) { + return nil; + } else { + FPath *path = [self.prefixPath parent]; + return [[FIRMutableData alloc] initWithPrefixPath:path + andSnapshotHolder:self.data]; + } +} + +- (void)setValue:(id)aValue { + id node = [FSnapshotUtilities nodeFrom:aValue + withValidationFrom:@"setValue:"]; + [self.data updateSnapshot:self.prefixPath withNewSnapshot:node]; +} + +- (void)setPriority:(id)aPriority { + id node = [self.data getNode:self.prefixPath]; + id pri = [FSnapshotUtilities nodeFrom:aPriority]; + node = [node updatePriority:pri]; + [self.data updateSnapshot:self.prefixPath withNewSnapshot:node]; +} + +- (id)value { + return [[self.data getNode:self.prefixPath] val]; +} + +- (id)priority { + return [[[self.data getNode:self.prefixPath] getPriority] val]; +} + +- (BOOL)hasChildren { + id node = [self.data getNode:self.prefixPath]; + return ![node isLeafNode] && ![(FChildrenNode *)node isEmpty]; +} + +- (BOOL)hasChildAtPath:(NSString *)path { + id node = [self.data getNode:self.prefixPath]; + FPath *childPath = [[FPath alloc] initWith:path]; + return ![[node getChild:childPath] isEmpty]; +} + +- (NSUInteger)childrenCount { + return [[self.data getNode:self.prefixPath] numChildren]; +} + +- (NSString *)key { + return [self.prefixPath getBack]; +} + +- (id)nodeValue { + return [self.data getNode:self.prefixPath]; +} + +- (NSEnumerator *)children { + FIndexedNode *indexedNode = + [FIndexedNode indexedNodeWithNode:self.nodeValue]; + return [[FTransformedEnumerator alloc] + initWithEnumerator:[indexedNode childEnumerator] + andTransform:^id(FNamedNode *node) { + FPath *childPath = [self.prefixPath childFromString:node.name]; + FIRMutableData *childData = + [[FIRMutableData alloc] initWithPrefixPath:childPath + andSnapshotHolder:self.data]; + return childData; + }]; +} + +- (BOOL)isEqualToData:(FIRMutableData *)other { + return self.data == other.data && + [[self.prefixPath description] + isEqualToString:[other.prefixPath description]]; +} + +- (NSString *)description { + if (self.key == nil) { + return [NSString + stringWithFormat:@"FIRMutableData (top-most transaction) %@ %@", + self.key, self.value]; + } else { + return [NSString + stringWithFormat:@"FIRMutableData (%@) %@", self.key, self.value]; + } +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRServerValue.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRServerValue.m new file mode 100644 index 0000000..e5be5c5 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRServerValue.m @@ -0,0 +1,30 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRServerValue.h" +#import "FIRDatabaseReference.h" + +@implementation FIRServerValue + ++ (NSDictionary *)timestamp { + static NSDictionary *timestamp = nil; + if (timestamp == nil) { + timestamp = @{@".sv" : @"timestamp"}; + } + return timestamp; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRTransactionResult.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRTransactionResult.m new file mode 100644 index 0000000..9fec862 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/FIRTransactionResult.m @@ -0,0 +1,39 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRTransactionResult.h" +#import "FIRTransactionResult_Private.h" + +@implementation FIRTransactionResult + +@synthesize update; +@synthesize isSuccess; + ++ (FIRTransactionResult *)successWithValue:(FIRMutableData *)value { + FIRTransactionResult *result = [[FIRTransactionResult alloc] init]; + result.isSuccess = YES; + result.update = value; + return result; +} + ++ (FIRTransactionResult *)abort { + FIRTransactionResult *result = [[FIRTransactionResult alloc] init]; + result.isSuccess = NO; + result.update = nil; + return result; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRDataSnapshot_Private.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRDataSnapshot_Private.h new file mode 100644 index 0000000..ac23045 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRDataSnapshot_Private.h @@ -0,0 +1,28 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRDataSnapshot.h" +#import "FIndexedNode.h" +#import "FTypedefs_Private.h" + +@interface FIRDataSnapshot () + +// in _Private for testing purposes +@property(nonatomic, strong) FIndexedNode *node; + +- (id)initWithRef:(FIRDatabaseReference *)ref indexedNode:(FIndexedNode *)node; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRDatabaseQuery_Private.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRDatabaseQuery_Private.h new file mode 100644 index 0000000..30f19f0 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRDatabaseQuery_Private.h @@ -0,0 +1,43 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRDatabaseQuery.h" +#import "FPath.h" +#import "FQueryParams.h" +#import "FRepo.h" +#import "FRepoManager.h" +#import "FTypedefs_Private.h" + +@interface FIRDatabaseQuery () + ++ (dispatch_queue_t)sharedQueue; + +- (id)initWithRepo:(FRepo *)repo path:(FPath *)path; +- (id)initWithRepo:(FRepo *)repo + path:(FPath *)path + params:(FQueryParams *)params + orderByCalled:(BOOL)orderByCalled + priorityMethodCalled:(BOOL)priorityMethodCalled; + +@property(nonatomic, strong) FRepo *repo; +@property(nonatomic, strong) FPath *path; +@property(nonatomic, strong) FQueryParams *queryParams; +@property(nonatomic) BOOL orderByCalled; +@property(nonatomic) BOOL priorityMethodCalled; + +- (FQuerySpec *)querySpec; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRDatabaseReference_Private.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRDatabaseReference_Private.h new file mode 100644 index 0000000..1d7e37c --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRDatabaseReference_Private.h @@ -0,0 +1,27 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRDatabaseConfig.h" +#import "FIRDatabaseReference.h" +#import "FRepo.h" +#import "FTypedefs_Private.h" + +@interface FIRDatabaseReference () + +- (id)initWithConfig:(FIRDatabaseConfig *)config; +- (id)initWithRepo:(FRepo *)repo path:(FPath *)path; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRDatabase_Private.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRDatabase_Private.h new file mode 100644 index 0000000..0ff3e70 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRDatabase_Private.h @@ -0,0 +1,37 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRDatabase.h" + +@class FRepo; +@class FRepoInfo; +@class FIRDatabaseConfig; + +@interface FIRDatabase () + +@property(nonatomic, strong) FRepoInfo *repoInfo; +@property(nonatomic, strong) FIRDatabaseConfig *config; +@property(nonatomic, strong) FRepo *repo; + +- (id)initWithApp:(FIRApp *)app + repoInfo:(FRepoInfo *)info + config:(FIRDatabaseConfig *)config; + ++ (NSString *)buildVersion; ++ (FIRDatabase *)createDatabaseForTests:(FRepoInfo *)repoInfo + config:(FIRDatabaseConfig *)config; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRMutableData_Private.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRMutableData_Private.h new file mode 100644 index 0000000..fded102 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRMutableData_Private.h @@ -0,0 +1,26 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRMutableData.h" +#import "FNode.h" + +@interface FIRMutableData () + +- (id)initWithNode:(id)node; +- (id)nodeValue; +- (BOOL)isEqualToData:(FIRMutableData *)other; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRTransactionResult_Private.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRTransactionResult_Private.h new file mode 100644 index 0000000..bdf250a --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRTransactionResult_Private.h @@ -0,0 +1,25 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRMutableData.h" +#import "FIRTransactionResult.h" + +@interface FIRTransactionResult () + +@property(nonatomic) BOOL isSuccess; +@property(nonatomic, strong) FIRMutableData *update; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FTypedefs_Private.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FTypedefs_Private.h new file mode 100644 index 0000000..dac55bc --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FTypedefs_Private.h @@ -0,0 +1,60 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __FTYPEDEFS_PRIVATE__ +#define __FTYPEDEFS_PRIVATE__ + +#import + +typedef NS_ENUM(NSInteger, FTransactionStatus) { + FTransactionInitializing, // 0 + FTransactionRun, // 1 + FTransactionSent, // 2 + FTransactionCompleted, // 3 + FTransactionSentNeedsAbort, // 4 + FTransactionNeedsAbort // 5 +}; + +@protocol FNode; +@class FPath; +@class FIRTransactionResult; +@class FIRMutableData; +@class FIRDataSnapshot; +@class FCompoundHash; + +typedef void (^fbt_void_nserror_bool_datasnapshot)(NSError *error, + BOOL committed, + FIRDataSnapshot *snapshot); +typedef FIRTransactionResult * (^fbt_transactionresult_mutabledata)( + FIRMutableData *currentData); +typedef void (^fbt_void_path_node)(FPath *, id); +typedef void (^fbt_void_nsstring)(NSString *); +typedef BOOL (^fbt_bool_nsstring_node)(NSString *, id); +typedef void (^fbt_void_path_node_marray)(FPath *, id, NSMutableArray *); +typedef BOOL (^fbt_bool_void)(void); +typedef void (^fbt_void_nsstring_nsstring)(NSString *str1, NSString *str2); +typedef void (^fbt_void_nsstring_nserror)(NSString *str, NSError *error); +typedef BOOL (^fbt_bool_path)(FPath *str); +typedef void (^fbt_void_id)(id data); +typedef NSString * (^fbt_nsstring_void)(void); +typedef FCompoundHash * (^fbt_compoundhash_void)(void); +typedef NSArray * (^fbt_nsarray_nsstring_id)(NSString *status, id Data); +typedef NSArray * (^fbt_nsarray_nsstring)(NSString *status); + +// WWDC 2012 session 712 starting in page 83 for saving blocks in properties +// (use @property (strong) type name). + +#endif diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Constants/FConstants.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Constants/FConstants.h new file mode 100644 index 0000000..4327a0d --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Constants/FConstants.h @@ -0,0 +1,195 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef Firebase_FConstants_h +#define Firebase_FConstants_h + +#import + +#pragma mark - +#pragma mark Wire Protocol Envelope Constants + +FOUNDATION_EXPORT NSString *const kFWPRequestType; +FOUNDATION_EXPORT NSString *const kFWPRequestTypeData; +FOUNDATION_EXPORT NSString *const kFWPRequestDataPayload; +FOUNDATION_EXPORT NSString *const kFWPRequestNumber; +FOUNDATION_EXPORT NSString *const kFWPRequestPayloadBody; +FOUNDATION_EXPORT NSString *const kFWPRequestError; +FOUNDATION_EXPORT NSString *const kFWPRequestAction; +FOUNDATION_EXPORT NSString *const kFWPResponseForRNData; +FOUNDATION_EXPORT NSString *const kFWPResponseForActionStatus; +FOUNDATION_EXPORT NSString *const kFWPResponseForActionStatusOk; +FOUNDATION_EXPORT NSString *const kFWPResponseForActionStatusDataStale; +FOUNDATION_EXPORT NSString *const kFWPResponseForActionData; +FOUNDATION_EXPORT NSString *const kFWPResponseDataWarnings; + +FOUNDATION_EXPORT NSString *const kFWPAsyncServerAction; +FOUNDATION_EXPORT NSString *const kFWPAsyncServerPayloadBody; +FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataUpdate; +FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataMerge; +FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataRangeMerge; +FOUNDATION_EXPORT NSString *const kFWPAsyncServerAuthRevoked; +FOUNDATION_EXPORT NSString *const kFWPASyncServerListenCancelled; +FOUNDATION_EXPORT NSString *const kFWPAsyncServerSecurityDebug; +FOUNDATION_EXPORT NSString + *const kFWPAsyncServerDataUpdateBodyPath; // {“a”: “d”, “b”: {“p”: “/”, “d”: + // “”}} +FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataUpdateBodyData; +FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataUpdateStartPath; +FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataUpdateEndPath; +FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataUpdateRangeMerge; +FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataUpdateBodyTag; +FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataQueries; + +FOUNDATION_EXPORT NSString *const kFWPAsyncServerEnvelopeType; +FOUNDATION_EXPORT NSString *const kFWPAsyncServerEnvelopeData; +FOUNDATION_EXPORT NSString *const kFWPAsyncServerControlMessage; +FOUNDATION_EXPORT NSString *const kFWPAsyncServerControlMessageType; +FOUNDATION_EXPORT NSString *const kFWPAsyncServerControlMessageData; +FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataMessage; + +FOUNDATION_EXPORT NSString *const kFWPAsyncServerHello; +FOUNDATION_EXPORT NSString *const kFWPAsyncServerHelloTimestamp; +FOUNDATION_EXPORT NSString *const kFWPAsyncServerHelloVersion; +FOUNDATION_EXPORT NSString *const kFWPAsyncServerHelloConnectedHost; +FOUNDATION_EXPORT NSString *const kFWPAsyncServerHelloSession; + +FOUNDATION_EXPORT NSString *const kFWPAsyncServerControlMessageShutdown; +FOUNDATION_EXPORT NSString *const kFWPAsyncServerControlMessageReset; + +#pragma mark - +#pragma mark Wire Protocol Payload Constants + +FOUNDATION_EXPORT NSString *const kFWPRequestActionPut; +FOUNDATION_EXPORT NSString *const kFWPRequestActionMerge; +FOUNDATION_EXPORT NSString *const kFWPRequestActionTaggedListen; +FOUNDATION_EXPORT NSString *const kFWPRequestActionTaggedUnlisten; +FOUNDATION_EXPORT NSString + *const kFWPRequestActionListen; // {"t": "d", "d": {"r": 1, "a": "l", "b": { + // "p": "/" } } } +FOUNDATION_EXPORT NSString *const kFWPRequestActionUnlisten; +FOUNDATION_EXPORT NSString *const kFWPRequestActionStats; +FOUNDATION_EXPORT NSString *const kFWPRequestActionDisconnectPut; +FOUNDATION_EXPORT NSString *const kFWPRequestActionDisconnectMerge; +FOUNDATION_EXPORT NSString *const kFWPRequestActionDisconnectCancel; +FOUNDATION_EXPORT NSString *const kFWPRequestActionAuth; +FOUNDATION_EXPORT NSString *const kFWPRequestActionUnauth; +FOUNDATION_EXPORT NSString *const kFWPRequestCredential; +FOUNDATION_EXPORT NSString *const kFWPRequestPath; +FOUNDATION_EXPORT NSString *const kFWPRequestCounters; +FOUNDATION_EXPORT NSString *const kFWPRequestQueries; +FOUNDATION_EXPORT NSString *const kFWPRequestTag; +FOUNDATION_EXPORT NSString *const kFWPRequestData; +FOUNDATION_EXPORT NSString *const kFWPRequestHash; +FOUNDATION_EXPORT NSString *const kFWPRequestCompoundHash; +FOUNDATION_EXPORT NSString *const kFWPRequestCompoundHashPaths; +FOUNDATION_EXPORT NSString *const kFWPRequestCompoundHashHashes; +FOUNDATION_EXPORT NSString *const kFWPRequestStatus; + +#pragma mark - +#pragma mark Websock Transport Constants + +FOUNDATION_EXPORT NSString *const kWireProtocolVersionParam; +FOUNDATION_EXPORT NSString *const kWebsocketProtocolVersion; +FOUNDATION_EXPORT NSString *const kWebsocketServerKillPacket; +FOUNDATION_EXPORT const int kWebsocketMaxFrameSize; +FOUNDATION_EXPORT NSUInteger const kWebsocketKeepaliveInterval; +FOUNDATION_EXPORT NSUInteger const kWebsocketConnectTimeout; + +FOUNDATION_EXPORT float const kPersistentConnReconnectMinDelay; +FOUNDATION_EXPORT float const kPersistentConnReconnectMaxDelay; +FOUNDATION_EXPORT float const kPersistentConnReconnectMultiplier; +FOUNDATION_EXPORT float const + kPersistentConnSuccessfulConnectionEstablishedDelay; + +#pragma mark - +#pragma mark Query / QueryParams constants + +FOUNDATION_EXPORT NSString *const kQueryDefault; +FOUNDATION_EXPORT NSString *const kQueryDefaultObject; +FOUNDATION_EXPORT NSString *const kViewManagerDictConstView; +FOUNDATION_EXPORT NSString *const kFQPIndexStartValue; +FOUNDATION_EXPORT NSString *const kFQPIndexStartName; +FOUNDATION_EXPORT NSString *const kFQPIndexEndValue; +FOUNDATION_EXPORT NSString *const kFQPIndexEndName; +FOUNDATION_EXPORT NSString *const kFQPLimit; +FOUNDATION_EXPORT NSString *const kFQPViewFrom; +FOUNDATION_EXPORT NSString *const kFQPViewFromLeft; +FOUNDATION_EXPORT NSString *const kFQPViewFromRight; +FOUNDATION_EXPORT NSString *const kFQPIndex; + +#pragma mark - +#pragma mark Interrupt Reasons + +FOUNDATION_EXPORT NSString *const kFInterruptReasonServerKill; +FOUNDATION_EXPORT NSString *const kFInterruptReasonWaitingForOpen; +FOUNDATION_EXPORT NSString *const kFInterruptReasonRepoInterrupt; +FOUNDATION_EXPORT NSString *const kFInterruptReasonAuthExpired; + +#pragma mark - +#pragma mark Payload constants + +FOUNDATION_EXPORT NSString *const kPayloadPriority; +FOUNDATION_EXPORT NSString *const kPayloadValue; +FOUNDATION_EXPORT NSString *const kPayloadMetadataPrefix; + +#pragma mark - +#pragma mark ServerValue constants + +FOUNDATION_EXPORT NSString *const kServerValueSubKey; +FOUNDATION_EXPORT NSString *const kServerValuePriority; + +#pragma mark - +#pragma mark.info/ constants + +FOUNDATION_EXPORT NSString *const kDotInfoPrefix; +FOUNDATION_EXPORT NSString *const kDotInfoConnected; +FOUNDATION_EXPORT NSString *const kDotInfoServerTimeOffset; + +#pragma mark - +#pragma mark ObjectiveC to JavaScript type constants + +FOUNDATION_EXPORT NSString *const kJavaScriptObject; +FOUNDATION_EXPORT NSString *const kJavaScriptString; +FOUNDATION_EXPORT NSString *const kJavaScriptBoolean; +FOUNDATION_EXPORT NSString *const kJavaScriptNumber; +FOUNDATION_EXPORT NSString *const kJavaScriptNull; +FOUNDATION_EXPORT NSString *const kJavaScriptTrue; +FOUNDATION_EXPORT NSString *const kJavaScriptFalse; + +#pragma mark - +#pragma mark Error handling constants + +FOUNDATION_EXPORT NSString *const kFErrorDomain; +FOUNDATION_EXPORT NSUInteger const kFAuthError; +FOUNDATION_EXPORT NSString *const kFErrorWriteCanceled; + +#pragma mark - +#pragma mark Validation Constants + +FOUNDATION_EXPORT NSUInteger const kFirebaseMaxObjectDepth; +FOUNDATION_EXPORT const unsigned int kFirebaseMaxLeafSize; + +#pragma mark - +#pragma mark Transaction Constants + +FOUNDATION_EXPORT NSUInteger const kFTransactionMaxRetries; +FOUNDATION_EXPORT NSString *const kFTransactionTooManyRetries; +FOUNDATION_EXPORT NSString *const kFTransactionNoData; +FOUNDATION_EXPORT NSString *const kFTransactionSet; +FOUNDATION_EXPORT NSString *const kFTransactionDisconnect; + +#endif diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Constants/FConstants.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Constants/FConstants.m new file mode 100644 index 0000000..1cf50ad --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Constants/FConstants.m @@ -0,0 +1,185 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FConstants.h" + +#pragma mark - +#pragma mark Wire Protocol Envelope Constants + +NSString *const kFWPRequestType = @"t"; +NSString *const kFWPRequestTypeData = @"d"; +NSString *const kFWPRequestDataPayload = @"d"; +NSString *const kFWPRequestNumber = @"r"; +NSString *const kFWPRequestPayloadBody = @"b"; +NSString *const kFWPRequestError = @"error"; +NSString *const kFWPRequestAction = @"a"; +NSString *const kFWPResponseForRNData = @"b"; +NSString *const kFWPResponseForActionStatus = @"s"; +NSString *const kFWPResponseForActionStatusOk = @"ok"; +NSString *const kFWPResponseForActionStatusDataStale = @"datastale"; +NSString *const kFWPResponseForActionData = @"d"; +NSString *const kFWPResponseDataWarnings = @"w"; +NSString *const kFWPAsyncServerAction = @"a"; +NSString *const kFWPAsyncServerPayloadBody = @"b"; +NSString *const kFWPAsyncServerDataUpdate = @"d"; +NSString *const kFWPAsyncServerDataMerge = @"m"; +NSString *const kFWPAsyncServerDataRangeMerge = @"rm"; +NSString *const kFWPAsyncServerAuthRevoked = @"ac"; +NSString *const kFWPASyncServerListenCancelled = @"c"; +NSString *const kFWPAsyncServerSecurityDebug = @"sd"; +NSString *const kFWPAsyncServerDataUpdateBodyPath = + @"p"; // {“a”: “d”, “b”: {“p”: “/”, “d”: “”}} +NSString *const kFWPAsyncServerDataUpdateBodyData = @"d"; +NSString *const kFWPAsyncServerDataUpdateStartPath = @"s"; +NSString *const kFWPAsyncServerDataUpdateEndPath = @"e"; +NSString *const kFWPAsyncServerDataUpdateRangeMerge = @"m"; +NSString *const kFWPAsyncServerDataUpdateBodyTag = @"t"; +NSString *const kFWPAsyncServerDataQueries = @"q"; + +NSString *const kFWPAsyncServerEnvelopeType = @"t"; +NSString *const kFWPAsyncServerEnvelopeData = @"d"; +NSString *const kFWPAsyncServerControlMessage = @"c"; +NSString *const kFWPAsyncServerControlMessageType = @"t"; +NSString *const kFWPAsyncServerControlMessageData = @"d"; +NSString *const kFWPAsyncServerDataMessage = @"d"; + +NSString *const kFWPAsyncServerHello = @"h"; +NSString *const kFWPAsyncServerHelloTimestamp = @"ts"; +NSString *const kFWPAsyncServerHelloVersion = @"v"; +NSString *const kFWPAsyncServerHelloConnectedHost = @"h"; +NSString *const kFWPAsyncServerHelloSession = @"s"; + +NSString *const kFWPAsyncServerControlMessageShutdown = @"s"; +NSString *const kFWPAsyncServerControlMessageReset = @"r"; + +#pragma mark - +#pragma mark Wire Protocol Payload Constants + +NSString *const kFWPRequestActionPut = @"p"; +NSString *const kFWPRequestActionMerge = @"m"; +NSString *const kFWPRequestActionListen = + @"l"; // {"t": "d", "d": {"r": 1, "a": "l", "b": { "p": "/" } } } +NSString *const kFWPRequestActionUnlisten = @"u"; +NSString *const kFWPRequestActionStats = @"s"; +NSString *const kFWPRequestActionTaggedListen = @"q"; +NSString *const kFWPRequestActionTaggedUnlisten = @"n"; +NSString *const kFWPRequestActionDisconnectPut = @"o"; +NSString *const kFWPRequestActionDisconnectMerge = @"om"; +NSString *const kFWPRequestActionDisconnectCancel = @"oc"; +NSString *const kFWPRequestActionAuth = @"auth"; +NSString *const kFWPRequestActionUnauth = @"unauth"; +NSString *const kFWPRequestCredential = @"cred"; +NSString *const kFWPRequestPath = @"p"; +NSString *const kFWPRequestCounters = @"c"; +NSString *const kFWPRequestQueries = @"q"; +NSString *const kFWPRequestTag = @"t"; +NSString *const kFWPRequestData = @"d"; +NSString *const kFWPRequestHash = @"h"; +NSString *const kFWPRequestCompoundHash = @"ch"; +NSString *const kFWPRequestCompoundHashPaths = @"ps"; +NSString *const kFWPRequestCompoundHashHashes = @"hs"; +NSString *const kFWPRequestStatus = @"s"; + +#pragma mark - +#pragma mark Websock Transport Constants + +NSString *const kWireProtocolVersionParam = @"v"; +NSString *const kWebsocketProtocolVersion = @"5"; +NSString *const kWebsocketServerKillPacket = @"kill"; +const int kWebsocketMaxFrameSize = 16384; +NSUInteger const kWebsocketKeepaliveInterval = 45; +NSUInteger const kWebsocketConnectTimeout = 30; + +float const kPersistentConnReconnectMinDelay = 1.0; +float const kPersistentConnReconnectMaxDelay = 30.0; +float const kPersistentConnReconnectMultiplier = 1.3f; +float const kPersistentConnSuccessfulConnectionEstablishedDelay = 30.0; + +#pragma mark - +#pragma mark Query constants + +NSString *const kQueryDefault = @"default"; +NSString *const kQueryDefaultObject = @"{}"; +NSString *const kViewManagerDictConstView = @"view"; +NSString *const kFQPIndexStartValue = @"sp"; +NSString *const kFQPIndexStartName = @"sn"; +NSString *const kFQPIndexEndValue = @"ep"; +NSString *const kFQPIndexEndName = @"en"; +NSString *const kFQPLimit = @"l"; +NSString *const kFQPViewFrom = @"vf"; +NSString *const kFQPViewFromLeft = @"l"; +NSString *const kFQPViewFromRight = @"r"; +NSString *const kFQPIndex = @"i"; + +#pragma mark - +#pragma mark Interrupt Reasons + +NSString *const kFInterruptReasonServerKill = @"server_kill"; +NSString *const kFInterruptReasonWaitingForOpen = @"waiting_for_open"; +NSString *const kFInterruptReasonRepoInterrupt = @"repo_interrupt"; + +#pragma mark - +#pragma mark Payload constants + +NSString *const kPayloadPriority = @".priority"; +NSString *const kPayloadValue = @".value"; +NSString *const kPayloadMetadataPrefix = @"."; + +#pragma mark - +#pragma mark ServerValue constants + +NSString *const kServerValueSubKey = @".sv"; +NSString *const kServerValuePriority = @"timestamp"; + +#pragma mark - +#pragma mark.info/ constants + +NSString *const kDotInfoPrefix = @".info"; +NSString *const kDotInfoConnected = @"connected"; +NSString *const kDotInfoServerTimeOffset = @"serverTimeOffset"; + +#pragma mark - +#pragma mark ObjectiveC to JavaScript type constants + +NSString *const kJavaScriptObject = @"object"; +NSString *const kJavaScriptString = @"string"; +NSString *const kJavaScriptBoolean = @"boolean"; +NSString *const kJavaScriptNumber = @"number"; +NSString *const kJavaScriptNull = @"null"; +NSString *const kJavaScriptTrue = @"true"; +NSString *const kJavaScriptFalse = @"false"; + +#pragma mark - +#pragma mark Error handling constants + +NSString *const kFErrorDomain = @"com.firebase"; +NSUInteger const kFAuthError = 1; +NSString *const kFErrorWriteCanceled = @"write_canceled"; + +#pragma mark - +#pragma mark Validation Constants + +NSUInteger const kFirebaseMaxObjectDepth = 1000; +const unsigned int kFirebaseMaxLeafSize = 1024 * 1024 * 10; // 10 MB + +#pragma mark - +#pragma mark Transaction Constants + +NSUInteger const kFTransactionMaxRetries = 25; +NSString *const kFTransactionTooManyRetries = @"maxretry"; +NSString *const kFTransactionNoData = @"nodata"; +NSString *const kFTransactionSet = @"set"; +NSString *const kFTransactionDisconnect = @"disconnect"; diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FCompoundHash.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FCompoundHash.h new file mode 100644 index 0000000..2453a81 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FCompoundHash.h @@ -0,0 +1,38 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FNode.h" + +@interface FCompoundHashBuilder : NSObject + +- (FPath *)currentPath; + +@end + +typedef BOOL (^FCompoundHashSplitStrategy)(FCompoundHashBuilder *builder); + +@interface FCompoundHash : NSObject + +@property(nonatomic, strong, readonly) NSArray *posts; +@property(nonatomic, strong, readonly) NSArray *hashes; + ++ (FCompoundHash *)fromNode:(id)node; ++ (FCompoundHash *)fromNode:(id)node + splitStrategy:(FCompoundHashSplitStrategy)strategy; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FCompoundHash.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FCompoundHash.m new file mode 100644 index 0000000..ced79cd --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FCompoundHash.m @@ -0,0 +1,259 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FCompoundHash.h" +#import "FChildrenNode.h" +#import "FLeafNode.h" +#import "FSnapshotUtilities.h" +#import "FStringUtilities.h" + +@interface FCompoundHashBuilder () + +@property(nonatomic, strong) FCompoundHashSplitStrategy splitStrategy; + +@property(nonatomic, strong) NSMutableArray *currentPaths; +@property(nonatomic, strong) NSMutableArray *currentHashes; + +@end + +@implementation FCompoundHashBuilder { + + // NOTE: We use the existence of this to know if we've started building a + // range (i.e. encountered a leaf node). + NSMutableString *optHashValueBuilder; + + // The current path as a stack. This is used in combination with + // currentPathDepth to simultaneously store the last leaf node path. The + // depth is changed when descending and ascending, at the same time the + // current key is set for the current depth. Because the keys are left + // unchanged for ascending the path will also contain the path of the last + // visited leaf node (using lastLeafDepth elements) + NSMutableArray *currentPath; + NSInteger lastLeafDepth; + NSInteger currentPathDepth; + + BOOL needsComma; +} + +- (instancetype)initWithSplitStrategy:(FCompoundHashSplitStrategy)strategy { + self = [super init]; + if (self != nil) { + self->_splitStrategy = strategy; + self->optHashValueBuilder = nil; + self->currentPath = [NSMutableArray array]; + self->lastLeafDepth = -1; + self->currentPathDepth = 0; + self->needsComma = YES; + self->_currentPaths = [NSMutableArray array]; + self->_currentHashes = [NSMutableArray array]; + } + return self; +} + +- (BOOL)isBuildingRange { + return self->optHashValueBuilder != nil; +} + +- (NSUInteger)currentHashLength { + return self->optHashValueBuilder.length; +} + +- (FPath *)currentPath { + return [self currentPathWithDepth:self->currentPathDepth]; +} + +- (FPath *)currentPathWithDepth:(NSInteger)depth { + NSArray *pieces = + [self->currentPath subarrayWithRange:NSMakeRange(0, depth)]; + return [[FPath alloc] initWithPieces:pieces andPieceNum:0]; +} + +- (void)enumerateCurrentPathToDepth:(NSInteger)depth + withBlock:(void (^)(NSString *key))block { + for (NSInteger i = 0; i < depth; i++) { + block(self->currentPath[i]); + } +} + +- (void)appendKey:(NSString *)key toString:(NSMutableString *)string { + [FSnapshotUtilities appendHashV2RepresentationForString:key + toString:string]; +} + +- (void)ensureRange { + if (![self isBuildingRange]) { + optHashValueBuilder = [NSMutableString string]; + [optHashValueBuilder appendString:@"("]; + [self + enumerateCurrentPathToDepth:self->currentPathDepth + withBlock:^(NSString *key) { + [self appendKey:key + toString:self->optHashValueBuilder]; + [self->optHashValueBuilder appendString:@":("]; + }]; + self->needsComma = NO; + } +} + +- (void)processLeaf:(FLeafNode *)leafNode { + [self ensureRange]; + + self->lastLeafDepth = self->currentPathDepth; + [FSnapshotUtilities + appendHashRepresentationForLeafNode:leafNode + toString:self->optHashValueBuilder + hashVersion:FDataHashVersionV2]; + self->needsComma = YES; + if (self.splitStrategy(self)) { + [self endRange]; + } +} + +- (void)startChild:(NSString *)key { + [self ensureRange]; + + if (self->needsComma) { + [self->optHashValueBuilder appendString:@","]; + } + [self appendKey:key toString:self->optHashValueBuilder]; + [self->optHashValueBuilder appendString:@":("]; + if (self->currentPathDepth == currentPath.count) { + [self->currentPath addObject:key]; + } else { + self->currentPath[self->currentPathDepth] = key; + } + self->currentPathDepth++; + self->needsComma = NO; +} + +- (void)endChild { + self->currentPathDepth--; + if ([self isBuildingRange]) { + [self->optHashValueBuilder appendString:@")"]; + } + self->needsComma = YES; +} + +- (void)finishHashing { + NSAssert(self->currentPathDepth == 0, + @"Can't finish hashing in the middle of processing a child"); + if ([self isBuildingRange]) { + [self endRange]; + } + + // Always close with the empty hash for the remaining range to allow simple + // appending + [self.currentHashes addObject:@""]; +} + +- (void)endRange { + NSAssert([self isBuildingRange], + @"Can't end range without starting a range!"); + // Add closing parenthesis for current depth + for (NSUInteger i = 0; i < currentPathDepth; i++) { + [self->optHashValueBuilder appendString:@")"]; + } + [self->optHashValueBuilder appendString:@")"]; + + FPath *lastLeafPath = [self currentPathWithDepth:self->lastLeafDepth]; + NSString *hash = + [FStringUtilities base64EncodedSha1:self->optHashValueBuilder]; + [self.currentHashes addObject:hash]; + [self.currentPaths addObject:lastLeafPath]; + + self->optHashValueBuilder = nil; +} + +@end + +@interface FCompoundHash () + +@property(nonatomic, strong, readwrite) NSArray *posts; +@property(nonatomic, strong, readwrite) NSArray *hashes; + +@end + +@implementation FCompoundHash + +- (id)initWithPosts:(NSArray *)posts hashes:(NSArray *)hashes { + self = [super init]; + if (self != nil) { + if (posts.count != hashes.count - 1) { + [NSException raise:NSInvalidArgumentException + format:@"Number of posts need to be n-1 for n hashes " + @"in FCompoundHash"]; + } + self.posts = posts; + self.hashes = hashes; + } + return self; +} + ++ (FCompoundHashSplitStrategy)simpleSizeSplitStrategyForNode:(id)node { + NSUInteger estimatedSize = + [FSnapshotUtilities estimateSerializedNodeSize:node]; + + // Splits for + // 1k -> 512 (2 parts) + // 5k -> 715 (7 parts) + // 100k -> 3.2k (32 parts) + // 500k -> 7k (71 parts) + // 5M -> 23k (228 parts) + NSUInteger splitThreshold = MAX(512, (NSUInteger)sqrt(estimatedSize * 100)); + + return ^BOOL(FCompoundHashBuilder *builder) { + // Never split on priorities + return [builder currentHashLength] > splitThreshold && + ![[[builder currentPath] getBack] isEqualToString:@".priority"]; + }; +} + ++ (FCompoundHash *)fromNode:(id)node { + return [FCompoundHash + fromNode:node + splitStrategy:[FCompoundHash simpleSizeSplitStrategyForNode:node]]; +} + ++ (FCompoundHash *)fromNode:(id)node + splitStrategy:(FCompoundHashSplitStrategy)strategy { + if ([node isEmpty]) { + return [[FCompoundHash alloc] initWithPosts:@[] hashes:@[ @"" ]]; + } else { + FCompoundHashBuilder *builder = + [[FCompoundHashBuilder alloc] initWithSplitStrategy:strategy]; + [FCompoundHash processNode:node builder:builder]; + [builder finishHashing]; + return [[FCompoundHash alloc] initWithPosts:builder.currentPaths + hashes:builder.currentHashes]; + } +} + ++ (void)processNode:(id)node builder:(FCompoundHashBuilder *)builder { + if ([node isLeafNode]) { + [builder processLeaf:node]; + } else { + NSAssert(![node isEmpty], @"Can't calculate hash on empty node!"); + FChildrenNode *childrenNode = (FChildrenNode *)node; + [childrenNode enumerateChildrenAndPriorityUsingBlock:^( + NSString *key, id node, BOOL *stop) { + [builder startChild:key]; + [self processNode:node builder:builder]; + [builder endChild]; + }]; + } +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FListenProvider.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FListenProvider.h new file mode 100644 index 0000000..8efd803 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FListenProvider.h @@ -0,0 +1,32 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FTypedefs_Private.h" + +@class FQuerySpec; +@protocol FSyncTreeHash; + +typedef NSArray * (^fbt_startListeningBlock)(FQuerySpec *query, NSNumber *tagId, + id hash, + fbt_nsarray_nsstring onComplete); +typedef void (^fbt_stopListeningBlock)(FQuerySpec *query, NSNumber *tagId); + +@interface FListenProvider : NSObject + +@property(nonatomic, copy) fbt_startListeningBlock startListening; +@property(nonatomic, copy) fbt_stopListeningBlock stopListening; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FListenProvider.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FListenProvider.m new file mode 100644 index 0000000..27da015 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FListenProvider.m @@ -0,0 +1,25 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FListenProvider.h" +#import "FIRDatabaseQuery.h" + +@implementation FListenProvider + +@synthesize startListening; +@synthesize stopListening; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FPersistentConnection.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FPersistentConnection.h new file mode 100644 index 0000000..81836fd --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FPersistentConnection.h @@ -0,0 +1,99 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FConnection.h" +#import "FRepoInfo.h" +#import "FTypedefs.h" +#import "FTypedefs_Private.h" +#import + +@protocol FPersistentConnectionDelegate; +@protocol FSyncTreeHash; +@class FQuerySpec; +@class FIRDatabaseConfig; + +@interface FPersistentConnection : NSObject + +@property(nonatomic, weak) id delegate; +@property(nonatomic) BOOL pauseWrites; + +- (id)initWithRepoInfo:(FRepoInfo *)repoInfo + dispatchQueue:(dispatch_queue_t)queue + config:(FIRDatabaseConfig *)config; + +- (void)open; + +- (void)putData:(id)data + forPath:(NSString *)pathString + withHash:(NSString *)hash + withCallback:(fbt_void_nsstring_nsstring)onComplete; +- (void)mergeData:(id)data + forPath:(NSString *)pathString + withCallback:(fbt_void_nsstring_nsstring)onComplete; + +- (void)listen:(FQuerySpec *)query + tagId:(NSNumber *)tagId + hash:(id)hash + onComplete:(fbt_void_nsstring)onComplete; + +- (void)unlisten:(FQuerySpec *)query tagId:(NSNumber *)tagId; +- (void)refreshAuthToken:(NSString *)token; +- (void)onDisconnectPutData:(id)data + forPath:(FPath *)path + withCallback:(fbt_void_nsstring_nsstring)callback; +- (void)onDisconnectMergeData:(id)data + forPath:(FPath *)path + withCallback:(fbt_void_nsstring_nsstring)callback; +- (void)onDisconnectCancelPath:(FPath *)path + withCallback:(fbt_void_nsstring_nsstring)callback; +- (void)ackPuts; +- (void)purgeOutstandingWrites; + +- (void)interruptForReason:(NSString *)reason; +- (void)resumeForReason:(NSString *)reason; +- (BOOL)isInterruptedForReason:(NSString *)reason; + +// FConnection delegate methods +- (void)onReady:(FConnection *)fconnection + atTime:(NSNumber *)timestamp + sessionID:(NSString *)sessionID; +- (void)onDataMessage:(FConnection *)fconnection + withMessage:(NSDictionary *)message; +- (void)onDisconnect:(FConnection *)fconnection + withReason:(FDisconnectReason)reason; +- (void)onKill:(FConnection *)fconnection withReason:(NSString *)reason; + +// Testing methods +- (NSDictionary *)dumpListens; + +@end + +@protocol FPersistentConnectionDelegate + +- (void)onDataUpdate:(FPersistentConnection *)fpconnection + forPath:(NSString *)pathString + message:(id)message + isMerge:(BOOL)isMerge + tagId:(NSNumber *)tagId; +- (void)onRangeMerge:(NSArray *)ranges + forPath:(NSString *)path + tagId:(NSNumber *)tag; +- (void)onConnect:(FPersistentConnection *)fpconnection; +- (void)onDisconnect:(FPersistentConnection *)fpconnection; +- (void)onServerInfoUpdate:(FPersistentConnection *)fpconnection + updates:(NSDictionary *)updates; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FPersistentConnection.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FPersistentConnection.m new file mode 100644 index 0000000..69f52c1 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FPersistentConnection.m @@ -0,0 +1,1141 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#import + +#import "FAtomicNumber.h" +#import "FAuthTokenProvider.h" +#import "FCompoundHash.h" +#import "FConstants.h" +#import "FIRDatabaseConfig.h" +#import "FIRDatabaseConfig_Private.h" +#import "FIRDatabaseReference.h" +#import "FIRRetryHelper.h" +#import "FIndex.h" +#import "FPersistentConnection.h" +#import "FQueryParams.h" +#import "FQuerySpec.h" +#import "FRangeMerge.h" +#import "FSnapshotUtilities.h" +#import "FSyncTree.h" +#import "FTupleCallbackStatus.h" +#import "FTupleOnDisconnect.h" +#import "FUtilities.h" +#import +#import +#import +#import + +@interface FOutstandingQuery : NSObject + +@property(nonatomic, strong) FQuerySpec *query; +@property(nonatomic, strong) NSNumber *tagId; +@property(nonatomic, strong) id syncTreeHash; +@property(nonatomic, copy) fbt_void_nsstring onComplete; + +@end + +@implementation FOutstandingQuery + +@end + +@interface FOutstandingPut : NSObject + +@property(nonatomic, strong) NSString *action; +@property(nonatomic, strong) NSDictionary *request; +@property(nonatomic, copy) fbt_void_nsstring_nsstring onCompleteBlock; +@property(nonatomic) BOOL sent; + +@end + +@implementation FOutstandingPut + +@end + +typedef enum { + ConnectionStateDisconnected, + ConnectionStateGettingToken, + ConnectionStateConnecting, + ConnectionStateAuthenticating, + ConnectionStateConnected +} ConnectionState; + +@interface FPersistentConnection () { + ConnectionState connectionState; + BOOL firstConnection; + NSTimeInterval reconnectDelay; + NSTimeInterval lastConnectionAttemptTime; + NSTimeInterval lastConnectionEstablishedTime; + SCNetworkReachabilityRef reachability; +} + +- (int)getNextRequestNumber; +- (void)onDataPushWithAction:(NSString *)action andBody:(NSDictionary *)body; +- (void)handleTimestamp:(NSNumber *)timestamp; +- (void)sendOnDisconnectAction:(NSString *)action + forPath:(NSString *)pathString + withData:(id)data + andCallback:(fbt_void_nsstring_nsstring)callback; + +@property(nonatomic, strong) FConnection *realtime; +@property(nonatomic, strong) NSMutableDictionary *listens; +@property(nonatomic, strong) NSMutableDictionary *outstandingPuts; +@property(nonatomic, strong) NSMutableArray *onDisconnectQueue; +@property(nonatomic, strong) FRepoInfo *repoInfo; +@property(nonatomic, strong) FAtomicNumber *putCounter; +@property(nonatomic, strong) FAtomicNumber *requestNumber; +@property(nonatomic, strong) NSMutableDictionary *requestCBHash; +@property(nonatomic, strong) FIRDatabaseConfig *config; +@property(nonatomic) NSUInteger unackedListensCount; +@property(nonatomic, strong) NSMutableArray *putsToAck; +@property(nonatomic, strong) dispatch_queue_t dispatchQueue; +@property(nonatomic, strong) NSString *lastSessionID; +@property(nonatomic, strong) NSMutableSet *interruptReasons; +@property(nonatomic, strong) FIRRetryHelper *retryHelper; +@property(nonatomic, strong) id authTokenProvider; +@property(nonatomic, strong) NSString *authToken; +@property(nonatomic) BOOL forceAuthTokenRefresh; +@property(nonatomic) NSUInteger currentFetchTokenAttempt; + +@end + +@implementation FPersistentConnection + +- (id)initWithRepoInfo:(FRepoInfo *)repoInfo + dispatchQueue:(dispatch_queue_t)dispatchQueue + config:(FIRDatabaseConfig *)config { + self = [super init]; + if (self) { + self->_config = config; + self->_repoInfo = repoInfo; + self->_dispatchQueue = dispatchQueue; + self->_authTokenProvider = config.authTokenProvider; + NSAssert(self->_authTokenProvider != nil, + @"Expected auth token provider"); + self.interruptReasons = [NSMutableSet set]; + + self.listens = [[NSMutableDictionary alloc] init]; + self.outstandingPuts = [[NSMutableDictionary alloc] init]; + self.onDisconnectQueue = [[NSMutableArray alloc] init]; + self.putCounter = [[FAtomicNumber alloc] init]; + self.requestNumber = [[FAtomicNumber alloc] init]; + self.requestCBHash = [[NSMutableDictionary alloc] init]; + self.unackedListensCount = 0; + self.putsToAck = [NSMutableArray array]; + connectionState = ConnectionStateDisconnected; + firstConnection = YES; + reconnectDelay = kPersistentConnReconnectMinDelay; + + self->_retryHelper = [[FIRRetryHelper alloc] + initWithDispatchQueue:dispatchQueue + minRetryDelayAfterFailure:kPersistentConnReconnectMinDelay + maxRetryDelay:kPersistentConnReconnectMaxDelay + retryExponent:kPersistentConnReconnectMultiplier + jitterFactor:0.7]; + + [self setupNotifications]; + // Make sure we don't actually connect until open is called + [self interruptForReason:kFInterruptReasonWaitingForOpen]; + } + // nb: The reason establishConnection isn't called here like the JS version + // is because callers need to set the delegate first. The ctor can be + // modified to accept the delegate but that deviates from normal ios + // conventions. After the delegate has been set, the caller is responsible + // for calling establishConnection: + return self; +} + +- (void)dealloc { + if (reachability) { + // Unschedule the notifications + SCNetworkReachabilitySetDispatchQueue(reachability, NULL); + CFRelease(reachability); + } +} + +#pragma mark - +#pragma mark Public methods + +- (void)open { + [self resumeForReason:kFInterruptReasonWaitingForOpen]; +} + +/** + * Note that the listens dictionary has a type of Map[String (pathString), + * Map[FQueryParams, FOutstandingQuery]] + * + * This means, for each path we care about, there are sets of queryParams that + * correspond to an FOutstandingQuery object. There can be multiple sets at a + * path since we overlap listens for a short time while adding or removing a + * query from a location in the tree. + */ +- (void)listen:(FQuerySpec *)query + tagId:(NSNumber *)tagId + hash:(id)hash + onComplete:(fbt_void_nsstring)onComplete { + FFLog(@"I-RDB034001", @"Listen called for %@", query); + + NSAssert(self.listens[query] == nil, + @"listen() called twice for the same query"); + NSAssert(query.isDefault || !query.loadsAllData, + @"listen called for non-default but complete query"); + FOutstandingQuery *outstanding = [[FOutstandingQuery alloc] init]; + outstanding.query = query; + outstanding.tagId = tagId; + outstanding.syncTreeHash = hash; + outstanding.onComplete = onComplete; + [self.listens setObject:outstanding forKey:query]; + if ([self connected]) { + [self sendListen:outstanding]; + } +} + +- (void)putData:(id)data + forPath:(NSString *)pathString + withHash:(NSString *)hash + withCallback:(fbt_void_nsstring_nsstring)onComplete { + [self putInternal:data + forAction:kFWPRequestActionPut + forPath:pathString + withHash:hash + withCallback:onComplete]; +} + +- (void)mergeData:(id)data + forPath:(NSString *)pathString + withCallback:(fbt_void_nsstring_nsstring)onComplete { + [self putInternal:data + forAction:kFWPRequestActionMerge + forPath:pathString + withHash:nil + withCallback:onComplete]; +} + +- (void)onDisconnectPutData:(id)data + forPath:(FPath *)path + withCallback:(fbt_void_nsstring_nsstring)callback { + if ([self canSendWrites]) { + [self sendOnDisconnectAction:kFWPRequestActionDisconnectPut + forPath:[path description] + withData:data + andCallback:callback]; + } else { + FTupleOnDisconnect *tuple = [[FTupleOnDisconnect alloc] init]; + tuple.pathString = [path description]; + tuple.action = kFWPRequestActionDisconnectPut; + tuple.data = data; + tuple.onComplete = callback; + [self.onDisconnectQueue addObject:tuple]; + } +} + +- (void)onDisconnectMergeData:(id)data + forPath:(FPath *)path + withCallback:(fbt_void_nsstring_nsstring)callback { + if ([self canSendWrites]) { + [self sendOnDisconnectAction:kFWPRequestActionDisconnectMerge + forPath:[path description] + withData:data + andCallback:callback]; + } else { + FTupleOnDisconnect *tuple = [[FTupleOnDisconnect alloc] init]; + tuple.pathString = [path description]; + tuple.action = kFWPRequestActionDisconnectMerge; + tuple.data = data; + tuple.onComplete = callback; + [self.onDisconnectQueue addObject:tuple]; + } +} + +- (void)onDisconnectCancelPath:(FPath *)path + withCallback:(fbt_void_nsstring_nsstring)callback { + if ([self canSendWrites]) { + [self sendOnDisconnectAction:kFWPRequestActionDisconnectCancel + forPath:[path description] + withData:[NSNull null] + andCallback:callback]; + } else { + FTupleOnDisconnect *tuple = [[FTupleOnDisconnect alloc] init]; + tuple.pathString = [path description]; + tuple.action = kFWPRequestActionDisconnectCancel; + tuple.data = [NSNull null]; + tuple.onComplete = callback; + [self.onDisconnectQueue addObject:tuple]; + } +} + +- (void)unlisten:(FQuerySpec *)query tagId:(NSNumber *)tagId { + FPath *path = query.path; + FFLog(@"I-RDB034002", @"Unlistening for %@", query); + + NSArray *outstanding = [self removeListen:query]; + if (outstanding.count > 0 && [self connected]) { + [self sendUnlisten:path queryParams:query.params tagId:tagId]; + } +} + +- (void)refreshAuthToken:(NSString *)token { + self.authToken = token; + if ([self connected]) { + if (token != nil) { + [self sendAuthAndRestoreStateAfterComplete:NO]; + } else { + [self sendUnauth]; + } + } +} + +#pragma mark - +#pragma mark Connection status + +- (BOOL)connected { + return self->connectionState == ConnectionStateAuthenticating || + self->connectionState == ConnectionStateConnected; +} + +- (BOOL)canSendWrites { + return self->connectionState == ConnectionStateConnected; +} + +#pragma mark - +#pragma mark FConnection delegate methods + +- (void)onReady:(FConnection *)fconnection + atTime:(NSNumber *)timestamp + sessionID:(NSString *)sessionID { + FFLog(@"I-RDB034003", @"On ready"); + lastConnectionEstablishedTime = [[NSDate date] timeIntervalSince1970]; + [self handleTimestamp:timestamp]; + + if (firstConnection) { + [self sendConnectStats]; + } + + [self restoreAuth]; + firstConnection = NO; + self.lastSessionID = sessionID; + dispatch_async(self.dispatchQueue, ^{ + [self.delegate onConnect:self]; + }); +} + +- (void)onDataMessage:(FConnection *)fconnection + withMessage:(NSDictionary *)message { + if (message[kFWPRequestNumber] != nil) { + // this is a response to a request we sent + NSNumber *rn = [NSNumber + numberWithInt:[[message objectForKey:kFWPRequestNumber] intValue]]; + if ([self.requestCBHash objectForKey:rn]) { + void (^callback)(NSDictionary *) = + [self.requestCBHash objectForKey:rn]; + [self.requestCBHash removeObjectForKey:rn]; + + if (callback) { + // dispatch_async(self.dispatchQueue, ^{ + callback([message objectForKey:kFWPResponseForRNData]); + //}); + } + } + } else if (message[kFWPRequestError] != nil) { + NSString *error = [message objectForKey:kFWPRequestError]; + @throw [[NSException alloc] initWithName:@"FirebaseDatabaseServerError" + reason:error + userInfo:nil]; + } else if (message[kFWPAsyncServerAction] != nil) { + // this is a server push of some sort + NSString *action = [message objectForKey:kFWPAsyncServerAction]; + NSDictionary *body = [message objectForKey:kFWPAsyncServerPayloadBody]; + [self onDataPushWithAction:action andBody:body]; + } +} + +- (void)onDisconnect:(FConnection *)fconnection + withReason:(FDisconnectReason)reason { + FFLog(@"I-RDB034004", @"Got on disconnect due to %s", + (reason == DISCONNECT_REASON_SERVER_RESET) ? "server_reset" + : "other"); + connectionState = ConnectionStateDisconnected; + // Drop the realtime connection + self.realtime = nil; + [self cancelSentTransactions]; + [self.requestCBHash removeAllObjects]; + self.unackedListensCount = 0; + if ([self shouldReconnect]) { + NSTimeInterval timeSinceLastConnectSucceeded = + [[NSDate date] timeIntervalSince1970] - + lastConnectionEstablishedTime; + BOOL lastConnectionWasSuccessful; + if (lastConnectionEstablishedTime > 0) { + lastConnectionWasSuccessful = + timeSinceLastConnectSucceeded > + kPersistentConnSuccessfulConnectionEstablishedDelay; + } else { + lastConnectionWasSuccessful = NO; + } + + if (reason == DISCONNECT_REASON_SERVER_RESET || + lastConnectionWasSuccessful) { + [self.retryHelper signalSuccess]; + } + [self tryScheduleReconnect]; + } + lastConnectionEstablishedTime = 0; + [self.delegate onDisconnect:self]; +} + +- (void)onKill:(FConnection *)fconnection withReason:(NSString *)reason { + FFWarn(@"I-RDB034005", + @"Firebase Database connection was forcefully killed by the server. " + @" Will not attempt reconnect. Reason: %@", + reason); + [self interruptForReason:kFInterruptReasonServerKill]; +} + +#pragma mark - +#pragma mark Connection handling methods + +- (void)interruptForReason:(NSString *)reason { + FFLog(@"I-RDB034006", @"Connection interrupted for: %@", reason); + + [self.interruptReasons addObject:reason]; + if (self.realtime) { + // Will call onDisconnect and set the connection state to Disconnected + [self.realtime close]; + self.realtime = nil; + } else { + [self.retryHelper cancel]; + self->connectionState = ConnectionStateDisconnected; + } + // Reset timeouts + [self.retryHelper signalSuccess]; +} + +- (void)resumeForReason:(NSString *)reason { + FFLog(@"I-RDB034007", @"Connection no longer interrupted for: %@", reason); + [self.interruptReasons removeObject:reason]; + + if ([self shouldReconnect] && + connectionState == ConnectionStateDisconnected) { + [self tryScheduleReconnect]; + } +} + +- (BOOL)shouldReconnect { + return self.interruptReasons.count == 0; +} + +- (BOOL)isInterruptedForReason:(NSString *)reason { + return [self.interruptReasons containsObject:reason]; +} + +#pragma mark - +#pragma mark Private methods + +- (void)tryScheduleReconnect { + if ([self shouldReconnect]) { + NSAssert(self->connectionState == ConnectionStateDisconnected, + @"Not in disconnected state: %d", self->connectionState); + BOOL forceRefresh = self.forceAuthTokenRefresh; + self.forceAuthTokenRefresh = NO; + FFLog(@"I-RDB034008", @"Scheduling connection attempt"); + [self.retryHelper retry:^{ + FFLog(@"I-RDB034009", @"Trying to fetch auth token"); + NSAssert(self->connectionState == ConnectionStateDisconnected, + @"Not in disconnected state: %d", self->connectionState); + self->connectionState = ConnectionStateGettingToken; + self.currentFetchTokenAttempt++; + NSUInteger thisFetchTokenAttempt = self.currentFetchTokenAttempt; + [self.authTokenProvider + fetchTokenForcingRefresh:forceRefresh + withCallback:^(NSString *token, NSError *error) { + if (thisFetchTokenAttempt == + self.currentFetchTokenAttempt) { + if (error != nil) { + self->connectionState = + ConnectionStateDisconnected; + FFLog(@"I-RDB034010", + @"Error fetching token: %@", error); + [self tryScheduleReconnect]; + } else { + // Someone could have interrupted us while + // fetching the token, marking the + // connection as Disconnected + if (self->connectionState == + ConnectionStateGettingToken) { + FFLog(@"I-RDB034011", + @"Successfully fetched token, " + @"opening connection"); + [self openNetworkConnectionWithToken: + token]; + } else { + NSAssert( + self->connectionState == + ConnectionStateDisconnected, + @"Expected connection state " + @"disconnected, but got %d", + self->connectionState); + FFLog(@"I-RDB034012", + @"Not opening connection after " + @"token refresh, because " + @"connection was set to " + @"disconnected."); + } + } + } else { + FFLog(@"I-RDB034013", + @"Ignoring fetch token result, because " + @"this was not the latest attempt."); + } + }]; + }]; + } +} + +- (void)openNetworkConnectionWithToken:(NSString *)token { + NSAssert(self->connectionState == ConnectionStateGettingToken, + @"Trying to open network connection while in wrong state: %d", + self->connectionState); + self.authToken = token; + self->connectionState = ConnectionStateConnecting; + self.realtime = [[FConnection alloc] initWith:self.repoInfo + andDispatchQueue:self.dispatchQueue + lastSessionID:self.lastSessionID]; + self.realtime.delegate = self; + [self.realtime open]; +} + +static void reachabilityCallback(SCNetworkReachabilityRef ref, + SCNetworkReachabilityFlags flags, void *info) { + if (flags & kSCNetworkReachabilityFlagsReachable) { + FFLog(@"I-RDB034014", + @"Network became reachable. Trigger a connection attempt"); + FPersistentConnection *self = (__bridge FPersistentConnection *)info; + // Reset reconnect delay + [self.retryHelper signalSuccess]; + if (self->connectionState == ConnectionStateDisconnected) { + [self tryScheduleReconnect]; + } + } else { + FFLog(@"I-RDB034015", @"Network is not reachable"); + } +} + +- (void)enteringForeground { + dispatch_async(self.dispatchQueue, ^{ + // Reset reconnect delay + [self.retryHelper signalSuccess]; + if (self->connectionState == ConnectionStateDisconnected) { + [self tryScheduleReconnect]; + } + }); +} + +- (void)setupNotifications { + + NSString *const *foregroundConstant = (NSString *const *)dlsym( + RTLD_DEFAULT, "UIApplicationWillEnterForegroundNotification"); + if (foregroundConstant) { + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(enteringForeground) + name:*foregroundConstant + object:nil]; + } + // An empty address is interpreted a generic internet access + struct sockaddr_in zeroAddress; + bzero(&zeroAddress, sizeof(zeroAddress)); + zeroAddress.sin_len = sizeof(zeroAddress); + zeroAddress.sin_family = AF_INET; + reachability = SCNetworkReachabilityCreateWithAddress( + kCFAllocatorDefault, (const struct sockaddr *)&zeroAddress); + SCNetworkReachabilityContext ctx = {0, (__bridge void *)(self), NULL, NULL, + NULL}; + if (SCNetworkReachabilitySetCallback(reachability, reachabilityCallback, + &ctx)) { + SCNetworkReachabilitySetDispatchQueue(reachability, self.dispatchQueue); + } else { + FFLog(@"I-RDB034016", + @"Failed to set up network reachability monitoring"); + CFRelease(reachability); + reachability = NULL; + } +} + +- (void)sendAuthAndRestoreStateAfterComplete:(BOOL)restoreStateAfterComplete { + NSAssert([self connected], @"Must be connected to send auth"); + NSAssert(self.authToken != nil, + @"Can't send auth if there is no credential"); + + NSDictionary *requestData = @{kFWPRequestCredential : self.authToken}; + [self sendAction:kFWPRequestActionAuth + body:requestData + sensitive:YES + callback:^(NSDictionary *data) { + self->connectionState = ConnectionStateConnected; + NSString *status = + [data objectForKey:kFWPResponseForActionStatus]; + id responseData = [data objectForKey:kFWPResponseForActionData]; + if (responseData == nil) { + responseData = @"error"; + } + + BOOL statusOk = + [status isEqualToString:kFWPResponseForActionStatusOk]; + if (statusOk) { + if (restoreStateAfterComplete) { + [self restoreState]; + } + } else { + self.authToken = nil; + self.forceAuthTokenRefresh = YES; + if ([status isEqualToString:@"expired_token"]) { + FFLog(@"I-RDB034017", @"Authentication failed: %@ (%@)", + status, responseData); + } else { + FFWarn(@"I-RDB034018", @"Authentication failed: %@ (%@)", + status, responseData); + } + [self.realtime close]; + } + }]; +} + +- (void)sendUnauth { + [self sendAction:kFWPRequestActionUnauth + body:@{} + sensitive:NO + callback:nil]; +} + +- (void)onAuthRevokedWithStatus:(NSString *)status + andReason:(NSString *)reason { + // This might be for an earlier token than we just recently sent. But since + // we need to close the connection anyways, we can set it to null here and + // we will refresh the token later on reconnect + if ([status isEqualToString:@"expired_token"]) { + FFLog(@"I-RDB034019", @"Auth token revoked: %@ (%@)", status, reason); + } else { + FFWarn(@"I-RDB034020", @"Auth token revoked: %@ (%@)", status, reason); + } + self.authToken = nil; + self.forceAuthTokenRefresh = YES; + // Try reconnecting on auth revocation + [self.realtime close]; +} + +- (void)onListenRevoked:(FPath *)path { + NSArray *queries = [self removeAllListensAtPath:path]; + for (FOutstandingQuery *query in queries) { + query.onComplete(@"permission_denied"); + } +} + +- (void)sendOnDisconnectAction:(NSString *)action + forPath:(NSString *)pathString + withData:(id)data + andCallback:(fbt_void_nsstring_nsstring)callback { + + NSDictionary *request = + @{kFWPRequestPath : pathString, kFWPRequestData : data}; + FFLog(@"I-RDB034021", @"onDisconnect %@: %@", action, request); + + [self sendAction:action + body:request + sensitive:NO + callback:^(NSDictionary *data) { + NSString *status = + [data objectForKey:kFWPResponseForActionStatus]; + NSString *errorReason = + [data objectForKey:kFWPResponseForActionData]; + callback(status, errorReason); + }]; +} + +- (void)sendPut:(NSNumber *)index { + NSAssert([self canSendWrites], + @"sendPut called when not able to send writes"); + FOutstandingPut *put = self.outstandingPuts[index]; + assert(put != nil); + fbt_void_nsstring_nsstring onComplete = put.onCompleteBlock; + + // Do not async this block; copying the block insinde sendAction: doesn't + // happen in time (or something) so coredumps + put.sent = YES; + [self sendAction:put.action + body:put.request + sensitive:NO + callback:^(NSDictionary *data) { + FOutstandingPut *currentPut = self.outstandingPuts[index]; + if (currentPut == put) { + [self.outstandingPuts removeObjectForKey:index]; + + if (onComplete != nil) { + NSString *status = + [data objectForKey:kFWPResponseForActionStatus]; + NSString *errorReason = + [data objectForKey:kFWPResponseForActionData]; + if (self.unackedListensCount == 0) { + onComplete(status, errorReason); + } else { + FTupleCallbackStatus *putToAck = + [[FTupleCallbackStatus alloc] init]; + putToAck.block = onComplete; + putToAck.status = status; + putToAck.errorReason = errorReason; + [self.putsToAck addObject:putToAck]; + } + } + } else { + FFLog(@"I-RDB034022", + @"Ignoring on complete for put %@ because it was " + @"already removed", + index); + } + }]; +} + +- (void)sendUnlisten:(FPath *)path + queryParams:(FQueryParams *)queryParams + tagId:(NSNumber *)tagId { + FFLog(@"I-RDB034023", @"Unlisten on %@ for %@", path, queryParams); + + NSMutableDictionary *request = [NSMutableDictionary + dictionaryWithObjectsAndKeys:[path toString], kFWPRequestPath, nil]; + if (tagId != nil) { + [request setObject:queryParams.wireProtocolParams + forKey:kFWPRequestQueries]; + [request setObject:tagId forKey:kFWPRequestTag]; + } + + [self sendAction:kFWPRequestActionTaggedUnlisten + body:request + sensitive:NO + callback:nil]; +} + +- (void)putInternal:(id)data + forAction:(NSString *)action + forPath:(NSString *)pathString + withHash:(NSString *)hash + withCallback:(fbt_void_nsstring_nsstring)onComplete { + + NSMutableDictionary *request = [NSMutableDictionary + dictionaryWithObjectsAndKeys:pathString, kFWPRequestPath, data, + kFWPRequestData, nil]; + if (hash) { + [request setObject:hash forKey:kFWPRequestHash]; + } + + FOutstandingPut *put = [[FOutstandingPut alloc] init]; + put.action = action; + put.request = request; + put.onCompleteBlock = onComplete; + put.sent = NO; + + NSNumber *index = [self.putCounter getAndIncrement]; + self.outstandingPuts[index] = put; + + if ([self canSendWrites]) { + FFLog(@"I-RDB034024", @"Was connected, and added as index: %@", index); + [self sendPut:index]; + } else { + FFLog(@"I-RDB034025", + @"Wasn't connected or writes paused, so added to outstanding " + @"puts only. Path: %@", + pathString); + } +} + +- (void)sendListen:(FOutstandingQuery *)listenSpec { + FQuerySpec *query = listenSpec.query; + FFLog(@"I-RDB034026", @"Listen for %@", query); + NSMutableDictionary *request = + [NSMutableDictionary dictionaryWithObject:[query.path toString] + forKey:kFWPRequestPath]; + + // Only bother to send query if it's non-default + if (listenSpec.tagId != nil) { + [request setObject:[query.params wireProtocolParams] + forKey:kFWPRequestQueries]; + [request setObject:listenSpec.tagId forKey:kFWPRequestTag]; + } + + [request setObject:[listenSpec.syncTreeHash simpleHash] + forKey:kFWPRequestHash]; + if ([listenSpec.syncTreeHash includeCompoundHash]) { + FCompoundHash *compoundHash = [listenSpec.syncTreeHash compoundHash]; + NSMutableArray *posts = [NSMutableArray array]; + for (FPath *path in compoundHash.posts) { + [posts addObject:path.wireFormat]; + } + request[kFWPRequestCompoundHash] = @{ + kFWPRequestCompoundHashHashes : compoundHash.hashes, + kFWPRequestCompoundHashPaths : posts + }; + } + + fbt_void_nsdictionary onResponse = ^(NSDictionary *response) { + FFLog(@"I-RDB034027", @"Listen response %@", response); + // warn in any case, even if the listener was removed + [self warnOnListenWarningsForQuery:query + payload:response[kFWPResponseForActionData]]; + + FOutstandingQuery *currentListenSpec = self.listens[query]; + + // only trigger actions if the listen hasn't been removed (and maybe + // readded) + if (currentListenSpec == listenSpec) { + NSString *status = [response objectForKey:kFWPRequestStatus]; + if (![status isEqualToString:@"ok"]) { + [self removeListen:query]; + } + + if (listenSpec.onComplete) { + listenSpec.onComplete(status); + } + } + + self.unackedListensCount--; + NSAssert(self.unackedListensCount >= 0, + @"unackedListensCount decremented to be negative."); + if (self.unackedListensCount == 0) { + [self ackPuts]; + } + }; + + [self sendAction:kFWPRequestActionTaggedListen + body:request + sensitive:NO + callback:onResponse]; + + self.unackedListensCount++; +} + +- (void)warnOnListenWarningsForQuery:(FQuerySpec *)query payload:(id)payload { + if (payload != nil && [payload isKindOfClass:[NSDictionary class]]) { + NSDictionary *payloadDict = payload; + id warnings = payloadDict[kFWPResponseDataWarnings]; + if (warnings != nil && [warnings isKindOfClass:[NSArray class]]) { + NSArray *warningsArr = warnings; + if ([warningsArr containsObject:@"no_index"]) { + NSString *indexSpec = [NSString + stringWithFormat:@"\".indexOn\": \"%@\"", + [query.params.index queryDefinition]]; + NSString *indexPath = [query.path description]; + FFWarn(@"I-RDB034028", + @"Using an unspecified index. Your data will be " + @"downloaded and filtered on the client. " + "Consider adding %@ at %@ to your security rules for " + "better performance", + indexSpec, indexPath); + } + } + } +} + +- (int)getNextRequestNumber { + return [[self.requestNumber getAndIncrement] intValue]; +} + +- (void)sendAction:(NSString *)action + body:(NSDictionary *)message + sensitive:(BOOL)sensitive + callback:(void (^)(NSDictionary *data))onMessage { + // Hold onto the onMessage callback for this request before firing it off + NSNumber *rn = [NSNumber numberWithInt:[self getNextRequestNumber]]; + NSDictionary *msg = [NSDictionary + dictionaryWithObjectsAndKeys:rn, kFWPRequestNumber, action, + kFWPRequestAction, message, + kFWPRequestPayloadBody, nil]; + + [self.realtime sendRequest:msg sensitive:sensitive]; + + if (onMessage) { + // Debug message without a callback; bump the rn, but don't hold onto + // the cb + [self.requestCBHash setObject:[onMessage copy] forKey:rn]; + } +} + +- (void)cancelSentTransactions { + NSMutableDictionary + *cancelledOutstandingPuts = [[NSMutableDictionary alloc] init]; + + for (NSNumber *index in self.outstandingPuts) { + FOutstandingPut *put = self.outstandingPuts[index]; + if (put.request[kFWPRequestHash] && put.sent) { + // This is a sent transaction put. + cancelledOutstandingPuts[index] = put; + } + } + + [cancelledOutstandingPuts + enumerateKeysAndObjectsUsingBlock:^( + NSNumber *index, FOutstandingPut *outstandingPut, BOOL *stop) { + // `onCompleteBlock:` may invoke `rerunTransactionsForPath:` and + // enqueue new writes. We defer calling it until we have finished + // enumerating all existing writes. + outstandingPut.onCompleteBlock( + kFTransactionDisconnect, + @"Client was disconnected while running a transaction"); + [self.outstandingPuts removeObjectForKey:index]; + }]; +} + +- (void)onDataPushWithAction:(NSString *)action andBody:(NSDictionary *)body { + FFLog(@"I-RDB034029", @"handleServerMessage: %@, %@", action, body); + id delegate = self.delegate; + if ([action isEqualToString:kFWPAsyncServerDataUpdate] || + [action isEqualToString:kFWPAsyncServerDataMerge]) { + BOOL isMerge = [action isEqualToString:kFWPAsyncServerDataMerge]; + + if ([body objectForKey:kFWPAsyncServerDataUpdateBodyPath] && + [body objectForKey:kFWPAsyncServerDataUpdateBodyData]) { + NSString *path = + [body objectForKey:kFWPAsyncServerDataUpdateBodyPath]; + id payloadData = + [body objectForKey:kFWPAsyncServerDataUpdateBodyData]; + if (isMerge && [payloadData isKindOfClass:[NSDictionary class]] && + [payloadData count] == 0) { + // ignore empty merge + } else { + [delegate + onDataUpdate:self + forPath:path + message:payloadData + isMerge:isMerge + tagId:[body objectForKey: + kFWPAsyncServerDataUpdateBodyTag]]; + } + } else { + FFLog( + @"I-RDB034030", + @"Malformed data response from server missing path or data: %@", + body); + } + } else if ([action isEqualToString:kFWPAsyncServerDataRangeMerge]) { + NSString *path = body[kFWPAsyncServerDataUpdateBodyPath]; + NSArray *ranges = body[kFWPAsyncServerDataUpdateBodyData]; + NSNumber *tag = body[kFWPAsyncServerDataUpdateBodyTag]; + NSMutableArray *rangeMerges = [NSMutableArray array]; + for (NSDictionary *range in ranges) { + NSString *startString = range[kFWPAsyncServerDataUpdateStartPath]; + NSString *endString = range[kFWPAsyncServerDataUpdateEndPath]; + id updateData = range[kFWPAsyncServerDataUpdateRangeMerge]; + id updates = [FSnapshotUtilities nodeFrom:updateData]; + FPath *start = (startString != nil) + ? [[FPath alloc] initWith:startString] + : nil; + FPath *end = + (endString != nil) ? [[FPath alloc] initWith:endString] : nil; + FRangeMerge *merge = [[FRangeMerge alloc] initWithStart:start + end:end + updates:updates]; + [rangeMerges addObject:merge]; + } + [delegate onRangeMerge:rangeMerges forPath:path tagId:tag]; + } else if ([action isEqualToString:kFWPAsyncServerAuthRevoked]) { + NSString *status = [body objectForKey:kFWPResponseForActionStatus]; + NSString *reason = [body objectForKey:kFWPResponseForActionData]; + [self onAuthRevokedWithStatus:status andReason:reason]; + } else if ([action isEqualToString:kFWPASyncServerListenCancelled]) { + NSString *pathString = + [body objectForKey:kFWPAsyncServerDataUpdateBodyPath]; + [self onListenRevoked:[[FPath alloc] initWith:pathString]]; + } else if ([action isEqualToString:kFWPAsyncServerSecurityDebug]) { + NSString *msg = [body objectForKey:@"msg"]; + if (msg != nil) { + NSArray *msgs = [msg componentsSeparatedByString:@"\n"]; + for (NSString *m in msgs) { + FFWarn(@"I-RDB034031", @"%@", m); + } + } + } else { + // TODO: revoke listens, auth, security debug + FFLog(@"I-RDB034032", @"Unsupported action from server: %@", action); + } +} + +- (void)restoreAuth { + FFLog(@"I-RDB034033", @"Calling restore state"); + + NSAssert(self->connectionState == ConnectionStateConnecting, + @"Wanted to restore auth, but was in wrong state: %d", + self->connectionState); + if (self.authToken == nil) { + FFLog(@"I-RDB034034", @"Not restoring auth because token is nil"); + self->connectionState = ConnectionStateConnected; + [self restoreState]; + } else { + FFLog(@"I-RDB034035", @"Restoring auth"); + self->connectionState = ConnectionStateAuthenticating; + [self sendAuthAndRestoreStateAfterComplete:YES]; + } +} + +- (void)restoreState { + NSAssert(self->connectionState == ConnectionStateConnected, + @"Should be connected if we're restoring state, but we are: %d", + self->connectionState); + + [self.listens enumerateKeysAndObjectsUsingBlock:^( + FQuerySpec *query, FOutstandingQuery *outstandingListen, + BOOL *stop) { + FFLog(@"I-RDB034036", @"Restoring listen for %@", query); + [self sendListen:outstandingListen]; + }]; + + NSArray *keys = [[self.outstandingPuts allKeys] + sortedArrayUsingSelector:@selector(compare:)]; + for (int i = 0; i < [keys count]; i++) { + if ([self.outstandingPuts objectForKey:[keys objectAtIndex:i]] != nil) { + FFLog(@"I-RDB034037", @"Restoring put: %d", i); + [self sendPut:[keys objectAtIndex:i]]; + } else { + FFLog(@"I-RDB034038", @"Restoring put: skipped nil: %d", i); + } + } + + for (FTupleOnDisconnect *tuple in self.onDisconnectQueue) { + [self sendOnDisconnectAction:tuple.action + forPath:tuple.pathString + withData:tuple.data + andCallback:tuple.onComplete]; + } + [self.onDisconnectQueue removeAllObjects]; +} + +- (NSArray *)removeListen:(FQuerySpec *)query { + NSAssert(query.isDefault || !query.loadsAllData, + @"removeListen called for non-default but complete query"); + + FOutstandingQuery *outstanding = self.listens[query]; + if (!outstanding) { + FFLog(@"I-RDB034039", + @"Trying to remove listener for query %@ but no listener exists", + query); + return @[]; + } else { + [self.listens removeObjectForKey:query]; + return @[ outstanding ]; + } +} + +- (NSArray *)removeAllListensAtPath:(FPath *)path { + FFLog(@"I-RDB034040", @"Removing all listens at path %@", path); + NSMutableArray *removed = [NSMutableArray array]; + NSMutableArray *toRemove = [NSMutableArray array]; + [self.listens + enumerateKeysAndObjectsUsingBlock:^( + FQuerySpec *spec, FOutstandingQuery *outstanding, BOOL *stop) { + if ([spec.path isEqual:path]) { + [removed addObject:outstanding]; + [toRemove addObject:spec]; + } + }]; + [self.listens removeObjectsForKeys:toRemove]; + return removed; +} + +- (void)purgeOutstandingWrites { + // We might have unacked puts in our queue that we need to ack now before we + // send out any cancels... + [self ackPuts]; + // Cancel in order + NSArray *keys = [[self.outstandingPuts allKeys] + sortedArrayUsingSelector:@selector(compare:)]; + for (NSNumber *key in keys) { + FOutstandingPut *put = self.outstandingPuts[key]; + if (put.onCompleteBlock != nil) { + put.onCompleteBlock(kFErrorWriteCanceled, nil); + } + } + for (FTupleOnDisconnect *onDisconnect in self.onDisconnectQueue) { + if (onDisconnect.onComplete != nil) { + onDisconnect.onComplete(kFErrorWriteCanceled, nil); + } + } + [self.outstandingPuts removeAllObjects]; + [self.onDisconnectQueue removeAllObjects]; +} + +- (void)ackPuts { + for (FTupleCallbackStatus *put in self.putsToAck) { + put.block(put.status, put.errorReason); + } + [self.putsToAck removeAllObjects]; +} + +- (void)handleTimestamp:(NSNumber *)timestamp { + FFLog(@"I-RDB034041", @"Handling timestamp: %@", timestamp); + double timestampDeltaMs = [timestamp doubleValue] - + ([[NSDate date] timeIntervalSince1970] * 1000); + [self.delegate onServerInfoUpdate:self + updates:@{ + kDotInfoServerTimeOffset : [NSNumber + numberWithDouble:timestampDeltaMs] + }]; +} + +- (void)sendStats:(NSDictionary *)stats { + if ([stats count] > 0) { + NSDictionary *request = @{kFWPRequestCounters : stats}; + [self sendAction:kFWPRequestActionStats + body:request + sensitive:NO + callback:^(NSDictionary *data) { + NSString *status = + [data objectForKey:kFWPResponseForActionStatus]; + NSString *errorReason = + [data objectForKey:kFWPResponseForActionData]; + BOOL statusOk = + [status isEqualToString:kFWPResponseForActionStatusOk]; + if (!statusOk) { + FFLog(@"I-RDB034042", @"Failed to send stats: %@", + errorReason); + } + }]; + } else { + FFLog(@"I-RDB034043", @"Not sending stats because stats are empty"); + } +} + +- (void)sendConnectStats { + NSMutableDictionary *stats = [NSMutableDictionary dictionary]; + +#if TARGET_OS_IOS || TARGET_OS_TV + if (self.config.persistenceEnabled) { + stats[@"persistence.ios.enabled"] = @1; + } +#elif TARGET_OS_OSX + if (self.config.persistenceEnabled) { + stats[@"persistence.osx.enabled"] = @1; + } +#endif + NSString *sdkVersion = + [[FIRDatabase sdkVersion] stringByReplacingOccurrencesOfString:@"." + withString:@"-"]; + NSString *sdkStatName = + [NSString stringWithFormat:@"sdk.objc.%@", sdkVersion]; + stats[sdkStatName] = @1; + FFLog(@"I-RDB034044", @"Sending first connection stats"); + [self sendStats:stats]; +} + +- (NSDictionary *)dumpListens { + return self.listens; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FQueryParams.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FQueryParams.h new file mode 100644 index 0000000..5d957c5 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FQueryParams.h @@ -0,0 +1,60 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@protocol FIndex +, FNodeFilter, FNode; + +@interface FQueryParams : NSObject + +@property(nonatomic, readonly) BOOL limitSet; +@property(nonatomic, readonly) NSInteger limit; + +@property(nonatomic, strong, readonly) NSString *viewFrom; +@property(nonatomic, strong, readonly) id indexStartValue; +@property(nonatomic, strong, readonly) NSString *indexStartKey; +@property(nonatomic, strong, readonly) id indexEndValue; +@property(nonatomic, strong, readonly) NSString *indexEndKey; + +@property(nonatomic, strong, readonly) id index; + +- (BOOL)loadsAllData; +- (BOOL)isDefault; +- (BOOL)isValid; +- (BOOL)hasAnchoredLimit; + +- (FQueryParams *)limitTo:(NSInteger)limit; +- (FQueryParams *)limitToFirst:(NSInteger)newLimit; +- (FQueryParams *)limitToLast:(NSInteger)newLimit; + +- (FQueryParams *)startAt:(id)indexValue childKey:(NSString *)key; +- (FQueryParams *)startAt:(id)indexValue; +- (FQueryParams *)endAt:(id)indexValue childKey:(NSString *)key; +- (FQueryParams *)endAt:(id)indexValue; + +- (FQueryParams *)orderBy:(id)index; + ++ (FQueryParams *)defaultInstance; ++ (FQueryParams *)fromQueryObject:(NSDictionary *)dict; + +- (BOOL)hasStart; +- (BOOL)hasEnd; + +- (NSDictionary *)wireProtocolParams; +- (BOOL)isViewFromLeft; +- (id)nodeFilter; +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FQueryParams.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FQueryParams.m new file mode 100644 index 0000000..c19cfba --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FQueryParams.m @@ -0,0 +1,393 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FQueryParams.h" +#import "FConstants.h" +#import "FIndex.h" +#import "FIndexedFilter.h" +#import "FLimitedFilter.h" +#import "FNode.h" +#import "FNodeFilter.h" +#import "FPriorityIndex.h" +#import "FRangedFilter.h" +#import "FSnapshotUtilities.h" +#import "FUtilities.h" +#import "FValidation.h" + +@interface FQueryParams () + +@property(nonatomic, readwrite) BOOL limitSet; +@property(nonatomic, readwrite) NSInteger limit; + +@property(nonatomic, strong, readwrite) NSString *viewFrom; +/** + * indexStartValue is anything you can store as a priority / value. + */ +@property(nonatomic, strong, readwrite) id indexStartValue; +@property(nonatomic, strong, readwrite) NSString *indexStartKey; +/** + * indexStartValue is anything you can store as a priority / value. + */ +@property(nonatomic, strong, readwrite) id indexEndValue; +@property(nonatomic, strong, readwrite) NSString *indexEndKey; + +@property(nonatomic, strong, readwrite) id index; + +@end + +@implementation FQueryParams + ++ (FQueryParams *)defaultInstance { + static FQueryParams *defaultParams = nil; + static dispatch_once_t defaultParamsToken; + dispatch_once(&defaultParamsToken, ^{ + defaultParams = [[FQueryParams alloc] init]; + }); + return defaultParams; +} + +- (id)init { + self = [super init]; + if (self) { + self->_limitSet = NO; + self->_limit = 0; + + self->_viewFrom = nil; + self->_indexStartValue = nil; + self->_indexStartKey = nil; + self->_indexEndValue = nil; + self->_indexEndKey = nil; + + self->_index = [FPriorityIndex priorityIndex]; + } + return self; +} + +/** + * Only valid if hasStart is true + */ +- (id)indexStartValue { + NSAssert([self hasStart], @"Only valid if start has been set"); + return _indexStartValue; +} + +/** + * Only valid if hasStart is true. + * @return The starting key name for the range defined by these query parameters + */ +- (NSString *)indexStartKey { + NSAssert([self hasStart], @"Only valid if start has been set"); + if (_indexStartKey == nil) { + return [FUtilities minName]; + } else { + return _indexStartKey; + } +} + +/** + * Only valid if hasEnd is true. + */ +- (id)indexEndValue { + NSAssert([self hasEnd], @"Only valid if end has been set"); + return _indexEndValue; +} + +/** + * Only valid if hasEnd is true. + * @return The end key name for the range defined by these query parameters + */ +- (NSString *)indexEndKey { + NSAssert([self hasEnd], @"Only valid if end has been set"); + if (_indexEndKey == nil) { + return [FUtilities maxName]; + } else { + return _indexEndKey; + } +} + +/** + * @return true if a limit has been set and has been explicitly anchored + */ +- (BOOL)hasAnchoredLimit { + return self.limitSet && self.viewFrom != nil; +} + +/** + * Only valid to call if limitSet returns true + */ +- (NSInteger)limit { + NSAssert(self.limitSet, @"Only valid if limit has been set"); + return _limit; +} + +- (BOOL)hasStart { + return self->_indexStartValue != nil; +} + +- (BOOL)hasEnd { + return self->_indexEndValue != nil; +} + +- (id)copyWithZone:(NSZone *)zone { + // Immutable + return self; +} + +- (id)mutableCopy { + FQueryParams *other = [[[self class] alloc] init]; + // Maybe need to do extra copying here + other->_limitSet = _limitSet; + other->_limit = _limit; + other->_indexStartValue = _indexStartValue; + other->_indexStartKey = _indexStartKey; + other->_indexEndValue = _indexEndValue; + other->_indexEndKey = _indexEndKey; + other->_viewFrom = _viewFrom; + other->_index = _index; + return other; +} + +- (FQueryParams *)limitTo:(NSInteger)newLimit { + FQueryParams *newParams = [self mutableCopy]; + newParams->_limitSet = YES; + newParams->_limit = newLimit; + newParams->_viewFrom = nil; + return newParams; +} + +- (FQueryParams *)limitToFirst:(NSInteger)newLimit { + FQueryParams *newParams = [self mutableCopy]; + newParams->_limitSet = YES; + newParams->_limit = newLimit; + newParams->_viewFrom = kFQPViewFromLeft; + return newParams; +} + +- (FQueryParams *)limitToLast:(NSInteger)newLimit { + FQueryParams *newParams = [self mutableCopy]; + newParams->_limitSet = YES; + newParams->_limit = newLimit; + newParams->_viewFrom = kFQPViewFromRight; + return newParams; +} + +- (FQueryParams *)startAt:(id)indexValue childKey:(NSString *)key { + NSAssert([indexValue isLeafNode] || [indexValue isEmpty], nil); + FQueryParams *newParams = [self mutableCopy]; + newParams->_indexStartValue = indexValue; + newParams->_indexStartKey = key; + return newParams; +} + +- (FQueryParams *)startAt:(id)indexValue { + return [self startAt:indexValue childKey:nil]; +} + +- (FQueryParams *)endAt:(id)indexValue childKey:(NSString *)key { + NSAssert([indexValue isLeafNode] || [indexValue isEmpty], nil); + FQueryParams *newParams = [self mutableCopy]; + newParams->_indexEndValue = indexValue; + newParams->_indexEndKey = key; + return newParams; +} + +- (FQueryParams *)endAt:(id)indexValue { + return [self endAt:indexValue childKey:nil]; +} + +- (FQueryParams *)orderBy:(id)newIndex { + FQueryParams *newParams = [self mutableCopy]; + newParams->_index = newIndex; + return newParams; +} + +- (NSDictionary *)wireProtocolParams { + NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; + if ([self hasStart]) { + [dict setObject:[self.indexStartValue valForExport:YES] + forKey:kFQPIndexStartValue]; + + // Don't use property as it will be [MIN-NAME] + if (self->_indexStartKey != nil) { + [dict setObject:self->_indexStartKey forKey:kFQPIndexStartName]; + } + } + + if ([self hasEnd]) { + [dict setObject:[self.indexEndValue valForExport:YES] + forKey:kFQPIndexEndValue]; + + // Don't use property as it will be [MAX-NAME] + if (self->_indexEndKey != nil) { + [dict setObject:self->_indexEndKey forKey:kFQPIndexEndName]; + } + } + + if (self.limitSet) { + [dict setObject:[NSNumber numberWithInteger:self.limit] + forKey:kFQPLimit]; + NSString *vf = self.viewFrom; + if (vf == nil) { + // limit() rather than limitToFirst or limitToLast was called. + // This means that only one of startSet or endSet is true. Use them + // to calculate which side of the view to anchor to. If neither is + // set, Anchor to end + if ([self hasStart]) { + vf = kFQPViewFromLeft; + } else { + vf = kFQPViewFromRight; + } + } + [dict setObject:vf forKey:kFQPViewFrom]; + } + + // For now, priority index is the default, so we only specify if it's some + // other index. + if (![self.index isEqual:[FPriorityIndex priorityIndex]]) { + [dict setObject:[self.index queryDefinition] forKey:kFQPIndex]; + } + + return dict; +} + ++ (FQueryParams *)fromQueryObject:(NSDictionary *)dict { + if (dict.count == 0) { + return [FQueryParams defaultInstance]; + } + + FQueryParams *params = [[FQueryParams alloc] init]; + if (dict[kFQPLimit] != nil) { + params->_limitSet = YES; + params->_limit = [dict[kFQPLimit] integerValue]; + } + + if (dict[kFQPIndexStartValue] != nil) { + params->_indexStartValue = + [FSnapshotUtilities nodeFrom:dict[kFQPIndexStartValue]]; + if (dict[kFQPIndexStartName] != nil) { + params->_indexStartKey = dict[kFQPIndexStartName]; + } + } + + if (dict[kFQPIndexEndValue] != nil) { + params->_indexEndValue = + [FSnapshotUtilities nodeFrom:dict[kFQPIndexEndValue]]; + if (dict[kFQPIndexEndName] != nil) { + params->_indexEndKey = dict[kFQPIndexEndName]; + } + } + + if (dict[kFQPViewFrom] != nil) { + NSString *viewFrom = dict[kFQPViewFrom]; + if (![viewFrom isEqualToString:kFQPViewFromLeft] && + ![viewFrom isEqualToString:kFQPViewFromRight]) { + [NSException raise:NSInvalidArgumentException + format:@"Unknown view from paramter: %@", viewFrom]; + } + params->_viewFrom = viewFrom; + } + + NSString *index = dict[kFQPIndex]; + if (index != nil) { + params->_index = [FIndex indexFromQueryDefinition:index]; + } + + return params; +} + +- (BOOL)isViewFromLeft { + if (self.viewFrom != nil) { + // Not null, we can just check + return [self.viewFrom isEqualToString:kFQPViewFromLeft]; + } else { + // If start is set, it's view from left. Otherwise not. + return self.hasStart; + } +} + +- (id)nodeFilter { + if (self.loadsAllData) { + return [[FIndexedFilter alloc] initWithIndex:self.index]; + } else if (self.limitSet) { + return [[FLimitedFilter alloc] initWithQueryParams:self]; + } else { + return [[FRangedFilter alloc] initWithQueryParams:self]; + } +} + +- (BOOL)isValid { + return !(self.hasStart && self.hasEnd && self.limitSet && + !self.hasAnchoredLimit); +} + +- (BOOL)loadsAllData { + return !(self.hasStart || self.hasEnd || self.limitSet); +} + +- (BOOL)isDefault { + return [self loadsAllData] && + [self.index isEqual:[FPriorityIndex priorityIndex]]; +} + +- (NSString *)description { + return [[self wireProtocolParams] description]; +} + +- (BOOL)isEqual:(id)obj { + if (self == obj) { + return YES; + } + if (![obj isKindOfClass:[self class]]) { + return NO; + } + FQueryParams *other = (FQueryParams *)obj; + if (self->_limitSet != other->_limitSet) + return NO; + if (self->_limit != other->_limit) + return NO; + if ((self->_index != other->_index) && ! + [self->_index isEqual:other->_index]) + return NO; + if ((self->_indexStartKey != other->_indexStartKey) && + ![self->_indexStartKey isEqualToString:other->_indexStartKey]) + return NO; + if ((self->_indexStartValue != other->_indexStartValue) && + ![self->_indexStartValue isEqual:other->_indexStartValue]) + return NO; + if ((self->_indexEndKey != other->_indexEndKey) && + ![self->_indexEndKey isEqualToString:other->_indexEndKey]) + return NO; + if ((self->_indexEndValue != other->_indexEndValue) && + ![self->_indexEndValue isEqual:other->_indexEndValue]) + return NO; + if ([self isViewFromLeft] != [other isViewFromLeft]) + return NO; + + return YES; +} + +- (NSUInteger)hash { + NSUInteger result = _limitSet ? _limit : 0; + result = 31 * result + ([self isViewFromLeft] ? 1231 : 1237); + result = 31 * result + [_indexStartKey hash]; + result = 31 * result + [_indexStartValue hash]; + result = 31 * result + [_indexEndKey hash]; + result = 31 * result + [_indexEndValue hash]; + result = 31 * result + [_index hash]; + return result; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FQuerySpec.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FQuerySpec.h new file mode 100644 index 0000000..2eece87 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FQuerySpec.h @@ -0,0 +1,36 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIndex.h" +#import "FPath.h" +#import "FQueryParams.h" + +@interface FQuerySpec : NSObject + +@property(nonatomic, strong, readonly) FPath *path; +@property(nonatomic, strong, readonly) FQueryParams *params; + +- (id)initWithPath:(FPath *)path params:(FQueryParams *)params; + ++ (FQuerySpec *)defaultQueryAtPath:(FPath *)path; + +- (id)index; +- (BOOL)isDefault; +- (BOOL)loadsAllData; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FQuerySpec.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FQuerySpec.m new file mode 100644 index 0000000..c408bf8 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FQuerySpec.m @@ -0,0 +1,86 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FQuerySpec.h" + +@interface FQuerySpec () + +@property(nonatomic, strong, readwrite) FPath *path; +@property(nonatomic, strong, readwrite) FQueryParams *params; + +@end + +@implementation FQuerySpec + +- (id)initWithPath:(FPath *)path params:(FQueryParams *)params { + self = [super init]; + if (self != nil) { + self->_path = path; + self->_params = params; + } + return self; +} + ++ (FQuerySpec *)defaultQueryAtPath:(FPath *)path { + return [[FQuerySpec alloc] initWithPath:path + params:[FQueryParams defaultInstance]]; +} + +- (id)copyWithZone:(NSZone *)zone { + // Immutable + return self; +} + +- (id)index { + return self.params.index; +} + +- (BOOL)isDefault { + return self.params.isDefault; +} + +- (BOOL)loadsAllData { + return self.params.loadsAllData; +} + +- (BOOL)isEqual:(id)object { + if (self == object) { + return YES; + } + + if (![object isKindOfClass:[FQuerySpec class]]) { + return NO; + } + + FQuerySpec *other = (FQuerySpec *)object; + + if (![self.path isEqual:other.path]) { + return NO; + } + + return [self.params isEqual:other.params]; +} + +- (NSUInteger)hash { + return self.path.hash * 31 + self.params.hash; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"FQuerySpec (path: %@, params: %@)", + self.path, self.params]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FRangeMerge.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FRangeMerge.h new file mode 100644 index 0000000..5f7938a --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FRangeMerge.h @@ -0,0 +1,39 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FNode.h" + +/** + * Applies a merge of a snap for a given interval of paths. + * Each leaf in the current node which the relative path lies *after* (the + * optional) start and lies *before or at* (the optional) end will be deleted. + * Each leaf in snap that lies in the interval will be added to the resulting + * node. Nodes outside of the range are ignored. nil for start and end are + * sentinel values that represent -infinity and +infinity respectively (aka + * includes any path). Priorities of children nodes are treated as leaf children + * of that node. + */ +@interface FRangeMerge : NSObject + +- (instancetype)initWithStart:(FPath *)start + end:(FPath *)end + updates:(id)updates; + +- (id)applyToNode:(id)node; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FRangeMerge.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FRangeMerge.m new file mode 100644 index 0000000..3dc1576 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FRangeMerge.m @@ -0,0 +1,134 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FRangeMerge.h" + +#import "FEmptyNode.h" + +@interface FRangeMerge () + +@property(nonatomic, strong) FPath *optExclusiveStart; +@property(nonatomic, strong) FPath *optInclusiveEnd; +@property(nonatomic, strong) id updates; + +@end + +@implementation FRangeMerge + +- (instancetype)initWithStart:(FPath *)start + end:(FPath *)end + updates:(id)updates { + self = [super init]; + if (self != nil) { + self->_optExclusiveStart = start; + self->_optInclusiveEnd = end; + self->_updates = updates; + } + return self; +} + +- (id)applyToNode:(id)node { + return [self updateRangeInNode:[FPath empty] + node:node + updates:self.updates]; +} + +- (id)updateRangeInNode:(FPath *)currentPath + node:(id)node + updates:(id)updates { + NSComparisonResult startComparison = + (self.optExclusiveStart == nil) + ? NSOrderedDescending + : [currentPath compare:self.optExclusiveStart]; + NSComparisonResult endComparison = + (self.optInclusiveEnd == nil) + ? NSOrderedAscending + : [currentPath compare:self.optInclusiveEnd]; + BOOL startInNode = self.optExclusiveStart != nil && + [currentPath contains:self.optExclusiveStart]; + BOOL endInNode = self.optInclusiveEnd != nil && + [currentPath contains:self.optInclusiveEnd]; + if (startComparison == NSOrderedDescending && + endComparison == NSOrderedAscending && !endInNode) { + // child is completly contained + return updates; + } else if (startComparison == NSOrderedDescending && endInNode && + [updates isLeafNode]) { + return updates; + } else if (startComparison == NSOrderedDescending && + endComparison == NSOrderedSame) { + NSAssert(endInNode, @"End not in node"); + NSAssert(![updates isLeafNode], @"Found leaf node update, this case " + @"should have been handled above."); + if ([node isLeafNode]) { + // Update node was not a leaf node, so we can delete it + return [FEmptyNode emptyNode]; + } else { + // Unaffected by range, ignore + return node; + } + } else if (startInNode || endInNode) { + // There is a partial update we need to do, so collect all relevant + // children + NSMutableSet *allChildren = [NSMutableSet set]; + [node enumerateChildrenUsingBlock:^(NSString *key, id node, + BOOL *stop) { + [allChildren addObject:key]; + }]; + [updates enumerateChildrenUsingBlock:^(NSString *key, id node, + BOOL *stop) { + [allChildren addObject:key]; + }]; + + __block id newNode = node; + void (^action)(id, BOOL *) = ^void(NSString *key, BOOL *stop) { + id currentChild = [node getImmediateChild:key]; + id updatedChild = + [self updateRangeInNode:[currentPath childFromString:key] + node:currentChild + updates:[updates getImmediateChild:key]]; + // Only need to update if the node changed + if (updatedChild != currentChild) { + newNode = [newNode updateImmediateChild:key + withNewChild:updatedChild]; + } + }; + + [allChildren enumerateObjectsUsingBlock:action]; + + // Add priority last, so the node is not empty when applying + if (!updates.getPriority.isEmpty || !node.getPriority.isEmpty) { + BOOL stop = NO; + action(@".priority", &stop); + } + return newNode; + } else { + // Unaffected by this range + NSAssert(endComparison == NSOrderedDescending || + startComparison <= NSOrderedSame, + @"Invalid range for update"); + return node; + } +} + +- (NSString *)description { + return [NSString stringWithFormat:@"RangeMerge (optExclusiveStart = %@, " + @"optExclusiveEng = %@, updates = %@)", + self.optExclusiveStart, + self.optInclusiveEnd, self.updates]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FRepo.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FRepo.h new file mode 100644 index 0000000..4e3899a --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FRepo.h @@ -0,0 +1,93 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRDataEventType.h" +#import "FPersistentConnection.h" +#import "FRepoInfo.h" +#import "FTupleUserCallback.h" +#import + +@class FQuerySpec; +@class FPersistence; +@class FAuthenticationManager; +@class FIRDatabaseConfig; +@protocol FEventRegistration; +@class FCompoundWrite; +@protocol FClock; +@class FIRDatabase; + +@interface FRepo : NSObject + +@property(nonatomic, strong) FIRDatabaseConfig *config; + +- (id)initWithRepoInfo:(FRepoInfo *)info + config:(FIRDatabaseConfig *)config + database:(FIRDatabase *)database; + +- (void)set:(FPath *)path + withNode:(id)node + withCallback:(fbt_void_nserror_ref)onComplete; +- (void)update:(FPath *)path + withNodes:(FCompoundWrite *)compoundWrite + withCallback:(fbt_void_nserror_ref)callback; +- (void)purgeOutstandingWrites; + +- (void)addEventRegistration:(id)eventRegistration + forQuery:(FQuerySpec *)query; +- (void)removeEventRegistration:(id)eventRegistration + forQuery:(FQuerySpec *)query; +- (void)keepQuery:(FQuerySpec *)query synced:(BOOL)synced; + +- (NSString *)name; +- (NSTimeInterval)serverTime; + +- (void)onDataUpdate:(FPersistentConnection *)fpconnection + forPath:(NSString *)pathString + message:(id)message + isMerge:(BOOL)isMerge + tagId:(NSNumber *)tagId; +- (void)onConnect:(FPersistentConnection *)fpconnection; +- (void)onDisconnect:(FPersistentConnection *)fpconnection; + +// Disconnect methods +- (void)onDisconnectCancel:(FPath *)path + withCallback:(fbt_void_nserror_ref)callback; +- (void)onDisconnectSet:(FPath *)path + withNode:(id)node + withCallback:(fbt_void_nserror_ref)callback; +- (void)onDisconnectUpdate:(FPath *)path + withNodes:(FCompoundWrite *)compoundWrite + withCallback:(fbt_void_nserror_ref)callback; + +// Connection Management. +- (void)interrupt; +- (void)resume; + +// Transactions +- (void)startTransactionOnPath:(FPath *)path + update:(fbt_transactionresult_mutabledata)update + onComplete:(fbt_void_nserror_bool_datasnapshot)onComplete + withLocalEvents:(BOOL)applyLocally; + +// Testing methods +- (NSDictionary *)dumpListens; +- (void)dispose; +- (void)setHijackHash:(BOOL)hijack; + +@property(nonatomic, strong, readonly) FAuthenticationManager *auth; +@property(nonatomic, strong, readonly) FIRDatabase *database; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FRepo.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FRepo.m new file mode 100644 index 0000000..5d894bf --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FRepo.m @@ -0,0 +1,1450 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FAtomicNumber.h" +#import "FCachePolicy.h" +#import "FClock.h" +#import "FConstants.h" +#import "FEmptyNode.h" +#import "FEventRaiser.h" +#import "FEventRegistration.h" +#import "FIRDataSnapshot.h" +#import "FIRDataSnapshot_Private.h" +#import "FIRDatabaseConfig_Private.h" +#import "FIRDatabaseQuery_Private.h" +#import "FIRDatabase_Private.h" +#import "FIRMutableData.h" +#import "FIRMutableData_Private.h" +#import "FIRTransactionResult.h" +#import "FIRTransactionResult_Private.h" +#import "FLevelDBStorageEngine.h" +#import "FListenProvider.h" +#import "FPersistenceManager.h" +#import "FQuerySpec.h" +#import "FRepo.h" +#import "FRepoManager.h" +#import "FRepo_Private.h" +#import "FServerValues.h" +#import "FSnapshotHolder.h" +#import "FSnapshotUtilities.h" +#import "FSyncTree.h" +#import "FTree.h" +#import "FTupleNodePath.h" +#import "FTupleSetIdPath.h" +#import "FTupleTransaction.h" +#import "FValueEventRegistration.h" +#import "FWriteRecord.h" +#import +#import + +#if TARGET_OS_IOS || TARGET_OS_TV +#import +#endif + +@interface FRepo () + +@property(nonatomic, strong) FOffsetClock *serverClock; +@property(nonatomic, strong) FPersistenceManager *persistenceManager; +@property(nonatomic, strong) FIRDatabase *database; +@property(nonatomic, strong, readwrite) FAuthenticationManager *auth; +@property(nonatomic, strong) FSyncTree *infoSyncTree; +@property(nonatomic) NSInteger writeIdCounter; +@property(nonatomic) BOOL hijackHash; +@property(nonatomic, strong) FTree *transactionQueueTree; +@property(nonatomic) BOOL loggedTransactionPersistenceWarning; + +/** + * Test only. For load testing the server. + */ +@property(nonatomic, strong) id (^interceptServerDataCallback) + (NSString *pathString, id data); +@end + +@implementation FRepo + +- (id)initWithRepoInfo:(FRepoInfo *)info + config:(FIRDatabaseConfig *)config + database:(FIRDatabase *)database { + self = [super init]; + if (self) { + self.repoInfo = info; + self.config = config; + self.database = database; + + // Access can occur outside of shared queue, so the clock needs to be + // initialized here + self.serverClock = + [[FOffsetClock alloc] initWithClock:[FSystemClock clock] offset:0]; + + self.connection = [[FPersistentConnection alloc] + initWithRepoInfo:self.repoInfo + dispatchQueue:[FIRDatabaseQuery sharedQueue] + config:self.config]; + + // Needs to be called before authentication manager is instantiated + self.eventRaiser = + [[FEventRaiser alloc] initWithQueue:self.config.callbackQueue]; + + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + [self deferredInit]; + }); + } + return self; +} + +- (void)deferredInit { + // TODO: cleanup on dealloc + __weak FRepo *weakSelf = self; + [self.config.authTokenProvider listenForTokenChanges:^(NSString *token) { + [weakSelf.connection refreshAuthToken:token]; + }]; + + // Open connection now so that by the time we are connected the deferred + // init has run This relies on the fact that all callbacks run on repos + // queue + self.connection.delegate = self; + [self.connection open]; + + self.dataUpdateCount = 0; + self.rangeMergeUpdateCount = 0; + self.interceptServerDataCallback = nil; + + if (self.config.persistenceEnabled) { + NSString *repoHashString = + [NSString stringWithFormat:@"%@_%@", self.repoInfo.host, + self.repoInfo.namespace]; + NSString *persistencePrefix = + [NSString stringWithFormat:@"%@/%@", self.config.sessionIdentifier, + repoHashString]; + + id cachePolicy = [[FLRUCachePolicy alloc] + initWithMaxSize:self.config.persistenceCacheSizeBytes]; + + id engine; + if (self.config.forceStorageEngine != nil) { + engine = self.config.forceStorageEngine; + } else { + FLevelDBStorageEngine *levelDBEngine = + [[FLevelDBStorageEngine alloc] initWithPath:persistencePrefix]; + // We need the repo info to run the legacy migration. Future + // migrations will be managed by the database itself Remove this + // once we are confident that no-one is using legacy migration + // anymore... + [levelDBEngine runLegacyMigration:self.repoInfo]; + engine = levelDBEngine; + } + + self.persistenceManager = + [[FPersistenceManager alloc] initWithStorageEngine:engine + cachePolicy:cachePolicy]; + } else { + self.persistenceManager = nil; + } + + [self initTransactions]; + + // A list of data pieces and paths to be set when this client disconnects + self.onDisconnect = [[FSparseSnapshotTree alloc] init]; + self.infoData = [[FSnapshotHolder alloc] init]; + + FListenProvider *infoListenProvider = [[FListenProvider alloc] init]; + infoListenProvider.startListening = + ^(FQuerySpec *query, NSNumber *tagId, id hash, + fbt_nsarray_nsstring onComplete) { + NSArray *infoEvents = @[]; + FRepo *strongSelf = weakSelf; + id node = [strongSelf.infoData getNode:query.path]; + // This is possibly a hack, but we have different semantics for .info + // endpoints. We don't raise null events on initial data... + if (![node isEmpty]) { + infoEvents = + [strongSelf.infoSyncTree applyServerOverwriteAtPath:query.path + newData:node]; + [strongSelf.eventRaiser raiseCallback:^{ + onComplete(kFWPResponseForActionStatusOk); + }]; + } + return infoEvents; + }; + infoListenProvider.stopListening = ^(FQuerySpec *query, NSNumber *tagId) { + }; + self.infoSyncTree = + [[FSyncTree alloc] initWithListenProvider:infoListenProvider]; + + FListenProvider *serverListenProvider = [[FListenProvider alloc] init]; + serverListenProvider.startListening = + ^(FQuerySpec *query, NSNumber *tagId, id hash, + fbt_nsarray_nsstring onComplete) { + [weakSelf.connection listen:query + tagId:tagId + hash:hash + onComplete:^(NSString *status) { + NSArray *events = onComplete(status); + [weakSelf.eventRaiser raiseEvents:events]; + }]; + // No synchronous events for network-backed sync trees + return @[]; + }; + serverListenProvider.stopListening = ^(FQuerySpec *query, NSNumber *tag) { + [weakSelf.connection unlisten:query tagId:tag]; + }; + self.serverSyncTree = + [[FSyncTree alloc] initWithPersistenceManager:self.persistenceManager + listenProvider:serverListenProvider]; + + [self restoreWrites]; + + [self updateInfo:kDotInfoConnected withValue:@NO]; + + [self setupNotifications]; +} + +- (void)restoreWrites { + NSArray *writes = self.persistenceManager.userWrites; + + NSDictionary *serverValues = + [FServerValues generateServerValues:self.serverClock]; + __block NSInteger lastWriteId = NSIntegerMin; + [writes enumerateObjectsUsingBlock:^(FWriteRecord *write, NSUInteger idx, + BOOL *stop) { + NSInteger writeId = write.writeId; + fbt_void_nsstring_nsstring callback = + ^(NSString *status, NSString *errorReason) { + [self warnIfWriteFailedAtPath:write.path + status:status + message:@"Persisted write"]; + [self ackWrite:writeId + rerunTransactionsAtPath:write.path + status:status]; + }; + if (lastWriteId >= writeId) { + [NSException raise:NSInternalInconsistencyException + format:@"Restored writes were not in order!"]; + } + lastWriteId = writeId; + self.writeIdCounter = writeId + 1; + if ([write isOverwrite]) { + FFLog(@"I-RDB038001", @"Restoring overwrite with id %ld", + (long)write.writeId); + [self.connection putData:[write.overwrite valForExport:YES] + forPath:[write.path toString] + withHash:nil + withCallback:callback]; + id resolved = + [FServerValues resolveDeferredValueSnapshot:write.overwrite + withServerValues:serverValues]; + [self.serverSyncTree applyUserOverwriteAtPath:write.path + newData:resolved + writeId:writeId + isVisible:YES]; + } else { + FFLog(@"I-RDB038002", @"Restoring merge with id %ld", + (long)write.writeId); + [self.connection mergeData:[write.merge valForExport:YES] + forPath:[write.path toString] + withCallback:callback]; + FCompoundWrite *resolved = + [FServerValues resolveDeferredValueCompoundWrite:write.merge + withServerValues:serverValues]; + [self.serverSyncTree applyUserMergeAtPath:write.path + changedChildren:resolved + writeId:writeId]; + } + }]; +} + +- (NSString *)name { + return self.repoInfo.namespace; +} + +- (NSString *)description { + return [self.repoInfo description]; +} + +- (void)interrupt { + [self.connection interruptForReason:kFInterruptReasonRepoInterrupt]; +} + +- (void)resume { + [self.connection resumeForReason:kFInterruptReasonRepoInterrupt]; +} + +// NOTE: Typically if you're calling this, you should be in an @autoreleasepool +// block to make sure that ARC kicks in and cleans up things no longer +// referenced (i.e. pendingPutsDB). +- (void)dispose { + [self.connection interruptForReason:kFInterruptReasonRepoInterrupt]; + + // We need to nil out any references to LevelDB, to make sure the + // LevelDB exclusive locks are released. + [self.persistenceManager close]; +} + +- (NSInteger)nextWriteId { + return self->_writeIdCounter++; +} + +- (NSTimeInterval)serverTime { + return [self.serverClock currentTime]; +} + +- (void)set:(FPath *)path + withNode:(id)node + withCallback:(fbt_void_nserror_ref)onComplete { + id value = [node valForExport:YES]; + FFLog(@"I-RDB038003", @"Setting: %@ with %@ pri: %@", [path toString], + [value description], [[node getPriority] val]); + + // TODO: Optimize this behavior to either (a) store flag to skip resolving + // where possible and / or (b) store unresolved paths on JSON parse + NSDictionary *serverValues = + [FServerValues generateServerValues:self.serverClock]; + id newNode = + [FServerValues resolveDeferredValueSnapshot:node + withServerValues:serverValues]; + + NSInteger writeId = [self nextWriteId]; + [self.persistenceManager saveUserOverwrite:node + atPath:path + writeId:writeId]; + NSArray *events = [self.serverSyncTree applyUserOverwriteAtPath:path + newData:newNode + writeId:writeId + isVisible:YES]; + [self.eventRaiser raiseEvents:events]; + + [self.connection putData:value + forPath:[path toString] + withHash:nil + withCallback:^(NSString *status, NSString *errorReason) { + [self warnIfWriteFailedAtPath:path + status:status + message:@"setValue: or removeValue:"]; + [self ackWrite:writeId + rerunTransactionsAtPath:path + status:status]; + [self callOnComplete:onComplete + withStatus:status + errorReason:errorReason + andPath:path]; + }]; + + FPath *affectedPath = [self abortTransactionsAtPath:path + error:kFTransactionSet]; + [self rerunTransactionsForPath:affectedPath]; +} + +- (void)update:(FPath *)path + withNodes:(FCompoundWrite *)nodes + withCallback:(fbt_void_nserror_ref)callback { + NSDictionary *values = [nodes valForExport:YES]; + + FFLog(@"I-RDB038004", @"Updating: %@ with %@", [path toString], + [values description]); + NSDictionary *serverValues = + [FServerValues generateServerValues:self.serverClock]; + FCompoundWrite *resolved = + [FServerValues resolveDeferredValueCompoundWrite:nodes + withServerValues:serverValues]; + + if (!resolved.isEmpty) { + NSInteger writeId = [self nextWriteId]; + [self.persistenceManager saveUserMerge:nodes + atPath:path + writeId:writeId]; + NSArray *events = [self.serverSyncTree applyUserMergeAtPath:path + changedChildren:resolved + writeId:writeId]; + [self.eventRaiser raiseEvents:events]; + + [self.connection mergeData:values + forPath:[path description] + withCallback:^(NSString *status, NSString *errorReason) { + [self warnIfWriteFailedAtPath:path + status:status + message:@"updateChildValues:"]; + [self ackWrite:writeId + rerunTransactionsAtPath:path + status:status]; + [self callOnComplete:callback + withStatus:status + errorReason:errorReason + andPath:path]; + }]; + + [nodes enumerateWrites:^(FPath *childPath, id node, BOOL *stop) { + FPath *pathFromRoot = [path child:childPath]; + FFLog(@"I-RDB038005", @"Cancelling transactions at path: %@", + pathFromRoot); + FPath *affectedPath = [self abortTransactionsAtPath:pathFromRoot + error:kFTransactionSet]; + [self rerunTransactionsForPath:affectedPath]; + }]; + } else { + FFLog(@"I-RDB038006", @"update called with empty data. Doing nothing"); + // Do nothing, just call the callback + [self callOnComplete:callback + withStatus:@"ok" + errorReason:nil + andPath:path]; + } +} + +- (void)onDisconnectCancel:(FPath *)path + withCallback:(fbt_void_nserror_ref)callback { + [self.connection + onDisconnectCancelPath:path + withCallback:^(NSString *status, NSString *errorReason) { + BOOL success = + [status isEqualToString:kFWPResponseForActionStatusOk]; + if (success) { + [self.onDisconnect forgetPath:path]; + } else { + FFLog(@"I-RDB038007", + @"cancelDisconnectOperations: at %@ failed: %@", + path, status); + } + + [self callOnComplete:callback + withStatus:status + errorReason:errorReason + andPath:path]; + }]; +} + +- (void)onDisconnectSet:(FPath *)path + withNode:(id)node + withCallback:(fbt_void_nserror_ref)callback { + [self.connection + onDisconnectPutData:[node valForExport:YES] + forPath:path + withCallback:^(NSString *status, NSString *errorReason) { + BOOL success = + [status isEqualToString:kFWPResponseForActionStatusOk]; + if (success) { + [self.onDisconnect rememberData:node onPath:path]; + } else { + FFWarn(@"I-RDB038008", + @"onDisconnectSetValue: or " + @"onDisconnectRemoveValue: at %@ failed: %@", + path, status); + } + + [self callOnComplete:callback + withStatus:status + errorReason:errorReason + andPath:path]; + }]; +} + +- (void)onDisconnectUpdate:(FPath *)path + withNodes:(FCompoundWrite *)nodes + withCallback:(fbt_void_nserror_ref)callback { + if (!nodes.isEmpty) { + NSDictionary *values = [nodes valForExport:YES]; + + [self.connection + onDisconnectMergeData:values + forPath:path + withCallback:^(NSString *status, NSString *errorReason) { + BOOL success = [status + isEqualToString:kFWPResponseForActionStatusOk]; + if (success) { + [nodes enumerateWrites:^(FPath *relativePath, + id nodeUnresolved, + BOOL *stop) { + FPath *childPath = [path child:relativePath]; + [self.onDisconnect rememberData:nodeUnresolved + onPath:childPath]; + }]; + } else { + FFWarn(@"I-RDB038009", + @"onDisconnectUpdateChildValues: at %@ " + @"failed %@", + path, status); + } + + [self callOnComplete:callback + withStatus:status + errorReason:errorReason + andPath:path]; + }]; + } else { + // Do nothing, just call the callback + [self callOnComplete:callback + withStatus:@"ok" + errorReason:nil + andPath:path]; + } +} + +- (void)purgeOutstandingWrites { + FFLog(@"I-RDB038010", @"Purging outstanding writes"); + NSArray *events = [self.serverSyncTree removeAllWrites]; + [self.eventRaiser raiseEvents:events]; + // Abort any transactions + [self abortTransactionsAtPath:[FPath empty] error:kFErrorWriteCanceled]; + // Remove outstanding writes from connection + [self.connection purgeOutstandingWrites]; +} + +- (void)addEventRegistration:(id)eventRegistration + forQuery:(FQuerySpec *)query { + NSArray *events = nil; + if ([[query.path getFront] isEqualToString:kDotInfoPrefix]) { + events = [self.infoSyncTree addEventRegistration:eventRegistration + forQuery:query]; + } else { + events = [self.serverSyncTree addEventRegistration:eventRegistration + forQuery:query]; + } + [self.eventRaiser raiseEvents:events]; +} + +- (void)removeEventRegistration:(id)eventRegistration + forQuery:(FQuerySpec *)query { + // These are guaranteed not to raise events, since we're not passing in a + // cancelError. However we can future-proof a little bit by handling the + // return values anyways. + FFLog(@"I-RDB038011", @"Removing event registration with hande: %lu", + (unsigned long)eventRegistration.handle); + NSArray *events = nil; + if ([[query.path getFront] isEqualToString:kDotInfoPrefix]) { + events = [self.infoSyncTree removeEventRegistration:eventRegistration + forQuery:query + cancelError:nil]; + } else { + events = [self.serverSyncTree removeEventRegistration:eventRegistration + forQuery:query + cancelError:nil]; + } + [self.eventRaiser raiseEvents:events]; +} + +- (void)keepQuery:(FQuerySpec *)query synced:(BOOL)synced { + NSAssert(![[query.path getFront] isEqualToString:kDotInfoPrefix], + @"Can't keep .info tree synced!"); + [self.serverSyncTree keepQuery:query synced:synced]; +} + +- (void)updateInfo:(NSString *)pathString withValue:(id)value { + // hack to make serverTimeOffset available in a threadsafe way. Property is + // marked as atomic + if ([pathString isEqualToString:kDotInfoServerTimeOffset]) { + NSTimeInterval offset = [(NSNumber *)value doubleValue] / 1000.0; + self.serverClock = + [[FOffsetClock alloc] initWithClock:[FSystemClock clock] + offset:offset]; + } + + FPath *path = [[FPath alloc] + initWith:[NSString + stringWithFormat:@"%@/%@", kDotInfoPrefix, pathString]]; + id newNode = [FSnapshotUtilities nodeFrom:value]; + [self.infoData updateSnapshot:path withNewSnapshot:newNode]; + NSArray *events = [self.infoSyncTree applyServerOverwriteAtPath:path + newData:newNode]; + [self.eventRaiser raiseEvents:events]; +} + +- (void)callOnComplete:(fbt_void_nserror_ref)onComplete + withStatus:(NSString *)status + errorReason:(NSString *)errorReason + andPath:(FPath *)path { + if (onComplete) { + FIRDatabaseReference *ref = + [[FIRDatabaseReference alloc] initWithRepo:self path:path]; + BOOL statusOk = [status isEqualToString:kFWPResponseForActionStatusOk]; + NSError *err = nil; + if (!statusOk) { + err = [FUtilities errorForStatus:status andReason:errorReason]; + } + [self.eventRaiser raiseCallback:^{ + onComplete(err, ref); + }]; + } +} + +- (void)ackWrite:(NSInteger)writeId + rerunTransactionsAtPath:(FPath *)path + status:(NSString *)status { + if ([status isEqualToString:kFErrorWriteCanceled]) { + // This write was already removed, we just need to ignore it... + } else { + BOOL success = [status isEqualToString:kFWPResponseForActionStatusOk]; + NSArray *clearEvents = + [self.serverSyncTree ackUserWriteWithWriteId:writeId + revert:!success + persist:YES + clock:self.serverClock]; + if ([clearEvents count] > 0) { + [self rerunTransactionsForPath:path]; + } + [self.eventRaiser raiseEvents:clearEvents]; + } +} + +- (void)warnIfWriteFailedAtPath:(FPath *)path + status:(NSString *)status + message:(NSString *)message { + if (!([status isEqualToString:kFWPResponseForActionStatusOk] || + [status isEqualToString:kFErrorWriteCanceled])) { + FFWarn(@"I-RDB038012", @"%@ at %@ failed: %@", message, path, status); + } +} + +#pragma mark - +#pragma mark FPersistentConnectionDelegate methods + +- (void)onDataUpdate:(FPersistentConnection *)fpconnection + forPath:(NSString *)pathString + message:(id)data + isMerge:(BOOL)isMerge + tagId:(NSNumber *)tagId { + FFLog(@"I-RDB038013", @"onDataUpdateForPath: %@ withMessage: %@", + pathString, data); + + // For testing. + self.dataUpdateCount++; + + FPath *path = [[FPath alloc] initWith:pathString]; + data = self.interceptServerDataCallback + ? self.interceptServerDataCallback(pathString, data) + : data; + NSArray *events = nil; + + if (tagId != nil) { + if (isMerge) { + NSDictionary *message = data; + FCompoundWrite *taggedChildren = + [FCompoundWrite compoundWriteWithValueDictionary:message]; + events = + [self.serverSyncTree applyTaggedQueryMergeAtPath:path + changedChildren:taggedChildren + tagId:tagId]; + } else { + id taggedSnap = [FSnapshotUtilities nodeFrom:data]; + events = + [self.serverSyncTree applyTaggedQueryOverwriteAtPath:path + newData:taggedSnap + tagId:tagId]; + } + } else if (isMerge) { + NSDictionary *message = data; + FCompoundWrite *changedChildren = + [FCompoundWrite compoundWriteWithValueDictionary:message]; + events = [self.serverSyncTree applyServerMergeAtPath:path + changedChildren:changedChildren]; + } else { + id snap = [FSnapshotUtilities nodeFrom:data]; + events = [self.serverSyncTree applyServerOverwriteAtPath:path + newData:snap]; + } + + if ([events count] > 0) { + // Since we have a listener outstanding for each transaction, receiving + // any events is a proxy for some change having occurred. + [self rerunTransactionsForPath:path]; + } + + [self.eventRaiser raiseEvents:events]; +} + +- (void)onRangeMerge:(NSArray *)ranges + forPath:(NSString *)pathString + tagId:(NSNumber *)tag { + FFLog(@"I-RDB038014", @"onRangeMerge: %@ => %@", pathString, ranges); + + // For testing + self.rangeMergeUpdateCount++; + + FPath *path = [[FPath alloc] initWith:pathString]; + NSArray *events; + if (tag != nil) { + events = [self.serverSyncTree applyTaggedServerRangeMergeAtPath:path + updates:ranges + tagId:tag]; + } else { + events = [self.serverSyncTree applyServerRangeMergeAtPath:path + updates:ranges]; + } + if (events.count > 0) { + // Since we have a listener outstanding for each transaction, receiving + // any events is a proxy for some change having occurred. + [self rerunTransactionsForPath:path]; + } + + [self.eventRaiser raiseEvents:events]; +} + +- (void)onConnect:(FPersistentConnection *)fpconnection { + [self updateInfo:kDotInfoConnected withValue:@YES]; +} + +- (void)onDisconnect:(FPersistentConnection *)fpconnection { + [self updateInfo:kDotInfoConnected withValue:@NO]; + [self runOnDisconnectEvents]; +} + +- (void)onServerInfoUpdate:(FPersistentConnection *)fpconnection + updates:(NSDictionary *)updates { + for (NSString *key in updates) { + id val = [updates objectForKey:key]; + [self updateInfo:key withValue:val]; + } +} + +- (void)setupNotifications { + NSString *const *backgroundConstant = (NSString *const *)dlsym( + RTLD_DEFAULT, "UIApplicationDidEnterBackgroundNotification"); + if (backgroundConstant) { + FFLog(@"I-RDB038015", @"Registering for background notification."); + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(didEnterBackground) + name:*backgroundConstant + object:nil]; + } else { + FFLog(@"I-RDB038016", + @"Skipped registering for background notification."); + } +} + +- (void)didEnterBackground { + if (!self.config.persistenceEnabled) + return; + +// Targetted compilation is ONLY for testing. UIKit is weak-linked in actual +// release build. +#if TARGET_OS_IOS || TARGET_OS_TV + // The idea is to wait until any outstanding sets get written to disk. Since + // the sets might still be in our dispatch queue, we wait for the dispatch + // queue to catch up and for persistence to catch up. This may be + // undesirable though. The dispatch queue might just be processing a bunch + // of incoming data or something. We might want to keep track of whether + // there are any unpersisted sets or something. + FFLog(@"I-RDB038017", + @"Entering background. Starting background task to finish work."); + Class uiApplicationClass = NSClassFromString(@"UIApplication"); + assert(uiApplicationClass); // If we are here, we should be on iOS and + // UIApplication should be available. + + UIApplication *application = [uiApplicationClass sharedApplication]; + __block UIBackgroundTaskIdentifier bgTask = + [application beginBackgroundTaskWithExpirationHandler:^{ + [application endBackgroundTask:bgTask]; + }]; + + NSDate *start = [NSDate date]; + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + NSTimeInterval finishTime = [start timeIntervalSinceNow] * -1; + FFLog(@"I-RDB038018", @"Background task completed. Queue time: %f", + finishTime); + [application endBackgroundTask:bgTask]; + }); +#endif +} + +#pragma mark - +#pragma mark Internal methods + +/** + * Applies all the changes stored up in the onDisconnect tree + */ +- (void)runOnDisconnectEvents { + FFLog(@"I-RDB038019", @"Running onDisconnectEvents"); + NSDictionary *serverValues = + [FServerValues generateServerValues:self.serverClock]; + FSparseSnapshotTree *resolvedTree = + [FServerValues resolveDeferredValueTree:self.onDisconnect + withServerValues:serverValues]; + NSMutableArray *events = [[NSMutableArray alloc] init]; + + [resolvedTree + forEachTreeAtPath:[FPath empty] + do:^(FPath *path, id node) { + [events addObjectsFromArray: + [self.serverSyncTree + applyServerOverwriteAtPath:path + newData:node]]; + FPath *affectedPath = + [self abortTransactionsAtPath:path + error:kFTransactionSet]; + [self rerunTransactionsForPath:affectedPath]; + }]; + + self.onDisconnect = [[FSparseSnapshotTree alloc] init]; + [self.eventRaiser raiseEvents:events]; +} + +- (NSDictionary *)dumpListens { + return [self.connection dumpListens]; +} + +#pragma mark - +#pragma mark Transactions + +/** + * Setup the transaction data structures + */ +- (void)initTransactions { + self.transactionQueueTree = [[FTree alloc] init]; + self.hijackHash = NO; + self.loggedTransactionPersistenceWarning = NO; +} + +/** + * Creates a new transaction, add its to the transactions we're tracking, and + * sends it to the server if possible + */ +- (void)startTransactionOnPath:(FPath *)path + update:(fbt_transactionresult_mutabledata)update + onComplete:(fbt_void_nserror_bool_datasnapshot)onComplete + withLocalEvents:(BOOL)applyLocally { + if (self.config.persistenceEnabled && + !self.loggedTransactionPersistenceWarning) { + self.loggedTransactionPersistenceWarning = YES; + FFInfo(@"I-RDB038020", + @"runTransactionBlock: usage detected while persistence is " + @"enabled. Please be aware that transactions " + @"*will not* be persisted across app restarts. " + @"See " + @"https://www.firebase.com/docs/ios/guide/" + @"offline-capabilities.html#section-handling-transactions-" + @"offline for more details."); + } + + FIRDatabaseReference *watchRef = + [[FIRDatabaseReference alloc] initWithRepo:self path:path]; + // make sure we're listening on this node + // Note: we can't do this asynchronously. To preserve event ordering, it has + // to be done in this block. This is ok, this block is guaranteed to be our + // own event loop + NSUInteger handle = [[FUtilities LUIDGenerator] integerValue]; + fbt_void_datasnapshot cb = ^(FIRDataSnapshot *snapshot) { + }; + FValueEventRegistration *registration = + [[FValueEventRegistration alloc] initWithRepo:self + handle:handle + callback:cb + cancelCallback:nil]; + [watchRef.repo addEventRegistration:registration + forQuery:watchRef.querySpec]; + fbt_void_void unwatcher = ^{ + [watchRef removeObserverWithHandle:handle]; + }; + + // Save all the data that represents this transaction + FTupleTransaction *transaction = [[FTupleTransaction alloc] init]; + transaction.path = path; + transaction.update = update; + transaction.onComplete = onComplete; + transaction.status = FTransactionInitializing; + transaction.order = [FUtilities LUIDGenerator]; + transaction.applyLocally = applyLocally; + transaction.retryCount = 0; + transaction.unwatcher = unwatcher; + transaction.currentWriteId = nil; + transaction.currentInputSnapshot = nil; + transaction.currentOutputSnapshotRaw = nil; + transaction.currentOutputSnapshotResolved = nil; + + // Run transaction initially + id currentState = [self latestStateAtPath:path excludeWriteIds:nil]; + transaction.currentInputSnapshot = currentState; + FIRMutableData *mutableCurrent = + [[FIRMutableData alloc] initWithNode:currentState]; + FIRTransactionResult *result = transaction.update(mutableCurrent); + + if (!result.isSuccess) { + // Abort the transaction + transaction.unwatcher(); + transaction.currentOutputSnapshotRaw = nil; + transaction.currentOutputSnapshotResolved = nil; + if (transaction.onComplete) { + FIRDatabaseReference *ref = + [[FIRDatabaseReference alloc] initWithRepo:self + path:transaction.path]; + FIndexedNode *indexedNode = [FIndexedNode + indexedNodeWithNode:transaction.currentInputSnapshot]; + FIRDataSnapshot *snap = + [[FIRDataSnapshot alloc] initWithRef:ref + indexedNode:indexedNode]; + [self.eventRaiser raiseCallback:^{ + transaction.onComplete(nil, NO, snap); + }]; + } + } else { + // Note: different from js. We don't need to validate, FIRMutableData + // does validation. We also don't have to worry about priorities. Just + // mark as run and add to queue. + transaction.status = FTransactionRun; + FTree *queueNode = [self.transactionQueueTree subTree:transaction.path]; + NSMutableArray *nodeQueue = [queueNode getValue]; + if (nodeQueue == nil) { + nodeQueue = [[NSMutableArray alloc] init]; + } + [nodeQueue addObject:transaction]; + [queueNode setValue:nodeQueue]; + + // Update visibleData and raise events + // Note: We intentionally raise events after updating all of our + // transaction state, since the user could start new transactions from + // the event callbacks + NSDictionary *serverValues = + [FServerValues generateServerValues:self.serverClock]; + id newValUnresolved = [result.update nodeValue]; + id newVal = + [FServerValues resolveDeferredValueSnapshot:newValUnresolved + withServerValues:serverValues]; + transaction.currentOutputSnapshotRaw = newValUnresolved; + transaction.currentOutputSnapshotResolved = newVal; + transaction.currentWriteId = + [NSNumber numberWithInteger:[self nextWriteId]]; + + NSArray *events = [self.serverSyncTree + applyUserOverwriteAtPath:path + newData:newVal + writeId:[transaction.currentWriteId integerValue] + isVisible:transaction.applyLocally]; + [self.eventRaiser raiseEvents:events]; + + [self sendAllReadyTransactions]; + } +} + +/** + * @param writeIdsToExclude A specific set to exclude + */ +- (id)latestStateAtPath:(FPath *)path + excludeWriteIds:(NSArray *)writeIdsToExclude { + id latestState = + [self.serverSyncTree calcCompleteEventCacheAtPath:path + excludeWriteIds:writeIdsToExclude]; + return latestState ? latestState : [FEmptyNode emptyNode]; +} + +/** + * Sends any already-run transactions that aren't waiting for outstanding + * transactions to complete. + * + * Externally, call the version with no arguments. + * Internally, calls itself recursively with a particular transactionQueueTree + * node to recurse through the tree + */ +- (void)sendAllReadyTransactions { + FTree *node = self.transactionQueueTree; + + [self pruneCompletedTransactionsBelowNode:node]; + [self sendReadyTransactionsForTree:node]; +} + +- (void)sendReadyTransactionsForTree:(FTree *)node { + NSMutableArray *queue = [node getValue]; + if (queue != nil) { + queue = [self buildTransactionQueueAtNode:node]; + NSAssert([queue count] > 0, @"Sending zero length transaction queue"); + + NSUInteger notRunIndex = [queue + indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) { + return ((FTupleTransaction *)obj).status != FTransactionRun; + }]; + + // If they're all run (and not sent), we can send them. Else, we must + // wait. + if (notRunIndex == NSNotFound) { + [self sendTransactionQueue:queue atPath:node.path]; + } + } else if ([node hasChildren]) { + [node forEachChild:^(FTree *child) { + [self sendReadyTransactionsForTree:child]; + }]; + } +} + +/** + * Given a list of run transactions, send them to the server and then handle the + * result (success or failure). + */ +- (void)sendTransactionQueue:(NSMutableArray *)queue atPath:(FPath *)path { + // Mark transactions as sent and bump the retry count + NSMutableArray *writeIdsToExclude = [[NSMutableArray alloc] init]; + for (FTupleTransaction *transaction in queue) { + [writeIdsToExclude addObject:transaction.currentWriteId]; + } + id latestState = [self latestStateAtPath:path + excludeWriteIds:writeIdsToExclude]; + id snapToSend = latestState; + NSString *latestHash = [latestState dataHash]; + for (FTupleTransaction *transaction in queue) { + NSAssert( + transaction.status == FTransactionRun, + @"[FRepo sendTransactionQueue:] items in queue should all be run."); + FFLog(@"I-RDB038021", @"Transaction at %@ set to SENT", + transaction.path); + transaction.status = FTransactionSent; + transaction.retryCount++; + FPath *relativePath = [FPath relativePathFrom:path to:transaction.path]; + // If we've gotten to this point, the output snapshot must be defined. + snapToSend = + [snapToSend updateChild:relativePath + withNewChild:transaction.currentOutputSnapshotRaw]; + } + + id dataToSend = [snapToSend valForExport:YES]; + NSString *pathToSend = [path description]; + latestHash = self.hijackHash ? @"badhash" : latestHash; + + // Send the put + [self.connection + putData:dataToSend + forPath:pathToSend + withHash:latestHash + withCallback:^(NSString *status, NSString *errorReason) { + FFLog(@"I-RDB038022", @"Transaction put response: %@ : %@", + pathToSend, status); + + NSMutableArray *events = [[NSMutableArray alloc] init]; + if ([status isEqualToString:kFWPResponseForActionStatusOk]) { + // Queue up the callbacks and fire them after cleaning up all of + // our transaction state, since the callback could trigger more + // transactions or sets. + NSMutableArray *callbacks = [[NSMutableArray alloc] init]; + for (FTupleTransaction *transaction in queue) { + transaction.status = FTransactionCompleted; + [events addObjectsFromArray: + [self.serverSyncTree + ackUserWriteWithWriteId: + [transaction.currentWriteId integerValue] + revert:NO + persist:NO + clock:self.serverClock]]; + if (transaction.onComplete) { + // We never unset the output snapshot, and given that this + // transaction is complete, it should be set + id node = + transaction.currentOutputSnapshotResolved; + FIndexedNode *indexedNode = + [FIndexedNode indexedNodeWithNode:node]; + FIRDatabaseReference *ref = [[FIRDatabaseReference alloc] + initWithRepo:self + path:transaction.path]; + FIRDataSnapshot *snapshot = + [[FIRDataSnapshot alloc] initWithRef:ref + indexedNode:indexedNode]; + fbt_void_void cb = ^{ + transaction.onComplete(nil, YES, snapshot); + }; + [callbacks addObject:[cb copy]]; + } + transaction.unwatcher(); + } + + // Now remove the completed transactions. + [self + pruneCompletedTransactionsBelowNode:[self.transactionQueueTree + subTree:path]]; + // There may be pending transactions that we can now send. + [self sendAllReadyTransactions]; + + // Finally, trigger onComplete callbacks + [self.eventRaiser raiseCallbacks:callbacks]; + } else { + // transactions are no longer sent. Update their status + // appropriately. + if ([status + isEqualToString:kFWPResponseForActionStatusDataStale]) { + for (FTupleTransaction *transaction in queue) { + if (transaction.status == FTransactionSentNeedsAbort) { + transaction.status = FTransactionNeedsAbort; + } else { + transaction.status = FTransactionRun; + } + } + } else { + FFWarn(@"I-RDB038023", + @"runTransactionBlock: at %@ failed: %@", path, + status); + for (FTupleTransaction *transaction in queue) { + transaction.status = FTransactionNeedsAbort; + [transaction setAbortStatus:status reason:errorReason]; + } + } + } + + [self rerunTransactionsForPath:path]; + [self.eventRaiser raiseEvents:events]; + }]; +} + +/** + * Finds all transactions dependent on the data at changed Path and reruns them. + * + * Should be called any time cached data changes. + * + * Return the highest path that was affected by rerunning transactions. This is + * the path at which events need to be raised for. + */ +- (FPath *)rerunTransactionsForPath:(FPath *)changedPath { + // For the common case that there are no transactions going on, skip all + // this! + if ([self.transactionQueueTree isEmpty]) { + return changedPath; + } else { + FTree *rootMostTransactionNode = + [self getAncestorTransactionNodeForPath:changedPath]; + FPath *path = rootMostTransactionNode.path; + + NSArray *queue = + [self buildTransactionQueueAtNode:rootMostTransactionNode]; + [self rerunTransactionQueue:queue atPath:path]; + + return path; + } +} + +/** + * Does all the work of rerunning transactions (as well as cleans up aborted + * transactions and whatnot). + */ +- (void)rerunTransactionQueue:(NSArray *)queue atPath:(FPath *)path { + if (queue.count == 0) { + return; // nothing to do + } + + // Queue up the callbacks and fire them after cleaning up all of our + // transaction state, since the callback could trigger more transactions or + // sets. + NSMutableArray *events = [[NSMutableArray alloc] init]; + NSMutableArray *callbacks = [[NSMutableArray alloc] init]; + + // Ignore, by default, all of the sets in this queue, since we're re-running + // all of them. However, we want to include the results of new sets + // triggered as part of this re-run, so we don't want to ignore a range, + // just these specific sets. + NSMutableArray *writeIdsToExclude = [[NSMutableArray alloc] init]; + for (FTupleTransaction *transaction in queue) { + [writeIdsToExclude addObject:transaction.currentWriteId]; + } + + for (FTupleTransaction *transaction in queue) { + FPath *relativePath __unused = + [FPath relativePathFrom:path to:transaction.path]; + BOOL abortTransaction = NO; + NSAssert(relativePath != nil, @"[FRepo rerunTransactionsQueue:] " + @"relativePath should not be null."); + + if (transaction.status == FTransactionNeedsAbort) { + abortTransaction = YES; + if (![transaction.abortStatus + isEqualToString:kFErrorWriteCanceled]) { + NSArray *ackEvents = [self.serverSyncTree + ackUserWriteWithWriteId:[transaction.currentWriteId + integerValue] + revert:YES + persist:NO + clock:self.serverClock]; + [events addObjectsFromArray:ackEvents]; + } + } else if (transaction.status == FTransactionRun) { + if (transaction.retryCount >= kFTransactionMaxRetries) { + abortTransaction = YES; + [transaction setAbortStatus:kFTransactionTooManyRetries + reason:nil]; + [events + addObjectsFromArray: + [self.serverSyncTree + ackUserWriteWithWriteId:[transaction.currentWriteId + integerValue] + revert:YES + persist:NO + clock:self.serverClock]]; + } else { + // This code reruns a transaction + id currentNode = + [self latestStateAtPath:transaction.path + excludeWriteIds:writeIdsToExclude]; + transaction.currentInputSnapshot = currentNode; + FIRMutableData *mutableCurrent = + [[FIRMutableData alloc] initWithNode:currentNode]; + FIRTransactionResult *result = + transaction.update(mutableCurrent); + if (result.isSuccess) { + NSNumber *oldWriteId = transaction.currentWriteId; + NSDictionary *serverValues = + [FServerValues generateServerValues:self.serverClock]; + + id newVal = [result.update nodeValue]; + id newValResolved = [FServerValues + resolveDeferredValueSnapshot:newVal + withServerValues:serverValues]; + + transaction.currentOutputSnapshotRaw = newVal; + transaction.currentOutputSnapshotResolved = newValResolved; + + transaction.currentWriteId = + [NSNumber numberWithInteger:[self nextWriteId]]; + // Mutates writeIdsToExclude in place + [writeIdsToExclude removeObject:oldWriteId]; + [events + addObjectsFromArray: + [self.serverSyncTree + applyUserOverwriteAtPath:transaction.path + newData: + transaction + .currentOutputSnapshotResolved + writeId: + [transaction.currentWriteId + integerValue] + isVisible:transaction + .applyLocally]]; + [events addObjectsFromArray: + [self.serverSyncTree + ackUserWriteWithWriteId:[oldWriteId + integerValue] + revert:YES + persist:NO + clock:self.serverClock]]; + } else { + abortTransaction = YES; + // The user aborted the transaction. JS treats ths as a + // "nodata" abort, but it's not an error, so we don't send + // them an error. + [transaction setAbortStatus:nil reason:nil]; + [events + addObjectsFromArray: + [self.serverSyncTree + ackUserWriteWithWriteId: + [transaction.currentWriteId integerValue] + revert:YES + persist:NO + clock:self.serverClock]]; + } + } + } + + [self.eventRaiser raiseEvents:events]; + events = nil; + + if (abortTransaction) { + // Abort + transaction.status = FTransactionCompleted; + transaction.unwatcher(); + if (transaction.onComplete) { + FIRDatabaseReference *ref = [[FIRDatabaseReference alloc] + initWithRepo:self + path:transaction.path]; + FIndexedNode *lastInput = [FIndexedNode + indexedNodeWithNode:transaction.currentInputSnapshot]; + FIRDataSnapshot *snap = + [[FIRDataSnapshot alloc] initWithRef:ref + indexedNode:lastInput]; + fbt_void_void cb = ^{ + // Unlike JS, no need to check for "nodata" because ObjC has + // abortError = nil + transaction.onComplete(transaction.abortError, NO, snap); + }; + [callbacks addObject:[cb copy]]; + } + } + } + + // Note: unlike current js client, we don't need to preserve priority. Users + // can set priority via FIRMutableData + + // Clean up completed transactions. + [self pruneCompletedTransactionsBelowNode:self.transactionQueueTree]; + + // Now fire callbacks, now that we're in a good, known state. + [self.eventRaiser raiseCallbacks:callbacks]; + + // Try to send the transaction result to the server + [self sendAllReadyTransactions]; +} + +- (FTree *)getAncestorTransactionNodeForPath:(FPath *)path { + FTree *transactionNode = self.transactionQueueTree; + + while (![path isEmpty] && [transactionNode getValue] == nil) { + NSString *front = [path getFront]; + transactionNode = + [transactionNode subTree:[[FPath alloc] initWith:front]]; + path = [path popFront]; + } + + return transactionNode; +} + +- (NSMutableArray *)buildTransactionQueueAtNode:(FTree *)node { + NSMutableArray *queue = [[NSMutableArray alloc] init]; + [self aggregateTransactionQueuesForNode:node andQueue:queue]; + + [queue sortUsingComparator:^NSComparisonResult(FTupleTransaction *obj1, + FTupleTransaction *obj2) { + return [obj1.order compare:obj2.order]; + }]; + + return queue; +} + +- (void)aggregateTransactionQueuesForNode:(FTree *)node + andQueue:(NSMutableArray *)queue { + NSArray *nodeQueue = [node getValue]; + [queue addObjectsFromArray:nodeQueue]; + + [node forEachChild:^(FTree *child) { + [self aggregateTransactionQueuesForNode:child andQueue:queue]; + }]; +} + +/** + * Remove COMPLETED transactions at or below this node in the + * transactionQueueTree + */ +- (void)pruneCompletedTransactionsBelowNode:(FTree *)node { + NSMutableArray *queue = [node getValue]; + if (queue != nil) { + int i = 0; + // remove all of the completed transactions from the queue + while (i < queue.count) { + FTupleTransaction *transaction = [queue objectAtIndex:i]; + if (transaction.status == FTransactionCompleted) { + [queue removeObjectAtIndex:i]; + } else { + i++; + } + } + if (queue.count > 0) { + [node setValue:queue]; + } else { + [node setValue:nil]; + } + } + + [node forEachChildMutationSafe:^(FTree *child) { + [self pruneCompletedTransactionsBelowNode:child]; + }]; +} + +/** + * Aborts all transactions on ancestors or descendants of the specified path. + * Called when doing a setValue: or updateChildValues: since we consider them + * incompatible with transactions + * + * @param path path for which we want to abort related transactions. + */ +- (FPath *)abortTransactionsAtPath:(FPath *)path error:(NSString *)error { + // For the common case that there are no transactions going on, skip all + // this! + if ([self.transactionQueueTree isEmpty]) { + return path; + } else { + FPath *affectedPath = + [self getAncestorTransactionNodeForPath:path].path; + + FTree *transactionNode = [self.transactionQueueTree subTree:path]; + [transactionNode forEachAncestor:^BOOL(FTree *ancestor) { + [self abortTransactionsAtNode:ancestor error:error]; + return NO; + }]; + + [self abortTransactionsAtNode:transactionNode error:error]; + + [transactionNode forEachDescendant:^(FTree *child) { + [self abortTransactionsAtNode:child error:error]; + }]; + + return affectedPath; + } +} + +/** + * Abort transactions stored in this transactions queue node. + * + * @param node Node to abort transactions for. + */ +- (void)abortTransactionsAtNode:(FTree *)node error:(NSString *)error { + NSMutableArray *queue = [node getValue]; + if (queue != nil) { + + // Queue up the callbacks and fire them after cleaning up all of our + // transaction state, since can be immediately aborted and removed. + NSMutableArray *callbacks = [[NSMutableArray alloc] init]; + + // Go through queue. Any already-sent transactions must be marked for + // abort, while the unsent ones can be immediately aborted and removed + NSMutableArray *events = [[NSMutableArray alloc] init]; + int lastSent = -1; + // Note: all of the sent transactions will be at the front of the queue, + // so safe to increment lastSent + for (FTupleTransaction *transaction in queue) { + if (transaction.status == FTransactionSentNeedsAbort) { + // No-op. already marked. + } else if (transaction.status == FTransactionSent) { + // Mark this transaction for abort when it returns + lastSent++; + transaction.status = FTransactionSentNeedsAbort; + [transaction setAbortStatus:error reason:nil]; + } else { + // we can abort this immediately + transaction.unwatcher(); + if ([error isEqualToString:kFTransactionSet]) { + [events + addObjectsFromArray: + [self.serverSyncTree + ackUserWriteWithWriteId: + [transaction.currentWriteId integerValue] + revert:YES + persist:NO + clock:self.serverClock]]; + } else { + // If it was cancelled it was already removed from the sync + // tree, no need to ack + NSAssert([error isEqualToString:kFErrorWriteCanceled], nil); + } + + if (transaction.onComplete) { + NSError *abortReason = [FUtilities errorForStatus:error + andReason:nil]; + FIRDataSnapshot *snapshot = nil; + fbt_void_void cb = ^{ + transaction.onComplete(abortReason, NO, snapshot); + }; + [callbacks addObject:[cb copy]]; + } + } + } + if (lastSent == -1) { + // We're not waiting for any sent transactions. We can clear the + // queue. + [node setValue:nil]; + } else { + // Remove the transactions we aborted + NSRange theRange; + theRange.location = lastSent + 1; + theRange.length = queue.count - theRange.location; + [queue removeObjectsInRange:theRange]; + } + + // Now fire the callbacks + [self.eventRaiser raiseEvents:events]; + [self.eventRaiser raiseCallbacks:callbacks]; + } +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FRepoInfo.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FRepoInfo.h new file mode 100644 index 0000000..46e9b8b --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FRepoInfo.h @@ -0,0 +1,40 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@interface FRepoInfo : NSObject + +@property(nonatomic, readonly, strong) NSString *host; +@property(nonatomic, readonly, strong) NSString *namespace; +@property(nonatomic, strong) NSString *internalHost; +@property(nonatomic, readonly) bool secure; + +- (id)initWithHost:(NSString *)host + isSecure:(bool)secure + withNamespace:(NSString *)namespace; + +- (NSString *)connectionURLWithLastSessionID:(NSString *)lastSessionID; +- (NSString *)connectionURL; +- (void)clearInternalHostCache; +- (BOOL)isDemoHost; +- (BOOL)isCustomHost; + +- (id)copyWithZone:(NSZone *)zone; +- (NSUInteger)hash; +- (BOOL)isEqual:(id)anObject; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FRepoInfo.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FRepoInfo.m new file mode 100644 index 0000000..b2dd2e2 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FRepoInfo.m @@ -0,0 +1,144 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FRepoInfo.h" +#import "FConstants.h" + +@interface FRepoInfo () + +@property(nonatomic, strong) NSString *domain; + +@end + +@implementation FRepoInfo + +@synthesize namespace; +@synthesize host; +@synthesize internalHost; +@synthesize secure; +@synthesize domain; + +- (id)initWithHost:(NSString *)aHost + isSecure:(bool)isSecure + withNamespace:(NSString *)aNamespace { + self = [super init]; + if (self) { + host = aHost; + domain = + [host containsString:@"."] + ? [host + substringFromIndex:[host rangeOfString:@"."].location + 1] + : host; + secure = isSecure; + namespace = aNamespace; + + // Get cached internal host if it exists + NSString *internalHostKey = + [NSString stringWithFormat:@"firebase:host:%@", self.host]; + NSString *cachedInternalHost = [[NSUserDefaults standardUserDefaults] + stringForKey:internalHostKey]; + if (cachedInternalHost != nil) { + internalHost = cachedInternalHost; + } else { + internalHost = self.host; + } + } + return self; +} + +- (NSString *)description { + // The namespace is encoded in the hostname, so we can just return this. + return [NSString + stringWithFormat:@"http%@://%@", (self.secure ? @"s" : @""), self.host]; +} + +- (void)setInternalHost:(NSString *)newHost { + if (![internalHost isEqualToString:newHost]) { + internalHost = newHost; + + // Cache the internal host so we don't need to redirect later on + NSString *internalHostKey = + [NSString stringWithFormat:@"firebase:host:%@", self.host]; + NSUserDefaults *cache = [NSUserDefaults standardUserDefaults]; + [cache setObject:internalHost forKey:internalHostKey]; + [cache synchronize]; + } +} + +- (void)clearInternalHostCache { + internalHost = self.host; + + // Remove the cached entry + NSString *internalHostKey = + [NSString stringWithFormat:@"firebase:host:%@", self.host]; + NSUserDefaults *cache = [NSUserDefaults standardUserDefaults]; + [cache removeObjectForKey:internalHostKey]; + [cache synchronize]; +} + +- (BOOL)isDemoHost { + return [self.domain isEqualToString:@"firebaseio-demo.com"]; +} + +- (BOOL)isCustomHost { + return ![self.domain isEqualToString:@"firebaseio-demo.com"] && + ![self.domain isEqualToString:@"firebaseio.com"]; +} + +- (NSString *)connectionURL { + return [self connectionURLWithLastSessionID:nil]; +} + +- (NSString *)connectionURLWithLastSessionID:(NSString *)lastSessionID { + NSString *scheme; + if (self.secure) { + scheme = @"wss"; + } else { + scheme = @"ws"; + } + NSString *url = + [NSString stringWithFormat:@"%@://%@/.ws?%@=%@&ns=%@", scheme, + self.internalHost, kWireProtocolVersionParam, + kWebsocketProtocolVersion, self.namespace]; + + if (lastSessionID != nil) { + url = [NSString stringWithFormat:@"%@&ls=%@", url, lastSessionID]; + } + return url; +} + +- (id)copyWithZone:(NSZone *)zone; +{ + return self; // Immutable +} + +- (NSUInteger)hash { + NSUInteger result = host.hash; + result = 31 * result + (secure ? 1 : 0); + result = 31 * result + namespace.hash; + result = 31 * result + host.hash; + return result; +} + +- (BOOL)isEqual:(id)anObject { + if (![anObject isKindOfClass:[FRepoInfo class]]) + return NO; + FRepoInfo *other = (FRepoInfo *)anObject; + return secure == other.secure && [host isEqualToString:other.host] && + [namespace isEqualToString:other.namespace]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FRepoManager.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FRepoManager.h new file mode 100644 index 0000000..ba8d1fc --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FRepoManager.h @@ -0,0 +1,34 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRDatabaseConfig.h" +#import "FRepo.h" +#import "FRepoInfo.h" +#import + +@interface FRepoManager : NSObject + ++ (FRepo *)getRepo:(FRepoInfo *)repoInfo config:(FIRDatabaseConfig *)config; ++ (FRepo *)createRepo:(FRepoInfo *)repoInfo + config:(FIRDatabaseConfig *)config + database:(FIRDatabase *)database; ++ (void)interruptAll; ++ (void)interrupt:(FIRDatabaseConfig *)config; ++ (void)resumeAll; ++ (void)resume:(FIRDatabaseConfig *)config; ++ (void)disposeRepos:(FIRDatabaseConfig *)config; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FRepoManager.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FRepoManager.m new file mode 100644 index 0000000..54ad196 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FRepoManager.m @@ -0,0 +1,148 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FRepoManager.h" +#import "FAtomicNumber.h" +#import "FIRDatabaseConfig_Private.h" +#import "FIRDatabaseQuery_Private.h" +#import "FIRDatabase_Private.h" +#import "FRepo.h" +#import + +@implementation FRepoManager + +typedef NSMutableDictionary *> + FRepoDictionary; + ++ (FRepoDictionary *)configs { + static dispatch_once_t pred = 0; + static FRepoDictionary *configs; + dispatch_once(&pred, ^{ + configs = [NSMutableDictionary dictionary]; + }); + return configs; +} + +/** + * Used for legacy unit tests. The public API should go through + * FirebaseDatabase which calls createRepo. + */ ++ (FRepo *)getRepo:(FRepoInfo *)repoInfo config:(FIRDatabaseConfig *)config { + [config freeze]; + FRepoDictionary *configs = [FRepoManager configs]; + @synchronized(configs) { + NSMutableDictionary *repos = + configs[config.sessionIdentifier]; + if (!repos || repos[repoInfo] == nil) { + // Calling this should create the repo. + [FIRDatabase createDatabaseForTests:repoInfo config:config]; + } + + return configs[config.sessionIdentifier][repoInfo]; + } +} + ++ (FRepo *)createRepo:(FRepoInfo *)repoInfo + config:(FIRDatabaseConfig *)config + database:(FIRDatabase *)database { + [config freeze]; + FRepoDictionary *configs = [FRepoManager configs]; + @synchronized(configs) { + NSMutableDictionary *repos = + configs[config.sessionIdentifier]; + if (!repos) { + repos = [NSMutableDictionary dictionary]; + configs[config.sessionIdentifier] = repos; + } + FRepo *repo = repos[repoInfo]; + if (repo == nil) { + repo = [[FRepo alloc] initWithRepoInfo:repoInfo + config:config + database:database]; + repos[repoInfo] = repo; + return repo; + } else { + [NSException + raise:@"RepoExists" + format:@"createRepo called for Repo that already exists."]; + return nil; + } + } +} + ++ (void)interrupt:(FIRDatabaseConfig *)config { + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + FRepoDictionary *configs = [FRepoManager configs]; + NSMutableDictionary *repos = + configs[config.sessionIdentifier]; + for (FRepo *repo in [repos allValues]) { + [repo interrupt]; + } + }); +} + ++ (void)interruptAll { + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + FRepoDictionary *configs = [FRepoManager configs]; + for (NSMutableDictionary *repos in + [configs allValues]) { + for (FRepo *repo in [repos allValues]) { + [repo interrupt]; + } + } + }); +} + ++ (void)resume:(FIRDatabaseConfig *)config { + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + FRepoDictionary *configs = [FRepoManager configs]; + NSMutableDictionary *repos = + configs[config.sessionIdentifier]; + for (FRepo *repo in [repos allValues]) { + [repo resume]; + } + }); +} + ++ (void)resumeAll { + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + FRepoDictionary *configs = [FRepoManager configs]; + for (NSMutableDictionary *repos in + [configs allValues]) { + for (FRepo *repo in [repos allValues]) { + [repo resume]; + } + } + }); +} + ++ (void)disposeRepos:(FIRDatabaseConfig *)config { + // Do this synchronously to make sure we release our references to LevelDB + // before returning, allowing LevelDB to close and release its exclusive + // locks. + dispatch_sync([FIRDatabaseQuery sharedQueue], ^{ + FFLog(@"I-RDB040001", @"Disposing all repos for Config with name %@", + config.sessionIdentifier); + NSMutableDictionary *configs = [FRepoManager configs]; + for (FRepo *repo in [configs[config.sessionIdentifier] allValues]) { + [repo dispose]; + } + [configs removeObjectForKey:config.sessionIdentifier]; + }); +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FRepo_Private.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FRepo_Private.h new file mode 100644 index 0000000..8dc1350 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FRepo_Private.h @@ -0,0 +1,42 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FRepo.h" +#import "FSparseSnapshotTree.h" + +@class FSyncTree; +@class FAtomicNumber; +@class FEventRaiser; +@class FSnapshotHolder; + +@interface FRepo () + +- (void)runOnDisconnectEvents; + +@property(nonatomic, strong) FRepoInfo *repoInfo; +@property(nonatomic, strong) FPersistentConnection *connection; +@property(nonatomic, strong) FSnapshotHolder *infoData; +@property(nonatomic, strong) FSparseSnapshotTree *onDisconnect; +@property(nonatomic, strong) FEventRaiser *eventRaiser; +@property(nonatomic, strong) FSyncTree *serverSyncTree; + +// For testing. +@property(nonatomic) long dataUpdateCount; +@property(nonatomic) long rangeMergeUpdateCount; + +- (NSInteger)nextWriteId; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FServerValues.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FServerValues.h new file mode 100644 index 0000000..33d1e57 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FServerValues.h @@ -0,0 +1,33 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FClock.h" +#import "FCompoundWrite.h" +#import "FNode.h" +#import "FSparseSnapshotTree.h" +#import + +@interface FServerValues : NSObject + ++ (NSDictionary *)generateServerValues:(id)clock; ++ (id)resolveDeferredValueCompoundWrite:(FCompoundWrite *)write + withServerValues:(NSDictionary *)serverValues; ++ (id)resolveDeferredValueSnapshot:(id)node + withServerValues:(NSDictionary *)serverValues; ++ (id)resolveDeferredValueTree:(FSparseSnapshotTree *)tree + withServerValues:(NSDictionary *)serverValues; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FServerValues.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FServerValues.m new file mode 100644 index 0000000..5519862 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FServerValues.m @@ -0,0 +1,115 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FServerValues.h" +#import "FChildrenNode.h" +#import "FConstants.h" +#import "FLeafNode.h" +#import "FSnapshotUtilities.h" + +@implementation FServerValues + ++ (NSDictionary *)generateServerValues:(id)clock { + long long millis = (long long)([clock currentTime] * 1000); + return @{@"timestamp" : [NSNumber numberWithLongLong:millis]}; +} + ++ (id)resolveDeferredValue:(id)val + withServerValues:(NSDictionary *)serverValues { + if ([val isKindOfClass:[NSDictionary class]]) { + NSDictionary *dict = val; + if (dict[kServerValueSubKey] != nil) { + NSString *serverValueType = [dict objectForKey:kServerValueSubKey]; + if (serverValues[serverValueType] != nil) { + return [serverValues objectForKey:serverValueType]; + } else { + // TODO: Throw unrecognizedServerValue error here + } + } + } + return val; +} + ++ (FCompoundWrite *)resolveDeferredValueCompoundWrite:(FCompoundWrite *)write + withServerValues: + (NSDictionary *)serverValues { + __block FCompoundWrite *resolved = write; + [write enumerateWrites:^(FPath *path, id node, BOOL *stop) { + id resolvedNode = + [FServerValues resolveDeferredValueSnapshot:node + withServerValues:serverValues]; + // Node actually changed, use pointer inequality here + if (resolvedNode != node) { + resolved = [resolved addWrite:resolvedNode atPath:path]; + } + }]; + return resolved; +} + ++ (id)resolveDeferredValueTree:(FSparseSnapshotTree *)tree + withServerValues:(NSDictionary *)serverValues { + FSparseSnapshotTree *resolvedTree = [[FSparseSnapshotTree alloc] init]; + [tree + forEachTreeAtPath:[FPath empty] + do:^(FPath *path, id node) { + [resolvedTree + rememberData: + [FServerValues + resolveDeferredValueSnapshot:node + withServerValues:serverValues] + onPath:path]; + }]; + return resolvedTree; +} + ++ (id)resolveDeferredValueSnapshot:(id)node + withServerValues:(NSDictionary *)serverValues { + id priorityVal = + [FServerValues resolveDeferredValue:[[node getPriority] val] + withServerValues:serverValues]; + id priority = [FSnapshotUtilities nodeFrom:priorityVal]; + + if ([node isLeafNode]) { + id value = [self resolveDeferredValue:[node val] + withServerValues:serverValues]; + if (![value isEqual:[node val]] || + ![priority isEqual:[node getPriority]]) { + return [[FLeafNode alloc] initWithValue:value + withPriority:priority]; + } else { + return node; + } + } else { + __block FChildrenNode *newNode = node; + if (![priority isEqual:[node getPriority]]) { + newNode = [newNode updatePriority:priority]; + } + + [node enumerateChildrenUsingBlock:^(NSString *childKey, + id childNode, BOOL *stop) { + id newChildNode = + [FServerValues resolveDeferredValueSnapshot:childNode + withServerValues:serverValues]; + if (![newChildNode isEqual:childNode]) { + newNode = [newNode updateImmediateChild:childKey + withNewChild:newChildNode]; + } + }]; + return newNode; + } +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FSnapshotHolder.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FSnapshotHolder.h new file mode 100644 index 0000000..d682a8e --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FSnapshotHolder.h @@ -0,0 +1,27 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FNode.h" +#import + +@interface FSnapshotHolder : NSObject + +- (id)getNode:(FPath *)path; +- (void)updateSnapshot:(FPath *)path withNewSnapshot:(id)newSnapshotNode; + +@property(nonatomic, strong) id rootNode; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FSnapshotHolder.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FSnapshotHolder.m new file mode 100644 index 0000000..59a7b52 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FSnapshotHolder.m @@ -0,0 +1,46 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FSnapshotHolder.h" +#import "FEmptyNode.h" + +@interface FSnapshotHolder () + +@end + +@implementation FSnapshotHolder + +@synthesize rootNode; + +- (id)init { + self = [super init]; + if (self) { + self.rootNode = [FEmptyNode emptyNode]; + } + return self; +} + +- (id)getNode:(FPath *)path { + return [self.rootNode getChild:path]; +} + +- (void)updateSnapshot:(FPath *)path + withNewSnapshot:(id)newSnapshotNode { + self.rootNode = [self.rootNode updateChild:path + withNewChild:newSnapshotNode]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FSparseSnapshotTree.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FSparseSnapshotTree.h new file mode 100644 index 0000000..dab5406 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FSparseSnapshotTree.h @@ -0,0 +1,34 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FNode.h" +#import "FPath.h" +#import "FTypedefs_Private.h" +#import + +@class FSparseSnapshotTree; + +typedef void (^fbt_void_nsstring_sstree)(NSString *, FSparseSnapshotTree *); + +@interface FSparseSnapshotTree : NSObject + +- (id)findPath:(FPath *)path; +- (void)rememberData:(id)data onPath:(FPath *)path; +- (BOOL)forgetPath:(FPath *)path; +- (void)forEachTreeAtPath:(FPath *)prefixPath do:(fbt_void_path_node)func; +- (void)forEachChild:(fbt_void_nsstring_sstree)func; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FSparseSnapshotTree.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FSparseSnapshotTree.m new file mode 100644 index 0000000..6219309 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FSparseSnapshotTree.m @@ -0,0 +1,144 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FSparseSnapshotTree.h" +#import "FChildrenNode.h" + +@interface FSparseSnapshotTree () { + id value; + NSMutableDictionary *children; +} + +@end + +@implementation FSparseSnapshotTree + +- (id)init { + self = [super init]; + if (self) { + value = nil; + children = nil; + } + return self; +} + +- (id)findPath:(FPath *)path { + if (value != nil) { + return [value getChild:path]; + } else if (![path isEmpty] && children != nil) { + NSString *childKey = [path getFront]; + path = [path popFront]; + FSparseSnapshotTree *childTree = children[childKey]; + if (childTree != nil) { + return [childTree findPath:path]; + } else { + return nil; + } + } else { + return nil; + } +} + +- (void)rememberData:(id)data onPath:(FPath *)path { + if ([path isEmpty]) { + value = data; + children = nil; + } else if (value != nil) { + value = [value updateChild:path withNewChild:data]; + } else { + if (children == nil) { + children = [[NSMutableDictionary alloc] init]; + } + + NSString *childKey = [path getFront]; + if (children[childKey] == nil) { + children[childKey] = [[FSparseSnapshotTree alloc] init]; + } + + FSparseSnapshotTree *child = children[childKey]; + path = [path popFront]; + [child rememberData:data onPath:path]; + } +} + +- (BOOL)forgetPath:(FPath *)path { + if ([path isEmpty]) { + value = nil; + children = nil; + return YES; + } else { + if (value != nil) { + if ([value isLeafNode]) { + // non-empty path at leaf. the path leads to nowhere + return NO; + } else { + id tmp = value; + value = nil; + + [tmp enumerateChildrenUsingBlock:^(NSString *key, + id node, BOOL *stop) { + [self rememberData:node onPath:[[FPath alloc] initWith:key]]; + }]; + + // we've cleared out the value and set children. Call ourself + // again to hit the next case + return [self forgetPath:path]; + } + } else if (children != nil) { + NSString *childKey = [path getFront]; + path = [path popFront]; + + if (children[childKey] != nil) { + FSparseSnapshotTree *child = children[childKey]; + BOOL safeToRemove = [child forgetPath:path]; + if (safeToRemove) { + [children removeObjectForKey:childKey]; + } + } + + if ([children count] == 0) { + children = nil; + return YES; + } else { + return NO; + } + } else { + return YES; + } + } +} + +- (void)forEachTreeAtPath:(FPath *)prefixPath do:(fbt_void_path_node)func { + if (value != nil) { + func(prefixPath, value); + } else { + [self forEachChild:^(NSString *key, FSparseSnapshotTree *tree) { + FPath *path = [prefixPath childFromString:key]; + [tree forEachTreeAtPath:path do:func]; + }]; + } +} + +- (void)forEachChild:(fbt_void_nsstring_sstree)func { + if (children != nil) { + for (NSString *key in children) { + FSparseSnapshotTree *tree = [children objectForKey:key]; + func(key, tree); + } + } +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FSyncPoint.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FSyncPoint.h new file mode 100644 index 0000000..a9bd4d9 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FSyncPoint.h @@ -0,0 +1,69 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@protocol FOperation; +@class FWriteTreeRef; +@protocol FNode; +@protocol FEventRegistration; +@class FQuerySpec; +@class FChildrenNode; +@class FTupleRemovedQueriesEvents; +@class FView; +@class FPath; +@class FCacheNode; +@class FPersistenceManager; + +@interface FSyncPoint : NSObject + +- (id)initWithPersistenceManager:(FPersistenceManager *)persistence; + +- (BOOL)isEmpty; + +/** + * Returns array of FEvent + */ +- (NSArray *)applyOperation:(id)operation + writesCache:(FWriteTreeRef *)writesCache + serverCache:(id)optCompleteServerCache; + +/** + * Returns array of FEvent + */ +- (NSArray *)addEventRegistration:(id)eventRegistration + forNonExistingViewForQuery:(FQuerySpec *)query + writesCache:(FWriteTreeRef *)writesCache + serverCache:(FCacheNode *)serverCache; + +- (NSArray *)addEventRegistration:(id)eventRegistration + forExistingViewForQuery:(FQuerySpec *)query; + +- (FTupleRemovedQueriesEvents *)removeEventRegistration: + (id)eventRegistration + forQuery:(FQuerySpec *)query + cancelError:(NSError *)cancelError; +/** + * Returns array of FViews + */ +- (NSArray *)queryViews; +- (id)completeServerCacheAtPath:(FPath *)path; +- (FView *)viewForQuery:(FQuerySpec *)query; +- (BOOL)viewExistsForQuery:(FQuerySpec *)query; +- (BOOL)hasCompleteView; +- (FView *)completeView; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FSyncPoint.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FSyncPoint.m new file mode 100644 index 0000000..6496ae8 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FSyncPoint.m @@ -0,0 +1,302 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FSyncPoint.h" +#import "FCacheNode.h" +#import "FChildrenNode.h" +#import "FDataEvent.h" +#import "FEmptyNode.h" +#import "FEventRegistration.h" +#import "FIRDatabaseQuery.h" +#import "FNode.h" +#import "FOperation.h" +#import "FOperationSource.h" +#import "FPath.h" +#import "FPersistenceManager.h" +#import "FQueryParams.h" +#import "FQuerySpec.h" +#import "FTupleRemovedQueriesEvents.h" +#import "FView.h" +#import "FViewCache.h" +#import "FWriteTreeRef.h" + +/** + * SyncPoint represents a single location in a SyncTree with 1 or more event + * registrations, meaning we need to maintain 1 or more Views at this location + * to cache server data and raise appropriate events for server changes and user + * writes (set, transaction, update). + * + * It's responsible for: + * - Maintaining the set of 1 or more views necessary at this location (a + * SyncPoint with 0 views should be removed). + * - Proxying user / server operations to the views as appropriate (i.e. + * applyServerOverwrite, applyUserOverwrite, etc.) + */ +@interface FSyncPoint () +/** + * The Views being tracked at this location in the tree, stored as a map where + * the key is a queryParams and the value is the View for that query. + * + * NOTE: This list will be quite small (usually 1, but perhaps 2 or 3; any more + * is an odd use case). + * + * Maps NSString -> FView + */ +@property(nonatomic, strong) NSMutableDictionary *views; + +@property(nonatomic, strong) FPersistenceManager *persistenceManager; +@end + +@implementation FSyncPoint + +- (id)initWithPersistenceManager:(FPersistenceManager *)persistence { + self = [super init]; + if (self) { + self.persistenceManager = persistence; + self.views = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (BOOL)isEmpty { + return [self.views count] == 0; +} + +- (NSArray *)applyOperation:(id)operation + toView:(FView *)view + writesCache:(FWriteTreeRef *)writesCache + serverCache:(id)optCompleteServerCache { + FViewOperationResult *result = [view applyOperation:operation + writesCache:writesCache + serverCache:optCompleteServerCache]; + if (!view.query.loadsAllData) { + NSMutableSet *removed = [NSMutableSet set]; + NSMutableSet *added = [NSMutableSet set]; + [result.changes enumerateObjectsUsingBlock:^( + FChange *change, NSUInteger idx, BOOL *stop) { + if (change.type == FIRDataEventTypeChildAdded) { + [added addObject:change.childKey]; + } else if (change.type == FIRDataEventTypeChildRemoved) { + [removed addObject:change.childKey]; + } + }]; + if ([removed count] > 0 || [added count] > 0) { + [self.persistenceManager + updateTrackedQueryKeysWithAddedKeys:added + removedKeys:removed + forQuery:view.query]; + } + } + return result.events; +} + +- (NSArray *)applyOperation:(id)operation + writesCache:(FWriteTreeRef *)writesCache + serverCache:(id)optCompleteServerCache { + FQueryParams *queryParams = operation.source.queryParams; + if (queryParams != nil) { + FView *view = [self.views objectForKey:queryParams]; + NSAssert(view != nil, @"SyncTree gave us an op for an invalid query."); + return [self applyOperation:operation + toView:view + writesCache:writesCache + serverCache:optCompleteServerCache]; + } else { + NSMutableArray *events = [[NSMutableArray alloc] init]; + [self.views enumerateKeysAndObjectsUsingBlock:^( + FQueryParams *key, FView *view, BOOL *stop) { + NSArray *eventsForView = [self applyOperation:operation + toView:view + writesCache:writesCache + serverCache:optCompleteServerCache]; + [events addObjectsFromArray:eventsForView]; + }]; + return events; + } +} + +/** + * Add an event callback for the specified query + * Returns Array of FEvent events to raise. + */ +- (NSArray *)addEventRegistration:(id)eventRegistration + forNonExistingViewForQuery:(FQuerySpec *)query + writesCache:(FWriteTreeRef *)writesCache + serverCache:(FCacheNode *)serverCache { + NSAssert(self.views[query.params] == nil, @"Found view for query: %@", + query.params); + // TODO: make writesCache take flag for complete server node + id eventCache = [writesCache + calculateCompleteEventCacheWithCompleteServerCache: + serverCache.isFullyInitialized ? serverCache.node : nil]; + BOOL eventCacheComplete; + if (eventCache != nil) { + eventCacheComplete = YES; + } else { + eventCache = [writesCache + calculateCompleteEventChildrenWithCompleteServerChildren:serverCache + .node]; + eventCacheComplete = NO; + } + + FIndexedNode *indexed = [FIndexedNode indexedNodeWithNode:eventCache + index:query.index]; + FCacheNode *eventCacheNode = + [[FCacheNode alloc] initWithIndexedNode:indexed + isFullyInitialized:eventCacheComplete + isFiltered:NO]; + FViewCache *viewCache = + [[FViewCache alloc] initWithEventCache:eventCacheNode + serverCache:serverCache]; + FView *view = [[FView alloc] initWithQuery:query + initialViewCache:viewCache]; + // If this is a non-default query we need to tell persistence our current + // view of the data + if (!query.loadsAllData) { + NSMutableSet *allKeys = [NSMutableSet set]; + [view.eventCache enumerateChildrenUsingBlock:^( + NSString *key, id node, BOOL *stop) { + [allKeys addObject:key]; + }]; + [self.persistenceManager setTrackedQueryKeys:allKeys forQuery:query]; + } + self.views[query.params] = view; + return [self addEventRegistration:eventRegistration + forExistingViewForQuery:query]; +} + +- (NSArray *)addEventRegistration:(id)eventRegistration + forExistingViewForQuery:(FQuerySpec *)query { + FView *view = self.views[query.params]; + NSAssert(view != nil, @"No view for query: %@", query); + [view addEventRegistration:eventRegistration]; + return [view initialEvents:eventRegistration]; +} + +/** + * Remove event callback(s). Return cancelEvents if a cancelError is specified. + * + * If query is the default query, we'll check all views for the specified + * eventRegistration. If eventRegistration is nil, we'll remove all callbacks + * for the specified view(s). + * + * @return FTupleRemovedQueriesEvents removed queries and any cancel events + */ +- (FTupleRemovedQueriesEvents *)removeEventRegistration: + (id)eventRegistration + forQuery:(FQuerySpec *)query + cancelError:(NSError *)cancelError { + NSMutableArray *removedQueries = [[NSMutableArray alloc] init]; + __block NSMutableArray *cancelEvents = [[NSMutableArray alloc] init]; + BOOL hadCompleteView = [self hasCompleteView]; + if ([query isDefault]) { + // When you do [ref removeObserverWithHandle:], we search all views for + // the registration to remove. + [self.views enumerateKeysAndObjectsUsingBlock:^( + FQueryParams *viewQueryParams, FView *view, + BOOL *stop) { + [cancelEvents + addObjectsFromArray:[view + removeEventRegistration:eventRegistration + cancelError:cancelError]]; + if ([view isEmpty]) { + [self.views removeObjectForKey:viewQueryParams]; + + // We'll deal with complete views later + if (![view.query loadsAllData]) { + [removedQueries addObject:view.query]; + } + } + }]; + } else { + // remove the callback from the specific view + FView *view = [self.views objectForKey:query.params]; + if (view != nil) { + [cancelEvents addObjectsFromArray: + [view removeEventRegistration:eventRegistration + cancelError:cancelError]]; + + if ([view isEmpty]) { + [self.views removeObjectForKey:query.params]; + + // We'll deal with complete views later + if (![view.query loadsAllData]) { + [removedQueries addObject:view.query]; + } + } + } + } + + if (hadCompleteView && ![self hasCompleteView]) { + // We removed our last complete view + [removedQueries addObject:[FQuerySpec defaultQueryAtPath:query.path]]; + } + + return [[FTupleRemovedQueriesEvents alloc] + initWithRemovedQueries:removedQueries + cancelEvents:cancelEvents]; +} + +- (NSArray *)queryViews { + __block NSMutableArray *filteredViews = [[NSMutableArray alloc] init]; + + [self.views enumerateKeysAndObjectsUsingBlock:^(FQueryParams *key, + FView *view, BOOL *stop) { + if (![view.query loadsAllData]) { + [filteredViews addObject:view]; + } + }]; + + return filteredViews; +} + +- (id)completeServerCacheAtPath:(FPath *)path { + __block id serverCache = nil; + [self.views enumerateKeysAndObjectsUsingBlock:^(FQueryParams *key, + FView *view, BOOL *stop) { + serverCache = [view completeServerCacheFor:path]; + *stop = (serverCache != nil); + }]; + return serverCache; +} + +- (FView *)viewForQuery:(FQuerySpec *)query { + return [self.views objectForKey:query.params]; +} + +- (BOOL)viewExistsForQuery:(FQuerySpec *)query { + return [self viewForQuery:query] != nil; +} + +- (BOOL)hasCompleteView { + return [self completeView] != nil; +} + +- (FView *)completeView { + __block FView *completeView = nil; + + [self.views enumerateKeysAndObjectsUsingBlock:^(FQueryParams *key, + FView *view, BOOL *stop) { + if ([view.query loadsAllData]) { + completeView = view; + *stop = YES; + } + }]; + + return completeView; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FSyncTree.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FSyncTree.h new file mode 100644 index 0000000..ef89774 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FSyncTree.h @@ -0,0 +1,82 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FListenProvider; +@protocol FNode; +@class FPath; +@protocol FEventRegistration; +@protocol FPersistedServerCache; +@class FQuerySpec; +@class FCompoundWrite; +@class FPersistenceManager; +@class FCompoundHash; +@protocol FClock; + +@protocol FSyncTreeHash + +- (NSString *)simpleHash; +- (FCompoundHash *)compoundHash; +- (BOOL)includeCompoundHash; + +@end + +@interface FSyncTree : NSObject + +- (id)initWithListenProvider:(FListenProvider *)provider; +- (id)initWithPersistenceManager:(FPersistenceManager *)persistenceManager + listenProvider:(FListenProvider *)provider; + +// These methods all return NSArray of FEvent +- (NSArray *)applyUserOverwriteAtPath:(FPath *)path + newData:(id)newData + writeId:(NSInteger)writeId + isVisible:(BOOL)visible; +- (NSArray *)applyUserMergeAtPath:(FPath *)path + changedChildren:(FCompoundWrite *)changedChildren + writeId:(NSInteger)writeId; +- (NSArray *)ackUserWriteWithWriteId:(NSInteger)writeId + revert:(BOOL)revert + persist:(BOOL)persist + clock:(id)clock; +- (NSArray *)applyServerOverwriteAtPath:(FPath *)path + newData:(id)newData; +- (NSArray *)applyServerMergeAtPath:(FPath *)path + changedChildren:(FCompoundWrite *)changedChildren; +- (NSArray *)applyServerRangeMergeAtPath:(FPath *)path + updates:(NSArray *)ranges; +- (NSArray *)applyTaggedQueryOverwriteAtPath:(FPath *)path + newData:(id)newData + tagId:(NSNumber *)tagId; +- (NSArray *)applyTaggedQueryMergeAtPath:(FPath *)path + changedChildren:(FCompoundWrite *)changedChildren + tagId:(NSNumber *)tagId; +- (NSArray *)applyTaggedServerRangeMergeAtPath:(FPath *)path + updates:(NSArray *)ranges + tagId:(NSNumber *)tagId; +- (NSArray *)addEventRegistration:(id)eventRegistration + forQuery:(FQuerySpec *)query; +- (NSArray *)removeEventRegistration:(id)eventRegistration + forQuery:(FQuerySpec *)query + cancelError:(NSError *)cancelError; +- (void)keepQuery:(FQuerySpec *)query synced:(BOOL)keepSynced; +- (NSArray *)removeAllWrites; + +- (id)calcCompleteEventCacheAtPath:(FPath *)path + excludeWriteIds:(NSArray *)writeIdsToExclude; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FSyncTree.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FSyncTree.m new file mode 100644 index 0000000..e7fa3dc --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FSyncTree.m @@ -0,0 +1,1038 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FSyncTree.h" +#import "FAckUserWrite.h" +#import "FAtomicNumber.h" +#import "FCacheNode.h" +#import "FChildrenNode.h" +#import "FCompoundHash.h" +#import "FCompoundWrite.h" +#import "FEmptyNode.h" +#import "FEventRaiser.h" +#import "FEventRegistration.h" +#import "FImmutableTree.h" +#import "FKeepSyncedEventRegistration.h" +#import "FListenComplete.h" +#import "FListenProvider.h" +#import "FMerge.h" +#import "FNode.h" +#import "FOperation.h" +#import "FOperationSource.h" +#import "FOverwrite.h" +#import "FPath.h" +#import "FPersistenceManager.h" +#import "FQueryParams.h" +#import "FQuerySpec.h" +#import "FRangeMerge.h" +#import "FServerValues.h" +#import "FSnapshotHolder.h" +#import "FSnapshotUtilities.h" +#import "FSyncPoint.h" +#import "FTupleRemovedQueriesEvents.h" +#import "FUtilities.h" +#import "FView.h" +#import "FWriteRecord.h" +#import "FWriteTree.h" +#import "FWriteTreeRef.h" +#import + +// Size after which we start including the compound hash +static const NSUInteger kFSizeThresholdForCompoundHash = 1024; + +@interface FListenContainer : NSObject + +@property(nonatomic, strong) FView *view; +@property(nonatomic, copy) fbt_nsarray_nsstring onComplete; + +@end + +@implementation FListenContainer + +- (instancetype)initWithView:(FView *)view + onComplete:(fbt_nsarray_nsstring)onComplete { + self = [super init]; + if (self != nil) { + self->_view = view; + self->_onComplete = onComplete; + } + return self; +} + +- (id)serverCache { + return self.view.serverCache; +} + +- (FCompoundHash *)compoundHash { + return [FCompoundHash fromNode:[self serverCache]]; +} + +- (NSString *)simpleHash { + return [[self serverCache] dataHash]; +} + +- (BOOL)includeCompoundHash { + return [FSnapshotUtilities estimateSerializedNodeSize:[self serverCache]] > + kFSizeThresholdForCompoundHash; +} + +@end + +@interface FSyncTree () + +/** + * Tree of SyncPoints. There's a SyncPoint at any location that has 1 or more + * views. + */ +@property(nonatomic, strong) FImmutableTree *syncPointTree; + +/** + * A tree of all pending user writes (user-initiated set, transactions, updates, + * etc) + */ +@property(nonatomic, strong) FWriteTree *pendingWriteTree; + +/** + * Maps tagId -> FTuplePathQueryParams + */ +@property(nonatomic, strong) NSMutableDictionary *tagToQueryMap; +@property(nonatomic, strong) NSMutableDictionary *queryToTagMap; +@property(nonatomic, strong) FListenProvider *listenProvider; +@property(nonatomic, strong) FPersistenceManager *persistenceManager; +@property(nonatomic, strong) FAtomicNumber *queryTagCounter; +@property(nonatomic, strong) NSMutableSet *keepSyncedQueries; + +@end + +/** + * SyncTree is the central class for managing event callback registration, data + * caching, views (query processing), and event generation. There are typically + * two SyncTree instances for each Repo, one for the normal Firebase data, and + * one for the .info data. + * + * It has a number of responsibilities, including: + * - Tracking all user event callbacks (registered via addEventRegistration: + * and removeEventRegistration:). + * - Applying and caching data changes for user setValue:, + * runTransactionBlock:, and updateChildValues: calls + * (applyUserOverwriteAtPath:, applyUserMergeAtPath:). + * - Applying and caching data changes for server data changes + * (applyServerOverwriteAtPath:, applyServerMergeAtPath:). + * - Generating user-facing events for server and user changes (all of the + * apply* methods return the set of events that need to be raised as a result). + * - Maintaining the appropriate set of server listens to ensure we are always + * subscribed to the correct set of paths and queries to satisfy the current set + * of user event callbacks (listens are started/stopped using the provided + * listenProvider). + * + * NOTE: Although SyncTree tracks event callbacks and calculates events to + * raise, the actual events are returned to the caller rather than raised + * synchronously. + */ +@implementation FSyncTree + +- (id)initWithListenProvider:(FListenProvider *)provider { + return [self initWithPersistenceManager:nil listenProvider:provider]; +} + +- (id)initWithPersistenceManager:(FPersistenceManager *)persistenceManager + listenProvider:(FListenProvider *)provider { + self = [super init]; + if (self) { + self.syncPointTree = [FImmutableTree empty]; + self.pendingWriteTree = [[FWriteTree alloc] init]; + self.tagToQueryMap = [[NSMutableDictionary alloc] init]; + self.queryToTagMap = [[NSMutableDictionary alloc] init]; + self.listenProvider = provider; + self.persistenceManager = persistenceManager; + self.queryTagCounter = [[FAtomicNumber alloc] init]; + self.keepSyncedQueries = [NSMutableSet set]; + } + return self; +} + +#pragma mark - +#pragma mark Apply Operations + +/** + * Apply data changes for a user-generated setValue: runTransactionBlock: + * updateChildValues:, etc. + * @return NSArray of FEvent to raise. + */ +- (NSArray *)applyUserOverwriteAtPath:(FPath *)path + newData:(id)newData + writeId:(NSInteger)writeId + isVisible:(BOOL)visible { + // Record pending write + [self.pendingWriteTree addOverwriteAtPath:path + newData:newData + writeId:writeId + isVisible:visible]; + if (!visible) { + return @[]; + } else { + FOverwrite *operation = + [[FOverwrite alloc] initWithSource:[FOperationSource userInstance] + path:path + snap:newData]; + return [self applyOperationToSyncPoints:operation]; + } +} + +/** + * Apply the data from a user-generated updateChildValues: call + * @return NSArray of FEvent to raise. + */ +- (NSArray *)applyUserMergeAtPath:(FPath *)path + changedChildren:(FCompoundWrite *)changedChildren + writeId:(NSInteger)writeId { + // Record pending merge + [self.pendingWriteTree addMergeAtPath:path + changedChildren:changedChildren + writeId:writeId]; + + FMerge *operation = + [[FMerge alloc] initWithSource:[FOperationSource userInstance] + path:path + children:changedChildren]; + return [self applyOperationToSyncPoints:operation]; +} + +/** + * Acknowledge a pending user write that was previously registered with + * applyUserOverwriteAtPath: or applyUserMergeAtPath: + * TODO[offline]: Taking a serverClock here is awkward, but server values are + * awkward. :-( + * @return NSArray of FEvent to raise. + */ +- (NSArray *)ackUserWriteWithWriteId:(NSInteger)writeId + revert:(BOOL)revert + persist:(BOOL)persist + clock:(id)clock { + FWriteRecord *write = [self.pendingWriteTree writeForId:writeId]; + BOOL needToReevaluate = [self.pendingWriteTree removeWriteId:writeId]; + if (write.visible) { + if (persist) { + [self.persistenceManager removeUserWrite:writeId]; + } + if (!revert) { + NSDictionary *serverValues = + [FServerValues generateServerValues:clock]; + if ([write isOverwrite]) { + id resolvedNode = + [FServerValues resolveDeferredValueSnapshot:write.overwrite + withServerValues:serverValues]; + [self.persistenceManager applyUserWrite:resolvedNode + toServerCacheAtPath:write.path]; + } else { + FCompoundWrite *resolvedMerge = [FServerValues + resolveDeferredValueCompoundWrite:write.merge + withServerValues:serverValues]; + [self.persistenceManager applyUserMerge:resolvedMerge + toServerCacheAtPath:write.path]; + } + } + } + if (!needToReevaluate) { + return @[]; + } else { + __block FImmutableTree *affectedTree = [FImmutableTree empty]; + if (write.isOverwrite) { + affectedTree = [affectedTree setValue:@YES atPath:[FPath empty]]; + } else { + [write.merge + enumerateWrites:^(FPath *path, id node, BOOL *stop) { + affectedTree = [affectedTree setValue:@YES atPath:path]; + }]; + } + FAckUserWrite *operation = + [[FAckUserWrite alloc] initWithPath:write.path + affectedTree:affectedTree + revert:revert]; + return [self applyOperationToSyncPoints:operation]; + } +} + +/** + * Apply new server data for the specified path + * @return NSArray of FEvent to raise. + */ +- (NSArray *)applyServerOverwriteAtPath:(FPath *)path + newData:(id)newData { + [self.persistenceManager + updateServerCacheWithNode:newData + forQuery:[FQuerySpec defaultQueryAtPath:path]]; + FOverwrite *operation = + [[FOverwrite alloc] initWithSource:[FOperationSource serverInstance] + path:path + snap:newData]; + return [self applyOperationToSyncPoints:operation]; +} + +/** + * Applied new server data to be merged in at the specified path + * @return NSArray of FEvent to raise. + */ +- (NSArray *)applyServerMergeAtPath:(FPath *)path + changedChildren:(FCompoundWrite *)changedChildren { + [self.persistenceManager updateServerCacheWithMerge:changedChildren + atPath:path]; + FMerge *operation = + [[FMerge alloc] initWithSource:[FOperationSource serverInstance] + path:path + children:changedChildren]; + return [self applyOperationToSyncPoints:operation]; +} + +- (NSArray *)applyServerRangeMergeAtPath:(FPath *)path + updates:(NSArray *)ranges { + FSyncPoint *syncPoint = [self.syncPointTree valueAtPath:path]; + if (syncPoint == nil) { + // Removed view, so it's safe to just ignore this update + return @[]; + } else { + // This could be for any "complete" (unfiltered) view, and if there is + // more than one complete view, they should each have the same cache so + // it doesn't matter which one we use. + FView *view = [syncPoint completeView]; + if (view != nil) { + id serverNode = [view serverCache]; + for (FRangeMerge *merge in ranges) { + serverNode = [merge applyToNode:serverNode]; + } + return [self applyServerOverwriteAtPath:path newData:serverNode]; + } else { + // There doesn't exist a view for this update, so it was removed and + // it's safe to just ignore this range merge + return @[]; + } + } +} + +/** + * Apply a listen complete to a path + * @return NSArray of FEvent to raise. + */ +- (NSArray *)applyListenCompleteAtPath:(FPath *)path { + [self.persistenceManager + setQueryComplete:[FQuerySpec defaultQueryAtPath:path]]; + id operation = [[FListenComplete alloc] + initWithSource:[FOperationSource serverInstance] + path:path]; + return [self applyOperationToSyncPoints:operation]; +} + +/** + * Apply a listen complete to a path + * @return NSArray of FEvent to raise. + */ +- (NSArray *)applyTaggedListenCompleteAtPath:(FPath *)path + tagId:(NSNumber *)tagId { + FQuerySpec *query = [self queryForTag:tagId]; + if (query != nil) { + [self.persistenceManager setQueryComplete:query]; + FPath *relativePath = [FPath relativePathFrom:query.path to:path]; + id op = [[FListenComplete alloc] + initWithSource:[FOperationSource forServerTaggedQuery:query.params] + path:relativePath]; + return [self applyTaggedOperation:op atPath:query.path]; + } else { + // We've already removed the query. No big deal, ignore the update. + return @[]; + } +} + +/** + * Internal helper method to apply tagged operation + */ +- (NSArray *)applyTaggedOperation:(id)operation + atPath:(FPath *)path { + FSyncPoint *syncPoint = [self.syncPointTree valueAtPath:path]; + NSAssert(syncPoint != nil, + @"Missing sync point for query tag that we're tracking."); + FWriteTreeRef *writesCache = + [self.pendingWriteTree childWritesForPath:path]; + return [syncPoint applyOperation:operation + writesCache:writesCache + serverCache:nil]; +} + +/** + * Apply new server data for the specified tagged query + * @return NSArray of FEvent to raise. + */ +- (NSArray *)applyTaggedQueryOverwriteAtPath:(FPath *)path + newData:(id)newData + tagId:(NSNumber *)tagId { + FQuerySpec *query = [self queryForTag:tagId]; + if (query != nil) { + FPath *relativePath = [FPath relativePathFrom:query.path to:path]; + FQuerySpec *queryToOverwrite = + relativePath.isEmpty ? query : [FQuerySpec defaultQueryAtPath:path]; + [self.persistenceManager updateServerCacheWithNode:newData + forQuery:queryToOverwrite]; + FOverwrite *operation = [[FOverwrite alloc] + initWithSource:[FOperationSource forServerTaggedQuery:query.params] + path:relativePath + snap:newData]; + return [self applyTaggedOperation:operation atPath:query.path]; + } else { + // Query must have been removed already + return @[]; + } +} + +/** + * Apply server data to be merged in for the specified tagged query + * @return NSArray of FEvent to raise. + */ +- (NSArray *)applyTaggedQueryMergeAtPath:(FPath *)path + changedChildren:(FCompoundWrite *)changedChildren + tagId:(NSNumber *)tagId { + FQuerySpec *query = [self queryForTag:tagId]; + if (query != nil) { + FPath *relativePath = [FPath relativePathFrom:query.path to:path]; + [self.persistenceManager updateServerCacheWithMerge:changedChildren + atPath:path]; + FMerge *operation = [[FMerge alloc] + initWithSource:[FOperationSource forServerTaggedQuery:query.params] + path:relativePath + children:changedChildren]; + return [self applyTaggedOperation:operation atPath:query.path]; + } else { + // We've already removed the query. No big deal, ignore the update. + return @[]; + } +} + +- (NSArray *)applyTaggedServerRangeMergeAtPath:(FPath *)path + updates:(NSArray *)ranges + tagId:(NSNumber *)tagId { + FQuerySpec *query = [self queryForTag:tagId]; + if (query != nil) { + NSAssert([path isEqual:query.path], + @"Tagged update path and query path must match"); + FSyncPoint *syncPoint = [self.syncPointTree valueAtPath:path]; + NSAssert(syncPoint != nil, + @"Missing sync point for query tag that we're tracking."); + FView *view = [syncPoint viewForQuery:query]; + NSAssert(view != nil, + @"Missing view for query tag that we're tracking"); + id serverNode = [view serverCache]; + for (FRangeMerge *merge in ranges) { + serverNode = [merge applyToNode:serverNode]; + } + return [self applyTaggedQueryOverwriteAtPath:path + newData:serverNode + tagId:tagId]; + } else { + // We've already removed the query. No big deal, ignore the update. + return @[]; + } +} + +/** + * Add an event callback for the specified query + * @return NSArray of FEvent to raise. + */ +- (NSArray *)addEventRegistration:(id)eventRegistration + forQuery:(FQuerySpec *)query { + FPath *path = query.path; + + __block BOOL foundAncestorDefaultView = NO; + [self.syncPointTree + forEachOnPath:query.path + whileBlock:^BOOL(FPath *pathToSyncPoint, FSyncPoint *syncPoint) { + foundAncestorDefaultView = + foundAncestorDefaultView || [syncPoint hasCompleteView]; + return !foundAncestorDefaultView; + }]; + + [self.persistenceManager setQueryActive:query]; + + FSyncPoint *syncPoint = [self.syncPointTree valueAtPath:path]; + if (syncPoint == nil) { + syncPoint = [[FSyncPoint alloc] + initWithPersistenceManager:self.persistenceManager]; + self.syncPointTree = [self.syncPointTree setValue:syncPoint + atPath:path]; + } + + BOOL viewAlreadyExists = [syncPoint viewExistsForQuery:query]; + NSArray *events; + if (viewAlreadyExists) { + events = [syncPoint addEventRegistration:eventRegistration + forExistingViewForQuery:query]; + } else { + if (![query loadsAllData]) { + // We need to track a tag for this query + NSAssert(self.queryToTagMap[query] == nil, + @"View does not exist, but we have a tag"); + NSNumber *tagId = [self.queryTagCounter getAndIncrement]; + self.queryToTagMap[query] = tagId; + self.tagToQueryMap[tagId] = query; + } + + FWriteTreeRef *writesCache = + [self.pendingWriteTree childWritesForPath:path]; + FCacheNode *serverCache = [self serverCacheForQuery:query]; + events = [syncPoint addEventRegistration:eventRegistration + forNonExistingViewForQuery:query + writesCache:writesCache + serverCache:serverCache]; + + // There was no view and no default listen + if (!foundAncestorDefaultView) { + FView *view = [syncPoint viewForQuery:query]; + NSMutableArray *mutableEvents = [events mutableCopy]; + [mutableEvents + addObjectsFromArray:[self setupListenerOnQuery:query + view:view]]; + events = mutableEvents; + } + } + + return events; +} + +- (FCacheNode *)serverCacheForQuery:(FQuerySpec *)query { + __block id serverCacheNode = nil; + + [self.syncPointTree + forEachOnPath:query.path + whileBlock:^BOOL(FPath *pathToSyncPoint, FSyncPoint *syncPoint) { + FPath *relativePath = [FPath relativePathFrom:pathToSyncPoint + to:query.path]; + serverCacheNode = + [syncPoint completeServerCacheAtPath:relativePath]; + return serverCacheNode == nil; + }]; + + FCacheNode *serverCache; + if (serverCacheNode != nil) { + FIndexedNode *indexed = + [FIndexedNode indexedNodeWithNode:serverCacheNode + index:query.index]; + serverCache = [[FCacheNode alloc] initWithIndexedNode:indexed + isFullyInitialized:YES + isFiltered:NO]; + } else { + FCacheNode *persistenceServerCache = + [self.persistenceManager serverCacheForQuery:query]; + if (persistenceServerCache.isFullyInitialized) { + serverCache = persistenceServerCache; + } else { + serverCacheNode = [FEmptyNode emptyNode]; + + FImmutableTree *subtree = + [self.syncPointTree subtreeAtPath:query.path]; + [subtree + forEachChild:^(NSString *childKey, FSyncPoint *childSyncPoint) { + id completeCache = + [childSyncPoint completeServerCacheAtPath:[FPath empty]]; + if (completeCache) { + serverCacheNode = + [serverCacheNode updateImmediateChild:childKey + withNewChild:completeCache]; + } + }]; + // Fill the node with any available children we have + [persistenceServerCache.node + enumerateChildrenUsingBlock:^(NSString *key, id node, + BOOL *stop) { + if (![serverCacheNode hasChild:key]) { + serverCacheNode = + [serverCacheNode updateImmediateChild:key + withNewChild:node]; + } + }]; + FIndexedNode *indexed = + [FIndexedNode indexedNodeWithNode:serverCacheNode + index:query.index]; + serverCache = [[FCacheNode alloc] initWithIndexedNode:indexed + isFullyInitialized:NO + isFiltered:NO]; + } + } + + return serverCache; +} + +/** + * Remove event callback(s). + * + * If query is the default query, we'll check all queries for the specified + * eventRegistration. If eventRegistration is null, we'll remove all callbacks + * for the specified query/queries. + * + * @param eventRegistration if nil, all callbacks are removed + * @param cancelError If provided, appropriate cancel events will be returned + * @return NSArray of FEvent to raise. + */ +- (NSArray *)removeEventRegistration:(id)eventRegistration + forQuery:(FQuerySpec *)query + cancelError:(NSError *)cancelError { + // Find the syncPoint first. Then deal with whether or not it has matching + // listeners + FPath *path = query.path; + FSyncPoint *maybeSyncPoint = [self.syncPointTree valueAtPath:path]; + NSArray *cancelEvents = @[]; + + // A removal on a default query affects all queries at that location. A + // removal on an indexed query, even one without other query constraints, + // does *not* affect all queries at that location. So this check must be for + // 'default', and not loadsAllData: + if (maybeSyncPoint && + ([query isDefault] || [maybeSyncPoint viewExistsForQuery:query])) { + FTupleRemovedQueriesEvents *removedAndEvents = + [maybeSyncPoint removeEventRegistration:eventRegistration + forQuery:query + cancelError:cancelError]; + if ([maybeSyncPoint isEmpty]) { + self.syncPointTree = [self.syncPointTree removeValueAtPath:path]; + } + NSArray *removed = removedAndEvents.removedQueries; + cancelEvents = removedAndEvents.cancelEvents; + + // We may have just removed one of many listeners and can short-circuit + // this whole process We may also not have removed a default listener, + // in which case all of the descendant listeners should already be + // properly set up. + // + // Since indexed queries can shadow if they don't have other query + // constraints, check for loadsAllData: instead of isDefault: + NSUInteger defaultQueryIndex = [removed + indexOfObjectPassingTest:^BOOL(FQuerySpec *q, NSUInteger idx, + BOOL *stop) { + return [q loadsAllData]; + }]; + BOOL removingDefault = defaultQueryIndex != NSNotFound; + [removed enumerateObjectsUsingBlock:^(FQuerySpec *query, NSUInteger idx, + BOOL *stop) { + [self.persistenceManager setQueryInactive:query]; + }]; + NSNumber *covered = [self.syncPointTree + findOnPath:path + andApplyBlock:^id(FPath *relativePath, + FSyncPoint *parentSyncPoint) { + return + [NSNumber numberWithBool:[parentSyncPoint hasCompleteView]]; + }]; + + if (removingDefault && ![covered boolValue]) { + FImmutableTree *subtree = [self.syncPointTree subtreeAtPath:path]; + // There are potentially child listeners. Determine what if any + // listens we need to send before executing the removal + if (![subtree isEmpty]) { + // We need to fold over our subtree and collect the listeners to + // send + NSArray *newViews = + [self collectDistinctViewsForSubTree:subtree]; + + // Ok, we've collected all the listens we need. Set them up. + [newViews enumerateObjectsUsingBlock:^( + FView *view, NSUInteger idx, BOOL *stop) { + FQuerySpec *newQuery = view.query; + FListenContainer *listenContainer = + [self createListenerForView:view]; + self.listenProvider.startListening( + [self queryForListening:newQuery], + [self tagForQuery:newQuery], listenContainer, + listenContainer.onComplete); + }]; + } else { + // There's nothing below us, so nothing we need to start + // listening on + } + } + + // If we removed anything and we're not covered by a higher up listen, + // we need to stop listening on this query. The above block has us + // covered in terms of making sure we're set up on listens lower in the + // tree. Also, note that if we have a cancelError, it's already been + // removed at the provider level. + if (![covered boolValue] && [removed count] > 0 && cancelError == nil) { + // If we removed a default, then we weren't listening on any of the + // other queries here. Just cancel the one default. Otherwise, we + // need to iterate through and cancel each individual query + if (removingDefault) { + // We don't tag default listeners + self.listenProvider.stopListening( + [self queryForListening:query], nil); + } else { + [removed + enumerateObjectsUsingBlock:^(FQuerySpec *queryToRemove, + NSUInteger idx, BOOL *stop) { + NSNumber *tagToRemove = + [self.queryToTagMap objectForKey:queryToRemove]; + self.listenProvider.stopListening( + [self queryForListening:queryToRemove], tagToRemove); + }]; + } + } + // Now, clear all the tags we're tracking for the removed listens. + [self removeTags:removed]; + } else { + // No-op, this listener must've been already removed + } + return cancelEvents; +} + +- (void)keepQuery:(FQuerySpec *)query synced:(BOOL)keepSynced { + // Only do something if we actually need to add/remove an event registration + if (keepSynced && ![self.keepSyncedQueries containsObject:query]) { + [self addEventRegistration:[FKeepSyncedEventRegistration instance] + forQuery:query]; + [self.keepSyncedQueries addObject:query]; + } else if (!keepSynced && [self.keepSyncedQueries containsObject:query]) { + [self removeEventRegistration:[FKeepSyncedEventRegistration instance] + forQuery:query + cancelError:nil]; + [self.keepSyncedQueries removeObject:query]; + } +} + +- (NSArray *)removeAllWrites { + [self.persistenceManager removeAllUserWrites]; + NSArray *removedWrites = [self.pendingWriteTree removeAllWrites]; + if (removedWrites.count > 0) { + FImmutableTree *affectedTree = + [[FImmutableTree empty] setValue:@YES atPath:[FPath empty]]; + return [self applyOperationToSyncPoints:[[FAckUserWrite alloc] + initWithPath:[FPath empty] + affectedTree:affectedTree + revert:YES]]; + } else { + return @[]; + } +} + +/** + * Returns a complete cache, if we have one, of the data at a particular path. + * The location must have a listener above it, but as this is only used by + * transaction code, that should always be the case anyways. + * + * Note: this method will *include* hidden writes from transaction with + * applyLocally set to false. + * @param path The path to the data we want + * @param writeIdsToExclude A specific set to be excluded + */ +- (id)calcCompleteEventCacheAtPath:(FPath *)path + excludeWriteIds:(NSArray *)writeIdsToExclude { + BOOL includeHiddenSets = YES; + FWriteTree *writeTree = self.pendingWriteTree; + id serverCache = [self.syncPointTree + findOnPath:path + andApplyBlock:^id(FPath *pathSoFar, FSyncPoint *syncPoint) { + FPath *relativePath = [FPath relativePathFrom:pathSoFar to:path]; + id serverCache = + [syncPoint completeServerCacheAtPath:relativePath]; + if (serverCache) { + return serverCache; + } else { + return nil; + } + }]; + return [writeTree calculateCompleteEventCacheAtPath:path + completeServerCache:serverCache + excludeWriteIds:writeIdsToExclude + includeHiddenWrites:includeHiddenSets]; +} + +#pragma mark - +#pragma mark Private Methods +/** + * This collapses multiple unfiltered views into a single view, since we only + * need a single listener for them. + * @return NSArray of FView + */ +- (NSArray *)collectDistinctViewsForSubTree:(FImmutableTree *)subtree { + return [subtree foldWithBlock:^NSArray *(FPath *relativePath, + FSyncPoint *maybeChildSyncPoint, + NSDictionary *childMap) { + if (maybeChildSyncPoint && [maybeChildSyncPoint hasCompleteView]) { + FView *completeView = [maybeChildSyncPoint completeView]; + return @[ completeView ]; + } else { + // No complete view here, flatten any deeper listens into an array + NSMutableArray *views = [[NSMutableArray alloc] init]; + if (maybeChildSyncPoint) { + views = [[maybeChildSyncPoint queryViews] mutableCopy]; + } + [childMap enumerateKeysAndObjectsUsingBlock:^( + NSString *childKey, NSArray *childViews, BOOL *stop) { + [views addObjectsFromArray:childViews]; + }]; + return views; + } + }]; +} + +/** + * @param queries NSArray of FQuerySpec + */ +- (void)removeTags:(NSArray *)queries { + [queries enumerateObjectsUsingBlock:^(FQuerySpec *removedQuery, + NSUInteger idx, BOOL *stop) { + if (![removedQuery loadsAllData]) { + // We should have a tag for this + NSNumber *removedQueryTag = self.queryToTagMap[removedQuery]; + [self.queryToTagMap removeObjectForKey:removedQuery]; + [self.tagToQueryMap removeObjectForKey:removedQueryTag]; + } + }]; +} + +- (FQuerySpec *)queryForListening:(FQuerySpec *)query { + if (query.loadsAllData && !query.isDefault) { + // We treat queries that load all data as default queries + return [FQuerySpec defaultQueryAtPath:query.path]; + } else { + return query; + } +} + +/** + * For a given new listen, manage the de-duplication of outstanding + * subscriptions. + * @return NSArray of FEvent events to support synchronous data sources + */ +- (NSArray *)setupListenerOnQuery:(FQuerySpec *)query view:(FView *)view { + FPath *path = query.path; + NSNumber *tagId = [self tagForQuery:query]; + FListenContainer *listenContainer = [self createListenerForView:view]; + + NSArray *events = self.listenProvider.startListening( + [self queryForListening:query], tagId, listenContainer, + listenContainer.onComplete); + + FImmutableTree *subtree = [self.syncPointTree subtreeAtPath:path]; + // The root of this subtree has our query. We're here because we definitely + // need to send a listen for that, but we may need to shadow other listens + // as well. + if (tagId != nil) { + NSAssert(![subtree.value hasCompleteView], + @"If we're adding a query, it shouldn't be shadowed"); + } else { + // Shadow everything at or below this location, this is a default + // listener. + NSArray *queriesToStop = + [subtree foldWithBlock:^id(FPath *relativePath, + FSyncPoint *maybeChildSyncPoint, + NSDictionary *childMap) { + if (![relativePath isEmpty] && maybeChildSyncPoint != nil && + [maybeChildSyncPoint hasCompleteView]) { + return @[ [maybeChildSyncPoint completeView].query ]; + } else { + // No default listener here, flatten any deeper queries into + // an array + NSMutableArray *queries = [[NSMutableArray alloc] init]; + if (maybeChildSyncPoint != nil) { + for (FView *view in [maybeChildSyncPoint queryViews]) { + [queries addObject:view.query]; + } + } + [childMap + enumerateKeysAndObjectsUsingBlock:^( + NSString *key, NSArray *childQueries, BOOL *stop) { + [queries addObjectsFromArray:childQueries]; + }]; + return queries; + } + }]; + for (FQuerySpec *queryToStop in queriesToStop) { + self.listenProvider.stopListening( + [self queryForListening:queryToStop], + [self tagForQuery:queryToStop]); + } + } + return events; +} + +- (FListenContainer *)createListenerForView:(FView *)view { + FQuerySpec *query = view.query; + NSNumber *tagId = [self tagForQuery:query]; + + FListenContainer *listenContainer = [[FListenContainer alloc] + initWithView:view + onComplete:^(NSString *status) { + if ([status isEqualToString:@"ok"]) { + if (tagId != nil) { + return [self applyTaggedListenCompleteAtPath:query.path + tagId:tagId]; + } else { + return [self applyListenCompleteAtPath:query.path]; + } + } else { + // If a listen failed, kill all of the listeners here, not just + // the one that triggered the error. Note that this may need to + // be scoped to just this listener if we change permissions on + // filtered children + NSError *error = [FUtilities errorForStatus:status + andReason:nil]; + FFWarn(@"I-RDB038012", @"Listener at %@ failed: %@", query.path, + status); + return [self removeEventRegistration:nil + forQuery:query + cancelError:error]; + } + }]; + + return listenContainer; +} + +/** + * @return The query associated with the given tag, if we have one + */ +- (FQuerySpec *)queryForTag:(NSNumber *)tagId { + return self.tagToQueryMap[tagId]; +} + +/** + * @return The tag associated with the given query + */ +- (NSNumber *)tagForQuery:(FQuerySpec *)query { + return self.queryToTagMap[query]; +} + +#pragma mark - +#pragma mark applyOperation Helpers + +/** +* A helper method that visits all descendant and ancestor SyncPoints, applying +the operation. +* +* NOTES: +* - Descendant SyncPoints will be visited first (since we raise events +depth-first). + +* - We call applyOperation: on each SyncPoint passing three things: +* 1. A version of the Operation that has been made relative to the SyncPoint +location. +* 2. A WriteTreeRef of any writes we have cached at the SyncPoint location. +* 3. A snapshot Node with cached server data, if we have it. + +* - We concatenate all of the events returned by each SyncPoint and return the +result. +* +* @return Array of FEvent +*/ +- (NSArray *)applyOperationToSyncPoints:(id)operation { + return [self applyOperationHelper:operation + syncPointTree:self.syncPointTree + serverCache:nil + writesCache:[self.pendingWriteTree + childWritesForPath:[FPath empty]]]; +} + +/** + * Recursive helper for applyOperationToSyncPoints_ + */ +- (NSArray *)applyOperationHelper:(id)operation + syncPointTree:(FImmutableTree *)syncPointTree + serverCache:(id)serverCache + writesCache:(FWriteTreeRef *)writesCache { + if ([operation.path isEmpty]) { + return [self applyOperationDescendantsHelper:operation + syncPointTree:syncPointTree + serverCache:serverCache + writesCache:writesCache]; + } else { + FSyncPoint *syncPoint = syncPointTree.value; + + // If we don't have cached server data, see if we can get it from this + // SyncPoint + if (serverCache == nil && syncPoint != nil) { + serverCache = [syncPoint completeServerCacheAtPath:[FPath empty]]; + } + + NSMutableArray *events = [[NSMutableArray alloc] init]; + NSString *childKey = [operation.path getFront]; + id childOperation = [operation operationForChild:childKey]; + FImmutableTree *childTree = [syncPointTree.children get:childKey]; + if (childTree != nil && childOperation != nil) { + id childServerCache = + serverCache ? [serverCache getImmediateChild:childKey] : nil; + FWriteTreeRef *childWritesCache = + [writesCache childWriteTreeRef:childKey]; + [events + addObjectsFromArray:[self + applyOperationHelper:childOperation + syncPointTree:childTree + serverCache:childServerCache + writesCache:childWritesCache]]; + } + + if (syncPoint) { + [events addObjectsFromArray:[syncPoint applyOperation:operation + writesCache:writesCache + serverCache:serverCache]]; + } + + return events; + } +} + +/** + * Recursive helper for applyOperationToSyncPoints: + */ +- (NSArray *)applyOperationDescendantsHelper:(id)operation + syncPointTree:(FImmutableTree *)syncPointTree + serverCache:(id)serverCache + writesCache:(FWriteTreeRef *)writesCache { + FSyncPoint *syncPoint = syncPointTree.value; + + // If we don't have cached server data, see if we can get it from this + // SyncPoint + id resolvedServerCache; + if (serverCache == nil & syncPoint != nil) { + resolvedServerCache = + [syncPoint completeServerCacheAtPath:[FPath empty]]; + } else { + resolvedServerCache = serverCache; + } + + NSMutableArray *events = [[NSMutableArray alloc] init]; + [syncPointTree.children enumerateKeysAndObjectsUsingBlock:^( + NSString *childKey, FImmutableTree *childTree, + BOOL *stop) { + id childServerCache = nil; + if (resolvedServerCache != nil) { + childServerCache = [resolvedServerCache getImmediateChild:childKey]; + } + FWriteTreeRef *childWritesCache = + [writesCache childWriteTreeRef:childKey]; + id childOperation = [operation operationForChild:childKey]; + if (childOperation != nil) { + [events addObjectsFromArray: + [self applyOperationDescendantsHelper:childOperation + syncPointTree:childTree + serverCache:childServerCache + writesCache:childWritesCache]]; + } + }]; + + if (syncPoint) { + [events + addObjectsFromArray:[syncPoint applyOperation:operation + writesCache:writesCache + serverCache:resolvedServerCache]]; + } + + return events; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FWriteRecord.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FWriteRecord.h new file mode 100644 index 0000000..435fb6a --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FWriteRecord.h @@ -0,0 +1,45 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FPath; +@class FCompoundWrite; +@protocol FNode; + +@interface FWriteRecord : NSObject + +- initWithPath:(FPath *)path + overwrite:(id)overwrite + writeId:(NSInteger)writeId + visible:(BOOL)isVisible; +- initWithPath:(FPath *)path + merge:(FCompoundWrite *)merge + writeId:(NSInteger)writeId; + +@property(nonatomic, readonly) NSInteger writeId; +@property(nonatomic, strong, readonly) FPath *path; +@property(nonatomic, strong, readonly) id overwrite; +/** + * Maps NSString -> id + */ +@property(nonatomic, strong, readonly) FCompoundWrite *merge; +@property(nonatomic, readonly) BOOL visible; + +- (BOOL)isMerge; +- (BOOL)isOverwrite; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FWriteRecord.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FWriteRecord.m new file mode 100644 index 0000000..be1816e --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FWriteRecord.m @@ -0,0 +1,139 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FWriteRecord.h" +#import "FCompoundWrite.h" +#import "FNode.h" +#import "FPath.h" + +@interface FWriteRecord () +@property(nonatomic, readwrite) NSInteger writeId; +@property(nonatomic, strong, readwrite) FPath *path; +@property(nonatomic, strong, readwrite) id overwrite; +@property(nonatomic, strong, readwrite) FCompoundWrite *merge; +@property(nonatomic, readwrite) BOOL visible; +@end + +@implementation FWriteRecord + +- (id)initWithPath:(FPath *)path + overwrite:(id)overwrite + writeId:(NSInteger)writeId + visible:(BOOL)isVisible { + self = [super init]; + if (self) { + self.path = path; + if (overwrite == nil) { + [NSException raise:NSInvalidArgumentException + format:@"Can't pass nil as overwrite parameter to an " + @"overwrite write record"]; + } + self.overwrite = overwrite; + self.merge = nil; + self.writeId = writeId; + self.visible = isVisible; + } + return self; +} + +- (id)initWithPath:(FPath *)path + merge:(FCompoundWrite *)merge + writeId:(NSInteger)writeId { + self = [super init]; + if (self) { + self.path = path; + if (merge == nil) { + [NSException raise:NSInvalidArgumentException + format:@"Can't pass nil as merge parameter to an merge " + @"write record"]; + } + self.overwrite = nil; + self.merge = merge; + self.writeId = writeId; + self.visible = YES; + } + return self; +} + +- (id)overwrite { + if (self->_overwrite == nil) { + [NSException raise:NSInvalidArgumentException + format:@"Can't get overwrite for merge write record!"]; + } + return self->_overwrite; +} + +- (FCompoundWrite *)compoundWrite { + if (self->_merge == nil) { + [NSException raise:NSInvalidArgumentException + format:@"Can't get merge for overwrite write record!"]; + } + return self->_merge; +} + +- (BOOL)isMerge { + return self->_merge != nil; +} + +- (BOOL)isOverwrite { + return self->_overwrite != nil; +} + +- (NSString *)description { + if (self.isOverwrite) { + return + [NSString stringWithFormat:@"FWriteRecord { writeId = %lu, path = " + @"%@, overwrite = %@, visible = %d }", + (unsigned long)self.writeId, self.path, + self.overwrite, self.visible]; + } else { + return [NSString + stringWithFormat: + @"FWriteRecord { writeId = %lu, path = %@, merge = %@ }", + (unsigned long)self.writeId, self.path, self.merge]; + } +} + +- (BOOL)isEqual:(id)object { + if (![object isKindOfClass:[self class]]) { + return NO; + } + FWriteRecord *other = (FWriteRecord *)object; + if (self->_writeId != other->_writeId) + return NO; + if (self->_path != other->_path && ![self->_path isEqual:other->_path]) + return NO; + if (self->_overwrite != other->_overwrite && + ![self->_overwrite isEqual:other->_overwrite]) + return NO; + if (self->_merge != other->_merge && ![self->_merge isEqual:other->_merge]) + return NO; + if (self->_visible != other->_visible) + return NO; + + return YES; +} + +- (NSUInteger)hash { + NSUInteger hash = self->_writeId * 17; + hash = hash * 31 + self->_path.hash; + hash = hash * 31 + self->_overwrite.hash; + hash = hash * 31 + self->_merge.hash; + hash = hash * 31 + ((self->_visible) ? 1 : 0); + return hash; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FWriteTree.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FWriteTree.h new file mode 100644 index 0000000..178946b --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FWriteTree.h @@ -0,0 +1,70 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FPath; +@protocol FNode; +@class FCompoundWrite; +@class FWriteTreeRef; +@class FChildrenNode; +@class FNamedNode; +@class FWriteRecord; +@protocol FIndex; +@class FCacheNode; + +@interface FWriteTree : NSObject + +- (FWriteTreeRef *)childWritesForPath:(FPath *)path; +- (void)addOverwriteAtPath:(FPath *)path + newData:(id)newData + writeId:(NSInteger)writeId + isVisible:(BOOL)visible; +- (void)addMergeAtPath:(FPath *)path + changedChildren:(FCompoundWrite *)changedChildren + writeId:(NSInteger)writeId; +- (BOOL)removeWriteId:(NSInteger)writeId; +- (NSArray *)removeAllWrites; +- (FWriteRecord *)writeForId:(NSInteger)writeId; + +- (id)calculateCompleteEventCacheAtPath:(FPath *)treePath + completeServerCache:(id)completeServerCache + excludeWriteIds:(NSArray *)writeIdsToExclude + includeHiddenWrites:(BOOL)includeHiddenWrites; + +- (id)calculateCompleteEventChildrenAtPath:(FPath *)treePath + completeServerChildren: + (id)completeServerChildren; + +- (id) + calculateEventCacheAfterServerOverwriteAtPath:(FPath *)treePath + childPath:(FPath *)childPath + existingEventSnap:(id)existingEventSnap + existingServerSnap:(id)existingServerSnap; + +- (id)calculateCompleteChildAtPath:(FPath *)treePath + childKey:(NSString *)childKey + cache:(FCacheNode *)existingServerCache; + +- (id)shadowingWriteAtPath:(FPath *)path; + +- (FNamedNode *)calculateNextNodeAfterPost:(FNamedNode *)post + atPath:(FPath *)path + completeServerData:(id)completeServerData + reverse:(BOOL)reverse + index:(id)index; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FWriteTree.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FWriteTree.m new file mode 100644 index 0000000..d8592c0 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FWriteTree.m @@ -0,0 +1,577 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FWriteTree.h" +#import "FCacheNode.h" +#import "FChildrenNode.h" +#import "FCompoundWrite.h" +#import "FEmptyNode.h" +#import "FImmutableTree.h" +#import "FIndex.h" +#import "FNamedNode.h" +#import "FNode.h" +#import "FPath.h" +#import "FWriteRecord.h" +#import "FWriteTreeRef.h" + +@interface FWriteTree () +/** + * A tree tracking the results of applying all visible writes. This does not + * include transactions with applyLocally=false or writes that are completely + * shadowed by other writes. Contains id as values. + */ +@property(nonatomic, strong) FCompoundWrite *visibleWrites; +/** + * A list of pending writes, regardless of visibility and shadowed-ness. Used to + * calcuate arbitrary sets of the changed data, such as hidden writes (from + * transactions) or changes with certain writes excluded (also used by + * transactions). Contains FWriteRecords. + */ +@property(nonatomic, strong) NSMutableArray *allWrites; +@property(nonatomic) NSInteger lastWriteId; +@end + +/** + * FWriteTree tracks all pending user-initiated writes and has methods to + * calcuate the result of merging them with underlying server data (to create + * "event cache" data). Pending writes are added with addOverwriteAtPath: and + * addMergeAtPath: and removed with removeWriteId:. + */ +@implementation FWriteTree + +@synthesize allWrites; +@synthesize lastWriteId; + +- (id)init { + self = [super init]; + if (self) { + self.visibleWrites = [FCompoundWrite emptyWrite]; + self.allWrites = [[NSMutableArray alloc] init]; + self.lastWriteId = -1; + } + return self; +} + +/** + * Create a new WriteTreeRef for the given path. For use with a new sync point + * at the given path. + */ +- (FWriteTreeRef *)childWritesForPath:(FPath *)path { + return [[FWriteTreeRef alloc] initWithPath:path writeTree:self]; +} + +/** + * Record a new overwrite from user code. + * @param visible Is set to false by some transactions. It should be excluded + * from event caches. + */ +- (void)addOverwriteAtPath:(FPath *)path + newData:(id)newData + writeId:(NSInteger)writeId + isVisible:(BOOL)visible { + NSAssert(writeId > self.lastWriteId, + @"Stacking an older write on top of a newer one"); + FWriteRecord *record = [[FWriteRecord alloc] initWithPath:path + overwrite:newData + writeId:writeId + visible:visible]; + [self.allWrites addObject:record]; + + if (visible) { + self.visibleWrites = [self.visibleWrites addWrite:newData atPath:path]; + } + + self.lastWriteId = writeId; +} + +/** + * Record a new merge from user code. + * @param changedChildren maps NSString -> id + */ +- (void)addMergeAtPath:(FPath *)path + changedChildren:(FCompoundWrite *)changedChildren + writeId:(NSInteger)writeId { + NSAssert(writeId > self.lastWriteId, + @"Stacking an older merge on top of newer one"); + FWriteRecord *record = [[FWriteRecord alloc] initWithPath:path + merge:changedChildren + writeId:writeId]; + [self.allWrites addObject:record]; + + self.visibleWrites = [self.visibleWrites addCompoundWrite:changedChildren + atPath:path]; + self.lastWriteId = writeId; +} + +- (FWriteRecord *)writeForId:(NSInteger)writeId { + NSUInteger index = [self.allWrites + indexOfObjectPassingTest:^BOOL(FWriteRecord *write, NSUInteger idx, + BOOL *stop) { + return write.writeId == writeId; + }]; + return (index == NSNotFound) ? nil : self.allWrites[index]; +} + +/** + * Remove a write (either an overwrite or merge) that has been successfully + * acknowledged by the server. Recalculates the tree if necessary. We return the + * path of the write and whether it may have been visible, meaning views need to + * reevaluate. + * + * @return YES if the write may have been visible (meaning we'll need to + * reevaluate / raise events as a result). + */ +- (BOOL)removeWriteId:(NSInteger)writeId { + NSUInteger index = [self.allWrites + indexOfObjectPassingTest:^BOOL(FWriteRecord *record, NSUInteger idx, + BOOL *stop) { + if (record.writeId == writeId) { + return YES; + } else { + return NO; + } + }]; + NSAssert(index != NSNotFound, + @"[FWriteTree removeWriteId:] called with nonexistent writeId."); + FWriteRecord *writeToRemove = self.allWrites[index]; + [self.allWrites removeObjectAtIndex:index]; + + BOOL removedWriteWasVisible = writeToRemove.visible; + BOOL removedWriteOverlapsWithOtherWrites = NO; + NSInteger i = [self.allWrites count] - 1; + + while (removedWriteWasVisible && i >= 0) { + FWriteRecord *currentWrite = [self.allWrites objectAtIndex:i]; + if (currentWrite.visible) { + if (i >= index && [self record:currentWrite + containsPath:writeToRemove.path]) { + // The removed write was completely shadowed by a subsequent + // write. + removedWriteWasVisible = NO; + } else if ([writeToRemove.path contains:currentWrite.path]) { + // Either we're covering some writes or they're covering part of + // us (depending on which came first). + removedWriteOverlapsWithOtherWrites = YES; + } + } + i--; + } + + if (!removedWriteWasVisible) { + return NO; + } else if (removedWriteOverlapsWithOtherWrites) { + // There's some shadowing going on. Just rebuild the visible writes from + // scratch. + [self resetTree]; + return YES; + } else { + // There's no shadowing. We can safely just remove the write(s) from + // visibleWrites. + if ([writeToRemove isOverwrite]) { + self.visibleWrites = + [self.visibleWrites removeWriteAtPath:writeToRemove.path]; + } else { + FCompoundWrite *merge = writeToRemove.merge; + [merge enumerateWrites:^(FPath *path, id node, BOOL *stop) { + self.visibleWrites = [self.visibleWrites + removeWriteAtPath:[writeToRemove.path child:path]]; + }]; + } + return YES; + } +} + +- (NSArray *)removeAllWrites { + NSArray *writes = self.allWrites; + self.visibleWrites = [FCompoundWrite emptyWrite]; + self.allWrites = [NSMutableArray array]; + return writes; +} + +/** + * @return A complete snapshot for the given path if there's visible write data + * at that path, else nil. No server data is considered. + */ +- (id)completeWriteDataAtPath:(FPath *)path { + return [self.visibleWrites completeNodeAtPath:path]; +} + +/** + * Given optional, underlying server data, and an optional set of constraints + * (exclude some sets, include hidden writes), attempt to calculate a complete + * snapshot for the given path + * @param includeHiddenWrites Defaults to false, whether or not to layer on + * writes with visible set to false + */ +- (id)calculateCompleteEventCacheAtPath:(FPath *)treePath + completeServerCache:(id)completeServerCache + excludeWriteIds:(NSArray *)writeIdsToExclude + includeHiddenWrites:(BOOL)includeHiddenWrites { + if (writeIdsToExclude == nil && !includeHiddenWrites) { + id shadowingNode = + [self.visibleWrites completeNodeAtPath:treePath]; + if (shadowingNode != nil) { + return shadowingNode; + } else { + // No cache here. Can't claim complete knowledge. + FCompoundWrite *subMerge = + [self.visibleWrites childCompoundWriteAtPath:treePath]; + if (subMerge.isEmpty) { + return completeServerCache; + } else if (completeServerCache == nil && + ![subMerge hasCompleteWriteAtPath:[FPath empty]]) { + // We wouldn't have a complete snapshot since there's no + // underlying data and no complete shadow + return nil; + } else { + id layeredCache = completeServerCache != nil + ? completeServerCache + : [FEmptyNode emptyNode]; + return [subMerge applyToNode:layeredCache]; + } + } + } else { + FCompoundWrite *merge = + [self.visibleWrites childCompoundWriteAtPath:treePath]; + if (!includeHiddenWrites && merge.isEmpty) { + return completeServerCache; + } else { + // If the server cache is null and we don't have a complete cache, + // we need to return nil + if (!includeHiddenWrites && completeServerCache == nil && + ![merge hasCompleteWriteAtPath:[FPath empty]]) { + return nil; + } else { + BOOL (^filter)(FWriteRecord *) = ^(FWriteRecord *record) { + return (BOOL)( + (record.visible || includeHiddenWrites) && + (writeIdsToExclude == nil || + ![writeIdsToExclude + containsObject: + [NSNumber numberWithInteger:record.writeId]]) && + ([record.path contains:treePath] || + [treePath contains:record.path])); + }; + FCompoundWrite *mergeAtPath = + [FWriteTree layerTreeFromWrites:self.allWrites + filter:filter + treeRoot:treePath]; + id layeredCache = completeServerCache + ? completeServerCache + : [FEmptyNode emptyNode]; + return [mergeAtPath applyToNode:layeredCache]; + } + } + } +} + +/** + * With optional, underlying server data, attempt to return a children node of + * children that we have complete data for. Used when creating new views, to + * pre-fill their complete event children snapshot. + */ +- (FChildrenNode *)calculateCompleteEventChildrenAtPath:(FPath *)treePath + completeServerChildren: + (id)completeServerChildren { + __block id completeChildren = [FEmptyNode emptyNode]; + id topLevelSet = [self.visibleWrites completeNodeAtPath:treePath]; + if (topLevelSet != nil) { + if (![topLevelSet isLeafNode]) { + // We're shadowing everything. Return the children. + FChildrenNode *topChildrenNode = topLevelSet; + [topChildrenNode enumerateChildrenUsingBlock:^( + NSString *key, id node, BOOL *stop) { + completeChildren = [completeChildren updateImmediateChild:key + withNewChild:node]; + }]; + } + return completeChildren; + } else { + // Layer any children we have on top of this + // We know we don't have a top-level set, so just enumerate existing + // children, and apply any updates + FCompoundWrite *merge = + [self.visibleWrites childCompoundWriteAtPath:treePath]; + [completeServerChildren enumerateChildrenUsingBlock:^( + NSString *key, id node, BOOL *stop) { + FCompoundWrite *childMerge = + [merge childCompoundWriteAtPath:[[FPath alloc] initWith:key]]; + id newChildNode = [childMerge applyToNode:node]; + completeChildren = + [completeChildren updateImmediateChild:key + withNewChild:newChildNode]; + }]; + // Add any complete children we have from the set. + for (FNamedNode *node in merge.completeChildren) { + completeChildren = + [completeChildren updateImmediateChild:node.name + withNewChild:node.node]; + } + return completeChildren; + } +} + +/** + * Given that the underlying server data has updated, determine what, if + * anything, needs to be applied to the event cache. + * + * Possibilities + * + * 1. No write are shadowing. Events should be raised, the snap to be applied + * comes from the server data. + * + * 2. Some write is completely shadowing. No events to be raised. + * + * 3. Is partially shadowed. Events .. + * + * Either existingEventSnap or existingServerSnap must exist. + */ +- (id)calculateEventCacheAfterServerOverwriteAtPath:(FPath *)treePath + childPath:(FPath *)childPath + existingEventSnap: + (id)existingEventSnap + existingServerSnap: + (id)existingServerSnap { + NSAssert(existingEventSnap != nil || existingServerSnap != nil, + @"Either existingEventSnap or existingServerSanp must exist."); + + FPath *path = [treePath child:childPath]; + if ([self.visibleWrites hasCompleteWriteAtPath:path]) { + // At this point we can probably guarantee that we're in case 2, meaning + // no events May need to check visibility while doing the + // findRootMostValueAndPath call + return nil; + } else { + // This could be more efficient if the serverNode + updates doesn't + // change the eventSnap However this is tricky to find out, since user + // updates don't necessary change the server snap, e.g. priority updates + // on empty nodes, or deep deletes. Another special case is if the + // server adds nodes, but doesn't change any existing writes. It is + // therefore not enough to only check if the updates change the + // serverNode. Maybe check if the merge tree contains these special + // cases and only do a full overwrite in that case? + FCompoundWrite *childMerge = + [self.visibleWrites childCompoundWriteAtPath:path]; + if (childMerge.isEmpty) { + // We're not shadowing at all. Case 1 + return [existingServerSnap getChild:childPath]; + } else { + return [childMerge + applyToNode:[existingServerSnap getChild:childPath]]; + } + } +} + +/** + * Returns a complete child for a given server snap after applying all user + * writes or nil if there is no complete child for this child key. + */ +- (id)calculateCompleteChildAtPath:(FPath *)treePath + childKey:(NSString *)childKey + cache:(FCacheNode *)existingServerCache { + FPath *path = [treePath childFromString:childKey]; + id shadowingNode = [self.visibleWrites completeNodeAtPath:path]; + if (shadowingNode != nil) { + return shadowingNode; + } else { + if ([existingServerCache isCompleteForChild:childKey]) { + FCompoundWrite *childMerge = + [self.visibleWrites childCompoundWriteAtPath:path]; + return [childMerge applyToNode:[existingServerCache.node + getImmediateChild:childKey]]; + } else { + return nil; + } + } +} + +/** + * Returns a node if there is a complete overwrite for this path. More + * specifically, if there is a write at a higher path, this will return the + * child of that write relative to the write and this path. Returns null if + * there is no write at this path. + */ +- (id)shadowingWriteAtPath:(FPath *)path { + return [self.visibleWrites completeNodeAtPath:path]; +} + +/** + * This method is used when processing child remove events on a query. If we + * can, we pull in children that were outside the window, but may now be in the + * window. + */ +- (FNamedNode *)calculateNextNodeAfterPost:(FNamedNode *)post + atPath:(FPath *)treePath + completeServerData:(id)completeServerData + reverse:(BOOL)reverse + index:(id)index { + __block id toIterate; + FCompoundWrite *merge = + [self.visibleWrites childCompoundWriteAtPath:treePath]; + id shadowingNode = [merge completeNodeAtPath:[FPath empty]]; + if (shadowingNode != nil) { + toIterate = shadowingNode; + } else if (completeServerData != nil) { + toIterate = [merge applyToNode:completeServerData]; + } else { + return nil; + } + + __block NSString *currentNextKey = nil; + __block id currentNextNode = nil; + [toIterate enumerateChildrenUsingBlock:^(NSString *key, id node, + BOOL *stop) { + if ([index compareKey:key + andNode:node + toOtherKey:post.name + andNode:post.node + reverse:reverse] > NSOrderedSame && + (!currentNextKey || [index compareKey:key + andNode:node + toOtherKey:currentNextKey + andNode:currentNextNode + reverse:reverse] < NSOrderedSame)) { + currentNextKey = key; + currentNextNode = node; + } + }]; + + if (currentNextKey != nil) { + return [FNamedNode nodeWithName:currentNextKey node:currentNextNode]; + } else { + return nil; + } +} + +#pragma mark - +#pragma mark Private Methods + +- (BOOL)record:(FWriteRecord *)record containsPath:(FPath *)path { + if ([record isOverwrite]) { + return [record.path contains:path]; + } else { + __block BOOL contains = NO; + [record.merge + enumerateWrites:^(FPath *childPath, id node, BOOL *stop) { + contains = [[record.path child:childPath] contains:path]; + *stop = contains; + }]; + return contains; + } +} + +/** + * Re-layer the writes and merges into a tree so we can efficiently calculate + * event snapshots + */ +- (void)resetTree { + self.visibleWrites = + [FWriteTree layerTreeFromWrites:self.allWrites + filter:[FWriteTree defaultFilter] + treeRoot:[FPath empty]]; + if ([self.allWrites count] > 0) { + FWriteRecord *lastRecord = self.allWrites[[self.allWrites count] - 1]; + self.lastWriteId = lastRecord.writeId; + } else { + self.lastWriteId = -1; + } +} + +/** + * The default filter used when constructing the tree. Keep everything that's + * visible. + */ ++ (BOOL (^)(FWriteRecord *record))defaultFilter { + static BOOL (^filter)(FWriteRecord *); + static dispatch_once_t filterToken; + dispatch_once(&filterToken, ^{ + filter = ^(FWriteRecord *record) { + return YES; + }; + }); + return filter; +} + +/** + * Static method. Given an array of WriteRecords, a filter for which ones to + * include, and a path, construct a merge at that path + * @return An FImmutableTree of ids. + */ ++ (FCompoundWrite *)layerTreeFromWrites:(NSArray *)writes + filter:(BOOL (^)(FWriteRecord *record))filter + treeRoot:(FPath *)treeRoot { + __block FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; + [writes enumerateObjectsUsingBlock:^(FWriteRecord *record, NSUInteger idx, + BOOL *stop) { + // Theory, a later set will either: + // a) abort a relevant transaction, so no need to worry about excluding it + // from calculating that transaction b) not be relevant to a transaction + // (separate branch), so again will not affect the data for that + // transaction + if (filter(record)) { + FPath *writePath = record.path; + if ([record isOverwrite]) { + if ([treeRoot contains:writePath]) { + FPath *relativePath = [FPath relativePathFrom:treeRoot + to:writePath]; + compoundWrite = [compoundWrite addWrite:record.overwrite + atPath:relativePath]; + } else if ([writePath contains:treeRoot]) { + id child = [record.overwrite + getChild:[FPath relativePathFrom:writePath to:treeRoot]]; + compoundWrite = [compoundWrite addWrite:child + atPath:[FPath empty]]; + } else { + // There is no overlap between root path and write path, + // ignore write + } + } else { + if ([treeRoot contains:writePath]) { + FPath *relativePath = [FPath relativePathFrom:treeRoot + to:writePath]; + compoundWrite = [compoundWrite addCompoundWrite:record.merge + atPath:relativePath]; + } else if ([writePath contains:treeRoot]) { + FPath *relativePath = [FPath relativePathFrom:writePath + to:treeRoot]; + if (relativePath.isEmpty) { + compoundWrite = + [compoundWrite addCompoundWrite:record.merge + atPath:[FPath empty]]; + } else { + id child = + [record.merge completeNodeAtPath:relativePath]; + if (child != nil) { + // There exists a child in this node that matches the + // root path + id deepNode = + [child getChild:[relativePath popFront]]; + compoundWrite = + [compoundWrite addWrite:deepNode + atPath:[FPath empty]]; + } + } + } else { + // There is no overlap between root path and write path, + // ignore write + } + } + } + }]; + return compoundWrite; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FWriteTreeRef.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FWriteTreeRef.h new file mode 100644 index 0000000..962ad5c --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FWriteTreeRef.h @@ -0,0 +1,57 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@protocol FNode; +@class FChildrenNode; +@class FPath; +@class FNamedNode; +@class FWriteRecord; +@class FWriteTree; +@protocol FIndex; +@class FCacheNode; + +@interface FWriteTreeRef : NSObject + +- (id)initWithPath:(FPath *)aPath writeTree:(FWriteTree *)tree; + +- (id)calculateCompleteEventCacheWithCompleteServerCache: + (id)completeServerCache; + +- (FChildrenNode *)calculateCompleteEventChildrenWithCompleteServerChildren: + (FChildrenNode *)completeServerChildren; + +- (id) + calculateEventCacheAfterServerOverwriteWithChildPath:(FPath *)childPath + existingEventSnap: + (id)existingEventSnap + existingServerSnap: + (id)existingServerSnap; + +- (id)shadowingWriteAtPath:(FPath *)path; + +- (FNamedNode *)calculateNextNodeAfterPost:(FNamedNode *)post + completeServerData:(id)completeServerData + reverse:(BOOL)reverse + index:(id)index; + +- (id)calculateCompleteChild:(NSString *)childKey + cache:(FCacheNode *)existingServerCache; + +- (FWriteTreeRef *)childWriteTreeRef:(NSString *)childKey; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FWriteTreeRef.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FWriteTreeRef.m new file mode 100644 index 0000000..809d1a9 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/FWriteTreeRef.m @@ -0,0 +1,159 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FWriteTreeRef.h" +#import "FCacheNode.h" +#import "FChildrenNode.h" +#import "FIndex.h" +#import "FNamedNode.h" +#import "FNode.h" +#import "FPath.h" +#import "FWriteRecord.h" +#import "FWriteTree.h" + +@interface FWriteTreeRef () +/** + * The path to this particular FWriteTreeRef. Used for calling methods on + * writeTree while exposing a simpler interface to callers. + */ +@property(nonatomic, strong) FPath *path; +/** + * A reference to the actual tree of the write data. All methods are + * pass-through to the tree, but with the appropriate path prefixed. + * + * This lets us make cheap references to points in the tree for sync points + * without having to copy and maintain all of the data. + */ +@property(nonatomic, strong) FWriteTree *writeTree; +@end + +/** + * A FWriteTreeRef wraps a FWriteTree and a FPath, for convenient access to a + * particular subtree. All the methods just proxy to the underlying FWriteTree. + */ +@implementation FWriteTreeRef +- (id)initWithPath:(FPath *)aPath writeTree:(FWriteTree *)tree { + self = [super init]; + if (self) { + self.path = aPath; + self.writeTree = tree; + } + return self; +} + +/** + * @return If possible, returns a complete event cache, using the underlying + * server data if possible. In addition, can be used to get a cache that + * includes hidden writes, and excludes arbitrary writes. Note that customizing + * the returned node can lead to a more expensive calculation. + */ +- (id)calculateCompleteEventCacheWithCompleteServerCache: + (id)completeServerCache { + return [self.writeTree calculateCompleteEventCacheAtPath:self.path + completeServerCache:completeServerCache + excludeWriteIds:nil + includeHiddenWrites:NO]; +} + +/** + * @return If possible, returns a children node containing all of the complete + * children we have data for. The returned data is a mix of the given server + * data and write data. + */ +- (FChildrenNode *)calculateCompleteEventChildrenWithCompleteServerChildren: + (id)completeServerChildren { + return [self.writeTree + calculateCompleteEventChildrenAtPath:self.path + completeServerChildren:completeServerChildren]; +} + +/** + * Given that either the underlying server data has updated or the outstanding + * writes have been updating, determine what, if anything, needs to be applied + * to the event cache. + * + * Possibilities: + * + * 1. No writes are shadowing. Events should be raised, the snap to be applied + * comes from the server data. + * + * 2. Some writes are completly shadowing. No events to be raised. + * + * 3. Is partially shadowed. Events should be raised. + * + * Either existingEventSnap or existingServerSnap must exist, this is validated + * via an assert. + */ +- (id) + calculateEventCacheAfterServerOverwriteWithChildPath:(FPath *)childPath + existingEventSnap: + (id)existingEventSnap + existingServerSnap: + (id)existingServerSnap { + return [self.writeTree + calculateEventCacheAfterServerOverwriteAtPath:self.path + childPath:childPath + existingEventSnap:existingEventSnap + existingServerSnap:existingServerSnap]; +} + +/** + * Returns a node if there is a complete overwrite for this path. More + * specifically, if there is a write at a higher path, this will return the + * child of that write relative to the write and this path. Returns nil if there + * is no write at this path. + */ +- (id)shadowingWriteAtPath:(FPath *)path { + return [self.writeTree shadowingWriteAtPath:[self.path child:path]]; +} + +/** + * This method is used when processing child remove events on a query. If we + * can, we pull in children that are outside the window, but may now be in the + * window. + */ +- (FNamedNode *)calculateNextNodeAfterPost:(FNamedNode *)post + completeServerData:(id)completeServerData + reverse:(BOOL)reverse + index:(id)index { + return [self.writeTree calculateNextNodeAfterPost:post + atPath:self.path + completeServerData:completeServerData + reverse:reverse + index:index]; +} + +/** + * Returns a complete child for a given server snap after applying all user + * writes or nil if there is no complete child for this child key. + */ +- (id)calculateCompleteChild:(NSString *)childKey + cache:(FCacheNode *)existingServerCache { + return [self.writeTree calculateCompleteChildAtPath:self.path + childKey:childKey + cache:existingServerCache]; +} + +/** + * @return a WriteTreeref for a child. + */ +- (FWriteTreeRef *)childWriteTreeRef:(NSString *)childKey { + return + [[FWriteTreeRef alloc] initWithPath:[self.path childFromString:childKey] + writeTree:self.writeTree]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Operation/FAckUserWrite.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Operation/FAckUserWrite.h new file mode 100644 index 0000000..97a23bf --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Operation/FAckUserWrite.h @@ -0,0 +1,37 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FOperation.h" + +@class FPath; +@class FOperationSource; +@class FImmutableTree; + +@interface FAckUserWrite : NSObject + +- initWithPath:(FPath *)operationPath + affectedTree:(FImmutableTree *)affectedTree + revert:(BOOL)shouldRevert; + +@property(nonatomic, strong, readonly) FOperationSource *source; +@property(nonatomic, readonly) FOperationType type; +@property(nonatomic, strong, readonly) FPath *path; +// A FImmutableTree, containing @YES for each affected path. Affected paths +// can't overlap. +@property(nonatomic, strong, readonly) FImmutableTree *affectedTree; +@property(nonatomic, readonly) BOOL revert; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Operation/FAckUserWrite.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Operation/FAckUserWrite.m new file mode 100644 index 0000000..2a1f287 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Operation/FAckUserWrite.m @@ -0,0 +1,66 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FAckUserWrite.h" +#import "FImmutableTree.h" +#import "FOperationSource.h" +#import "FPath.h" + +@implementation FAckUserWrite + +- (id)initWithPath:(FPath *)operationPath + affectedTree:(FImmutableTree *)tree + revert:(BOOL)shouldRevert { + self = [super init]; + if (self) { + self->_source = [FOperationSource userInstance]; + self->_type = FOperationTypeAckUserWrite; + self->_path = operationPath; + self->_affectedTree = tree; + self->_revert = shouldRevert; + } + return self; +} + +- (FAckUserWrite *)operationForChild:(NSString *)childKey { + if (![self.path isEmpty]) { + NSAssert([self.path.getFront isEqualToString:childKey], + @"operationForChild called for unrelated child."); + return [[FAckUserWrite alloc] initWithPath:[self.path popFront] + affectedTree:self.affectedTree + revert:self.revert]; + } else if (self.affectedTree.value != nil) { + NSAssert(self.affectedTree.children.isEmpty, + @"affectedTree should not have overlapping affected paths."); + // All child locations are affected as well; just return same operation. + return self; + } else { + FImmutableTree *childTree = + [self.affectedTree subtreeAtPath:[[FPath alloc] initWith:childKey]]; + return [[FAckUserWrite alloc] initWithPath:[FPath empty] + affectedTree:childTree + revert:self.revert]; + } +} + +- (NSString *)description { + return + [NSString stringWithFormat: + @"FAckUserWrite { path=%@, revert=%d, affectedTree=%@ }", + self.path, self.revert, self.affectedTree]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Operation/FMerge.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Operation/FMerge.h new file mode 100644 index 0000000..56fbec9 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Operation/FMerge.h @@ -0,0 +1,32 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FOperation.h" + +@class FCompoundWrite; + +@interface FMerge : NSObject + +- (id)initWithSource:(FOperationSource *)aSource + path:(FPath *)aPath + children:(FCompoundWrite *)children; + +@property(nonatomic, strong, readonly) FOperationSource *source; +@property(nonatomic, readonly) FOperationType type; +@property(nonatomic, strong, readonly) FPath *path; +@property(nonatomic, strong, readonly) FCompoundWrite *children; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Operation/FMerge.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Operation/FMerge.m new file mode 100644 index 0000000..c344755 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Operation/FMerge.m @@ -0,0 +1,85 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FMerge.h" +#import "FCompoundWrite.h" +#import "FNode.h" +#import "FOperationSource.h" +#import "FOverwrite.h" +#import "FPath.h" + +@interface FMerge () +@property(nonatomic, strong, readwrite) FOperationSource *source; +@property(nonatomic, readwrite) FOperationType type; +@property(nonatomic, strong, readwrite) FPath *path; +@property(nonatomic, strong) FCompoundWrite *children; +@end + +@implementation FMerge + +@synthesize source; +@synthesize type; +@synthesize path; +@synthesize children; + +- (id)initWithSource:(FOperationSource *)aSource + path:(FPath *)aPath + children:(FCompoundWrite *)someChildren { + self = [super init]; + if (self) { + self.source = aSource; + self.type = FOperationTypeMerge; + self.path = aPath; + self.children = someChildren; + } + return self; +} + +- (id)operationForChild:(NSString *)childKey { + if ([self.path isEmpty]) { + FCompoundWrite *childTree = [self.children + childCompoundWriteAtPath:[[FPath alloc] initWith:childKey]]; + if (childTree.isEmpty) { + return nil; + } else if (childTree.rootWrite != nil) { + // We have a snapshot for the child in question. This becomes an + // overwrite of the child. + return [[FOverwrite alloc] initWithSource:self.source + path:[FPath empty] + snap:childTree.rootWrite]; + } else { + // This is a merge at a deeper level + return [[FMerge alloc] initWithSource:self.source + path:[FPath empty] + children:childTree]; + } + } else { + NSAssert( + [self.path.getFront isEqualToString:childKey], + @"Can't get a merge for a child not on the path of the operation"); + return [[FMerge alloc] initWithSource:self.source + path:[self.path popFront] + children:self.children]; + } +} + +- (NSString *)description { + return + [NSString stringWithFormat:@"FMerge { path=%@, soruce=%@ children=%@}", + self.path, self.source, self.children]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Operation/FOperation.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Operation/FOperation.h new file mode 100644 index 0000000..41f6054 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Operation/FOperation.h @@ -0,0 +1,34 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FOperationSource; +@class FPath; + +typedef NS_ENUM(NSInteger, FOperationType) { + FOperationTypeOverwrite = 0, + FOperationTypeMerge = 1, + FOperationTypeAckUserWrite = 2, + FOperationTypeListenComplete = 3 +}; + +@protocol FOperation +@property(nonatomic, strong, readonly) FOperationSource *source; +@property(nonatomic, readonly) FOperationType type; +@property(nonatomic, strong, readonly) FPath *path; +- (id)operationForChild:(NSString *)childKey; +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Operation/FOperationSource.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Operation/FOperationSource.h new file mode 100644 index 0000000..747487b --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Operation/FOperationSource.h @@ -0,0 +1,37 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FQueryParams; + +@interface FOperationSource : NSObject + +@property(nonatomic, readonly) BOOL fromUser; +@property(nonatomic, readonly) BOOL fromServer; +@property(nonatomic, readonly) BOOL isTagged; +@property(nonatomic, strong, readonly) FQueryParams *queryParams; + +- initWithFromUser:(BOOL)isFromUser + fromServer:(BOOL)isFromServer + queryParams:(FQueryParams *)params + tagged:(BOOL)isTagged; + ++ (FOperationSource *)userInstance; ++ (FOperationSource *)serverInstance; ++ (FOperationSource *)forServerTaggedQuery:(FQueryParams *)params; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Operation/FOperationSource.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Operation/FOperationSource.m new file mode 100644 index 0000000..92e3db8 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Operation/FOperationSource.m @@ -0,0 +1,86 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FOperationSource.h" +#import "FPath.h" +#import "FQueryParams.h" + +@interface FOperationSource () +@property(nonatomic, readwrite) BOOL fromUser; +@property(nonatomic, readwrite) BOOL fromServer; +@property(nonatomic, readwrite) BOOL isTagged; +@property(nonatomic, strong, readwrite) FQueryParams *queryParams; +@end + +@implementation FOperationSource + +@synthesize fromUser; +@synthesize fromServer; +@synthesize queryParams; + +- (id)initWithFromUser:(BOOL)isFromUser + fromServer:(BOOL)isFromServer + queryParams:(FQueryParams *)params + tagged:(BOOL)tagged { + self = [super init]; + if (self) { + self.fromUser = isFromUser; + self.fromServer = isFromServer; + self.queryParams = params; + self.isTagged = tagged; + } + return self; +} + ++ (FOperationSource *)userInstance { + static FOperationSource *user = nil; + static dispatch_once_t userToken; + dispatch_once(&userToken, ^{ + user = [[FOperationSource alloc] initWithFromUser:YES + fromServer:NO + queryParams:nil + tagged:NO]; + }); + return user; +} + ++ (FOperationSource *)serverInstance { + static FOperationSource *server = nil; + static dispatch_once_t serverToken; + dispatch_once(&serverToken, ^{ + server = [[FOperationSource alloc] initWithFromUser:NO + fromServer:YES + queryParams:nil + tagged:NO]; + }); + return server; +} + ++ (FOperationSource *)forServerTaggedQuery:(FQueryParams *)params { + return [[FOperationSource alloc] initWithFromUser:NO + fromServer:YES + queryParams:params + tagged:YES]; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"FOperationSource { fromUser=%d, " + @"fromServer=%d, queryId=%@, tagged=%d }", + self.fromUser, self.fromServer, + self.queryParams, self.isTagged]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Operation/FOverwrite.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Operation/FOverwrite.h new file mode 100644 index 0000000..7d738ac --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Operation/FOverwrite.h @@ -0,0 +1,32 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FOperation.h" + +@protocol FNode; + +@interface FOverwrite : NSObject + +- (id)initWithSource:(FOperationSource *)aSource + path:(FPath *)aPath + snap:(id)aSnap; + +@property(nonatomic, strong, readonly) FOperationSource *source; +@property(nonatomic, readonly) FOperationType type; +@property(nonatomic, strong, readonly) FPath *path; +@property(nonatomic, strong, readonly) id snap; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Operation/FOverwrite.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Operation/FOverwrite.m new file mode 100644 index 0000000..ad4daec --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Operation/FOverwrite.m @@ -0,0 +1,67 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FOverwrite.h" +#import "FNode.h" +#import "FOperationSource.h" + +@interface FOverwrite () +@property(nonatomic, strong, readwrite) FOperationSource *source; +@property(nonatomic, readwrite) FOperationType type; +@property(nonatomic, strong, readwrite) FPath *path; +@property(nonatomic, strong) id snap; +@end + +@implementation FOverwrite + +@synthesize source; +@synthesize type; +@synthesize path; +@synthesize snap; + +- (id)initWithSource:(FOperationSource *)aSource + path:(FPath *)aPath + snap:(id)aSnap { + self = [super init]; + if (self) { + self.source = aSource; + self.type = FOperationTypeOverwrite; + self.path = aPath; + self.snap = aSnap; + } + return self; +} + +- (FOverwrite *)operationForChild:(NSString *)childKey { + if ([self.path isEmpty]) { + return [[FOverwrite alloc] + initWithSource:self.source + path:[FPath empty] + snap:[self.snap getImmediateChild:childKey]]; + } else { + return [[FOverwrite alloc] initWithSource:self.source + path:[self.path popFront] + snap:self.snap]; + } +} + +- (NSString *)description { + return [NSString + stringWithFormat:@"FOverwrite { path=%@, source=%@, snapshot=%@ }", + self.path, self.source, self.snap]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FIRRetryHelper.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FIRRetryHelper.h new file mode 100644 index 0000000..a63d0d8 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FIRRetryHelper.h @@ -0,0 +1,33 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@interface FIRRetryHelper : NSObject + +- (instancetype)initWithDispatchQueue:(dispatch_queue_t)dispatchQueue + minRetryDelayAfterFailure:(NSTimeInterval)minRetryDelayAfterFailure + maxRetryDelay:(NSTimeInterval)maxRetryDelay + retryExponent:(double)retryExponent + jitterFactor:(double)jitterFactor; + +- (void)retry:(void (^)(void))block; + +- (void)cancel; + +- (void)signalSuccess; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FIRRetryHelper.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FIRRetryHelper.m new file mode 100644 index 0000000..ef24d12 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FIRRetryHelper.m @@ -0,0 +1,140 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRRetryHelper.h" +#import "FUtilities.h" +#import + +@interface FIRRetryHelperTask : NSObject + +@property(nonatomic, strong) void (^block)(void); + +@end + +@implementation FIRRetryHelperTask + +- (instancetype)initWithBlock:(void (^)(void))block { + self = [super init]; + if (self != nil) { + self->_block = [block copy]; + } + return self; +} + +- (BOOL)isCanceled { + return self.block == nil; +} + +- (void)cancel { + self.block = nil; +} + +- (void)execute { + if (self.block) { + self.block(); + } +} + +@end + +@interface FIRRetryHelper () + +@property(nonatomic, strong) dispatch_queue_t dispatchQueue; +@property(nonatomic) NSTimeInterval minRetryDelayAfterFailure; +@property(nonatomic) NSTimeInterval maxRetryDelay; +@property(nonatomic) double retryExponent; +@property(nonatomic) double jitterFactor; + +@property(nonatomic) BOOL lastWasSuccess; +@property(nonatomic) NSTimeInterval currentRetryDelay; + +@property(nonatomic, strong) FIRRetryHelperTask *scheduledRetry; + +@end + +@implementation FIRRetryHelper + +- (instancetype)initWithDispatchQueue:(dispatch_queue_t)dispatchQueue + minRetryDelayAfterFailure:(NSTimeInterval)minRetryDelayAfterFailure + maxRetryDelay:(NSTimeInterval)maxRetryDelay + retryExponent:(double)retryExponent + jitterFactor:(double)jitterFactor { + self = [super init]; + if (self != nil) { + self->_dispatchQueue = dispatchQueue; + self->_minRetryDelayAfterFailure = minRetryDelayAfterFailure; + self->_maxRetryDelay = maxRetryDelay; + self->_retryExponent = retryExponent; + self->_jitterFactor = jitterFactor; + self->_lastWasSuccess = YES; + } + return self; +} + +- (void)retry:(void (^)(void))block { + if (self.scheduledRetry != nil) { + FFLog(@"I-RDB054001", @"Canceling existing retry attempt"); + [self.scheduledRetry cancel]; + self.scheduledRetry = nil; + } + + NSTimeInterval delay; + if (self.lastWasSuccess) { + delay = 0; + } else { + if (self.currentRetryDelay == 0) { + self.currentRetryDelay = self.minRetryDelayAfterFailure; + } else { + NSTimeInterval newDelay = + (self.currentRetryDelay * self.retryExponent); + self.currentRetryDelay = MIN(newDelay, self.maxRetryDelay); + } + + delay = ((1 - self.jitterFactor) * self.currentRetryDelay) + + (self.jitterFactor * self.currentRetryDelay * + [FUtilities randomDouble]); + FFLog(@"I-RDB054002", @"Scheduling retry in %fs", delay); + } + self.lastWasSuccess = NO; + FIRRetryHelperTask *task = [[FIRRetryHelperTask alloc] initWithBlock:block]; + self.scheduledRetry = task; + dispatch_time_t popTime = + dispatch_time(DISPATCH_TIME_NOW, (long long)(delay * NSEC_PER_SEC)); + dispatch_after(popTime, self.dispatchQueue, ^{ + if (![task isCanceled]) { + self.scheduledRetry = nil; + [task execute]; + } + }); +} + +- (void)signalSuccess { + self.lastWasSuccess = YES; + self.currentRetryDelay = 0; +} + +- (void)cancel { + if (self.scheduledRetry != nil) { + FFLog(@"I-RDB054003", @"Canceling existing retry attempt"); + [self.scheduledRetry cancel]; + self.scheduledRetry = nil; + } else { + FFLog(@"I-RDB054004", @"No existing retry attempt to cancel"); + } + self.currentRetryDelay = 0; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FImmutableTree.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FImmutableTree.h new file mode 100644 index 0000000..e6d4961 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FImmutableTree.h @@ -0,0 +1,59 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FImmutableSortedDictionary.h" +#import "FPath.h" +#import "FTuplePathValue.h" + +@interface FImmutableTree : NSObject + +- (id)initWithValue:(id)aValue; +- (id)initWithValue:(id)aValue + children:(FImmutableSortedDictionary *)childrenMap; + ++ (FImmutableTree *)empty; +- (BOOL)isEmpty; + +- (FTuplePathValue *)findRootMostMatchingPath:(FPath *)relativePath + predicate:(BOOL (^)(id))predicate; +- (FTuplePathValue *)findRootMostValueAndPath:(FPath *)relativePath; +- (FImmutableTree *)subtreeAtPath:(FPath *)relativePath; +- (FImmutableTree *)setValue:(id)newValue atPath:(FPath *)relativePath; +- (FImmutableTree *)removeValueAtPath:(FPath *)relativePath; +- (id)valueAtPath:(FPath *)relativePath; +- (id)rootMostValueOnPath:(FPath *)path; +- (id)rootMostValueOnPath:(FPath *)path matching:(BOOL (^)(id))predicate; +- (id)leafMostValueOnPath:(FPath *)path; +- (id)leafMostValueOnPath:(FPath *)relativePath + matching:(BOOL (^)(id))predicate; +- (BOOL)containsValueMatching:(BOOL (^)(id))predicate; +- (FImmutableTree *)setTree:(FImmutableTree *)newTree + atPath:(FPath *)relativePath; +- (id)foldWithBlock:(id (^)(FPath *path, id value, + NSDictionary *foldedChildren))block; +- (id)findOnPath:(FPath *)path + andApplyBlock:(id (^)(FPath *path, id value))block; +- (FPath *)forEachOnPath:(FPath *)path + whileBlock:(BOOL (^)(FPath *path, id value))block; +- (FImmutableTree *)forEachOnPath:(FPath *)path + performBlock:(void (^)(FPath *path, id value))block; +- (void)forEach:(void (^)(FPath *path, id value))block; +- (void)forEachChild:(void (^)(NSString *childKey, id childValue))block; + +@property(nonatomic, strong, readonly) id value; +@property(nonatomic, strong, readonly) FImmutableSortedDictionary *children; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FImmutableTree.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FImmutableTree.m new file mode 100644 index 0000000..34e62ae --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FImmutableTree.m @@ -0,0 +1,486 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FImmutableTree.h" +#import "FImmutableSortedDictionary.h" +#import "FPath.h" +#import "FUtilities.h" + +@interface FImmutableTree () +@property(nonatomic, strong, readwrite) id value; +/** + * Maps NSString -> FImmutableTree, where is type of value. + */ +@property(nonatomic, strong, readwrite) FImmutableSortedDictionary *children; +@end + +@implementation FImmutableTree +@synthesize value; +@synthesize children; + +- (id)initWithValue:(id)aValue { + self = [super init]; + if (self) { + self.value = aValue; + self.children = [FImmutableTree emptyChildren]; + } + return self; +} + +- (id)initWithValue:(id)aValue + children:(FImmutableSortedDictionary *)childrenMap { + self = [super init]; + if (self) { + self.value = aValue; + self.children = childrenMap; + } + return self; +} + ++ (FImmutableSortedDictionary *)emptyChildren { + static dispatch_once_t emptyChildrenToken; + static FImmutableSortedDictionary *emptyChildren; + dispatch_once(&emptyChildrenToken, ^{ + emptyChildren = [FImmutableSortedDictionary + dictionaryWithComparator:[FUtilities stringComparator]]; + }); + return emptyChildren; +} + ++ (FImmutableTree *)empty { + static dispatch_once_t emptyImmutableTreeToken; + static FImmutableTree *emptyTree = nil; + dispatch_once(&emptyImmutableTreeToken, ^{ + emptyTree = [[FImmutableTree alloc] initWithValue:nil]; + }); + return emptyTree; +} + +- (BOOL)isEmpty { + return self.value == nil && [self.children isEmpty]; +} + +/** + * Given a path and a predicate, return the first node and the path to that node + * where the predicate returns true + * // TODO Do a perf test. If we're creating a bunch of FTuplePathValue objects + * on the way back out, it may be better to pass down a pathSoFar FPath + */ +- (FTuplePathValue *)findRootMostMatchingPath:(FPath *)relativePath + predicate:(BOOL (^)(id value))predicate { + if (self.value != nil && predicate(self.value)) { + return [[FTuplePathValue alloc] initWithPath:[FPath empty] + value:self.value]; + } else { + if ([relativePath isEmpty]) { + return nil; + } else { + NSString *front = [relativePath getFront]; + FImmutableTree *child = [self.children get:front]; + if (child != nil) { + FTuplePathValue *childExistingPathAndValue = + [child findRootMostMatchingPath:[relativePath popFront] + predicate:predicate]; + if (childExistingPathAndValue != nil) { + FPath *fullPath = [[[FPath alloc] initWith:front] + child:childExistingPathAndValue.path]; + return [[FTuplePathValue alloc] + initWithPath:fullPath + value:childExistingPathAndValue.value]; + } else { + return nil; + } + } else { + // No child matching path + return nil; + } + } + } +} + +/** + * Find, if it exists, the shortest subpath of the given path that points a + * defined value in the tree + */ +- (FTuplePathValue *)findRootMostValueAndPath:(FPath *)relativePath { + return [self findRootMostMatchingPath:relativePath + predicate:^BOOL(__unsafe_unretained id value) { + return YES; + }]; +} + +- (id)rootMostValueOnPath:(FPath *)path { + return [self rootMostValueOnPath:path + matching:^BOOL(id value) { + return YES; + }]; +} + +- (id)rootMostValueOnPath:(FPath *)path matching:(BOOL (^)(id))predicate { + if (self.value != nil && predicate(self.value)) { + return self.value; + } else if (path.isEmpty) { + return nil; + } else { + return [[self.children get:path.getFront] + rootMostValueOnPath:[path popFront] + matching:predicate]; + } +} + +- (id)leafMostValueOnPath:(FPath *)path { + return [self leafMostValueOnPath:path + matching:^BOOL(id value) { + return YES; + }]; +} + +- (id)leafMostValueOnPath:(FPath *)relativePath + matching:(BOOL (^)(id))predicate { + __block id currentValue = self.value; + __block FImmutableTree *currentTree = self; + [relativePath enumerateComponentsUsingBlock:^(NSString *key, BOOL *stop) { + currentTree = [currentTree.children get:key]; + if (currentTree == nil) { + *stop = YES; + } else { + id treeValue = currentTree.value; + if (treeValue != nil && predicate(treeValue)) { + currentValue = treeValue; + } + } + }]; + return currentValue; +} + +- (BOOL)containsValueMatching:(BOOL (^)(id))predicate { + if (self.value != nil && predicate(self.value)) { + return YES; + } else { + __block BOOL found = NO; + [self.children enumerateKeysAndObjectsUsingBlock:^( + NSString *key, FImmutableTree *subtree, BOOL *stop) { + found = [subtree containsValueMatching:predicate]; + if (found) + *stop = YES; + }]; + return found; + } +} + +- (FImmutableTree *)subtreeAtPath:(FPath *)relativePath { + if ([relativePath isEmpty]) { + return self; + } else { + NSString *front = [relativePath getFront]; + FImmutableTree *childTree = [self.children get:front]; + if (childTree != nil) { + return [childTree subtreeAtPath:[relativePath popFront]]; + } else { + return [FImmutableTree empty]; + } + } +} + +/** + * Sets a value at the specified path + */ +- (FImmutableTree *)setValue:(id)newValue atPath:(FPath *)relativePath { + if ([relativePath isEmpty]) { + return [[FImmutableTree alloc] initWithValue:newValue + children:self.children]; + } else { + NSString *front = [relativePath getFront]; + FImmutableTree *child = [self.children get:front]; + if (child == nil) { + child = [FImmutableTree empty]; + } + FImmutableTree *newChild = [child setValue:newValue + atPath:[relativePath popFront]]; + FImmutableSortedDictionary *newChildren = + [self.children insertKey:front withValue:newChild]; + return [[FImmutableTree alloc] initWithValue:self.value + children:newChildren]; + } +} + +/** + * Remove the value at the specified path + */ +- (FImmutableTree *)removeValueAtPath:(FPath *)relativePath { + if ([relativePath isEmpty]) { + if ([self.children isEmpty]) { + return [FImmutableTree empty]; + } else { + return [[FImmutableTree alloc] initWithValue:nil + children:self.children]; + } + } else { + NSString *front = [relativePath getFront]; + FImmutableTree *child = [self.children get:front]; + if (child) { + FImmutableTree *newChild = + [child removeValueAtPath:[relativePath popFront]]; + FImmutableSortedDictionary *newChildren; + if ([newChild isEmpty]) { + newChildren = [self.children removeKey:front]; + } else { + newChildren = [self.children insertKey:front + withValue:newChild]; + } + if (self.value == nil && [newChildren isEmpty]) { + return [FImmutableTree empty]; + } else { + return [[FImmutableTree alloc] initWithValue:self.value + children:newChildren]; + } + } else { + return self; + } + } +} + +/** + * Gets a value from the tree + */ +- (id)valueAtPath:(FPath *)relativePath { + if ([relativePath isEmpty]) { + return self.value; + } else { + NSString *front = [relativePath getFront]; + FImmutableTree *child = [self.children get:front]; + if (child) { + return [child valueAtPath:[relativePath popFront]]; + } else { + return nil; + } + } +} + +/** + * Replaces the subtree at the specified path with the given new tree + */ +- (FImmutableTree *)setTree:(FImmutableTree *)newTree + atPath:(FPath *)relativePath { + if ([relativePath isEmpty]) { + return newTree; + } else { + NSString *front = [relativePath getFront]; + FImmutableTree *child = [self.children get:front]; + if (child == nil) { + child = [FImmutableTree empty]; + } + FImmutableTree *newChild = [child setTree:newTree + atPath:[relativePath popFront]]; + FImmutableSortedDictionary *newChildren; + if ([newChild isEmpty]) { + newChildren = [self.children removeKey:front]; + } else { + newChildren = [self.children insertKey:front withValue:newChild]; + } + return [[FImmutableTree alloc] initWithValue:self.value + children:newChildren]; + } +} + +/** + * Performs a depth first fold on this tree. Transforms a tree into a single + * value, given a function that operates on the path to a node, an optional + * current value, and a map of the child names to folded subtrees + */ +- (id)foldWithBlock:(id (^)(FPath *path, id value, + NSDictionary *foldedChildren))block { + return [self foldWithPathSoFar:[FPath empty] withBlock:block]; +} + +/** + * Recursive helper for public facing foldWithBlock: method + */ +- (id)foldWithPathSoFar:(FPath *)pathSoFar + withBlock:(id (^)(FPath *path, id value, + NSDictionary *foldedChildren))block { + __block NSMutableDictionary *accum = [[NSMutableDictionary alloc] init]; + [self.children + enumerateKeysAndObjectsUsingBlock:^( + NSString *childKey, FImmutableTree *childTree, BOOL *stop) { + accum[childKey] = + [childTree foldWithPathSoFar:[pathSoFar childFromString:childKey] + withBlock:block]; + }]; + return block(pathSoFar, self.value, accum); +} + +/** + * Find the first matching value on the given path. Return the result of + * applying block to it. + */ +- (id)findOnPath:(FPath *)path + andApplyBlock:(id (^)(FPath *path, id value))block { + return [self findOnPath:path pathSoFar:[FPath empty] andApplyBlock:block]; +} + +- (id)findOnPath:(FPath *)pathToFollow + pathSoFar:(FPath *)pathSoFar + andApplyBlock:(id (^)(FPath *path, id value))block { + id result = self.value ? block(pathSoFar, self.value) : nil; + if (result != nil) { + return result; + } else { + if ([pathToFollow isEmpty]) { + return nil; + } else { + NSString *front = [pathToFollow getFront]; + FImmutableTree *nextChild = [self.children get:front]; + if (nextChild != nil) { + return [nextChild findOnPath:[pathToFollow popFront] + pathSoFar:[pathSoFar childFromString:front] + andApplyBlock:block]; + } else { + return nil; + } + } + } +} +/** + * Call the block on each value along the path for as long as that function + * returns true + * @return The path to the deepest location inspected + */ +- (FPath *)forEachOnPath:(FPath *)path whileBlock:(BOOL (^)(FPath *, id))block { + return [self forEachOnPath:path pathSoFar:[FPath empty] whileBlock:block]; +} + +- (FPath *)forEachOnPath:(FPath *)pathToFollow + pathSoFar:(FPath *)pathSoFar + whileBlock:(BOOL (^)(FPath *, id))block { + if ([pathToFollow isEmpty]) { + if (self.value) { + block(pathSoFar, self.value); + } + return pathSoFar; + } else { + BOOL shouldContinue = YES; + if (self.value) { + shouldContinue = block(pathSoFar, self.value); + } + if (shouldContinue) { + NSString *front = [pathToFollow getFront]; + FImmutableTree *nextChild = [self.children get:front]; + if (nextChild) { + return + [nextChild forEachOnPath:[pathToFollow popFront] + pathSoFar:[pathSoFar childFromString:front] + whileBlock:block]; + } else { + return pathSoFar; + } + } else { + return pathSoFar; + } + } +} + +- (FImmutableTree *)forEachOnPath:(FPath *)path + performBlock:(void (^)(FPath *path, id value))block { + return [self forEachOnPath:path pathSoFar:[FPath empty] performBlock:block]; +} + +- (FImmutableTree *)forEachOnPath:(FPath *)pathToFollow + pathSoFar:(FPath *)pathSoFar + performBlock:(void (^)(FPath *path, id value))block { + if ([pathToFollow isEmpty]) { + return self; + } else { + if (self.value) { + block(pathSoFar, self.value); + } + NSString *front = [pathToFollow getFront]; + FImmutableTree *nextChild = [self.children get:front]; + if (nextChild) { + return [nextChild forEachOnPath:[pathToFollow popFront] + pathSoFar:[pathSoFar childFromString:front] + performBlock:block]; + } else { + return [FImmutableTree empty]; + } + } +} +/** + * Calls the given block for each node in the tree that has a value. Called in + * depth-first order + */ +- (void)forEach:(void (^)(FPath *path, id value))block { + [self forEachPathSoFar:[FPath empty] withBlock:block]; +} + +- (void)forEachPathSoFar:(FPath *)pathSoFar + withBlock:(void (^)(FPath *path, id value))block { + [self.children + enumerateKeysAndObjectsUsingBlock:^( + NSString *childKey, FImmutableTree *childTree, BOOL *stop) { + [childTree forEachPathSoFar:[pathSoFar childFromString:childKey] + withBlock:block]; + }]; + if (self.value) { + block(pathSoFar, self.value); + } +} + +- (void)forEachChild:(void (^)(NSString *childKey, id childValue))block { + [self.children + enumerateKeysAndObjectsUsingBlock:^( + NSString *childKey, FImmutableTree *childTree, BOOL *stop) { + if (childTree.value) { + block(childKey, childTree.value); + } + }]; +} + +- (BOOL)isEqual:(id)object { + if (![object isKindOfClass:[FImmutableTree class]]) { + return NO; + } + FImmutableTree *other = (FImmutableTree *)object; + return (self.value == other.value || [self.value isEqual:other.value]) && + [self.children isEqual:other.children]; +} + +- (NSUInteger)hash { + return self.children.hash * 31 + [self.value hash]; +} + +- (NSString *)description { + NSMutableString *string = [[NSMutableString alloc] init]; + [string appendString:@"FImmutableTree { value="]; + [string appendString:(self.value ? [self.value description] : @"")]; + [string appendString:@", children={"]; + [self.children + enumerateKeysAndObjectsUsingBlock:^( + NSString *childKey, FImmutableTree *childTree, BOOL *stop) { + [string appendString:@" "]; + [string appendString:childKey]; + [string appendString:@"="]; + [string appendString:[childTree.value description]]; + }]; + [string appendString:@" } }"]; + return [NSString stringWithString:string]; +} + +- (NSString *)debugDescription { + return [self description]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FPath.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FPath.h new file mode 100644 index 0000000..cfa86aa --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FPath.h @@ -0,0 +1,46 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@interface FPath : NSObject + ++ (FPath *)relativePathFrom:(FPath *)outer to:(FPath *)inner; ++ (FPath *)empty; ++ (FPath *)pathWithString:(NSString *)string; + +- (id)initWith:(NSString *)path; +- (id)initWithPieces:(NSArray *)somePieces andPieceNum:(NSInteger)aPieceNum; + +- (id)copyWithZone:(NSZone *)zone; + +- (void)enumerateComponentsUsingBlock:(void (^)(NSString *key, + BOOL *stop))block; +- (NSString *)getFront; +- (NSUInteger)length; +- (FPath *)popFront; +- (NSString *)getBack; +- (NSString *)toString; +- (NSString *)toStringWithTrailingSlash; +- (NSString *)wireFormat; +- (FPath *)parent; +- (FPath *)child:(FPath *)childPathObj; +- (FPath *)childFromString:(NSString *)childPath; +- (BOOL)isEmpty; +- (BOOL)contains:(FPath *)other; +- (NSComparisonResult)compare:(FPath *)other; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FPath.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FPath.m new file mode 100644 index 0000000..f655240 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FPath.m @@ -0,0 +1,304 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FPath.h" + +#import "FUtilities.h" + +@interface FPath () + +@property(nonatomic, readwrite, assign) NSInteger pieceNum; +@property(nonatomic, strong) NSArray *pieces; + +@end + +@implementation FPath + +#pragma mark - +#pragma mark Initializers + ++ (FPath *)relativePathFrom:(FPath *)outer to:(FPath *)inner { + NSString *outerFront = [outer getFront]; + NSString *innerFront = [inner getFront]; + if (outerFront == nil) { + return inner; + } else if ([outerFront isEqualToString:innerFront]) { + return [self relativePathFrom:[outer popFront] to:[inner popFront]]; + } else { + @throw [[NSException alloc] + initWithName:@"FirebaseDatabaseInternalError" + reason:[NSString + stringWithFormat: + @"innerPath (%@) is not within outerPath (%@)", + inner, outer] + userInfo:nil]; + } +} + ++ (FPath *)pathWithString:(NSString *)string { + return [[FPath alloc] initWith:string]; +} + +- (id)initWith:(NSString *)path { + self = [super init]; + if (self) { + NSArray *pathPieces = [path componentsSeparatedByString:@"/"]; + NSMutableArray *newPieces = [[NSMutableArray alloc] init]; + for (NSInteger i = 0; i < pathPieces.count; i++) { + NSString *piece = [pathPieces objectAtIndex:i]; + if (piece.length > 0) { + [newPieces addObject:piece]; + } + } + + self.pieces = newPieces; + self.pieceNum = 0; + } + return self; +} + +- (id)initWithPieces:(NSArray *)somePieces andPieceNum:(NSInteger)aPieceNum { + self = [super init]; + if (self) { + self.pieceNum = aPieceNum; + self.pieces = somePieces; + } + return self; +} + +- (id)copyWithZone:(NSZone *)zone { + // Immutable, so it's safe to return self + return self; +} + +- (NSString *)description { + return [self toString]; +} + +#pragma mark - +#pragma mark Public methods + +- (NSString *)getFront { + if (self.pieceNum >= self.pieces.count) { + return nil; + } + return [self.pieces objectAtIndex:self.pieceNum]; +} + +/** + * @return The number of segments in this path + */ +- (NSUInteger)length { + return self.pieces.count - self.pieceNum; +} + +- (FPath *)popFront { + NSInteger newPieceNum = self.pieceNum; + if (newPieceNum < self.pieces.count) { + newPieceNum++; + } + return [[FPath alloc] initWithPieces:self.pieces andPieceNum:newPieceNum]; +} + +- (NSString *)getBack { + if (self.pieceNum < self.pieces.count) { + return [self.pieces lastObject]; + } else { + return nil; + } +} + +- (NSString *)toString { + return [self toStringWithTrailingSlash:NO]; +} + +- (NSString *)toStringWithTrailingSlash { + return [self toStringWithTrailingSlash:YES]; +} + +- (NSString *)toStringWithTrailingSlash:(BOOL)trailingSlash { + NSMutableString *pathString = [[NSMutableString alloc] init]; + for (NSInteger i = self.pieceNum; i < self.pieces.count; i++) { + [pathString appendString:@"/"]; + [pathString appendString:[self.pieces objectAtIndex:i]]; + } + if ([pathString length] == 0) { + return @"/"; + } else { + if (trailingSlash) { + [pathString appendString:@"/"]; + } + return pathString; + } +} + +- (NSString *)wireFormat { + if ([self isEmpty]) { + return @"/"; + } else { + NSMutableString *pathString = [[NSMutableString alloc] init]; + for (NSInteger i = self.pieceNum; i < self.pieces.count; i++) { + if (i > self.pieceNum) { + [pathString appendString:@"/"]; + } + [pathString appendString:[self.pieces objectAtIndex:i]]; + } + return pathString; + } +} + +- (FPath *)parent { + if (self.pieceNum >= self.pieces.count) { + return nil; + } else { + NSMutableArray *newPieces = [[NSMutableArray alloc] init]; + for (NSInteger i = self.pieceNum; i < self.pieces.count - 1; i++) { + [newPieces addObject:[self.pieces objectAtIndex:i]]; + } + return [[FPath alloc] initWithPieces:newPieces andPieceNum:0]; + } +} + +- (FPath *)child:(FPath *)childPathObj { + NSMutableArray *newPieces = [[NSMutableArray alloc] init]; + for (NSInteger i = self.pieceNum; i < self.pieces.count; i++) { + [newPieces addObject:[self.pieces objectAtIndex:i]]; + } + + for (NSInteger i = childPathObj.pieceNum; i < childPathObj.pieces.count; + i++) { + [newPieces addObject:[childPathObj.pieces objectAtIndex:i]]; + } + + return [[FPath alloc] initWithPieces:newPieces andPieceNum:0]; +} + +- (FPath *)childFromString:(NSString *)childPath { + NSMutableArray *newPieces = [[NSMutableArray alloc] init]; + for (NSInteger i = self.pieceNum; i < self.pieces.count; i++) { + [newPieces addObject:[self.pieces objectAtIndex:i]]; + } + + NSArray *pathPieces = [childPath componentsSeparatedByString:@"/"]; + for (unsigned int i = 0; i < pathPieces.count; i++) { + NSString *piece = [pathPieces objectAtIndex:i]; + if (piece.length > 0) { + [newPieces addObject:piece]; + } + } + + return [[FPath alloc] initWithPieces:newPieces andPieceNum:0]; +} + +/** + * @return True if there are no segments in this path + */ +- (BOOL)isEmpty { + return self.pieceNum >= self.pieces.count; +} + +/** + * @return Singleton to represent an empty path + */ ++ (FPath *)empty { + static dispatch_once_t oneEmptyPath; + static FPath *emptyPath; + dispatch_once(&oneEmptyPath, ^{ + emptyPath = [[FPath alloc] initWith:@""]; + }); + return emptyPath; +} + +- (BOOL)contains:(FPath *)other { + if (self.length > other.length) { + return NO; + } + + NSInteger i = self.pieceNum; + NSInteger j = other.pieceNum; + while (i < self.pieces.count) { + NSString *thisSeg = [self.pieces objectAtIndex:i]; + NSString *otherSeg = [other.pieces objectAtIndex:j]; + if (![thisSeg isEqualToString:otherSeg]) { + return NO; + } + ++i; + ++j; + } + return YES; +} + +- (void)enumerateComponentsUsingBlock:(void (^)(NSString *, BOOL *))block { + BOOL stop = NO; + for (NSInteger i = self.pieceNum; !stop && i < self.pieces.count; i++) { + block(self.pieces[i], &stop); + } +} + +- (NSComparisonResult)compare:(FPath *)other { + NSInteger myCount = self.pieces.count; + NSInteger otherCount = other.pieces.count; + for (NSInteger i = self.pieceNum, j = other.pieceNum; + i < myCount && j < otherCount; i++, j++) { + NSComparisonResult comparison = [FUtilities compareKey:self.pieces[i] + toKey:other.pieces[j]]; + if (comparison != NSOrderedSame) { + return comparison; + } + } + if (self.length < other.length) { + return NSOrderedAscending; + } else if (other.length < self.length) { + return NSOrderedDescending; + } else { + NSAssert(self.length == other.length, + @"Paths must be the same lengths"); + return NSOrderedSame; + } +} + +/** + * @return YES if paths are the same + */ +- (BOOL)isEqual:(id)other { + if (other == self) { + return YES; + } + if (!other || ![other isKindOfClass:[self class]]) { + return NO; + } + FPath *otherPath = (FPath *)other; + if (self.length != otherPath.length) { + return NO; + } + for (NSUInteger i = self.pieceNum, j = otherPath.pieceNum; + i < self.pieces.count; i++, j++) { + if (![self.pieces[i] isEqualToString:otherPath.pieces[j]]) { + return NO; + } + } + return YES; +} + +- (NSUInteger)hash { + NSUInteger hashCode = 0; + for (NSInteger i = self.pieceNum; i < self.pieces.count; i++) { + hashCode = hashCode * 37 + [self.pieces[i] hash]; + } + return hashCode; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FTree.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FTree.h new file mode 100644 index 0000000..d5c77ab --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FTree.h @@ -0,0 +1,52 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FPath.h" +#import "FTreeNode.h" +#import + +@interface FTree : NSObject + +- (id)init; +- (id)initWithName:(NSString *)aName + withParent:(FTree *)aParent + withNode:(FTreeNode *)aNode; + +- (FTree *)subTree:(FPath *)path; +- (id)getValue; +- (void)setValue:(id)value; +- (void)clear; +- (BOOL)hasChildren; +- (BOOL)isEmpty; +- (void)forEachChildMutationSafe:(void (^)(FTree *))action; +- (void)forEachChild:(void (^)(FTree *))action; +- (void)forEachDescendant:(void (^)(FTree *))action; +- (void)forEachDescendant:(void (^)(FTree *))action + includeSelf:(BOOL)incSelf + childrenFirst:(BOOL)childFirst; +- (BOOL)forEachAncestor:(BOOL (^)(FTree *))action; +- (BOOL)forEachAncestor:(BOOL (^)(FTree *))action includeSelf:(BOOL)incSelf; +- (void)forEachImmediateDescendantWithValue:(void (^)(FTree *))action; +- (BOOL)valueExistsAtOrAbove:(FPath *)path; +- (FPath *)path; +- (void)updateParents; +- (void)updateChild:(NSString *)childName withNode:(FTree *)child; + +@property(nonatomic, strong) NSString *name; +@property(nonatomic, strong) FTree *parent; +@property(nonatomic, strong) FTreeNode *node; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FTree.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FTree.m new file mode 100644 index 0000000..209b59f --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FTree.m @@ -0,0 +1,193 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FTree.h" +#import "FPath.h" +#import "FTreeNode.h" +#import "FUtilities.h" + +@implementation FTree + +@synthesize name; +@synthesize parent; +@synthesize node; + +- (id)init { + self = [super init]; + if (self) { + self.name = @""; + self.parent = nil; + self.node = [[FTreeNode alloc] init]; + } + return self; +} + +- (id)initWithName:(NSString *)aName + withParent:(FTree *)aParent + withNode:(FTreeNode *)aNode { + self = [super init]; + if (self) { + self.name = aName != nil ? aName : @""; + self.parent = aParent != nil ? aParent : nil; + self.node = aNode != nil ? aNode : [[FTreeNode alloc] init]; + } + return self; +} + +- (FTree *)subTree:(FPath *)path { + FTree *child = self; + NSString *next = [path getFront]; + while (next != nil) { + FTreeNode *childNode = child.node.children[next]; + if (childNode == nil) { + childNode = [[FTreeNode alloc] init]; + } + child = [[FTree alloc] initWithName:next + withParent:child + withNode:childNode]; + path = [path popFront]; + next = [path getFront]; + } + return child; +} + +- (id)getValue { + return self.node.value; +} + +- (void)setValue:(id)value { + self.node.value = value; + [self updateParents]; +} + +- (void)clear { + self.node.value = nil; + [self.node.children removeAllObjects]; + self.node.childCount = 0; + [self updateParents]; +} + +- (BOOL)hasChildren { + return self.node.childCount > 0; +} + +- (BOOL)isEmpty { + return [self getValue] == nil && ![self hasChildren]; +} + +- (void)forEachChild:(void (^)(FTree *))action { + for (NSString *key in self.node.children) { + action([[FTree alloc] + initWithName:key + withParent:self + withNode:[self.node.children objectForKey:key]]); + } +} + +- (void)forEachChildMutationSafe:(void (^)(FTree *))action { + for (NSString *key in [self.node.children copy]) { + action([[FTree alloc] + initWithName:key + withParent:self + withNode:[self.node.children objectForKey:key]]); + } +} + +- (void)forEachDescendant:(void (^)(FTree *))action { + [self forEachDescendant:action includeSelf:NO childrenFirst:NO]; +} + +- (void)forEachDescendant:(void (^)(FTree *))action + includeSelf:(BOOL)incSelf + childrenFirst:(BOOL)childFirst { + if (incSelf && !childFirst) { + action(self); + } + + [self forEachChild:^(FTree *child) { + [child forEachDescendant:action includeSelf:YES childrenFirst:childFirst]; + }]; + + if (incSelf && childFirst) { + action(self); + } +} + +- (BOOL)forEachAncestor:(BOOL (^)(FTree *))action { + return [self forEachAncestor:action includeSelf:NO]; +} + +- (BOOL)forEachAncestor:(BOOL (^)(FTree *))action includeSelf:(BOOL)incSelf { + FTree *aNode = (incSelf) ? self : self.parent; + while (aNode != nil) { + if (action(aNode)) { + return YES; + } + aNode = aNode.parent; + } + return NO; +} + +- (void)forEachImmediateDescendantWithValue:(void (^)(FTree *))action { + [self forEachChild:^(FTree *child) { + if ([child getValue] != nil) { + action(child); + } else { + [child forEachImmediateDescendantWithValue:action]; + } + }]; +} + +- (BOOL)valueExistsAtOrAbove:(FPath *)path { + FTreeNode *aNode = self.node; + while (aNode != nil) { + if (aNode.value != nil) { + return YES; + } + aNode = [aNode.children objectForKey:path.getFront]; + path = [path popFront]; + } + // XXX Check with Michael if this is correct; deviates from JS. + return NO; +} + +- (FPath *)path { + return [[FPath alloc] + initWith:(self.parent == nil) + ? self.name + : [NSString stringWithFormat:@"%@/%@", [self.parent path], + self.name]]; +} + +- (void)updateParents { + [self.parent updateChild:self.name withNode:self]; +} + +- (void)updateChild:(NSString *)childName withNode:(FTree *)child { + BOOL childEmpty = [child isEmpty]; + BOOL childExists = self.node.children[childName] != nil; + if (childEmpty && childExists) { + [self.node.children removeObjectForKey:childName]; + self.node.childCount = self.node.childCount - 1; + [self updateParents]; + } else if (!childEmpty && !childExists) { + [self.node.children setObject:child.node forKey:childName]; + self.node.childCount = self.node.childCount + 1; + [self updateParents]; + } +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FTreeNode.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FTreeNode.h new file mode 100644 index 0000000..549f3b1 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FTreeNode.h @@ -0,0 +1,25 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@interface FTreeNode : NSObject + +@property(nonatomic, strong) NSMutableDictionary *children; +@property(nonatomic, readwrite, assign) int childCount; +@property(nonatomic, strong) id value; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FTreeNode.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FTreeNode.m new file mode 100644 index 0000000..bae6c62 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/Utilities/FTreeNode.m @@ -0,0 +1,35 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FTreeNode.h" + +@implementation FTreeNode + +@synthesize children; +@synthesize childCount; +@synthesize value; + +- (id)init { + self = [super init]; + if (self) { + self.children = [[NSMutableDictionary alloc] init]; + self.childCount = 0; + self.value = nil; + } + return self; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FCacheNode.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FCacheNode.h new file mode 100644 index 0000000..eff0cb0 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FCacheNode.h @@ -0,0 +1,46 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@protocol FNode; +@class FIndexedNode; +@class FPath; + +/** + * A cache node only stores complete children. Additionally it holds a flag + * whether the node can be considered fully initialized in the sense that we + * know at one point in time, this represented a valid state of the world, e.g. + * initialized with data from the server, or a complete overwrite by the client. + * It is not necessarily complete because it may have been from a tagged query. + * The filtered flag also tracks whether a node potentially had children removed + * due to a filter. + */ +@interface FCacheNode : NSObject + +- (id)initWithIndexedNode:(FIndexedNode *)indexedNode + isFullyInitialized:(BOOL)fullyInitialized + isFiltered:(BOOL)filtered; + +- (BOOL)isCompleteForPath:(FPath *)path; +- (BOOL)isCompleteForChild:(NSString *)childKey; + +@property(nonatomic, readonly) BOOL isFullyInitialized; +@property(nonatomic, readonly) BOOL isFiltered; +@property(nonatomic, strong, readonly) FIndexedNode *indexedNode; +@property(nonatomic, strong, readonly) id node; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FCacheNode.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FCacheNode.m new file mode 100644 index 0000000..a991d52 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FCacheNode.m @@ -0,0 +1,60 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FCacheNode.h" +#import "FEmptyNode.h" +#import "FIndexedNode.h" +#import "FNode.h" +#import "FPath.h" + +@interface FCacheNode () +@property(nonatomic, readwrite) BOOL isFullyInitialized; +@property(nonatomic, readwrite) BOOL isFiltered; +@property(nonatomic, strong, readwrite) FIndexedNode *indexedNode; +@end + +@implementation FCacheNode +- (id)initWithIndexedNode:(FIndexedNode *)indexedNode + isFullyInitialized:(BOOL)fullyInitialized + isFiltered:(BOOL)filtered { + self = [super init]; + if (self) { + self.indexedNode = indexedNode; + self.isFullyInitialized = fullyInitialized; + self.isFiltered = filtered; + } + return self; +} + +- (BOOL)isCompleteForPath:(FPath *)path { + if (path.isEmpty) { + return self.isFullyInitialized && !self.isFiltered; + } else { + NSString *childKey = [path getFront]; + return [self isCompleteForChild:childKey]; + } +} + +- (BOOL)isCompleteForChild:(NSString *)childKey { + return (self.isFullyInitialized && !self.isFiltered) || + [self.node hasChild:childKey]; +} + +- (id)node { + return self.indexedNode.node; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FCancelEvent.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FCancelEvent.h new file mode 100644 index 0000000..dd58642 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FCancelEvent.h @@ -0,0 +1,31 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FEvent.h" +#import + +@protocol FEventRegistration; + +@interface FCancelEvent : NSObject + +- initWithEventRegistration:(id)eventRegistration + error:(NSError *)error + path:(FPath *)path; + +@property(nonatomic, strong, readonly) NSError *error; +@property(nonatomic, strong, readonly) FPath *path; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FCancelEvent.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FCancelEvent.m new file mode 100644 index 0000000..b069a71 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FCancelEvent.m @@ -0,0 +1,57 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FCancelEvent.h" +#import "FEventRegistration.h" +#import "FPath.h" + +@interface FCancelEvent () +@property(nonatomic, strong) id eventRegistration; +@property(nonatomic, strong, readwrite) NSError *error; +@property(nonatomic, strong, readwrite) FPath *path; +@end + +@implementation FCancelEvent + +@synthesize eventRegistration; +@synthesize error; +@synthesize path; + +- (id)initWithEventRegistration:(id)registration + error:(NSError *)anError + path:(FPath *)aPath { + self = [super init]; + if (self) { + self.eventRegistration = registration; + self.error = anError; + self.path = aPath; + } + return self; +} + +- (void)fireEventOnQueue:(dispatch_queue_t)queue { + [self.eventRegistration fireEvent:self queue:queue]; +} + +- (BOOL)isCancelEvent { + return YES; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"%@: cancel", self.path]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FChange.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FChange.h new file mode 100644 index 0000000..101ec8f --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FChange.h @@ -0,0 +1,41 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRDatabaseReference.h" +#import "FIndexedNode.h" +#import "FNode.h" +#import + +@interface FChange : NSObject + +@property(nonatomic, readonly) FIRDataEventType type; +@property(nonatomic, strong, readonly) FIndexedNode *indexedNode; +@property(nonatomic, strong, readonly) NSString *childKey; +@property(nonatomic, strong, readonly) NSString *prevKey; +@property(nonatomic, strong, readonly) FIndexedNode *oldIndexedNode; + +- (id)initWithType:(FIRDataEventType)type + indexedNode:(FIndexedNode *)indexedNode; +- (id)initWithType:(FIRDataEventType)type + indexedNode:(FIndexedNode *)indexedNode + childKey:(NSString *)childKey; +- (id)initWithType:(FIRDataEventType)type + indexedNode:(FIndexedNode *)indexedNode + childKey:(NSString *)childKey + oldIndexedNode:(FIndexedNode *)oldIndexedNode; + +- (FChange *)changeWithPrevKey:(NSString *)prevKey; +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FChange.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FChange.m new file mode 100644 index 0000000..8ad1cfc --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FChange.m @@ -0,0 +1,72 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FChange.h" + +@interface FChange () + +@property(nonatomic, strong, readwrite) NSString *prevKey; + +@end + +@implementation FChange + +- (id)initWithType:(FIRDataEventType)type + indexedNode:(FIndexedNode *)indexedNode { + return [self initWithType:type + indexedNode:indexedNode + childKey:nil + oldIndexedNode:nil]; +} + +- (id)initWithType:(FIRDataEventType)type + indexedNode:(FIndexedNode *)indexedNode + childKey:(NSString *)childKey { + return [self initWithType:type + indexedNode:indexedNode + childKey:childKey + oldIndexedNode:nil]; +} + +- (id)initWithType:(FIRDataEventType)type + indexedNode:(FIndexedNode *)indexedNode + childKey:(NSString *)childKey + oldIndexedNode:(FIndexedNode *)oldIndexedNode { + self = [super init]; + if (self != nil) { + self->_type = type; + self->_indexedNode = indexedNode; + self->_childKey = childKey; + self->_oldIndexedNode = oldIndexedNode; + } + return self; +} + +- (FChange *)changeWithPrevKey:(NSString *)prevKey { + FChange *newChange = [[FChange alloc] initWithType:self.type + indexedNode:self.indexedNode + childKey:self.childKey + oldIndexedNode:self.oldIndexedNode]; + newChange.prevKey = prevKey; + return newChange; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"event: %d, data: %@", (int)self.type, + [self.indexedNode.node val]]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FChildEventRegistration.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FChildEventRegistration.h new file mode 100644 index 0000000..0b0c633 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FChildEventRegistration.h @@ -0,0 +1,37 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FEventRegistration.h" +#import "FTypedefs.h" +#import + +@class FRepo; + +@interface FChildEventRegistration : NSObject + +- (id)initWithRepo:(FRepo *)repo + handle:(FIRDatabaseHandle)fHandle + callbacks:(NSDictionary *)callbackBlocks + cancelCallback:(fbt_void_nserror)cancelCallbackBlock; + +/** + * Maps FIRDataEventType (as NSNumber) to fbt_void_datasnapshot_nsstring + */ +@property(nonatomic, copy, readonly) NSDictionary *callbacks; +@property(nonatomic, copy, readonly) fbt_void_nserror cancelCallback; +@property(nonatomic, readonly) FIRDatabaseHandle handle; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FChildEventRegistration.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FChildEventRegistration.m new file mode 100644 index 0000000..1b688c1 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FChildEventRegistration.m @@ -0,0 +1,112 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FChildEventRegistration.h" +#import "FCancelEvent.h" +#import "FDataEvent.h" +#import "FIRDataSnapshot_Private.h" +#import "FIRDatabaseQuery_Private.h" +#import "FQueryParams.h" +#import "FQuerySpec.h" +#import + +@interface FChildEventRegistration () +@property(nonatomic, strong) FRepo *repo; +@property(nonatomic, copy, readwrite) NSDictionary *callbacks; +@property(nonatomic, copy, readwrite) fbt_void_nserror cancelCallback; +@property(nonatomic, readwrite) FIRDatabaseHandle handle; +@end + +@implementation FChildEventRegistration + +- (id)initWithRepo:(id)repo + handle:(FIRDatabaseHandle)fHandle + callbacks:(NSDictionary *)callbackBlocks + cancelCallback:(fbt_void_nserror)cancelCallbackBlock { + self = [super init]; + if (self) { + self.repo = repo; + self.handle = fHandle; + self.callbacks = callbackBlocks; + self.cancelCallback = cancelCallbackBlock; + } + return self; +} + +- (BOOL)responseTo:(FIRDataEventType)eventType { + return self.callbacks != nil && + [self.callbacks + objectForKey:[NSNumber numberWithInteger:eventType]] != nil; +} + +- (FDataEvent *)createEventFrom:(FChange *)change query:(FQuerySpec *)query { + FIRDatabaseReference *ref = [[FIRDatabaseReference alloc] + initWithRepo:self.repo + path:[query.path childFromString:change.childKey]]; + FIRDataSnapshot *snapshot = + [[FIRDataSnapshot alloc] initWithRef:ref + indexedNode:change.indexedNode]; + + FDataEvent *eventData = + [[FDataEvent alloc] initWithEventType:change.type + eventRegistration:self + dataSnapshot:snapshot + prevName:change.prevKey]; + return eventData; +} + +- (void)fireEvent:(id)event queue:(dispatch_queue_t)queue { + if ([event isCancelEvent]) { + FCancelEvent *cancelEvent = event; + FFLog(@"I-RDB061001", @"Raising cancel value event on %@", event.path); + NSAssert( + self.cancelCallback != nil, + @"Raising a cancel event on a listener with no cancel callback"); + dispatch_async(queue, ^{ + self.cancelCallback(cancelEvent.error); + }); + } else if (self.callbacks != nil) { + FDataEvent *dataEvent = event; + FFLog(@"I-RDB061002", @"Raising event callback (%ld) on %@", + (long)dataEvent.eventType, dataEvent.path); + fbt_void_datasnapshot_nsstring callback = [self.callbacks + objectForKey:[NSNumber numberWithInteger:dataEvent.eventType]]; + + if (callback != nil) { + dispatch_async(queue, ^{ + callback(dataEvent.snapshot, dataEvent.prevName); + }); + } + } +} + +- (FCancelEvent *)createCancelEventFromError:(NSError *)error + path:(FPath *)path { + if (self.cancelCallback != nil) { + return [[FCancelEvent alloc] initWithEventRegistration:self + error:error + path:path]; + } else { + return nil; + } +} + +- (BOOL)matches:(id)other { + return self.handle == NSNotFound || other.handle == NSNotFound || + self.handle == other.handle; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FDataEvent.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FDataEvent.h new file mode 100644 index 0000000..a0d28f9 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FDataEvent.h @@ -0,0 +1,41 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FEvent.h" +#import "FIRDataSnapshot.h" +#import "FIRDatabaseReference.h" +#import "FTupleUserCallback.h" +#import + +@protocol FEventRegistration; +@protocol FIndex; + +@interface FDataEvent : NSObject + +- initWithEventType:(FIRDataEventType)type + eventRegistration:(id)eventRegistration + dataSnapshot:(FIRDataSnapshot *)dataSnapshot; +- initWithEventType:(FIRDataEventType)type + eventRegistration:(id)eventRegistration + dataSnapshot:(FIRDataSnapshot *)snapshot + prevName:(NSString *)prevName; + +@property(nonatomic, strong, readonly) id eventRegistration; +@property(nonatomic, strong, readonly) FIRDataSnapshot *snapshot; +@property(nonatomic, strong, readonly) NSString *prevName; +@property(nonatomic, readonly) FIRDataEventType eventType; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FDataEvent.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FDataEvent.m new file mode 100644 index 0000000..886a21e --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FDataEvent.m @@ -0,0 +1,83 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FDataEvent.h" +#import "FEventRegistration.h" +#import "FIRDatabaseQuery_Private.h" +#import "FIndex.h" + +@interface FDataEvent () +@property(nonatomic, strong, readwrite) id + eventRegistration; +@property(nonatomic, strong, readwrite) FIRDataSnapshot *snapshot; +@property(nonatomic, strong, readwrite) NSString *prevName; +@property(nonatomic, readwrite) FIRDataEventType eventType; +@end + +@implementation FDataEvent + +@synthesize eventRegistration; +@synthesize snapshot; +@synthesize prevName; +@synthesize eventType; + +- (id)initWithEventType:(FIRDataEventType)type + eventRegistration:(id)registration + dataSnapshot:(FIRDataSnapshot *)dataSnapshot { + return [self initWithEventType:type + eventRegistration:registration + dataSnapshot:dataSnapshot + prevName:nil]; +} + +- (id)initWithEventType:(FIRDataEventType)type + eventRegistration:(id)registration + dataSnapshot:(FIRDataSnapshot *)dataSnapshot + prevName:(NSString *)previousName { + self = [super init]; + if (self) { + self.eventRegistration = registration; + self.snapshot = dataSnapshot; + self.prevName = previousName; + self.eventType = type; + } + return self; +} + +- (FPath *)path { + // Used for logging, so delay calculation + FIRDatabaseReference *ref = self.snapshot.ref; + if (self.eventType == FIRDataEventTypeValue) { + return ref.path; + } else { + return ref.parent.path; + } +} + +- (void)fireEventOnQueue:(dispatch_queue_t)queue { + [self.eventRegistration fireEvent:self queue:queue]; +} + +- (BOOL)isCancelEvent { + return NO; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"event %d, data: %@", (int)eventType, + [snapshot value]]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FEvent.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FEvent.h new file mode 100644 index 0000000..cddcf73 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FEvent.h @@ -0,0 +1,27 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRDataEventType.h" +#import + +@class FPath; + +@protocol FEvent +- (FPath *)path; +- (void)fireEventOnQueue:(dispatch_queue_t)queue; +- (BOOL)isCancelEvent; +- (NSString *)description; +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FEventRaiser.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FEventRaiser.h new file mode 100644 index 0000000..c46bff5 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FEventRaiser.h @@ -0,0 +1,36 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FTypedefs.h" + +@class FPath; +@class FRepo; +@class FIRDatabaseConfig; + +/** + * Left as instance methods rather than class methods so that we could + * potentially callback on different queues for different repos. This is + * semi-parallel to JS's FEventQueue + */ +@interface FEventRaiser : NSObject + +- (id)initWithQueue:(dispatch_queue_t)queue; + +- (void)raiseEvents:(NSArray *)eventDataList; +- (void)raiseCallback:(fbt_void_void)callback; +- (void)raiseCallbacks:(NSArray *)callbackList; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FEventRaiser.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FEventRaiser.m new file mode 100644 index 0000000..d745358 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FEventRaiser.m @@ -0,0 +1,74 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FEventRaiser.h" +#import "FDataEvent.h" +#import "FRepo.h" +#import "FRepoManager.h" +#import "FTupleUserCallback.h" +#import "FTypedefs.h" +#import "FUtilities.h" + +@interface FEventRaiser () + +@property(nonatomic, strong) dispatch_queue_t queue; + +@end + +/** + * This class exists for symmetry with other clients, but since events are + * async, we don't need to do the complicated stuff the JS client does to + * preserve event order. + */ +@implementation FEventRaiser + +- (id)init { + [NSException raise:NSInternalInconsistencyException + format:@"Can't use default constructor"]; + return nil; +} + +- (id)initWithQueue:(dispatch_queue_t)queue { + self = [super init]; + if (self != nil) { + self->_queue = queue; + } + return self; +} + +- (void)raiseEvents:(NSArray *)eventDataList { + for (id event in eventDataList) { + [event fireEventOnQueue:self.queue]; + } +} + +- (void)raiseCallback:(fbt_void_void)callback { + dispatch_async(self.queue, callback); +} + +- (void)raiseCallbacks:(NSArray *)callbackList { + for (fbt_void_void callback in callbackList) { + dispatch_async(self.queue, callback); + } +} + ++ (void)raiseCallbacks:(NSArray *)callbackList queue:(dispatch_queue_t)queue { + for (fbt_void_void callback in callbackList) { + dispatch_async(queue, callback); + } +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FEventRegistration.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FEventRegistration.h new file mode 100644 index 0000000..79a2eb0 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FEventRegistration.h @@ -0,0 +1,38 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FChange.h" +#import "FIRDataEventType.h" +#import + +@protocol FEvent; +@class FDataEvent; +@class FCancelEvent; +@class FQuerySpec; + +@protocol FEventRegistration +- (BOOL)responseTo:(FIRDataEventType)eventType; +- (FDataEvent *)createEventFrom:(FChange *)change query:(FQuerySpec *)query; +- (void)fireEvent:(id)event queue:(dispatch_queue_t)queue; +- (FCancelEvent *)createCancelEventFromError:(NSError *)error + path:(FPath *)path; +/** + * Used to figure out what event registration match the event registration that + * needs to be removed. + */ +- (BOOL)matches:(id)other; +@property(nonatomic, readonly) FIRDatabaseHandle handle; +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FKeepSyncedEventRegistration.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FKeepSyncedEventRegistration.h new file mode 100644 index 0000000..d4dca60 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FKeepSyncedEventRegistration.h @@ -0,0 +1,28 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FEventRegistration.h" + +/** + * A singleton event registration to mark a query as keep synced + */ +@interface FKeepSyncedEventRegistration : NSObject + ++ (FKeepSyncedEventRegistration *)instance; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FKeepSyncedEventRegistration.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FKeepSyncedEventRegistration.m new file mode 100644 index 0000000..1821829 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FKeepSyncedEventRegistration.m @@ -0,0 +1,70 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FKeepSyncedEventRegistration.h" + +@interface FKeepSyncedEventRegistration () + +@end + +@implementation FKeepSyncedEventRegistration + ++ (FKeepSyncedEventRegistration *)instance { + static dispatch_once_t onceToken; + static FKeepSyncedEventRegistration *keepSynced; + dispatch_once(&onceToken, ^{ + keepSynced = [[FKeepSyncedEventRegistration alloc] init]; + }); + return keepSynced; +} + +- (BOOL)responseTo:(FIRDataEventType)eventType { + return NO; +} + +- (FDataEvent *)createEventFrom:(FChange *)change query:(FQuerySpec *)query { + [NSException + raise:NSInternalInconsistencyException + format:@"Should never create event for FKeepSyncedEventRegistration"]; + return nil; +} + +- (void)fireEvent:(id)event queue:(dispatch_queue_t)queue { + [NSException + raise:NSInternalInconsistencyException + format:@"Should never raise event for FKeepSyncedEventRegistration"]; +} + +- (FCancelEvent *)createCancelEventFromError:(NSError *)error + path:(FPath *)path { + // Don't create cancel events.... + return nil; +} + +- (FIRDatabaseHandle)handle { + // TODO[offline]: returning arbitray, can't return NSNotFound since that is + // used to match other event registrations We should really redo this to + // match on different kind of events (single observer, all observers, + // cancelled) rather than on a NSNotFound handle... + return NSNotFound - 1; +} + +- (BOOL)matches:(id)other { + // Only matches singleton instance + return self == other; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FValueEventRegistration.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FValueEventRegistration.h new file mode 100644 index 0000000..819febe --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FValueEventRegistration.h @@ -0,0 +1,34 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FEventRegistration.h" +#import "FTypedefs.h" +#import + +@class FRepo; + +@interface FValueEventRegistration : NSObject + +- (id)initWithRepo:(FRepo *)repo + handle:(FIRDatabaseHandle)fHandle + callback:(fbt_void_datasnapshot)callbackBlock + cancelCallback:(fbt_void_nserror)cancelCallbackBlock; + +@property(nonatomic, copy, readonly) fbt_void_datasnapshot callback; +@property(nonatomic, copy, readonly) fbt_void_nserror cancelCallback; +@property(nonatomic, readonly) FIRDatabaseHandle handle; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FValueEventRegistration.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FValueEventRegistration.m new file mode 100644 index 0000000..ad9368b --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FValueEventRegistration.m @@ -0,0 +1,102 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FValueEventRegistration.h" +#import "FCancelEvent.h" +#import "FDataEvent.h" +#import "FIRDataSnapshot_Private.h" +#import "FIRDatabaseQuery_Private.h" +#import "FQueryParams.h" +#import "FQuerySpec.h" +#import + +@interface FValueEventRegistration () +@property(nonatomic, strong) FRepo *repo; +@property(nonatomic, copy, readwrite) fbt_void_datasnapshot callback; +@property(nonatomic, copy, readwrite) fbt_void_nserror cancelCallback; +@property(nonatomic, readwrite) FIRDatabaseHandle handle; +@end + +@implementation FValueEventRegistration + +- (id)initWithRepo:(FRepo *)repo + handle:(FIRDatabaseHandle)fHandle + callback:(fbt_void_datasnapshot)callbackBlock + cancelCallback:(fbt_void_nserror)cancelCallbackBlock { + self = [super init]; + if (self) { + self.repo = repo; + self.handle = fHandle; + self.callback = callbackBlock; + self.cancelCallback = cancelCallbackBlock; + } + return self; +} + +- (BOOL)responseTo:(FIRDataEventType)eventType { + return eventType == FIRDataEventTypeValue; +} + +- (FDataEvent *)createEventFrom:(FChange *)change query:(FQuerySpec *)query { + FIRDatabaseReference *ref = + [[FIRDatabaseReference alloc] initWithRepo:self.repo path:query.path]; + FIRDataSnapshot *snapshot = + [[FIRDataSnapshot alloc] initWithRef:ref + indexedNode:change.indexedNode]; + FDataEvent *eventData = + [[FDataEvent alloc] initWithEventType:FIRDataEventTypeValue + eventRegistration:self + dataSnapshot:snapshot]; + return eventData; +} + +- (void)fireEvent:(id)event queue:(dispatch_queue_t)queue { + if ([event isCancelEvent]) { + FCancelEvent *cancelEvent = event; + FFLog(@"I-RDB065001", @"Raising cancel value event on %@", event.path); + NSAssert( + self.cancelCallback != nil, + @"Raising a cancel event on a listener with no cancel callback"); + dispatch_async(queue, ^{ + self.cancelCallback(cancelEvent.error); + }); + } else if (self.callback != nil) { + FDataEvent *dataEvent = event; + FFLog(@"I-RDB065002", @"Raising value event on %@", + dataEvent.snapshot.key); + dispatch_async(queue, ^{ + self.callback(dataEvent.snapshot); + }); + } +} + +- (FCancelEvent *)createCancelEventFromError:(NSError *)error + path:(FPath *)path { + if (self.cancelCallback != nil) { + return [[FCancelEvent alloc] initWithEventRegistration:self + error:error + path:path]; + } else { + return nil; + } +} + +- (BOOL)matches:(id)other { + return self.handle == NSNotFound || other.handle == NSNotFound || + self.handle == other.handle; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FView.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FView.h new file mode 100644 index 0000000..0b4b9d8 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FView.h @@ -0,0 +1,56 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@protocol FNode; +@protocol FOperation; +@protocol FEventRegistration; +@class FWriteTreeRef; +@class FQuerySpec; +@class FChange; +@class FPath; +@class FViewCache; + +@interface FViewOperationResult : NSObject + +@property(nonatomic, strong, readonly) NSArray *changes; +@property(nonatomic, strong, readonly) NSArray *events; + +@end + +@interface FView : NSObject + +@property(nonatomic, strong, readonly) FQuerySpec *query; + +- (id)initWithQuery:(FQuerySpec *)query + initialViewCache:(FViewCache *)initialViewCache; + +- (id)eventCache; +- (id)serverCache; +- (id)completeServerCacheFor:(FPath *)path; +- (BOOL)isEmpty; + +- (void)addEventRegistration:(id)eventRegistration; +- (NSArray *)removeEventRegistration:(id)eventRegistration + cancelError:(NSError *)cancelError; + +- (FViewOperationResult *)applyOperation:(id)operation + writesCache:(FWriteTreeRef *)writesCache + serverCache:(id)optCompleteServerCache; +- (NSArray *)initialEvents:(id)registration; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FView.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FView.m new file mode 100644 index 0000000..a15e13d --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FView.m @@ -0,0 +1,266 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FView.h" +#import "FCacheNode.h" +#import "FCancelEvent.h" +#import "FEmptyNode.h" +#import "FEventGenerator.h" +#import "FEventRegistration.h" +#import "FIRDatabaseQuery.h" +#import "FIRDatabaseQuery_Private.h" +#import "FIndexedFilter.h" +#import "FIndexedNode.h" +#import "FNode.h" +#import "FOperation.h" +#import "FOperationSource.h" +#import "FPath.h" +#import "FQueryParams.h" +#import "FQuerySpec.h" +#import "FViewCache.h" +#import "FViewProcessor.h" +#import "FViewProcessorResult.h" +#import "FWriteTreeRef.h" + +@interface FViewOperationResult () + +@property(nonatomic, strong, readwrite) NSArray *changes; +@property(nonatomic, strong, readwrite) NSArray *events; + +@end + +@implementation FViewOperationResult + +- (id)initWithChanges:(NSArray *)changes events:(NSArray *)events { + self = [super init]; + if (self != nil) { + self->_changes = changes; + self->_events = events; + } + return self; +} + +@end + +/** + * A view represents a specific location and query that has 1 or more event + * registrations. + * + * It does several things: + * - Maintains the list of event registration for this location/query. + * - Maintains a cache of the data visible for this location/query. + * - Applies new operations (via applyOperation), updates the cache, and based + * on the event registrations returns the set of events to be raised. + */ +@interface FView () + +@property(nonatomic, strong, readwrite) FQuerySpec *query; +@property(nonatomic, strong) FViewProcessor *processor; +@property(nonatomic, strong) FViewCache *viewCache; +@property(nonatomic, strong) NSMutableArray *eventRegistrations; +@property(nonatomic, strong) FEventGenerator *eventGenerator; + +@end + +@implementation FView +- (id)initWithQuery:(FQuerySpec *)query + initialViewCache:(FViewCache *)initialViewCache { + self = [super init]; + if (self) { + self.query = query; + + FIndexedFilter *indexFilter = + [[FIndexedFilter alloc] initWithIndex:query.index]; + id filter = query.params.nodeFilter; + self.processor = [[FViewProcessor alloc] initWithFilter:filter]; + FCacheNode *initialServerCache = initialViewCache.cachedServerSnap; + FCacheNode *initialEventCache = initialViewCache.cachedEventSnap; + + // Don't filter server node with other filter than index, wait for + // tagged listen + FIndexedNode *emptyIndexedNode = + [FIndexedNode indexedNodeWithNode:[FEmptyNode emptyNode] + index:query.index]; + FIndexedNode *serverSnap = + [indexFilter updateFullNode:emptyIndexedNode + withNewNode:initialServerCache.indexedNode + accumulator:nil]; + FIndexedNode *eventSnap = + [filter updateFullNode:emptyIndexedNode + withNewNode:initialEventCache.indexedNode + accumulator:nil]; + FCacheNode *newServerCache = [[FCacheNode alloc] + initWithIndexedNode:serverSnap + isFullyInitialized:initialServerCache.isFullyInitialized + isFiltered:indexFilter.filtersNodes]; + FCacheNode *newEventCache = [[FCacheNode alloc] + initWithIndexedNode:eventSnap + isFullyInitialized:initialEventCache.isFullyInitialized + isFiltered:filter.filtersNodes]; + + self.viewCache = [[FViewCache alloc] initWithEventCache:newEventCache + serverCache:newServerCache]; + + self.eventRegistrations = [[NSMutableArray alloc] init]; + + self.eventGenerator = [[FEventGenerator alloc] initWithQuery:query]; + } + + return self; +} + +- (id)serverCache { + return self.viewCache.cachedServerSnap.node; +} + +- (id)eventCache { + return self.viewCache.cachedEventSnap.node; +} + +- (id)completeServerCacheFor:(FPath *)path { + id cache = self.viewCache.completeServerSnap; + if (cache) { + // If this isn't a "loadsAllData" view, then cache isn't actually a + // complete cache and we need to see if it contains the child we're + // interested in. + if ([self.query loadsAllData] || + (!path.isEmpty && + ![cache getImmediateChild:path.getFront].isEmpty)) { + return [cache getChild:path]; + } + } + return nil; +} + +- (BOOL)isEmpty { + return self.eventRegistrations.count == 0; +} + +- (void)addEventRegistration:(id)eventRegistration { + [self.eventRegistrations addObject:eventRegistration]; +} + +/** + * @param eventRegistration If null, remove all callbacks. + * @param cancelError If a cancelError is provided, appropriate cancel events + * will be returned. + * @return Cancel events, if cancelError was provided. + */ +- (NSArray *)removeEventRegistration:(id)eventRegistration + cancelError:(NSError *)cancelError { + NSMutableArray *cancelEvents = [[NSMutableArray alloc] init]; + if (cancelError != nil) { + NSAssert(eventRegistration == nil, + @"A cancel should cancel all event registrations."); + FPath *path = self.query.path; + for (id registration in self.eventRegistrations) { + FCancelEvent *maybeEvent = + [registration createCancelEventFromError:cancelError path:path]; + if (maybeEvent) { + [cancelEvents addObject:maybeEvent]; + } + } + } + + if (eventRegistration) { + NSUInteger i = 0; + while (i < self.eventRegistrations.count) { + id existing = self.eventRegistrations[i]; + if ([existing matches:eventRegistration]) { + [self.eventRegistrations removeObjectAtIndex:i]; + } else { + i++; + } + } + } else { + [self.eventRegistrations removeAllObjects]; + } + return cancelEvents; +} + +/** + * Applies the given Operation, updates our cache, and returns the appropriate + * events and changes + */ +- (FViewOperationResult *)applyOperation:(id)operation + writesCache:(FWriteTreeRef *)writesCache + serverCache:(id)optCompleteServerCache { + if (operation.type == FOperationTypeMerge && + operation.source.queryParams != nil) { + NSAssert(self.viewCache.completeServerSnap != nil, + @"We should always have a full cache before handling merges"); + NSAssert(self.viewCache.completeEventSnap != nil, + @"Missing event cache, even though we have a server cache"); + } + FViewCache *oldViewCache = self.viewCache; + FViewProcessorResult *result = + [self.processor applyOperationOn:oldViewCache + operation:operation + writesCache:writesCache + completeCache:optCompleteServerCache]; + + NSAssert(result.viewCache.cachedServerSnap.isFullyInitialized || + !oldViewCache.cachedServerSnap.isFullyInitialized, + @"Once a server snap is complete, it should never go back."); + + self.viewCache = result.viewCache; + NSArray *events = [self + generateEventsForChanges:result.changes + eventCache:result.viewCache.cachedEventSnap.indexedNode + registration:nil]; + return [[FViewOperationResult alloc] initWithChanges:result.changes + events:events]; +} + +- (NSArray *)initialEvents:(id)registration { + FCacheNode *eventSnap = self.viewCache.cachedEventSnap; + NSMutableArray *initialChanges = [[NSMutableArray alloc] init]; + [eventSnap.indexedNode.node enumerateChildrenUsingBlock:^( + NSString *key, id node, BOOL *stop) { + FIndexedNode *indexed = [FIndexedNode indexedNodeWithNode:node]; + FChange *change = [[FChange alloc] initWithType:FIRDataEventTypeChildAdded + indexedNode:indexed + childKey:key]; + [initialChanges addObject:change]; + }]; + if (eventSnap.isFullyInitialized) { + FChange *change = [[FChange alloc] initWithType:FIRDataEventTypeValue + indexedNode:eventSnap.indexedNode]; + [initialChanges addObject:change]; + } + return [self generateEventsForChanges:initialChanges + eventCache:eventSnap.indexedNode + registration:registration]; +} + +- (NSArray *)generateEventsForChanges:(NSArray *)changes + eventCache:(FIndexedNode *)eventCache + registration:(id)registration { + NSArray *registrations; + if (registration == nil) { + registrations = [[NSArray alloc] initWithArray:self.eventRegistrations]; + } else { + registrations = [[NSArray alloc] initWithObjects:registration, nil]; + } + return [self.eventGenerator generateEventsForChanges:changes + eventCache:eventCache + eventRegistrations:registrations]; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"FView (%@)", self.query]; +} +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FViewCache.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FViewCache.h new file mode 100644 index 0000000..62618d2 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FViewCache.h @@ -0,0 +1,40 @@ +#/* +* Copyright 2017 Google +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#import + +@protocol FNode; +@class FCacheNode; +@class FIndexedNode; + +@interface FViewCache : NSObject + +- (id)initWithEventCache:(FCacheNode *)eventCache + serverCache:(FCacheNode *)serverCache; + +- (FViewCache *)updateEventSnap:(FIndexedNode *)eventSnap + isComplete:(BOOL)complete + isFiltered:(BOOL)filtered; +- (FViewCache *)updateServerSnap:(FIndexedNode *)serverSnap + isComplete:(BOOL)complete + isFiltered:(BOOL)filtered; + +@property(nonatomic, strong, readonly) FCacheNode *cachedEventSnap; +@property(nonatomic, strong, readonly) id completeEventSnap; +@property(nonatomic, strong, readonly) FCacheNode *cachedServerSnap; +@property(nonatomic, strong, readonly) id completeServerSnap; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FViewCache.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FViewCache.m new file mode 100644 index 0000000..13e9283 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/FViewCache.m @@ -0,0 +1,72 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FViewCache.h" +#import "FCacheNode.h" +#import "FEmptyNode.h" +#import "FNode.h" + +@interface FViewCache () +@property(nonatomic, strong, readwrite) FCacheNode *cachedEventSnap; +@property(nonatomic, strong, readwrite) FCacheNode *cachedServerSnap; +@end + +@implementation FViewCache + +- (id)initWithEventCache:(FCacheNode *)eventCache + serverCache:(FCacheNode *)serverCache { + self = [super init]; + if (self) { + self.cachedEventSnap = eventCache; + self.cachedServerSnap = serverCache; + } + return self; +} + +- (FViewCache *)updateEventSnap:(FIndexedNode *)eventSnap + isComplete:(BOOL)complete + isFiltered:(BOOL)filtered { + FCacheNode *updatedEventCache = + [[FCacheNode alloc] initWithIndexedNode:eventSnap + isFullyInitialized:complete + isFiltered:filtered]; + return [[FViewCache alloc] initWithEventCache:updatedEventCache + serverCache:self.cachedServerSnap]; +} + +- (FViewCache *)updateServerSnap:(FIndexedNode *)serverSnap + isComplete:(BOOL)complete + isFiltered:(BOOL)filtered { + FCacheNode *updatedServerCache = + [[FCacheNode alloc] initWithIndexedNode:serverSnap + isFullyInitialized:complete + isFiltered:filtered]; + return [[FViewCache alloc] initWithEventCache:self.cachedEventSnap + serverCache:updatedServerCache]; +} + +- (id)completeEventSnap { + return (self.cachedEventSnap.isFullyInitialized) ? self.cachedEventSnap.node + : nil; +} + +- (id)completeServerSnap { + return (self.cachedServerSnap.isFullyInitialized) + ? self.cachedServerSnap.node + : nil; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/Filter/FChildChangeAccumulator.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/Filter/FChildChangeAccumulator.h new file mode 100644 index 0000000..bf25163 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/Filter/FChildChangeAccumulator.h @@ -0,0 +1,27 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FChange; + +@interface FChildChangeAccumulator : NSObject + +- (id)init; +- (void)trackChildChange:(FChange *)change; +- (NSArray *)changes; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/Filter/FChildChangeAccumulator.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/Filter/FChildChangeAccumulator.m new file mode 100644 index 0000000..e35c2e2 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/Filter/FChildChangeAccumulator.m @@ -0,0 +1,99 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FChildChangeAccumulator.h" +#import "FChange.h" +#import "FIndex.h" + +@interface FChildChangeAccumulator () +@property(nonatomic, strong) NSMutableDictionary *changeMap; +@end + +@implementation FChildChangeAccumulator + +- (id)init { + self = [super init]; + if (self) { + self.changeMap = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (void)trackChildChange:(FChange *)change { + FIRDataEventType type = change.type; + NSString *childKey = change.childKey; + NSAssert(type == FIRDataEventTypeChildAdded || + type == FIRDataEventTypeChildChanged || + type == FIRDataEventTypeChildRemoved, + @"Only child changes supported for tracking."); + NSAssert(![change.childKey isEqualToString:@".priority"], + @"Changes not tracked on priority"); + if (self.changeMap[childKey] != nil) { + FChange *oldChange = [self.changeMap objectForKey:childKey]; + FIRDataEventType oldType = oldChange.type; + if (type == FIRDataEventTypeChildAdded && + oldType == FIRDataEventTypeChildRemoved) { + FChange *newChange = + [[FChange alloc] initWithType:FIRDataEventTypeChildChanged + indexedNode:change.indexedNode + childKey:childKey + oldIndexedNode:oldChange.indexedNode]; + [self.changeMap setObject:newChange forKey:childKey]; + } else if (type == FIRDataEventTypeChildRemoved && + oldType == FIRDataEventTypeChildAdded) { + [self.changeMap removeObjectForKey:childKey]; + } else if (type == FIRDataEventTypeChildRemoved && + oldType == FIRDataEventTypeChildChanged) { + FChange *newChange = + [[FChange alloc] initWithType:FIRDataEventTypeChildRemoved + indexedNode:oldChange.oldIndexedNode + childKey:childKey]; + [self.changeMap setObject:newChange forKey:childKey]; + } else if (type == FIRDataEventTypeChildChanged && + oldType == FIRDataEventTypeChildAdded) { + FChange *newChange = + [[FChange alloc] initWithType:FIRDataEventTypeChildAdded + indexedNode:change.indexedNode + childKey:childKey]; + [self.changeMap setObject:newChange forKey:childKey]; + } else if (type == FIRDataEventTypeChildChanged && + oldType == FIRDataEventTypeChildChanged) { + FChange *newChange = + [[FChange alloc] initWithType:FIRDataEventTypeChildChanged + indexedNode:change.indexedNode + childKey:childKey + oldIndexedNode:oldChange.oldIndexedNode]; + [self.changeMap setObject:newChange forKey:childKey]; + } else { + NSString *reason = [NSString + stringWithFormat: + @"Illegal combination of changes: %@ occurred after %@", + change, oldChange]; + @throw [[NSException alloc] + initWithName:@"FirebaseDatabaseInternalError" + reason:reason + userInfo:nil]; + } + } else { + [self.changeMap setObject:change forKey:childKey]; + } +} + +- (NSArray *)changes { + return [self.changeMap allValues]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/Filter/FCompleteChildSource.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/Filter/FCompleteChildSource.h new file mode 100644 index 0000000..0c04bc1 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/Filter/FCompleteChildSource.h @@ -0,0 +1,30 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@protocol FNode; +@class FNamedNode; +@protocol FIndex; + +@protocol FCompleteChildSource + +- (id)completeChild:(NSString *)childKey; +- (FNamedNode *)childByIndex:(id)index + afterChild:(FNamedNode *)child + isReverse:(BOOL)reverse; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/Filter/FIndexedFilter.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/Filter/FIndexedFilter.h new file mode 100644 index 0000000..291e79a --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/Filter/FIndexedFilter.h @@ -0,0 +1,26 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FNodeFilter.h" +#import + +@protocol FIndex; + +@interface FIndexedFilter : NSObject + +- (id)initWithIndex:(id)theIndex; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/Filter/FIndexedFilter.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/Filter/FIndexedFilter.m new file mode 100644 index 0000000..f49af79 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/Filter/FIndexedFilter.m @@ -0,0 +1,164 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIndexedFilter.h" +#import "FChange.h" +#import "FChildChangeAccumulator.h" +#import "FChildrenNode.h" +#import "FEmptyNode.h" +#import "FIndex.h" +#import "FIndexedNode.h" +#import "FKeyIndex.h" +#import "FNode.h" + +@interface FIndexedFilter () +@property(nonatomic, strong, readwrite) id index; +@end + +@implementation FIndexedFilter +- (id)initWithIndex:(id)theIndex { + self = [super init]; + if (self) { + self.index = theIndex; + } + return self; +} + +- (FIndexedNode *)updateChildIn:(FIndexedNode *)indexedNode + forChildKey:(NSString *)childKey + newChild:(id)newChildSnap + affectedPath:(FPath *)affectedPath + fromSource:(id)source + accumulator: + (FChildChangeAccumulator *)optChangeAccumulator { + NSAssert([indexedNode hasIndex:self.index], + @"The index in FIndexedNode must match the index of the filter"); + id node = indexedNode.node; + id oldChildSnap = [node getImmediateChild:childKey]; + + // Check if anything actually changed. + if ([[oldChildSnap getChild:affectedPath] + isEqual:[newChildSnap getChild:affectedPath]]) { + // There's an edge case where a child can enter or leave the view + // because affectedPath was set to null. In this case, affectedPath will + // appear null in both the old and new snapshots. So we need to avoid + // treating these cases as "nothing changed." + if (oldChildSnap.isEmpty == newChildSnap.isEmpty) { +// Nothing changed. +#ifdef DEBUG + NSAssert([oldChildSnap isEqual:newChildSnap], + @"Old and new snapshots should be equal."); +#endif + + return indexedNode; + } + } + if (optChangeAccumulator) { + if (newChildSnap.isEmpty) { + if ([node hasChild:childKey]) { + FChange *change = [[FChange alloc] + initWithType:FIRDataEventTypeChildRemoved + indexedNode:[FIndexedNode indexedNodeWithNode:oldChildSnap] + childKey:childKey]; + [optChangeAccumulator trackChildChange:change]; + } else { + NSAssert(node.isLeafNode, + @"A child remove without an old child only makes " + @"sense on a leaf node."); + } + } else if (oldChildSnap.isEmpty) { + FChange *change = [[FChange alloc] + initWithType:FIRDataEventTypeChildAdded + indexedNode:[FIndexedNode indexedNodeWithNode:newChildSnap] + childKey:childKey]; + [optChangeAccumulator trackChildChange:change]; + } else { + FChange *change = [[FChange alloc] + initWithType:FIRDataEventTypeChildChanged + indexedNode:[FIndexedNode indexedNodeWithNode:newChildSnap] + childKey:childKey + oldIndexedNode:[FIndexedNode indexedNodeWithNode:oldChildSnap]]; + [optChangeAccumulator trackChildChange:change]; + } + } + if (node.isLeafNode && newChildSnap.isEmpty) { + return indexedNode; + } else { + return [indexedNode updateChild:childKey withNewChild:newChildSnap]; + } +} + +- (FIndexedNode *)updateFullNode:(FIndexedNode *)oldSnap + withNewNode:(FIndexedNode *)newSnap + accumulator: + (FChildChangeAccumulator *)optChangeAccumulator { + if (optChangeAccumulator) { + [oldSnap.node enumerateChildrenUsingBlock:^( + NSString *childKey, id childNode, BOOL *stop) { + if (![newSnap.node hasChild:childKey]) { + FChange *change = [[FChange alloc] + initWithType:FIRDataEventTypeChildRemoved + indexedNode:[FIndexedNode indexedNodeWithNode:childNode] + childKey:childKey]; + [optChangeAccumulator trackChildChange:change]; + } + }]; + + [newSnap.node enumerateChildrenUsingBlock:^( + NSString *childKey, id childNode, BOOL *stop) { + if ([oldSnap.node hasChild:childKey]) { + id oldChildSnap = + [oldSnap.node getImmediateChild:childKey]; + if (![oldChildSnap isEqual:childNode]) { + FChange *change = [[FChange alloc] + initWithType:FIRDataEventTypeChildChanged + indexedNode:[FIndexedNode + indexedNodeWithNode:childNode] + childKey:childKey + oldIndexedNode:[FIndexedNode + indexedNodeWithNode:oldChildSnap]]; + [optChangeAccumulator trackChildChange:change]; + } + } else { + FChange *change = [[FChange alloc] + initWithType:FIRDataEventTypeChildAdded + indexedNode:[FIndexedNode indexedNodeWithNode:childNode] + childKey:childKey]; + [optChangeAccumulator trackChildChange:change]; + } + }]; + } + return newSnap; +} + +- (FIndexedNode *)updatePriority:(id)priority + forNode:(FIndexedNode *)oldSnap { + if ([oldSnap.node isEmpty]) { + return oldSnap; + } else { + return [oldSnap updatePriority:priority]; + } +} + +- (BOOL)filtersNodes { + return NO; +} + +- (id)indexedFilter { + return self; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/Filter/FLimitedFilter.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/Filter/FLimitedFilter.h new file mode 100644 index 0000000..0642e72 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/Filter/FLimitedFilter.h @@ -0,0 +1,25 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FNodeFilter.h" +#import + +@class FQueryParams; + +@interface FLimitedFilter : NSObject + +- (id)initWithQueryParams:(FQueryParams *)params; +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/Filter/FLimitedFilter.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/Filter/FLimitedFilter.m new file mode 100644 index 0000000..fab9aab --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/Filter/FLimitedFilter.m @@ -0,0 +1,285 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FLimitedFilter.h" +#import "FChange.h" +#import "FChildChangeAccumulator.h" +#import "FChildrenNode.h" +#import "FCompleteChildSource.h" +#import "FEmptyNode.h" +#import "FIndex.h" +#import "FNamedNode.h" +#import "FQueryParams.h" +#import "FRangedFilter.h" +#import "FTreeSortedDictionary.h" + +@interface FLimitedFilter () +@property(nonatomic, strong) FRangedFilter *rangedFilter; +@property(nonatomic, strong, readwrite) id index; +@property(nonatomic) NSInteger limit; +@property(nonatomic) BOOL reverse; + +@end + +@implementation FLimitedFilter +- (id)initWithQueryParams:(FQueryParams *)params { + self = [super init]; + if (self) { + self.rangedFilter = [[FRangedFilter alloc] initWithQueryParams:params]; + self.index = params.index; + self.limit = params.limit; + self.reverse = !params.isViewFromLeft; + } + return self; +} + +- (FIndexedNode *)updateChildIn:(FIndexedNode *)oldSnap + forChildKey:(NSString *)childKey + newChild:(id)newChildSnap + affectedPath:(FPath *)affectedPath + fromSource:(id)source + accumulator: + (FChildChangeAccumulator *)optChangeAccumulator { + if (![self.rangedFilter matchesKey:childKey andNode:newChildSnap]) { + newChildSnap = [FEmptyNode emptyNode]; + } + if ([[oldSnap.node getImmediateChild:childKey] isEqual:newChildSnap]) { + // No change + return oldSnap; + } else if (oldSnap.node.numChildren < self.limit) { + return [[self.rangedFilter indexedFilter] + updateChildIn:oldSnap + forChildKey:childKey + newChild:newChildSnap + affectedPath:affectedPath + fromSource:source + accumulator:optChangeAccumulator]; + } else { + return [self fullLimitUpdateNode:oldSnap + forChildKey:childKey + newChild:newChildSnap + fromSource:source + accumulator:optChangeAccumulator]; + } +} + +- (FIndexedNode *)fullLimitUpdateNode:(FIndexedNode *)oldIndexed + forChildKey:(NSString *)childKey + newChild:(id)newChildSnap + fromSource:(id)source + accumulator: + (FChildChangeAccumulator *)optChangeAccumulator { + NSAssert(oldIndexed.node.numChildren == self.limit, + @"Should have number of children equal to limit."); + + FNamedNode *windowBoundary = + self.reverse ? oldIndexed.firstChild : oldIndexed.lastChild; + + BOOL inRange = [self.rangedFilter matchesKey:childKey andNode:newChildSnap]; + if ([oldIndexed.node hasChild:childKey]) { + // `childKey` was already in `oldSnap`. Figure out if it remains in the + // window or needs to be replaced. + id oldChildSnap = [oldIndexed.node getImmediateChild:childKey]; + + // In case the `newChildSnap` falls outside the window, get the + // `nextChild` that might replace it. + FNamedNode *nextChild = [source childByIndex:self.index + afterChild:windowBoundary + isReverse:(BOOL)self.reverse]; + if (nextChild != nil && ([nextChild.name isEqualToString:childKey] || + [oldIndexed.node hasChild:nextChild.name])) { + // There is a weird edge case where a node is updated as part of a + // merge in the write tree, but hasn't been applied to the limited + // filter yet. Ignore this next child which will be updated later in + // the limited filter... + nextChild = [source childByIndex:self.index + afterChild:nextChild + isReverse:self.reverse]; + } + + // Figure out if `newChildSnap` is in range and ordered before + // `nextChild` + BOOL remainsInWindow = inRange && !newChildSnap.isEmpty; + remainsInWindow = remainsInWindow && + (!nextChild || [self.index compareKey:nextChild.name + andNode:nextChild.node + toOtherKey:childKey + andNode:newChildSnap + reverse:self.reverse] >= + NSOrderedSame); + if (remainsInWindow) { + // `newChildSnap` is ordered before `nextChild`, so it's a child + // changed event + if (optChangeAccumulator != nil) { + FChange *change = [[FChange alloc] + initWithType:FIRDataEventTypeChildChanged + indexedNode:[FIndexedNode + indexedNodeWithNode:newChildSnap] + childKey:childKey + oldIndexedNode:[FIndexedNode + indexedNodeWithNode:oldChildSnap]]; + [optChangeAccumulator trackChildChange:change]; + } + return [oldIndexed updateChild:childKey withNewChild:newChildSnap]; + } else { + // `newChildSnap` is ordered after `nextChild`, so it's a child + // removed event + if (optChangeAccumulator != nil) { + FChange *change = [[FChange alloc] + initWithType:FIRDataEventTypeChildRemoved + indexedNode:[FIndexedNode indexedNodeWithNode:oldChildSnap] + childKey:childKey]; + [optChangeAccumulator trackChildChange:change]; + } + FIndexedNode *newIndexed = + [oldIndexed updateChild:childKey + withNewChild:[FEmptyNode emptyNode]]; + + // We need to check if the `nextChild` is actually in range before + // adding it + BOOL nextChildInRange = + (nextChild != nil) && + [self.rangedFilter matchesKey:nextChild.name + andNode:nextChild.node]; + if (nextChildInRange) { + if (optChangeAccumulator != nil) { + FChange *change = [[FChange alloc] + initWithType:FIRDataEventTypeChildAdded + indexedNode:[FIndexedNode + indexedNodeWithNode:nextChild.node] + childKey:nextChild.name]; + [optChangeAccumulator trackChildChange:change]; + } + return [newIndexed updateChild:nextChild.name + withNewChild:nextChild.node]; + } else { + return newIndexed; + } + } + } else if (newChildSnap.isEmpty) { + // We're deleting a node, but it was not in the window, so ignore it. + return oldIndexed; + } else if (inRange) { + // `newChildSnap` is in range, but was ordered after `windowBoundary`. + // If this has changed, we bump out the `windowBoundary` and add the + // `newChildSnap` + if ([self.index compareKey:windowBoundary.name + andNode:windowBoundary.node + toOtherKey:childKey + andNode:newChildSnap + reverse:self.reverse] >= NSOrderedSame) { + if (optChangeAccumulator != nil) { + FChange *removedChange = [[FChange alloc] + initWithType:FIRDataEventTypeChildRemoved + indexedNode:[FIndexedNode + indexedNodeWithNode:windowBoundary.node] + childKey:windowBoundary.name]; + FChange *addedChange = [[FChange alloc] + initWithType:FIRDataEventTypeChildAdded + indexedNode:[FIndexedNode indexedNodeWithNode:newChildSnap] + childKey:childKey]; + [optChangeAccumulator trackChildChange:removedChange]; + [optChangeAccumulator trackChildChange:addedChange]; + } + return [[oldIndexed updateChild:childKey withNewChild:newChildSnap] + updateChild:windowBoundary.name + withNewChild:[FEmptyNode emptyNode]]; + } else { + return oldIndexed; + } + } else { + // `newChildSnap` was not in range and remains not in range, so ignore + // it. + return oldIndexed; + } +} + +- (FIndexedNode *)updateFullNode:(FIndexedNode *)oldSnap + withNewNode:(FIndexedNode *)newSnap + accumulator: + (FChildChangeAccumulator *)optChangeAccumulator { + __block FIndexedNode *filtered; + if (newSnap.node.isLeafNode || newSnap.node.isEmpty) { + // Make sure we have a children node with the correct index, not a leaf + // node + filtered = [FIndexedNode indexedNodeWithNode:[FEmptyNode emptyNode] + index:self.index]; + } else { + filtered = newSnap; + // Don't support priorities on queries. + filtered = [filtered updatePriority:[FEmptyNode emptyNode]]; + FNamedNode *startPost = nil; + FNamedNode *endPost = nil; + if (self.reverse) { + startPost = self.rangedFilter.endPost; + endPost = self.rangedFilter.startPost; + } else { + startPost = self.rangedFilter.startPost; + endPost = self.rangedFilter.endPost; + } + __block BOOL foundStartPost = NO; + __block NSUInteger count = 0; + [newSnap + enumerateChildrenReverse:self.reverse + usingBlock:^(NSString *childKey, id childNode, + BOOL *stop) { + if (!foundStartPost && + [self.index + compareKey:startPost.name + andNode:startPost.node + toOtherKey:childKey + andNode:childNode + reverse:self.reverse] <= NSOrderedSame) { + // Start adding + foundStartPost = YES; + } + BOOL inRange = foundStartPost && count < self.limit; + inRange = inRange && + [self.index compareKey:childKey + andNode:childNode + toOtherKey:endPost.name + andNode:endPost.node + reverse:self.reverse] <= + NSOrderedSame; + if (inRange) { + count++; + } else { + filtered = [filtered + updateChild:childKey + withNewChild:[FEmptyNode emptyNode]]; + } + }]; + } + return [self.indexedFilter updateFullNode:oldSnap + withNewNode:filtered + accumulator:optChangeAccumulator]; +} + +- (FIndexedNode *)updatePriority:(id)priority + forNode:(FIndexedNode *)oldSnap { + // Don't support priorities on queries. + return oldSnap; +} + +- (BOOL)filtersNodes { + return YES; +} + +- (id)indexedFilter { + return self.rangedFilter.indexedFilter; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/Filter/FNodeFilter.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/Filter/FNodeFilter.h new file mode 100644 index 0000000..d19c6fb --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Core/View/Filter/FNodeFilter.h @@ -0,0 +1,77 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@protocol FNode; +@class FIndexedNode; +@protocol FCompleteChildSource; +@class FChildChangeAccumulator; +@protocol FIndex; +@class FPath; + +/** + * FNodeFilter is used to update nodes and complete children of nodes while + * applying queries on the fly and keeping track of any child changes. This + * class does not track value changes as value changes depend on more than just + * the node itself. Different kind of queries require different kind of + * implementations of this interface. + */ +@protocol FNodeFilter + +/** + * Update a single complete child in the snap. If the child equals the old child + * in the snap, this is a no-op. The method expects an indexed snap. + */ +- (FIndexedNode *)updateChildIn:(FIndexedNode *)oldSnap + forChildKey:(NSString *)childKey + newChild:(id)newChildSnap + affectedPath:(FPath *)affectedPath + fromSource:(id)source + accumulator:(FChildChangeAccumulator *)optChangeAccumulator; + +/** + * Update a node in full and output any resulting change from this complete + * update. + */ +- (FIndexedNode *)updateFullNode:(FIndexedNode *)oldSnap + withNewNode:(FIndexedNode *)newSnap + accumulator: + (FChildChangeAccumulator *)optChangeAccumulator; + +/** + * Update the priority of the root node + */ +- (FIndexedNode *)updatePriority:(id)priority + forNode:(FIndexedNode *)oldSnap; + +/** + * Returns true if children might be filtered due to query critiera + */ +- (BOOL)filtersNodes; + +/** + * Returns the index filter that this filter uses to get a NodeFilter that + * doesn't filter any children. + */ +@property(nonatomic, strong, readonly) id indexedFilter; + +/** + * Returns the index that this filter uses + */ +@property(nonatomic, strong, readonly) id index; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FClock.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/FClock.h new file mode 100644 index 0000000..e85cb2a --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FClock.h @@ -0,0 +1,35 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@protocol FClock + +- (NSTimeInterval)currentTime; + +@end + +@interface FSystemClock : NSObject + ++ (FSystemClock *)clock; + +@end + +@interface FOffsetClock : NSObject + +- (id)initWithClock:(id)clock offset:(NSTimeInterval)offset; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FClock.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/FClock.m new file mode 100644 index 0000000..5d9a9ec --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FClock.m @@ -0,0 +1,58 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FClock.h" + +@implementation FSystemClock + +- (NSTimeInterval)currentTime { + return [[NSDate date] timeIntervalSince1970]; +} + ++ (FSystemClock *)clock { + static dispatch_once_t onceToken; + static FSystemClock *clock; + dispatch_once(&onceToken, ^{ + clock = [[FSystemClock alloc] init]; + }); + return clock; +} + +@end + +@interface FOffsetClock () + +@property(nonatomic, strong) id clock; +@property(nonatomic) NSTimeInterval offset; + +@end + +@implementation FOffsetClock + +- (NSTimeInterval)currentTime { + return [self.clock currentTime] + self.offset; +} + +- (id)initWithClock:(id)clock offset:(NSTimeInterval)offset { + self = [super init]; + if (self != nil) { + self->_clock = clock; + self->_offset = offset; + } + return self; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FEventGenerator.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/FEventGenerator.h new file mode 100644 index 0000000..443664e --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FEventGenerator.h @@ -0,0 +1,28 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FQuerySpec; +@class FIndexedNode; +@protocol FNode; + +@interface FEventGenerator : NSObject +- (id)initWithQuery:(FQuerySpec *)query; +- (NSArray *)generateEventsForChanges:(NSArray *)changes + eventCache:(FIndexedNode *)eventCache + eventRegistrations:(NSArray *)registrations; +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FEventGenerator.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/FEventGenerator.m new file mode 100644 index 0000000..162b0ac --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FEventGenerator.m @@ -0,0 +1,169 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FEventGenerator.h" +#import "FChange.h" +#import "FDataEvent.h" +#import "FEvent.h" +#import "FEventRegistration.h" +#import "FIRDatabaseQuery_Private.h" +#import "FNamedNode.h" +#import "FNode.h" +#import "FQueryParams.h" +#import "FQuerySpec.h" + +@interface FEventGenerator () +@property(nonatomic, strong) FQuerySpec *query; +@end + +/** + * An EventGenerator is used to convert "raw" changes (fb.core.view.Change) as + * computed by the CacheDiffer into actual events (fb.core.view.Event) that can + * be raised. See generateEventsForChanges() for details. + */ +@implementation FEventGenerator + +- (id)initWithQuery:(FQuerySpec *)query { + self = [super init]; + if (self) { + self.query = query; + } + return self; +} + +/** + * Given a set of raw changes (no moved events, and prevName not specified yet), + * and a set of EventRegistrations that should be notified of these changes, + * generate the actual events to be raised. + * + * Notes: + * - child_moved events will be synthesized at this time for any child_changed + * events that affect our index + * - prevName will be calculated based on the index ordering + * + * @param changes NSArray of FChange, not necessarily in order. + * @param registrations is NSArray of FEventRegistration. + * @return NSArray of FEvent. + */ +- (NSArray *)generateEventsForChanges:(NSArray *)changes + eventCache:(FIndexedNode *)eventCache + eventRegistrations:(NSArray *)registrations { + NSMutableArray *events = [[NSMutableArray alloc] init]; + + // child_moved is index-specific, so check all our child_changed events to + // see if we need to materialize child_moved events with this view's index + NSMutableArray *moves = [[NSMutableArray alloc] init]; + for (FChange *change in changes) { + if (change.type == FIRDataEventTypeChildChanged && + [self.query.index + indexedValueChangedBetween:change.oldIndexedNode.node + and:change.indexedNode.node]) { + FChange *moveChange = + [[FChange alloc] initWithType:FIRDataEventTypeChildMoved + indexedNode:change.indexedNode + childKey:change.childKey + oldIndexedNode:nil]; + [moves addObject:moveChange]; + } + } + + [self generateEvents:events + forType:FIRDataEventTypeChildRemoved + changes:changes + eventCache:eventCache + eventRegistrations:registrations]; + [self generateEvents:events + forType:FIRDataEventTypeChildAdded + changes:changes + eventCache:eventCache + eventRegistrations:registrations]; + [self generateEvents:events + forType:FIRDataEventTypeChildMoved + changes:moves + eventCache:eventCache + eventRegistrations:registrations]; + [self generateEvents:events + forType:FIRDataEventTypeChildChanged + changes:changes + eventCache:eventCache + eventRegistrations:registrations]; + [self generateEvents:events + forType:FIRDataEventTypeValue + changes:changes + eventCache:eventCache + eventRegistrations:registrations]; + + return events; +} + +- (void)generateEvents:(NSMutableArray *)events + forType:(FIRDataEventType)eventType + changes:(NSArray *)changes + eventCache:(FIndexedNode *)eventCache + eventRegistrations:(NSArray *)registrations { + NSMutableArray *filteredChanges = [[NSMutableArray alloc] init]; + for (FChange *change in changes) { + if (change.type == eventType) { + [filteredChanges addObject:change]; + } + } + + id index = self.query.index; + + [filteredChanges + sortUsingComparator:^NSComparisonResult(FChange *one, FChange *two) { + if (one.childKey == nil || two.childKey == nil) { + @throw [[NSException alloc] + initWithName:@"InternalInconsistencyError" + reason:@"Should only compare child_ events" + userInfo:nil]; + } + return [index compareKey:one.childKey + andNode:one.indexedNode.node + toOtherKey:two.childKey + andNode:two.indexedNode.node]; + }]; + + for (FChange *change in filteredChanges) { + for (id registration in registrations) { + if ([registration responseTo:eventType]) { + id event = [self generateEventForChange:change + registration:registration + eventCache:eventCache]; + [events addObject:event]; + } + } + } +} + +- (id)generateEventForChange:(FChange *)change + registration:(id)registration + eventCache:(FIndexedNode *)eventCache { + FChange *materializedChange; + if (change.type == FIRDataEventTypeValue || + change.type == FIRDataEventTypeChildRemoved) { + materializedChange = change; + } else { + NSString *prevChildKey = + [eventCache predecessorForChildKey:change.childKey + childNode:change.indexedNode.node + index:self.query.index]; + materializedChange = [change changeWithPrevKey:prevChildKey]; + } + return [registration createEventFrom:materializedChange query:self.query]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FIRDatabaseConfig_Private.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/FIRDatabaseConfig_Private.h new file mode 100644 index 0000000..ca2f34b --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FIRDatabaseConfig_Private.h @@ -0,0 +1,31 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FAuthTokenProvider.h" +#import "FIRDatabaseConfig.h" + +@protocol FStorageEngine; + +@interface FIRDatabaseConfig () + +@property(nonatomic, readonly) BOOL isFrozen; +@property(nonatomic, strong, readonly) NSString *sessionIdentifier; +@property(nonatomic, strong) id authTokenProvider; +@property(nonatomic, strong) id forceStorageEngine; + +- (void)freeze; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FIRDatabaseReference.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/FIRDatabaseReference.m new file mode 100644 index 0000000..b3c0106 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FIRDatabaseReference.m @@ -0,0 +1,528 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRDatabaseReference.h" +#import "FIRDatabase.h" +#import "FIRDatabaseConfig.h" +#import "FIRDatabaseConfig_Private.h" +#import "FIRDatabaseQuery_Private.h" +#import "FIRDatabaseReference_Private.h" +#import "FNextPushId.h" +#import "FQueryParams.h" +#import "FSnapshotUtilities.h" +#import "FStringUtilities.h" +#import "FUtilities.h" +#import "FValidation.h" +#import +#import + +@implementation FIRDatabaseReference + +#pragma mark - +#pragma mark Constructors + +- (id)initWithConfig:(FIRDatabaseConfig *)config { + FParsedUrl *parsedUrl = + [FUtilities parseUrl:[[FIRApp defaultApp] options].databaseURL]; + [FValidation validateFrom:@"initWithUrl:" validURL:parsedUrl]; + return [self initWithRepo:[FRepoManager getRepo:parsedUrl.repoInfo + config:config] + path:parsedUrl.path]; +} + +- (id)initWithRepo:(FRepo *)repo path:(FPath *)path { + return [super initWithRepo:repo + path:path + params:[FQueryParams defaultInstance] + orderByCalled:NO + priorityMethodCalled:NO]; +} + +#pragma mark - +#pragma mark Ancillary methods + +- (nullable NSString *)key { + if ([self.path isEmpty]) { + return nil; + } else { + return [self.path getBack]; + } +} + +- (FIRDatabase *)database { + return self.repo.database; +} + +- (FIRDatabaseReference *)parent { + FPath *parentPath = [self.path parent]; + FIRDatabaseReference *parent = nil; + if (parentPath != nil) { + parent = [[FIRDatabaseReference alloc] initWithRepo:self.repo + path:parentPath]; + } + return parent; +} + +- (NSString *)URL { + FIRDatabaseReference *parent = [self parent]; + return parent == nil + ? [self.repo description] + : [NSString + stringWithFormat:@"%@/%@", [parent description], + [FStringUtilities urlEncoded:self.key]]; +} + +- (NSString *)description { + return [self URL]; +} + +- (FIRDatabaseReference *)root { + return [[FIRDatabaseReference alloc] + initWithRepo:self.repo + path:[[FPath alloc] initWith:@""]]; +} + +#pragma mark - +#pragma mark Child methods + +- (FIRDatabaseReference *)child:(NSString *)pathString { + if ([self.path getFront] == nil) { + // we're at the root + [FValidation validateFrom:@"child:" validRootPathString:pathString]; + } else { + [FValidation validateFrom:@"child:" validPathString:pathString]; + } + FPath *path = [self.path childFromString:pathString]; + FIRDatabaseReference *firebaseRef = + [[FIRDatabaseReference alloc] initWithRepo:self.repo path:path]; + return firebaseRef; +} + +- (FIRDatabaseReference *)childByAutoId { + [FValidation validateFrom:@"childByAutoId:" writablePath:self.path]; + + NSString *name = [FNextPushId get:self.repo.serverTime]; + return [self child:name]; +} + +#pragma mark - +#pragma mark Basic write methods + +- (void)setValue:(id)value { + [self setValueInternal:value + andPriority:nil + withCompletionBlock:nil + from:@"setValue:"]; +} + +- (void)setValue:(id)value withCompletionBlock:(fbt_void_nserror_ref)block { + [self setValueInternal:value + andPriority:nil + withCompletionBlock:block + from:@"setValue:withCompletionBlock:"]; +} + +- (void)setValue:(id)value andPriority:(id)priority { + [self setValueInternal:value + andPriority:priority + withCompletionBlock:nil + from:@"setValue:andPriority:"]; +} + +- (void)setValue:(id)value + andPriority:(id)priority + withCompletionBlock:(fbt_void_nserror_ref)block { + [self setValueInternal:value + andPriority:priority + withCompletionBlock:block + from:@"setValue:andPriority:withCompletionBlock:"]; +} + +- (void)setValueInternal:(id)value + andPriority:(id)priority + withCompletionBlock:(fbt_void_nserror_ref)block + from:(NSString *)fn { + [FValidation validateFrom:fn writablePath:self.path]; + + fbt_void_nserror_ref userCallback = [block copy]; + id newNode = [FSnapshotUtilities nodeFrom:value + priority:priority + withValidationFrom:fn]; + + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + [self.repo set:self.path withNode:newNode withCallback:userCallback]; + }); +} + +- (void)removeValue { + [self setValueInternal:nil + andPriority:nil + withCompletionBlock:nil + from:@"removeValue:"]; +} + +- (void)removeValueWithCompletionBlock:(fbt_void_nserror_ref)block { + [self setValueInternal:nil + andPriority:nil + withCompletionBlock:block + from:@"removeValueWithCompletionBlock:"]; +} + +- (void)setPriority:(id)priority { + [self setPriorityInternal:priority + withCompletionBlock:nil + from:@"setPriority:"]; +} + +- (void)setPriority:(id)priority + withCompletionBlock:(fbt_void_nserror_ref)block { + + [self setPriorityInternal:priority + withCompletionBlock:block + from:@"setPriority:withCompletionBlock:"]; +} + +- (void)setPriorityInternal:(id)priority + withCompletionBlock:(fbt_void_nserror_ref)block + from:(NSString *)fn { + [FValidation validateFrom:fn writablePath:self.path]; + + fbt_void_nserror_ref userCallback = [block copy]; + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + [self.repo set:[self.path childFromString:@".priority"] + withNode:[FSnapshotUtilities nodeFrom:priority] + withCallback:userCallback]; + }); +} + +- (void)updateChildValues:(NSDictionary *)values { + [self updateChildValuesInternal:values + withCompletionBlock:nil + from:@"updateChildValues:"]; +} + +- (void)updateChildValues:(NSDictionary *)values + withCompletionBlock:(fbt_void_nserror_ref)block { + [self updateChildValuesInternal:values + withCompletionBlock:block + from:@"updateChildValues:withCompletionBlock:"]; +} + +- (void)updateChildValuesInternal:(NSDictionary *)values + withCompletionBlock:(fbt_void_nserror_ref)block + from:(NSString *)fn { + [FValidation validateFrom:fn writablePath:self.path]; + + FCompoundWrite *merge = + [FSnapshotUtilities compoundWriteFromDictionary:values + withValidationFrom:fn]; + + fbt_void_nserror_ref userCallback = [block copy]; + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + [self.repo update:self.path withNodes:merge withCallback:userCallback]; + }); +} + +#pragma mark - +#pragma mark Disconnect Operations + +- (void)onDisconnectSetValue:(id)value { + [self onDisconnectSetValueInternal:value + andPriority:nil + withCompletionBlock:nil + from:@"onDisconnectSetValue:"]; +} + +- (void)onDisconnectSetValue:(id)value + withCompletionBlock:(fbt_void_nserror_ref)block { + [self onDisconnectSetValueInternal:value + andPriority:nil + withCompletionBlock:block + from:@"onDisconnectSetValue:" + @"withCompletionBlock:"]; +} + +- (void)onDisconnectSetValue:(id)value andPriority:(id)priority { + [self onDisconnectSetValueInternal:value + andPriority:priority + withCompletionBlock:nil + from:@"onDisconnectSetValue:andPriority:"]; +} + +- (void)onDisconnectSetValue:(id)value + andPriority:(id)priority + withCompletionBlock:(fbt_void_nserror_ref)block { + [self onDisconnectSetValueInternal:value + andPriority:priority + withCompletionBlock:block + from:@"onDisconnectSetValue:andPriority:" + @"withCompletionBlock:"]; +} + +- (void)onDisconnectSetValueInternal:(id)value + andPriority:(id)priority + withCompletionBlock:(fbt_void_nserror_ref)block + from:(NSString *)fn { + [FValidation validateFrom:fn writablePath:self.path]; + + id newNodeUnresolved = [FSnapshotUtilities nodeFrom:value + priority:priority + withValidationFrom:fn]; + + fbt_void_nserror_ref userCallback = [block copy]; + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + [self.repo onDisconnectSet:self.path + withNode:newNodeUnresolved + withCallback:userCallback]; + }); +} + +- (void)onDisconnectRemoveValue { + [self onDisconnectSetValueInternal:nil + andPriority:nil + withCompletionBlock:nil + from:@"onDisconnectRemoveValue:"]; +} + +- (void)onDisconnectRemoveValueWithCompletionBlock:(fbt_void_nserror_ref)block { + [self onDisconnectSetValueInternal:nil + andPriority:nil + withCompletionBlock:block + from:@"onDisconnectRemoveValueWithCompletionB" + @"lock:"]; +} + +- (void)onDisconnectUpdateChildValues:(NSDictionary *)values { + [self + onDisconnectUpdateChildValuesInternal:values + withCompletionBlock:nil + from: + @"onDisconnectUpdateChildValues:"]; +} + +- (void)onDisconnectUpdateChildValues:(NSDictionary *)values + withCompletionBlock:(fbt_void_nserror_ref)block { + [self onDisconnectUpdateChildValuesInternal:values + withCompletionBlock:block + from:@"onDisconnectUpdateChildValues" + @":withCompletionBlock:"]; +} + +- (void)onDisconnectUpdateChildValuesInternal:(NSDictionary *)values + withCompletionBlock:(fbt_void_nserror_ref)block + from:(NSString *)fn { + [FValidation validateFrom:fn writablePath:self.path]; + + FCompoundWrite *merge = + [FSnapshotUtilities compoundWriteFromDictionary:values + withValidationFrom:fn]; + + fbt_void_nserror_ref userCallback = [block copy]; + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + [self.repo onDisconnectUpdate:self.path + withNodes:merge + withCallback:userCallback]; + }); +} + +- (void)cancelDisconnectOperations { + [self cancelDisconnectOperationsWithCompletionBlock:nil]; +} + +- (void)cancelDisconnectOperationsWithCompletionBlock: + (fbt_void_nserror_ref)block { + fbt_void_nserror_ref callback = nil; + if (block != nil) { + callback = [block copy]; + } + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + [self.repo onDisconnectCancel:self.path withCallback:callback]; + }); +} + +#pragma mark - +#pragma mark Connection management methods + ++ (void)goOffline { + [FRepoManager interruptAll]; +} + ++ (void)goOnline { + [FRepoManager resumeAll]; +} + +#pragma mark - +#pragma mark Data reading methods deferred to FQuery + +- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType + withBlock:(fbt_void_datasnapshot)block { + return [self observeEventType:eventType + withBlock:block + withCancelBlock:nil]; +} + +- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType + andPreviousSiblingKeyWithBlock:(fbt_void_datasnapshot_nsstring)block { + return [self observeEventType:eventType + andPreviousSiblingKeyWithBlock:block + withCancelBlock:nil]; +} + +- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType + withBlock:(fbt_void_datasnapshot)block + withCancelBlock:(fbt_void_nserror)cancelBlock { + return [super observeEventType:eventType + withBlock:block + withCancelBlock:cancelBlock]; +} + +- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType + andPreviousSiblingKeyWithBlock:(fbt_void_datasnapshot_nsstring)block + withCancelBlock:(fbt_void_nserror)cancelBlock { + return [super observeEventType:eventType + andPreviousSiblingKeyWithBlock:block + withCancelBlock:cancelBlock]; +} + +- (void)removeObserverWithHandle:(FIRDatabaseHandle)handle { + [super removeObserverWithHandle:handle]; +} + +- (void)removeAllObservers { + [super removeAllObservers]; +} + +- (void)keepSynced:(BOOL)keepSynced { + [super keepSynced:keepSynced]; +} + +- (void)observeSingleEventOfType:(FIRDataEventType)eventType + withBlock:(fbt_void_datasnapshot)block { + [self observeSingleEventOfType:eventType + withBlock:block + withCancelBlock:nil]; +} + +- (void)observeSingleEventOfType:(FIRDataEventType)eventType + andPreviousSiblingKeyWithBlock:(fbt_void_datasnapshot_nsstring)block { + [self observeSingleEventOfType:eventType + andPreviousSiblingKeyWithBlock:block + withCancelBlock:nil]; +} + +- (void)observeSingleEventOfType:(FIRDataEventType)eventType + withBlock:(fbt_void_datasnapshot)block + withCancelBlock:(fbt_void_nserror)cancelBlock { + [super observeSingleEventOfType:eventType + withBlock:block + withCancelBlock:cancelBlock]; +} + +- (void)observeSingleEventOfType:(FIRDataEventType)eventType + andPreviousSiblingKeyWithBlock:(fbt_void_datasnapshot_nsstring)block + withCancelBlock:(fbt_void_nserror)cancelBlock { + [super observeSingleEventOfType:eventType + andPreviousSiblingKeyWithBlock:block + withCancelBlock:cancelBlock]; +} + +#pragma mark - +#pragma mark Query methods +// These methods suppress warnings from having method definitions in +// FIRDatabaseReference.h for docs generation. + +- (FIRDatabaseQuery *)queryLimitedToFirst:(NSUInteger)limit { + return [super queryLimitedToFirst:limit]; +} + +- (FIRDatabaseQuery *)queryLimitedToLast:(NSUInteger)limit { + return [super queryLimitedToLast:limit]; +} + +- (FIRDatabaseQuery *)queryOrderedByChild:(NSString *)key { + return [super queryOrderedByChild:key]; +} + +- (FIRDatabaseQuery *)queryOrderedByKey { + return [super queryOrderedByKey]; +} + +- (FIRDatabaseQuery *)queryOrderedByPriority { + return [super queryOrderedByPriority]; +} + +- (FIRDatabaseQuery *)queryStartingAtValue:(id)startValue { + return [super queryStartingAtValue:startValue]; +} + +- (FIRDatabaseQuery *)queryStartingAtValue:(id)startValue + childKey:(NSString *)childKey { + return [super queryStartingAtValue:startValue childKey:childKey]; +} + +- (FIRDatabaseQuery *)queryEndingAtValue:(id)endValue { + return [super queryEndingAtValue:endValue]; +} + +- (FIRDatabaseQuery *)queryEndingAtValue:(id)endValue + childKey:(NSString *)childKey { + return [super queryEndingAtValue:endValue childKey:childKey]; +} + +- (FIRDatabaseQuery *)queryEqualToValue:(id)value { + return [super queryEqualToValue:value]; +} + +- (FIRDatabaseQuery *)queryEqualToValue:(id)value + childKey:(NSString *)childKey { + return [super queryEqualToValue:value childKey:childKey]; +} + +#pragma mark - +#pragma mark Transaction methods + +- (void)runTransactionBlock:(fbt_transactionresult_mutabledata)block { + [FValidation validateFrom:@"runTransactionBlock:" writablePath:self.path]; + [self runTransactionBlock:block andCompletionBlock:nil withLocalEvents:YES]; +} + +- (void)runTransactionBlock:(fbt_transactionresult_mutabledata)update + andCompletionBlock: + (fbt_void_nserror_bool_datasnapshot)completionBlock { + [FValidation validateFrom:@"runTransactionBlock:andCompletionBlock:" + writablePath:self.path]; + [self runTransactionBlock:update + andCompletionBlock:completionBlock + withLocalEvents:YES]; +} + +- (void)runTransactionBlock:(fbt_transactionresult_mutabledata)block + andCompletionBlock:(fbt_void_nserror_bool_datasnapshot)completionBlock + withLocalEvents:(BOOL)localEvents { + [FValidation + validateFrom:@"runTransactionBlock:andCompletionBlock:withLocalEvents:" + writablePath:self.path]; + fbt_transactionresult_mutabledata updateCopy = [block copy]; + fbt_void_nserror_bool_datasnapshot onCompleteCopy = [completionBlock copy]; + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + [self.repo startTransactionOnPath:self.path + update:updateCopy + onComplete:onCompleteCopy + withLocalEvents:localEvents]; + }); +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FIndex.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/FIndex.h new file mode 100644 index 0000000..89bec76 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FIndex.h @@ -0,0 +1,51 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FImmutableSortedDictionary; +@class FNamedNode; +@protocol FNode; + +@protocol FIndex +- (NSComparisonResult)compareKey:(NSString *)key1 + andNode:(id)node1 + toOtherKey:(NSString *)key2 + andNode:(id)node2; + +- (NSComparisonResult)compareKey:(NSString *)key1 + andNode:(id)node1 + toOtherKey:(NSString *)key2 + andNode:(id)node2 + reverse:(BOOL)reverse; + +- (NSComparisonResult)compareNamedNode:(FNamedNode *)namedNode1 + toNamedNode:(FNamedNode *)namedNode2; + +- (BOOL)isDefinedOn:(id)node; +- (BOOL)indexedValueChangedBetween:(id)oldNode and:(id)newNode; +- (FNamedNode *)minPost; +- (FNamedNode *)maxPost; +- (FNamedNode *)makePost:(id)indexValue name:(NSString *)name; +- (NSString *)queryDefinition; + +@end + +@interface FIndex : NSObject + ++ (id)indexFromQueryDefinition:(NSString *)string; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FIndex.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/FIndex.m new file mode 100644 index 0000000..0366399 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FIndex.m @@ -0,0 +1,39 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIndex.h" + +#import "FKeyIndex.h" +#import "FPathIndex.h" +#import "FPriorityIndex.h" +#import "FValueIndex.h" + +@implementation FIndex + ++ (id)indexFromQueryDefinition:(NSString *)string { + if ([string isEqualToString:@".key"]) { + return [FKeyIndex keyIndex]; + } else if ([string isEqualToString:@".value"]) { + return [FValueIndex valueIndex]; + } else if ([string isEqualToString:@".priority"]) { + return [FPriorityIndex priorityIndex]; + } else { + return + [[FPathIndex alloc] initWithPath:[[FPath alloc] initWith:string]]; + } +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FKeyIndex.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/FKeyIndex.h new file mode 100644 index 0000000..281a5ac --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FKeyIndex.h @@ -0,0 +1,22 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIndex.h" +#import + +@interface FKeyIndex : NSObject ++ (id)keyIndex; +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FKeyIndex.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/FKeyIndex.m new file mode 100644 index 0000000..ac10829 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FKeyIndex.m @@ -0,0 +1,123 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FKeyIndex.h" +#import "FEmptyNode.h" +#import "FNamedNode.h" +#import "FSnapshotUtilities.h" +#import "FUtilities.h" + +@interface FKeyIndex () + +@property(nonatomic, strong) FNamedNode *maxPost; + +@end + +@implementation FKeyIndex + +- (id)init { + self = [super init]; + if (self) { + self.maxPost = [[FNamedNode alloc] initWithName:[FUtilities maxName] + andNode:[FEmptyNode emptyNode]]; + } + return self; +} + +- (NSComparisonResult)compareKey:(NSString *)key1 + andNode:(id)node1 + toOtherKey:(NSString *)key2 + andNode:(id)node2 { + return [FUtilities compareKey:key1 toKey:key2]; +} + +- (NSComparisonResult)compareKey:(NSString *)key1 + andNode:(id)node1 + toOtherKey:(NSString *)key2 + andNode:(id)node2 + reverse:(BOOL)reverse { + if (reverse) { + return [self compareKey:key2 + andNode:node2 + toOtherKey:key1 + andNode:node1]; + } else { + return [self compareKey:key1 + andNode:node1 + toOtherKey:key2 + andNode:node2]; + } +} + +- (NSComparisonResult)compareNamedNode:(FNamedNode *)namedNode1 + toNamedNode:(FNamedNode *)namedNode2 { + return [self compareKey:namedNode1.name + andNode:namedNode1.node + toOtherKey:namedNode2.name + andNode:namedNode2.node]; +} + +- (BOOL)isDefinedOn:(id)node { + return YES; +} + +- (BOOL)indexedValueChangedBetween:(id)oldNode and:(id)newNode { + return NO; // The key for a node never changes. +} + +- (FNamedNode *)minPost { + return [FNamedNode min]; +} + +- (FNamedNode *)makePost:(id)indexValue name:(NSString *)name { + NSString *key = indexValue.val; + NSAssert([key isKindOfClass:[NSString class]], + @"KeyIndex indexValue must always be a string."); + // We just use empty node, but it'll never be compared, since our comparator + // only looks at name. + return [[FNamedNode alloc] initWithName:key andNode:[FEmptyNode emptyNode]]; +} + +- (NSString *)queryDefinition { + return @".key"; +} + +- (NSString *)description { + return @"FKeyIndex"; +} + +- (id)copyWithZone:(NSZone *)zone { + return self; +} + +- (BOOL)isEqual:(id)other { + // since we're a singleton. + return (other == self); +} + +- (NSUInteger)hash { + return [@".key" hash]; +} + ++ (id)keyIndex { + static id keyIndex; + static dispatch_once_t once; + dispatch_once(&once, ^{ + keyIndex = [[FKeyIndex alloc] init]; + }); + return keyIndex; +} +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FListenComplete.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/FListenComplete.h new file mode 100644 index 0000000..99aabf8 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FListenComplete.h @@ -0,0 +1,28 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FOperation.h" +#import + +@interface FListenComplete : NSObject + +- (id)initWithSource:(FOperationSource *)aSource path:(FPath *)aPath; + +@property(nonatomic, strong, readonly) FOperationSource *source; +@property(nonatomic, strong, readonly) FPath *path; +@property(nonatomic, readonly) FOperationType type; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FListenComplete.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/FListenComplete.m new file mode 100644 index 0000000..26370c6 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FListenComplete.m @@ -0,0 +1,55 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FListenComplete.h" +#import "FOperationSource.h" +#import "FPath.h" + +@interface FListenComplete () +@property(nonatomic, strong, readwrite) FOperationSource *source; +@property(nonatomic, strong, readwrite) FPath *path; +@property(nonatomic, readwrite) FOperationType type; +@end + +@implementation FListenComplete +- (id)initWithSource:(FOperationSource *)aSource path:(FPath *)aPath { + NSAssert(!aSource.fromUser, + @"Can't have a listen complete from a user source"); + self = [super init]; + if (self) { + self.source = aSource; + self.path = aPath; + self.type = FOperationTypeListenComplete; + } + return self; +} + +- (id)operationForChild:(NSString *)childKey { + if ([self.path isEmpty]) { + return [[FListenComplete alloc] initWithSource:self.source + path:[FPath empty]]; + } else { + return [[FListenComplete alloc] initWithSource:self.source + path:[self.path popFront]]; + } +} + +- (NSString *)description { + return [NSString stringWithFormat:@"FListenComplete { path=%@, source=%@ }", + self.path, self.source]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FMaxNode.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/FMaxNode.h new file mode 100644 index 0000000..c68137e --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FMaxNode.h @@ -0,0 +1,22 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FChildrenNode.h" +#import + +@interface FMaxNode : FChildrenNode ++ (id)maxNode; +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FMaxNode.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/FMaxNode.m new file mode 100644 index 0000000..28e7672 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FMaxNode.m @@ -0,0 +1,58 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FMaxNode.h" +#import "FEmptyNode.h" +#import "FUtilities.h" + +@implementation FMaxNode { +} +- (id)init { + self = [super init]; + if (self) { + } + return self; +} + ++ (id)maxNode { + static FMaxNode *maxNode = nil; + static dispatch_once_t once; + dispatch_once(&once, ^{ + maxNode = [[FMaxNode alloc] init]; + }); + return maxNode; +} + +- (NSComparisonResult)compare:(id)other { + if (other == self) { + return NSOrderedSame; + } else { + return NSOrderedDescending; + } +} + +- (BOOL)isEqual:(id)other { + return other == self; +} + +- (id)getImmediateChild:(NSString *)childName { + return [FEmptyNode emptyNode]; +} + +- (BOOL)isEmpty { + return NO; +} +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FNamedNode.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/FNamedNode.h new file mode 100644 index 0000000..4414dfd --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FNamedNode.h @@ -0,0 +1,31 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FNode.h" +#import + +@interface FNamedNode : NSObject + +@property(nonatomic, strong, readonly) NSString *name; +@property(nonatomic, strong, readonly) id node; + +- (id)initWithName:(NSString *)name andNode:(id)node; + ++ (FNamedNode *)nodeWithName:(NSString *)name node:(id)node; + ++ (FNamedNode *)min; ++ (FNamedNode *)max; +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FNamedNode.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/FNamedNode.m new file mode 100644 index 0000000..3b2eaf7 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FNamedNode.m @@ -0,0 +1,102 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FNamedNode.h" +#import "FEmptyNode.h" +#import "FIndex.h" +#import "FMaxNode.h" +#import "FUtilities.h" + +@interface FNamedNode () +@property(nonatomic, strong, readwrite) NSString *name; +@property(nonatomic, strong, readwrite) id node; +@end + +@implementation FNamedNode + ++ (FNamedNode *)nodeWithName:(NSString *)name node:(id)node { + return [[FNamedNode alloc] initWithName:name andNode:node]; +} + +- (id)initWithName:(NSString *)name andNode:(id)node { + self = [super init]; + if (self) { + self.name = name; + self.node = node; + } + return self; +} + +- (id)copy { + return self; +} + +- (id)copyWithZone:(NSZone *)zone { + return self; +} + ++ (FNamedNode *)min { + static FNamedNode *min = nil; + static dispatch_once_t once; + dispatch_once(&once, ^{ + min = [[FNamedNode alloc] initWithName:[FUtilities minName] + andNode:[FEmptyNode emptyNode]]; + }); + return min; +} + ++ (FNamedNode *)max { + static FNamedNode *max = nil; + static dispatch_once_t once; + dispatch_once(&once, ^{ + max = [[FNamedNode alloc] initWithName:[FUtilities maxName] + andNode:[FMaxNode maxNode]]; + }); + return max; +} + +- (NSString *)description { + return + [NSString stringWithFormat:@"NamedNode[%@] %@", self.name, self.node]; +} + +- (BOOL)isEqual:(id)object { + if (self == object) { + return YES; + } + if (object == nil || ![object isKindOfClass:[FNamedNode class]]) { + return NO; + } + + FNamedNode *namedNode = object; + if (![self.name isEqualToString:namedNode.name]) { + return NO; + } + if (![self.node isEqual:namedNode.node]) { + return NO; + } + + return YES; +} + +- (NSUInteger)hash { + NSUInteger nameHash = [self.name hash]; + NSUInteger nodeHash = [self.node hash]; + NSUInteger result = 31 * nameHash + nodeHash; + return result; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FPathIndex.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/FPathIndex.h new file mode 100644 index 0000000..6ab9151 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FPathIndex.h @@ -0,0 +1,23 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIndex.h" +#import "FPath.h" +#import + +@interface FPathIndex : NSObject +- (id)initWithPath:(FPath *)path; +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FPathIndex.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/FPathIndex.m new file mode 100644 index 0000000..214eab8 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FPathIndex.m @@ -0,0 +1,135 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FPathIndex.h" +#import "FEmptyNode.h" +#import "FMaxNode.h" +#import "FNamedNode.h" +#import "FPath.h" +#import "FSnapshotUtilities.h" +#import "FUtilities.h" + +@interface FPathIndex () +@property(nonatomic, strong) FPath *path; +@end + +@implementation FPathIndex + +- (id)initWithPath:(FPath *)path { + self = [super init]; + if (self) { + if (path.isEmpty || [path.getFront isEqualToString:@".priority"]) { + [NSException raise:NSInvalidArgumentException + format:@"Invalid path for PathIndex: %@", path]; + } + _path = path; + } + return self; +} + +- (NSComparisonResult)compareKey:(NSString *)key1 + andNode:(id)node1 + toOtherKey:(NSString *)key2 + andNode:(id)node2 { + id child1 = [node1 getChild:self.path]; + id child2 = [node2 getChild:self.path]; + NSComparisonResult indexCmp = [child1 compare:child2]; + if (indexCmp == NSOrderedSame) { + return [FUtilities compareKey:key1 toKey:key2]; + } else { + return indexCmp; + } +} + +- (NSComparisonResult)compareKey:(NSString *)key1 + andNode:(id)node1 + toOtherKey:(NSString *)key2 + andNode:(id)node2 + reverse:(BOOL)reverse { + if (reverse) { + return [self compareKey:key2 + andNode:node2 + toOtherKey:key1 + andNode:node1]; + } else { + return [self compareKey:key1 + andNode:node1 + toOtherKey:key2 + andNode:node2]; + } +} + +- (NSComparisonResult)compareNamedNode:(FNamedNode *)namedNode1 + toNamedNode:(FNamedNode *)namedNode2 { + return [self compareKey:namedNode1.name + andNode:namedNode1.node + toOtherKey:namedNode2.name + andNode:namedNode2.node]; +} + +- (BOOL)isDefinedOn:(id)node { + return ![node getChild:self.path].isEmpty; +} + +- (BOOL)indexedValueChangedBetween:(id)oldNode and:(id)newNode { + id oldValue = [oldNode getChild:self.path]; + id newValue = [newNode getChild:self.path]; + return [oldValue compare:newValue] != NSOrderedSame; +} + +- (FNamedNode *)minPost { + return FNamedNode.min; +} + +- (FNamedNode *)maxPost { + id maxNode = [[FEmptyNode emptyNode] updateChild:self.path + withNewChild:[FMaxNode maxNode]]; + + return [[FNamedNode alloc] initWithName:[FUtilities maxName] + andNode:maxNode]; +} + +- (FNamedNode *)makePost:(id)indexValue name:(NSString *)name { + id node = [[FEmptyNode emptyNode] updateChild:self.path + withNewChild:indexValue]; + return [[FNamedNode alloc] initWithName:name andNode:node]; +} + +- (NSString *)queryDefinition { + return [self.path wireFormat]; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"FPathIndex(%@)", self.path]; +} + +- (id)copyWithZone:(NSZone *)zone { + // Safe since we're immutable. + return self; +} + +- (BOOL)isEqual:(id)other { + if (![other isKindOfClass:[FPathIndex class]]) { + return NO; + } + return ([self.path isEqual:((FPathIndex *)other).path]); +} + +- (NSUInteger)hash { + return [self.path hash]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FPriorityIndex.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/FPriorityIndex.h new file mode 100644 index 0000000..1b4534e --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FPriorityIndex.h @@ -0,0 +1,23 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIndex.h" + +@interface FPriorityIndex : NSObject ++ (id)priorityIndex; +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FPriorityIndex.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/FPriorityIndex.m new file mode 100644 index 0000000..0b33837 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FPriorityIndex.m @@ -0,0 +1,126 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FPriorityIndex.h" + +#import "FEmptyNode.h" +#import "FLeafNode.h" +#import "FMaxNode.h" +#import "FNamedNode.h" +#import "FNode.h" +#import "FUtilities.h" + +// TODO: Abstract into some common base class? + +@implementation FPriorityIndex + +- (NSComparisonResult)compareKey:(NSString *)key1 + andNode:(id)node1 + toOtherKey:(NSString *)key2 + andNode:(id)node2 { + id child1 = [node1 getPriority]; + id child2 = [node2 getPriority]; + NSComparisonResult indexCmp = [child1 compare:child2]; + if (indexCmp == NSOrderedSame) { + return [FUtilities compareKey:key1 toKey:key2]; + } else { + return indexCmp; + } +} + +- (NSComparisonResult)compareKey:(NSString *)key1 + andNode:(id)node1 + toOtherKey:(NSString *)key2 + andNode:(id)node2 + reverse:(BOOL)reverse { + if (reverse) { + return [self compareKey:key2 + andNode:node2 + toOtherKey:key1 + andNode:node1]; + } else { + return [self compareKey:key1 + andNode:node1 + toOtherKey:key2 + andNode:node2]; + } +} + +- (NSComparisonResult)compareNamedNode:(FNamedNode *)namedNode1 + toNamedNode:(FNamedNode *)namedNode2 { + return [self compareKey:namedNode1.name + andNode:namedNode1.node + toOtherKey:namedNode2.name + andNode:namedNode2.node]; +} + +- (BOOL)isDefinedOn:(id)node { + return !node.getPriority.isEmpty; +} + +- (BOOL)indexedValueChangedBetween:(id)oldNode and:(id)newNode { + id oldValue = [oldNode getPriority]; + id newValue = [newNode getPriority]; + return ![oldValue isEqual:newValue]; +} + +- (FNamedNode *)minPost { + return FNamedNode.min; +} + +- (FNamedNode *)maxPost { + return [self makePost:[FMaxNode maxNode] name:[FUtilities maxName]]; +} + +- (FNamedNode *)makePost:(id)indexValue name:(NSString *)name { + id node = [[FLeafNode alloc] initWithValue:@"[PRIORITY-POST]" + withPriority:indexValue]; + return [[FNamedNode alloc] initWithName:name andNode:node]; +} + +- (NSString *)queryDefinition { + return @".priority"; +} + +- (NSString *)description { + return @"FPriorityIndex"; +} + +- (id)copyWithZone:(NSZone *)zone { + // Safe since we're immutable. + return self; +} + +- (BOOL)isEqual:(id)other { + return [other isKindOfClass:[FPriorityIndex class]]; +} + +- (NSUInteger)hash { + // chosen by a fair dice roll. Guaranteed to be random + return 3155577; +} + ++ (id)priorityIndex { + static id index; + static dispatch_once_t once; + dispatch_once(&once, ^{ + index = [[FPriorityIndex alloc] init]; + }); + + return index; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FRangedFilter.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/FRangedFilter.h new file mode 100644 index 0000000..3c1aadd --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FRangedFilter.h @@ -0,0 +1,31 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FNodeFilter.h" +#import + +@class FQueryParams; +@class FNamedNode; + +@interface FRangedFilter : NSObject + +- (id)initWithQueryParams:(FQueryParams *)params; +- (BOOL)matchesKey:(NSString *)key andNode:(id)node; + +@property(nonatomic, strong, readonly) FNamedNode *startPost; +@property(nonatomic, strong, readonly) FNamedNode *endPost; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FRangedFilter.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/FRangedFilter.m new file mode 100644 index 0000000..bd8ef81 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FRangedFilter.m @@ -0,0 +1,129 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FRangedFilter.h" +#import "FChildChangeAccumulator.h" +#import "FChildrenNode.h" +#import "FEmptyNode.h" +#import "FIndexedFilter.h" +#import "FIndexedNode.h" +#import "FNamedNode.h" +#import "FQueryParams.h" + +@interface FRangedFilter () +@property(nonatomic, strong, readwrite) id indexedFilter; +@property(nonatomic, strong, readwrite) id index; +@property(nonatomic, strong, readwrite) FNamedNode *startPost; +@property(nonatomic, strong, readwrite) FNamedNode *endPost; +@end + +@implementation FRangedFilter +- (id)initWithQueryParams:(FQueryParams *)params { + self = [super init]; + if (self) { + self.indexedFilter = + [[FIndexedFilter alloc] initWithIndex:params.index]; + self.index = params.index; + self.startPost = [FRangedFilter startPostFromQueryParams:params]; + self.endPost = [FRangedFilter endPostFromQueryParams:params]; + } + return self; +} + ++ (FNamedNode *)startPostFromQueryParams:(FQueryParams *)params { + if ([params hasStart]) { + NSString *startKey = params.indexStartKey; + return [params.index makePost:params.indexStartValue name:startKey]; + } else { + return params.index.minPost; + } +} + ++ (FNamedNode *)endPostFromQueryParams:(FQueryParams *)params { + if ([params hasEnd]) { + NSString *endKey = params.indexEndKey; + return [params.index makePost:params.indexEndValue name:endKey]; + } else { + return params.index.maxPost; + } +} + +- (BOOL)matchesKey:(NSString *)key andNode:(id)node { + return ([self.index compareKey:self.startPost.name + andNode:self.startPost.node + toOtherKey:key + andNode:node] <= NSOrderedSame && + [self.index compareKey:key + andNode:node + toOtherKey:self.endPost.name + andNode:self.endPost.node] <= NSOrderedSame); +} + +- (FIndexedNode *)updateChildIn:(FIndexedNode *)oldSnap + forChildKey:(NSString *)childKey + newChild:(id)newChildSnap + affectedPath:(FPath *)affectedPath + fromSource:(id)source + accumulator: + (FChildChangeAccumulator *)optChangeAccumulator { + if (![self matchesKey:childKey andNode:newChildSnap]) { + newChildSnap = [FEmptyNode emptyNode]; + } + return [self.indexedFilter updateChildIn:oldSnap + forChildKey:childKey + newChild:newChildSnap + affectedPath:affectedPath + fromSource:source + accumulator:optChangeAccumulator]; +} + +- (FIndexedNode *)updateFullNode:(FIndexedNode *)oldSnap + withNewNode:(FIndexedNode *)newSnap + accumulator: + (FChildChangeAccumulator *)optChangeAccumulator { + __block FIndexedNode *filtered; + if (newSnap.node.isLeafNode) { + // Make sure we have a children node with the correct index, not a leaf + // node + filtered = [FIndexedNode indexedNodeWithNode:[FEmptyNode emptyNode] + index:self.index]; + } else { + // Dont' support priorities on queries + filtered = [newSnap updatePriority:[FEmptyNode emptyNode]]; + [newSnap.node enumerateChildrenUsingBlock:^( + NSString *key, id node, BOOL *stop) { + if (![self matchesKey:key andNode:node]) { + filtered = [filtered updateChild:key + withNewChild:[FEmptyNode emptyNode]]; + } + }]; + } + return [self.indexedFilter updateFullNode:oldSnap + withNewNode:filtered + accumulator:optChangeAccumulator]; +} + +- (FIndexedNode *)updatePriority:(id)priority + forNode:(FIndexedNode *)oldSnap { + // Don't support priorities on queries + return oldSnap; +} + +- (BOOL)filtersNodes { + return YES; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FTransformedEnumerator.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/FTransformedEnumerator.h new file mode 100644 index 0000000..25a59b4 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FTransformedEnumerator.h @@ -0,0 +1,24 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@interface FTransformedEnumerator : NSEnumerator +- (id)initWithEnumerator:(NSEnumerator *)enumerator + andTransform:(id (^)(id))transform; +- (id)nextObject; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FTransformedEnumerator.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/FTransformedEnumerator.m new file mode 100644 index 0000000..567e100 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FTransformedEnumerator.m @@ -0,0 +1,44 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FTransformedEnumerator.h" + +@interface FTransformedEnumerator () +@property(nonatomic, strong) NSEnumerator *enumerator; +@property(nonatomic, copy) id (^transform)(id); +@end + +@implementation FTransformedEnumerator +- (id)initWithEnumerator:(NSEnumerator *)enumerator + andTransform:(id (^)(id))transform { + self = [super init]; + if (self) { + self.enumerator = enumerator; + self.transform = transform; + } + return self; +} + +- (id)nextObject { + id next = self.enumerator.nextObject; + if (next != nil) { + return self.transform(next); + } else { + return nil; + } +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FValueIndex.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/FValueIndex.h new file mode 100644 index 0000000..a79f202 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FValueIndex.h @@ -0,0 +1,22 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIndex.h" +#import + +@interface FValueIndex : NSObject ++ (id)valueIndex; +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FValueIndex.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/FValueIndex.m new file mode 100644 index 0000000..61021c8 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FValueIndex.m @@ -0,0 +1,112 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FValueIndex.h" +#import "FMaxNode.h" +#import "FNamedNode.h" +#import "FSnapshotUtilities.h" +#import "FUtilities.h" + +@implementation FValueIndex + +- (NSComparisonResult)compareKey:(NSString *)key1 + andNode:(id)node1 + toOtherKey:(NSString *)key2 + andNode:(id)node2 { + NSComparisonResult indexCmp = [node1 compare:node2]; + if (indexCmp == NSOrderedSame) { + return [FUtilities compareKey:key1 toKey:key2]; + } else { + return indexCmp; + } +} + +- (NSComparisonResult)compareKey:(NSString *)key1 + andNode:(id)node1 + toOtherKey:(NSString *)key2 + andNode:(id)node2 + reverse:(BOOL)reverse { + if (reverse) { + return [self compareKey:key2 + andNode:node2 + toOtherKey:key1 + andNode:node1]; + } else { + return [self compareKey:key1 + andNode:node1 + toOtherKey:key2 + andNode:node2]; + } +} + +- (NSComparisonResult)compareNamedNode:(FNamedNode *)namedNode1 + toNamedNode:(FNamedNode *)namedNode2 { + return [self compareKey:namedNode1.name + andNode:namedNode1.node + toOtherKey:namedNode2.name + andNode:namedNode2.node]; +} + +- (BOOL)isDefinedOn:(id)node { + return YES; +} + +- (BOOL)indexedValueChangedBetween:(id)oldNode and:(id)newNode { + return ![oldNode isEqual:newNode]; +} + +- (FNamedNode *)minPost { + return FNamedNode.min; +} + +- (FNamedNode *)maxPost { + return FNamedNode.max; +} + +- (FNamedNode *)makePost:(id)indexValue name:(NSString *)name { + return [[FNamedNode alloc] initWithName:name andNode:indexValue]; +} + +- (NSString *)queryDefinition { + return @".value"; +} + +- (NSString *)description { + return @"FValueIndex"; +} + +- (id)copyWithZone:(NSZone *)zone { + return self; +} + +- (BOOL)isEqual:(id)other { + // since we're a singleton. + return (other == self); +} + +- (NSUInteger)hash { + return [@".value" hash]; +} + ++ (id)valueIndex { + static id valueIndex; + static dispatch_once_t once; + dispatch_once(&once, ^{ + valueIndex = [[FValueIndex alloc] init]; + }); + return valueIndex; +} +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FViewProcessor.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/FViewProcessor.h new file mode 100644 index 0000000..ea6676e --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FViewProcessor.h @@ -0,0 +1,42 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FViewCache; +@class FViewProcessorResult; +@class FChildChangeAccumulator; +@protocol FNode; +@class FWriteTreeRef; +@class FPath; +@protocol FOperation; +@protocol FNodeFilter; + +@interface FViewProcessor : NSObject + +- (id)initWithFilter:(id)nodeFilter; + +- (FViewProcessorResult *)applyOperationOn:(FViewCache *)oldViewCache + operation:(id)operation + writesCache:(FWriteTreeRef *)writesCache + completeCache:(id)optCompleteCache; +- (FViewCache *)revertUserWriteOn:(FViewCache *)viewCache + path:(FPath *)path + writesCache:(FWriteTreeRef *)writesCache + completeCache:(id)optCompleteCache + accumulator:(FChildChangeAccumulator *)accumulator; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FViewProcessor.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/FViewProcessor.m new file mode 100644 index 0000000..5524996 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FViewProcessor.m @@ -0,0 +1,831 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FViewProcessor.h" +#import "FAckUserWrite.h" +#import "FCacheNode.h" +#import "FChange.h" +#import "FChildChangeAccumulator.h" +#import "FChildrenNode.h" +#import "FCompleteChildSource.h" +#import "FCompoundWrite.h" +#import "FEmptyNode.h" +#import "FIRDataEventType.h" +#import "FImmutableTree.h" +#import "FKeyIndex.h" +#import "FMerge.h" +#import "FNode.h" +#import "FNodeFilter.h" +#import "FOperation.h" +#import "FOperationSource.h" +#import "FOverwrite.h" +#import "FPath.h" +#import "FViewCache.h" +#import "FViewProcessorResult.h" +#import "FWriteTreeRef.h" + +/** + * An implementation of FCompleteChildSource that never returns any additional + * children + */ +@interface FNoCompleteChildSource : NSObject +@end + +@implementation FNoCompleteChildSource ++ (FNoCompleteChildSource *)instance { + static FNoCompleteChildSource *source = nil; + static dispatch_once_t once; + dispatch_once(&once, ^{ + source = [[FNoCompleteChildSource alloc] init]; + }); + return source; +} + +- (id)completeChild:(NSString *)childKey { + return nil; +} + +- (FNamedNode *)childByIndex:(id)index + afterChild:(FNamedNode *)child + isReverse:(BOOL)reverse { + return nil; +} +@end + +/** + * An implementation of FCompleteChildSource that uses a FWriteTree in addition + * to any other server data or old event caches available to calculate complete + * children. + */ +@interface FWriteTreeCompleteChildSource : NSObject +@property(nonatomic, strong) FWriteTreeRef *writes; +@property(nonatomic, strong) FViewCache *viewCache; +@property(nonatomic, strong) id optCompleteServerCache; +@end + +@implementation FWriteTreeCompleteChildSource +- (id)initWithWrites:(FWriteTreeRef *)writes + viewCache:(FViewCache *)viewCache + serverCache:(id)optCompleteServerCache { + self = [super init]; + if (self) { + self.writes = writes; + self.viewCache = viewCache; + self.optCompleteServerCache = optCompleteServerCache; + } + return self; +} + +- (id)completeChild:(NSString *)childKey { + FCacheNode *node = self.viewCache.cachedEventSnap; + if ([node isCompleteForChild:childKey]) { + return [node.node getImmediateChild:childKey]; + } else { + FCacheNode *serverNode; + if (self.optCompleteServerCache) { + // Since we're only ever getting child nodes, we can use the key + // index here + FIndexedNode *indexed = + [FIndexedNode indexedNodeWithNode:self.optCompleteServerCache + index:[FKeyIndex keyIndex]]; + serverNode = [[FCacheNode alloc] initWithIndexedNode:indexed + isFullyInitialized:YES + isFiltered:NO]; + } else { + serverNode = self.viewCache.cachedServerSnap; + } + return [self.writes calculateCompleteChild:childKey cache:serverNode]; + } +} + +- (FNamedNode *)childByIndex:(id)index + afterChild:(FNamedNode *)child + isReverse:(BOOL)reverse { + id completeServerData = self.optCompleteServerCache != nil + ? self.optCompleteServerCache + : self.viewCache.completeServerSnap; + return [self.writes calculateNextNodeAfterPost:child + completeServerData:completeServerData + reverse:reverse + index:index]; +} + +@end + +@interface FViewProcessor () +@property(nonatomic, strong) id filter; +@end + +@implementation FViewProcessor + +- (id)initWithFilter:(id)nodeFilter { + self = [super init]; + if (self) { + self.filter = nodeFilter; + } + return self; +} + +- (FViewProcessorResult *)applyOperationOn:(FViewCache *)oldViewCache + operation:(id)operation + writesCache:(FWriteTreeRef *)writesCache + completeCache:(id)optCompleteCache { + FChildChangeAccumulator *accumulator = + [[FChildChangeAccumulator alloc] init]; + FViewCache *newViewCache; + + if (operation.type == FOperationTypeOverwrite) { + FOverwrite *overwrite = (FOverwrite *)operation; + if (operation.source.fromUser) { + newViewCache = [self applyUserOverwriteTo:oldViewCache + changePath:overwrite.path + changedSnap:overwrite.snap + writesCache:writesCache + completeCache:optCompleteCache + accumulator:accumulator]; + } else { + NSAssert(operation.source.fromServer, + @"Unknown source for overwrite."); + // We filter the node if it's a tagged update or the node has been + // previously filtered and the update is not at the root in which + // case it is ok (and necessary) to mark the node unfiltered again + BOOL filterServerNode = overwrite.source.isTagged || + (oldViewCache.cachedServerSnap.isFiltered && + !overwrite.path.isEmpty); + newViewCache = [self applyServerOverwriteTo:oldViewCache + changePath:overwrite.path + snap:overwrite.snap + writesCache:writesCache + completeCache:optCompleteCache + filterServerNode:filterServerNode + accumulator:accumulator]; + } + } else if (operation.type == FOperationTypeMerge) { + FMerge *merge = (FMerge *)operation; + if (operation.source.fromUser) { + newViewCache = [self applyUserMergeTo:oldViewCache + path:merge.path + changedChildren:merge.children + writesCache:writesCache + completeCache:optCompleteCache + accumulator:accumulator]; + } else { + NSAssert(operation.source.fromServer, @"Unknown source for merge."); + // We filter the node if it's a tagged update or the node has been + // previously filtered + BOOL filterServerNode = merge.source.isTagged || + oldViewCache.cachedServerSnap.isFiltered; + newViewCache = [self applyServerMergeTo:oldViewCache + path:merge.path + changedChildren:merge.children + writesCache:writesCache + completeCache:optCompleteCache + filterServerNode:filterServerNode + accumulator:accumulator]; + } + } else if (operation.type == FOperationTypeAckUserWrite) { + FAckUserWrite *ackWrite = (FAckUserWrite *)operation; + if (!ackWrite.revert) { + newViewCache = [self ackUserWriteOn:oldViewCache + ackPath:ackWrite.path + affectedTree:ackWrite.affectedTree + writesCache:writesCache + completeCache:optCompleteCache + accumulator:accumulator]; + } else { + newViewCache = [self revertUserWriteOn:oldViewCache + path:ackWrite.path + writesCache:writesCache + completeCache:optCompleteCache + accumulator:accumulator]; + } + } else if (operation.type == FOperationTypeListenComplete) { + newViewCache = [self listenCompleteOldCache:oldViewCache + path:operation.path + writesCache:writesCache + serverCache:optCompleteCache + accumulator:accumulator]; + } else { + [NSException + raise:NSInternalInconsistencyException + format:@"Unknown operation encountered %ld.", (long)operation.type]; + return nil; + } + + NSArray *changes = [self maybeAddValueFromOldViewCache:oldViewCache + newViewCache:newViewCache + changes:accumulator.changes]; + FViewProcessorResult *results = + [[FViewProcessorResult alloc] initWithViewCache:newViewCache + changes:changes]; + return results; +} + +- (NSArray *)maybeAddValueFromOldViewCache:(FViewCache *)oldViewCache + newViewCache:(FViewCache *)newViewCache + changes:(NSArray *)changes { + NSArray *newChanges = changes; + FCacheNode *eventSnap = newViewCache.cachedEventSnap; + if (eventSnap.isFullyInitialized) { + BOOL isLeafOrEmpty = + eventSnap.node.isLeafNode || eventSnap.node.isEmpty; + if ([changes count] > 0 || + !oldViewCache.cachedEventSnap.isFullyInitialized || + (isLeafOrEmpty && + ![eventSnap.node isEqual:oldViewCache.completeEventSnap]) || + ![eventSnap.node.getPriority + isEqual:oldViewCache.completeEventSnap.getPriority]) { + FChange *valueChange = + [[FChange alloc] initWithType:FIRDataEventTypeValue + indexedNode:eventSnap.indexedNode]; + NSMutableArray *mutableChanges = [changes mutableCopy]; + [mutableChanges addObject:valueChange]; + newChanges = mutableChanges; + } + } + return newChanges; +} + +- (FViewCache *) + generateEventCacheAfterServerEvent:(FViewCache *)viewCache + path:(FPath *)changePath + writesCache:(FWriteTreeRef *)writesCache + source:(id)source + accumulator:(FChildChangeAccumulator *)accumulator { + FCacheNode *oldEventSnap = viewCache.cachedEventSnap; + if ([writesCache shadowingWriteAtPath:changePath] != nil) { + // we have a shadowing write, ignore changes. + return viewCache; + } else { + FIndexedNode *newEventCache; + if (changePath.isEmpty) { + // TODO: figure out how this plays with "sliding ack windows" + NSAssert( + viewCache.cachedServerSnap.isFullyInitialized, + @"If change path is empty, we must have complete server data"); + id nodeWithLocalWrites; + if (viewCache.cachedServerSnap.isFiltered) { + // We need to special case this, because we need to only apply + // writes to complete children, or we might end up raising + // events for incomplete children. If the server data is + // filtered deep writes cannot be guaranteed to be complete + id serverCache = viewCache.completeServerSnap; + FChildrenNode *completeChildren = + ([serverCache isKindOfClass:[FChildrenNode class]]) + ? serverCache + : [FEmptyNode emptyNode]; + nodeWithLocalWrites = [writesCache + calculateCompleteEventChildrenWithCompleteServerChildren: + completeChildren]; + } else { + nodeWithLocalWrites = [writesCache + calculateCompleteEventCacheWithCompleteServerCache: + viewCache.completeServerSnap]; + } + FIndexedNode *indexedNode = + [FIndexedNode indexedNodeWithNode:nodeWithLocalWrites + index:self.filter.index]; + newEventCache = [self.filter + updateFullNode:viewCache.cachedEventSnap.indexedNode + withNewNode:indexedNode + accumulator:accumulator]; + } else { + NSString *childKey = [changePath getFront]; + if ([childKey isEqualToString:@".priority"]) { + NSAssert( + changePath.length == 1, + @"Can't have a priority with additional path components"); + id oldEventNode = oldEventSnap.node; + id serverNode = viewCache.cachedServerSnap.node; + // we might have overwrites for this priority + id updatedPriority = [writesCache + calculateEventCacheAfterServerOverwriteWithChildPath: + changePath + existingEventSnap: + oldEventNode + existingServerSnap: + serverNode]; + if (updatedPriority != nil) { + newEventCache = + [self.filter updatePriority:updatedPriority + forNode:oldEventSnap.indexedNode]; + } else { + // priority didn't change, keep old node + newEventCache = oldEventSnap.indexedNode; + } + } else { + FPath *childChangePath = [changePath popFront]; + id newEventChild; + if ([oldEventSnap isCompleteForChild:childKey]) { + id serverNode = viewCache.cachedServerSnap.node; + id eventChildUpdate = [writesCache + calculateEventCacheAfterServerOverwriteWithChildPath: + changePath + existingEventSnap: + oldEventSnap.node + existingServerSnap: + serverNode]; + if (eventChildUpdate != nil) { + newEventChild = + [[oldEventSnap.node getImmediateChild:childKey] + updateChild:childChangePath + withNewChild:eventChildUpdate]; + } else { + // Nothing changed, just keep the old child + newEventChild = + [oldEventSnap.node getImmediateChild:childKey]; + } + } else { + newEventChild = [writesCache + calculateCompleteChild:childKey + cache:viewCache.cachedServerSnap]; + } + if (newEventChild != nil) { + newEventCache = + [self.filter updateChildIn:oldEventSnap.indexedNode + forChildKey:childKey + newChild:newEventChild + affectedPath:childChangePath + fromSource:source + accumulator:accumulator]; + } else { + // No complete children available or no change + newEventCache = oldEventSnap.indexedNode; + } + } + } + return [viewCache updateEventSnap:newEventCache + isComplete:(oldEventSnap.isFullyInitialized || + changePath.isEmpty) + isFiltered:self.filter.filtersNodes]; + } +} + +- (FViewCache *)applyServerOverwriteTo:(FViewCache *)oldViewCache + changePath:(FPath *)changePath + snap:(id)changedSnap + writesCache:(FWriteTreeRef *)writesCache + completeCache:(id)optCompleteCache + filterServerNode:(BOOL)filterServerNode + accumulator:(FChildChangeAccumulator *)accumulator { + FCacheNode *oldServerSnap = oldViewCache.cachedServerSnap; + FIndexedNode *newServerCache; + id serverFilter = + filterServerNode ? self.filter : self.filter.indexedFilter; + + if (changePath.isEmpty) { + FIndexedNode *indexed = + [FIndexedNode indexedNodeWithNode:changedSnap + index:serverFilter.index]; + newServerCache = [serverFilter updateFullNode:oldServerSnap.indexedNode + withNewNode:indexed + accumulator:nil]; + } else if (serverFilter.filtersNodes && !oldServerSnap.isFiltered) { + // We want to filter the server node, but we didn't filter the server + // node yet, so simulate a full update + NSAssert(![changePath isEmpty], + @"An empty path should been caught in the other branch"); + NSString *childKey = [changePath getFront]; + FPath *updatePath = [changePath popFront]; + id newChild = [[oldServerSnap.node getImmediateChild:childKey] + updateChild:updatePath + withNewChild:changedSnap]; + FIndexedNode *indexed = + [oldServerSnap.indexedNode updateChild:childKey + withNewChild:newChild]; + newServerCache = [serverFilter updateFullNode:oldServerSnap.indexedNode + withNewNode:indexed + accumulator:nil]; + } else { + NSString *childKey = [changePath getFront]; + if (![oldServerSnap isCompleteForPath:changePath] && + changePath.length > 1) { + // We don't update incomplete nodes with updates intended for other + // listeners. + return oldViewCache; + } + FPath *childChangePath = [changePath popFront]; + id childNode = [oldServerSnap.node getImmediateChild:childKey]; + id newChildNode = [childNode updateChild:childChangePath + withNewChild:changedSnap]; + if ([childKey isEqualToString:@".priority"]) { + newServerCache = + [serverFilter updatePriority:newChildNode + forNode:oldServerSnap.indexedNode]; + } else { + newServerCache = + [serverFilter updateChildIn:oldServerSnap.indexedNode + forChildKey:childKey + newChild:newChildNode + affectedPath:childChangePath + fromSource:[FNoCompleteChildSource instance] + accumulator:nil]; + } + } + FViewCache *newViewCache = + [oldViewCache updateServerSnap:newServerCache + isComplete:(oldServerSnap.isFullyInitialized || + changePath.isEmpty) + isFiltered:serverFilter.filtersNodes]; + id source = + [[FWriteTreeCompleteChildSource alloc] initWithWrites:writesCache + viewCache:newViewCache + serverCache:optCompleteCache]; + return [self generateEventCacheAfterServerEvent:newViewCache + path:changePath + writesCache:writesCache + source:source + accumulator:accumulator]; +} + +- (FViewCache *)applyUserOverwriteTo:(FViewCache *)oldViewCache + changePath:(FPath *)changePath + changedSnap:(id)changedSnap + writesCache:(FWriteTreeRef *)writesCache + completeCache:(id)optCompleteCache + accumulator:(FChildChangeAccumulator *)accumulator { + FCacheNode *oldEventSnap = oldViewCache.cachedEventSnap; + FViewCache *newViewCache; + id source = + [[FWriteTreeCompleteChildSource alloc] initWithWrites:writesCache + viewCache:oldViewCache + serverCache:optCompleteCache]; + if (changePath.isEmpty) { + FIndexedNode *newIndexed = + [FIndexedNode indexedNodeWithNode:changedSnap + index:self.filter.index]; + FIndexedNode *newEventCache = + [self.filter updateFullNode:oldEventSnap.indexedNode + withNewNode:newIndexed + accumulator:accumulator]; + newViewCache = [oldViewCache updateEventSnap:newEventCache + isComplete:YES + isFiltered:self.filter.filtersNodes]; + } else { + NSString *childKey = [changePath getFront]; + if ([childKey isEqualToString:@".priority"]) { + FIndexedNode *newEventCache = [self.filter + updatePriority:changedSnap + forNode:oldViewCache.cachedEventSnap.indexedNode]; + newViewCache = + [oldViewCache updateEventSnap:newEventCache + isComplete:oldEventSnap.isFullyInitialized + isFiltered:oldEventSnap.isFiltered]; + } else { + FPath *childChangePath = [changePath popFront]; + id oldChild = [oldEventSnap.node getImmediateChild:childKey]; + id newChild; + if (childChangePath.isEmpty) { + // Child overwrite, we can replace the child + newChild = changedSnap; + } else { + id childNode = [source completeChild:childKey]; + if (childNode != nil) { + if ([[childChangePath getBack] + isEqualToString:@".priority"] && + [childNode getChild:[childChangePath parent]].isEmpty) { + // This is a priority update on an empty node. If this + // node exists on the server, the server will send down + // the priority in the update, so ignore for now + newChild = childNode; + } else { + newChild = [childNode updateChild:childChangePath + withNewChild:changedSnap]; + } + } else { + newChild = [FEmptyNode emptyNode]; + } + } + if (![oldChild isEqual:newChild]) { + FIndexedNode *newEventSnap = + [self.filter updateChildIn:oldEventSnap.indexedNode + forChildKey:childKey + newChild:newChild + affectedPath:childChangePath + fromSource:source + accumulator:accumulator]; + newViewCache = [oldViewCache + updateEventSnap:newEventSnap + isComplete:oldEventSnap.isFullyInitialized + isFiltered:self.filter.filtersNodes]; + } else { + newViewCache = oldViewCache; + } + } + } + return newViewCache; +} + ++ (BOOL)cache:(FViewCache *)viewCache hasChild:(NSString *)childKey { + return [viewCache.cachedEventSnap isCompleteForChild:childKey]; +} + +/** + * @param changedChildren NSDictionary of child name (NSString*) to child value + * (id) + */ +- (FViewCache *)applyUserMergeTo:(FViewCache *)viewCache + path:(FPath *)path + changedChildren:(FCompoundWrite *)changedChildren + writesCache:(FWriteTreeRef *)writesCache + completeCache:(id)serverCache + accumulator:(FChildChangeAccumulator *)accumulator { + // HACK: In the case of a limit query, there may be some changes that bump + // things out of the window leaving room for new items. It's important we + // process these changes first, so we iterate the changes twice, first + // processing any that affect items currently in view. + // TODO: I consider an item "in view" if cacheHasChild is true, which checks + // both the server and event snap. I'm not sure if this will result in edge + // cases when a child is in one but not the other. + __block FViewCache *curViewCache = viewCache; + + [changedChildren enumerateWrites:^(FPath *relativePath, id childNode, + BOOL *stop) { + FPath *writePath = [path child:relativePath]; + if ([FViewProcessor cache:viewCache hasChild:[writePath getFront]]) { + curViewCache = [self applyUserOverwriteTo:curViewCache + changePath:writePath + changedSnap:childNode + writesCache:writesCache + completeCache:serverCache + accumulator:accumulator]; + } + }]; + + [changedChildren enumerateWrites:^(FPath *relativePath, id childNode, + BOOL *stop) { + FPath *writePath = [path child:relativePath]; + if (![FViewProcessor cache:viewCache hasChild:[writePath getFront]]) { + curViewCache = [self applyUserOverwriteTo:curViewCache + changePath:writePath + changedSnap:childNode + writesCache:writesCache + completeCache:serverCache + accumulator:accumulator]; + } + }]; + + return curViewCache; +} + +- (FViewCache *)applyServerMergeTo:(FViewCache *)viewCache + path:(FPath *)path + changedChildren:(FCompoundWrite *)changedChildren + writesCache:(FWriteTreeRef *)writesCache + completeCache:(id)serverCache + filterServerNode:(BOOL)filterServerNode + accumulator:(FChildChangeAccumulator *)accumulator { + // If we don't have a cache yet, this merge was intended for a previously + // listen in the same location. Ignore it and wait for the complete data + // update coming soon. + if (viewCache.cachedServerSnap.node.isEmpty && + !viewCache.cachedServerSnap.isFullyInitialized) { + return viewCache; + } + + // HACK: In the case of a limit query, there may be some changes that bump + // things out of the window leaving room for new items. It's important we + // process these changes first, so we iterate the changes twice, first + // processing any that affect items currently in view. + // TODO: I consider an item "in view" if cacheHasChild is true, which checks + // both the server and event snap. I'm not sure if this will result in edge + // cases when a child is in one but not the other. + __block FViewCache *curViewCache = viewCache; + FCompoundWrite *actualMerge; + if (path.isEmpty) { + actualMerge = changedChildren; + } else { + actualMerge = + [[FCompoundWrite emptyWrite] addCompoundWrite:changedChildren + atPath:path]; + } + id serverNode = viewCache.cachedServerSnap.node; + + NSDictionary *childCompoundWrites = actualMerge.childCompoundWrites; + [childCompoundWrites + enumerateKeysAndObjectsUsingBlock:^( + NSString *childKey, FCompoundWrite *childMerge, BOOL *stop) { + if ([serverNode hasChild:childKey]) { + id serverChild = + [viewCache.cachedServerSnap.node getImmediateChild:childKey]; + id newChild = [childMerge applyToNode:serverChild]; + curViewCache = + [self applyServerOverwriteTo:curViewCache + changePath:[[FPath alloc] initWith:childKey] + snap:newChild + writesCache:writesCache + completeCache:serverCache + filterServerNode:filterServerNode + accumulator:accumulator]; + } + }]; + + [childCompoundWrites + enumerateKeysAndObjectsUsingBlock:^( + NSString *childKey, FCompoundWrite *childMerge, BOOL *stop) { + bool isUnknownDeepMerge = + ![viewCache.cachedServerSnap isCompleteForChild:childKey] && + childMerge.rootWrite == nil; + if (![serverNode hasChild:childKey] && !isUnknownDeepMerge) { + id serverChild = + [viewCache.cachedServerSnap.node getImmediateChild:childKey]; + id newChild = [childMerge applyToNode:serverChild]; + curViewCache = + [self applyServerOverwriteTo:curViewCache + changePath:[[FPath alloc] initWith:childKey] + snap:newChild + writesCache:writesCache + completeCache:serverCache + filterServerNode:filterServerNode + accumulator:accumulator]; + } + }]; + + return curViewCache; +} + +- (FViewCache *)ackUserWriteOn:(FViewCache *)viewCache + ackPath:(FPath *)ackPath + affectedTree:(FImmutableTree *)affectedTree + writesCache:(FWriteTreeRef *)writesCache + completeCache:(id)optCompleteCache + accumulator:(FChildChangeAccumulator *)accumulator { + + if ([writesCache shadowingWriteAtPath:ackPath] != nil) { + return viewCache; + } + + // Only filter server node if it is currently filtered + BOOL filterServerNode = viewCache.cachedServerSnap.isFiltered; + + // Essentially we'll just get our existing server cache for the affected + // paths and re-apply it as a server update now that it won't be shadowed. + FCacheNode *serverCache = viewCache.cachedServerSnap; + if (affectedTree.value != nil) { + // This is an overwrite. + if ((ackPath.isEmpty && serverCache.isFullyInitialized) || + [serverCache isCompleteForPath:ackPath]) { + return + [self applyServerOverwriteTo:viewCache + changePath:ackPath + snap:[serverCache.node getChild:ackPath] + writesCache:writesCache + completeCache:optCompleteCache + filterServerNode:filterServerNode + accumulator:accumulator]; + } else if (ackPath.isEmpty) { + // This is a goofy edge case where we are acking data at this + // location but don't have full data. We should just re-apply + // whatever we have in our cache as a merge. + FCompoundWrite *changedChildren = [FCompoundWrite emptyWrite]; + for (FNamedNode *child in serverCache.node.childEnumerator) { + changedChildren = [changedChildren addWrite:child.node + atKey:child.name]; + } + return [self applyServerMergeTo:viewCache + path:ackPath + changedChildren:changedChildren + writesCache:writesCache + completeCache:optCompleteCache + filterServerNode:filterServerNode + accumulator:accumulator]; + } else { + return viewCache; + } + } else { + // This is a merge. + __block FCompoundWrite *changedChildren = [FCompoundWrite emptyWrite]; + [affectedTree forEach:^(FPath *mergePath, id value) { + FPath *serverCachePath = [ackPath child:mergePath]; + if ([serverCache isCompleteForPath:serverCachePath]) { + changedChildren = [changedChildren + addWrite:[serverCache.node getChild:serverCachePath] + atPath:mergePath]; + } + }]; + return [self applyServerMergeTo:viewCache + path:ackPath + changedChildren:changedChildren + writesCache:writesCache + completeCache:optCompleteCache + filterServerNode:filterServerNode + accumulator:accumulator]; + } +} + +- (FViewCache *)revertUserWriteOn:(FViewCache *)viewCache + path:(FPath *)path + writesCache:(FWriteTreeRef *)writesCache + completeCache:(id)optCompleteCache + accumulator:(FChildChangeAccumulator *)accumulator { + if ([writesCache shadowingWriteAtPath:path] != nil) { + return viewCache; + } else { + id source = [[FWriteTreeCompleteChildSource alloc] + initWithWrites:writesCache + viewCache:viewCache + serverCache:optCompleteCache]; + FIndexedNode *oldEventCache = viewCache.cachedEventSnap.indexedNode; + FIndexedNode *newEventCache; + if (path.isEmpty || [[path getFront] isEqualToString:@".priority"]) { + id newNode; + if (viewCache.cachedServerSnap.isFullyInitialized) { + newNode = [writesCache + calculateCompleteEventCacheWithCompleteServerCache: + viewCache.completeServerSnap]; + } else { + newNode = [writesCache + calculateCompleteEventChildrenWithCompleteServerChildren: + viewCache.cachedServerSnap.node]; + } + FIndexedNode *indexedNode = + [FIndexedNode indexedNodeWithNode:newNode + index:self.filter.index]; + newEventCache = [self.filter updateFullNode:oldEventCache + withNewNode:indexedNode + accumulator:accumulator]; + } else { + NSString *childKey = [path getFront]; + id newChild = + [writesCache calculateCompleteChild:childKey + cache:viewCache.cachedServerSnap]; + if (newChild == nil && + [viewCache.cachedServerSnap isCompleteForChild:childKey]) { + newChild = [oldEventCache.node getImmediateChild:childKey]; + } + if (newChild != nil) { + newEventCache = [self.filter updateChildIn:oldEventCache + forChildKey:childKey + newChild:newChild + affectedPath:[path popFront] + fromSource:source + accumulator:accumulator]; + } else if (newChild == nil && + [viewCache.cachedEventSnap.node hasChild:childKey]) { + // No complete child available, delete the existing one, if any + newEventCache = + [self.filter updateChildIn:oldEventCache + forChildKey:childKey + newChild:[FEmptyNode emptyNode] + affectedPath:[path popFront] + fromSource:source + accumulator:accumulator]; + } else { + newEventCache = oldEventCache; + } + if (newEventCache.node.isEmpty && + viewCache.cachedServerSnap.isFullyInitialized) { + // We might have reverted all child writes. Maybe the old event + // was a leaf node. + id complete = [writesCache + calculateCompleteEventCacheWithCompleteServerCache: + viewCache.completeServerSnap]; + if (complete.isLeafNode) { + FIndexedNode *indexed = + [FIndexedNode indexedNodeWithNode:complete]; + newEventCache = [self.filter updateFullNode:newEventCache + withNewNode:indexed + accumulator:accumulator]; + } + } + } + BOOL complete = viewCache.cachedServerSnap.isFullyInitialized || + [writesCache shadowingWriteAtPath:[FPath empty]] != nil; + return [viewCache updateEventSnap:newEventCache + isComplete:complete + isFiltered:self.filter.filtersNodes]; + } +} + +- (FViewCache *)listenCompleteOldCache:(FViewCache *)viewCache + path:(FPath *)path + writesCache:(FWriteTreeRef *)writesCache + serverCache:(id)servercache + accumulator:(FChildChangeAccumulator *)accumulator { + FCacheNode *oldServerNode = viewCache.cachedServerSnap; + FViewCache *newViewCache = [viewCache + updateServerSnap:oldServerNode.indexedNode + isComplete:(oldServerNode.isFullyInitialized || path.isEmpty) + isFiltered:oldServerNode.isFiltered]; + return [self + generateEventCacheAfterServerEvent:newViewCache + path:path + writesCache:writesCache + source:[FNoCompleteChildSource instance] + accumulator:accumulator]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FViewProcessorResult.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/FViewProcessorResult.h new file mode 100644 index 0000000..8d3e2ef --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FViewProcessorResult.h @@ -0,0 +1,29 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FViewCache; + +@interface FViewProcessorResult : NSObject +@property(nonatomic, strong, readonly) FViewCache *viewCache; +/** + * List of FChanges. + */ +@property(nonatomic, strong, readonly) NSArray *changes; + +- (id)initWithViewCache:(FViewCache *)viewCache changes:(NSArray *)changes; +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/FViewProcessorResult.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/FViewProcessorResult.m new file mode 100644 index 0000000..0d38947 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/FViewProcessorResult.m @@ -0,0 +1,35 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FViewProcessorResult.h" +#import "FViewCache.h" + +@interface FViewProcessorResult () +@property(nonatomic, strong, readwrite) FViewCache *viewCache; +@property(nonatomic, strong, readwrite) NSArray *changes; +@end + +@implementation FViewProcessorResult +- (id)initWithViewCache:(FViewCache *)viewCache changes:(NSArray *)changes { + self = [super init]; + if (self) { + self.viewCache = viewCache; + self.changes = changes; + } + return self; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Login/FAuthTokenProvider.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Login/FAuthTokenProvider.h new file mode 100644 index 0000000..a4f8b15 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Login/FAuthTokenProvider.h @@ -0,0 +1,39 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FTypedefs.h" +#import "FTypedefs_Private.h" + +@protocol FIRAuthInterop; + +@protocol FAuthTokenProvider + +- (void)fetchTokenForcingRefresh:(BOOL)forceRefresh + withCallback:(fbt_void_nsstring_nserror)callback; + +- (void)listenForTokenChanges:(fbt_void_nsstring)listener; + +@end + +@interface FAuthTokenProvider : NSObject + ++ (id)authTokenProviderWithAuth:(id)auth; + +- (instancetype)init NS_UNAVAILABLE; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Login/FAuthTokenProvider.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Login/FAuthTokenProvider.m new file mode 100644 index 0000000..cd7fbe7 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Login/FAuthTokenProvider.m @@ -0,0 +1,123 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FAuthTokenProvider.h" + +#import +#import +#import +#import + +#import "FIRDatabaseQuery_Private.h" +#import "FIRNoopAuthTokenProvider.h" +#import "FUtilities.h" + +@interface FAuthStateListenerWrapper : NSObject + +@property(nonatomic, copy) fbt_void_nsstring listener; +@property(nonatomic, weak) id auth; + +@end + +@implementation FAuthStateListenerWrapper + +- (instancetype)initWithListener:(fbt_void_nsstring)listener + auth:(id)auth { + self = [super init]; + if (self != nil) { + self->_listener = listener; + self->_auth = auth; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(authStateDidChangeNotification:) + name:FIRAuthStateDidChangeInternalNotification + object:nil]; + } + return self; +} + +- (void)authStateDidChangeNotification:(NSNotification *)notification { + NSDictionary *userInfo = notification.userInfo; + if (notification.object == self.auth) { + NSString *token = + userInfo[FIRAuthStateDidChangeInternalNotificationTokenKey]; + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + self.listener(token); + }); + } +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +@end + +@interface FIRFirebaseAuthTokenProvider : NSObject + +@property(nonatomic, strong) id auth; +/** Strong references to the auth listeners as they are only weak in + * FIRFirebaseApp */ +@property(nonatomic, strong) NSMutableArray *authListeners; + +- (instancetype)initWithAuth:(id)auth; + +@end + +@implementation FIRFirebaseAuthTokenProvider + +- (instancetype)initWithAuth:(id)auth { + self = [super init]; + if (self != nil) { + self->_auth = auth; + self->_authListeners = [NSMutableArray array]; + } + return self; +} + +- (void)fetchTokenForcingRefresh:(BOOL)forceRefresh + withCallback:(fbt_void_nsstring_nserror)callback { + if (self.auth == nil) { + // Signal that Auth is not available by returning nil. + callback(nil, nil); + } else { + [self.auth getTokenForcingRefresh:forceRefresh + withCallback:^(NSString *_Nullable token, + NSError *_Nullable error) { + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + callback(token, error); + }); + }]; + } +} + +- (void)listenForTokenChanges:(_Nonnull fbt_void_nsstring)listener { + FAuthStateListenerWrapper *wrapper = + [[FAuthStateListenerWrapper alloc] initWithListener:listener + auth:self.auth]; + [self.authListeners addObject:wrapper]; +} + +@end + +@implementation FAuthTokenProvider + ++ (id)authTokenProviderWithAuth: + (id)authInterop { + return [[FIRFirebaseAuthTokenProvider alloc] initWithAuth:authInterop]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Login/FIRNoopAuthTokenProvider.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Login/FIRNoopAuthTokenProvider.h new file mode 100644 index 0000000..137c0ce --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Login/FIRNoopAuthTokenProvider.h @@ -0,0 +1,22 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FAuthTokenProvider.h" +#import + +@interface FIRNoopAuthTokenProvider : NSObject + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Login/FIRNoopAuthTokenProvider.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Login/FIRNoopAuthTokenProvider.m new file mode 100644 index 0000000..c50d308 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Login/FIRNoopAuthTokenProvider.m @@ -0,0 +1,34 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRNoopAuthTokenProvider.h" +#import "FAuthTokenProvider.h" +#import "FIRDatabaseQuery_Private.h" + +@implementation FIRNoopAuthTokenProvider + +- (void)fetchTokenForcingRefresh:(BOOL)forceRefresh + withCallback:(fbt_void_nsstring_nserror)callback { + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + callback(nil, nil); + }); +} + +- (void)listenForTokenChanges:(fbt_void_nsstring)listener { + // no-op, because token never changes +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FCachePolicy.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FCachePolicy.h new file mode 100644 index 0000000..6305d34 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FCachePolicy.h @@ -0,0 +1,41 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@protocol FCachePolicy + +- (BOOL)shouldPruneCacheWithSize:(NSUInteger)cacheSize + numberOfTrackedQueries:(NSUInteger)numTrackedQueries; +- (BOOL)shouldCheckCacheSize:(NSUInteger)serverUpdatesSinceLastCheck; +- (float)percentOfQueriesToPruneAtOnce; +- (NSUInteger)maxNumberOfQueriesToKeep; + +@end + +@interface FLRUCachePolicy : NSObject + +@property(nonatomic, readonly) NSUInteger maxSize; + +- (id)initWithMaxSize:(NSUInteger)maxSize; + +@end + +@interface FNoCachePolicy : NSObject + ++ (FNoCachePolicy *)noCachePolicy; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FCachePolicy.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FCachePolicy.m new file mode 100644 index 0000000..c1ecd98 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FCachePolicy.m @@ -0,0 +1,82 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FCachePolicy.h" + +@interface FLRUCachePolicy () + +@property(nonatomic, readwrite) NSUInteger maxSize; + +@end + +static const NSUInteger kFServerUpdatesBetweenCacheSizeChecks = 1000; +static const NSUInteger kFMaxNumberOfPrunableQueriesToKeep = 1000; +static const float kFPercentOfQueriesToPruneAtOnce = 0.2f; + +@implementation FLRUCachePolicy + +- (id)initWithMaxSize:(NSUInteger)maxSize { + self = [super init]; + if (self != nil) { + self->_maxSize = maxSize; + } + return self; +} + +- (BOOL)shouldPruneCacheWithSize:(NSUInteger)cacheSize + numberOfTrackedQueries:(NSUInteger)numTrackedQueries { + return cacheSize > self.maxSize || + numTrackedQueries > kFMaxNumberOfPrunableQueriesToKeep; +} + +- (BOOL)shouldCheckCacheSize:(NSUInteger)serverUpdatesSinceLastCheck { + return serverUpdatesSinceLastCheck > kFServerUpdatesBetweenCacheSizeChecks; +} + +- (float)percentOfQueriesToPruneAtOnce { + return kFPercentOfQueriesToPruneAtOnce; +} + +- (NSUInteger)maxNumberOfQueriesToKeep { + return kFMaxNumberOfPrunableQueriesToKeep; +} + +@end + +@implementation FNoCachePolicy + ++ (FNoCachePolicy *)noCachePolicy { + return [[FNoCachePolicy alloc] init]; +} + +- (BOOL)shouldPruneCacheWithSize:(NSUInteger)cacheSize + numberOfTrackedQueries:(NSUInteger)numTrackedQueries { + return NO; +} + +- (BOOL)shouldCheckCacheSize:(NSUInteger)serverUpdatesSinceLastCheck { + return NO; +} + +- (float)percentOfQueriesToPruneAtOnce { + return 0; +} + +- (NSUInteger)maxNumberOfQueriesToKeep { + return NSUIntegerMax; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FLevelDBStorageEngine.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FLevelDBStorageEngine.h new file mode 100644 index 0000000..6d32b98 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FLevelDBStorageEngine.h @@ -0,0 +1,39 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FCompoundWrite.h" +#import "FNode.h" +#import "FPath.h" +#import "FQuerySpec.h" +#import "FStorageEngine.h" + +@class FCacheNode; +@class FTrackedQuery; +@class FPruneForest; +@class FRepoInfo; + +@interface FLevelDBStorageEngine : NSObject + ++ (NSString *)firebaseDir; + +- (id)initWithPath:(NSString *)path; + +- (void)runLegacyMigration:(FRepoInfo *)info; +- (void)purgeEverything; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FLevelDBStorageEngine.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FLevelDBStorageEngine.m new file mode 100644 index 0000000..77a8303 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FLevelDBStorageEngine.m @@ -0,0 +1,990 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FLevelDBStorageEngine.h" + +#import "APLevelDB.h" +#import "FEmptyNode.h" +#import "FPendingPut.h" // For legacy migration +#import "FPruneForest.h" +#import "FQueryParams.h" +#import "FSnapshotUtilities.h" +#import "FTrackedQuery.h" +#import "FUtilities.h" +#import "FWriteRecord.h" +#import + +@interface FLevelDBStorageEngine () + +@property(nonatomic, strong) NSString *basePath; +@property(nonatomic, strong) APLevelDB *writesDB; +@property(nonatomic, strong) APLevelDB *serverCacheDB; + +@end + +// WARNING: If you change this, you need to write a migration script +static NSString *const kFPersistenceVersion = @"1"; + +static NSString *const kFServerDBPath = @"server_data"; +static NSString *const kFWritesDBPath = @"writes"; + +static NSString *const kFUserWriteId = @"id"; +static NSString *const kFUserWritePath = @"path"; +static NSString *const kFUserWriteOverwrite = @"o"; +static NSString *const kFUserWriteMerge = @"m"; + +static NSString *const kFTrackedQueryId = @"id"; +static NSString *const kFTrackedQueryPath = @"path"; +static NSString *const kFTrackedQueryParams = @"p"; +static NSString *const kFTrackedQueryLastUse = @"lu"; +static NSString *const kFTrackedQueryIsComplete = @"c"; +static NSString *const kFTrackedQueryIsActive = @"a"; + +static NSString *const kFServerCachePrefix = @"/server_cache/"; +// '~' is the last non-control character in the ASCII table until 127 +// We wan't the entire range of thing stored in the DB +static NSString *const kFServerCacheRangeEnd = @"/server_cache~"; +static NSString *const kFTrackedQueriesPrefix = @"/tracked_queries/"; +static NSString *const kFTrackedQueryKeysPrefix = @"/tracked_query_keys/"; + +// Failed to load JSON because a valid JSON turns out to be NaN while +// deserializing +static const NSInteger kFNanFailureCode = 3840; + +static NSString *writeRecordKey(NSUInteger writeId) { + return [NSString stringWithFormat:@"%lu", (unsigned long)(writeId)]; +} + +static NSString *serverCacheKey(FPath *path) { + return [NSString stringWithFormat:@"%@%@", kFServerCachePrefix, + ([path toStringWithTrailingSlash])]; +} + +static NSString *trackedQueryKey(NSUInteger trackedQueryId) { + return [NSString stringWithFormat:@"%@%lu", kFTrackedQueriesPrefix, + (unsigned long)trackedQueryId]; +} + +static NSString *trackedQueryKeysKeyPrefix(NSUInteger trackedQueryId) { + return [NSString stringWithFormat:@"%@%lu/", kFTrackedQueryKeysPrefix, + (unsigned long)trackedQueryId]; +} + +static NSString *trackedQueryKeysKey(NSUInteger trackedQueryId, NSString *key) { + return [NSString stringWithFormat:@"%@%lu/%@", kFTrackedQueryKeysPrefix, + (unsigned long)trackedQueryId, key]; +} + +@implementation FLevelDBStorageEngine +#pragma mark - Constructors + +- (id)initWithPath:(NSString *)dbPath { + self = [super init]; + if (self) { + self.basePath = [[FLevelDBStorageEngine firebaseDir] + stringByAppendingPathComponent:dbPath]; + /* For reference: + serverDataDB = [aPersistence createDbByName:@"server_data"]; + FPangolinDB *completenessDb = [aPersistence + createDbByName:@"server_complete"]; + */ + [FLevelDBStorageEngine ensureDir:self.basePath markAsDoNotBackup:YES]; + [self runMigration]; + [self openDatabases]; + } + return self; +} + +- (void)runMigration { + // Currently we're at version 1, so all we need to do is write that to a + // file + NSString *versionFile = + [self.basePath stringByAppendingPathComponent:@"version"]; + NSError *error; + NSString *oldVersion = + [NSString stringWithContentsOfFile:versionFile + encoding:NSUTF8StringEncoding + error:&error]; + if (!oldVersion) { + // This is probably fine, we don't have a version file yet + BOOL success = [kFPersistenceVersion writeToFile:versionFile + atomically:NO + encoding:NSUTF8StringEncoding + error:&error]; + if (!success) { + FFWarn(@"I-RDB076001", @"Failed to write version for database: %@", + error); + } + } else if ([oldVersion isEqualToString:kFPersistenceVersion]) { + // Everythings fine no need for migration + } else { + // If we add more versions in the future, we need to run migration here + [NSException raise:NSInternalInconsistencyException + format:@"Unrecognized database version: %@", oldVersion]; + } +} + +- (void)runLegacyMigration:(FRepoInfo *)info { + NSArray *dirPaths = NSSearchPathForDirectoriesInDomains( + NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDir = [dirPaths objectAtIndex:0]; + NSString *firebaseDir = + [documentsDir stringByAppendingPathComponent:@"firebase"]; + NSString *repoHashString = + [NSString stringWithFormat:@"%@_%@", info.host, info.namespace]; + NSString *legacyBaseDir = + [NSString stringWithFormat:@"%@/1/%@/v1", firebaseDir, repoHashString]; + if ([[NSFileManager defaultManager] fileExistsAtPath:legacyBaseDir]) { + FFWarn(@"I-RDB076002", @"Legacy database found, migrating..."); + // We only need to migrate writes + NSError *error = nil; + APLevelDB *writes = [APLevelDB + levelDBWithPath:[legacyBaseDir stringByAppendingPathComponent: + @"outstanding_puts"] + error:&error]; + if (writes != nil) { + __block NSUInteger numberOfWritesRestored = 0; + // Maybe we could use write batches, but what the heck, I'm sure + // it'll go fine :P + [writes enumerateKeysAndValuesAsData:^(NSString *key, NSData *data, + BOOL *stop) { + id pendingPut = [NSKeyedUnarchiver unarchiveObjectWithData:data]; + if ([pendingPut isKindOfClass:[FPendingPut class]]) { + FPendingPut *put = pendingPut; + id newNode = + [FSnapshotUtilities nodeFrom:put.data + priority:put.priority]; + [self saveUserOverwrite:newNode + atPath:put.path + writeId:[key integerValue]]; + numberOfWritesRestored++; + } else if ([pendingPut + isKindOfClass:[FPendingPutPriority class]]) { + // This is for backwards compatibility. Older clients will + // save FPendingPutPriority. New ones will need to read it and + // translate. + FPendingPutPriority *putPriority = pendingPut; + FPath *priorityPath = + [putPriority.path childFromString:@".priority"]; + id newNode = + [FSnapshotUtilities nodeFrom:putPriority.priority + priority:nil]; + [self saveUserOverwrite:newNode + atPath:priorityPath + writeId:[key integerValue]]; + numberOfWritesRestored++; + } else if ([pendingPut isKindOfClass:[FPendingUpdate class]]) { + FPendingUpdate *update = pendingPut; + FCompoundWrite *merge = [FCompoundWrite + compoundWriteWithValueDictionary:update.data]; + [self saveUserMerge:merge + atPath:update.path + writeId:[key integerValue]]; + numberOfWritesRestored++; + } else { + FFWarn(@"I-RDB076003", + @"Failed to migrate legacy write, meh!"); + } + }]; + FFWarn(@"I-RDB076004", @"Migrated %lu writes", + (unsigned long)numberOfWritesRestored); + [writes close]; + FFWarn(@"I-RDB076005", @"Deleting legacy database..."); + BOOL success = + [[NSFileManager defaultManager] removeItemAtPath:legacyBaseDir + error:&error]; + if (!success) { + FFWarn(@"I-RDB076006", @"Failed to delete legacy database: %@", + error); + } else { + FFWarn(@"I-RDB076007", @"Finished migrating legacy database."); + } + } else { + FFWarn(@"I-RDB076008", @"Failed to migrate old database: %@", + error); + } + } +} + +- (void)openDatabases { + self.serverCacheDB = [self createDB:kFServerDBPath]; + self.writesDB = [self createDB:kFWritesDBPath]; +} + +- (void)purgeDatabase:(NSString *)dbPath { + NSString *path = [self.basePath stringByAppendingPathComponent:dbPath]; + NSError *error; + FFWarn(@"I-RDB076009", @"Deleting database at path %@", path); + BOOL success = [[NSFileManager defaultManager] removeItemAtPath:path + error:&error]; + if (!success) { + [NSException raise:NSInternalInconsistencyException + format:@"Failed to delete database files: %@", error]; + } +} + +- (void)purgeEverything { + [self close]; + [@[ kFServerDBPath, kFWritesDBPath ] + enumerateObjectsUsingBlock:^(NSString *dbPath, NSUInteger idx, + BOOL *stop) { + [self purgeDatabase:dbPath]; + }]; + + [self openDatabases]; +} + +- (void)close { + // autoreleasepool will cause deallocation which will close the DB + @autoreleasepool { + [self.serverCacheDB close]; + self.serverCacheDB = nil; + [self.writesDB close]; + self.writesDB = nil; + } +} + ++ (NSString *)firebaseDir { +#if TARGET_OS_IOS || TARGET_OS_TV + NSArray *dirPaths = NSSearchPathForDirectoriesInDomains( + NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDir = [dirPaths objectAtIndex:0]; + return [documentsDir stringByAppendingPathComponent:@"firebase"]; +#elif TARGET_OS_OSX + return [NSHomeDirectory() stringByAppendingPathComponent:@".firebase"]; +#endif +} + +- (APLevelDB *)createDB:(NSString *)dbName { + NSError *err = nil; + NSString *path = [self.basePath stringByAppendingPathComponent:dbName]; + APLevelDB *db = [APLevelDB levelDBWithPath:path error:&err]; + + if (err) { + FFWarn(@"I-RDB076036", + @"Failed to read database persistence file '%@': %@", dbName, + [err localizedDescription]); + err = nil; + + // Delete the database and try again. + [self purgeDatabase:dbName]; + db = [APLevelDB levelDBWithPath:path error:&err]; + + if (err) { + NSString *reason = [NSString + stringWithFormat:@"Error initializing persistence: %@", + [err description]]; + @throw [NSException + exceptionWithName:@"FirebaseDatabasePersistenceFailure" + reason:reason + userInfo:nil]; + } + } + + return db; +} + +- (void)saveUserOverwrite:(id)node + atPath:(FPath *)path + writeId:(NSUInteger)writeId { + NSDictionary *write = @{ + kFUserWriteId : @(writeId), + kFUserWritePath : [path toStringWithTrailingSlash], + kFUserWriteOverwrite : [node valForExport:YES] + }; + NSError *error = nil; + NSData *data = [NSJSONSerialization dataWithJSONObject:write + options:0 + error:&error]; + NSAssert(data, @"Failed to serialize user overwrite: %@, (Error: %@)", + write, error); + [self.writesDB setData:data forKey:writeRecordKey(writeId)]; +} + +- (void)saveUserMerge:(FCompoundWrite *)merge + atPath:(FPath *)path + writeId:(NSUInteger)writeId { + NSDictionary *write = @{ + kFUserWriteId : @(writeId), + kFUserWritePath : [path toStringWithTrailingSlash], + kFUserWriteMerge : [merge valForExport:YES] + }; + NSError *error = nil; + NSData *data = [NSJSONSerialization dataWithJSONObject:write + options:0 + error:&error]; + NSAssert(data, @"Failed to serialize user merge: %@ (Error: %@)", write, + error); + [self.writesDB setData:data forKey:writeRecordKey(writeId)]; +} + +- (void)removeUserWrite:(NSUInteger)writeId { + [self.writesDB removeKey:writeRecordKey(writeId)]; +} + +- (void)removeAllUserWrites { + __block NSUInteger count = 0; + NSDate *start = [NSDate date]; + id batch = [self.writesDB beginWriteBatch]; + [self.writesDB enumerateKeys:^(NSString *key, BOOL *stop) { + [batch removeKey:key]; + count++; + }]; + BOOL success = [batch commit]; + if (!success) { + FFWarn(@"I-RDB076010", @"Failed to remove all users writes on disk!"); + } else { + FFDebug(@"I-RDB076011", @"Removed %lu writes in %fms", + (unsigned long)count, [start timeIntervalSinceNow] * -1000); + } +} + +- (NSArray *)userWrites { + NSDate *date = [NSDate date]; + NSMutableArray *writes = [NSMutableArray array]; + [self.writesDB enumerateKeysAndValuesAsData:^(NSString *key, NSData *data, + BOOL *stop) { + NSError *error = nil; + NSDictionary *writeJSON = [NSJSONSerialization JSONObjectWithData:data + options:0 + error:&error]; + if (writeJSON == nil) { + if (error.code == kFNanFailureCode) { + FFWarn(@"I-RDB076012", + @"Failed to deserialize write (%@), likely because of out " + @"of range doubles (Error: %@)", + [[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding], + error); + FFWarn(@"I-RDB076013", @"Removing failed write with key %@", key); + [self.writesDB removeKey:key]; + } else { + [NSException raise:NSInternalInconsistencyException + format:@"Failed to deserialize write: %@", error]; + } + } else { + NSInteger writeId = + ((NSNumber *)writeJSON[kFUserWriteId]).integerValue; + FPath *path = [FPath pathWithString:writeJSON[kFUserWritePath]]; + FWriteRecord *writeRecord; + if (writeJSON[kFUserWriteMerge] != nil) { + // It's a merge + FCompoundWrite *merge = [FCompoundWrite + compoundWriteWithValueDictionary:writeJSON[kFUserWriteMerge]]; + writeRecord = [[FWriteRecord alloc] initWithPath:path + merge:merge + writeId:writeId]; + } else { + // It's an overwrite + NSAssert(writeJSON[kFUserWriteOverwrite] != nil, + @"Persisted write did not contain merge or overwrite!"); + id node = + [FSnapshotUtilities nodeFrom:writeJSON[kFUserWriteOverwrite]]; + writeRecord = [[FWriteRecord alloc] initWithPath:path + overwrite:node + writeId:writeId + visible:YES]; + } + [writes addObject:writeRecord]; + } + }]; + // Make sure writes are sorted + [writes sortUsingComparator:^NSComparisonResult(FWriteRecord *one, + FWriteRecord *two) { + if (one.writeId < two.writeId) { + return NSOrderedAscending; + } else if (one.writeId > two.writeId) { + return NSOrderedDescending; + } else { + return NSOrderedSame; + } + }]; + FFDebug(@"I-RDB076014", @"Loaded %lu writes in %fms", + (unsigned long)writes.count, [date timeIntervalSinceNow] * -1000); + return writes; +} + +- (id)serverCacheAtPath:(FPath *)path { + NSDate *start = [NSDate date]; + id data = [self internalNestedDataForPath:path]; + id node = [FSnapshotUtilities nodeFrom:data]; + FFDebug(@"I-RDB076015", @"Loaded node with %d children at %@ in %fms", + [node numChildren], path, [start timeIntervalSinceNow] * -1000); + return node; +} + +- (id)serverCacheForKeys:(NSSet *)keys atPath:(FPath *)path { + NSDate *start = [NSDate date]; + __block id node = [FEmptyNode emptyNode]; + [keys enumerateObjectsUsingBlock:^(NSString *key, BOOL *stop) { + id data = [self internalNestedDataForPath:[path childFromString:key]]; + node = [node updateImmediateChild:key + withNewChild:[FSnapshotUtilities nodeFrom:data]]; + }]; + FFDebug(@"I-RDB076016", + @"Loaded node with %d children for %lu keys at %@ in %fms", + [node numChildren], (unsigned long)keys.count, path, + [start timeIntervalSinceNow] * -1000); + return node; +} + +- (void)updateServerCache:(id)node + atPath:(FPath *)path + merge:(BOOL)merge { + NSDate *start = [NSDate date]; + id batch = [self.serverCacheDB beginWriteBatch]; + // Remove any leaf nodes that might be higher up + [self removeAllLeafNodesOnPath:path batch:batch]; + __block NSUInteger counter = 0; + if (merge) { + // remove any children that exist + [node enumerateChildrenUsingBlock:^(NSString *childKey, + id childNode, BOOL *stop) { + FPath *childPath = [path childFromString:childKey]; + [self removeAllWithPrefix:serverCacheKey(childPath) + batch:batch + database:self.serverCacheDB]; + [self saveNodeInternal:childNode + atPath:childPath + batch:batch + counter:&counter]; + }]; + } else { + // remove everything + [self removeAllWithPrefix:serverCacheKey(path) + batch:batch + database:self.serverCacheDB]; + [self saveNodeInternal:node atPath:path batch:batch counter:&counter]; + } + BOOL success = [batch commit]; + if (!success) { + FFWarn(@"I-RDB076017", @"Failed to update server cache on disk!"); + } else { + FFDebug(@"I-RDB076018", @"Saved %lu leaf nodes for overwrite in %fms", + (unsigned long)counter, [start timeIntervalSinceNow] * -1000); + } +} + +- (void)updateServerCacheWithMerge:(FCompoundWrite *)merge + atPath:(FPath *)path { + NSDate *start = [NSDate date]; + __block NSUInteger counter = 0; + id batch = [self.serverCacheDB beginWriteBatch]; + // Remove any leaf nodes that might be higher up + [self removeAllLeafNodesOnPath:path batch:batch]; + [merge enumerateWrites:^(FPath *relativePath, id node, BOOL *stop) { + FPath *childPath = [path child:relativePath]; + [self removeAllWithPrefix:serverCacheKey(childPath) + batch:batch + database:self.serverCacheDB]; + [self saveNodeInternal:node + atPath:childPath + batch:batch + counter:&counter]; + }]; + BOOL success = [batch commit]; + if (!success) { + FFWarn(@"I-RDB076019", @"Failed to update server cache on disk!"); + } else { + FFDebug(@"I-RDB076020", @"Saved %lu leaf nodes for merge in %fms", + (unsigned long)counter, [start timeIntervalSinceNow] * -1000); + } +} + +- (void)saveNodeInternal:(id)node + atPath:(FPath *)path + batch:(id)batch + counter:(NSUInteger *)counter { + id data = [node valForExport:YES]; + if (data != nil && ![data isKindOfClass:[NSNull class]]) { + [self internalSetNestedData:data + forKey:serverCacheKey(path) + withBatch:batch + counter:counter]; + } +} + +- (NSUInteger)serverCacheEstimatedSizeInBytes { + // Use the exact size, because for pruning the approximate size can lead to + // weird situations where we prune everything because no compaction is ever + // run + return [self.serverCacheDB exactSizeFrom:kFServerCachePrefix + to:kFServerCacheRangeEnd]; +} + +- (void)pruneCache:(FPruneForest *)pruneForest atPath:(FPath *)path { + // TODO: be more intelligent, don't scan entire database... + + __block NSUInteger pruned = 0; + __block NSUInteger kept = 0; + NSDate *start = [NSDate date]; + + NSString *prefix = serverCacheKey(path); + id batch = [self.serverCacheDB beginWriteBatch]; + + [self.serverCacheDB + enumerateKeysWithPrefix:prefix + usingBlock:^(NSString *dbKey, BOOL *stop) { + NSString *pathStr = + [dbKey substringFromIndex:prefix.length]; + FPath *relativePath = [[FPath alloc] initWith:pathStr]; + if ([pruneForest shouldPruneUnkeptDescendantsAtPath: + relativePath]) { + pruned++; + [batch removeKey:dbKey]; + } else { + kept++; + } + }]; + BOOL success = [batch commit]; + if (!success) { + FFWarn(@"I-RDB076021", @"Failed to prune cache on disk!"); + } else { + FFDebug(@"I-RDB076022", @"Pruned %lu paths, kept %lu paths in %fms", + (unsigned long)pruned, (unsigned long)kept, + [start timeIntervalSinceNow] * -1000); + } +} + +#pragma mark - Tracked Queries + +- (NSArray *)loadTrackedQueries { + NSDate *date = [NSDate date]; + NSMutableArray *trackedQueries = [NSMutableArray array]; + [self.serverCacheDB + enumerateKeysWithPrefix:kFTrackedQueriesPrefix + asData:^(NSString *key, NSData *data, BOOL *stop) { + NSError *error = nil; + NSDictionary *queryJSON = + [NSJSONSerialization JSONObjectWithData:data + options:0 + error:&error]; + if (queryJSON == nil) { + if (error.code == kFNanFailureCode) { + FFWarn( + @"I-RDB076023", + @"Failed to deserialize tracked query " + @"(%@), likely because of out of range " + @"doubles (Error: %@)", + [[NSString alloc] + initWithData:data + encoding:NSUTF8StringEncoding], + error); + FFWarn(@"I-RDB076024", + @"Removing failed tracked query with " + @"key %@", + key); + [self.serverCacheDB removeKey:key]; + } else { + [NSException + raise:NSInternalInconsistencyException + format:@"Failed to deserialize tracked " + @"query: %@", + error]; + } + } else { + NSUInteger queryId = + ((NSNumber *)queryJSON[kFTrackedQueryId]) + .unsignedIntegerValue; + FPath *path = + [FPath pathWithString: + queryJSON[kFTrackedQueryPath]]; + FQueryParams *params = [FQueryParams + fromQueryObject:queryJSON + [kFTrackedQueryParams]]; + FQuerySpec *query = + [[FQuerySpec alloc] initWithPath:path + params:params]; + BOOL isComplete = + [queryJSON[kFTrackedQueryIsComplete] + boolValue]; + BOOL isActive = + [queryJSON[kFTrackedQueryIsActive] + boolValue]; + NSTimeInterval lastUse = + [queryJSON[kFTrackedQueryLastUse] + doubleValue]; + + FTrackedQuery *trackedQuery = + [[FTrackedQuery alloc] + initWithId:queryId + query:query + lastUse:lastUse + isActive:isActive + isComplete:isComplete]; + + [trackedQueries addObject:trackedQuery]; + } + }]; + FFDebug(@"I-RDB076025", @"Loaded %lu tracked queries in %fms", + (unsigned long)trackedQueries.count, + [date timeIntervalSinceNow] * -1000); + return trackedQueries; +} + +- (void)removeTrackedQuery:(NSUInteger)queryId { + NSDate *start = [NSDate date]; + id batch = [self.serverCacheDB beginWriteBatch]; + [batch removeKey:trackedQueryKey(queryId)]; + __block NSUInteger keyCount = 0; + [self.serverCacheDB + enumerateKeysWithPrefix:trackedQueryKeysKeyPrefix(queryId) + usingBlock:^(NSString *key, BOOL *stop) { + [batch removeKey:key]; + keyCount++; + }]; + + BOOL success = [batch commit]; + if (!success) { + FFWarn(@"I-RDB076026", @"Failed to remove tracked query on disk!"); + } else { + FFDebug(@"I-RDB076027", + @"Removed query with id %lu (and removed %lu keys) in %fms", + (unsigned long)queryId, (unsigned long)keyCount, + [start timeIntervalSinceNow] * -1000); + } +} + +- (void)saveTrackedQuery:(FTrackedQuery *)query { + NSDate *start = [NSDate date]; + NSDictionary *trackedQuery = @{ + kFTrackedQueryId : @(query.queryId), + kFTrackedQueryPath : [query.query.path toStringWithTrailingSlash], + kFTrackedQueryParams : [query.query.params wireProtocolParams], + kFTrackedQueryLastUse : @(query.lastUse), + kFTrackedQueryIsComplete : @(query.isComplete), + kFTrackedQueryIsActive : @(query.isActive) + }; + NSError *error = nil; + NSData *data = [NSJSONSerialization dataWithJSONObject:trackedQuery + options:0 + error:&error]; + NSAssert(data, @"Failed to serialize tracked query (Error: %@)", error); + [self.serverCacheDB setData:data forKey:trackedQueryKey(query.queryId)]; + FFDebug(@"I-RDB076028", @"Saved tracked query %lu in %fms", + (unsigned long)query.queryId, [start timeIntervalSinceNow] * -1000); +} + +- (void)setTrackedQueryKeys:(NSSet *)keys forQueryId:(NSUInteger)queryId { + NSDate *start = [NSDate date]; + __block NSUInteger removed = 0; + __block NSUInteger added = 0; + id batch = [self.serverCacheDB beginWriteBatch]; + NSMutableSet *seenKeys = [NSMutableSet set]; + // First, delete any keys that might be stored and are not part of the + // current keys + [self.serverCacheDB + enumerateKeysWithPrefix:trackedQueryKeysKeyPrefix(queryId) + asStrings:^(NSString *dbKey, NSString *actualKey, + BOOL *stop) { + if ([keys containsObject:actualKey]) { + // Already in DB + [seenKeys addObject:actualKey]; + } else { + // Not part of set, delete key + [batch removeKey:dbKey]; + removed++; + } + }]; + + // Next add any keys that are missing in the database + [keys enumerateObjectsUsingBlock:^(NSString *childKey, BOOL *stop) { + if (![seenKeys containsObject:childKey]) { + [batch setString:childKey + forKey:trackedQueryKeysKey(queryId, childKey)]; + added++; + } + }]; + BOOL success = [batch commit]; + if (!success) { + FFWarn(@"I-RDB076029", @"Failed to set tracked queries on disk!"); + } else { + FFDebug(@"I-RDB076030", + @"Set %lu tracked keys (%lu added, %lu removed) for query %lu " + @"in %fms", + (unsigned long)keys.count, (unsigned long)added, + (unsigned long)removed, (unsigned long)queryId, + [start timeIntervalSinceNow] * -1000); + } +} + +- (void)updateTrackedQueryKeysWithAddedKeys:(NSSet *)added + removedKeys:(NSSet *)removed + forQueryId:(NSUInteger)queryId { + NSDate *start = [NSDate date]; + id batch = [self.serverCacheDB beginWriteBatch]; + [removed enumerateObjectsUsingBlock:^(NSString *key, BOOL *stop) { + [batch removeKey:trackedQueryKeysKey(queryId, key)]; + }]; + [added enumerateObjectsUsingBlock:^(NSString *key, BOOL *stop) { + [batch setString:key forKey:trackedQueryKeysKey(queryId, key)]; + }]; + BOOL success = [batch commit]; + if (!success) { + FFWarn(@"I-RDB076031", @"Failed to update tracked queries on disk!"); + } else { + FFDebug(@"I-RDB076032", + @"Added %lu tracked keys, removed %lu for query %lu in %fms", + (unsigned long)added.count, (unsigned long)removed.count, + (unsigned long)queryId, [start timeIntervalSinceNow] * -1000); + } +} + +- (NSSet *)trackedQueryKeysForQuery:(NSUInteger)queryId { + NSDate *start = [NSDate date]; + NSMutableSet *set = [NSMutableSet set]; + [self.serverCacheDB + enumerateKeysWithPrefix:trackedQueryKeysKeyPrefix(queryId) + asStrings:^(NSString *dbKey, NSString *actualKey, + BOOL *stop) { + [set addObject:actualKey]; + }]; + FFDebug(@"I-RDB076033", @"Loaded %lu tracked keys for query %lu in %fms", + (unsigned long)set.count, (unsigned long)queryId, + [start timeIntervalSinceNow] * -1000); + return set; +} + +#pragma mark - Internal methods + +- (void)removeAllLeafNodesOnPath:(FPath *)path + batch:(id)batch { + while (!path.isEmpty) { + [batch removeKey:serverCacheKey(path)]; + path = [path parent]; + } + // Make sure to delete any nodes at the root + [batch removeKey:serverCacheKey([FPath empty])]; +} + +- (void)removeAllWithPrefix:(NSString *)prefix + batch:(id)batch + database:(APLevelDB *)database { + assert(prefix != nil); + + [database enumerateKeysWithPrefix:prefix + usingBlock:^(NSString *key, BOOL *stop) { + [batch removeKey:key]; + }]; +} + +#pragma mark - Internal helper methods + +- (void)internalSetNestedData:(id)value + forKey:(NSString *)key + withBatch:(id)batch + counter:(NSUInteger *)counter { + if ([value isKindOfClass:[NSDictionary class]]) { + NSDictionary *dictionary = value; + [dictionary enumerateKeysAndObjectsUsingBlock:^(id childKey, id obj, + BOOL *stop) { + assert(obj != nil); + NSString *childPath = + [NSString stringWithFormat:@"%@%@/", key, childKey]; + [self internalSetNestedData:obj + forKey:childPath + withBatch:batch + counter:counter]; + }]; + } else { + NSData *data = [self serializePrimitive:value]; + [batch setData:data forKey:key]; + (*counter)++; + } +} + +- (id)internalNestedDataForPath:(FPath *)path { + NSAssert(path != nil, @"Path was nil!"); + + NSString *baseKey = serverCacheKey(path); + + // HACK to make sure iter is freed now to avoid race conditions (if self.db + // is deleted before iter, you get an access violation). + @autoreleasepool { + APLevelDBIterator *iter = + [APLevelDBIterator iteratorWithLevelDB:self.serverCacheDB]; + + [iter seekToKey:baseKey]; + if (iter.key == nil || ![iter.key hasPrefix:baseKey]) { + // No data. + return nil; + } else { + return [self internalNestedDataFromIterator:iter + andKeyPrefix:baseKey]; + } + } +} + +- (id)internalNestedDataFromIterator:(APLevelDBIterator *)iterator + andKeyPrefix:(NSString *)prefix { + NSString *key = iterator.key; + + if ([key isEqualToString:prefix]) { + id result = [self deserializePrimitive:iterator.valueAsData]; + [iterator nextKey]; + return result; + } else { + NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; + while (key != nil && [key hasPrefix:prefix]) { + NSString *relativePath = [key substringFromIndex:prefix.length]; + NSArray *pathPieces = + [relativePath componentsSeparatedByString:@"/"]; + assert(pathPieces.count > 0); + NSString *childName = pathPieces[0]; + NSString *childPath = + [NSString stringWithFormat:@"%@%@/", prefix, childName]; + id childValue = [self internalNestedDataFromIterator:iterator + andKeyPrefix:childPath]; + [dict setValue:childValue forKey:childName]; + + key = iterator.key; + } + return dict; + } +} + +- (NSData *)serializePrimitive:(id)value { + // HACK: The built-in serialization only works on dicts and arrays. So we + // create an array and then strip off the leading / trailing byte (the [ and + // ]). + NSError *error = nil; + NSData *data = [NSJSONSerialization dataWithJSONObject:@[ value ] + options:0 + error:&error]; + NSAssert(data, @"Failed to serialize primitive: %@", error); + + return [data subdataWithRange:NSMakeRange(1, data.length - 2)]; +} + +- (id)fixDoubleParsing:(id)value + __attribute__((no_sanitize("float-cast-overflow"))) { + if ([value isKindOfClass:[NSDecimalNumber class]]) { + // In case the value is an NSDecimalNumber, we may be dealing with + // precisions that are higher than what can be represented in a double. + // In this case it does not suffice to check for integral numbers by + // casting the [value doubleValue] to an int64_t, because this will + // cause the compared values to be rounded to double precision. + // Coupled with a bug in [NSDecimalNumber longLongValue] that triggers + // when converting values with high precision, this would cause + // values of high precision, but with an integral 'doubleValue' + // representation to be converted to bogus values. + // A radar for the NSDecimalNumber issue can be found here: + // http://www.openradar.me/radar?id=5007005597040640 + // Consider the NSDecimalNumber value: 999.9999999999999487 + // This number has a 'doubleValue' of 1000. Using the previous version + // of this method would cause the value to be interpreted to be integral + // and then the resulting value would be based on the longLongValue + // which due to the NSDecimalNumber issue would turn out as -844. + // By using NSDecimal logic to test for integral values, + // 999.9999999999999487 will not be considered integral, and instead + // of triggering the 'longLongValue' issue, it will be returned as + // the 'doubleValue' representation (1000). + // Please note, that even without the NSDecimalNumber issue, the + // 'correct' longLongValue of 999.9999999999999487 is 999 and not 1000, + // so the previous code would cause issues even without the bug + // referenced in the radar. + NSDecimal original = [(NSDecimalNumber *)value decimalValue]; + NSDecimal rounded; + NSDecimalRound(&rounded, &original, 0, NSRoundPlain); + if (NSDecimalCompare(&original, &rounded) != NSOrderedSame) { + NSString *doubleString = [value stringValue]; + return [NSNumber numberWithDouble:[doubleString doubleValue]]; + } else { + return [NSNumber numberWithLongLong:[value longLongValue]]; + } + } else if ([value isKindOfClass:[NSNumber class]]) { + // The parser for double values in JSONSerialization at the root takes + // some short-cuts and delivers wrong results (wrong rounding) for some + // double values, including 2.47. Because we use the exact bytes for + // hashing on the server this will lead to hash mismatches. The parser + // of NSNumber seems to be more in line with what the server expects, so + // we use that here + CFNumberType type = CFNumberGetType((CFNumberRef)value); + if (type == kCFNumberDoubleType || type == kCFNumberFloatType) { + // The NSJSON parser returns all numbers as double values, even + // those that contain no exponent. To make sure that the String + // conversion below doesn't unexpectedly reduce precision, we make + // sure that our number is indeed not an integer. + if ((double)(int64_t)[value doubleValue] != [value doubleValue]) { + NSString *doubleString = [value stringValue]; + return [NSNumber numberWithDouble:[doubleString doubleValue]]; + } else { + return [NSNumber numberWithLongLong:[value longLongValue]]; + } + } + } + return value; +} + +- (id)deserializePrimitive:(NSData *)data { + NSError *error = nil; + id result = + [NSJSONSerialization JSONObjectWithData:data + options:NSJSONReadingAllowFragments + error:&error]; + if (result != nil) { + return [self fixDoubleParsing:result]; + } else { + if (error.code == kFNanFailureCode) { + FFWarn(@"I-RDB076034", + @"Failed to load primitive %@, likely because doubles where " + @"out of range (Error: %@)", + [[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding], + error); + return [NSNull null]; + } else { + [NSException raise:NSInternalInconsistencyException + format:@"Failed to deserialiaze primitive: %@", error]; + return nil; + } + } +} + ++ (void)ensureDir:(NSString *)path markAsDoNotBackup:(BOOL)markAsDoNotBackup { + NSError *error; + BOOL success = + [[NSFileManager defaultManager] createDirectoryAtPath:path + withIntermediateDirectories:YES + attributes:nil + error:&error]; + if (!success) { + @throw [NSException + exceptionWithName:@"FailedToCreatePersistenceDir" + reason:@"Failed to create persistence directory." + userInfo:@{@"path" : path}]; + } + + if (markAsDoNotBackup) { + NSURL *firebaseDirURL = [NSURL fileURLWithPath:path]; + success = [firebaseDirURL setResourceValue:@YES + forKey:NSURLIsExcludedFromBackupKey + error:&error]; + if (!success) { + FFWarn( + @"I-RDB076035", + @"Failed to mark firebase database folder as do not backup: %@", + error); + [NSException raise:@"Error marking as do not backup" + format:@"Failed to mark folder %@ as do not backup", + firebaseDirURL]; + } + } +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FPendingPut.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FPendingPut.h new file mode 100644 index 0000000..602bf8c --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FPendingPut.h @@ -0,0 +1,53 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FPath.h" +#import + +// These are all legacy classes and are used to migrate older persistence data +// base to newer ones These classes should not be used in newer code + +@interface FPendingPut : NSObject + +@property(nonatomic, strong) FPath *path; +@property(nonatomic, strong) id data; +@property(nonatomic, strong) id priority; + +- (id)initWithPath:(FPath *)aPath andData:(id)aData andPriority:aPriority; +- (void)encodeWithCoder:(NSCoder *)aCoder; +- (id)initWithCoder:(NSCoder *)aDecoder; +@end + +@interface FPendingPutPriority : NSObject + +@property(nonatomic, strong) FPath *path; +@property(nonatomic, strong) id priority; + +- (id)initWithPath:(FPath *)aPath andPriority:(id)aPriority; +- (void)encodeWithCoder:(NSCoder *)aCoder; +- (id)initWithCoder:(NSCoder *)aDecoder; + +@end + +@interface FPendingUpdate : NSObject + +@property(nonatomic, strong) FPath *path; +@property(nonatomic, strong) NSDictionary *data; + +- (id)initWithPath:(FPath *)aPath andData:(NSDictionary *)aData; +- (void)encodeWithCoder:(NSCoder *)aCoder; +- (id)initWithCoder:(NSCoder *)aDecoder; +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FPendingPut.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FPendingPut.m new file mode 100644 index 0000000..c519599 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FPendingPut.m @@ -0,0 +1,113 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FPendingPut.h" + +@implementation FPendingPut + +@synthesize path; +@synthesize data; + +- (id)initWithPath:(FPath *)aPath andData:(id)aData andPriority:(id)aPriority { + self = [super init]; + if (self) { + self.path = aPath; + self.data = aData; + self.priority = aPriority; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:[self.path description] forKey:@"path"]; + [aCoder encodeObject:self.data forKey:@"data"]; + [aCoder encodeObject:self.priority forKey:@"priority"]; +} + +- (id)initWithCoder:(NSCoder *)aDecoder { + self = [super init]; + if (self) { + self.path = + [[FPath alloc] initWith:[aDecoder decodeObjectForKey:@"path"]]; + self.data = [aDecoder decodeObjectForKey:@"data"]; + self.priority = [aDecoder decodeObjectForKey:@"priority"]; + } + return self; +} + +@end + +@implementation FPendingPutPriority + +@synthesize path; +@synthesize priority; + +- (id)initWithPath:(FPath *)aPath andPriority:(id)aPriority { + self = [super init]; + if (self) { + self.path = aPath; + self.priority = aPriority; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:[self.path description] forKey:@"path"]; + [aCoder encodeObject:self.priority forKey:@"priority"]; +} + +- (id)initWithCoder:(NSCoder *)aDecoder { + self = [super init]; + if (self) { + self.path = + [[FPath alloc] initWith:[aDecoder decodeObjectForKey:@"path"]]; + self.priority = [aDecoder decodeObjectForKey:@"priority"]; + } + return self; +} + +@end + +@implementation FPendingUpdate + +@synthesize path; +@synthesize data; + +- (id)initWithPath:(FPath *)aPath andData:(id)aData { + self = [super init]; + if (self) { + self.path = aPath; + self.data = aData; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:[self.path description] forKey:@"path"]; + [aCoder encodeObject:self.data forKey:@"data"]; +} + +- (id)initWithCoder:(NSCoder *)aDecoder { + self = [super init]; + if (self) { + self.path = + [[FPath alloc] initWith:[aDecoder decodeObjectForKey:@"path"]]; + self.data = [aDecoder decodeObjectForKey:@"data"]; + } + return self; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FPersistenceManager.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FPersistenceManager.h new file mode 100644 index 0000000..681c56d --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FPersistenceManager.h @@ -0,0 +1,60 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FCacheNode.h" +#import "FCachePolicy.h" +#import "FCompoundWrite.h" +#import "FNode.h" +#import "FQuerySpec.h" +#import "FRepoInfo.h" +#import "FStorageEngine.h" + +@interface FPersistenceManager : NSObject + +- (id)initWithStorageEngine:(id)storageEngine + cachePolicy:(id)cachePolicy; +- (void)close; + +- (void)saveUserOverwrite:(id)node + atPath:(FPath *)path + writeId:(NSUInteger)writeId; +- (void)saveUserMerge:(FCompoundWrite *)merge + atPath:(FPath *)path + writeId:(NSUInteger)writeId; +- (void)removeUserWrite:(NSUInteger)writeId; +- (void)removeAllUserWrites; +- (NSArray *)userWrites; + +- (FCacheNode *)serverCacheForQuery:(FQuerySpec *)spec; +- (void)updateServerCacheWithNode:(id)node forQuery:(FQuerySpec *)spec; +- (void)updateServerCacheWithMerge:(FCompoundWrite *)merge atPath:(FPath *)path; + +- (void)applyUserWrite:(id)write toServerCacheAtPath:(FPath *)path; +- (void)applyUserMerge:(FCompoundWrite *)merge + toServerCacheAtPath:(FPath *)path; + +- (void)setQueryComplete:(FQuerySpec *)spec; +- (void)setQueryActive:(FQuerySpec *)spec; +- (void)setQueryInactive:(FQuerySpec *)spec; + +- (void)setTrackedQueryKeys:(NSSet *)keys forQuery:(FQuerySpec *)query; +- (void)updateTrackedQueryKeysWithAddedKeys:(NSSet *)added + removedKeys:(NSSet *)removed + forQuery:(FQuerySpec *)query; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FPersistenceManager.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FPersistenceManager.m new file mode 100644 index 0000000..8924802 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FPersistenceManager.m @@ -0,0 +1,231 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FPersistenceManager.h" +#import "FCacheNode.h" +#import "FClock.h" +#import "FIndexedNode.h" +#import "FLevelDBStorageEngine.h" +#import "FPruneForest.h" +#import "FTrackedQuery.h" +#import "FTrackedQueryManager.h" +#import "FUtilities.h" +#import + +@interface FPersistenceManager () + +@property(nonatomic, strong) id storageEngine; +@property(nonatomic, strong) id cachePolicy; +@property(nonatomic, strong) FTrackedQueryManager *trackedQueryManager; +@property(nonatomic) NSUInteger serverCacheUpdatesSinceLastPruneCheck; + +@end + +@implementation FPersistenceManager + +- (id)initWithStorageEngine:(id)storageEngine + cachePolicy:(id)cachePolicy { + self = [super init]; + if (self != nil) { + self->_storageEngine = storageEngine; + self->_cachePolicy = cachePolicy; + self->_trackedQueryManager = [[FTrackedQueryManager alloc] + initWithStorageEngine:self.storageEngine + clock:[FSystemClock clock]]; + } + return self; +} + +- (void)close { + [self.storageEngine close]; + self.storageEngine = nil; + self.trackedQueryManager = nil; +} + +- (void)saveUserOverwrite:(id)node + atPath:(FPath *)path + writeId:(NSUInteger)writeId { + [self.storageEngine saveUserOverwrite:node atPath:path writeId:writeId]; +} + +- (void)saveUserMerge:(FCompoundWrite *)merge + atPath:(FPath *)path + writeId:(NSUInteger)writeId { + [self.storageEngine saveUserMerge:merge atPath:path writeId:writeId]; +} + +- (void)removeUserWrite:(NSUInteger)writeId { + [self.storageEngine removeUserWrite:writeId]; +} + +- (void)removeAllUserWrites { + [self.storageEngine removeAllUserWrites]; +} + +- (NSArray *)userWrites { + return [self.storageEngine userWrites]; +} + +- (FCacheNode *)serverCacheForQuery:(FQuerySpec *)query { + NSSet *trackedKeys; + BOOL complete; + // TODO[offline]: Should we use trackedKeys to find out if this location is + // a child of a complete query? + if ([self.trackedQueryManager isQueryComplete:query]) { + complete = YES; + FTrackedQuery *trackedQuery = + [self.trackedQueryManager findTrackedQuery:query]; + if (!query.loadsAllData && trackedQuery.isComplete) { + trackedKeys = [self.storageEngine + trackedQueryKeysForQuery:trackedQuery.queryId]; + } else { + trackedKeys = nil; + } + } else { + complete = NO; + trackedKeys = + [self.trackedQueryManager knownCompleteChildrenAtPath:query.path]; + } + + id node; + if (trackedKeys != nil) { + node = [self.storageEngine serverCacheForKeys:trackedKeys + atPath:query.path]; + } else { + node = [self.storageEngine serverCacheAtPath:query.path]; + } + + FIndexedNode *indexedNode = [FIndexedNode indexedNodeWithNode:node + index:query.index]; + return [[FCacheNode alloc] initWithIndexedNode:indexedNode + isFullyInitialized:complete + isFiltered:(trackedKeys != nil)]; +} + +- (void)updateServerCacheWithNode:(id)node forQuery:(FQuerySpec *)query { + BOOL merge = !query.loadsAllData; + [self.storageEngine updateServerCache:node atPath:query.path merge:merge]; + [self setQueryComplete:query]; + [self doPruneCheckAfterServerUpdate]; +} + +- (void)updateServerCacheWithMerge:(FCompoundWrite *)merge + atPath:(FPath *)path { + [self.storageEngine updateServerCacheWithMerge:merge atPath:path]; + [self doPruneCheckAfterServerUpdate]; +} + +- (void)applyUserMerge:(FCompoundWrite *)merge + toServerCacheAtPath:(FPath *)path { + // TODO[offline]: rework this to be more efficient + [merge enumerateWrites:^(FPath *relativePath, id node, BOOL *stop) { + [self applyUserWrite:node toServerCacheAtPath:[path child:relativePath]]; + }]; +} + +- (void)applyUserWrite:(id)write toServerCacheAtPath:(FPath *)path { + // This is a hack to guess whether we already cached this because we got a + // server data update for this write via an existing active default query. + // If we didn't, then we'll manually cache this and add a tracked query to + // mark it complete and keep it cached. Unfortunately this is just a guess + // and it's possible that we *did* get an update (e.g. via a filtered query) + // and by overwriting the cache here, we'll actually store an incorrect + // value (e.g. in the case that we wrote a ServerValue.TIMESTAMP and the + // server resolved it to a different value). + // TODO[offline]: Consider reworking. + if (![self.trackedQueryManager hasActiveDefaultQueryAtPath:path]) { + [self.storageEngine updateServerCache:write atPath:path merge:NO]; + [self.trackedQueryManager ensureCompleteTrackedQueryAtPath:path]; + } +} + +- (void)setQueryComplete:(FQuerySpec *)query { + if (query.loadsAllData) { + [self.trackedQueryManager setQueriesCompleteAtPath:query.path]; + } else { + [self.trackedQueryManager setQueryComplete:query]; + } +} + +- (void)setQueryActive:(FQuerySpec *)spec { + [self.trackedQueryManager setQueryActive:spec]; +} + +- (void)setQueryInactive:(FQuerySpec *)spec { + [self.trackedQueryManager setQueryInactive:spec]; +} + +- (void)doPruneCheckAfterServerUpdate { + self.serverCacheUpdatesSinceLastPruneCheck++; + if ([self.cachePolicy + shouldCheckCacheSize:self.serverCacheUpdatesSinceLastPruneCheck]) { + FFDebug(@"I-RDB078001", @"Reached prune check threshold. Checking..."); + NSDate *date = [NSDate date]; + self.serverCacheUpdatesSinceLastPruneCheck = 0; + BOOL canPrune = YES; + NSUInteger cacheSize = + [self.storageEngine serverCacheEstimatedSizeInBytes]; + FFDebug(@"I-RDB078002", @"Server cache size: %lu", + (unsigned long)cacheSize); + while (canPrune && + [self.cachePolicy + shouldPruneCacheWithSize:cacheSize + numberOfTrackedQueries:self.trackedQueryManager + .numberOfPrunableQueries]) { + FPruneForest *pruneForest = + [self.trackedQueryManager pruneOldQueries:self.cachePolicy]; + if (pruneForest.prunesAnything) { + [self.storageEngine pruneCache:pruneForest + atPath:[FPath empty]]; + } else { + canPrune = NO; + } + cacheSize = [self.storageEngine serverCacheEstimatedSizeInBytes]; + FFDebug(@"I-RDB078003", @"Cache size after pruning: %lu", + (unsigned long)cacheSize); + } + FFDebug(@"I-RDB078004", @"Pruning round took %fms", + [date timeIntervalSinceNow] * -1000); + } +} + +- (void)setTrackedQueryKeys:(NSSet *)keys forQuery:(FQuerySpec *)query { + NSAssert(!query.loadsAllData, + @"We should only track keys for filtered queries"); + FTrackedQuery *trackedQuery = + [self.trackedQueryManager findTrackedQuery:query]; + NSAssert(trackedQuery.isActive, + @"We only expect tracked keys for currently-active queries."); + [self.storageEngine setTrackedQueryKeys:keys + forQueryId:trackedQuery.queryId]; +} + +- (void)updateTrackedQueryKeysWithAddedKeys:(NSSet *)added + removedKeys:(NSSet *)removed + forQuery:(FQuerySpec *)query { + NSAssert(!query.loadsAllData, + @"We should only track keys for filtered queries"); + FTrackedQuery *trackedQuery = + [self.trackedQueryManager findTrackedQuery:query]; + NSAssert(trackedQuery.isActive, + @"We only expect tracked keys for currently-active queries."); + [self.storageEngine + updateTrackedQueryKeysWithAddedKeys:added + removedKeys:removed + forQueryId:trackedQuery.queryId]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FPruneForest.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FPruneForest.h new file mode 100644 index 0000000..9e77217 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FPruneForest.h @@ -0,0 +1,38 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FPath; + +@interface FPruneForest : NSObject + ++ (FPruneForest *)empty; + +- (BOOL)prunesAnything; +- (BOOL)shouldPruneUnkeptDescendantsAtPath:(FPath *)path; +- (BOOL)shouldKeepPath:(FPath *)path; +- (BOOL)affectsPath:(FPath *)path; +- (FPruneForest *)child:(NSString *)childKey; +- (FPruneForest *)childAtPath:(FPath *)childKey; +- (FPruneForest *)prunePath:(FPath *)path; +- (FPruneForest *)keepPath:(FPath *)path; +- (FPruneForest *)keepAll:(NSSet *)children atPath:(FPath *)path; +- (FPruneForest *)pruneAll:(NSSet *)children atPath:(FPath *)path; + +- (void)enumarateKeptNodesUsingBlock:(void (^)(FPath *path))block; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FPruneForest.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FPruneForest.m new file mode 100644 index 0000000..e795c69 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FPruneForest.m @@ -0,0 +1,194 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FPruneForest.h" + +#import "FImmutableTree.h" + +@interface FPruneForest () + +@property(nonatomic, strong) FImmutableTree *pruneForest; + +@end + +@implementation FPruneForest + +static BOOL (^kFPrunePredicate)(id) = ^BOOL(NSNumber *pruneValue) { + return [pruneValue boolValue]; +}; + +static BOOL (^kFKeepPredicate)(id) = ^BOOL(NSNumber *pruneValue) { + return ![pruneValue boolValue]; +}; + ++ (FImmutableTree *)pruneTree { + static dispatch_once_t onceToken; + static FImmutableTree *pruneTree; + dispatch_once(&onceToken, ^{ + pruneTree = [[FImmutableTree alloc] initWithValue:@YES]; + }); + return pruneTree; +} + ++ (FImmutableTree *)keepTree { + static dispatch_once_t onceToken; + static FImmutableTree *keepTree; + dispatch_once(&onceToken, ^{ + keepTree = [[FImmutableTree alloc] initWithValue:@NO]; + }); + return keepTree; +} + +- (id)initWithForest:(FImmutableTree *)tree { + self = [super init]; + if (self != nil) { + self->_pruneForest = tree; + } + return self; +} + ++ (FPruneForest *)empty { + static dispatch_once_t onceToken; + static FPruneForest *forest; + dispatch_once(&onceToken, ^{ + forest = [[FPruneForest alloc] initWithForest:[FImmutableTree empty]]; + }); + return forest; +} + +- (BOOL)prunesAnything { + return [self.pruneForest containsValueMatching:kFPrunePredicate]; +} + +- (BOOL)shouldPruneUnkeptDescendantsAtPath:(FPath *)path { + NSNumber *shouldPrune = [self.pruneForest leafMostValueOnPath:path]; + return shouldPrune != nil && [shouldPrune boolValue]; +} + +- (BOOL)shouldKeepPath:(FPath *)path { + NSNumber *shouldPrune = [self.pruneForest leafMostValueOnPath:path]; + return shouldPrune != nil && ![shouldPrune boolValue]; +} + +- (BOOL)affectsPath:(FPath *)path { + return [self.pruneForest rootMostValueOnPath:path] != nil || + ![[self.pruneForest subtreeAtPath:path] isEmpty]; +} + +- (FPruneForest *)child:(NSString *)childKey { + FImmutableTree *childPruneForest = [self.pruneForest.children get:childKey]; + if (childPruneForest == nil) { + if (self.pruneForest.value != nil) { + childPruneForest = [self.pruneForest.value boolValue] + ? [FPruneForest pruneTree] + : [FPruneForest keepTree]; + } else { + childPruneForest = [FImmutableTree empty]; + } + } else { + if (childPruneForest.value == nil && self.pruneForest.value != nil) { + childPruneForest = [childPruneForest setValue:self.pruneForest.value + atPath:[FPath empty]]; + } + } + return [[FPruneForest alloc] initWithForest:childPruneForest]; +} + +- (FPruneForest *)childAtPath:(FPath *)path { + if (path.isEmpty) { + return self; + } else { + return [[self child:path.getFront] childAtPath:[path popFront]]; + } +} + +- (FPruneForest *)prunePath:(FPath *)path { + if ([self.pruneForest rootMostValueOnPath:path matching:kFKeepPredicate]) { + [NSException raise:NSInvalidArgumentException + format:@"Can't prune path that was kept previously!"]; + } + if ([self.pruneForest rootMostValueOnPath:path matching:kFPrunePredicate]) { + // This path will already be pruned + return self; + } else { + FImmutableTree *newPruneForest = + [self.pruneForest setTree:[FPruneForest pruneTree] atPath:path]; + return [[FPruneForest alloc] initWithForest:newPruneForest]; + } +} + +- (FPruneForest *)keepPath:(FPath *)path { + if ([self.pruneForest rootMostValueOnPath:path matching:kFKeepPredicate]) { + // This path will already be kept + return self; + } else { + FImmutableTree *newPruneForest = + [self.pruneForest setTree:[FPruneForest keepTree] atPath:path]; + return [[FPruneForest alloc] initWithForest:newPruneForest]; + } +} + +- (FPruneForest *)keepAll:(NSSet *)children atPath:(FPath *)path { + if ([self.pruneForest rootMostValueOnPath:path matching:kFKeepPredicate]) { + // This path will already be kept + return self; + } else { + return [self setPruneValue:[FPruneForest keepTree] + forAll:children + atPath:path]; + } +} + +- (FPruneForest *)pruneAll:(NSSet *)children atPath:(FPath *)path { + if ([self.pruneForest rootMostValueOnPath:path matching:kFKeepPredicate]) { + [NSException raise:NSInvalidArgumentException + format:@"Can't prune path that was kept previously!"]; + } + if ([self.pruneForest rootMostValueOnPath:path matching:kFPrunePredicate]) { + // This path will already be pruned + return self; + } else { + return [self setPruneValue:[FPruneForest pruneTree] + forAll:children + atPath:path]; + } +} + +- (FPruneForest *)setPruneValue:(FImmutableTree *)pruneValue + forAll:(NSSet *)children + atPath:(FPath *)path { + FImmutableTree *subtree = [self.pruneForest subtreeAtPath:path]; + __block FImmutableSortedDictionary *childrenDictionary = subtree.children; + [children enumerateObjectsUsingBlock:^(NSString *childKey, BOOL *stop) { + childrenDictionary = [childrenDictionary insertKey:childKey + withValue:pruneValue]; + }]; + FImmutableTree *newSubtree = + [[FImmutableTree alloc] initWithValue:subtree.value + children:childrenDictionary]; + return [[FPruneForest alloc] + initWithForest:[self.pruneForest setTree:newSubtree atPath:path]]; +} + +- (void)enumarateKeptNodesUsingBlock:(void (^)(FPath *))block { + [self.pruneForest forEach:^(FPath *path, id value) { + if (value != nil && ![value boolValue]) { + block(path); + } + }]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FStorageEngine.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FStorageEngine.h new file mode 100644 index 0000000..5f418b8 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FStorageEngine.h @@ -0,0 +1,60 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@protocol FNode; +@class FPruneForest; +@class FPath; +@class FCompoundWrite; +@class FQuerySpec; +@class FTrackedQuery; + +@protocol FStorageEngine + +- (void)close; + +- (void)saveUserOverwrite:(id)node + atPath:(FPath *)path + writeId:(NSUInteger)writeId; +- (void)saveUserMerge:(FCompoundWrite *)merge + atPath:(FPath *)path + writeId:(NSUInteger)writeId; +- (void)removeUserWrite:(NSUInteger)writeId; +- (void)removeAllUserWrites; +- (NSArray *)userWrites; + +- (id)serverCacheAtPath:(FPath *)path; +- (id)serverCacheForKeys:(NSSet *)keys atPath:(FPath *)path; +- (void)updateServerCache:(id)node + atPath:(FPath *)path + merge:(BOOL)merge; +- (void)updateServerCacheWithMerge:(FCompoundWrite *)merge atPath:(FPath *)path; +- (NSUInteger)serverCacheEstimatedSizeInBytes; + +- (void)pruneCache:(FPruneForest *)pruneForest atPath:(FPath *)path; + +- (NSArray *)loadTrackedQueries; +- (void)removeTrackedQuery:(NSUInteger)queryId; +- (void)saveTrackedQuery:(FTrackedQuery *)query; + +- (void)setTrackedQueryKeys:(NSSet *)keys forQueryId:(NSUInteger)queryId; +- (void)updateTrackedQueryKeysWithAddedKeys:(NSSet *)added + removedKeys:(NSSet *)removed + forQueryId:(NSUInteger)queryId; +- (NSSet *)trackedQueryKeysForQuery:(NSUInteger)queryId; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FTrackedQuery.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FTrackedQuery.h new file mode 100644 index 0000000..7413816 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FTrackedQuery.h @@ -0,0 +1,43 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FQuerySpec; + +@interface FTrackedQuery : NSObject + +@property(nonatomic, readonly) NSUInteger queryId; +@property(nonatomic, strong, readonly) FQuerySpec *query; +@property(nonatomic, readonly) NSTimeInterval lastUse; +@property(nonatomic, readonly) BOOL isComplete; +@property(nonatomic, readonly) BOOL isActive; + +- (id)initWithId:(NSUInteger)queryId + query:(FQuerySpec *)query + lastUse:(NSTimeInterval)lastUse + isActive:(BOOL)isActive; +- (id)initWithId:(NSUInteger)queryId + query:(FQuerySpec *)query + lastUse:(NSTimeInterval)lastUse + isActive:(BOOL)isActive + isComplete:(BOOL)isComplete; + +- (FTrackedQuery *)updateLastUse:(NSTimeInterval)lastUse; +- (FTrackedQuery *)setComplete; +- (FTrackedQuery *)setActiveState:(BOOL)isActive; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FTrackedQuery.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FTrackedQuery.m new file mode 100644 index 0000000..6ca7ec0 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FTrackedQuery.m @@ -0,0 +1,113 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FTrackedQuery.h" + +#import "FQuerySpec.h" + +@interface FTrackedQuery () + +@property(nonatomic, readwrite) NSUInteger queryId; +@property(nonatomic, strong, readwrite) FQuerySpec *query; +@property(nonatomic, readwrite) NSTimeInterval lastUse; +@property(nonatomic, readwrite) BOOL isComplete; +@property(nonatomic, readwrite) BOOL isActive; + +@end + +@implementation FTrackedQuery + +- (id)initWithId:(NSUInteger)queryId + query:(FQuerySpec *)query + lastUse:(NSTimeInterval)lastUse + isActive:(BOOL)isActive + isComplete:(BOOL)isComplete { + self = [super init]; + if (self != nil) { + self->_queryId = queryId; + self->_query = query; + self->_lastUse = lastUse; + self->_isComplete = isComplete; + self->_isActive = isActive; + } + return self; +} + +- (id)initWithId:(NSUInteger)queryId + query:(FQuerySpec *)query + lastUse:(NSTimeInterval)lastUse + isActive:(BOOL)isActive { + return [self initWithId:queryId + query:query + lastUse:lastUse + isActive:isActive + isComplete:NO]; +} + +- (FTrackedQuery *)updateLastUse:(NSTimeInterval)lastUse { + return [[FTrackedQuery alloc] initWithId:self.queryId + query:self.query + lastUse:lastUse + isActive:self.isActive + isComplete:self.isComplete]; +} + +- (FTrackedQuery *)setComplete { + return [[FTrackedQuery alloc] initWithId:self.queryId + query:self.query + lastUse:self.lastUse + isActive:self.isActive + isComplete:YES]; +} + +- (FTrackedQuery *)setActiveState:(BOOL)isActive { + return [[FTrackedQuery alloc] initWithId:self.queryId + query:self.query + lastUse:self.lastUse + isActive:isActive + isComplete:self.isComplete]; +} + +- (BOOL)isEqual:(id)object { + if (![object isKindOfClass:[FTrackedQuery class]]) { + return NO; + } + FTrackedQuery *other = (FTrackedQuery *)object; + if (self.queryId != other.queryId) + return NO; + if (self.query != other.query && ![self.query isEqual:other.query]) + return NO; + if (self.lastUse != other.lastUse) + return NO; + if (self.isComplete != other.isComplete) + return NO; + if (self.isActive != other.isActive) + return NO; + + return YES; +} + +- (NSUInteger)hash { + NSUInteger hash = self.queryId; + hash = hash * 31 + self.query.hash; + hash = hash * 31 + (self.isActive ? 1 : 0); + hash = hash * 31 + (NSUInteger)self.lastUse; + hash = hash * 31 + (self.isComplete ? 1 : 0); + hash = hash * 31 + (self.isActive ? 1 : 0); + return hash; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FTrackedQueryManager.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FTrackedQueryManager.h new file mode 100644 index 0000000..cd7d5a1 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FTrackedQueryManager.h @@ -0,0 +1,52 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@protocol FStorageEngine; +@protocol FClock; +@protocol FCachePolicy; +@class FQuerySpec; +@class FPath; +@class FTrackedQuery; +@class FPruneForest; + +@interface FTrackedQueryManager : NSObject + +- (id)initWithStorageEngine:(id)storageEngine + clock:(id)clock; + +- (FTrackedQuery *)findTrackedQuery:(FQuerySpec *)query; + +- (BOOL)isQueryComplete:(FQuerySpec *)query; + +- (void)removeTrackedQuery:(FQuerySpec *)query; +- (void)setQueryComplete:(FQuerySpec *)query; +- (void)setQueriesCompleteAtPath:(FPath *)path; +- (void)setQueryActive:(FQuerySpec *)query; +- (void)setQueryInactive:(FQuerySpec *)query; + +- (BOOL)hasActiveDefaultQueryAtPath:(FPath *)path; +- (void)ensureCompleteTrackedQueryAtPath:(FPath *)path; + +- (FPruneForest *)pruneOldQueries:(id)cachePolicy; +- (NSUInteger)numberOfPrunableQueries; +- (NSSet *)knownCompleteChildrenAtPath:(FPath *)path; + +// For testing +- (void)verifyCache; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FTrackedQueryManager.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FTrackedQueryManager.m new file mode 100644 index 0000000..26accaf --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Persistence/FTrackedQueryManager.m @@ -0,0 +1,375 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FTrackedQueryManager.h" +#import "FCachePolicy.h" +#import "FClock.h" +#import "FImmutableTree.h" +#import "FLevelDBStorageEngine.h" +#import "FPruneForest.h" +#import "FTrackedQuery.h" +#import "FUtilities.h" +#import + +@interface FTrackedQueryManager () + +@property(nonatomic, strong) FImmutableTree *trackedQueryTree; +@property(nonatomic, strong) id storageEngine; +@property(nonatomic, strong) id clock; +@property(nonatomic) NSUInteger currentQueryId; + +@end + +@implementation FTrackedQueryManager + +- (id)initWithStorageEngine:(id)storageEngine + clock:(id)clock { + self = [super init]; + if (self != nil) { + self->_storageEngine = storageEngine; + self->_clock = clock; + self->_trackedQueryTree = [FImmutableTree empty]; + + NSTimeInterval lastUse = [clock currentTime]; + + NSArray *trackedQueries = [self.storageEngine loadTrackedQueries]; + [trackedQueries enumerateObjectsUsingBlock:^( + FTrackedQuery *trackedQuery, NSUInteger idx, + BOOL *stop) { + self.currentQueryId = + MAX(trackedQuery.queryId + 1, self.currentQueryId); + if (trackedQuery.isActive) { + trackedQuery = + [[trackedQuery setActiveState:NO] updateLastUse:lastUse]; + FFDebug( + @"I-RDB081001", + @"Setting active query %lu from previous app start inactive", + (unsigned long)trackedQuery.queryId); + [self.storageEngine saveTrackedQuery:trackedQuery]; + } + [self cacheTrackedQuery:trackedQuery]; + }]; + } + return self; +} + ++ (void)assertValidTrackedQuery:(FQuerySpec *)query { + NSAssert(!query.loadsAllData || query.isDefault, + @"Can't have tracked non-default query that loads all data"); +} + ++ (FQuerySpec *)normalizeQuery:(FQuerySpec *)query { + return query.loadsAllData ? [FQuerySpec defaultQueryAtPath:query.path] + : query; +} + +- (FTrackedQuery *)findTrackedQuery:(FQuerySpec *)query { + query = [FTrackedQueryManager normalizeQuery:query]; + NSDictionary *set = [self.trackedQueryTree valueAtPath:query.path]; + return set[query.params]; +} + +- (void)removeTrackedQuery:(FQuerySpec *)query { + query = [FTrackedQueryManager normalizeQuery:query]; + FTrackedQuery *trackedQuery = [self findTrackedQuery:query]; + NSAssert(trackedQuery, @"Tracked query must exist to be removed!"); + + [self.storageEngine removeTrackedQuery:trackedQuery.queryId]; + NSMutableDictionary *trackedQueries = + [self.trackedQueryTree valueAtPath:query.path]; + [trackedQueries removeObjectForKey:query.params]; +} + +- (void)setQueryActive:(FQuerySpec *)query { + [self setQueryActive:YES forQuery:query]; +} + +- (void)setQueryInactive:(FQuerySpec *)query { + [self setQueryActive:NO forQuery:query]; +} + +- (void)setQueryActive:(BOOL)isActive forQuery:(FQuerySpec *)query { + query = [FTrackedQueryManager normalizeQuery:query]; + FTrackedQuery *trackedQuery = [self findTrackedQuery:query]; + + // Regardless of whether it's now active or no langer active, we update the + // lastUse time + NSTimeInterval lastUse = [self.clock currentTime]; + if (trackedQuery != nil) { + trackedQuery = + [[trackedQuery updateLastUse:lastUse] setActiveState:isActive]; + [self.storageEngine saveTrackedQuery:trackedQuery]; + } else { + NSAssert(isActive, @"If we're setting the query to inactive, we should " + @"already be tracking it!"); + trackedQuery = [[FTrackedQuery alloc] initWithId:self.currentQueryId++ + query:query + lastUse:lastUse + isActive:isActive]; + [self.storageEngine saveTrackedQuery:trackedQuery]; + } + + [self cacheTrackedQuery:trackedQuery]; +} + +- (void)setQueryComplete:(FQuerySpec *)query { + query = [FTrackedQueryManager normalizeQuery:query]; + FTrackedQuery *trackedQuery = [self findTrackedQuery:query]; + if (!trackedQuery) { + // We might have removed a query and pruned it before we got the + // complete message from the server... + FFWarn(@"I-RDB081002", + @"Trying to set a query complete that is not tracked!"); + } else if (!trackedQuery.isComplete) { + trackedQuery = [trackedQuery setComplete]; + [self.storageEngine saveTrackedQuery:trackedQuery]; + [self cacheTrackedQuery:trackedQuery]; + } else { + // Nothing to do, already marked complete + } +} + +- (void)setQueriesCompleteAtPath:(FPath *)path { + [[self.trackedQueryTree subtreeAtPath:path] + forEach:^(FPath *childPath, NSDictionary *trackedQueries) { + [trackedQueries enumerateKeysAndObjectsUsingBlock:^( + FQueryParams *parms, FTrackedQuery *trackedQuery, + BOOL *stop) { + if (!trackedQuery.isComplete) { + FTrackedQuery *newTrackedQuery = [trackedQuery setComplete]; + [self.storageEngine saveTrackedQuery:newTrackedQuery]; + [self cacheTrackedQuery:newTrackedQuery]; + } + }]; + }]; +} + +- (BOOL)isQueryComplete:(FQuerySpec *)query { + if ([self isIncludedInDefaultCompleteQuery:query]) { + return YES; + } else if (query.loadsAllData) { + // We didn't find a default complete query, so must not be complete. + return NO; + } else { + NSDictionary *trackedQueries = + [self.trackedQueryTree valueAtPath:query.path]; + return [trackedQueries[query.params] isComplete]; + } +} + +- (BOOL)hasActiveDefaultQueryAtPath:(FPath *)path { + return [self.trackedQueryTree + rootMostValueOnPath:path + matching:^BOOL(NSDictionary *trackedQueries) { + return + [trackedQueries[[FQueryParams defaultInstance]] + isActive]; + }] != nil; +} + +- (void)ensureCompleteTrackedQueryAtPath:(FPath *)path { + FQuerySpec *query = [FQuerySpec defaultQueryAtPath:path]; + if (![self isIncludedInDefaultCompleteQuery:query]) { + FTrackedQuery *trackedQuery = [self findTrackedQuery:query]; + if (trackedQuery == nil) { + trackedQuery = + [[FTrackedQuery alloc] initWithId:self.currentQueryId++ + query:query + lastUse:[self.clock currentTime] + isActive:NO + isComplete:YES]; + } else { + NSAssert(!trackedQuery.isComplete, + @"This should have been handled above!"); + trackedQuery = [trackedQuery setComplete]; + } + [self.storageEngine saveTrackedQuery:trackedQuery]; + [self cacheTrackedQuery:trackedQuery]; + } +} + +- (BOOL)isIncludedInDefaultCompleteQuery:(FQuerySpec *)query { + return + [self.trackedQueryTree + findRootMostMatchingPath:query.path + predicate:^BOOL(NSDictionary *trackedQueries) { + return + [trackedQueries[[FQueryParams defaultInstance]] + isComplete]; + }] != nil; +} + +- (void)cacheTrackedQuery:(FTrackedQuery *)query { + [FTrackedQueryManager assertValidTrackedQuery:query.query]; + NSMutableDictionary *trackedDict = + [self.trackedQueryTree valueAtPath:query.query.path]; + if (trackedDict == nil) { + trackedDict = [NSMutableDictionary dictionary]; + self.trackedQueryTree = + [self.trackedQueryTree setValue:trackedDict + atPath:query.query.path]; + } + trackedDict[query.query.params] = query; +} + +- (NSUInteger)numberOfQueriesToPrune:(id)cachePolicy + prunableCount:(NSUInteger)numPrunable { + NSUInteger numPercent = (NSUInteger)ceilf( + numPrunable * [cachePolicy percentOfQueriesToPruneAtOnce]); + NSUInteger maxToKeep = [cachePolicy maxNumberOfQueriesToKeep]; + NSUInteger numMax = (numPrunable > maxToKeep) ? numPrunable - maxToKeep : 0; + // Make sure we get below number of max queries to prune + return MAX(numMax, numPercent); +} + +- (FPruneForest *)pruneOldQueries:(id)cachePolicy { + NSMutableArray *pruneableQueries = [NSMutableArray array]; + NSMutableArray *unpruneableQueries = [NSMutableArray array]; + [self.trackedQueryTree + forEach:^(FPath *path, NSDictionary *trackedQueries) { + [trackedQueries enumerateKeysAndObjectsUsingBlock:^( + FQueryParams *params, FTrackedQuery *trackedQuery, + BOOL *stop) { + if (!trackedQuery.isActive) { + [pruneableQueries addObject:trackedQuery]; + } else { + [unpruneableQueries addObject:trackedQuery]; + } + }]; + }]; + [pruneableQueries sortUsingComparator:^NSComparisonResult( + FTrackedQuery *q1, FTrackedQuery *q2) { + if (q1.lastUse < q2.lastUse) { + return NSOrderedAscending; + } else if (q1.lastUse > q2.lastUse) { + return NSOrderedDescending; + } else { + return NSOrderedSame; + } + }]; + + __block FPruneForest *pruneForest = [FPruneForest empty]; + NSUInteger numToPrune = + [self numberOfQueriesToPrune:cachePolicy + prunableCount:pruneableQueries.count]; + + // TODO: do in transaction + for (NSUInteger i = 0; i < numToPrune; i++) { + FTrackedQuery *toPrune = pruneableQueries[i]; + pruneForest = [pruneForest prunePath:toPrune.query.path]; + [self removeTrackedQuery:toPrune.query]; + } + + // Keep the rest of the prunable queries + for (NSUInteger i = numToPrune; i < pruneableQueries.count; i++) { + FTrackedQuery *toKeep = pruneableQueries[i]; + pruneForest = [pruneForest keepPath:toKeep.query.path]; + } + + // Also keep unprunable queries + [unpruneableQueries enumerateObjectsUsingBlock:^( + FTrackedQuery *toKeep, NSUInteger idx, BOOL *stop) { + pruneForest = [pruneForest keepPath:toKeep.query.path]; + }]; + + return pruneForest; +} + +- (NSUInteger)numberOfPrunableQueries { + __block NSUInteger count = 0; + [self.trackedQueryTree + forEach:^(FPath *path, NSDictionary *trackedQueries) { + [trackedQueries enumerateKeysAndObjectsUsingBlock:^( + FQueryParams *params, FTrackedQuery *trackedQuery, + BOOL *stop) { + if (!trackedQuery.isActive) { + count++; + } + }]; + }]; + return count; +} + +- (NSSet *)filteredQueryIdsAtPath:(FPath *)path { + NSDictionary *queries = [self.trackedQueryTree valueAtPath:path]; + if (queries) { + NSMutableSet *ids = [NSMutableSet set]; + [queries enumerateKeysAndObjectsUsingBlock:^( + FQueryParams *params, FTrackedQuery *query, BOOL *stop) { + if (!query.query.loadsAllData) { + [ids addObject:@(query.queryId)]; + } + }]; + return ids; + } else { + return [NSSet set]; + } +} + +- (NSSet *)knownCompleteChildrenAtPath:(FPath *)path { + NSAssert(![self isQueryComplete:[FQuerySpec defaultQueryAtPath:path]], + @"Path is fully complete"); + + NSMutableSet *completeChildren = [NSMutableSet set]; + // First, get complete children from any queries at this location. + NSSet *queryIds = [self filteredQueryIdsAtPath:path]; + [queryIds enumerateObjectsUsingBlock:^(NSNumber *queryId, BOOL *stop) { + NSSet *keys = [self.storageEngine + trackedQueryKeysForQuery:[queryId unsignedIntegerValue]]; + [completeChildren unionSet:keys]; + }]; + + // Second, get any complete default queries immediately below us. + [[[self.trackedQueryTree subtreeAtPath:path] children] + enumerateKeysAndObjectsUsingBlock:^( + NSString *childKey, FImmutableTree *childTree, BOOL *stop) { + if ([childTree.value[[FQueryParams defaultInstance]] isComplete]) { + [completeChildren addObject:childKey]; + } + }]; + + return completeChildren; +} + +- (void)verifyCache { + NSArray *storedTrackedQueries = [self.storageEngine loadTrackedQueries]; + NSMutableArray *trackedQueries = [NSMutableArray array]; + + [self.trackedQueryTree forEach:^(FPath *path, NSDictionary *queryDict) { + [trackedQueries addObjectsFromArray:queryDict.allValues]; + }]; + NSComparator comparator = + ^NSComparisonResult(FTrackedQuery *q1, FTrackedQuery *q2) { + if (q1.queryId < q2.queryId) { + return NSOrderedAscending; + } else if (q1.queryId > q2.queryId) { + return NSOrderedDescending; + } else { + return NSOrderedSame; + } + }; + [trackedQueries sortUsingComparator:comparator]; + storedTrackedQueries = + [storedTrackedQueries sortedArrayUsingComparator:comparator]; + + if (![trackedQueries isEqualToArray:storedTrackedQueries]) { + [NSException + raise:NSInternalInconsistencyException + format:@"Tracked queries and queries stored on disk don't match"]; + } +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Public/FIRDataEventType.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Public/FIRDataEventType.h new file mode 100644 index 0000000..3aecd81 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Public/FIRDataEventType.h @@ -0,0 +1,39 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef Firebase_FIRDataEventType_h +#define Firebase_FIRDataEventType_h + +#import + +/** + * This enum is the set of events that you can observe at a Firebase Database + * location. + */ +typedef NS_ENUM(NSInteger, FIRDataEventType) { + /// A new child node is added to a location. + FIRDataEventTypeChildAdded, + /// A child node is removed from a location. + FIRDataEventTypeChildRemoved, + /// A child node at a location changes. + FIRDataEventTypeChildChanged, + /// A child node moves relative to the other child nodes at a location. + FIRDataEventTypeChildMoved, + /// Any data changes at a location or, recursively, at any child node. + FIRDataEventTypeValue +} NS_SWIFT_NAME(DataEventType); + +#endif diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Public/FIRDataSnapshot.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Public/FIRDataSnapshot.h new file mode 100644 index 0000000..e88febb --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Public/FIRDataSnapshot.h @@ -0,0 +1,142 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class FIRDatabaseReference; + +/** + * A FIRDataSnapshot contains data from a Firebase Database location. Any time + * you read Firebase data, you receive the data as a FIRDataSnapshot. + * + * FIRDataSnapshots are passed to the blocks you attach with + * observeEventType:withBlock: or observeSingleEvent:withBlock:. They are + * efficiently-generated immutable copies of the data at a Firebase Database + * location. They can't be modified and will never change. To modify data at a + * location, use a FIRDatabaseReference (e.g. with setValue:). + */ +NS_SWIFT_NAME(DataSnapshot) +@interface FIRDataSnapshot : NSObject + +#pragma mark - Navigating and inspecting a snapshot + +/** + * Gets a FIRDataSnapshot for the location at the specified relative path. + * The relative path can either be a simple child key (e.g. 'fred') + * or a deeper slash-separated path (e.g. 'fred/name/first'). If the child + * location has no data, an empty FIRDataSnapshot is returned. + * + * @param childPathString A relative path to the location of child data. + * @return The FIRDataSnapshot for the child location. + */ +- (FIRDataSnapshot *)childSnapshotForPath:(NSString *)childPathString; + +/** + * Return YES if the specified child exists. + * + * @param childPathString A relative path to the location of a potential child. + * @return YES if data exists at the specified childPathString, else NO. + */ +- (BOOL)hasChild:(NSString *)childPathString; + +/** + * Return YES if the DataSnapshot has any children. + * + * @return YES if this snapshot has any children, else NO. + */ +- (BOOL)hasChildren; + +/** + * Return YES if the DataSnapshot contains a non-null value. + * + * @return YES if this snapshot contains a non-null value, else NO. + */ +- (BOOL)exists; + +#pragma mark - Data export + +/** + * Returns the raw value at this location, coupled with any metadata, such as + * priority. + * + * Priorities, where they exist, are accessible under the ".priority" key in + * instances of NSDictionary. For leaf locations with priorities, the value will + * be under the ".value" key. + */ +- (id __nullable)valueInExportFormat; + +#pragma mark - Properties + +/** + * Returns the contents of this data snapshot as native types. + * + * Data types returned: + * + NSDictionary + * + NSArray + * + NSNumber (also includes booleans) + * + NSString + * + * @return The data as a native object. + */ +@property(strong, readonly, nonatomic, nullable) id value; + +/** + * Gets the number of children for this DataSnapshot. + * + * @return An integer indicating the number of children. + */ +@property(readonly, nonatomic) NSUInteger childrenCount; + +/** + * Gets a FIRDatabaseReference for the location that this data came from. + * + * @return A FIRDatabaseReference instance for the location of this data. + */ +@property(nonatomic, readonly, strong) FIRDatabaseReference *ref; + +/** + * The key of the location that generated this FIRDataSnapshot. + * + * @return An NSString containing the key for the location of this + * FIRDataSnapshot. + */ +@property(strong, readonly, nonatomic) NSString *key; + +/** + * An iterator for snapshots of the child nodes in this snapshot. + * You can use the native for..in syntax: + * + * for (FIRDataSnapshot* child in snapshot.children) { + * ... + * } + * + * @return An NSEnumerator of the children. + */ +@property(strong, readonly, nonatomic) + NSEnumerator *children; + +/** + * The priority of the data in this FIRDataSnapshot. + * + * @return The priority as a string, or nil if no priority was set. + */ +@property(strong, readonly, nonatomic, nullable) id priority; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Public/FIRDatabase.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Public/FIRDatabase.h new file mode 100644 index 0000000..02af8c7 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Public/FIRDatabase.h @@ -0,0 +1,182 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRDatabaseReference.h" +#import + +@class FIRApp; + +NS_ASSUME_NONNULL_BEGIN + +/** + * The entry point for accessing a Firebase Database. You can get an instance + * by calling [FIRDatabase database]. To access a location in the database and + * read or write data, use [FIRDatabase reference]. + */ +NS_SWIFT_NAME(Database) +@interface FIRDatabase : NSObject + +/** + * The NSObject initializer that has been marked as unavailable. Use the + * `database` method instead + * + * @return An instancetype instance + */ +- (instancetype)init + __attribute__((unavailable("use the database method instead"))); + +/** + * Gets the instance of FIRDatabase for the default FIRApp. + * + * @return A FIRDatabase instance. + */ ++ (FIRDatabase *)database NS_SWIFT_NAME(database()); + +/** + * Gets a FirebaseDatabase instance for the specified URL. + * + * @param url The URL to the Firebase Database instance you want to access. + * @return A FIRDatabase instance. + */ ++ (FIRDatabase *)databaseWithURL:(NSString *)url NS_SWIFT_NAME(database(url:)); + +/** + * Gets a FirebaseDatabase instance for the specified URL, using the specified + * FirebaseApp. + * + * @param app The FIRApp to get a FIRDatabase for. + * @param url The URL to the Firebase Database instance you want to access. + * @return A FIRDatabase instance. + */ +// clang-format off ++ (FIRDatabase *)databaseForApp:(FIRApp *)app + URL:(NSString *)url NS_SWIFT_NAME(database(app:url:)); +// clang-format on + +/** + * Gets an instance of FIRDatabase for a specific FIRApp. + * + * @param app The FIRApp to get a FIRDatabase for. + * @return A FIRDatabase instance. + */ ++ (FIRDatabase *)databaseForApp:(FIRApp *)app NS_SWIFT_NAME(database(app:)); + +/** The FIRApp instance to which this FIRDatabase belongs. */ +@property(weak, readonly, nonatomic) FIRApp *app; + +/** + * Gets a FIRDatabaseReference for the root of your Firebase Database. + */ +- (FIRDatabaseReference *)reference; + +/** + * Gets a FIRDatabaseReference for the provided path. + * + * @param path Path to a location in your Firebase Database. + * @return A FIRDatabaseReference pointing to the specified path. + */ +- (FIRDatabaseReference *)referenceWithPath:(NSString *)path; + +/** + * Gets a FIRDatabaseReference for the provided URL. The URL must be a URL to a + * path within this Firebase Database. To create a FIRDatabaseReference to a + * different database, create a FIRApp} with a FIROptions object configured with + * the appropriate database URL. + * + * @param databaseUrl A URL to a path within your database. + * @return A FIRDatabaseReference for the provided URL. + */ +- (FIRDatabaseReference *)referenceFromURL:(NSString *)databaseUrl; + +/** + * The Firebase Database client automatically queues writes and sends them to + * the server at the earliest opportunity, depending on network connectivity. In + * some cases (e.g. offline usage) there may be a large number of writes waiting + * to be sent. Calling this method will purge all outstanding writes so they are + * abandoned. + * + * All writes will be purged, including transactions and onDisconnect writes. + * The writes will be rolled back locally, perhaps triggering events for + * affected event listeners, and the client will not (re-)send them to the + * Firebase Database backend. + */ +- (void)purgeOutstandingWrites; + +/** + * Shuts down our connection to the Firebase Database backend until goOnline is + * called. + */ +- (void)goOffline; + +/** + * Resumes our connection to the Firebase Database backend after a previous + * goOffline call. + */ +- (void)goOnline; + +/** + * The Firebase Database client will cache synchronized data and keep track of + * all writes you've initiated while your application is running. It seamlessly + * handles intermittent network connections and re-sends write operations when + * the network connection is restored. + * + * However by default your write operations and cached data are only stored + * in-memory and will be lost when your app restarts. By setting this value to + * `YES`, the data will be persisted to on-device (disk) storage and will thus + * be available again when the app is restarted (even when there is no network + * connectivity at that time). Note that this property must be set before + * creating your first Database reference and only needs to be called once per + * application. + * + */ +@property(nonatomic) BOOL persistenceEnabled NS_SWIFT_NAME(isPersistenceEnabled) + ; + +/** + * By default the Firebase Database client will use up to 10MB of disk space to + * cache data. If the cache grows beyond this size, the client will start + * removing data that hasn't been recently used. If you find that your + * application caches too little or too much data, call this method to change + * the cache size. This property must be set before creating your first + * FIRDatabaseReference and only needs to be called once per application. + * + * Note that the specified cache size is only an approximation and the size on + * disk may temporarily exceed it at times. Cache sizes smaller than 1 MB or + * greater than 100 MB are not supported. + */ +@property(nonatomic) NSUInteger persistenceCacheSizeBytes; + +/** + * Sets the dispatch queue on which all events are raised. The default queue is + * the main queue. + * + * Note that this must be set before creating your first Database reference. + */ +@property(nonatomic, strong) dispatch_queue_t callbackQueue; + +/** + * Enables verbose diagnostic logging. + * + * @param enabled YES to enable logging, NO to disable. + */ ++ (void)setLoggingEnabled:(BOOL)enabled; + +/** Retrieve the Firebase Database SDK version. */ ++ (NSString *)sdkVersion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Public/FIRDatabaseQuery.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Public/FIRDatabaseQuery.h new file mode 100644 index 0000000..82057aa --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Public/FIRDatabaseQuery.h @@ -0,0 +1,395 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRDataEventType.h" +#import "FIRDataSnapshot.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * A FIRDatabaseHandle is used to identify listeners of Firebase Database + * events. These handles are returned by observeEventType: and can later be + * passed to removeObserverWithHandle: to stop receiving updates. + */ +typedef NSUInteger FIRDatabaseHandle NS_SWIFT_NAME(DatabaseHandle); + +/** + * A FIRDatabaseQuery instance represents a query over the data at a particular + * location. + * + * You create one by calling one of the query methods (queryOrderedByChild:, + * queryStartingAtValue:, etc.) on a FIRDatabaseReference. The query methods can + * be chained to further specify the data you are interested in observing + */ +NS_SWIFT_NAME(DatabaseQuery) +@interface FIRDatabaseQuery : NSObject + +#pragma mark - Attach observers to read data + +/** + * observeEventType:withBlock: is used to listen for data changes at a + * particular location. This is the primary way to read data from the Firebase + * Database. Your block will be triggered for the initial data and again + * whenever the data changes. + * + * Use removeObserverWithHandle: to stop receiving updates. + * + * @param eventType The type of event to listen for. + * @param block The block that should be called with initial data and updates. + * It is passed the data as a FIRDataSnapshot. + * @return A handle used to unregister this block later using + * removeObserverWithHandle: + */ +- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType + withBlock: + (void (^)(FIRDataSnapshot *snapshot))block; + +/** + * observeEventType:andPreviousSiblingKeyWithBlock: is used to listen for data + * changes at a particular location. This is the primary way to read data from + * the Firebase Database. Your block will be triggered for the initial data and + * again whenever the data changes. In addition, for FIRDataEventTypeChildAdded, + * FIRDataEventTypeChildMoved, and FIRDataEventTypeChildChanged events, your + * block will be passed the key of the previous node by priority order. + * + * Use removeObserverWithHandle: to stop receiving updates. + * + * @param eventType The type of event to listen for. + * @param block The block that should be called with initial data and updates. + * It is passed the data as a FIRDataSnapshot and the previous child's key. + * @return A handle used to unregister this block later using + * removeObserverWithHandle: + */ +- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType + andPreviousSiblingKeyWithBlock: + (void (^)(FIRDataSnapshot *snapshot, + NSString *__nullable prevKey))block; + +/** + * observeEventType:withBlock: is used to listen for data changes at a + * particular location. This is the primary way to read data from the Firebase + * Database. Your block will be triggered for the initial data and again + * whenever the data changes. + * + * The cancelBlock will be called if you will no longer receive new events due + * to no longer having permission. + * + * Use removeObserverWithHandle: to stop receiving updates. + * + * @param eventType The type of event to listen for. + * @param block The block that should be called with initial data and updates. + * It is passed the data as a FIRDataSnapshot. + * @param cancelBlock The block that should be called if this client no longer + * has permission to receive these events + * @return A handle used to unregister this block later using + * removeObserverWithHandle: + */ +- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType + withBlock:(void (^)(FIRDataSnapshot *snapshot))block + withCancelBlock: + (nullable void (^)(NSError *error))cancelBlock; + +/** + * observeEventType:andPreviousSiblingKeyWithBlock: is used to listen for data + * changes at a particular location. This is the primary way to read data from + * the Firebase Database. Your block will be triggered for the initial data and + * again whenever the data changes. In addition, for FIRDataEventTypeChildAdded, + * FIRDataEventTypeChildMoved, and FIRDataEventTypeChildChanged events, your + * block will be passed the key of the previous node by priority order. + * + * The cancelBlock will be called if you will no longer receive new events due + * to no longer having permission. + * + * Use removeObserverWithHandle: to stop receiving updates. + * + * @param eventType The type of event to listen for. + * @param block The block that should be called with initial data and updates. + * It is passed the data as a FIRDataSnapshot and the previous child's key. + * @param cancelBlock The block that should be called if this client no longer + * has permission to receive these events + * @return A handle used to unregister this block later using + * removeObserverWithHandle: + */ +- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType + andPreviousSiblingKeyWithBlock: + (void (^)(FIRDataSnapshot *snapshot, + NSString *__nullable prevKey))block + withCancelBlock: + (nullable void (^)(NSError *error))cancelBlock; + +/** + * This is equivalent to observeEventType:withBlock:, except the block is + * immediately canceled after the initial data is returned. + * + * @param eventType The type of event to listen for. + * @param block The block that should be called. It is passed the data as a + * FIRDataSnapshot. + */ +- (void)observeSingleEventOfType:(FIRDataEventType)eventType + withBlock:(void (^)(FIRDataSnapshot *snapshot))block; + +/** + * This is equivalent to observeEventType:withBlock:, except the block is + * immediately canceled after the initial data is returned. In addition, for + * FIRDataEventTypeChildAdded, FIRDataEventTypeChildMoved, and + * FIRDataEventTypeChildChanged events, your block will be passed the key of the + * previous node by priority order. + * + * @param eventType The type of event to listen for. + * @param block The block that should be called. It is passed the data as a + * FIRDataSnapshot and the previous child's key. + */ +- (void)observeSingleEventOfType:(FIRDataEventType)eventType + andPreviousSiblingKeyWithBlock: + (void (^)(FIRDataSnapshot *snapshot, + NSString *__nullable prevKey))block; + +/** + * This is equivalent to observeEventType:withBlock:, except the block is + * immediately canceled after the initial data is returned. + * + * The cancelBlock will be called if you do not have permission to read data at + * this location. + * + * @param eventType The type of event to listen for. + * @param block The block that should be called. It is passed the data as a + * FIRDataSnapshot. + * @param cancelBlock The block that will be called if you don't have permission + * to access this data + */ +- (void)observeSingleEventOfType:(FIRDataEventType)eventType + withBlock:(void (^)(FIRDataSnapshot *snapshot))block + withCancelBlock:(nullable void (^)(NSError *error))cancelBlock; + +/** + * This is equivalent to observeEventType:withBlock:, except the block is + * immediately canceled after the initial data is returned. In addition, for + * FIRDataEventTypeChildAdded, FIRDataEventTypeChildMoved, and + * FIRDataEventTypeChildChanged events, your block will be passed the key of the + * previous node by priority order. + * + * The cancelBlock will be called if you do not have permission to read data at + * this location. + * + * @param eventType The type of event to listen for. + * @param block The block that should be called. It is passed the data as a + * FIRDataSnapshot and the previous child's key. + * @param cancelBlock The block that will be called if you don't have permission + * to access this data + */ +- (void)observeSingleEventOfType:(FIRDataEventType)eventType + andPreviousSiblingKeyWithBlock:(void (^)(FIRDataSnapshot *snapshot, + NSString *__nullable prevKey))block + withCancelBlock: + (nullable void (^)(NSError *error))cancelBlock; + +#pragma mark - Detaching observers + +/** + * Detach a block previously attached with observeEventType:withBlock:. + * + * @param handle The handle returned by the call to observeEventType:withBlock: + * which we are trying to remove. + */ +- (void)removeObserverWithHandle:(FIRDatabaseHandle)handle; + +/** + * Detach all blocks previously attached to this Firebase Database location with + * observeEventType:withBlock: + */ +- (void)removeAllObservers; + +/** + * By calling `keepSynced:YES` on a location, the data for that location will + * automatically be downloaded and kept in sync, even when no listeners are + * attached for that location. Additionally, while a location is kept synced, it + * will not be evicted from the persistent disk cache. + * + * @param keepSynced Pass YES to keep this location synchronized, pass NO to + * stop synchronization. + */ +- (void)keepSynced:(BOOL)keepSynced; + +#pragma mark - Querying and limiting + +/** + * queryLimitedToFirst: is used to generate a reference to a limited view of the + * data at this location. The FIRDatabaseQuery instance returned by + * queryLimitedToFirst: will respond to at most the first limit child nodes. + * + * @param limit The upper bound, inclusive, for the number of child nodes to + * receive events for + * @return A FIRDatabaseQuery instance, limited to at most limit child nodes. + */ +- (FIRDatabaseQuery *)queryLimitedToFirst:(NSUInteger)limit; + +/** + * queryLimitedToLast: is used to generate a reference to a limited view of the + * data at this location. The FIRDatabaseQuery instance returned by + * queryLimitedToLast: will respond to at most the last limit child nodes. + * + * @param limit The upper bound, inclusive, for the number of child nodes to + * receive events for + * @return A FIRDatabaseQuery instance, limited to at most limit child nodes. + */ +- (FIRDatabaseQuery *)queryLimitedToLast:(NSUInteger)limit; + +/** + * queryOrderBy: is used to generate a reference to a view of the data that's + * been sorted by the values of a particular child key. This method is intended + * to be used in combination with queryStartingAtValue:, queryEndingAtValue:, or + * queryEqualToValue:. + * + * @param key The child key to use in ordering data visible to the returned + * FIRDatabaseQuery + * @return A FIRDatabaseQuery instance, ordered by the values of the specified + * child key. + */ +- (FIRDatabaseQuery *)queryOrderedByChild:(NSString *)key; + +/** + * queryOrderedByKey: is used to generate a reference to a view of the data + * that's been sorted by child key. This method is intended to be used in + * combination with queryStartingAtValue:, queryEndingAtValue:, or + * queryEqualToValue:. + * + * @return A FIRDatabaseQuery instance, ordered by child keys. + */ +- (FIRDatabaseQuery *)queryOrderedByKey; + +/** + * queryOrderedByValue: is used to generate a reference to a view of the data + * that's been sorted by child value. This method is intended to be used in + * combination with queryStartingAtValue:, queryEndingAtValue:, or + * queryEqualToValue:. + * + * @return A FIRDatabaseQuery instance, ordered by child value. + */ +- (FIRDatabaseQuery *)queryOrderedByValue; + +/** + * queryOrderedByPriority: is used to generate a reference to a view of the data + * that's been sorted by child priority. This method is intended to be used in + * combination with queryStartingAtValue:, queryEndingAtValue:, or + * queryEqualToValue:. + * + * @return A FIRDatabaseQuery instance, ordered by child priorities. + */ +- (FIRDatabaseQuery *)queryOrderedByPriority; + +/** + * queryStartingAtValue: is used to generate a reference to a limited view of + * the data at this location. The FIRDatabaseQuery instance returned by + * queryStartingAtValue: will respond to events at nodes with a value greater + * than or equal to startValue. + * + * @param startValue The lower bound, inclusive, for the value of data visible + * to the returned FIRDatabaseQuery + * @return A FIRDatabaseQuery instance, limited to data with value greater than + * or equal to startValue + */ +- (FIRDatabaseQuery *)queryStartingAtValue:(nullable id)startValue; + +/** + * queryStartingAtValue:childKey: is used to generate a reference to a limited + * view of the data at this location. The FIRDatabaseQuery instance returned by + * queryStartingAtValue:childKey will respond to events at nodes with a value + * greater than startValue, or equal to startValue and with a key greater than + * or equal to childKey. This is most useful when implementing pagination in a + * case where multiple nodes can match the startValue. + * + * @param startValue The lower bound, inclusive, for the value of data visible + * to the returned FIRDatabaseQuery + * @param childKey The lower bound, inclusive, for the key of nodes with value + * equal to startValue + * @return A FIRDatabaseQuery instance, limited to data with value greater than + * or equal to startValue + */ +- (FIRDatabaseQuery *)queryStartingAtValue:(nullable id)startValue + childKey:(nullable NSString *)childKey; + +/** + * queryEndingAtValue: is used to generate a reference to a limited view of the + * data at this location. The FIRDatabaseQuery instance returned by + * queryEndingAtValue: will respond to events at nodes with a value less than or + * equal to endValue. + * + * @param endValue The upper bound, inclusive, for the value of data visible to + * the returned FIRDatabaseQuery + * @return A FIRDatabaseQuery instance, limited to data with value less than or + * equal to endValue + */ +- (FIRDatabaseQuery *)queryEndingAtValue:(nullable id)endValue; + +/** + * queryEndingAtValue:childKey: is used to generate a reference to a limited + * view of the data at this location. The FIRDatabaseQuery instance returned by + * queryEndingAtValue:childKey will respond to events at nodes with a value less + * than endValue, or equal to endValue and with a key less than or equal to + * childKey. This is most useful when implementing pagination in a case where + * multiple nodes can match the endValue. + * + * @param endValue The upper bound, inclusive, for the value of data visible to + * the returned FIRDatabaseQuery + * @param childKey The upper bound, inclusive, for the key of nodes with value + * equal to endValue + * @return A FIRDatabaseQuery instance, limited to data with value less than or + * equal to endValue + */ +- (FIRDatabaseQuery *)queryEndingAtValue:(nullable id)endValue + childKey:(nullable NSString *)childKey; + +/** + * queryEqualToValue: is used to generate a reference to a limited view of the + * data at this location. The FIRDatabaseQuery instance returned by + * queryEqualToValue: will respond to events at nodes with a value equal to the + * supplied argument. + * + * @param value The value that the data returned by this FIRDatabaseQuery will + * have + * @return A FIRDatabaseQuery instance, limited to data with the supplied value. + */ +- (FIRDatabaseQuery *)queryEqualToValue:(nullable id)value; + +/** + * queryEqualToValue:childKey: is used to generate a reference to a limited view + * of the data at this location. The FIRDatabaseQuery instance returned by + * queryEqualToValue:childKey will respond to events at nodes with a value equal + * to the supplied argument and with their key equal to childKey. There will be + * at most one node that matches because child keys are unique. + * + * @param value The value that the data returned by this FIRDatabaseQuery will + * have + * @param childKey The name of nodes with the right value + * @return A FIRDatabaseQuery instance, limited to data with the supplied value + * and the key. + */ +- (FIRDatabaseQuery *)queryEqualToValue:(nullable id)value + childKey:(nullable NSString *)childKey; + +#pragma mark - Properties + +/** + * Gets a FIRDatabaseReference for the location of this query. + * + * @return A FIRDatabaseReference for the location of this query. + */ +@property(nonatomic, readonly, strong) FIRDatabaseReference *ref; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Public/FIRDatabaseReference.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Public/FIRDatabaseReference.h new file mode 100644 index 0000000..c80b2f8 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Public/FIRDatabaseReference.h @@ -0,0 +1,861 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRDataSnapshot.h" +#import "FIRDatabase.h" +#import "FIRDatabaseQuery.h" +#import "FIRMutableData.h" +#import "FIRServerValue.h" +#import "FIRTransactionResult.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@class FIRDatabase; + +/** + * A FIRDatabaseReference represents a particular location in your Firebase + * Database and can be used for reading or writing data to that Firebase + * Database location. + * + * This class is the starting point for all Firebase Database operations. After + * you've obtained your first FIRDatabaseReference via [FIRDatabase reference], + * you can use it to read data (ie. observeEventType:withBlock:), write data + * (ie. setValue:), and to create new FIRDatabaseReferences (ie. child:). + */ +NS_SWIFT_NAME(DatabaseReference) +@interface FIRDatabaseReference : FIRDatabaseQuery + +#pragma mark - Getting references to children locations + +/** + * Gets a FIRDatabaseReference for the location at the specified relative path. + * The relative path can either be a simple child key (e.g. 'fred') or a + * deeper slash-separated path (e.g. 'fred/name/first'). + * + * @param pathString A relative path from this location to the desired child + * location. + * @return A FIRDatabaseReference for the specified relative path. + */ +- (FIRDatabaseReference *)child:(NSString *)pathString; + +/** + * childByAutoId generates a new child location using a unique key and returns a + * FIRDatabaseReference to it. This is useful when the children of a Firebase + * Database location represent a list of items. + * + * The unique key generated by childByAutoId: is prefixed with a + * client-generated timestamp so that the resulting list will be + * chronologically-sorted. + * + * @return A FIRDatabaseReference for the generated location. + */ +- (FIRDatabaseReference *)childByAutoId; + +#pragma mark - Writing data + +/** Write data to this Firebase Database location. + +This will overwrite any data at this location and all child locations. + +Data types that can be set are: + +- NSString -- @"Hello World" +- NSNumber (also includes boolean) -- @YES, @43, @4.333 +- NSDictionary -- @{@"key": @"value", @"nested": @{@"another": @"value"} } +- NSArray + +The effect of the write will be visible immediately and the corresponding +events will be triggered. Synchronization of the data to the Firebase Database +servers will also be started. + +Passing null for the new value is equivalent to calling remove:; +all data at this location or any child location will be deleted. + +Note that setValue: will remove any priority stored at this location, so if +priority is meant to be preserved, you should use setValue:andPriority: instead. + +@param value The value to be written. + */ +- (void)setValue:(nullable id)value; + +/** + * The same as setValue: with a block that gets triggered after the write + * operation has been committed to the Firebase Database servers. + * + * @param value The value to be written. + * @param block The block to be called after the write has been committed to the + * Firebase Database servers. + */ +- (void)setValue:(nullable id)value + withCompletionBlock: + (void (^)(NSError *__nullable error, FIRDatabaseReference *ref))block; + +/** + * The same as setValue: with an additional priority to be attached to the data + * being written. Priorities are used to order items. + * + * @param value The value to be written. + * @param priority The priority to be attached to that data. + */ +- (void)setValue:(nullable id)value andPriority:(nullable id)priority; + +/** + * The same as setValue:andPriority: with a block that gets triggered after the + * write operation has been committed to the Firebase Database servers. + * + * @param value The value to be written. + * @param priority The priority to be attached to that data. + * @param block The block to be called after the write has been committed to the + * Firebase Database servers. + */ +- (void)setValue:(nullable id)value + andPriority:(nullable id)priority + withCompletionBlock: + (void (^)(NSError *__nullable error, FIRDatabaseReference *ref))block; + +/** + * Remove the data at this Firebase Database location. Any data at child + * locations will also be deleted. + * + * The effect of the delete will be visible immediately and the corresponding + * events will be triggered. Synchronization of the delete to the Firebase + * Database servers will also be started. + * + * remove: is equivalent to calling setValue:nil + */ +- (void)removeValue; + +/** + * The same as remove: with a block that gets triggered after the remove + * operation has been committed to the Firebase Database servers. + * + * @param block The block to be called after the remove has been committed to + * the Firebase Database servers. + */ +- (void)removeValueWithCompletionBlock: + (void (^)(NSError *__nullable error, FIRDatabaseReference *ref))block; + +/** + * Sets a priority for the data at this Firebase Database location. + * Priorities can be used to provide a custom ordering for the children at a + * location (if no priorities are specified, the children are ordered by key). + * + * You cannot set a priority on an empty location. For this reason + * setValue:andPriority: should be used when setting initial data with a + * specific priority and setPriority: should be used when updating the priority + * of existing data. + * + * Children are sorted based on this priority using the following rules: + * + * Children with no priority come first. + * Children with a number as their priority come next. They are sorted + * numerically by priority (small to large). Children with a string as their + * priority come last. They are sorted lexicographically by priority. Whenever + * two children have the same priority (including no priority), they are sorted + * by key. Numeric keys come first (sorted numerically), followed by the + * remaining keys (sorted lexicographically). + * + * Note that priorities are parsed and ordered as IEEE 754 double-precision + * floating-point numbers. Keys are always stored as strings and are treated as + * numbers only when they can be parsed as a 32-bit integer + * + * @param priority The priority to set at the specified location. + */ +- (void)setPriority:(nullable id)priority; + +/** + * The same as setPriority: with a block that is called once the priority has + * been committed to the Firebase Database servers. + * + * @param priority The priority to set at the specified location. + * @param block The block that is triggered after the priority has been written + * on the servers. + */ +- (void)setPriority:(nullable id)priority + withCompletionBlock: + (void (^)(NSError *__nullable error, FIRDatabaseReference *ref))block; + +/** + * Updates the values at the specified paths in the dictionary without + * overwriting other keys at this location. + * + * @param values A dictionary of the keys to change and their new values + */ +- (void)updateChildValues:(NSDictionary *)values; + +/** + * The same as update: with a block that is called once the update has been + * committed to the Firebase Database servers + * + * @param values A dictionary of the keys to change and their new values + * @param block The block that is triggered after the update has been written on + * the Firebase Database servers + */ +- (void)updateChildValues:(NSDictionary *)values + withCompletionBlock: + (void (^)(NSError *__nullable error, FIRDatabaseReference *ref))block; + +#pragma mark - Attaching observers to read data + +/** + * observeEventType:withBlock: is used to listen for data changes at a + * particular location. This is the primary way to read data from the Firebase + * Database. Your block will be triggered for the initial data and again + * whenever the data changes. + * + * Use removeObserverWithHandle: to stop receiving updates. + * @param eventType The type of event to listen for. + * @param block The block that should be called with initial data and updates. + * It is passed the data as a FIRDataSnapshot. + * @return A handle used to unregister this block later using + * removeObserverWithHandle: + */ +- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType + withBlock: + (void (^)(FIRDataSnapshot *snapshot))block; + +/** + * observeEventType:andPreviousSiblingKeyWithBlock: is used to listen for data + * changes at a particular location. This is the primary way to read data from + * the Firebase Database. Your block will be triggered for the initial data and + * again whenever the data changes. In addition, for FIRDataEventTypeChildAdded, + * FIRDataEventTypeChildMoved, and FIRDataEventTypeChildChanged events, your + * block will be passed the key of the previous node by priority order. + * + * Use removeObserverWithHandle: to stop receiving updates. + * + * @param eventType The type of event to listen for. + * @param block The block that should be called with initial data and updates. + * It is passed the data as a FIRDataSnapshot and the previous child's key. + * @return A handle used to unregister this block later using + * removeObserverWithHandle: + */ +- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType + andPreviousSiblingKeyWithBlock: + (void (^)(FIRDataSnapshot *snapshot, + NSString *__nullable prevKey))block; + +/** + * observeEventType:withBlock: is used to listen for data changes at a + * particular location. This is the primary way to read data from the Firebase + * Database. Your block will be triggered for the initial data and again + * whenever the data changes. + * + * The cancelBlock will be called if you will no longer receive new events due + * to no longer having permission. + * + * Use removeObserverWithHandle: to stop receiving updates. + * + * @param eventType The type of event to listen for. + * @param block The block that should be called with initial data and updates. + * It is passed the data as a FIRDataSnapshot. + * @param cancelBlock The block that should be called if this client no longer + * has permission to receive these events + * @return A handle used to unregister this block later using + * removeObserverWithHandle: + */ +- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType + withBlock:(void (^)(FIRDataSnapshot *snapshot))block + withCancelBlock: + (nullable void (^)(NSError *error))cancelBlock; + +/** + * observeEventType:andPreviousSiblingKeyWithBlock: is used to listen for data + * changes at a particular location. This is the primary way to read data from + * the Firebase Database. Your block will be triggered for the initial data and + * again whenever the data changes. In addition, for FIRDataEventTypeChildAdded, + * FIRDataEventTypeChildMoved, and FIRDataEventTypeChildChanged events, your + * block will be passed the key of the previous node by priority order. + * + * The cancelBlock will be called if you will no longer receive new events due + * to no longer having permission. + * + * Use removeObserverWithHandle: to stop receiving updates. + * + * @param eventType The type of event to listen for. + * @param block The block that should be called with initial data and updates. + * It is passed the data as a FIRDataSnapshot and the previous child's key. + * @param cancelBlock The block that should be called if this client no longer + * has permission to receive these events + * @return A handle used to unregister this block later using + * removeObserverWithHandle: + */ +- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType + andPreviousSiblingKeyWithBlock: + (void (^)(FIRDataSnapshot *snapshot, + NSString *__nullable prevKey))block + withCancelBlock: + (nullable void (^)(NSError *error))cancelBlock; + +/** + * This is equivalent to observeEventType:withBlock:, except the block is + * immediately canceled after the initial data is returned. + * + * @param eventType The type of event to listen for. + * @param block The block that should be called. It is passed the data as a + * FIRDataSnapshot. + */ +- (void)observeSingleEventOfType:(FIRDataEventType)eventType + withBlock:(void (^)(FIRDataSnapshot *snapshot))block; + +/** + * This is equivalent to observeEventType:withBlock:, except the block is + * immediately canceled after the initial data is returned. In addition, for + * FIRDataEventTypeChildAdded, FIRDataEventTypeChildMoved, and + * FIRDataEventTypeChildChanged events, your block will be passed the key of the + * previous node by priority order. + * + * @param eventType The type of event to listen for. + * @param block The block that should be called. It is passed the data as a + * FIRDataSnapshot and the previous child's key. + */ +- (void)observeSingleEventOfType:(FIRDataEventType)eventType + andPreviousSiblingKeyWithBlock: + (void (^)(FIRDataSnapshot *snapshot, + NSString *__nullable prevKey))block; + +/** + * This is equivalent to observeEventType:withBlock:, except the block is + * immediately canceled after the initial data is returned. + * + * The cancelBlock will be called if you do not have permission to read data at + * this location. + * + * @param eventType The type of event to listen for. + * @param block The block that should be called. It is passed the data as a + * FIRDataSnapshot. + * @param cancelBlock The block that will be called if you don't have permission + * to access this data + */ +- (void)observeSingleEventOfType:(FIRDataEventType)eventType + withBlock:(void (^)(FIRDataSnapshot *snapshot))block + withCancelBlock:(nullable void (^)(NSError *error))cancelBlock; + +/** + * This is equivalent to observeEventType:withBlock:, except the block is + * immediately canceled after the initial data is returned. In addition, for + * FIRDataEventTypeChildAdded, FIRDataEventTypeChildMoved, and + * FIRDataEventTypeChildChanged events, your block will be passed the key of the + * previous node by priority order. + * + * The cancelBlock will be called if you do not have permission to read data at + * this location. + * + * @param eventType The type of event to listen for. + * @param block The block that should be called. It is passed the data as a + * FIRDataSnapshot and the previous child's key. + * @param cancelBlock The block that will be called if you don't have permission + * to access this data + */ +- (void)observeSingleEventOfType:(FIRDataEventType)eventType + andPreviousSiblingKeyWithBlock:(void (^)(FIRDataSnapshot *snapshot, + NSString *__nullable prevKey))block + withCancelBlock: + (nullable void (^)(NSError *error))cancelBlock; + +#pragma mark - Detaching observers + +/** + * Detach a block previously attached with observeEventType:withBlock:. + * + * @param handle The handle returned by the call to observeEventType:withBlock: + * which we are trying to remove. + */ +- (void)removeObserverWithHandle:(FIRDatabaseHandle)handle; + +/** + * By calling `keepSynced:YES` on a location, the data for that location will + * automatically be downloaded and kept in sync, even when no listeners are + * attached for that location. Additionally, while a location is kept synced, it + * will not be evicted from the persistent disk cache. + * + * @param keepSynced Pass YES to keep this location synchronized, pass NO to + * stop synchronization. + */ +- (void)keepSynced:(BOOL)keepSynced; + +/** + * Removes all observers at the current reference, but does not remove any + * observers at child references. removeAllObservers must be called again for + * each child reference where a listener was established to remove the + * observers. + */ +- (void)removeAllObservers; + +#pragma mark - Querying and limiting + +/** + * queryLimitedToFirst: is used to generate a reference to a limited view of the + * data at this location. The FIRDatabaseQuery instance returned by + * queryLimitedToFirst: will respond to at most the first limit child nodes. + * + * @param limit The upper bound, inclusive, for the number of child nodes to + * receive events for + * @return A FIRDatabaseQuery instance, limited to at most limit child nodes. + */ +- (FIRDatabaseQuery *)queryLimitedToFirst:(NSUInteger)limit; + +/** + * queryLimitedToLast: is used to generate a reference to a limited view of the + * data at this location. The FIRDatabaseQuery instance returned by + * queryLimitedToLast: will respond to at most the last limit child nodes. + * + * @param limit The upper bound, inclusive, for the number of child nodes to + * receive events for + * @return A FIRDatabaseQuery instance, limited to at most limit child nodes. + */ +- (FIRDatabaseQuery *)queryLimitedToLast:(NSUInteger)limit; + +/** + * queryOrderBy: is used to generate a reference to a view of the data that's + * been sorted by the values of a particular child key. This method is intended + * to be used in combination with queryStartingAtValue:, queryEndingAtValue:, or + * queryEqualToValue:. + * + * @param key The child key to use in ordering data visible to the returned + * FIRDatabaseQuery + * @return A FIRDatabaseQuery instance, ordered by the values of the specified + * child key. + */ +- (FIRDatabaseQuery *)queryOrderedByChild:(NSString *)key; + +/** + * queryOrderedByKey: is used to generate a reference to a view of the data + * that's been sorted by child key. This method is intended to be used in + * combination with queryStartingAtValue:, queryEndingAtValue:, or + * queryEqualToValue:. + * + * @return A FIRDatabaseQuery instance, ordered by child keys. + */ +- (FIRDatabaseQuery *)queryOrderedByKey; + +/** + * queryOrderedByPriority: is used to generate a reference to a view of the data + * that's been sorted by child priority. This method is intended to be used in + * combination with queryStartingAtValue:, queryEndingAtValue:, or + * queryEqualToValue:. + * + * @return A FIRDatabaseQuery instance, ordered by child priorities. + */ +- (FIRDatabaseQuery *)queryOrderedByPriority; + +/** + * queryStartingAtValue: is used to generate a reference to a limited view of + * the data at this location. The FIRDatabaseQuery instance returned by + * queryStartingAtValue: will respond to events at nodes with a value greater + * than or equal to startValue. + * + * @param startValue The lower bound, inclusive, for the value of data visible + * to the returned FIRDatabaseQuery + * @return A FIRDatabaseQuery instance, limited to data with value greater than + * or equal to startValue + */ +- (FIRDatabaseQuery *)queryStartingAtValue:(nullable id)startValue; + +/** + * queryStartingAtValue:childKey: is used to generate a reference to a limited + * view of the data at this location. The FIRDatabaseQuery instance returned by + * queryStartingAtValue:childKey will respond to events at nodes with a value + * greater than startValue, or equal to startValue and with a key greater than + * or equal to childKey. + * + * @param startValue The lower bound, inclusive, for the value of data visible + * to the returned FIRDatabaseQuery + * @param childKey The lower bound, inclusive, for the key of nodes with value + * equal to startValue + * @return A FIRDatabaseQuery instance, limited to data with value greater than + * or equal to startValue + */ +- (FIRDatabaseQuery *)queryStartingAtValue:(nullable id)startValue + childKey:(nullable NSString *)childKey; + +/** + * queryEndingAtValue: is used to generate a reference to a limited view of the + * data at this location. The FIRDatabaseQuery instance returned by + * queryEndingAtValue: will respond to events at nodes with a value less than or + * equal to endValue. + * + * @param endValue The upper bound, inclusive, for the value of data visible to + * the returned FIRDatabaseQuery + * @return A FIRDatabaseQuery instance, limited to data with value less than or + * equal to endValue + */ +- (FIRDatabaseQuery *)queryEndingAtValue:(nullable id)endValue; + +/** + * queryEndingAtValue:childKey: is used to generate a reference to a limited + * view of the data at this location. The FIRDatabaseQuery instance returned by + * queryEndingAtValue:childKey will respond to events at nodes with a value less + * than endValue, or equal to endValue and with a key less than or equal to + * childKey. + * + * @param endValue The upper bound, inclusive, for the value of data visible to + * the returned FIRDatabaseQuery + * @param childKey The upper bound, inclusive, for the key of nodes with value + * equal to endValue + * @return A FIRDatabaseQuery instance, limited to data with value less than or + * equal to endValue + */ +- (FIRDatabaseQuery *)queryEndingAtValue:(nullable id)endValue + childKey:(nullable NSString *)childKey; + +/** + * queryEqualToValue: is used to generate a reference to a limited view of the + * data at this location. The FIRDatabaseQuery instance returned by + * queryEqualToValue: will respond to events at nodes with a value equal to the + * supplied argument. + * + * @param value The value that the data returned by this FIRDatabaseQuery will + * have + * @return A FIRDatabaseQuery instance, limited to data with the supplied value. + */ +- (FIRDatabaseQuery *)queryEqualToValue:(nullable id)value; + +/** + * queryEqualToValue:childKey: is used to generate a reference to a limited view + * of the data at this location. The FIRDatabaseQuery instance returned by + * queryEqualToValue:childKey will respond to events at nodes with a value equal + * to the supplied argument with a key equal to childKey. There will be at most + * one node that matches because child keys are unique. + * + * @param value The value that the data returned by this FIRDatabaseQuery will + * have + * @param childKey The key of nodes with the right value + * @return A FIRDatabaseQuery instance, limited to data with the supplied value + * and the key. + */ +- (FIRDatabaseQuery *)queryEqualToValue:(nullable id)value + childKey:(nullable NSString *)childKey; + +#pragma mark - Managing presence + +/** + * Ensure the data at this location is set to the specified value when + * the client is disconnected (due to closing the browser, navigating + * to a new page, or network issues). + * + * onDisconnectSetValue: is especially useful for implementing "presence" + * systems, where a value should be changed or cleared when a user disconnects + * so that he appears "offline" to other users. + * + * @param value The value to be set after the connection is lost. + */ +- (void)onDisconnectSetValue:(nullable id)value; + +/** + * Ensure the data at this location is set to the specified value when + * the client is disconnected (due to closing the browser, navigating + * to a new page, or network issues). + * + * The completion block will be triggered when the operation has been + * successfully queued up on the Firebase Database servers + * + * @param value The value to be set after the connection is lost. + * @param block Block to be triggered when the operation has been queued up on + * the Firebase Database servers + */ +- (void)onDisconnectSetValue:(nullable id)value + withCompletionBlock:(void (^)(NSError *__nullable error, + FIRDatabaseReference *ref))block; + +/** + * Ensure the data at this location is set to the specified value and priority + * when the client is disconnected (due to closing the browser, navigating to a + * new page, or network issues). + * + * @param value The value to be set after the connection is lost. + * @param priority The priority to be set after the connection is lost. + */ +- (void)onDisconnectSetValue:(nullable id)value andPriority:(id)priority; + +/** + * Ensure the data at this location is set to the specified value and priority + * when the client is disconnected (due to closing the browser, navigating to a + * new page, or network issues). + * + * The completion block will be triggered when the operation has been + * successfully queued up on the Firebase Database servers + * + * @param value The value to be set after the connection is lost. + * @param priority The priority to be set after the connection is lost. + * @param block Block to be triggered when the operation has been queued up on + * the Firebase Database servers + */ +- (void)onDisconnectSetValue:(nullable id)value + andPriority:(nullable id)priority + withCompletionBlock:(void (^)(NSError *__nullable error, + FIRDatabaseReference *ref))block; + +/** + * Ensure the data at this location is removed when + * the client is disconnected (due to closing the app, navigating + * to a new page, or network issues). + * + * onDisconnectRemoveValue is especially useful for implementing "presence" + * systems. + */ +- (void)onDisconnectRemoveValue; + +/** + * Ensure the data at this location is removed when + * the client is disconnected (due to closing the app, navigating + * to a new page, or network issues). + * + * onDisconnectRemoveValueWithCompletionBlock: is especially useful for + * implementing "presence" systems. + * + * @param block Block to be triggered when the operation has been queued up on + * the Firebase Database servers + */ +- (void)onDisconnectRemoveValueWithCompletionBlock: + (void (^)(NSError *__nullable error, FIRDatabaseReference *ref))block; + +/** + * Ensure the data has the specified child values updated when + * the client is disconnected (due to closing the browser, navigating + * to a new page, or network issues). + * + * + * @param values A dictionary of child node keys and the values to set them to + * after the connection is lost. + */ +- (void)onDisconnectUpdateChildValues:(NSDictionary *)values; + +/** + * Ensure the data has the specified child values updated when + * the client is disconnected (due to closing the browser, navigating + * to a new page, or network issues). + * + * + * @param values A dictionary of child node keys and the values to set them to + * after the connection is lost. + * @param block A block that will be called once the operation has been queued + * up on the Firebase Database servers + */ +- (void)onDisconnectUpdateChildValues:(NSDictionary *)values + withCompletionBlock: + (void (^)(NSError *__nullable error, + FIRDatabaseReference *ref))block; + +/** + * Cancel any operations that are set to run on disconnect. If you previously + * called onDisconnectSetValue:, onDisconnectRemoveValue:, or + * onDisconnectUpdateChildValues:, and no longer want the values updated when + * the connection is lost, call cancelDisconnectOperations: + */ +- (void)cancelDisconnectOperations; + +/** + * Cancel any operations that are set to run on disconnect. If you previously + * called onDisconnectSetValue:, onDisconnectRemoveValue:, or + * onDisconnectUpdateChildValues:, and no longer want the values updated when + * the connection is lost, call cancelDisconnectOperations: + * + * @param block A block that will be triggered once the Firebase Database + * servers have acknowledged the cancel request. + */ +- (void)cancelDisconnectOperationsWithCompletionBlock: + (nullable void (^)(NSError *__nullable error, + FIRDatabaseReference *ref))block; + +#pragma mark - Manual Connection Management + +/** + * Manually disconnect the Firebase Database client from the server and disable + * automatic reconnection. + * + * The Firebase Database client automatically maintains a persistent connection + * to the Firebase Database server, which will remain active indefinitely and + * reconnect when disconnected. However, the goOffline( ) and goOnline( ) + * methods may be used to manually control the client connection in cases where + * a persistent connection is undesirable. + * + * While offline, the Firebase Database client will no longer receive data + * updates from the server. However, all database operations performed locally + * will continue to immediately fire events, allowing your application to + * continue behaving normally. Additionally, each operation performed locally + * will automatically be queued and retried upon reconnection to the Firebase + * Database server. + * + * To reconnect to the Firebase Database server and begin receiving remote + * events, see goOnline( ). Once the connection is reestablished, the Firebase + * Database client will transmit the appropriate data and fire the appropriate + * events so that your client "catches up" automatically. + * + * Note: Invoking this method will impact all Firebase Database connections. + */ ++ (void)goOffline; + +/** + * Manually reestablish a connection to the Firebase Database server and enable + * automatic reconnection. + * + * The Firebase Database client automatically maintains a persistent connection + * to the Firebase Database server, which will remain active indefinitely and + * reconnect when disconnected. However, the goOffline( ) and goOnline( ) + * methods may be used to manually control the client connection in cases where + * a persistent connection is undesirable. + * + * This method should be used after invoking goOffline( ) to disable the active + * connection. Once reconnected, the Firebase Database client will automatically + * transmit the proper data and fire the appropriate events so that your client + * "catches up" automatically. + * + * To disconnect from the Firebase Database server, see goOffline( ). + * + * Note: Invoking this method will impact all Firebase Database connections. + */ ++ (void)goOnline; + +#pragma mark - Transactions + +/** + * Performs an optimistic-concurrency transactional update to the data at this + * location. Your block will be called with a FIRMutableData instance that + * contains the current data at this location. Your block should update this + * data to the value you wish to write to this location, and then return an + * instance of FIRTransactionResult with the new data. + * + * If, when the operation reaches the server, it turns out that this client had + * stale data, your block will be run again with the latest data from the + * server. + * + * When your block is run, you may decide to abort the transaction by returning + * [FIRTransactionResult abort]. + * + * @param block This block receives the current data at this location and must + * return an instance of FIRTransactionResult + */ +- (void)runTransactionBlock: + (FIRTransactionResult * (^)(FIRMutableData *currentData))block; + +/** + * Performs an optimistic-concurrency transactional update to the data at this + * location. Your block will be called with a FIRMutableData instance that + * contains the current data at this location. Your block should update this + * data to the value you wish to write to this location, and then return an + * instance of FIRTransactionResult with the new data. + * + * If, when the operation reaches the server, it turns out that this client had + * stale data, your block will be run again with the latest data from the + * server. + * + * When your block is run, you may decide to abort the transaction by returning + * [FIRTransactionResult abort]. + * + * @param block This block receives the current data at this location and must + * return an instance of FIRTransactionResult + * @param completionBlock This block will be triggered once the transaction is + * complete, whether it was successful or not. It will indicate if there was an + * error, whether or not the data was committed, and what the current value of + * the data at this location is. + */ +- (void)runTransactionBlock: + (FIRTransactionResult * (^)(FIRMutableData *currentData))block + andCompletionBlock: + (void (^)(NSError *__nullable error, BOOL committed, + FIRDataSnapshot *__nullable snapshot))completionBlock; + +/** + * Performs an optimistic-concurrency transactional update to the data at this + * location. Your block will be called with a FIRMutableData instance that + * contains the current data at this location. Your block should update this + * data to the value you wish to write to this location, and then return an + * instance of FIRTransactionResult with the new data. + * + * If, when the operation reaches the server, it turns out that this client had + * stale data, your block will be run again with the latest data from the + * server. + * + * When your block is run, you may decide to abort the transaction by return + * [FIRTransactionResult abort]. + * + * Since your block may be run multiple times, this client could see several + * immediate states that don't exist on the server. You can suppress those + * immediate states until the server confirms the final state of the + * transaction. + * + * @param block This block receives the current data at this location and must + * return an instance of FIRTransactionResult + * @param completionBlock This block will be triggered once the transaction is + * complete, whether it was successful or not. It will indicate if there was an + * error, whether or not the data was committed, and what the current value of + * the data at this location is. + * @param localEvents Set this to NO to suppress events raised for intermediate + * states, and only get events based on the final state of the transaction. + */ +- (void)runTransactionBlock: + (FIRTransactionResult * (^)(FIRMutableData *currentData))block + andCompletionBlock: + (nullable void (^)(NSError *__nullable error, BOOL committed, + FIRDataSnapshot *__nullable snapshot)) + completionBlock + withLocalEvents:(BOOL)localEvents; + +#pragma mark - Retrieving String Representation + +/** + * Gets the absolute URL of this Firebase Database location. + * + * @return The absolute URL of the referenced Firebase Database location. + */ +- (NSString *)description; + +#pragma mark - Properties + +/** + * Gets a FIRDatabaseReference for the parent location. + * If this instance refers to the root of your Firebase Database, it has no + * parent, and therefore parent( ) will return null. + * + * @return A FIRDatabaseReference for the parent location. + */ +@property(strong, readonly, nonatomic, nullable) FIRDatabaseReference *parent; + +/** + * Gets a FIRDatabaseReference for the root location + * + * @return A new FIRDatabaseReference to root location. + */ +@property(strong, readonly, nonatomic) FIRDatabaseReference *root; + +/** + * Gets the last token in a Firebase Database location (e.g. 'fred' in + * https://SampleChat.firebaseIO-demo.com/users/fred) + * + * @return The key of the location this reference points to. + */ +@property(strong, readonly, nonatomic, nullable) NSString *key; + +/** + * Gets the URL for the Firebase Database location referenced by this + * FIRDatabaseReference. + * + * @return The url of the location this reference points to. + */ +@property(strong, readonly, nonatomic) NSString *URL; + +/** + * Gets the FIRDatabase instance associated with this reference. + * + * @return The FIRDatabase object for this reference. + */ +@property(strong, readonly, nonatomic) FIRDatabase *database; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Public/FIRMutableData.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Public/FIRMutableData.h new file mode 100644 index 0000000..9797a67 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Public/FIRMutableData.h @@ -0,0 +1,128 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * A FIRMutableData instance is populated with data from a Firebase Database + * location. When you are using runTransactionBlock:, you will be given an + * instance containing the current data at that location. Your block will be + * responsible for updating that instance to the data you wish to save at that + * location, and then returning using [FIRTransactionResult successWithValue:]. + * + * To modify the data, set its value property to any of the native types support + * by Firebase Database: + * + * + NSNumber (includes BOOL) + * + NSDictionary + * + NSArray + * + NSString + * + nil / NSNull to remove the data + * + * Note that changes made to a child FIRMutableData instance will be visible to + * the parent. + */ +NS_SWIFT_NAME(MutableData) +@interface FIRMutableData : NSObject + +#pragma mark - Inspecting and navigating the data + +/** + * Returns boolean indicating whether this mutable data has children. + * + * @return YES if this data contains child nodes. + */ +- (BOOL)hasChildren; + +/** + * Indicates whether this mutable data has a child at the given path. + * + * @param path A path string, consisting either of a single segment, like + * 'child', or multiple segments, 'a/deeper/child' + * @return YES if this data contains a child at the specified relative path + */ +- (BOOL)hasChildAtPath:(NSString *)path; + +/** + * Used to obtain a FIRMutableData instance that encapsulates the data at the + * given relative path. Note that changes made to the child will be visible to + * the parent. + * + * @param path A path string, consisting either of a single segment, like + * 'child', or multiple segments, 'a/deeper/child' + * @return A FIRMutableData instance containing the data at the given path + */ +- (FIRMutableData *)childDataByAppendingPath:(NSString *)path; + +#pragma mark - Properties + +/** + * To modify the data contained by this instance of FIRMutableData, set this to + * any of the native types supported by Firebase Database: + * + * + NSNumber (includes BOOL) + * + NSDictionary + * + NSArray + * + NSString + * + nil / NSNull to remove the data + * + * Note that setting this value will override the priority at this location. + * + * @return The current data at this location as a native object + */ +@property(strong, nonatomic, nullable) id value; + +/** + * Set this property to update the priority of the data at this location. Can be + * set to the following types: + * + * + NSNumber + * + NSString + * + nil / NSNull to remove the priority + * + * @return The priority of the data at this location + */ +@property(strong, nonatomic, nullable) id priority; + +/** + * @return The number of child nodes at this location + */ +@property(readonly, nonatomic) NSUInteger childrenCount; + +/** + * Used to iterate over the children at this location. You can use the native + * for .. in syntax: + * + * for (FIRMutableData* child in data.children) { + * ... + * } + * + * Note that this enumerator operates on an immutable copy of the child list. + * So, you can modify the instance during iteration, but the new additions will + * not be visible until you get a new enumerator. + */ +@property(readonly, nonatomic, strong) NSEnumerator *children; + +/** + * @return The key name of this node, or nil if it is the top-most location + */ +@property(readonly, nonatomic, strong, nullable) NSString *key; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Public/FIRServerValue.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Public/FIRServerValue.h new file mode 100644 index 0000000..8fca5e0 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Public/FIRServerValue.h @@ -0,0 +1,36 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * Placeholder values you may write into Firebase Database as a value or + * priority that will automatically be populated by the Firebase Database + * server. + */ +NS_SWIFT_NAME(ServerValue) +@interface FIRServerValue : NSObject + +/** + * Placeholder value for the number of milliseconds since the Unix epoch + */ ++ (NSDictionary *)timestamp; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Public/FIRTransactionResult.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Public/FIRTransactionResult.h new file mode 100644 index 0000000..7f5ccc1 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Public/FIRTransactionResult.h @@ -0,0 +1,50 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRMutableData.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * Used for runTransactionBlock:. An FIRTransactionResult instance is a + * container for the results of the transaction. + */ +NS_SWIFT_NAME(TransactionResult) +@interface FIRTransactionResult : NSObject + +/** + * Used for runTransactionBlock:. Indicates that the new value should be saved + * at this location + * + * @param value A FIRMutableData instance containing the new value to be set + * @return An FIRTransactionResult instance that can be used as a return value + * from the block given to runTransactionBlock: + */ ++ (FIRTransactionResult *)successWithValue:(FIRMutableData *)value; + +/** + * Used for runTransactionBlock:. Indicates that the current transaction should + * no longer proceed. + * + * @return An FIRTransactionResult instance that can be used as a return value + * from the block given to runTransactionBlock: + */ ++ (FIRTransactionResult *)abort; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Public/FirebaseDatabase.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Public/FirebaseDatabase.h new file mode 100644 index 0000000..ae6b933 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Public/FirebaseDatabase.h @@ -0,0 +1,29 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FirebaseDatabase_h +#define FirebaseDatabase_h + +#import "FIRDataEventType.h" +#import "FIRDataSnapshot.h" +#import "FIRDatabase.h" +#import "FIRDatabaseQuery.h" +#import "FIRDatabaseReference.h" +#import "FIRMutableData.h" +#import "FIRServerValue.h" +#import "FIRTransactionResult.h" + +#endif /* FirebaseDatabase_h */ diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Realtime/FConnection.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Realtime/FConnection.h new file mode 100644 index 0000000..7198615 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Realtime/FConnection.h @@ -0,0 +1,59 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FTypedefs.h" +#import "FWebSocketConnection.h" +#import + +@protocol FConnectionDelegate; + +@interface FConnection : NSObject + +@property(nonatomic, weak) id delegate; + +- (id)initWith:(FRepoInfo *)aRepoInfo + andDispatchQueue:(dispatch_queue_t)queue + lastSessionID:(NSString *)lastSessionID; + +- (void)open; +- (void)close; +- (void)sendRequest:(NSDictionary *)dataMsg sensitive:(BOOL)sensitive; + +// FWebSocketDelegate delegate methods +- (void)onMessage:(FWebSocketConnection *)fwebSocket + withMessage:(NSDictionary *)message; +- (void)onDisconnect:(FWebSocketConnection *)fwebSocket + wasEverConnected:(BOOL)everConnected; + +@end + +typedef enum { + DISCONNECT_REASON_SERVER_RESET = 0, + DISCONNECT_REASON_OTHER = 1 +} FDisconnectReason; + +@protocol FConnectionDelegate + +- (void)onReady:(FConnection *)fconnection + atTime:(NSNumber *)timestamp + sessionID:(NSString *)sessionID; +- (void)onDataMessage:(FConnection *)fconnection + withMessage:(NSDictionary *)message; +- (void)onDisconnect:(FConnection *)fconnection + withReason:(FDisconnectReason)reason; +- (void)onKill:(FConnection *)fconnection withReason:(NSString *)reason; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Realtime/FConnection.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Realtime/FConnection.m new file mode 100644 index 0000000..a33ebd7 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Realtime/FConnection.m @@ -0,0 +1,234 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FConnection.h" +#import "FConstants.h" +#import + +typedef enum { + REALTIME_STATE_CONNECTING = 0, + REALTIME_STATE_CONNECTED = 1, + REALTIME_STATE_DISCONNECTED = 2, +} FConnectionState; + +@interface FConnection () { + FConnectionState state; +} + +@property(nonatomic, strong) FWebSocketConnection *conn; +@property(nonatomic, strong) FRepoInfo *repoInfo; + +@end + +#pragma mark - +#pragma mark FConnection implementation + +@implementation FConnection + +@synthesize delegate; +@synthesize conn; +@synthesize repoInfo; + +#pragma mark - +#pragma mark Initializers + +- (id)initWith:(FRepoInfo *)aRepoInfo + andDispatchQueue:(dispatch_queue_t)queue + lastSessionID:(NSString *)lastSessionID { + self = [super init]; + if (self) { + state = REALTIME_STATE_CONNECTING; + self.repoInfo = aRepoInfo; + self.conn = [[FWebSocketConnection alloc] initWith:self.repoInfo + andQueue:queue + lastSessionID:lastSessionID]; + self.conn.delegate = self; + } + return self; +} + +#pragma mark - +#pragma mark Public method implementation + +- (void)open { + FFLog(@"I-RDB082001", @"Calling open in FConnection"); + [self.conn open]; +} + +- (void)closeWithReason:(FDisconnectReason)reason { + if (state != REALTIME_STATE_DISCONNECTED) { + FFLog(@"I-RDB082002", @"Closing realtime connection."); + state = REALTIME_STATE_DISCONNECTED; + + if (self.conn) { + FFLog(@"I-RDB082003", @"Calling close again."); + [self.conn close]; + self.conn = nil; + } + + [self.delegate onDisconnect:self withReason:reason]; + } +} + +- (void)close { + [self closeWithReason:DISCONNECT_REASON_OTHER]; +} + +- (void)sendRequest:(NSDictionary *)dataMsg sensitive:(BOOL)sensitive { + // since this came from the persistent connection, wrap it in a data message + // envelope + NSDictionary *msg = @{ + kFWPRequestType : kFWPRequestTypeData, + kFWPRequestDataPayload : dataMsg + }; + [self sendData:msg sensitive:sensitive]; +} + +#pragma mark - +#pragma mark Helpers + +- (void)sendData:(NSDictionary *)data sensitive:(BOOL)sensitive { + if (state != REALTIME_STATE_CONNECTED) { + @throw [[NSException alloc] + initWithName:@"InvalidConnectionState" + reason:@"Tried to send data on an unconnected FConnection" + userInfo:nil]; + } else { + if (sensitive) { + FFLog(@"I-RDB082004", @"Sending data (contents hidden)"); + } else { + FFLog(@"I-RDB082005", @"Sending: %@", data); + } + [self.conn send:data]; + } +} + +#pragma mark - +#pragma mark FWebSocketConnectinDelegate implementation + +// Corresponds to onConnectionLost in JS +- (void)onDisconnect:(FWebSocketConnection *)fwebSocket + wasEverConnected:(BOOL)everConnected { + + self.conn = nil; + if (!everConnected && state == REALTIME_STATE_CONNECTING) { + FFLog(@"I-RDB082006", @"Realtime connection failed."); + + // Since we failed to connect at all, clear any cached entry for this + // namespace in case the machine went away + [self.repoInfo clearInternalHostCache]; + } else if (state == REALTIME_STATE_CONNECTED) { + FFLog(@"I-RDB082007", @"Realtime connection lost."); + } + + [self close]; +} + +// Corresponds to onMessageReceived in JS +- (void)onMessage:(FWebSocketConnection *)fwebSocket + withMessage:(NSDictionary *)message { + NSString *rawMessageType = + [message objectForKey:kFWPAsyncServerEnvelopeType]; + if (rawMessageType != nil) { + if ([rawMessageType isEqualToString:kFWPAsyncServerDataMessage]) { + [self onDataMessage:[message + objectForKey:kFWPAsyncServerEnvelopeData]]; + } else if ([rawMessageType + isEqualToString:kFWPAsyncServerControlMessage]) { + [self onControl:[message objectForKey:kFWPAsyncServerEnvelopeData]]; + } else { + FFLog(@"I-RDB082008", @"Unrecognized server packet type: %@", + rawMessageType); + } + } else { + FFLog(@"I-RDB082009", @"Unrecognized raw server packet received: %@", + message); + } +} + +- (void)onDataMessage:(NSDictionary *)message { + // we don't do anything with data messages, just kick them up a level + FFLog(@"I-RDB082010", @"Got data message: %@", message); + [self.delegate onDataMessage:self withMessage:message]; +} + +- (void)onControl:(NSDictionary *)message { + FFLog(@"I-RDB082011", @"Got control message: %@", message); + NSString *type = [message objectForKey:kFWPAsyncServerControlMessageType]; + if ([type isEqualToString:kFWPAsyncServerControlMessageShutdown]) { + NSString *reason = + [message objectForKey:kFWPAsyncServerControlMessageData]; + [self onConnectionShutdownWithReason:reason]; + } else if ([type isEqualToString:kFWPAsyncServerControlMessageReset]) { + NSString *host = + [message objectForKey:kFWPAsyncServerControlMessageData]; + [self onReset:host]; + } else if ([type isEqualToString:kFWPAsyncServerHello]) { + NSDictionary *handshakeData = + [message objectForKey:kFWPAsyncServerControlMessageData]; + [self onHandshake:handshakeData]; + } else { + FFLog(@"I-RDB082012", + @"Unknown control message returned from server: %@", message); + } +} + +- (void)onConnectionShutdownWithReason:(NSString *)reason { + FFLog(@"I-RDB082013", + @"Connection shutdown command received. Shutting down..."); + + [self.delegate onKill:self withReason:reason]; + [self close]; +} + +- (void)onHandshake:(NSDictionary *)handshake { + NSNumber *timestamp = + [handshake objectForKey:kFWPAsyncServerHelloTimestamp]; + // NSString* version = [handshake + // objectForKey:kFWPAsyncServerHelloVersion]; + NSString *host = [handshake objectForKey:kFWPAsyncServerHelloConnectedHost]; + NSString *sessionID = [handshake objectForKey:kFWPAsyncServerHelloSession]; + + self.repoInfo.internalHost = host; + + if (state == REALTIME_STATE_CONNECTING) { + [self.conn start]; + [self onConnection:self.conn readyAtTime:timestamp sessionID:sessionID]; + } +} + +- (void)onConnection:(FWebSocketConnection *)conn + readyAtTime:(NSNumber *)timestamp + sessionID:(NSString *)sessionID { + FFLog(@"I-RDB082014", @"Realtime connection established"); + state = REALTIME_STATE_CONNECTED; + + [self.delegate onReady:self atTime:timestamp sessionID:sessionID]; +} + +- (void)onReset:(NSString *)host { + FFLog( + @"I-RDB082015", + @"Got a reset; killing connection to: %@; Updating internalHost to: %@", + repoInfo.internalHost, host); + self.repoInfo.internalHost = host; + + // Explicitly close the connection with SERVER_RESET so calling code knows + // to reconnect immediately. + [self closeWithReason:DISCONNECT_REASON_SERVER_RESET]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Realtime/FWebSocketConnection.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Realtime/FWebSocketConnection.h new file mode 100644 index 0000000..abb7474 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Realtime/FWebSocketConnection.h @@ -0,0 +1,53 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FSRWebSocket.h" +#import "FUtilities.h" +#import + +@protocol FWebSocketDelegate; + +@interface FWebSocketConnection : NSObject + +@property(nonatomic, weak) id delegate; + +- (id)initWith:(FRepoInfo *)repoInfo + andQueue:(dispatch_queue_t)queue + lastSessionID:(NSString *)lastSessionID; + +- (void)open; +- (void)close; +- (void)start; +- (void)send:(NSDictionary *)dictionary; + +- (void)webSocket:(FSRWebSocket *)webSocket didReceiveMessage:(id)message; +- (void)webSocketDidOpen:(FSRWebSocket *)webSocket; +- (void)webSocket:(FSRWebSocket *)webSocket didFailWithError:(NSError *)error; +- (void)webSocket:(FSRWebSocket *)webSocket + didCloseWithCode:(NSInteger)code + reason:(NSString *)reason + wasClean:(BOOL)wasClean; + +@end + +@protocol FWebSocketDelegate + +- (void)onMessage:(FWebSocketConnection *)fwebSocket + withMessage:(NSDictionary *)message; +- (void)onDisconnect:(FWebSocketConnection *)fwebSocket + wasEverConnected:(BOOL)everConnected; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Realtime/FWebSocketConnection.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Realtime/FWebSocketConnection.m new file mode 100644 index 0000000..edcae09 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Realtime/FWebSocketConnection.m @@ -0,0 +1,346 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Targetted compilation is ONLY for testing. UIKit is weak-linked in actual +// release build. + +#import + +#import "FConstants.h" +#import "FIRDatabaseReference.h" +#import "FIRDatabase_Private.h" +#import "FStringUtilities.h" +#import "FWebSocketConnection.h" +#import + +#if TARGET_OS_IOS || TARGET_OS_TV +#import +#endif + +@interface FWebSocketConnection () { + NSMutableString *frame; + BOOL everConnected; + BOOL isClosed; + NSTimer *keepAlive; +} + +- (void)shutdown; +- (void)onClosed; +- (void)closeIfNeverConnected; + +@property(nonatomic, strong) FSRWebSocket *webSocket; +@property(nonatomic, strong) NSNumber *connectionId; +@property(nonatomic, readwrite) int totalFrames; +@property(nonatomic, readonly) BOOL buffering; +@property(nonatomic, readonly) NSString *userAgent; +@property(nonatomic) dispatch_queue_t dispatchQueue; + +- (void)nop:(NSTimer *)timer; + +@end + +@implementation FWebSocketConnection + +@synthesize delegate; +@synthesize webSocket; +@synthesize connectionId; + +- (id)initWith:(FRepoInfo *)repoInfo + andQueue:(dispatch_queue_t)queue + lastSessionID:(NSString *)lastSessionID { + self = [super init]; + if (self) { + everConnected = NO; + isClosed = NO; + self.connectionId = [FUtilities LUIDGenerator]; + self.totalFrames = 0; + self.dispatchQueue = queue; + frame = nil; + + NSString *connectionUrl = + [repoInfo connectionURLWithLastSessionID:lastSessionID]; + NSString *ua = [self userAgent]; + FFLog(@"I-RDB083001", @"(wsc:%@) Connecting to: %@ as %@", + self.connectionId, connectionUrl, ua); + + NSURLRequest *req = [[NSURLRequest alloc] + initWithURL:[[NSURL alloc] initWithString:connectionUrl]]; + self.webSocket = [[FSRWebSocket alloc] initWithURLRequest:req + queue:queue + andUserAgent:ua]; + [self.webSocket setDelegateDispatchQueue:queue]; + self.webSocket.delegate = self; + } + return self; +} + +- (NSString *)userAgent { + NSString *systemVersion; + NSString *deviceName; + BOOL hasUiDeviceClass = NO; + +// Targetted compilation is ONLY for testing. UIKit is weak-linked in actual +// release build. +#if TARGET_OS_IOS || TARGET_OS_TV + Class uiDeviceClass = NSClassFromString(@"UIDevice"); + if (uiDeviceClass) { + systemVersion = [uiDeviceClass currentDevice].systemVersion; + deviceName = [uiDeviceClass currentDevice].model; + hasUiDeviceClass = YES; + } +#endif + + if (!hasUiDeviceClass) { + NSDictionary *systemVersionDictionary = [NSDictionary + dictionaryWithContentsOfFile: + @"/System/Library/CoreServices/SystemVersion.plist"]; + systemVersion = + [systemVersionDictionary objectForKey:@"ProductVersion"]; + deviceName = [systemVersionDictionary objectForKey:@"ProductName"]; + } + + NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier]; + + // Sanitize '/'s in deviceName and bundleIdentifier for stats + deviceName = [FStringUtilities sanitizedForUserAgent:deviceName]; + bundleIdentifier = + [FStringUtilities sanitizedForUserAgent:bundleIdentifier]; + + // Firebase/5/__//{device model / + // os (Mac OS X, iPhone, etc.}_ + NSString *ua = [NSString + stringWithFormat:@"Firebase/%@/%@/%@/%@_%@", kWebsocketProtocolVersion, + [FIRDatabase buildVersion], systemVersion, deviceName, + bundleIdentifier]; + return ua; +} + +- (BOOL)buffering { + return frame != nil; +} + +#pragma mark - +#pragma mark Public FWebSocketConnection methods + +- (void)open { + FFLog(@"I-RDB083002", @"(wsc:%@) FWebSocketConnection open.", + self.connectionId); + assert(delegate); + everConnected = NO; + // TODO Assert url + [self.webSocket open]; + dispatch_time_t when = dispatch_time( + DISPATCH_TIME_NOW, kWebsocketConnectTimeout * NSEC_PER_SEC); + dispatch_after(when, self.dispatchQueue, ^{ + [self closeIfNeverConnected]; + }); +} + +- (void)close { + FFLog(@"I-RDB083003", @"(wsc:%@) FWebSocketConnection is being closed.", + self.connectionId); + isClosed = YES; + [self.webSocket close]; +} + +- (void)start { + // Start is a no-op for websockets. +} + +- (void)send:(NSDictionary *)dictionary { + + [self resetKeepAlive]; + + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dictionary + options:kNilOptions + error:nil]; + + NSString *data = [[NSString alloc] initWithData:jsonData + encoding:NSUTF8StringEncoding]; + + NSArray *dataSegs = [FUtilities splitString:data + intoMaxSize:kWebsocketMaxFrameSize]; + + // First send the header so the server knows how many segments are + // forthcoming + if (dataSegs.count > 1) { + [self.webSocket + send:[NSString + stringWithFormat:@"%u", (unsigned int)dataSegs.count]]; + } + + // Then, actually send the segments. + for (NSString *segment in dataSegs) { + [self.webSocket send:segment]; + } +} + +- (void)nop:(NSTimer *)timer { + if (!isClosed) { + FFLog(@"I-RDB083004", @"(wsc:%@) nop", self.connectionId); + [self.webSocket send:@"0"]; + } else { + FFLog(@"I-RDB083005", + @"(wsc:%@) No more websocket; invalidating nop timer.", + self.connectionId); + [timer invalidate]; + } +} + +- (void)handleNewFrameCount:(int)numFrames { + self.totalFrames = numFrames; + frame = [[NSMutableString alloc] initWithString:@""]; + FFLog(@"I-RDB083006", @"(wsc:%@) handleNewFrameCount: %d", + self.connectionId, self.totalFrames); +} + +- (NSString *)extractFrameCount:(NSString *)message { + if ([message length] <= 4) { + int frameCount = [message intValue]; + if (frameCount > 0) { + [self handleNewFrameCount:frameCount]; + return nil; + } + } + [self handleNewFrameCount:1]; + return message; +} + +- (void)appendFrame:(NSString *)message { + [frame appendString:message]; + self.totalFrames = self.totalFrames - 1; + + if (self.totalFrames == 0) { + // Call delegate and pass an immutable version of the frame + NSDictionary *json = [NSJSONSerialization + JSONObjectWithData:[frame dataUsingEncoding:NSUTF8StringEncoding] + options:kNilOptions + error:nil]; + frame = nil; + FFLog(@"I-RDB083007", + @"(wsc:%@) handleIncomingFrame sending complete frame: %d", + self.connectionId, self.totalFrames); + + @autoreleasepool { + [self.delegate onMessage:self withMessage:json]; + } + } +} + +- (void)handleIncomingFrame:(NSString *)message { + [self resetKeepAlive]; + if (self.buffering) { + [self appendFrame:message]; + } else { + NSString *remaining = [self extractFrameCount:message]; + if (remaining) { + [self appendFrame:remaining]; + } + } +} + +#pragma mark - +#pragma mark SRWebSocketDelegate implementation +- (void)webSocket:(FSRWebSocket *)webSocket didReceiveMessage:(id)message { + [self handleIncomingFrame:message]; +} + +- (void)webSocketDidOpen:(FSRWebSocket *)webSocket { + FFLog(@"I-RDB083008", @"(wsc:%@) webSocketDidOpen", self.connectionId); + + everConnected = YES; + + dispatch_async(dispatch_get_main_queue(), ^{ + self->keepAlive = + [NSTimer scheduledTimerWithTimeInterval:kWebsocketKeepaliveInterval + target:self + selector:@selector(nop:) + userInfo:nil + repeats:YES]; + FFLog(@"I-RDB083009", @"(wsc:%@) nop timer kicked off", + self.connectionId); + }); +} + +- (void)webSocket:(FSRWebSocket *)webSocket didFailWithError:(NSError *)error { + FFLog(@"I-RDB083010", @"(wsc:%@) didFailWithError didFailWithError: %@", + self.connectionId, [error description]); + [self onClosed]; +} + +- (void)webSocket:(FSRWebSocket *)webSocket + didCloseWithCode:(NSInteger)code + reason:(NSString *)reason + wasClean:(BOOL)wasClean { + FFLog(@"I-RDB083011", @"(wsc:%@) didCloseWithCode: %ld %@", + self.connectionId, (long)code, reason); + [self onClosed]; +} + +#pragma mark - +#pragma mark Private methods + +/** + * Note that the close / onClosed / shutdown cycle here is a little different + * from the javascript client. In order to properly handle deallocation, no + * close-related action is taken at a higher level until we have received + * notification from the websocket itself that it is closed. Otherwise, we end + * up deallocating this class and the FConnection class before the websocket has + * a change to call some of its delegate methods. So, since close is the + * external close handler, we just set a flag saying not to call our own + * delegate method and close the websocket. That will trigger a callback into + * this class that can then do things like clean up the keepalive timer. + */ + +- (void)closeIfNeverConnected { + if (!everConnected) { + FFLog(@"I-RDB083012", @"(wsc:%@) Websocket timed out on connect", + self.connectionId); + [self.webSocket close]; + } +} + +- (void)shutdown { + isClosed = YES; + + // Call delegate methods + [self.delegate onDisconnect:self wasEverConnected:everConnected]; +} + +- (void)onClosed { + if (!isClosed) { + FFLog(@"I-RDB083013", @"Websocket is closing itself"); + [self shutdown]; + } + self.webSocket = nil; + if (keepAlive.isValid) { + [keepAlive invalidate]; + } +} + +- (void)resetKeepAlive { + NSDate *newTime = + [NSDate dateWithTimeIntervalSinceNow:kWebsocketKeepaliveInterval]; + // Calling setFireDate is actually kinda' expensive, so wait at least 5 + // seconds before updating it. + if ([newTime timeIntervalSinceDate:keepAlive.fireDate] > 5) { + FFLog(@"I-RDB083014", @"(wsc:%@) resetting keepalive, to %@ ; old: %@", + self.connectionId, newTime, [keepAlive fireDate]); + [keepAlive setFireDate:newTime]; + } +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FChildrenNode.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FChildrenNode.h new file mode 100644 index 0000000..cc3239e --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FChildrenNode.h @@ -0,0 +1,42 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FImmutableSortedDictionary.h" +#import "FNode.h" +#import "FTypedefs.h" +#import "FTypedefs_Private.h" +#import + +@class FNamedNode; + +@interface FChildrenNode : NSObject + +- (id)initWithChildren:(FImmutableSortedDictionary *)someChildren; +- (id)initWithPriority:(id)aPriority + children:(FImmutableSortedDictionary *)someChildren; + +// FChildrenNode specific methods + +- (void)enumerateChildrenAndPriorityUsingBlock:(void (^)(NSString *, id, + BOOL *))block; + +- (FNamedNode *)firstChild; +- (FNamedNode *)lastChild; + +@property(nonatomic, strong) FImmutableSortedDictionary *children; +@property(nonatomic, strong) id priorityNode; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FChildrenNode.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FChildrenNode.m new file mode 100644 index 0000000..bd02bf8 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FChildrenNode.m @@ -0,0 +1,418 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FChildrenNode.h" +#import "FConstants.h" +#import "FEmptyNode.h" +#import "FMaxNode.h" +#import "FNamedNode.h" +#import "FPriorityIndex.h" +#import "FSnapshotUtilities.h" +#import "FStringUtilities.h" +#import "FTransformedEnumerator.h" +#import "FUtilities.h" + +@interface FChildrenNode () +@property(nonatomic, strong) NSString *lazyHash; +@end + +@implementation FChildrenNode + +// Note: The only reason we allow nil priority is to for EmptyNode, since we +// can't use EmptyNode as the priority of EmptyNode. We might want to consider +// making EmptyNode its own class instead of an empty ChildrenNode. + +- (id)init { + return [self + initWithPriority:nil + children:[FImmutableSortedDictionary + dictionaryWithComparator:[FUtilities + keyComparator]]]; +} + +- (id)initWithChildren:(FImmutableSortedDictionary *)someChildren { + return [self initWithPriority:nil children:someChildren]; +} + +- (id)initWithPriority:(id)aPriority + children:(FImmutableSortedDictionary *)someChildren { + if (someChildren.isEmpty && aPriority != nil && ![aPriority isEmpty]) { + [NSException raise:NSInvalidArgumentException + format:@"Can't create empty node with priority!"]; + } + self = [super init]; + if (self) { + self.children = someChildren; + self.priorityNode = aPriority; + } + return self; +} + +- (NSString *)description { + return [[self valForExport:YES] description]; +} + +#pragma mark - +#pragma mark FNode methods + +- (BOOL)isLeafNode { + return NO; +} + +- (id)getPriority { + if (self.priorityNode) { + return self.priorityNode; + } else { + return [FEmptyNode emptyNode]; + } +} + +- (id)updatePriority:(id)aPriority { + if ([self.children isEmpty]) { + return [FEmptyNode emptyNode]; + } else { + return [[FChildrenNode alloc] initWithPriority:aPriority + children:self.children]; + } +} + +- (id)getImmediateChild:(NSString *)childName { + if ([childName isEqualToString:@".priority"]) { + return [self getPriority]; + } else { + id child = [self.children objectForKey:childName]; + return (child == nil) ? [FEmptyNode emptyNode] : child; + } +} + +- (id)getChild:(FPath *)path { + NSString *front = [path getFront]; + if (front == nil) { + return self; + } else { + return [[self getImmediateChild:front] getChild:[path popFront]]; + } +} + +- (BOOL)hasChild:(NSString *)childName { + return ![self getImmediateChild:childName].isEmpty; +} + +- (id)updateImmediateChild:(NSString *)childName + withNewChild:(id)newChildNode { + NSAssert(newChildNode != nil, @"Should always be passing nodes."); + + if ([childName isEqualToString:@".priority"]) { + return [self updatePriority:newChildNode]; + } else { + FImmutableSortedDictionary *newChildren; + if (newChildNode.isEmpty) { + newChildren = [self.children removeObjectForKey:childName]; + } else { + newChildren = [self.children setObject:newChildNode + forKey:childName]; + } + if (newChildren.isEmpty) { + return [FEmptyNode emptyNode]; + } else { + return [[FChildrenNode alloc] initWithPriority:self.getPriority + children:newChildren]; + } + } +} + +- (id)updateChild:(FPath *)path withNewChild:(id)newChildNode { + NSString *front = [path getFront]; + if (front == nil) { + return newChildNode; + } else { + NSAssert(![front isEqualToString:@".priority"] || path.length == 1, + @".priority must be the last token in a path."); + id newImmediateChild = + [[self getImmediateChild:front] updateChild:[path popFront] + withNewChild:newChildNode]; + return [self updateImmediateChild:front withNewChild:newImmediateChild]; + } +} + +- (BOOL)isEmpty { + return [self.children isEmpty]; +} + +- (int)numChildren { + return [self.children count]; +} + +- (id)val { + return [self valForExport:NO]; +} + +- (id)valForExport:(BOOL)exp { + if ([self isEmpty]) { + return [NSNull null]; + } + + __block int numKeys = 0; + __block NSInteger maxKey = 0; + __block BOOL allIntegerKeys = YES; + + NSMutableDictionary *obj = + [[NSMutableDictionary alloc] initWithCapacity:[self.children count]]; + [self enumerateChildrenUsingBlock:^(NSString *key, id childNode, + BOOL *stop) { + [obj setObject:[childNode valForExport:exp] forKey:key]; + + numKeys++; + + // If we already found a string key, don't bother with any of this + if (!allIntegerKeys) { + return; + } + + // Treat leading zeroes that are not exactly "0" as strings + NSString *firstChar = [key substringWithRange:NSMakeRange(0, 1)]; + if ([firstChar isEqualToString:@"0"] && [key length] > 1) { + allIntegerKeys = NO; + } else { + NSNumber *keyAsNum = [FUtilities intForString:key]; + if (keyAsNum != nil) { + NSInteger keyAsInt = [keyAsNum integerValue]; + if (keyAsInt > maxKey) { + maxKey = keyAsInt; + } + } else { + allIntegerKeys = NO; + } + } + }]; + + if (!exp && allIntegerKeys && maxKey < 2 * numKeys) { + // convert to an array + NSMutableArray *array = + [[NSMutableArray alloc] initWithCapacity:maxKey + 1]; + for (int i = 0; i <= maxKey; ++i) { + NSString *keyString = [NSString stringWithFormat:@"%i", i]; + id child = obj[keyString]; + if (child != nil) { + [array addObject:child]; + } else { + [array addObject:[NSNull null]]; + } + } + return array; + } else { + + if (exp && [self getPriority] != nil && !self.getPriority.isEmpty) { + obj[kPayloadPriority] = [self.getPriority val]; + } + + return obj; + } +} + +- (NSString *)dataHash { + if (self.lazyHash == nil) { + NSMutableString *toHash = [[NSMutableString alloc] init]; + + if (!self.getPriority.isEmpty) { + [toHash appendString:@"priority:"]; + [FSnapshotUtilities + appendHashRepresentationForLeafNode:(FLeafNode *) + self.getPriority + toString:toHash + hashVersion:FDataHashVersionV1]; + [toHash appendString:@":"]; + } + + __block BOOL sawPriority = NO; + [self enumerateChildrenUsingBlock:^(NSString *key, id node, + BOOL *stop) { + sawPriority = sawPriority || [[node getPriority] isEmpty]; + *stop = sawPriority; + }]; + if (sawPriority) { + NSMutableArray *array = [NSMutableArray array]; + [self enumerateChildrenUsingBlock:^(NSString *key, id node, + BOOL *stop) { + FNamedNode *namedNode = [[FNamedNode alloc] initWithName:key + andNode:node]; + [array addObject:namedNode]; + }]; + [array sortUsingComparator:^NSComparisonResult( + FNamedNode *namedNode1, FNamedNode *namedNode2) { + return + [[FPriorityIndex priorityIndex] compareNamedNode:namedNode1 + toNamedNode:namedNode2]; + }]; + [array enumerateObjectsUsingBlock:^(FNamedNode *namedNode, + NSUInteger idx, BOOL *stop) { + NSString *childHash = [namedNode.node dataHash]; + if (![childHash isEqualToString:@""]) { + [toHash appendFormat:@":%@:%@", namedNode.name, childHash]; + } + }]; + } else { + [self enumerateChildrenUsingBlock:^(NSString *key, id node, + BOOL *stop) { + NSString *childHash = [node dataHash]; + if (![childHash isEqualToString:@""]) { + [toHash appendFormat:@":%@:%@", key, childHash]; + } + }]; + } + self.lazyHash = [toHash isEqualToString:@""] + ? @"" + : [FStringUtilities base64EncodedSha1:toHash]; + } + return self.lazyHash; +} + +- (NSComparisonResult)compare:(id)other { + // children nodes come last, unless this is actually an empty node, then we + // come first. + if (self.isEmpty) { + if (other.isEmpty) { + return NSOrderedSame; + } else { + return NSOrderedAscending; + } + } else if (other.isLeafNode || other.isEmpty) { + return NSOrderedDescending; + } else if (other == [FMaxNode maxNode]) { + return NSOrderedAscending; + } else { + // Must be another node with children. + return NSOrderedSame; + } +} + +- (BOOL)isEqual:(id)other { + if (other == self) { + return YES; + } else if (other == nil) { + return NO; + } else if (other.isLeafNode) { + return NO; + } else if (self.isEmpty && [other isEmpty]) { + // Empty nodes do not have priority + return YES; + } else { + FChildrenNode *otherChildrenNode = other; + if (![self.getPriority isEqual:other.getPriority]) { + return NO; + } else if (self.children.count == otherChildrenNode.children.count) { + __block BOOL equal = YES; + [self enumerateChildrenUsingBlock:^(NSString *key, id node, + BOOL *stop) { + id child = [otherChildrenNode getImmediateChild:key]; + if (![child isEqual:node]) { + equal = NO; + *stop = YES; + } + }]; + return equal; + } else { + return NO; + } + } +} + +- (NSUInteger)hash { + __block NSUInteger hashCode = 0; + [self enumerateChildrenUsingBlock:^(NSString *key, id node, + BOOL *stop) { + hashCode = 31 * hashCode + key.hash; + hashCode = 17 * hashCode + node.hash; + }]; + return 17 * hashCode + self.priorityNode.hash; +} + +- (void)enumerateChildrenAndPriorityUsingBlock:(void (^)(NSString *, id, + BOOL *))block { + if ([self.getPriority isEmpty]) { + [self enumerateChildrenUsingBlock:block]; + } else { + __block BOOL passedPriorityKey = NO; + [self enumerateChildrenUsingBlock:^(NSString *key, id node, + BOOL *stop) { + if (!passedPriorityKey && + [FUtilities compareKey:key + toKey:@".priority"] == NSOrderedDescending) { + passedPriorityKey = YES; + BOOL stopAfterPriority = NO; + block(@".priority", [self getPriority], &stopAfterPriority); + if (stopAfterPriority) + return; + } + block(key, node, stop); + }]; + } +} + +- (void)enumerateChildrenUsingBlock:(void (^)(NSString *, id, + BOOL *))block { + [self.children enumerateKeysAndObjectsUsingBlock:block]; +} + +- (void)enumerateChildrenReverse:(BOOL)reverse + usingBlock: + (void (^)(NSString *, id, BOOL *))block { + [self.children enumerateKeysAndObjectsReverse:reverse usingBlock:block]; +} + +- (NSEnumerator *)childEnumerator { + return [[FTransformedEnumerator alloc] + initWithEnumerator:self.children.keyEnumerator + andTransform:^id(NSString *key) { + return [FNamedNode nodeWithName:key + node:[self getImmediateChild:key]]; + }]; +} + +- (NSString *)predecessorChildKey:(NSString *)childKey { + return [self.children getPredecessorKey:childKey]; +} + +#pragma mark - +#pragma mark FChildrenNode specific methods + +- (id)childrenGetter:(id)key { + return [self.children objectForKey:key]; +} + +- (FNamedNode *)firstChild { + NSString *childKey = self.children.minKey; + if (childKey) { + return + [[FNamedNode alloc] initWithName:childKey + andNode:[self getImmediateChild:childKey]]; + } else { + return nil; + } +} + +- (FNamedNode *)lastChild { + NSString *childKey = self.children.maxKey; + if (childKey) { + return + [[FNamedNode alloc] initWithName:childKey + andNode:[self getImmediateChild:childKey]]; + } else { + return nil; + } +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FCompoundWrite.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FCompoundWrite.h new file mode 100644 index 0000000..47de66f --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FCompoundWrite.h @@ -0,0 +1,64 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FImmutableTree; +@protocol FNode; +@class FPath; + +/** + * This class holds a collection of writes that can be applied to nodes in + * unison. It abstracts away the logic with dealing with priority writes and + * multiple nested writes. At any given path, there is only allowed to be one + * write modifying that path. Any write to an existing path or shadowing an + * existing path will modify that existing write to reflect the write added. + */ +@interface FCompoundWrite : NSObject + +- (id)initWithWriteTree:(FImmutableTree *)tree; + +/** + * Creates a compound write with NSDictionary from path string to object + */ ++ (FCompoundWrite *)compoundWriteWithValueDictionary:(NSDictionary *)dictionary; +/** + * Creates a compound write with NSDictionary from path string to node + */ ++ (FCompoundWrite *)compoundWriteWithNodeDictionary:(NSDictionary *)dictionary; + ++ (FCompoundWrite *)emptyWrite; + +- (FCompoundWrite *)addWrite:(id)node atPath:(FPath *)path; +- (FCompoundWrite *)addWrite:(id)node atKey:(NSString *)key; +- (FCompoundWrite *)addCompoundWrite:(FCompoundWrite *)node + atPath:(FPath *)path; +- (FCompoundWrite *)removeWriteAtPath:(FPath *)path; +- (id)rootWrite; +- (BOOL)hasCompleteWriteAtPath:(FPath *)path; +- (id)completeNodeAtPath:(FPath *)path; +- (NSArray *)completeChildren; +- (NSDictionary *)childCompoundWrites; +- (FCompoundWrite *)childCompoundWriteAtPath:(FPath *)path; +- (id)applyToNode:(id)node; +- (void)enumerateWrites:(void (^)(FPath *path, id node, + BOOL *stop))block; + +- (NSDictionary *)valForExport:(BOOL)exportFormat; + +- (BOOL)isEmpty; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FCompoundWrite.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FCompoundWrite.m new file mode 100644 index 0000000..bc40b2b --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FCompoundWrite.m @@ -0,0 +1,304 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FCompoundWrite.h" +#import "FImmutableTree.h" +#import "FNamedNode.h" +#import "FNode.h" +#import "FPath.h" +#import "FSnapshotUtilities.h" + +@interface FCompoundWrite () +@property(nonatomic, strong) FImmutableTree *writeTree; +@end + +@implementation FCompoundWrite + +- (id)initWithWriteTree:(FImmutableTree *)tree { + self = [super init]; + if (self) { + self.writeTree = tree; + } + return self; +} + ++ (FCompoundWrite *)compoundWriteWithValueDictionary: + (NSDictionary *)dictionary { + __block FImmutableTree *writeTree = [FImmutableTree empty]; + [dictionary enumerateKeysAndObjectsUsingBlock:^(NSString *pathString, + id value, BOOL *stop) { + id node = [FSnapshotUtilities nodeFrom:value]; + FImmutableTree *tree = [[FImmutableTree alloc] initWithValue:node]; + writeTree = [writeTree setTree:tree + atPath:[[FPath alloc] initWith:pathString]]; + }]; + return [[FCompoundWrite alloc] initWithWriteTree:writeTree]; +} + ++ (FCompoundWrite *)compoundWriteWithNodeDictionary:(NSDictionary *)dictionary { + __block FImmutableTree *writeTree = [FImmutableTree empty]; + [dictionary enumerateKeysAndObjectsUsingBlock:^(NSString *pathString, + id node, BOOL *stop) { + FImmutableTree *tree = [[FImmutableTree alloc] initWithValue:node]; + writeTree = [writeTree setTree:tree + atPath:[[FPath alloc] initWith:pathString]]; + }]; + return [[FCompoundWrite alloc] initWithWriteTree:writeTree]; +} + ++ (FCompoundWrite *)emptyWrite { + static dispatch_once_t pred = 0; + static FCompoundWrite *empty = nil; + dispatch_once(&pred, ^{ + empty = [[FCompoundWrite alloc] + initWithWriteTree:[[FImmutableTree alloc] initWithValue:nil]]; + }); + return empty; +} + +- (FCompoundWrite *)addWrite:(id)node atPath:(FPath *)path { + if (path.isEmpty) { + return [[FCompoundWrite alloc] + initWithWriteTree:[[FImmutableTree alloc] initWithValue:node]]; + } else { + FTuplePathValue *rootMost = + [self.writeTree findRootMostValueAndPath:path]; + if (rootMost != nil) { + FPath *relativePath = [FPath relativePathFrom:rootMost.path + to:path]; + id value = [rootMost.value updateChild:relativePath + withNewChild:node]; + return [[FCompoundWrite alloc] + initWithWriteTree:[self.writeTree setValue:value + atPath:rootMost.path]]; + } else { + FImmutableTree *subtree = + [[FImmutableTree alloc] initWithValue:node]; + FImmutableTree *newWriteTree = [self.writeTree setTree:subtree + atPath:path]; + return [[FCompoundWrite alloc] initWithWriteTree:newWriteTree]; + } + } +} + +- (FCompoundWrite *)addWrite:(id)node atKey:(NSString *)key { + return [self addWrite:node atPath:[[FPath alloc] initWith:key]]; +} + +- (FCompoundWrite *)addCompoundWrite:(FCompoundWrite *)compoundWrite + atPath:(FPath *)path { + __block FCompoundWrite *newWrite = self; + [compoundWrite.writeTree forEach:^(FPath *childPath, id value) { + newWrite = [newWrite addWrite:value atPath:[path child:childPath]]; + }]; + return newWrite; +} + +/** + * Will remove a write at the given path and deeper paths. This will + * not modify a write at a higher location, which must be removed by + * calling this method with that path. + * @param path The path at which a write and all deeper writes should be + * removed. + * @return The new FWriteCompound with the removed path. + */ +- (FCompoundWrite *)removeWriteAtPath:(FPath *)path { + if (path.isEmpty) { + return [FCompoundWrite emptyWrite]; + } else { + FImmutableTree *newWriteTree = + [self.writeTree setTree:[FImmutableTree empty] atPath:path]; + return [[FCompoundWrite alloc] initWithWriteTree:newWriteTree]; + } +} + +/** + * Returns whether this FCompoundWrite will fully overwrite a node at a given + * location and can therefore be considered "complete". + * @param path The path to check for + * @return Whether there is a complete write at that path. + */ +- (BOOL)hasCompleteWriteAtPath:(FPath *)path { + return [self completeNodeAtPath:path] != nil; +} + +/** + * Returns a node for a path if and only if the node is a "complete" overwrite + * at that path. This will not aggregate writes from depeer paths, but will + * return child nodes from a more shallow path. + * @param path The path to get a complete write + * @return The node if complete at that path, or nil otherwise. + */ +- (id)completeNodeAtPath:(FPath *)path { + FTuplePathValue *rootMost = [self.writeTree findRootMostValueAndPath:path]; + if (rootMost != nil) { + FPath *relativePath = [FPath relativePathFrom:rootMost.path to:path]; + return [rootMost.value getChild:relativePath]; + } else { + return nil; + } +} + +// TODO: change into traversal method... +- (NSArray *)completeChildren { + NSMutableArray *children = [[NSMutableArray alloc] init]; + if (self.writeTree.value != nil) { + id node = self.writeTree.value; + [node enumerateChildrenUsingBlock:^(NSString *key, id node, + BOOL *stop) { + [children addObject:[[FNamedNode alloc] initWithName:key + andNode:node]]; + }]; + } else { + [self.writeTree.children + enumerateKeysAndObjectsUsingBlock:^( + NSString *childKey, FImmutableTree *childTree, BOOL *stop) { + if (childTree.value != nil) { + [children addObject:[[FNamedNode alloc] + initWithName:childKey + andNode:childTree.value]]; + } + }]; + } + return children; +} + +// TODO: change into enumarate method +- (NSDictionary *)childCompoundWrites { + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + [self.writeTree.children + enumerateKeysAndObjectsUsingBlock:^( + NSString *key, FImmutableTree *childWrite, BOOL *stop) { + dict[key] = [[FCompoundWrite alloc] initWithWriteTree:childWrite]; + }]; + return dict; +} + +- (FCompoundWrite *)childCompoundWriteAtPath:(FPath *)path { + if (path.isEmpty) { + return self; + } else { + id shadowingNode = [self completeNodeAtPath:path]; + if (shadowingNode != nil) { + return [[FCompoundWrite alloc] + initWithWriteTree:[[FImmutableTree alloc] + initWithValue:shadowingNode]]; + } else { + return [[FCompoundWrite alloc] + initWithWriteTree:[self.writeTree subtreeAtPath:path]]; + } + } +} + +- (id)applySubtreeWrite:(FImmutableTree *)subtreeWrite + atPath:(FPath *)relativePath + toNode:(id)node { + if (subtreeWrite.value != nil) { + // Since a write there is always a leaf, we're done here. + return [node updateChild:relativePath withNewChild:subtreeWrite.value]; + } else { + __block id priorityWrite = nil; + __block id blockNode = node; + [subtreeWrite.children + enumerateKeysAndObjectsUsingBlock:^( + NSString *childKey, FImmutableTree *childTree, BOOL *stop) { + if ([childKey isEqualToString:@".priority"]) { + // Apply priorities at the end so we don't update priorities + // for either empty nodes or forget to apply priorities to + // empty nodes that are later filled. + NSAssert(childTree.value != nil, + @"Priority writes must always be leaf nodes"); + priorityWrite = childTree.value; + } else { + blockNode = [self + applySubtreeWrite:childTree + atPath:[relativePath childFromString:childKey] + toNode:blockNode]; + } + }]; + // If there was a priority write, we only apply it if the node is not + // empty + if (![blockNode getChild:relativePath].isEmpty && + priorityWrite != nil) { + blockNode = [blockNode + updateChild:[relativePath childFromString:@".priority"] + withNewChild:priorityWrite]; + } + return blockNode; + } +} + +- (void)enumerateWrites:(void (^)(FPath *, id, BOOL *))block { + __block BOOL stop = NO; + // TODO: add stop to tree iterator... + [self.writeTree forEach:^(FPath *path, id value) { + if (!stop) { + block(path, value, &stop); + } + }]; +} + +/** + * Applies this FCompoundWrite to a node. The node is returned with all writes + * from this FCompoundWrite applied to the node. + * @param node The node to apply this FCompoundWrite to + * @return The node with all writes applied + */ +- (id)applyToNode:(id)node { + return [self applySubtreeWrite:self.writeTree + atPath:[FPath empty] + toNode:node]; +} + +/** + * Return true if this CompoundWrite is empty and therefore does not modify any + * nodes. + * @return Whether this CompoundWrite is empty + */ +- (BOOL)isEmpty { + return self.writeTree.isEmpty; +} + +- (id)rootWrite { + return self.writeTree.value; +} + +- (BOOL)isEqual:(id)object { + if (![object isKindOfClass:[FCompoundWrite class]]) { + return NO; + } + FCompoundWrite *other = (FCompoundWrite *)object; + return + [[self valForExport:YES] isEqualToDictionary:[other valForExport:YES]]; +} + +- (NSUInteger)hash { + return [[self valForExport:YES] hash]; +} + +- (NSDictionary *)valForExport:(BOOL)exportFormat { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + [self.writeTree forEach:^(FPath *path, id value) { + dictionary[path.wireFormat] = [value valForExport:exportFormat]; + }]; + return dictionary; +} + +- (NSString *)description { + return [[self valForExport:YES] description]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FEmptyNode.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FEmptyNode.h new file mode 100644 index 0000000..d002df0 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FEmptyNode.h @@ -0,0 +1,24 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FNode.h" +#import + +@interface FEmptyNode : NSObject + ++ (id)emptyNode; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FEmptyNode.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FEmptyNode.m new file mode 100644 index 0000000..bde3a8a --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FEmptyNode.m @@ -0,0 +1,30 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FEmptyNode.h" +#import "FChildrenNode.h" + +@implementation FEmptyNode + ++ (id)emptyNode { + static FChildrenNode *empty = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + empty = [[FChildrenNode alloc] init]; + }); + return empty; +} +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FIndexedNode.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FIndexedNode.h new file mode 100644 index 0000000..b161dea --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FIndexedNode.h @@ -0,0 +1,55 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIndex.h" +#import "FNamedNode.h" +#import "FNode.h" + +/** + * Represents a node together with an index. The index and node are updated in + * unison. In the case where the index does not affect the ordering (i.e. the + * ordering is identical to the key ordering) this class uses a fallback index + * to save memory. Everything operating on the index must special case the + * fallback index. + */ +@interface FIndexedNode : NSObject + +@property(nonatomic, strong, readonly) id node; + ++ (FIndexedNode *)indexedNodeWithNode:(id)node; ++ (FIndexedNode *)indexedNodeWithNode:(id)node index:(id)index; + +- (BOOL)hasIndex:(id)index; +- (FIndexedNode *)updateChild:(NSString *)key + withNewChild:(id)newChildNode; +- (FIndexedNode *)updatePriority:(id)priority; + +- (FNamedNode *)firstChild; +- (FNamedNode *)lastChild; + +- (NSString *)predecessorForChildKey:(NSString *)childKey + childNode:(id)childNode + index:(id)index; + +- (void)enumerateChildrenReverse:(BOOL)reverse + usingBlock:(void (^)(NSString *key, id node, + BOOL *stop))block; + +- (NSEnumerator *)childEnumerator; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FIndexedNode.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FIndexedNode.m new file mode 100644 index 0000000..dafe9c3 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FIndexedNode.m @@ -0,0 +1,218 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIndexedNode.h" + +#import "FChildrenNode.h" +#import "FImmutableSortedSet.h" +#import "FIndex.h" +#import "FKeyIndex.h" +#import "FPriorityIndex.h" + +static FImmutableSortedSet *FALLBACK_INDEX; + +@interface FIndexedNode () + +@property(nonatomic, strong) id node; +/** + * The indexed set is initialized lazily to prevent creation when it is not + * needed + */ +@property(nonatomic, strong) FImmutableSortedSet *indexed; +@property(nonatomic, strong) id index; + +@end + +@implementation FIndexedNode + ++ (FImmutableSortedSet *)fallbackIndex { + static FImmutableSortedSet *fallbackIndex; + static dispatch_once_t once; + dispatch_once(&once, ^{ + fallbackIndex = [[FImmutableSortedSet alloc] init]; + }); + return fallbackIndex; +} + ++ (FIndexedNode *)indexedNodeWithNode:(id)node { + return [[FIndexedNode alloc] initWithNode:node + index:[FPriorityIndex priorityIndex]]; +} + ++ (FIndexedNode *)indexedNodeWithNode:(id)node index:(id)index { + return [[FIndexedNode alloc] initWithNode:node index:index]; +} + +- (id)initWithNode:(id)node index:(id)index { + // Initialize indexed lazily + return [self initWithNode:node index:index indexed:nil]; +} + +- (id)initWithNode:(id)node + index:(id)index + indexed:(FImmutableSortedSet *)indexed { + self = [super init]; + if (self != nil) { + self->_node = node; + self->_index = index; + self->_indexed = indexed; + } + return self; +} + +- (void)ensureIndexed { + if (!self.indexed) { + if ([self.index isEqual:[FKeyIndex keyIndex]]) { + self.indexed = [FIndexedNode fallbackIndex]; + } else { + __block BOOL sawChild = NO; + [self.node enumerateChildrenUsingBlock:^( + NSString *key, id node, BOOL *stop) { + sawChild = sawChild || [self.index isDefinedOn:node]; + *stop = sawChild; + }]; + if (sawChild) { + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + [self.node enumerateChildrenUsingBlock:^( + NSString *key, id node, BOOL *stop) { + FNamedNode *namedNode = + [[FNamedNode alloc] initWithName:key andNode:node]; + dict[namedNode] = [NSNull null]; + }]; + // Make sure to assign index here, because the comparator will + // be retained and using self will cause a cycle + id index = self.index; + self.indexed = [FImmutableSortedSet + setWithKeysFromDictionary:dict + comparator:^NSComparisonResult( + FNamedNode *namedNode1, + FNamedNode *namedNode2) { + return [index compareNamedNode:namedNode1 + toNamedNode:namedNode2]; + }]; + } else { + self.indexed = [FIndexedNode fallbackIndex]; + } + } + } +} + +- (BOOL)hasIndex:(id)index { + return [self.index isEqual:index]; +} + +- (FIndexedNode *)updateChild:(NSString *)key + withNewChild:(id)newChildNode { + id newNode = [self.node updateImmediateChild:key + withNewChild:newChildNode]; + if (self.indexed == [FIndexedNode fallbackIndex] && + ![self.index isDefinedOn:newChildNode]) { + // doesn't affect the index, no need to create an index + return [[FIndexedNode alloc] initWithNode:newNode + index:self.index + indexed:[FIndexedNode fallbackIndex]]; + } else if (!self.indexed || self.indexed == [FIndexedNode fallbackIndex]) { + // No need to index yet, index lazily + return [[FIndexedNode alloc] initWithNode:newNode index:self.index]; + } else { + id oldChild = [self.node getImmediateChild:key]; + FImmutableSortedSet *newIndexed = [self.indexed + removeObject:[FNamedNode nodeWithName:key node:oldChild]]; + if (![newChildNode isEmpty]) { + newIndexed = [newIndexed + addObject:[FNamedNode nodeWithName:key node:newChildNode]]; + } + return [[FIndexedNode alloc] initWithNode:newNode + index:self.index + indexed:newIndexed]; + } +} + +- (FIndexedNode *)updatePriority:(id)priority { + return + [[FIndexedNode alloc] initWithNode:[self.node updatePriority:priority] + index:self.index + indexed:self.indexed]; +} + +- (FNamedNode *)firstChild { + if (![self.node isKindOfClass:[FChildrenNode class]]) { + return nil; + } else { + [self ensureIndexed]; + if (self.indexed == [FIndexedNode fallbackIndex]) { + return [((FChildrenNode *)self.node) firstChild]; + } else { + return self.indexed.firstObject; + } + } +} + +- (FNamedNode *)lastChild { + if (![self.node isKindOfClass:[FChildrenNode class]]) { + return nil; + } else { + [self ensureIndexed]; + if (self.indexed == [FIndexedNode fallbackIndex]) { + return [((FChildrenNode *)self.node) lastChild]; + } else { + return self.indexed.lastObject; + } + } +} + +- (NSString *)predecessorForChildKey:(NSString *)childKey + childNode:(id)childNode + index:(id)index { + if (![self.index isEqual:index]) { + [NSException raise:NSInvalidArgumentException + format:@"Index not available in IndexedNode!"]; + } + [self ensureIndexed]; + if (self.indexed == [FIndexedNode fallbackIndex]) { + return [self.node predecessorChildKey:childKey]; + } else { + FNamedNode *node = [self.indexed + predecessorEntry:[FNamedNode nodeWithName:childKey node:childNode]]; + return node.name; + } +} + +- (void)enumerateChildrenReverse:(BOOL)reverse + usingBlock: + (void (^)(NSString *, id, BOOL *))block { + [self ensureIndexed]; + if (self.indexed == [FIndexedNode fallbackIndex]) { + [self.node enumerateChildrenReverse:reverse usingBlock:block]; + } else { + [self.indexed + enumerateObjectsReverse:reverse + usingBlock:^(FNamedNode *namedNode, BOOL *stop) { + block(namedNode.name, namedNode.node, stop); + }]; + } +} + +- (NSEnumerator *)childEnumerator { + [self ensureIndexed]; + if (self.indexed == [FIndexedNode fallbackIndex]) { + return [self.node childEnumerator]; + } else { + return [self.indexed objectEnumerator]; + } +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FLeafNode.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FLeafNode.h new file mode 100644 index 0000000..6bd2862 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FLeafNode.h @@ -0,0 +1,27 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FNode.h" +#import + +@interface FLeafNode : NSObject + +- (id)initWithValue:(id)aValue; +- (id)initWithValue:(id)aValue withPriority:(id)aPriority; + +@property(nonatomic, strong) id value; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FLeafNode.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FLeafNode.m new file mode 100644 index 0000000..e741fd9 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FLeafNode.m @@ -0,0 +1,266 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FLeafNode.h" +#import "FChildrenNode.h" +#import "FConstants.h" +#import "FEmptyNode.h" +#import "FImmutableSortedDictionary.h" +#import "FSnapshotUtilities.h" +#import "FStringUtilities.h" +#import "FUtilities.h" + +@interface FLeafNode () +@property(nonatomic, strong) id priorityNode; +@property(nonatomic, strong) NSString *lazyHash; + +@end + +@implementation FLeafNode + +@synthesize value; +@synthesize priorityNode; + +- (id)initWithValue:(id)aValue { + self = [super init]; + if (self) { + self.value = aValue; + self.priorityNode = [FEmptyNode emptyNode]; + } + return self; +} + +- (id)initWithValue:(id)aValue withPriority:(id)aPriority { + self = [super init]; + if (self) { + self.value = aValue; + [FSnapshotUtilities validatePriorityNode:aPriority]; + self.priorityNode = aPriority; + } + return self; +} + +#pragma mark - +#pragma mark FNode methods + +- (BOOL)isLeafNode { + return YES; +} + +- (id)getPriority { + return self.priorityNode; +} + +- (id)updatePriority:(id)aPriority { + return [[FLeafNode alloc] initWithValue:self.value withPriority:aPriority]; +} + +- (id)getImmediateChild:(NSString *)childName { + if ([childName isEqualToString:@".priority"]) { + return self.priorityNode; + } else { + return [FEmptyNode emptyNode]; + } +} + +- (id)getChild:(FPath *)path { + if (path.getFront == nil) { + return self; + } else if ([[path getFront] isEqualToString:@".priority"]) { + return [self getPriority]; + } else { + return [FEmptyNode emptyNode]; + } +} + +- (BOOL)hasChild:(NSString *)childName { + return + [childName isEqualToString:@".priority"] && ![self getPriority].isEmpty; +} + +- (NSString *)predecessorChildKey:(NSString *)childKey { + return nil; +} + +- (id)updateImmediateChild:(NSString *)childName + withNewChild:(id)newChildNode { + if ([childName isEqualToString:@".priority"]) { + return [self updatePriority:newChildNode]; + } else if (newChildNode.isEmpty) { + return self; + } else { + FChildrenNode *childrenNode = [[FChildrenNode alloc] init]; + childrenNode = [childrenNode updateImmediateChild:childName + withNewChild:newChildNode]; + childrenNode = [childrenNode updatePriority:self.priorityNode]; + return childrenNode; + } +} + +- (id)updateChild:(FPath *)path withNewChild:(id)newChildNode { + NSString *front = [path getFront]; + if (front == nil) { + return newChildNode; + } else if (newChildNode.isEmpty && ![front isEqualToString:@".priority"]) { + return self; + } else { + NSAssert(![front isEqualToString:@".priority"] || path.length == 1, + @".priority must be the last token in a path."); + return [self updateImmediateChild:front + withNewChild:[[FEmptyNode emptyNode] + updateChild:[path popFront] + withNewChild:newChildNode]]; + } +} + +- (id)val { + return [self valForExport:NO]; +} + +- (id)valForExport:(BOOL)exp { + if (exp && !self.getPriority.isEmpty) { + return @{ + kPayloadValue : self.value, + kPayloadPriority : [[self getPriority] val] + }; + } else { + return self.value; + } +} + +- (BOOL)isEqual:(id)other { + if (other == self) { + return YES; + } else if (other.isLeafNode) { + FLeafNode *otherLeaf = other; + if ([FUtilities getJavascriptType:self.value] != + [FUtilities getJavascriptType:otherLeaf.value]) { + return NO; + } + return [otherLeaf.value isEqual:self.value] && + [otherLeaf.priorityNode isEqual:self.priorityNode]; + } else { + return NO; + } +} + +- (NSUInteger)hash { + return [self.value hash] * 17 + self.priorityNode.hash; +} + +- (id)withIndex:(id)index { + return self; +} + +- (BOOL)isIndexed:(id)index { + return YES; +} + +- (BOOL)isEmpty { + return NO; +} + +- (int)numChildren { + return 0; +} + +- (void)enumerateChildrenUsingBlock:(void (^)(NSString *, id, + BOOL *))block { + // Nothing to iterate over +} + +- (void)enumerateChildrenReverse:(BOOL)reverse + usingBlock: + (void (^)(NSString *, id, BOOL *))block { + // Nothing to iterate over +} + +- (NSEnumerator *)childEnumerator { + // Nothing to iterate over + return [@[] objectEnumerator]; +} + +- (NSString *)dataHash { + if (self.lazyHash == nil) { + NSMutableString *toHash = [[NSMutableString alloc] init]; + [FSnapshotUtilities + appendHashRepresentationForLeafNode:self + toString:toHash + hashVersion:FDataHashVersionV1]; + + self.lazyHash = [FStringUtilities base64EncodedSha1:toHash]; + } + return self.lazyHash; +} + +- (NSComparisonResult)compare:(id)other { + if (other == [FEmptyNode emptyNode]) { + return NSOrderedDescending; + } else if ([other isKindOfClass:[FChildrenNode class]]) { + return NSOrderedAscending; + } else { + NSAssert(other.isLeafNode, @"Compared against unknown type of node."); + return [self compareToLeafNode:(FLeafNode *)other]; + } +} + ++ (NSArray *)valueTypeOrder { + static NSArray *valueOrder = nil; + static dispatch_once_t once; + dispatch_once(&once, ^{ + valueOrder = @[ + kJavaScriptObject, kJavaScriptBoolean, kJavaScriptNumber, + kJavaScriptString + ]; + }); + return valueOrder; +} + +- (NSComparisonResult)compareToLeafNode:(FLeafNode *)other { + NSString *thisLeafType = [FUtilities getJavascriptType:self.value]; + NSString *otherLeafType = [FUtilities getJavascriptType:other.value]; + NSUInteger thisIndex = + [[FLeafNode valueTypeOrder] indexOfObject:thisLeafType]; + NSUInteger otherIndex = + [[FLeafNode valueTypeOrder] indexOfObject:otherLeafType]; + assert(thisIndex >= 0 && otherIndex >= 0); + if (otherIndex == thisIndex) { + // Same type. Compare values. + if (thisLeafType == kJavaScriptObject) { + // Deferred value nodes are all equal, but we should also never get + // to this point... + return NSOrderedSame; + } else if (thisLeafType == kJavaScriptString) { + return [self.value compare:other.value options:NSLiteralSearch]; + } else { + return [self.value compare:other.value]; + } + } else { + return thisIndex > otherIndex ? NSOrderedDescending + : NSOrderedAscending; + } +} + +- (NSString *)description { + return [[self valForExport:YES] description]; +} + +- (void)forEachChildDo:(fbt_bool_nsstring_node)action { + // There are no children, so there is nothing to do. + return; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FNode.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FNode.h new file mode 100644 index 0000000..ea251b6 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FNode.h @@ -0,0 +1,50 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FPath.h" +#import "FTypedefs_Private.h" +#import + +@protocol FIndex; + +@protocol FNode + +- (BOOL)isLeafNode; +- (id)getPriority; +- (id)updatePriority:(id)priority; +- (id)getImmediateChild:(NSString *)childKey; +- (id)getChild:(FPath *)path; +- (NSString *)predecessorChildKey:(NSString *)childKey; +- (id)updateImmediateChild:(NSString *)childKey + withNewChild:(id)newChildNode; +- (id)updateChild:(FPath *)path withNewChild:(id)newChildNode; +- (BOOL)hasChild:(NSString *)childKey; +- (BOOL)isEmpty; +- (int)numChildren; +- (id)val; +- (id)valForExport:(BOOL)exp; +- (NSString *)dataHash; +- (NSComparisonResult)compare:(id)other; +- (BOOL)isEqual:(id)other; +- (void)enumerateChildrenUsingBlock:(void (^)(NSString *key, id node, + BOOL *stop))block; +- (void)enumerateChildrenReverse:(BOOL)reverse + usingBlock:(void (^)(NSString *key, id node, + BOOL *stop))block; + +- (NSEnumerator *)childEnumerator; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FSnapshotUtilities.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FSnapshotUtilities.h new file mode 100644 index 0000000..bf44660 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FSnapshotUtilities.h @@ -0,0 +1,49 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FNode.h" +#import + +@class FImmutableSortedDictionary; +@class FCompoundWrite; +@class FLeafNode; +@protocol FNode; + +typedef NS_ENUM(NSInteger, FDataHashVersion) { + FDataHashVersionV1, + FDataHashVersionV2, +}; + +@interface FSnapshotUtilities : NSObject + ++ (id)nodeFrom:(id)val; ++ (id)nodeFrom:(id)val priority:(id)priority; ++ (id)nodeFrom:(id)val withValidationFrom:(NSString *)fn; ++ (id)nodeFrom:(id)val + priority:(id)priority + withValidationFrom:(NSString *)fn; ++ (FCompoundWrite *)compoundWriteFromDictionary:(NSDictionary *)values + withValidationFrom:(NSString *)fn; ++ (void)validatePriorityNode:(id)priorityNode; ++ (void)appendHashRepresentationForLeafNode:(FLeafNode *)val + toString:(NSMutableString *)string + hashVersion:(FDataHashVersion)hashVersion; ++ (void)appendHashV2RepresentationForString:(NSString *)string + toString:(NSMutableString *)mutableString; + ++ (NSUInteger)estimateSerializedNodeSize:(id)node; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FSnapshotUtilities.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FSnapshotUtilities.m new file mode 100644 index 0000000..f3f9ed5 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Snapshot/FSnapshotUtilities.m @@ -0,0 +1,394 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FSnapshotUtilities.h" +#import "FChildrenNode.h" +#import "FCompoundWrite.h" +#import "FConstants.h" +#import "FEmptyNode.h" +#import "FLLRBValueNode.h" +#import "FLeafNode.h" +#import "FMaxNode.h" +#import "FNamedNode.h" +#import "FUtilities.h" +#import "FValidation.h" + +@implementation FSnapshotUtilities + ++ (id)nodeFrom:(id)val { + return [FSnapshotUtilities nodeFrom:val priority:nil]; +} + ++ (id)nodeFrom:(id)val priority:(id)priority { + return [FSnapshotUtilities nodeFrom:val + priority:priority + withValidationFrom:@"nodeFrom:priority:"]; +} + ++ (id)nodeFrom:(id)val withValidationFrom:(NSString *)fn { + return [FSnapshotUtilities nodeFrom:val priority:nil withValidationFrom:fn]; +} + ++ (id)nodeFrom:(id)val + priority:(id)priority + withValidationFrom:(NSString *)fn { + return [FSnapshotUtilities nodeFrom:val + priority:priority + withValidationFrom:fn + atDepth:0 + path:[[NSMutableArray alloc] init]]; +} + ++ (id)nodeFrom:(id)val + priority:(id)aPriority + withValidationFrom:(NSString *)fn + atDepth:(int)depth + path:(NSMutableArray *)path { + @autoreleasepool { + return [FSnapshotUtilities internalNodeFrom:val + priority:aPriority + withValidationFrom:fn + atDepth:depth + path:path]; + } +} + ++ (id)internalNodeFrom:(id)val + priority:(id)aPriority + withValidationFrom:(NSString *)fn + atDepth:(int)depth + path:(NSMutableArray *)path { + + if (depth > kFirebaseMaxObjectDepth) { + NSRange range; + range.location = 0; + range.length = 100; + NSString *pathString = + [[path subarrayWithRange:range] componentsJoinedByString:@"."]; + @throw [[NSException alloc] + initWithName:@"InvalidFirebaseData" + reason:[NSString stringWithFormat: + @"(%@) Max object depth exceeded: %@...", + fn, pathString] + userInfo:nil]; + } + + if (val == nil || val == [NSNull null]) { + // Null is a valid type to store + return [FEmptyNode emptyNode]; + } + + [FValidation validateFrom:fn isValidPriorityValue:aPriority withPath:path]; + id priority = [FSnapshotUtilities nodeFrom:aPriority]; + + id value = val; + BOOL isLeafNode = NO; + + if ([value isKindOfClass:[NSDictionary class]]) { + NSDictionary *dict = val; + if (dict[kPayloadPriority] != nil) { + id rawPriority = [dict objectForKey:kPayloadPriority]; + [FValidation validateFrom:fn + isValidPriorityValue:rawPriority + withPath:path]; + priority = [FSnapshotUtilities nodeFrom:rawPriority]; + } + + if (dict[kPayloadValue] != nil) { + value = [dict objectForKey:kPayloadValue]; + if ([FValidation validateFrom:fn + isValidLeafValue:value + withPath:path]) { + isLeafNode = YES; + } else { + @throw [[NSException alloc] + initWithName:@"InvalidLeafValueType" + reason:[NSString stringWithFormat: + @"(%@) Invalid data type used " + @"with .value. Can only use " + "NSString and NSNumber or be " + "null. Found %@ instead.", + fn, [[value class] description]] + userInfo:nil]; + } + } + } + + if ([FValidation validateFrom:fn isValidLeafValue:value withPath:path]) { + isLeafNode = YES; + } + + if (isLeafNode) { + return [[FLeafNode alloc] initWithValue:value withPriority:priority]; + } + + // Unlike with JS, we have to handle the dictionary and array cases + // separately. + if ([value isKindOfClass:[NSDictionary class]]) { + NSDictionary *dval = (NSDictionary *)value; + NSMutableDictionary *children = + [NSMutableDictionary dictionaryWithCapacity:dval.count]; + + // Avoid creating a million newPaths by appending to old one + for (id keyId in dval) { + [FValidation validateFrom:fn + validDictionaryKey:keyId + withPath:path]; + NSString *key = (NSString *)keyId; + + if (![key hasPrefix:kPayloadMetadataPrefix]) { + [path addObject:key]; + id childNode = [FSnapshotUtilities nodeFrom:dval[key] + priority:nil + withValidationFrom:fn + atDepth:depth + 1 + path:path]; + [path removeLastObject]; + + if (![childNode isEmpty]) { + children[key] = childNode; + } + } + } + + if ([children count] == 0) { + return [FEmptyNode emptyNode]; + } else { + FImmutableSortedDictionary *childrenDict = + [FImmutableSortedDictionary + fromDictionary:children + withComparator:[FUtilities keyComparator]]; + return [[FChildrenNode alloc] initWithPriority:priority + children:childrenDict]; + } + } else if ([value isKindOfClass:[NSArray class]]) { + NSArray *aval = (NSArray *)value; + NSMutableDictionary *children = + [NSMutableDictionary dictionaryWithCapacity:aval.count]; + + for (int i = 0; i < [aval count]; i++) { + NSString *key = [NSString stringWithFormat:@"%i", i]; + [path addObject:key]; + id childNode = + [FSnapshotUtilities nodeFrom:[aval objectAtIndex:i] + priority:nil + withValidationFrom:fn + atDepth:depth + 1 + path:path]; + [path removeLastObject]; + + if (![childNode isEmpty]) { + children[key] = childNode; + } + } + + if ([children count] == 0) { + return [FEmptyNode emptyNode]; + } else { + FImmutableSortedDictionary *childrenDict = + [FImmutableSortedDictionary + fromDictionary:children + withComparator:[FUtilities keyComparator]]; + return [[FChildrenNode alloc] initWithPriority:priority + children:childrenDict]; + } + } else { + NSRange range; + range.location = 0; + range.length = MIN(path.count, 50); + NSString *pathString = + [[path subarrayWithRange:range] componentsJoinedByString:@"."]; + + @throw [[NSException alloc] + initWithName:@"InvalidFirebaseData" + reason:[NSString + stringWithFormat: + @"(%@) Cannot store object of type %@ at %@. " + "Can only store objects of type NSNumber, " + "NSString, NSDictionary, and NSArray.", + fn, [[value class] description], pathString] + userInfo:nil]; + } +} + ++ (FCompoundWrite *)compoundWriteFromDictionary:(NSDictionary *)values + withValidationFrom:(NSString *)fn { + FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; + + NSMutableArray *updatePaths = + [NSMutableArray arrayWithCapacity:values.count]; + for (NSString *keyId in values) { + id value = values[keyId]; + [FValidation validateFrom:fn + validUpdateDictionaryKey:keyId + withValue:value]; + + FPath *path = [FPath pathWithString:keyId]; + id node = [FSnapshotUtilities nodeFrom:value + withValidationFrom:fn]; + + [updatePaths addObject:path]; + compoundWrite = [compoundWrite addWrite:node atPath:path]; + } + + // Check that the update paths are not descendants of each other. + [updatePaths + sortUsingComparator:^NSComparisonResult(FPath *left, FPath *right) { + return [left compare:right]; + }]; + FPath *prevPath = nil; + for (FPath *path in updatePaths) { + if (prevPath != nil && [prevPath contains:path]) { + @throw [[NSException alloc] + initWithName:@"InvalidFirebaseData" + reason:[NSString stringWithFormat: + @"(%@) Invalid path in object. Path " + @"(%@) is an ancestor of (%@).", + fn, prevPath, path] + userInfo:nil]; + } + prevPath = path; + } + + return compoundWrite; +} + ++ (void)validatePriorityNode:(id)priorityNode { + assert(priorityNode != nil); + if (priorityNode.isLeafNode) { + id val = priorityNode.val; + if ([val isKindOfClass:[NSDictionary class]]) { + NSDictionary *valDict __unused = (NSDictionary *)val; + NSAssert(valDict[kServerValueSubKey] != nil, + @"Priority can't be object unless it's a deferred value"); + } else { + NSString *jsType __unused = [FUtilities getJavascriptType:val]; + NSAssert(jsType == kJavaScriptString || jsType == kJavaScriptNumber, + @"Priority of unexpected type."); + } + } else { + NSAssert(priorityNode == [FMaxNode maxNode] || priorityNode.isEmpty, + @"Priority of unexpected type."); + } + // Don't call getPriority() on MAX_NODE to avoid hitting assertion. + NSAssert(priorityNode == [FMaxNode maxNode] || + priorityNode.getPriority.isEmpty, + @"Priority nodes can't have a priority of their own."); +} + ++ (void)appendHashRepresentationForLeafNode:(FLeafNode *)leafNode + toString:(NSMutableString *)string + hashVersion:(FDataHashVersion)hashVersion { + NSAssert(hashVersion == FDataHashVersionV1 || + hashVersion == FDataHashVersionV2, + @"Unknown hash version: %lu", (unsigned long)hashVersion); + if (!leafNode.getPriority.isEmpty) { + [string appendString:@"priority:"]; + [FSnapshotUtilities + appendHashRepresentationForLeafNode:leafNode.getPriority + toString:string + hashVersion:hashVersion]; + [string appendString:@":"]; + } + + NSString *jsType = [FUtilities getJavascriptType:leafNode.val]; + [string appendString:jsType]; + [string appendString:@":"]; + + if (jsType == kJavaScriptBoolean) { + NSString *boolString = + [leafNode.val boolValue] ? kJavaScriptTrue : kJavaScriptFalse; + [string appendString:boolString]; + } else if (jsType == kJavaScriptNumber) { + NSString *numberString = + [FUtilities ieee754StringForNumber:leafNode.val]; + [string appendString:numberString]; + } else if (jsType == kJavaScriptString) { + if (hashVersion == FDataHashVersionV1) { + [string appendString:leafNode.val]; + } else { + NSAssert(hashVersion == FDataHashVersionV2, + @"Invalid hash version found"); + [FSnapshotUtilities appendHashV2RepresentationForString:leafNode.val + toString:string]; + } + } else { + [NSException raise:NSInvalidArgumentException + format:@"Unknown value for hashing: %@", leafNode]; + } +} + ++ (void)appendHashV2RepresentationForString:(NSString *)string + toString:(NSMutableString *)mutableString { + string = [string stringByReplacingOccurrencesOfString:@"\\" + withString:@"\\\\"]; + string = [string stringByReplacingOccurrencesOfString:@"\"" + withString:@"\\\""]; + [mutableString appendString:@"\""]; + [mutableString appendString:string]; + [mutableString appendString:@"\""]; +} + ++ (NSUInteger)estimateLeafNodeSize:(FLeafNode *)leafNode { + NSString *jsType = [FUtilities getJavascriptType:leafNode.val]; + // These values are somewhat arbitrary, but we don't need an exact value so + // prefer performance over exact value + NSUInteger valueSize; + if (jsType == kJavaScriptNumber) { + valueSize = 8; // estimate each float with 8 bytes + } else if (jsType == kJavaScriptBoolean) { + valueSize = 4; // true or false need roughly 4 bytes + } else if (jsType == kJavaScriptString) { + valueSize = 2 + [leafNode.val length]; // add 2 for quotes + } else { + [NSException raise:NSInvalidArgumentException + format:@"Unknown leaf type: %@", leafNode]; + return 0; + } + + if (leafNode.getPriority.isEmpty) { + return valueSize; + } else { + // Account for extra overhead due to the extra JSON object and the + // ".value" and ".priority" keys, colons, comma + NSUInteger leafPriorityOverhead = 2 + 8 + 11 + 2 + 1; + return leafPriorityOverhead + valueSize + + [FSnapshotUtilities estimateLeafNodeSize:leafNode.getPriority]; + } +} + ++ (NSUInteger)estimateSerializedNodeSize:(id)node { + if ([node isEmpty]) { + return 4; // null keyword + } else if ([node isLeafNode]) { + return [FSnapshotUtilities estimateLeafNodeSize:node]; + } else { + NSAssert([node isKindOfClass:[FChildrenNode class]], + @"Unexpected node type: %@", [node class]); + __block NSUInteger sum = 1; // opening brackets + [((FChildrenNode *)node) enumerateChildrenAndPriorityUsingBlock:^( + NSString *key, id child, + BOOL *stop) { + sum += key.length; + sum += + 4; // quotes around key and colon and (comma or closing bracket) + sum += [FSnapshotUtilities estimateSerializedNodeSize:child]; + }]; + return sum; + } +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FAtomicNumber.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FAtomicNumber.h new file mode 100644 index 0000000..b8668d2 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FAtomicNumber.h @@ -0,0 +1,23 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@interface FAtomicNumber : NSObject + +- (NSNumber *)getAndIncrement; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FAtomicNumber.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FAtomicNumber.m new file mode 100644 index 0000000..20cfd8d --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FAtomicNumber.m @@ -0,0 +1,55 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FAtomicNumber.h" + +@interface FAtomicNumber () { + unsigned long number; +} + +@property(nonatomic, strong) NSLock *lock; + +@end + +@implementation FAtomicNumber + +@synthesize lock; + +- (id)init { + self = [super init]; + if (self) { + number = 1; + self.lock = [[NSLock alloc] init]; + } + return self; +} + +- (NSNumber *)getAndIncrement { + NSNumber *result; + + // See: + // http://developer.apple.com/library/ios/#DOCUMENTATION/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html#//apple_ref/doc/uid/10000057i-CH8-SW14 + // to improve, etc. + + [self.lock lock]; + result = [NSNumber numberWithUnsignedLong:number]; + number = number + 1; + [self.lock unlock]; + + return result; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FEventEmitter.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FEventEmitter.h new file mode 100644 index 0000000..fc19ca7 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FEventEmitter.h @@ -0,0 +1,36 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRDatabaseConfig.h" +#import "FIRDatabaseQuery.h" +#import "FTypedefs_Private.h" + +@interface FEventEmitter : NSObject + +- (id)initWithAllowedEvents:(NSArray *)theAllowedEvents + queue:(dispatch_queue_t)queue; + +- (id)getInitialEventForType:(NSString *)eventType; +- (void)triggerEventType:(NSString *)eventType data:(id)data; + +- (FIRDatabaseHandle)observeEventType:(NSString *)eventType + withBlock:(fbt_void_id)block; +- (void)removeObserverForEventType:(NSString *)eventType + withHandle:(FIRDatabaseHandle)handle; + +- (void)validateEventType:(NSString *)eventType; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FEventEmitter.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FEventEmitter.m new file mode 100644 index 0000000..7d02239 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FEventEmitter.m @@ -0,0 +1,161 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FEventEmitter.h" +#import "FIRDatabaseQuery_Private.h" +#import "FRepoManager.h" +#import "FUtilities.h" + +@interface FEventListener : NSObject + +@property(nonatomic, copy) fbt_void_id userCallback; +@property(nonatomic) FIRDatabaseHandle handle; + +@end + +@implementation FEventListener + +@synthesize userCallback; +@synthesize handle; + +@end + +@interface FEventEmitter () + +@property(nonatomic, strong) NSArray *allowedEvents; +@property(nonatomic, strong) NSMutableDictionary *listeners; +@property(nonatomic, strong) dispatch_queue_t queue; + +@end + +@implementation FEventEmitter + +@synthesize allowedEvents; +@synthesize listeners; + +- (id)initWithAllowedEvents:(NSArray *)theAllowedEvents + queue:(dispatch_queue_t)queue { + if (theAllowedEvents == nil || [theAllowedEvents count] == 0) { + @throw [NSException + exceptionWithName:@"AllowedEventsValidation" + reason:@"FEventEmitters must be initialized with at " + @"least one valid event." + userInfo:nil]; + } + + self = [super init]; + + if (self) { + self.allowedEvents = [theAllowedEvents copy]; + self.listeners = [[NSMutableDictionary alloc] init]; + self.queue = queue; + } + + return self; +} + +- (id)getInitialEventForType:(NSString *)eventType { + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:@"You must override getInitialEvent: " + @"when subclassing FEventEmitter" + userInfo:nil]; +} + +- (void)triggerEventType:(NSString *)eventType data:(id)data { + [self validateEventType:eventType]; + NSMutableDictionary *eventTypeListeners = + [self.listeners objectForKey:eventType]; + for (FEventListener *listener in eventTypeListeners) { + [self triggerListener:listener withData:data]; + } +} + +- (void)triggerListener:(FEventListener *)listener withData:(id)data { + // TODO, should probably get this from FRepo or something although it ends + // up being the same. (Except maybe for testing) + if (listener.userCallback) { + dispatch_async(self.queue, ^{ + listener.userCallback(data); + }); + } +} + +- (FIRDatabaseHandle)observeEventType:(NSString *)eventType + withBlock:(fbt_void_id)block { + [self validateEventType:eventType]; + + // Create listener + FEventListener *listener = [[FEventListener alloc] init]; + listener.handle = [[FUtilities LUIDGenerator] integerValue]; + listener.userCallback = block; // copies block automatically + + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + [self addEventListener:listener forEventType:eventType]; + }); + + return listener.handle; +} + +- (void)addEventListener:(FEventListener *)listener + forEventType:(NSString *)eventType { + // Get or initializer listeners map [FIRDatabaseHandle -> callback block] + // for eventType + NSMutableArray *eventTypeListeners = + [self.listeners objectForKey:eventType]; + if (eventTypeListeners == nil) { + eventTypeListeners = [[NSMutableArray alloc] init]; + [self.listeners setObject:eventTypeListeners forKey:eventType]; + } + + // Add listener and fire the current event for this listener + [eventTypeListeners addObject:listener]; + id initialData = [self getInitialEventForType:eventType]; + [self triggerListener:listener withData:initialData]; +} + +- (void)removeObserverForEventType:(NSString *)eventType + withHandle:(FIRDatabaseHandle)handle { + [self validateEventType:eventType]; + + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + [self removeEventListenerWithHandle:handle forEventType:eventType]; + }); +} + +- (void)removeEventListenerWithHandle:(FIRDatabaseHandle)handle + forEventType:(NSString *)eventType { + NSMutableArray *eventTypeListeners = + [self.listeners objectForKey:eventType]; + for (FEventListener *listener in [eventTypeListeners copy]) { + if (handle == NSNotFound || handle == listener.handle) { + [eventTypeListeners removeObject:listener]; + } + } +} + +- (void)validateEventType:(NSString *)eventType { + if ([self.allowedEvents indexOfObject:eventType] == NSNotFound) { + @throw [NSException + exceptionWithName:@"InvalidEventType" + reason:[NSString stringWithFormat: + @"%@ is not a valid event type. %@ " + @"is the list of valid events.", + eventType, self.allowedEvents] + userInfo:nil]; + } +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FNextPushId.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FNextPushId.h new file mode 100644 index 0000000..5a66196 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FNextPushId.h @@ -0,0 +1,23 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@interface FNextPushId : NSObject + ++ (NSString *)get:(NSTimeInterval)now; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FNextPushId.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FNextPushId.m new file mode 100644 index 0000000..864e147 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FNextPushId.m @@ -0,0 +1,62 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FNextPushId.h" +#import "FUtilities.h" + +static NSString *const PUSH_CHARS = + @"-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"; + +@implementation FNextPushId + ++ (NSString *)get:(NSTimeInterval)currentTime { + static long long lastPushTime = 0; + static int lastRandChars[12]; + + long long now = (long long)(currentTime * 1000); + + BOOL duplicateTime = now == lastPushTime; + lastPushTime = now; + + unichar timeStampChars[8]; + for (int i = 7; i >= 0; i--) { + timeStampChars[i] = [PUSH_CHARS characterAtIndex:(now % 64)]; + now = (long long)floor(now / 64); + } + + NSMutableString *id = [[NSMutableString alloc] init]; + [id appendString:[NSString stringWithCharacters:timeStampChars length:8]]; + + if (!duplicateTime) { + for (int i = 0; i < 12; i++) { + lastRandChars[i] = (int)floor(arc4random() % 64); + } + } else { + int i = 0; + for (i = 11; i >= 0 && lastRandChars[i] == 63; i--) { + lastRandChars[i] = 0; + } + lastRandChars[i]++; + } + + for (int i = 0; i < 12; i++) { + [id appendFormat:@"%C", [PUSH_CHARS characterAtIndex:lastRandChars[i]]]; + } + + return [NSString stringWithString:id]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FParsedUrl.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FParsedUrl.h new file mode 100644 index 0000000..1c07284 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FParsedUrl.h @@ -0,0 +1,25 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FPath.h" +#import "FRepoInfo.h" + +@interface FParsedUrl : NSObject + +@property(nonatomic, strong) FRepoInfo *repoInfo; +@property(nonatomic, strong) FPath *path; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FParsedUrl.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FParsedUrl.m new file mode 100644 index 0000000..eb83330 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FParsedUrl.m @@ -0,0 +1,24 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FParsedUrl.h" + +@implementation FParsedUrl + +@synthesize repoInfo; +@synthesize path; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FStringUtilities.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FStringUtilities.h new file mode 100644 index 0000000..f7d19b6 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FStringUtilities.h @@ -0,0 +1,26 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@interface FStringUtilities : NSObject + ++ (NSString *)base64EncodedSha1:(NSString *)str; ++ (NSString *)urlDecoded:(NSString *)url; ++ (NSString *)urlEncoded:(NSString *)url; ++ (NSString *)sanitizedForUserAgent:(NSString *)str; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FStringUtilities.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FStringUtilities.m new file mode 100644 index 0000000..2515257 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FStringUtilities.m @@ -0,0 +1,72 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FStringUtilities.h" +#import "NSData+SRB64Additions.h" +#import + +@implementation FStringUtilities + +// http://stackoverflow.com/questions/3468268/objective-c-sha1 +// http://stackoverflow.com/questions/7310457/ios-objective-c-sha-1-and-base64-problem ++ (NSString *)base64EncodedSha1:(NSString *)str { + const char *cstr = [str cStringUsingEncoding:NSUTF8StringEncoding]; + // NSString reports length in characters, but we want it in bytes, which + // strlen will give us. + unsigned long dataLen = strlen(cstr); + NSData *data = [NSData dataWithBytes:cstr length:dataLen]; + uint8_t digest[CC_SHA1_DIGEST_LENGTH]; + CC_SHA1(data.bytes, (unsigned int)data.length, digest); + NSData *output = [[NSData alloc] initWithBytes:digest + length:CC_SHA1_DIGEST_LENGTH]; + return [FSRUtilities base64EncodedStringFromData:output]; +} + ++ (NSString *)urlDecoded:(NSString *)url { + NSString *replaced = [url stringByReplacingOccurrencesOfString:@"+" + withString:@" "]; + NSString *decoded = [replaced stringByRemovingPercentEncoding]; + // This is kind of a hack, but is generally how the js client works. We + // could run into trouble if some piece is a correctly escaped %-sequence, + // and another isn't. But, that's bad input anyways... + if (decoded) { + return decoded; + } else { + return replaced; + } +} + ++ (NSString *)urlEncoded:(NSString *)url { + // Didn't seem like there was an Apple NSCharacterSet that had our version + // of the encoding So I made my own, following RFC 2396 + // https://www.ietf.org/rfc/rfc2396.txt allowedCharacters = alphanum | "-" | + // "_" | "~" + NSCharacterSet *allowedCharacters = [NSCharacterSet + characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGH" + @"IJKLMNOPQRSTUVWXYZ0123456789-_~"]; + return [url + stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters]; +} + ++ (NSString *)sanitizedForUserAgent:(NSString *)str { + return + [str stringByReplacingOccurrencesOfString:@"/|_" + withString:@"|" + options:NSRegularExpressionSearch + range:NSMakeRange(0, [str length])]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FTypedefs.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FTypedefs.h new file mode 100644 index 0000000..56d97ff --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FTypedefs.h @@ -0,0 +1,46 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#ifndef Firebase_FTypedefs_h +#define Firebase_FTypedefs_h + +/** + * Stub... + */ +@class FIRDataSnapshot; +@class FIRDatabaseReference; +@class FAuthData; +@protocol FNode; + +// fbt = Firebase Block Typedef + +typedef void (^fbt_void_void)(void); +typedef void (^fbt_void_datasnapshot_nsstring)(FIRDataSnapshot *snapshot, + NSString *prevName); +typedef void (^fbt_void_datasnapshot)(FIRDataSnapshot *snapshot); +typedef void (^fbt_void_user)(FAuthData *user); +typedef void (^fbt_void_nsstring_id)(NSString *status, id data); +typedef void (^fbt_void_nserror_id)(NSError *error, id data); +typedef void (^fbt_void_nserror)(NSError *error); +typedef void (^fbt_void_nserror_ref)(NSError *error, FIRDatabaseReference *ref); +typedef void (^fbt_void_nserror_user)(NSError *error, FAuthData *user); +typedef void (^fbt_void_nserror_json)(NSError *error, NSDictionary *json); +typedef void (^fbt_void_nsdictionary)(NSDictionary *data); +typedef id (^fbt_id_node_nsstring)(id node, NSString *childName); + +#endif diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FUtilities.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FUtilities.h new file mode 100644 index 0000000..9458f24 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FUtilities.h @@ -0,0 +1,82 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import +#import + +#import "FParsedUrl.h" + +@interface FUtilities : NSObject + ++ (NSArray *)splitString:(NSString *)str intoMaxSize:(const unsigned int)size; ++ (NSNumber *)LUIDGenerator; ++ (FParsedUrl *)parseUrl:(NSString *)url; ++ (NSString *)getJavascriptType:(id)obj; ++ (NSError *)errorForStatus:(NSString *)status andReason:(NSString *)reason; ++ (NSNumber *)intForString:(NSString *)string; ++ (NSString *)ieee754StringForNumber:(NSNumber *)val; ++ (void)setLoggingEnabled:(BOOL)enabled; ++ (BOOL)getLoggingEnabled; + ++ (NSString *)minName; ++ (NSString *)maxName; ++ (NSComparisonResult)compareKey:(NSString *)a toKey:(NSString *)b; ++ (NSComparator)stringComparator; ++ (NSComparator)keyComparator; + ++ (double)randomDouble; + +@end + +typedef enum { + FLogLevelDebug = 1, + FLogLevelInfo = 2, + FLogLevelWarn = 3, + FLogLevelError = 4, + FLogLevelNone = 5 +} FLogLevel; + +// Log tags +FOUNDATION_EXPORT NSString *const kFPersistenceLogTag; + +#define FFLog(code, format, ...) FFDebug((code), (format), ##__VA_ARGS__) + +#define FFDebug(code, format, ...) \ + do { \ + if (FFIsLoggingEnabled(FLogLevelDebug)) { \ + FIRLogDebug(kFIRLoggerDatabase, (code), (format), ##__VA_ARGS__); \ + } \ + } while (0) + +#define FFInfo(code, format, ...) \ + do { \ + if (FFIsLoggingEnabled(FLogLevelInfo)) { \ + FIRLogError(kFIRLoggerDatabase, (code), (format), ##__VA_ARGS__); \ + } \ + } while (0) + +#define FFWarn(code, format, ...) \ + do { \ + if (FFIsLoggingEnabled(FLogLevelWarn)) { \ + FIRLogWarning(kFIRLoggerDatabase, (code), (format), \ + ##__VA_ARGS__); \ + } \ + } while (0) + +extern FIRLoggerService kFIRLoggerDatabase; +BOOL FFIsLoggingEnabled(FLogLevel logLevel); +void firebaseUncaughtExceptionHandler(NSException *exception); +void firebaseJobsTroll(void); diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FUtilities.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FUtilities.m new file mode 100644 index 0000000..a24c134 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FUtilities.m @@ -0,0 +1,433 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FUtilities.h" +#import "FAtomicNumber.h" +#import "FConstants.h" +#import "FStringUtilities.h" +#import + +#define ARC4RANDOM_MAX 0x100000000 +#define INTEGER_32_MIN (-2147483648) +#define INTEGER_32_MAX 2147483647 + +#pragma mark - +#pragma mark C functions + +FIRLoggerService kFIRLoggerDatabase = @"[Firebase/Database]"; +static FLogLevel logLevel = FLogLevelInfo; // Default log level is info +static NSMutableDictionary *options = nil; + +BOOL FFIsLoggingEnabled(FLogLevel level) { return level >= logLevel; } + +void firebaseJobsTroll(void) { + FFLog(@"I-RDB095001", + @"password super secret; JFK conspiracy; Hello there! Having fun " + @"digging through Firebase? We're always hiring! jobs@firebase.com"); +} + +#pragma mark - +#pragma mark Private property and singleton specification + +@interface FUtilities () { +} + +@property(nonatomic, strong) FAtomicNumber *localUid; + ++ (FUtilities *)singleton; + +@end + +@implementation FUtilities + +@synthesize localUid; + +- (id)init { + self = [super init]; + if (self) { + self.localUid = [[FAtomicNumber alloc] init]; + } + return self; +} + +// TODO: We really want to be able to set the log level ++ (void)setLoggingEnabled:(BOOL)enabled { + logLevel = enabled ? FLogLevelDebug : FLogLevelInfo; +} + ++ (BOOL)getLoggingEnabled { + return logLevel == FLogLevelDebug; +} + ++ (FUtilities *)singleton { + static dispatch_once_t pred = 0; + __strong static id _sharedObject = nil; + dispatch_once(&pred, ^{ + _sharedObject = [[self alloc] init]; // or some other init method + }); + return _sharedObject; +} + +// Refactor as a category of NSString ++ (NSArray *)splitString:(NSString *)str intoMaxSize:(const unsigned int)size { + if (str.length <= size) { + return [NSArray arrayWithObject:str]; + } + + NSMutableArray *dataSegs = [[NSMutableArray alloc] init]; + for (int c = 0; c < str.length; c += size) { + if (c + size > str.length) { + int rangeStart = c; + unsigned long rangeLength = size - ((c + size) - str.length); + [dataSegs + addObject:[str substringWithRange:NSMakeRange(rangeStart, + rangeLength)]]; + } else { + int rangeStart = c; + int rangeLength = size; + [dataSegs + addObject:[str substringWithRange:NSMakeRange(rangeStart, + rangeLength)]]; + } + } + return dataSegs; +} + ++ (NSNumber *)LUIDGenerator { + FUtilities *f = [FUtilities singleton]; + return [f.localUid getAndIncrement]; +} + ++ (NSString *)decodePath:(NSString *)pathString { + NSMutableArray *decodedPieces = [[NSMutableArray alloc] init]; + NSArray *pieces = [pathString componentsSeparatedByString:@"/"]; + for (NSString *piece in pieces) { + if (piece.length > 0) { + [decodedPieces addObject:[FStringUtilities urlDecoded:piece]]; + } + } + return [NSString + stringWithFormat:@"/%@", [decodedPieces componentsJoinedByString:@"/"]]; +} + ++ (NSString *)extractPathFromUrlString:(NSString *)url { + NSString *path = url; + + NSRange schemeIndex = [path rangeOfString:@"//"]; + if (schemeIndex.location != NSNotFound) { + path = [path substringFromIndex:schemeIndex.location + 2]; + } + + NSUInteger pathIndex = [path rangeOfString:@"/"].location; + if (pathIndex != NSNotFound) { + path = [path substringFromIndex:pathIndex + 1]; + } else { + path = @""; + } + + NSUInteger queryParamIndex = [path rangeOfString:@"?"].location; + if (queryParamIndex != NSNotFound) { + path = [path substringToIndex:queryParamIndex]; + } + + return path; +} + ++ (FParsedUrl *)parseUrl:(NSString *)url { + // For backwards compatibility, support URLs without schemes on iOS. + if (![url containsString:@"://"]) { + url = [@"http://" stringByAppendingString:url]; + } + + NSString *originalPathString = [self extractPathFromUrlString:url]; + + // Sanitize the database URL by removing the path component, which may + // contain invalid URL characters. + NSString *sanitizedUrlWithoutPath = + [url stringByReplacingOccurrencesOfString:originalPathString + withString:@""]; + NSURLComponents *urlComponents = + [NSURLComponents componentsWithString:sanitizedUrlWithoutPath]; + if (!urlComponents) { + [NSException raise:@"Failed to parse database URL" + format:@"Failed to parse database URL: %@", url]; + } + + NSString *host = [urlComponents.host lowercaseString]; + NSString *namespace; + bool secure; + + if (urlComponents.port != nil) { + secure = [urlComponents.scheme isEqualToString:@"https"] || + [urlComponents.scheme isEqualToString:@"wss"]; + host = [host stringByAppendingFormat:@":%@", urlComponents.port]; + } else { + secure = YES; + }; + + NSArray *parts = [urlComponents.host componentsSeparatedByString:@"."]; + if ([parts count] == 3) { + namespace = [parts[0] lowercaseString]; + } else { + // Attempt to extract namespace from "ns" query param. + NSArray *queryItems = urlComponents.queryItems; + for (NSURLQueryItem *item in queryItems) { + if ([item.name isEqualToString:@"ns"]) { + namespace = item.value; + break; + } + } + + if (!namespace) { + namespace = [parts[0] lowercaseString]; + } + } + + NSString *pathString = [self + decodePath:[NSString stringWithFormat:@"/%@", originalPathString]]; + FPath *path = [[FPath alloc] initWith:pathString]; + FRepoInfo *repoInfo = [[FRepoInfo alloc] initWithHost:host + isSecure:secure + withNamespace:namespace]; + + FFLog(@"I-RDB095002", @"---> Parsed (%@) to: (%@,%@); ns=(%@); path=(%@)", + url, [repoInfo description], [repoInfo connectionURL], + repoInfo.namespace, [path description]); + + FParsedUrl *parsedUrl = [[FParsedUrl alloc] init]; + parsedUrl.repoInfo = repoInfo; + parsedUrl.path = path; + + return parsedUrl; +} + +/* + case str: JString => priString + "string:" + str.s; + case bool: JBool => priString + "boolean:" + bool.value; + case double: JDouble => priString + "number:" + double.num; + case int: JInt => priString + "number:" + int.num; + case _ => { + error("Leaf node has value '" + data.value + "' of invalid type '" + + data.value.getClass.toString + "'"); + ""; + } + */ + ++ (NSString *)getJavascriptType:(id)obj { + if ([obj isKindOfClass:[NSDictionary class]]) { + return kJavaScriptObject; + } else if ([obj isKindOfClass:[NSString class]]) { + return kJavaScriptString; + } else if ([obj isKindOfClass:[NSNumber class]]) { + // We used to just compare to @encode(BOOL) as suggested at + // http://stackoverflow.com/questions/2518761/get-type-of-nsnumber, but + // on arm64, @encode(BOOL) returns "B" instead of "c" even though + // objCType still returns 'c' (signed char). So check both. + if (strcmp([obj objCType], @encode(BOOL)) == 0 || + strcmp([obj objCType], @encode(signed char)) == 0) { + return kJavaScriptBoolean; + } else { + return kJavaScriptNumber; + } + } else { + return kJavaScriptNull; + } +} + ++ (NSError *)errorForStatus:(NSString *)status andReason:(NSString *)reason { + static dispatch_once_t pred = 0; + __strong static NSDictionary *errorMap = nil; + __strong static NSDictionary *errorCodes = nil; + dispatch_once(&pred, ^{ + errorMap = @{ + @"permission_denied" : @"Permission Denied", + @"unavailable" : @"Service is unavailable", + kFErrorWriteCanceled : @"Write cancelled by user" + }; + errorCodes = @{ + @"permission_denied" : @1, + @"unavailable" : @2, + kFErrorWriteCanceled : @3 + }; + }); + + if ([status isEqualToString:kFWPResponseForActionStatusOk]) { + return nil; + } else { + NSInteger code; + NSString *desc = nil; + if (reason) { + desc = reason; + } else if ([errorMap objectForKey:status] != nil) { + desc = [errorMap objectForKey:status]; + } else { + desc = status; + } + + if ([errorCodes objectForKey:status] != nil) { + NSNumber *num = [errorCodes objectForKey:status]; + code = [num integerValue]; + } else { + // XXX what to do here? + code = 9999; + } + + return [[NSError alloc] + initWithDomain:kFErrorDomain + code:code + userInfo:@{NSLocalizedDescriptionKey : desc}]; + } +} + ++ (NSNumber *)intForString:(NSString *)string { + static NSCharacterSet *notDigits = nil; + if (!notDigits) { + notDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet]; + } + if ([string rangeOfCharacterFromSet:notDigits].length == 0) { + NSInteger num; + NSScanner *scanner = [NSScanner scannerWithString:string]; + if ([scanner scanInteger:&num]) { + return [NSNumber numberWithInteger:num]; + } + } + return nil; +} + ++ (NSString *)ieee754StringForNumber:(NSNumber *)val { + double d = [val doubleValue]; + NSData *data = [NSData dataWithBytes:&d length:sizeof(double)]; + NSMutableString *str = [[NSMutableString alloc] init]; + const unsigned char *buffer = (const unsigned char *)[data bytes]; + for (int i = 0; i < data.length; i++) { + unsigned char byte = buffer[7 - i]; + [str appendFormat:@"%02x", byte]; + } + return str; +} + +static inline BOOL tryParseStringToInt(__unsafe_unretained NSString *str, + NSInteger *integer) { + // First do some cheap checks (NOTE: The below checks are significantly + // faster than an equivalent regex :-( ). + NSUInteger length = str.length; + if (length > 11 || length == 0) { + return NO; + } + long long value = 0; + BOOL negative = NO; + NSUInteger i = 0; + if ([str characterAtIndex:0] == '-') { + if (length == 1) { + return NO; + } + negative = YES; + i = 1; + } + for (; i < length; i++) { + unichar c = [str characterAtIndex:i]; + // Must be a digit, or '-' if it's the first char. + if (c < '0' || c > '9') { + return NO; + } else { + int charValue = c - '0'; + value = value * 10 + charValue; + } + } + + value = (negative) ? -value : value; + + if (value < INTEGER_32_MIN || value > INTEGER_32_MAX) { + return NO; + } else { + *integer = (NSInteger)value; + return YES; + } +} + ++ (NSString *)maxName { + static dispatch_once_t once; + static NSString *maxName; + dispatch_once(&once, ^{ + maxName = [[NSString alloc] initWithFormat:@"[MAX_NAME]"]; + }); + return maxName; +} + ++ (NSString *)minName { + static dispatch_once_t once; + static NSString *minName; + dispatch_once(&once, ^{ + minName = [[NSString alloc] initWithFormat:@"[MIN_NAME]"]; + }); + return minName; +} + ++ (NSComparisonResult)compareKey:(NSString *)a toKey:(NSString *)b { + if (a == b) { + return NSOrderedSame; + } else if (a == [FUtilities minName] || b == [FUtilities maxName]) { + return NSOrderedAscending; + } else if (b == [FUtilities minName] || a == [FUtilities maxName]) { + return NSOrderedDescending; + } else { + NSInteger aAsInt, bAsInt; + if (tryParseStringToInt(a, &aAsInt)) { + if (tryParseStringToInt(b, &bAsInt)) { + if (aAsInt > bAsInt) { + return NSOrderedDescending; + } else if (aAsInt < bAsInt) { + return NSOrderedAscending; + } else if (a.length > b.length) { + return NSOrderedDescending; + } else if (a.length < b.length) { + return NSOrderedAscending; + } else { + return NSOrderedSame; + } + } else { + return (NSComparisonResult)NSOrderedAscending; + } + } else if (tryParseStringToInt(b, &bAsInt)) { + return (NSComparisonResult)NSOrderedDescending; + } else { + // Perform literal character by character search to prevent a > b && + // b > a issues. Note that calling -(NSString + // *)decomposedStringWithCanonicalMapping also works. + return [a compare:b options:NSLiteralSearch]; + } + } +} + ++ (NSComparator)keyComparator { + return ^NSComparisonResult(__unsafe_unretained NSString *a, + __unsafe_unretained NSString *b) { + return [FUtilities compareKey:a toKey:b]; + }; +} + ++ (NSComparator)stringComparator { + return ^NSComparisonResult(__unsafe_unretained NSString *a, + __unsafe_unretained NSString *b) { + return [a compare:b]; + }; +} + ++ (double)randomDouble { + return ((double)arc4random() / ARC4RANDOM_MAX); +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FValidation.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FValidation.h new file mode 100644 index 0000000..276eb26 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FValidation.h @@ -0,0 +1,55 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRDataEventType.h" +#import "FParsedUrl.h" +#import "FPath.h" +#import "FTypedefs.h" +#import + +@interface FValidation : NSObject + ++ (void)validateFrom:(NSString *)fn writablePath:(FPath *)path; ++ (void)validateFrom:(NSString *)fn knownEventType:(FIRDataEventType)event; ++ (void)validateFrom:(NSString *)fn validPathString:(NSString *)pathString; ++ (void)validateFrom:(NSString *)fn validRootPathString:(NSString *)pathString; ++ (void)validateFrom:(NSString *)fn validKey:(NSString *)key; ++ (void)validateFrom:(NSString *)fn validURL:(FParsedUrl *)parsedUrl; + ++ (void)validateToken:(NSString *)token; + +// Functions for handling passing errors back ++ (void)handleError:(NSError *)error + withUserCallback:(fbt_void_nserror_id)userCallback; ++ (void)handleError:(NSError *)error + withSuccessCallback:(fbt_void_nserror)userCallback; + +// Functions used for validating while creating snapshots in FSnapshotUtilities ++ (BOOL)validateFrom:(NSString *)fn + isValidLeafValue:(id)value + withPath:(NSArray *)path; ++ (void)validateFrom:(NSString *)fn + validDictionaryKey:(id)keyId + withPath:(NSArray *)path; ++ (void)validateFrom:(NSString *)fn + validUpdateDictionaryKey:(id)keyId + withValue:(id)value; ++ (void)validateFrom:(NSString *)fn + isValidPriorityValue:(id)value + withPath:(NSArray *)path; ++ (BOOL)validatePriorityValue:value; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FValidation.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FValidation.m new file mode 100644 index 0000000..c700b9c --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/FValidation.m @@ -0,0 +1,461 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FValidation.h" +#import "FConstants.h" +#import "FParsedUrl.h" +#import "FTypedefs.h" + +// Have to escape: * ? + [ ( ) { } ^ $ | \ . / +// See: +// https://developer.apple.com/library/mac/#documentation/Foundation/Reference/NSRegularExpression_Class/Reference/Reference.html + +NSString *const kInvalidPathCharacters = @"[].#$"; +NSString *const kInvalidKeyCharacters = @"[].#$/"; + +@implementation FValidation + ++ (void)validateFrom:(NSString *)fn writablePath:(FPath *)path { + if ([[path getFront] isEqualToString:kDotInfoPrefix]) { + @throw [[NSException alloc] + initWithName:@"WritablePathValidation" + reason:[NSString + stringWithFormat:@"(%@) failed to path %@: Can't " + @"modify data under %@", + fn, [path description], + kDotInfoPrefix] + userInfo:nil]; + } +} + ++ (void)validateFrom:(NSString *)fn knownEventType:(FIRDataEventType)event { + switch (event) { + case FIRDataEventTypeValue: + case FIRDataEventTypeChildAdded: + case FIRDataEventTypeChildChanged: + case FIRDataEventTypeChildMoved: + case FIRDataEventTypeChildRemoved: + return; + break; + default: + @throw [[NSException alloc] + initWithName:@"KnownEventTypeValidation" + reason:[NSString + stringWithFormat:@"(%@) Unknown event type: %d", + fn, (int)event] + userInfo:nil]; + break; + } +} + ++ (BOOL)isValidPathString:(NSString *)pathString { + static dispatch_once_t token; + static NSCharacterSet *badPathChars = nil; + dispatch_once(&token, ^{ + badPathChars = [NSCharacterSet + characterSetWithCharactersInString:kInvalidPathCharacters]; + }); + return pathString != nil && [pathString length] != 0 && + [pathString rangeOfCharacterFromSet:badPathChars].location == + NSNotFound; +} + ++ (void)validateFrom:(NSString *)fn validPathString:(NSString *)pathString { + if (![self isValidPathString:pathString]) { + @throw [[NSException alloc] + initWithName:@"InvalidPathValidation" + reason:[NSString stringWithFormat: + @"(%@) Must be a non-empty string and " + @"not contain '.' '#' '$' '[' or ']'", + fn] + userInfo:nil]; + } +} + ++ (void)validateFrom:(NSString *)fn validRootPathString:(NSString *)pathString { + static dispatch_once_t token; + static NSRegularExpression *dotInfoRegex = nil; + dispatch_once(&token, ^{ + dotInfoRegex = [NSRegularExpression + regularExpressionWithPattern:@"^\\/*\\.info(\\/|$)" + options:0 + error:nil]; + }); + + NSString *tempPath = pathString; + // HACK: Obj-C regex are kinda' slow. Do a plain string search first before + // bothering with the regex. + if ([pathString rangeOfString:@".info"].location != NSNotFound) { + tempPath = [dotInfoRegex + stringByReplacingMatchesInString:pathString + options:0 + range:NSMakeRange(0, pathString.length) + withTemplate:@"/"]; + } + [self validateFrom:fn validPathString:tempPath]; +} + ++ (BOOL)isValidKey:(NSString *)key { + static dispatch_once_t token; + static NSCharacterSet *badKeyChars = nil; + dispatch_once(&token, ^{ + badKeyChars = [NSCharacterSet + characterSetWithCharactersInString:kInvalidKeyCharacters]; + }); + return key != nil && key.length > 0 && + [key rangeOfCharacterFromSet:badKeyChars].location == NSNotFound; +} + ++ (void)validateFrom:(NSString *)fn validKey:(NSString *)key { + if (![self isValidKey:key]) { + @throw [[NSException alloc] + initWithName:@"InvalidKeyValidation" + reason:[NSString + stringWithFormat: + @"(%@) Must be a non-empty string and not " + @"contain '/' '.' '#' '$' '[' or ']'", + fn] + userInfo:nil]; + } +} + ++ (void)validateFrom:(NSString *)fn validURL:(FParsedUrl *)parsedUrl { + NSString *pathString = [parsedUrl.path description]; + [self validateFrom:fn validRootPathString:pathString]; +} + +#pragma mark - +#pragma mark Authentication validation + ++ (BOOL)stringNonempty:(NSString *)str { + return str != nil && ![str isKindOfClass:[NSNull class]] && str.length > 0; +} + ++ (void)validateToken:(NSString *)token { + if (![FValidation stringNonempty:token]) { + [NSException raise:NSInvalidArgumentException + format:@"Can't have empty string or nil for custom token"]; + } +} + +#pragma mark - +#pragma mark Handling authentication errors + +/** + * This function immediately calls the callback. + * It assumes that it is not on FirebaseWorker thread. + * It assumes it's on a user-controlled thread. + */ ++ (void)handleError:(NSError *)error + withUserCallback:(fbt_void_nserror_id)userCallback { + if (userCallback) { + userCallback(error, nil); + } +} + +/** + * This function immediately calls the callback. + * It assumes that it is not on FirebaseWorker thread. + * It assumes it's on a user-controlled thread. + */ ++ (void)handleError:(NSError *)error + withSuccessCallback:(fbt_void_nserror)userCallback { + if (userCallback) { + userCallback(error); + } +} + +#pragma mark - +#pragma mark Snapshot validation + ++ (BOOL)validateFrom:(NSString *)fn + isValidLeafValue:(id)value + withPath:(NSArray *)path { + if ([value isKindOfClass:[NSString class]]) { + // Try to avoid conversion to bytes if possible + NSString *theString = value; + if ([theString maximumLengthOfBytesUsingEncoding:NSUTF8StringEncoding] > + kFirebaseMaxLeafSize && + [theString lengthOfBytesUsingEncoding:NSUTF8StringEncoding] > + kFirebaseMaxLeafSize) { + NSRange range; + range.location = 0; + range.length = MIN(path.count, 50); + NSString *pathString = + [[path subarrayWithRange:range] componentsJoinedByString:@"."]; + @throw [[NSException alloc] + initWithName:@"InvalidFirebaseData" + reason:[NSString + stringWithFormat:@"(%@) String exceeds max " + @"size of %u utf8 bytes: %@", + fn, (int)kFirebaseMaxLeafSize, + pathString] + userInfo:nil]; + } + return YES; + } + + else if ([value isKindOfClass:[NSNumber class]]) { + // Cannot store NaN, but otherwise can store NSNumbers. + if ([[NSDecimalNumber notANumber] isEqualToNumber:value]) { + NSRange range; + range.location = 0; + range.length = MIN(path.count, 50); + NSString *pathString = + [[path subarrayWithRange:range] componentsJoinedByString:@"."]; + @throw [[NSException alloc] + initWithName:@"InvalidFirebaseData" + reason:[NSString + stringWithFormat: + @"(%@) Cannot store NaN at path: %@.", fn, + pathString] + userInfo:nil]; + } + return YES; + } + + else if ([value isKindOfClass:[NSDictionary class]]) { + NSDictionary *dval = value; + if (dval[kServerValueSubKey] != nil) { + if ([dval count] > 1) { + NSRange range; + range.location = 0; + range.length = MIN(path.count, 50); + NSString *pathString = [[path subarrayWithRange:range] + componentsJoinedByString:@"."]; + @throw [[NSException alloc] + initWithName:@"InvalidFirebaseData" + reason:[NSString stringWithFormat: + @"(%@) Cannot store other keys " + @"with server value keys.%@.", + fn, pathString] + userInfo:nil]; + } + return YES; + } + return NO; + } + + else if (value == [NSNull null] || value == nil) { + // Null is valid type to store at leaf + return YES; + } + + return NO; +} + ++ (NSString *)parseAndValidateKey:(id)keyId + fromFunction:(NSString *)fn + path:(NSArray *)path { + if (![keyId isKindOfClass:[NSString class]]) { + NSRange range; + range.location = 0; + range.length = MIN(path.count, 50); + NSString *pathString = + [[path subarrayWithRange:range] componentsJoinedByString:@"."]; + @throw [[NSException alloc] + initWithName:@"InvalidFirebaseData" + reason:[NSString + stringWithFormat:@"(%@) Non-string keys are not " + @"allowed in object at path: %@", + fn, pathString] + userInfo:nil]; + } + return (NSString *)keyId; +} + ++ (void)validateFrom:(NSString *)fn + validDictionaryKey:(id)keyId + withPath:(NSArray *)path { + NSString *key = [self parseAndValidateKey:keyId fromFunction:fn path:path]; + if (![key isEqualToString:kPayloadPriority] && + ![key isEqualToString:kPayloadValue] && + ![key isEqualToString:kServerValueSubKey] && + ![FValidation isValidKey:key]) { + NSRange range; + range.location = 0; + range.length = MIN(path.count, 50); + NSString *pathString = + [[path subarrayWithRange:range] componentsJoinedByString:@"."]; + @throw [[NSException alloc] + initWithName:@"InvalidFirebaseData" + reason:[NSString stringWithFormat: + @"(%@) Invalid key in object at path: " + @"%@. Keys must be non-empty and cannot " + @"contain '/' '.' '#' '$' '[' or ']'", + fn, pathString] + userInfo:nil]; + } +} + ++ (void)validateFrom:(NSString *)fn + validUpdateDictionaryKey:(id)keyId + withValue:(id)value { + FPath *path = [FPath pathWithString:[self parseAndValidateKey:keyId + fromFunction:fn + path:@[]]]; + __block NSInteger keyNum = 0; + [path enumerateComponentsUsingBlock:^void(NSString *key, BOOL *stop) { + if ([key isEqualToString:kPayloadPriority] && + keyNum == [path length] - 1) { + [self validateFrom:fn isValidPriorityValue:value withPath:@[]]; + } else { + keyNum++; + + if (![FValidation isValidKey:key]) { + @throw [[NSException alloc] + initWithName:@"InvalidFirebaseData" + reason:[NSString + stringWithFormat: + @"(%@) Invalid key in object. Keys must " + @"be non-empty and cannot contain '.' " + @"'#' '$' '[' or ']'", + fn] + userInfo:nil]; + } + } + }]; +} + ++ (void)validateFrom:(NSString *)fn + isValidPriorityValue:(id)value + withPath:(NSArray *)path { + [self validateFrom:fn + isValidPriorityValue:value + withPath:path + throwError:YES]; +} + +/** + * Returns YES if priority is valid. + */ ++ (BOOL)validatePriorityValue:value { + return [self validateFrom:nil + isValidPriorityValue:value + withPath:nil + throwError:NO]; +} + +/** + * Helper for validating priorities. If passed YES for throwError, it'll throw + * descriptive errors on validation problems. Else, it'll just return YES/NO. + */ ++ (BOOL)validateFrom:(NSString *)fn + isValidPriorityValue:(id)value + withPath:(NSArray *)path + throwError:(BOOL)throwError { + if ([value isKindOfClass:[NSNumber class]]) { + if ([[NSDecimalNumber notANumber] isEqualToNumber:value]) { + if (throwError) { + NSRange range; + range.location = 0; + range.length = MIN(path.count, 50); + NSString *pathString = [[path subarrayWithRange:range] + componentsJoinedByString:@"."]; + @throw [[NSException alloc] + initWithName:@"InvalidFirebaseData" + reason:[NSString stringWithFormat: + @"(%@) Cannot store NaN as " + @"priority at path: %@.", + fn, pathString] + userInfo:nil]; + } else { + return NO; + } + } else if (value == (id)kCFBooleanFalse || + value == (id)kCFBooleanTrue) { + if (throwError) { + NSRange range; + range.location = 0; + range.length = MIN(path.count, 50); + NSString *pathString = [[path subarrayWithRange:range] + componentsJoinedByString:@"."]; + @throw [[NSException alloc] + initWithName:@"InvalidFirebaseData" + reason:[NSString stringWithFormat: + @"(%@) Cannot store true/false " + @"as priority at path: %@.", + fn, pathString] + userInfo:nil]; + } else { + return NO; + } + } + } else if ([value isKindOfClass:[NSDictionary class]]) { + NSDictionary *dval = value; + if (dval[kServerValueSubKey] != nil) { + if ([dval count] > 1) { + if (throwError) { + NSRange range; + range.location = 0; + range.length = MIN(path.count, 50); + NSString *pathString = [[path subarrayWithRange:range] + componentsJoinedByString:@"."]; + @throw [[NSException alloc] + initWithName:@"InvalidFirebaseData" + reason:[NSString + stringWithFormat: + @"(%@) Cannot store other keys " + @"with server value keys as " + @"priority at path: %@.", + fn, pathString] + userInfo:nil]; + } else { + return NO; + } + } + } else { + if (throwError) { + NSRange range; + range.location = 0; + range.length = MIN(path.count, 50); + NSString *pathString = [[path subarrayWithRange:range] + componentsJoinedByString:@"."]; + @throw [[NSException alloc] + initWithName:@"InvalidFirebaseData" + reason:[NSString + stringWithFormat: + @"(%@) Cannot store an NSDictionary " + @"as priority at path: %@.", + fn, pathString] + userInfo:nil]; + } else { + return NO; + } + } + } else if ([value isKindOfClass:[NSArray class]]) { + if (throwError) { + NSRange range; + range.location = 0; + range.length = MIN(path.count, 50); + NSString *pathString = + [[path subarrayWithRange:range] componentsJoinedByString:@"."]; + @throw [[NSException alloc] + initWithName:@"InvalidFirebaseData" + reason:[NSString stringWithFormat: + @"(%@) Cannot store an NSArray as " + @"priority at path: %@.", + fn, pathString] + userInfo:nil]; + } else { + return NO; + } + } + + // It's valid! + return YES; +} +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleBoolBlock.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleBoolBlock.h new file mode 100644 index 0000000..f5c0859 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleBoolBlock.h @@ -0,0 +1,25 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FTypedefs.h" +#import + +@interface FTupleBoolBlock : NSObject + +@property(nonatomic, readwrite) BOOL boolean; +@property(nonatomic, copy) fbt_void_void block; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleBoolBlock.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleBoolBlock.m new file mode 100644 index 0000000..c4cd8bf --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleBoolBlock.m @@ -0,0 +1,24 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FTupleBoolBlock.h" + +@implementation FTupleBoolBlock + +@synthesize boolean; +@synthesize block; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleCallbackStatus.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleCallbackStatus.h new file mode 100644 index 0000000..bd8c4a9 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleCallbackStatus.h @@ -0,0 +1,24 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FTypedefs_Private.h" +#import + +@interface FTupleCallbackStatus : NSObject +@property(nonatomic, copy) fbt_void_nsstring_nsstring block; +@property(nonatomic) NSString *status; +@property(nonatomic) NSString *errorReason; +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleCallbackStatus.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleCallbackStatus.m new file mode 100644 index 0000000..05914bf --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleCallbackStatus.m @@ -0,0 +1,22 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FTupleCallbackStatus.h" + +@implementation FTupleCallbackStatus +@synthesize block; +@synthesize status; +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleFirebase.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleFirebase.h new file mode 100644 index 0000000..5e41f9e --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleFirebase.h @@ -0,0 +1,26 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRDatabaseReference.h" +#import + +@interface FTupleFirebase : NSObject + +@property(nonatomic, strong) FIRDatabaseReference *one; +@property(nonatomic, strong) FIRDatabaseReference *two; +@property(nonatomic, strong) FIRDatabaseReference *three; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleFirebase.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleFirebase.m new file mode 100644 index 0000000..3956f8b --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleFirebase.m @@ -0,0 +1,25 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FTupleFirebase.h" + +@implementation FTupleFirebase + +@synthesize one; +@synthesize two; +@synthesize three; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleNodePath.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleNodePath.h new file mode 100644 index 0000000..19b5217 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleNodePath.h @@ -0,0 +1,28 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FNode.h" +#import "FPath.h" +#import + +@interface FTupleNodePath : NSObject + +@property(nonatomic, strong) FPath *path; +@property(nonatomic, strong) id node; + +- (id)initWithNode:(id)aNode andPath:(FPath *)aPath; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleNodePath.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleNodePath.m new file mode 100644 index 0000000..620ae76 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleNodePath.m @@ -0,0 +1,33 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FTupleNodePath.h" + +@implementation FTupleNodePath + +@synthesize path; +@synthesize node; + +- (id)initWithNode:(id)aNode andPath:(FPath *)aPath { + self = [super init]; + if (self) { + self.path = aPath; + self.node = aNode; + } + return self; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleObjectNode.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleObjectNode.h new file mode 100644 index 0000000..1717a22 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleObjectNode.h @@ -0,0 +1,27 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FNode.h" +#import + +@interface FTupleObjectNode : NSObject + +- (id)initWithObject:(id)aObj andNode:(id)aNode; + +@property(nonatomic, strong) id node; +@property(nonatomic, strong) id obj; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleObjectNode.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleObjectNode.m new file mode 100644 index 0000000..4c533b0 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleObjectNode.m @@ -0,0 +1,32 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#import "FTupleObjectNode.h" + +@implementation FTupleObjectNode + +@synthesize obj; +@synthesize node; + +- (id)initWithObject:(id)aObj andNode:(id)aNode { + self = [super init]; + if (self) { + self.obj = aObj; + self.node = aNode; + } + return self; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleObjects.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleObjects.h new file mode 100644 index 0000000..05b3141 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleObjects.h @@ -0,0 +1,24 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@interface FTupleObjects : NSObject + +@property(nonatomic, strong) id objA; +@property(nonatomic, strong) id objB; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleObjects.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleObjects.m new file mode 100644 index 0000000..a9e4c88 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleObjects.m @@ -0,0 +1,24 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FTupleObjects.h" + +@implementation FTupleObjects + +@synthesize objA; +@synthesize objB; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleOnDisconnect.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleOnDisconnect.h new file mode 100644 index 0000000..e68ab71 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleOnDisconnect.h @@ -0,0 +1,27 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FTypedefs_Private.h" +#import + +@interface FTupleOnDisconnect : NSObject + +@property(strong, nonatomic) NSString *pathString; +@property(strong, nonatomic) NSString *action; +@property(strong, nonatomic) id data; +@property(strong, nonatomic) fbt_void_nsstring_nsstring onComplete; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleOnDisconnect.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleOnDisconnect.m new file mode 100644 index 0000000..bd45822 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleOnDisconnect.m @@ -0,0 +1,26 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FTupleOnDisconnect.h" + +@implementation FTupleOnDisconnect + +@synthesize pathString; +@synthesize action; +@synthesize data; +@synthesize onComplete; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTuplePathValue.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTuplePathValue.h new file mode 100644 index 0000000..b0a515c --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTuplePathValue.h @@ -0,0 +1,25 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FPath; + +@interface FTuplePathValue : NSObject +@property(nonatomic, strong, readonly) FPath *path; +@property(nonatomic, strong, readonly) id value; +- (id)initWithPath:(FPath *)aPath value:(id)aValue; +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTuplePathValue.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTuplePathValue.m new file mode 100644 index 0000000..91de883 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTuplePathValue.m @@ -0,0 +1,38 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FTuplePathValue.h" +#import "FPath.h" + +@interface FTuplePathValue () +@property(nonatomic, strong, readwrite) id value; +@property(nonatomic, strong, readwrite) FPath *path; +@end + +@implementation FTuplePathValue +@synthesize path; +@synthesize value; + +- (id)initWithPath:(FPath *)aPath value:(id)aValue { + self = [super init]; + if (self) { + self.value = aValue; + self.path = aPath; + } + return self; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleRemovedQueriesEvents.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleRemovedQueriesEvents.h new file mode 100644 index 0000000..7269c2f --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleRemovedQueriesEvents.h @@ -0,0 +1,30 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@interface FTupleRemovedQueriesEvents : NSObject +/** + * `FIRDatabaseQuery`s removed with [SyncPoint removeEventRegistration:] + */ +@property(nonatomic, strong, readonly) NSArray *removedQueries; +/** + * cancel events as FEvent + */ +@property(nonatomic, strong, readonly) NSArray *cancelEvents; + +- (id)initWithRemovedQueries:(NSArray *)removed cancelEvents:(NSArray *)events; +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleRemovedQueriesEvents.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleRemovedQueriesEvents.m new file mode 100644 index 0000000..324cfcb --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleRemovedQueriesEvents.m @@ -0,0 +1,37 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FTupleRemovedQueriesEvents.h" + +@interface FTupleRemovedQueriesEvents () +@property(nonatomic, strong, readwrite) NSArray *removedQueries; +@property(nonatomic, strong, readwrite) NSArray *cancelEvents; +@end + +@implementation FTupleRemovedQueriesEvents +@synthesize removedQueries; +@synthesize cancelEvents; + +- (id)initWithRemovedQueries:(NSArray *)removed cancelEvents:(NSArray *)events { + self = [super init]; + if (self) { + self.removedQueries = removed; + self.cancelEvents = events; + } + return self; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleSetIdPath.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleSetIdPath.h new file mode 100644 index 0000000..337f8d6 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleSetIdPath.h @@ -0,0 +1,27 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FPath.h" +#import + +@interface FTupleSetIdPath : NSObject + +- (id)initWithSetId:(NSNumber *)aSetId andPath:(FPath *)aPath; + +@property(strong, nonatomic) NSNumber *setId; +@property(strong, nonatomic) FPath *path; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleSetIdPath.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleSetIdPath.m new file mode 100644 index 0000000..2763c83 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleSetIdPath.m @@ -0,0 +1,33 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FTupleSetIdPath.h" + +@implementation FTupleSetIdPath + +@synthesize path; +@synthesize setId; + +- (id)initWithSetId:(NSNumber *)aSetId andPath:(FPath *)aPath { + self = [super init]; + if (self) { + self.setId = aSetId; + self.path = aPath; + } + return self; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleStringNode.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleStringNode.h new file mode 100644 index 0000000..0e3d7fa --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleStringNode.h @@ -0,0 +1,27 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FNode.h" +#import + +@interface FTupleStringNode : NSObject + +- (id)initWithString:(NSString *)aString andNode:(id)aNode; + +@property(nonatomic, strong) id node; +@property(nonatomic, strong) NSString *string; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleStringNode.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleStringNode.m new file mode 100644 index 0000000..d820ef8 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleStringNode.m @@ -0,0 +1,33 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FTupleStringNode.h" + +@implementation FTupleStringNode + +@synthesize string; +@synthesize node; + +- (id)initWithString:(NSString *)aString andNode:(id)aNode { + self = [super init]; + if (self) { + self.string = aString; + self.node = aNode; + } + return self; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleTSN.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleTSN.h new file mode 100644 index 0000000..b1052da --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleTSN.h @@ -0,0 +1,25 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FTupleStringNode.h" +#import + +@interface FTupleTSN : NSObject + +@property(nonatomic, strong) FTupleStringNode *from; +@property(nonatomic, strong) FTupleStringNode *to; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleTSN.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleTSN.m new file mode 100644 index 0000000..348c319 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleTSN.m @@ -0,0 +1,24 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FTupleTSN.h" + +@implementation FTupleTSN + +@synthesize from; +@synthesize to; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleTransaction.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleTransaction.h new file mode 100644 index 0000000..82f4495 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleTransaction.h @@ -0,0 +1,75 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FPath.h" +#import "FTypedefs.h" +#import "FTypedefs_Private.h" +#import + +@interface FTupleTransaction : NSObject + +@property(nonatomic, strong) FPath *path; +@property(nonatomic, copy) fbt_transactionresult_mutabledata update; +@property(nonatomic, copy) fbt_void_nserror_bool_datasnapshot onComplete; +@property(nonatomic) FTransactionStatus status; + +/** + * Used when combining transaction at different locations to figure out which + * one goes first. + */ +@property(nonatomic, strong) NSNumber *order; +/** + * Whether to raise local events for this transaction + */ +@property(nonatomic) BOOL applyLocally; + +/** + * Count how many times we've retried the transaction + */ +@property(nonatomic) int retryCount; + +/** + * Function to call to clean up our listener + */ +@property(nonatomic, copy) fbt_void_void unwatcher; + +/** + * Stores why a transaction was aborted + */ +@property(nonatomic, strong, readonly) NSString *abortStatus; +@property(nonatomic, strong, readonly) NSString *abortReason; + +- (void)setAbortStatus:(NSString *)abortStatus reason:(NSString *)reason; +- (NSError *)abortError; + +@property(nonatomic, strong) NSNumber *currentWriteId; + +/** + * Stores the input snapshot, before the update + */ +@property(nonatomic, strong) id currentInputSnapshot; + +/** + * Stores the unresolved (for server values) output snapshot, after the update + */ +@property(nonatomic, strong) id currentOutputSnapshotRaw; + +/** + * Stores the resolved (for server values) output snapshot, after the update + */ +@property(nonatomic, strong) id currentOutputSnapshotResolved; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleTransaction.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleTransaction.m new file mode 100644 index 0000000..68977ef --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleTransaction.m @@ -0,0 +1,41 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FTupleTransaction.h" +#import "FUtilities.h" + +@interface FTupleTransaction () + +@property(nonatomic, strong) NSString *abortStatus; +@property(nonatomic, strong) NSString *abortReason; + +@end + +@implementation FTupleTransaction + +- (void)setAbortStatus:(NSString *)abortStatus reason:(NSString *)reason { + self.abortStatus = abortStatus; + self.abortReason = reason; +} + +- (NSError *)abortError { + return (self.abortStatus != nil) + ? [FUtilities errorForStatus:self.abortStatus + andReason:self.abortReason] + : nil; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleUserCallback.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleUserCallback.h new file mode 100644 index 0000000..b94eef6 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleUserCallback.h @@ -0,0 +1,32 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FQueryParams.h" +#import "FTypedefs.h" +#import + +@interface FTupleUserCallback : NSObject + +- (id)initWithHandle:(NSUInteger)handle; + +@property(nonatomic, copy) + fbt_void_datasnapshot_nsstring datasnapshotPrevnameCallback; +@property(nonatomic, copy) fbt_void_datasnapshot datasnapshotCallback; +@property(nonatomic, copy) fbt_void_nserror cancelCallback; +@property(nonatomic, copy) FQueryParams *queryParams; +@property(nonatomic) NSUInteger handle; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleUserCallback.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleUserCallback.m new file mode 100644 index 0000000..ba5861f --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/Utilities/Tuples/FTupleUserCallback.m @@ -0,0 +1,35 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FTupleUserCallback.h" + +@implementation FTupleUserCallback + +@synthesize datasnapshotCallback; +@synthesize datasnapshotPrevnameCallback; +@synthesize cancelCallback; +@synthesize queryParams; +@synthesize handle; + +- (id)initWithHandle:(NSUInteger)theHandle { + self = [super init]; + if (self) { + self.handle = theHandle; + } + return self; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FArraySortedDictionary.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FArraySortedDictionary.h new file mode 100644 index 0000000..3ab7476 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FArraySortedDictionary.h @@ -0,0 +1,21 @@ +#import +#import "FImmutableSortedDictionary.h" + +/** + * This is an array backed implementation of FImmutableSortedDictionary. It uses arrays and linear lookups to achieve + * good memory efficiency while maintaining good performance for small collections. It also uses less allocations than + * a comparable red black tree. To avoid degrading performance with increasing collection size it will automatically + * convert to a FTreeSortedDictionary after an insert call above a certain threshold. + */ +@interface FArraySortedDictionary : FImmutableSortedDictionary + ++ (FArraySortedDictionary *)fromDictionary:(NSDictionary *)dictionary withComparator:(NSComparator)comparator; + +- (id)initWithComparator:(NSComparator)comparator; + +#pragma mark - +#pragma mark Properties + +@property (nonatomic, copy, readonly) NSComparator comparator; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FArraySortedDictionary.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FArraySortedDictionary.m new file mode 100644 index 0000000..15d2d8b --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FArraySortedDictionary.m @@ -0,0 +1,266 @@ +#import "FArraySortedDictionary.h" +#import "FTreeSortedDictionary.h" + +@interface FArraySortedDictionaryEnumerator : NSEnumerator + +- (id)initWithKeys:(NSArray *)keys startPos:(NSInteger)pos isReverse:(BOOL)reverse; +- (id)nextObject; + +@property (nonatomic) NSInteger pos; +@property (nonatomic) BOOL reverse; +@property (nonatomic, strong) NSArray *keys; + +@end + +@implementation FArraySortedDictionaryEnumerator + +- (id)initWithKeys:(NSArray *)keys startPos:(NSInteger)pos isReverse:(BOOL)reverse +{ + self = [super init]; + if (self != nil) { + self->_pos = pos; + self->_reverse = reverse; + self->_keys = keys; + } + return self; +} + +- (id)nextObject +{ + NSInteger pos = self->_pos; + if (pos >= 0 && pos < self.keys.count) { + if (self.reverse) { + self->_pos--; + } else { + self->_pos++; + } + return self.keys[pos]; + } else { + return nil; + } +} + +@end + +@interface FArraySortedDictionary () + +- (id)initWithComparator:(NSComparator)comparator; + +@property (nonatomic, copy, readwrite) NSComparator comparator; +@property (nonatomic, strong) NSArray *keys; +@property (nonatomic, strong) NSArray *values; + +@end + +@implementation FArraySortedDictionary + ++ (FArraySortedDictionary *)fromDictionary:(NSDictionary *)dictionary withComparator:(NSComparator)comparator +{ + NSMutableArray *keys = [NSMutableArray arrayWithCapacity:dictionary.count]; + [dictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + [keys addObject:key]; + }]; + [keys sortUsingComparator:comparator]; + + [keys enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + if (idx > 0) { + if (comparator(keys[idx - 1], obj) != NSOrderedAscending) { + [NSException raise:NSInvalidArgumentException format:@"Can't create FImmutableSortedDictionary with keys with same ordering!"]; + } + } + }]; + + NSMutableArray *values = [NSMutableArray arrayWithCapacity:keys.count]; + NSInteger pos = 0; + for (id key in keys) { + values[pos++] = dictionary[key]; + } + NSAssert(values.count == keys.count, @"We added as many keys as values"); + return [[FArraySortedDictionary alloc] initWithComparator:comparator keys:keys values:values]; +} + +- (id)initWithComparator:(NSComparator)comparator +{ + self = [super init]; + if (self != nil) { + self->_comparator = comparator; + self->_keys = [NSArray array]; + self->_values = [NSArray array]; + } + return self; +} + +- (id)initWithComparator:(NSComparator)comparator keys:(NSArray *)keys values:(NSArray *)values +{ + self = [super init]; + if (self != nil) { + self->_comparator = comparator; + self->_keys = keys; + self->_values = values; + } + return self; +} + +- (NSInteger) findInsertPositionForKey:(id)key +{ + NSInteger newPos = 0; + while (newPos < self.keys.count && self.comparator(self.keys[newPos], key) < NSOrderedSame) { + newPos++; + } + return newPos; +} + +- (NSInteger) findKey:(id)key +{ + if (key == nil) { + return NSNotFound; + } + for (NSInteger pos = 0; pos < self.keys.count; pos++) { + NSComparisonResult result = self.comparator(key, self.keys[pos]); + if (result == NSOrderedSame) { + return pos; + } else if (result == NSOrderedAscending) { + return NSNotFound; + } + } + return NSNotFound; +} + +- (FImmutableSortedDictionary *) insertKey:(id)key withValue:(id)value +{ + NSInteger pos = [self findKey:key]; + + if (pos == NSNotFound) { + /* + * If we're above the threshold we want to convert it to a tree backed implementation to not have + * degrading performance + */ + if (self.count >= SORTED_DICTIONARY_ARRAY_TO_RB_TREE_SIZE_THRESHOLD) { + NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:self.count]; + for (NSInteger i = 0; i < self.keys.count; i++) { + dict[self.keys[i]] = self.values[i]; + } + dict[key] = value; + return [FTreeSortedDictionary fromDictionary:dict withComparator:self.comparator]; + } else { + NSMutableArray *newKeys = [NSMutableArray arrayWithArray:self.keys]; + NSMutableArray *newValues = [NSMutableArray arrayWithArray:self.values]; + NSInteger newPos = [self findInsertPositionForKey:key]; + [newKeys insertObject:key atIndex:newPos]; + [newValues insertObject:value atIndex:newPos]; + return [[FArraySortedDictionary alloc] initWithComparator:self.comparator keys:newKeys values:newValues]; + } + } else { + NSMutableArray *newKeys = [NSMutableArray arrayWithArray:self.keys]; + NSMutableArray *newValues = [NSMutableArray arrayWithArray:self.values]; + newKeys[pos] = key; + newValues[pos] = value; + return [[FArraySortedDictionary alloc] initWithComparator:self.comparator keys:newKeys values:newValues]; + } +} + +- (FImmutableSortedDictionary *) removeKey:(id)key +{ + NSInteger pos = [self findKey:key]; + if (pos == NSNotFound) { + return self; + } else { + NSMutableArray *newKeys = [NSMutableArray arrayWithArray:self.keys]; + NSMutableArray *newValues = [NSMutableArray arrayWithArray:self.values]; + [newKeys removeObjectAtIndex:pos]; + [newValues removeObjectAtIndex:pos]; + return [[FArraySortedDictionary alloc] initWithComparator:self.comparator keys:newKeys values:newValues]; + } +} + +- (id) get:(id)key +{ + NSInteger pos = [self findKey:key]; + if (pos == NSNotFound) { + return nil; + } else { + return self.values[pos]; + } +} + +- (id) getPredecessorKey:(id) key { + NSInteger pos = [self findKey:key]; + if (pos == NSNotFound) { + [NSException raise:NSInternalInconsistencyException format:@"Can't get predecessor key for non-existent key"]; + return nil; + } else if (pos == 0) { + return nil; + } else { + return self.keys[pos - 1]; + } +} + +- (BOOL) isEmpty { + return self.keys.count == 0; +} + +- (int) count +{ + return (int)self.keys.count; +} + +- (id) minKey +{ + return [self.keys firstObject]; +} + +- (id) maxKey +{ + return [self.keys lastObject]; +} + +- (void) enumerateKeysAndObjectsUsingBlock:(void (^)(id, id, BOOL *))block +{ + [self enumerateKeysAndObjectsReverse:NO usingBlock:block]; +} + +- (void) enumerateKeysAndObjectsReverse:(BOOL)reverse usingBlock:(void (^)(id, id, BOOL *))block +{ + if (reverse) { + BOOL stop = NO; + for (NSInteger i = self.keys.count - 1; i >= 0; i--) { + block(self.keys[i], self.values[i], &stop); + if (stop) return; + } + } else { + BOOL stop = NO; + for (NSInteger i = 0; i < self.keys.count; i++) { + block(self.keys[i], self.values[i], &stop); + if (stop) return; + } + } +} + +- (BOOL) contains:(id)key { + return [self findKey:key] != NSNotFound; +} + +- (NSEnumerator *) keyEnumerator { + return [self.keys objectEnumerator]; +} + +- (NSEnumerator *) keyEnumeratorFrom:(id)startKey { + NSInteger startPos = [self findInsertPositionForKey:startKey]; + return [[FArraySortedDictionaryEnumerator alloc] initWithKeys:self.keys startPos:startPos isReverse:NO]; +} + +- (NSEnumerator *) reverseKeyEnumerator { + return [self.keys reverseObjectEnumerator]; +} + +- (NSEnumerator *) reverseKeyEnumeratorFrom:(id)startKey { + NSInteger startPos = [self findInsertPositionForKey:startKey]; + // if there's no exact match, findKeyOrInsertPosition will return the index *after* the closest match, but + // since this is a reverse iterator, we want to start just *before* the closest match. + if (startPos >= self.keys.count || self.comparator(self.keys[startPos], startKey) != NSOrderedSame) { + startPos -= 1; + } + return [[FArraySortedDictionaryEnumerator alloc] initWithKeys:self.keys startPos:startPos isReverse:YES]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FImmutableSortedDictionary.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FImmutableSortedDictionary.h new file mode 100644 index 0000000..d6687d8 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FImmutableSortedDictionary.h @@ -0,0 +1,54 @@ +/** + * @fileoverview Implementation of an immutable SortedMap using a Left-leaning + * Red-Black Tree, adapted from the implementation in Mugs + * (http://mads379.github.com/mugs/) by Mads Hartmann Jensen + * (mads379@gmail.com). + * + * Original paper on Left-leaning Red-Black Trees: + * http://www.cs.princeton.edu/~rs/talks/LLRB/LLRB.pdf + * + * Invariant 1: No red node has a red child + * Invariant 2: Every leaf path has the same number of black nodes + * Invariant 3: Only the left child can be red (left leaning) + */ + +#import + +/** + * The size threshold where we use a tree backed sorted map instead of an array backed sorted map. + * This is a more or less arbitrary chosen value, that was chosen to be large enough to fit most of object kind + * of Firebase data, but small enough to not notice degradation in performance for inserting and lookups. + * Feel free to empirically determine this constant, but don't expect much gain in real world performance. + */ +#define SORTED_DICTIONARY_ARRAY_TO_RB_TREE_SIZE_THRESHOLD 25 + +@interface FImmutableSortedDictionary : NSObject + ++ (FImmutableSortedDictionary *)dictionaryWithComparator:(NSComparator)comparator; ++ (FImmutableSortedDictionary *)fromDictionary:(NSDictionary *)dictionary withComparator:(NSComparator)comparator; + +- (FImmutableSortedDictionary *) insertKey:(id)aKey withValue:(id)aValue; +- (FImmutableSortedDictionary *) removeKey:(id)aKey; +- (id) get:(id) key; +- (id) getPredecessorKey:(id) key; +- (BOOL) isEmpty; +- (int) count; +- (id) minKey; +- (id) maxKey; +- (void) enumerateKeysAndObjectsUsingBlock:(void(^)(id key, id value, BOOL *stop))block; +- (void) enumerateKeysAndObjectsReverse:(BOOL)reverse usingBlock:(void(^)(id key, id value, BOOL *stop))block; +- (BOOL) contains:(id)key; +- (NSEnumerator *) keyEnumerator; +- (NSEnumerator *) keyEnumeratorFrom:(id)startKey; +- (NSEnumerator *) reverseKeyEnumerator; +- (NSEnumerator *) reverseKeyEnumeratorFrom:(id)startKey; + +#pragma mark - +#pragma mark Methods similar to NSMutableDictionary + +- (FImmutableSortedDictionary *) setObject:(id)anObject forKey:(id)aKey; +- (id) objectForKey:(id)key; +- (FImmutableSortedDictionary *) removeObjectForKey:(id)aKey; + +@end + diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FImmutableSortedDictionary.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FImmutableSortedDictionary.m new file mode 100644 index 0000000..659c63b --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FImmutableSortedDictionary.m @@ -0,0 +1,142 @@ +#import "FImmutableSortedDictionary.h" +#import "FArraySortedDictionary.h" +#import "FTreeSortedDictionary.h" + +#define THROW_ABSTRACT_METHOD_EXCEPTION(sel) do { \ + @throw [NSException exceptionWithName:NSInternalInconsistencyException \ + reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(sel)] \ + userInfo:nil]; \ +} while(0) + +@implementation FImmutableSortedDictionary + ++ (FImmutableSortedDictionary *)dictionaryWithComparator:(NSComparator)comparator +{ + return [[FArraySortedDictionary alloc] initWithComparator:comparator]; +} + ++ (FImmutableSortedDictionary *)fromDictionary:(NSDictionary *)dictionary withComparator:(NSComparator)comparator +{ + if (dictionary.count <= SORTED_DICTIONARY_ARRAY_TO_RB_TREE_SIZE_THRESHOLD) { + return [FArraySortedDictionary fromDictionary:dictionary withComparator:comparator]; + } else { + return [FTreeSortedDictionary fromDictionary:dictionary withComparator:comparator]; + } +} + +- (FImmutableSortedDictionary *) insertKey:(id)aKey withValue:(id)aValue { + THROW_ABSTRACT_METHOD_EXCEPTION(@selector(insertKey:withValue:)); +} + +- (FImmutableSortedDictionary *) removeKey:(id)aKey { + THROW_ABSTRACT_METHOD_EXCEPTION(@selector(removeKey:)); +} + +- (id) get:(id) key { + THROW_ABSTRACT_METHOD_EXCEPTION(@selector(get:)); +} + +- (id) getPredecessorKey:(id) key { + THROW_ABSTRACT_METHOD_EXCEPTION(@selector(getPredecessorKey:)); +} + +- (BOOL) isEmpty { + THROW_ABSTRACT_METHOD_EXCEPTION(@selector(isEmpty)); +} + +- (int) count { + THROW_ABSTRACT_METHOD_EXCEPTION(@selector((count))); +} + +- (id) minKey { + THROW_ABSTRACT_METHOD_EXCEPTION(@selector(minKey)); +} + +- (id) maxKey { + THROW_ABSTRACT_METHOD_EXCEPTION(@selector(maxKey)); +} + +- (void) enumerateKeysAndObjectsUsingBlock:(void (^)(id, id, BOOL *))block { + THROW_ABSTRACT_METHOD_EXCEPTION(@selector(enumerateKeysAndObjectsUsingBlock:)); +} + +- (void) enumerateKeysAndObjectsReverse:(BOOL)reverse usingBlock:(void (^)(id, id, BOOL *))block { + THROW_ABSTRACT_METHOD_EXCEPTION(@selector(enumerateKeysAndObjectsReverse:usingBlock:)); +} + +- (BOOL) contains:(id)key { + THROW_ABSTRACT_METHOD_EXCEPTION(@selector(contains:)); +} + +- (NSEnumerator *) keyEnumerator { + THROW_ABSTRACT_METHOD_EXCEPTION(@selector(keyEnumerator)); +} + +- (NSEnumerator *) keyEnumeratorFrom:(id)startKey { + THROW_ABSTRACT_METHOD_EXCEPTION(@selector(keyEnumeratorFrom:)); +} + +- (NSEnumerator *) reverseKeyEnumerator { + THROW_ABSTRACT_METHOD_EXCEPTION(@selector(reverseKeyEnumerator)); +} + +- (NSEnumerator *) reverseKeyEnumeratorFrom:(id)startKey { + THROW_ABSTRACT_METHOD_EXCEPTION(@selector(reverseKeyEnumeratorFrom:)); +} + +- (BOOL)isEqual:(id)object { + if (![object isKindOfClass:[FImmutableSortedDictionary class]]) { + return NO; + } + FImmutableSortedDictionary *other = (FImmutableSortedDictionary *)object; + if (self.count != other.count) { + return NO; + } + __block BOOL isEqual = YES; + [self enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) { + id otherValue = [other objectForKey:key]; + isEqual = isEqual && (value == otherValue || [value isEqual:otherValue]); + *stop = !isEqual; + }]; + return isEqual; +} + +- (NSUInteger)hash { + __block NSUInteger hash = 0; + [self enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) { + hash = (hash * 31 + [key hash]) * 17 + [value hash]; + }]; + return hash; +} + +- (NSString *)description { + NSMutableString *str = [[NSMutableString alloc] init]; + __block BOOL first = YES; + [str appendString:@"{ "]; + [self enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) { + if (!first) { + [str appendString:@", "]; + } + first = NO; + [str appendString:[NSString stringWithFormat:@"%@: %@", key, value]]; + }]; + [str appendString:@" }"]; + return str; +} + +#pragma mark - +#pragma mark Methods similar to NSMutableDictionary + +- (FImmutableSortedDictionary *) setObject:(__unsafe_unretained id)anObject forKey:(__unsafe_unretained id)aKey { + return [self insertKey:aKey withValue:anObject]; +} + +- (FImmutableSortedDictionary *) removeObjectForKey:(__unsafe_unretained id)aKey { + return [self removeKey:aKey]; +} + +- (id) objectForKey:(__unsafe_unretained id)key { + return [self get:key]; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FImmutableSortedSet.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FImmutableSortedSet.h new file mode 100644 index 0000000..ac15c2f --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FImmutableSortedSet.h @@ -0,0 +1,22 @@ +#import + +@interface FImmutableSortedSet : NSObject + ++ (FImmutableSortedSet *)setWithKeysFromDictionary:(NSDictionary *)array comparator:(NSComparator)comparator; + +- (BOOL)containsObject:(id)object; +- (FImmutableSortedSet *)addObject:(id)object; +- (FImmutableSortedSet *)removeObject:(id)object; +- (id)firstObject; +- (id)lastObject; +- (NSUInteger)count; +- (BOOL)isEmpty; + +- (id)predecessorEntry:(id)entry; + +- (void)enumerateObjectsUsingBlock:(void (^)(id obj, BOOL *stop))block; +- (void)enumerateObjectsReverse:(BOOL)reverse usingBlock:(void (^)(id obj, BOOL *stop))block; + +- (NSEnumerator *)objectEnumerator; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FImmutableSortedSet.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FImmutableSortedSet.m new file mode 100644 index 0000000..1953af1 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FImmutableSortedSet.m @@ -0,0 +1,115 @@ +#import "FImmutableSortedSet.h" +#import "FImmutableSortedDictionary.h" + +@interface FImmutableSortedSet () + +@property (nonatomic, strong) FImmutableSortedDictionary *dictionary; + +@end + +@implementation FImmutableSortedSet + ++ (FImmutableSortedSet *)setWithKeysFromDictionary:(NSDictionary *)dictionary comparator:(NSComparator)comparator +{ + FImmutableSortedDictionary *setDict = [FImmutableSortedDictionary fromDictionary:dictionary withComparator:comparator]; + return [[FImmutableSortedSet alloc] initWithDictionary:setDict]; +} + +- (id)initWithDictionary:(FImmutableSortedDictionary *)dictionary +{ + self = [super init]; + if (self != nil) { + self->_dictionary = dictionary; + } + return self; +} + +- (BOOL)contains:(id)object +{ + return [self.dictionary contains:object]; +} + +- (FImmutableSortedSet *)addObject:(id)object +{ + FImmutableSortedDictionary *newDictionary = [self.dictionary insertKey:object withValue:[NSNull null]]; + if (newDictionary != self.dictionary) { + return [[FImmutableSortedSet alloc] initWithDictionary:newDictionary]; + } else { + return self; + } +} + +- (FImmutableSortedSet *)removeObject:(id)object +{ + FImmutableSortedDictionary *newDictionary = [self.dictionary removeObjectForKey:object]; + if (newDictionary != self.dictionary) { + return [[FImmutableSortedSet alloc] initWithDictionary:newDictionary]; + } else { + return self; + } +} + +- (BOOL)containsObject:(id)object +{ + return [self.dictionary contains:object]; +} + +- (id)firstObject +{ + return [self.dictionary minKey]; +} + +- (id)lastObject +{ + return [self.dictionary maxKey]; +} + +- (id)predecessorEntry:(id)entry +{ + return [self.dictionary getPredecessorKey:entry]; +} + +- (NSUInteger)count +{ + return [self.dictionary count]; +} + +- (BOOL)isEmpty +{ + return [self.dictionary isEmpty]; +} + +- (void)enumerateObjectsUsingBlock:(void (^)(id, BOOL *))block +{ + [self enumerateObjectsReverse:NO usingBlock:block]; +} + +- (void)enumerateObjectsReverse:(BOOL)reverse usingBlock:(void (^)(id, BOOL *))block +{ + [self.dictionary enumerateKeysAndObjectsReverse:reverse usingBlock:^(id key, id value, BOOL *stop) { + block(key, stop); + }]; +} + +- (NSEnumerator *)objectEnumerator +{ + return [self.dictionary keyEnumerator]; +} + +- (NSString *)description +{ + NSMutableString *str = [[NSMutableString alloc] init]; + __block BOOL first = YES; + [str appendString:@"FImmutableSortedSet ( "]; + [self enumerateObjectsUsingBlock:^(id obj, BOOL *stop) { + if (!first) { + [str appendString:@", "]; + } + first = NO; + [str appendString:[NSString stringWithFormat:@"%@", obj]]; + }]; + [str appendString:@" )"]; + return str; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FLLRBEmptyNode.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FLLRBEmptyNode.h new file mode 100644 index 0000000..833f2a5 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FLLRBEmptyNode.h @@ -0,0 +1,27 @@ +#import +#import "FLLRBNode.h" + +@interface FLLRBEmptyNode : NSObject + ++ (id)emptyNode; + +- (id)copyWith:(id) aKey withValue:(id) aValue withColor:(FLLRBColor*) aColor withLeft:(id)aLeft withRight:(id)aRight; +- (id) insertKey:(id) aKey forValue:(id)aValue withComparator:(NSComparator)aComparator; +- (id) remove:(id) aKey withComparator:(NSComparator)aComparator; +- (int) count; +- (BOOL) isEmpty; +- (BOOL) inorderTraversal:(BOOL (^)(id key, id value))action; +- (BOOL) reverseTraversal:(BOOL (^)(id key, id value))action; +- (id) min; +- (id) minKey; +- (id) maxKey; +- (BOOL) isRed; +- (int) check; + +@property (nonatomic, strong) id key; +@property (nonatomic, strong) id value; +@property (nonatomic, strong) FLLRBColor* color; +@property (nonatomic, strong) id left; +@property (nonatomic, strong) id right; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FLLRBEmptyNode.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FLLRBEmptyNode.m new file mode 100644 index 0000000..3ca6f06 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FLLRBEmptyNode.m @@ -0,0 +1,72 @@ +#import "FLLRBEmptyNode.h" +#import "FLLRBValueNode.h" + +@implementation FLLRBEmptyNode + +@synthesize key, value, color, left, right; + +- (NSString *) description { + return [NSString stringWithFormat:@"[key=%@ val=%@ color=%@]", key, value, + (color != nil ? @"true" : @"false")]; +} + ++ (id)emptyNode +{ + static dispatch_once_t pred = 0; + __strong static id _sharedObject = nil; + dispatch_once(&pred, ^{ + _sharedObject = [[self alloc] init]; // or some other init method + }); + return _sharedObject; +} + +- (id)copyWith:(id) aKey withValue:(id) aValue withColor:(FLLRBColor*) aColor withLeft:(id)aLeft withRight:(id)aRight { + return self; +} + +- (id) insertKey:(id) aKey forValue:(id)aValue withComparator:(NSComparator)aComparator { + FLLRBValueNode* result = [[FLLRBValueNode alloc] initWithKey:aKey withValue:aValue withColor:nil withLeft:nil withRight:nil]; + return result; +} + +- (id) remove:(id) key withComparator:(NSComparator)aComparator { + return self; +} + +- (int) count { + return 0; +} + +- (BOOL) isEmpty { + return YES; +} + +- (BOOL) inorderTraversal:(BOOL (^)(id key, id value))action { + return NO; +} + +- (BOOL) reverseTraversal:(BOOL (^)(id key, id value))action { + return NO; +} + +- (id) min { + return self; +} + +- (id) minKey { + return nil; +} + +- (id) maxKey { + return nil; +} + +- (BOOL) isRed { + return NO; +} + +- (int) check { + return 0; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FLLRBNode.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FLLRBNode.h new file mode 100644 index 0000000..09b234c --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FLLRBNode.h @@ -0,0 +1,29 @@ +#import + +#define RED @true +#define BLACK @false + +typedef NSNumber FLLRBColor; + +@protocol FLLRBNode + +- (id)copyWith:(id) aKey withValue:(id) aValue withColor:(FLLRBColor*) aColor withLeft:(id)aLeft withRight:(id)aRight; +- (id) insertKey:(id) aKey forValue:(id)aValue withComparator:(NSComparator)aComparator; +- (id) remove:(id) key withComparator:(NSComparator)aComparator; +- (int) count; +- (BOOL) isEmpty; +- (BOOL) inorderTraversal:(BOOL (^)(id key, id value))action; +- (BOOL) reverseTraversal:(BOOL (^)(id key, id value))action; +- (id) min; +- (id) minKey; +- (id) maxKey; +- (BOOL) isRed; +- (int) check; + +@property (nonatomic, strong) id key; +@property (nonatomic, strong) id value; +@property (nonatomic, strong) FLLRBColor* color; +@property (nonatomic, strong) id left; +@property (nonatomic, strong) id right; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FLLRBValueNode.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FLLRBValueNode.h new file mode 100644 index 0000000..50438bb --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FLLRBValueNode.h @@ -0,0 +1,29 @@ +#import +#import "FLLRBNode.h" + +@interface FLLRBValueNode : NSObject + + +- (id)initWithKey:(id) key withValue:(id) value withColor:(FLLRBColor*) color withLeft:(id)left withRight:(id)right; +- (id)copyWith:(id) aKey withValue:(id) aValue withColor:(FLLRBColor*) aColor withLeft:(id)aLeft withRight:(id)aRight; +- (id) insertKey:(id) aKey forValue:(id)aValue withComparator:(NSComparator)aComparator; +- (id) remove:(id) aKey withComparator:(NSComparator)aComparator; +- (int) count; +- (BOOL) isEmpty; +- (BOOL) inorderTraversal:(BOOL (^)(id key, id value))action; +- (BOOL) reverseTraversal:(BOOL (^)(id key, id value))action; +- (id) min; +- (id) minKey; +- (id) maxKey; +- (BOOL) isRed; +- (int) check; + +- (BOOL) checkMaxDepth; + +@property (nonatomic, strong) id key; +@property (nonatomic, strong) id value; +@property (nonatomic, strong) FLLRBColor* color; +@property (nonatomic, strong) id left; +@property (nonatomic, strong) id right; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FLLRBValueNode.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FLLRBValueNode.m new file mode 100644 index 0000000..831b97f --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FLLRBValueNode.m @@ -0,0 +1,230 @@ +#import "FLLRBValueNode.h" +#import "FLLRBEmptyNode.h" + +@implementation FLLRBValueNode + +@synthesize key, value, color, left, right; + +- (NSString *) description { + return [NSString stringWithFormat:@"[key=%@ val=%@ color=%@]", key, value, + (color != nil ? @"true" : @"false")]; +} + +- (id)initWithKey:(__unsafe_unretained id) aKey withValue:(__unsafe_unretained id) aValue withColor:(__unsafe_unretained FLLRBColor*) aColor withLeft:(__unsafe_unretained id)aLeft withRight:(__unsafe_unretained id)aRight +{ + self = [super init]; + if (self) { + self.key = aKey; + self.value = aValue; + self.color = aColor != nil ? aColor : RED; + self.left = aLeft != nil ? aLeft : [FLLRBEmptyNode emptyNode]; + self.right = aRight != nil ? aRight : [FLLRBEmptyNode emptyNode]; + } + return self; +} + +- (id)copyWith:(__unsafe_unretained id) aKey withValue:(__unsafe_unretained id) aValue withColor:(__unsafe_unretained FLLRBColor*) aColor withLeft:(__unsafe_unretained id)aLeft withRight:(__unsafe_unretained id)aRight { + return [[FLLRBValueNode alloc] initWithKey:(aKey != nil) ? aKey : self.key + withValue:(aValue != nil) ? aValue : self.value + withColor:(aColor != nil) ? aColor : self.color + withLeft:(aLeft != nil) ? aLeft : self.left + withRight:(aRight != nil) ? aRight : self.right]; +} + +- (int) count { + return [self.left count] + 1 + [self.right count]; +} + +- (BOOL) isEmpty { + return NO; +} + +/** +* Early terminates if aciton returns YES. +* @return The first truthy value returned by action, or the last falsey value returned by action. +*/ +- (BOOL) inorderTraversal:(BOOL (^)(id key, id value))action { + return [self.left inorderTraversal:action] || + action(self.key, self.value) || + [self.right inorderTraversal:action]; +} + +- (BOOL) reverseTraversal:(BOOL (^)(id key, id value))action { + return [self.right reverseTraversal:action] || + action(self.key, self.value) || + [self.left reverseTraversal:action]; +} + +- (id) min { + if([self.left isEmpty]) { + return self; + } + else { + return [self.left min]; + } +} + +- (id) minKey { + return [[self min] key]; +} + +- (id) maxKey { + if([self.right isEmpty]) { + return self.key; + } + else { + return [self.right maxKey]; + } +} + +- (id) insertKey:(__unsafe_unretained id) aKey forValue:(__unsafe_unretained id)aValue withComparator:(NSComparator)aComparator { + NSComparisonResult cmp = aComparator(aKey, self.key); + FLLRBValueNode* n = self; + + if(cmp == NSOrderedAscending) { + n = [n copyWith:nil withValue:nil withColor:nil withLeft:[n.left insertKey:aKey forValue:aValue withComparator:aComparator] withRight:nil]; + } + else if(cmp == NSOrderedSame) { + n = [n copyWith:nil withValue:aValue withColor:nil withLeft:nil withRight:nil]; + } + else { + n = [n copyWith:nil withValue:nil withColor:nil withLeft:nil withRight:[n.right insertKey:aKey forValue:aValue withComparator:aComparator]]; + } + + return [n fixUp]; +} + +- (id) removeMin { + + if([self.left isEmpty]) { + return [FLLRBEmptyNode emptyNode]; + } + + FLLRBValueNode* n = self; + if(! [n.left isRed] && ! [n.left.left isRed]) { + n = [n moveRedLeft]; + } + + n = [n copyWith:nil withValue:nil withColor:nil withLeft:[(FLLRBValueNode*)n.left removeMin] withRight:nil]; + return [n fixUp]; +} + + +- (id) fixUp { + FLLRBValueNode* n = self; + if([n.right isRed] && ! [n.left isRed]) n = [n rotateLeft]; + if([n.left isRed] && [n.left.left isRed]) n = [n rotateRight]; + if([n.left isRed] && [n.right isRed]) n = [n colorFlip]; + return n; +} + +- (FLLRBValueNode*) moveRedLeft { + FLLRBValueNode* n = [self colorFlip]; + if([n.right.left isRed]) { + n = [n copyWith:nil withValue:nil withColor:nil withLeft:nil withRight:[(FLLRBValueNode*)n.right rotateRight]]; + n = [n rotateLeft]; + n = [n colorFlip]; + } + return n; +} + +- (FLLRBValueNode*) moveRedRight { + FLLRBValueNode* n = [self colorFlip]; + if([n.left.left isRed]) { + n = [n rotateRight]; + n = [n colorFlip]; + } + return n; +} + +- (id) rotateLeft { + id nl = [self copyWith:nil withValue:nil withColor:RED withLeft:nil withRight:self.right.left]; + return [self.right copyWith:nil withValue:nil withColor:self.color withLeft:nl withRight:nil];; +} + +- (id) rotateRight { + id nr = [self copyWith:nil withValue:nil withColor:RED withLeft:self.left.right withRight:nil]; + return [self.left copyWith:nil withValue:nil withColor:self.color withLeft:nil withRight:nr]; +} + +- (id) colorFlip { + id nleft = [self.left copyWith:nil withValue:nil withColor:[NSNumber numberWithBool:![self.left.color boolValue]] withLeft:nil withRight:nil]; + id nright = [self.right copyWith:nil withValue:nil withColor:[NSNumber numberWithBool:![self.right.color boolValue]] withLeft:nil withRight:nil]; + + return [self copyWith:nil withValue:nil withColor:[NSNumber numberWithBool:![self.color boolValue]] withLeft:nleft withRight:nright]; +} + +- (id) remove:(__unsafe_unretained id) aKey withComparator:(NSComparator)comparator { + id smallest; + FLLRBValueNode* n = self; + + if(comparator(aKey, n.key) == NSOrderedAscending) { + if(![n.left isEmpty] && ![n.left isRed] && ![n.left.left isRed]) { + n = [n moveRedLeft]; + } + n = [n copyWith:nil withValue:nil withColor:nil withLeft:[n.left remove:aKey withComparator:comparator] withRight:nil]; + } + else { + if([n.left isRed]) { + n = [n rotateRight]; + } + + if(![n.right isEmpty] && ![n.right isRed] && ![n.right.left isRed]) { + n = [n moveRedRight]; + } + + if(comparator(aKey, n.key) == NSOrderedSame) { + if([n.right isEmpty]) { + return [FLLRBEmptyNode emptyNode]; + } + else { + smallest = [n.right min]; + n = [n copyWith:smallest.key withValue:smallest.value withColor:nil withLeft:nil withRight:[(FLLRBValueNode*)n.right removeMin]]; + } + } + n = [n copyWith:nil withValue:nil withColor:nil withLeft:nil withRight:[n.right remove:aKey withComparator:comparator]]; + } + return [n fixUp]; +} + +- (BOOL) isRed { + return [self.color boolValue]; +} + +- (BOOL) checkMaxDepth { + int blackDepth = [self check]; + if(pow(2.0, blackDepth) <= ([self count] + 1)) { + return YES; + } + else { + return NO; + } +} + +- (int) check { + int blackDepth = 0; + + if([self isRed] && [self.left isRed]) { + @throw [[NSException alloc] initWithName:@"check" reason:@"Red node has a red child" userInfo:nil]; + } + + if([self.right isRed]) { + @throw [[NSException alloc] initWithName:@"check" reason:@"Right child is red" userInfo:nil]; + } + + blackDepth = [self.left check]; +// NSLog(err); + if(blackDepth != [self.right check]) { + NSString* err = [NSString stringWithFormat:@"(%@ -> %@)blackDepth: %d ; self.right check: %d", self.value, [self.color boolValue] ? @"red" : @"black", blackDepth, [self.right check]]; +// return 10; + @throw [[NSException alloc] initWithName:@"check" reason:err userInfo:nil]; + } + else { + int ret = blackDepth + ([self isRed] ? 0 : 1); +// NSLog(@"black depth is: %d; other is: %d, ret is: %d", blackDepth, ([self isRed] ? 0 : 1), ret); + return ret; + } +} + + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FTreeSortedDictionary.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FTreeSortedDictionary.h new file mode 100644 index 0000000..934ca8b --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FTreeSortedDictionary.h @@ -0,0 +1,30 @@ +/** + * @fileoverview Implementation of an immutable SortedMap using a Left-leaning + * Red-Black Tree, adapted from the implementation in Mugs + * (http://mads379.github.com/mugs/) by Mads Hartmann Jensen + * (mads379@gmail.com). + * + * Original paper on Left-leaning Red-Black Trees: + * http://www.cs.princeton.edu/~rs/talks/LLRB/LLRB.pdf + * + * Invariant 1: No red node has a red child + * Invariant 2: Every leaf path has the same number of black nodes + * Invariant 3: Only the left child can be red (left leaning) + */ + +#import +#import "FImmutableSortedDictionary.h" +#import "FLLRBNode.h" + +@interface FTreeSortedDictionary : FImmutableSortedDictionary + +@property (nonatomic, copy, readonly) NSComparator comparator; +@property (nonatomic, strong, readonly) id root; + +- (id)initWithComparator:(NSComparator)aComparator; + +// Override methods to return subtype +- (FTreeSortedDictionary *) insertKey:(id)aKey withValue:(id)aValue; +- (FTreeSortedDictionary *) removeKey:(id)aKey; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FTreeSortedDictionary.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FTreeSortedDictionary.m new file mode 100644 index 0000000..e9f0683 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FTreeSortedDictionary.m @@ -0,0 +1,326 @@ +#import "FTreeSortedDictionary.h" +#import "FLLRBEmptyNode.h" +#import "FLLRBValueNode.h" +#import "FTreeSortedDictionaryEnumerator.h" + +typedef void (^fbt_void_nsnumber_int)(NSNumber* color, NSUInteger chunkSize); + +@interface FTreeSortedDictionary () + +@property (nonatomic, strong) id root; +@property (nonatomic, copy, readwrite) NSComparator comparator; + +@end + +@implementation FTreeSortedDictionary + +- (id)initWithComparator:(NSComparator)aComparator { + self = [super init]; + if (self) { + self.root = [FLLRBEmptyNode emptyNode]; + self.comparator = aComparator; + } + return self; +} + +- (id)initWithComparator:(NSComparator)aComparator withRoot:(__unsafe_unretained id)aRoot { + self = [super init]; + if (self) { + self.root = aRoot; + self.comparator = aComparator; + } + return self; +} + +/** + * Returns a copy of the map, with the specified key/value added or replaced. + */ +- (FTreeSortedDictionary *) insertKey:(__unsafe_unretained id)aKey withValue:(__unsafe_unretained id)aValue { + return [[FTreeSortedDictionary alloc] initWithComparator:self.comparator + withRoot:[[self.root insertKey:aKey forValue:aValue withComparator:self.comparator] + copyWith:nil + withValue:nil + withColor:BLACK + withLeft:nil + withRight:nil]]; +} + + +- (FTreeSortedDictionary *) removeKey:(__unsafe_unretained id)aKey { + // Remove is somewhat expensive even if the key doesn't exist (the tree does rebalancing and stuff). So avoid it. + if (![self contains:aKey]) { + return self; + } else { + return [[FTreeSortedDictionary alloc] + initWithComparator:self.comparator + withRoot:[[self.root remove:aKey withComparator:self.comparator] + copyWith:nil + withValue:nil + withColor:BLACK + withLeft:nil + withRight:nil]]; + } +} + +- (id) get:(__unsafe_unretained id) key { + if (key == nil) { + return nil; + } + NSComparisonResult cmp; + id node = self.root; + while(![node isEmpty]) { + cmp = self.comparator(key, node.key); + if(cmp == NSOrderedSame) { + return node.value; + } + else if (cmp == NSOrderedAscending) { + node = node.left; + } + else { + node = node.right; + } + } + return nil; +} + +- (id) getPredecessorKey:(__unsafe_unretained id) key { + NSComparisonResult cmp; + id node = self.root; + id rightParent = nil; + while(![node isEmpty]) { + cmp = self.comparator(key, node.key); + if(cmp == NSOrderedSame) { + if(![node.left isEmpty]) { + node = node.left; + while(! [node.right isEmpty]) { + node = node.right; + } + return node.key; + } + else if (rightParent != nil) { + return rightParent.key; + } + else { + return nil; + } + } + else if (cmp == NSOrderedAscending) { + node = node.left; + } + else if (cmp == NSOrderedDescending) { + rightParent = node; + node = node.right; + } + } + @throw [NSException exceptionWithName:@"NonexistentKey" reason:@"getPredecessorKey called with nonexistent key." userInfo:@{@"key": [key description] }]; +} + +- (BOOL) isEmpty { + return [self.root isEmpty]; +} + +- (int) count { + return [self.root count]; +} + +- (id) minKey { + return [self.root minKey]; +} + +- (id) maxKey { + return [self.root maxKey]; +} + +- (void) enumerateKeysAndObjectsUsingBlock:(void (^)(id, id, BOOL *))block +{ + [self enumerateKeysAndObjectsReverse:NO usingBlock:block]; +} + +- (void) enumerateKeysAndObjectsReverse:(BOOL)reverse usingBlock:(void (^)(id, id, BOOL *))block +{ + if (reverse) { + __block BOOL stop = NO; + [self.root reverseTraversal:^BOOL(id key, id value) { + block(key, value, &stop); + return stop; + }]; + } else { + __block BOOL stop = NO; + [self.root inorderTraversal:^BOOL(id key, id value) { + block(key, value, &stop); + return stop; + }]; + } +} + +- (BOOL) contains:(__unsafe_unretained id)key { + return ([self objectForKey:key] != nil); +} + +- (NSEnumerator *) keyEnumerator { + return [[FTreeSortedDictionaryEnumerator alloc] + initWithImmutableSortedDictionary:self startKey:nil isReverse:NO]; +} + +- (NSEnumerator *) keyEnumeratorFrom:(id)startKey { + return [[FTreeSortedDictionaryEnumerator alloc] + initWithImmutableSortedDictionary:self startKey:startKey isReverse:NO]; +} + +- (NSEnumerator *) reverseKeyEnumerator { + return [[FTreeSortedDictionaryEnumerator alloc] + initWithImmutableSortedDictionary:self startKey:nil isReverse:YES]; +} + +- (NSEnumerator *) reverseKeyEnumeratorFrom:(id)startKey { + return [[FTreeSortedDictionaryEnumerator alloc] + initWithImmutableSortedDictionary:self startKey:startKey isReverse:YES]; +} + + +#pragma mark - +#pragma mark Tree Builder + +// Code to efficiently build a RB Tree +typedef struct _base1_2list { + unsigned int bits; + unsigned short count; + unsigned short current; +} Base1_2List; + +Base1_2List *base1_2List_new(unsigned int length); +void base1_2List_free(Base1_2List* list); +unsigned int log_base2(unsigned int num); +BOOL base1_2List_next(Base1_2List* list); + +unsigned int log_base2(unsigned int num) { + return (unsigned int)(log(num) / log(2)); +} + +/** + * Works like an iterator, so it moves to the next bit. Do not call more than list->count times. + * @return whether or not the next bit is a 1 in base {1,2}. + */ +BOOL base1_2List_next(Base1_2List* list) { + BOOL result = !(list->bits & (0x1 << list->current)); + list->current--; + return result; +} + +static inline unsigned bit_mask(int x) { + return (x >= sizeof(unsigned) * CHAR_BIT) ? (unsigned) -1 : (1U << x) - 1; +} + +/** + * We represent the base{1,2} number as the combination of a binary number and a number of bits that we care about + * We iterate backwards, from most significant bit to least, to build up the llrb nodes. 0 base 2 => 1 base {1,2}, 1 base 2 => 2 base {1,2} + */ +Base1_2List *base1_2List_new(unsigned int length) { + size_t sz = sizeof(Base1_2List); + Base1_2List* list = calloc(1, sz); + // Calculate the number of bits that we care about + list->count = (unsigned short)log_base2(length + 1); + unsigned int mask = bit_mask(list->count); + list->bits = (length + 1) & mask; + list->current = list->count - 1; + return list; +} + + +void base1_2List_free(Base1_2List* list) { + free(list); +} + ++ (id) buildBalancedTree:(NSArray *)keys dictionary:(NSDictionary *)dictionary subArrayStartIndex:(NSUInteger)startIndex length:(NSUInteger)length { + length = MIN(keys.count - startIndex, length); // Bound length by the actual length of the array + if (length == 0) { + return nil; + } else if (length == 1) { + id key = keys[startIndex]; + return [[FLLRBValueNode alloc] initWithKey:key withValue:dictionary[key] withColor:BLACK withLeft:nil withRight:nil]; + } else { + NSUInteger middle = length / 2; + id left = [FTreeSortedDictionary buildBalancedTree:keys dictionary:dictionary subArrayStartIndex:startIndex length:middle]; + id right = [FTreeSortedDictionary buildBalancedTree:keys dictionary:dictionary subArrayStartIndex:(startIndex+middle+1) length:middle]; + id key = keys[startIndex + middle]; + return [[FLLRBValueNode alloc] initWithKey:key withValue:dictionary[key] withColor:BLACK withLeft:left withRight:right]; + } +} + ++ (id) rootFrom12List:(Base1_2List *)base1_2List keyList:(NSArray *)keyList dictionary:(NSDictionary *)dictionary { + __block id root = nil; + __block id node = nil; + __block NSUInteger index = keyList.count; + + fbt_void_nsnumber_int buildPennant = ^(NSNumber* color, NSUInteger chunkSize) { + NSUInteger startIndex = index - chunkSize + 1; + index -= chunkSize; + id key = keyList[index]; + id childTree = [self buildBalancedTree:keyList dictionary:dictionary subArrayStartIndex:startIndex length:(chunkSize - 1)]; + id pennant = [[FLLRBValueNode alloc] initWithKey:key withValue:dictionary[key] withColor:color withLeft:nil withRight:childTree]; + //attachPennant(pennant); + if (node) { + node.left = pennant; + node = pennant; + } else { + root = pennant; + node = pennant; + } + }; + + for (int i = 0; i < base1_2List->count; ++i) { + BOOL isOne = base1_2List_next(base1_2List); + NSUInteger chunkSize = (NSUInteger)pow(2.0, base1_2List->count - (i + 1)); + if (isOne) { + buildPennant(BLACK, chunkSize); + } else { + buildPennant(BLACK, chunkSize); + buildPennant(RED, chunkSize); + } + } + return root; +} + +/** + * Uses the algorithm linked here: + * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.46.1458 + */ + ++ (FImmutableSortedDictionary *)fromDictionary:(NSDictionary *)dictionary withComparator:(NSComparator)comparator +{ + // Steps: + // 0. Sort the array + // 1. Calculate the 1-2 number + // 2. Build From 1-2 number + // 0. for each digit in 1-2 number + // 0. calculate chunk size + // 1. build 1 or 2 pennants of that size + // 2. attach pennants and update node pointer + // 1. return root + NSMutableArray *sortedKeyList = [NSMutableArray arrayWithCapacity:dictionary.count]; + [dictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + [sortedKeyList addObject:key]; + }]; + [sortedKeyList sortUsingComparator:comparator]; + + [sortedKeyList enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + if (idx > 0) { + if (comparator(sortedKeyList[idx - 1], obj) != NSOrderedAscending) { + [NSException raise:NSInvalidArgumentException format:@"Can't create FImmutableSortedDictionary with keys with same ordering!"]; + } + } + }]; + + Base1_2List* list = base1_2List_new((unsigned int)sortedKeyList.count); + id root = [self rootFrom12List:list keyList:sortedKeyList dictionary:dictionary]; + base1_2List_free(list); + + if (root != nil) { + return [[FTreeSortedDictionary alloc] initWithComparator:comparator withRoot:root]; + } else { + return [[FTreeSortedDictionary alloc] initWithComparator:comparator]; + } +} + +@end + diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FTreeSortedDictionaryEnumerator.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FTreeSortedDictionaryEnumerator.h new file mode 100644 index 0000000..d79fe8e --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FTreeSortedDictionaryEnumerator.h @@ -0,0 +1,9 @@ +#import +#import "FTreeSortedDictionary.h" + +@interface FTreeSortedDictionaryEnumerator : NSEnumerator + +- (id)initWithImmutableSortedDictionary:(FTreeSortedDictionary *)aDict startKey:(id)startKey isReverse:(BOOL)reverse; +- (id)nextObject; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FTreeSortedDictionaryEnumerator.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FTreeSortedDictionaryEnumerator.m new file mode 100644 index 0000000..2aca86e --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FTreeSortedDictionaryEnumerator.m @@ -0,0 +1,83 @@ +#import "FTreeSortedDictionaryEnumerator.h" + +@interface FTreeSortedDictionaryEnumerator() +@property (nonatomic, strong) FTreeSortedDictionary* immutableSortedDictionary; +@property (nonatomic, strong) NSMutableArray* stack; +@property (nonatomic) BOOL isReverse; + +@end + +@implementation FTreeSortedDictionaryEnumerator + +- (id)initWithImmutableSortedDictionary:(FTreeSortedDictionary *)aDict + startKey:(id)startKey isReverse:(BOOL)reverse { + self = [super init]; + if (self) { + self.immutableSortedDictionary = aDict; + self.stack = [[NSMutableArray alloc] init]; + self.isReverse = reverse; + + NSComparator comparator = aDict.comparator; + id node = self.immutableSortedDictionary.root; + + NSInteger cmp; + while(![node isEmpty]) { + cmp = startKey ? comparator(node.key, startKey) : 1; + // flip the comparison if we're going in reverse + if (self.isReverse) cmp *= -1; + + if (cmp < 0) { + // This node is less than our start key. Ignore it. + if (self.isReverse) { + node = node.left; + } else { + node = node.right; + } + } else if (cmp == 0) { + // This node is exactly equal to our start key. Push it on the stack, but stop iterating: + [self.stack addObject:node]; + break; + } else { + // This node is greater than our start key, add it to the stack and move on to the next one. + [self.stack addObject:node]; + if (self.isReverse) { + node = node.right; + } else { + node = node.left; + } + } + } + } + return self; +} + +- (id)nextObject { + if([self.stack count] == 0) { + return nil; + } + + id node = nil; + @synchronized(self.stack) { + node = [self.stack lastObject]; + [self.stack removeLastObject]; + } + id result = node.key; + + if (self.isReverse) { + node = node.left; + while (![node isEmpty]) { + [self.stack addObject:node]; + node = node.right; + } + } else { + node = node.right; + while (![node isEmpty]) { + [self.stack addObject:node]; + node = node.left; + } + } + + return result; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/SocketRocket/FSRWebSocket.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/SocketRocket/FSRWebSocket.h new file mode 100644 index 0000000..1ca1815 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/SocketRocket/FSRWebSocket.h @@ -0,0 +1,107 @@ +// +// Copyright 2012 Square Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +typedef enum { + SR_CONNECTING = 0, + SR_OPEN = 1, + SR_CLOSING = 2, + SR_CLOSED = 3, + +} FSRReadyState; + +@class FSRWebSocket; + +extern NSString *const FSRWebSocketErrorDomain; + +@protocol FSRWebSocketDelegate; + +@interface FSRWebSocket : NSObject + +@property (nonatomic, weak) id delegate; + +@property (nonatomic, readonly) FSRReadyState readyState; +@property (nonatomic, readonly, retain) NSURL *url; + +// This returns the negotiated protocol. +// It will be niluntil after the handshake completes. +@property (nonatomic, readonly, copy) NSString *protocol; + +// Protocols should be an array of strings that turn into Sec-WebSocket-Protocol +- (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols queue:(dispatch_queue_t)queue andUserAgent:(NSString *)userAgent; +- (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols; +- (id)initWithURLRequest:(NSURLRequest *)request queue:(dispatch_queue_t)queue andUserAgent:(NSString *)userAgent; +- (id)initWithURLRequest:(NSURLRequest *)request; + +// Some helper constructors +- (id)initWithURL:(NSURL *)url protocols:(NSArray *)protocols; +- (id)initWithURL:(NSURL *)url; + +// Delegate queue will be dispatch_main_queue by default. +// You cannot set both OperationQueue and dispatch_queue. +- (void)setDelegateOperationQueue:(NSOperationQueue*) queue; +- (void)setDelegateDispatchQueue:(dispatch_queue_t)queue; + +// By default, it will schedule itself on +[NSRunLoop SR_networkRunLoop] using defaultModes. +- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; +- (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; + +// SRWebSockets are intended one-time-use only. Open should be called once and only once +- (void)open; + +- (void)close; +- (void)closeWithCode:(NSInteger)code reason:(NSString *)reason; + +// Send a UTF8 String or Data +- (void)send:(id)data; + +@end + +@protocol FSRWebSocketDelegate + +// message will either be an NSString if the server is using text +// or NSData if the server is using binary +- (void)webSocket:(FSRWebSocket *)webSocket didReceiveMessage:(id)message; + +@optional + +- (void)webSocketDidOpen:(FSRWebSocket *)webSocket; +- (void)webSocket:(FSRWebSocket *)webSocket didFailWithError:(NSError *)error; +- (void)webSocket:(FSRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean; + +@end + + +@interface NSURLRequest (FCertificateAdditions) + +@property (nonatomic, retain, readonly) NSArray *FSR_SSLPinnedCertificates; + +@end + + +@interface NSMutableURLRequest (FCertificateAdditions) + +@property (nonatomic, retain) NSArray *FSR_SSLPinnedCertificates; + +@end + +@interface NSRunLoop (FSRWebSocket) + ++ (NSRunLoop *)FSR_networkRunLoop; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/SocketRocket/FSRWebSocket.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/SocketRocket/FSRWebSocket.m new file mode 100644 index 0000000..92d9d55 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/SocketRocket/FSRWebSocket.m @@ -0,0 +1,1858 @@ +// +// Copyright 2012 Square Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +#import "FSRWebSocket.h" + +#if __has_include() +#define HAS_ICU +#endif + +#import + +#ifdef HAS_ICU +#import +#endif + +#if __has_include() +#import +#else +#import +#endif + +#import +#import +#import "fbase64.h" +#import "NSData+SRB64Additions.h" + +#if OS_OBJECT_USE_OBJC_RETAIN_RELEASE +#define sr_dispatch_retain(x) +#define sr_dispatch_release(x) +#define maybe_bridge(x) ((__bridge void *) x) +#else +#define sr_dispatch_retain(x) dispatch_retain(x) +#define sr_dispatch_release(x) dispatch_release(x) +#define maybe_bridge(x) (x) +#endif + +typedef enum { + SROpCodeTextFrame = 0x1, + SROpCodeBinaryFrame = 0x2, + //3-7Reserved + SROpCodeConnectionClose = 0x8, + SROpCodePing = 0x9, + SROpCodePong = 0xA, + //B-F reserved +} FSROpCode; + +typedef enum { + SRStatusCodeNormal = 1000, + SRStatusCodeGoingAway = 1001, + SRStatusCodeProtocolError = 1002, + SRStatusCodeUnhandledType = 1003, + // 1004 reserved + SRStatusNoStatusReceived = 1005, + // 1004-1006 reserved + SRStatusCodeInvalidUTF8 = 1007, + SRStatusCodePolicyViolated = 1008, + SRStatusCodeMessageTooBig = 1009, +} FSRStatusCode; + +typedef struct { + BOOL fin; +// BOOL rsv1; +// BOOL rsv2; +// BOOL rsv3; + uint8_t opcode; + BOOL masked; + uint64_t payload_length; +} frame_header; + +static NSString *const SRWebSocketAppendToSecKeyString = @"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + +static inline int32_t validate_dispatch_data_partial_string(NSData *data); +static inline void SRFastLog(NSString *format, ...); + +@interface NSData (FSRWebSocket) + +- (NSString *)stringBySHA1ThenBase64Encoding; + +@end + + +@interface NSString (FSRWebSocket) + +- (NSString *)stringBySHA1ThenBase64Encoding; + +@end + + +@interface NSURL (FSRWebSocket) + +// The origin isn't really applicable for a native application +// So instead, just map ws -> http and wss -> https +- (NSString *)SR_origin; + +@end + +@interface _FSRRunLoopThread : NSThread + +@property (nonatomic, readonly) NSRunLoop *runLoop; + +@end + +static NSString *newSHA1String(const char *bytes, size_t length) { + uint8_t md[CC_SHA1_DIGEST_LENGTH]; + + CC_SHA1(bytes, (int)length, md); + + size_t buffer_size = ((sizeof(md) * 3 + 2) / 2); + + char *buffer = (char *)malloc(buffer_size); + + int len = f_b64_ntop(md, CC_SHA1_DIGEST_LENGTH, buffer, buffer_size); + if (len == -1) { + free(buffer); + return nil; + } else{ + return [[NSString alloc] initWithBytesNoCopy:buffer length:len encoding:NSASCIIStringEncoding freeWhenDone:YES]; + } +} + +@implementation NSData (FSRWebSocket) + +- (NSString *)stringBySHA1ThenBase64Encoding; +{ + return newSHA1String(self.bytes, self.length); +} + +@end + + +@implementation NSString (FSRWebSocket) + +- (NSString *)stringBySHA1ThenBase64Encoding; +{ + return newSHA1String(self.UTF8String, self.length); +} + +@end + +NSString *const FSRWebSocketErrorDomain = @"FSRWebSocketErrorDomain"; + +// Returns number of bytes consumed. returning 0 means you didn't match. +// Sends bytes to callback handler; +typedef size_t (^stream_scanner)(NSData *collected_data); + +typedef void (^data_callback)(FSRWebSocket *webSocket, NSData *data); + +@interface FSRIOConsumer : NSObject { + stream_scanner _scanner; + data_callback _handler; + size_t _bytesNeeded; + BOOL _readToCurrentFrame; + BOOL _unmaskBytes; +} +@property (nonatomic, copy, readonly) stream_scanner consumer; +@property (nonatomic, copy, readonly) data_callback handler; +@property (nonatomic, assign) size_t bytesNeeded; +@property (nonatomic, assign, readonly) BOOL readToCurrentFrame; +@property (nonatomic, assign, readonly) BOOL unmaskBytes; + +@end + +// This class is not thread-safe, and is expected to always be run on the same queue. +@interface FSRIOConsumerPool : NSObject + +- (id)initWithBufferCapacity:(NSUInteger)poolSize; + +- (FSRIOConsumer *)consumerWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +- (void)returnConsumer:(FSRIOConsumer *)consumer; + +@end + +@interface FSRWebSocket () + +- (void)_writeData:(NSData *)data; +- (void)_closeWithProtocolError:(NSString *)message; +- (void)_failWithError:(NSError *)error; + +- (void)_disconnect; + +- (void)_readFrameNew; +- (void)_readFrameContinue; + +- (void)_pumpScanner; + +- (void)_pumpWriting; + +- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback; +- (void)_addConsumerWithDataLength:(size_t)dataLength callback:(data_callback)callback readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback dataLength:(size_t)dataLength; +- (void)_readUntilBytes:(const void *)bytes length:(size_t)length callback:(data_callback)dataHandler; +- (void)_readUntilHeaderCompleteWithCallback:(data_callback)dataHandler; + +- (void)_sendFrameWithOpcode:(FSROpCode)opcode data:(id)data; + +- (BOOL)_checkHandshake:(CFHTTPMessageRef)httpMessage; +- (void)_SR_commonInit; + +- (void)_initializeStreams; +- (void)_connect; + +@property (nonatomic) FSRReadyState readyState; + +@property (nonatomic) NSOperationQueue *delegateOperationQueue; +@property (nonatomic) dispatch_queue_t delegateDispatchQueue; + +@end + + +@implementation FSRWebSocket { + NSInteger _webSocketVersion; + + NSOperationQueue *_delegateOperationQueue; + dispatch_queue_t _delegateDispatchQueue; + dispatch_queue_t _workQueue; + NSMutableArray *_consumers; + + NSInputStream *_inputStream; + NSOutputStream *_outputStream; + + NSMutableData *_readBuffer; + NSInteger _readBufferOffset; + + NSMutableData *_outputBuffer; + NSInteger _outputBufferOffset; + + uint8_t _currentFrameOpcode; + size_t _currentFrameCount; + size_t _readOpCount; + uint32_t _currentStringScanPosition; + NSMutableData *_currentFrameData; + + NSString *_closeReason; + + NSString *_secKey; + + BOOL _pinnedCertFound; + + uint8_t _currentReadMaskKey[4]; + size_t _currentReadMaskOffset; + + BOOL _consumerStopped; + + BOOL _closeWhenFinishedWriting; + BOOL _failed; + + BOOL _secure; + NSURLRequest *_urlRequest; + NSString *_userAgent; + + CFHTTPMessageRef _receivedHTTPHeaders; + + BOOL _sentClose; + BOOL _didFail; + BOOL _cleanupScheduled; + int _closeCode; + + BOOL _isPumping; + + NSMutableSet *_scheduledRunloops; + + // We use this to retain ourselves. + __strong FSRWebSocket *_selfRetain; + + NSArray *_requestedProtocols; + FSRIOConsumerPool *_consumerPool; +} + +@synthesize delegate = _delegate; +@synthesize url = _url; +@synthesize readyState = _readyState; +@synthesize protocol = _protocol; + +static __strong NSData *CRLFCRLF; + ++ (void)initialize; +{ + CRLFCRLF = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4]; +} + +- (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols queue:(dispatch_queue_t)queue andUserAgent:(NSString *)userAgent; +{ + self = [super init]; + if (self) { + assert(request.URL); + _url = request.URL; + NSString *scheme = [_url scheme]; + + _requestedProtocols = [protocols copy]; + _userAgent = userAgent; + + assert([scheme isEqualToString:@"ws"] || [scheme isEqualToString:@"http"] || [scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]); + _urlRequest = request; + + if ([scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]) { + _secure = YES; + } + + if (!queue) { + _delegateDispatchQueue = dispatch_get_main_queue(); + } else { + _delegateDispatchQueue = queue; + } + + [self _SR_commonInit]; + } + + return self; +} + +- (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols; +{ + return [self initWithURLRequest:request protocols:nil queue:nil andUserAgent:nil]; +} + +- (id)initWithURLRequest:(NSURLRequest *)request queue:(dispatch_queue_t)queue andUserAgent:(NSString *)userAgent; +{ + return [self initWithURLRequest:request protocols:nil queue:queue andUserAgent:userAgent]; +} + +- (id)initWithURLRequest:(NSURLRequest *)request; +{ + return [self initWithURLRequest:request protocols:nil]; +} + +- (id)initWithURL:(NSURL *)url; +{ + return [self initWithURL:url protocols:nil]; +} + +- (id)initWithURL:(NSURL *)url protocols:(NSArray *)protocols; +{ + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; + return [self initWithURLRequest:request protocols:protocols]; +} + +- (void)_SR_commonInit; +{ + _readyState = SR_CONNECTING; + + _consumerStopped = YES; + + _webSocketVersion = 13; + + _workQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); + + // Going to set a specific on the queue so we can validate we're on the work queue + dispatch_queue_set_specific(_workQueue, (__bridge void *)self, maybe_bridge(_workQueue), NULL); + + sr_dispatch_retain(_delegateDispatchQueue); + + _readBuffer = [[NSMutableData alloc] init]; + _outputBuffer = [[NSMutableData alloc] init]; + + _currentFrameData = [[NSMutableData alloc] init]; + + _consumers = [[NSMutableArray alloc] init]; + + _consumerPool = [[FSRIOConsumerPool alloc] init]; + + _scheduledRunloops = [[NSMutableSet alloc] init]; + + [self _initializeStreams]; + + // default handlers +} + +- (void)assertOnWorkQueue; +{ + assert(dispatch_get_specific((__bridge void *)self) == maybe_bridge(_workQueue)); +} + +- (void)dealloc +{ + _inputStream.delegate = nil; + _outputStream.delegate = nil; + + [_inputStream close]; + [_outputStream close]; + + sr_dispatch_release(_workQueue); + _workQueue = NULL; + + if (_receivedHTTPHeaders) { + CFRelease(_receivedHTTPHeaders); + _receivedHTTPHeaders = NULL; + } + + if (_delegateDispatchQueue) { + sr_dispatch_release(_delegateDispatchQueue); + _delegateDispatchQueue = NULL; + } +} + +#ifndef NDEBUG + +- (void)setReadyState:(FSRReadyState)aReadyState; +{ + [self willChangeValueForKey:@"readyState"]; + assert(aReadyState > _readyState); + _readyState = aReadyState; + [self didChangeValueForKey:@"readyState"]; +} + +#endif + +- (void)open; +{ + assert(_url); + NSAssert(_readyState == SR_CONNECTING, @"Cannot call -(void)open on SRWebSocket more than once"); + + _selfRetain = self; + + [self _connect]; +} + +// Calls block on delegate queue +- (void)_performDelegateBlock:(dispatch_block_t)block; +{ + if (_delegateOperationQueue) { + [_delegateOperationQueue addOperationWithBlock:block]; + } else { + assert(_delegateDispatchQueue); + dispatch_async(_delegateDispatchQueue, block); + } +} + +- (void)setDelegateDispatchQueue:(dispatch_queue_t)queue; +{ + if (queue) { + sr_dispatch_retain(queue); + } + + if (_delegateDispatchQueue) { + sr_dispatch_release(_delegateDispatchQueue); + } + + _delegateDispatchQueue = queue; +} + +- (BOOL)_checkHandshake:(CFHTTPMessageRef)httpMessage; +{ + NSString *acceptHeader = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(httpMessage, CFSTR("Sec-WebSocket-Accept"))); + + if (acceptHeader == nil) { + return NO; + } + + NSString *concattedString = [_secKey stringByAppendingString:SRWebSocketAppendToSecKeyString]; + NSString *expectedAccept = [concattedString stringBySHA1ThenBase64Encoding]; + + return [acceptHeader isEqualToString:expectedAccept]; +} + +- (void)_HTTPHeadersDidFinish; +{ + NSInteger responseCode = CFHTTPMessageGetResponseStatusCode(_receivedHTTPHeaders); + + if (responseCode >= 400) { + SRFastLog(@"Request failed with response code %d", responseCode); + [self _failWithError:[NSError errorWithDomain:@"org.lolrus.SocketRocket" code:2132 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"received bad response code from server %u", (int)responseCode] forKey:NSLocalizedDescriptionKey]]]; + return; + + } + + if(![self _checkHandshake:_receivedHTTPHeaders]) { + [self _failWithError:[NSError errorWithDomain:FSRWebSocketErrorDomain code:2133 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Invalid Sec-WebSocket-Accept response"] forKey:NSLocalizedDescriptionKey]]]; + return; + } + + NSString *negotiatedProtocol = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(_receivedHTTPHeaders, CFSTR("Sec-WebSocket-Protocol"))); + if (negotiatedProtocol) { + // Make sure we requested the protocol + if ([_requestedProtocols indexOfObject:negotiatedProtocol] == NSNotFound) { + [self _failWithError:[NSError errorWithDomain:FSRWebSocketErrorDomain code:2133 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Server specified Sec-WebSocket-Protocol that wasn't requested"] forKey:NSLocalizedDescriptionKey]]]; + return; + } + + _protocol = negotiatedProtocol; + } + + self.readyState = SR_OPEN; + + if (!_didFail) { + [self _readFrameNew]; + } + + [self _performDelegateBlock:^{ + if ([self.delegate respondsToSelector:@selector(webSocketDidOpen:)]) { + [self.delegate webSocketDidOpen:self]; + }; + }]; +} + + +- (void)_readHTTPHeader; +{ + if (_receivedHTTPHeaders == NULL) { + _receivedHTTPHeaders = CFHTTPMessageCreateEmpty(NULL, NO); + } + + [self _readUntilHeaderCompleteWithCallback:^(FSRWebSocket *self, NSData *data) { + CFHTTPMessageAppendBytes(self->_receivedHTTPHeaders, (const UInt8 *)data.bytes, data.length); + + if (CFHTTPMessageIsHeaderComplete(self->_receivedHTTPHeaders)) { + SRFastLog(@"Finished reading headers %@", CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(self->_receivedHTTPHeaders))); + [self _HTTPHeadersDidFinish]; + } else { + [self _readHTTPHeader]; + } + }]; +} + +- (void)didConnect +{ + SRFastLog(@"Connected"); + CFHTTPMessageRef request = CFHTTPMessageCreateRequest(NULL, CFSTR("GET"), (__bridge CFURLRef)_url, kCFHTTPVersion1_1); + + // Set host first so it defaults + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Host"), (__bridge CFStringRef) + (_url.port != nil ? [NSString stringWithFormat:@"%@:%@", + _url.host, _url.port] : _url.host)); + + NSMutableData *keyBytes = [[NSMutableData alloc] initWithLength:16]; + int result = SecRandomCopyBytes(kSecRandomDefault, keyBytes.length, keyBytes.mutableBytes); + assert(result == 0); + _secKey = [FSRUtilities base64EncodedStringFromData:keyBytes]; + assert([_secKey length] == 24); + + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Upgrade"), CFSTR("websocket")); + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Connection"), CFSTR("Upgrade")); + if (_userAgent) { + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("User-Agent"), (__bridge CFStringRef)_userAgent); + } + + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Key"), (__bridge CFStringRef)_secKey); + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Version"), (__bridge CFStringRef)[NSString stringWithFormat:@"%u", (int)_webSocketVersion]); + + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Origin"), (__bridge CFStringRef)_url.SR_origin); + + if (_requestedProtocols) { + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Protocol"), (__bridge CFStringRef)[_requestedProtocols componentsJoinedByString:@", "]); + } + + [_urlRequest.allHTTPHeaderFields enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + CFHTTPMessageSetHeaderFieldValue(request, (__bridge CFStringRef)key, (__bridge CFStringRef)obj); + }]; + + NSData *message = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(request)); + + CFRelease(request); + + [self _writeData:message]; + [self _readHTTPHeader]; +} + +//- (void)_connectToHost:(NSString *)host port:(NSInteger)port; +- (void)_initializeStreams; +{ + NSInteger port = _url.port.integerValue; + if (port == 0) { + if (!_secure) { + port = 80; + } else { + port = 443; + } + } + NSString *host = _url.host; + + CFReadStreamRef readStream = NULL; + CFWriteStreamRef writeStream = NULL; + + CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, (int)port, &readStream, &writeStream); + + // XXX + CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeBackground); + CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeBackground); + + _outputStream = CFBridgingRelease(writeStream); + _inputStream = CFBridgingRelease(readStream); + + + if (_secure) { + NSMutableDictionary *SSLOptions = [[NSMutableDictionary alloc] init]; + + [_outputStream setProperty:(__bridge id)kCFStreamSocketSecurityLevelNegotiatedSSL forKey:(__bridge id)kCFStreamPropertySocketSecurityLevel]; + + // If we're using pinned certs, don't validate the certificate chain + if ([_urlRequest FSR_SSLPinnedCertificates].count) { + [SSLOptions setValue:[NSNumber numberWithBool:NO] forKey:(__bridge id)kCFStreamSSLValidatesCertificateChain]; + } + + [_outputStream setProperty:SSLOptions + forKey:(__bridge id)kCFStreamPropertySSLSettings]; + } + + _inputStream.delegate = self; + _outputStream.delegate = self; + + [_outputStream open]; + [_inputStream open]; +} + +- (void)_connect; +{ + if (!_scheduledRunloops.count) { + [self scheduleInRunLoop:[NSRunLoop FSR_networkRunLoop] forMode:NSDefaultRunLoopMode]; + } + + + [_outputStream open]; + [_inputStream open]; +} + +- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; +{ + [_outputStream scheduleInRunLoop:aRunLoop forMode:mode]; + [_inputStream scheduleInRunLoop:aRunLoop forMode:mode]; + + [_scheduledRunloops addObject:@[aRunLoop, mode]]; +} + +- (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; +{ + [_outputStream removeFromRunLoop:aRunLoop forMode:mode]; + [_inputStream removeFromRunLoop:aRunLoop forMode:mode]; + + [_scheduledRunloops removeObject:@[aRunLoop, mode]]; +} + +- (void)close; +{ + [self closeWithCode:-1 reason:nil]; +} + +- (void)closeWithCode:(NSInteger)code reason:(NSString *)reason; +{ + assert(code); + dispatch_async(_workQueue, ^{ + if (self.readyState == SR_CLOSING || self.readyState == SR_CLOSED) { + return; + } + + BOOL wasConnecting = self.readyState == SR_CONNECTING; + + self.readyState = SR_CLOSING; + + SRFastLog(@"Closing with code %d reason %@", code, reason); + + if (wasConnecting) { + [self _disconnect]; + return; + } + + size_t maxMsgSize = [reason maximumLengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + NSMutableData *mutablePayload = [[NSMutableData alloc] initWithLength:sizeof(uint16_t) + maxMsgSize]; + NSData *payload = mutablePayload; + + ((uint16_t *)mutablePayload.mutableBytes)[0] = EndianU16_BtoN(code); + + if (reason) { + NSRange remainingRange = {0}; + + NSUInteger usedLength = 0; + + BOOL success = [reason getBytes:(char *)mutablePayload.mutableBytes + sizeof(uint16_t) maxLength:payload.length - sizeof(uint16_t) usedLength:&usedLength encoding:NSUTF8StringEncoding options:NSStringEncodingConversionExternalRepresentation range:NSMakeRange(0, reason.length) remainingRange:&remainingRange]; + + assert(success); + assert(remainingRange.length == 0); + + if (usedLength != maxMsgSize) { + payload = [payload subdataWithRange:NSMakeRange(0, usedLength + sizeof(uint16_t))]; + } + } + + + [self _sendFrameWithOpcode:SROpCodeConnectionClose data:payload]; + }); +} + +- (void)_closeWithProtocolError:(NSString *)message; +{ + // Need to shunt this on the _callbackQueue first to see if they received any messages + [self _performDelegateBlock:^{ + [self closeWithCode:SRStatusCodeProtocolError reason:message]; + dispatch_async(self->_workQueue, ^{ + [self _disconnect]; + }); + }]; +} + +- (void)_failWithError:(NSError *)error; +{ + dispatch_async(_workQueue, ^{ + if (self.readyState != SR_CLOSED) { + self->_failed = YES; + [self _performDelegateBlock:^{ + if ([self.delegate respondsToSelector:@selector(webSocket:didFailWithError:)]) { + [self.delegate webSocket:self didFailWithError:error]; + } + }]; + + self.readyState = SR_CLOSED; + + SRFastLog(@"Failing with error %@", error.localizedDescription); + + [self _disconnect]; + [self _scheduleCleanup]; + } + }); +} + +- (void)_writeData:(NSData *)data; +{ + [self assertOnWorkQueue]; + + if (_closeWhenFinishedWriting) { + return; + } + [_outputBuffer appendData:data]; + [self _pumpWriting]; +} +- (void)send:(id)data; +{ + SRFastLog(@"Sending data %@", data); + NSAssert(self.readyState != SR_CONNECTING, @"Invalid State: Cannot call send: until connection is open"); + // TODO: maybe not copy this for performance + data = [data copy]; + dispatch_async(_workQueue, ^{ + if ([data isKindOfClass:[NSString class]]) { + [self _sendFrameWithOpcode:SROpCodeTextFrame data:[(NSString *)data dataUsingEncoding:NSUTF8StringEncoding]]; + } else if ([data isKindOfClass:[NSData class]]) { + [self _sendFrameWithOpcode:SROpCodeBinaryFrame data:data]; + } else if (data == nil) { + [self _sendFrameWithOpcode:SROpCodeTextFrame data:data]; + } else { + assert(NO); + } + }); +} + +- (void)handlePing:(NSData *)pingData; +{ + // Need to pingpong this off _callbackQueue first to make sure messages happen in order + [self _performDelegateBlock:^{ + dispatch_async(self->_workQueue, ^{ + [self _sendFrameWithOpcode:SROpCodePong data:pingData]; + }); + }]; +} + +- (void)handlePong; +{ + // NOOP +} + +- (void)_handleMessage:(id)message +{ + SRFastLog(@"Received message"); + [self _performDelegateBlock:^{ + if ([self.delegate respondsToSelector:@selector(webSocket:didReceiveMessage:)]) { + [self.delegate webSocket:self didReceiveMessage:message]; + } + }]; +} + + +static inline BOOL closeCodeIsValid(int closeCode) { + if (closeCode < 1000) { + return NO; + } + + if (closeCode >= 1000 && closeCode <= 1011) { + if (closeCode == 1004 || + closeCode == 1005 || + closeCode == 1006) { + return NO; + } + return YES; + } + + if (closeCode >= 3000 && closeCode <= 3999) { + return YES; + } + + if (closeCode >= 4000 && closeCode <= 4999) { + return YES; + } + + return NO; +} + +// Note from RFC: +// +// If there is a body, the first two +// bytes of the body MUST be a 2-byte unsigned integer (in network byte +// order) representing a status code with value /code/ defined in +// Section 7.4. Following the 2-byte integer the body MAY contain UTF-8 +// encoded data with value /reason/, the interpretation of which is not +// defined by this specification. + +- (void)handleCloseWithData:(NSData *)data; +{ + size_t dataSize = data.length; + __block uint16_t closeCode = 0; + + SRFastLog(@"Received close frame"); + + if (dataSize == 1) { + // TODO handle error + [self _closeWithProtocolError:@"Payload for close must be larger than 2 bytes"]; + return; + } else if (dataSize >= 2) { + [data getBytes:&closeCode length:sizeof(closeCode)]; + _closeCode = EndianU16_BtoN(closeCode); + if (!closeCodeIsValid(_closeCode)) { + [self _closeWithProtocolError:[NSString stringWithFormat:@"Cannot have close code of %d", _closeCode]]; + return; + } + if (dataSize > 2) { + _closeReason = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(2, dataSize - 2)] encoding:NSUTF8StringEncoding]; + if (!_closeReason) { + [self _closeWithProtocolError:@"Close reason MUST be valid UTF-8"]; + return; + } + } + } else { + _closeCode = SRStatusNoStatusReceived; + } + + [self assertOnWorkQueue]; + + if (self.readyState == SR_OPEN) { + [self closeWithCode:1000 reason:nil]; + } + dispatch_async(_workQueue, ^{ + [self _disconnect]; + }); +} + +- (void)_disconnect; +{ + [self assertOnWorkQueue]; + SRFastLog(@"Trying to disconnect"); + _closeWhenFinishedWriting = YES; + [self _pumpWriting]; +} + +- (void)_handleFrameWithData:(NSData *)frameData opCode:(NSInteger)opcode; +{ + // Check that the current data is valid UTF8 + + BOOL isControlFrame = (opcode == SROpCodePing || opcode == SROpCodePong || opcode == SROpCodeConnectionClose); + if (!isControlFrame) { + [self _readFrameNew]; + } else { + dispatch_async(_workQueue, ^{ + [self _readFrameContinue]; + }); + } + + switch (opcode) { + case SROpCodeTextFrame: { + NSString *str = [[NSString alloc] initWithData:frameData encoding:NSUTF8StringEncoding]; + if (str == nil && frameData) { + [self closeWithCode:SRStatusCodeInvalidUTF8 reason:@"Text frames must be valid UTF-8"]; + dispatch_async(_workQueue, ^{ + [self _disconnect]; + }); + + return; + } + [self _handleMessage:str]; + break; + } + case SROpCodeBinaryFrame: + [self _handleMessage:[frameData copy]]; + break; + case SROpCodeConnectionClose: + [self handleCloseWithData:frameData]; + break; + case SROpCodePing: + [self handlePing:frameData]; + break; + case SROpCodePong: + [self handlePong]; + break; + default: + [self _closeWithProtocolError:[NSString stringWithFormat:@"Unknown opcode %u", (int)opcode]]; + // TODO: Handle invalid opcode + break; + } +} + +- (void)_handleFrameHeader:(frame_header)frame_header curData:(NSData *)curData; +{ + assert(frame_header.opcode != 0); + + if (self.readyState != SR_OPEN) { + return; + } + + + BOOL isControlFrame = (frame_header.opcode == SROpCodePing || frame_header.opcode == SROpCodePong || frame_header.opcode == SROpCodeConnectionClose); + + if (isControlFrame && !frame_header.fin) { + [self _closeWithProtocolError:@"Fragmented control frames not allowed"]; + return; + } + + if (isControlFrame && frame_header.payload_length >= 126) { + [self _closeWithProtocolError:@"Control frames cannot have payloads larger than 126 bytes"]; + return; + } + + if (!isControlFrame) { + _currentFrameOpcode = frame_header.opcode; + _currentFrameCount += 1; + } + + if (frame_header.payload_length == 0) { + if (isControlFrame) { + [self _handleFrameWithData:curData opCode:frame_header.opcode]; + } else { + if (frame_header.fin) { + [self _handleFrameWithData:_currentFrameData opCode:frame_header.opcode]; + } else { + // TODO add assert that opcode is not a control; + [self _readFrameContinue]; + } + } + } else { + [self _addConsumerWithDataLength:(size_t)frame_header.payload_length callback:^(FSRWebSocket *self, NSData *newData) { + if (isControlFrame) { + [self _handleFrameWithData:newData opCode:frame_header.opcode]; + } else { + if (frame_header.fin) { + [self _handleFrameWithData:self->_currentFrameData opCode:frame_header.opcode]; + } else { + // TODO add assert that opcode is not a control; + [self _readFrameContinue]; + } + + } + } readToCurrentFrame:!isControlFrame unmaskBytes:frame_header.masked]; + } +} + +/* From RFC: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-------+-+-------------+-------------------------------+ + |F|R|R|R| opcode|M| Payload len | Extended payload length | + |I|S|S|S| (4) |A| (7) | (16/64) | + |N|V|V|V| |S| | (if payload len==126/127) | + | |1|2|3| |K| | | + +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + | Extended payload length continued, if payload len == 127 | + + - - - - - - - - - - - - - - - +-------------------------------+ + | |Masking-key, if MASK set to 1 | + +-------------------------------+-------------------------------+ + | Masking-key (continued) | Payload Data | + +-------------------------------- - - - - - - - - - - - - - - - + + : Payload Data continued ... : + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + | Payload Data continued ... | + +---------------------------------------------------------------+ + */ + +static const uint8_t SRFinMask = 0x80; +static const uint8_t SROpCodeMask = 0x0F; +static const uint8_t SRRsvMask = 0x70; +static const uint8_t SRMaskMask = 0x80; +static const uint8_t SRPayloadLenMask = 0x7F; + + +- (void)_readFrameContinue; +{ + assert((_currentFrameCount == 0 && _currentFrameOpcode == 0) || (_currentFrameCount > 0 && _currentFrameOpcode > 0)); + + [self _addConsumerWithDataLength:2 callback:^(FSRWebSocket *self, NSData *data) { + __block frame_header header = {0}; + + const uint8_t *headerBuffer = data.bytes; + assert(data.length >= 2); + + if (headerBuffer[0] & SRRsvMask) { + [self _closeWithProtocolError:@"Server used RSV bits"]; + return; + } + + uint8_t receivedOpcode = (SROpCodeMask & headerBuffer[0]); + + BOOL isControlFrame = (receivedOpcode == SROpCodePing || receivedOpcode == SROpCodePong || receivedOpcode == SROpCodeConnectionClose); + + if (!isControlFrame && receivedOpcode != 0 && self->_currentFrameCount > 0) { + [self _closeWithProtocolError:@"all data frames after the initial data frame must have opcode 0"]; + return; + } + + if (receivedOpcode == 0 && self->_currentFrameCount == 0) { + [self _closeWithProtocolError:@"cannot continue a message"]; + return; + } + + header.opcode = receivedOpcode == 0 ? self->_currentFrameOpcode : receivedOpcode; + + header.fin = !!(SRFinMask & headerBuffer[0]); + + + header.masked = !!(SRMaskMask & headerBuffer[1]); + header.payload_length = SRPayloadLenMask & headerBuffer[1]; + + headerBuffer = NULL; + + if (header.masked) { + [self _closeWithProtocolError:@"Client must receive unmasked data"]; + } + + size_t extra_bytes_needed = header.masked ? sizeof(self->_currentReadMaskKey) : 0; + + if (header.payload_length == 126) { + extra_bytes_needed += sizeof(uint16_t); + } else if (header.payload_length == 127) { + extra_bytes_needed += sizeof(uint64_t); + } + + if (extra_bytes_needed == 0) { + [self _handleFrameHeader:header curData:self->_currentFrameData]; + } else { + [self _addConsumerWithDataLength:extra_bytes_needed callback:^(FSRWebSocket *self, NSData *data) { + size_t mapped_size = data.length; + const void *mapped_buffer = data.bytes; + size_t offset = 0; + + if (header.payload_length == 126) { + assert(mapped_size >= sizeof(uint16_t)); + uint16_t newLen = EndianU16_BtoN(*(uint16_t *)(mapped_buffer)); + header.payload_length = newLen; + offset += sizeof(uint16_t); + } else if (header.payload_length == 127) { + assert(mapped_size >= sizeof(uint64_t)); + header.payload_length = EndianU64_BtoN(*(uint64_t *)(mapped_buffer)); + offset += sizeof(uint64_t); + } else { + assert(header.payload_length < 126 && header.payload_length >= 0); + } + + + if (header.masked) { + assert(mapped_size >= sizeof(self->_currentReadMaskOffset) + offset); + memcpy(self->_currentReadMaskKey, ((uint8_t *)mapped_buffer) + offset, sizeof(self->_currentReadMaskKey)); + } + + [self _handleFrameHeader:header curData:self->_currentFrameData]; + } readToCurrentFrame:NO unmaskBytes:NO]; + } + } readToCurrentFrame:NO unmaskBytes:NO]; +} + +- (void)_readFrameNew; +{ + dispatch_async(_workQueue, ^{ + [self->_currentFrameData setLength:0]; + + self->_currentFrameOpcode = 0; + self->_currentFrameCount = 0; + self->_readOpCount = 0; + self->_currentStringScanPosition = 0; + + [self _readFrameContinue]; + }); +} + +- (void)_pumpWriting; +{ + [self assertOnWorkQueue]; + + NSUInteger dataLength = _outputBuffer.length; + if (dataLength - _outputBufferOffset > 0 && _outputStream.hasSpaceAvailable) { + NSUInteger bytesWritten = [_outputStream write:_outputBuffer.bytes + _outputBufferOffset maxLength:dataLength - _outputBufferOffset]; + if (bytesWritten == -1) { + [self _failWithError:[NSError errorWithDomain:@"org.lolrus.SocketRocket" code:2145 userInfo:[NSDictionary dictionaryWithObject:@"Error writing to stream" forKey:NSLocalizedDescriptionKey]]]; + return; + } + + _outputBufferOffset += bytesWritten; + + if (_outputBufferOffset > 4096 && _outputBufferOffset > (_outputBuffer.length >> 1)) { + _outputBuffer = [[NSMutableData alloc] initWithBytes:(char *)_outputBuffer.bytes + _outputBufferOffset length:_outputBuffer.length - _outputBufferOffset]; + _outputBufferOffset = 0; + } + } + + if (_closeWhenFinishedWriting && + _outputBuffer.length - _outputBufferOffset == 0 && + (_inputStream.streamStatus != NSStreamStatusNotOpen && + _inputStream.streamStatus != NSStreamStatusClosed) && + !_sentClose) { + _sentClose = YES; + + @synchronized (self) { + [_outputStream close]; + [_inputStream close]; + + // TODO: Why are we missing the SocketRocket code to call unscheduleFromRunLoop??? + } + + if (!_failed) { + [self _performDelegateBlock:^{ + if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) { + [self.delegate webSocket:self didCloseWithCode:self->_closeCode reason:self->_closeReason wasClean:YES]; + } + }]; + } + [self _scheduleCleanup]; + } +} + +- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback; +{ + [self assertOnWorkQueue]; + [self _addConsumerWithScanner:consumer callback:callback dataLength:0]; +} + +- (void)_addConsumerWithDataLength:(size_t)dataLength callback:(data_callback)callback readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +{ + [self assertOnWorkQueue]; + assert(dataLength); + + [_consumers addObject:[_consumerPool consumerWithScanner:nil handler:callback bytesNeeded:dataLength readToCurrentFrame:readToCurrentFrame unmaskBytes:unmaskBytes]]; + [self _pumpScanner]; +} + +- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback dataLength:(size_t)dataLength; +{ + [self assertOnWorkQueue]; + [_consumers addObject:[_consumerPool consumerWithScanner:consumer handler:callback bytesNeeded:dataLength readToCurrentFrame:NO unmaskBytes:NO]]; + [self _pumpScanner]; +} + + +- (void)_scheduleCleanup +{ + @synchronized(self) { + if (_cleanupScheduled) { + return; + } + + _cleanupScheduled = YES; + + // Cleanup NSStream delegate's in the same RunLoop used by the streams themselves: + // This way we'll prevent race conditions between handleEvent and SRWebsocket's dealloc + NSTimer *timer = [NSTimer timerWithTimeInterval:(0.0f) target:self selector:@selector(_cleanupSelfReference:) userInfo:nil repeats:NO]; + [[NSRunLoop FSR_networkRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; + } +} + +- (void)_cleanupSelfReference:(NSTimer *)timer +{ + @synchronized(self) { + // Nuke NSStream delegate's + _inputStream.delegate = nil; + _outputStream.delegate = nil; + + // Remove the streams, right now, from the networkRunLoop + [_inputStream close]; + [_outputStream close]; + } + + // Cleanup selfRetain in the same GCD queue as usual + dispatch_async(_workQueue, ^{ + self->_selfRetain = nil; + }); +} + + +static const char CRLFCRLFBytes[] = {'\r', '\n', '\r', '\n'}; + +- (void)_readUntilHeaderCompleteWithCallback:(data_callback)dataHandler; +{ + [self _readUntilBytes:CRLFCRLFBytes length:sizeof(CRLFCRLFBytes) callback:dataHandler]; +} + +- (void)_readUntilBytes:(const void *)bytes length:(size_t)length callback:(data_callback)dataHandler; +{ + // TODO optimize so this can continue from where we last searched + stream_scanner consumer = ^size_t(NSData *data) { + __block size_t found_size = 0; + __block size_t match_count = 0; + + size_t size = data.length; + const unsigned char *buffer = data.bytes; + for (int i = 0; i < size; i++ ) { + if (((const unsigned char *)buffer)[i] == ((const unsigned char *)bytes)[match_count]) { + match_count += 1; + if (match_count == length) { + found_size = i + 1; + break; + } + } else { + match_count = 0; + } + } + return found_size; + }; + [self _addConsumerWithScanner:consumer callback:dataHandler]; +} + + +// Returns true if did work +- (BOOL)_innerPumpScanner { + + BOOL didWork = NO; + + if (self.readyState >= SR_CLOSING) { + return didWork; + } + + if (!_consumers.count) { + return didWork; + } + + size_t curSize = _readBuffer.length - _readBufferOffset; + if (!curSize) { + return didWork; + } + + FSRIOConsumer *consumer = [_consumers objectAtIndex:0]; + + size_t bytesNeeded = consumer.bytesNeeded; + + size_t foundSize = 0; + if (consumer.consumer) { + NSData *tempView = [NSData dataWithBytesNoCopy:(char *)_readBuffer.bytes + _readBufferOffset length:_readBuffer.length - _readBufferOffset freeWhenDone:NO]; + foundSize = consumer.consumer(tempView); + } else { + assert(consumer.bytesNeeded); + if (curSize >= bytesNeeded) { + foundSize = bytesNeeded; + } else if (consumer.readToCurrentFrame) { + foundSize = curSize; + } + } + + NSData *slice = nil; + if (consumer.readToCurrentFrame || foundSize) { + NSRange sliceRange = NSMakeRange(_readBufferOffset, foundSize); + slice = [_readBuffer subdataWithRange:sliceRange]; + + _readBufferOffset += foundSize; + + if (_readBufferOffset > 4096 && _readBufferOffset > (_readBuffer.length >> 1)) { + _readBuffer = [[NSMutableData alloc] initWithBytes:(char *)_readBuffer.bytes + _readBufferOffset length:_readBuffer.length - _readBufferOffset]; _readBufferOffset = 0; + } + + if (consumer.unmaskBytes) { + NSMutableData *mutableSlice = [slice mutableCopy]; + + NSUInteger len = mutableSlice.length; + uint8_t *bytes = mutableSlice.mutableBytes; + + for (int i = 0; i < len; i++) { + bytes[i] = bytes[i] ^ _currentReadMaskKey[_currentReadMaskOffset % sizeof(_currentReadMaskKey)]; + _currentReadMaskOffset += 1; + } + + slice = mutableSlice; + } + + if (consumer.readToCurrentFrame) { + [_currentFrameData appendData:slice]; + + _readOpCount += 1; + + if (_currentFrameOpcode == SROpCodeTextFrame) { + // Validate UTF8 stuff. + size_t currentDataSize = _currentFrameData.length; + if (_currentFrameOpcode == SROpCodeTextFrame && currentDataSize > 0) { + // TODO: Optimize the crap out of this. Don't really have to copy all the data each time + + size_t scanSize = currentDataSize - _currentStringScanPosition; + + NSData *scan_data = [_currentFrameData subdataWithRange:NSMakeRange(_currentStringScanPosition, scanSize)]; + int32_t valid_utf8_size = validate_dispatch_data_partial_string(scan_data); + + if (valid_utf8_size == -1) { + [self closeWithCode:SRStatusCodeInvalidUTF8 reason:@"Text frames must be valid UTF-8"]; + dispatch_async(_workQueue, ^{ + [self _disconnect]; + }); + return didWork; + } else { + _currentStringScanPosition += valid_utf8_size; + } + } + + } + + consumer.bytesNeeded -= foundSize; + + if (consumer.bytesNeeded == 0) { + [_consumers removeObjectAtIndex:0]; + consumer.handler(self, nil); + didWork = YES; + } + } else if (foundSize) { + [_consumers removeObjectAtIndex:0]; + consumer.handler(self, slice); + didWork = YES; + } + } + return didWork; +} + +-(void)_pumpScanner; +{ + [self assertOnWorkQueue]; + + if (!_isPumping) { + _isPumping = YES; + } else { + return; + } + + while ([self _innerPumpScanner]) { + + } + + _isPumping = NO; +} + +//#define NOMASK + +static const size_t SRFrameHeaderOverhead = 32; + +- (void)_sendFrameWithOpcode:(FSROpCode)opcode data:(id)data; +{ + [self assertOnWorkQueue]; + + if (data == nil) { + return; + } + + NSAssert([data isKindOfClass:[NSData class]] || [data isKindOfClass:[NSString class]], @"Function expects nil, NSString or NSData"); + + size_t payloadLength = [data isKindOfClass:[NSString class]] ? [(NSString *)data lengthOfBytesUsingEncoding:NSUTF8StringEncoding] : [data length]; + + NSMutableData *frame = [[NSMutableData alloc] initWithLength:payloadLength + SRFrameHeaderOverhead]; + if (!frame) { + [self closeWithCode:SRStatusCodeMessageTooBig reason:@"Message too big"]; + return; + } + uint8_t *frame_buffer = (uint8_t *)[frame mutableBytes]; + + // set fin + frame_buffer[0] = SRFinMask | opcode; + + BOOL useMask = YES; +#ifdef NOMASK + useMask = NO; +#endif + + if (useMask) { + // set the mask and header + frame_buffer[1] |= SRMaskMask; + } + + size_t frame_buffer_size = 2; + + const uint8_t *unmasked_payload = NULL; + if ([data isKindOfClass:[NSData class]]) { + unmasked_payload = (uint8_t *)[data bytes]; + } else if ([data isKindOfClass:[NSString class]]) { + unmasked_payload = (const uint8_t *)[data UTF8String]; + } else { + assert(NO); + } + + if (payloadLength < 126) { + frame_buffer[1] |= payloadLength; + } else if (payloadLength <= UINT16_MAX) { + frame_buffer[1] |= 126; + *((uint16_t *)(frame_buffer + frame_buffer_size)) = EndianU16_BtoN((uint16_t)payloadLength); + frame_buffer_size += sizeof(uint16_t); + } else { + frame_buffer[1] |= 127; + *((uint64_t *)(frame_buffer + frame_buffer_size)) = EndianU64_BtoN((uint64_t)payloadLength); + frame_buffer_size += sizeof(uint64_t); + } + + if (!useMask) { + for (int i = 0; i < payloadLength; i++) { + frame_buffer[frame_buffer_size] = unmasked_payload[i]; + frame_buffer_size += 1; + } + } else { + uint8_t *mask_key = frame_buffer + frame_buffer_size; + int result = SecRandomCopyBytes(kSecRandomDefault, sizeof(uint32_t), (uint8_t *)mask_key); + assert(result == 0); + frame_buffer_size += sizeof(uint32_t); + + // TODO: could probably optimize this with SIMD + for (int i = 0; i < payloadLength; i++) { + frame_buffer[frame_buffer_size] = unmasked_payload[i] ^ mask_key[i % sizeof(uint32_t)]; + frame_buffer_size += 1; + } + } + + assert(frame_buffer_size <= [frame length]); + frame.length = frame_buffer_size; + + [self _writeData:frame]; +} + +- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode; +{ + __weak __typeof__(self) weakSelf = self; + + // turn on keep-alive for the output stream. + if (eventCode == NSStreamEventOpenCompleted && aStream == _outputStream) { + CFDataRef socketData = CFWriteStreamCopyProperty((CFWriteStreamRef)_outputStream, kCFStreamPropertySocketNativeHandle); + // In rare cases socketData might be nil (there are crash reports out there), in which case we'll have to just + // live without keep-alive :( + if (socketData != nil) { + CFSocketNativeHandle socket; + CFDataGetBytes(socketData, CFRangeMake(0, sizeof(CFSocketNativeHandle)), (UInt8 *)&socket); + CFRelease(socketData); + + int keepAliveOn = 1; + if (setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, &keepAliveOn, sizeof(keepAliveOn)) == -1) { + SRFastLog(@"Failed to turn on TCP keepalive for websocket"); + } + } + } + + if (_secure && !_pinnedCertFound && (eventCode == NSStreamEventHasBytesAvailable || eventCode == NSStreamEventHasSpaceAvailable)) { + + NSArray *sslCerts = [_urlRequest FSR_SSLPinnedCertificates]; + if (sslCerts) { + SecTrustRef secTrust = (__bridge SecTrustRef)[aStream propertyForKey:(__bridge id)kCFStreamPropertySSLPeerTrust]; + if (secTrust) { + NSInteger numCerts = SecTrustGetCertificateCount(secTrust); + for (NSInteger i = 0; i < numCerts && !_pinnedCertFound; i++) { + SecCertificateRef cert = SecTrustGetCertificateAtIndex(secTrust, i); + NSData *certData = CFBridgingRelease(SecCertificateCopyData(cert)); + + for (id ref in sslCerts) { + SecCertificateRef trustedCert = (__bridge SecCertificateRef)ref; + NSData *trustedCertData = CFBridgingRelease(SecCertificateCopyData(trustedCert)); + + if ([trustedCertData isEqualToData:certData]) { + _pinnedCertFound = YES; + break; + } + } + } + } + + if (!_pinnedCertFound) { + dispatch_async(_workQueue, ^{ + NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : @"Invalid server cert" }; + [weakSelf _failWithError:[NSError errorWithDomain:@"org.lolrus.SocketRocket" code:23556 userInfo:userInfo]]; + }); + return; + } + } + } + + // SRFastLog(@"%@ Got stream event %d", aStream, eventCode); + dispatch_async(_workQueue, ^{ + [weakSelf safeHandleEvent:eventCode stream:aStream]; + }); +} + +- (void)safeHandleEvent:(NSStreamEvent)eventCode stream:(NSStream *)aStream +{ + switch (eventCode) { + case NSStreamEventOpenCompleted: { + SRFastLog(@"NSStreamEventOpenCompleted %@", aStream); + if (self.readyState >= SR_CLOSING) { + return; + } + + + assert(_readBuffer); + + if (self.readyState == SR_CONNECTING && aStream == _inputStream) { + [self didConnect]; + } + [self _pumpWriting]; + [self _pumpScanner]; + break; + } + + case NSStreamEventErrorOccurred: { + // Note: The upstream code for SocketRocket logs the error message, but this causes + // crashes on iOS 13 (https://github.com/firebase/firebase-ios-sdk/issues/3950) + SRFastLog(@"NSStreamEventErrorOccurred %@", aStream); + /// TODO specify error better! + [self _failWithError:aStream.streamError]; + _readBufferOffset = 0; + [_readBuffer setLength:0]; + break; + + } + + case NSStreamEventEndEncountered: { + [self _pumpScanner]; + SRFastLog(@"NSStreamEventEndEncountered %@", aStream); + if (aStream.streamError) { + [self _failWithError:aStream.streamError]; + } else { + dispatch_async(_workQueue, ^{ + if (self.readyState != SR_CLOSED) { + self.readyState = SR_CLOSED; + [self _scheduleCleanup]; + } + + if (!self->_sentClose && !self->_failed) { + self->_sentClose = YES; + // If we get closed in this state it's probably not clean because we should be sending this when we send messages + [self _performDelegateBlock:^{ + if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) { + [self.delegate webSocket:self didCloseWithCode:0 reason:@"Stream end encountered" wasClean:NO]; + } + }]; + } + }); + } + + break; + } + + case NSStreamEventHasBytesAvailable: { + SRFastLog(@"NSStreamEventHasBytesAvailable %@", aStream); + const NSUInteger bufferSize = 2048; + uint8_t buffer[bufferSize]; + + while (_inputStream.hasBytesAvailable) { + NSInteger bytes_read = [_inputStream read:buffer maxLength:bufferSize]; + + if (bytes_read > 0) { + [_readBuffer appendBytes:buffer length:bytes_read]; + } else if (bytes_read < 0) { + [self _failWithError:_inputStream.streamError]; + } + + if (bytes_read != bufferSize) { + break; + } + }; + [self _pumpScanner]; + break; + } + + case NSStreamEventHasSpaceAvailable: { + SRFastLog(@"NSStreamEventHasSpaceAvailable %@", aStream); + [self _pumpWriting]; + break; + } + + default: + SRFastLog(@"(default) %@", aStream); + break; + } +} + +@end + + +@implementation FSRIOConsumer + +@synthesize bytesNeeded = _bytesNeeded; +@synthesize consumer = _scanner; +@synthesize handler = _handler; +@synthesize readToCurrentFrame = _readToCurrentFrame; +@synthesize unmaskBytes = _unmaskBytes; + +- (void)setupWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +{ + _scanner = [scanner copy]; + _handler = [handler copy]; + _bytesNeeded = bytesNeeded; + _readToCurrentFrame = readToCurrentFrame; + _unmaskBytes = unmaskBytes; + assert(_scanner || _bytesNeeded); +} + +@end + +@implementation FSRIOConsumerPool { + NSUInteger _poolSize; + NSMutableArray *_bufferedConsumers; +} + +- (id)initWithBufferCapacity:(NSUInteger)poolSize; +{ + self = [super init]; + if (self) { + _poolSize = poolSize; + _bufferedConsumers = [[NSMutableArray alloc] initWithCapacity:poolSize]; + } + return self; +} + +- (id)init +{ + return [self initWithBufferCapacity:8]; +} + +- (FSRIOConsumer *)consumerWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +{ + FSRIOConsumer *consumer = nil; + if (_bufferedConsumers.count) { + consumer = [_bufferedConsumers lastObject]; + [_bufferedConsumers removeLastObject]; + } else { + consumer = [[FSRIOConsumer alloc] init]; + } + + [consumer setupWithScanner:scanner handler:handler bytesNeeded:bytesNeeded readToCurrentFrame:readToCurrentFrame unmaskBytes:unmaskBytes]; + + return consumer; +} + +- (void)returnConsumer:(FSRIOConsumer *)consumer; +{ + if (_bufferedConsumers.count < _poolSize) { + [_bufferedConsumers addObject:consumer]; + } +} + +@end + +@implementation NSURLRequest (FCertificateAdditions) + +- (NSArray *)FSR_SSLPinnedCertificates; +{ + return [NSURLProtocol propertyForKey:@"FSR_SSLPinnedCertificates" inRequest:self]; +} + +@end + +@implementation NSMutableURLRequest (FCertificateAdditions) + +- (NSArray *)FSR_SSLPinnedCertificates; +{ + return [NSURLProtocol propertyForKey:@"FSR_SSLPinnedCertificates" inRequest:self]; +} + +- (void)setFSR_SSLPinnedCertificates:(NSArray *)FSR_SSLPinnedCertificates; +{ + [NSURLProtocol setProperty:FSR_SSLPinnedCertificates forKey:@"FSR_SSLPinnedCertificates" inRequest:self]; +} + +@end + +@implementation NSURL (FSRWebSocket) + +- (NSString *)SR_origin; +{ + NSString *scheme = [self.scheme lowercaseString]; + + if ([scheme isEqualToString:@"wss"]) { + scheme = @"https"; + } else if ([scheme isEqualToString:@"ws"]) { + scheme = @"http"; + } + + if (self.port != nil) { + return [NSString stringWithFormat:@"%@://%@:%@/", scheme, self.host, self.port]; + } else { + return [NSString stringWithFormat:@"%@://%@/", scheme, self.host]; + } +} + +@end + +// #define SR_ENABLE_LOG + +static inline void SRFastLog(NSString *format, ...) { +#ifdef SR_ENABLE_LOG + __block va_list arg_list; + va_start (arg_list, format); + + NSString *formattedString = [[NSString alloc] initWithFormat:format arguments:arg_list]; + + va_end(arg_list); + + NSLog(@"[SR] %@", formattedString); +#endif +} + + +#ifdef HAS_ICU + +static inline int32_t validate_dispatch_data_partial_string(NSData *data) { + + const void * contents = [data bytes]; + long size = [data length]; + + const uint8_t *str = (const uint8_t *)contents; + + + UChar32 codepoint = 1; + int32_t offset = 0; + int32_t lastOffset = 0; + while(offset < size && codepoint > 0) { + lastOffset = offset; + U8_NEXT(str, offset, size, codepoint); + } + + if (codepoint == -1) { + // Check to see if the last byte is valid or whether it was just continuing + if (!U8_IS_LEAD(str[lastOffset]) || U8_COUNT_TRAIL_BYTES(str[lastOffset]) + lastOffset < (int32_t)size) { + + size = -1; + } else { + uint8_t leadByte = str[lastOffset]; + U8_MASK_LEAD_BYTE(leadByte, U8_COUNT_TRAIL_BYTES(leadByte)); + + for (int i = lastOffset + 1; i < offset; i++) { + + if (U8_IS_SINGLE(str[i]) || U8_IS_LEAD(str[i]) || !U8_IS_TRAIL(str[i])) { + size = -1; + } + } + + if (size != -1) { + size = lastOffset; + } + } + } + + if (size != -1 && ![[NSString alloc] initWithBytesNoCopy:(char *)[data bytes] length:size encoding:NSUTF8StringEncoding freeWhenDone:NO]) { + size = -1; + } + + return (int32_t)size; +} + +#else + +// This is a hack, and probably not optimal +static inline int32_t validate_dispatch_data_partial_string(NSData *data) { + static const int maxCodepointSize = 3; + + for (int i = 0; i < maxCodepointSize; i++) { + NSString *str = [[NSString alloc] initWithBytesNoCopy:(char *)data.bytes length:data.length - i encoding:NSUTF8StringEncoding freeWhenDone:NO]; + if (str) { + return (int)(data.length - i); + } + } + + return -1; +} + +#endif + +static _FSRRunLoopThread *networkThread = nil; +static NSRunLoop *networkRunLoop = nil; + +@implementation NSRunLoop (FSRWebSocket) + ++ (NSRunLoop *)FSR_networkRunLoop { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + networkThread = [[_FSRRunLoopThread alloc] init]; + networkThread.name = @"com.squareup.SocketRocket.NetworkThread"; + [networkThread start]; + networkRunLoop = networkThread.runLoop; + }); + + return networkRunLoop; +} + +@end + + +@implementation _FSRRunLoopThread { + dispatch_group_t _waitGroup; +} + +@synthesize runLoop = _runLoop; + +- (void)dealloc +{ + sr_dispatch_release(_waitGroup); +} + +- (id)init +{ + self = [super init]; + if (self) { + _waitGroup = dispatch_group_create(); + dispatch_group_enter(_waitGroup); + } + return self; +} + + +/** + * This is the main method of the thread on which the socket events are scheduled in a run loop. + */ +- (void)main; +{ + @autoreleasepool { + _runLoop = [NSRunLoop currentRunLoop]; + dispatch_group_leave(_waitGroup); + + // Add an empty run loop source to prevent runloop from spinning. + CFRunLoopSourceContext sourceCtx = { + .version = 0, + .info = NULL, + .retain = NULL, + .release = NULL, + .copyDescription = NULL, + .equal = NULL, + .hash = NULL, + .schedule = NULL, + .cancel = NULL, + .perform = NULL + }; + CFRunLoopSourceRef source = CFRunLoopSourceCreate(NULL, 0, &sourceCtx); + CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode); + CFRelease(source); + + while ([_runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) { + + } + assert(NO); + } +} + +- (NSRunLoop *)runLoop; +{ + dispatch_group_wait(_waitGroup, DISPATCH_TIME_FOREVER); + return _runLoop; +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/SocketRocket/NSData+SRB64Additions.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/SocketRocket/NSData+SRB64Additions.h new file mode 100644 index 0000000..bac393b --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/SocketRocket/NSData+SRB64Additions.h @@ -0,0 +1,23 @@ +// +// Copyright 2012 Square Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +@interface FSRUtilities : NSObject + ++ (NSString *)base64EncodedStringFromData:(NSData *)data; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/SocketRocket/NSData+SRB64Additions.m b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/SocketRocket/NSData+SRB64Additions.m new file mode 100644 index 0000000..2be1d84 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/SocketRocket/NSData+SRB64Additions.m @@ -0,0 +1,37 @@ +// +// Copyright 2012 Square Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NSData+SRB64Additions.h" +#import "fbase64.h" + +@implementation FSRUtilities + ++ (NSString *)base64EncodedStringFromData:(NSData *)data { + size_t buffer_size = ((data.length * 3 + 2) / 2); + + char *buffer = (char *)malloc(buffer_size); + + int len = f_b64_ntop(data.bytes, data.length, buffer, buffer_size); + + if (len == -1) { + free(buffer); + return nil; + } else{ + return [[NSString alloc] initWithBytesNoCopy:buffer length:len encoding:NSUTF8StringEncoding freeWhenDone:YES]; + } +} + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/SocketRocket/fbase64.c b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/SocketRocket/fbase64.c new file mode 100644 index 0000000..238c23c --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/SocketRocket/fbase64.c @@ -0,0 +1,318 @@ +/* $OpenBSD: base64.c,v 1.5 2006/10/21 09:55:03 otto Exp $ */ + +/* + * Copyright (c) 1996 by Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE + * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +/* + * Portions Copyright (c) 1995 by International Business Machines, Inc. + * + * International Business Machines, Inc. (hereinafter called IBM) grants + * permission under its copyrights to use, copy, modify, and distribute this + * Software with or without fee, provided that the above copyright notice and + * all paragraphs of this notice appear in all copies, and that the name of IBM + * not be used in connection with the marketing of any product incorporating + * the Software or modifications thereof, without specific, written prior + * permission. + * + * To the extent it has a right to do so, IBM grants an immunity from suit + * under its patents, if any, for the use, sale or manufacture of products to + * the extent that such products are used for performing Domain Name System + * dynamic updates in TCP/IP networks by means of the Software. No immunity is + * granted for any product per se or for any other function of any product. + * + * THE SOFTWARE IS PROVIDED "AS IS", AND IBM DISCLAIMS ALL WARRANTIES, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE. IN NO EVENT SHALL IBM BE LIABLE FOR ANY SPECIAL, + * DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE, EVEN + * IF IBM IS APPRISED OF THE POSSIBILITY OF SUCH DAMAGES. + */ + +/* OPENBSD ORIGINAL: lib/libc/net/base64.c */ + + +// +// Distributed with modifications by Firebase ( https://www.firebase.com ) +// + +#if (!defined(HAVE_B64_NTOP) && !defined(HAVE___B64_NTOP)) || (!defined(HAVE_B64_PTON) && !defined(HAVE___B64_PTON)) + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "fbase64.h" + +static const char Base64[] = +"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char Pad64 = '='; + +/* (From RFC1521 and draft-ietf-dnssec-secext-03.txt) + The following encoding technique is taken from RFC 1521 by Borenstein + and Freed. It is reproduced here in a slightly edited form for + convenience. + + A 65-character subset of US-ASCII is used, enabling 6 bits to be + represented per printable character. (The extra 65th character, "=", + is used to signify a special processing function.) + + The encoding process represents 24-bit groups of input bits as output + strings of 4 encoded characters. Proceeding from left to right, a + 24-bit input group is formed by concatenating 3 8-bit input groups. + These 24 bits are then treated as 4 concatenated 6-bit groups, each + of which is translated into a single digit in the base64 alphabet. + + Each 6-bit group is used as an index into an array of 64 printable + characters. The character referenced by the index is placed in the + output string. + + Table 1: The Base64 Alphabet + + Value Encoding Value Encoding Value Encoding Value Encoding + 0 A 17 R 34 i 51 z + 1 B 18 S 35 j 52 0 + 2 C 19 T 36 k 53 1 + 3 D 20 U 37 l 54 2 + 4 E 21 V 38 m 55 3 + 5 F 22 W 39 n 56 4 + 6 G 23 X 40 o 57 5 + 7 H 24 Y 41 p 58 6 + 8 I 25 Z 42 q 59 7 + 9 J 26 a 43 r 60 8 + 10 K 27 b 44 s 61 9 + 11 L 28 c 45 t 62 + + 12 M 29 d 46 u 63 / + 13 N 30 e 47 v + 14 O 31 f 48 w (pad) = + 15 P 32 g 49 x + 16 Q 33 h 50 y + + Special processing is performed if fewer than 24 bits are available + at the end of the data being encoded. A full encoding quantum is + always completed at the end of a quantity. When fewer than 24 input + bits are available in an input group, zero bits are added (on the + right) to form an integral number of 6-bit groups. Padding at the + end of the data is performed using the '=' character. + + Since all base64 input is an integral number of octets, only the + ------------------------------------------------- + following cases can arise: + + (1) the final quantum of encoding input is an integral + multiple of 24 bits; here, the final unit of encoded + output will be an integral multiple of 4 characters + with no "=" padding, + (2) the final quantum of encoding input is exactly 8 bits; + here, the final unit of encoded output will be two + characters followed by two "=" padding characters, or + (3) the final quantum of encoding input is exactly 16 bits; + here, the final unit of encoded output will be three + characters followed by one "=" padding character. + */ + +#if !defined(HAVE_B64_NTOP) && !defined(HAVE___B64_NTOP) +int +f_b64_ntop(u_char const *src, size_t srclength, char *target, size_t targsize) +{ + size_t datalength = 0; + u_char input[3]; + u_char output[4]; + u_int i; + + while (2 < srclength) { + input[0] = *src++; + input[1] = *src++; + input[2] = *src++; + srclength -= 3; + + output[0] = input[0] >> 2; + output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4); + output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6); + output[3] = input[2] & 0x3f; + + if (datalength + 4 > targsize) + return (-1); + target[datalength++] = Base64[output[0]]; + target[datalength++] = Base64[output[1]]; + target[datalength++] = Base64[output[2]]; + target[datalength++] = Base64[output[3]]; + } + + /* Now we worry about padding. */ + if (0 != srclength) { + /* Get what's left. */ + input[0] = input[1] = input[2] = '\0'; + for (i = 0; i < srclength; i++) + input[i] = *src++; + + output[0] = input[0] >> 2; + output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4); + output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6); + + if (datalength + 4 > targsize) + return (-1); + target[datalength++] = Base64[output[0]]; + target[datalength++] = Base64[output[1]]; + if (srclength == 1) + target[datalength++] = Pad64; + else + target[datalength++] = Base64[output[2]]; + target[datalength++] = Pad64; + } + if (datalength >= targsize) + return (-1); + target[datalength] = '\0'; /* Returned value doesn't count \0. */ + return (int)(datalength); +} +#endif /* !defined(HAVE_B64_NTOP) && !defined(HAVE___B64_NTOP) */ + +#if !defined(HAVE_B64_PTON) && !defined(HAVE___B64_PTON) + +/* skips all whitespace anywhere. + converts characters, four at a time, starting at (or after) + src from base - 64 numbers into three 8 bit bytes in the target area. + it returns the number of data bytes stored at the target, or -1 on error. + */ + +int +f_b64_pton(char const *src, u_char *target, size_t targsize) +{ + u_int tarindex, state; + int ch; + char *pos; + + state = 0; + tarindex = 0; + + while ((ch = *src++) != '\0') { + if (isspace(ch)) /* Skip whitespace anywhere. */ + continue; + + if (ch == Pad64) + break; + + pos = strchr(Base64, ch); + if (pos == 0) /* A non-base64 character. */ + return (-1); + + switch (state) { + case 0: + if (target) { + if (tarindex >= targsize) + return (-1); + target[tarindex] = (pos - Base64) << 2; + } + state = 1; + break; + case 1: + if (target) { + if (tarindex + 1 >= targsize) + return (-1); + target[tarindex] |= (pos - Base64) >> 4; + target[tarindex+1] = ((pos - Base64) & 0x0f) + << 4 ; + } + tarindex++; + state = 2; + break; + case 2: + if (target) { + if (tarindex + 1 >= targsize) + return (-1); + target[tarindex] |= (pos - Base64) >> 2; + target[tarindex+1] = ((pos - Base64) & 0x03) + << 6; + } + tarindex++; + state = 3; + break; + case 3: + if (target) { + if (tarindex >= targsize) + return (-1); + target[tarindex] |= (pos - Base64); + } + tarindex++; + state = 0; + break; + } + } + + /* + * We are done decoding Base-64 chars. Let's see if we ended + * on a byte boundary, and/or with erroneous trailing characters. + */ + + if (ch == Pad64) { /* We got a pad char. */ + ch = *src++; /* Skip it, get next. */ + switch (state) { + case 0: /* Invalid = in first position */ + case 1: /* Invalid = in second position */ + return (-1); + + case 2: /* Valid, means one byte of info */ + /* Skip any number of spaces. */ + for (; ch != '\0'; ch = *src++) + if (!isspace(ch)) + break; + /* Make sure there is another trailing = sign. */ + if (ch != Pad64) + return (-1); + ch = *src++; /* Skip the = */ + /* Fall through to "single trailing =" case. */ + /* FALLTHROUGH */ + + case 3: /* Valid, means two bytes of info */ + /* + * We know this char is an =. Is there anything but + * whitespace after it? + */ + for (; ch != '\0'; ch = *src++) + if (!isspace(ch)) + return (-1); + + /* + * Now make sure for cases 2 and 3 that the "extra" + * bits that slopped past the last full byte were + * zeros. If we don't check them, they become a + * subliminal channel. + */ + if (target && target[tarindex] != 0) + return (-1); + } + } else { + /* + * We ended by seeing the end of the string. Make sure we + * have no partial bytes lying around. + */ + if (state != 0) + return (-1); + } + + return (tarindex); +} + +#endif /* !defined(HAVE_B64_PTON) && !defined(HAVE___B64_PTON) */ +#endif diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/SocketRocket/fbase64.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/SocketRocket/fbase64.h new file mode 100644 index 0000000..a9bf142 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/SocketRocket/fbase64.h @@ -0,0 +1,33 @@ +// Copyright 2012 Square Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef FSocketRocket_base64_h +#define FSocketRocket_base64_h + +#include + +extern int +f_b64_ntop(u_char const *src, + size_t srclength, + char *target, + size_t targsize); + +extern int +f_b64_pton(char const *src, + u_char *target, + size_t targsize); + + +#endif diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/Wrap-leveldb/APLevelDB.h b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/Wrap-leveldb/APLevelDB.h new file mode 100644 index 0000000..c0baa22 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/Wrap-leveldb/APLevelDB.h @@ -0,0 +1,105 @@ +// +// APLevelDB.h +// +// Created by Adam Preble on 1/23/12. +// Copyright (c) 2012 Adam Preble. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +extern NSString * const APLevelDBErrorDomain; + +@class APLevelDBIterator; +@protocol APLevelDBWriteBatch; + +@interface APLevelDB : NSObject + +@property (nonatomic, readonly, strong) NSString *path; + ++ (APLevelDB *)levelDBWithPath:(NSString *)path error:(NSError *__autoreleasing*)errorOut; +- (void)close; + +- (BOOL)setData:(NSData *)data forKey:(NSString *)key; +- (BOOL)setString:(NSString *)str forKey:(NSString *)key; + +- (NSData *)dataForKey:(NSString *)key; +- (NSString *)stringForKey:(NSString *)key; + +- (BOOL)removeKey:(NSString *)key; + +- (NSArray *)allKeys; + +- (void)enumerateKeys:(void (^)(NSString *key, BOOL *stop))block; +- (void)enumerateKeysWithPrefix:(NSString *)prefix usingBlock:(void (^)(NSString *key, BOOL *stop))block; + +- (void)enumerateKeysAndValuesAsStrings:(void (^)(NSString *key, NSString *value, BOOL *stop))block; +- (void)enumerateKeysWithPrefix:(NSString *)prefix asStrings:(void (^)(NSString *key, NSString *value, BOOL *stop))block; + +- (void)enumerateKeysAndValuesAsData:(void (^)(NSString *key, NSData *value, BOOL *stop))block; +- (void)enumerateKeysWithPrefix:(NSString *)prefix asData:(void (^)(NSString *key, NSData *value, BOOL *stop))block; + +- (NSUInteger)approximateSizeFrom:(NSString *)from to:(NSString *)to; +- (NSUInteger)exactSizeFrom:(NSString *)from to:(NSString *)to; + +// Objective-C Subscripting Support: +// The database object supports subscripting for string-string and string-data key-value access and assignment. +// Examples: +// db[@"key"] = @"value"; +// db[@"key"] = [NSData data]; +// NSString *s = db[@"key"]; +// An NSInvalidArgumentException is raised if the key is not an NSString, or if the assigned object is not an +// instance of NSString or NSData. +- (id)objectForKeyedSubscript:(id)key; +- (void)setObject:(id)object forKeyedSubscript:(id)key; + +// Batch write/atomic update support: +- (id)beginWriteBatch; + +@end + + +@interface APLevelDBIterator : NSObject + ++ (id)iteratorWithLevelDB:(APLevelDB *)db; + +// Designated initializer: +- (id)initWithLevelDB:(APLevelDB *)db; + +- (BOOL)seekToKey:(NSString *)key; +- (NSString *)nextKey; +- (NSString *)key; +- (NSString *)valueAsString; +- (NSData *)valueAsData; + +@end + + +@protocol APLevelDBWriteBatch + +- (void)setData:(NSData *)data forKey:(NSString *)key; +- (void)setString:(NSString *)str forKey:(NSString *)key; + +- (void)removeKey:(NSString *)key; + +// Remove all of the buffered sets and removes: +- (void)clear; +- (BOOL)commit; + +@end diff --git a/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/Wrap-leveldb/APLevelDB.mm b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/Wrap-leveldb/APLevelDB.mm new file mode 100644 index 0000000..cdecce6 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/Firebase/Database/third_party/Wrap-leveldb/APLevelDB.mm @@ -0,0 +1,500 @@ +// +// APLevelDB.m +// +// Created by Adam Preble on 1/23/12. +// Copyright (c) 2012 Adam Preble. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// +// Portions of APLevelDB are based on LevelDB-ObjC: +// https://github.com/hoisie/LevelDB-ObjC +// Specifically the SliceFromString/StringFromSlice macros, and the structure of +// the enumeration methods. License for those potions follows: +// +// Copyright (c) 2011 Pave Labs +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#import "APLevelDB.h" + +#import "leveldb/db.h" +#import "leveldb/options.h" +#import "leveldb/write_batch.h" + +NSString * const APLevelDBErrorDomain = @"APLevelDBErrorDomain"; + +#define SliceFromString(_string_) (leveldb::Slice((char *)[_string_ UTF8String], [_string_ lengthOfBytesUsingEncoding:NSUTF8StringEncoding])) +#define StringFromSlice(_slice_) ([[NSString alloc] initWithBytes:_slice_.data() length:_slice_.size() encoding:NSUTF8StringEncoding]) + + +@interface APLevelDBWriteBatch : NSObject { + @package + leveldb::WriteBatch _batch; +} + +@property (nonatomic, strong) APLevelDB *levelDB; + +- (id)initWithLevelDB:(APLevelDB *)levelDB; +@end + + +#pragma mark - APLevelDB + +@interface APLevelDB () { + leveldb::DB *_db; + leveldb::ReadOptions _readOptions; + leveldb::WriteOptions _writeOptions; +} +- (id)initWithPath:(NSString *)path error:(NSError **)errorOut; ++ (leveldb::Options)defaultCreateOptions; +@property (nonatomic, readonly) leveldb::DB *db; +@end + + +@implementation APLevelDB + +@synthesize path = _path; +@synthesize db = _db; + ++ (APLevelDB *)levelDBWithPath:(NSString *)path error:(NSError *__autoreleasing *)errorOut +{ + return [[APLevelDB alloc] initWithPath:path error:errorOut]; +} + +- (id)initWithPath:(NSString *)path error:(NSError *__autoreleasing *)errorOut +{ + if ((self = [super init])) + { + _path = path; + + leveldb::Options options = [[self class] defaultCreateOptions]; + + leveldb::Status status = leveldb::DB::Open(options, [_path UTF8String], &_db); + + if (!status.ok()) + { + if (errorOut) + { + NSString *statusString = [[NSString alloc] initWithCString:status.ToString().c_str() encoding:NSUTF8StringEncoding]; + *errorOut = [NSError errorWithDomain:APLevelDBErrorDomain + code:0 + userInfo:[NSDictionary dictionaryWithObjectsAndKeys:statusString, NSLocalizedDescriptionKey, nil]]; + } + return nil; + } + + _writeOptions.sync = false; + } + return self; +} + +- (void)close { + if (_db != NULL) { + delete _db; + _db = NULL; + } +} + +- (void)dealloc +{ + if (_db != NULL) { + delete _db; + _db = NULL; + } +} + ++ (leveldb::Options)defaultCreateOptions +{ + leveldb::Options options; + options.create_if_missing = true; + return options; +} + +- (BOOL)setData:(NSData *)data forKey:(NSString *)key +{ + leveldb::Slice keySlice = SliceFromString(key); + leveldb::Slice valueSlice = leveldb::Slice((const char *)[data bytes], (size_t)[data length]); + leveldb::Status status = _db->Put(_writeOptions, keySlice, valueSlice); + return (status.ok() == true); +} + +- (BOOL)setString:(NSString *)str forKey:(NSString *)key +{ + // This could have been based on + leveldb::Slice keySlice = SliceFromString(key); + leveldb::Slice valueSlice = SliceFromString(str); + leveldb::Status status = _db->Put(_writeOptions, keySlice, valueSlice); + return (status.ok() == true); +} + +- (NSData *)dataForKey:(NSString *)key +{ + leveldb::Slice keySlice = SliceFromString(key); + std::string valueCPPString; + leveldb::Status status = _db->Get(_readOptions, keySlice, &valueCPPString); + + if (!status.ok()) + return nil; + else + return [NSData dataWithBytes:valueCPPString.data() length:valueCPPString.size()]; +} + +- (NSString *)stringForKey:(NSString *)key +{ + leveldb::Slice keySlice = SliceFromString(key); + std::string valueCPPString; + leveldb::Status status = _db->Get(_readOptions, keySlice, &valueCPPString); + + // We assume (dangerously?) UTF-8 string encoding: + if (!status.ok()) + return nil; + else + return [[NSString alloc] initWithBytes:valueCPPString.data() length:valueCPPString.size() encoding:NSUTF8StringEncoding]; +} + +- (BOOL)removeKey:(NSString *)key +{ + leveldb::Slice keySlice = SliceFromString(key); + leveldb::Status status = _db->Delete(_writeOptions, keySlice); + return (status.ok() == true); +} + +- (NSArray *)allKeys +{ + NSMutableArray *keys = [NSMutableArray array]; + [self enumerateKeys:^(NSString *key, BOOL *stop) { + [keys addObject:key]; + }]; + return keys; +} + +- (void)enumerateKeysAndValuesAsStrings:(void (^)(NSString *key, NSString *value, BOOL *stop))block +{ + [self enumerateKeysWithPrefix:@"" asStrings:block]; +} + +- (void)enumerateKeysWithPrefix:(NSString *)prefixString asStrings:(void (^)(NSString *, NSString *, BOOL *))block +{ + @autoreleasepool { + BOOL stop = NO; + leveldb::Iterator* iter = _db->NewIterator(leveldb::ReadOptions()); + leveldb::Slice prefix = SliceFromString(prefixString); + for (iter->Seek(prefix); iter->Valid(); iter->Next()) { + leveldb::Slice key = iter->key(), value = iter->value(); + if (key.starts_with(prefix)) { + NSString *k = StringFromSlice(key); + NSString *v = [[NSString alloc] initWithBytes:value.data() length:value.size() encoding:NSUTF8StringEncoding]; + block(k, v, &stop); + if (stop) + break; + } else { + break; + } + } + + delete iter; + } +} + +- (void)enumerateKeys:(void (^)(NSString *key, BOOL *stop))block +{ + [self enumerateKeysWithPrefix:@"" usingBlock:block]; +} + +- (void)enumerateKeysWithPrefix:(NSString *)prefixString usingBlock:(void (^)(NSString *key, BOOL *stop))block; +{ + @autoreleasepool { + BOOL stop = NO; + leveldb::Slice prefix = SliceFromString(prefixString); + leveldb::Iterator* iter = _db->NewIterator(leveldb::ReadOptions()); + for (iter->Seek(prefix); iter->Valid(); iter->Next()) { + leveldb::Slice key = iter->key(); + if (key.starts_with(prefix)) { + NSString *k = StringFromSlice(key); + block(k, &stop); + if (stop) + break; + } else { + break; + } + } + + delete iter; + } +} + +- (void)enumerateKeysAndValuesAsData:(void (^)(NSString *key, NSData *data, BOOL *stop))block +{ + [self enumerateKeysWithPrefix:@"" asData:block]; +} + +- (void)enumerateKeysWithPrefix:(NSString *)prefixString asData:(void (^)(NSString *, NSData *, BOOL *))block +{ + @autoreleasepool { + BOOL stop = NO; + leveldb::Iterator* iter = _db->NewIterator(leveldb::ReadOptions()); + leveldb::Slice prefix = SliceFromString(prefixString); + for (iter->Seek(prefix); iter->Valid(); iter->Next()) { + leveldb::Slice key = iter->key(), value = iter->value(); + if (key.starts_with(prefix)) { + NSString *k = StringFromSlice(key); + NSData *data = [NSData dataWithBytes:value.data() length:value.size()]; + block(k, data, &stop); + if (stop) + break; + } else { + break; + } + } + + delete iter; + } +} + +- (NSUInteger)exactSizeFrom:(NSString *)from to:(NSString *)to { + NSUInteger size = 0; + leveldb::Iterator* iter = _db->NewIterator(leveldb::ReadOptions()); + leveldb::Slice fromSlice = SliceFromString(from); + leveldb::Slice toSlice = SliceFromString(to); + iter->Seek(fromSlice); + while (iter->Valid() && iter->key().compare(toSlice) <= 0) { + size += iter->value().size(); + iter->Next(); + } + delete iter; + return size; +} + + +- (NSUInteger)approximateSizeFrom:(NSString *)from to:(NSString *)to { + leveldb::Range ranges[1]; + leveldb::Slice fromSlice = SliceFromString(from); + leveldb::Slice toSlice = SliceFromString(to); + ranges[0] = leveldb::Range(fromSlice, toSlice); + uint64_t sizes[1]; + _db->GetApproximateSizes(ranges, 1, sizes); + return (NSUInteger)sizes[0]; +} + +#pragma mark - Subscripting Support + +- (id)objectForKeyedSubscript:(id)key +{ + if (![key respondsToSelector: @selector(componentsSeparatedByString:)]) + { + [NSException raise:NSInvalidArgumentException format:@"key must be an NSString"]; + } + return [self stringForKey:key]; +} +- (void)setObject:(id)thing forKeyedSubscript:(id)key +{ + id idKey = (id) key; + if (![idKey respondsToSelector: @selector(componentsSeparatedByString:)]) + { + [NSException raise:NSInvalidArgumentException format:@"key must be NSString or NSData"]; + } + + if ([thing respondsToSelector:@selector(componentsSeparatedByString:)]) + [self setString:thing forKey:(NSString *)key]; + else if ([thing respondsToSelector:@selector(subdataWithRange:)]) + [self setData:thing forKey:(NSString *)key]; + else + [NSException raise:NSInvalidArgumentException format:@"object must be NSString or NSData"]; +} + +#pragma mark - Atomic Updates + +- (id)beginWriteBatch +{ + APLevelDBWriteBatch *batch = [[APLevelDBWriteBatch alloc] initWithLevelDB:self]; + return batch; +} + +- (BOOL)commitWriteBatch:(id)theBatch +{ + if (!theBatch) + return NO; + + APLevelDBWriteBatch *batch = theBatch; + + leveldb::Status status; + status = _db->Write(_writeOptions, &batch->_batch); + return (status.ok() == true); +} + +@end + + +#pragma mark - APLevelDBIterator + +@interface APLevelDBIterator () { + leveldb::Iterator *_iter; +} + +@property (nonatomic, strong) APLevelDB *levelDB; +@end + + + +@implementation APLevelDBIterator + ++ (id)iteratorWithLevelDB:(APLevelDB *)db +{ + APLevelDBIterator *iter = [[[self class] alloc] initWithLevelDB:db]; + return iter; +} + +- (id)initWithLevelDB:(APLevelDB *)db +{ + if ((self = [super init])) + { + // Hold on to the database so it doesn't get deallocated before the iterator is deallocated + self->_levelDB = db; + _iter = db.db->NewIterator(leveldb::ReadOptions()); + _iter->SeekToFirst(); + if (!_iter->Valid()) + return nil; + } + return self; +} + +- (id)init +{ + [NSException raise:@"BadInitializer" format:@"Use the designated initializer, -initWithLevelDB:, instead."]; + return nil; +} + +- (void)dealloc +{ + self->_levelDB = nil; + delete _iter; + _iter = NULL; +} + +- (BOOL)seekToKey:(NSString *)key +{ + leveldb::Slice target = SliceFromString(key); + _iter->Seek(target); + return _iter->Valid() == true; +} + +- (void)seekToFirst +{ + _iter->SeekToFirst(); +} + +- (void)seekToLast +{ + _iter->SeekToLast(); +} + +- (NSString *)nextKey +{ + _iter->Next(); + return [self key]; +} + +- (NSString *)key +{ + if (_iter->Valid() == false) + return nil; + leveldb::Slice value = _iter->key(); + return StringFromSlice(value); +} + +- (NSString *)valueAsString +{ + if (_iter->Valid() == false) + return nil; + leveldb::Slice value = _iter->value(); + return StringFromSlice(value); +} + +- (NSData *)valueAsData +{ + if (_iter->Valid() == false) + return nil; + leveldb::Slice value = _iter->value(); + return [NSData dataWithBytes:value.data() length:value.size()]; +} + +@end + + + +#pragma mark - APLevelDBWriteBatch + +@implementation APLevelDBWriteBatch + +- (id)initWithLevelDB:(APLevelDB *)levelDB { + self = [super init]; + if (self != nil) { + self->_levelDB = levelDB; + } + return self; +} + +- (void)setData:(NSData *)data forKey:(NSString *)key +{ + leveldb::Slice keySlice = SliceFromString(key); + leveldb::Slice valueSlice = leveldb::Slice((const char *)[data bytes], (size_t)[data length]); + _batch.Put(keySlice, valueSlice); +} +- (void)setString:(NSString *)str forKey:(NSString *)key +{ + leveldb::Slice keySlice = SliceFromString(key); + leveldb::Slice valueSlice = SliceFromString(str); + _batch.Put(keySlice, valueSlice); +} + +- (void)removeKey:(NSString *)key +{ + leveldb::Slice keySlice = SliceFromString(key); + _batch.Delete(keySlice); +} + +- (void)clear +{ + _batch.Clear(); +} + +- (BOOL)commit { + return [self.levelDB commitWriteBatch:self]; +} + +@end + diff --git a/!main project/Pods/FirebaseDatabase/LICENSE b/!main project/Pods/FirebaseDatabase/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/!main project/Pods/FirebaseDatabase/README.md b/!main project/Pods/FirebaseDatabase/README.md new file mode 100644 index 0000000..3ddc8fb --- /dev/null +++ b/!main project/Pods/FirebaseDatabase/README.md @@ -0,0 +1,251 @@ +# Firebase iOS Open Source Development [![Build Status](https://travis-ci.org/firebase/firebase-ios-sdk.svg?branch=master)](https://travis-ci.org/firebase/firebase-ios-sdk) + +This repository contains a subset of the Firebase iOS SDK source. It currently +includes FirebaseCore, FirebaseABTesting, FirebaseAuth, FirebaseDatabase, +FirebaseFirestore, FirebaseFunctions, FirebaseInstanceID, FirebaseInAppMessaging, +FirebaseInAppMessagingDisplay, FirebaseMessaging, FirebaseRemoteConfig, and +FirebaseStorage. + +The repository also includes GoogleUtilities source. The +[GoogleUtilities](GoogleUtilities/README.md) pod is +a set of utilities used by Firebase and other Google products. + +Firebase is an app development platform with tools to help you build, grow and +monetize your app. More information about Firebase can be found at +[https://firebase.google.com](https://firebase.google.com). + +## Installation + +See the three subsections for details about three different installation methods. +1. [Standard pod install](README.md#standard-pod-install) +1. [Installing from the GitHub repo](README.md#installing-from-github) +1. [Experimental Carthage](README.md#carthage-ios-only) + +### Standard pod install + +Go to +[https://firebase.google.com/docs/ios/setup](https://firebase.google.com/docs/ios/setup). + +### Installing from GitHub + +For releases starting with 5.0.0, the source for each release is also deployed +to CocoaPods master and available via standard +[CocoaPods Podfile syntax](https://guides.cocoapods.org/syntax/podfile.html#pod). + +These instructions can be used to access the Firebase repo at other branches, +tags, or commits. + +#### Background + +See +[the Podfile Syntax Reference](https://guides.cocoapods.org/syntax/podfile.html#pod) +for instructions and options about overriding pod source locations. + +#### Accessing Firebase Source Snapshots + +All of the official releases are tagged in this repo and available via CocoaPods. To access a local +source snapshot or unreleased branch, use Podfile directives like the following: + +To access FirebaseFirestore via a branch: +``` +pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +``` + +To access FirebaseMessaging via a checked out version of the firebase-ios-sdk repo do: + +``` +pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk' +pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk' +``` + +### Carthage (iOS only) + +Instructions for the experimental Carthage distribution are at +[Carthage](Carthage.md). + +### Rome + +Instructions for installing binary frameworks via +[Rome](https://github.com/CocoaPods/Rome) are at [Rome](Rome.md). + +## Development + +To develop Firebase software in this repository, ensure that you have at least +the following software: + + * Xcode 10.1 (or later) + * CocoaPods 1.7.2 (or later) + * [CocoaPods generate](https://github.com/square/cocoapods-generate) + +For the pod that you want to develop: + +`pod gen Firebase{name here}.podspec --local-sources=./ --auto-open --platforms=ios` + +Note: If the CocoaPods cache is out of date, you may need to run +`pod repo update` before the `pod gen` command. + +Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for +those platforms. Since 10.2, Xcode does not properly handle multi-platform +CocoaPods workspaces. + +Firestore has a self contained Xcode project. See +[Firestore/README.md](Firestore/README.md). + +### Development for Catalyst +* `pod gen {name here}.podspec --local-sources=./ --auto-open --platforms=ios` +* Check the Mac box in the App-iOS Build Settings +* Sign the App in the Settings Signing & Capabilities tab +* Click Pods in the Project Manager +* Add Signing to the iOS host app and unit test targets +* Select the Unit-unit scheme +* Run it to build and test + +### Adding a New Firebase Pod + +See [AddNewPod.md](AddNewPod.md). + +### Code Formatting + +To ensure that the code is formatted consistently, run the script +[./scripts/style.sh](https://github.com/firebase/firebase-ios-sdk/blob/master/scripts/style.sh) +before creating a PR. + +Travis will verify that any code changes are done in a style compliant way. Install +`clang-format` and `swiftformat`. +These commands will get the right versions: + +``` +brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/e3496d9/Formula/clang-format.rb +brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/7963c3d/Formula/swiftformat.rb +``` + +Note: if you already have a newer version of these installed you may need to +`brew switch` to this version. + +To update this section, find the versions of clang-format and swiftformat.rb to +match the versions in the CI failure logs +[here](https://github.com/Homebrew/homebrew-core/tree/master/Formula). + +### Running Unit Tests + +Select a scheme and press Command-u to build a component and run its unit tests. + +#### Viewing Code Coverage + +First, make sure that [xcov](https://github.com/nakiostudio/xcov) is installed with `gem install xcov`. + +After running the `AllUnitTests_iOS` scheme in Xcode, execute +`xcov --workspace Firebase.xcworkspace --scheme AllUnitTests_iOS --output_directory xcov_output` +at Example/ in the terminal. This will aggregate the coverage, and you can run `open xcov_output/index.html` to see the results. + +### Running Sample Apps +In order to run the sample apps and integration tests, you'll need valid +`GoogleService-Info.plist` files for those samples. The Firebase Xcode project contains dummy plist +files without real values, but can be replaced with real plist files. To get your own +`GoogleService-Info.plist` files: + +1. Go to the [Firebase Console](https://console.firebase.google.com/) +2. Create a new Firebase project, if you don't already have one +3. For each sample app you want to test, create a new Firebase app with the sample app's bundle +identifier (e.g. `com.google.Database-Example`) +4. Download the resulting `GoogleService-Info.plist` and replace the appropriate dummy plist file +(e.g. in [Example/Database/App/](Example/Database/App/)); + +Some sample apps like Firebase Messaging ([Example/Messaging/App](Example/Messaging/App)) require +special Apple capabilities, and you will have to change the sample app to use a unique bundle +identifier that you can control in your own Apple Developer account. + +## Specific Component Instructions +See the sections below for any special instructions for those components. + +### Firebase Auth + +If you're doing specific Firebase Auth development, see +[the Auth Sample README](Example/Auth/README.md) for instructions about +building and running the FirebaseAuth pod along with various samples and tests. + +### Firebase Database + +To run the Database Integration tests, make your database authentication rules +[public](https://firebase.google.com/docs/database/security/quickstart). + +### Firebase Storage + +To run the Storage Integration tests, follow the instructions in +[FIRStorageIntegrationTests.m](Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m). + +#### Push Notifications + +Push notifications can only be delivered to specially provisioned App IDs in the developer portal. +In order to actually test receiving push notifications, you will need to: + +1. Change the bundle identifier of the sample app to something you own in your Apple Developer +account, and enable that App ID for push notifications. +2. You'll also need to +[upload your APNs Provider Authentication Key or certificate to the Firebase Console](https://firebase.google.com/docs/cloud-messaging/ios/certs) +at **Project Settings > Cloud Messaging > [Your Firebase App]**. +3. Ensure your iOS device is added to your Apple Developer portal as a test device. + +#### iOS Simulator + +The iOS Simulator cannot register for remote notifications, and will not receive push notifications. +In order to receive push notifications, you'll have to follow the steps above and run the app on a +physical device. + +## Community Supported Efforts + +We've seen an amazing amount of interest and contributions to improve the Firebase SDKs, and we are +very grateful! We'd like to empower as many developers as we can to be able to use Firebase and +participate in the Firebase community. + +### tvOS, macOS, and Catalyst +Thanks to contributions from the community, FirebaseABTesting, FirebaseAuth, FirebaseCore, +FirebaseDatabase, FirebaseMessaging, FirebaseFirestore, +FirebaseFunctions, FirebaseRemoteConfig, and FirebaseStorage now compile, run unit tests, and work on +tvOS, macOS, and Catalyst. + +For tvOS, checkout the [Sample](Example/tvOSSample). + +Keep in mind that macOS, Catalyst and tvOS are not officially supported by Firebase, and this +repository is actively developed primarily for iOS. While we can catch basic unit test issues with +Travis, there may be some changes where the SDK no longer works as expected on macOS or tvOS. If you +encounter this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues). + +To install, add a subset of the following to the Podfile: + +``` +pod 'Firebase/ABTesting' +pod 'Firebase/Auth' +pod 'Firebase/Database' +pod 'Firebase/Firestore' +pod 'Firebase/Functions' +pod 'Firebase/Messaging' +pod 'Firebase/RemoteConfig' +pod 'Firebase/Storage' +``` + +#### Additional Catalyst Notes + +* FirebaseAuth and FirebaseMessaging require adding `Keychain Sharing Capability` +to Build Settings. +* FirebaseFirestore requires signing the +[gRPC Resource target](https://github.com/firebase/firebase-ios-sdk/issues/3500#issuecomment-518741681). + +## Roadmap + +See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source +plans and directions. + +## Contributing + +See [Contributing](CONTRIBUTING.md) for more information on contributing to the Firebase +iOS SDK. + +## License + +The contents of this repository is licensed under the +[Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0). + +Your use of Firebase is governed by the +[Terms of Service for Firebase Services](https://firebase.google.com/terms/). diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.h b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.h new file mode 100644 index 0000000..5bc21a1 --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.h @@ -0,0 +1,56 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import + +@class FIRInstallationsHTTPError; + +NS_ASSUME_NONNULL_BEGIN + +void FIRInstallationsItemSetErrorToPointer(NSError *error, NSError **pointer); + +@interface FIRInstallationsErrorUtil : NSObject + ++ (NSError *)keyedArchiverErrorWithException:(NSException *)exception; ++ (NSError *)keyedArchiverErrorWithError:(NSError *)error; + ++ (NSError *)keychainErrorWithFunction:(NSString *)keychainFunction status:(OSStatus)status; + ++ (NSError *)installationItemNotFoundForAppID:(NSString *)appID appName:(NSString *)appName; + ++ (NSError *)JSONSerializationError:(NSError *)error; + ++ (NSError *)networkErrorWithError:(NSError *)error; + ++ (NSError *)FIDRegistrationErrorWithResponseMissingField:(NSString *)missingFieldName; + ++ (NSError *)corruptedIIDTokenData; + ++ (FIRInstallationsHTTPError *)APIErrorWithHTTPResponse:(NSHTTPURLResponse *)HTTPResponse + data:(nullable NSData *)data; ++ (BOOL)isAPIError:(NSError *)error withHTTPCode:(NSInteger)HTTPCode; + +/** + * Returns the passed error if it is already in the public domain or a new error with the passed + * error at `NSUnderlyingErrorKey`. + */ ++ (NSError *)publicDomainErrorWithError:(NSError *)error; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.m b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.m new file mode 100644 index 0000000..f85923a --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.m @@ -0,0 +1,124 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstallationsErrorUtil.h" + +#import "FIRInstallationsHTTPError.h" + +NSString *const kFirebaseInstallationsErrorDomain = @"com.firebase.installations"; + +void FIRInstallationsItemSetErrorToPointer(NSError *error, NSError **pointer) { + if (pointer != NULL) { + *pointer = error; + } +} + +@implementation FIRInstallationsErrorUtil + ++ (NSError *)keyedArchiverErrorWithException:(NSException *)exception { + NSString *failureReason = [NSString + stringWithFormat:@"NSKeyedArchiver exception with name: %@, reason: %@, userInfo: %@", + exception.name, exception.reason, exception.userInfo]; + return [self installationsErrorWithCode:FIRInstallationsErrorCodeUnknown + failureReason:failureReason + underlyingError:nil]; +} + ++ (NSError *)keyedArchiverErrorWithError:(NSError *)error { + NSString *failureReason = [NSString stringWithFormat:@"NSKeyedArchiver error."]; + return [self installationsErrorWithCode:FIRInstallationsErrorCodeUnknown + failureReason:failureReason + underlyingError:error]; +} + ++ (NSError *)keychainErrorWithFunction:(NSString *)keychainFunction status:(OSStatus)status { + NSString *failureReason = [NSString stringWithFormat:@"%@ (%li)", keychainFunction, (long)status]; + return [self installationsErrorWithCode:FIRInstallationsErrorCodeKeychain + failureReason:failureReason + underlyingError:nil]; +} + ++ (NSError *)installationItemNotFoundForAppID:(NSString *)appID appName:(NSString *)appName { + NSString *failureReason = + [NSString stringWithFormat:@"Installation for appID %@ appName %@ not found", appID, appName]; + return [self installationsErrorWithCode:FIRInstallationsErrorCodeUnknown + failureReason:failureReason + underlyingError:nil]; +} + ++ (NSError *)corruptedIIDTokenData { + NSString *failureReason = + @"IID token data stored in Keychain is corrupted or in an incompatible format."; + return [self installationsErrorWithCode:FIRInstallationsErrorCodeUnknown + failureReason:failureReason + underlyingError:nil]; +} + ++ (FIRInstallationsHTTPError *)APIErrorWithHTTPResponse:(NSHTTPURLResponse *)HTTPResponse + data:(nullable NSData *)data { + return [[FIRInstallationsHTTPError alloc] initWithHTTPResponse:HTTPResponse data:data]; +} + ++ (BOOL)isAPIError:(NSError *)error withHTTPCode:(NSInteger)HTTPCode { + if (![error isKindOfClass:[FIRInstallationsHTTPError class]]) { + return NO; + } + + return [(FIRInstallationsHTTPError *)error HTTPResponse].statusCode == HTTPCode; +} + ++ (NSError *)JSONSerializationError:(NSError *)error { + NSString *failureReason = [NSString stringWithFormat:@"Failed to serialize JSON data."]; + return [self installationsErrorWithCode:FIRInstallationsErrorCodeUnknown + failureReason:failureReason + underlyingError:nil]; +} + ++ (NSError *)FIDRegistrationErrorWithResponseMissingField:(NSString *)missingFieldName { + NSString *failureReason = [NSString + stringWithFormat:@"A required response field with name %@ is missing", missingFieldName]; + return [self installationsErrorWithCode:FIRInstallationsErrorCodeUnknown + failureReason:failureReason + underlyingError:nil]; +} + ++ (NSError *)networkErrorWithError:(NSError *)error { + return [self installationsErrorWithCode:FIRInstallationsErrorCodeServerUnreachable + failureReason:@"Network connection error." + underlyingError:error]; +} + ++ (NSError *)publicDomainErrorWithError:(NSError *)error { + if ([error.domain isEqualToString:kFirebaseInstallationsErrorDomain]) { + return error; + } + + return [self installationsErrorWithCode:FIRInstallationsErrorCodeUnknown + failureReason:nil + underlyingError:error]; +} + ++ (NSError *)installationsErrorWithCode:(FIRInstallationsErrorCode)code + failureReason:(nullable NSString *)failureReason + underlyingError:(nullable NSError *)underlyingError { + NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; + userInfo[NSUnderlyingErrorKey] = underlyingError; + userInfo[NSLocalizedFailureReasonErrorKey] = failureReason; + + return [NSError errorWithDomain:kFirebaseInstallationsErrorDomain code:code userInfo:userInfo]; +} + +@end diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Errors/FIRInstallationsHTTPError.h b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Errors/FIRInstallationsHTTPError.h new file mode 100644 index 0000000..ad0eb8c --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Errors/FIRInstallationsHTTPError.h @@ -0,0 +1,54 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Represents an error caused by an unexpected API response. */ +@interface FIRInstallationsHTTPError : NSError + +@property(nonatomic, readonly) NSHTTPURLResponse *HTTPResponse; +@property(nonatomic, readonly, nonnull) NSData *data; + +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithHTTPResponse:(NSHTTPURLResponse *)HTTPResponse data:(nullable NSData *)data; + +@end + +NS_ASSUME_NONNULL_END + +typedef NS_ENUM(NSInteger, FIRInstallationsHTTPCodes) { + FIRInstallationsHTTPCodesTooManyRequests = 429, + FIRInstallationsHTTPCodesServerInternalError = 500, +}; + +/** Possible response HTTP codes for `CreateInstallation` API request. */ +typedef NS_ENUM(NSInteger, FIRInstallationsRegistrationHTTPCode) { + FIRInstallationsRegistrationHTTPCodeSuccess = 201, + FIRInstallationsRegistrationHTTPCodeInvalidArgument = 400, + FIRInstallationsRegistrationHTTPCodeInvalidAPIKey = 401, + FIRInstallationsRegistrationHTTPCodeAPIKeyToProjectIDMismatch = 403, + FIRInstallationsRegistrationHTTPCodeProjectNotFound = 404, + FIRInstallationsRegistrationHTTPCodeTooManyRequests = 429, + FIRInstallationsRegistrationHTTPCodeServerInternalError = 500 +}; + +typedef NS_ENUM(NSInteger, FIRInstallationsAuthTokenHTTPCode) { + FIRInstallationsAuthTokenHTTPCodeInvalidAuthentication = 401, + FIRInstallationsAuthTokenHTTPCodeFIDNotFound = 404, +}; diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Errors/FIRInstallationsHTTPError.m b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Errors/FIRInstallationsHTTPError.m new file mode 100644 index 0000000..5b3eae2 --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Errors/FIRInstallationsHTTPError.m @@ -0,0 +1,78 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstallationsHTTPError.h" +#import "FIRInstallationsErrorUtil.h" + +@implementation FIRInstallationsHTTPError + +- (instancetype)initWithHTTPResponse:(NSHTTPURLResponse *)HTTPResponse + data:(nullable NSData *)data { + NSDictionary *userInfo = [FIRInstallationsHTTPError userInfoWithHTTPResponse:HTTPResponse + data:data]; + self = [super + initWithDomain:kFirebaseInstallationsErrorDomain + code:[FIRInstallationsHTTPError errorCodeWithHTTPCode:HTTPResponse.statusCode] + userInfo:userInfo]; + if (self) { + _HTTPResponse = HTTPResponse; + _data = data; + } + return self; +} + ++ (FIRInstallationsErrorCode)errorCodeWithHTTPCode:(NSInteger)HTTPCode { + return FIRInstallationsErrorCodeUnknown; +} + ++ (NSDictionary *)userInfoWithHTTPResponse:(NSHTTPURLResponse *)HTTPResponse + data:(nullable NSData *)data { + NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + NSString *failureReason = [NSString + stringWithFormat:@"The server responded with an error. HTTP response: %@\nResponse body: %@", + HTTPResponse, responseString]; + return @{NSLocalizedFailureReasonErrorKey : failureReason}; +} + +#pragma mark - NSCopying + +- (id)copyWithZone:(NSZone *)zone { + return [[FIRInstallationsHTTPError alloc] initWithHTTPResponse:self.HTTPResponse data:self.data]; +} + +#pragma mark - NSSecureCoding + +- (nullable instancetype)initWithCoder:(NSCoder *)coder { + NSHTTPURLResponse *HTTPResponse = [coder decodeObjectOfClass:[NSHTTPURLResponse class] + forKey:@"HTTPResponse"]; + if (!HTTPResponse) { + return nil; + } + NSData *data = [coder decodeObjectOfClass:[NSData class] forKey:@"data"]; + + return [self initWithHTTPResponse:HTTPResponse data:data]; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + [coder encodeObject:self.HTTPResponse forKey:@"HTTPResponse"]; + [coder encodeObject:self.data forKey:@"data"]; +} + ++ (BOOL)supportsSecureCoding { + return YES; +} + +@end diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallations.m b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallations.m new file mode 100644 index 0000000..71e7dd4 --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallations.m @@ -0,0 +1,248 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstallations.h" + +#if __has_include() +#import +#else +#import "FBLPromises.h" +#endif + +#import +#import +#import +#import +#import +#import + +#import "FIRInstallationsAuthTokenResultInternal.h" + +#import "FIRInstallationsErrorUtil.h" +#import "FIRInstallationsIDController.h" +#import "FIRInstallationsItem.h" +#import "FIRInstallationsLogger.h" +#import "FIRInstallationsStoredAuthToken.h" +#import "FIRInstallationsVersion.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol FIRInstallationsInstanceProvider +@end + +@interface FIRInstallations () +@property(nonatomic, readonly) FIROptions *appOptions; +@property(nonatomic, readonly) NSString *appName; + +@property(nonatomic, readonly) FIRInstallationsIDController *installationsIDController; + +@end + +@implementation FIRInstallations + +#pragma mark - Firebase component + ++ (void)load { + [FIRApp registerInternalLibrary:(Class)self + withName:@"fire-install" + withVersion:[NSString stringWithUTF8String:FIRInstallationsVersionStr]]; +} + ++ (nonnull NSArray *)componentsToRegister { + FIRComponentCreationBlock creationBlock = + ^id _Nullable(FIRComponentContainer *container, BOOL *isCacheable) { + *isCacheable = YES; + FIRInstallations *installations = [[FIRInstallations alloc] initWithApp:container.app]; + return installations; + }; + + FIRComponent *installationsProvider = + [FIRComponent componentWithProtocol:@protocol(FIRInstallationsInstanceProvider) + instantiationTiming:FIRInstantiationTimingAlwaysEager + dependencies:@[] + creationBlock:creationBlock]; + return @[ installationsProvider ]; +} + +- (instancetype)initWithApp:(FIRApp *)app { + return [self initWitAppOptions:app.options appName:app.name]; +} + +- (instancetype)initWitAppOptions:(FIROptions *)appOptions appName:(NSString *)appName { + FIRInstallationsIDController *IDController = + [[FIRInstallationsIDController alloc] initWithGoogleAppID:appOptions.googleAppID + appName:appName + APIKey:appOptions.APIKey + projectID:appOptions.projectID + GCMSenderID:appOptions.GCMSenderID + accessGroup:appOptions.appGroupID]; + return [self initWithAppOptions:appOptions + appName:appName + installationsIDController:IDController + prefetchAuthToken:YES]; +} + +/// The initializer is supposed to be used by tests to inject `installationsStore`. +- (instancetype)initWithAppOptions:(FIROptions *)appOptions + appName:(NSString *)appName + installationsIDController:(FIRInstallationsIDController *)installationsIDController + prefetchAuthToken:(BOOL)prefetchAuthToken { + self = [super init]; + if (self) { + [[self class] validateAppOptions:appOptions appName:appName]; + [[self class] assertCompatibleIIDVersion]; + + _appOptions = [appOptions copy]; + _appName = [appName copy]; + _installationsIDController = installationsIDController; + + // Pre-fetch auth token. + if (prefetchAuthToken) { + [self authTokenWithCompletion:^(FIRInstallationsAuthTokenResult *_Nullable tokenResult, + NSError *_Nullable error){ + }]; + } + } + return self; +} + ++ (void)validateAppOptions:(FIROptions *)appOptions appName:(NSString *)appName { + NSMutableArray *missingFields = [NSMutableArray array]; + if (appName.length < 1) { + [missingFields addObject:@"`FirebaseApp.name`"]; + } + if (appOptions.APIKey.length < 1) { + [missingFields addObject:@"`FirebaseOptions.APIKey`"]; + } + if (appOptions.googleAppID.length < 1) { + [missingFields addObject:@"`FirebaseOptions.googleAppID`"]; + } + + // TODO(#4692): Check for `appOptions.projectID.length < 1` only. + // We can use `GCMSenderID` instead of `projectID` temporary. + if (appOptions.projectID.length < 1 && appOptions.GCMSenderID.length < 1) { + [missingFields addObject:@"`FirebaseOptions.projectID`"]; + } + + if (missingFields.count > 0) { + [NSException + raise:kFirebaseInstallationsErrorDomain + format: + @"%@[%@] Could not configure Firebase Installations due to invalid FirebaseApp " + @"options. The following parameters are nil or empty: %@. If you use " + @"GoogleServices-Info.plist please download the most recent version from the Firebase " + @"Console. If you configure Firebase in code, please make sure you specify all " + @"required parameters.", + kFIRLoggerInstallations, kFIRInstallationsMessageCodeInvalidFirebaseAppOptions, + [missingFields componentsJoinedByString:@", "]]; + } +} + +#pragma mark - Public + ++ (FIRInstallations *)installations { + FIRApp *defaultApp = [FIRApp defaultApp]; + if (!defaultApp) { + [NSException raise:kFirebaseInstallationsErrorDomain + format:@"The default FirebaseApp instance must be configured before the default" + @"FirebaseApp instance can be initialized. One way to ensure that is to " + @"call `[FIRApp configure];` (`FirebaseApp.configure()` in Swift) in the App" + @" Delegate's `application:didFinishLaunchingWithOptions:` " + @"(`application(_:didFinishLaunchingWithOptions:)` in Swift)."]; + } + + return [self installationsWithApp:defaultApp]; +} + ++ (FIRInstallations *)installationsWithApp:(FIRApp *)app { + id installations = + FIR_COMPONENT(FIRInstallationsInstanceProvider, app.container); + return (FIRInstallations *)installations; +} + +- (void)installationIDWithCompletion:(FIRInstallationsIDHandler)completion { + [self.installationsIDController getInstallationItem] + .then(^id(FIRInstallationsItem *installation) { + completion(installation.firebaseInstallationID, nil); + return nil; + }) + .catch(^(NSError *error) { + completion(nil, [FIRInstallationsErrorUtil publicDomainErrorWithError:error]); + }); +} + +- (void)authTokenWithCompletion:(FIRInstallationsTokenHandler)completion { + [self authTokenForcingRefresh:NO completion:completion]; +} + +- (void)authTokenForcingRefresh:(BOOL)forceRefresh + completion:(FIRInstallationsTokenHandler)completion { + [self.installationsIDController getAuthTokenForcingRefresh:forceRefresh] + .then(^FIRInstallationsAuthTokenResult *(FIRInstallationsItem *installation) { + FIRInstallationsAuthTokenResult *result = [[FIRInstallationsAuthTokenResult alloc] + initWithToken:installation.authToken.token + expirationDate:installation.authToken.expirationDate]; + return result; + }) + .then(^id(FIRInstallationsAuthTokenResult *token) { + completion(token, nil); + return nil; + }) + .catch(^void(NSError *error) { + completion(nil, [FIRInstallationsErrorUtil publicDomainErrorWithError:error]); + }); +} + +- (void)deleteWithCompletion:(void (^)(NSError *__nullable error))completion { + [self.installationsIDController deleteInstallation] + .then(^id(id result) { + completion(nil); + return nil; + }) + .catch(^void(NSError *error) { + completion([FIRInstallationsErrorUtil publicDomainErrorWithError:error]); + }); +} + +#pragma mark - IID version compatibility + ++ (void)assertCompatibleIIDVersion { + // We use this flag to disable IID compatibility exception for unit tests. +#ifdef FIR_INSTALLATIONS_ALLOWS_INCOMPATIBLE_IID_VERSION + return; +#else + if (![self isIIDVersionCompatible]) { + [NSException raise:kFirebaseInstallationsErrorDomain + format:@"FirebaseInstallations will not work correctly with current version of " + @"Firebase Instance ID. Please update your Firebase Instance ID version."]; + } +#endif +} + ++ (BOOL)isIIDVersionCompatible { + Class IIDClass = NSClassFromString(@"FIRInstanceID"); + if (IIDClass == nil) { + // It is OK if there is no IID at all. + return YES; + } + // We expect a compatible version having the method `+[FIRInstanceID usesFIS]` defined. + BOOL isCompatibleVersion = [IIDClass respondsToSelector:NSSelectorFromString(@"usesFIS")]; + return isCompatibleVersion; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallationsAuthTokenResult.m b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallationsAuthTokenResult.m new file mode 100644 index 0000000..92e5fab --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallationsAuthTokenResult.m @@ -0,0 +1,30 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstallationsAuthTokenResultInternal.h" + +@implementation FIRInstallationsAuthTokenResult + +- (instancetype)initWithToken:(NSString *)token expirationDate:(NSDate *)expirationDate { + self = [super init]; + if (self) { + _authToken = [token copy]; + _expirationDate = expirationDate; + } + return self; +} + +@end diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallationsAuthTokenResultInternal.h b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallationsAuthTokenResultInternal.h new file mode 100644 index 0000000..0c959db --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallationsAuthTokenResultInternal.h @@ -0,0 +1,27 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRInstallationsAuthTokenResult (Internal) + +- (instancetype)initWithToken:(NSString *)token expirationDate:(NSDate *)expirationTime; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallationsItem.h b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallationsItem.h new file mode 100644 index 0000000..95fdf83 --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallationsItem.h @@ -0,0 +1,86 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRInstallationsStatus.h" + +@class FIRInstallationsStoredItem; +@class FIRInstallationsStoredAuthToken; +@class FIRInstallationsStoredIIDCheckin; + +NS_ASSUME_NONNULL_BEGIN + +/** + * The class represents the required installation ID and auth token data including possible states. + * The data is stored to Keychain via `FIRInstallationsStoredItem` which has only the storage + * relevant data and does not contain any logic. `FIRInstallationsItem` must be used on the logic + * level (not `FIRInstallationsStoredItem`). + */ +@interface FIRInstallationsItem : NSObject + +/// A `FirebaseApp` identifier. +@property(nonatomic, readonly) NSString *appID; +/// A `FirebaseApp` name. +@property(nonatomic, readonly) NSString *firebaseAppName; +/// A stable identifier that uniquely identifies the app instance. +@property(nonatomic, copy, nullable) NSString *firebaseInstallationID; +/// The `refreshToken` is used to authorize the auth token requests. +@property(nonatomic, copy, nullable) NSString *refreshToken; + +@property(nonatomic, nullable) FIRInstallationsStoredAuthToken *authToken; +@property(nonatomic, assign) FIRInstallationsStatus registrationStatus; + +/// Instance ID default token imported from IID store as a part of IID migration. +@property(nonatomic, nullable) NSString *IIDDefaultToken; + +- (instancetype)initWithAppID:(NSString *)appID firebaseAppName:(NSString *)firebaseAppName; + +/** + * Populates `FIRInstallationsItem` properties with data from `FIRInstallationsStoredItem`. + * @param item An instance of `FIRInstallationsStoredItem` to get data from. + */ +- (void)updateWithStoredItem:(FIRInstallationsStoredItem *)item; + +/** + * Creates a stored item with data from the object. + * @return Returns a `FIRInstallationsStoredItem` instance with the data from the object. + */ +- (FIRInstallationsStoredItem *)storedItem; + +/** + * The installation identifier. + * @return Returns a string uniquely identifying the installation. + */ +- (NSString *)identifier; + +/** + * The installation identifier. + * @param appID A `FirebaseApp` identifier. + * @param appName A `FirebaseApp` name. + * @return Returns a string uniquely identifying the installation. + */ ++ (NSString *)identifierWithAppID:(NSString *)appID appName:(NSString *)appName; + +/** + * Generate a new Firebase Installation Identifier. + * @return Returns a 22 characters long globally unique string created based on UUID. + */ ++ (NSString *)generateFID; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallationsItem.m b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallationsItem.m new file mode 100644 index 0000000..bc819bf --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallationsItem.m @@ -0,0 +1,104 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstallationsItem.h" + +#import "FIRInstallationsStoredAuthToken.h" +#import "FIRInstallationsStoredItem.h" + +@implementation FIRInstallationsItem + +- (instancetype)initWithAppID:(NSString *)appID firebaseAppName:(NSString *)firebaseAppName { + self = [super init]; + if (self) { + _appID = [appID copy]; + _firebaseAppName = [firebaseAppName copy]; + } + return self; +} + +- (nonnull id)copyWithZone:(nullable NSZone *)zone { + FIRInstallationsItem *clone = [[FIRInstallationsItem alloc] initWithAppID:self.appID + firebaseAppName:self.firebaseAppName]; + clone.firebaseInstallationID = [self.firebaseInstallationID copy]; + clone.refreshToken = [self.refreshToken copy]; + clone.authToken = [self.authToken copy]; + clone.registrationStatus = self.registrationStatus; + + return clone; +} + +- (void)updateWithStoredItem:(FIRInstallationsStoredItem *)item { + self.firebaseInstallationID = item.firebaseInstallationID; + self.refreshToken = item.refreshToken; + self.authToken = item.authToken; + self.registrationStatus = item.registrationStatus; + self.IIDDefaultToken = item.IIDDefaultToken; +} + +- (FIRInstallationsStoredItem *)storedItem { + FIRInstallationsStoredItem *storedItem = [[FIRInstallationsStoredItem alloc] init]; + storedItem.firebaseInstallationID = self.firebaseInstallationID; + storedItem.refreshToken = self.refreshToken; + storedItem.authToken = self.authToken; + storedItem.registrationStatus = self.registrationStatus; + storedItem.IIDDefaultToken = self.IIDDefaultToken; + return storedItem; +} + +- (nonnull NSString *)identifier { + return [[self class] identifierWithAppID:self.appID appName:self.firebaseAppName]; +} + ++ (NSString *)identifierWithAppID:(NSString *)appID appName:(NSString *)appName { + return [appID stringByAppendingString:appName]; +} + ++ (NSString *)generateFID { + NSUUID *UUID = [NSUUID UUID]; + uuid_t UUIDBytes; + [UUID getUUIDBytes:UUIDBytes]; + + NSUInteger UUIDLength = sizeof(uuid_t); + NSData *UUIDData = [NSData dataWithBytes:UUIDBytes length:UUIDLength]; + + uint8_t UUIDLast4Bits = UUIDBytes[UUIDLength - 1] & 0b00001111; + + // FID first 4 bits must be `0111`. The last 4 UUID bits will be cut later to form a proper FID. + // To keep 16 random bytes we copy these last 4 UUID to the FID 1st byte after `0111` prefix. + uint8_t FIDPrefix = 0b01110000 | UUIDLast4Bits; + NSMutableData *FIDData = [NSMutableData dataWithBytes:&FIDPrefix length:1]; + + [FIDData appendData:UUIDData]; + NSString *FIDString = [self base64URLEncodedStringWithData:FIDData]; + + // A valid FID has exactly 22 base64 characters, which is 132 bits, or 16.5 bytes. + // Our generated ID has 16 bytes UUID + 1 byte prefix which after encoding with base64 will become + // 23 characters plus 1 character for "=" padding. + + // Remove the 23rd character that was added because of the extra 4 bits at the + // end of our 17 byte data and the '=' padding. + return [FIDString substringWithRange:NSMakeRange(0, 22)]; +} + ++ (NSString *)base64URLEncodedStringWithData:(NSData *)data { + NSString *string = [data base64EncodedStringWithOptions:0]; + string = [string stringByReplacingOccurrencesOfString:@"/" withString:@"_"]; + string = [string stringByReplacingOccurrencesOfString:@"+" withString:@"-"]; + return string; +} + +@end diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallationsLogger.h b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallationsLogger.h new file mode 100644 index 0000000..baeadb2 --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallationsLogger.h @@ -0,0 +1,51 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import + +extern FIRLoggerService kFIRLoggerInstallations; + +// FIRInstallationsAPIService.m +extern NSString *const kFIRInstallationsMessageCodeSendAPIRequest; +extern NSString *const kFIRInstallationsMessageCodeAPIRequestNetworkError; +extern NSString *const kFIRInstallationsMessageCodeAPIRequestResponse; +extern NSString *const kFIRInstallationsMessageCodeUnexpectedAPIRequestResponse; +extern NSString *const kFIRInstallationsMessageCodeParsingAPIResponse; +extern NSString *const kFIRInstallationsMessageCodeAPIResponseParsingInstallationFailed; +extern NSString *const kFIRInstallationsMessageCodeAPIResponseParsingInstallationSucceed; +extern NSString *const kFIRInstallationsMessageCodeAPIResponseParsingAuthTokenFailed; +extern NSString *const kFIRInstallationsMessageCodeAPIResponseParsingAuthTokenSucceed; + +// FIRInstallationsIDController.m +extern NSString *const kFIRInstallationsMessageCodeNewGetInstallationOperationCreated; +extern NSString *const kFIRInstallationsMessageCodeNewGetAuthTokenOperationCreated; +extern NSString *const kFIRInstallationsMessageCodeNewDeleteInstallationOperationCreated; +extern NSString *const kFIRInstallationsMessageCodeInvalidFirebaseConfiguration; + +// FIRInstallationsStoredItem.m +extern NSString *const kFIRInstallationsMessageCodeInstallationCoderVersionMismatch; + +// FIRInstallationsStoredAuthToken.m +extern NSString *const kFIRInstallationsMessageCodeAuthTokenCoderVersionMismatch; + +// FIRInstallationsStoredIIDCheckin.m +extern NSString *const kFIRInstallationsMessageCodeIIDCheckinCoderVersionMismatch; +extern NSString *const kFIRInstallationsMessageCodeIIDCheckinFailedToDecode; + +// FIRInstallations.m +extern NSString *const kFIRInstallationsMessageCodeInvalidFirebaseAppOptions; diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallationsLogger.m b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallationsLogger.m new file mode 100644 index 0000000..c2bdf37 --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallationsLogger.m @@ -0,0 +1,49 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstallationsLogger.h" + +FIRLoggerService kFIRLoggerInstallations = @"[Firebase/Installations]"; + +// FIRInstallationsAPIService.m +NSString *const kFIRInstallationsMessageCodeSendAPIRequest = @"I-FIS001001"; +NSString *const kFIRInstallationsMessageCodeAPIRequestNetworkError = @"I-FIS001002"; +NSString *const kFIRInstallationsMessageCodeAPIRequestResponse = @"I-FIS001003"; +NSString *const kFIRInstallationsMessageCodeUnexpectedAPIRequestResponse = @"I-FIS001004"; +NSString *const kFIRInstallationsMessageCodeParsingAPIResponse = @"I-FIS001005"; +NSString *const kFIRInstallationsMessageCodeAPIResponseParsingInstallationFailed = @"I-FIS001006"; +NSString *const kFIRInstallationsMessageCodeAPIResponseParsingInstallationSucceed = @"I-FIS001007"; +NSString *const kFIRInstallationsMessageCodeAPIResponseParsingAuthTokenFailed = @"I-FIS001008"; +NSString *const kFIRInstallationsMessageCodeAPIResponseParsingAuthTokenSucceed = @"I-FIS001009"; + +// FIRInstallationsIDController.m +NSString *const kFIRInstallationsMessageCodeNewGetInstallationOperationCreated = @"I-FIS002000"; +NSString *const kFIRInstallationsMessageCodeNewGetAuthTokenOperationCreated = @"I-FIS002001"; +NSString *const kFIRInstallationsMessageCodeNewDeleteInstallationOperationCreated = @"I-FIS002002"; +NSString *const kFIRInstallationsMessageCodeInvalidFirebaseConfiguration = @"I-FIS002003"; + +// FIRInstallationsStoredItem.m +NSString *const kFIRInstallationsMessageCodeInstallationCoderVersionMismatch = @"I-FIS003000"; + +// FIRInstallationsStoredAuthToken.m +NSString *const kFIRInstallationsMessageCodeAuthTokenCoderVersionMismatch = @"I-FIS004000"; + +// FIRInstallationsStoredIIDCheckin.m +NSString *const kFIRInstallationsMessageCodeIIDCheckinCoderVersionMismatch = @"I-FIS007000"; +NSString *const kFIRInstallationsMessageCodeIIDCheckinFailedToDecode = @"I-FIS007001"; + +// FIRInstallations.m +NSString *const kFIRInstallationsMessageCodeInvalidFirebaseAppOptions = @"I-FIS008000"; diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallationsVersion.m b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallationsVersion.m new file mode 100644 index 0000000..a75e3f5 --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallationsVersion.m @@ -0,0 +1,23 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstallationsVersion.h" + +// Convert the macro to a string +#define STR(x) STR_EXPAND(x) +#define STR_EXPAND(x) #x + +const char *const FIRInstallationsVersionStr = (const char *const)STR(FIRInstallations_LIB_VERSION); diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDStore.h b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDStore.h new file mode 100644 index 0000000..e2408ca --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDStore.h @@ -0,0 +1,48 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FBLPromise; + +NS_ASSUME_NONNULL_BEGIN + +/** The class encapsulates a port of a piece FirebaseInstanceID logic required to migrate IID. */ +@interface FIRInstallationsIIDStore : NSObject + +/** + * Retrieves existing IID if present. + * @return Returns a promise that is resolved with IID string if IID has been found or rejected with + * an error otherwise. + */ +- (FBLPromise *)existingIID; + +/** + * Deletes existing IID if present. + * @return Returns a promise that is resolved with `[NSNull null]` if the IID was successfully. + * deleted or was not found. The promise is rejected otherwise. + */ +- (FBLPromise *)deleteExistingIID; + +#if TARGET_OS_OSX +/// If not `nil`, then only this keychain will be used to save and read data (see +/// `kSecMatchSearchList` and `kSecUseKeychain`. It is mostly intended to be used by unit tests. +@property(nonatomic, nullable) SecKeychainRef keychainRef; +#endif // TARGET_OSX + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDStore.m b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDStore.m new file mode 100644 index 0000000..1f3a82a --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDStore.m @@ -0,0 +1,236 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstallationsIIDStore.h" + +#if __has_include() +#import +#else +#import "FBLPromises.h" +#endif + +#import +#import "FIRInstallationsErrorUtil.h" + +static NSString *const kFIRInstallationsIIDKeyPairPublicTagPrefix = + @"com.google.iid.keypair.public-"; +static NSString *const kFIRInstallationsIIDKeyPairPrivateTagPrefix = + @"com.google.iid.keypair.private-"; +static NSString *const kFIRInstallationsIIDCreationTimePlistKey = @"|S|cre"; + +@implementation FIRInstallationsIIDStore + +- (FBLPromise *)existingIID { + return [FBLPromise onQueue:dispatch_get_global_queue(QOS_CLASS_UTILITY, 0) + do:^id _Nullable { + if (![self hasPlistIIDFlag]) { + return nil; + } + + NSData *IIDPublicKeyData = [self IIDPublicKeyData]; + return [self IIDWithPublicKeyData:IIDPublicKeyData]; + }] + .validate(^BOOL(NSString *_Nullable IID) { + return IID.length > 0; + }); +} + +- (FBLPromise *)deleteExistingIID { + return [FBLPromise onQueue:dispatch_get_global_queue(QOS_CLASS_UTILITY, 0) + do:^id _Nullable { + NSError *error; + if (![self deleteIIDFlagFromPlist:&error]) { + return error; + } + + if (![self deleteIID:&error]) { + return error; + } + + return [NSNull null]; + }]; +} + +#pragma mark - IID decoding + +- (NSString *)IIDWithPublicKeyData:(NSData *)publicKeyData { + NSData *publicKeySHA1 = [self sha1WithData:publicKeyData]; + + const uint8_t *bytes = publicKeySHA1.bytes; + NSMutableData *identityData = [NSMutableData dataWithData:publicKeySHA1]; + + uint8_t b0 = bytes[0]; + // Take the first byte and make the initial four 7 by initially making the initial 4 bits 0 + // and then adding 0x70 to it. + b0 = 0x70 + (0xF & b0); + // failsafe should give you back b0 itself + b0 = (b0 & 0xFF); + [identityData replaceBytesInRange:NSMakeRange(0, 1) withBytes:&b0]; + NSData *data = [identityData subdataWithRange:NSMakeRange(0, 8 * sizeof(Byte))]; + return [self base64URLEncodedStringWithData:data]; +} + +- (NSData *)sha1WithData:(NSData *)data { + unsigned char output[CC_SHA1_DIGEST_LENGTH]; + unsigned int length = (unsigned int)[data length]; + + CC_SHA1(data.bytes, length, output); + return [NSData dataWithBytes:output length:CC_SHA1_DIGEST_LENGTH]; +} + +- (NSString *)base64URLEncodedStringWithData:(NSData *)data { + NSString *string = [data base64EncodedStringWithOptions:0]; + string = [string stringByReplacingOccurrencesOfString:@"/" withString:@"_"]; + string = [string stringByReplacingOccurrencesOfString:@"+" withString:@"-"]; + string = [string stringByReplacingOccurrencesOfString:@"=" withString:@""]; + return string; +} + +#pragma mark - Keychain + +- (NSData *)IIDPublicKeyData { + NSString *tag = [self keychainKeyTagWithPrefix:kFIRInstallationsIIDKeyPairPublicTagPrefix]; + NSDictionary *query = [self keyPairQueryWithTag:tag returnData:YES]; + + CFTypeRef keyRef = NULL; + OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&keyRef); + + if (status != noErr) { + if (keyRef) { + CFRelease(keyRef); + } + return nil; + } + + return (__bridge NSData *)keyRef; +} + +- (BOOL)deleteIID:(NSError **)outError { + if (![self deleteKeychainKeyWithTagPrefix:kFIRInstallationsIIDKeyPairPublicTagPrefix + error:outError]) { + return NO; + } + + if (![self deleteKeychainKeyWithTagPrefix:kFIRInstallationsIIDKeyPairPrivateTagPrefix + error:outError]) { + return NO; + } + + return YES; +} + +- (BOOL)deleteKeychainKeyWithTagPrefix:(NSString *)tagPrefix error:(NSError **)outError { + NSString *keyTag = [self keychainKeyTagWithPrefix:kFIRInstallationsIIDKeyPairPublicTagPrefix]; + NSDictionary *keyQuery = [self keyPairQueryWithTag:keyTag returnData:NO]; + + OSStatus status = SecItemDelete((__bridge CFDictionaryRef)keyQuery); + + // When item is not found, it should NOT be considered as an error. The operation should + // continue. + if (status != noErr && status != errSecItemNotFound) { + FIRInstallationsItemSetErrorToPointer( + [FIRInstallationsErrorUtil keychainErrorWithFunction:@"SecItemDelete" status:status], + outError); + return NO; + } + + return YES; +} + +- (NSDictionary *)keyPairQueryWithTag:(NSString *)tag returnData:(BOOL)shouldReturnData { + NSMutableDictionary *query = [NSMutableDictionary dictionary]; + NSData *tagData = [tag dataUsingEncoding:NSUTF8StringEncoding]; + + query[(__bridge id)kSecClass] = (__bridge id)kSecClassKey; + query[(__bridge id)kSecAttrApplicationTag] = tagData; + query[(__bridge id)kSecAttrKeyType] = (__bridge id)kSecAttrKeyTypeRSA; + if (shouldReturnData) { + query[(__bridge id)kSecReturnData] = @(YES); + } + +#if TARGET_OS_OSX + if (self.keychainRef) { + query[(__bridge NSString *)kSecMatchSearchList] = @[ (__bridge id)(self.keychainRef) ]; + } +#endif // TARGET_OSX + + return query; +} + +- (NSString *)keychainKeyTagWithPrefix:(NSString *)prefix { + NSString *mainAppBundleID = [[NSBundle mainBundle] bundleIdentifier]; + if (mainAppBundleID.length == 0) { + return nil; + } + return [NSString stringWithFormat:@"%@%@", prefix, mainAppBundleID]; +} + +- (NSString *)mainbundleIdentifier { + NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier]; + if (!bundleIdentifier.length) { + return nil; + } + return bundleIdentifier; +} + +#pragma mark - Plist + +- (BOOL)deleteIIDFlagFromPlist:(NSError **)outError { + NSString *path = [self plistPath]; + if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { + return YES; + } + + NSMutableDictionary *plistContent = [[NSMutableDictionary alloc] initWithContentsOfFile:path]; + plistContent[kFIRInstallationsIIDCreationTimePlistKey] = nil; + + if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) { + return [plistContent writeToURL:[NSURL fileURLWithPath:path] error:outError]; + } + + return [plistContent writeToFile:path atomically:YES]; +} + +- (BOOL)hasPlistIIDFlag { + NSString *path = [self plistPath]; + if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { + return NO; + } + + NSDictionary *plistContent = [[NSDictionary alloc] initWithContentsOfFile:path]; + return plistContent[kFIRInstallationsIIDCreationTimePlistKey] != nil; +} + +- (NSString *)plistPath { + NSString *plistNameWithExtension = @"com.google.iid-keypair.plist"; + NSString *_subDirectoryName = @"Google/FirebaseInstanceID"; + + NSArray *directoryPaths = + NSSearchPathForDirectoriesInDomains([self supportedDirectory], NSUserDomainMask, YES); + NSArray *components = @[ directoryPaths.lastObject, _subDirectoryName, plistNameWithExtension ]; + + return [NSString pathWithComponents:components]; +} + +- (NSSearchPathDirectory)supportedDirectory { +#if TARGET_OS_TV + return NSCachesDirectory; +#else + return NSApplicationSupportDirectory; +#endif +} + +@end diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDTokenStore.h b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDTokenStore.h new file mode 100644 index 0000000..ed98e3d --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDTokenStore.h @@ -0,0 +1,36 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FBLPromise; + +NS_ASSUME_NONNULL_BEGIN + +/** + * The class reads a default IID token from IID store if available. + */ +@interface FIRInstallationsIIDTokenStore : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithGCMSenderID:(NSString *)GCMSenderID; + +- (FBLPromise *)existingIIDDefaultToken; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDTokenStore.m b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDTokenStore.m new file mode 100644 index 0000000..1c9dbab --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDTokenStore.m @@ -0,0 +1,157 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstallationsIIDTokenStore.h" + +#if __has_include() +#import +#else +#import "FBLPromises.h" +#endif + +#import "FIRInstallationsErrorUtil.h" +#import "FIRInstallationsKeychainUtils.h" + +static NSString *const kFIRInstallationsIIDTokenKeychainId = @"com.google.iid-tokens"; + +@interface FIRInstallationsIIDTokenInfo : NSObject +@property(nonatomic, nullable, copy) NSString *token; +@end + +@implementation FIRInstallationsIIDTokenInfo + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (void)encodeWithCoder:(nonnull NSCoder *)coder { +} + +- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder { + self = [super init]; + if (self) { + _token = [coder decodeObjectOfClass:[NSString class] forKey:@"token"]; + } + return self; +} + +@end + +@interface FIRInstallationsIIDTokenStore () +@property(nonatomic, readonly) NSString *GCMSenderID; +@end + +@implementation FIRInstallationsIIDTokenStore + +- (instancetype)initWithGCMSenderID:(NSString *)GCMSenderID { + self = [super init]; + if (self) { + _GCMSenderID = GCMSenderID; + } + return self; +} + +- (FBLPromise *)existingIIDDefaultToken { + return [[FBLPromise onQueue:dispatch_get_global_queue(QOS_CLASS_UTILITY, 0) + do:^id _Nullable { + return [self IIDDefaultTokenData]; + }] onQueue:dispatch_get_global_queue(QOS_CLASS_UTILITY, 0) + then:^id _Nullable(NSData *_Nullable keychainData) { + return [self IIDCheckinWithData:keychainData]; + }]; +} + +- (FBLPromise *)IIDCheckinWithData:(NSData *)data { + FBLPromise *resultPromise = [FBLPromise pendingPromise]; + + NSError *archiverError; + NSKeyedUnarchiver *unarchiver; + if (@available(iOS 11.0, tvOS 11.0, macOS 10.13, *)) { + unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:data error:&archiverError]; + } else { + @try { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; +#pragma clang diagnostic pop + } @catch (NSException *exception) { + archiverError = [FIRInstallationsErrorUtil keyedArchiverErrorWithException:exception]; + } + } + + if (!unarchiver) { + NSError *error = archiverError ?: [FIRInstallationsErrorUtil corruptedIIDTokenData]; + [resultPromise reject:error]; + return resultPromise; + } + + [unarchiver setClass:[FIRInstallationsIIDTokenInfo class] forClassName:@"FIRInstanceIDTokenInfo"]; + FIRInstallationsIIDTokenInfo *IIDTokenInfo = + [unarchiver decodeObjectOfClass:[FIRInstallationsIIDTokenInfo class] + forKey:NSKeyedArchiveRootObjectKey]; + + if (IIDTokenInfo.token.length < 1) { + [resultPromise reject:[FIRInstallationsErrorUtil corruptedIIDTokenData]]; + return resultPromise; + } + + [resultPromise fulfill:IIDTokenInfo.token]; + + return resultPromise; +} + +- (FBLPromise *)IIDDefaultTokenData { + FBLPromise *resultPromise = [FBLPromise pendingPromise]; + + NSMutableDictionary *keychainQuery = [self IIDDefaultTokenDataKeychainQuery]; + NSError *error; + NSData *data = [FIRInstallationsKeychainUtils getItemWithQuery:keychainQuery error:&error]; + + if (data) { + [resultPromise fulfill:data]; + return resultPromise; + } else { + NSError *outError = error ?: [FIRInstallationsErrorUtil corruptedIIDTokenData]; + [resultPromise reject:outError]; + return resultPromise; + } +} + +- (NSMutableDictionary *)IIDDefaultTokenDataKeychainQuery { + NSDictionary *query = @{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword}; + + NSMutableDictionary *finalQuery = [NSMutableDictionary dictionaryWithDictionary:query]; + finalQuery[(__bridge NSString *)kSecAttrGeneric] = kFIRInstallationsIIDTokenKeychainId; + + NSString *account = [self IIDAppIdentifier]; + if ([account length]) { + finalQuery[(__bridge NSString *)kSecAttrAccount] = account; + } + + finalQuery[(__bridge NSString *)kSecAttrService] = + [self serviceKeyForAuthorizedEntity:self.GCMSenderID scope:@"*"]; + return finalQuery; +} + +- (NSString *)IIDAppIdentifier { + return [[NSBundle mainBundle] bundleIdentifier] ?: @""; +} + +- (NSString *)serviceKeyForAuthorizedEntity:(NSString *)authorizedEntity scope:(NSString *)scope { + return [NSString stringWithFormat:@"%@:%@", authorizedEntity, scope]; +} + +@end diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsAPIService.h b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsAPIService.h new file mode 100644 index 0000000..b45475d --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsAPIService.h @@ -0,0 +1,62 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FBLPromise; +@class FIRInstallationsItem; + +NS_ASSUME_NONNULL_BEGIN + +FOUNDATION_EXPORT NSString *const kFIRInstallationsUserAgentKey; + +FOUNDATION_EXPORT NSString *const kFIRInstallationsHeartbeatKey; + +/** + * The class is responsible for interacting with HTTP REST API for Installations. + */ +@interface FIRInstallationsAPIService : NSObject + +/** + * The default initializer. + * @param APIKey The Firebase project API key (see `FIROptions.APIKey`). + * @param projectID The Firebase project ID (see `FIROptions.projectID`). + */ +- (instancetype)initWithAPIKey:(NSString *)APIKey projectID:(NSString *)projectID; + +/** + * Sends a request to register a new FID to get auth and refresh tokens. + * @param installation The `FIRInstallationsItem` instance with the FID to register. + * @return A promise that is resolved with a new `FIRInstallationsItem` instance with valid tokens. + * It is rejected with an error in case of a failure. + */ +- (FBLPromise *)registerInstallation:(FIRInstallationsItem *)installation; + +- (FBLPromise *)refreshAuthTokenForInstallation: + (FIRInstallationsItem *)installation; + +/** + * Sends a request to delete the installation, related auth tokens and all related data from the + * server. + * @param installation The installation to delete. + * @return Returns a promise that is resolved with the passed installation on successful deletion or + * is rejected with an error otherwise. + */ +- (FBLPromise *)deleteInstallation:(FIRInstallationsItem *)installation; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsAPIService.m b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsAPIService.m new file mode 100644 index 0000000..5bd7e3b --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsAPIService.m @@ -0,0 +1,346 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstallationsAPIService.h" + +#import + +#if __has_include() +#import +#else +#import "FBLPromises.h" +#endif + +#import +#import +#import "FIRInstallationsErrorUtil.h" +#import "FIRInstallationsItem+RegisterInstallationAPI.h" +#import "FIRInstallationsLogger.h" + +NSString *const kFIRInstallationsAPIBaseURL = @"https://firebaseinstallations.googleapis.com"; +NSString *const kFIRInstallationsAPIKey = @"X-Goog-Api-Key"; +NSString *const kFIRInstallationsBundleId = @"X-Ios-Bundle-Identifier"; +NSString *const kFIRInstallationsIIDMigrationAuthHeader = @"x-goog-fis-ios-iid-migration-auth"; +NSString *const kFIRInstallationsHeartbeatKey = @"X-firebase-client-log-type"; +NSString *const kFIRInstallationsHeartbeatTag = @"fire-installations"; +NSString *const kFIRInstallationsUserAgentKey = @"X-firebase-client"; + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRInstallationsURLSessionResponse : NSObject +@property(nonatomic) NSHTTPURLResponse *HTTPResponse; +@property(nonatomic) NSData *data; + +- (instancetype)initWithResponse:(NSHTTPURLResponse *)response data:(nullable NSData *)data; +@end + +@implementation FIRInstallationsURLSessionResponse + +- (instancetype)initWithResponse:(NSHTTPURLResponse *)response data:(nullable NSData *)data { + self = [super init]; + if (self) { + _HTTPResponse = response; + _data = data ?: [NSData data]; + } + return self; +} + +@end + +@interface FIRInstallationsAPIService () +@property(nonatomic, readonly) NSURLSession *URLSession; +@property(nonatomic, readonly) NSString *APIKey; +@property(nonatomic, readonly) NSString *projectID; +@end + +NS_ASSUME_NONNULL_END + +@implementation FIRInstallationsAPIService + +- (instancetype)initWithAPIKey:(NSString *)APIKey projectID:(NSString *)projectID { + NSURLSession *URLSession = [NSURLSession + sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; + return [self initWithURLSession:URLSession APIKey:APIKey projectID:projectID]; +} + +/// The initializer for tests. +- (instancetype)initWithURLSession:(NSURLSession *)URLSession + APIKey:(NSString *)APIKey + projectID:(NSString *)projectID { + self = [super init]; + if (self) { + _URLSession = URLSession; + _APIKey = [APIKey copy]; + _projectID = [projectID copy]; + } + return self; +} + +#pragma mark - Public + +- (FBLPromise *)registerInstallation:(FIRInstallationsItem *)installation { + NSURLRequest *request = [self registerRequestWithInstallation:installation]; + return [self sendURLRequest:request].then( + ^id _Nullable(FIRInstallationsURLSessionResponse *response) { + return [self registeredInstallationWithInstallation:installation serverResponse:response]; + }); +} + +- (FBLPromise *)refreshAuthTokenForInstallation: + (FIRInstallationsItem *)installation { + NSURLRequest *request = [self authTokenRequestWithInstallation:installation]; + return [self sendURLRequest:request] + .then(^FBLPromise *( + FIRInstallationsURLSessionResponse *response) { + return [self authTokenWithServerResponse:response]; + }) + .then(^FIRInstallationsItem *(FIRInstallationsStoredAuthToken *authToken) { + FIRInstallationsItem *updatedInstallation = [installation copy]; + updatedInstallation.authToken = authToken; + return updatedInstallation; + }); +} + +- (FBLPromise *)deleteInstallation:(FIRInstallationsItem *)installation { + NSURLRequest *request = [self deleteInstallationRequestWithInstallation:installation]; + return [[self sendURLRequest:request] + then:^id _Nullable(FIRInstallationsURLSessionResponse *_Nullable value) { + // Return the original installation on success. + return installation; + }]; +} + +#pragma mark - Register Installation + +- (NSURLRequest *)registerRequestWithInstallation:(FIRInstallationsItem *)installation { + NSString *URLString = [NSString stringWithFormat:@"%@/v1/projects/%@/installations/", + kFIRInstallationsAPIBaseURL, self.projectID]; + NSURL *URL = [NSURL URLWithString:URLString]; + + NSDictionary *bodyDict = @{ + @"fid" : installation.firebaseInstallationID, + @"authVersion" : @"FIS_v2", + @"appId" : installation.appID, + @"sdkVersion" : [self SDKVersion] + }; + + NSDictionary *headers; + if (installation.IIDDefaultToken) { + headers = @{kFIRInstallationsIIDMigrationAuthHeader : installation.IIDDefaultToken}; + } + + return [self requestWithURL:URL + HTTPMethod:@"POST" + bodyDict:bodyDict + refreshToken:nil + additionalHeaders:headers]; +} + +- (FBLPromise *) + registeredInstallationWithInstallation:(FIRInstallationsItem *)installation + serverResponse:(FIRInstallationsURLSessionResponse *)response { + return [FBLPromise do:^id { + FIRLogDebug(kFIRLoggerInstallations, kFIRInstallationsMessageCodeParsingAPIResponse, + @"Parsing server response for %@.", response.HTTPResponse.URL); + NSError *error; + FIRInstallationsItem *registeredInstallation = + [installation registeredInstallationWithJSONData:response.data + date:[NSDate date] + error:&error]; + if (registeredInstallation == nil) { + FIRLogDebug(kFIRLoggerInstallations, + kFIRInstallationsMessageCodeAPIResponseParsingInstallationFailed, + @"Failed to parse FIRInstallationsItem: %@.", error); + return error; + } + + FIRLogDebug(kFIRLoggerInstallations, + kFIRInstallationsMessageCodeAPIResponseParsingInstallationSucceed, + @"FIRInstallationsItem parsed successfully."); + return registeredInstallation; + }]; +} + +#pragma mark - Auth token + +- (NSURLRequest *)authTokenRequestWithInstallation:(FIRInstallationsItem *)installation { + NSString *URLString = + [NSString stringWithFormat:@"%@/v1/projects/%@/installations/%@/authTokens:generate", + kFIRInstallationsAPIBaseURL, self.projectID, + installation.firebaseInstallationID]; + NSURL *URL = [NSURL URLWithString:URLString]; + + NSDictionary *bodyDict = @{@"installation" : @{@"sdkVersion" : [self SDKVersion]}}; + return [self requestWithURL:URL + HTTPMethod:@"POST" + bodyDict:bodyDict + refreshToken:installation.refreshToken]; +} + +- (FBLPromise *)authTokenWithServerResponse: + (FIRInstallationsURLSessionResponse *)response { + return [FBLPromise do:^id { + FIRLogDebug(kFIRLoggerInstallations, kFIRInstallationsMessageCodeParsingAPIResponse, + @"Parsing server response for %@.", response.HTTPResponse.URL); + NSError *error; + FIRInstallationsStoredAuthToken *token = + [FIRInstallationsItem authTokenWithGenerateTokenAPIJSONData:response.data + date:[NSDate date] + error:&error]; + if (token == nil) { + FIRLogDebug(kFIRLoggerInstallations, + kFIRInstallationsMessageCodeAPIResponseParsingAuthTokenFailed, + @"Failed to parse FIRInstallationsStoredAuthToken: %@.", error); + return error; + } + + FIRLogDebug(kFIRLoggerInstallations, + kFIRInstallationsMessageCodeAPIResponseParsingAuthTokenSucceed, + @"FIRInstallationsStoredAuthToken parsed successfully."); + return token; + }]; +} + +#pragma mark - Delete Installation + +- (NSURLRequest *)deleteInstallationRequestWithInstallation:(FIRInstallationsItem *)installation { + NSString *URLString = [NSString stringWithFormat:@"%@/v1/projects/%@/installations/%@/", + kFIRInstallationsAPIBaseURL, self.projectID, + installation.firebaseInstallationID]; + NSURL *URL = [NSURL URLWithString:URLString]; + + return [self requestWithURL:URL + HTTPMethod:@"DELETE" + bodyDict:@{} + refreshToken:installation.refreshToken]; +} + +#pragma mark - URL Request +- (NSURLRequest *)requestWithURL:(NSURL *)requestURL + HTTPMethod:(NSString *)HTTPMethod + bodyDict:(NSDictionary *)bodyDict + refreshToken:(nullable NSString *)refreshToken { + return [self requestWithURL:requestURL + HTTPMethod:HTTPMethod + bodyDict:bodyDict + refreshToken:refreshToken + additionalHeaders:nil]; +} + +- (NSURLRequest *)requestWithURL:(NSURL *)requestURL + HTTPMethod:(NSString *)HTTPMethod + bodyDict:(NSDictionary *)bodyDict + refreshToken:(nullable NSString *)refreshToken + additionalHeaders: + (nullable NSDictionary *)additionalHeaders { + __block NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:requestURL]; + request.HTTPMethod = HTTPMethod; + NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier]; + [request addValue:self.APIKey forHTTPHeaderField:kFIRInstallationsAPIKey]; + [request addValue:bundleIdentifier forHTTPHeaderField:kFIRInstallationsBundleId]; + [self setJSONHTTPBody:bodyDict forRequest:request]; + if (refreshToken) { + NSString *authHeader = [NSString stringWithFormat:@"FIS_v2 %@", refreshToken]; + [request setValue:authHeader forHTTPHeaderField:@"Authorization"]; + } + // User agent Header. + [request setValue:[FIRApp firebaseUserAgent] forHTTPHeaderField:kFIRInstallationsUserAgentKey]; + // Heartbeat Header. + [request setValue:@([FIRHeartbeatInfo heartbeatCodeForTag:kFIRInstallationsHeartbeatTag]) + .stringValue + forHTTPHeaderField:kFIRInstallationsHeartbeatKey]; + [additionalHeaders enumerateKeysAndObjectsUsingBlock:^( + NSString *_Nonnull key, NSString *_Nonnull obj, BOOL *_Nonnull stop) { + [request setValue:obj forHTTPHeaderField:key]; + }]; + + return [request copy]; +} + +- (FBLPromise *)URLRequestPromise:(NSURLRequest *)request { + return [[FBLPromise async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { + FIRLogDebug(kFIRLoggerInstallations, kFIRInstallationsMessageCodeSendAPIRequest, + @"Sending request: %@, body:%@, headers: %@.", request, + [[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding], + request.allHTTPHeaderFields); + [[self.URLSession + dataTaskWithRequest:request + completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + FIRLogDebug(kFIRLoggerInstallations, + kFIRInstallationsMessageCodeAPIRequestNetworkError, + @"Request failed: %@, error: %@.", request, error); + reject(error); + } else { + FIRLogDebug(kFIRLoggerInstallations, kFIRInstallationsMessageCodeAPIRequestResponse, + @"Request response received: %@, error: %@, body: %@.", request, error, + [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); + fulfill([[FIRInstallationsURLSessionResponse alloc] + initWithResponse:(NSHTTPURLResponse *)response + data:data]); + } + }] resume]; + }] then:^id _Nullable(FIRInstallationsURLSessionResponse *response) { + return [self validateHTTPResponseStatusCode:response]; + }]; +} + +- (FBLPromise *)validateHTTPResponseStatusCode: + (FIRInstallationsURLSessionResponse *)response { + NSInteger statusCode = response.HTTPResponse.statusCode; + return [FBLPromise do:^id _Nullable { + if (statusCode < 200 || statusCode >= 300) { + FIRLogDebug(kFIRLoggerInstallations, kFIRInstallationsMessageCodeUnexpectedAPIRequestResponse, + @"Unexpected API response: %@, body: %@.", response.HTTPResponse, + [[NSString alloc] initWithData:response.data encoding:NSUTF8StringEncoding]); + return [FIRInstallationsErrorUtil APIErrorWithHTTPResponse:response.HTTPResponse + data:response.data]; + } + return response; + }]; +} + +- (FBLPromise *)sendURLRequest:(NSURLRequest *)request { + return [FBLPromise attempts:1 + delay:1 + condition:^BOOL(NSInteger remainingAttempts, NSError *_Nonnull error) { + return [FIRInstallationsErrorUtil isAPIError:error withHTTPCode:500]; + } + retry:^id _Nullable { + return [self URLRequestPromise:request]; + }]; +} + +- (NSString *)SDKVersion { + return [NSString stringWithFormat:@"i:%s", FIRInstallationsVersionStr]; +} + +#pragma mark - JSON + +- (void)setJSONHTTPBody:(NSDictionary *)body + forRequest:(NSMutableURLRequest *)request { + [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; + + NSError *error; + NSData *JSONData = [NSJSONSerialization dataWithJSONObject:body options:0 error:&error]; + if (JSONData == nil) { + // TODO: Log or return an error. + } + request.HTTPBody = JSONData; +} + +@end diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsItem+RegisterInstallationAPI.h b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsItem+RegisterInstallationAPI.h new file mode 100644 index 0000000..cc6b543 --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsItem+RegisterInstallationAPI.h @@ -0,0 +1,53 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstallationsItem.h" + +@class FIRInstallationsStoredAuthToken; + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRInstallationsItem (RegisterInstallationAPI) + +/** + * Parses and validates the Register Installation API response and returns a corresponding + * `FIRInstallationsItem` instance on success. + * @param JSONData The data with JSON encoded API response. + * @param date The Auth Token expiration date will be calculated as `date` + + * `response.authToken.expiresIn`. For most of the cases `[NSDate date]` should be passed there. A + * different value may be passed e.g. for unit tests. + * @param outError A pointer to assign a specific `NSError` instance in case of failure. No error is + * assigned in case of success. + * @return Returns a new `FIRInstallationsItem` instance in the success case or `nil` otherwise. + */ +- (nullable FIRInstallationsItem *)registeredInstallationWithJSONData:(NSData *)JSONData + date:(NSDate *)date + error: + (NSError *_Nullable *)outError; + ++ (nullable FIRInstallationsStoredAuthToken *)authTokenWithGenerateTokenAPIJSONData:(NSData *)data + date:(NSDate *)date + error:(NSError **) + outError; + ++ (nullable FIRInstallationsStoredAuthToken *)authTokenWithJSONDict: + (NSDictionary *)dict + date:(NSDate *)date + error:(NSError **)outError; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsItem+RegisterInstallationAPI.m b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsItem+RegisterInstallationAPI.m new file mode 100644 index 0000000..569e35b --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsItem+RegisterInstallationAPI.m @@ -0,0 +1,142 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstallationsItem+RegisterInstallationAPI.h" + +#import "FIRInstallationsErrorUtil.h" +#import "FIRInstallationsStoredAuthToken.h" + +@implementation FIRInstallationsItem (RegisterInstallationAPI) + +- (nullable FIRInstallationsItem *) + registeredInstallationWithJSONData:(NSData *)data + date:(NSDate *)date + error:(NSError *__autoreleasing _Nullable *_Nullable)outError { + NSDictionary *responseJSON = [FIRInstallationsItem dictionaryFromJSONData:data error:outError]; + if (!responseJSON) { + return nil; + } + + NSString *refreshToken = [FIRInstallationsItem validStringOrNilForKey:@"refreshToken" + fromDict:responseJSON]; + if (refreshToken == nil) { + FIRInstallationsItemSetErrorToPointer( + [FIRInstallationsErrorUtil FIDRegistrationErrorWithResponseMissingField:@"refreshToken"], + outError); + return nil; + } + + NSDictionary *authTokenDict = responseJSON[@"authToken"]; + if (![authTokenDict isKindOfClass:[NSDictionary class]]) { + FIRInstallationsItemSetErrorToPointer( + [FIRInstallationsErrorUtil FIDRegistrationErrorWithResponseMissingField:@"authToken"], + outError); + return nil; + } + + FIRInstallationsStoredAuthToken *authToken = + [FIRInstallationsItem authTokenWithJSONDict:authTokenDict date:date error:outError]; + if (authToken == nil) { + return nil; + } + + FIRInstallationsItem *installation = + [[FIRInstallationsItem alloc] initWithAppID:self.appID firebaseAppName:self.firebaseAppName]; + NSString *installationID = [FIRInstallationsItem validStringOrNilForKey:@"fid" + fromDict:responseJSON]; + installation.firebaseInstallationID = installationID ?: self.firebaseInstallationID; + installation.refreshToken = refreshToken; + installation.authToken = authToken; + installation.registrationStatus = FIRInstallationStatusRegistered; + + return installation; +} + +#pragma mark - Auth token + ++ (nullable FIRInstallationsStoredAuthToken *)authTokenWithGenerateTokenAPIJSONData:(NSData *)data + date:(NSDate *)date + error:(NSError **) + outError { + NSDictionary *dict = [self dictionaryFromJSONData:data error:outError]; + if (!dict) { + return nil; + } + + return [self authTokenWithJSONDict:dict date:date error:outError]; +} + ++ (nullable FIRInstallationsStoredAuthToken *)authTokenWithJSONDict: + (NSDictionary *)dict + date:(NSDate *)date + error:(NSError **)outError { + NSString *token = [self validStringOrNilForKey:@"token" fromDict:dict]; + if (token == nil) { + FIRInstallationsItemSetErrorToPointer( + [FIRInstallationsErrorUtil FIDRegistrationErrorWithResponseMissingField:@"authToken.token"], + outError); + return nil; + } + + NSString *expiresInString = [self validStringOrNilForKey:@"expiresIn" fromDict:dict]; + if (expiresInString == nil) { + FIRInstallationsItemSetErrorToPointer( + [FIRInstallationsErrorUtil + FIDRegistrationErrorWithResponseMissingField:@"authToken.expiresIn"], + outError); + return nil; + } + + // The response should contain the string in format like "604800s". + // The server should never response with anything else except seconds. + // Just drop the last character and parse a number from string. + NSString *expiresInSeconds = [expiresInString substringToIndex:expiresInString.length - 1]; + NSTimeInterval expiresIn = [expiresInSeconds doubleValue]; + NSDate *expirationDate = [date dateByAddingTimeInterval:expiresIn]; + + FIRInstallationsStoredAuthToken *authToken = [[FIRInstallationsStoredAuthToken alloc] init]; + authToken.status = FIRInstallationsAuthTokenStatusTokenReceived; + authToken.token = token; + authToken.expirationDate = expirationDate; + + return authToken; +} + +#pragma mark - JSON + ++ (nullable NSDictionary *)dictionaryFromJSONData:(NSData *)data + error:(NSError **)outError { + NSError *error; + NSDictionary *responseJSON = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; + + if (![responseJSON isKindOfClass:[NSDictionary class]]) { + FIRInstallationsItemSetErrorToPointer([FIRInstallationsErrorUtil JSONSerializationError:error], + outError); + return nil; + } + + return responseJSON; +} + ++ (NSString *)validStringOrNilForKey:(NSString *)key fromDict:(NSDictionary *)dict { + NSString *string = dict[key]; + if ([string isKindOfClass:[NSString class]] && string.length > 0) { + return string; + } + return nil; +} + +@end diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsIDController.h b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsIDController.h new file mode 100644 index 0000000..ab2092d --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsIDController.h @@ -0,0 +1,44 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class FBLPromise; +@class FIRInstallationsItem; + +/** + * The class is responsible for managing FID for a given `FIRApp`. + */ +@interface FIRInstallationsIDController : NSObject + +- (instancetype)initWithGoogleAppID:(NSString *)appID + appName:(NSString *)appName + APIKey:(NSString *)APIKey + projectID:(NSString *)projectID + GCMSenderID:(NSString *)GCMSenderID + accessGroup:(nullable NSString *)accessGroup; + +- (FBLPromise *)getInstallationItem; + +- (FBLPromise *)getAuthTokenForcingRefresh:(BOOL)forceRefresh; + +- (FBLPromise *)deleteInstallation; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsIDController.m b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsIDController.m new file mode 100644 index 0000000..1982a57 --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsIDController.m @@ -0,0 +1,458 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstallationsIDController.h" + +#if __has_include() +#import +#else +#import "FBLPromises.h" +#endif + +#import + +#import "FIRInstallationsAPIService.h" +#import "FIRInstallationsErrorUtil.h" +#import "FIRInstallationsIIDStore.h" +#import "FIRInstallationsIIDTokenStore.h" +#import "FIRInstallationsItem.h" +#import "FIRInstallationsLogger.h" +#import "FIRInstallationsSingleOperationPromiseCache.h" +#import "FIRInstallationsStore.h" +#import "FIRSecureStorage.h" + +#import "FIRInstallationsHTTPError.h" +#import "FIRInstallationsStoredAuthToken.h" + +const NSNotificationName FIRInstallationIDDidChangeNotification = + @"FIRInstallationIDDidChangeNotification"; +NSString *const kFIRInstallationIDDidChangeNotificationAppNameKey = + @"FIRInstallationIDDidChangeNotification"; + +NSTimeInterval const kFIRInstallationsTokenExpirationThreshold = 60 * 60; // 1 hour. + +@interface FIRInstallationsIDController () +@property(nonatomic, readonly) NSString *appID; +@property(nonatomic, readonly) NSString *appName; + +@property(nonatomic, readonly) FIRInstallationsStore *installationsStore; +@property(nonatomic, readonly) FIRInstallationsIIDStore *IIDStore; +@property(nonatomic, readonly) FIRInstallationsIIDTokenStore *IIDTokenStore; + +@property(nonatomic, readonly) FIRInstallationsAPIService *APIService; + +@property(nonatomic, readonly) FIRInstallationsSingleOperationPromiseCache + *getInstallationPromiseCache; +@property(nonatomic, readonly) + FIRInstallationsSingleOperationPromiseCache *authTokenPromiseCache; +@property(nonatomic, readonly) FIRInstallationsSingleOperationPromiseCache + *authTokenForcingRefreshPromiseCache; +@property(nonatomic, readonly) + FIRInstallationsSingleOperationPromiseCache *deleteInstallationPromiseCache; +@end + +@implementation FIRInstallationsIDController + +- (instancetype)initWithGoogleAppID:(NSString *)appID + appName:(NSString *)appName + APIKey:(NSString *)APIKey + projectID:(NSString *)projectID + GCMSenderID:(NSString *)GCMSenderID + accessGroup:(NSString *)accessGroup { + FIRSecureStorage *secureStorage = [[FIRSecureStorage alloc] init]; + FIRInstallationsStore *installationsStore = + [[FIRInstallationsStore alloc] initWithSecureStorage:secureStorage accessGroup:accessGroup]; + + // Use `GCMSenderID` as project identifier when `projectID` is not available. + NSString *APIServiceProjectID = (projectID.length > 0) ? projectID : GCMSenderID; + FIRInstallationsAPIService *apiService = + [[FIRInstallationsAPIService alloc] initWithAPIKey:APIKey projectID:APIServiceProjectID]; + + FIRInstallationsIIDStore *IIDStore = [[FIRInstallationsIIDStore alloc] init]; + FIRInstallationsIIDTokenStore *IIDCheckingStore = + [[FIRInstallationsIIDTokenStore alloc] initWithGCMSenderID:GCMSenderID]; + + return [self initWithGoogleAppID:appID + appName:appName + installationsStore:installationsStore + APIService:apiService + IIDStore:IIDStore + IIDTokenStore:IIDCheckingStore]; +} + +/// The initializer is supposed to be used by tests to inject `installationsStore`. +- (instancetype)initWithGoogleAppID:(NSString *)appID + appName:(NSString *)appName + installationsStore:(FIRInstallationsStore *)installationsStore + APIService:(FIRInstallationsAPIService *)APIService + IIDStore:(FIRInstallationsIIDStore *)IIDStore + IIDTokenStore:(FIRInstallationsIIDTokenStore *)IIDTokenStore { + self = [super init]; + if (self) { + _appID = appID; + _appName = appName; + _installationsStore = installationsStore; + _APIService = APIService; + _IIDStore = IIDStore; + _IIDTokenStore = IIDTokenStore; + + __weak FIRInstallationsIDController *weakSelf = self; + + _getInstallationPromiseCache = [[FIRInstallationsSingleOperationPromiseCache alloc] + initWithNewOperationHandler:^FBLPromise *_Nonnull { + FIRInstallationsIDController *strongSelf = weakSelf; + return [strongSelf createGetInstallationItemPromise]; + }]; + + _authTokenPromiseCache = [[FIRInstallationsSingleOperationPromiseCache alloc] + initWithNewOperationHandler:^FBLPromise *_Nonnull { + FIRInstallationsIDController *strongSelf = weakSelf; + return [strongSelf installationWithValidAuthTokenForcingRefresh:NO]; + }]; + + _authTokenForcingRefreshPromiseCache = [[FIRInstallationsSingleOperationPromiseCache alloc] + initWithNewOperationHandler:^FBLPromise *_Nonnull { + FIRInstallationsIDController *strongSelf = weakSelf; + return [strongSelf installationWithValidAuthTokenForcingRefresh:YES]; + }]; + + _deleteInstallationPromiseCache = [[FIRInstallationsSingleOperationPromiseCache alloc] + initWithNewOperationHandler:^FBLPromise *_Nonnull { + FIRInstallationsIDController *strongSelf = weakSelf; + return [strongSelf createDeleteInstallationPromise]; + }]; + } + return self; +} + +#pragma mark - Get Installation. + +- (FBLPromise *)getInstallationItem { + return [self.getInstallationPromiseCache getExistingPendingOrCreateNewPromise]; +} + +- (FBLPromise *)createGetInstallationItemPromise { + FIRLogDebug(kFIRLoggerInstallations, + kFIRInstallationsMessageCodeNewGetInstallationOperationCreated, @"%s, appName: %@", + __PRETTY_FUNCTION__, self.appName); + + FBLPromise *installationItemPromise = + [self getStoredInstallation].recover(^id(NSError *error) { + return [self createAndSaveFID]; + }); + + // Initiate registration process on success if needed, but return the installation without waiting + // for it. + installationItemPromise.then(^id(FIRInstallationsItem *installation) { + [self getAuthTokenForcingRefresh:NO]; + return nil; + }); + + return installationItemPromise; +} + +- (FBLPromise *)getStoredInstallation { + return [self.installationsStore installationForAppID:self.appID appName:self.appName].validate( + ^BOOL(FIRInstallationsItem *installation) { + BOOL isValid = NO; + switch (installation.registrationStatus) { + case FIRInstallationStatusUnregistered: + case FIRInstallationStatusRegistered: + isValid = YES; + break; + + case FIRInstallationStatusUnknown: + isValid = NO; + break; + } + + return isValid; + }); +} + +- (FBLPromise *)createAndSaveFID { + return [self migrateOrGenerateInstallation] + .then(^FBLPromise *(FIRInstallationsItem *installation) { + return [self saveInstallation:installation]; + }) + .then(^FIRInstallationsItem *(FIRInstallationsItem *installation) { + [self postFIDDidChangeNotification]; + return installation; + }); +} + +- (FBLPromise *)saveInstallation:(FIRInstallationsItem *)installation { + return [self.installationsStore saveInstallation:installation].then( + ^FIRInstallationsItem *(NSNull *result) { + return installation; + }); +} + +/** + * Tries to migrate IID data stored by FirebaseInstanceID SDK or generates a new Installation ID if + * not found. + */ +- (FBLPromise *)migrateOrGenerateInstallation { + if (![self isDefaultApp]) { + // Existing IID should be used only for default FirebaseApp. + FIRInstallationsItem *installation = + [self createInstallationWithFID:[FIRInstallationsItem generateFID] IIDDefaultToken:nil]; + return [FBLPromise resolvedWith:installation]; + } + + return [[[FBLPromise + all:@[ [self.IIDStore existingIID], [self.IIDTokenStore existingIIDDefaultToken] ]] + then:^id _Nullable(NSArray *_Nullable results) { + NSString *existingIID = results[0]; + NSString *IIDDefaultToken = results[1]; + + return [self createInstallationWithFID:existingIID IIDDefaultToken:IIDDefaultToken]; + }] recover:^id _Nullable(NSError *_Nonnull error) { + return [self createInstallationWithFID:[FIRInstallationsItem generateFID] IIDDefaultToken:nil]; + }]; +} + +- (FIRInstallationsItem *)createInstallationWithFID:(NSString *)FID + IIDDefaultToken:(nullable NSString *)IIDDefaultToken { + FIRInstallationsItem *installation = [[FIRInstallationsItem alloc] initWithAppID:self.appID + firebaseAppName:self.appName]; + installation.firebaseInstallationID = FID; + installation.IIDDefaultToken = IIDDefaultToken; + installation.registrationStatus = FIRInstallationStatusUnregistered; + return installation; +} + +#pragma mark - FID registration + +- (FBLPromise *)registerInstallationIfNeeded: + (FIRInstallationsItem *)installation { + switch (installation.registrationStatus) { + case FIRInstallationStatusRegistered: + // Already registered. Do nothing. + return [FBLPromise resolvedWith:installation]; + + case FIRInstallationStatusUnknown: + case FIRInstallationStatusUnregistered: + // Registration required. Proceed. + break; + } + + return [self.APIService registerInstallation:installation] + .catch(^(NSError *_Nonnull error) { + if ([self doesRegistrationErrorRequireConfigChange:error]) { + FIRLogError(kFIRLoggerInstallations, + kFIRInstallationsMessageCodeInvalidFirebaseConfiguration, + @"Firebase Installation registration failed for app with name: %@, error: " + @"%@\nPlease make sure you use valid GoogleService-Info.plist", + self.appName, error); + } + }) + .then(^id(FIRInstallationsItem *registeredInstallation) { + return [self saveInstallation:registeredInstallation]; + }) + .then(^FIRInstallationsItem *(FIRInstallationsItem *registeredInstallation) { + // Server may respond with a different FID if the sent one cannot be accepted. + if (![registeredInstallation.firebaseInstallationID + isEqualToString:installation.firebaseInstallationID]) { + [self postFIDDidChangeNotification]; + } + return registeredInstallation; + }); +} + +- (BOOL)doesRegistrationErrorRequireConfigChange:(NSError *)error { + FIRInstallationsHTTPError *HTTPError = (FIRInstallationsHTTPError *)error; + if (![HTTPError isKindOfClass:[FIRInstallationsHTTPError class]]) { + return NO; + } + + switch (HTTPError.HTTPResponse.statusCode) { + // These are the errors that require Firebase configuration change. + case FIRInstallationsRegistrationHTTPCodeInvalidArgument: + case FIRInstallationsRegistrationHTTPCodeInvalidAPIKey: + case FIRInstallationsRegistrationHTTPCodeAPIKeyToProjectIDMismatch: + case FIRInstallationsRegistrationHTTPCodeProjectNotFound: + return YES; + + default: + return NO; + } +} + +#pragma mark - Auth Token + +- (FBLPromise *)getAuthTokenForcingRefresh:(BOOL)forceRefresh { + if (forceRefresh || [self.authTokenForcingRefreshPromiseCache getExistingPendingPromise] != nil) { + return [self.authTokenForcingRefreshPromiseCache getExistingPendingOrCreateNewPromise]; + } else { + return [self.authTokenPromiseCache getExistingPendingOrCreateNewPromise]; + } +} + +- (FBLPromise *)installationWithValidAuthTokenForcingRefresh: + (BOOL)forceRefresh { + FIRLogDebug(kFIRLoggerInstallations, kFIRInstallationsMessageCodeNewGetAuthTokenOperationCreated, + @"-[FIRInstallationsIDController installationWithValidAuthTokenForcingRefresh:%@], " + @"appName: %@", + @(forceRefresh), self.appName); + + return [self getInstallationItem] + .then(^FBLPromise *(FIRInstallationsItem *installation) { + return [self registerInstallationIfNeeded:installation]; + }) + .then(^id(FIRInstallationsItem *registeredInstallation) { + BOOL isTokenExpiredOrExpiresSoon = + [registeredInstallation.authToken.expirationDate timeIntervalSinceDate:[NSDate date]] < + kFIRInstallationsTokenExpirationThreshold; + if (forceRefresh || isTokenExpiredOrExpiresSoon) { + return [self refreshAuthTokenForInstallation:registeredInstallation]; + } else { + return registeredInstallation; + } + }) + .recover(^id(NSError *error) { + return [self regenerateFIDOnRefreshTokenErrorIfNeeded:error]; + }); +} + +- (FBLPromise *)refreshAuthTokenForInstallation: + (FIRInstallationsItem *)installation { + return [[self.APIService refreshAuthTokenForInstallation:installation] + then:^id _Nullable(FIRInstallationsItem *_Nullable refreshedInstallation) { + return [self saveInstallation:refreshedInstallation]; + }]; +} + +- (id)regenerateFIDOnRefreshTokenErrorIfNeeded:(NSError *)error { + if (![error isKindOfClass:[FIRInstallationsHTTPError class]]) { + // No recovery possible. Return the same error. + return error; + } + + FIRInstallationsHTTPError *HTTPError = (FIRInstallationsHTTPError *)error; + switch (HTTPError.HTTPResponse.statusCode) { + case FIRInstallationsAuthTokenHTTPCodeInvalidAuthentication: + case FIRInstallationsAuthTokenHTTPCodeFIDNotFound: + // The stored installation was damaged or blocked by the server. + // Delete the stored installation then generate and register a new one. + return [self getInstallationItem] + .then(^FBLPromise *(FIRInstallationsItem *installation) { + return [self deleteInstallationLocally:installation]; + }) + .then(^FBLPromise *(id result) { + return [self installationWithValidAuthTokenForcingRefresh:NO]; + }); + + default: + // No recovery possible. Return the same error. + return error; + } +} + +#pragma mark - Delete FID + +- (FBLPromise *)deleteInstallation { + return [self.deleteInstallationPromiseCache getExistingPendingOrCreateNewPromise]; +} + +- (FBLPromise *)createDeleteInstallationPromise { + FIRLogDebug(kFIRLoggerInstallations, + kFIRInstallationsMessageCodeNewDeleteInstallationOperationCreated, @"%s, appName: %@", + __PRETTY_FUNCTION__, self.appName); + + // Check for ongoing requests first, if there is no a request, then check local storage for + // existing installation. + FBLPromise *currentInstallationPromise = + [self mostRecentInstallationOperation] ?: [self getStoredInstallation]; + + return currentInstallationPromise + .then(^id(FIRInstallationsItem *installation) { + return [self sendDeleteInstallationRequestIfNeeded:installation]; + }) + .then(^id(FIRInstallationsItem *installation) { + // Remove the installation from the local storage. + return [self deleteInstallationLocally:installation]; + }); +} + +- (FBLPromise *)deleteInstallationLocally:(FIRInstallationsItem *)installation { + return [self.installationsStore removeInstallationForAppID:installation.appID + appName:installation.firebaseAppName] + .then(^FBLPromise *(NSNull *result) { + return [self deleteExistingIIDIfNeeded]; + }) + .then(^NSNull *(NSNull *result) { + [self postFIDDidChangeNotification]; + return result; + }); +} + +- (FBLPromise *)sendDeleteInstallationRequestIfNeeded: + (FIRInstallationsItem *)installation { + switch (installation.registrationStatus) { + case FIRInstallationStatusUnknown: + case FIRInstallationStatusUnregistered: + // The installation is not registered, so it is safe to be deleted as is, so return early. + return [FBLPromise resolvedWith:installation]; + break; + + case FIRInstallationStatusRegistered: + // Proceed to de-register the installation on the server. + break; + } + + return [self.APIService deleteInstallation:installation].recover(^id(NSError *APIError) { + if ([FIRInstallationsErrorUtil isAPIError:APIError withHTTPCode:404]) { + // The installation was not found on the server. + // Return success. + return installation; + } else { + // Re-throw the error otherwise. + return APIError; + } + }); +} + +- (FBLPromise *)deleteExistingIIDIfNeeded { + if ([self isDefaultApp]) { + return [self.IIDStore deleteExistingIID]; + } else { + return [FBLPromise resolvedWith:[NSNull null]]; + } +} + +- (nullable FBLPromise *)mostRecentInstallationOperation { + return [self.authTokenForcingRefreshPromiseCache getExistingPendingPromise] + ?: [self.authTokenPromiseCache getExistingPendingPromise] + ?: [self.getInstallationPromiseCache getExistingPendingPromise]; +} + +#pragma mark - Notifications + +- (void)postFIDDidChangeNotification { + [[NSNotificationCenter defaultCenter] + postNotificationName:FIRInstallationIDDidChangeNotification + object:nil + userInfo:@{kFIRInstallationIDDidChangeNotificationAppNameKey : self.appName}]; +} + +#pragma mark - Default App + +- (BOOL)isDefaultApp { + return [self.appName isEqualToString:kFIRDefaultAppName]; +} + +@end diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsSingleOperationPromiseCache.h b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsSingleOperationPromiseCache.h new file mode 100644 index 0000000..aeb54e5 --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsSingleOperationPromiseCache.h @@ -0,0 +1,58 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FBLPromise; + +NS_ASSUME_NONNULL_BEGIN + +/** + * The class makes sure the a single operation (represented by a promise) is performed at a time. If + * there is an ongoing operation, then its existing corresponding promise will be returned instead + * of starting a new operation. + */ +@interface FIRInstallationsSingleOperationPromiseCache<__covariant ResultType> : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +/** + * The designated initializer. + * @param newOperationHandler The block that must return a new promise representing the + * single-at-a-time operation. The promise should be fulfilled when the operation is completed. The + * factory block will be used to create a new promise when needed. + */ +- (instancetype)initWithNewOperationHandler: + (FBLPromise *_Nonnull (^)(void))newOperationHandler NS_DESIGNATED_INITIALIZER; + +/** + * Creates a new promise or returns an existing pending one. + * @return Returns and existing pending promise if exists. If the pending promise does not exist + * then a new one will be created using the `factory` block passed in the initializer. Once the + * pending promise gets resolved, it is removed, so calling the method again will lead to creating + * and caching another promise. + */ +- (FBLPromise *)getExistingPendingOrCreateNewPromise; + +/** + * Returns an existing pending promise or `nil`. + * @return Returns an existing pending promise if there is one or `nil` otherwise. + */ +- (nullable FBLPromise *)getExistingPendingPromise; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsSingleOperationPromiseCache.m b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsSingleOperationPromiseCache.m new file mode 100644 index 0000000..dfccfe3 --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsSingleOperationPromiseCache.m @@ -0,0 +1,75 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstallationsSingleOperationPromiseCache.h" + +#if __has_include() +#import +#else +#import "FBLPromises.h" +#endif + +@interface FIRInstallationsSingleOperationPromiseCache () +@property(nonatomic, readonly) FBLPromise *_Nonnull (^newOperationHandler)(void); +@property(nonatomic, nullable) FBLPromise *pendingPromise; +@end + +@implementation FIRInstallationsSingleOperationPromiseCache + +- (instancetype)initWithNewOperationHandler: + (FBLPromise *_Nonnull (^)(void))newOperationHandler { + if (newOperationHandler == nil) { + [NSException raise:NSInvalidArgumentException + format:@"`newOperationHandler` must not be `nil`."]; + } + + self = [super init]; + if (self) { + _newOperationHandler = [newOperationHandler copy]; + } + return self; +} + +- (FBLPromise *)getExistingPendingOrCreateNewPromise { + @synchronized(self) { + if (!self.pendingPromise) { + self.pendingPromise = self.newOperationHandler(); + + self.pendingPromise + .then(^id(id result) { + @synchronized(self) { + self.pendingPromise = nil; + return nil; + } + }) + .catch(^void(NSError *error) { + @synchronized(self) { + self.pendingPromise = nil; + } + }); + } + + return self.pendingPromise; + } +} + +- (nullable FBLPromise *)getExistingPendingPromise { + @synchronized(self) { + return self.pendingPromise; + } +} + +@end diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsStatus.h b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsStatus.h new file mode 100644 index 0000000..3edc692 --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsStatus.h @@ -0,0 +1,35 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +/** + * The enum represent possible states of the installation ID. + * + * WARNING: The enum is stored to Keychain as a part of `FIRInstallationsStoredItem`. Modification + * of it can lead to incompatibility with previous version. Any modification must be evaluated and, + * if it is really needed, the `storageVersion` must be bumped and proper migration code added. + */ +typedef NS_ENUM(NSInteger, FIRInstallationsStatus) { + /** Represents either an initial status when a FIRInstallationsItem instance was created but not + * stored to Keychain or an undefined status (e.g. when the status failed to deserialize). + */ + FIRInstallationStatusUnknown, + /// The Firebase Installation has not yet been registered with FIS. + FIRInstallationStatusUnregistered, + /// The Firebase Installation has successfully been registered with FIS. + FIRInstallationStatusRegistered, +}; diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStore.h b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStore.h new file mode 100644 index 0000000..5334cc9 --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStore.h @@ -0,0 +1,71 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FBLPromise; +@class FIRInstallationsItem; +@class FIRSecureStorage; + +NS_ASSUME_NONNULL_BEGIN + +/// The user defaults suite name used to store data. +extern NSString *const kFIRInstallationsStoreUserDefaultsID; + +/// The class is responsible for storing and accessing the installations data. +@interface FIRInstallationsStore : NSObject + +/** + * The default initializer. + * @param storage The secure storage to save installations data. + * @param accessGroup The Keychain Access Group to store and request the installations data. + */ +- (instancetype)initWithSecureStorage:(FIRSecureStorage *)storage + accessGroup:(nullable NSString *)accessGroup; + +/** + * Retrieves existing installation ID if there is. + * @param appID The Firebase(Google) Application ID. + * @param appName The Firebase Application Name. + * + * @return Returns a `FBLPromise` instance. The promise is resolved with a FIRInstallationsItem + * instance if there is a valid installation stored for `appID` and `appName`. The promise is + * rejected with a specific error when the installation has not been found or with another possible + * error. + */ +- (FBLPromise *)installationForAppID:(NSString *)appID + appName:(NSString *)appName; + +/** + * Saves the given installation. + * + * @param installationItem The installation data. + * @return Returns a promise that is resolved with `[NSNull null]` on success. + */ +- (FBLPromise *)saveInstallation:(FIRInstallationsItem *)installationItem; + +/** + * Removes installation data for the given app parameters. + * @param appID The Firebase(Google) Application ID. + * @param appName The Firebase Application Name. + * + * @return Returns a promise that is resolved with `[NSNull null]` on success. + */ +- (FBLPromise *)removeInstallationForAppID:(NSString *)appID appName:(NSString *)appName; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStore.m b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStore.m new file mode 100644 index 0000000..9fcfd74 --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStore.m @@ -0,0 +1,125 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstallationsStore.h" + +#import + +#if __has_include() +#import +#else +#import "FBLPromises.h" +#endif + +#import "FIRInstallationsErrorUtil.h" +#import "FIRInstallationsItem.h" +#import "FIRInstallationsStoredItem.h" +#import "FIRSecureStorage.h" + +NSString *const kFIRInstallationsStoreUserDefaultsID = @"com.firebase.FIRInstallations"; + +@interface FIRInstallationsStore () +@property(nonatomic, readonly) FIRSecureStorage *secureStorage; +@property(nonatomic, readonly, nullable) NSString *accessGroup; +@property(nonatomic, readonly) dispatch_queue_t queue; +@property(nonatomic, readonly) GULUserDefaults *userDefaults; +@end + +@implementation FIRInstallationsStore + +- (instancetype)initWithSecureStorage:(FIRSecureStorage *)storage + accessGroup:(NSString *)accessGroup { + self = [super init]; + if (self) { + _secureStorage = storage; + _accessGroup = [accessGroup copy]; + _queue = dispatch_queue_create("com.firebase.FIRInstallationsStore", DISPATCH_QUEUE_SERIAL); + + NSString *userDefaultsSuiteName = _accessGroup ?: kFIRInstallationsStoreUserDefaultsID; + _userDefaults = [[GULUserDefaults alloc] initWithSuiteName:userDefaultsSuiteName]; + } + return self; +} + +- (FBLPromise *)installationForAppID:(NSString *)appID + appName:(NSString *)appName { + NSString *itemID = [FIRInstallationsItem identifierWithAppID:appID appName:appName]; + return [self installationExistsForAppID:appID appName:appName] + .then(^id(id result) { + return [self.secureStorage getObjectForKey:itemID + objectClass:[FIRInstallationsStoredItem class] + accessGroup:self.accessGroup]; + }) + .then(^id(FIRInstallationsStoredItem *_Nullable storedItem) { + if (storedItem == nil) { + return [FIRInstallationsErrorUtil installationItemNotFoundForAppID:appID appName:appName]; + } + + FIRInstallationsItem *item = [[FIRInstallationsItem alloc] initWithAppID:appID + firebaseAppName:appName]; + [item updateWithStoredItem:storedItem]; + return item; + }); +} + +- (FBLPromise *)saveInstallation:(FIRInstallationsItem *)installationItem { + FIRInstallationsStoredItem *storedItem = [installationItem storedItem]; + NSString *identifier = [installationItem identifier]; + + return + [self.secureStorage setObject:storedItem forKey:identifier accessGroup:self.accessGroup].then( + ^id(id result) { + return [self setInstallationExists:YES forItemWithIdentifier:identifier]; + }); +} + +- (FBLPromise *)removeInstallationForAppID:(NSString *)appID appName:(NSString *)appName { + NSString *identifier = [FIRInstallationsItem identifierWithAppID:appID appName:appName]; + return [self.secureStorage removeObjectForKey:identifier accessGroup:self.accessGroup].then( + ^id(id result) { + return [self setInstallationExists:NO forItemWithIdentifier:identifier]; + }); +} + +#pragma mark - User defaults + +- (FBLPromise *)installationExistsForAppID:(NSString *)appID appName:(NSString *)appName { + NSString *identifier = [FIRInstallationsItem identifierWithAppID:appID appName:appName]; + return [FBLPromise onQueue:self.queue + do:^id _Nullable { + return [[self userDefaults] objectForKey:identifier] != nil + ? [NSNull null] + : [FIRInstallationsErrorUtil + installationItemNotFoundForAppID:appID + appName:appName]; + }]; +} + +- (FBLPromise *)setInstallationExists:(BOOL)exists + forItemWithIdentifier:(NSString *)identifier { + return [FBLPromise onQueue:self.queue + do:^id _Nullable { + if (exists) { + [[self userDefaults] setBool:YES forKey:identifier]; + } else { + [[self userDefaults] removeObjectForKey:identifier]; + } + + return [NSNull null]; + }]; +} + +@end diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.h b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.h new file mode 100644 index 0000000..f6e4282 --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.h @@ -0,0 +1,58 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * The enum represent possible states of the installation auth token. + * + * WARNING: The enum is stored to Keychain as a part of `FIRInstallationsStoredAuthToken`. + * Modification of it can lead to incompatibility with previous version. Any modification must be + * evaluated and, if it is really needed, the `storageVersion` must be bumped and proper migration + * code added. + */ +typedef NS_ENUM(NSInteger, FIRInstallationsAuthTokenStatus) { + /// An initial status or an undefined value. + FIRInstallationsAuthTokenStatusUnknown, + /// The auth token has been received from the server. + FIRInstallationsAuthTokenStatusTokenReceived +}; + +/** + * This class serializes and deserializes the installation data into/from `NSData` to be stored in + * Keychain. This class is primarily used by `FIRInstallationsStore`. It is also used on the logic + * level as a data object (see `FIRInstallationsItem.authToken`). + * + * WARNING: Modification of the class properties can lead to incompatibility with the stored data + * encoded by the previous class versions. Any modification must be evaluated and, if it is really + * needed, the `storageVersion` must be bumped and proper migration code added. + */ +@interface FIRInstallationsStoredAuthToken : NSObject +@property FIRInstallationsAuthTokenStatus status; + +/// The token that can be used to authorize requests to Firebase backend. +@property(nullable, copy) NSString *token; +/// The date when the auth token expires. +@property(nullable, copy) NSDate *expirationDate; + +/// The version of local storage. +@property(nonatomic, readonly) NSInteger storageVersion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.m b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.m new file mode 100644 index 0000000..b21f6dd --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.m @@ -0,0 +1,77 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstallationsStoredAuthToken.h" + +#import "FIRInstallationsLogger.h" + +NSString *const kFIRInstallationsStoredAuthTokenStatusKey = @"status"; +NSString *const kFIRInstallationsStoredAuthTokenTokenKey = @"token"; +NSString *const kFIRInstallationsStoredAuthTokenExpirationDateKey = @"expirationDate"; +NSString *const kFIRInstallationsStoredAuthTokenStorageVersionKey = @"storageVersion"; + +NSInteger const kFIRInstallationsStoredAuthTokenStorageVersion = 1; + +@implementation FIRInstallationsStoredAuthToken + +- (NSInteger)storageVersion { + return kFIRInstallationsStoredAuthTokenStorageVersion; +} + +- (nonnull id)copyWithZone:(nullable NSZone *)zone { + FIRInstallationsStoredAuthToken *clone = [[FIRInstallationsStoredAuthToken alloc] init]; + clone.status = self.status; + clone.token = [self.token copy]; + clone.expirationDate = self.expirationDate; + return clone; +} + +- (void)encodeWithCoder:(nonnull NSCoder *)aCoder { + [aCoder encodeInteger:self.status forKey:kFIRInstallationsStoredAuthTokenStatusKey]; + [aCoder encodeObject:self.token forKey:kFIRInstallationsStoredAuthTokenTokenKey]; + [aCoder encodeObject:self.expirationDate + forKey:kFIRInstallationsStoredAuthTokenExpirationDateKey]; + [aCoder encodeInteger:self.storageVersion + forKey:kFIRInstallationsStoredAuthTokenStorageVersionKey]; +} + +- (nullable instancetype)initWithCoder:(nonnull NSCoder *)aDecoder { + NSInteger storageVersion = + [aDecoder decodeIntegerForKey:kFIRInstallationsStoredAuthTokenStorageVersionKey]; + if (storageVersion > kFIRInstallationsStoredAuthTokenStorageVersion) { + FIRLogWarning(kFIRLoggerInstallations, + kFIRInstallationsMessageCodeAuthTokenCoderVersionMismatch, + @"FIRInstallationsStoredAuthToken was encoded by a newer coder version %ld. " + @"Current coder version is %ld. Some auth token data may be lost.", + (long)storageVersion, (long)kFIRInstallationsStoredAuthTokenStorageVersion); + } + + FIRInstallationsStoredAuthToken *object = [[FIRInstallationsStoredAuthToken alloc] init]; + object.status = [aDecoder decodeIntegerForKey:kFIRInstallationsStoredAuthTokenStatusKey]; + object.token = [aDecoder decodeObjectOfClass:[NSString class] + forKey:kFIRInstallationsStoredAuthTokenTokenKey]; + object.expirationDate = + [aDecoder decodeObjectOfClass:[NSDate class] + forKey:kFIRInstallationsStoredAuthTokenExpirationDateKey]; + + return object; +} + ++ (BOOL)supportsSecureCoding { + return YES; +} + +@end diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredItem.h b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredItem.h new file mode 100644 index 0000000..4926588 --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredItem.h @@ -0,0 +1,51 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRInstallationsStatus.h" + +@class FIRInstallationsStoredAuthToken; +@class FIRInstallationsStoredIIDCheckin; + +NS_ASSUME_NONNULL_BEGIN + +/** + * The class is supposed to be used by `FIRInstallationsStore` only. It is required to + * serialize/deserialize the installation data into/from `NSData` to be stored in Keychain. + * + * WARNING: Modification of the class properties can lead to incompatibility with the stored data + * encoded by the previous class versions. Any modification must be evaluated and, if it is really + * needed, the `storageVersion` must be bumped and proper migration code added. + */ +@interface FIRInstallationsStoredItem : NSObject + +/// A stable identifier that uniquely identifies the app instance. +@property(nonatomic, copy, nullable) NSString *firebaseInstallationID; +/// The `refreshToken` is used to authorize the auth token requests. +@property(nonatomic, copy, nullable) NSString *refreshToken; + +@property(nonatomic, nullable) FIRInstallationsStoredAuthToken *authToken; +@property(nonatomic) FIRInstallationsStatus registrationStatus; + +/// Instance ID default auth token imported from IID store as a part of IID migration. +@property(nonatomic, nullable) NSString *IIDDefaultToken; + +/// The version of local storage. +@property(nonatomic, readonly) NSInteger storageVersion; +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredItem.m b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredItem.m new file mode 100644 index 0000000..0c7655c --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredItem.m @@ -0,0 +1,80 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstallationsStoredItem.h" + +#import "FIRInstallationsLogger.h" +#import "FIRInstallationsStoredAuthToken.h" + +NSString *const kFIRInstallationsStoredItemFirebaseInstallationIDKey = @"firebaseInstallationID"; +NSString *const kFIRInstallationsStoredItemRefreshTokenKey = @"refreshToken"; +NSString *const kFIRInstallationsStoredItemAuthTokenKey = @"authToken"; +NSString *const kFIRInstallationsStoredItemRegistrationStatusKey = @"registrationStatus"; +NSString *const kFIRInstallationsStoredItemIIDDefaultTokenKey = @"IIDDefaultToken"; +NSString *const kFIRInstallationsStoredItemStorageVersionKey = @"storageVersion"; + +NSInteger const kFIRInstallationsStoredItemStorageVersion = 1; + +@implementation FIRInstallationsStoredItem + +- (NSInteger)storageVersion { + return kFIRInstallationsStoredItemStorageVersion; +} + +- (void)encodeWithCoder:(nonnull NSCoder *)aCoder { + [aCoder encodeObject:self.firebaseInstallationID + forKey:kFIRInstallationsStoredItemFirebaseInstallationIDKey]; + [aCoder encodeObject:self.refreshToken forKey:kFIRInstallationsStoredItemRefreshTokenKey]; + [aCoder encodeObject:self.authToken forKey:kFIRInstallationsStoredItemAuthTokenKey]; + [aCoder encodeInteger:self.registrationStatus + forKey:kFIRInstallationsStoredItemRegistrationStatusKey]; + [aCoder encodeObject:self.IIDDefaultToken forKey:kFIRInstallationsStoredItemIIDDefaultTokenKey]; + [aCoder encodeInteger:self.storageVersion forKey:kFIRInstallationsStoredItemStorageVersionKey]; +} + +- (nullable instancetype)initWithCoder:(nonnull NSCoder *)aDecoder { + NSInteger storageVersion = + [aDecoder decodeIntegerForKey:kFIRInstallationsStoredItemStorageVersionKey]; + if (storageVersion > self.storageVersion) { + FIRLogWarning(kFIRLoggerInstallations, + kFIRInstallationsMessageCodeInstallationCoderVersionMismatch, + @"FIRInstallationsStoredItem was encoded by a newer coder version %ld. Current " + @"coder version is %ld. Some installation data may be lost.", + (long)storageVersion, (long)kFIRInstallationsStoredItemStorageVersion); + } + + FIRInstallationsStoredItem *item = [[FIRInstallationsStoredItem alloc] init]; + item.firebaseInstallationID = + [aDecoder decodeObjectOfClass:[NSString class] + forKey:kFIRInstallationsStoredItemFirebaseInstallationIDKey]; + item.refreshToken = [aDecoder decodeObjectOfClass:[NSString class] + forKey:kFIRInstallationsStoredItemRefreshTokenKey]; + item.authToken = [aDecoder decodeObjectOfClass:[FIRInstallationsStoredAuthToken class] + forKey:kFIRInstallationsStoredItemAuthTokenKey]; + item.registrationStatus = + [aDecoder decodeIntegerForKey:kFIRInstallationsStoredItemRegistrationStatusKey]; + item.IIDDefaultToken = + [aDecoder decodeObjectOfClass:[NSString class] + forKey:kFIRInstallationsStoredItemIIDDefaultTokenKey]; + + return item; +} + ++ (BOOL)supportsSecureCoding { + return YES; +} + +@end diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Public/FIRInstallations.h b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Public/FIRInstallations.h new file mode 100644 index 0000000..4839b4e --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Public/FIRInstallations.h @@ -0,0 +1,120 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FIRApp; +@class FIRInstallationsAuthTokenResult; + +NS_ASSUME_NONNULL_BEGIN + +/** A notification with this name is sent each time an installation is created or deleted. */ +FOUNDATION_EXPORT const NSNotificationName FIRInstallationIDDidChangeNotification; +/** `userInfo` key for the `FirebaseApp.name` in `FIRInstallationIDDidChangeNotification`. */ +FOUNDATION_EXPORT NSString *const kFIRInstallationIDDidChangeNotificationAppNameKey; + +/** + * An installation ID handler block. + * @param identifier The installation ID string if exists or `nil` otherwise. + * @param error The error when `identifier == nil` or `nil` otherwise. + */ +typedef void (^FIRInstallationsIDHandler)(NSString *__nullable identifier, + NSError *__nullable error) + NS_SWIFT_NAME(InstallationsIDHandler); + +/** + * An authorization token handler block. + * @param tokenResult An instance of `InstallationsAuthTokenResult` in case of success or `nil` + * otherwise. + * @param error The error when `tokenResult == nil` or `nil` otherwise. + */ +typedef void (^FIRInstallationsTokenHandler)( + FIRInstallationsAuthTokenResult *__nullable tokenResult, NSError *__nullable error) + NS_SWIFT_NAME(InstallationsTokenHandler); + +/** + * The class provides API for Firebase Installations. + * Each configured `FirebaseApp` has a corresponding single instance of `Installations`. + * An instance of the class provides access to the installation info for the `FirebaseApp` as well + * as the ability to delete it. A Firebase Installation is unique by `FirebaseApp.name` and + * `FirebaseApp.options.googleAppID` . + */ +NS_SWIFT_NAME(Installations) +@interface FIRInstallations : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +/** + * Returns a default instance of `Installations`. + * @returns An instance of `Installations` for `FirebaseApp.defaultApp(). + * @throw Throws an exception if the default app is not configured yet or required `FirebaseApp` + * options are missing. + */ ++ (FIRInstallations *)installations NS_SWIFT_NAME(installations()); + +/** + * Returns an instance of `Installations` for an application. + * @param application A configured `FirebaseApp` instance. + * @returns An instance of `Installations` corresponding to the passed application. + * @throw Throws an exception if required `FirebaseApp` options are missing. + */ ++ (FIRInstallations *)installationsWithApp:(FIRApp *)application NS_SWIFT_NAME(installations(app:)); + +/** + * The method creates or retrieves an installation ID. The installation ID is a stable identifier + * that uniquely identifies the app instance. NOTE: If the application already has an existing + * FirebaseInstanceID then the InstanceID identifier will be used. + * @param completion A completion handler which is invoked when the operation completes. See + * `InstallationsIDHandler` for additional details. + */ +- (void)installationIDWithCompletion:(FIRInstallationsIDHandler)completion; + +/** + * Retrieves (locally if it exists or from the server) a valid authorization token. An existing + * token may be invalidated or expired, so it is recommended to fetch the auth token before each + * server request. The method does the same as `Installations.authTokenForcingRefresh(:, + * completion:)` with forcing refresh `NO`. + * @param completion A completion handler which is invoked when the operation completes. See + * `InstallationsTokenHandler` for additional details. + */ +- (void)authTokenWithCompletion:(FIRInstallationsTokenHandler)completion; + +/** + * Retrieves (locally or from the server depending on `forceRefresh` value) a valid authorization + * token. An existing token may be invalidated or expire, so it is recommended to fetch the auth + * token before each server request. This method should be used with `forceRefresh == YES` when e.g. + * a request with the previously fetched auth token failed with "Not Authorized" error. + * @param forceRefresh If `YES` then the locally cached auth token will be ignored and a new one + * will be requested from the server. If `NO`, then the locally cached auth token will be returned + * if exists and has not expired yet. + * @param completion A completion handler which is invoked when the operation completes. See + * `InstallationsTokenHandler` for additional details. + */ +- (void)authTokenForcingRefresh:(BOOL)forceRefresh + completion:(FIRInstallationsTokenHandler)completion; + +/** + * Deletes all the installation data including the unique identifier, auth tokens and + * all related data on the server side. A network connection is required for the method to + * succeed. If fails, the existing installation data remains untouched. + * @param completion A completion handler which is invoked when the operation completes. `error == + * nil` indicates success. + */ +- (void)deleteWithCompletion:(void (^)(NSError *__nullable error))completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Public/FIRInstallationsAuthTokenResult.h b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Public/FIRInstallationsAuthTokenResult.h new file mode 100644 index 0000000..7753132 --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Public/FIRInstallationsAuthTokenResult.h @@ -0,0 +1,33 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** The class represents a result of the auth token request. */ +NS_SWIFT_NAME(InstallationsAuthTokenResult) +@interface FIRInstallationsAuthTokenResult : NSObject + +/** The authorization token string. */ +@property(nonatomic, readonly) NSString *authToken; + +/** The auth token expiration date. */ +@property(nonatomic, readonly) NSDate *expirationDate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Public/FIRInstallationsErrors.h b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Public/FIRInstallationsErrors.h new file mode 100644 index 0000000..d0c3b99 --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Public/FIRInstallationsErrors.h @@ -0,0 +1,34 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +extern NSString *const kFirebaseInstallationsErrorDomain; + +typedef NS_ENUM(NSUInteger, FIRInstallationsErrorCode) { + /** Unknown error. See `userInfo` for details. */ + FIRInstallationsErrorCodeUnknown = 0, + + /** Keychain error. See `userInfo` for details. */ + FIRInstallationsErrorCodeKeychain = 1, + + /** Server unreachable. A network error or server is unavailable. See `userInfo` for details. */ + FIRInstallationsErrorCodeServerUnreachable = 2, + + /** FirebaseApp configuration issues e.g. invalid GMP-App-ID, etc. See `userInfo` for details. */ + FIRInstallationsErrorCodeInvalidConfiguration = 3, + +} NS_SWIFT_NAME(InstallationsErrorCode); diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Public/FIRInstallationsVersion.h b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Public/FIRInstallationsVersion.h new file mode 100644 index 0000000..8cdf677 --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Public/FIRInstallationsVersion.h @@ -0,0 +1,19 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +FOUNDATION_EXPORT const char *const FIRInstallationsVersionStr; diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Public/FirebaseInstallations.h b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Public/FirebaseInstallations.h new file mode 100644 index 0000000..accc9ac --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Public/FirebaseInstallations.h @@ -0,0 +1,20 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstallations.h" +#import "FIRInstallationsAuthTokenResult.h" +#import "FIRInstallationsErrors.h" +#import "FIRInstallationsVersion.h" diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/SecureStorage/FIRInstallationsKeychainUtils.h b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/SecureStorage/FIRInstallationsKeychainUtils.h new file mode 100644 index 0000000..4d73ec0 --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/SecureStorage/FIRInstallationsKeychainUtils.h @@ -0,0 +1,35 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// Helper functions to access Keychain. +@interface FIRInstallationsKeychainUtils : NSObject + ++ (nullable NSData *)getItemWithQuery:(NSDictionary *)query + error:(NSError *_Nullable *_Nullable)outError; + ++ (BOOL)setItem:(NSData *)item + withQuery:(NSDictionary *)query + error:(NSError *_Nullable *_Nullable)outError; + ++ (BOOL)removeItemWithQuery:(NSDictionary *)query error:(NSError *_Nullable *_Nullable)outError; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/SecureStorage/FIRInstallationsKeychainUtils.m b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/SecureStorage/FIRInstallationsKeychainUtils.m new file mode 100644 index 0000000..51da86a --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/SecureStorage/FIRInstallationsKeychainUtils.m @@ -0,0 +1,107 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstallationsKeychainUtils.h" + +#import "FIRInstallationsErrorUtil.h" + +@implementation FIRInstallationsKeychainUtils + ++ (nullable NSData *)getItemWithQuery:(NSDictionary *)query + error:(NSError *_Nullable *_Nullable)outError { + NSMutableDictionary *mutableQuery = [query mutableCopy]; + + mutableQuery[(__bridge id)kSecReturnData] = @YES; + mutableQuery[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne; + + CFDataRef result = NULL; + OSStatus status = + SecItemCopyMatching((__bridge CFDictionaryRef)mutableQuery, (CFTypeRef *)&result); + + if (status == errSecSuccess && result != NULL) { + if (outError) { + *outError = nil; + } + + return (__bridge_transfer NSData *)result; + } + + if (status == errSecItemNotFound) { + if (outError) { + *outError = nil; + } + } else { + if (outError) { + *outError = [FIRInstallationsErrorUtil keychainErrorWithFunction:@"SecItemCopyMatching" + status:status]; + } + } + return nil; +} + ++ (BOOL)setItem:(NSData *)item + withQuery:(NSDictionary *)query + error:(NSError *_Nullable *_Nullable)outError { + NSData *existingItem = [self getItemWithQuery:query error:outError]; + if (outError && *outError) { + return NO; + } + + NSMutableDictionary *mutableQuery = [query mutableCopy]; + mutableQuery[(__bridge id)kSecAttrAccessible] = + (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly; + + OSStatus status; + if (!existingItem) { + mutableQuery[(__bridge id)kSecValueData] = item; + status = SecItemAdd((__bridge CFDictionaryRef)mutableQuery, NULL); + } else { + NSDictionary *attributes = @{(__bridge id)kSecValueData : item}; + status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)attributes); + } + + if (status == noErr) { + if (outError) { + *outError = nil; + } + return YES; + } + + NSString *function = existingItem ? @"SecItemUpdate" : @"SecItemAdd"; + if (outError) { + *outError = [FIRInstallationsErrorUtil keychainErrorWithFunction:function status:status]; + } + return NO; +} + ++ (BOOL)removeItemWithQuery:(NSDictionary *)query error:(NSError *_Nullable *_Nullable)outError { + OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query); + + if (status == noErr || status == errSecItemNotFound) { + if (outError) { + *outError = nil; + } + return YES; + } + + if (outError) { + *outError = [FIRInstallationsErrorUtil keychainErrorWithFunction:@"SecItemDelete" + status:status]; + } + return NO; +} + +@end diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/SecureStorage/FIRSecureStorage.h b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/SecureStorage/FIRSecureStorage.h new file mode 100644 index 0000000..5548e3e --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/SecureStorage/FIRSecureStorage.h @@ -0,0 +1,71 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FBLPromise; + +NS_ASSUME_NONNULL_BEGIN + +/// The class provides a convenient abstraction on top of the iOS Keychain API to save data. +@interface FIRSecureStorage : NSObject + +/** + * Get an object by key. + * @param key The key. + * @param objectClass The expected object class required by `NSSecureCoding`. + * @param accessGroup The Keychain Access Group. + * + * @return Returns a promise. It is resolved with an object stored by key if exists. It is resolved + * with `nil` when the object not found. It fails on a Keychain error. + */ +- (FBLPromise> *)getObjectForKey:(NSString *)key + objectClass:(Class)objectClass + accessGroup:(nullable NSString *)accessGroup; + +/** + * Saves the given object by the given key. + * @param object The object to store. + * @param key The key to store the object. If there is an existing object by the key, it will be + * overridden. + * @param accessGroup The Keychain Access Group. + * + * @return Returns which is resolved with `[NSNull null]` on success. + */ +- (FBLPromise *)setObject:(id)object + forKey:(NSString *)key + accessGroup:(nullable NSString *)accessGroup; + +/** + * Removes the object by the given key. + * @param key The key to store the object. If there is an existing object by the key, it will be + * overridden. + * @param accessGroup The Keychain Access Group. + * + * @return Returns which is resolved with `[NSNull null]` on success. + */ +- (FBLPromise *)removeObjectForKey:(NSString *)key + accessGroup:(nullable NSString *)accessGroup; + +#if TARGET_OS_OSX +/// If not `nil`, then only this keychain will be used to save and read data (see +/// `kSecMatchSearchList` and `kSecUseKeychain`. It is mostly intended to be used by unit tests. +@property(nonatomic, nullable) SecKeychainRef keychainRef; +#endif // TARGET_OSX + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/SecureStorage/FIRSecureStorage.m b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/SecureStorage/FIRSecureStorage.m new file mode 100644 index 0000000..543e848 --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/SecureStorage/FIRSecureStorage.m @@ -0,0 +1,255 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRSecureStorage.h" +#import + +#if __has_include() +#import +#else +#import "FBLPromises.h" +#endif + +#import "FIRInstallationsErrorUtil.h" +#import "FIRInstallationsKeychainUtils.h" + +@interface FIRSecureStorage () +@property(nonatomic, readonly) dispatch_queue_t keychainQueue; +@property(nonatomic, readonly) dispatch_queue_t inMemoryCacheQueue; +@property(nonatomic, readonly) NSString *service; +@property(nonatomic, readonly) NSCache> *inMemoryCache; +@end + +@implementation FIRSecureStorage + +- (instancetype)init { + NSCache *cache = [[NSCache alloc] init]; + // Cache up to 5 installations. + cache.countLimit = 5; + return [self initWithService:@"com.firebase.FIRInstallations.installations" cache:cache]; +} + +- (instancetype)initWithService:(NSString *)service cache:(NSCache *)cache { + self = [super init]; + if (self) { + _keychainQueue = dispatch_queue_create( + "com.firebase.FIRInstallations.FIRSecureStorage.Keychain", DISPATCH_QUEUE_SERIAL); + _inMemoryCacheQueue = dispatch_queue_create( + "com.firebase.FIRInstallations.FIRSecureStorage.InMemoryCache", DISPATCH_QUEUE_SERIAL); + _service = [service copy]; + _inMemoryCache = cache; + } + return self; +} + +#pragma mark - Public + +- (FBLPromise> *)getObjectForKey:(NSString *)key + objectClass:(Class)objectClass + accessGroup:(nullable NSString *)accessGroup { + return [FBLPromise onQueue:self.inMemoryCacheQueue + do:^id _Nullable { + // Return cached object or fail otherwise. + id object = [self.inMemoryCache objectForKey:key]; + return object + ?: [[NSError alloc] + initWithDomain:FBLPromiseErrorDomain + code:FBLPromiseErrorCodeValidationFailure + userInfo:nil]; + }] + .recover(^id _Nullable(NSError *error) { + // Look for the object in the keychain. + return [self getObjectFromKeychainForKey:key + objectClass:objectClass + accessGroup:accessGroup]; + }); +} + +- (FBLPromise *)setObject:(id)object + forKey:(NSString *)key + accessGroup:(nullable NSString *)accessGroup { + return [FBLPromise onQueue:self.inMemoryCacheQueue + do:^id _Nullable { + // Save to the in-memory cache first. + [self.inMemoryCache setObject:object forKey:[key copy]]; + return [NSNull null]; + }] + .thenOn(self.keychainQueue, ^id(id result) { + // Then store the object to the keychain. + NSDictionary *query = [self keychainQueryWithKey:key accessGroup:accessGroup]; + NSError *error; + NSData *encodedObject = [self archiveDataForObject:object error:&error]; + if (!encodedObject) { + return error; + } + + if (![FIRInstallationsKeychainUtils setItem:encodedObject withQuery:query error:&error]) { + return error; + } + + return [NSNull null]; + }); +} + +- (FBLPromise *)removeObjectForKey:(NSString *)key + accessGroup:(nullable NSString *)accessGroup { + return [FBLPromise onQueue:self.inMemoryCacheQueue + do:^id _Nullable { + [self.inMemoryCache removeObjectForKey:key]; + return nil; + }] + .thenOn(self.keychainQueue, ^id(id result) { + NSDictionary *query = [self keychainQueryWithKey:key accessGroup:accessGroup]; + + NSError *error; + if (![FIRInstallationsKeychainUtils removeItemWithQuery:query error:&error]) { + return error; + } + + return [NSNull null]; + }); +} + +#pragma mark - Private + +- (FBLPromise> *)getObjectFromKeychainForKey:(NSString *)key + objectClass:(Class)objectClass + accessGroup:(nullable NSString *)accessGroup { + // Look for the object in the keychain. + return [FBLPromise onQueue:self.keychainQueue + do:^id { + NSDictionary *query = [self keychainQueryWithKey:key + accessGroup:accessGroup]; + NSError *error; + NSData *encodedObject = + [FIRInstallationsKeychainUtils getItemWithQuery:query error:&error]; + + if (error) { + return error; + } + if (!encodedObject) { + return nil; + } + id object = [self unarchivedObjectOfClass:objectClass + fromData:encodedObject + error:&error]; + if (error) { + return error; + } + + return object; + }] + .thenOn(self.inMemoryCacheQueue, + ^id _Nullable(id _Nullable object) { + // Save object to the in-memory cache if exists and return the object. + if (object) { + [self.inMemoryCache setObject:object forKey:[key copy]]; + } + return object; + }); +} + +- (void)resetInMemoryCache { + [self.inMemoryCache removeAllObjects]; +} + +#pragma mark - Keychain + +- (NSMutableDictionary *)keychainQueryWithKey:(NSString *)key + accessGroup:(nullable NSString *)accessGroup { + NSMutableDictionary *query = [NSMutableDictionary dictionary]; + + query[(__bridge NSString *)kSecClass] = (__bridge NSString *)kSecClassGenericPassword; + query[(__bridge NSString *)kSecAttrService] = self.service; + query[(__bridge NSString *)kSecAttrAccount] = key; + + if (accessGroup) { + query[(__bridge NSString *)kSecAttrAccessGroup] = accessGroup; + } + +#if TARGET_OS_OSX + if (self.keychainRef) { + query[(__bridge NSString *)kSecUseKeychain] = (__bridge id)(self.keychainRef); + query[(__bridge NSString *)kSecMatchSearchList] = @[ (__bridge id)(self.keychainRef) ]; + } +#endif // TARGET_OSX + + return query; +} + +- (nullable NSData *)archiveDataForObject:(id)object error:(NSError **)outError { + NSData *archiveData; + if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) { + NSError *error; + archiveData = [NSKeyedArchiver archivedDataWithRootObject:object + requiringSecureCoding:YES + error:&error]; + if (error && outError) { + *outError = [FIRInstallationsErrorUtil keyedArchiverErrorWithError:error]; + } + } else { + @try { + NSMutableData *data = [NSMutableData data]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; +#pragma clang diagnostic pop + archiver.requiresSecureCoding = YES; + + [archiver encodeObject:object forKey:NSKeyedArchiveRootObjectKey]; + [archiver finishEncoding]; + + archiveData = [data copy]; + } @catch (NSException *exception) { + if (outError) { + *outError = [FIRInstallationsErrorUtil keyedArchiverErrorWithException:exception]; + } + } + } + + return archiveData; +} + +- (nullable id)unarchivedObjectOfClass:(Class)class + fromData:(NSData *)data + error:(NSError **)outError { + id object; + if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) { + NSError *error; + object = [NSKeyedUnarchiver unarchivedObjectOfClass:class fromData:data error:&error]; + if (error && outError) { + *outError = [FIRInstallationsErrorUtil keyedArchiverErrorWithError:error]; + } + } else { + @try { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; +#pragma clang diagnostic pop + unarchiver.requiresSecureCoding = YES; + + object = [unarchiver decodeObjectOfClass:class forKey:NSKeyedArchiveRootObjectKey]; + } @catch (NSException *exception) { + if (outError) { + *outError = [FIRInstallationsErrorUtil keyedArchiverErrorWithException:exception]; + } + } + } + + return object; +} + +@end diff --git a/!main project/Pods/FirebaseInstallations/LICENSE b/!main project/Pods/FirebaseInstallations/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/!main project/Pods/FirebaseInstallations/README.md b/!main project/Pods/FirebaseInstallations/README.md new file mode 100644 index 0000000..3ddc8fb --- /dev/null +++ b/!main project/Pods/FirebaseInstallations/README.md @@ -0,0 +1,251 @@ +# Firebase iOS Open Source Development [![Build Status](https://travis-ci.org/firebase/firebase-ios-sdk.svg?branch=master)](https://travis-ci.org/firebase/firebase-ios-sdk) + +This repository contains a subset of the Firebase iOS SDK source. It currently +includes FirebaseCore, FirebaseABTesting, FirebaseAuth, FirebaseDatabase, +FirebaseFirestore, FirebaseFunctions, FirebaseInstanceID, FirebaseInAppMessaging, +FirebaseInAppMessagingDisplay, FirebaseMessaging, FirebaseRemoteConfig, and +FirebaseStorage. + +The repository also includes GoogleUtilities source. The +[GoogleUtilities](GoogleUtilities/README.md) pod is +a set of utilities used by Firebase and other Google products. + +Firebase is an app development platform with tools to help you build, grow and +monetize your app. More information about Firebase can be found at +[https://firebase.google.com](https://firebase.google.com). + +## Installation + +See the three subsections for details about three different installation methods. +1. [Standard pod install](README.md#standard-pod-install) +1. [Installing from the GitHub repo](README.md#installing-from-github) +1. [Experimental Carthage](README.md#carthage-ios-only) + +### Standard pod install + +Go to +[https://firebase.google.com/docs/ios/setup](https://firebase.google.com/docs/ios/setup). + +### Installing from GitHub + +For releases starting with 5.0.0, the source for each release is also deployed +to CocoaPods master and available via standard +[CocoaPods Podfile syntax](https://guides.cocoapods.org/syntax/podfile.html#pod). + +These instructions can be used to access the Firebase repo at other branches, +tags, or commits. + +#### Background + +See +[the Podfile Syntax Reference](https://guides.cocoapods.org/syntax/podfile.html#pod) +for instructions and options about overriding pod source locations. + +#### Accessing Firebase Source Snapshots + +All of the official releases are tagged in this repo and available via CocoaPods. To access a local +source snapshot or unreleased branch, use Podfile directives like the following: + +To access FirebaseFirestore via a branch: +``` +pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +``` + +To access FirebaseMessaging via a checked out version of the firebase-ios-sdk repo do: + +``` +pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk' +pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk' +``` + +### Carthage (iOS only) + +Instructions for the experimental Carthage distribution are at +[Carthage](Carthage.md). + +### Rome + +Instructions for installing binary frameworks via +[Rome](https://github.com/CocoaPods/Rome) are at [Rome](Rome.md). + +## Development + +To develop Firebase software in this repository, ensure that you have at least +the following software: + + * Xcode 10.1 (or later) + * CocoaPods 1.7.2 (or later) + * [CocoaPods generate](https://github.com/square/cocoapods-generate) + +For the pod that you want to develop: + +`pod gen Firebase{name here}.podspec --local-sources=./ --auto-open --platforms=ios` + +Note: If the CocoaPods cache is out of date, you may need to run +`pod repo update` before the `pod gen` command. + +Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for +those platforms. Since 10.2, Xcode does not properly handle multi-platform +CocoaPods workspaces. + +Firestore has a self contained Xcode project. See +[Firestore/README.md](Firestore/README.md). + +### Development for Catalyst +* `pod gen {name here}.podspec --local-sources=./ --auto-open --platforms=ios` +* Check the Mac box in the App-iOS Build Settings +* Sign the App in the Settings Signing & Capabilities tab +* Click Pods in the Project Manager +* Add Signing to the iOS host app and unit test targets +* Select the Unit-unit scheme +* Run it to build and test + +### Adding a New Firebase Pod + +See [AddNewPod.md](AddNewPod.md). + +### Code Formatting + +To ensure that the code is formatted consistently, run the script +[./scripts/style.sh](https://github.com/firebase/firebase-ios-sdk/blob/master/scripts/style.sh) +before creating a PR. + +Travis will verify that any code changes are done in a style compliant way. Install +`clang-format` and `swiftformat`. +These commands will get the right versions: + +``` +brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/e3496d9/Formula/clang-format.rb +brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/7963c3d/Formula/swiftformat.rb +``` + +Note: if you already have a newer version of these installed you may need to +`brew switch` to this version. + +To update this section, find the versions of clang-format and swiftformat.rb to +match the versions in the CI failure logs +[here](https://github.com/Homebrew/homebrew-core/tree/master/Formula). + +### Running Unit Tests + +Select a scheme and press Command-u to build a component and run its unit tests. + +#### Viewing Code Coverage + +First, make sure that [xcov](https://github.com/nakiostudio/xcov) is installed with `gem install xcov`. + +After running the `AllUnitTests_iOS` scheme in Xcode, execute +`xcov --workspace Firebase.xcworkspace --scheme AllUnitTests_iOS --output_directory xcov_output` +at Example/ in the terminal. This will aggregate the coverage, and you can run `open xcov_output/index.html` to see the results. + +### Running Sample Apps +In order to run the sample apps and integration tests, you'll need valid +`GoogleService-Info.plist` files for those samples. The Firebase Xcode project contains dummy plist +files without real values, but can be replaced with real plist files. To get your own +`GoogleService-Info.plist` files: + +1. Go to the [Firebase Console](https://console.firebase.google.com/) +2. Create a new Firebase project, if you don't already have one +3. For each sample app you want to test, create a new Firebase app with the sample app's bundle +identifier (e.g. `com.google.Database-Example`) +4. Download the resulting `GoogleService-Info.plist` and replace the appropriate dummy plist file +(e.g. in [Example/Database/App/](Example/Database/App/)); + +Some sample apps like Firebase Messaging ([Example/Messaging/App](Example/Messaging/App)) require +special Apple capabilities, and you will have to change the sample app to use a unique bundle +identifier that you can control in your own Apple Developer account. + +## Specific Component Instructions +See the sections below for any special instructions for those components. + +### Firebase Auth + +If you're doing specific Firebase Auth development, see +[the Auth Sample README](Example/Auth/README.md) for instructions about +building and running the FirebaseAuth pod along with various samples and tests. + +### Firebase Database + +To run the Database Integration tests, make your database authentication rules +[public](https://firebase.google.com/docs/database/security/quickstart). + +### Firebase Storage + +To run the Storage Integration tests, follow the instructions in +[FIRStorageIntegrationTests.m](Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m). + +#### Push Notifications + +Push notifications can only be delivered to specially provisioned App IDs in the developer portal. +In order to actually test receiving push notifications, you will need to: + +1. Change the bundle identifier of the sample app to something you own in your Apple Developer +account, and enable that App ID for push notifications. +2. You'll also need to +[upload your APNs Provider Authentication Key or certificate to the Firebase Console](https://firebase.google.com/docs/cloud-messaging/ios/certs) +at **Project Settings > Cloud Messaging > [Your Firebase App]**. +3. Ensure your iOS device is added to your Apple Developer portal as a test device. + +#### iOS Simulator + +The iOS Simulator cannot register for remote notifications, and will not receive push notifications. +In order to receive push notifications, you'll have to follow the steps above and run the app on a +physical device. + +## Community Supported Efforts + +We've seen an amazing amount of interest and contributions to improve the Firebase SDKs, and we are +very grateful! We'd like to empower as many developers as we can to be able to use Firebase and +participate in the Firebase community. + +### tvOS, macOS, and Catalyst +Thanks to contributions from the community, FirebaseABTesting, FirebaseAuth, FirebaseCore, +FirebaseDatabase, FirebaseMessaging, FirebaseFirestore, +FirebaseFunctions, FirebaseRemoteConfig, and FirebaseStorage now compile, run unit tests, and work on +tvOS, macOS, and Catalyst. + +For tvOS, checkout the [Sample](Example/tvOSSample). + +Keep in mind that macOS, Catalyst and tvOS are not officially supported by Firebase, and this +repository is actively developed primarily for iOS. While we can catch basic unit test issues with +Travis, there may be some changes where the SDK no longer works as expected on macOS or tvOS. If you +encounter this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues). + +To install, add a subset of the following to the Podfile: + +``` +pod 'Firebase/ABTesting' +pod 'Firebase/Auth' +pod 'Firebase/Database' +pod 'Firebase/Firestore' +pod 'Firebase/Functions' +pod 'Firebase/Messaging' +pod 'Firebase/RemoteConfig' +pod 'Firebase/Storage' +``` + +#### Additional Catalyst Notes + +* FirebaseAuth and FirebaseMessaging require adding `Keychain Sharing Capability` +to Build Settings. +* FirebaseFirestore requires signing the +[gRPC Resource target](https://github.com/firebase/firebase-ios-sdk/issues/3500#issuecomment-518741681). + +## Roadmap + +See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source +plans and directions. + +## Contributing + +See [Contributing](CONTRIBUTING.md) for more information on contributing to the Firebase +iOS SDK. + +## License + +The contents of this repository is licensed under the +[Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0). + +Your use of Firebase is governed by the +[Terms of Service for Firebase Services](https://firebase.google.com/terms/). diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRIMessageCode.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRIMessageCode.h new file mode 100644 index 0000000..e3e36e9 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRIMessageCode.h @@ -0,0 +1,150 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +// The format of the debug code will show in the log as: e.g. +// for code 1000, it will show as I-IID001000. +typedef NS_ENUM(NSInteger, FIRInstanceIDMessageCode) { + // DO NOT USE 2000, 2002. + kFIRInstanceIDMessageCodeFIRApp000 = 1000, // I-IID001000 + kFIRInstanceIDMessageCodeFIRApp001 = 1001, + kFIRInstanceIDMessageCodeFIRApp002 = 1002, + kFIRInstanceIDMessageCodeInternal001 = 2001, + kFIRInstanceIDMessageCodeInternal002 = 2002, + // FIRInstanceID.m + // DO NOT USE 4000. + kFIRInstanceIDMessageCodeInstanceID000 = 3000, + kFIRInstanceIDMessageCodeInstanceID001 = 3001, + kFIRInstanceIDMessageCodeInstanceID002 = 3002, + kFIRInstanceIDMessageCodeInstanceID003 = 3003, + kFIRInstanceIDMessageCodeInstanceID004 = 3004, + kFIRInstanceIDMessageCodeInstanceID005 = 3005, + kFIRInstanceIDMessageCodeInstanceID006 = 3006, + kFIRInstanceIDMessageCodeInstanceID007 = 3007, + kFIRInstanceIDMessageCodeInstanceID008 = 3008, + kFIRInstanceIDMessageCodeInstanceID009 = 3009, + kFIRInstanceIDMessageCodeInstanceID010 = 3010, + kFIRInstanceIDMessageCodeInstanceID011 = 3011, + kFIRInstanceIDMessageCodeInstanceID012 = 3012, + kFIRInstanceIDMessageCodeInstanceID013 = 3013, + kFIRInstanceIDMessageCodeInstanceID014 = 3014, + kFIRInstanceIDMessageCodeInstanceID015 = 3015, + kFIRInstanceIDMessageCodeRefetchingTokenForAPNS = 3016, + kFIRInstanceIDMessageCodeInstanceID017 = 3017, + kFIRInstanceIDMessageCodeInstanceID018 = 3018, + // FIRInstanceIDAuthService.m + kFIRInstanceIDMessageCodeAuthService000 = 5000, + kFIRInstanceIDMessageCodeAuthService001 = 5001, + kFIRInstanceIDMessageCodeAuthService002 = 5002, + kFIRInstanceIDMessageCodeAuthService003 = 5003, + kFIRInstanceIDMessageCodeAuthService004 = 5004, + kFIRInstanceIDMessageCodeAuthServiceCheckinInProgress = 5004, + + // FIRInstanceIDBackupExcludedPlist.m + // Do NOT USE 6003 + kFIRInstanceIDMessageCodeBackupExcludedPlist000 = 6000, + kFIRInstanceIDMessageCodeBackupExcludedPlist001 = 6001, + kFIRInstanceIDMessageCodeBackupExcludedPlist002 = 6002, + // FIRInstanceIDCheckinService.m + kFIRInstanceIDMessageCodeService000 = 7000, + kFIRInstanceIDMessageCodeService001 = 7001, + kFIRInstanceIDMessageCodeService002 = 7002, + kFIRInstanceIDMessageCodeService003 = 7003, + kFIRInstanceIDMessageCodeService004 = 7004, + kFIRInstanceIDMessageCodeService005 = 7005, + kFIRInstanceIDMessageCodeService006 = 7006, + kFIRInstanceIDInvalidNetworkSession = 7007, + kFIRInstanceIDInvalidSettingResponse = 7008, + // FIRInstanceIDCheckinStore.m + // DO NOT USE 8002, 8004 - 8008 + kFIRInstanceIDMessageCodeCheckinStore000 = 8000, + kFIRInstanceIDMessageCodeCheckinStore001 = 8001, + kFIRInstanceIDMessageCodeCheckinStore003 = 8003, + kFIRInstanceIDMessageCodeCheckinStoreCheckinPlistDeleted = 8009, + kFIRInstanceIDMessageCodeCheckinStoreCheckinPlistSaved = 8010, + + // DO NOT USE 9000 - 9006 + + // DO NOT USE 10000 - 10009 + + // DO NOT USE 11000 - 11002 + + // DO NOT USE 12000 - 12014 + + // FIRInstanceIDStore.m + // DO NOT USE 13004, 13005, 13007, 13008, 13010, 13011, 13013, 13014 + kFIRInstanceIDMessageCodeStore000 = 13000, + kFIRInstanceIDMessageCodeStore001 = 13001, + kFIRInstanceIDMessageCodeStore002 = 13002, + kFIRInstanceIDMessageCodeStore003 = 13003, + kFIRInstanceIDMessageCodeStore006 = 13006, + kFIRInstanceIDMessageCodeStore009 = 13009, + kFIRInstanceIDMessageCodeStore012 = 13012, + // FIRInstanceIDTokenManager.m + // DO NOT USE 14002, 14005 + kFIRInstanceIDMessageCodeTokenManager000 = 14000, + kFIRInstanceIDMessageCodeTokenManager001 = 14001, + kFIRInstanceIDMessageCodeTokenManager003 = 14003, + kFIRInstanceIDMessageCodeTokenManager004 = 14004, + kFIRInstanceIDMessageCodeTokenManagerErrorDeletingFCMTokensOnAppReset = 14006, + kFIRInstanceIDMessageCodeTokenManagerDeletedFCMTokensOnAppReset = 14007, + kFIRInstanceIDMessageCodeTokenManagerSavedAppVersion = 14008, + kFIRInstanceIDMessageCodeTokenManagerErrorInvalidatingAllTokens = 14009, + kFIRInstanceIDMessageCodeTokenManagerAPNSChanged = 14010, + kFIRInstanceIDMessageCodeTokenManagerAPNSChangedTokenInvalidated = 14011, + kFIRInstanceIDMessageCodeTokenManagerInvalidateStaleToken = 14012, + // FIRInstanceIDTokenStore.m + // DO NOT USE 15002 - 15013 + kFIRInstanceIDMessageCodeTokenStore000 = 15000, + kFIRInstanceIDMessageCodeTokenStore001 = 15001, + kFIRInstanceIDMessageCodeTokenStoreExceptionUnarchivingTokenInfo = 15015, + + // DO NOT USE 16000, 18004 + + // FIRInstanceIDUtilities.m + kFIRInstanceIDMessageCodeUtilitiesMissingBundleIdentifier = 18000, + kFIRInstanceIDMessageCodeUtilitiesAppEnvironmentUtilNotAvailable = 18001, + kFIRInstanceIDMessageCodeUtilitiesCannotGetHardwareModel = 18002, + kFIRInstanceIDMessageCodeUtilitiesCannotGetSystemVersion = 18003, + // FIRInstanceIDTokenOperation.m + kFIRInstanceIDMessageCodeTokenOperationFailedToSignParams = 19000, + // FIRInstanceIDTokenFetchOperation.m + // DO NOT USE 20004, 20005 + kFIRInstanceIDMessageCodeTokenFetchOperationFetchRequest = 20000, + kFIRInstanceIDMessageCodeTokenFetchOperationRequestError = 20001, + kFIRInstanceIDMessageCodeTokenFetchOperationBadResponse = 20002, + kFIRInstanceIDMessageCodeTokenFetchOperationBadTokenStructure = 20003, + // FIRInstanceIDTokenDeleteOperation.m + kFIRInstanceIDMessageCodeTokenDeleteOperationFetchRequest = 21000, + kFIRInstanceIDMessageCodeTokenDeleteOperationRequestError = 21001, + kFIRInstanceIDMessageCodeTokenDeleteOperationBadResponse = 21002, + // FIRInstanceIDTokenInfo.m + kFIRInstanceIDMessageCodeTokenInfoBadAPNSInfo = 22000, + kFIRInstanceIDMessageCodeTokenInfoFirebaseAppIDChanged = 22001, + kFIRInstanceIDMessageCodeTokenInfoLocaleChanged = 22002, + // FIRInstanceIDKeychain.m + kFIRInstanceIDKeychainReadItemError = 23000, + kFIRInstanceIDKeychainAddItemError = 23001, + kFIRInstanceIDKeychainDeleteItemError = 23002, + kFIRInstanceIDKeychainCreateKeyPairError = 23003, + kFIRInstanceIDKeychainUpdateItemError = 23004, + + // FIRInstanceIDStringEncoding.m + kFIRInstanceIDStringEncodingBufferUnderflow = 24000, + kFIRInstanceIDStringEncodingBufferOverflow = 24001, + +}; diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceID+Private.m b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceID+Private.m new file mode 100644 index 0000000..c5ffe7a --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceID+Private.m @@ -0,0 +1,56 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceID+Private.h" + +#import + +#import +#import "FIRInstanceIDAuthService.h" +#import "FIRInstanceIDDefines.h" +#import "FIRInstanceIDTokenManager.h" + +@class FIRInstallations; + +@interface FIRInstanceID () + +@property(nonatomic, readonly, strong) FIRInstanceIDTokenManager *tokenManager; + +@end + +@implementation FIRInstanceID (Private) + +// This method just wraps our pre-configured auth service to make the request. +// This method is only needed by first-party users, like Remote Config. +- (void)fetchCheckinInfoWithHandler:(FIRInstanceIDDeviceCheckinCompletion)handler { + [self.tokenManager.authService fetchCheckinInfoWithHandler:handler]; +} + +// TODO(#4486): Delete the method, `self.firebaseInstallationsID` and related +// code for Firebase 7 release. +- (NSString *)appInstanceID:(NSError **)outError { + return self.firebaseInstallationsID; +} + +#pragma mark - Firebase Installations Compatibility + +/// Presence of this method indicates that this version of IID uses FirebaseInstallations under the +/// hood. It is checked by FirebaseInstallations SDK. ++ (BOOL)usesFIS { + return YES; +} + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceID.m b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceID.m new file mode 100644 index 0000000..f9cba4f --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceID.m @@ -0,0 +1,1130 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceID.h" + +#import + +#import +#import +#import +#import +#import +#import +#import +#import "FIRInstanceID+Private.h" +#import "FIRInstanceIDAuthService.h" +#import "FIRInstanceIDCheckinPreferences.h" +#import "FIRInstanceIDCombinedHandler.h" +#import "FIRInstanceIDConstants.h" +#import "FIRInstanceIDDefines.h" +#import "FIRInstanceIDLogger.h" +#import "FIRInstanceIDStore.h" +#import "FIRInstanceIDTokenInfo.h" +#import "FIRInstanceIDTokenManager.h" +#import "FIRInstanceIDUtilities.h" +#import "FIRInstanceIDVersionUtilities.h" +#import "NSError+FIRInstanceID.h" + +// Public constants +NSString *const kFIRInstanceIDScopeFirebaseMessaging = @"fcm"; + +#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 +const NSNotificationName kFIRInstanceIDTokenRefreshNotification = + @"com.firebase.iid.notif.refresh-token"; +#else +NSString *const kFIRInstanceIDTokenRefreshNotification = @"com.firebase.iid.notif.refresh-token"; +#endif // defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 + +NSString *const kFIRInstanceIDInvalidNilHandlerError = @"Invalid nil handler."; + +// Private constants +int64_t const kMaxRetryIntervalForDefaultTokenInSeconds = 20 * 60; // 20 minutes +int64_t const kMinRetryIntervalForDefaultTokenInSeconds = 10; // 10 seconds +// we retry only a max 5 times. +// TODO(chliangGoogle): If we still fail we should listen for the network change notification +// since GCM would have started Reachability. We only start retrying after we see a configuration +// change. +NSInteger const kMaxRetryCountForDefaultToken = 5; + +#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH +static NSString *const kEntitlementsAPSEnvironmentKey = @"Entitlements.aps-environment"; +#else +static NSString *const kEntitlementsAPSEnvironmentKey = + @"Entitlements.com.apple.developer.aps-environment"; +#endif +static NSString *const kAPSEnvironmentDevelopmentValue = @"development"; +/// FIRMessaging selector that returns the current FIRMessaging auto init +/// enabled flag. +static NSString *const kFIRInstanceIDFCMSelectorAutoInitEnabled = + @"isAutoInitEnabledWithUserDefaults:"; + +static NSString *const kFIRInstanceIDAPNSTokenType = @"APNSTokenType"; +static NSString *const kFIRIIDAppReadyToConfigureSDKNotification = + @"FIRAppReadyToConfigureSDKNotification"; +static NSString *const kFIRIIDAppNameKey = @"FIRAppNameKey"; +static NSString *const kFIRIIDErrorDomain = @"com.firebase.instanceid"; +static NSString *const kFIRIIDServiceInstanceID = @"InstanceID"; + +/** + * The APNS token type for the app. If the token type is set to `UNKNOWN` + * InstanceID will implicitly try to figure out what the actual token type + * is from the provisioning profile. + * This must match FIRMessagingAPNSTokenType in FIRMessaging.h + */ +typedef NS_ENUM(NSInteger, FIRInstanceIDAPNSTokenType) { + /// Unknown token type. + FIRInstanceIDAPNSTokenTypeUnknown, + /// Sandbox token type. + FIRInstanceIDAPNSTokenTypeSandbox, + /// Production token type. + FIRInstanceIDAPNSTokenTypeProd, +} NS_SWIFT_NAME(InstanceIDAPNSTokenType); + +@interface FIRInstanceIDResult () +@property(nonatomic, readwrite, copy) NSString *instanceID; +@property(nonatomic, readwrite, copy) NSString *token; +@end + +@interface FIRInstanceID () + +// FIRApp configuration objects. +@property(nonatomic, readwrite, copy) NSString *fcmSenderID; +@property(nonatomic, readwrite, copy) NSString *firebaseAppID; + +// Raw APNS token data +@property(nonatomic, readwrite, strong) NSData *apnsTokenData; + +@property(nonatomic, readwrite) FIRInstanceIDAPNSTokenType apnsTokenType; +// String-based, internal representation of APNS token +@property(nonatomic, readwrite, copy) NSString *APNSTupleString; +// Token fetched from the server automatically for the default app. +@property(nonatomic, readwrite, copy) NSString *defaultFCMToken; + +@property(nonatomic, readwrite, strong) FIRInstanceIDTokenManager *tokenManager; +@property(nonatomic, readwrite, strong) FIRInstallations *installations; + +// backoff and retry for default token +@property(nonatomic, readwrite, assign) NSInteger retryCountForDefaultToken; +@property(atomic, strong, nullable) + FIRInstanceIDCombinedHandler *defaultTokenFetchHandler; + +/// A cached value of FID. Should be used only for `-[FIRInstanceID appInstanceID:]`. +@property(atomic, copy, nullable) NSString *firebaseInstallationsID; + +@end + +// InstanceID doesn't provide any functionality to other components, +// so it provides a private, empty protocol that it conforms to and use it for registration. + +@protocol FIRInstanceIDInstanceProvider +@end + +@interface FIRInstanceID () +@end + +@implementation FIRInstanceIDResult +- (id)copyWithZone:(NSZone *)zone { + FIRInstanceIDResult *result = [[[self class] allocWithZone:zone] init]; + result.instanceID = self.instanceID; + result.token = self.token; + return result; +} +@end + +@implementation FIRInstanceID + +// File static to support InstanceID tests that call [FIRInstanceID instanceID] after +// [FIRInstanceID instanceIDForTests]. +static FIRInstanceID *gInstanceID; + ++ (instancetype)instanceID { + // If the static instance was created, return it. This should only be set in tests and we should + // eventually use proper dependency injection for a better test structure. + if (gInstanceID != nil) { + return gInstanceID; + } + FIRApp *defaultApp = [FIRApp defaultApp]; // Missing configure will be logged here. + FIRInstanceID *instanceID = + (FIRInstanceID *)FIR_COMPONENT(FIRInstanceIDInstanceProvider, defaultApp.container); + return instanceID; +} + +- (instancetype)initPrivately { + self = [super init]; + if (self != nil) { + // Use automatic detection of sandbox, unless otherwise set by developer + _apnsTokenType = FIRInstanceIDAPNSTokenTypeUnknown; + } + return self; +} + ++ (FIRInstanceID *)instanceIDForTests { + gInstanceID = [[FIRInstanceID alloc] initPrivately]; + [gInstanceID start]; + return gInstanceID; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +#pragma mark - Tokens + +- (NSString *)token { + if (!self.fcmSenderID.length) { + return nil; + } + + NSString *cachedToken = [self cachedTokenIfAvailable]; + + if (cachedToken) { + return cachedToken; + } else { + // If we've never had a cached default token, we should fetch one because unrelatedly, + // this request will help us determine whether the locally-generated Instance ID keypair is not + // unique, and therefore generate a new one. + [self defaultTokenWithHandler:nil]; + return nil; + } +} + +- (void)instanceIDWithHandler:(FIRInstanceIDResultHandler)handler { + FIRInstanceID_WEAKIFY(self); + [self getIDWithHandler:^(NSString *identity, NSError *error) { + FIRInstanceID_STRONGIFY(self); + // This is in main queue already + if (error) { + if (handler) { + handler(nil, error); + } + return; + } + FIRInstanceIDResult *result = [[FIRInstanceIDResult alloc] init]; + result.instanceID = identity; + NSString *cachedToken = [self cachedTokenIfAvailable]; + if (cachedToken) { + if (handler) { + result.token = cachedToken; + handler(result, nil); + } + // If no handler, simply return since client has generated iid and token. + return; + } + [self defaultTokenWithHandler:^(NSString *_Nullable token, NSError *_Nullable error) { + if (handler) { + if (error) { + handler(nil, error); + return; + } + result.token = token; + handler(result, nil); + } + }]; + }]; +} + +- (NSString *)cachedTokenIfAvailable { + FIRInstanceIDTokenInfo *cachedTokenInfo = + [self.tokenManager cachedTokenInfoWithAuthorizedEntity:self.fcmSenderID + scope:kFIRInstanceIDDefaultTokenScope]; + return cachedTokenInfo.token; +} + +- (void)setDefaultFCMToken:(NSString *)defaultFCMToken { + if (_defaultFCMToken && defaultFCMToken && [defaultFCMToken isEqualToString:_defaultFCMToken]) { + return; + } + + _defaultFCMToken = defaultFCMToken; + + // Sending this notification out will ensure that FIRMessaging has the updated + // default FCM token. + NSNotification *internalDefaultTokenNotification = + [NSNotification notificationWithName:kFIRInstanceIDDefaultGCMTokenNotification + object:_defaultFCMToken]; + [[NSNotificationQueue defaultQueue] enqueueNotification:internalDefaultTokenNotification + postingStyle:NSPostASAP]; +} + +- (void)tokenWithAuthorizedEntity:(NSString *)authorizedEntity + scope:(NSString *)scope + options:(NSDictionary *)options + handler:(FIRInstanceIDTokenHandler)handler { + if (!handler) { + FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeInstanceID000, + kFIRInstanceIDInvalidNilHandlerError); + return; + } + + // Add internal options + NSMutableDictionary *tokenOptions = [NSMutableDictionary dictionary]; + if (options.count) { + [tokenOptions addEntriesFromDictionary:options]; + } + + NSString *APNSKey = kFIRInstanceIDTokenOptionsAPNSKey; + NSString *serverTypeKey = kFIRInstanceIDTokenOptionsAPNSIsSandboxKey; + if (tokenOptions[APNSKey] != nil && tokenOptions[serverTypeKey] == nil) { + // APNS key was given, but server type is missing. Supply the server type with automatic + // checking. This can happen when the token is requested from FCM, which does not include a + // server type during its request. + tokenOptions[serverTypeKey] = @([self isSandboxApp]); + } + if (self.firebaseAppID) { + tokenOptions[kFIRInstanceIDTokenOptionsFirebaseAppIDKey] = self.firebaseAppID; + } + + // comparing enums to ints directly throws a warning + FIRInstanceIDErrorCode noError = INT_MAX; + FIRInstanceIDErrorCode errorCode = noError; + if (FIRInstanceIDIsValidGCMScope(scope) && !tokenOptions[APNSKey]) { + errorCode = kFIRInstanceIDErrorCodeMissingAPNSToken; + } else if (FIRInstanceIDIsValidGCMScope(scope) && + ![tokenOptions[APNSKey] isKindOfClass:[NSData class]]) { + errorCode = kFIRInstanceIDErrorCodeInvalidRequest; + } else if (![authorizedEntity length]) { + errorCode = kFIRInstanceIDErrorCodeInvalidAuthorizedEntity; + } else if (![scope length]) { + errorCode = kFIRInstanceIDErrorCodeInvalidScope; + } else if (!self.installations) { + errorCode = kFIRInstanceIDErrorCodeInvalidStart; + } + + FIRInstanceIDTokenHandler newHandler = ^(NSString *token, NSError *error) { + dispatch_async(dispatch_get_main_queue(), ^{ + handler(token, error); + }); + }; + + if (errorCode != noError) { + newHandler(nil, [NSError errorWithFIRInstanceIDErrorCode:errorCode]); + return; + } + + FIRInstanceID_WEAKIFY(self); + FIRInstanceIDAuthService *authService = self.tokenManager.authService; + [authService fetchCheckinInfoWithHandler:^(FIRInstanceIDCheckinPreferences *preferences, + NSError *error) { + FIRInstanceID_STRONGIFY(self); + if (error) { + newHandler(nil, error); + return; + } + + FIRInstanceID_WEAKIFY(self); + [self.installations installationIDWithCompletion:^(NSString *_Nullable identifier, + NSError *_Nullable error) { + FIRInstanceID_STRONGIFY(self); + + if (error) { + NSError *newError = + [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeInvalidKeyPair]; + newHandler(nil, newError); + + } else { + FIRInstanceIDTokenInfo *cachedTokenInfo = + [self.tokenManager cachedTokenInfoWithAuthorizedEntity:authorizedEntity scope:scope]; + if (cachedTokenInfo) { + FIRInstanceIDAPNSInfo *optionsAPNSInfo = + [[FIRInstanceIDAPNSInfo alloc] initWithTokenOptionsDictionary:tokenOptions]; + // Check if APNS Info is changed + if ((!cachedTokenInfo.APNSInfo && !optionsAPNSInfo) || + [cachedTokenInfo.APNSInfo isEqualToAPNSInfo:optionsAPNSInfo]) { + // check if token is fresh + if ([cachedTokenInfo isFreshWithIID:identifier]) { + newHandler(cachedTokenInfo.token, nil); + return; + } + } + } + [self.tokenManager fetchNewTokenWithAuthorizedEntity:[authorizedEntity copy] + scope:[scope copy] + instanceID:identifier + options:tokenOptions + handler:newHandler]; + } + }]; + }]; +} + +- (void)deleteTokenWithAuthorizedEntity:(NSString *)authorizedEntity + scope:(NSString *)scope + handler:(FIRInstanceIDDeleteTokenHandler)handler { + if (!handler) { + FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeInstanceID001, + kFIRInstanceIDInvalidNilHandlerError); + } + + // comparing enums to ints directly throws a warning + FIRInstanceIDErrorCode noError = INT_MAX; + FIRInstanceIDErrorCode errorCode = noError; + + if (![authorizedEntity length]) { + errorCode = kFIRInstanceIDErrorCodeInvalidAuthorizedEntity; + } else if (![scope length]) { + errorCode = kFIRInstanceIDErrorCodeInvalidScope; + } else if (!self.installations) { + errorCode = kFIRInstanceIDErrorCodeInvalidStart; + } + + FIRInstanceIDDeleteTokenHandler newHandler = ^(NSError *error) { + // If a default token is deleted successfully, reset the defaultFCMToken too. + if (!error && [authorizedEntity isEqualToString:self.fcmSenderID] && + [scope isEqualToString:kFIRInstanceIDDefaultTokenScope]) { + self.defaultFCMToken = nil; + } + dispatch_async(dispatch_get_main_queue(), ^{ + handler(error); + }); + }; + + if (errorCode != noError) { + newHandler([NSError errorWithFIRInstanceIDErrorCode:errorCode]); + return; + } + + FIRInstanceID_WEAKIFY(self); + FIRInstanceIDAuthService *authService = self.tokenManager.authService; + [authService + fetchCheckinInfoWithHandler:^(FIRInstanceIDCheckinPreferences *preferences, NSError *error) { + FIRInstanceID_STRONGIFY(self); + if (error) { + newHandler(error); + return; + } + + FIRInstanceID_WEAKIFY(self); + [self.installations installationIDWithCompletion:^(NSString *_Nullable identifier, + NSError *_Nullable error) { + FIRInstanceID_STRONGIFY(self); + if (error) { + NSError *newError = + [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeInvalidKeyPair]; + newHandler(newError); + + } else { + [self.tokenManager deleteTokenWithAuthorizedEntity:authorizedEntity + scope:scope + instanceID:identifier + handler:newHandler]; + } + }]; + }]; +} + +#pragma mark - Identity + +- (void)getIDWithHandler:(FIRInstanceIDHandler)handler { + if (!handler) { + FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeInstanceID003, + kFIRInstanceIDInvalidNilHandlerError); + return; + } + + FIRInstanceID_WEAKIFY(self); + [self.installations + installationIDWithCompletion:^(NSString *_Nullable identifier, NSError *_Nullable error) { + FIRInstanceID_STRONGIFY(self); + // When getID is explicitly called, trigger getToken to make sure token always exists. + // This is to avoid ID conflict (ID is not checked for conflict until we generate a token) + if (identifier) { + [self token]; + } + handler(identifier, error); + }]; +} + +- (void)deleteIDWithHandler:(FIRInstanceIDDeleteHandler)handler { + if (!handler) { + FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeInstanceID004, + kFIRInstanceIDInvalidNilHandlerError); + return; + } + + void (^callHandlerOnMainThread)(NSError *) = ^(NSError *error) { + if ([NSThread isMainThread]) { + handler(error); + return; + } + dispatch_async(dispatch_get_main_queue(), ^{ + handler(error); + }); + }; + + if (!self.installations) { + FIRInstanceIDErrorCode error = kFIRInstanceIDErrorCodeInvalidStart; + callHandlerOnMainThread([NSError errorWithFIRInstanceIDErrorCode:error]); + return; + } + + FIRInstanceID_WEAKIFY(self); + void (^deleteTokensHandler)(NSError *) = ^void(NSError *error) { + FIRInstanceID_STRONGIFY(self); + if (error) { + callHandlerOnMainThread(error); + return; + } + [self deleteIdentityWithHandler:^(NSError *error) { + callHandlerOnMainThread(error); + }]; + }; + + [self.installations + installationIDWithCompletion:^(NSString *_Nullable identifier, NSError *_Nullable error) { + FIRInstanceID_STRONGIFY(self); + if (error) { + NSError *newError = + [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeInvalidKeyPair]; + callHandlerOnMainThread(newError); + } else { + [self.tokenManager deleteAllTokensWithInstanceID:identifier handler:deleteTokensHandler]; + } + }]; +} + +- (void)notifyIdentityReset { + [self deleteIdentityWithHandler:nil]; +} + +// Delete all the local cache checkin, IID and token. +- (void)deleteIdentityWithHandler:(FIRInstanceIDDeleteHandler)handler { + // Delete tokens. + [self.tokenManager deleteAllTokensLocallyWithHandler:^(NSError *deleteTokenError) { + // Reset FCM token. + self.defaultFCMToken = nil; + if (deleteTokenError) { + if (handler) { + handler(deleteTokenError); + } + return; + } + + // Delete Instance ID. + [self.installations deleteWithCompletion:^(NSError *_Nullable error) { + if (error) { + if (handler) { + handler(error); + } + return; + } + + [self.tokenManager.authService resetCheckinWithHandler:^(NSError *error) { + if (error) { + if (handler) { + handler(error); + } + return; + } + // Only request new token if FCM auto initialization is + // enabled. + if ([self isFCMAutoInitEnabled]) { + // Deletion succeeds! Requesting new checkin, IID and token. + // TODO(chliangGoogle) see if dispatch_after is necessary + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + [self defaultTokenWithHandler:nil]; + }); + } + if (handler) { + handler(nil); + } + }]; + }]; + }]; +} + +#pragma mark - Checkin + +- (BOOL)tryToLoadValidCheckinInfo { + FIRInstanceIDCheckinPreferences *checkinPreferences = + [self.tokenManager.authService checkinPreferences]; + return [checkinPreferences hasValidCheckinInfo]; +} + +- (NSString *)deviceAuthID { + return [self.tokenManager.authService checkinPreferences].deviceID; +} + +- (NSString *)secretToken { + return [self.tokenManager.authService checkinPreferences].secretToken; +} + +- (NSString *)versionInfo { + return [self.tokenManager.authService checkinPreferences].versionInfo; +} + +#pragma mark - Config + ++ (void)load { + [FIRApp registerInternalLibrary:(Class)self + withName:@"fire-iid" + withVersion:FIRInstanceIDCurrentLibraryVersion()]; +} + ++ (nonnull NSArray *)componentsToRegister { + FIRComponentCreationBlock creationBlock = + ^id _Nullable(FIRComponentContainer *container, BOOL *isCacheable) { + // InstanceID only works with the default app. + if (!container.app.isDefaultApp) { + // Only configure for the default FIRApp. + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeFIRApp002, + @"Firebase Instance ID only works with the default app."); + return nil; + } + + // Ensure it's cached so it returns the same instance every time instanceID is called. + *isCacheable = YES; + FIRInstanceID *instanceID = [[FIRInstanceID alloc] initPrivately]; + [instanceID start]; + [instanceID configureInstanceIDWithOptions:container.app.options]; + return instanceID; + }; + FIRComponent *instanceIDProvider = + [FIRComponent componentWithProtocol:@protocol(FIRInstanceIDInstanceProvider) + instantiationTiming:FIRInstantiationTimingEagerInDefaultApp + dependencies:@[] + creationBlock:creationBlock]; + return @[ instanceIDProvider ]; +} + +- (void)configureInstanceIDWithOptions:(FIROptions *)options { + NSString *GCMSenderID = options.GCMSenderID; + if (!GCMSenderID.length) { + FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeFIRApp000, + @"Firebase not set up correctly, nil or empty senderID."); + [NSException raise:kFIRIIDErrorDomain + format:@"Could not configure Firebase InstanceID. GCMSenderID must not be nil or " + @"empty."]; + } + + self.fcmSenderID = GCMSenderID; + self.firebaseAppID = options.googleAppID; + + [self updateFirebaseInstallationID]; + + // FCM generates a FCM token during app start for sending push notification to device. + // This is not needed for app extension except for watch. +#if TARGET_OS_WATCH + [self didCompleteConfigure]; +#else + if (![GULAppEnvironmentUtil isAppExtension]) { + [self didCompleteConfigure]; + } +#endif +} + +// This is used to start any operations when we receive FirebaseSDK setup notification +// from FIRCore. +- (void)didCompleteConfigure { + NSString *cachedToken = [self cachedTokenIfAvailable]; + // When there is a cached token, do the token refresh. + if (cachedToken) { + // Clean up expired tokens by checking the token refresh policy. + [self.installations + installationIDWithCompletion:^(NSString *_Nullable identifier, NSError *_Nullable error) { + if ([self.tokenManager checkTokenRefreshPolicyWithIID:identifier]) { + // Default token is expired, fetch default token from server. + [self defaultTokenWithHandler:nil]; + } + // Notify FCM with the default token. + self.defaultFCMToken = [self token]; + }]; + } else if ([self isFCMAutoInitEnabled]) { + // When there is no cached token, must check auto init is enabled. + // If it's disabled, don't initiate token generation/refresh. + // If no cache token and auto init is enabled, fetch a token from server. + [self defaultTokenWithHandler:nil]; + // Notify FCM with the default token. + self.defaultFCMToken = [self token]; + } + // ONLY checkin when auto data collection is turned on. + if ([self isFCMAutoInitEnabled]) { + [self.tokenManager.authService scheduleCheckin:YES]; + } +} + +- (BOOL)isFCMAutoInitEnabled { + Class messagingClass = NSClassFromString(kFIRInstanceIDFCMSDKClassString); + // Firebase Messaging is not installed, auto init should be disabled since it's for FCM. + if (!messagingClass) { + return NO; + } + + // Messaging doesn't have the class method, auto init should be enabled since FCM exists. + SEL autoInitSelector = NSSelectorFromString(kFIRInstanceIDFCMSelectorAutoInitEnabled); + if (![messagingClass respondsToSelector:autoInitSelector]) { + return YES; + } + + // Get the autoInitEnabled class method. + IMP isAutoInitEnabledIMP = [messagingClass methodForSelector:autoInitSelector]; + BOOL(*isAutoInitEnabled) + (Class, SEL, GULUserDefaults *) = (BOOL(*)(id, SEL, GULUserDefaults *))isAutoInitEnabledIMP; + + // Check FCM's isAutoInitEnabled property. + return isAutoInitEnabled(messagingClass, autoInitSelector, + [GULUserDefaults standardUserDefaults]); +} + +// Actually makes InstanceID instantiate both the IID and Token-related subsystems. +- (void)start { + if (![FIRInstanceIDStore hasSubDirectory:kFIRInstanceIDSubDirectoryName]) { + [FIRInstanceIDStore createSubDirectory:kFIRInstanceIDSubDirectoryName]; + } + + [self setupTokenManager]; + self.installations = [FIRInstallations installations]; + [self setupNotificationListeners]; +} + +// Creates the token manager, which is used for fetching, caching, and retrieving tokens. +- (void)setupTokenManager { + self.tokenManager = [[FIRInstanceIDTokenManager alloc] init]; +} + +- (void)setupNotificationListeners { + // To prevent double notifications remove observer from all events during setup. + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center removeObserver:self]; + [center addObserver:self + selector:@selector(notifyIdentityReset) + name:kFIRInstanceIDIdentityInvalidatedNotification + object:nil]; + [center addObserver:self + selector:@selector(notifyAPNSTokenIsSet:) + name:kFIRInstanceIDAPNSTokenNotification + object:nil]; + [self observeFirebaseInstallationIDChanges]; +} + +#pragma mark - Private Helpers +/// Maximum retry count to fetch the default token. ++ (int64_t)maxRetryCountForDefaultToken { + return kMaxRetryCountForDefaultToken; +} + +/// Minimum interval in seconds between retries to fetch the default token. ++ (int64_t)minIntervalForDefaultTokenRetry { + return kMinRetryIntervalForDefaultTokenInSeconds; +} + +/// Maximum retry interval between retries to fetch default token. ++ (int64_t)maxRetryIntervalForDefaultTokenInSeconds { + return kMaxRetryIntervalForDefaultTokenInSeconds; +} + +- (NSInteger)retryIntervalToFetchDefaultToken { + if (self.retryCountForDefaultToken >= [[self class] maxRetryCountForDefaultToken]) { + return (NSInteger)[[self class] maxRetryIntervalForDefaultTokenInSeconds]; + } + // exponential backoff with a fixed initial retry time + // 11s, 22s, 44s, 88s ... + int64_t minInterval = [[self class] minIntervalForDefaultTokenRetry]; + return (NSInteger)MIN( + (1 << self.retryCountForDefaultToken) + minInterval * self.retryCountForDefaultToken, + kMaxRetryIntervalForDefaultTokenInSeconds); +} + +- (void)defaultTokenWithHandler:(nullable FIRInstanceIDTokenHandler)aHandler { + [self defaultTokenWithRetry:NO handler:aHandler]; +} + +/** + * @param retry Indicates if the method is called to perform a retry after a failed attempt. + * If `YES`, then actual token request will be performed even if `self.defaultTokenFetchHandler != + * nil` + */ +- (void)defaultTokenWithRetry:(BOOL)retry handler:(nullable FIRInstanceIDTokenHandler)aHandler { + BOOL shouldPerformRequest = retry || self.defaultTokenFetchHandler == nil; + + if (!self.defaultTokenFetchHandler) { + self.defaultTokenFetchHandler = [[FIRInstanceIDCombinedHandler alloc] init]; + } + + if (aHandler) { + [self.defaultTokenFetchHandler addHandler:aHandler]; + } + + if (!shouldPerformRequest) { + return; + } + + NSDictionary *instanceIDOptions = @{}; + BOOL hasFirebaseMessaging = NSClassFromString(kFIRInstanceIDFCMSDKClassString) != nil; + if (hasFirebaseMessaging && self.apnsTokenData) { + BOOL isSandboxApp = (self.apnsTokenType == FIRInstanceIDAPNSTokenTypeSandbox); + if (self.apnsTokenType == FIRInstanceIDAPNSTokenTypeUnknown) { + isSandboxApp = [self isSandboxApp]; + } + instanceIDOptions = @{ + kFIRInstanceIDTokenOptionsAPNSKey : self.apnsTokenData, + kFIRInstanceIDTokenOptionsAPNSIsSandboxKey : @(isSandboxApp), + }; + } + + FIRInstanceID_WEAKIFY(self); + FIRInstanceIDTokenHandler newHandler = ^void(NSString *token, NSError *error) { + FIRInstanceID_STRONGIFY(self); + + if (error) { + FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeInstanceID009, + @"Failed to fetch default token %@", error); + + // This notification can be sent multiple times since we can't guarantee success at any point + // of time. + NSNotification *tokenFetchFailNotification = + [NSNotification notificationWithName:kFIRInstanceIDDefaultGCMTokenFailNotification + object:[error copy]]; + [[NSNotificationQueue defaultQueue] enqueueNotification:tokenFetchFailNotification + postingStyle:NSPostASAP]; + + self.retryCountForDefaultToken = (NSInteger)MIN(self.retryCountForDefaultToken + 1, + [[self class] maxRetryCountForDefaultToken]); + + // Do not retry beyond the maximum limit. + if (self.retryCountForDefaultToken < [[self class] maxRetryCountForDefaultToken]) { + NSInteger retryInterval = [self retryIntervalToFetchDefaultToken]; + [self retryGetDefaultTokenAfter:retryInterval]; + } else { + FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeInstanceID007, + @"Failed to retrieve the default FCM token after %ld retries", + (long)self.retryCountForDefaultToken); + [self performDefaultTokenHandlerWithToken:nil error:error]; + } + } else { + // If somebody updated IID with APNS token while our initial request did not have it + // set we need to update it on the server. + NSData *deviceTokenInRequest = instanceIDOptions[kFIRInstanceIDTokenOptionsAPNSKey]; + BOOL isSandboxInRequest = + [instanceIDOptions[kFIRInstanceIDTokenOptionsAPNSIsSandboxKey] boolValue]; + // Note that APNSTupleStringInRequest will be nil if deviceTokenInRequest is nil + NSString *APNSTupleStringInRequest = FIRInstanceIDAPNSTupleStringForTokenAndServerType( + deviceTokenInRequest, isSandboxInRequest); + // If the APNs value either remained nil, or was the same non-nil value, the APNs value + // did not change. + BOOL APNSRemainedSameDuringFetch = + (self.APNSTupleString == nil && APNSTupleStringInRequest == nil) || + ([self.APNSTupleString isEqualToString:APNSTupleStringInRequest]); + if (!APNSRemainedSameDuringFetch && hasFirebaseMessaging) { + // APNs value did change mid-fetch, so the token should be re-fetched with the current APNs + // value. + [self retryGetDefaultTokenAfter:0]; + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeRefetchingTokenForAPNS, + @"Received APNS token while fetching default token. " + @"Refetching default token."); + // Do not notify and handle completion handler since this is a retry. + // Simply return. + return; + } else { + FIRInstanceIDLoggerInfo(kFIRInstanceIDMessageCodeInstanceID010, + @"Successfully fetched default token."); + } + // Post the required notifications if somebody is waiting. + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeInstanceID008, @"Got default token %@", + token); + NSString *previousFCMToken = self.defaultFCMToken; + self.defaultFCMToken = token; + + // Only notify of token refresh if we have a new valid token that's different than before + if (self.defaultFCMToken.length && ![self.defaultFCMToken isEqualToString:previousFCMToken]) { + NSNotification *tokenRefreshNotification = + [NSNotification notificationWithName:kFIRInstanceIDTokenRefreshNotification + object:[self.defaultFCMToken copy]]; + [[NSNotificationQueue defaultQueue] enqueueNotification:tokenRefreshNotification + postingStyle:NSPostASAP]; + } + + [self performDefaultTokenHandlerWithToken:token error:nil]; + } + }; + + [self tokenWithAuthorizedEntity:self.fcmSenderID + scope:kFIRInstanceIDDefaultTokenScope + options:instanceIDOptions + handler:newHandler]; +} + +/** + * + */ +- (void)performDefaultTokenHandlerWithToken:(NSString *)token error:(NSError *)error { + if (!self.defaultTokenFetchHandler) { + return; + } + + [self.defaultTokenFetchHandler combinedHandler](token, error); + self.defaultTokenFetchHandler = nil; +} + +- (void)retryGetDefaultTokenAfter:(NSTimeInterval)retryInterval { + FIRInstanceID_WEAKIFY(self); + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(retryInterval * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + FIRInstanceID_STRONGIFY(self); + // Pass nil: no new handlers to be added, currently existing handlers + // will be called + [self defaultTokenWithRetry:YES handler:nil]; + }); +} + +#pragma mark - APNS Token +// This should only be triggered from FCM. +- (void)notifyAPNSTokenIsSet:(NSNotification *)notification { + NSData *token = notification.object; + if (!token || ![token isKindOfClass:[NSData class]]) { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeInternal002, @"Invalid APNS token type %@", + NSStringFromClass([notification.object class])); + return; + } + NSInteger type = [notification.userInfo[kFIRInstanceIDAPNSTokenType] integerValue]; + + // The APNS token is being added, or has changed (rare) + if ([self.apnsTokenData isEqualToData:token]) { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeInstanceID011, + @"Trying to reset APNS token to the same value. Will return"); + return; + } + // Use this token type for when we have to automatically fetch tokens in the future + self.apnsTokenType = type; + BOOL isSandboxApp = (type == FIRInstanceIDAPNSTokenTypeSandbox); + if (self.apnsTokenType == FIRInstanceIDAPNSTokenTypeUnknown) { + isSandboxApp = [self isSandboxApp]; + } + self.apnsTokenData = [token copy]; + self.APNSTupleString = FIRInstanceIDAPNSTupleStringForTokenAndServerType(token, isSandboxApp); + + // Pro-actively invalidate the default token, if the APNs change makes it + // invalid. Previously, we invalidated just before fetching the token. + NSArray *invalidatedTokens = + [self.tokenManager updateTokensToAPNSDeviceToken:self.apnsTokenData isSandbox:isSandboxApp]; + + // Re-fetch any invalidated tokens automatically, this time with the current APNs token, so that + // they are up-to-date. + if (invalidatedTokens.count > 0) { + FIRInstanceID_WEAKIFY(self); + + [self.installations + installationIDWithCompletion:^(NSString *_Nullable identifier, NSError *_Nullable error) { + FIRInstanceID_STRONGIFY(self); + if (self == nil) { + FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeInstanceID017, + @"Instance ID shut down during token reset. Aborting"); + return; + } + if (self.apnsTokenData == nil) { + FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeInstanceID018, + @"apnsTokenData was set to nil during token reset. Aborting"); + return; + } + + NSMutableDictionary *tokenOptions = [@{ + kFIRInstanceIDTokenOptionsAPNSKey : self.apnsTokenData, + kFIRInstanceIDTokenOptionsAPNSIsSandboxKey : @(isSandboxApp) + } mutableCopy]; + if (self.firebaseAppID) { + tokenOptions[kFIRInstanceIDTokenOptionsFirebaseAppIDKey] = self.firebaseAppID; + } + + for (FIRInstanceIDTokenInfo *tokenInfo in invalidatedTokens) { + if ([tokenInfo.token isEqualToString:self.defaultFCMToken]) { + // We will perform a special fetch for the default FCM token, so that the delegate + // methods are called. For all others, we will do an internal re-fetch. + [self defaultTokenWithHandler:nil]; + } else { + [self.tokenManager fetchNewTokenWithAuthorizedEntity:tokenInfo.authorizedEntity + scope:tokenInfo.scope + instanceID:identifier + options:tokenOptions + handler:^(NSString *_Nullable token, + NSError *_Nullable error){ + + }]; + } + } + }]; + } +} + +- (BOOL)isSandboxApp { + static BOOL isSandboxApp = YES; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + isSandboxApp = ![self isProductionApp]; + }); + return isSandboxApp; +} + +- (BOOL)isProductionApp { + const BOOL defaultAppTypeProd = YES; + + NSError *error = nil; + if ([GULAppEnvironmentUtil isSimulator]) { + [self logAPNSConfigurationError:@"Running InstanceID on a simulator doesn't have APNS. " + @"Use prod profile by default."]; + return defaultAppTypeProd; + } + + if ([GULAppEnvironmentUtil isFromAppStore]) { + // Apps distributed via AppStore or TestFlight use the Production APNS certificates. + return defaultAppTypeProd; + } +#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH + NSString *path = [[[NSBundle mainBundle] bundlePath] + stringByAppendingPathComponent:@"embedded.mobileprovision"]; +#elif TARGET_OS_OSX + NSString *path = [[[[NSBundle mainBundle] resourcePath] stringByDeletingLastPathComponent] + stringByAppendingPathComponent:@"embedded.provisionprofile"]; +#endif + + if ([GULAppEnvironmentUtil isAppStoreReceiptSandbox] && !path.length) { + // Distributed via TestFlight + return defaultAppTypeProd; + } + + NSMutableData *profileData = [NSMutableData dataWithContentsOfFile:path options:0 error:&error]; + + if (!profileData.length || error) { + NSString *errorString = + [NSString stringWithFormat:@"Error while reading embedded mobileprovision %@", error]; + [self logAPNSConfigurationError:errorString]; + return defaultAppTypeProd; + } + + // The "embedded.mobileprovision" sometimes contains characters with value 0, which signals the + // end of a c-string and halts the ASCII parser, or with value > 127, which violates strict 7-bit + // ASCII. Replace any 0s or invalid characters in the input. + uint8_t *profileBytes = (uint8_t *)profileData.bytes; + for (int i = 0; i < profileData.length; i++) { + uint8_t currentByte = profileBytes[i]; + if (!currentByte || currentByte > 127) { + profileBytes[i] = '.'; + } + } + + NSString *embeddedProfile = [[NSString alloc] initWithBytesNoCopy:profileBytes + length:profileData.length + encoding:NSASCIIStringEncoding + freeWhenDone:NO]; + + if (error || !embeddedProfile.length) { + NSString *errorString = + [NSString stringWithFormat:@"Error while reading embedded mobileprovision %@", error]; + [self logAPNSConfigurationError:errorString]; + return defaultAppTypeProd; + } + + NSScanner *scanner = [NSScanner scannerWithString:embeddedProfile]; + NSString *plistContents; + if ([scanner scanUpToString:@"" intoString:&plistContents]) { + plistContents = [plistContents stringByAppendingString:@""]; + } + } + + if (!plistContents.length) { + return defaultAppTypeProd; + } + + NSData *data = [plistContents dataUsingEncoding:NSUTF8StringEncoding]; + if (!data.length) { + [self logAPNSConfigurationError:@"Couldn't read plist fetched from embedded mobileprovision"]; + return defaultAppTypeProd; + } + + NSError *plistMapError; + id plistData = [NSPropertyListSerialization propertyListWithData:data + options:NSPropertyListImmutable + format:nil + error:&plistMapError]; + if (plistMapError || ![plistData isKindOfClass:[NSDictionary class]]) { + NSString *errorString = + [NSString stringWithFormat:@"Error while converting assumed plist to dict %@", + plistMapError.localizedDescription]; + [self logAPNSConfigurationError:errorString]; + return defaultAppTypeProd; + } + NSDictionary *plistMap = (NSDictionary *)plistData; + + if ([plistMap valueForKeyPath:@"ProvisionedDevices"]) { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeInstanceID012, + @"Provisioning profile has specifically provisioned devices, " + @"most likely a Dev profile."); + } + + NSString *apsEnvironment = [plistMap valueForKeyPath:kEntitlementsAPSEnvironmentKey]; + NSString *debugString __unused = + [NSString stringWithFormat:@"APNS Environment in profile: %@", apsEnvironment]; + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeInstanceID013, @"%@", debugString); + + // No aps-environment in the profile. + if (!apsEnvironment.length) { + [self logAPNSConfigurationError:@"No aps-environment set. If testing on a device APNS is not " + @"correctly configured. Please recheck your provisioning " + @"profiles. If testing on a simulator this is fine since APNS " + @"doesn't work on the simulator."]; + return defaultAppTypeProd; + } + + if ([apsEnvironment isEqualToString:kAPSEnvironmentDevelopmentValue]) { + return NO; + } + + return defaultAppTypeProd; +} + +/// Log error messages only when Messaging exists in the pod. +- (void)logAPNSConfigurationError:(NSString *)errorString { + BOOL hasFirebaseMessaging = NSClassFromString(kFIRInstanceIDFCMSDKClassString) != nil; + if (hasFirebaseMessaging) { + FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeInstanceID014, @"%@", errorString); + } else { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeInstanceID015, @"%@", errorString); + } +} + +#pragma mark - Sync InstanceID + +- (void)updateFirebaseInstallationID { + FIRInstanceID_WEAKIFY(self); + [self.installations + installationIDWithCompletion:^(NSString *_Nullable installationID, NSError *_Nullable error) { + FIRInstanceID_STRONGIFY(self); + self.firebaseInstallationsID = installationID; + }]; +} + +- (void)installationIDDidChangeNotificationReceived:(NSNotification *)notification { + NSString *installationAppID = + notification.userInfo[kFIRInstallationIDDidChangeNotificationAppNameKey]; + if ([installationAppID isKindOfClass:[NSString class]] && + [installationAppID isEqual:self.firebaseAppID]) { + [self updateFirebaseInstallationID]; + } +} + +- (void)observeFirebaseInstallationIDChanges { + [[NSNotificationCenter defaultCenter] removeObserver:self + name:FIRInstallationIDDidChangeNotification + object:nil]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(installationIDDidChangeNotificationReceived:) + name:FIRInstallationIDDidChangeNotification + object:nil]; +} + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDAPNSInfo.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDAPNSInfo.h new file mode 100644 index 0000000..92b2469 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDAPNSInfo.h @@ -0,0 +1,64 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * Represents an APNS device token and whether its environment is for sandbox. + * It can read from and write to an NSDictionary for simple serialization. + */ +@interface FIRInstanceIDAPNSInfo : NSObject + +/// The APNs device token, provided by the OS to the application delegate +@property(nonatomic, readonly, strong) NSData *deviceToken; +/// Represents whether or not this is deviceToken is for the sandbox +/// environment, or production. +@property(nonatomic, readonly, getter=isSandbox) BOOL sandbox; + +/** + * Initializes the receiver with an APNs device token, and boolean + * representing whether that token is for the sandbox environment. + * + * @param deviceToken The APNs device token typically provided by the + * operating system. + * @param isSandbox YES if the APNs device token is for the sandbox + * environment, or NO if it is for production. + * @return An instance of FIRInstanceIDAPNSInfo. + */ +- (instancetype)initWithDeviceToken:(NSData *)deviceToken isSandbox:(BOOL)isSandbox; + +/** + * Initializes the receiver from a token options dictionary containing data + * within the `kFIRInstanceIDTokenOptionsAPNSKey` and + * `kFIRInstanceIDTokenOptionsAPNSIsSandboxKey` keys. The token should be an + * NSData blob, and the sandbox value should be an NSNumber + * representing a boolean value. + * + * @param dictionary A dictionary containing values under the keys + * `kFIRInstanceIDTokenOptionsAPNSKey` and + * `kFIRInstanceIDTokenOptionsAPNSIsSandboxKey`. + * @return An instance of FIRInstanceIDAPNSInfo, or nil if the + * dictionary data was invalid or missing. + */ +- (nullable instancetype)initWithTokenOptionsDictionary:(NSDictionary *)dictionary; + +- (BOOL)isEqualToAPNSInfo:(FIRInstanceIDAPNSInfo *)otherInfo; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDAPNSInfo.m b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDAPNSInfo.m new file mode 100644 index 0000000..d1f9d08 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDAPNSInfo.m @@ -0,0 +1,79 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceIDAPNSInfo.h" + +#import "FIRInstanceIDConstants.h" + +/// The key used to find the APNs device token in an archive. +NSString *const kFIRInstanceIDAPNSInfoTokenKey = @"device_token"; +/// The key used to find the sandbox value in an archive. +NSString *const kFIRInstanceIDAPNSInfoSandboxKey = @"sandbox"; + +@implementation FIRInstanceIDAPNSInfo + +- (instancetype)initWithDeviceToken:(NSData *)deviceToken isSandbox:(BOOL)isSandbox { + self = [super init]; + if (self) { + _deviceToken = [deviceToken copy]; + _sandbox = isSandbox; + } + return self; +} + +- (instancetype)initWithTokenOptionsDictionary:(NSDictionary *)dictionary { + id deviceToken = dictionary[kFIRInstanceIDTokenOptionsAPNSKey]; + if (![deviceToken isKindOfClass:[NSData class]]) { + return nil; + } + + id isSandbox = dictionary[kFIRInstanceIDTokenOptionsAPNSIsSandboxKey]; + if (![isSandbox isKindOfClass:[NSNumber class]]) { + return nil; + } + self = [super init]; + if (self) { + _deviceToken = (NSData *)deviceToken; + _sandbox = ((NSNumber *)isSandbox).boolValue; + } + return self; +} + +#pragma mark - NSCoding + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + id deviceToken = [aDecoder decodeObjectForKey:kFIRInstanceIDAPNSInfoTokenKey]; + if (![deviceToken isKindOfClass:[NSData class]]) { + return nil; + } + BOOL isSandbox = [aDecoder decodeBoolForKey:kFIRInstanceIDAPNSInfoSandboxKey]; + return [self initWithDeviceToken:(NSData *)deviceToken isSandbox:isSandbox]; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:self.deviceToken forKey:kFIRInstanceIDAPNSInfoTokenKey]; + [aCoder encodeBool:self.sandbox forKey:kFIRInstanceIDAPNSInfoSandboxKey]; +} + +- (BOOL)isEqualToAPNSInfo:(FIRInstanceIDAPNSInfo *)otherInfo { + if ([super isEqual:otherInfo]) { + return YES; + } + return ([self.deviceToken isEqualToData:otherInfo.deviceToken] && + self.isSandbox == otherInfo.isSandbox); +} + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDAuthKeyChain.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDAuthKeyChain.h new file mode 100644 index 0000000..8d453b8 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDAuthKeyChain.h @@ -0,0 +1,95 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +extern NSString *__nonnull const kFIRInstanceIDKeychainWildcardIdentifier; + +NS_ASSUME_NONNULL_BEGIN + +/** + * Wrapper around storing FCM auth data in iOS keychain. + */ +@interface FIRInstanceIDAuthKeychain : NSObject + +/** + * Designated Initializer. Init a generic `SecClassGenericPassword` keychain with `identifier` + * as the `kSecAttrGeneric`. + * + * @param identifier The generic attribute to be used by the keychain. + * + * @return A Keychain object with `kSecAttrGeneric` attribute set to identifier. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier; + +/** + * Get keychain items matching the given service and account. The service and/or account + * can be a wildcard (`kFIRInstanceIDKeychainWildcardIdentifier`), which case the query + * will include all items matching any services and/or accounts. + * + * @param service The kSecAttrService used to save the password. Can be wildcard. + * @param account The kSecAttrAccount used to save the password. Can be wildcard. + * + * @return An array of |NSData|s matching the provided inputs. + */ +- (NSArray *)itemsMatchingService:(NSString *)service account:(NSString *)account; + +/** + * Get keychain item for a given service and account. + * + * @param service The kSecAttrService used to save the password. + * @param account The kSecAttrAccount used to save the password. + * + * @return A cached keychain item for a given account and service, or nil if it was not + * found or could not be retrieved. + */ +- (NSData *)dataForService:(NSString *)service account:(NSString *)account; + +/** + * Remove the cached items from the keychain matching the service, account and access group. + * In case the items do not exist, YES is returned but with a valid error object with code + * `errSecItemNotFound`. + * + * @param service The kSecAttrService used to save the password. + * @param account The kSecAttrAccount used to save the password. + * @param handler The callback handler which is invoked when the remove operation is complete, with + * an error if there is any. + */ +- (void)removeItemsMatchingService:(NSString *)service + account:(NSString *)account + handler:(nullable void (^)(NSError *error))handler; + +/** + * Set the data for a given service and account. + * We use `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly` which + * prevents backup and restore to iCloud, and works for app extension that can + * execute right after a device is restarted (and not unlocked). + * + * @param data The data to save. + * @param service The `kSecAttrService` used to save the password. + * @param account The `kSecAttrAccount` used to save the password. + * @param handler The callback handler which is invoked when the add operation is complete, + * with an error if there is any. + * + */ +- (void)setData:(NSData *)data + forService:(NSString *)service + account:(NSString *)account + handler:(nullable void (^)(NSError *))handler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDAuthKeyChain.m b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDAuthKeyChain.m new file mode 100644 index 0000000..dfce2f7 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDAuthKeyChain.m @@ -0,0 +1,216 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceIDAuthKeyChain.h" +#import "FIRInstanceIDKeychain.h" +#import "FIRInstanceIDLogger.h" + +/** + * The error type representing why we couldn't read data from the keychain. + */ +typedef NS_ENUM(int, FIRInstanceIDKeychainErrorType) { + kFIRInstanceIDKeychainErrorBadArguments = -1301, +}; + +NSString *const kFIRInstanceIDKeychainWildcardIdentifier = @"*"; + +@interface FIRInstanceIDAuthKeychain () + +@property(nonatomic, copy) NSString *generic; +// cachedKeychainData is keyed by service and account, the value is an array of NSData. +// It is used to cache the tokens per service, per account, as well as checkin data per service, +// per account inside the keychain. +@property(nonatomic) + NSMutableDictionary *> *> + *cachedKeychainData; + +@end + +@implementation FIRInstanceIDAuthKeychain + +- (instancetype)initWithIdentifier:(NSString *)identifier { + self = [super init]; + if (self) { + _generic = [identifier copy]; + _cachedKeychainData = [[NSMutableDictionary alloc] init]; + } + return self; +} + ++ (NSMutableDictionary *)keychainQueryForService:(NSString *)service + account:(NSString *)account + generic:(NSString *)generic { + NSDictionary *query = @{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword}; + + NSMutableDictionary *finalQuery = [NSMutableDictionary dictionaryWithDictionary:query]; + if ([generic length] && ![kFIRInstanceIDKeychainWildcardIdentifier isEqualToString:generic]) { + finalQuery[(__bridge NSString *)kSecAttrGeneric] = generic; + } + if ([account length] && ![kFIRInstanceIDKeychainWildcardIdentifier isEqualToString:account]) { + finalQuery[(__bridge NSString *)kSecAttrAccount] = account; + } + if ([service length] && ![kFIRInstanceIDKeychainWildcardIdentifier isEqualToString:service]) { + finalQuery[(__bridge NSString *)kSecAttrService] = service; + } + return finalQuery; +} + +- (NSMutableDictionary *)keychainQueryForService:(NSString *)service account:(NSString *)account { + return [[self class] keychainQueryForService:service account:account generic:self.generic]; +} + +- (NSArray *)itemsMatchingService:(NSString *)service account:(NSString *)account { + // If query wildcard service, it asks for all the results, which always query from keychain. + if (![service isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier] && + ![account isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier] && + _cachedKeychainData[service][account]) { + // As long as service, account array exist, even it's empty, it means we've queried it before, + // returns the cache value. + return _cachedKeychainData[service][account]; + } + + NSMutableDictionary *keychainQuery = [self keychainQueryForService:service account:account]; + NSMutableArray *results; + keychainQuery[(__bridge id)kSecReturnData] = (__bridge id)kCFBooleanTrue; +#if TARGET_OS_IOS || TARGET_OS_TV + keychainQuery[(__bridge id)kSecReturnAttributes] = (__bridge id)kCFBooleanTrue; + keychainQuery[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll; + // FIRInstanceIDKeychain should only take a query and return a result, will handle the query here. + NSArray *passwordInfos = + CFBridgingRelease([[FIRInstanceIDKeychain sharedInstance] itemWithQuery:keychainQuery]); +#elif TARGET_OS_OSX || TARGET_OS_WATCH + keychainQuery[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne; + NSData *passwordInfos = + CFBridgingRelease([[FIRInstanceIDKeychain sharedInstance] itemWithQuery:keychainQuery]); +#endif + + if (!passwordInfos) { + // Nothing was found, simply return from this sync block. + // Make sure to label the cache entry empty, signaling that we've queried this entry. + if ([service isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier] || + [account isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier]) { + // Do not update cache if it's wildcard query. + return @[]; + } else if (_cachedKeychainData[service]) { + [_cachedKeychainData[service] setObject:@[] forKey:account]; + } else { + [_cachedKeychainData setObject:[@{account : @[]} mutableCopy] forKey:service]; + } + return @[]; + } + results = [[NSMutableArray alloc] init]; +#if TARGET_OS_IOS || TARGET_OS_TV + NSInteger numPasswords = passwordInfos.count; + for (NSUInteger i = 0; i < numPasswords; i++) { + NSDictionary *passwordInfo = [passwordInfos objectAtIndex:i]; + if (passwordInfo[(__bridge id)kSecValueData]) { + [results addObject:passwordInfo[(__bridge id)kSecValueData]]; + } + } +#elif TARGET_OS_OSX || TARGET_OS_WATCH + [results addObject:passwordInfos]; +#endif + // We query the keychain because it didn't exist in cache, now query is done, update the result in + // the cache. + if ([service isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier] || + [account isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier]) { + // Do not update cache if it's wildcard query. + return [results copy]; + } else if (_cachedKeychainData[service]) { + [_cachedKeychainData[service] setObject:[results copy] forKey:account]; + } else { + NSMutableDictionary *entry = [@{account : [results copy]} mutableCopy]; + [_cachedKeychainData setObject:entry forKey:service]; + } + return [results copy]; +} + +- (NSData *)dataForService:(NSString *)service account:(NSString *)account { + NSArray *items = [self itemsMatchingService:service account:account]; + // If items is nil or empty, nil will be returned. + return items.firstObject; +} + +- (void)removeItemsMatchingService:(NSString *)service + account:(NSString *)account + handler:(void (^)(NSError *error))handler { + if ([service isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier]) { + // Delete all keychain items. + _cachedKeychainData = [[NSMutableDictionary alloc] init]; + } else if ([account isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier]) { + // Delete all entries under service, + if (_cachedKeychainData[service]) { + _cachedKeychainData[service] = [[NSMutableDictionary alloc] init]; + } + } else if (_cachedKeychainData[service]) { + // We should keep the service/account entry instead of nil so we know + // it's "empty entry" instead of "not query from keychain yet". + [_cachedKeychainData[service] setObject:@[] forKey:account]; + } else { + [_cachedKeychainData setObject:[@{account : @[]} mutableCopy] forKey:service]; + } + NSMutableDictionary *keychainQuery = [self keychainQueryForService:service account:account]; + [[FIRInstanceIDKeychain sharedInstance] removeItemWithQuery:keychainQuery handler:handler]; +} + +- (void)setData:(NSData *)data + forService:(NSString *)service + account:(NSString *)account + handler:(void (^)(NSError *))handler { + if ([service isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier] || + [account isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier]) { + if (handler) { + handler([NSError errorWithDomain:kFIRInstanceIDKeychainErrorDomain + code:kFIRInstanceIDKeychainErrorBadArguments + userInfo:nil]); + } + return; + } + [self removeItemsMatchingService:service + account:account + handler:^(NSError *error) { + if (error) { + if (handler) { + handler(error); + } + return; + } + if (data.length > 0) { + NSMutableDictionary *keychainQuery = + [self keychainQueryForService:service account:account]; + keychainQuery[(__bridge id)kSecValueData] = data; + + keychainQuery[(__bridge id)kSecAttrAccessible] = + (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly; + [[FIRInstanceIDKeychain sharedInstance] + addItemWithQuery:keychainQuery + handler:handler]; + } + }]; + // Set the cache value. This must happen after removeItemsMatchingService:account:handler was + // called, so the cache value was reset before setting a new value. + if (_cachedKeychainData[service]) { + if (_cachedKeychainData[service][account]) { + _cachedKeychainData[service][account] = @[ data ]; + } else { + [_cachedKeychainData[service] setObject:@[ data ] forKey:account]; + } + } else { + [_cachedKeychainData setObject:[@{account : @[ data ]} mutableCopy] forKey:service]; + } +} + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDAuthService.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDAuthService.h new file mode 100644 index 0000000..1fb715e --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDAuthService.h @@ -0,0 +1,91 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import +#import "FIRInstanceIDCheckinService.h" + +@class FIRInstanceIDCheckinPreferences; +@class FIRInstanceIDStore; + +/** + * FIRInstanceIDAuthService is responsible for retrieving, caching, and supplying checkin info + * for the rest of Instance ID. A checkin can be scheduled, meaning that it will keep retrying the + * checkin request until it is successful. A checkin can also be requested directly, with a + * completion handler. + */ +@interface FIRInstanceIDAuthService : NSObject + +/** + * Used only for testing. In addition to taking a store (for locally caching the checkin info), it + * also takes a checkinService. + */ +- (instancetype)initWithCheckinService:(FIRInstanceIDCheckinService *)checkinService + store:(FIRInstanceIDStore *)store; + +/** + * Initializes the auth service given a store (which provides the local caching of checkin info). + * This initializer will create its own instance of FIRInstanceIDCheckinService. + */ +- (instancetype)initWithStore:(FIRInstanceIDStore *)store; + +#pragma mark - Checkin Service + +/** + * Checks if the current deviceID and secret are valid or not. + * + * @return YES if the checkin credentials are valid else NO. + */ +- (BOOL)hasValidCheckinInfo; + +/** + * Fetch checkin info from the server. This would usually refresh the existing + * checkin credentials for the current app. + * + * @param handler The completion handler to invoke once the checkin info has been + * refreshed. + */ +- (void)fetchCheckinInfoWithHandler:(FIRInstanceIDDeviceCheckinCompletion)handler; + +/** + * Schedule checkin. Will hit the network only if the currently loaded checkin + * preferences are stale. + * + * @param immediately YES if we want it to be scheduled immediately else NO. + */ +- (void)scheduleCheckin:(BOOL)immediately; + +/** + * Returns the checkin preferences currently loaded in memory. The Checkin preferences + * can be either valid or invalid. + * + * @return The checkin preferences loaded in memory. + */ +- (FIRInstanceIDCheckinPreferences *)checkinPreferences; + +/** + * Cancels any ongoing checkin fetch, if any. + */ +- (void)stopCheckinRequest; + +/** + * Resets the checkin information. + * + * @param handler The callback handler which is invoked when checkin reset is complete, + * with an error if there is any. + */ +- (void)resetCheckinWithHandler:(void (^)(NSError *error))handler; + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDAuthService.m b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDAuthService.m new file mode 100644 index 0000000..8c33c44 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDAuthService.m @@ -0,0 +1,302 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceIDAuthService.h" + +#import "FIRInstanceIDCheckinPreferences+Internal.h" +#import "FIRInstanceIDCheckinPreferences.h" +#import "FIRInstanceIDCheckinPreferences_Private.h" +#import "FIRInstanceIDConstants.h" +#import "FIRInstanceIDDefines.h" +#import "FIRInstanceIDLogger.h" +#import "FIRInstanceIDStore.h" +#import "NSError+FIRInstanceID.h" + +// Max time interval between checkin retry in seconds. +static const int64_t kMaxCheckinRetryIntervalInSeconds = 1 << 5; + +@interface FIRInstanceIDAuthService () + +// Used to retrieve and cache the checkin info to disk and Keychain. +@property(nonatomic, readwrite, strong) FIRInstanceIDStore *store; +// Used to perform single checkin fetches. +@property(nonatomic, readwrite, strong) FIRInstanceIDCheckinService *checkinService; +// The current checkin info. It will be compared to what is retrieved to determine whether it is +// different than what is in the cache. +@property(nonatomic, readwrite, strong) FIRInstanceIDCheckinPreferences *checkinPreferences; + +// This array will track multiple handlers waiting for checkin to be performed. When a checkin +// request completes, all the handlers will be notified. +// Changes to the checkinHandlers array should happen in a thread-safe manner. +@property(nonatomic, readonly, strong) + NSMutableArray *checkinHandlers; + +// This is set to true if there is a checkin request in-flight. +@property(atomic, readwrite, assign) BOOL isCheckinInProgress; +// This timer is used a perform checkin retries. It is cancellable. +@property(atomic, readwrite, strong) NSTimer *scheduledCheckinTimer; +// The number of times checkin has been retried during a scheduled checkin. +@property(atomic, readwrite, assign) int checkinRetryCount; + +@end + +@implementation FIRInstanceIDAuthService + +- (instancetype)initWithCheckinService:(FIRInstanceIDCheckinService *)checkinService + store:(FIRInstanceIDStore *)store { + self = [super init]; + if (self) { + _store = store; + _checkinPreferences = [_store cachedCheckinPreferences]; + _checkinService = checkinService; + _checkinHandlers = [NSMutableArray array]; + } + return self; +} + +- (void)dealloc { + [_scheduledCheckinTimer invalidate]; +} + +- (instancetype)initWithStore:(FIRInstanceIDStore *)store { + FIRInstanceIDCheckinService *checkinService = [[FIRInstanceIDCheckinService alloc] init]; + return [self initWithCheckinService:checkinService store:store]; +} + +#pragma mark - Schedule Checkin + +- (void)scheduleCheckin:(BOOL)immediately { + // Checkin is still valid, so a remote checkin is not required. + if ([self.checkinPreferences hasValidCheckinInfo]) { + return; + } + + // Checkin is already scheduled, so this (non-immediate) request can be ignored. + if (!immediately && [self.scheduledCheckinTimer isValid]) { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeAuthService000, + @"Checkin sync already scheduled. Will not schedule."); + return; + } + + if (immediately) { + [self performScheduledCheckin]; + } else { + int64_t checkinRetryDuration = [self calculateNextCheckinRetryIntervalInSeconds]; + [self startCheckinTimerWithDuration:(NSTimeInterval)checkinRetryDuration]; + } +} + +- (void)startCheckinTimerWithDuration:(NSTimeInterval)timerDuration { + self.scheduledCheckinTimer = + [NSTimer scheduledTimerWithTimeInterval:timerDuration + target:self + selector:@selector(onScheduledCheckinTimerFired:) + userInfo:nil + repeats:NO]; + // Add some tolerance to the timer, to allow iOS to be more flexible with this timer + self.scheduledCheckinTimer.tolerance = 0.5; +} + +- (void)clearScheduledCheckinTimer { + [self.scheduledCheckinTimer invalidate]; + self.scheduledCheckinTimer = nil; +} + +- (void)onScheduledCheckinTimerFired:(NSTimer *)timer { + [self performScheduledCheckin]; +} + +- (void)performScheduledCheckin { + // No checkin scheduled as of now. + [self clearScheduledCheckinTimer]; + + // Checkin is still valid, so a remote checkin is not required. + if ([self.checkinPreferences hasValidCheckinInfo]) { + return; + } + + FIRInstanceID_WEAKIFY(self); + [self + fetchCheckinInfoWithHandler:^(FIRInstanceIDCheckinPreferences *preferences, NSError *error) { + FIRInstanceID_STRONGIFY(self); + self.checkinRetryCount++; + + if (error) { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeAuthService001, @"Checkin error %@.", + error); + + dispatch_async(dispatch_get_main_queue(), ^{ + // Schedule another checkin + [self scheduleCheckin:NO]; + }); + + } else { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeAuthService002, @"Checkin success."); + } + }]; +} + +- (int64_t)calculateNextCheckinRetryIntervalInSeconds { + // persistent failures can lead to overflow prevent that. + if (self.checkinRetryCount >= 10) { + return kMaxCheckinRetryIntervalInSeconds; + } + return MIN(1 << self.checkinRetryCount, kMaxCheckinRetryIntervalInSeconds); +} + +#pragma mark - Checkin Service + +- (BOOL)hasValidCheckinInfo { + return [self.checkinPreferences hasValidCheckinInfo]; +} + +- (void)fetchCheckinInfoWithHandler:(nonnull FIRInstanceIDDeviceCheckinCompletion)handler { + // Perform any changes to self.checkinHandlers and _isCheckinInProgress in a thread-safe way. + @synchronized(self) { + [self.checkinHandlers addObject:handler]; + + if (_isCheckinInProgress) { + // Nothing more to do until our checkin request is done + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeAuthServiceCheckinInProgress, + @"Checkin is in progress\n"); + return; + } + } + + // Checkin is still valid, so a remote checkin is not required. + if ([self.checkinPreferences hasValidCheckinInfo]) { + [self notifyCheckinHandlersWithCheckin:self.checkinPreferences error:nil]; + return; + } + + @synchronized(self) { + _isCheckinInProgress = YES; + } + [self.checkinService + checkinWithExistingCheckin:self.checkinPreferences + completion:^(FIRInstanceIDCheckinPreferences *checkinPreferences, + NSError *error) { + @synchronized(self) { + self->_isCheckinInProgress = NO; + } + if (error) { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeAuthService003, + @"Failed to checkin device %@", error); + [self notifyCheckinHandlersWithCheckin:nil error:error]; + return; + } + + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeAuthService004, + @"Successfully got checkin credentials"); + BOOL hasSameCachedPreferences = + [self cachedCheckinMatchesCheckin:checkinPreferences]; + checkinPreferences.hasPreCachedAuthCredentials = hasSameCachedPreferences; + + // Update to the most recent checkin preferences + self.checkinPreferences = checkinPreferences; + + // Save the checkin info to disk + // Keychain might not be accessible, so confirm that checkin preferences can + // be saved + [self.store + saveCheckinPreferences:checkinPreferences + handler:^(NSError *checkinSaveError) { + if (checkinSaveError && !hasSameCachedPreferences) { + // The checkin info was new, but it couldn't be + // written to the Keychain. Delete any stuff that was + // cached in memory. This doesn't delete any + // previously persisted preferences. + FIRInstanceIDLoggerError( + kFIRInstanceIDMessageCodeService004, + @"Unable to save checkin info, resetting " + @"checkin preferences " + "in memory."); + [checkinPreferences reset]; + [self + notifyCheckinHandlersWithCheckin:nil + error: + checkinSaveError]; + } else { + // The checkin is either new, or it was the same (and + // it couldn't be saved). Either way, report that the + // checkin preferences were received successfully. + [self notifyCheckinHandlersWithCheckin: + checkinPreferences + error:nil]; + if (!hasSameCachedPreferences) { + // Checkin is new. + // Notify any listeners that might be waiting for + // checkin to be fetched, such as Firebase + // Messaging (for its MCS connection). + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] + postNotificationName: + kFIRInstanceIDCheckinFetchedNotification + object:nil]; + }); + } + } + }]; + }]; +} + +- (FIRInstanceIDCheckinPreferences *)checkinPreferences { + return _checkinPreferences; +} + +- (void)stopCheckinRequest { + [self.checkinService stopFetching]; +} + +- (void)resetCheckinWithHandler:(void (^)(NSError *error))handler { + [self.store removeCheckinPreferencesWithHandler:^(NSError *error) { + if (!error) { + self.checkinPreferences = nil; + } + if (handler) { + handler(error); + } + }]; +} + +#pragma mark - Private + +/** + * Goes through the current list of checkin handlers and fires them with the same checkin and/or + * error info. The checkin handlers will get cleared after. + */ +- (void)notifyCheckinHandlersWithCheckin:(nullable FIRInstanceIDCheckinPreferences *)checkin + error:(nullable NSError *)error { + @synchronized(self) { + for (FIRInstanceIDDeviceCheckinCompletion handler in self.checkinHandlers) { + handler(checkin, error); + } + [self.checkinHandlers removeAllObjects]; + } +} + +/** + * Given a |checkin|, it will compare it to the current checkinPreferences to see if the + * deviceID and secretToken are the same. + */ +- (BOOL)cachedCheckinMatchesCheckin:(FIRInstanceIDCheckinPreferences *)checkin { + if (self.checkinPreferences && checkin) { + return ([self.checkinPreferences.deviceID isEqualToString:checkin.deviceID] && + [self.checkinPreferences.secretToken isEqualToString:checkin.secretToken]); + } + return NO; +} +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDBackupExcludedPlist.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDBackupExcludedPlist.h new file mode 100644 index 0000000..bccaced --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDBackupExcludedPlist.h @@ -0,0 +1,81 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@interface FIRInstanceIDBackupExcludedPlist : NSObject + +/** + * Caches the plist contents in memory so we don't hit the disk each time we want + * to query something in the plist. This is loaded lazily i.e. if you write to the + * plist the contents you want to write will be stored here if the write was + * successful. The other case where it is loaded is if you read the plist contents + * by calling `contentAsDictionary`. + * + * In case you write to the plist and then try to read the file using + * `contentAsDictionary` we would just return the cachedPlistContents since it would + * represent the disk contents. + */ +@property(nonatomic, readonly, strong) NSDictionary *cachedPlistContents; + +/** + * Init a backup excluded plist file. + * + * @param fileName The filename for the plist file. + * @param subDirectory The subdirectory in Application Support to save the plist. + * + * @return Helper which allows to read write data to a backup excluded plist. + */ +- (instancetype)initWithFileName:(NSString *)fileName subDirectory:(NSString *)subDirectory; + +/** + * Write dictionary data to the backup excluded plist file. If the file does not exist + * it would be created before writing to it. + * + * @param dict The data to be written to the plist. + * @param error The error object if any while writing the data. + * + * @return YES if the write was successful else NO. + */ +- (BOOL)writeDictionary:(NSDictionary *)dict error:(NSError **)error; + +/** + * Delete the backup excluded plist created with the above filename. + * + * @param error The error object if any while deleting the file. + * + * @return YES If the delete was successful else NO. + */ +- (BOOL)deleteFile:(NSError **)error; + +/** + * The contents of the plist file. We also store the contents of the file in-memory. + * If the in-memory contents are valid we return the in-memory contents else we read + * the file from disk. + * + * @return A dictionary object that contains the contents of the plist file if the file + * exists else nil. + */ +- (NSDictionary *)contentAsDictionary; + +/** + * Check if the plist exists on the disk or not. + * + * @return YES if the file exists on the disk else NO. + */ +- (BOOL)doesFileExist; + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDBackupExcludedPlist.m b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDBackupExcludedPlist.m new file mode 100644 index 0000000..c1085ca --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDBackupExcludedPlist.m @@ -0,0 +1,117 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceIDBackupExcludedPlist.h" + +#import "FIRInstanceIDLogger.h" + +@interface FIRInstanceIDBackupExcludedPlist () + +@property(nonatomic, readwrite, copy) NSString *fileName; +@property(nonatomic, readwrite, copy) NSString *subDirectoryName; +@property(nonatomic, readwrite, strong) NSDictionary *cachedPlistContents; + +@end + +@implementation FIRInstanceIDBackupExcludedPlist + +- (instancetype)initWithFileName:(NSString *)fileName subDirectory:(NSString *)subDirectory { + self = [super init]; + if (self) { + _fileName = [fileName copy]; + _subDirectoryName = [subDirectory copy]; + } + return self; +} + +- (BOOL)writeDictionary:(NSDictionary *)dict error:(NSError **)error { + NSString *path = [self plistPathInDirectory]; + if (![dict writeToFile:path atomically:YES]) { + FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeBackupExcludedPlist000, + @"Failed to write to %@.plist", self.fileName); + return NO; + } + + // Successfully wrote contents -- change the in-memory contents + self.cachedPlistContents = [dict copy]; + + NSURL *URL = [NSURL fileURLWithPath:path]; + if (error) { + *error = nil; + } + + NSDictionary *preferences = [URL resourceValuesForKeys:@[ NSURLIsExcludedFromBackupKey ] + error:error]; + if ([preferences[NSURLIsExcludedFromBackupKey] boolValue]) { + return YES; + } + + BOOL success = [URL setResourceValue:@(YES) forKey:NSURLIsExcludedFromBackupKey error:error]; + if (!success) { + FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeBackupExcludedPlist001, + @"Error excluding %@ from backup, %@", [URL lastPathComponent], + error ? *error : @""); + } + return success; +} + +- (BOOL)deleteFile:(NSError **)error { + BOOL success = YES; + NSString *path = [self plistPathInDirectory]; + if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { + success = [[NSFileManager defaultManager] removeItemAtPath:path error:error]; + } + // remove the in-memory contents + self.cachedPlistContents = nil; + return success; +} + +- (NSDictionary *)contentAsDictionary { + if (!self.cachedPlistContents) { + NSString *path = [self plistPathInDirectory]; + if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { + self.cachedPlistContents = [[NSDictionary alloc] initWithContentsOfFile:path]; + } + } + return self.cachedPlistContents; +} + +- (BOOL)doesFileExist { + NSString *path = [self plistPathInDirectory]; + return [[NSFileManager defaultManager] fileExistsAtPath:path]; +} + +#pragma mark - Private + +- (NSString *)plistPathInDirectory { + NSArray *directoryPaths; + NSString *plistNameWithExtension = [NSString stringWithFormat:@"%@.plist", self.fileName]; + directoryPaths = + NSSearchPathForDirectoriesInDomains([self supportedDirectory], NSUserDomainMask, YES); + NSArray *components = @[ directoryPaths.lastObject, _subDirectoryName, plistNameWithExtension ]; + + return [NSString pathWithComponents:components]; +} + +- (NSSearchPathDirectory)supportedDirectory { +#if TARGET_OS_TV + return NSCachesDirectory; +#else + return NSApplicationSupportDirectory; +#endif +} + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCheckinPreferences+Internal.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCheckinPreferences+Internal.h new file mode 100644 index 0000000..d2f286d --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCheckinPreferences+Internal.h @@ -0,0 +1,64 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@interface FIRInstanceIDCheckinPreferences (Internal) + +/** + * Parse the checkin auth credentials saved in the Keychain to initialize checkin + * preferences. + * + * @param keychainContent The checkin auth credentials saved in the Keychain. + * + * @return A valid checkin preferences object if the checkin auth credentials in the + * keychain can be parsed successfully else nil. + */ ++ (FIRInstanceIDCheckinPreferences *)preferencesFromKeychainContents:(NSString *)keychainContent; + +/** + * Default initializer for InstanceID checkin preferences. + * + * @param deviceID The deviceID for the app. + * @param secretToken The secret token the app uses to authenticate with the server. + * + * @return A checkin preferences object with given deviceID and secretToken. + */ +- (instancetype)initWithDeviceID:(NSString *)deviceID secretToken:(NSString *)secretToken; + +/** + * Update checkin preferences from the preferences dict persisted as a plist. The dict contains + * all the checkin preferences retrieved from the server except the deviceID and secret which + * are stored in the Keychain. + * + * @param checkinPlistContent The checkin preferences saved in a plist on the disk. + */ +- (void)updateWithCheckinPlistContents:(NSDictionary *)checkinPlistContent; + +/** + * Reset the current checkin preferences object. + */ +- (void)reset; + +/** + * The string that contains the checkin auth credentials i.e. deviceID and secret. This + * needs to be stored in the Keychain. + * + * @return The checkin auth credential string containing the deviceID and secret. + */ +- (NSString *)checkinKeychainContent; + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCheckinPreferences+Internal.m b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCheckinPreferences+Internal.m new file mode 100644 index 0000000..88cc40a --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCheckinPreferences+Internal.m @@ -0,0 +1,112 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceIDCheckinPreferences+Internal.h" + +#import "FIRInstanceIDCheckinService.h" +#import "FIRInstanceIDUtilities.h" + +static NSString *const kCheckinKeychainContentSeparatorString = @"|"; + +@interface FIRInstanceIDCheckinPreferences () + +@property(nonatomic, readwrite, copy) NSString *deviceID; +@property(nonatomic, readwrite, copy) NSString *secretToken; +@property(nonatomic, readwrite, copy) NSString *digest; +@property(nonatomic, readwrite, copy) NSString *versionInfo; +@property(nonatomic, readwrite, copy) NSString *deviceDataVersion; + +@property(nonatomic, readwrite, strong) NSMutableDictionary *gServicesData; +@property(nonatomic, readwrite, assign) int64_t lastCheckinTimestampMillis; + +@end + +@implementation FIRInstanceIDCheckinPreferences (Internal) + ++ (FIRInstanceIDCheckinPreferences *)preferencesFromKeychainContents:(NSString *)keychainContent { + NSString *deviceID = [self checkinDeviceIDFromKeychainContent:keychainContent]; + NSString *secret = [self checkinSecretFromKeychainContent:keychainContent]; + if ([deviceID length] && [secret length]) { + return [[FIRInstanceIDCheckinPreferences alloc] initWithDeviceID:deviceID secretToken:secret]; + } else { + return nil; + } +} + +- (instancetype)initWithDeviceID:(NSString *)deviceID secretToken:(NSString *)secretToken { + self = [super init]; + if (self) { + self.deviceID = [deviceID copy]; + self.secretToken = [secretToken copy]; + } + return self; +} + +- (void)reset { + self.deviceID = nil; + self.secretToken = nil; + self.digest = nil; + self.versionInfo = nil; + self.gServicesData = nil; + self.deviceDataVersion = nil; + self.lastCheckinTimestampMillis = 0; +} + +- (void)updateWithCheckinPlistContents:(NSDictionary *)checkinPlistContent { + for (NSString *key in checkinPlistContent) { + if ([kFIRInstanceIDDigestStringKey isEqualToString:key]) { + self.digest = [checkinPlistContent[key] copy]; + } else if ([kFIRInstanceIDVersionInfoStringKey isEqualToString:key]) { + self.versionInfo = [checkinPlistContent[key] copy]; + } else if ([kFIRInstanceIDLastCheckinTimeKey isEqualToString:key]) { + self.lastCheckinTimestampMillis = [checkinPlistContent[key] longLongValue]; + } else if ([kFIRInstanceIDGServicesDictionaryKey isEqualToString:key]) { + self.gServicesData = [checkinPlistContent[key] mutableCopy]; + } else if ([kFIRInstanceIDDeviceDataVersionKey isEqualToString:key]) { + self.deviceDataVersion = [checkinPlistContent[key] copy]; + } + // Otherwise we have some keys we don't care about + } +} + +- (NSString *)checkinKeychainContent { + if ([self.deviceID length] && [self.secretToken length]) { + return [NSString stringWithFormat:@"%@%@%@", self.deviceID, + kCheckinKeychainContentSeparatorString, self.secretToken]; + } else { + return nil; + } +} + ++ (NSString *)checkinDeviceIDFromKeychainContent:(NSString *)keychainContent { + return [self checkinKeychainContent:keychainContent forIndex:0]; +} + ++ (NSString *)checkinSecretFromKeychainContent:(NSString *)keychainContent { + return [self checkinKeychainContent:keychainContent forIndex:1]; +} + ++ (NSString *)checkinKeychainContent:(NSString *)keychainContent forIndex:(int)index { + NSArray *keychainComponents = + [keychainContent componentsSeparatedByString:kCheckinKeychainContentSeparatorString]; + if (index >= 0 && index < 2 && [keychainComponents count] == 2) { + return keychainComponents[index]; + } else { + return nil; + } +} + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCheckinPreferences.m b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCheckinPreferences.m new file mode 100644 index 0000000..2f3c55e --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCheckinPreferences.m @@ -0,0 +1,95 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceIDCheckinPreferences.h" + +#import +#import "FIRInstanceIDCheckinService.h" +#import "FIRInstanceIDUtilities.h" + +const NSTimeInterval kFIRInstanceIDDefaultCheckinInterval = 7 * 24 * 60 * 60; // 7 days. + +@interface FIRInstanceIDCheckinPreferences () + +@property(nonatomic, readwrite, copy) NSString *deviceID; +@property(nonatomic, readwrite, copy) NSString *secretToken; +@property(nonatomic, readwrite, copy) NSString *digest; +@property(nonatomic, readwrite, copy) NSString *versionInfo; +@property(nonatomic, readwrite, copy) NSString *deviceDataVersion; + +@property(nonatomic, readwrite, strong) NSMutableDictionary *gServicesData; +@property(nonatomic, readwrite, assign) int64_t lastCheckinTimestampMillis; + +// This flag indicates that we have already saved the above deviceID and secret +// to our keychain and hence we don't need to save again. This is helpful since +// on checkin refresh we can avoid writing to the Keychain which can sometimes +// be very buggy. For info check this https://forums.developer.apple.com/thread/4743 +@property(nonatomic, readwrite, assign) BOOL hasPreCachedAuthCredentials; + +@end + +@implementation FIRInstanceIDCheckinPreferences + +- (NSDictionary *)checkinPlistContents { + NSMutableDictionary *checkinPlistContents = [NSMutableDictionary dictionary]; + checkinPlistContents[kFIRInstanceIDDigestStringKey] = self.digest ?: @""; + checkinPlistContents[kFIRInstanceIDVersionInfoStringKey] = self.versionInfo ?: @""; + checkinPlistContents[kFIRInstanceIDDeviceDataVersionKey] = self.deviceDataVersion ?: @""; + checkinPlistContents[kFIRInstanceIDLastCheckinTimeKey] = @(self.lastCheckinTimestampMillis); + checkinPlistContents[kFIRInstanceIDGServicesDictionaryKey] = + [self.gServicesData count] ? self.gServicesData : @{}; + return checkinPlistContents; +} + +- (BOOL)hasCheckinInfo { + return (self.deviceID.length && self.secretToken.length); +} + +- (BOOL)hasValidCheckinInfo { + int64_t currentTimestampInMillis = FIRInstanceIDCurrentTimestampInMilliseconds(); + int64_t timeSinceLastCheckinInMillis = currentTimestampInMillis - self.lastCheckinTimestampMillis; + + BOOL hasCheckinInfo = [self hasCheckinInfo]; + NSString *lastLocale = + [[GULUserDefaults standardUserDefaults] stringForKey:kFIRInstanceIDUserDefaultsKeyLocale]; + // If it's app's first time open and checkin is already fetched and no locale information is + // stored, then checkin info is valid. We should not checkin again because locale is considered + // "changed". + if (hasCheckinInfo && !lastLocale) { + NSString *currentLocale = FIRInstanceIDCurrentLocale(); + [[GULUserDefaults standardUserDefaults] setObject:currentLocale + forKey:kFIRInstanceIDUserDefaultsKeyLocale]; + return YES; + } + + // If locale has changed, checkin info is no longer valid. + // Also update locale information if changed. (Only do it here not in token refresh) + if (FIRInstanceIDHasLocaleChanged()) { + NSString *currentLocale = FIRInstanceIDCurrentLocale(); + [[GULUserDefaults standardUserDefaults] setObject:currentLocale + forKey:kFIRInstanceIDUserDefaultsKeyLocale]; + return NO; + } + + return (hasCheckinInfo && + (timeSinceLastCheckinInMillis / 1000.0 < kFIRInstanceIDDefaultCheckinInterval)); +} + +- (void)setHasPreCachedAuthCredentials:(BOOL)hasPreCachedAuthCredentials { + _hasPreCachedAuthCredentials = hasPreCachedAuthCredentials; +} + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCheckinPreferences_Private.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCheckinPreferences_Private.h new file mode 100644 index 0000000..9c5850b --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCheckinPreferences_Private.h @@ -0,0 +1,27 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +/** Checkin refresh interval. **/ +FOUNDATION_EXPORT const NSTimeInterval kFIRInstanceIDDefaultCheckinInterval; + +@interface FIRInstanceIDCheckinPreferences () + +- (BOOL)hasPreCachedAuthCredentials; +- (void)setHasPreCachedAuthCredentials:(BOOL)hasPreCachedAuthCredentials; + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCheckinService.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCheckinService.h new file mode 100644 index 0000000..e14b51c --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCheckinService.h @@ -0,0 +1,68 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import +#import "FIRInstanceIDUtilities.h" + +NS_ASSUME_NONNULL_BEGIN + +// keys in Checkin preferences +FOUNDATION_EXPORT NSString *const kFIRInstanceIDDeviceAuthIdKey; +FOUNDATION_EXPORT NSString *const kFIRInstanceIDSecretTokenKey; +FOUNDATION_EXPORT NSString *const kFIRInstanceIDDigestStringKey; +FOUNDATION_EXPORT NSString *const kFIRInstanceIDLastCheckinTimeKey; +FOUNDATION_EXPORT NSString *const kFIRInstanceIDVersionInfoStringKey; +FOUNDATION_EXPORT NSString *const kFIRInstanceIDGServicesDictionaryKey; +FOUNDATION_EXPORT NSString *const kFIRInstanceIDDeviceDataVersionKey; + +@class FIRInstanceIDCheckinPreferences; + +/** + * Register the device with Checkin Service and get back the `authID`, `secret + * token` etc. for the client. Checkin results are cached in the + * `FIRInstanceIDCache` and periodically refreshed to prevent them from being stale. + * Each client needs to register with checkin before registering with InstanceID. + */ +@interface FIRInstanceIDCheckinService : NSObject + +/** + * Execute a device checkin request to obtain an deviceID, secret token, + * gService data. + * + * @param existingCheckin An existing checkin preference object, if available. + * @param completion Completion hander called on success or failure of device checkin. + */ +- (void)checkinWithExistingCheckin:(nullable FIRInstanceIDCheckinPreferences *)existingCheckin + completion:(FIRInstanceIDDeviceCheckinCompletion)completion; + +/** + * This would stop any request that the service made to the checkin backend and also + * release any callback handlers that it holds. + */ +- (void)stopFetching; + +/** + * Set test block for mock testing network requests. + * + * @param block The block to invoke as a mock response from the network. + */ ++ (void)setCheckinTestBlock:(nullable FIRInstanceIDURLRequestTestBlock)block; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCheckinService.m b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCheckinService.m new file mode 100644 index 0000000..8b33579 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCheckinService.m @@ -0,0 +1,247 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceIDCheckinService.h" + +#import "FIRInstanceIDCheckinPreferences+Internal.h" +#import "FIRInstanceIDCheckinPreferences_Private.h" +#import "FIRInstanceIDDefines.h" +#import "FIRInstanceIDLogger.h" +#import "FIRInstanceIDStore.h" +#import "FIRInstanceIDUtilities.h" +#import "NSError+FIRInstanceID.h" + +static NSString *const kDeviceCheckinURL = @"https://device-provisioning.googleapis.com/checkin"; + +// keys in Checkin preferences +NSString *const kFIRInstanceIDDeviceAuthIdKey = @"GMSInstanceIDDeviceAuthIdKey"; +NSString *const kFIRInstanceIDSecretTokenKey = @"GMSInstanceIDSecretTokenKey"; +NSString *const kFIRInstanceIDDigestStringKey = @"GMSInstanceIDDigestKey"; +NSString *const kFIRInstanceIDLastCheckinTimeKey = @"GMSInstanceIDLastCheckinTimestampKey"; +NSString *const kFIRInstanceIDVersionInfoStringKey = @"GMSInstanceIDVersionInfo"; +NSString *const kFIRInstanceIDGServicesDictionaryKey = @"GMSInstanceIDGServicesData"; +NSString *const kFIRInstanceIDDeviceDataVersionKey = @"GMSInstanceIDDeviceDataVersion"; + +static NSUInteger const kCheckinType = 2; // DeviceType IOS in l/w/a/_checkin.proto +static NSUInteger const kCheckinVersion = 2; +static NSUInteger const kFragment = 0; + +static FIRInstanceIDURLRequestTestBlock testBlock; + +@interface FIRInstanceIDCheckinService () + +@property(nonatomic, readwrite, strong) NSURLSession *session; + +@end + +@implementation FIRInstanceIDCheckinService +; + +- (instancetype)init { + self = [super init]; + if (self) { + // Create an URLSession once, even though checkin should happen about once a day + NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; + config.timeoutIntervalForResource = 60.0f; // 1 minute + config.allowsCellularAccess = YES; + _session = [NSURLSession sessionWithConfiguration:config]; + _session.sessionDescription = @"com.google.iid-checkin"; + } + return self; +} + +- (void)dealloc { + testBlock = nil; + [self.session invalidateAndCancel]; +} + +- (void)checkinWithExistingCheckin:(FIRInstanceIDCheckinPreferences *)existingCheckin + completion:(FIRInstanceIDDeviceCheckinCompletion)completion { + if (self.session == nil) { + FIRInstanceIDLoggerError(kFIRInstanceIDInvalidNetworkSession, + @"Inconsistent state: NSURLSession has been invalidated"); + NSError *error = + [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeRegistrarFailedToCheckIn]; + if (completion) { + completion(nil, error); + } + return; + } + + NSURL *url = [NSURL URLWithString:kDeviceCheckinURL]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; + [request setValue:@"application/json" forHTTPHeaderField:@"content-type"]; + NSDictionary *checkinParameters = [self checkinParametersWithExistingCheckin:existingCheckin]; + NSData *checkinData = [NSJSONSerialization dataWithJSONObject:checkinParameters + options:0 + error:nil]; + request.HTTPMethod = @"POST"; + request.HTTPBody = checkinData; + + void (^handler)(NSData *, NSURLResponse *, NSError *) = + ^(NSData *data, NSURLResponse *response, NSError *error) { + if (error) { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeService000, + @"Device checkin HTTP fetch error. Error Code: %ld", + (long)error.code); + if (completion) { + completion(nil, error); + } + return; + } + + NSError *serializationError; + NSDictionary *dataResponse = [NSJSONSerialization JSONObjectWithData:data + options:0 + error:&serializationError]; + if (serializationError) { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeService001, + @"Error serializing json object. Error Code: %ld", + _FIRInstanceID_L(serializationError.code)); + if (completion) { + completion(nil, serializationError); + } + return; + } + + NSString *deviceAuthID = [dataResponse[@"android_id"] stringValue]; + NSString *secretToken = [dataResponse[@"security_token"] stringValue]; + if ([deviceAuthID length] == 0) { + NSError *error = + [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeInvalidRequest]; + if (completion) { + completion(nil, error); + } + return; + } + + int64_t lastCheckinTimestampMillis = [dataResponse[@"time_msec"] longLongValue]; + int64_t currentTimestampMillis = FIRInstanceIDCurrentTimestampInMilliseconds(); + // Somehow the server clock gets out of sync with the device clock. + // Reset the last checkin timestamp in case this happens. + if (lastCheckinTimestampMillis > currentTimestampMillis) { + FIRInstanceIDLoggerDebug( + kFIRInstanceIDMessageCodeService002, @"Invalid last checkin timestamp %@ in future.", + [NSDate dateWithTimeIntervalSince1970:lastCheckinTimestampMillis / 1000.0]); + lastCheckinTimestampMillis = currentTimestampMillis; + } + + NSString *deviceDataVersionInfo = dataResponse[@"device_data_version_info"] ?: @""; + NSString *digest = dataResponse[@"digest"] ?: @""; + + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeService003, + @"Checkin successful with authId: %@, " + @"digest: %@, " + @"lastCheckinTimestamp: %lld", + deviceAuthID, digest, lastCheckinTimestampMillis); + + NSString *versionInfo = dataResponse[@"version_info"] ?: @""; + NSMutableDictionary *gservicesData = [NSMutableDictionary dictionary]; + + // Read gServices data. + NSArray *flatSettings = dataResponse[@"setting"]; + for (NSDictionary *dict in flatSettings) { + if (dict[@"name"] && dict[@"value"]) { + gservicesData[dict[@"name"]] = dict[@"value"]; + } else { + FIRInstanceIDLoggerDebug(kFIRInstanceIDInvalidSettingResponse, + @"Invalid setting in checkin response: (%@: %@)", + dict[@"name"], dict[@"value"]); + } + } + + FIRInstanceIDCheckinPreferences *checkinPreferences = + [[FIRInstanceIDCheckinPreferences alloc] initWithDeviceID:deviceAuthID + secretToken:secretToken]; + NSDictionary *preferences = @{ + kFIRInstanceIDDigestStringKey : digest, + kFIRInstanceIDVersionInfoStringKey : versionInfo, + kFIRInstanceIDLastCheckinTimeKey : @(lastCheckinTimestampMillis), + kFIRInstanceIDGServicesDictionaryKey : gservicesData, + kFIRInstanceIDDeviceDataVersionKey : deviceDataVersionInfo, + }; + [checkinPreferences updateWithCheckinPlistContents:preferences]; + if (completion) { + completion(checkinPreferences, nil); + } + }; + // Test block + if (testBlock) { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeService005, + @"Test block set, will not hit the server"); + testBlock(request, handler); + return; + } + + NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:handler]; + [task resume]; +} + +- (void)stopFetching { + [self.session invalidateAndCancel]; + // The session cannot be reused after invalidation. Dispose it to prevent accident reusing. + self.session = nil; +} + +#pragma mark - Private + +- (NSDictionary *)checkinParametersWithExistingCheckin: + (nullable FIRInstanceIDCheckinPreferences *)checkinPreferences { + NSString *deviceModel = FIRInstanceIDDeviceModel(); + NSString *systemVersion = FIRInstanceIDOperatingSystemVersion(); + NSString *osVersion = [NSString stringWithFormat:@"IOS_%@", systemVersion]; + + // Get locale from GCM if GCM exists else use system API. + NSString *locale = FIRInstanceIDCurrentLocale(); + + NSInteger userNumber = 0; // Multi Profile may change this. + NSInteger userSerialNumber = 0; // Multi Profile may change this + + // This ID is generated for logging purpose and it is only logged for performance + // information for backend, not secure information. + // TODO(chliang): Talk to backend team to see if this ID is still needed. + uint32_t loggingID = arc4random(); + NSString *timeZone = [NSTimeZone localTimeZone].name; + int64_t lastCheckingTimestampMillis = checkinPreferences.lastCheckinTimestampMillis; + + NSDictionary *checkinParameters = @{ + @"checkin" : @{ + @"iosbuild" : @{@"model" : deviceModel, @"os_version" : osVersion}, + @"type" : @(kCheckinType), + @"user_number" : @(userNumber), + @"last_checkin_msec" : @(lastCheckingTimestampMillis), + }, + @"fragment" : @(kFragment), + @"logging_id" : @(loggingID), + @"locale" : locale, + @"version" : @(kCheckinVersion), + @"digest" : checkinPreferences.digest ?: @"", + @"time_zone" : timeZone, + @"user_serial_number" : @(userSerialNumber), + @"id" : @([checkinPreferences.deviceID longLongValue]), + @"security_token" : @([checkinPreferences.secretToken longLongValue]), + }; + + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeService006, @"Checkin parameters: %@", + checkinParameters); + return checkinParameters; +} + ++ (void)setCheckinTestBlock:(FIRInstanceIDURLRequestTestBlock)block { + testBlock = [block copy]; +} + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCheckinStore.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCheckinStore.h new file mode 100644 index 0000000..5e1b119 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCheckinStore.h @@ -0,0 +1,108 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FIRInstanceIDAuthKeychain; +@class FIRInstanceIDBackupExcludedPlist; +@class FIRInstanceIDCheckinPreferences; + +// These values exposed for testing +extern NSString *const kFIRInstanceIDCheckinKeychainService; +extern NSString *const kFIRInstanceIDLegacyCheckinKeychainAccount; +extern NSString *const kFIRInstanceIDLegacyCheckinKeychainService; + +/** + * Checkin preferences backing store. + */ +@interface FIRInstanceIDCheckinStore : NSObject + +/** + * Designated Initializer. Initialize a checkin store with the given backup excluded + * plist filename. + * + * @param checkinFilename The backup excluded plist filename to persist checkin + * preferences. + * + * @param subDirectoryName Sub-directory in standard directory where we write + * InstanceID plist. + * + * @return Store to persist checkin preferences. + */ +- (instancetype)initWithCheckinPlistFileName:(NSString *)checkinFilename + subDirectoryName:(NSString *)subDirectoryName; + +/** + * Initialize a checkin store with the given backup excluded plist and keychain. + * + * @param plist The backup excluded plist to persist checkin preferences. + * @param keychain The keychain used to persist checkin auth preferences. + * + * @return Store to persist checkin preferences. + */ +- (instancetype)initWithCheckinPlist:(FIRInstanceIDBackupExcludedPlist *)plist + keychain:(FIRInstanceIDAuthKeychain *)keychain; + +/** + * Checks whether the backup excluded checkin preferences are present on the disk or not. + * + * @return YES if the backup excluded checkin plist exists on the disks else NO. + */ +- (BOOL)hasCheckinPlist; + +#pragma mark - Save + +/** + * Save the checkin preferences to backing store. + * + * @param preferences Checkin preferences to save. + * @param handler The callback handler which is invoked when the operation is complete, + * with an error if there is any. + */ +- (void)saveCheckinPreferences:(FIRInstanceIDCheckinPreferences *)preferences + handler:(void (^)(NSError *error))handler; + +#pragma mark - Delete + +/** + * Remove the cached checkin preferences. + * + * @param handler The callback handler which is invoked when the operation is complete, + * with an error if there is any. + */ +- (void)removeCheckinPreferencesWithHandler:(void (^)(NSError *error))handler; + +#pragma mark - Get + +/** + * Get the cached device secret. If we cannot access it for some reason we + * return the appropriate error object. + * + * @return The cached checkin preferences if present else nil. + */ +- (FIRInstanceIDCheckinPreferences *)cachedCheckinPreferences; + +/** + * Migrate the checkin item from old service/account to the new one. + * The new account is dynamic as it uses bundle ID. + * This is to ensure checkin is not shared across apps, but still the same + * if app has used GCM before. + * This call should only happen once. + * + */ +- (void)migrateCheckinItemIfNeeded; + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCheckinStore.m b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCheckinStore.m new file mode 100644 index 0000000..1142778 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCheckinStore.m @@ -0,0 +1,236 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceIDCheckinStore.h" + +#import "FIRInstanceIDAuthKeyChain.h" +#import "FIRInstanceIDBackupExcludedPlist.h" +#import "FIRInstanceIDCheckinPreferences+Internal.h" +#import "FIRInstanceIDCheckinPreferences_Private.h" +#import "FIRInstanceIDCheckinService.h" +#import "FIRInstanceIDLogger.h" +#import "FIRInstanceIDUtilities.h" +#import "FIRInstanceIDVersionUtilities.h" +#import "NSError+FIRInstanceID.h" + +static NSString *const kFIRInstanceIDCheckinKeychainGeneric = @"com.google.iid"; + +NSString *const kFIRInstanceIDCheckinKeychainService = @"com.google.iid.checkin"; +NSString *const kFIRInstanceIDLegacyCheckinKeychainAccount = @"com.google.iid.checkin-account"; +NSString *const kFIRInstanceIDLegacyCheckinKeychainService = @"com.google.iid.checkin-service"; + +// Checkin plist used to have the deviceID and secret stored in them and that's why they +// had 6 items in it. Since the deviceID and secret have been moved to the keychain +// there would only be 4 items. +static const NSInteger kOldCheckinPlistCount = 6; + +@interface FIRInstanceIDCheckinStore () + +@property(nonatomic, readwrite, strong) FIRInstanceIDBackupExcludedPlist *plist; +@property(nonatomic, readwrite, strong) FIRInstanceIDAuthKeychain *keychain; +// Checkin will store items under +// Keychain account: , +// Keychain service: |kFIRInstanceIDCheckinKeychainService| +@property(nonatomic, readonly) NSString *bundleIdentifierForKeychainAccount; + +@end + +@implementation FIRInstanceIDCheckinStore + +- (instancetype)initWithCheckinPlistFileName:(NSString *)checkinFilename + subDirectoryName:(NSString *)subDirectoryName { + FIRInstanceIDBackupExcludedPlist *plist = + [[FIRInstanceIDBackupExcludedPlist alloc] initWithFileName:checkinFilename + subDirectory:subDirectoryName]; + + FIRInstanceIDAuthKeychain *keychain = + [[FIRInstanceIDAuthKeychain alloc] initWithIdentifier:kFIRInstanceIDCheckinKeychainGeneric]; + return [self initWithCheckinPlist:plist keychain:keychain]; +} + +- (instancetype)initWithCheckinPlist:(FIRInstanceIDBackupExcludedPlist *)plist + keychain:(FIRInstanceIDAuthKeychain *)keychain { + self = [super init]; + if (self) { + _plist = plist; + _keychain = keychain; + } + return self; +} + +- (BOOL)hasCheckinPlist { + return [self.plist doesFileExist]; +} + +- (NSString *)bundleIdentifierForKeychainAccount { + static NSString *bundleIdentifier; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + bundleIdentifier = FIRInstanceIDAppIdentifier(); + }); + return bundleIdentifier; +} + +- (void)saveCheckinPreferences:(FIRInstanceIDCheckinPreferences *)preferences + handler:(void (^)(NSError *error))handler { + NSDictionary *checkinPlistContents = [preferences checkinPlistContents]; + NSString *checkinKeychainContent = [preferences checkinKeychainContent]; + + if (![checkinKeychainContent length]) { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeCheckinStore000, + @"Failed to get checkin keychain content from memory."); + if (handler) { + handler([NSError + errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeRegistrarFailedToCheckIn]); + } + return; + } + if (![checkinPlistContents count]) { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeCheckinStore001, + @"Failed to get checkin plist contents from memory."); + if (handler) { + handler([NSError + errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeRegistrarFailedToCheckIn]); + } + return; + } + + // Save all other checkin preferences in a plist + NSError *error; + if (![self.plist writeDictionary:checkinPlistContents error:&error]) { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeCheckinStore003, + @"Failed to save checkin plist contents." + @"Will delete auth credentials"); + [self.keychain removeItemsMatchingService:kFIRInstanceIDCheckinKeychainService + account:self.bundleIdentifierForKeychainAccount + handler:nil]; + if (handler) { + handler(error); + } + return; + } + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeCheckinStoreCheckinPlistSaved, + @"Checkin plist file is saved"); + + // Save the deviceID and secret in the Keychain + if (!preferences.hasPreCachedAuthCredentials) { + NSData *data = [checkinKeychainContent dataUsingEncoding:NSUTF8StringEncoding]; + [self.keychain setData:data + forService:kFIRInstanceIDCheckinKeychainService + account:self.bundleIdentifierForKeychainAccount + handler:^(NSError *error) { + if (error) { + if (handler) { + handler(error); + } + return; + } + if (handler) { + handler(nil); + } + }]; + } else { + handler(nil); + } +} + +- (void)removeCheckinPreferencesWithHandler:(void (^)(NSError *error))handler { + // Delete the checkin preferences plist first to avoid delay. + NSError *deletePlistError; + if (![self.plist deleteFile:&deletePlistError]) { + handler(deletePlistError); + return; + } + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeCheckinStoreCheckinPlistDeleted, + @"Deleted checkin plist file."); + // Remove deviceID and secret from Keychain + [self.keychain + removeItemsMatchingService:kFIRInstanceIDCheckinKeychainService + account:self.bundleIdentifierForKeychainAccount + handler:^(NSError *error) { + // Try to remove from old location as well because migration + // is no longer needed. Consider this is either a fresh install + // or an identity wipe. + [self.keychain + removeItemsMatchingService:kFIRInstanceIDLegacyCheckinKeychainService + account:kFIRInstanceIDLegacyCheckinKeychainAccount + handler:nil]; + handler(error); + }]; +} + +- (FIRInstanceIDCheckinPreferences *)cachedCheckinPreferences { + // Query the keychain for deviceID and secret + NSData *item = [self.keychain dataForService:kFIRInstanceIDCheckinKeychainService + account:self.bundleIdentifierForKeychainAccount]; + + // Check info found in keychain + NSString *checkinKeychainContent = [[NSString alloc] initWithData:item + encoding:NSUTF8StringEncoding]; + FIRInstanceIDCheckinPreferences *checkinPreferences = + [FIRInstanceIDCheckinPreferences preferencesFromKeychainContents:checkinKeychainContent]; + + NSDictionary *checkinPlistContents = [self.plist contentAsDictionary]; + + NSString *plistDeviceAuthID = checkinPlistContents[kFIRInstanceIDDeviceAuthIdKey]; + NSString *plistSecretToken = checkinPlistContents[kFIRInstanceIDSecretTokenKey]; + + // If deviceID and secret not found in the keychain verify that we don't have them in the + // checkin preferences plist. + if (![checkinPreferences.deviceID length] && ![checkinPreferences.secretToken length]) { + if ([plistDeviceAuthID length] && [plistSecretToken length]) { + // Couldn't find checkin credentials in keychain but found them in the plist. + checkinPreferences = + [[FIRInstanceIDCheckinPreferences alloc] initWithDeviceID:plistDeviceAuthID + secretToken:plistSecretToken]; + } else { + // Couldn't find checkin credentials in keychain nor plist + return nil; + } + } else if (kOldCheckinPlistCount == checkinPlistContents.count) { + // same check as above but just to be extra sure that we cover all upgrade cases properly. + // TODO(chliangGoogle): Remove this case, after verifying it's not needed + if ([plistDeviceAuthID length] && [plistSecretToken length]) { + checkinPreferences = + [[FIRInstanceIDCheckinPreferences alloc] initWithDeviceID:plistDeviceAuthID + secretToken:plistSecretToken]; + } + } + + [checkinPreferences updateWithCheckinPlistContents:checkinPlistContents]; + return checkinPreferences; +} + +- (void)migrateCheckinItemIfNeeded { + // Check for checkin in the old location, using the legacy keys + // Query the keychain for deviceID and secret + NSData *dataInOldLocation = + [self.keychain dataForService:kFIRInstanceIDLegacyCheckinKeychainService + account:kFIRInstanceIDLegacyCheckinKeychainAccount]; + if (dataInOldLocation) { + // Save to new location + [self.keychain setData:dataInOldLocation + forService:kFIRInstanceIDCheckinKeychainService + account:self.bundleIdentifierForKeychainAccount + handler:nil]; + // Remove from old location + [self.keychain removeItemsMatchingService:kFIRInstanceIDLegacyCheckinKeychainService + account:kFIRInstanceIDLegacyCheckinKeychainAccount + handler:nil]; + } +} + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCombinedHandler.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCombinedHandler.h new file mode 100644 index 0000000..dcb5429 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCombinedHandler.h @@ -0,0 +1,31 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * A generic class to combine several handler blocks into a single block in a thread-safe manner + */ +@interface FIRInstanceIDCombinedHandler : NSObject + +- (void)addHandler:(void (^)(ResultType _Nullable result, NSError* _Nullable error))handler; +- (void (^)(ResultType _Nullable result, NSError* _Nullable error))combinedHandler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCombinedHandler.m b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCombinedHandler.m new file mode 100644 index 0000000..bc6be6c --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDCombinedHandler.m @@ -0,0 +1,64 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceIDCombinedHandler.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef void (^FIRInstanseIDHandler)(id _Nullable result, NSError *_Nullable error); + +@interface FIRInstanceIDCombinedHandler () +@property(atomic, readonly, strong) NSMutableArray *handlers; +@end + +NS_ASSUME_NONNULL_END + +@implementation FIRInstanceIDCombinedHandler + +- (instancetype)init { + self = [super init]; + if (self) { + _handlers = [NSMutableArray array]; + } + return self; +} + +- (void)addHandler:(FIRInstanseIDHandler)handler { + if (!handler) { + return; + } + + @synchronized(self) { + [self.handlers addObject:handler]; + } +} + +- (FIRInstanseIDHandler)combinedHandler { + FIRInstanseIDHandler combinedHandler = nil; + + @synchronized(self) { + NSArray *handlers = [self.handlers copy]; + combinedHandler = ^(id result, NSError *error) { + for (FIRInstanseIDHandler handler in handlers) { + handler(result, error); + } + }; + } + + return combinedHandler; +} + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDConstants.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDConstants.h new file mode 100644 index 0000000..cd2e131 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDConstants.h @@ -0,0 +1,63 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#pragma mark - Commands + +/** + * Value included in a structured response or GCM message from IID, indicating + * an identity reset. + */ +FOUNDATION_EXPORT NSString *const kFIRInstanceID_CMD_RST; + +#pragma mark - Notifications + +/// Notification used to deliver GCM messages for InstanceID. +FOUNDATION_EXPORT NSString *const kFIRInstanceIDCheckinFetchedNotification; +FOUNDATION_EXPORT NSString *const kFIRInstanceIDAPNSTokenNotification; +FOUNDATION_EXPORT NSString *const kFIRInstanceIDDefaultGCMTokenNotification; +FOUNDATION_EXPORT NSString *const kFIRInstanceIDDefaultGCMTokenFailNotification; + +FOUNDATION_EXPORT NSString *const kFIRInstanceIDIdentityInvalidatedNotification; + +#pragma mark - Miscellaneous + +/// The scope used to save the IID "*" scope token. This is used for saving the +/// IID auth token that we receive from the server. This feature was never +/// implemented on the server side. +FOUNDATION_EXPORT NSString *const kFIRInstanceIDAllScopeIdentifier; +/// The scope used to save the IID "*" scope token. +FOUNDATION_EXPORT NSString *const kFIRInstanceIDDefaultTokenScope; + +/// Subdirectory in search path directory to store InstanceID preferences. +FOUNDATION_EXPORT NSString *const kFIRInstanceIDSubDirectoryName; + +/// The key for APNS token in options dictionary. +FOUNDATION_EXPORT NSString *const kFIRInstanceIDTokenOptionsAPNSKey; + +/// The key for APNS token environment type in options dictionary. +FOUNDATION_EXPORT NSString *const kFIRInstanceIDTokenOptionsAPNSIsSandboxKey; + +/// The key for GMP AppID sent in registration requests. +FOUNDATION_EXPORT NSString *const kFIRInstanceIDTokenOptionsFirebaseAppIDKey; + +/// The key to enable auto-register by swizzling AppDelegate's methods. +FOUNDATION_EXPORT NSString *const kFIRInstanceIDAppDelegateProxyEnabledInfoPlistKey; + +/// Error code for missing entitlements in Keychain. iOS Keychain error +/// https://forums.developer.apple.com/thread/4743 +FOUNDATION_EXPORT const int kFIRInstanceIDSecMissingEntitlementErrorCode; diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDConstants.m b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDConstants.m new file mode 100644 index 0000000..81f4620 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDConstants.m @@ -0,0 +1,46 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceIDConstants.h" + +// Commands +NSString *const kFIRInstanceID_CMD_RST = @"RST"; + +// NOTIFICATIONS +NSString *const kFIRInstanceIDCheckinFetchedNotification = @"com.google.gcm.notif-checkin-fetched"; +NSString *const kFIRInstanceIDAPNSTokenNotification = @"com.firebase.iid.notif.apns-token"; +NSString *const kFIRInstanceIDDefaultGCMTokenNotification = @"com.firebase.iid.notif.fcm-token"; +NSString *const kFIRInstanceIDDefaultGCMTokenFailNotification = + @"com.firebase.iid.notif.fcm-token-fail"; + +NSString *const kFIRInstanceIDIdentityInvalidatedNotification = @"com.google.iid.identity-invalid"; + +// Miscellaneous +NSString *const kFIRInstanceIDAllScopeIdentifier = @"iid-all"; +NSString *const kFIRInstanceIDDefaultTokenScope = @"*"; +NSString *const kFIRInstanceIDSubDirectoryName = @"Google/FirebaseInstanceID"; + +// Registration Options +NSString *const kFIRInstanceIDTokenOptionsAPNSKey = @"apns_token"; +NSString *const kFIRInstanceIDTokenOptionsAPNSIsSandboxKey = @"apns_sandbox"; +NSString *const kFIRInstanceIDTokenOptionsFirebaseAppIDKey = @"gmp_app_id"; + +NSString *const kFIRInstanceIDAppDelegateProxyEnabledInfoPlistKey = + @"FirebaseAppDelegateProxyEnabled"; + +// iOS Keychain error https://forums.developer.apple.com/thread/4743 +// An undocumented error code hence need to be redeclared. +const int kFIRInstanceIDSecMissingEntitlementErrorCode = -34018; diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDDefines.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDDefines.h new file mode 100644 index 0000000..ccc25b3 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDDefines.h @@ -0,0 +1,47 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRInstanceIDLib_FIRInstanceIDDefines_h +#define FIRInstanceIDLib_FIRInstanceIDDefines_h + +#define _FIRInstanceID_VERBOSE_LOGGING 1 + +// Verbose Logging +#if (_FIRInstanceID_VERBOSE_LOGGING) +#define FIRInstanceID_DEV_VERBOSE_LOG(...) NSLog(__VA_ARGS__) +#else +#define FIRInstanceID_DEV_VERBOSE_LOG(...) \ + do { \ + } while (0) +#endif // VERBOSE_LOGGING + +// WEAKIFY & STRONGIFY +// Helper macro. +#define _FIRInstanceID_WEAKNAME(VAR) VAR##_weak_ + +#define FIRInstanceID_WEAKIFY(VAR) __weak __typeof__(VAR) _FIRInstanceID_WEAKNAME(VAR) = (VAR); + +#define FIRInstanceID_STRONGIFY(VAR) \ + _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wshadow\"") \ + __strong __typeof__(VAR) VAR = _FIRInstanceID_WEAKNAME(VAR); \ + _Pragma("clang diagnostic pop") + +// Type Conversions (used for NSInteger etc) +#ifndef _FIRInstanceID_L +#define _FIRInstanceID_L(v) (long)(v) +#endif + +#endif diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDKeychain.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDKeychain.h new file mode 100644 index 0000000..1c80d8e --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDKeychain.h @@ -0,0 +1,62 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +/* The Keychain error domain */ +extern NSString *const kFIRInstanceIDKeychainErrorDomain; + +/* + * Wrapping the keychain operations in a serialize queue. This is to avoid keychain operation + * blocking main queue. + */ +@interface FIRInstanceIDKeychain : NSObject + +/** + * FIRInstanceIDKeychain. + * + * @return A shared instance of FIRInstanceIDKeychain. + */ ++ (instancetype)sharedInstance; + +/** + * Get keychain items matching the given a query. + * + * @param keychainQuery The keychain query. + * + * @return An CFTypeRef result matching the provided inputs. + */ +- (CFTypeRef)itemWithQuery:(NSDictionary *)keychainQuery; + +/** + * Remove the cached items from the keychain matching the query. + * + * @param keychainQuery The keychain query. + * @param handler The callback handler which is invoked when the remove operation is + * complete, with an error if there is any. + */ +- (void)removeItemWithQuery:(NSDictionary *)keychainQuery handler:(void (^)(NSError *error))handler; + +/** + * Add the item with a given query. + * + * @param keychainQuery The keychain query. + * @param handler The callback handler which is invoked when the add operation is + * complete, with an error if there is any. + */ +- (void)addItemWithQuery:(NSDictionary *)keychainQuery handler:(void (^)(NSError *))handler; + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDKeychain.m b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDKeychain.m new file mode 100644 index 0000000..df1b4f7 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDKeychain.m @@ -0,0 +1,114 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceIDKeychain.h" + +#import "FIRInstanceIDLogger.h" + +NSString *const kFIRInstanceIDKeychainErrorDomain = @"com.google.iid"; + +@interface FIRInstanceIDKeychain () { + dispatch_queue_t _keychainOperationQueue; +} + +@end + +@implementation FIRInstanceIDKeychain + ++ (instancetype)sharedInstance { + static FIRInstanceIDKeychain *sharedInstance; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[FIRInstanceIDKeychain alloc] init]; + }); + return sharedInstance; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _keychainOperationQueue = + dispatch_queue_create("com.google.FirebaseInstanceID.Keychain", DISPATCH_QUEUE_SERIAL); + } + return self; +} + +- (CFTypeRef)itemWithQuery:(NSDictionary *)keychainQuery { + __block SecKeyRef keyRef = NULL; + dispatch_sync(_keychainOperationQueue, ^{ + OSStatus status = + SecItemCopyMatching((__bridge CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyRef); + + if (status != noErr) { + if (keyRef) { + CFRelease(keyRef); + } + FIRInstanceIDLoggerDebug(kFIRInstanceIDKeychainReadItemError, + @"Info is not found in Keychain. OSStatus: %d. Keychain query: %@", + (int)status, keychainQuery); + } + }); + return keyRef; +} + +- (void)removeItemWithQuery:(NSDictionary *)keychainQuery + handler:(void (^)(NSError *error))handler { + dispatch_async(_keychainOperationQueue, ^{ + OSStatus status = SecItemDelete((__bridge CFDictionaryRef)keychainQuery); + if (status != noErr) { + FIRInstanceIDLoggerDebug( + kFIRInstanceIDKeychainDeleteItemError, + @"Couldn't delete item from Keychain OSStatus: %d with the keychain query %@", + (int)status, keychainQuery); + } + + if (handler) { + NSError *error; + // When item is not found, it should NOT be considered as an error. The operation should + // continue. + if (status != noErr && status != errSecItemNotFound) { + error = [NSError errorWithDomain:kFIRInstanceIDKeychainErrorDomain + code:status + userInfo:nil]; + } + dispatch_async(dispatch_get_main_queue(), ^{ + handler(error); + }); + } + }); +} + +- (void)addItemWithQuery:(NSDictionary *)keychainQuery handler:(void (^)(NSError *))handler { + dispatch_async(_keychainOperationQueue, ^{ + OSStatus status = SecItemAdd((__bridge CFDictionaryRef)keychainQuery, NULL); + + if (handler) { + NSError *error; + if (status != noErr) { + FIRInstanceIDLoggerWarning(kFIRInstanceIDKeychainAddItemError, + @"Couldn't add item to Keychain OSStatus: %d", (int)status); + error = [NSError errorWithDomain:kFIRInstanceIDKeychainErrorDomain + code:status + userInfo:nil]; + } + dispatch_async(dispatch_get_main_queue(), ^{ + handler(error); + }); + } + }); +} + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDLogger.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDLogger.h new file mode 100644 index 0000000..ab93976 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDLogger.h @@ -0,0 +1,66 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRIMessageCode.h" + +// The convenience macros are only defined if they haven't already been defined. +#ifndef FIRInstanceIDLoggerInfo + +// Convenience macros that log to the shared GTMLogger instance. These macros +// are how users should typically log to FIRInstanceIDLogger. +#define FIRInstanceIDLoggerDebug(code, ...) \ + [FIRInstanceIDSharedLogger() logFuncDebug:__func__ messageCode:code msg:__VA_ARGS__] +#define FIRInstanceIDLoggerInfo(code, ...) \ + [FIRInstanceIDSharedLogger() logFuncInfo:__func__ messageCode:code msg:__VA_ARGS__] +#define FIRInstanceIDLoggerNotice(code, ...) \ + [FIRInstanceIDSharedLogger() logFuncNotice:__func__ messageCode:code msg:__VA_ARGS__] +#define FIRInstanceIDLoggerWarning(code, ...) \ + [FIRInstanceIDSharedLogger() logFuncWarning:__func__ messageCode:code msg:__VA_ARGS__] +#define FIRInstanceIDLoggerError(code, ...) \ + [FIRInstanceIDSharedLogger() logFuncError:__func__ messageCode:code msg:__VA_ARGS__] + +#endif // !defined(FIRInstanceIDLoggerInfo) + +@interface FIRInstanceIDLogger : NSObject + +- (void)logFuncDebug:(const char *)func + messageCode:(FIRInstanceIDMessageCode)messageCode + msg:(NSString *)fmt, ... NS_FORMAT_FUNCTION(3, 4); + +- (void)logFuncInfo:(const char *)func + messageCode:(FIRInstanceIDMessageCode)messageCode + msg:(NSString *)fmt, ... NS_FORMAT_FUNCTION(3, 4); + +- (void)logFuncNotice:(const char *)func + messageCode:(FIRInstanceIDMessageCode)messageCode + msg:(NSString *)fmt, ... NS_FORMAT_FUNCTION(3, 4); + +- (void)logFuncWarning:(const char *)func + messageCode:(FIRInstanceIDMessageCode)messageCode + msg:(NSString *)fmt, ... NS_FORMAT_FUNCTION(3, 4); + +- (void)logFuncError:(const char *)func + messageCode:(FIRInstanceIDMessageCode)messageCode + msg:(NSString *)fmt, ... NS_FORMAT_FUNCTION(3, 4); + +@end + +/** + * Instantiates and/or returns a shared GTMLogger used exclusively + * for InstanceID log messages. + * @return the shared GTMLogger instance + */ +FIRInstanceIDLogger *FIRInstanceIDSharedLogger(void); diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDLogger.m b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDLogger.m new file mode 100644 index 0000000..2600d3b --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDLogger.m @@ -0,0 +1,92 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceIDLogger.h" + +#import + +// Re-definition of FIRLogger service, as it is not included in :FIRAppHeaders target +NSString *const kFIRInstanceIDLoggerService = @"[Firebase/InstanceID]"; + +@implementation FIRInstanceIDLogger + +#pragma mark - Log Helpers + ++ (NSString *)formatMessageCode:(FIRInstanceIDMessageCode)messageCode { + return [NSString stringWithFormat:@"I-IID%06ld", (long)messageCode]; +} + +- (void)logFuncDebug:(const char *)func + messageCode:(FIRInstanceIDMessageCode)messageCode + msg:(NSString *)fmt, ... { + va_list args; + va_start(args, fmt); + FIRLogBasic(FIRLoggerLevelDebug, kFIRInstanceIDLoggerService, + [FIRInstanceIDLogger formatMessageCode:messageCode], fmt, args); + va_end(args); +} + +- (void)logFuncInfo:(const char *)func + messageCode:(FIRInstanceIDMessageCode)messageCode + msg:(NSString *)fmt, ... { + va_list args; + va_start(args, fmt); + FIRLogBasic(FIRLoggerLevelInfo, kFIRInstanceIDLoggerService, + [FIRInstanceIDLogger formatMessageCode:messageCode], fmt, args); + va_end(args); +} + +- (void)logFuncNotice:(const char *)func + messageCode:(FIRInstanceIDMessageCode)messageCode + msg:(NSString *)fmt, ... { + va_list args; + va_start(args, fmt); + FIRLogBasic(FIRLoggerLevelNotice, kFIRInstanceIDLoggerService, + [FIRInstanceIDLogger formatMessageCode:messageCode], fmt, args); + va_end(args); +} + +- (void)logFuncWarning:(const char *)func + messageCode:(FIRInstanceIDMessageCode)messageCode + msg:(NSString *)fmt, ... { + va_list args; + va_start(args, fmt); + FIRLogBasic(FIRLoggerLevelWarning, kFIRInstanceIDLoggerService, + [FIRInstanceIDLogger formatMessageCode:messageCode], fmt, args); + va_end(args); +} + +- (void)logFuncError:(const char *)func + messageCode:(FIRInstanceIDMessageCode)messageCode + msg:(NSString *)fmt, ... { + va_list args; + va_start(args, fmt); + FIRLogBasic(FIRLoggerLevelError, kFIRInstanceIDLoggerService, + [FIRInstanceIDLogger formatMessageCode:messageCode], fmt, args); + va_end(args); +} + +@end + +FIRInstanceIDLogger *FIRInstanceIDSharedLogger() { + static dispatch_once_t onceToken; + static FIRInstanceIDLogger *logger; + dispatch_once(&onceToken, ^{ + logger = [[FIRInstanceIDLogger alloc] init]; + }); + + return logger; +} diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDStore.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDStore.h new file mode 100644 index 0000000..d1f2634 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDStore.h @@ -0,0 +1,183 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class FIRInstanceIDBackupExcludedPlist; +@class FIRInstanceIDCheckinPreferences; +@class FIRInstanceIDCheckinStore; +@class FIRInstanceIDTokenInfo; +@class FIRInstanceIDTokenStore; + +@class FIRInstanceIDStore; +@protocol FIRInstanceIDStoreDelegate + +/** + * This is called when the store has decided to invalide its tokens associated with the + * previous checkin credentials. After deleting the tokens locally, it calls this method + * to notify the delegate of the change. If possible, the delegate should use this time + * to request the invalidation of the tokens on the server as well. + */ +- (void)store:(FIRInstanceIDStore *)store + didDeleteFCMScopedTokensForCheckin:(FIRInstanceIDCheckinPreferences *)checkin; + +@end + +/** + * Used to persist the InstanceID tokens. This is also used to cache the Checkin + * credentials. The store also checks for stale entries in the store and + * let's us know if things in the store are stale or not. It does not however + * acts on stale entries in anyway. + */ +@interface FIRInstanceIDStore : NSObject + +/** + * The delegate set in the initializer which is notified of changes in the store. + */ +@property(nonatomic, readonly, weak) NSObject *delegate; + +- (instancetype)init __attribute__((unavailable("Use initWithDelegate: instead."))); + +/** + * Initialize a default store to persist InstanceID tokens and options. + * + * @param delegate The delegate with which to be notified of changes in the store. + * @return Store to persist InstanceID tokens. + */ +- (instancetype)initWithDelegate:(NSObject *)delegate; + +/** + * Initialize a store with the token store used to persist tokens, and a checkin store. + * Used for testing. + * + * @param checkinStore Persistent store that persists checkin preferences. + * @param tokenStore Persistent store that persists tokens. + * + * @return Store to persist InstanceID tokens and options. + */ +- (instancetype)initWithCheckinStore:(FIRInstanceIDCheckinStore *)checkinStore + tokenStore:(FIRInstanceIDTokenStore *)tokenStore + delegate:(NSObject *)delegate + NS_DESIGNATED_INITIALIZER; + +#pragma mark - Save +/** + * Save the instanceID token info to the store. + * + * @param tokenInfo The token info to store. + * @param handler The callback handler which is invoked when the operation is complete, + * with an error if there is any. + */ +- (void)saveTokenInfo:(FIRInstanceIDTokenInfo *)tokenInfo handler:(void (^)(NSError *))handler; + +#pragma mark - Get + +/** + * Get the cached token info. + * + * @param authorizedEntity The authorized entity for which we want the token. + * @param scope The scope for which we want the token. + * + * @return The cached token info if any for the given authorizedEntity and scope else + * returns nil. + */ +- (nullable FIRInstanceIDTokenInfo *)tokenInfoWithAuthorizedEntity:(NSString *)authorizedEntity + scope:(NSString *)scope; +/** + * Return all cached token infos from the Keychain. + * + * @return The cached token infos, if any, that are stored in the Keychain. + */ +- (NSArray *)cachedTokenInfos; + +#pragma mark - Delete + +/** + * Remove the cached token for a given authorizedEntity and scope. If the token was never + * cached or deleted from the cache before this is a no-op. + * + * @param authorizedEntity The authorizedEntity for the cached token. + * @param scope The scope for the cached token + */ +- (void)removeCachedTokenWithAuthorizedEntity:(NSString *)authorizedEntity scope:(NSString *)scope; + +/** + * Removes all cached tokens from the persistent store. In case deleting the cached tokens + * fails we try to delete the backup excluded plist that stores the tokens. + * + * @param handler The callback handler which is invoked when the operation is complete, + * with an error if there is any. + * + */ +- (void)removeAllCachedTokensWithHandler:(nullable void (^)(NSError *error))handler; + +#pragma mark - Persisting Checkin Preferences + +/** + * Save the checkin preferences + * + * @param preferences Checkin preferences to save. + * @param handler The callback handler which is invoked when the operation is complete, + * with an error if there is any. + */ +- (void)saveCheckinPreferences:(FIRInstanceIDCheckinPreferences *)preferences + handler:(nullable void (^)(NSError *error))handler; + +/** + * Return the cached checkin preferences. + * + * @return Checkin preferences. + */ +- (FIRInstanceIDCheckinPreferences *)cachedCheckinPreferences; + +/** + * Remove the cached checkin preferences from the store. + * + * @param handler The callback handler which is invoked when the operation is complete, + * with an error if there is any. + */ +- (void)removeCheckinPreferencesWithHandler:(nullable void (^)(NSError *error))handler; + +#pragma mark - Standard Directory sub-directory + +/** + * Check if supported directory has InstanceID subdirectory + * + * @return YES if the Application Support directory has InstanceID subdirectory else NO. + */ ++ (BOOL)hasSubDirectory:(NSString *)subDirectoryName; + +/** + * Create InstanceID subdirectory in Application support directory. + * + * @return YES if the subdirectory was created successfully else NO. + */ ++ (BOOL)createSubDirectory:(NSString *)subDirectoryName; + +/** + * Removes Application Support subdirectory for InstanceID. + * + * @param error The error object if any while trying to delete the sub-directory. + * + * @return YES if the deletion was successful else NO. + */ ++ (BOOL)removeSubDirectory:(NSString *)subDirectoryName error:(NSError **)error; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDStore.m b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDStore.m new file mode 100644 index 0000000..1c7a0d0 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDStore.m @@ -0,0 +1,242 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceIDStore.h" + +#import "FIRInstanceIDCheckinPreferences.h" +#import "FIRInstanceIDCheckinStore.h" +#import "FIRInstanceIDConstants.h" +#import "FIRInstanceIDLogger.h" +#import "FIRInstanceIDTokenStore.h" +#import "FIRInstanceIDVersionUtilities.h" + +// NOTE: These values should be in sync with what InstanceID saves in as. +static NSString *const kCheckinFileName = @"g-checkin"; + +// APNS token (use the old key value i.e. with prefix GMS) +static NSString *const kFIRInstanceIDLibraryVersion = @"GMSInstanceID-version"; + +@interface FIRInstanceIDStore () + +@property(nonatomic, readwrite, strong) FIRInstanceIDCheckinStore *checkinStore; +@property(nonatomic, readwrite, strong) FIRInstanceIDTokenStore *tokenStore; + +@end + +@implementation FIRInstanceIDStore + +- (instancetype)initWithDelegate:(NSObject *)delegate { + FIRInstanceIDCheckinStore *checkinStore = [[FIRInstanceIDCheckinStore alloc] + initWithCheckinPlistFileName:kCheckinFileName + subDirectoryName:kFIRInstanceIDSubDirectoryName]; + + FIRInstanceIDTokenStore *tokenStore = [FIRInstanceIDTokenStore defaultStore]; + + return [self initWithCheckinStore:checkinStore tokenStore:tokenStore delegate:delegate]; +} + +- (instancetype)initWithCheckinStore:(FIRInstanceIDCheckinStore *)checkinStore + tokenStore:(FIRInstanceIDTokenStore *)tokenStore + delegate:(NSObject *)delegate { + self = [super init]; + if (self) { + _checkinStore = checkinStore; + _tokenStore = tokenStore; + _delegate = delegate; + [self resetCredentialsIfNeeded]; + } + return self; +} + +#pragma mark - Upgrades + ++ (BOOL)hasSubDirectory:(NSString *)subDirectoryName { + NSString *subDirectoryPath = [self pathForSupportSubDirectory:subDirectoryName]; + BOOL isDirectory; + if (![[NSFileManager defaultManager] fileExistsAtPath:subDirectoryPath + isDirectory:&isDirectory]) { + return NO; + } else if (!isDirectory) { + return NO; + } + return YES; +} + ++ (NSSearchPathDirectory)supportedDirectory { +#if TARGET_OS_TV + return NSCachesDirectory; +#else + return NSApplicationSupportDirectory; +#endif +} + ++ (NSString *)pathForSupportSubDirectory:(NSString *)subDirectoryName { + NSArray *directoryPaths = + NSSearchPathForDirectoriesInDomains([self supportedDirectory], NSUserDomainMask, YES); + NSString *dirPath = directoryPaths.lastObject; + NSArray *components = @[ dirPath, subDirectoryName ]; + return [NSString pathWithComponents:components]; +} + ++ (BOOL)createSubDirectory:(NSString *)subDirectoryName { + NSString *subDirectoryPath = [self pathForSupportSubDirectory:subDirectoryName]; + BOOL hasSubDirectory; + + if (![[NSFileManager defaultManager] fileExistsAtPath:subDirectoryPath + isDirectory:&hasSubDirectory]) { + NSError *error; + [[NSFileManager defaultManager] createDirectoryAtPath:subDirectoryPath + withIntermediateDirectories:YES + attributes:nil + error:&error]; + if (error) { + FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeStore000, + @"Cannot create directory %@, error: %@", subDirectoryPath, error); + return NO; + } + } else { + if (!hasSubDirectory) { + FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeStore001, + @"Found file instead of directory at %@", subDirectoryPath); + return NO; + } + } + return YES; +} + ++ (BOOL)removeSubDirectory:(NSString *)subDirectoryName error:(NSError **)error { + if ([self hasSubDirectory:subDirectoryName]) { + NSString *subDirectoryPath = [self pathForSupportSubDirectory:subDirectoryName]; + BOOL isDirectory; + if ([[NSFileManager defaultManager] fileExistsAtPath:subDirectoryPath + isDirectory:&isDirectory]) { + return [[NSFileManager defaultManager] removeItemAtPath:subDirectoryPath error:error]; + } + } + return YES; +} + +/** + * Reset the keychain preferences if the app had been deleted earlier and then reinstalled. + * Keychain preferences are not cleared in the above scenario so explicitly clear them. + * + * In case of an iCloud backup and restore the Keychain preferences should already be empty + * since the Keychain items are marked with `*BackupThisDeviceOnly`. + */ +- (void)resetCredentialsIfNeeded { + BOOL checkinPlistExists = [self.checkinStore hasCheckinPlist]; + // Checkin info existed in backup excluded plist. Should not be a fresh install. + if (checkinPlistExists) { + // FCM user can still have the old version of checkin, migration should only happen once. + [self.checkinStore migrateCheckinItemIfNeeded]; + return; + } + + // reset checkin in keychain if a fresh install. + // set the old checkin preferences to unregister pre-registered tokens + FIRInstanceIDCheckinPreferences *oldCheckinPreferences = + [self.checkinStore cachedCheckinPreferences]; + + if (oldCheckinPreferences) { + [self.checkinStore removeCheckinPreferencesWithHandler:^(NSError *error) { + if (!error) { + FIRInstanceIDLoggerDebug( + kFIRInstanceIDMessageCodeStore002, + @"Removed cached checkin preferences from Keychain because this is a fresh install."); + } else { + FIRInstanceIDLoggerError( + kFIRInstanceIDMessageCodeStore003, + @"Couldn't remove cached checkin preferences for a fresh install. Error: %@", error); + } + if (oldCheckinPreferences.deviceID.length && oldCheckinPreferences.secretToken.length) { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeStore006, + @"App reset detected. Will delete server registrations."); + // We don't really need to delete old FCM tokens created via IID auth tokens since + // those tokens are already hashed by APNS token as the has so creating a new + // token should automatically delete the old-token. + [self.delegate store:self didDeleteFCMScopedTokensForCheckin:oldCheckinPreferences]; + } else { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeStore009, + @"App reset detected but no valid checkin auth preferences found." + @" Will not delete server registrations."); + } + }]; + } +} + +#pragma mark - Get + +- (FIRInstanceIDTokenInfo *)tokenInfoWithAuthorizedEntity:(NSString *)authorizedEntity + scope:(NSString *)scope { + // TODO(chliangGoogle): If we don't have the token plist we should delete all the tokens from + // the keychain. This is because not having the plist signifies a backup and restore operation. + // In case the keychain has any tokens these would now be stale and therefore should be + // deleted. + if (![authorizedEntity length] || ![scope length]) { + return nil; + } + FIRInstanceIDTokenInfo *info = [self.tokenStore tokenInfoWithAuthorizedEntity:authorizedEntity + scope:scope]; + return info; +} + +- (NSArray *)cachedTokenInfos { + return [self.tokenStore cachedTokenInfos]; +} + +#pragma mark - Save + +- (void)saveTokenInfo:(FIRInstanceIDTokenInfo *)tokenInfo + handler:(void (^)(NSError *error))handler { + [self.tokenStore saveTokenInfo:tokenInfo handler:handler]; +} + +#pragma mark - Delete + +- (void)removeCachedTokenWithAuthorizedEntity:(NSString *)authorizedEntity scope:(NSString *)scope { + if (![authorizedEntity length] || ![scope length]) { + FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeStore012, + @"Will not delete token with invalid entity: %@, scope: %@", + authorizedEntity, scope); + return; + } + [self.tokenStore removeTokenWithAuthorizedEntity:authorizedEntity scope:scope]; +} + +- (void)removeAllCachedTokensWithHandler:(void (^)(NSError *error))handler { + [self.tokenStore removeAllTokensWithHandler:handler]; +} + +#pragma mark - FIRInstanceIDCheckinCache protocol + +- (void)saveCheckinPreferences:(FIRInstanceIDCheckinPreferences *)preferences + handler:(void (^)(NSError *error))handler { + [self.checkinStore saveCheckinPreferences:preferences handler:handler]; +} + +- (FIRInstanceIDCheckinPreferences *)cachedCheckinPreferences { + return [self.checkinStore cachedCheckinPreferences]; +} + +- (void)removeCheckinPreferencesWithHandler:(void (^)(NSError *))handler { + [self.checkinStore removeCheckinPreferencesWithHandler:^(NSError *error) { + if (handler) { + handler(error); + } + }]; +} + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDStringEncoding.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDStringEncoding.h new file mode 100644 index 0000000..8f2f369 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDStringEncoding.h @@ -0,0 +1,66 @@ +// +// GTMStringEncoding.h +// +// Copyright 2010 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +// This is a copy of GTMStringEncoding. FIRInstanceID wants to avoid +// a CocoaPods GTM dependency. Hence we use our own version of StringEncoding. + +#import + +// A generic class for arbitrary base-2 to 128 string encoding and decoding. +@interface FIRInstanceIDStringEncoding : NSObject { + @private + NSData *charMapData_; + char *charMap_; + int reverseCharMap_[128]; + int shift_; + unsigned int mask_; + BOOL doPad_; + char paddingChar_; + int padLen_; +} + ++ (id)rfc4648Base64WebsafeStringEncoding; + +// Create a new, autoreleased GTMStringEncoding object with the given string, +// as described below. ++ (id)stringEncodingWithString:(NSString *)string; + +// Initialize a new GTMStringEncoding object with the string. +// +// The length of the string must be a power of 2, at least 2 and at most 128. +// Only 7-bit ASCII characters are permitted in the string. +// +// These characters are the canonical set emitted during encoding. +// If the characters have alternatives (e.g. case, easily transposed) then use +// addDecodeSynonyms: to configure them. +- (id)initWithString:(NSString *)string; + +// Indicates whether padding is performed during encoding. +- (BOOL)doPad; +- (void)setDoPad:(BOOL)doPad; + +// Sets the padding character to use during encoding. +- (void)setPaddingChar:(char)c; + +// Encode a raw binary buffer to a 7-bit ASCII string. +- (NSString *)encode:(NSData *)data; + +// Decode a 7-bit ASCII string to a raw binary buffer. +- (NSData *)decode:(NSString *)string; + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDStringEncoding.m b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDStringEncoding.m new file mode 100644 index 0000000..e1ab269 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDStringEncoding.m @@ -0,0 +1,208 @@ +// +// FIRInstanceIDStringEncoding.m +// +// Copyright 2009 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +// This is a copy of GTMStringEncoding. FIRInstanceID wants to avoid +// a CocoaPods GTM dependency. Hence we use our own version of StringEncoding. + +#import "FIRInstanceIDStringEncoding.h" + +#import "FIRInstanceIDLogger.h" + +enum { kUnknownChar = -1, kPaddingChar = -2, kIgnoreChar = -3 }; + +@implementation FIRInstanceIDStringEncoding + ++ (id)rfc4648Base64WebsafeStringEncoding { + FIRInstanceIDStringEncoding *ret = [self + stringEncodingWithString:@"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"]; + + [ret setPaddingChar:'=']; + [ret setDoPad:YES]; + return ret; +} + +static inline int lcm(int a, int b) { + for (int aa = a, bb = b;;) { + if (aa == bb) + return aa; + else if (aa < bb) + aa += a; + else + bb += b; + } +} + ++ (id)stringEncodingWithString:(NSString *)string { + return [[FIRInstanceIDStringEncoding alloc] initWithString:string]; +} + +- (id)initWithString:(NSString *)string { + if ((self = [super init])) { + charMapData_ = [string dataUsingEncoding:NSASCIIStringEncoding]; + if (!charMapData_) { + // Unable to convert string to ASCII + return nil; + } + charMap_ = (char *)[charMapData_ bytes]; + NSUInteger length = [charMapData_ length]; + if (length < 2 || length > 128 || length & (length - 1)) { + // Length not a power of 2 between 2 and 128 + return nil; + } + + memset(reverseCharMap_, kUnknownChar, sizeof(reverseCharMap_)); + for (unsigned int i = 0; i < length; i++) { + if (reverseCharMap_[(int)charMap_[i]] != kUnknownChar) { + // Duplicate character at |i| + return nil; + } + reverseCharMap_[(int)charMap_[i]] = i; + } + + for (NSUInteger i = 1; i < length; i <<= 1) shift_++; + mask_ = (1 << shift_) - 1; + padLen_ = lcm(8, shift_) / shift_; + } + return self; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"", 1 << shift_, charMapData_]; +} + +- (BOOL)doPad { + return doPad_; +} + +- (void)setDoPad:(BOOL)doPad { + doPad_ = doPad; +} + +- (void)setPaddingChar:(char)c { + paddingChar_ = c; + reverseCharMap_[(int)c] = kPaddingChar; +} + +- (NSString *)encode:(NSData *)inData { + NSUInteger inLen = [inData length]; + if (inLen <= 0) { + // Empty input + return @""; + } + unsigned char *inBuf = (unsigned char *)[inData bytes]; + NSUInteger inPos = 0; + + NSUInteger outLen = (inLen * 8 + shift_ - 1) / shift_; + if (doPad_) { + outLen = ((outLen + padLen_ - 1) / padLen_) * padLen_; + } + NSMutableData *outData = [NSMutableData dataWithLength:outLen]; + unsigned char *outBuf = (unsigned char *)[outData mutableBytes]; + NSUInteger outPos = 0; + + unsigned int buffer = inBuf[inPos++]; + int bitsLeft = 8; + while (bitsLeft > 0 || inPos < inLen) { + if (bitsLeft < shift_) { + if (inPos < inLen) { + buffer <<= 8; + buffer |= (inBuf[inPos++] & 0xff); + bitsLeft += 8; + } else { + int pad = shift_ - bitsLeft; + buffer <<= pad; + bitsLeft += pad; + } + } + unsigned int idx = (buffer >> (bitsLeft - shift_)) & mask_; + bitsLeft -= shift_; + outBuf[outPos++] = charMap_[idx]; + } + + if (doPad_) { + while (outPos < outLen) outBuf[outPos++] = paddingChar_; + } + + if (outPos != outLen) { + FIRInstanceIDLoggerError(kFIRInstanceIDStringEncodingBufferUnderflow, + @"Underflowed output buffer"); + return nil; + } + [outData setLength:outPos]; + + return [[NSString alloc] initWithData:outData encoding:NSASCIIStringEncoding]; +} + +- (NSData *)decode:(NSString *)inString { + char *inBuf = (char *)[inString cStringUsingEncoding:NSASCIIStringEncoding]; + if (!inBuf) { + // Unable to convert buffer to ASCII + return nil; + } + NSUInteger inLen = strlen(inBuf); + + NSUInteger outLen = inLen * shift_ / 8; + NSMutableData *outData = [NSMutableData dataWithLength:outLen]; + unsigned char *outBuf = (unsigned char *)[outData mutableBytes]; + NSUInteger outPos = 0; + + int buffer = 0; + int bitsLeft = 0; + BOOL expectPad = NO; + for (NSUInteger i = 0; i < inLen; i++) { + int val = reverseCharMap_[(int)inBuf[i]]; + switch (val) { + case kIgnoreChar: + break; + case kPaddingChar: + expectPad = YES; + break; + case kUnknownChar: + // Unexpected data at input pos |i| + return nil; + default: + if (expectPad) { + // Expected further padding characters + return nil; + } + buffer <<= shift_; + buffer |= val & mask_; + bitsLeft += shift_; + if (bitsLeft >= 8) { + outBuf[outPos++] = (unsigned char)(buffer >> (bitsLeft - 8)); + bitsLeft -= 8; + } + break; + } + } + + if (bitsLeft && buffer & ((1 << bitsLeft) - 1)) { + // Incomplete trailing data + return nil; + } + + // Shorten buffer if needed due to padding chars + if (outPos > outLen) { + FIRInstanceIDLoggerError(kFIRInstanceIDStringEncodingBufferOverflow, @"Overflowed buffer"); + } + [outData setLength:outPos]; + + return outData; +} + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenDeleteOperation.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenDeleteOperation.h new file mode 100644 index 0000000..b6723fd --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenDeleteOperation.h @@ -0,0 +1,31 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceIDTokenOperation.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRInstanceIDTokenDeleteOperation : FIRInstanceIDTokenOperation + +- (instancetype)initWithAuthorizedEntity:(nullable NSString *)authorizedEntity + scope:(nullable NSString *)scope + checkinPreferences:(FIRInstanceIDCheckinPreferences *)checkinPreferences + instanceID:(nullable NSString *)instanceID + action:(FIRInstanceIDTokenAction)action; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenDeleteOperation.m b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenDeleteOperation.m new file mode 100644 index 0000000..34511c4 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenDeleteOperation.m @@ -0,0 +1,118 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceIDTokenDeleteOperation.h" + +#import "FIRInstanceIDCheckinPreferences.h" +#import "FIRInstanceIDDefines.h" +#import "FIRInstanceIDLogger.h" +#import "FIRInstanceIDTokenOperation+Private.h" +#import "FIRInstanceIDURLQueryItem.h" +#import "FIRInstanceIDUtilities.h" +#import "NSError+FIRInstanceID.h" + +@implementation FIRInstanceIDTokenDeleteOperation + +- (instancetype)initWithAuthorizedEntity:(NSString *)authorizedEntity + scope:(NSString *)scope + checkinPreferences:(FIRInstanceIDCheckinPreferences *)checkinPreferences + instanceID:(NSString *)instanceID + action:(FIRInstanceIDTokenAction)action { + self = [super initWithAction:action + forAuthorizedEntity:authorizedEntity + scope:scope + options:nil + checkinPreferences:checkinPreferences + instanceID:instanceID]; + if (self) { + } + return self; +} + +- (void)performTokenOperation { + NSMutableURLRequest *request = [self tokenRequest]; + + // Build form-encoded body + NSString *deviceAuthID = self.checkinPreferences.deviceID; + NSMutableArray *queryItems = + [FIRInstanceIDTokenOperation standardQueryItemsWithDeviceID:deviceAuthID scope:self.scope]; + [queryItems addObject:[FIRInstanceIDURLQueryItem queryItemWithName:@"delete" value:@"true"]]; + if (self.action == FIRInstanceIDTokenActionDeleteTokenAndIID) { + [queryItems addObject:[FIRInstanceIDURLQueryItem queryItemWithName:@"iid-operation" + value:@"delete"]]; + } + if (self.authorizedEntity) { + [queryItems addObject:[FIRInstanceIDURLQueryItem queryItemWithName:@"sender" + value:self.authorizedEntity]]; + } + // Typically we include our public key-signed url items, but in some cases (like deleting all FCM + // tokens), we don't. + if (self.instanceID.length > 0) { + [queryItems addObjectsFromArray:[self queryItemsWithInstanceID:self.instanceID]]; + } + + NSString *content = FIRInstanceIDQueryFromQueryItems(queryItems); + request.HTTPBody = [content dataUsingEncoding:NSUTF8StringEncoding]; + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenDeleteOperationFetchRequest, + @"Unregister request to %@ content: %@", FIRInstanceIDRegisterServer(), + content); + + FIRInstanceID_WEAKIFY(self); + void (^requestHandler)(NSData *, NSURLResponse *, NSError *) = + ^(NSData *data, NSURLResponse *response, NSError *error) { + FIRInstanceID_STRONGIFY(self); + [self handleResponseWithData:data response:response error:error]; + }; + + // Test block + if (self.testBlock) { + self.testBlock(request, requestHandler); + return; + } + + NSURLSession *session = [FIRInstanceIDTokenOperation sharedURLSession]; + self.dataTask = [session dataTaskWithRequest:request completionHandler:requestHandler]; + [self.dataTask resume]; +} + +- (void)handleResponseWithData:(NSData *)data + response:(NSURLResponse *)response + error:(NSError *)error { + if (error) { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenDeleteOperationRequestError, + @"Device unregister HTTP fetch error. Error code: %ld", + _FIRInstanceID_L(error.code)); + [self finishWithResult:FIRInstanceIDTokenOperationError token:nil error:error]; + return; + } + + NSString *dataResponse = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + if (dataResponse.length == 0) { + NSError *error = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeUnknown]; + [self finishWithResult:FIRInstanceIDTokenOperationError token:nil error:error]; + return; + } + + if (![dataResponse hasPrefix:@"deleted="] && ![dataResponse hasPrefix:@"token="]) { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenDeleteOperationBadResponse, + @"Invalid unregister response %@", response); + NSError *error = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeUnknown]; + [self finishWithResult:FIRInstanceIDTokenOperationError token:nil error:error]; + return; + } + [self finishWithResult:FIRInstanceIDTokenOperationSucceeded token:nil error:nil]; +} +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenFetchOperation.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenFetchOperation.h new file mode 100644 index 0000000..6fa800e --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenFetchOperation.h @@ -0,0 +1,34 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceIDTokenOperation.h" + +NS_ASSUME_NONNULL_BEGIN + +FOUNDATION_EXPORT NSString *const kFIRInstanceIDFirebaseUserAgentKey; + +FOUNDATION_EXPORT NSString *const kFIRInstanceIDFirebaseHeartbeatKey; + +@interface FIRInstanceIDTokenFetchOperation : FIRInstanceIDTokenOperation + +- (instancetype)initWithAuthorizedEntity:(NSString *)authorizedEntity + scope:(NSString *)scope + options:(nullable NSDictionary *)options + checkinPreferences:(FIRInstanceIDCheckinPreferences *)checkinPreferences + instanceID:(NSString *)instanceID; + +@end +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenFetchOperation.m b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenFetchOperation.m new file mode 100644 index 0000000..bdc8701 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenFetchOperation.m @@ -0,0 +1,207 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceIDTokenFetchOperation.h" + +#import "FIRInstanceIDCheckinPreferences.h" +#import "FIRInstanceIDConstants.h" +#import "FIRInstanceIDDefines.h" +#import "FIRInstanceIDLogger.h" +#import "FIRInstanceIDTokenOperation+Private.h" +#import "FIRInstanceIDURLQueryItem.h" +#import "FIRInstanceIDUtilities.h" +#import "NSError+FIRInstanceID.h" + +#import +#import + +// We can have a static int since this error should theoretically only +// happen once (for the first time). If it repeats there is something +// else that is wrong. +static int phoneRegistrationErrorRetryCount = 0; +static const int kMaxPhoneRegistrationErrorRetryCount = 10; +NSString *const kFIRInstanceIDFirebaseUserAgentKey = @"X-firebase-client"; +NSString *const kFIRInstanceIDFirebaseHeartbeatKey = @"X-firebase-client-log-type"; +NSString *const kFIRInstanceIDHeartbeatTag = @"fire-iid"; + +@implementation FIRInstanceIDTokenFetchOperation + +- (instancetype)initWithAuthorizedEntity:(NSString *)authorizedEntity + scope:(NSString *)scope + options:(nullable NSDictionary *)options + checkinPreferences:(FIRInstanceIDCheckinPreferences *)checkinPreferences + instanceID:(NSString *)instanceID { + self = [super initWithAction:FIRInstanceIDTokenActionFetch + forAuthorizedEntity:authorizedEntity + scope:scope + options:options + checkinPreferences:checkinPreferences + instanceID:instanceID]; + if (self) { + } + return self; +} + +- (void)performTokenOperation { + NSMutableURLRequest *request = [self tokenRequest]; + NSString *checkinVersionInfo = self.checkinPreferences.versionInfo; + [request setValue:checkinVersionInfo forHTTPHeaderField:@"info"]; + [request setValue:[FIRApp firebaseUserAgent] + forHTTPHeaderField:kFIRInstanceIDFirebaseUserAgentKey]; + [request setValue:@([FIRHeartbeatInfo heartbeatCodeForTag:kFIRInstanceIDHeartbeatTag]).stringValue + forHTTPHeaderField:kFIRInstanceIDFirebaseHeartbeatKey]; + + // Build form-encoded body + NSString *deviceAuthID = self.checkinPreferences.deviceID; + NSMutableArray *queryItems = + [[self class] standardQueryItemsWithDeviceID:deviceAuthID scope:self.scope]; + [queryItems addObject:[FIRInstanceIDURLQueryItem queryItemWithName:@"sender" + value:self.authorizedEntity]]; + [queryItems addObject:[FIRInstanceIDURLQueryItem queryItemWithName:@"X-subtype" + value:self.authorizedEntity]]; + + [queryItems addObjectsFromArray:[self queryItemsWithInstanceID:self.instanceID]]; + + // Create query items from passed-in options + id apnsTokenData = self.options[kFIRInstanceIDTokenOptionsAPNSKey]; + id apnsSandboxValue = self.options[kFIRInstanceIDTokenOptionsAPNSIsSandboxKey]; + if ([apnsTokenData isKindOfClass:[NSData class]] && + [apnsSandboxValue isKindOfClass:[NSNumber class]]) { + NSString *APNSString = FIRInstanceIDAPNSTupleStringForTokenAndServerType( + apnsTokenData, ((NSNumber *)apnsSandboxValue).boolValue); + // The name of the query item happens to be the same as the dictionary key + FIRInstanceIDURLQueryItem *item = + [FIRInstanceIDURLQueryItem queryItemWithName:kFIRInstanceIDTokenOptionsAPNSKey + value:APNSString]; + [queryItems addObject:item]; + } + id firebaseAppID = self.options[kFIRInstanceIDTokenOptionsFirebaseAppIDKey]; + if ([firebaseAppID isKindOfClass:[NSString class]]) { + // The name of the query item happens to be the same as the dictionary key + FIRInstanceIDURLQueryItem *item = + [FIRInstanceIDURLQueryItem queryItemWithName:kFIRInstanceIDTokenOptionsFirebaseAppIDKey + value:(NSString *)firebaseAppID]; + [queryItems addObject:item]; + } + + NSString *content = FIRInstanceIDQueryFromQueryItems(queryItems); + request.HTTPBody = [content dataUsingEncoding:NSUTF8StringEncoding]; + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenFetchOperationFetchRequest, + @"Register request to %@ content: %@", FIRInstanceIDRegisterServer(), + content); + + FIRInstanceID_WEAKIFY(self); + void (^requestHandler)(NSData *, NSURLResponse *, NSError *) = + ^(NSData *data, NSURLResponse *response, NSError *error) { + FIRInstanceID_STRONGIFY(self); + [self handleResponseWithData:data response:response error:error]; + }; + + // Test block + if (self.testBlock) { + self.testBlock(request, requestHandler); + return; + } + + NSURLSession *session = [FIRInstanceIDTokenOperation sharedURLSession]; + self.dataTask = [session dataTaskWithRequest:request completionHandler:requestHandler]; + [self.dataTask resume]; +} + +#pragma mark - Request Handling + +- (void)handleResponseWithData:(NSData *)data + response:(NSURLResponse *)response + error:(NSError *)error { + if (error) { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenFetchOperationRequestError, + @"Token fetch HTTP error. Error Code: %ld", (long)error.code); + [self finishWithResult:FIRInstanceIDTokenOperationError token:nil error:error]; + return; + } + NSString *dataResponse = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + + if (dataResponse.length == 0) { + NSError *error = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeUnknown]; + [self finishWithResult:FIRInstanceIDTokenOperationError token:nil error:error]; + return; + } + NSDictionary *parsedResponse = [self parseFetchTokenResponse:dataResponse]; + + if ([parsedResponse[@"token"] length]) { + [self finishWithResult:FIRInstanceIDTokenOperationSucceeded + token:parsedResponse[@"token"] + error:nil]; + return; + } + + NSString *errorValue = parsedResponse[@"Error"]; + NSError *responseError; + if (errorValue.length) { + NSArray *errorComponents = [errorValue componentsSeparatedByString:@":"]; + // HACK (Kansas replication delay), PHONE_REGISTRATION_ERROR on App + // uninstall and reinstall. + if ([errorComponents containsObject:@"PHONE_REGISTRATION_ERROR"]) { + // Encountered issue http://b/27043795 + // Retry register until successful or another error encountered or a + // certain number of tries are over. + + if (phoneRegistrationErrorRetryCount < kMaxPhoneRegistrationErrorRetryCount) { + const int nextRetryInterval = 1 << phoneRegistrationErrorRetryCount; + FIRInstanceID_WEAKIFY(self); + + dispatch_after( + dispatch_time(DISPATCH_TIME_NOW, (int64_t)(nextRetryInterval * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + FIRInstanceID_STRONGIFY(self); + phoneRegistrationErrorRetryCount++; + [self performTokenOperation]; + }); + return; + } + } else if ([errorComponents containsObject:kFIRInstanceID_CMD_RST]) { + // Server detected the identity we use is no longer valid. + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center postNotificationName:kFIRInstanceIDIdentityInvalidatedNotification object:nil]; + + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeInternal001, + @"Identity is invalid. Server request identity reset."); + responseError = + [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeInvalidIdentity]; + } + } + if (!responseError) { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenFetchOperationBadResponse, + @"Invalid fetch response, expected 'token' or 'Error' key"); + responseError = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeUnknown]; + } + [self finishWithResult:FIRInstanceIDTokenOperationError token:nil error:responseError]; +} + +// expect a response e.g. "token=\nGOOG.ttl=123" +- (NSDictionary *)parseFetchTokenResponse:(NSString *)response { + NSArray *lines = [response componentsSeparatedByString:@"\n"]; + NSMutableDictionary *parsedResponse = [NSMutableDictionary dictionary]; + for (NSString *line in lines) { + NSArray *keyAndValue = [line componentsSeparatedByString:@"="]; + if ([keyAndValue count] > 1) { + parsedResponse[keyAndValue[0]] = keyAndValue[1]; + } + } + return parsedResponse; +} + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenInfo.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenInfo.h new file mode 100644 index 0000000..3b752a3 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenInfo.h @@ -0,0 +1,92 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRInstanceIDAPNSInfo.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * Represents an Instance ID token, and all of the relevant information + * associated with it. It can read from and write to an NSDictionary object, for + * simple serialization. + */ +@interface FIRInstanceIDTokenInfo : NSObject + +/// The authorized entity (also known as Sender ID), associated with the token. +@property(nonatomic, readonly, copy) NSString *authorizedEntity; +/// The scope associated with the token. This is an arbitrary string, typically "*". +@property(nonatomic, readonly, copy) NSString *scope; +/// The token value itself, with which all other properties are associated. +@property(nonatomic, readonly, copy) NSString *token; + +// These properties are nullable because they might not exist for tokens fetched from +// legacy storage formats. + +/// The app version that this token represents. +@property(nonatomic, readonly, copy, nullable) NSString *appVersion; +/// The Firebase app ID (also known as GMP App ID), that this token is associated with. +@property(nonatomic, readonly, copy, nullable) NSString *firebaseAppID; + +/// Tokens may not always be associated with an APNs token, and may be associated after +/// being created. +@property(nonatomic, strong, nullable) FIRInstanceIDAPNSInfo *APNSInfo; +/// The time that this token info was updated. The cache time is writeable, since in +/// some cases the token info may be refreshed from the server. In those situations, +/// the cacheTime would be updated. +@property(nonatomic, copy, nullable) NSDate *cacheTime; + +/** + * Initializes a FIRInstanceIDTokenInfo object with the required parameters. These + * parameters represent all the relevant associated data with a token. + * + * @param authorizedEntity The authorized entity (also known as Sender ID). + * @param scope The scope of the token, typically "*" meaning + * it's a "default scope". + * @param token The token value itself. + * @param appVersion The application version that this token is associated with. + * @param firebaseAppID The Firebase app ID which this token is associated with. + * @return An instance of FIRInstanceIDTokenInfo. + */ +- (instancetype)initWithAuthorizedEntity:(NSString *)authorizedEntity + scope:(NSString *)scope + token:(NSString *)token + appVersion:(nullable NSString *)appVersion + firebaseAppID:(nullable NSString *)firebaseAppID; + +/** + * Check whether the token is still fresh based on: + * 1. Last fetch token is within the 7 days. + * 2. Language setting is not changed. + * 3. App version is current. + * 4. GMP App ID is current. + * 5. token is consistent with the current IID. + * 6. APNS info has changed. + * @param IID The app identifiier that is used to check if token is prefixed with. + * @return If token is fresh. + * + */ +- (BOOL)isFreshWithIID:(NSString *)IID; + +/* + * Check whether the token is default token. + */ +- (BOOL)isDefaultToken; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenInfo.m b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenInfo.m new file mode 100644 index 0000000..59b0e92 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenInfo.m @@ -0,0 +1,212 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceIDTokenInfo.h" + +#import "FIRInstanceIDConstants.h" +#import "FIRInstanceIDLogger.h" +#import "FIRInstanceIDUtilities.h" + +/** + * @enum Token Info Dictionary Key Constants + * @discussion The keys that are checked when a token info is + * created from a dictionary. The same keys are used + * when decoding/encoding an archive. + */ +/// Specifies a dictonary key whose value represents the authorized entity, or +/// Sender ID for the token. +static NSString *const kFIRInstanceIDAuthorizedEntityKey = @"authorized_entity"; +/// Specifies a dictionary key whose value represents the scope of the token, +/// typically "*". +static NSString *const kFIRInstanceIDScopeKey = @"scope"; +/// Specifies a dictionary key which represents the token value itself. +static NSString *const kFIRInstanceIDTokenKey = @"token"; +/// Specifies a dictionary key which represents the app version associated +/// with the token. +static NSString *const kFIRInstanceIDAppVersionKey = @"app_version"; +/// Specifies a dictionary key which represents the GMP App ID associated with +/// the token. +static NSString *const kFIRInstanceIDFirebaseAppIDKey = @"firebase_app_id"; +/// Specifies a dictionary key representing an archive for a +/// `FIRInstanceIDAPNSInfo` object. +static NSString *const kFIRInstanceIDAPNSInfoKey = @"apns_info"; +/// Specifies a dictionary key representing the "last cached" time for the token. +static NSString *const kFIRInstanceIDCacheTimeKey = @"cache_time"; +/// Default interval that token stays fresh. +const NSTimeInterval kDefaultFetchTokenInterval = 7 * 24 * 60 * 60; // 7 days. + +@implementation FIRInstanceIDTokenInfo + +- (instancetype)initWithAuthorizedEntity:(NSString *)authorizedEntity + scope:(NSString *)scope + token:(NSString *)token + appVersion:(NSString *)appVersion + firebaseAppID:(NSString *)firebaseAppID { + self = [super init]; + if (self) { + _authorizedEntity = [authorizedEntity copy]; + _scope = [scope copy]; + _token = [token copy]; + _appVersion = [appVersion copy]; + _firebaseAppID = [firebaseAppID copy]; + } + return self; +} + +- (BOOL)isFreshWithIID:(NSString *)IID { + // Last fetch token cache time could be null if token is from legacy storage format. Then token is + // considered not fresh and should be refreshed and overwrite with the latest storage format. + if (!IID) { + return NO; + } + if (!_cacheTime) { + return NO; + } + + // Check if it's consistent with IID + if (![self.token hasPrefix:IID]) { + return NO; + } + + // Check if app has just been updated to a new version. + NSString *currentAppVersion = FIRInstanceIDCurrentAppVersion(); + if (!_appVersion || ![_appVersion isEqualToString:currentAppVersion]) { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenManager004, + @"Invalidating cached token for %@ (%@) due to app version change.", + _authorizedEntity, _scope); + return NO; + } + + // Check if GMP App ID has changed + NSString *currentFirebaseAppID = FIRInstanceIDFirebaseAppID(); + if (!_firebaseAppID || ![_firebaseAppID isEqualToString:currentFirebaseAppID]) { + FIRInstanceIDLoggerDebug( + kFIRInstanceIDMessageCodeTokenInfoFirebaseAppIDChanged, + @"Invalidating cached token due to Firebase App IID change from %@ to %@", _firebaseAppID, + currentFirebaseAppID); + return NO; + } + + // Check whether locale has changed, if yes, token needs to be updated with server for locale + // information. + if (FIRInstanceIDHasLocaleChanged()) { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenInfoLocaleChanged, + @"Invalidating cached token due to locale change"); + return NO; + } + + // Locale is not changed, check whether token has been fetched within 7 days. + NSTimeInterval lastFetchTokenTimestamp = [_cacheTime timeIntervalSince1970]; + NSTimeInterval currentTimestamp = FIRInstanceIDCurrentTimestampInSeconds(); + NSTimeInterval timeSinceLastFetchToken = currentTimestamp - lastFetchTokenTimestamp; + return (timeSinceLastFetchToken < kDefaultFetchTokenInterval); +} + +- (BOOL)isDefaultToken { + return [self.scope isEqualToString:kFIRInstanceIDDefaultTokenScope]; +} + +#pragma mark - NSCoding + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + // These value cannot be nil + + id authorizedEntity = [aDecoder decodeObjectForKey:kFIRInstanceIDAuthorizedEntityKey]; + if (![authorizedEntity isKindOfClass:[NSString class]]) { + return nil; + } + + id scope = [aDecoder decodeObjectForKey:kFIRInstanceIDScopeKey]; + if (![scope isKindOfClass:[NSString class]]) { + return nil; + } + + id token = [aDecoder decodeObjectForKey:kFIRInstanceIDTokenKey]; + if (![token isKindOfClass:[NSString class]]) { + return nil; + } + + // These values are nullable, so only fail the decode if the type does not match + + id appVersion = [aDecoder decodeObjectForKey:kFIRInstanceIDAppVersionKey]; + if (appVersion && ![appVersion isKindOfClass:[NSString class]]) { + return nil; + } + + id firebaseAppID = [aDecoder decodeObjectForKey:kFIRInstanceIDFirebaseAppIDKey]; + if (firebaseAppID && ![firebaseAppID isKindOfClass:[NSString class]]) { + return nil; + } + + id rawAPNSInfo = [aDecoder decodeObjectForKey:kFIRInstanceIDAPNSInfoKey]; + if (rawAPNSInfo && ![rawAPNSInfo isKindOfClass:[NSData class]]) { + return nil; + } + + FIRInstanceIDAPNSInfo *APNSInfo = nil; + if (rawAPNSInfo) { + // TODO(chliangGoogle: Use the new API and secureCoding protocol. + @try { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + APNSInfo = [NSKeyedUnarchiver unarchiveObjectWithData:rawAPNSInfo]; +#pragma clang diagnostic pop + } @catch (NSException *exception) { + FIRInstanceIDLoggerInfo(kFIRInstanceIDMessageCodeTokenInfoBadAPNSInfo, + @"Could not parse raw APNS Info while parsing archived token info."); + APNSInfo = nil; + } @finally { + } + } + + id cacheTime = [aDecoder decodeObjectForKey:kFIRInstanceIDCacheTimeKey]; + if (cacheTime && ![cacheTime isKindOfClass:[NSDate class]]) { + return nil; + } + + self = [super init]; + if (self) { + _authorizedEntity = authorizedEntity; + _scope = scope; + _token = token; + _appVersion = appVersion; + _firebaseAppID = firebaseAppID; + _APNSInfo = APNSInfo; + _cacheTime = cacheTime; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:self.authorizedEntity forKey:kFIRInstanceIDAuthorizedEntityKey]; + [aCoder encodeObject:self.scope forKey:kFIRInstanceIDScopeKey]; + [aCoder encodeObject:self.token forKey:kFIRInstanceIDTokenKey]; + [aCoder encodeObject:self.appVersion forKey:kFIRInstanceIDAppVersionKey]; + [aCoder encodeObject:self.firebaseAppID forKey:kFIRInstanceIDFirebaseAppIDKey]; + NSData *rawAPNSInfo; + if (self.APNSInfo) { + // TODO(chliangGoogle: Use the new API and secureCoding protocol. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + rawAPNSInfo = [NSKeyedArchiver archivedDataWithRootObject:self.APNSInfo]; +#pragma clang diagnostic pop + + [aCoder encodeObject:rawAPNSInfo forKey:kFIRInstanceIDAPNSInfoKey]; + } + [aCoder encodeObject:self.cacheTime forKey:kFIRInstanceIDCacheTimeKey]; +} + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenManager.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenManager.h new file mode 100644 index 0000000..46e1ac8 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenManager.h @@ -0,0 +1,149 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceID.h" + +@class FIRInstanceIDAuthService; +@class FIRInstanceIDCheckinPreferences; +@class FIRInstanceIDTokenInfo; +@class FIRInstanceIDStore; + +typedef NS_OPTIONS(NSUInteger, FIRInstanceIDInvalidTokenReason) { + FIRInstanceIDInvalidTokenReasonNone = 0, // 0 + FIRInstanceIDInvalidTokenReasonAppVersion = (1 << 0), // 0...00001 + FIRInstanceIDInvalidTokenReasonAPNSToken = (1 << 1), // 0...00010 +}; + +/** + * Manager for the InstanceID token requests i.e `newToken` and `deleteToken`. This + * manages the overall interaction of the `InstanceIDStore`, the token register + * service and the callbacks associated with `GCMInstanceID`. + */ +@interface FIRInstanceIDTokenManager : NSObject + +/// Expose the auth service, so it can be used by others +@property(nonatomic, readonly, strong) FIRInstanceIDAuthService *authService; + +/** + * Fetch new token for the given authorizedEntity and scope. This makes an + * asynchronous request to the InstanceID backend to create a new token for + * the service and returns it. This will replace any old token for the given + * authorizedEntity and scope that has been cached before. + * + * @param authorizedEntity The authorized entity for the token, should not be nil. + * @param scope The scope for the token, should not be nil. + * @param instanceID The unique string identifying the app instance. + * @param options The options to be added to the fetch request. + * @param handler The handler to be invoked once we have the token or the + * fetch request to InstanceID backend results in an error. Also + * since it's a public handler it should always be called + * asynchronously. This should be non-nil. + */ +- (void)fetchNewTokenWithAuthorizedEntity:(NSString *)authorizedEntity + scope:(NSString *)scope + instanceID:(NSString *)instanceID + options:(NSDictionary *)options + handler:(FIRInstanceIDTokenHandler)handler; + +/** + * Return the cached token info, if one exists, for the given authorizedEntity and scope. + * + * @param authorizedEntity The authorized entity for the token. + * @param scope The scope for the token. + * + * @return The cached token info, if available, matching the parameters. + */ +- (FIRInstanceIDTokenInfo *)cachedTokenInfoWithAuthorizedEntity:(NSString *)authorizedEntity + scope:(NSString *)scope; + +/** + * Delete the token for the given authorizedEntity and scope. If the token has + * been cached, it will be deleted from the store. It will also make an + * asynchronous request to the InstanceID backend to invalidate the token. + * + * @param authorizedEntity The authorized entity for the token, should not be nil. + * @param scope The scope for the token, should not be nil. + * @param instanceID The unique string identifying the app instance. + * @param handler The handler to be invoked once the delete request to + * InstanceID backend has returned. If the request was + * successful we invoke the handler with a nil error; + * otherwise we call it with an appropriate error. Also since + * it's a public handler it should always be called + * asynchronously. This should be non-nil. + */ +- (void)deleteTokenWithAuthorizedEntity:(NSString *)authorizedEntity + scope:(NSString *)scope + instanceID:(NSString *)instanceID + handler:(FIRInstanceIDDeleteTokenHandler)handler; + +/** + * Deletes all cached tokens from the persistent store. This method should only be triggered + * when InstanceID is deleted + * + * @param instanceID The unique string identifying the app instance. + * @param handler The handler to be invoked once the delete request to InstanceID backend + * has returned. If the request was successful we invoke the handler with + * a nil error; else we pass in an appropriate error. This should be non-nil + * and be called asynchronously. + */ +- (void)deleteAllTokensWithInstanceID:(NSString *)instanceID + handler:(FIRInstanceIDDeleteHandler)handler; + +/** + * Deletes all cached tokens from the persistent store. + * @param handler The callback handler which is invoked when tokens deletion is complete, + * with an error if there is any. + * + */ +- (void)deleteAllTokensLocallyWithHandler:(void (^)(NSError *error))handler; + +/** + * Stop any ongoing token operations. + */ +- (void)stopAllTokenOperations; + +#pragma mark - Invalidating Cached Tokens + +/** + * Invalidate any cached tokens, if the app version has changed since last launch or if the token + * is cached for more than 7 days. + * @param IID The cached instanceID, check if token is prefixed by such IID. + * + * @return Whether we should fetch default token from server. + * + * @discussion This should safely be called prior to any tokens being retrieved from + * the cache or being fetched from the network. + */ +- (BOOL)checkTokenRefreshPolicyWithIID:(NSString *)IID; + +/** + * Upon being provided with different APNs or sandbox, any locally cached tokens + * should be deleted, and the new APNs token should be cached. + * + * @discussion It is possible for this method to be called while token operations are + * in-progress or queued. In this case, the in-flight token operations will have stale + * APNs information. The default token is checked for being out-of-date by Instance ID, + * and re-fetched. Custom tokens are not currently checked. + * + * @param deviceToken The APNS device token, provided by the operating system. + * @param isSandbox YES if the device token is for the sandbox environment, NO otherwise. + * + * @return The array of FIRInstanceIDTokenInfo objects which were invalidated. + */ +- (NSArray *)updateTokensToAPNSDeviceToken:(NSData *)deviceToken + isSandbox:(BOOL)isSandbox; + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenManager.m b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenManager.m new file mode 100644 index 0000000..0ebcfc8 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenManager.m @@ -0,0 +1,340 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceIDTokenManager.h" + +#import "FIRInstanceIDAuthKeyChain.h" +#import "FIRInstanceIDAuthService.h" +#import "FIRInstanceIDCheckinPreferences.h" +#import "FIRInstanceIDConstants.h" +#import "FIRInstanceIDDefines.h" +#import "FIRInstanceIDLogger.h" +#import "FIRInstanceIDStore.h" +#import "FIRInstanceIDTokenDeleteOperation.h" +#import "FIRInstanceIDTokenFetchOperation.h" +#import "FIRInstanceIDTokenInfo.h" +#import "FIRInstanceIDTokenOperation.h" +#import "NSError+FIRInstanceID.h" + +@interface FIRInstanceIDTokenManager () + +@property(nonatomic, readwrite, strong) FIRInstanceIDStore *instanceIDStore; +@property(nonatomic, readwrite, strong) FIRInstanceIDAuthService *authService; +@property(nonatomic, readonly, strong) NSOperationQueue *tokenOperations; + +@property(nonatomic, readwrite, strong) FIRInstanceIDAPNSInfo *currentAPNSInfo; + +@end + +@implementation FIRInstanceIDTokenManager + +- (instancetype)init { + self = [super init]; + if (self) { + _instanceIDStore = [[FIRInstanceIDStore alloc] initWithDelegate:self]; + _authService = [[FIRInstanceIDAuthService alloc] initWithStore:_instanceIDStore]; + [self configureTokenOperations]; + } + return self; +} + +- (void)dealloc { + [self stopAllTokenOperations]; +} + +- (void)configureTokenOperations { + _tokenOperations = [[NSOperationQueue alloc] init]; + _tokenOperations.name = @"com.google.iid-token-operations"; + // For now, restrict the operations to be serial, because in some cases (like if the + // authorized entity and scope are the same), order matters. + // If we have to deal with several different token requests simultaneously, it would be a good + // idea to add some better intelligence around this (performing unrelated token operations + // simultaneously, etc.). + _tokenOperations.maxConcurrentOperationCount = 1; + if ([_tokenOperations respondsToSelector:@selector(qualityOfService)]) { + _tokenOperations.qualityOfService = NSOperationQualityOfServiceUtility; + } +} + +- (void)fetchNewTokenWithAuthorizedEntity:(NSString *)authorizedEntity + scope:(NSString *)scope + instanceID:(NSString *)instanceID + options:(NSDictionary *)options + handler:(FIRInstanceIDTokenHandler)handler { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenManager000, + @"Fetch new token for authorizedEntity: %@, scope: %@", authorizedEntity, + scope); + FIRInstanceIDTokenFetchOperation *operation = + [self createFetchOperationWithAuthorizedEntity:authorizedEntity + scope:scope + options:options + instanceID:instanceID]; + FIRInstanceID_WEAKIFY(self); + FIRInstanceIDTokenOperationCompletion completion = + ^(FIRInstanceIDTokenOperationResult result, NSString *_Nullable token, + NSError *_Nullable error) { + FIRInstanceID_STRONGIFY(self); + if (error) { + handler(nil, error); + return; + } + NSString *firebaseAppID = options[kFIRInstanceIDTokenOptionsFirebaseAppIDKey]; + FIRInstanceIDTokenInfo *tokenInfo = [[FIRInstanceIDTokenInfo alloc] + initWithAuthorizedEntity:authorizedEntity + scope:scope + token:token + appVersion:FIRInstanceIDCurrentAppVersion() + firebaseAppID:firebaseAppID]; + tokenInfo.APNSInfo = [[FIRInstanceIDAPNSInfo alloc] initWithTokenOptionsDictionary:options]; + + [self.instanceIDStore + saveTokenInfo:tokenInfo + handler:^(NSError *error) { + if (!error) { + // Do not send the token back in case the save was unsuccessful. Since with + // the new asychronous fetch mechanism this can lead to infinite loops, for + // example, we will return a valid token even though we weren't able to store + // it in our cache. The first token will lead to a onTokenRefresh callback + // wherein the user again calls `getToken` but since we weren't able to save + // it we won't hit the cache but hit the server again leading to an infinite + // loop. + FIRInstanceIDLoggerDebug( + kFIRInstanceIDMessageCodeTokenManager001, + @"Token fetch successful, token: %@, authorizedEntity: %@, scope:%@", + token, authorizedEntity, scope); + + if (handler) { + handler(token, nil); + } + } else { + if (handler) { + handler(nil, error); + } + } + }]; + }; + // Add completion handler, and ensure it's called on the main queue + [operation addCompletionHandler:^(FIRInstanceIDTokenOperationResult result, + NSString *_Nullable token, NSError *_Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(result, token, error); + }); + }]; + [self.tokenOperations addOperation:operation]; +} + +- (FIRInstanceIDTokenInfo *)cachedTokenInfoWithAuthorizedEntity:(NSString *)authorizedEntity + scope:(NSString *)scope { + return [self.instanceIDStore tokenInfoWithAuthorizedEntity:authorizedEntity scope:scope]; +} + +- (void)deleteTokenWithAuthorizedEntity:(NSString *)authorizedEntity + scope:(NSString *)scope + instanceID:(NSString *)instanceID + handler:(FIRInstanceIDDeleteTokenHandler)handler { + if ([self.instanceIDStore tokenInfoWithAuthorizedEntity:authorizedEntity scope:scope]) { + [self.instanceIDStore removeCachedTokenWithAuthorizedEntity:authorizedEntity scope:scope]; + } + // Does not matter if we cannot find it in the cache. Still make an effort to unregister + // from the server. + FIRInstanceIDCheckinPreferences *checkinPreferences = self.authService.checkinPreferences; + FIRInstanceIDTokenDeleteOperation *operation = + [self createDeleteOperationWithAuthorizedEntity:authorizedEntity + scope:scope + checkinPreferences:checkinPreferences + instanceID:instanceID + action:FIRInstanceIDTokenActionDeleteToken]; + + if (handler) { + [operation addCompletionHandler:^(FIRInstanceIDTokenOperationResult result, + NSString *_Nullable token, NSError *_Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^{ + handler(error); + }); + }]; + } + [self.tokenOperations addOperation:operation]; +} + +- (void)deleteAllTokensWithInstanceID:(NSString *)instanceID + handler:(FIRInstanceIDDeleteHandler)handler { + // delete all tokens + FIRInstanceIDCheckinPreferences *checkinPreferences = self.authService.checkinPreferences; + if (!checkinPreferences) { + // The checkin is already deleted. No need to trigger the token delete operation as client no + // longer has the checkin information for server to delete. + dispatch_async(dispatch_get_main_queue(), ^{ + handler(nil); + }); + return; + } + FIRInstanceIDTokenDeleteOperation *operation = + [self createDeleteOperationWithAuthorizedEntity:kFIRInstanceIDKeychainWildcardIdentifier + scope:kFIRInstanceIDKeychainWildcardIdentifier + checkinPreferences:checkinPreferences + instanceID:instanceID + action:FIRInstanceIDTokenActionDeleteTokenAndIID]; + if (handler) { + [operation addCompletionHandler:^(FIRInstanceIDTokenOperationResult result, + NSString *_Nullable token, NSError *_Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^{ + handler(error); + }); + }]; + } + [self.tokenOperations addOperation:operation]; +} + +- (void)deleteAllTokensLocallyWithHandler:(void (^)(NSError *error))handler { + [self.instanceIDStore removeAllCachedTokensWithHandler:handler]; +} + +- (void)stopAllTokenOperations { + [self.authService stopCheckinRequest]; + [self.tokenOperations cancelAllOperations]; +} + +#pragma mark - FIRInstanceIDStoreDelegate + +- (void)store:(FIRInstanceIDStore *)store + didDeleteFCMScopedTokensForCheckin:(FIRInstanceIDCheckinPreferences *)checkin { + // Make a best effort try to delete the old client related state on the FCM server. This is + // required to delete old pubusb registrations which weren't cleared when the app was deleted. + // + // This is only a one time effort. If this call fails the client would still receive duplicate + // pubsub notifications if he is again subscribed to the same topic. + // + // The client state should be cleared on the server for the provided checkin preferences. + FIRInstanceIDTokenDeleteOperation *operation = + [self createDeleteOperationWithAuthorizedEntity:nil + scope:nil + checkinPreferences:checkin + instanceID:nil + action:FIRInstanceIDTokenActionDeleteToken]; + [operation addCompletionHandler:^(FIRInstanceIDTokenOperationResult result, + NSString *_Nullable token, NSError *_Nullable error) { + if (error) { + FIRInstanceIDMessageCode code = + kFIRInstanceIDMessageCodeTokenManagerErrorDeletingFCMTokensOnAppReset; + FIRInstanceIDLoggerDebug(code, @"Failed to delete GCM server registrations on app reset."); + } else { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenManagerDeletedFCMTokensOnAppReset, + @"Successfully deleted GCM server registrations on app reset"); + } + }]; + + [self.tokenOperations addOperation:operation]; +} + +#pragma mark - Unit Testing Stub Helpers +// We really have this method so that we can more easily stub it out for unit testing +- (FIRInstanceIDTokenFetchOperation *) + createFetchOperationWithAuthorizedEntity:(NSString *)authorizedEntity + scope:(NSString *)scope + options:(NSDictionary *)options + instanceID:(NSString *)instanceID { + FIRInstanceIDCheckinPreferences *checkinPreferences = self.authService.checkinPreferences; + FIRInstanceIDTokenFetchOperation *operation = + [[FIRInstanceIDTokenFetchOperation alloc] initWithAuthorizedEntity:authorizedEntity + scope:scope + options:options + checkinPreferences:checkinPreferences + instanceID:instanceID]; + return operation; +} + +// We really have this method so that we can more easily stub it out for unit testing +- (FIRInstanceIDTokenDeleteOperation *) + createDeleteOperationWithAuthorizedEntity:(NSString *)authorizedEntity + scope:(NSString *)scope + checkinPreferences:(FIRInstanceIDCheckinPreferences *)checkinPreferences + instanceID:(NSString *)instanceID + action:(FIRInstanceIDTokenAction)action { + FIRInstanceIDTokenDeleteOperation *operation = + [[FIRInstanceIDTokenDeleteOperation alloc] initWithAuthorizedEntity:authorizedEntity + scope:scope + checkinPreferences:checkinPreferences + instanceID:instanceID + action:action]; + return operation; +} + +#pragma mark - Invalidating Cached Tokens +- (BOOL)checkTokenRefreshPolicyWithIID:(NSString *)IID { + // We know at least one cached token exists. + BOOL shouldFetchDefaultToken = NO; + NSArray *tokenInfos = [self.instanceIDStore cachedTokenInfos]; + + NSMutableArray *tokenInfosToDelete = + [NSMutableArray arrayWithCapacity:tokenInfos.count]; + for (FIRInstanceIDTokenInfo *tokenInfo in tokenInfos) { + if ([tokenInfo isFreshWithIID:IID]) { + // Token is fresh and in right format, do nothing + continue; + } + if ([tokenInfo isDefaultToken]) { + // Default token is expired, do not mark for deletion. Fetch directly from server to + // replace the current one. + shouldFetchDefaultToken = YES; + } else { + // Non-default token is expired, mark for deletion. + [tokenInfosToDelete addObject:tokenInfo]; + } + FIRInstanceIDLoggerDebug( + kFIRInstanceIDMessageCodeTokenManagerInvalidateStaleToken, + @"Invalidating cached token for %@ (%@) due to token is no longer fresh.", + tokenInfo.authorizedEntity, tokenInfo.scope); + } + for (FIRInstanceIDTokenInfo *tokenInfoToDelete in tokenInfosToDelete) { + [self.instanceIDStore removeCachedTokenWithAuthorizedEntity:tokenInfoToDelete.authorizedEntity + scope:tokenInfoToDelete.scope]; + } + return shouldFetchDefaultToken; +} + +- (NSArray *)updateTokensToAPNSDeviceToken:(NSData *)deviceToken + isSandbox:(BOOL)isSandbox { + // Each cached IID token that is missing an APNSInfo, or has an APNSInfo associated should be + // checked and invalidated if needed. + FIRInstanceIDAPNSInfo *APNSInfo = [[FIRInstanceIDAPNSInfo alloc] initWithDeviceToken:deviceToken + isSandbox:isSandbox]; + if ([self.currentAPNSInfo isEqualToAPNSInfo:APNSInfo]) { + return @[]; + } + self.currentAPNSInfo = APNSInfo; + + NSArray *tokenInfos = [self.instanceIDStore cachedTokenInfos]; + NSMutableArray *tokenInfosToDelete = + [NSMutableArray arrayWithCapacity:tokenInfos.count]; + for (FIRInstanceIDTokenInfo *cachedTokenInfo in tokenInfos) { + // Check if the cached APNSInfo is nil, or if it is an old APNSInfo. + if (!cachedTokenInfo.APNSInfo || + ![cachedTokenInfo.APNSInfo isEqualToAPNSInfo:self.currentAPNSInfo]) { + // Mark for invalidation. + [tokenInfosToDelete addObject:cachedTokenInfo]; + } + } + for (FIRInstanceIDTokenInfo *tokenInfoToDelete in tokenInfosToDelete) { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenManagerAPNSChangedTokenInvalidated, + @"Invalidating cached token for %@ (%@) due to APNs token change.", + tokenInfoToDelete.authorizedEntity, tokenInfoToDelete.scope); + [self.instanceIDStore removeCachedTokenWithAuthorizedEntity:tokenInfoToDelete.authorizedEntity + scope:tokenInfoToDelete.scope]; + } + return tokenInfosToDelete; +} + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenOperation+Private.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenOperation+Private.h new file mode 100644 index 0000000..3383875 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenOperation+Private.h @@ -0,0 +1,69 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceIDTokenOperation.h" + +#import "FIRInstanceIDUtilities.h" + +@class FIRInstanceIDURLQueryItem; + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRInstanceIDTokenOperation (Private) + +@property(atomic, strong) NSURLSessionDataTask *dataTask; +@property(readonly, strong) + NSMutableArray *completionHandlers; + +// For testing only +@property(nonatomic, readwrite, copy) FIRInstanceIDURLRequestTestBlock testBlock; + ++ (NSURLSession *)sharedURLSession; + +#pragma mark - Initialization +- (instancetype)initWithAction:(FIRInstanceIDTokenAction)action + forAuthorizedEntity:(nullable NSString *)authorizedEntity + scope:(NSString *)scope + options:(nullable NSDictionary *)options + checkinPreferences:(FIRInstanceIDCheckinPreferences *)checkinPreferences + instanceID:(NSString *)instanceID; + +#pragma mark - Request Construction ++ (NSMutableArray *)standardQueryItemsWithDeviceID:(NSString *)deviceID + scope:(NSString *)scope; +- (NSMutableURLRequest *)tokenRequest; +- (NSArray *)queryItemsWithInstanceID:(NSString *)instanceID; + +#pragma mark - HTTP Headers +/** + * Given a valid checkin preferences object, it will return a string that can be used + * in the "Authorization" HTTP header to authenticate this request. + * + * @param checkin The valid checkin preferences object, with a deviceID and secretToken. + */ ++ (NSString *)HTTPAuthHeaderFromCheckin:(FIRInstanceIDCheckinPreferences *)checkin; + +#pragma mark - Result +- (void)finishWithResult:(FIRInstanceIDTokenOperationResult)result + token:(nullable NSString *)token + error:(nullable NSError *)error; + +#pragma mark - Methods to override +- (void)performTokenOperation; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenOperation.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenOperation.h new file mode 100644 index 0000000..fa8ad08 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenOperation.h @@ -0,0 +1,72 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FIRInstanceIDCheckinPreferences; + +NS_ASSUME_NONNULL_BEGIN + +/** + * Represents the action taken on an FCM token. + */ +typedef NS_ENUM(NSInteger, FIRInstanceIDTokenAction) { + FIRInstanceIDTokenActionFetch, + FIRInstanceIDTokenActionDeleteToken, + FIRInstanceIDTokenActionDeleteTokenAndIID, +}; + +/** + * Represents the possible results of a token operation. + */ +typedef NS_ENUM(NSInteger, FIRInstanceIDTokenOperationResult) { + FIRInstanceIDTokenOperationSucceeded, + FIRInstanceIDTokenOperationError, + FIRInstanceIDTokenOperationCancelled, +}; + +/** + * Callback to invoke once the HTTP call to FIRMessaging backend for updating + * subscription finishes. + * + * @param result The result of the operation. + * @param token If the action for fetching a token and the request was successful, this will hold + * the value of the token. Otherwise nil. + * @param error The error which occurred while performing the token operation. This will be nil + * in case the operation was successful, or if the operation was cancelled. + */ +typedef void (^FIRInstanceIDTokenOperationCompletion)(FIRInstanceIDTokenOperationResult result, + NSString *_Nullable token, + NSError *_Nullable error); + +@interface FIRInstanceIDTokenOperation : NSOperation + +@property(nonatomic, readonly) FIRInstanceIDTokenAction action; +@property(nonatomic, readonly, nullable) NSString *authorizedEntity; +@property(nonatomic, readonly, nullable) NSString *scope; +@property(nonatomic, readonly, nullable) NSDictionary *options; +@property(nonatomic, readonly, strong) FIRInstanceIDCheckinPreferences *checkinPreferences; +@property(nonatomic, readonly, strong) NSString *instanceID; + +@property(nonatomic, readonly) FIRInstanceIDTokenOperationResult result; + +- (instancetype)init NS_UNAVAILABLE; + +- (void)addCompletionHandler:(FIRInstanceIDTokenOperationCompletion)handler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenOperation.m b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenOperation.m new file mode 100644 index 0000000..aa0f75e --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenOperation.m @@ -0,0 +1,258 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceIDTokenOperation.h" + +#import + +#import "FIRInstanceIDCheckinPreferences.h" +#import "FIRInstanceIDLogger.h" +#import "FIRInstanceIDURLQueryItem.h" +#import "FIRInstanceIDUtilities.h" +#import "NSError+FIRInstanceID.h" + +static const NSInteger kFIRInstanceIDPlatformVersionIOS = 2; + +static NSString *const kFIRInstanceIDParamInstanceID = @"appid"; +// Scope parameter that defines the service using the token +static NSString *const kFIRInstanceIDParamScope = @"X-scope"; +// Defines the SDK version +static NSString *const kFIRInstanceIDParamFCMLibVersion = @"X-cliv"; + +@interface FIRInstanceIDTokenOperation () { + BOOL _isFinished; + BOOL _isExecuting; +} + +@property(nonatomic, readwrite, strong) FIRInstanceIDCheckinPreferences *checkinPreferences; +@property(nonatomic, readwrite, strong) NSString *instanceID; + +@property(atomic, strong) NSURLSessionDataTask *dataTask; +@property(readonly, strong) + NSMutableArray *completionHandlers; + +@property(atomic, strong, nullable) NSString *FISAuthToken; + +// For testing only +@property(nonatomic, readwrite, copy) FIRInstanceIDURLRequestTestBlock testBlock; + +@end + +@implementation FIRInstanceIDTokenOperation + ++ (NSURLSession *)sharedURLSession { + static NSURLSession *tokenOperationSharedSession; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; + config.timeoutIntervalForResource = 60.0f; // 1 minute + tokenOperationSharedSession = [NSURLSession sessionWithConfiguration:config]; + tokenOperationSharedSession.sessionDescription = @"com.google.iid.tokens.session"; + }); + return tokenOperationSharedSession; +} + +- (instancetype)initWithAction:(FIRInstanceIDTokenAction)action + forAuthorizedEntity:(NSString *)authorizedEntity + scope:(NSString *)scope + options:(NSDictionary *)options + checkinPreferences:(FIRInstanceIDCheckinPreferences *)checkinPreferences + instanceID:(NSString *)instanceID { + self = [super init]; + if (self) { + _action = action; + _authorizedEntity = [authorizedEntity copy]; + _scope = [scope copy]; + _options = [options copy]; + _checkinPreferences = checkinPreferences; + _instanceID = instanceID; + _completionHandlers = [NSMutableArray array]; + + _isExecuting = NO; + _isFinished = NO; + } + return self; +} + +- (void)dealloc { + _testBlock = nil; + _authorizedEntity = nil; + _scope = nil; + _options = nil; + _checkinPreferences = nil; + _instanceID = nil; + [_completionHandlers removeAllObjects]; + _completionHandlers = nil; +} + +- (void)addCompletionHandler:(FIRInstanceIDTokenOperationCompletion)handler { + [self.completionHandlers addObject:handler]; +} + +- (BOOL)isAsynchronous { + return YES; +} + +- (BOOL)isExecuting { + return _isExecuting; +} + +- (void)setExecuting:(BOOL)executing { + [self willChangeValueForKey:@"isExecuting"]; + _isExecuting = executing; + [self didChangeValueForKey:@"isExecuting"]; +} + +- (BOOL)isFinished { + return _isFinished; +} + +- (void)setFinished:(BOOL)finished { + [self willChangeValueForKey:@"isFinished"]; + _isFinished = finished; + [self didChangeValueForKey:@"isFinished"]; +} + +- (void)start { + if (self.isCancelled) { + [self finishWithResult:FIRInstanceIDTokenOperationCancelled token:nil error:nil]; + return; + } + + // Quickly validate whether or not the operation has all it needs to begin + BOOL checkinfoAvailable = [self.checkinPreferences hasCheckinInfo]; + if (!checkinfoAvailable) { + FIRInstanceIDErrorCode errorCode = kFIRInstanceIDErrorCodeRegistrarFailedToCheckIn; + [self finishWithResult:FIRInstanceIDTokenOperationError + token:nil + error:[NSError errorWithFIRInstanceIDErrorCode:errorCode]]; + return; + } + + [self setExecuting:YES]; + + [[FIRInstallations installations] + authTokenWithCompletion:^(FIRInstallationsAuthTokenResult *_Nullable tokenResult, + NSError *_Nullable error) { + if (tokenResult.authToken.length > 0) { + self.FISAuthToken = tokenResult.authToken; + [self performTokenOperation]; + } else { + [self finishWithResult:FIRInstanceIDTokenOperationError token:nil error:error]; + } + }]; +} + +- (void)finishWithResult:(FIRInstanceIDTokenOperationResult)result + token:(nullable NSString *)token + error:(nullable NSError *)error { + // Add a check to prevent this finish from being called more than once. + if (self.isFinished) { + return; + } + self.dataTask = nil; + _result = result; + // TODO(chliangGoogle): Call these in the main thread? + for (FIRInstanceIDTokenOperationCompletion completionHandler in self.completionHandlers) { + completionHandler(result, token, error); + } + + [self setExecuting:NO]; + [self setFinished:YES]; +} + +- (void)cancel { + [super cancel]; + [self.dataTask cancel]; + [self finishWithResult:FIRInstanceIDTokenOperationCancelled token:nil error:nil]; +} + +- (void)performTokenOperation { +} + +- (NSMutableURLRequest *)tokenRequest { + NSString *authHeader = + [FIRInstanceIDTokenOperation HTTPAuthHeaderFromCheckin:self.checkinPreferences]; + return [[self class] requestWithAuthHeader:authHeader FISAuthToken:self.FISAuthToken]; +} + +#pragma mark - Request Construction ++ (NSMutableURLRequest *)requestWithAuthHeader:(NSString *)authHeaderString + FISAuthToken:(NSString *)FISAuthToken { + NSURL *url = [NSURL URLWithString:FIRInstanceIDRegisterServer()]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; + + // Add HTTP headers + [request setValue:authHeaderString forHTTPHeaderField:@"Authorization"]; + [request setValue:FIRInstanceIDAppIdentifier() forHTTPHeaderField:@"app"]; + if (FISAuthToken) { + [request setValue:FISAuthToken forHTTPHeaderField:@"x-goog-firebase-installations-auth"]; + } + request.HTTPMethod = @"POST"; + return request; +} + ++ (NSMutableArray *)standardQueryItemsWithDeviceID:(NSString *)deviceID + scope:(NSString *)scope { + NSMutableArray *queryItems = [NSMutableArray arrayWithCapacity:8]; + + // E.g. X-osv=10.2.1 + NSString *systemVersion = FIRInstanceIDOperatingSystemVersion(); + [queryItems addObject:[FIRInstanceIDURLQueryItem queryItemWithName:@"X-osv" value:systemVersion]]; + // E.g. device= + if (deviceID) { + [queryItems addObject:[FIRInstanceIDURLQueryItem queryItemWithName:@"device" value:deviceID]]; + } + // E.g. X-scope=fcm + if (scope) { + [queryItems addObject:[FIRInstanceIDURLQueryItem queryItemWithName:kFIRInstanceIDParamScope + value:scope]]; + } + // E.g. plat=2 + NSString *platform = [NSString stringWithFormat:@"%ld", (long)kFIRInstanceIDPlatformVersionIOS]; + [queryItems addObject:[FIRInstanceIDURLQueryItem queryItemWithName:@"plat" value:platform]]; + // E.g. app=com.myapp.foo + NSString *appIdentifier = FIRInstanceIDAppIdentifier(); + [queryItems addObject:[FIRInstanceIDURLQueryItem queryItemWithName:@"app" value:appIdentifier]]; + // E.g. app_ver=1.5 + NSString *appVersion = FIRInstanceIDCurrentAppVersion(); + [queryItems addObject:[FIRInstanceIDURLQueryItem queryItemWithName:@"app_ver" value:appVersion]]; + // E.g. X-cliv=fiid-1.2.3 + NSString *fcmLibraryVersion = + [NSString stringWithFormat:@"fiid-%@", FIRInstanceIDCurrentGCMVersion()]; + if (fcmLibraryVersion.length) { + FIRInstanceIDURLQueryItem *gcmLibVersion = + [FIRInstanceIDURLQueryItem queryItemWithName:kFIRInstanceIDParamFCMLibVersion + value:fcmLibraryVersion]; + [queryItems addObject:gcmLibVersion]; + } + + return queryItems; +} + +- (NSArray *)queryItemsWithInstanceID:(NSString *)instanceID { + return @[ [FIRInstanceIDURLQueryItem queryItemWithName:kFIRInstanceIDParamInstanceID + value:instanceID] ]; +} + +#pragma mark - HTTP Header + ++ (NSString *)HTTPAuthHeaderFromCheckin:(FIRInstanceIDCheckinPreferences *)checkin { + NSString *deviceID = checkin.deviceID; + NSString *secret = checkin.secretToken; + return [NSString stringWithFormat:@"AidLogin %@:%@", deviceID, secret]; +} +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenStore.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenStore.h new file mode 100644 index 0000000..861c87b --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenStore.h @@ -0,0 +1,106 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FIRInstanceIDAPNSInfo; +@class FIRInstanceIDAuthKeychain; +@class FIRInstanceIDTokenInfo; + +/** + * This class is responsible for retrieving and saving `FIRInstanceIDTokenInfo` objects from the + * keychain. The keychain keys that are used are: + * Account:
(e.g. com.mycompany.myapp) + * Service: : (e.g. 1234567890:*) + */ +@interface FIRInstanceIDTokenStore : NSObject + +NS_ASSUME_NONNULL_BEGIN + +/** + * Create a default InstanceID token store. Uses a valid Keychain object as it's + * persistent backing store. + * + * @return A valid token store object. + */ ++ (instancetype)defaultStore; + +- (instancetype)init __attribute__((unavailable("Use -initWithKeychain: instead."))); + +/** + * Initialize a token store object with a Keychain object. Used for testing. + * + * @param keychain The Keychain object to use as the backing store for tokens. + * + * @return A valid token store object with the given Keychain as backing store. + */ +- (instancetype)initWithKeychain:(FIRInstanceIDAuthKeychain *)keychain; + +#pragma mark - Get + +/** + * Get the cached token from the Keychain. + * + * @param authorizedEntity The authorized entity for the token. + * @param scope The scope for the token. + * + * @return The cached token info if any for the given authorizedEntity and scope else + * nil. + */ +- (nullable FIRInstanceIDTokenInfo *)tokenInfoWithAuthorizedEntity:(NSString *)authorizedEntity + scope:(NSString *)scope; + +/** + * Return all cached token infos from the Keychain. + * + * @return The cached token infos, if any, that are stored in the Keychain. + */ +- (NSArray *)cachedTokenInfos; + +#pragma mark - Save + +/** + * Save the instanceID token info to the persistent store. + * + * @param tokenInfo The token info to store. + * @param handler The callback handler which is invoked when token saving is complete, + * with an error if there is any. + */ +- (void)saveTokenInfo:(FIRInstanceIDTokenInfo *)tokenInfo + handler:(nullable void (^)(NSError *))handler; + +#pragma mark - Delete + +/** + * Remove the cached token from Keychain. + * + * @param authorizedEntity The authorized entity for the token. + * @param scope The scope for the token. + * + */ +- (void)removeTokenWithAuthorizedEntity:(NSString *)authorizedEntity scope:(NSString *)scope; + +/** + * Remove all the cached tokens from the Keychain. + * @param handler The callback handler which is invoked when tokens deletion is complete, + * with an error if there is any. + * + */ +- (void)removeAllTokensWithHandler:(nullable void (^)(NSError *))handler; + +NS_ASSUME_NONNULL_END + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenStore.m b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenStore.m new file mode 100644 index 0000000..f97f932 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDTokenStore.m @@ -0,0 +1,143 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceIDTokenStore.h" + +#import "FIRInstanceIDAuthKeyChain.h" +#import "FIRInstanceIDConstants.h" +#import "FIRInstanceIDLogger.h" +#import "FIRInstanceIDTokenInfo.h" +#import "FIRInstanceIDUtilities.h" + +static NSString *const kFIRInstanceIDTokenKeychainId = @"com.google.iid-tokens"; + +@interface FIRInstanceIDTokenStore () + +@property(nonatomic, readwrite, strong) FIRInstanceIDAuthKeychain *keychain; + +@end + +@implementation FIRInstanceIDTokenStore + ++ (instancetype)defaultStore { + FIRInstanceIDAuthKeychain *tokenKeychain = + [[FIRInstanceIDAuthKeychain alloc] initWithIdentifier:kFIRInstanceIDTokenKeychainId]; + return [[FIRInstanceIDTokenStore alloc] initWithKeychain:tokenKeychain]; +} + +- (instancetype)initWithKeychain:(FIRInstanceIDAuthKeychain *)keychain { + self = [super init]; + if (self) { + _keychain = keychain; + } + return self; +} + +#pragma mark - Get + ++ (NSString *)serviceKeyForAuthorizedEntity:(NSString *)authorizedEntity scope:(NSString *)scope { + return [NSString stringWithFormat:@"%@:%@", authorizedEntity, scope]; +} + +- (nullable FIRInstanceIDTokenInfo *)tokenInfoWithAuthorizedEntity:(NSString *)authorizedEntity + scope:(NSString *)scope { + NSString *account = FIRInstanceIDAppIdentifier(); + NSString *service = [[self class] serviceKeyForAuthorizedEntity:authorizedEntity scope:scope]; + NSData *item = [self.keychain dataForService:service account:account]; + if (!item) { + return nil; + } + // Token infos created from legacy storage don't have appVersion, firebaseAppID, or APNSInfo. + FIRInstanceIDTokenInfo *tokenInfo = [[self class] tokenInfoFromKeychainItem:item]; + return tokenInfo; +} + +- (NSArray *)cachedTokenInfos { + NSString *account = FIRInstanceIDAppIdentifier(); + NSArray *items = + [self.keychain itemsMatchingService:kFIRInstanceIDKeychainWildcardIdentifier account:account]; + NSMutableArray *tokenInfos = + [NSMutableArray arrayWithCapacity:items.count]; + for (NSData *item in items) { + FIRInstanceIDTokenInfo *tokenInfo = [[self class] tokenInfoFromKeychainItem:item]; + if (tokenInfo) { + [tokenInfos addObject:tokenInfo]; + } + } + return tokenInfos; +} + ++ (nullable FIRInstanceIDTokenInfo *)tokenInfoFromKeychainItem:(NSData *)item { + // Check if it is saved as an archived FIRInstanceIDTokenInfo, otherwise return nil. + FIRInstanceIDTokenInfo *tokenInfo = nil; + // NOTE: Passing in nil to unarchiveObjectWithData will result in an iOS error logged + // in the console on iOS 10 and below. Avoid by checking item.data's existence. + if (item) { + // TODO(chliangGoogle: Use the new API and secureCoding protocol. + @try { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + tokenInfo = [NSKeyedUnarchiver unarchiveObjectWithData:item]; +#pragma clang diagnostic pop + + } @catch (NSException *exception) { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenStoreExceptionUnarchivingTokenInfo, + @"Unable to parse token info from Keychain item; item was in an " + @"invalid format"); + tokenInfo = nil; + } @finally { + } + } + return tokenInfo; +} + +#pragma mark - Save +// Token Infos will be saved under these Keychain keys: +// Account:
(e.g. com.mycompany.myapp) +// Service: : (e.g. 1234567890:*) +- (void)saveTokenInfo:(FIRInstanceIDTokenInfo *)tokenInfo + handler:(void (^)(NSError *))handler { // Keep the cachetime up-to-date. + tokenInfo.cacheTime = [NSDate date]; + // Always write to the Keychain, so that the cacheTime is up-to-date. + NSData *tokenInfoData; + // TODO(chliangGoogle: Use the new API and secureCoding protocol. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + tokenInfoData = [NSKeyedArchiver archivedDataWithRootObject:tokenInfo]; +#pragma clang diagnostic pop + NSString *account = FIRInstanceIDAppIdentifier(); + NSString *service = [[self class] serviceKeyForAuthorizedEntity:tokenInfo.authorizedEntity + scope:tokenInfo.scope]; + [self.keychain setData:tokenInfoData forService:service account:account handler:handler]; +} + +#pragma mark - Delete + +- (void)removeTokenWithAuthorizedEntity:(nonnull NSString *)authorizedEntity + scope:(nonnull NSString *)scope { + NSString *account = FIRInstanceIDAppIdentifier(); + NSString *service = [[self class] serviceKeyForAuthorizedEntity:authorizedEntity scope:scope]; + [self.keychain removeItemsMatchingService:service account:account handler:nil]; +} + +- (void)removeAllTokensWithHandler:(void (^)(NSError *error))handler { + NSString *account = FIRInstanceIDAppIdentifier(); + [self.keychain removeItemsMatchingService:kFIRInstanceIDKeychainWildcardIdentifier + account:account + handler:handler]; +} + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDURLQueryItem.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDURLQueryItem.h new file mode 100644 index 0000000..3a3a1d7 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDURLQueryItem.h @@ -0,0 +1,39 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +// Stand-in for NSURLQueryItem, which is only available on iOS 8.0 and up. +@interface FIRInstanceIDURLQueryItem : NSObject + +@property(nonatomic, readonly) NSString *name; +@property(nonatomic, readonly) NSString *value; + ++ (instancetype)queryItemWithName:(NSString *)name value:(NSString *)value; +- (instancetype)initWithName:(NSString *)name value:(NSString *)value; + +@end + +/** + * Given an array of query items, construct a URL query. On iOS 8.0 and above, this will use + * NSURLQueryItems internally to perform the string creation, and will be done manually in iOS + * 7 and below. + */ +NSString *FIRInstanceIDQueryFromQueryItems(NSArray *queryItems); + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDURLQueryItem.m b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDURLQueryItem.m new file mode 100644 index 0000000..59b4865 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDURLQueryItem.m @@ -0,0 +1,55 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceIDURLQueryItem.h" + +@implementation FIRInstanceIDURLQueryItem + ++ (instancetype)queryItemWithName:(NSString *)name value:(NSString *)value { + return [[[self class] alloc] initWithName:name value:value]; +} + +- (instancetype)initWithName:(NSString *)name value:(NSString *)value { + self = [super init]; + if (self) { + _name = [name copy]; + _value = [value copy]; + } + return self; +} +@end + +NSString *FIRInstanceIDQueryFromQueryItems(NSArray *queryItems) { + if ([NSURLQueryItem class]) { + // We are iOS 8.0 and above. Convert to NSURLQueryItems and get query that way + // to take advantage of any automatic encoding + NSMutableArray *urlItems = + [NSMutableArray arrayWithCapacity:queryItems.count]; + for (FIRInstanceIDURLQueryItem *queryItem in queryItems) { + [urlItems addObject:[NSURLQueryItem queryItemWithName:queryItem.name value:queryItem.value]]; + } + NSURLComponents *components = [[NSURLComponents alloc] init]; + components.queryItems = urlItems; + return components.query; + } else { + // We are on iOS 7.0. Manually create the query string + NSMutableArray *pairs = [NSMutableArray arrayWithCapacity:queryItems.count]; + for (FIRInstanceIDURLQueryItem *queryItem in queryItems) { + [pairs addObject:[NSString stringWithFormat:@"%@=%@", queryItem.name, queryItem.value]]; + } + return [pairs componentsJoinedByString:@"&"]; + } +} diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDUtilities.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDUtilities.h new file mode 100644 index 0000000..da6ebad --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDUtilities.h @@ -0,0 +1,85 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +/// FIRMessaging Class that responds to the FIRMessaging SDK version selector. +/// Verify at runtime if the class exists and implements the required method. +FOUNDATION_EXPORT NSString *const kFIRInstanceIDFCMSDKClassString; + +/// locale key stored in GULUserDefaults +FOUNDATION_EXPORT NSString *const kFIRInstanceIDUserDefaultsKeyLocale; + +#pragma mark - Test Blocks + +/** + * Response block for mock registration requests made during tests. + * + * @param data The data as returned by the mock request. + * @param response The response as returned by the mock request. + * @param error The error if any as returned by the mock request. + */ +typedef void (^FIRInstanceIDURLRequestTestResponseBlock)(NSData *data, + NSURLResponse *response, + NSError *error); + +/** + * Test block to mock registration requests response. + * + * @param request The request to mock response for. + * @param response The response block for the mocked request. + */ +typedef void (^FIRInstanceIDURLRequestTestBlock)(NSURLRequest *request, + FIRInstanceIDURLRequestTestResponseBlock response); + +#pragma mark - URL Helpers + +FOUNDATION_EXPORT NSString *FIRInstanceIDRegisterServer(void); + +#pragma mark - Time + +FOUNDATION_EXPORT int64_t FIRInstanceIDCurrentTimestampInSeconds(void); +FOUNDATION_EXPORT int64_t FIRInstanceIDCurrentTimestampInMilliseconds(void); + +#pragma mark - App Info + +FOUNDATION_EXPORT NSString *FIRInstanceIDCurrentAppVersion(void); +FOUNDATION_EXPORT NSString *FIRInstanceIDAppIdentifier(void); +FOUNDATION_EXPORT NSString *FIRInstanceIDFirebaseAppID(void); + +#pragma mark - Device Info + +FOUNDATION_EXPORT NSString *FIRInstanceIDDeviceModel(void); +FOUNDATION_EXPORT NSString *FIRInstanceIDOperatingSystemVersion(void); +FOUNDATION_EXPORT BOOL FIRInstanceIDHasLocaleChanged(void); + +#pragma mark - Helpers + +FOUNDATION_EXPORT BOOL FIRInstanceIDIsValidGCMScope(NSString *scope); +FOUNDATION_EXPORT NSString *FIRInstanceIDStringForAPNSDeviceToken(NSData *deviceToken); +FOUNDATION_EXPORT NSString *FIRInstanceIDAPNSTupleStringForTokenAndServerType(NSData *deviceToken, + BOOL isSandbox); + +#pragma mark - GCM Helpers +/// Returns the current GCM version if GCM library is found else returns nil. +FOUNDATION_EXPORT NSString *FIRInstanceIDCurrentGCMVersion(void); + +/// Returns the current locale. If GCM is present it queries GCM for a +/// Context Manager specific locale. Otherwise, it returns the system's first +/// preferred language (which may be set independently from locale). If the +/// system returns no preferred languages, this method returns the most common +/// language for the user's given locale. Guaranteed to return a nonnull value. +FOUNDATION_EXPORT NSString *FIRInstanceIDCurrentLocale(void); diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDUtilities.m b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDUtilities.m new file mode 100644 index 0000000..9eaafa7 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDUtilities.m @@ -0,0 +1,208 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceIDUtilities.h" + +#if TARGET_OS_IOS || TARGET_OS_TV +#import +#endif +#import + +#import +#import +#import "FIRInstanceID.h" +#import "FIRInstanceIDConstants.h" +#import "FIRInstanceIDLogger.h" + +// Convert the macro to a string +#define STR_EXPAND(x) #x +#define STR(x) STR_EXPAND(x) + +static NSString *const kFIRInstanceIDAPNSSandboxPrefix = @"s_"; +static NSString *const kFIRInstanceIDAPNSProdPrefix = @"p_"; + +/// FIRMessaging Class that responds to the FIRMessaging SDK version selector. +/// Verify at runtime if the class exists and implements the required method. +NSString *const kFIRInstanceIDFCMSDKClassString = @"FIRMessaging"; + +/// FIRMessaging selector that returns the current FIRMessaging library version. +static NSString *const kFIRInstanceIDFCMSDKVersionSelectorString = @"FIRMessagingSDKVersion"; + +/// FIRMessaging selector that returns the current device locale. +static NSString *const kFIRInstanceIDFCMSDKLocaleSelectorString = @"FIRMessagingSDKCurrentLocale"; + +NSString *const kFIRInstanceIDUserDefaultsKeyLocale = + @"com.firebase.instanceid.user_defaults.locale"; // locale key stored in GULUserDefaults + +/// Static values which will be populated once retrieved using +/// |FIRInstanceIDRetrieveEnvironmentInfoFromFirebaseCore|. +static NSString *operatingSystemVersion; +static NSString *hardwareDeviceModel; + +#pragma mark - URL Helpers + +NSString *FIRInstanceIDRegisterServer() { + return @"https://fcmtoken.googleapis.com/register"; +} + +#pragma mark - Time + +int64_t FIRInstanceIDCurrentTimestampInSeconds() { + return (int64_t)[[NSDate date] timeIntervalSince1970]; +} + +int64_t FIRInstanceIDCurrentTimestampInMilliseconds() { + return (int64_t)(FIRInstanceIDCurrentTimestampInSeconds() * 1000.0); +} + +#pragma mark - App Info + +NSString *FIRInstanceIDCurrentAppVersion() { + NSString *version = [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"]; + if (![version length]) { + return @""; + } + return version; +} + +NSString *FIRInstanceIDBundleIDByRemovingLastPartFrom(NSString *bundleID) { + NSString *bundleIDComponentsSeparator = @"."; + + NSMutableArray *bundleIDComponents = + [[bundleID componentsSeparatedByString:bundleIDComponentsSeparator] mutableCopy]; + [bundleIDComponents removeLastObject]; + + return [bundleIDComponents componentsJoinedByString:bundleIDComponentsSeparator]; +} + +NSString *FIRInstanceIDAppIdentifier() { + NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier]; + if (!bundleID.length) { + FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeUtilitiesMissingBundleIdentifier, + @"The mainBundle's bundleIdentifier returned '%@'. Bundle identifier " + @"expected to be non-empty.", + bundleID); + return @""; + } +#if TARGET_OS_WATCH + return FIRInstanceIDBundleIDByRemovingLastPartFrom(bundleID); +#endif + return bundleID; +} + +NSString *FIRInstanceIDFirebaseAppID() { + return [FIROptions defaultOptions].googleAppID; +} + +#pragma mark - Device Info +// Get the device model from Firebase Core's App Environment Util +NSString *FIRInstanceIDDeviceModel() { + static dispatch_once_t once; + dispatch_once(&once, ^{ + struct utsname systemInfo; + if (uname(&systemInfo) == 0) { + hardwareDeviceModel = [NSString stringWithUTF8String:systemInfo.machine]; + } + }); + return hardwareDeviceModel; +} + +// Get the system version from Firebase Core's App Environment Util +NSString *FIRInstanceIDOperatingSystemVersion() { +#if TARGET_OS_IOS || TARGET_OS_TV + return [UIDevice currentDevice].systemVersion; +#elif TARGET_OS_OSX || TARGET_OS_WATCH + return [NSProcessInfo processInfo].operatingSystemVersionString; +#endif +} + +BOOL FIRInstanceIDHasLocaleChanged() { + NSString *lastLocale = + [[GULUserDefaults standardUserDefaults] stringForKey:kFIRInstanceIDUserDefaultsKeyLocale]; + NSString *currentLocale = FIRInstanceIDCurrentLocale(); + if (lastLocale) { + if ([currentLocale isEqualToString:lastLocale]) { + return NO; + } + } + return YES; +} + +#pragma mark - Helpers + +BOOL FIRInstanceIDIsValidGCMScope(NSString *scope) { + return [scope compare:kFIRInstanceIDScopeFirebaseMessaging + options:NSCaseInsensitiveSearch] == NSOrderedSame; +} + +NSString *FIRInstanceIDStringForAPNSDeviceToken(NSData *deviceToken) { + NSMutableString *APNSToken = [NSMutableString string]; + unsigned char *bytes = (unsigned char *)[deviceToken bytes]; + for (int i = 0; i < (int)deviceToken.length; i++) { + [APNSToken appendFormat:@"%02x", bytes[i]]; + } + return APNSToken; +} + +NSString *FIRInstanceIDAPNSTupleStringForTokenAndServerType(NSData *deviceToken, BOOL isSandbox) { + if (deviceToken == nil) { + // A nil deviceToken leads to an invalid tuple string, so return nil. + return nil; + } + NSString *prefix = isSandbox ? kFIRInstanceIDAPNSSandboxPrefix : kFIRInstanceIDAPNSProdPrefix; + NSString *APNSString = FIRInstanceIDStringForAPNSDeviceToken(deviceToken); + NSString *APNSTupleString = [NSString stringWithFormat:@"%@%@", prefix, APNSString]; + + return APNSTupleString; +} + +#pragma mark - GCM Helpers + +NSString *FIRInstanceIDCurrentGCMVersion() { + Class versionClass = NSClassFromString(kFIRInstanceIDFCMSDKClassString); + SEL versionSelector = NSSelectorFromString(kFIRInstanceIDFCMSDKVersionSelectorString); + if ([versionClass respondsToSelector:versionSelector]) { + IMP getVersionIMP = [versionClass methodForSelector:versionSelector]; + NSString *(*getVersion)(id, SEL) = (void *)getVersionIMP; + return getVersion(versionClass, versionSelector); + } + return nil; +} + +NSString *FIRInstanceIDCurrentLocale() { + Class localeClass = NSClassFromString(kFIRInstanceIDFCMSDKClassString); + SEL localeSelector = NSSelectorFromString(kFIRInstanceIDFCMSDKLocaleSelectorString); + + if ([localeClass respondsToSelector:localeSelector]) { + IMP getLocaleIMP = [localeClass methodForSelector:localeSelector]; + NSString *(*getLocale)(id, SEL) = (void *)getLocaleIMP; + NSString *fcmLocale = getLocale(localeClass, localeSelector); + if (fcmLocale != nil) { + return fcmLocale; + } + } + + NSString *systemLanguage = [[NSLocale preferredLanguages] firstObject]; + if (systemLanguage != nil) { + return systemLanguage; + } + + if (@available(macOS 10.12, iOS 10.0, *)) { + return [NSLocale currentLocale].languageCode; + } else { + return nil; + } +} diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDVersionUtilities.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDVersionUtilities.h new file mode 100644 index 0000000..ec5a76c --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDVersionUtilities.h @@ -0,0 +1,35 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +/** + * Parsing utility for InstanceID Library versions. InstanceID lib follows semantic versioning. + * This provides utilities to parse the library versions to enable features and do + * updates based on appropriate library versions. + * + * Some example semantic versions are 1.0.1, 2.1.0, 2.1.1, 2.2.0-alpha1, 2.2.1-beta1 + */ + +FOUNDATION_EXPORT NSString *FIRInstanceIDCurrentLibraryVersion(void); +/// Returns the current Major version of GCM library. +FOUNDATION_EXPORT int FIRInstanceIDCurrentLibraryVersionMajor(void); +/// Returns the current Minor version of GCM library. +FOUNDATION_EXPORT int FIRInstanceIDCurrentLibraryVersionMinor(void); +/// Returns the current Patch version of GCM library. +FOUNDATION_EXPORT int FIRInstanceIDCurrentLibraryVersionPatch(void); +/// Returns YES if current library version is `beta` else NO. +FOUNDATION_EXPORT BOOL FIRInstanceIDCurrentLibraryVersionIsBeta(void); diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDVersionUtilities.m b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDVersionUtilities.m new file mode 100644 index 0000000..c2e532a --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/FIRInstanceIDVersionUtilities.m @@ -0,0 +1,85 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceIDVersionUtilities.h" + +// Convert the macro to a string +#define STR(x) STR_EXPAND(x) +#define STR_EXPAND(x) #x + +static NSString *const kSemanticVersioningSeparator = @"."; +static NSString *const kBetaVersionPrefix = @"-beta"; + +static NSString *libraryVersion; + +static int majorVersion; +static int minorVersion; +static int patchVersion; +static int betaVersion; + +void FIRInstanceIDParseCurrentLibraryVersion() { + static NSArray *allVersions; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSMutableString *daylightVersion = + [NSMutableString stringWithUTF8String:STR(FIRInstanceID_LIB_VERSION)]; + // Parse versions + // major, minor, patch[-beta#] + allVersions = [daylightVersion componentsSeparatedByString:kSemanticVersioningSeparator]; + if (allVersions.count == 3) { + majorVersion = [allVersions[0] intValue]; + minorVersion = [allVersions[1] intValue]; + + // Parse patch and beta versions + NSArray *patchAndBetaVersion = + [allVersions[2] componentsSeparatedByString:kBetaVersionPrefix]; + if (patchAndBetaVersion.count == 2) { + patchVersion = [patchAndBetaVersion[0] intValue]; + betaVersion = [patchAndBetaVersion[1] intValue]; + } else if (patchAndBetaVersion.count == 1) { + patchVersion = [patchAndBetaVersion[0] intValue]; + } + } + + // Copy library version + libraryVersion = [daylightVersion copy]; + }); +} + +NSString *FIRInstanceIDCurrentLibraryVersion() { + FIRInstanceIDParseCurrentLibraryVersion(); + return libraryVersion; +} + +int FIRInstanceIDCurrentLibraryVersionMajor() { + FIRInstanceIDParseCurrentLibraryVersion(); + return majorVersion; +} + +int FIRInstanceIDCurrentLibraryVersionMinor() { + FIRInstanceIDParseCurrentLibraryVersion(); + return minorVersion; +} + +int FIRInstanceIDCurrentLibraryVersionPatch() { + FIRInstanceIDParseCurrentLibraryVersion(); + return patchVersion; +} + +BOOL FIRInstanceIDCurrentLibraryVersionIsBeta() { + FIRInstanceIDParseCurrentLibraryVersion(); + return betaVersion > 0; +} diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/NSError+FIRInstanceID.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/NSError+FIRInstanceID.h new file mode 100644 index 0000000..b533dc4 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/NSError+FIRInstanceID.h @@ -0,0 +1,70 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +FOUNDATION_EXPORT NSString *const kFIRInstanceIDDomain; + +typedef NS_ENUM(NSUInteger, FIRInstanceIDErrorCode) { + // Unknown error. + kFIRInstanceIDErrorCodeUnknown = 0, + + // Http related errors. + kFIRInstanceIDErrorCodeAuthentication = 1, + kFIRInstanceIDErrorCodeNoAccess = 2, + kFIRInstanceIDErrorCodeTimeout = 3, + kFIRInstanceIDErrorCodeNetwork = 4, + + // Another operation is in progress. + kFIRInstanceIDErrorCodeOperationInProgress = 5, + + // Failed to perform device check in. + kFIRInstanceIDErrorCodeRegistrarFailedToCheckIn = 6, + + kFIRInstanceIDErrorCodeInvalidRequest = 7, + + // InstanceID generic errors + kFIRInstanceIDErrorCodeMissingDeviceID = 501, + + // InstanceID Token specific errors + kFIRInstanceIDErrorCodeMissingAPNSToken = 1001, + kFIRInstanceIDErrorCodeMissingAPNSServerType = 1002, + kFIRInstanceIDErrorCodeInvalidAuthorizedEntity = 1003, + kFIRInstanceIDErrorCodeInvalidScope = 1004, + kFIRInstanceIDErrorCodeInvalidStart = 1005, + kFIRInstanceIDErrorCodeInvalidKeyPair = 1006, + + // InstanceID Identity specific errors + // Generic InstanceID keypair error + kFIRInstanceIDErrorCodeMissingKeyPair = 2001, + kFIRInstanceIDErrorCodeInvalidKeyPairTags = 2002, + kFIRInstanceIDErrorCodeInvalidKeyPairCreationTime = 2005, + kFIRInstanceIDErrorCodeInvalidIdentity = 2006, + +}; + +@interface NSError (FIRInstanceID) + +@property(nonatomic, readonly) FIRInstanceIDErrorCode instanceIDErrorCode; + ++ (NSError *)errorWithFIRInstanceIDErrorCode:(FIRInstanceIDErrorCode)errorCode; + ++ (NSError *)errorWithFIRInstanceIDErrorCode:(FIRInstanceIDErrorCode)errorCode + userInfo:(NSDictionary *)userInfo; + ++ (NSError *)FIRInstanceIDErrorMissingCheckin; + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/NSError+FIRInstanceID.m b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/NSError+FIRInstanceID.m new file mode 100644 index 0000000..560a5df --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/NSError+FIRInstanceID.m @@ -0,0 +1,44 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "NSError+FIRInstanceID.h" + +NSString *const kFIRInstanceIDDomain = @"com.firebase.iid"; + +@implementation NSError (FIRInstanceID) + +- (FIRInstanceIDErrorCode)instanceIDErrorCode { + return (FIRInstanceIDErrorCode)self.code; +} + ++ (NSError *)errorWithFIRInstanceIDErrorCode:(FIRInstanceIDErrorCode)errorCode { + return [NSError errorWithFIRInstanceIDErrorCode:errorCode userInfo:nil]; +} + ++ (NSError *)errorWithFIRInstanceIDErrorCode:(FIRInstanceIDErrorCode)errorCode + userInfo:(NSDictionary *)userInfo { + return [NSError errorWithDomain:kFIRInstanceIDDomain code:errorCode userInfo:userInfo]; +} + ++ (NSError *)FIRInstanceIDErrorMissingCheckin { + NSDictionary *userInfo = @{@"msg" : @"Missing device credentials. Retry later."}; + + return [NSError errorWithDomain:kFIRInstanceIDDomain + code:kFIRInstanceIDErrorCodeMissingDeviceID + userInfo:userInfo]; +} + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/Private/FIRInstanceID+Private.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/Private/FIRInstanceID+Private.h new file mode 100644 index 0000000..632e21b --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/Private/FIRInstanceID+Private.h @@ -0,0 +1,63 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import +#import + +/** + * @related FIRInstanceIDCheckinService + * + * The completion handler invoked once the fetch from Checkin server finishes. + * For successful fetches we returned checkin information by the checkin service + * and `nil` error, else we return the appropriate error object as reported by the + * Checkin Service. + * + * @param checkinPreferences The checkin preferences as fetched from the server. + * @param error The error object which fetching GServices data. + */ +typedef void (^FIRInstanceIDDeviceCheckinCompletion)( + FIRInstanceIDCheckinPreferences *_Nullable checkinPreferences, NSError *_Nullable error); + +/** + * Private API used by Firebase SDK teams by calling in reflection or internal teams. + */ +@interface FIRInstanceID (Private) + +/** + * Fetches checkin info for the app. If the app has valid cached checkin preferences + * they are returned instead of making a network request. + * + * @param handler The completion handler to invoke once the request has completed. + */ +- (void)fetchCheckinInfoWithHandler:(nullable FIRInstanceIDDeviceCheckinCompletion)handler; + +/** + * Get the InstanceID for the app. If an ID was created before and cached + * successfully we will return that ID. If no cached ID exists we create + * a new ID, cache it and return that. + * + * This is a blocking call and should not really be called on the main thread. + * + * @param error The error object that represents the error while trying to + * retrieve the instance id. + * + * @return The InstanceID for the app. + */ +- (nullable NSString *)appInstanceID:(NSError *_Nullable *_Nullable)error + DEPRECATED_MSG_ATTRIBUTE("Please use getID(handler:) for Swift or " + "getIDWithHandler: for Objective-C instead."); + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/Private/FIRInstanceIDCheckinPreferences.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/Private/FIRInstanceIDCheckinPreferences.h new file mode 100644 index 0000000..be3ec57 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/Private/FIRInstanceIDCheckinPreferences.h @@ -0,0 +1,62 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +/** + * The preferences InstanceID loads from checkin server. The deviceID and secret that checkin + * provides is used to authenticate all future requests to the server. Besides the deviceID + * and secret the other information that checkin provides is stored in a plist on the device. + * The deviceID and secret are persisted in the device keychain. + */ +@interface FIRInstanceIDCheckinPreferences : NSObject + +/** + * DeviceID and secretToken are the checkin auth credentials and are stored in the Keychain. + */ +@property(nonatomic, readonly, copy) NSString *deviceID; +@property(nonatomic, readonly, copy) NSString *secretToken; + +/** + * All the other checkin preferences other than deviceID and secret are stored in a plist. + */ +@property(nonatomic, readonly, copy) NSString *deviceDataVersion; +@property(nonatomic, readonly, copy) NSString *digest; +@property(nonatomic, readonly, copy) NSString *versionInfo; +@property(nonatomic, readonly, assign) int64_t lastCheckinTimestampMillis; + +/** + * The content retrieved from checkin server that should be persisted in a plist. This + * doesn't contain the deviceID and secret which are stored in the Keychain since they + * should be more private. + * + * @return The checkin preferences that should be persisted in a plist. + */ +- (NSDictionary *)checkinPlistContents; + +/** + * Return whether checkin info exists, valid or not. + */ +- (BOOL)hasCheckinInfo; + +/** + * Verify if checkin preferences are valid or not. + * + * @return YES if valid checkin preferences else NO. + */ +- (BOOL)hasValidCheckinInfo; + +@end diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/Private/FIRInstanceID_Private.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/Private/FIRInstanceID_Private.h new file mode 100644 index 0000000..c343f88 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/Private/FIRInstanceID_Private.h @@ -0,0 +1,74 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class FIRInstanceIDCheckinPreferences; +@class FIRInstallations; + +/** + * Private API used by other Firebase SDKs. + */ +@interface FIRInstanceID () + +@property(nonatomic, readonly, strong) NSString *deviceAuthID; +@property(nonatomic, readonly, strong) NSString *secretToken; +@property(nonatomic, readonly, strong) NSString *versionInfo; + +@property(nonatomic, readonly, strong) FIRInstallations *installations; + +/// A cached value of FID. Should be used only for `-[FIRInstanceID appInstanceID:]`. +@property(atomic, readonly, copy, nullable) NSString *firebaseInstallationsID; + +/** + * Private initializer. + */ +- (instancetype)initPrivately; + +/** + * Returns a Firebase Messaging scoped token for the firebase app. + * + * @return Returns the stored token if the device has registered with Firebase Messaging, otherwise + * returns nil. + */ +- (nullable NSString *)token; + +/** + * Verify if valid checkin preferences have been loaded in memory. + * + * @return YES if valid checkin preferences exist in memory else NO. + */ +- (BOOL)hasValidCheckinInfo; + +/** + * Try to load prefetched checkin preferences from the cache. This supports the use case where + * InstanceID library has already obtained a valid checkin and we should be using that. + * + * This should be used as a last gasp effort to retreive any cached checkin preferences before + * hitting the FIRMessaging backend to retrieve new preferences. + * + * Note this is only required because InstanceID and FIRMessaging both require checkin preferences + * which need to be synced with each other. + * + * @return YES if successfully loaded cached checkin preferences into memory else NO. + */ +- (BOOL)tryToLoadValidCheckinInfo; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/Public/FIRInstanceID.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/Public/FIRInstanceID.h new file mode 100644 index 0000000..0f96d91 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/Public/FIRInstanceID.h @@ -0,0 +1,312 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class FIRInstanceIDResult; +/** + * @memberof FIRInstanceID + * + * The scope to be used when fetching/deleting a token for Firebase Messaging. + */ +FOUNDATION_EXPORT NSString *const kFIRInstanceIDScopeFirebaseMessaging + NS_SWIFT_NAME(InstanceIDScopeFirebaseMessaging); + +#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 +/** + * Called when the system determines that tokens need to be refreshed. + * This method is also called if Instance ID has been reset in which + * case, tokens and FCM topic subscriptions also need to be refreshed. + * + * Instance ID service will throttle the refresh event across all devices + * to control the rate of token updates on application servers. + */ +FOUNDATION_EXPORT const NSNotificationName kFIRInstanceIDTokenRefreshNotification + NS_SWIFT_NAME(InstanceIDTokenRefresh); +#else +/** + * Called when the system determines that tokens need to be refreshed. + * This method is also called if Instance ID has been reset in which + * case, tokens and FCM topic subscriptions also need to be refreshed. + * + * Instance ID service will throttle the refresh event across all devices + * to control the rate of token updates on application servers. + */ +FOUNDATION_EXPORT NSString *const kFIRInstanceIDTokenRefreshNotification + NS_SWIFT_NAME(InstanceIDTokenRefreshNotification); +#endif // defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 + +/** + * @related FIRInstanceID + * + * The completion handler invoked when the InstanceID token returns. If + * the call fails we return the appropriate `error code` as described below. + * + * @param token The valid token as returned by InstanceID backend. + * + * @param error The error describing why generating a new token + * failed. See the error codes below for a more detailed + * description. + */ +typedef void (^FIRInstanceIDTokenHandler)(NSString *__nullable token, NSError *__nullable error) + NS_SWIFT_NAME(InstanceIDTokenHandler); + +/** + * @related FIRInstanceID + * + * The completion handler invoked when the InstanceID `deleteToken` returns. If + * the call fails we return the appropriate `error code` as described below + * + * @param error The error describing why deleting the token failed. + * See the error codes below for a more detailed description. + */ +typedef void (^FIRInstanceIDDeleteTokenHandler)(NSError *error) + NS_SWIFT_NAME(InstanceIDDeleteTokenHandler); + +/** + * @related FIRInstanceID + * + * The completion handler invoked when the app identity is created. If the + * identity wasn't created for some reason we return the appropriate error code. + * + * @param identity A valid identity for the app instance, nil if there was an error + * while creating an identity. + * @param error The error if fetching the identity fails else nil. + */ +typedef void (^FIRInstanceIDHandler)(NSString *__nullable identity, NSError *__nullable error) + NS_SWIFT_NAME(InstanceIDHandler); + +/** + * @related FIRInstanceID + * + * The completion handler invoked when the app identity and all the tokens associated + * with it are deleted. Returns a valid error object in case of failure else nil. + * + * @param error The error if deleting the identity and all the tokens associated with + * it fails else nil. + */ +typedef void (^FIRInstanceIDDeleteHandler)(NSError *__nullable error) + NS_SWIFT_NAME(InstanceIDDeleteHandler); + +/** + * @related FIRInstanceID + * + * The completion handler invoked when the app identity and token are fetched. If the + * identity wasn't created for some reason we return the appropriate error code. + * + * @param result The result containing an identity for the app instance and a valid token, + * nil if there was an error while creating the result. + * @param error The error if fetching the identity or token fails else nil. + */ +typedef void (^FIRInstanceIDResultHandler)(FIRInstanceIDResult *__nullable result, + NSError *__nullable error) + NS_SWIFT_NAME(InstanceIDResultHandler); + +/** + * Public errors produced by InstanceID. + */ +typedef NS_ENUM(NSUInteger, FIRInstanceIDError) { + // Http related errors. + + /// Unknown error. + FIRInstanceIDErrorUnknown = 0, + + /// Auth Error -- GCM couldn't validate request from this client. + FIRInstanceIDErrorAuthentication = 1, + + /// NoAccess -- InstanceID service cannot be accessed. + FIRInstanceIDErrorNoAccess = 2, + + /// Timeout -- Request to InstanceID backend timed out. + FIRInstanceIDErrorTimeout = 3, + + /// Network -- No network available to reach the servers. + FIRInstanceIDErrorNetwork = 4, + + /// OperationInProgress -- Another similar operation in progress, + /// bailing this one. + FIRInstanceIDErrorOperationInProgress = 5, + + /// InvalidRequest -- Some parameters of the request were invalid. + FIRInstanceIDErrorInvalidRequest = 7, +} NS_SWIFT_NAME(InstanceIDError); + +/** + * A class contains the results of InstanceID and token query. + */ +NS_SWIFT_NAME(InstanceIDResult) +@interface FIRInstanceIDResult : NSObject + +/** + * An instanceID uniquely identifies the app instance. + */ +@property(nonatomic, readonly, copy) NSString *instanceID; + +/* + * Returns a Firebase Messaging scoped token for the firebase app. + */ +@property(nonatomic, readonly, copy) NSString *token; + +@end + +/** + * Instance ID provides a unique identifier for each app instance and a mechanism + * to authenticate and authorize actions (for example, sending an FCM message). + * + * Once an InstanceID is generated, the library periodically sends information about the + * application and the device where it's running to the Firebase backend. To stop this. see + * `[FIRInstanceID deleteIDWithHandler:]`. + * + * Instance ID is long lived but, may be reset if the device is not used for + * a long time or the Instance ID service detects a problem. + * If Instance ID is reset, the app will be notified via + * `kFIRInstanceIDTokenRefreshNotification`. + * + * If the Instance ID has become invalid, the app can request a new one and + * send it to the app server. + * To prove ownership of Instance ID and to allow servers to access data or + * services associated with the app, call + * `[FIRInstanceID tokenWithAuthorizedEntity:scope:options:handler]`. + */ +NS_SWIFT_NAME(InstanceID) +@interface FIRInstanceID : NSObject + +/** + * FIRInstanceID. + * + * @return A shared instance of FIRInstanceID. + */ ++ (instancetype)instanceID NS_SWIFT_NAME(instanceID()); + +/** + * Unavailable. Use +instanceID instead. + */ +- (instancetype)init __attribute__((unavailable("Use +instanceID instead."))); + +#pragma mark - Tokens + +/** + * Returns a result of app instance identifier InstanceID and a Firebase Messaging scoped token. + * param handler The callback handler invoked when an app instanceID and a default token + * are generated and returned. If instanceID and token fetching fail for some + * reason the callback is invoked with nil `result` and the appropriate error. + */ +- (void)instanceIDWithHandler:(FIRInstanceIDResultHandler)handler; + +/** + * Returns a token that authorizes an Entity (example: cloud service) to perform + * an action on behalf of the application identified by Instance ID. + * + * This is similar to an OAuth2 token except, it applies to the + * application instance instead of a user. + * + * This is an asynchronous call. If the token fetching fails for some reason + * we invoke the completion callback with nil `token` and the appropriate + * error. + * + * This generates an Instance ID if it does not exist yet, which starts periodically sending + * information to the Firebase backend (see `[FIRInstanceID getIDWithHandler:]`). + * + * Note, you can only have one `token` or `deleteToken` call for a given + * authorizedEntity and scope at any point of time. Making another such call with the + * same authorizedEntity and scope before the last one finishes will result in an + * error with code `OperationInProgress`. + * + * @see FIRInstanceID deleteTokenWithAuthorizedEntity:scope:handler: + * + * @param authorizedEntity Entity authorized by the token. + * @param scope Action authorized for authorizedEntity. + * @param options The extra options to be sent with your token request. The + * value for the `apns_token` should be the NSData object + * passed to the UIApplicationDelegate's + * `didRegisterForRemoteNotificationsWithDeviceToken` method. + * The value for `apns_sandbox` should be a boolean (or an + * NSNumber representing a BOOL in Objective-C) set to true if + * your app is a debug build, which means that the APNs + * device token is for the sandbox environment. It should be + * set to false otherwise. If the `apns_sandbox` key is not + * provided, an automatically-detected value shall be used. + * @param handler The callback handler which is invoked when the token is + * successfully fetched. In case of success a valid `token` and + * `nil` error are returned. In case of any error the `token` + * is nil and a valid `error` is returned. The valid error + * codes have been documented above. + */ +- (void)tokenWithAuthorizedEntity:(NSString *)authorizedEntity + scope:(NSString *)scope + options:(nullable NSDictionary *)options + handler:(FIRInstanceIDTokenHandler)handler; + +/** + * Revokes access to a scope (action) for an entity previously + * authorized by `[FIRInstanceID tokenWithAuthorizedEntity:scope:options:handler]`. + * + * This is an asynchronous call. Call this on the main thread since InstanceID lib + * is not thread safe. In case token deletion fails for some reason we invoke the + * `handler` callback passed in with the appropriate error code. + * + * Note, you can only have one `token` or `deleteToken` call for a given + * authorizedEntity and scope at a point of time. Making another such call with the + * same authorizedEntity and scope before the last one finishes will result in an error + * with code `OperationInProgress`. + * + * @param authorizedEntity Entity that must no longer have access. + * @param scope Action that entity is no longer authorized to perform. + * @param handler The handler that is invoked once the unsubscribe call ends. + * In case of error an appropriate error object is returned + * else error is nil. + */ +- (void)deleteTokenWithAuthorizedEntity:(NSString *)authorizedEntity + scope:(NSString *)scope + handler:(FIRInstanceIDDeleteTokenHandler)handler; + +#pragma mark - Identity + +/** + * Asynchronously fetch a stable identifier that uniquely identifies the app + * instance. If the identifier has been revoked or has expired, this method will + * return a new identifier. + * + * Once an InstanceID is generated, the library periodically sends information about the + * application and the device where it's running to the Firebase backend. To stop this. see + * `[FIRInstanceID deleteIDWithHandler:]`. + * + * @param handler The handler to invoke once the identifier has been fetched. + * In case of error an appropriate error object is returned else + * a valid identifier is returned and a valid identifier for the + * application instance. + */ +- (void)getIDWithHandler:(FIRInstanceIDHandler)handler NS_SWIFT_NAME(getID(handler:)); + +/** + * Resets Instance ID and revokes all tokens. + * + * This method also triggers a request to fetch a new Instance ID and Firebase Messaging scope + * token. Please listen to kFIRInstanceIDTokenRefreshNotification when the new ID and token are + * ready. + * + * This stops the periodic sending of data to the Firebase backend that began when the Instance ID + * was generated. No more data is sent until another library calls Instance ID internally again + * (like FCM, RemoteConfig or Analytics) or user explicitly calls Instance ID APIs to get an + * Instance ID and token again. + */ +- (void)deleteIDWithHandler:(FIRInstanceIDDeleteHandler)handler NS_SWIFT_NAME(deleteID(handler:)); + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/Public/FirebaseInstanceID.h b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/Public/FirebaseInstanceID.h new file mode 100644 index 0000000..78c9ef1 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/Firebase/InstanceID/Public/FirebaseInstanceID.h @@ -0,0 +1,17 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceID.h" diff --git a/!main project/Pods/FirebaseInstanceID/LICENSE b/!main project/Pods/FirebaseInstanceID/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/!main project/Pods/FirebaseInstanceID/README.md b/!main project/Pods/FirebaseInstanceID/README.md new file mode 100644 index 0000000..3ddc8fb --- /dev/null +++ b/!main project/Pods/FirebaseInstanceID/README.md @@ -0,0 +1,251 @@ +# Firebase iOS Open Source Development [![Build Status](https://travis-ci.org/firebase/firebase-ios-sdk.svg?branch=master)](https://travis-ci.org/firebase/firebase-ios-sdk) + +This repository contains a subset of the Firebase iOS SDK source. It currently +includes FirebaseCore, FirebaseABTesting, FirebaseAuth, FirebaseDatabase, +FirebaseFirestore, FirebaseFunctions, FirebaseInstanceID, FirebaseInAppMessaging, +FirebaseInAppMessagingDisplay, FirebaseMessaging, FirebaseRemoteConfig, and +FirebaseStorage. + +The repository also includes GoogleUtilities source. The +[GoogleUtilities](GoogleUtilities/README.md) pod is +a set of utilities used by Firebase and other Google products. + +Firebase is an app development platform with tools to help you build, grow and +monetize your app. More information about Firebase can be found at +[https://firebase.google.com](https://firebase.google.com). + +## Installation + +See the three subsections for details about three different installation methods. +1. [Standard pod install](README.md#standard-pod-install) +1. [Installing from the GitHub repo](README.md#installing-from-github) +1. [Experimental Carthage](README.md#carthage-ios-only) + +### Standard pod install + +Go to +[https://firebase.google.com/docs/ios/setup](https://firebase.google.com/docs/ios/setup). + +### Installing from GitHub + +For releases starting with 5.0.0, the source for each release is also deployed +to CocoaPods master and available via standard +[CocoaPods Podfile syntax](https://guides.cocoapods.org/syntax/podfile.html#pod). + +These instructions can be used to access the Firebase repo at other branches, +tags, or commits. + +#### Background + +See +[the Podfile Syntax Reference](https://guides.cocoapods.org/syntax/podfile.html#pod) +for instructions and options about overriding pod source locations. + +#### Accessing Firebase Source Snapshots + +All of the official releases are tagged in this repo and available via CocoaPods. To access a local +source snapshot or unreleased branch, use Podfile directives like the following: + +To access FirebaseFirestore via a branch: +``` +pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +``` + +To access FirebaseMessaging via a checked out version of the firebase-ios-sdk repo do: + +``` +pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk' +pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk' +``` + +### Carthage (iOS only) + +Instructions for the experimental Carthage distribution are at +[Carthage](Carthage.md). + +### Rome + +Instructions for installing binary frameworks via +[Rome](https://github.com/CocoaPods/Rome) are at [Rome](Rome.md). + +## Development + +To develop Firebase software in this repository, ensure that you have at least +the following software: + + * Xcode 10.1 (or later) + * CocoaPods 1.7.2 (or later) + * [CocoaPods generate](https://github.com/square/cocoapods-generate) + +For the pod that you want to develop: + +`pod gen Firebase{name here}.podspec --local-sources=./ --auto-open --platforms=ios` + +Note: If the CocoaPods cache is out of date, you may need to run +`pod repo update` before the `pod gen` command. + +Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for +those platforms. Since 10.2, Xcode does not properly handle multi-platform +CocoaPods workspaces. + +Firestore has a self contained Xcode project. See +[Firestore/README.md](Firestore/README.md). + +### Development for Catalyst +* `pod gen {name here}.podspec --local-sources=./ --auto-open --platforms=ios` +* Check the Mac box in the App-iOS Build Settings +* Sign the App in the Settings Signing & Capabilities tab +* Click Pods in the Project Manager +* Add Signing to the iOS host app and unit test targets +* Select the Unit-unit scheme +* Run it to build and test + +### Adding a New Firebase Pod + +See [AddNewPod.md](AddNewPod.md). + +### Code Formatting + +To ensure that the code is formatted consistently, run the script +[./scripts/style.sh](https://github.com/firebase/firebase-ios-sdk/blob/master/scripts/style.sh) +before creating a PR. + +Travis will verify that any code changes are done in a style compliant way. Install +`clang-format` and `swiftformat`. +These commands will get the right versions: + +``` +brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/e3496d9/Formula/clang-format.rb +brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/7963c3d/Formula/swiftformat.rb +``` + +Note: if you already have a newer version of these installed you may need to +`brew switch` to this version. + +To update this section, find the versions of clang-format and swiftformat.rb to +match the versions in the CI failure logs +[here](https://github.com/Homebrew/homebrew-core/tree/master/Formula). + +### Running Unit Tests + +Select a scheme and press Command-u to build a component and run its unit tests. + +#### Viewing Code Coverage + +First, make sure that [xcov](https://github.com/nakiostudio/xcov) is installed with `gem install xcov`. + +After running the `AllUnitTests_iOS` scheme in Xcode, execute +`xcov --workspace Firebase.xcworkspace --scheme AllUnitTests_iOS --output_directory xcov_output` +at Example/ in the terminal. This will aggregate the coverage, and you can run `open xcov_output/index.html` to see the results. + +### Running Sample Apps +In order to run the sample apps and integration tests, you'll need valid +`GoogleService-Info.plist` files for those samples. The Firebase Xcode project contains dummy plist +files without real values, but can be replaced with real plist files. To get your own +`GoogleService-Info.plist` files: + +1. Go to the [Firebase Console](https://console.firebase.google.com/) +2. Create a new Firebase project, if you don't already have one +3. For each sample app you want to test, create a new Firebase app with the sample app's bundle +identifier (e.g. `com.google.Database-Example`) +4. Download the resulting `GoogleService-Info.plist` and replace the appropriate dummy plist file +(e.g. in [Example/Database/App/](Example/Database/App/)); + +Some sample apps like Firebase Messaging ([Example/Messaging/App](Example/Messaging/App)) require +special Apple capabilities, and you will have to change the sample app to use a unique bundle +identifier that you can control in your own Apple Developer account. + +## Specific Component Instructions +See the sections below for any special instructions for those components. + +### Firebase Auth + +If you're doing specific Firebase Auth development, see +[the Auth Sample README](Example/Auth/README.md) for instructions about +building and running the FirebaseAuth pod along with various samples and tests. + +### Firebase Database + +To run the Database Integration tests, make your database authentication rules +[public](https://firebase.google.com/docs/database/security/quickstart). + +### Firebase Storage + +To run the Storage Integration tests, follow the instructions in +[FIRStorageIntegrationTests.m](Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m). + +#### Push Notifications + +Push notifications can only be delivered to specially provisioned App IDs in the developer portal. +In order to actually test receiving push notifications, you will need to: + +1. Change the bundle identifier of the sample app to something you own in your Apple Developer +account, and enable that App ID for push notifications. +2. You'll also need to +[upload your APNs Provider Authentication Key or certificate to the Firebase Console](https://firebase.google.com/docs/cloud-messaging/ios/certs) +at **Project Settings > Cloud Messaging > [Your Firebase App]**. +3. Ensure your iOS device is added to your Apple Developer portal as a test device. + +#### iOS Simulator + +The iOS Simulator cannot register for remote notifications, and will not receive push notifications. +In order to receive push notifications, you'll have to follow the steps above and run the app on a +physical device. + +## Community Supported Efforts + +We've seen an amazing amount of interest and contributions to improve the Firebase SDKs, and we are +very grateful! We'd like to empower as many developers as we can to be able to use Firebase and +participate in the Firebase community. + +### tvOS, macOS, and Catalyst +Thanks to contributions from the community, FirebaseABTesting, FirebaseAuth, FirebaseCore, +FirebaseDatabase, FirebaseMessaging, FirebaseFirestore, +FirebaseFunctions, FirebaseRemoteConfig, and FirebaseStorage now compile, run unit tests, and work on +tvOS, macOS, and Catalyst. + +For tvOS, checkout the [Sample](Example/tvOSSample). + +Keep in mind that macOS, Catalyst and tvOS are not officially supported by Firebase, and this +repository is actively developed primarily for iOS. While we can catch basic unit test issues with +Travis, there may be some changes where the SDK no longer works as expected on macOS or tvOS. If you +encounter this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues). + +To install, add a subset of the following to the Podfile: + +``` +pod 'Firebase/ABTesting' +pod 'Firebase/Auth' +pod 'Firebase/Database' +pod 'Firebase/Firestore' +pod 'Firebase/Functions' +pod 'Firebase/Messaging' +pod 'Firebase/RemoteConfig' +pod 'Firebase/Storage' +``` + +#### Additional Catalyst Notes + +* FirebaseAuth and FirebaseMessaging require adding `Keychain Sharing Capability` +to Build Settings. +* FirebaseFirestore requires signing the +[gRPC Resource target](https://github.com/firebase/firebase-ios-sdk/issues/3500#issuecomment-518741681). + +## Roadmap + +See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source +plans and directions. + +## Contributing + +See [Contributing](CONTRIBUTING.md) for more information on contributing to the Firebase +iOS SDK. + +## License + +The contents of this repository is licensed under the +[Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0). + +Your use of Firebase is governed by the +[Terms of Service for Firebase Services](https://firebase.google.com/terms/). diff --git a/!main project/Pods/GTMSessionFetcher/LICENSE b/!main project/Pods/GTMSessionFetcher/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/!main project/Pods/GTMSessionFetcher/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/!main project/Pods/GTMSessionFetcher/README.md b/!main project/Pods/GTMSessionFetcher/README.md new file mode 100644 index 0000000..478efde --- /dev/null +++ b/!main project/Pods/GTMSessionFetcher/README.md @@ -0,0 +1,23 @@ +# Google Toolbox for Mac - Session Fetcher # + +**Project site**
+**Discussion group** + +[![Build Status](https://travis-ci.org/google/gtm-session-fetcher.svg?branch=master)](https://travis-ci.org/google/gtm-session-fetcher) + +`GTMSessionFetcher` makes it easy for Cocoa applications to perform http +operations. The fetcher is implemented as a wrapper on `NSURLSession`, so its +behavior is asynchronous and uses operating-system settings on iOS and Mac OS X. + +Features include: +- Simple to build; only one source/header file pair is required +- Simple to use: takes just two lines of code to fetch a request +- Supports upload and download sessions +- Flexible cookie storage +- Automatic retry on errors, with exponential backoff +- Support for generating multipart MIME upload streams +- Easy, convenient logging of http requests and responses +- Supports plug-in authentication such as with GTMAppAuth +- Easily testable; self-mocking +- Automatic rate limiting when created by the `GTMSessionFetcherService` factory class +- Fully independent of other projects diff --git a/!main project/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.h b/!main project/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.h new file mode 100644 index 0000000..1937a32 --- /dev/null +++ b/!main project/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.h @@ -0,0 +1,1322 @@ +/* Copyright 2014 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// GTMSessionFetcher is a wrapper around NSURLSession for http operations. +// +// What does this offer on top of of NSURLSession? +// +// - Block-style callbacks for useful functionality like progress rather +// than delegate methods. +// - Out-of-process uploads and downloads using NSURLSession, including +// management of fetches after relaunch. +// - Integration with GTMAppAuth for invisible management and refresh of +// authorization tokens. +// - Pretty-printed http logging. +// - Cookies handling that does not interfere with or get interfered with +// by WebKit cookies or on Mac by Safari and other apps. +// - Credentials handling for the http operation. +// - Rate-limiting and cookie grouping when fetchers are created with +// GTMSessionFetcherService. +// +// If the bodyData or bodyFileURL property is set, then a POST request is assumed. +// +// Each fetcher is assumed to be for a one-shot fetch request; don't reuse the object +// for a second fetch. +// +// The fetcher will be self-retained as long as a connection is pending. +// +// To keep user activity private, URLs must have an https scheme (unless the property +// allowedInsecureSchemes is set to permit the scheme.) +// +// Callbacks will be released when the fetch completes or is stopped, so there is no need +// to use weak self references in the callback blocks. +// +// Sample usage: +// +// _fetcherService = [[GTMSessionFetcherService alloc] init]; +// +// GTMSessionFetcher *myFetcher = [_fetcherService fetcherWithURLString:myURLString]; +// myFetcher.retryEnabled = YES; +// myFetcher.comment = @"First profile image"; +// +// // Optionally specify a file URL or NSData for the request body to upload. +// myFetcher.bodyData = [postString dataUsingEncoding:NSUTF8StringEncoding]; +// +// [myFetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) { +// if (error != nil) { +// // Server status code or network error. +// // +// // If the domain is kGTMSessionFetcherStatusDomain then the error code +// // is a failure status from the server. +// } else { +// // Fetch succeeded. +// } +// }]; +// +// There is also a beginFetch call that takes a pointer and selector for the completion handler; +// a pointer and selector is a better style when the callback is a substantial, separate method. +// +// NOTE: Fetches may retrieve data from the server even though the server +// returned an error, so the criteria for success is a non-nil error. +// The completion handler is called when the server status is >= 300 with an NSError +// having domain kGTMSessionFetcherStatusDomain and code set to the server status. +// +// Status codes are at +// +// +// Background session support: +// +// Out-of-process uploads and downloads may be created by setting the fetcher's +// useBackgroundSession property. Data to be uploaded should be provided via +// the uploadFileURL property; the download destination should be specified with +// the destinationFileURL. NOTE: Background upload files should be in a location +// that will be valid even after the device is restarted, so the file should not +// be uploaded from a system temporary or cache directory. +// +// Background session transfers are slower, and should typically be used only +// for very large downloads or uploads (hundreds of megabytes). +// +// When background sessions are used in iOS apps, the application delegate must +// pass through the parameters from UIApplicationDelegate's +// application:handleEventsForBackgroundURLSession:completionHandler: to the +// fetcher class. +// +// When the application has been relaunched, it may also create a new fetcher +// instance to handle completion of the transfers. +// +// - (void)application:(UIApplication *)application +// handleEventsForBackgroundURLSession:(NSString *)identifier +// completionHandler:(void (^)())completionHandler { +// // Application was re-launched on completing an out-of-process download. +// +// // Pass the URLSession info related to this re-launch to the fetcher class. +// [GTMSessionFetcher application:application +// handleEventsForBackgroundURLSession:identifier +// completionHandler:completionHandler]; +// +// // Get a fetcher related to this re-launch and re-hook up a completionHandler to it. +// GTMSessionFetcher *fetcher = [GTMSessionFetcher fetcherWithSessionIdentifier:identifier]; +// NSURL *destinationFileURL = fetcher.destinationFileURL; +// fetcher.completionHandler = ^(NSData *data, NSError *error) { +// [self downloadCompletedToFile:destinationFileURL error:error]; +// }; +// } +// +// +// Threading and queue support: +// +// Networking always happens on a background thread; there is no advantage to +// changing thread or queue to create or start a fetcher. +// +// Callbacks are run on the main thread; alternatively, the app may set the +// fetcher's callbackQueue to a dispatch queue. +// +// Once the fetcher's beginFetch method has been called, the fetcher's methods and +// properties may be accessed from any thread. +// +// Downloading to disk: +// +// To have downloaded data saved directly to disk, specify a file URL for the +// destinationFileURL property. +// +// HTTP methods and headers: +// +// Alternative HTTP methods, like PUT, and custom headers can be specified by +// creating the fetcher with an appropriate NSMutableURLRequest. +// +// +// Caching: +// +// The fetcher avoids caching. That is best for API requests, but may hurt +// repeat fetches of static data. Apps may enable a persistent disk cache by +// customizing the config: +// +// fetcher.configurationBlock = ^(GTMSessionFetcher *configFetcher, +// NSURLSessionConfiguration *config) { +// config.URLCache = [NSURLCache sharedURLCache]; +// }; +// +// Or use the standard system config to share cookie storage with web views +// and to enable disk caching: +// +// fetcher.configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; +// +// +// Cookies: +// +// There are three supported mechanisms for remembering cookies between fetches. +// +// By default, a standalone GTMSessionFetcher uses a mutable array held +// statically to track cookies for all instantiated fetchers. This avoids +// cookies being set by servers for the application from interfering with +// Safari and WebKit cookie settings, and vice versa. +// The fetcher cookies are lost when the application quits. +// +// To rely instead on WebKit's global NSHTTPCookieStorage, set the fetcher's +// cookieStorage property: +// myFetcher.cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; +// +// To share cookies with other apps, use the method introduced in iOS 9/OS X 10.11: +// myFetcher.cookieStorage = +// [NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:kMyCompanyContainedID]; +// +// To ignore existing cookies and only have cookies related to the single fetch +// be applied, make a temporary cookie storage object: +// myFetcher.cookieStorage = [[GTMSessionCookieStorage alloc] init]; +// +// Note: cookies set while following redirects will be sent to the server, as +// the redirects are followed by the fetcher. +// +// To completely disable cookies, similar to setting cookieStorageMethod to +// kGTMHTTPFetcherCookieStorageMethodNone, adjust the session configuration +// appropriately in the fetcher or fetcher service: +// fetcher.configurationBlock = ^(GTMSessionFetcher *configFetcher, +// NSURLSessionConfiguration *config) { +// config.HTTPCookieAcceptPolicy = NSHTTPCookieAcceptPolicyNever; +// config.HTTPShouldSetCookies = NO; +// }; +// +// If the fetcher is created from a GTMSessionFetcherService object +// then the cookie storage mechanism is set to use the cookie storage in the +// service object rather than the static storage. Disabling cookies in the +// session configuration set on a service object will disable cookies for all +// fetchers created from that GTMSessionFetcherService object, since the session +// configuration is propagated to the fetcher. +// +// +// Monitoring data transfers. +// +// The fetcher supports a variety of properties for progress monitoring +// progress with callback blocks. +// GTMSessionFetcherSendProgressBlock sendProgressBlock +// GTMSessionFetcherReceivedProgressBlock receivedProgressBlock +// GTMSessionFetcherDownloadProgressBlock downloadProgressBlock +// +// If supplied by the server, the anticipated total download size is available +// as [[myFetcher response] expectedContentLength] (and may be -1 for unknown +// download sizes.) +// +// +// Automatic retrying of fetches +// +// The fetcher can optionally create a timer and reattempt certain kinds of +// fetch failures (status codes 408, request timeout; 502, gateway failure; +// 503, service unavailable; 504, gateway timeout; networking errors +// NSURLErrorTimedOut and NSURLErrorNetworkConnectionLost.) The user may +// set a retry selector to customize the type of errors which will be retried. +// +// Retries are done in an exponential-backoff fashion (that is, after 1 second, +// 2, 4, 8, and so on.) +// +// Enabling automatic retries looks like this: +// myFetcher.retryEnabled = YES; +// +// With retries enabled, the completion callbacks are called only +// when no more retries will be attempted. Calling the fetcher's stopFetching +// method will terminate the retry timer, without the finished or failure +// selectors being invoked. +// +// Optionally, the client may set the maximum retry interval: +// myFetcher.maxRetryInterval = 60.0; // in seconds; default is 60 seconds +// // for downloads, 600 for uploads +// +// Servers should never send a 400 or 500 status for errors that are retryable +// by clients, as those values indicate permanent failures. In nearly all +// cases, the default standard retry behavior is correct for clients, and no +// custom client retry behavior is needed or appropriate. Servers that send +// non-retryable status codes and expect the client to retry the request are +// faulty. +// +// Still, the client may provide a block to determine if a status code or other +// error should be retried. The block returns YES to set the retry timer or NO +// to fail without additional fetch attempts. +// +// The retry method may return the |suggestedWillRetry| argument to get the +// default retry behavior. Server status codes are present in the +// error argument, and have the domain kGTMSessionFetcherStatusDomain. The +// user's method may look something like this: +// +// myFetcher.retryBlock = ^(BOOL suggestedWillRetry, NSError *error, +// GTMSessionFetcherRetryResponse response) { +// // Perhaps examine error.domain and error.code, or fetcher.retryCount +// // +// // Respond with YES to start the retry timer, NO to proceed to the failure +// // callback, or suggestedWillRetry to get default behavior for the +// // current error domain and code values. +// response(suggestedWillRetry); +// }; + + +#import + +#if TARGET_OS_IPHONE +#import +#endif +#if TARGET_OS_WATCH +#import +#endif + +// By default it is stripped from non DEBUG builds. Developers can override +// this in their project settings. +#ifndef STRIP_GTM_FETCH_LOGGING + #if !DEBUG + #define STRIP_GTM_FETCH_LOGGING 1 + #else + #define STRIP_GTM_FETCH_LOGGING 0 + #endif +#endif + +// Logs in debug builds. +#ifndef GTMSESSION_LOG_DEBUG + #if DEBUG + #define GTMSESSION_LOG_DEBUG(...) NSLog(__VA_ARGS__) + #else + #define GTMSESSION_LOG_DEBUG(...) do { } while (0) + #endif +#endif + +// Asserts in debug builds (or logs in debug builds if GTMSESSION_ASSERT_AS_LOG +// or NS_BLOCK_ASSERTIONS are defined.) +#ifndef GTMSESSION_ASSERT_DEBUG + #if DEBUG && !defined(NS_BLOCK_ASSERTIONS) && !GTMSESSION_ASSERT_AS_LOG + #undef GTMSESSION_ASSERT_AS_LOG + #define GTMSESSION_ASSERT_AS_LOG 1 + #endif + + #if DEBUG && !GTMSESSION_ASSERT_AS_LOG + #define GTMSESSION_ASSERT_DEBUG(...) NSAssert(__VA_ARGS__) + #elif DEBUG + #define GTMSESSION_ASSERT_DEBUG(pred, ...) if (!(pred)) { NSLog(__VA_ARGS__); } + #else + #define GTMSESSION_ASSERT_DEBUG(pred, ...) do { } while (0) + #endif +#endif + +// Asserts in debug builds, logs in release builds (or logs in debug builds if +// GTMSESSION_ASSERT_AS_LOG is defined.) +#ifndef GTMSESSION_ASSERT_DEBUG_OR_LOG + #if DEBUG && !GTMSESSION_ASSERT_AS_LOG + #define GTMSESSION_ASSERT_DEBUG_OR_LOG(...) NSAssert(__VA_ARGS__) + #else + #define GTMSESSION_ASSERT_DEBUG_OR_LOG(pred, ...) if (!(pred)) { NSLog(__VA_ARGS__); } + #endif +#endif + +// Macro useful for examining messages from NSURLSession during debugging. +#if 0 +#define GTM_LOG_SESSION_DELEGATE(...) GTMSESSION_LOG_DEBUG(__VA_ARGS__) +#else +#define GTM_LOG_SESSION_DELEGATE(...) +#endif + +#ifndef GTM_NULLABLE + #if __has_feature(nullability) // Available starting in Xcode 6.3 + #define GTM_NULLABLE_TYPE __nullable + #define GTM_NONNULL_TYPE __nonnull + #define GTM_NULLABLE nullable + #define GTM_NONNULL_DECL nonnull // GTM_NONNULL is used by GTMDefines.h + #define GTM_NULL_RESETTABLE null_resettable + + #define GTM_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN + #define GTM_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END + #else + #define GTM_NULLABLE_TYPE + #define GTM_NONNULL_TYPE + #define GTM_NULLABLE + #define GTM_NONNULL_DECL + #define GTM_NULL_RESETTABLE + #define GTM_ASSUME_NONNULL_BEGIN + #define GTM_ASSUME_NONNULL_END + #endif // __has_feature(nullability) +#endif // GTM_NULLABLE + +#ifndef GTM_DECLARE_GENERICS + #if __has_feature(objc_generics) + #define GTM_DECLARE_GENERICS 1 + #else + #define GTM_DECLARE_GENERICS 0 + #endif +#endif + +#ifndef GTM_NSArrayOf + #if GTM_DECLARE_GENERICS + #define GTM_NSArrayOf(value) NSArray + #define GTM_NSDictionaryOf(key, value) NSDictionary + #else + #define GTM_NSArrayOf(value) NSArray + #define GTM_NSDictionaryOf(key, value) NSDictionary + #endif // __has_feature(objc_generics) +#endif // GTM_NSArrayOf + +// For iOS, the fetcher can declare itself a background task to allow fetches +// to finish when the app leaves the foreground. +// +// (This is unrelated to providing a background configuration, which allows +// out-of-process uploads and downloads.) +// +// To disallow use of background tasks during fetches, the target should define +// GTM_BACKGROUND_TASK_FETCHING to 0, or alternatively may set the +// skipBackgroundTask property to YES. +#if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !defined(GTM_BACKGROUND_TASK_FETCHING) + #define GTM_BACKGROUND_TASK_FETCHING 1 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if (TARGET_OS_TV \ + || TARGET_OS_WATCH \ + || (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_11) \ + || (TARGET_OS_IPHONE && defined(__IPHONE_9_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0)) + #ifndef GTM_USE_SESSION_FETCHER + #define GTM_USE_SESSION_FETCHER 1 + #endif +#endif + +#if !defined(GTMBridgeFetcher) + // These bridge macros should be identical in GTMHTTPFetcher.h and GTMSessionFetcher.h + #if GTM_USE_SESSION_FETCHER + // Macros to new fetcher class. + #define GTMBridgeFetcher GTMSessionFetcher + #define GTMBridgeFetcherService GTMSessionFetcherService + #define GTMBridgeFetcherServiceProtocol GTMSessionFetcherServiceProtocol + #define GTMBridgeAssertValidSelector GTMSessionFetcherAssertValidSelector + #define GTMBridgeCookieStorage GTMSessionCookieStorage + #define GTMBridgeCleanedUserAgentString GTMFetcherCleanedUserAgentString + #define GTMBridgeSystemVersionString GTMFetcherSystemVersionString + #define GTMBridgeApplicationIdentifier GTMFetcherApplicationIdentifier + #define kGTMBridgeFetcherStatusDomain kGTMSessionFetcherStatusDomain + #define kGTMBridgeFetcherStatusBadRequest GTMSessionFetcherStatusBadRequest + #else + // Macros to old fetcher class. + #define GTMBridgeFetcher GTMHTTPFetcher + #define GTMBridgeFetcherService GTMHTTPFetcherService + #define GTMBridgeFetcherServiceProtocol GTMHTTPFetcherServiceProtocol + #define GTMBridgeAssertValidSelector GTMAssertSelectorNilOrImplementedWithArgs + #define GTMBridgeCookieStorage GTMCookieStorage + #define GTMBridgeCleanedUserAgentString GTMCleanedUserAgentString + #define GTMBridgeSystemVersionString GTMSystemVersionString + #define GTMBridgeApplicationIdentifier GTMApplicationIdentifier + #define kGTMBridgeFetcherStatusDomain kGTMHTTPFetcherStatusDomain + #define kGTMBridgeFetcherStatusBadRequest kGTMHTTPFetcherStatusBadRequest + #endif // GTM_USE_SESSION_FETCHER +#endif + +// When creating background sessions to perform out-of-process uploads and +// downloads, on app launch any background sessions must be reconnected in +// order to receive events that occurred while the app was not running. +// +// The fetcher will automatically attempt to recreate the sessions on app +// start, but doing so reads from NSUserDefaults. This may have launch-time +// performance impacts. +// +// To avoid launch performance impacts, on iPhone/iPad with iOS 13+ the +// GTMSessionFetcher class will register for the app launch notification and +// perform the reconnect then. +// +// Apps targeting Mac or older iOS SDKs can opt into the new behavior by defining +// GTMSESSION_RECONNECT_BACKGROUND_SESSIONS_ON_LAUNCH=1. +// +// Apps targeting new SDKs can force the old behavior by defining +// GTMSESSION_RECONNECT_BACKGROUND_SESSIONS_ON_LAUNCH = 0. +#ifndef GTMSESSION_RECONNECT_BACKGROUND_SESSIONS_ON_LAUNCH + // Default to the on-launch behavior for iOS 13+. + #if TARGET_OS_IOS && defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0 + #define GTMSESSION_RECONNECT_BACKGROUND_SESSIONS_ON_LAUNCH 1 + #else + #define GTMSESSION_RECONNECT_BACKGROUND_SESSIONS_ON_LAUNCH 0 + #endif +#endif + +GTM_ASSUME_NONNULL_BEGIN + +// Notifications +// +// Fetch started and stopped, and fetch retry delay started and stopped. +extern NSString *const kGTMSessionFetcherStartedNotification; +extern NSString *const kGTMSessionFetcherStoppedNotification; +extern NSString *const kGTMSessionFetcherRetryDelayStartedNotification; +extern NSString *const kGTMSessionFetcherRetryDelayStoppedNotification; + +// Completion handler notification. This is intended for use by code capturing +// and replaying fetch requests and results for testing. For fetches where +// destinationFileURL or accumulateDataBlock is set for the fetcher, the data +// will be nil for successful fetches. +// +// This notification is posted on the main thread. +extern NSString *const kGTMSessionFetcherCompletionInvokedNotification; +extern NSString *const kGTMSessionFetcherCompletionDataKey; +extern NSString *const kGTMSessionFetcherCompletionErrorKey; + +// Constants for NSErrors created by the fetcher (excluding server status errors, +// and error objects originating in the OS.) +extern NSString *const kGTMSessionFetcherErrorDomain; + +// The fetcher turns server error status values (3XX, 4XX, 5XX) into NSErrors +// with domain kGTMSessionFetcherStatusDomain. +// +// Any server response body data accompanying the status error is added to the +// userInfo dictionary with key kGTMSessionFetcherStatusDataKey. +extern NSString *const kGTMSessionFetcherStatusDomain; +extern NSString *const kGTMSessionFetcherStatusDataKey; +extern NSString *const kGTMSessionFetcherStatusDataContentTypeKey; + +// When a fetch fails with an error, these keys are included in the error userInfo +// dictionary if retries were attempted. +extern NSString *const kGTMSessionFetcherNumberOfRetriesDoneKey; +extern NSString *const kGTMSessionFetcherElapsedIntervalWithRetriesKey; + +// Background session support requires access to NSUserDefaults. +// If [NSUserDefaults standardUserDefaults] doesn't yield the correct NSUserDefaults for your usage, +// ie for an App Extension, then implement this class/method to return the correct NSUserDefaults. +// https://developer.apple.com/library/ios/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//apple_ref/doc/uid/TP40014214-CH21-SW6 +@interface GTMSessionFetcherUserDefaultsFactory : NSObject + ++ (NSUserDefaults *)fetcherUserDefaults; + +@end + +#ifdef __cplusplus +} +#endif + +typedef NS_ENUM(NSInteger, GTMSessionFetcherError) { + GTMSessionFetcherErrorDownloadFailed = -1, + GTMSessionFetcherErrorUploadChunkUnavailable = -2, + GTMSessionFetcherErrorBackgroundExpiration = -3, + GTMSessionFetcherErrorBackgroundFetchFailed = -4, + GTMSessionFetcherErrorInsecureRequest = -5, + GTMSessionFetcherErrorTaskCreationFailed = -6, +}; + +typedef NS_ENUM(NSInteger, GTMSessionFetcherStatus) { + // Standard http status codes. + GTMSessionFetcherStatusNotModified = 304, + GTMSessionFetcherStatusBadRequest = 400, + GTMSessionFetcherStatusUnauthorized = 401, + GTMSessionFetcherStatusForbidden = 403, + GTMSessionFetcherStatusPreconditionFailed = 412 +}; + +#ifdef __cplusplus +extern "C" { +#endif + +@class GTMSessionCookieStorage; +@class GTMSessionFetcher; + +// The configuration block is for modifying the NSURLSessionConfiguration only. +// DO NOT change any fetcher properties in the configuration block. +typedef void (^GTMSessionFetcherConfigurationBlock)(GTMSessionFetcher *fetcher, + NSURLSessionConfiguration *configuration); +typedef void (^GTMSessionFetcherSystemCompletionHandler)(void); +typedef void (^GTMSessionFetcherCompletionHandler)(NSData * GTM_NULLABLE_TYPE data, + NSError * GTM_NULLABLE_TYPE error); +typedef void (^GTMSessionFetcherBodyStreamProviderResponse)(NSInputStream *bodyStream); +typedef void (^GTMSessionFetcherBodyStreamProvider)(GTMSessionFetcherBodyStreamProviderResponse response); +typedef void (^GTMSessionFetcherDidReceiveResponseDispositionBlock)(NSURLSessionResponseDisposition disposition); +typedef void (^GTMSessionFetcherDidReceiveResponseBlock)(NSURLResponse *response, + GTMSessionFetcherDidReceiveResponseDispositionBlock dispositionBlock); +typedef void (^GTMSessionFetcherChallengeDispositionBlock)(NSURLSessionAuthChallengeDisposition disposition, + NSURLCredential * GTM_NULLABLE_TYPE credential); +typedef void (^GTMSessionFetcherChallengeBlock)(GTMSessionFetcher *fetcher, + NSURLAuthenticationChallenge *challenge, + GTMSessionFetcherChallengeDispositionBlock dispositionBlock); +typedef void (^GTMSessionFetcherWillRedirectResponse)(NSURLRequest * GTM_NULLABLE_TYPE redirectedRequest); +typedef void (^GTMSessionFetcherWillRedirectBlock)(NSHTTPURLResponse *redirectResponse, + NSURLRequest *redirectRequest, + GTMSessionFetcherWillRedirectResponse response); +typedef void (^GTMSessionFetcherAccumulateDataBlock)(NSData * GTM_NULLABLE_TYPE buffer); +typedef void (^GTMSessionFetcherSimulateByteTransferBlock)(NSData * GTM_NULLABLE_TYPE buffer, + int64_t bytesWritten, + int64_t totalBytesWritten, + int64_t totalBytesExpectedToWrite); +typedef void (^GTMSessionFetcherReceivedProgressBlock)(int64_t bytesWritten, + int64_t totalBytesWritten); +typedef void (^GTMSessionFetcherDownloadProgressBlock)(int64_t bytesWritten, + int64_t totalBytesWritten, + int64_t totalBytesExpectedToWrite); +typedef void (^GTMSessionFetcherSendProgressBlock)(int64_t bytesSent, + int64_t totalBytesSent, + int64_t totalBytesExpectedToSend); +typedef void (^GTMSessionFetcherWillCacheURLResponseResponse)(NSCachedURLResponse * GTM_NULLABLE_TYPE cachedResponse); +typedef void (^GTMSessionFetcherWillCacheURLResponseBlock)(NSCachedURLResponse *proposedResponse, + GTMSessionFetcherWillCacheURLResponseResponse responseBlock); +typedef void (^GTMSessionFetcherRetryResponse)(BOOL shouldRetry); +typedef void (^GTMSessionFetcherRetryBlock)(BOOL suggestedWillRetry, + NSError * GTM_NULLABLE_TYPE error, + GTMSessionFetcherRetryResponse response); + +typedef void (^GTMSessionFetcherTestResponse)(NSHTTPURLResponse * GTM_NULLABLE_TYPE response, + NSData * GTM_NULLABLE_TYPE data, + NSError * GTM_NULLABLE_TYPE error); +typedef void (^GTMSessionFetcherTestBlock)(GTMSessionFetcher *fetcherToTest, + GTMSessionFetcherTestResponse testResponse); + +void GTMSessionFetcherAssertValidSelector(id GTM_NULLABLE_TYPE obj, SEL GTM_NULLABLE_TYPE sel, ...); + +// Utility functions for applications self-identifying to servers via a +// user-agent header + +// The "standard" user agent includes the application identifier, taken from the bundle, +// followed by a space and the system version string. Pass nil to use +mainBundle as the source +// of the bundle identifier. +// +// Applications may use this as a starting point for their own user agent strings, perhaps +// with additional sections appended. Use GTMFetcherCleanedUserAgentString() below to +// clean up any string being added to the user agent. +NSString *GTMFetcherStandardUserAgentString(NSBundle * GTM_NULLABLE_TYPE bundle); + +// Make a generic name and version for the current application, like +// com.example.MyApp/1.2.3 relying on the bundle identifier and the +// CFBundleShortVersionString or CFBundleVersion. +// +// The bundle ID may be overridden as the base identifier string by +// adding to the bundle's Info.plist a "GTMUserAgentID" key. +// +// If no bundle ID or override is available, the process name preceded +// by "proc_" is used. +NSString *GTMFetcherApplicationIdentifier(NSBundle * GTM_NULLABLE_TYPE bundle); + +// Make an identifier like "MacOSX/10.7.1" or "iPod_Touch/4.1 hw/iPod1_1" +NSString *GTMFetcherSystemVersionString(void); + +// Make a parseable user-agent identifier from the given string, replacing whitespace +// and commas with underscores, and removing other characters that may interfere +// with parsing of the full user-agent string. +// +// For example, @"[My App]" would become @"My_App" +NSString *GTMFetcherCleanedUserAgentString(NSString *str); + +// Grab the data from an input stream. Since streams cannot be assumed to be rewindable, +// this may be destructive; the caller can try to rewind the stream (by setting the +// NSStreamFileCurrentOffsetKey property) or can just use the NSData to make a new +// NSInputStream. This function is intended to facilitate testing rather than be used in +// production. +// +// This function operates synchronously on the current thread. Depending on how the +// input stream is implemented, it may be appropriate to dispatch to a different +// queue before calling this function. +// +// Failure is indicated by a returned data value of nil. +NSData * GTM_NULLABLE_TYPE GTMDataFromInputStream(NSInputStream *inputStream, NSError **outError); + +#ifdef __cplusplus +} // extern "C" +#endif + + +#if !GTM_USE_SESSION_FETCHER +@protocol GTMHTTPFetcherServiceProtocol; +#endif + +// This protocol allows abstract references to the fetcher service, primarily for +// fetchers (which may be compiled without the fetcher service class present.) +// +// Apps should not need to use this protocol. +@protocol GTMSessionFetcherServiceProtocol +// This protocol allows us to call into the service without requiring +// GTMSessionFetcherService sources in this project + +@property(atomic, strong) dispatch_queue_t callbackQueue; + +- (BOOL)fetcherShouldBeginFetching:(GTMSessionFetcher *)fetcher; +- (void)fetcherDidCreateSession:(GTMSessionFetcher *)fetcher; +- (void)fetcherDidBeginFetching:(GTMSessionFetcher *)fetcher; +- (void)fetcherDidStop:(GTMSessionFetcher *)fetcher; + +- (GTMSessionFetcher *)fetcherWithRequest:(NSURLRequest *)request; +- (BOOL)isDelayingFetcher:(GTMSessionFetcher *)fetcher; + +@property(atomic, assign) BOOL reuseSession; +- (GTM_NULLABLE NSURLSession *)session; +- (GTM_NULLABLE NSURLSession *)sessionForFetcherCreation; +- (GTM_NULLABLE id)sessionDelegate; +- (GTM_NULLABLE NSDate *)stoppedAllFetchersDate; + +// Methods for compatibility with the old GTMHTTPFetcher. +@property(atomic, readonly, strong, GTM_NULLABLE) NSOperationQueue *delegateQueue; + +@end // @protocol GTMSessionFetcherServiceProtocol + +#ifndef GTM_FETCHER_AUTHORIZATION_PROTOCOL +#define GTM_FETCHER_AUTHORIZATION_PROTOCOL 1 +@protocol GTMFetcherAuthorizationProtocol +@required +// This protocol allows us to call the authorizer without requiring its sources +// in this project. +- (void)authorizeRequest:(GTM_NULLABLE NSMutableURLRequest *)request + delegate:(id)delegate + didFinishSelector:(SEL)sel; + +- (void)stopAuthorization; + +- (void)stopAuthorizationForRequest:(NSURLRequest *)request; + +- (BOOL)isAuthorizingRequest:(NSURLRequest *)request; + +- (BOOL)isAuthorizedRequest:(NSURLRequest *)request; + +@property(atomic, strong, readonly, GTM_NULLABLE) NSString *userEmail; + +@optional + +// Indicate if authorization may be attempted. Even if this succeeds, +// authorization may fail if the user's permissions have been revoked. +@property(atomic, readonly) BOOL canAuthorize; + +// For development only, allow authorization of non-SSL requests, allowing +// transmission of the bearer token unencrypted. +@property(atomic, assign) BOOL shouldAuthorizeAllRequests; + +- (void)authorizeRequest:(GTM_NULLABLE NSMutableURLRequest *)request + completionHandler:(void (^)(NSError * GTM_NULLABLE_TYPE error))handler; + +#if GTM_USE_SESSION_FETCHER +@property(atomic, weak, GTM_NULLABLE) id fetcherService; +#else +@property(atomic, weak, GTM_NULLABLE) id fetcherService; +#endif + +- (BOOL)primeForRefresh; + +@end +#endif // GTM_FETCHER_AUTHORIZATION_PROTOCOL + +#if GTM_BACKGROUND_TASK_FETCHING +// A protocol for an alternative target for messages from GTMSessionFetcher to UIApplication. +// Set the target using +[GTMSessionFetcher setSubstituteUIApplication:] +@protocol GTMUIApplicationProtocol +- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithName:(nullable NSString *)taskName + expirationHandler:(void(^ __nullable)(void))handler; +- (void)endBackgroundTask:(UIBackgroundTaskIdentifier)identifier; +@end +#endif + +#pragma mark - + +// GTMSessionFetcher objects are used for async retrieval of an http get or post +// +// See additional comments at the beginning of this file +@interface GTMSessionFetcher : NSObject + +// Create a fetcher +// +// fetcherWithRequest will return an autoreleased fetcher, but if +// the connection is successfully created, the connection should retain the +// fetcher for the life of the connection as well. So the caller doesn't have +// to retain the fetcher explicitly unless they want to be able to cancel it. ++ (instancetype)fetcherWithRequest:(GTM_NULLABLE NSURLRequest *)request; + +// Convenience methods that make a request, like +fetcherWithRequest ++ (instancetype)fetcherWithURL:(NSURL *)requestURL; ++ (instancetype)fetcherWithURLString:(NSString *)requestURLString; + +// Methods for creating fetchers to continue previous fetches. ++ (instancetype)fetcherWithDownloadResumeData:(NSData *)resumeData; ++ (GTM_NULLABLE instancetype)fetcherWithSessionIdentifier:(NSString *)sessionIdentifier; + +// Returns an array of currently active fetchers for background sessions, +// both restarted and newly created ones. ++ (GTM_NSArrayOf(GTMSessionFetcher *) *)fetchersForBackgroundSessions; + +// Designated initializer. +// +// Applications should create fetchers with a "fetcherWith..." method on a fetcher +// service or a class method, not with this initializer. +// +// The configuration should typically be nil. Applications needing to customize +// the configuration may do so by setting the configurationBlock property. +- (instancetype)initWithRequest:(GTM_NULLABLE NSURLRequest *)request + configuration:(GTM_NULLABLE NSURLSessionConfiguration *)configuration; + +// The fetcher's request. This may not be set after beginFetch has been invoked. The request +// may change due to redirects. +@property(atomic, strong, GTM_NULLABLE) NSURLRequest *request; + +// Set a header field value on the request. Header field value changes will not +// affect a fetch after the fetch has begun. +- (void)setRequestValue:(GTM_NULLABLE NSString *)value forHTTPHeaderField:(NSString *)field; + +// Data used for resuming a download task. +@property(atomic, readonly, GTM_NULLABLE) NSData *downloadResumeData; + +// The configuration; this must be set before the fetch begins. If no configuration is +// set or inherited from the fetcher service, then the fetcher uses an ephemeral config. +// +// NOTE: This property should typically be nil. Applications needing to customize +// the configuration should do so by setting the configurationBlock property. +// That allows the fetcher to pick an appropriate base configuration, with the +// application setting only the configuration properties it needs to customize. +@property(atomic, strong, GTM_NULLABLE) NSURLSessionConfiguration *configuration; + +// A block the client may use to customize the configuration used to create the session. +// +// This is called synchronously, either on the thread that begins the fetch or, during a retry, +// on the main thread. The configuration block may be called repeatedly if multiple fetchers are +// created. +// +// The configuration block is for modifying the NSURLSessionConfiguration only. +// DO NOT change any fetcher properties in the configuration block. Fetcher properties +// may be set in the fetcher service prior to fetcher creation, or on the fetcher prior +// to invoking beginFetch. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherConfigurationBlock configurationBlock; + +// A session is created as needed by the fetcher. A fetcher service object +// may maintain sessions for multiple fetches to the same host. +@property(atomic, strong, GTM_NULLABLE) NSURLSession *session; + +// The task in flight. +@property(atomic, readonly, GTM_NULLABLE) NSURLSessionTask *sessionTask; + +// The background session identifier. +@property(atomic, readonly, GTM_NULLABLE) NSString *sessionIdentifier; + +// Indicates a fetcher created to finish a background session task. +@property(atomic, readonly) BOOL wasCreatedFromBackgroundSession; + +// Additional user-supplied data to encode into the session identifier. Since session identifier +// length limits are unspecified, this should be kept small. Key names beginning with an underscore +// are reserved for use by the fetcher. +@property(atomic, strong, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, NSString *) *sessionUserInfo; + +// The human-readable description to be assigned to the task. +@property(atomic, copy, GTM_NULLABLE) NSString *taskDescription; + +// The priority assigned to the task, if any. Use NSURLSessionTaskPriorityLow, +// NSURLSessionTaskPriorityDefault, or NSURLSessionTaskPriorityHigh. +@property(atomic, assign) float taskPriority; + +// The fetcher encodes information used to resume a session in the session identifier. +// This method, intended for internal use returns the encoded information. The sessionUserInfo +// dictionary is stored as identifier metadata. +- (GTM_NULLABLE GTM_NSDictionaryOf(NSString *, NSString *) *)sessionIdentifierMetadata; + +#if TARGET_OS_IPHONE && !TARGET_OS_WATCH +// The app should pass to this method the completion handler passed in the app delegate method +// application:handleEventsForBackgroundURLSession:completionHandler: ++ (void)application:(UIApplication *)application + handleEventsForBackgroundURLSession:(NSString *)identifier + completionHandler:(GTMSessionFetcherSystemCompletionHandler)completionHandler; +#endif + +// Indicate that a newly created session should be a background session. +// A new session identifier will be created by the fetcher. +// +// Warning: The only thing background sessions are for is rare download +// of huge, batched files of data. And even just for those, there's a lot +// of pain and hackery needed to get transfers to actually happen reliably +// with background sessions. +// +// Don't try to upload or download in many background sessions, since the system +// will impose an exponentially increasing time penalty to prevent the app from +// getting too much background execution time. +// +// References: +// +// "Moving to Fewer, Larger Transfers" +// https://forums.developer.apple.com/thread/14853 +// +// "NSURLSession’s Resume Rate Limiter" +// https://forums.developer.apple.com/thread/14854 +// +// "Background Session Task state persistence" +// https://forums.developer.apple.com/thread/11554 +// +@property(atomic, assign) BOOL useBackgroundSession; + +// Indicates if the fetcher was started using a background session. +@property(atomic, readonly, getter=isUsingBackgroundSession) BOOL usingBackgroundSession; + +// Indicates if uploads should use an upload task. This is always set for file or stream-provider +// bodies, but may be set explicitly for NSData bodies. +@property(atomic, assign) BOOL useUploadTask; + +// Indicates that the fetcher is using a session that may be shared with other fetchers. +@property(atomic, readonly) BOOL canShareSession; + +// By default, the fetcher allows only secure (https) schemes unless this +// property is set, or the GTM_ALLOW_INSECURE_REQUESTS build flag is set. +// +// For example, during debugging when fetching from a development server that lacks SSL support, +// this may be set to @[ @"http" ], or when the fetcher is used to retrieve local files, +// this may be set to @[ @"file" ]. +// +// This should be left as nil for release builds to avoid creating the opportunity for +// leaking private user behavior and data. If a server is providing insecure URLs +// for fetching by the client app, report the problem as server security & privacy bug. +// +// For builds with the iOS 9/OS X 10.11 and later SDKs, this property is required only when +// the app specifies NSAppTransportSecurity/NSAllowsArbitraryLoads in the main bundle's Info.plist. +@property(atomic, copy, GTM_NULLABLE) GTM_NSArrayOf(NSString *) *allowedInsecureSchemes; + +// By default, the fetcher prohibits localhost requests unless this property is set, +// or the GTM_ALLOW_INSECURE_REQUESTS build flag is set. +// +// For localhost requests, the URL scheme is not checked when this property is set. +// +// For builds with the iOS 9/OS X 10.11 and later SDKs, this property is required only when +// the app specifies NSAppTransportSecurity/NSAllowsArbitraryLoads in the main bundle's Info.plist. +@property(atomic, assign) BOOL allowLocalhostRequest; + +// By default, the fetcher requires valid server certs. This may be bypassed +// temporarily for development against a test server with an invalid cert. +@property(atomic, assign) BOOL allowInvalidServerCertificates; + +// Cookie storage object for this fetcher. If nil, the fetcher will use a static cookie +// storage instance shared among fetchers. If this fetcher was created by a fetcher service +// object, it will be set to use the service object's cookie storage. See Cookies section above for +// the full discussion. +// +// Because as of Jan 2014 standalone instances of NSHTTPCookieStorage do not actually +// store any cookies (Radar 15735276) we use our own subclass, GTMSessionCookieStorage, +// to hold cookies in memory. +@property(atomic, strong, GTM_NULLABLE) NSHTTPCookieStorage *cookieStorage; + +// Setting the credential is optional; it is used if the connection receives +// an authentication challenge. +@property(atomic, strong, GTM_NULLABLE) NSURLCredential *credential; + +// Setting the proxy credential is optional; it is used if the connection +// receives an authentication challenge from a proxy. +@property(atomic, strong, GTM_NULLABLE) NSURLCredential *proxyCredential; + +// If body data, body file URL, or body stream provider is not set, then a GET request +// method is assumed. +@property(atomic, strong, GTM_NULLABLE) NSData *bodyData; + +// File to use as the request body. This forces use of an upload task. +@property(atomic, strong, GTM_NULLABLE) NSURL *bodyFileURL; + +// Length of body to send, expected or actual. +@property(atomic, readonly) int64_t bodyLength; + +// The body stream provider may be called repeatedly to provide a body. +// Setting a body stream provider forces use of an upload task. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherBodyStreamProvider bodyStreamProvider; + +// Object to add authorization to the request, if needed. +// +// This may not be changed once beginFetch has been invoked. +@property(atomic, strong, GTM_NULLABLE) id authorizer; + +// The service object that created and monitors this fetcher, if any. +@property(atomic, strong) id service; + +// The host, if any, used to classify this fetcher in the fetcher service. +@property(atomic, copy, GTM_NULLABLE) NSString *serviceHost; + +// The priority, if any, used for starting fetchers in the fetcher service. +// +// Lower values are higher priority; the default is 0, and values may +// be negative or positive. This priority affects only the start order of +// fetchers that are being delayed by a fetcher service when the running fetchers +// exceeds the service's maxRunningFetchersPerHost. A priority of NSIntegerMin will +// exempt this fetcher from delay. +@property(atomic, assign) NSInteger servicePriority; + +// The delegate's optional didReceiveResponse block may be used to inspect or alter +// the session task response. +// +// This is called on the callback queue. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherDidReceiveResponseBlock didReceiveResponseBlock; + +// The delegate's optional challenge block may be used to inspect or alter +// the session task challenge. +// +// If this block is not set, the fetcher's default behavior for the NSURLSessionTask +// didReceiveChallenge: delegate method is to use the fetcher's respondToChallenge: method +// which relies on the fetcher's credential and proxyCredential properties. +// +// Warning: This may be called repeatedly if the challenge fails. Check +// challenge.previousFailureCount to identify repeated invocations. +// +// This is called on the callback queue. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherChallengeBlock challengeBlock; + +// The delegate's optional willRedirect block may be used to inspect or alter +// the redirection. +// +// This is called on the callback queue. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherWillRedirectBlock willRedirectBlock; + +// The optional send progress block reports body bytes uploaded. +// +// This is called on the callback queue. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherSendProgressBlock sendProgressBlock; + +// The optional accumulate block may be set by clients wishing to accumulate data +// themselves rather than let the fetcher append each buffer to an NSData. +// +// When this is called with nil data (such as on redirect) the client +// should empty its accumulation buffer. +// +// This is called on the callback queue. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherAccumulateDataBlock accumulateDataBlock; + +// The optional received progress block may be used to monitor data +// received from a data task. +// +// This is called on the callback queue. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherReceivedProgressBlock receivedProgressBlock; + +// The delegate's optional downloadProgress block may be used to monitor download +// progress in writing to disk. +// +// This is called on the callback queue. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherDownloadProgressBlock downloadProgressBlock; + +// The delegate's optional willCacheURLResponse block may be used to alter the cached +// NSURLResponse. The user may prevent caching by passing nil to the block's response. +// +// This is called on the callback queue. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherWillCacheURLResponseBlock willCacheURLResponseBlock; + +// Enable retrying; see comments at the top of this file. Setting +// retryEnabled=YES resets the min and max retry intervals. +@property(atomic, assign, getter=isRetryEnabled) BOOL retryEnabled; + +// Retry block is optional for retries. +// +// If present, this block should call the response block with YES to cause a retry or NO to end the +// fetch. +// See comments at the top of this file. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherRetryBlock retryBlock; + +// Retry intervals must be strictly less than maxRetryInterval, else +// they will be limited to maxRetryInterval and no further retries will +// be attempted. Setting maxRetryInterval to 0.0 will reset it to the +// default value, 60 seconds for downloads and 600 seconds for uploads. +@property(atomic, assign) NSTimeInterval maxRetryInterval; + +// Starting retry interval. Setting minRetryInterval to 0.0 will reset it +// to a random value between 1.0 and 2.0 seconds. Clients should normally not +// set this except for unit testing. +@property(atomic, assign) NSTimeInterval minRetryInterval; + +// Multiplier used to increase the interval between retries, typically 2.0. +// Clients should not need to set this. +@property(atomic, assign) double retryFactor; + +// Number of retries attempted. +@property(atomic, readonly) NSUInteger retryCount; + +// Interval delay to precede next retry. +@property(atomic, readonly) NSTimeInterval nextRetryInterval; + +#if GTM_BACKGROUND_TASK_FETCHING +// Skip use of a UIBackgroundTask, thus requiring fetches to complete when the app is in the +// foreground. +// +// Targets should define GTM_BACKGROUND_TASK_FETCHING to 0 to avoid use of a UIBackgroundTask +// on iOS to allow fetches to complete in the background. This property is available when +// it's not practical to set the preprocessor define. +@property(atomic, assign) BOOL skipBackgroundTask; +#endif // GTM_BACKGROUND_TASK_FETCHING + +// Begin fetching the request +// +// The delegate may optionally implement the callback or pass nil for the selector or handler. +// +// The delegate and all callback blocks are retained between the beginFetch call until after the +// finish callback, or until the fetch is stopped. +// +// An error is passed to the callback for server statuses 300 or +// higher, with the status stored as the error object's code. +// +// finishedSEL has a signature like: +// - (void)fetcher:(GTMSessionFetcher *)fetcher +// finishedWithData:(NSData *)data +// error:(NSError *)error; +// +// If the application has specified a destinationFileURL or an accumulateDataBlock +// for the fetcher, the data parameter passed to the callback will be nil. + +- (void)beginFetchWithDelegate:(GTM_NULLABLE id)delegate + didFinishSelector:(GTM_NULLABLE SEL)finishedSEL; + +- (void)beginFetchWithCompletionHandler:(GTM_NULLABLE GTMSessionFetcherCompletionHandler)handler; + +// Returns YES if this fetcher is in the process of fetching a URL. +@property(atomic, readonly, getter=isFetching) BOOL fetching; + +// Cancel the fetch of the request that's currently in progress. The completion handler +// will not be called. +- (void)stopFetching; + +// A block to be called when the fetch completes. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherCompletionHandler completionHandler; + +// A block to be called if download resume data becomes available. +@property(atomic, strong, GTM_NULLABLE) void (^resumeDataBlock)(NSData *); + +// Return the status code from the server response. +@property(atomic, readonly) NSInteger statusCode; + +// Return the http headers from the response. +@property(atomic, strong, readonly, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, NSString *) *responseHeaders; + +// The response, once it's been received. +@property(atomic, strong, readonly, GTM_NULLABLE) NSURLResponse *response; + +// Bytes downloaded so far. +@property(atomic, readonly) int64_t downloadedLength; + +// Buffer of currently-downloaded data, if available. +@property(atomic, readonly, strong, GTM_NULLABLE) NSData *downloadedData; + +// Local path to which the downloaded file will be moved. +// +// If a file already exists at the path, it will be overwritten. +// Will create the enclosing folders if they are not present. +@property(atomic, strong, GTM_NULLABLE) NSURL *destinationFileURL; + +// The time this fetcher originally began fetching. This is useful as a time +// barrier for ignoring irrelevant fetch notifications or callbacks. +@property(atomic, strong, readonly, GTM_NULLABLE) NSDate *initialBeginFetchDate; + +// userData is retained solely for the convenience of the client. +@property(atomic, strong, GTM_NULLABLE) id userData; + +// Stored property values are retained solely for the convenience of the client. +@property(atomic, copy, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, id) *properties; + +- (void)setProperty:(GTM_NULLABLE id)obj forKey:(NSString *)key; // Pass nil for obj to remove the property. +- (GTM_NULLABLE id)propertyForKey:(NSString *)key; + +- (void)addPropertiesFromDictionary:(GTM_NSDictionaryOf(NSString *, id) *)dict; + +// Comments are useful for logging, so are strongly recommended for each fetcher. +@property(atomic, copy, GTM_NULLABLE) NSString *comment; + +- (void)setCommentWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1, 2); + +// Log of request and response, if logging is enabled +@property(atomic, copy, GTM_NULLABLE) NSString *log; + +// Callbacks are run on this queue. If none is supplied, the main queue is used. +@property(atomic, strong, GTM_NULL_RESETTABLE) dispatch_queue_t callbackQueue; + +// The queue used internally by the session to invoke its delegate methods in the fetcher. +// +// Application callbacks are always called by the fetcher on the callbackQueue above, +// not on this queue. Apps should generally not change this queue. +// +// The default delegate queue is the main queue. +// +// This value is ignored after the session has been created, so this +// property should be set in the fetcher service rather in the fetcher as it applies +// to a shared session. +@property(atomic, strong, GTM_NULL_RESETTABLE) NSOperationQueue *sessionDelegateQueue; + +// Spin the run loop or sleep the thread, discarding events, until the fetch has completed. +// +// This is only for use in testing or in tools without a user interface. +// +// Note: Synchronous fetches should never be used by shipping apps; they are +// sufficient reason for rejection from the app store. +// +// Returns NO if timed out. +- (BOOL)waitForCompletionWithTimeout:(NSTimeInterval)timeoutInSeconds; + +// Test block is optional for testing. +// +// If present, this block will cause the fetcher to skip starting the session, and instead +// use the test block response values when calling the completion handler and delegate code. +// +// Test code can set this on the fetcher or on the fetcher service. For testing libraries +// that use a fetcher without exposing either the fetcher or the fetcher service, the global +// method setGlobalTestBlock: will set the block for all fetchers that do not have a test +// block set. +// +// The test code can pass nil for all response parameters to indicate that the fetch +// should proceed. +// +// Applications can exclude test block support by setting GTM_DISABLE_FETCHER_TEST_BLOCK. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherTestBlock testBlock; + ++ (void)setGlobalTestBlock:(GTM_NULLABLE GTMSessionFetcherTestBlock)block; + +// When using the testBlock, |testBlockAccumulateDataChunkCount| is the desired number of chunks to +// divide the response data into if the client has streaming enabled. The data will be divided up to +// |testBlockAccumulateDataChunkCount| chunks; however, the exact amount may vary depending on the +// size of the response data (e.g. a 1-byte response can only be divided into one chunk). +@property(atomic, readwrite) NSUInteger testBlockAccumulateDataChunkCount; + +#if GTM_BACKGROUND_TASK_FETCHING +// For testing or to override UIApplication invocations, apps may specify an alternative +// target for messages to UIApplication. ++ (void)setSubstituteUIApplication:(nullable id)substituteUIApplication; ++ (nullable id)substituteUIApplication; +#endif // GTM_BACKGROUND_TASK_FETCHING + +// Exposed for testing. ++ (GTMSessionCookieStorage *)staticCookieStorage; ++ (BOOL)appAllowsInsecureRequests; + +#if STRIP_GTM_FETCH_LOGGING +// If logging is stripped, provide a stub for the main method +// for controlling logging. ++ (void)setLoggingEnabled:(BOOL)flag; ++ (BOOL)isLoggingEnabled; + +#else + +// These methods let an application log specific body text, such as the text description of a binary +// request or response. The application should set the fetcher to defer response body logging until +// the response has been received and the log response body has been set by the app. For example: +// +// fetcher.logRequestBody = [binaryObject stringDescription]; +// fetcher.deferResponseBodyLogging = YES; +// [fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) { +// if (error == nil) { +// fetcher.logResponseBody = [[[MyThing alloc] initWithData:data] stringDescription]; +// } +// fetcher.deferResponseBodyLogging = NO; +// }]; + +@property(atomic, copy, GTM_NULLABLE) NSString *logRequestBody; +@property(atomic, assign) BOOL deferResponseBodyLogging; +@property(atomic, copy, GTM_NULLABLE) NSString *logResponseBody; + +// Internal logging support. +@property(atomic, readonly) NSData *loggedStreamData; +@property(atomic, assign) BOOL hasLoggedError; +@property(atomic, strong, GTM_NULLABLE) NSURL *redirectedFromURL; +- (void)appendLoggedStreamData:(NSData *)dataToAdd; +- (void)clearLoggedStreamData; + +#endif // STRIP_GTM_FETCH_LOGGING + +@end + +@interface GTMSessionFetcher (BackwardsCompatibilityOnly) +// Clients using GTMSessionFetcher should set the cookie storage explicitly themselves. +// This method is just for compatibility with the old GTMHTTPFetcher class. +- (void)setCookieStorageMethod:(NSInteger)method; +@end + +// Until we can just instantiate NSHTTPCookieStorage for local use, we'll +// implement all the public methods ourselves. This stores cookies only in +// memory. Additional methods are provided for testing. +// +// iOS 9/OS X 10.11 added +[NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:] +// which may also be used to create cookie storage. +@interface GTMSessionCookieStorage : NSHTTPCookieStorage + +// Add the array off cookies to the storage, replacing duplicates. +// Also removes expired cookies from the storage. +- (void)setCookies:(GTM_NULLABLE GTM_NSArrayOf(NSHTTPCookie *) *)cookies; + +- (void)removeAllCookies; + +@end + +// Macros to monitor synchronization blocks in debug builds. +// These report problems using GTMSessionCheckDebug. +// +// GTMSessionMonitorSynchronized Start monitoring a top-level-only +// @sync scope. +// GTMSessionMonitorRecursiveSynchronized Start monitoring a top-level or +// recursive @sync scope. +// GTMSessionCheckSynchronized Verify that the current execution +// is inside a @sync scope. +// GTMSessionCheckNotSynchronized Verify that the current execution +// is not inside a @sync scope. +// +// Example usage: +// +// - (void)myExternalMethod { +// @synchronized(self) { +// GTMSessionMonitorSynchronized(self) +// +// - (void)myInternalMethod { +// GTMSessionCheckSynchronized(self); +// +// - (void)callMyCallbacks { +// GTMSessionCheckNotSynchronized(self); +// +// GTMSessionCheckNotSynchronized is available for verifying the code isn't +// in a deadlockable @sync state when posting notifications and invoking +// callbacks. Don't use GTMSessionCheckNotSynchronized immediately before a +// @sync scope; the normal recursiveness check of GTMSessionMonitorSynchronized +// can catch those. + +#ifdef __OBJC__ +// If asserts are entirely no-ops, the synchronization monitor is just a bunch +// of counting code that doesn't report exceptional circumstances in any way. +// Only build the synchronization monitor code if NS_BLOCK_ASSERTIONS is not +// defined or asserts are being logged instead. +#if DEBUG && (!defined(NS_BLOCK_ASSERTIONS) || GTMSESSION_ASSERT_AS_LOG) + #define __GTMSessionMonitorSynchronizedVariableInner(varname, counter) \ + varname ## counter + #define __GTMSessionMonitorSynchronizedVariable(varname, counter) \ + __GTMSessionMonitorSynchronizedVariableInner(varname, counter) + + #define GTMSessionMonitorSynchronized(obj) \ + NS_VALID_UNTIL_END_OF_SCOPE id \ + __GTMSessionMonitorSynchronizedVariable(__monitor, __COUNTER__) = \ + [[GTMSessionSyncMonitorInternal alloc] initWithSynchronizationObject:obj \ + allowRecursive:NO \ + functionName:__func__] + + #define GTMSessionMonitorRecursiveSynchronized(obj) \ + NS_VALID_UNTIL_END_OF_SCOPE id \ + __GTMSessionMonitorSynchronizedVariable(__monitor, __COUNTER__) = \ + [[GTMSessionSyncMonitorInternal alloc] initWithSynchronizationObject:obj \ + allowRecursive:YES \ + functionName:__func__] + + #define GTMSessionCheckSynchronized(obj) { \ + GTMSESSION_ASSERT_DEBUG( \ + [GTMSessionSyncMonitorInternal functionsHoldingSynchronizationOnObject:obj], \ + @"GTMSessionCheckSynchronized(" #obj ") failed: not sync'd" \ + @" on " #obj " in %s. Call stack:\n%@", \ + __func__, [NSThread callStackSymbols]); \ + } + + #define GTMSessionCheckNotSynchronized(obj) { \ + GTMSESSION_ASSERT_DEBUG( \ + ![GTMSessionSyncMonitorInternal functionsHoldingSynchronizationOnObject:obj], \ + @"GTMSessionCheckNotSynchronized(" #obj ") failed: was sync'd" \ + @" on " #obj " in %s by %@. Call stack:\n%@", __func__, \ + [GTMSessionSyncMonitorInternal functionsHoldingSynchronizationOnObject:obj], \ + [NSThread callStackSymbols]); \ + } + +// GTMSessionSyncMonitorInternal is a private class that keeps track of the +// beginning and end of synchronized scopes. +// +// This class should not be used directly, but only via the +// GTMSessionMonitorSynchronized macro. +@interface GTMSessionSyncMonitorInternal : NSObject +- (instancetype)initWithSynchronizationObject:(id)object + allowRecursive:(BOOL)allowRecursive + functionName:(const char *)functionName; +// Return the names of the functions that hold sync on the object, or nil if none. ++ (NSArray *)functionsHoldingSynchronizationOnObject:(id)object; +@end + +#else + #define GTMSessionMonitorSynchronized(obj) do { } while (0) + #define GTMSessionMonitorRecursiveSynchronized(obj) do { } while (0) + #define GTMSessionCheckSynchronized(obj) do { } while (0) + #define GTMSessionCheckNotSynchronized(obj) do { } while (0) +#endif // !DEBUG +#endif // __OBJC__ + + +GTM_ASSUME_NONNULL_END diff --git a/!main project/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.m b/!main project/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.m new file mode 100644 index 0000000..c248847 --- /dev/null +++ b/!main project/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.m @@ -0,0 +1,4648 @@ +/* Copyright 2014 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "GTMSessionFetcher.h" +#if TARGET_OS_OSX && GTMSESSION_RECONNECT_BACKGROUND_SESSIONS_ON_LAUNCH +// To reconnect background sessions on Mac outside +load requires importing and linking +// AppKit to access the NSApplicationDidFinishLaunching symbol. +#import +#endif + +#import + +#ifndef STRIP_GTM_FETCH_LOGGING + #error GTMSessionFetcher headers should have defaulted this if it wasn't already defined. +#endif + +GTM_ASSUME_NONNULL_BEGIN + +NSString *const kGTMSessionFetcherStartedNotification = @"kGTMSessionFetcherStartedNotification"; +NSString *const kGTMSessionFetcherStoppedNotification = @"kGTMSessionFetcherStoppedNotification"; +NSString *const kGTMSessionFetcherRetryDelayStartedNotification = @"kGTMSessionFetcherRetryDelayStartedNotification"; +NSString *const kGTMSessionFetcherRetryDelayStoppedNotification = @"kGTMSessionFetcherRetryDelayStoppedNotification"; + +NSString *const kGTMSessionFetcherCompletionInvokedNotification = @"kGTMSessionFetcherCompletionInvokedNotification"; +NSString *const kGTMSessionFetcherCompletionDataKey = @"data"; +NSString *const kGTMSessionFetcherCompletionErrorKey = @"error"; + +NSString *const kGTMSessionFetcherErrorDomain = @"com.google.GTMSessionFetcher"; +NSString *const kGTMSessionFetcherStatusDomain = @"com.google.HTTPStatus"; +NSString *const kGTMSessionFetcherStatusDataKey = @"data"; // data returned with a kGTMSessionFetcherStatusDomain error +NSString *const kGTMSessionFetcherStatusDataContentTypeKey = @"data_content_type"; + +NSString *const kGTMSessionFetcherNumberOfRetriesDoneKey = @"kGTMSessionFetcherNumberOfRetriesDoneKey"; +NSString *const kGTMSessionFetcherElapsedIntervalWithRetriesKey = @"kGTMSessionFetcherElapsedIntervalWithRetriesKey"; + +static NSString *const kGTMSessionIdentifierPrefix = @"com.google.GTMSessionFetcher"; +static NSString *const kGTMSessionIdentifierDestinationFileURLMetadataKey = @"_destURL"; +static NSString *const kGTMSessionIdentifierBodyFileURLMetadataKey = @"_bodyURL"; + +// The default max retry interview is 10 minutes for uploads (POST/PUT/PATCH), +// 1 minute for downloads. +static const NSTimeInterval kUnsetMaxRetryInterval = -1.0; +static const NSTimeInterval kDefaultMaxDownloadRetryInterval = 60.0; +static const NSTimeInterval kDefaultMaxUploadRetryInterval = 60.0 * 10.; + +// The maximum data length that can be loaded to the error userInfo +static const int64_t kMaximumDownloadErrorDataLength = 20000; + +#ifdef GTMSESSION_PERSISTED_DESTINATION_KEY +// Projects using unique class names should also define a unique persisted destination key. +static NSString * const kGTMSessionFetcherPersistedDestinationKey = + GTMSESSION_PERSISTED_DESTINATION_KEY; +#else +static NSString * const kGTMSessionFetcherPersistedDestinationKey = + @"com.google.GTMSessionFetcher.downloads"; +#endif + +GTM_ASSUME_NONNULL_END + +// +// GTMSessionFetcher +// + +#if 0 +#define GTM_LOG_BACKGROUND_SESSION(...) GTMSESSION_LOG_DEBUG(__VA_ARGS__) +#else +#define GTM_LOG_BACKGROUND_SESSION(...) +#endif + +#ifndef GTM_TARGET_SUPPORTS_APP_TRANSPORT_SECURITY + #if (TARGET_OS_TV \ + || TARGET_OS_WATCH \ + || (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_11) \ + || (TARGET_OS_IPHONE && defined(__IPHONE_9_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0)) + #define GTM_TARGET_SUPPORTS_APP_TRANSPORT_SECURITY 1 + #endif +#endif + +#if ((defined(TARGET_OS_MACCATALYST) && TARGET_OS_MACCATALYST) || \ + (TARGET_OS_OSX && defined(__MAC_10_15) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_15) || \ + (TARGET_OS_IOS && defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_13_0) || \ + (TARGET_OS_WATCH && defined(__WATCHOS_6_0) && __WATCHOS_VERSION_MIN_REQUIRED >= __WATCHOS_6_0) || \ + (TARGET_OS_TV && defined(__TVOS_13_0) && __TVOS_VERSION_MIN_REQUIRED >= __TVOS_13_0)) +#define GTM_SDK_REQUIRES_TLSMINIMUMSUPPORTEDPROTOCOLVERSION 1 +#define GTM_SDK_SUPPORTS_TLSMINIMUMSUPPORTEDPROTOCOLVERSION 1 +#elif ((TARGET_OS_OSX && defined(__MAC_10_15) && __MAC_OS_X_VERSION_MAX_ALLOWED >= __MAC_10_15) || \ + (TARGET_OS_IOS && defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0) || \ + (TARGET_OS_WATCH && defined(__WATCHOS_6_0) && __WATCHOS_VERSION_MAX_ALLOWED >= __WATCHOS_6_0) || \ + (TARGET_OS_TV && defined(__TVOS_13_0) && __TVOS_VERSION_MAX_ALLOWED >= __TVOS_13_0)) +#define GTM_SDK_REQUIRES_TLSMINIMUMSUPPORTEDPROTOCOLVERSION 0 +#define GTM_SDK_SUPPORTS_TLSMINIMUMSUPPORTEDPROTOCOLVERSION 1 +#else +#define GTM_SDK_REQUIRES_TLSMINIMUMSUPPORTEDPROTOCOLVERSION 0 +#define GTM_SDK_SUPPORTS_TLSMINIMUMSUPPORTEDPROTOCOLVERSION 0 +#endif + +#if ((defined(TARGET_OS_MACCATALYST) && TARGET_OS_MACCATALYST) || \ + (TARGET_OS_OSX && defined(__MAC_10_15) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_15) || \ + (TARGET_OS_IOS && defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_13_0) || \ + (TARGET_OS_WATCH && defined(__WATCHOS_6_0) && __WATCHOS_VERSION_MIN_REQUIRED >= __WATCHOS_6_0) || \ + (TARGET_OS_TV && defined(__TVOS_13_0) && __TVOS_VERSION_MIN_REQUIRED >= __TVOS_13_0)) +#define GTM_SDK_REQUIRES_SECTRUSTEVALUATEWITHERROR 1 +#else +#define GTM_SDK_REQUIRES_SECTRUSTEVALUATEWITHERROR 0 +#endif + +@interface GTMSessionFetcher () + +@property(atomic, strong, readwrite, GTM_NULLABLE) NSData *downloadedData; +@property(atomic, strong, readwrite, GTM_NULLABLE) NSData *downloadResumeData; + +#if GTM_BACKGROUND_TASK_FETCHING +// Should always be accessed within an @synchronized(self). +@property(assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskIdentifier; +#endif + +@property(atomic, readwrite, getter=isUsingBackgroundSession) BOOL usingBackgroundSession; + +@end + +#if !GTMSESSION_BUILD_COMBINED_SOURCES +@interface GTMSessionFetcher (GTMSessionFetcherLoggingInternal) +- (void)logFetchWithError:(NSError *)error; +- (void)logNowWithError:(GTM_NULLABLE NSError *)error; +- (NSInputStream *)loggedInputStreamForInputStream:(NSInputStream *)inputStream; +- (GTMSessionFetcherBodyStreamProvider)loggedStreamProviderForStreamProvider: + (GTMSessionFetcherBodyStreamProvider)streamProvider; +@end +#endif // !GTMSESSION_BUILD_COMBINED_SOURCES + +GTM_ASSUME_NONNULL_BEGIN + +static NSTimeInterval InitialMinRetryInterval(void) { + return 1.0 + ((double)(arc4random_uniform(0x0FFFF)) / (double) 0x0FFFF); +} + +static BOOL IsLocalhost(NSString * GTM_NULLABLE_TYPE host) { + // We check if there's host, and then make the comparisons. + if (host == nil) return NO; + return ([host caseInsensitiveCompare:@"localhost"] == NSOrderedSame + || [host isEqual:@"::1"] + || [host isEqual:@"127.0.0.1"]); +} + +static NSDictionary *GTM_NULLABLE_TYPE GTMErrorUserInfoForData( + NSData *GTM_NULLABLE_TYPE data, NSDictionary *GTM_NULLABLE_TYPE responseHeaders) { + NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; + + if (data.length > 0) { + userInfo[kGTMSessionFetcherStatusDataKey] = data; + + NSString *contentType = responseHeaders[@"Content-Type"]; + if (contentType) { + userInfo[kGTMSessionFetcherStatusDataContentTypeKey] = contentType; + } + } + + return userInfo.count > 0 ? userInfo : nil; +} + +static GTMSessionFetcherTestBlock GTM_NULLABLE_TYPE gGlobalTestBlock; + +@implementation GTMSessionFetcher { + NSMutableURLRequest *_request; // after beginFetch, changed only in delegate callbacks + BOOL _useUploadTask; // immutable after beginFetch + NSURL *_bodyFileURL; // immutable after beginFetch + GTMSessionFetcherBodyStreamProvider _bodyStreamProvider; // immutable after beginFetch + NSURLSession *_session; + BOOL _shouldInvalidateSession; // immutable after beginFetch + NSURLSession *_sessionNeedingInvalidation; + NSURLSessionConfiguration *_configuration; + NSURLSessionTask *_sessionTask; + NSString *_taskDescription; + float _taskPriority; + NSURLResponse *_response; + NSString *_sessionIdentifier; + BOOL _wasCreatedFromBackgroundSession; + BOOL _didCreateSessionIdentifier; + NSString *_sessionIdentifierUUID; + BOOL _userRequestedBackgroundSession; + BOOL _usingBackgroundSession; + NSMutableData * GTM_NULLABLE_TYPE _downloadedData; + NSError *_downloadFinishedError; + NSData *_downloadResumeData; // immutable after construction + NSData * GTM_NULLABLE_TYPE _downloadTaskErrorData; // Data for when download task fails + NSURL *_destinationFileURL; + int64_t _downloadedLength; + NSURLCredential *_credential; // username & password + NSURLCredential *_proxyCredential; // credential supplied to proxy servers + BOOL _isStopNotificationNeeded; // set when start notification has been sent + BOOL _isUsingTestBlock; // set when a test block was provided (remains set when the block is released) + id _userData; // retained, if set by caller + NSMutableDictionary *_properties; // more data retained for caller + dispatch_queue_t _callbackQueue; + dispatch_group_t _callbackGroup; // read-only after creation + NSOperationQueue *_delegateQueue; // immutable after beginFetch + + id _authorizer; // immutable after beginFetch + + // The service object that created and monitors this fetcher, if any. + id _service; // immutable; set by the fetcher service upon creation + NSString *_serviceHost; + NSInteger _servicePriority; // immutable after beginFetch + BOOL _hasStoppedFetching; // counterpart to _initialBeginFetchDate + BOOL _userStoppedFetching; + + BOOL _isRetryEnabled; // user wants auto-retry + NSTimer *_retryTimer; + NSUInteger _retryCount; + NSTimeInterval _maxRetryInterval; // default 60 (download) or 600 (upload) seconds + NSTimeInterval _minRetryInterval; // random between 1 and 2 seconds + NSTimeInterval _retryFactor; // default interval multiplier is 2 + NSTimeInterval _lastRetryInterval; + NSDate *_initialBeginFetchDate; // date that beginFetch was first invoked; immutable after initial beginFetch + NSDate *_initialRequestDate; // date of first request to the target server (ignoring auth) + BOOL _hasAttemptedAuthRefresh; // accessed only in shouldRetryNowForStatus: + + NSString *_comment; // comment for log + NSString *_log; +#if !STRIP_GTM_FETCH_LOGGING + NSMutableData *_loggedStreamData; + NSURL *_redirectedFromURL; + NSString *_logRequestBody; + NSString *_logResponseBody; + BOOL _hasLoggedError; + BOOL _deferResponseBodyLogging; +#endif +} + +#if !GTMSESSION_UNIT_TESTING ++ (void)load { +#if GTMSESSION_RECONNECT_BACKGROUND_SESSIONS_ON_LAUNCH && TARGET_OS_IPHONE + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + [nc addObserver:self + selector:@selector(reconnectFetchersForBackgroundSessionsOnAppLaunch:) + name:UIApplicationDidFinishLaunchingNotification + object:nil]; +#elif GTMSESSION_RECONNECT_BACKGROUND_SESSIONS_ON_LAUNCH && TARGET_OS_OSX + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + [nc addObserver:self + selector:@selector(reconnectFetchersForBackgroundSessionsOnAppLaunch:) + name:NSApplicationDidFinishLaunchingNotification + object:nil]; +#else + [self fetchersForBackgroundSessions]; +#endif +} + ++ (void)reconnectFetchersForBackgroundSessionsOnAppLaunch:(NSNotification *)notification { + // Give all other app-did-launch handlers a chance to complete before + // reconnecting the fetchers. Not doing this may lead to reconnecting + // before the app delegate has a chance to run. + dispatch_async(dispatch_get_main_queue(), ^{ + [self fetchersForBackgroundSessions]; + }); +} +#endif // !GTMSESSION_UNIT_TESTING + ++ (instancetype)fetcherWithRequest:(GTM_NULLABLE NSURLRequest *)request { + return [[self alloc] initWithRequest:request configuration:nil]; +} + ++ (instancetype)fetcherWithURL:(NSURL *)requestURL { + return [self fetcherWithRequest:[NSURLRequest requestWithURL:requestURL]]; +} + ++ (instancetype)fetcherWithURLString:(NSString *)requestURLString { + return [self fetcherWithURL:(NSURL *)[NSURL URLWithString:requestURLString]]; +} + ++ (instancetype)fetcherWithDownloadResumeData:(NSData *)resumeData { + GTMSessionFetcher *fetcher = [self fetcherWithRequest:nil]; + fetcher.comment = @"Resuming download"; + fetcher.downloadResumeData = resumeData; + return fetcher; +} + ++ (GTM_NULLABLE instancetype)fetcherWithSessionIdentifier:(NSString *)sessionIdentifier { + GTMSESSION_ASSERT_DEBUG(sessionIdentifier != nil, @"Invalid session identifier"); + NSMapTable *sessionIdentifierToFetcherMap = [self sessionIdentifierToFetcherMap]; + GTMSessionFetcher *fetcher = [sessionIdentifierToFetcherMap objectForKey:sessionIdentifier]; + if (!fetcher && [sessionIdentifier hasPrefix:kGTMSessionIdentifierPrefix]) { + fetcher = [self fetcherWithRequest:nil]; + [fetcher setSessionIdentifier:sessionIdentifier]; + [sessionIdentifierToFetcherMap setObject:fetcher forKey:sessionIdentifier]; + fetcher->_wasCreatedFromBackgroundSession = YES; + [fetcher setCommentWithFormat:@"Resuming %@", + fetcher && fetcher->_sessionIdentifierUUID ? fetcher->_sessionIdentifierUUID : @"?"]; + } + return fetcher; +} + ++ (NSMapTable *)sessionIdentifierToFetcherMap { + // TODO: What if a service is involved in creating the fetcher? Currently, when re-creating + // fetchers, if a service was involved, it is not re-created. Should the service maintain a map? + static NSMapTable *gSessionIdentifierToFetcherMap = nil; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + gSessionIdentifierToFetcherMap = [NSMapTable strongToWeakObjectsMapTable]; + }); + return gSessionIdentifierToFetcherMap; +} + +#if !GTM_ALLOW_INSECURE_REQUESTS ++ (BOOL)appAllowsInsecureRequests { + // If the main bundle Info.plist key NSAppTransportSecurity is present, and it specifies + // NSAllowsArbitraryLoads, then we need to explicitly enforce secure schemes. +#if GTM_TARGET_SUPPORTS_APP_TRANSPORT_SECURITY + static BOOL allowsInsecureRequests; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSBundle *mainBundle = [NSBundle mainBundle]; + NSDictionary *appTransportSecurity = + [mainBundle objectForInfoDictionaryKey:@"NSAppTransportSecurity"]; + allowsInsecureRequests = + [[appTransportSecurity objectForKey:@"NSAllowsArbitraryLoads"] boolValue]; + }); + return allowsInsecureRequests; +#else + // For builds targeting iOS 8 or 10.10 and earlier, we want to require fetcher + // security checks. + return YES; +#endif // GTM_TARGET_SUPPORTS_APP_TRANSPORT_SECURITY +} +#else // GTM_ALLOW_INSECURE_REQUESTS ++ (BOOL)appAllowsInsecureRequests { + return YES; +} +#endif // !GTM_ALLOW_INSECURE_REQUESTS + + +- (instancetype)init { + return [self initWithRequest:nil configuration:nil]; +} + +- (instancetype)initWithRequest:(NSURLRequest *)request { + return [self initWithRequest:request configuration:nil]; +} + +- (instancetype)initWithRequest:(GTM_NULLABLE NSURLRequest *)request + configuration:(GTM_NULLABLE NSURLSessionConfiguration *)configuration { + self = [super init]; + if (self) { +#if GTM_BACKGROUND_TASK_FETCHING + _backgroundTaskIdentifier = UIBackgroundTaskInvalid; +#endif + _request = [request mutableCopy]; + _configuration = configuration; + + NSData *bodyData = request.HTTPBody; + if (bodyData) { + _bodyLength = (int64_t)bodyData.length; + } else { + _bodyLength = NSURLSessionTransferSizeUnknown; + } + + _callbackQueue = dispatch_get_main_queue(); + _callbackGroup = dispatch_group_create(); + _delegateQueue = [NSOperationQueue mainQueue]; + + _minRetryInterval = InitialMinRetryInterval(); + _maxRetryInterval = kUnsetMaxRetryInterval; + + _taskPriority = -1.0f; // Valid values if set are 0.0...1.0. + + _testBlockAccumulateDataChunkCount = 1; + +#if !STRIP_GTM_FETCH_LOGGING + // Encourage developers to set the comment property or use + // setCommentWithFormat: by providing a default string. + _comment = @"(No fetcher comment set)"; +#endif + } + return self; +} + +- (id)copyWithZone:(NSZone *)zone { + // disallow use of fetchers in a copy property + [self doesNotRecognizeSelector:_cmd]; + return nil; +} + +- (NSString *)description { + NSString *requestStr = self.request.URL.description; + if (requestStr.length == 0) { + if (self.downloadResumeData.length > 0) { + requestStr = @""; + } else if (_wasCreatedFromBackgroundSession) { + requestStr = @""; + } else { + requestStr = @""; + } + } + return [NSString stringWithFormat:@"%@ %p (%@)", [self class], self, requestStr]; +} + +- (void)dealloc { + GTMSESSION_ASSERT_DEBUG(!_isStopNotificationNeeded, + @"unbalanced fetcher notification for %@", _request.URL); + [self forgetSessionIdentifierForFetcherWithoutSyncCheck]; + + // Note: if a session task or a retry timer was pending, then this instance + // would be retained by those so it wouldn't be getting dealloc'd, + // hence we don't need to stopFetch here +} + +#pragma mark - + +// Begin fetching the URL (or begin a retry fetch). The delegate is retained +// for the duration of the fetch connection. + +- (void)beginFetchWithCompletionHandler:(GTM_NULLABLE GTMSessionFetcherCompletionHandler)handler { + GTMSessionCheckNotSynchronized(self); + + _completionHandler = [handler copy]; + + // The user may have called setDelegate: earlier if they want to use other + // delegate-style callbacks during the fetch; otherwise, the delegate is nil, + // which is fine. + [self beginFetchMayDelay:YES mayAuthorize:YES]; +} + +// Begin fetching the URL for a retry fetch. The delegate and completion handler +// are already provided, and do not need to be copied. +- (void)beginFetchForRetry { + GTMSessionCheckNotSynchronized(self); + + [self beginFetchMayDelay:YES mayAuthorize:YES]; +} + +- (GTMSessionFetcherCompletionHandler)completionHandlerWithTarget:(GTM_NULLABLE_TYPE id)target + didFinishSelector:(GTM_NULLABLE_TYPE SEL)finishedSelector { + GTMSessionFetcherAssertValidSelector(target, finishedSelector, @encode(GTMSessionFetcher *), + @encode(NSData *), @encode(NSError *), 0); + GTMSessionFetcherCompletionHandler completionHandler = ^(NSData *data, NSError *error) { + if (target && finishedSelector) { + id selfArg = self; // Placate ARC. + NSMethodSignature *sig = [target methodSignatureForSelector:finishedSelector]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig]; + [invocation setSelector:(SEL)finishedSelector]; + [invocation setTarget:target]; + [invocation setArgument:&selfArg atIndex:2]; + [invocation setArgument:&data atIndex:3]; + [invocation setArgument:&error atIndex:4]; + [invocation invoke]; + } + }; + return completionHandler; +} + +- (void)beginFetchWithDelegate:(GTM_NULLABLE_TYPE id)target + didFinishSelector:(GTM_NULLABLE_TYPE SEL)finishedSelector { + GTMSessionCheckNotSynchronized(self); + + GTMSessionFetcherCompletionHandler handler = [self completionHandlerWithTarget:target + didFinishSelector:finishedSelector]; + [self beginFetchWithCompletionHandler:handler]; +} + +- (void)beginFetchMayDelay:(BOOL)mayDelay + mayAuthorize:(BOOL)mayAuthorize { + // This is the internal entry point for re-starting fetches. + GTMSessionCheckNotSynchronized(self); + + NSMutableURLRequest *fetchRequest = _request; // The request property is now externally immutable. + NSURL *fetchRequestURL = fetchRequest.URL; + NSString *priorSessionIdentifier = self.sessionIdentifier; + + // A utility block for creating error objects when we fail to start the fetch. + NSError *(^beginFailureError)(NSInteger) = ^(NSInteger code){ + NSString *urlString = fetchRequestURL.absoluteString; + NSDictionary *userInfo = @{ + NSURLErrorFailingURLStringErrorKey : (urlString ? urlString : @"(missing URL)") + }; + return [NSError errorWithDomain:kGTMSessionFetcherErrorDomain + code:code + userInfo:userInfo]; + }; + + // Catch delegate queue maxConcurrentOperationCount values other than 1, particularly + // NSOperationQueueDefaultMaxConcurrentOperationCount (-1), to avoid the additional complexity + // of simultaneous or out-of-order delegate callbacks. + GTMSESSION_ASSERT_DEBUG(_delegateQueue.maxConcurrentOperationCount == 1, + @"delegate queue %@ should support one concurrent operation, not %ld", + _delegateQueue.name, + (long)_delegateQueue.maxConcurrentOperationCount); + + if (!_initialBeginFetchDate) { + // This ivar is set only here on the initial beginFetch so need not be synchronized. + _initialBeginFetchDate = [[NSDate alloc] init]; + } + + if (self.sessionTask != nil) { + // If cached fetcher returned through fetcherWithSessionIdentifier:, then it's + // already begun, but don't consider this a failure, since the user need not know this. + if (self.sessionIdentifier != nil) { + return; + } + GTMSESSION_ASSERT_DEBUG(NO, @"Fetch object %@ being reused; this should never happen", self); + [self failToBeginFetchWithError:beginFailureError(GTMSessionFetcherErrorDownloadFailed)]; + return; + } + + if (fetchRequestURL == nil && !_downloadResumeData && !priorSessionIdentifier) { + GTMSESSION_ASSERT_DEBUG(NO, @"Beginning a fetch requires a request with a URL"); + [self failToBeginFetchWithError:beginFailureError(GTMSessionFetcherErrorDownloadFailed)]; + return; + } + + // We'll respect the user's request for a background session (unless this is + // an upload fetcher, which does its initial request foreground.) + self.usingBackgroundSession = self.useBackgroundSession && [self canFetchWithBackgroundSession]; + + NSURL *bodyFileURL = self.bodyFileURL; + if (bodyFileURL) { + NSError *fileCheckError; + if (![bodyFileURL checkResourceIsReachableAndReturnError:&fileCheckError]) { + // This assert fires when the file being uploaded no longer exists once + // the fetcher is ready to start the upload. + GTMSESSION_ASSERT_DEBUG_OR_LOG(0, @"Body file is unreachable: %@\n %@", + bodyFileURL.path, fileCheckError); + [self failToBeginFetchWithError:fileCheckError]; + return; + } + } + + NSString *requestScheme = fetchRequestURL.scheme; + BOOL isDataRequest = [requestScheme isEqual:@"data"]; + if (isDataRequest) { + // NSURLSession does not support data URLs in background sessions. +#if DEBUG + if (priorSessionIdentifier || self.sessionIdentifier) { + GTMSESSION_LOG_DEBUG(@"Converting background to foreground session for %@", + fetchRequest); + } +#endif + [self setSessionIdentifierInternal:nil]; + self.useBackgroundSession = NO; + } + +#if GTM_ALLOW_INSECURE_REQUESTS + BOOL shouldCheckSecurity = NO; +#else + BOOL shouldCheckSecurity = (fetchRequestURL != nil + && !isDataRequest + && [[self class] appAllowsInsecureRequests]); +#endif + + if (shouldCheckSecurity) { + // Allow https only for requests, unless overridden by the client. + // + // Non-https requests may too easily be snooped, so we disallow them by default. + // + // file: and data: schemes are usually safe if they are hardcoded in the client or provided + // by a trusted source, but since it's fairly rare to need them, it's safest to make clients + // explicitly whitelist them. + BOOL isSecure = + requestScheme != nil && [requestScheme caseInsensitiveCompare:@"https"] == NSOrderedSame; + if (!isSecure) { + BOOL allowRequest = NO; + NSString *host = fetchRequestURL.host; + + // Check schemes first. A file scheme request may be allowed here, or as a localhost request. + for (NSString *allowedScheme in _allowedInsecureSchemes) { + if (requestScheme != nil && + [requestScheme caseInsensitiveCompare:allowedScheme] == NSOrderedSame) { + allowRequest = YES; + break; + } + } + if (!allowRequest) { + // Check for localhost requests. Security checks only occur for non-https requests, so + // this check won't happen for an https request to localhost. + BOOL isLocalhostRequest = (host.length == 0 && [fetchRequestURL isFileURL]) || IsLocalhost(host); + if (isLocalhostRequest) { + if (self.allowLocalhostRequest) { + allowRequest = YES; + } else { + GTMSESSION_ASSERT_DEBUG(NO, @"Fetch request for localhost but fetcher" + @" allowLocalhostRequest is not set: %@", fetchRequestURL); + } + } else { + GTMSESSION_ASSERT_DEBUG(NO, @"Insecure fetch request has a scheme (%@)" + @" not found in fetcher allowedInsecureSchemes (%@): %@", + requestScheme, _allowedInsecureSchemes ?: @" @[] ", fetchRequestURL); + } + } + + if (!allowRequest) { +#if !DEBUG + NSLog(@"Insecure fetch disallowed for %@", fetchRequestURL.description ?: @"nil request URL"); +#endif + [self failToBeginFetchWithError:beginFailureError(GTMSessionFetcherErrorInsecureRequest)]; + return; + } + } // !isSecure + } // (requestURL != nil) && !isDataRequest + + if (self.cookieStorage == nil) { + self.cookieStorage = [[self class] staticCookieStorage]; + } + + BOOL isRecreatingSession = (self.sessionIdentifier != nil) && (fetchRequest == nil); + + self.canShareSession = !isRecreatingSession && !self.usingBackgroundSession; + + if (!self.session && self.canShareSession) { + self.session = [_service sessionForFetcherCreation]; + // If _session is nil, then the service's session creation semaphore will block + // until this fetcher invokes fetcherDidCreateSession: below, so this *must* invoke + // that method, even if the session fails to be created. + } + + if (!self.session) { + // Create a session. + if (!_configuration) { + if (priorSessionIdentifier || self.usingBackgroundSession) { + NSString *sessionIdentifier = priorSessionIdentifier; + if (!sessionIdentifier) { + sessionIdentifier = [self createSessionIdentifierWithMetadata:nil]; + } + NSMapTable *sessionIdentifierToFetcherMap = [[self class] sessionIdentifierToFetcherMap]; + [sessionIdentifierToFetcherMap setObject:self forKey:self.sessionIdentifier]; + + if (@available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.10, *)) { + _configuration = + [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionIdentifier]; + } else { +#if ((!TARGET_OS_IPHONE && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_10) \ + || (TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0)) + // If building with support for iOS 7 or < macOS 10.10, allow using the older + // -backgroundSessionConfiguration: method, otherwise leave it out to avoid deprecated + // API warnings/errors. + _configuration = + [NSURLSessionConfiguration backgroundSessionConfiguration:sessionIdentifier]; +#endif + } + self.usingBackgroundSession = YES; + self.canShareSession = NO; + } else { + _configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration]; + } +#if !GTM_ALLOW_INSECURE_REQUESTS +#if GTM_SDK_REQUIRES_TLSMINIMUMSUPPORTEDPROTOCOLVERSION + _configuration.TLSMinimumSupportedProtocolVersion = tls_protocol_version_TLSv12; +#elif GTM_SDK_SUPPORTS_TLSMINIMUMSUPPORTEDPROTOCOLVERSION + if (@available(iOS 13, tvOS 13, watchOS 6, macOS 10.15, *)) { +#if TARGET_OS_IOS + // Early seeds of iOS 13 don't actually support the selector and several + // months later, those seeds are still in use, so validate if the selector + // is supported. + if ([_configuration respondsToSelector:@selector(setTLSMinimumSupportedProtocolVersion:)]) { + _configuration.TLSMinimumSupportedProtocolVersion = tls_protocol_version_TLSv12; + } else { + _configuration.TLSMinimumSupportedProtocol = kTLSProtocol12; + } +#else + _configuration.TLSMinimumSupportedProtocolVersion = tls_protocol_version_TLSv12; +#endif // TARGET_OS_IOS + } else { + _configuration.TLSMinimumSupportedProtocol = kTLSProtocol12; + } +#else + _configuration.TLSMinimumSupportedProtocol = kTLSProtocol12; +#endif // GTM_SDK_REQUIRES_TLSMINIMUMSUPPORTEDPROTOCOLVERSION +#endif + } // !_configuration + _configuration.HTTPCookieStorage = self.cookieStorage; + + if (_configurationBlock) { + _configurationBlock(self, _configuration); + } + + id delegate = [_service sessionDelegate]; + if (!delegate || !self.canShareSession) { + delegate = self; + } + self.session = [NSURLSession sessionWithConfiguration:_configuration + delegate:delegate + delegateQueue:self.sessionDelegateQueue]; + GTMSESSION_ASSERT_DEBUG(self.session, @"Couldn't create session"); + + // Tell the service about the session created by this fetcher. This also signals the + // service's semaphore to allow other fetchers to request this session. + [_service fetcherDidCreateSession:self]; + + // If this assertion fires, the client probably tried to use a session identifier that was + // already used. The solution is to make the client use a unique identifier (or better yet let + // the session fetcher assign the identifier). + GTMSESSION_ASSERT_DEBUG(self.session.delegate == delegate, @"Couldn't assign delegate."); + + if (self.session) { + BOOL isUsingSharedDelegate = (delegate != self); + if (!isUsingSharedDelegate) { + _shouldInvalidateSession = YES; + } + } + } + + if (isRecreatingSession) { + _shouldInvalidateSession = YES; + + // Let's make sure there are tasks still running or if not that we get a callback from a + // completed one; otherwise, we assume the tasks failed. + // This is the observed behavior perhaps 25% of the time within the Simulator running 7.0.3 on + // exiting the app after starting an upload and relaunching the app if we manage to relaunch + // after the task has completed, but before the system relaunches us in the background. + [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, + NSArray *downloadTasks) { + if (dataTasks.count == 0 && uploadTasks.count == 0 && downloadTasks.count == 0) { + double const kDelayInSeconds = 1.0; // We should get progress indication or completion soon + dispatch_time_t checkForFeedbackDelay = + dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kDelayInSeconds * NSEC_PER_SEC)); + dispatch_after(checkForFeedbackDelay, dispatch_get_main_queue(), ^{ + if (!self.sessionTask && !fetchRequest) { + // If our task and/or request haven't been restored, then we assume task feedback lost. + [self removePersistedBackgroundSessionFromDefaults]; + NSError *sessionError = + [NSError errorWithDomain:kGTMSessionFetcherErrorDomain + code:GTMSessionFetcherErrorBackgroundFetchFailed + userInfo:nil]; + [self failToBeginFetchWithError:sessionError]; + } + }); + } + }]; + return; + } + + self.downloadedData = nil; + self.downloadedLength = 0; + + if (_servicePriority == NSIntegerMin) { + mayDelay = NO; + } + if (mayDelay && _service) { + BOOL shouldFetchNow = [_service fetcherShouldBeginFetching:self]; + if (!shouldFetchNow) { + // The fetch is deferred, but will happen later. + // + // If this session is held by the fetcher service, clear the session now so that we don't + // assume it's still valid after the fetcher is restarted. + if (self.canShareSession) { + self.session = nil; + } + return; + } + } + + NSString *effectiveHTTPMethod = [fetchRequest valueForHTTPHeaderField:@"X-HTTP-Method-Override"]; + if (effectiveHTTPMethod == nil) { + effectiveHTTPMethod = fetchRequest.HTTPMethod; + } + BOOL isEffectiveHTTPGet = (effectiveHTTPMethod == nil + || [effectiveHTTPMethod isEqual:@"GET"]); + + BOOL needsUploadTask = (self.useUploadTask || self.bodyFileURL || self.bodyStreamProvider); + if (_bodyData || self.bodyStreamProvider || fetchRequest.HTTPBodyStream) { + if (isEffectiveHTTPGet) { + fetchRequest.HTTPMethod = @"POST"; + isEffectiveHTTPGet = NO; + } + + if (_bodyData) { + if (!needsUploadTask) { + fetchRequest.HTTPBody = _bodyData; + } +#if !STRIP_GTM_FETCH_LOGGING + } else if (fetchRequest.HTTPBodyStream) { + if ([self respondsToSelector:@selector(loggedInputStreamForInputStream:)]) { + fetchRequest.HTTPBodyStream = + [self performSelector:@selector(loggedInputStreamForInputStream:) + withObject:fetchRequest.HTTPBodyStream]; + } +#endif + } + } + + // We authorize after setting up the http method and body in the request + // because OAuth 1 may need to sign the request body + if (mayAuthorize && _authorizer && !isDataRequest) { + BOOL isAuthorized = [_authorizer isAuthorizedRequest:fetchRequest]; + if (!isAuthorized) { + // Authorization needed. + // + // If this session is held by the fetcher service, clear the session now so that we don't + // assume it's still valid after authorization completes. + if (self.canShareSession) { + self.session = nil; + } + + // Authorizing the request will recursively call this beginFetch:mayDelay: + // or failToBeginFetchWithError:. + [self authorizeRequest]; + return; + } + } + + // set the default upload or download retry interval, if necessary + if ([self isRetryEnabled] && self.maxRetryInterval <= 0) { + if (isEffectiveHTTPGet || [effectiveHTTPMethod isEqual:@"HEAD"]) { + [self setMaxRetryInterval:kDefaultMaxDownloadRetryInterval]; + } else { + [self setMaxRetryInterval:kDefaultMaxUploadRetryInterval]; + } + } + + // finally, start the connection + NSURLSessionTask *newSessionTask; + BOOL needsDataAccumulator = NO; + if (_downloadResumeData) { + newSessionTask = [_session downloadTaskWithResumeData:_downloadResumeData]; + GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask, + @"Failed downloadTaskWithResumeData for %@, resume data %lu bytes", + _session, (unsigned long)_downloadResumeData.length); + } else if (_destinationFileURL && !isDataRequest) { + newSessionTask = [_session downloadTaskWithRequest:fetchRequest]; + GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask, @"Failed downloadTaskWithRequest for %@, %@", + _session, fetchRequest); + } else if (needsUploadTask) { + if (bodyFileURL) { + newSessionTask = [_session uploadTaskWithRequest:fetchRequest + fromFile:bodyFileURL]; + GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask, + @"Failed uploadTaskWithRequest for %@, %@, file %@", + _session, fetchRequest, bodyFileURL.path); + } else if (self.bodyStreamProvider) { + newSessionTask = [_session uploadTaskWithStreamedRequest:fetchRequest]; + GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask, + @"Failed uploadTaskWithStreamedRequest for %@, %@", + _session, fetchRequest); + } else { + GTMSESSION_ASSERT_DEBUG_OR_LOG(_bodyData != nil, + @"Upload task needs body data, %@", fetchRequest); + newSessionTask = [_session uploadTaskWithRequest:fetchRequest + fromData:(NSData * GTM_NONNULL_TYPE)_bodyData]; + GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask, + @"Failed uploadTaskWithRequest for %@, %@, body data %lu bytes", + _session, fetchRequest, (unsigned long)_bodyData.length); + } + needsDataAccumulator = YES; + } else { + newSessionTask = [_session dataTaskWithRequest:fetchRequest]; + needsDataAccumulator = YES; + GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask, @"Failed dataTaskWithRequest for %@, %@", + _session, fetchRequest); + } + self.sessionTask = newSessionTask; + + if (!newSessionTask) { + // We shouldn't get here; if we're here, an earlier assertion should have fired to explain + // which session task creation failed. + [self failToBeginFetchWithError:beginFailureError(GTMSessionFetcherErrorTaskCreationFailed)]; + return; + } + + if (needsDataAccumulator && _accumulateDataBlock == nil) { + self.downloadedData = [NSMutableData data]; + } + if (_taskDescription) { + newSessionTask.taskDescription = _taskDescription; + } + if (_taskPriority >= 0) { + if (@available(iOS 8.0, macOS 10.10, *)) { + newSessionTask.priority = _taskPriority; + } + } + +#if GTM_DISABLE_FETCHER_TEST_BLOCK + GTMSESSION_ASSERT_DEBUG(_testBlock == nil && gGlobalTestBlock == nil, @"test blocks disabled"); + _testBlock = nil; +#else + if (!_testBlock) { + if (gGlobalTestBlock) { + // Note that the test block may pass nil for all of its response parameters, + // indicating that the fetch should actually proceed. This is useful when the + // global test block has been set, and the app is only testing a specific + // fetcher. The block simulation code will then resume the task. + _testBlock = gGlobalTestBlock; + } + } + _isUsingTestBlock = (_testBlock != nil); +#endif // GTM_DISABLE_FETCHER_TEST_BLOCK + +#if GTM_BACKGROUND_TASK_FETCHING + id app = [[self class] fetcherUIApplication]; + // Background tasks seem to interfere with out-of-process uploads and downloads. + if (app && !self.skipBackgroundTask && !self.useBackgroundSession) { + // Tell UIApplication that we want to continue even when the app is in the + // background. +#if DEBUG + NSString *bgTaskName = [NSString stringWithFormat:@"%@-%@", + [self class], fetchRequest.URL.host]; +#else + NSString *bgTaskName = @"GTMSessionFetcher"; +#endif + __block UIBackgroundTaskIdentifier bgTaskID = [app beginBackgroundTaskWithName:bgTaskName + expirationHandler:^{ + // Background task expiration callback - this block is always invoked by + // UIApplication on the main thread. + if (bgTaskID != UIBackgroundTaskInvalid) { + @synchronized(self) { + if (bgTaskID == self.backgroundTaskIdentifier) { + self.backgroundTaskIdentifier = UIBackgroundTaskInvalid; + } + } + [app endBackgroundTask:bgTaskID]; + } + }]; + @synchronized(self) { + self.backgroundTaskIdentifier = bgTaskID; + } + } +#endif + + if (!_initialRequestDate) { + _initialRequestDate = [[NSDate alloc] init]; + } + + // We don't expect to reach here even on retry or auth until a stop notification has been sent + // for the previous task, but we should ensure that we don't unbalance that. + GTMSESSION_ASSERT_DEBUG(!_isStopNotificationNeeded, @"Start notification without a prior stop"); + [self sendStopNotificationIfNeeded]; + + [self addPersistedBackgroundSessionToDefaults]; + + [self setStopNotificationNeeded:YES]; + + [self postNotificationOnMainThreadWithName:kGTMSessionFetcherStartedNotification + userInfo:nil + requireAsync:NO]; + + // The service needs to know our task if it is serving as NSURLSession delegate. + [_service fetcherDidBeginFetching:self]; + + if (_testBlock) { +#if !GTM_DISABLE_FETCHER_TEST_BLOCK + [self simulateFetchForTestBlock]; +#endif + } else { + // We resume the session task after posting the notification since the + // delegate callbacks may happen immediately if the fetch is started off + // the main thread or the session delegate queue is on a background thread, + // and we don't want to post a start notification after a premature finish + // of the session task. + [newSessionTask resume]; + } +} + +NSData * GTM_NULLABLE_TYPE GTMDataFromInputStream(NSInputStream *inputStream, NSError **outError) { + NSMutableData *data = [NSMutableData data]; + + [inputStream open]; + NSInteger numberOfBytesRead = 0; + while ([inputStream hasBytesAvailable]) { + uint8_t buffer[512]; + numberOfBytesRead = [inputStream read:buffer maxLength:sizeof(buffer)]; + if (numberOfBytesRead > 0) { + [data appendBytes:buffer length:(NSUInteger)numberOfBytesRead]; + } else { + break; + } + } + [inputStream close]; + NSError *streamError = inputStream.streamError; + + if (streamError) { + data = nil; + } + if (outError) { + *outError = streamError; + } + return data; +} + +#if !GTM_DISABLE_FETCHER_TEST_BLOCK + +- (void)simulateFetchForTestBlock { + // This is invoked on the same thread as the beginFetch method was. + // + // Callbacks will all occur on the callback queue. + _testBlock(self, ^(NSURLResponse *response, NSData *responseData, NSError *error) { + // Callback from test block. + if (response == nil && responseData == nil && error == nil) { + // Assume the fetcher should execute rather than be tested. + self->_testBlock = nil; + self->_isUsingTestBlock = NO; + [self->_sessionTask resume]; + return; + } + + GTMSessionFetcherBodyStreamProvider bodyStreamProvider = self.bodyStreamProvider; + if (bodyStreamProvider) { + bodyStreamProvider(^(NSInputStream *bodyStream){ + // Read from the input stream into an NSData buffer. We'll drain the stream + // explicitly on a background queue. + [self invokeOnCallbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0) + afterUserStopped:NO + block:^{ + NSError *streamError; + NSData *streamedData = GTMDataFromInputStream(bodyStream, &streamError); + + dispatch_async(dispatch_get_main_queue(), ^{ + // Continue callbacks on the main thread, since serial behavior + // is more reliable for tests. + [self simulateDataCallbacksForTestBlockWithBodyData:streamedData + response:response + responseData:responseData + error:(error ?: streamError)]; + }); + }]; + }); + } else { + // No input stream; use the supplied data or file URL. + NSURL *bodyFileURL = self.bodyFileURL; + if (bodyFileURL) { + NSError *readError; + self->_bodyData = [NSData dataWithContentsOfURL:bodyFileURL + options:NSDataReadingMappedIfSafe + error:&readError]; + error = readError; + } + + // No stream provider. + + // In real fetches, nothing happens until the run loop spins, so apps have leeway to + // set callbacks after they call beginFetch. We'll mirror that fetcher behavior by + // delaying callbacks here at least to the next spin of the run loop. That keeps + // immediate, synchronous setting of callback blocks after beginFetch working in tests. + dispatch_async(dispatch_get_main_queue(), ^{ + [self simulateDataCallbacksForTestBlockWithBodyData:self->_bodyData + response:response + responseData:responseData + error:error]; + }); + } + }); +} + +- (void)simulateByteTransferReportWithDataLength:(int64_t)totalDataLength + block:(GTMSessionFetcherSendProgressBlock)block { + // This utility method simulates transfer progress with up to three callbacks. + // It is used to call back to any of the progress blocks. + int64_t sendReportSize = totalDataLength / 3 + 1; + int64_t totalSent = 0; + while (totalSent < totalDataLength) { + int64_t bytesRemaining = totalDataLength - totalSent; + sendReportSize = MIN(sendReportSize, bytesRemaining); + totalSent += sendReportSize; + [self invokeOnCallbackQueueUnlessStopped:^{ + block(sendReportSize, totalSent, totalDataLength); + }]; + } +} + +- (void)simulateDataCallbacksForTestBlockWithBodyData:(NSData * GTM_NULLABLE_TYPE)bodyData + response:(NSURLResponse *)response + responseData:(NSData *)suppliedData + error:(NSError *)suppliedError { + __block NSData *responseData = suppliedData; + __block NSError *responseError = suppliedError; + + // This method does the test simulation of callbacks once the upload + // and download data are known. + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + // Get copies of ivars we'll access in async invocations. This simulation assumes + // they won't change during fetcher execution. + NSURL *destinationFileURL = _destinationFileURL; + GTMSessionFetcherWillRedirectBlock willRedirectBlock = _willRedirectBlock; + GTMSessionFetcherDidReceiveResponseBlock didReceiveResponseBlock = _didReceiveResponseBlock; + GTMSessionFetcherSendProgressBlock sendProgressBlock = _sendProgressBlock; + GTMSessionFetcherDownloadProgressBlock downloadProgressBlock = _downloadProgressBlock; + GTMSessionFetcherAccumulateDataBlock accumulateDataBlock = _accumulateDataBlock; + GTMSessionFetcherReceivedProgressBlock receivedProgressBlock = _receivedProgressBlock; + GTMSessionFetcherWillCacheURLResponseBlock willCacheURLResponseBlock = + _willCacheURLResponseBlock; + GTMSessionFetcherChallengeBlock challengeBlock = _challengeBlock; + + // Simulate receipt of redirection. + if (willRedirectBlock) { + [self invokeOnCallbackUnsynchronizedQueueAfterUserStopped:YES + block:^{ + willRedirectBlock((NSHTTPURLResponse *)response, self->_request, + ^(NSURLRequest *redirectRequest) { + // For simulation, we'll assume the app will just continue. + }); + }]; + } + + // If the fetcher has a challenge block, simulate a challenge. + // + // It might be nice to eventually let the user determine which testBlock + // fetches get challenged rather than always executing the supplied + // challenge block. + if (challengeBlock) { + [self invokeOnCallbackUnsynchronizedQueueAfterUserStopped:YES + block:^{ + NSURL *requestURL = self->_request.URL; + NSString *host = requestURL.host; + NSURLProtectionSpace *pspace = + [[NSURLProtectionSpace alloc] initWithHost:host + port:requestURL.port.integerValue + protocol:requestURL.scheme + realm:nil + authenticationMethod:NSURLAuthenticationMethodHTTPBasic]; + id unusedSender = + (id)[NSNull null]; + NSURLAuthenticationChallenge *challenge = + [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:pspace + proposedCredential:nil + previousFailureCount:0 + failureResponse:nil + error:nil + sender:unusedSender]; + challengeBlock(self, challenge, ^(NSURLSessionAuthChallengeDisposition disposition, + NSURLCredential * GTM_NULLABLE_TYPE credential){ + // We could change the responseData and responseError based on the disposition, + // but it's easier for apps to just supply the expected data and error + // directly to the test block. So this simulation ignores the disposition. + }); + }]; + } + + // Simulate receipt of an initial response. + if (response && didReceiveResponseBlock) { + [self invokeOnCallbackUnsynchronizedQueueAfterUserStopped:YES + block:^{ + didReceiveResponseBlock(response, ^(NSURLSessionResponseDisposition desiredDisposition) { + // For simulation, we'll assume the disposition is to continue. + }); + }]; + } + + // Simulate reporting send progress. + if (sendProgressBlock) { + [self simulateByteTransferReportWithDataLength:(int64_t)bodyData.length + block:^(int64_t bytesSent, + int64_t totalBytesSent, + int64_t totalBytesExpectedToSend) { + // This is invoked on the callback queue unless stopped. + sendProgressBlock(bytesSent, totalBytesSent, totalBytesExpectedToSend); + }]; + } + + if (destinationFileURL) { + // Simulate download to file progress. + if (downloadProgressBlock) { + [self simulateByteTransferReportWithDataLength:(int64_t)responseData.length + block:^(int64_t bytesDownloaded, + int64_t totalBytesDownloaded, + int64_t totalBytesExpectedToDownload) { + // This is invoked on the callback queue unless stopped. + downloadProgressBlock(bytesDownloaded, totalBytesDownloaded, + totalBytesExpectedToDownload); + }]; + } + + NSError *writeError; + [responseData writeToURL:destinationFileURL + options:NSDataWritingAtomic + error:&writeError]; + if (writeError) { + // Tell the test code that writing failed. + responseError = writeError; + } + } else { + // Simulate download to NSData progress. + if ((accumulateDataBlock || receivedProgressBlock) && responseData) { + [self simulateByteTransferWithData:responseData + block:^(NSData *data, + int64_t bytesReceived, + int64_t totalBytesReceived, + int64_t totalBytesExpectedToReceive) { + // This is invoked on the callback queue unless stopped. + if (accumulateDataBlock) { + accumulateDataBlock(data); + } + + if (receivedProgressBlock) { + receivedProgressBlock(bytesReceived, totalBytesReceived); + } + }]; + } + + if (!accumulateDataBlock) { + _downloadedData = [responseData mutableCopy]; + } + + if (willCacheURLResponseBlock) { + // Simulate letting the client inspect and alter the cached response. + NSData *cachedData = responseData ?: [[NSData alloc] init]; // Always have non-nil data. + NSCachedURLResponse *cachedResponse = + [[NSCachedURLResponse alloc] initWithResponse:response + data:cachedData]; + [self invokeOnCallbackUnsynchronizedQueueAfterUserStopped:YES + block:^{ + willCacheURLResponseBlock(cachedResponse, ^(NSCachedURLResponse *responseToCache){ + // The app may provide an alternative response, or nil to defeat caching. + }); + }]; + } + } + _response = response; + } // @synchronized(self) + + NSOperationQueue *queue = self.sessionDelegateQueue; + [queue addOperationWithBlock:^{ + // Rather than invoke failToBeginFetchWithError: we want to simulate completion of + // a connection that started and ended, so we'll call down to finishWithError: + NSInteger status = responseError ? responseError.code : 200; + if (status >= 200 && status <= 399) { + [self finishWithError:nil shouldRetry:NO]; + } else { + [self shouldRetryNowForStatus:status + error:responseError + forceAssumeRetry:NO + response:^(BOOL shouldRetry) { + [self finishWithError:responseError shouldRetry:shouldRetry]; + }]; + } + }]; +} + +- (void)simulateByteTransferWithData:(NSData *)responseData + block:(GTMSessionFetcherSimulateByteTransferBlock)transferBlock { + // This utility method simulates transfering data to the client. It divides the data into at most + // "chunkCount" chunks and then passes each chunk along with a progress update to transferBlock. + // This function can be used with accumulateDataBlock or receivedProgressBlock. + + NSUInteger chunkCount = MAX(self.testBlockAccumulateDataChunkCount, (NSUInteger) 1); + NSUInteger totalDataLength = responseData.length; + NSUInteger sendDataSize = totalDataLength / chunkCount + 1; + NSUInteger totalSent = 0; + while (totalSent < totalDataLength) { + NSUInteger bytesRemaining = totalDataLength - totalSent; + sendDataSize = MIN(sendDataSize, bytesRemaining); + NSData *chunkData = [responseData subdataWithRange:NSMakeRange(totalSent, sendDataSize)]; + totalSent += sendDataSize; + [self invokeOnCallbackQueueUnlessStopped:^{ + transferBlock(chunkData, + (int64_t)sendDataSize, + (int64_t)totalSent, + (int64_t)totalDataLength); + }]; + } +} + +#endif // !GTM_DISABLE_FETCHER_TEST_BLOCK + +- (void)setSessionTask:(NSURLSessionTask *)sessionTask { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_sessionTask != sessionTask) { + _sessionTask = sessionTask; + if (_sessionTask) { + // Request could be nil on restoring this fetcher from a background session. + if (!_request) { + _request = [_sessionTask.originalRequest mutableCopy]; + } + } + } + } // @synchronized(self) +} + +- (NSURLSessionTask * GTM_NULLABLE_TYPE)sessionTask { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _sessionTask; + } // @synchronized(self) +} + ++ (NSUserDefaults *)fetcherUserDefaults { + static NSUserDefaults *gFetcherUserDefaults = nil; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Class fetcherUserDefaultsClass = NSClassFromString(@"GTMSessionFetcherUserDefaultsFactory"); + if (fetcherUserDefaultsClass) { + gFetcherUserDefaults = [fetcherUserDefaultsClass fetcherUserDefaults]; + } else { + gFetcherUserDefaults = [NSUserDefaults standardUserDefaults]; + } + }); + return gFetcherUserDefaults; +} + +- (void)addPersistedBackgroundSessionToDefaults { + NSString *sessionIdentifier = self.sessionIdentifier; + if (!sessionIdentifier) { + return; + } + NSArray *oldBackgroundSessions = [[self class] activePersistedBackgroundSessions]; + if ([oldBackgroundSessions containsObject:_sessionIdentifier]) { + return; + } + NSMutableArray *newBackgroundSessions = + [NSMutableArray arrayWithArray:oldBackgroundSessions]; + [newBackgroundSessions addObject:sessionIdentifier]; + GTM_LOG_BACKGROUND_SESSION(@"Add to background sessions: %@", newBackgroundSessions); + + NSUserDefaults *userDefaults = [[self class] fetcherUserDefaults]; + [userDefaults setObject:newBackgroundSessions + forKey:kGTMSessionFetcherPersistedDestinationKey]; + [userDefaults synchronize]; +} + +- (void)removePersistedBackgroundSessionFromDefaults { + NSString *sessionIdentifier = self.sessionIdentifier; + if (!sessionIdentifier) return; + + NSArray *oldBackgroundSessions = [[self class] activePersistedBackgroundSessions]; + if (!oldBackgroundSessions) { + return; + } + NSMutableArray *newBackgroundSessions = + [NSMutableArray arrayWithArray:oldBackgroundSessions]; + NSUInteger sessionIndex = [newBackgroundSessions indexOfObject:sessionIdentifier]; + if (sessionIndex == NSNotFound) { + return; + } + [newBackgroundSessions removeObjectAtIndex:sessionIndex]; + GTM_LOG_BACKGROUND_SESSION(@"Remove from background sessions: %@", newBackgroundSessions); + + NSUserDefaults *userDefaults = [[self class] fetcherUserDefaults]; + if (newBackgroundSessions.count == 0) { + [userDefaults removeObjectForKey:kGTMSessionFetcherPersistedDestinationKey]; + } else { + [userDefaults setObject:newBackgroundSessions + forKey:kGTMSessionFetcherPersistedDestinationKey]; + } + [userDefaults synchronize]; +} + ++ (GTM_NULLABLE NSArray *)activePersistedBackgroundSessions { + NSUserDefaults *userDefaults = [[self class] fetcherUserDefaults]; + NSArray *oldBackgroundSessions = + [userDefaults arrayForKey:kGTMSessionFetcherPersistedDestinationKey]; + if (oldBackgroundSessions.count == 0) { + return nil; + } + NSMutableArray *activeBackgroundSessions = nil; + NSMapTable *sessionIdentifierToFetcherMap = [self sessionIdentifierToFetcherMap]; + for (NSString *sessionIdentifier in oldBackgroundSessions) { + GTMSessionFetcher *fetcher = [sessionIdentifierToFetcherMap objectForKey:sessionIdentifier]; + if (fetcher) { + if (!activeBackgroundSessions) { + activeBackgroundSessions = [[NSMutableArray alloc] init]; + } + [activeBackgroundSessions addObject:sessionIdentifier]; + } + } + return activeBackgroundSessions; +} + ++ (NSArray *)fetchersForBackgroundSessions { + NSUserDefaults *userDefaults = [[self class] fetcherUserDefaults]; + NSArray *backgroundSessions = + [userDefaults arrayForKey:kGTMSessionFetcherPersistedDestinationKey]; + NSMapTable *sessionIdentifierToFetcherMap = [self sessionIdentifierToFetcherMap]; + NSMutableArray *fetchers = [NSMutableArray array]; + for (NSString *sessionIdentifier in backgroundSessions) { + GTMSessionFetcher *fetcher = [sessionIdentifierToFetcherMap objectForKey:sessionIdentifier]; + if (!fetcher) { + fetcher = [self fetcherWithSessionIdentifier:sessionIdentifier]; + GTMSESSION_ASSERT_DEBUG(fetcher != nil, + @"Unexpected invalid session identifier: %@", sessionIdentifier); + [fetcher beginFetchWithCompletionHandler:nil]; + } + GTM_LOG_BACKGROUND_SESSION(@"%@ restoring session %@ by creating fetcher %@ %p", + [self class], sessionIdentifier, fetcher, fetcher); + if (fetcher != nil) { + [fetchers addObject:fetcher]; + } + } + return fetchers; +} + +#if TARGET_OS_IPHONE && !TARGET_OS_WATCH ++ (void)application:(UIApplication *)application + handleEventsForBackgroundURLSession:(NSString *)identifier + completionHandler:(GTMSessionFetcherSystemCompletionHandler)completionHandler { + GTMSessionFetcher *fetcher = [self fetcherWithSessionIdentifier:identifier]; + if (fetcher != nil) { + fetcher.systemCompletionHandler = completionHandler; + } else { + GTM_LOG_BACKGROUND_SESSION(@"%@ did not create background session identifier: %@", + [self class], identifier); + } +} +#endif + +- (NSString * GTM_NULLABLE_TYPE)sessionIdentifier { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _sessionIdentifier; + } // @synchronized(self) +} + +- (void)setSessionIdentifier:(NSString *)sessionIdentifier { + GTMSESSION_ASSERT_DEBUG(sessionIdentifier != nil, @"Invalid session identifier"); + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + GTMSESSION_ASSERT_DEBUG(!_session, @"Unable to set session identifier after session created"); + _sessionIdentifier = [sessionIdentifier copy]; + _usingBackgroundSession = YES; + _canShareSession = NO; + [self restoreDefaultStateForSessionIdentifierMetadata]; + } // @synchronized(self) +} + +- (void)setSessionIdentifierInternal:(GTM_NULLABLE NSString *)sessionIdentifier { + // This internal method only does a synchronized set of the session identifier. + // It does not have side effects on the background session, shared session, or + // session identifier metadata. + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _sessionIdentifier = [sessionIdentifier copy]; + } // @synchronized(self) +} + +- (NSDictionary * GTM_NULLABLE_TYPE)sessionUserInfo { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_sessionUserInfo == nil) { + // We'll return the metadata dictionary with internal keys removed. This avoids the user + // re-using the userInfo dictionary later and accidentally including the internal keys. + NSMutableDictionary *metadata = [[self sessionIdentifierMetadataUnsynchronized] mutableCopy]; + NSSet *keysToRemove = [metadata keysOfEntriesPassingTest:^BOOL(id key, id obj, BOOL *stop) { + return [key hasPrefix:@"_"]; + }]; + [metadata removeObjectsForKeys:[keysToRemove allObjects]]; + if (metadata.count > 0) { + _sessionUserInfo = metadata; + } + } + return _sessionUserInfo; + } // @synchronized(self) +} + +- (void)setSessionUserInfo:(NSDictionary * GTM_NULLABLE_TYPE)dictionary { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + GTMSESSION_ASSERT_DEBUG(_sessionIdentifier == nil, @"Too late to assign userInfo"); + _sessionUserInfo = dictionary; + } // @synchronized(self) +} + +- (GTM_NULLABLE NSDictionary *)sessionIdentifierDefaultMetadata { + GTMSessionCheckSynchronized(self); + + NSMutableDictionary *defaultUserInfo = [[NSMutableDictionary alloc] init]; + if (_destinationFileURL) { + defaultUserInfo[kGTMSessionIdentifierDestinationFileURLMetadataKey] = + [_destinationFileURL absoluteString]; + } + if (_bodyFileURL) { + defaultUserInfo[kGTMSessionIdentifierBodyFileURLMetadataKey] = [_bodyFileURL absoluteString]; + } + return (defaultUserInfo.count > 0) ? defaultUserInfo : nil; +} + +- (void)restoreDefaultStateForSessionIdentifierMetadata { + GTMSessionCheckSynchronized(self); + + NSDictionary *metadata = [self sessionIdentifierMetadataUnsynchronized]; + NSString *destinationFileURLString = metadata[kGTMSessionIdentifierDestinationFileURLMetadataKey]; + if (destinationFileURLString) { + _destinationFileURL = [NSURL URLWithString:destinationFileURLString]; + GTM_LOG_BACKGROUND_SESSION(@"Restoring destination file URL: %@", _destinationFileURL); + } + NSString *bodyFileURLString = metadata[kGTMSessionIdentifierBodyFileURLMetadataKey]; + if (bodyFileURLString) { + _bodyFileURL = [NSURL URLWithString:bodyFileURLString]; + GTM_LOG_BACKGROUND_SESSION(@"Restoring body file URL: %@", _bodyFileURL); + } +} + +- (NSDictionary * GTM_NULLABLE_TYPE)sessionIdentifierMetadata { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return [self sessionIdentifierMetadataUnsynchronized]; + } +} + +- (NSDictionary * GTM_NULLABLE_TYPE)sessionIdentifierMetadataUnsynchronized { + GTMSessionCheckSynchronized(self); + + // Session Identifier format: "com.google.__ + if (!_sessionIdentifier) { + return nil; + } + NSScanner *metadataScanner = [NSScanner scannerWithString:_sessionIdentifier]; + [metadataScanner setCharactersToBeSkipped:nil]; + NSString *metadataString; + NSString *uuid; + if ([metadataScanner scanUpToString:@"_" intoString:NULL] && + [metadataScanner scanString:@"_" intoString:NULL] && + [metadataScanner scanUpToString:@"_" intoString:&uuid] && + [metadataScanner scanString:@"_" intoString:NULL] && + [metadataScanner scanUpToString:@"\n" intoString:&metadataString]) { + _sessionIdentifierUUID = uuid; + NSData *metadataData = [metadataString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *metadataDict = + [NSJSONSerialization JSONObjectWithData:metadataData + options:0 + error:&error]; + GTM_LOG_BACKGROUND_SESSION(@"User Info from session identifier: %@ %@", + metadataDict, error ? error : @""); + return metadataDict; + } + return nil; +} + +- (NSString *)createSessionIdentifierWithMetadata:(NSDictionary * GTM_NULLABLE_TYPE)metadataToInclude { + NSString *result; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + // Session Identifier format: "com.google.__ + GTMSESSION_ASSERT_DEBUG(!_sessionIdentifier, @"Session identifier already created"); + _sessionIdentifierUUID = [[NSUUID UUID] UUIDString]; + _sessionIdentifier = + [NSString stringWithFormat:@"%@_%@", kGTMSessionIdentifierPrefix, _sessionIdentifierUUID]; + // Start with user-supplied keys so they cannot accidentally override the fetcher's keys. + NSMutableDictionary *metadataDict = + [NSMutableDictionary dictionaryWithDictionary:(NSDictionary * GTM_NONNULL_TYPE)_sessionUserInfo]; + + if (metadataToInclude) { + [metadataDict addEntriesFromDictionary:(NSDictionary *)metadataToInclude]; + } + NSDictionary *defaultMetadataDict = [self sessionIdentifierDefaultMetadata]; + if (defaultMetadataDict) { + [metadataDict addEntriesFromDictionary:defaultMetadataDict]; + } + if (metadataDict.count > 0) { + NSData *metadataData = [NSJSONSerialization dataWithJSONObject:metadataDict + options:0 + error:NULL]; + GTMSESSION_ASSERT_DEBUG(metadataData != nil, + @"Session identifier user info failed to convert to JSON"); + if (metadataData.length > 0) { + NSString *metadataString = [[NSString alloc] initWithData:metadataData + encoding:NSUTF8StringEncoding]; + _sessionIdentifier = + [_sessionIdentifier stringByAppendingFormat:@"_%@", metadataString]; + } + } + _didCreateSessionIdentifier = YES; + result = _sessionIdentifier; + } // @synchronized(self) + return result; +} + +- (void)failToBeginFetchWithError:(NSError *)error { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _hasStoppedFetching = YES; + } + + if (error == nil) { + error = [NSError errorWithDomain:kGTMSessionFetcherErrorDomain + code:GTMSessionFetcherErrorDownloadFailed + userInfo:nil]; + } + + [self invokeFetchCallbacksOnCallbackQueueWithData:nil + error:error]; + [self releaseCallbacks]; + + [_service fetcherDidStop:self]; + + self.authorizer = nil; +} + ++ (GTMSessionCookieStorage *)staticCookieStorage { + static GTMSessionCookieStorage *gCookieStorage = nil; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + gCookieStorage = [[GTMSessionCookieStorage alloc] init]; + }); + return gCookieStorage; +} + +#if GTM_BACKGROUND_TASK_FETCHING + +- (void)endBackgroundTask { + // Whenever the connection stops or background execution expires, + // we need to tell UIApplication we're done. + UIBackgroundTaskIdentifier bgTaskID; + @synchronized(self) { + bgTaskID = self.backgroundTaskIdentifier; + if (bgTaskID != UIBackgroundTaskInvalid) { + self.backgroundTaskIdentifier = UIBackgroundTaskInvalid; + } + } + + if (bgTaskID != UIBackgroundTaskInvalid) { + id app = [[self class] fetcherUIApplication]; + [app endBackgroundTask:bgTaskID]; + } +} + +#endif // GTM_BACKGROUND_TASK_FETCHING + +- (void)authorizeRequest { + GTMSessionCheckNotSynchronized(self); + + id authorizer = self.authorizer; + SEL asyncAuthSel = @selector(authorizeRequest:delegate:didFinishSelector:); + if ([authorizer respondsToSelector:asyncAuthSel]) { + SEL callbackSel = @selector(authorizer:request:finishedWithError:); + NSMutableURLRequest *mutableRequest = [self.request mutableCopy]; + [authorizer authorizeRequest:mutableRequest + delegate:self + didFinishSelector:callbackSel]; + } else { + GTMSESSION_ASSERT_DEBUG(authorizer == nil, @"invalid authorizer for fetch"); + + // No authorizing possible, and authorizing happens only after any delay; + // just begin fetching + [self beginFetchMayDelay:NO + mayAuthorize:NO]; + } +} + +- (void)authorizer:(id)auth + request:(NSMutableURLRequest *)authorizedRequest + finishedWithError:(NSError *)error { + GTMSessionCheckNotSynchronized(self); + + if (error != nil) { + // We can't fetch without authorization + [self failToBeginFetchWithError:error]; + } else { + @synchronized(self) { + _request = authorizedRequest; + } + [self beginFetchMayDelay:NO + mayAuthorize:NO]; + } +} + + +- (BOOL)canFetchWithBackgroundSession { + // Subclasses may override. + return YES; +} + +// Returns YES if the fetcher has been started and has not yet stopped. +// +// Fetching includes waiting for authorization or for retry, waiting to be allowed by the +// service object to start the request, and actually fetching the request. +- (BOOL)isFetching { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return [self isFetchingUnsynchronized]; + } +} + +- (BOOL)isFetchingUnsynchronized { + GTMSessionCheckSynchronized(self); + + BOOL hasBegun = (_initialBeginFetchDate != nil); + return hasBegun && !_hasStoppedFetching; +} + +- (NSURLResponse * GTM_NULLABLE_TYPE)response { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSURLResponse *response = [self responseUnsynchronized]; + return response; + } // @synchronized(self) +} + +- (NSURLResponse * GTM_NULLABLE_TYPE)responseUnsynchronized { + GTMSessionCheckSynchronized(self); + + NSURLResponse *response = _sessionTask.response; + if (!response) response = _response; + return response; +} + +- (NSInteger)statusCode { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSInteger statusCode = [self statusCodeUnsynchronized]; + return statusCode; + } // @synchronized(self) +} + +- (NSInteger)statusCodeUnsynchronized { + GTMSessionCheckSynchronized(self); + + NSURLResponse *response = [self responseUnsynchronized]; + NSInteger statusCode; + + if ([response respondsToSelector:@selector(statusCode)]) { + statusCode = [(NSHTTPURLResponse *)response statusCode]; + } else { + // Default to zero, in hopes of hinting "Unknown" (we can't be + // sure that things are OK enough to use 200). + statusCode = 0; + } + return statusCode; +} + +- (NSDictionary * GTM_NULLABLE_TYPE)responseHeaders { + GTMSessionCheckNotSynchronized(self); + + NSURLResponse *response = self.response; + if ([response respondsToSelector:@selector(allHeaderFields)]) { + NSDictionary *headers = [(NSHTTPURLResponse *)response allHeaderFields]; + return headers; + } + return nil; +} + +- (NSDictionary * GTM_NULLABLE_TYPE)responseHeadersUnsynchronized { + GTMSessionCheckSynchronized(self); + + NSURLResponse *response = [self responseUnsynchronized]; + if ([response respondsToSelector:@selector(allHeaderFields)]) { + NSDictionary *headers = [(NSHTTPURLResponse *)response allHeaderFields]; + return headers; + } + return nil; +} + +- (void)releaseCallbacks { + // Avoid releasing blocks in the sync section since objects dealloc'd by + // the blocks being released may call back into the fetcher or fetcher + // service. + dispatch_queue_t NS_VALID_UNTIL_END_OF_SCOPE holdCallbackQueue; + GTMSessionFetcherCompletionHandler NS_VALID_UNTIL_END_OF_SCOPE holdCompletionHandler; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + holdCallbackQueue = _callbackQueue; + holdCompletionHandler = _completionHandler; + + _callbackQueue = nil; + _completionHandler = nil; // Setter overridden in upload. Setter assumed to be used externally. + } + + // Set local callback pointers to nil here rather than let them release at the end of the scope + // to make any problems due to the blocks being released be a bit more obvious in a stack trace. + holdCallbackQueue = nil; + holdCompletionHandler = nil; + + self.configurationBlock = nil; + self.didReceiveResponseBlock = nil; + self.challengeBlock = nil; + self.willRedirectBlock = nil; + self.sendProgressBlock = nil; + self.receivedProgressBlock = nil; + self.downloadProgressBlock = nil; + self.accumulateDataBlock = nil; + self.willCacheURLResponseBlock = nil; + self.retryBlock = nil; + self.testBlock = nil; + self.resumeDataBlock = nil; +} + +- (void)forgetSessionIdentifierForFetcher { + GTMSessionCheckSynchronized(self); + [self forgetSessionIdentifierForFetcherWithoutSyncCheck]; +} + +- (void)forgetSessionIdentifierForFetcherWithoutSyncCheck { + // This should be called inside a @synchronized block (except during dealloc.) + if (_sessionIdentifier) { + NSMapTable *sessionIdentifierToFetcherMap = [[self class] sessionIdentifierToFetcherMap]; + [sessionIdentifierToFetcherMap removeObjectForKey:_sessionIdentifier]; + _sessionIdentifier = nil; + _didCreateSessionIdentifier = NO; + } +} + +// External stop method +- (void)stopFetching { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + // Prevent enqueued callbacks from executing. + _userStoppedFetching = YES; + } // @synchronized(self) + [self stopFetchReleasingCallbacks:YES]; +} + +// Cancel the fetch of the URL that's currently in progress. +// +// If shouldReleaseCallbacks is NO then the fetch will be retried so the callbacks +// need to still be retained. +- (void)stopFetchReleasingCallbacks:(BOOL)shouldReleaseCallbacks { + [self removePersistedBackgroundSessionFromDefaults]; + + id service; + NSMutableURLRequest *request; + + // If the task or the retry timer is all that's retaining the fetcher, + // we want to be sure this instance survives stopping at least long enough for + // the stack to unwind. + __autoreleasing GTMSessionFetcher *holdSelf = self; + + BOOL hasCanceledTask = NO; + + [holdSelf destroyRetryTimer]; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _hasStoppedFetching = YES; + + service = _service; + request = _request; + + if (_sessionTask) { + // In case cancelling the task or session calls this recursively, we want + // to ensure that we'll only release the task and delegate once, + // so first set _sessionTask to nil + // + // This may be called in a callback from the task, so use autorelease to avoid + // releasing the task in its own callback. + __autoreleasing NSURLSessionTask *oldTask = _sessionTask; + if (!_isUsingTestBlock) { + _response = _sessionTask.response; + } + _sessionTask = nil; + + if ([oldTask state] != NSURLSessionTaskStateCompleted) { + // For download tasks, when the fetch is stopped, we may provide resume data that can + // be used to create a new session. + BOOL mayResume = (_resumeDataBlock + && [oldTask respondsToSelector:@selector(cancelByProducingResumeData:)]); + if (!mayResume) { + [oldTask cancel]; + // A side effect of stopping the task is that URLSession:task:didCompleteWithError: + // will be invoked asynchronously on the delegate queue. + } else { + void (^resumeBlock)(NSData *) = _resumeDataBlock; + _resumeDataBlock = nil; + + // Save callbackQueue since releaseCallbacks clears it. + dispatch_queue_t callbackQueue = _callbackQueue; + dispatch_group_enter(_callbackGroup); + [(NSURLSessionDownloadTask *)oldTask cancelByProducingResumeData:^(NSData *resumeData) { + [self invokeOnCallbackQueue:callbackQueue + afterUserStopped:YES + block:^{ + resumeBlock(resumeData); + dispatch_group_leave(self->_callbackGroup); + }]; + }]; + } + hasCanceledTask = YES; + } + } + + // If the task was canceled, wait until the URLSession:task:didCompleteWithError: to call + // finishTasksAndInvalidate, since calling it immediately tends to crash, see radar 18471901. + if (_session) { + BOOL shouldInvalidate = _shouldInvalidateSession; +#if TARGET_OS_IPHONE + // Don't invalidate if we've got a systemCompletionHandler, since + // URLSessionDidFinishEventsForBackgroundURLSession: won't be called if invalidated. + shouldInvalidate = shouldInvalidate && !self.systemCompletionHandler; +#endif + if (shouldInvalidate) { + __autoreleasing NSURLSession *oldSession = _session; + _session = nil; + + if (!hasCanceledTask) { + [oldSession finishTasksAndInvalidate]; + } else { + _sessionNeedingInvalidation = oldSession; + } + } + } + } // @synchronized(self) + + // send the stopped notification + [self sendStopNotificationIfNeeded]; + + [_authorizer stopAuthorizationForRequest:request]; + + if (shouldReleaseCallbacks) { + [self releaseCallbacks]; + + self.authorizer = nil; + } + + [service fetcherDidStop:self]; + +#if GTM_BACKGROUND_TASK_FETCHING + [self endBackgroundTask]; +#endif +} + +- (void)setStopNotificationNeeded:(BOOL)flag { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _isStopNotificationNeeded = flag; + } // @synchronized(self) +} + +- (void)sendStopNotificationIfNeeded { + BOOL sendNow = NO; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_isStopNotificationNeeded) { + _isStopNotificationNeeded = NO; + sendNow = YES; + } + } // @synchronized(self) + + if (sendNow) { + [self postNotificationOnMainThreadWithName:kGTMSessionFetcherStoppedNotification + userInfo:nil + requireAsync:NO]; + } +} + +- (void)retryFetch { + [self stopFetchReleasingCallbacks:NO]; + + // A retry will need a configuration with a fresh session identifier. + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_sessionIdentifier && _didCreateSessionIdentifier) { + [self forgetSessionIdentifierForFetcher]; + _configuration = nil; + } + + if (_canShareSession) { + // Force a grab of the current session from the fetcher service in case + // the service's old one has become invalid. + _session = nil; + } + } // @synchronized(self) + + [self beginFetchForRetry]; +} + +- (BOOL)waitForCompletionWithTimeout:(NSTimeInterval)timeoutInSeconds { + // Uncovered in upload fetcher testing, because the chunk fetcher is being waited on, and gets + // released by the upload code. The uploader just holds onto it with an ivar, and that gets + // nilled in the chunk fetcher callback. + // Used once in while loop just to avoid unused variable compiler warning. + __autoreleasing GTMSessionFetcher *holdSelf = self; + + NSDate *giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds]; + + BOOL shouldSpinRunLoop = ([NSThread isMainThread] && + (!self.callbackQueue + || self.callbackQueue == dispatch_get_main_queue())); + BOOL expired = NO; + + // Loop until the callbacks have been called and released, and until + // the connection is no longer pending, until there are no callback dispatches + // in flight, or until the timeout has expired. + int64_t delta = (int64_t)(100 * NSEC_PER_MSEC); // 100 ms + while (1) { + BOOL isTaskInProgress = (holdSelf->_sessionTask + && [_sessionTask state] != NSURLSessionTaskStateCompleted); + BOOL needsToCallCompletion = (_completionHandler != nil); + BOOL isCallbackInProgress = (_callbackGroup + && dispatch_group_wait(_callbackGroup, dispatch_time(DISPATCH_TIME_NOW, delta))); + + if (!isTaskInProgress && !needsToCallCompletion && !isCallbackInProgress) break; + + expired = ([giveUpDate timeIntervalSinceNow] < 0); + if (expired) { + GTMSESSION_LOG_DEBUG(@"GTMSessionFetcher waitForCompletionWithTimeout:%0.1f expired -- " + @"%@%@%@", timeoutInSeconds, + isTaskInProgress ? @"taskInProgress " : @"", + needsToCallCompletion ? @"needsToCallCompletion " : @"", + isCallbackInProgress ? @"isCallbackInProgress" : @""); + break; + } + + // Run the current run loop 1/1000 of a second to give the networking + // code a chance to work + const NSTimeInterval kSpinInterval = 0.001; + if (shouldSpinRunLoop) { + NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:kSpinInterval]; + [[NSRunLoop currentRunLoop] runUntilDate:stopDate]; + } else { + [NSThread sleepForTimeInterval:kSpinInterval]; + } + } + return !expired; +} + ++ (void)setGlobalTestBlock:(GTMSessionFetcherTestBlock GTM_NULLABLE_TYPE)block { +#if GTM_DISABLE_FETCHER_TEST_BLOCK + GTMSESSION_ASSERT_DEBUG(block == nil, @"test blocks disabled"); +#endif + gGlobalTestBlock = [block copy]; +} + +#if GTM_BACKGROUND_TASK_FETCHING + +static GTM_NULLABLE_TYPE id gSubstituteUIApp; + ++ (void)setSubstituteUIApplication:(nullable id)app { + gSubstituteUIApp = app; +} + ++ (nullable id)substituteUIApplication { + return gSubstituteUIApp; +} + ++ (nullable id)fetcherUIApplication { + id app = gSubstituteUIApp; + if (app) return app; + + // iOS App extensions should not call [UIApplication sharedApplication], even + // if UIApplication responds to it. + + static Class applicationClass = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + BOOL isAppExtension = [[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]; + if (!isAppExtension) { + Class cls = NSClassFromString(@"UIApplication"); + if (cls && [cls respondsToSelector:NSSelectorFromString(@"sharedApplication")]) { + applicationClass = cls; + } + } + }); + + if (applicationClass) { + app = (id)[applicationClass sharedApplication]; + } + return app; +} +#endif // GTM_BACKGROUND_TASK_FETCHING + +#pragma mark NSURLSession Delegate Methods + +// NSURLSession documentation indicates that redirectRequest can be passed to the handler +// but empirically redirectRequest lacks the HTTP body, so passing it will break POSTs. +// Instead, we construct a new request, a copy of the original, with overrides from the +// redirect. + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task +willPerformHTTPRedirection:(NSHTTPURLResponse *)redirectResponse + newRequest:(NSURLRequest *)redirectRequest + completionHandler:(void (^)(NSURLRequest * GTM_NULLABLE_TYPE))handler { + [self setSessionTask:task]; + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ task:%@ willPerformHTTPRedirection:%@ newRequest:%@", + [self class], self, session, task, redirectResponse, redirectRequest); + + if ([self userStoppedFetching]) { + handler(nil); + return; + } + if (redirectRequest && redirectResponse) { + // Copy the original request, including the body. + NSURLRequest *originalRequest = self.request; + NSMutableURLRequest *newRequest = [originalRequest mutableCopy]; + + // The new requests's URL overrides the original's URL. + [newRequest setURL:[GTMSessionFetcher redirectURLWithOriginalRequestURL:originalRequest.URL + redirectRequestURL:redirectRequest.URL]]; + + // Any headers in the redirect override headers in the original. + NSDictionary *redirectHeaders = redirectRequest.allHTTPHeaderFields; + for (NSString *key in redirectHeaders) { + NSString *value = [redirectHeaders objectForKey:key]; + [newRequest setValue:value forHTTPHeaderField:key]; + } + + redirectRequest = newRequest; + + // Log the response we just received + [self setResponse:redirectResponse]; + [self logNowWithError:nil]; + + GTMSessionFetcherWillRedirectBlock willRedirectBlock = self.willRedirectBlock; + if (willRedirectBlock) { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + [self invokeOnCallbackQueueAfterUserStopped:YES + block:^{ + willRedirectBlock(redirectResponse, redirectRequest, ^(NSURLRequest *clientRequest) { + + // Update the request for future logging. + [self updateMutableRequest:[clientRequest mutableCopy]]; + + handler(clientRequest); + }); + }]; + } // @synchronized(self) + return; + } + // Continues here if the client did not provide a redirect block. + + // Update the request for future logging. + [self updateMutableRequest:[redirectRequest mutableCopy]]; + } + handler(redirectRequest); +} + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask +didReceiveResponse:(NSURLResponse *)response + completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))handler { + [self setSessionTask:dataTask]; + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ dataTask:%@ didReceiveResponse:%@", + [self class], self, session, dataTask, response); + void (^accumulateAndFinish)(NSURLSessionResponseDisposition) = + ^(NSURLSessionResponseDisposition dispositionValue) { + // This method is called when the server has determined that it + // has enough information to create the NSURLResponse + // it can be called multiple times, for example in the case of a + // redirect, so each time we reset the data. + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + BOOL hadPreviousData = self->_downloadedLength > 0; + + [self->_downloadedData setLength:0]; + self->_downloadedLength = 0; + + if (hadPreviousData && (dispositionValue != NSURLSessionResponseCancel)) { + // Tell the accumulate block to discard prior data. + GTMSessionFetcherAccumulateDataBlock accumulateBlock = self->_accumulateDataBlock; + if (accumulateBlock) { + [self invokeOnCallbackQueueUnlessStopped:^{ + accumulateBlock(nil); + }]; + } + } + } // @synchronized(self) + handler(dispositionValue); + }; + + GTMSessionFetcherDidReceiveResponseBlock receivedResponseBlock; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + receivedResponseBlock = _didReceiveResponseBlock; + if (receivedResponseBlock) { + // We will ultimately need to call back to NSURLSession's handler with the disposition value + // for this delegate method even if the user has stopped the fetcher. + [self invokeOnCallbackQueueAfterUserStopped:YES + block:^{ + receivedResponseBlock(response, ^(NSURLSessionResponseDisposition desiredDisposition) { + accumulateAndFinish(desiredDisposition); + }); + }]; + } + } // @synchronized(self) + + if (receivedResponseBlock == nil) { + accumulateAndFinish(NSURLSessionResponseAllow); + } +} + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask +didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask { + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ dataTask:%@ didBecomeDownloadTask:%@", + [self class], self, session, dataTask, downloadTask); + [self setSessionTask:downloadTask]; +} + + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task +didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge + completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, + NSURLCredential * GTM_NULLABLE_TYPE credential))handler { + [self setSessionTask:task]; + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ task:%@ didReceiveChallenge:%@", + [self class], self, session, task, challenge); + + GTMSessionFetcherChallengeBlock challengeBlock = self.challengeBlock; + if (challengeBlock) { + // The fetcher user has provided custom challenge handling. + // + // We will ultimately need to call back to NSURLSession's handler with the disposition value + // for this delegate method even if the user has stopped the fetcher. + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + [self invokeOnCallbackQueueAfterUserStopped:YES + block:^{ + challengeBlock(self, challenge, handler); + }]; + } + } else { + // No challenge block was provided by the client. + [self respondToChallenge:challenge + completionHandler:handler]; + } +} + +- (void)respondToChallenge:(NSURLAuthenticationChallenge *)challenge + completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, + NSURLCredential * GTM_NULLABLE_TYPE credential))handler { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSInteger previousFailureCount = [challenge previousFailureCount]; + if (previousFailureCount <= 2) { + NSURLProtectionSpace *protectionSpace = [challenge protectionSpace]; + NSString *authenticationMethod = [protectionSpace authenticationMethod]; + if ([authenticationMethod isEqual:NSURLAuthenticationMethodServerTrust]) { + // SSL. + // + // Background sessions seem to require an explicit check of the server trust object + // rather than default handling. + SecTrustRef serverTrust = challenge.protectionSpace.serverTrust; + if (serverTrust == NULL) { + // No server trust information is available. + handler(NSURLSessionAuthChallengePerformDefaultHandling, nil); + } else { + // Server trust information is available. + void (^callback)(SecTrustRef, BOOL) = ^(SecTrustRef trustRef, BOOL allow){ + if (allow) { + NSURLCredential *trustCredential = [NSURLCredential credentialForTrust:trustRef]; + handler(NSURLSessionAuthChallengeUseCredential, trustCredential); + } else { + GTMSESSION_LOG_DEBUG(@"Cancelling authentication challenge for %@", self->_request.URL); + handler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); + } + }; + if (_allowInvalidServerCertificates) { + callback(serverTrust, YES); + } else { + [[self class] evaluateServerTrust:serverTrust + forRequest:_request + completionHandler:callback]; + } + } + return; + } + + NSURLCredential *credential = _credential; + + if ([[challenge protectionSpace] isProxy] && _proxyCredential != nil) { + credential = _proxyCredential; + } + + if (credential) { + handler(NSURLSessionAuthChallengeUseCredential, credential); + } else { + // The credential is still nil; tell the OS to use the default handling. This is needed + // for things that can come out of the keychain (proxies, client certificates, etc.). + // + // Note: Looking up a credential with NSURLCredentialStorage's + // defaultCredentialForProtectionSpace: is *not* the same invoking the handler with + // NSURLSessionAuthChallengePerformDefaultHandling. In the case of + // NSURLAuthenticationMethodClientCertificate, you can get nil back from + // NSURLCredentialStorage, while using this code path instead works. + handler(NSURLSessionAuthChallengePerformDefaultHandling, nil); + } + + } else { + // We've failed auth 3 times. The completion handler will be called with code + // NSURLErrorCancelled. + handler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); + } + } // @synchronized(self) +} + +// Return redirect URL based on the original request URL and redirect request URL. +// +// Method disallows any scheme changes between the original request URL and redirect request URL +// aside from "http" to "https". If a change in scheme is detected the redirect URL inherits the +// scheme from the original request URL. ++ (GTM_NULLABLE NSURL *)redirectURLWithOriginalRequestURL:(GTM_NULLABLE NSURL *)originalRequestURL + redirectRequestURL:(GTM_NULLABLE NSURL *)redirectRequestURL { + // In the case of an NSURLSession redirect, neither URL should ever be nil; as a sanity check + // if either is nil return the other URL. + if (!redirectRequestURL) return originalRequestURL; + if (!originalRequestURL) return redirectRequestURL; + + NSString *originalScheme = originalRequestURL.scheme; + NSString *redirectScheme = redirectRequestURL.scheme; + BOOL insecureToSecureRedirect = + (originalScheme != nil && [originalScheme caseInsensitiveCompare:@"http"] == NSOrderedSame && + redirectScheme != nil && [redirectScheme caseInsensitiveCompare:@"https"] == NSOrderedSame); + + // This can't really be nil for the inputs, but to keep the analyzer happy + // for the -caseInsensitiveCompare: call below, give it a value if it were. + if (!originalScheme) originalScheme = @"https"; + + // Check for changes to the scheme and disallow any changes except for http to https. + if (!insecureToSecureRedirect && + (redirectScheme.length != originalScheme.length || + [redirectScheme caseInsensitiveCompare:originalScheme] != NSOrderedSame)) { + NSURLComponents *components = + [NSURLComponents componentsWithURL:(NSURL * _Nonnull)redirectRequestURL + resolvingAgainstBaseURL:NO]; + components.scheme = originalScheme; + return components.URL; + } + + return redirectRequestURL; +} + +// Validate the certificate chain. +// +// This may become a public method if it appears to be useful to users. ++ (void)evaluateServerTrust:(SecTrustRef)serverTrust + forRequest:(NSURLRequest *)request + completionHandler:(void (^)(SecTrustRef trustRef, BOOL allow))handler { + // Retain the trust object to avoid a SecTrustEvaluate() crash on iOS 7. + CFRetain(serverTrust); + + // Evaluate the certificate chain. + // + // The delegate queue may be the main thread. Trust evaluation could cause some + // blocking network activity, so we must evaluate async, as documented at + // https://developer.apple.com/library/ios/technotes/tn2232/ + // + // We must also avoid multiple uses of the trust object, per docs: + // "It is not safe to call this function concurrently with any other function that uses + // the same trust management object, or to re-enter this function for the same trust + // management object." + // + // SecTrustEvaluateAsync both does sync execution of Evaluate and calls back on the + // queue passed to it, according to at sources in + // http://www.opensource.apple.com/source/libsecurity_keychain/libsecurity_keychain-55050.9/lib/SecTrust.cpp + // It would require a global serial queue to ensure the evaluate happens only on a + // single thread at a time, so we'll stick with using SecTrustEvaluate on a background + // thread. + dispatch_queue_t evaluateBackgroundQueue = + dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dispatch_async(evaluateBackgroundQueue, ^{ + // It looks like the implementation of SecTrustEvaluate() on Mac grabs a global lock, + // so it may be redundant for us to also lock, but it's easy to synchronize here + // anyway. + BOOL shouldAllow; +#if GTM_SDK_REQUIRES_SECTRUSTEVALUATEWITHERROR + CFErrorRef errorRef = NULL; + @synchronized ([GTMSessionFetcher class]) { + GTMSessionMonitorSynchronized([GTMSessionFetcher class]); + + // SecTrustEvaluateWithError handles both the "proceed" and "unspecified" cases, + // so it is not necessary to check the trust result the evaluation returns true. + shouldAllow = SecTrustEvaluateWithError(serverTrust, &errorRef); + } + + if (errorRef) { + GTMSESSION_LOG_DEBUG(@"Error %d evaluating trust for %@", + (int)CFErrorGetCode(errorRef), request); + CFRelease(errorRef); + } +#else + SecTrustResultType trustEval = kSecTrustResultInvalid; + OSStatus trustError; + @synchronized([GTMSessionFetcher class]) { + GTMSessionMonitorSynchronized([GTMSessionFetcher class]); + + trustError = SecTrustEvaluate(serverTrust, &trustEval); + } + if (trustError != errSecSuccess) { + GTMSESSION_LOG_DEBUG(@"Error %d evaluating trust for %@", + (int)trustError, request); + shouldAllow = NO; + } else { + // Having a trust level "unspecified" by the user is the usual result, described at + // https://developer.apple.com/library/mac/qa/qa1360 + if (trustEval == kSecTrustResultUnspecified + || trustEval == kSecTrustResultProceed) { + shouldAllow = YES; + } else { + shouldAllow = NO; + GTMSESSION_LOG_DEBUG(@"Challenge SecTrustResultType %u for %@, properties: %@", + trustEval, request.URL.host, + CFBridgingRelease(SecTrustCopyProperties(serverTrust))); + } + } +#endif // GTM_SDK_REQUIRES_SECTRUSTEVALUATEWITHERROR + handler(serverTrust, shouldAllow); + + CFRelease(serverTrust); + }); +} + +- (void)invokeOnCallbackQueueUnlessStopped:(void (^)(void))block { + [self invokeOnCallbackQueueAfterUserStopped:NO + block:block]; +} + +- (void)invokeOnCallbackQueueAfterUserStopped:(BOOL)afterStopped + block:(void (^)(void))block { + GTMSessionCheckSynchronized(self); + + [self invokeOnCallbackUnsynchronizedQueueAfterUserStopped:afterStopped + block:block]; +} + +- (void)invokeOnCallbackUnsynchronizedQueueAfterUserStopped:(BOOL)afterStopped + block:(void (^)(void))block { + // testBlock simulation code may not be synchronizing when this is invoked. + [self invokeOnCallbackQueue:_callbackQueue + afterUserStopped:afterStopped + block:block]; +} + +- (void)invokeOnCallbackQueue:(dispatch_queue_t)callbackQueue + afterUserStopped:(BOOL)afterStopped + block:(void (^)(void))block { + if (callbackQueue) { + dispatch_group_async(_callbackGroup, callbackQueue, ^{ + if (!afterStopped) { + NSDate *serviceStoppedAllDate = [self->_service stoppedAllFetchersDate]; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + // Avoid a race between stopFetching and the callback. + if (self->_userStoppedFetching) { + return; + } + + // Also avoid calling back if the service has stopped all fetchers + // since this one was created. The fetcher may have stopped before + // stopAllFetchers was invoked, so _userStoppedFetching wasn't set, + // but the app still won't expect the callback to fire after + // the service's stopAllFetchers was invoked. + if (serviceStoppedAllDate + && [self->_initialBeginFetchDate compare:serviceStoppedAllDate] != NSOrderedDescending) { + // stopAllFetchers was called after this fetcher began. + return; + } + } // @synchronized(self) + } + block(); + }); + } +} + +- (void)invokeFetchCallbacksOnCallbackQueueWithData:(GTM_NULLABLE NSData *)data + error:(GTM_NULLABLE NSError *)error { + // Callbacks will be released in the method stopFetchReleasingCallbacks: + GTMSessionFetcherCompletionHandler handler; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + handler = _completionHandler; + + if (handler) { + [self invokeOnCallbackQueueUnlessStopped:^{ + handler(data, error); + + // Post a notification, primarily to allow code to collect responses for + // testing. + // + // The observing code is not likely on the fetcher's callback + // queue, so this posts explicitly to the main queue. + NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; + if (data) { + userInfo[kGTMSessionFetcherCompletionDataKey] = data; + } + if (error) { + userInfo[kGTMSessionFetcherCompletionErrorKey] = error; + } + [self postNotificationOnMainThreadWithName:kGTMSessionFetcherCompletionInvokedNotification + userInfo:userInfo + requireAsync:NO]; + }]; + } + } // @synchronized(self) +} + +- (void)postNotificationOnMainThreadWithName:(NSString *)noteName + userInfo:(GTM_NULLABLE NSDictionary *)userInfo + requireAsync:(BOOL)requireAsync { + dispatch_block_t postBlock = ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:noteName + object:self + userInfo:userInfo]; + }; + + if ([NSThread isMainThread] && !requireAsync) { + // Post synchronously for compatibility with older code using the fetcher. + + // Avoid calling out to other code from inside a sync block to avoid risk + // of a deadlock or of recursive sync. + GTMSessionCheckNotSynchronized(self); + + postBlock(); + } else { + dispatch_async(dispatch_get_main_queue(), postBlock); + } +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)uploadTask + needNewBodyStream:(void (^)(NSInputStream * GTM_NULLABLE_TYPE bodyStream))completionHandler { + [self setSessionTask:uploadTask]; + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ task:%@ needNewBodyStream:", + [self class], self, session, uploadTask); + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + GTMSessionFetcherBodyStreamProvider provider = _bodyStreamProvider; +#if !STRIP_GTM_FETCH_LOGGING + if ([self respondsToSelector:@selector(loggedStreamProviderForStreamProvider:)]) { + provider = [self performSelector:@selector(loggedStreamProviderForStreamProvider:) + withObject:provider]; + } +#endif + if (provider) { + [self invokeOnCallbackQueueUnlessStopped:^{ + provider(completionHandler); + }]; + } else { + GTMSESSION_ASSERT_DEBUG(NO, @"NSURLSession expects a stream provider"); + + completionHandler(nil); + } + } // @synchronized(self) +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + didSendBodyData:(int64_t)bytesSent + totalBytesSent:(int64_t)totalBytesSent +totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { + [self setSessionTask:task]; + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ task:%@ didSendBodyData:%lld" + @" totalBytesSent:%lld totalBytesExpectedToSend:%lld", + [self class], self, session, task, bytesSent, totalBytesSent, + totalBytesExpectedToSend); + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (!_sendProgressBlock) { + return; + } + // We won't hold on to send progress block; it's ok to not send it if the upload finishes. + [self invokeOnCallbackQueueUnlessStopped:^{ + GTMSessionFetcherSendProgressBlock progressBlock; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + progressBlock = self->_sendProgressBlock; + } + if (progressBlock) { + progressBlock(bytesSent, totalBytesSent, totalBytesExpectedToSend); + } + }]; + } // @synchronized(self) +} + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask + didReceiveData:(NSData *)data { + [self setSessionTask:dataTask]; + NSUInteger bufferLength = data.length; + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ dataTask:%@ didReceiveData:%p (%llu bytes)", + [self class], self, session, dataTask, data, + (unsigned long long)bufferLength); + if (bufferLength == 0) { + // Observed on completing an out-of-process upload. + return; + } + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + GTMSessionFetcherAccumulateDataBlock accumulateBlock = _accumulateDataBlock; + if (accumulateBlock) { + // Let the client accumulate the data. + _downloadedLength += bufferLength; + [self invokeOnCallbackQueueUnlessStopped:^{ + accumulateBlock(data); + }]; + } else if (!_userStoppedFetching) { + // Append to the mutable data buffer unless the fetch has been cancelled. + + // Resumed upload tasks may not yet have a data buffer. + if (_downloadedData == nil) { + // Using NSClassFromString for iOS 6 compatibility. + GTMSESSION_ASSERT_DEBUG( + ![dataTask isKindOfClass:NSClassFromString(@"NSURLSessionDownloadTask")], + @"Resumed download tasks should not receive data bytes"); + _downloadedData = [[NSMutableData alloc] init]; + } + + [_downloadedData appendData:data]; + _downloadedLength = (int64_t)_downloadedData.length; + + // We won't hold on to receivedProgressBlock here; it's ok to not send + // it if the transfer finishes. + if (_receivedProgressBlock) { + [self invokeOnCallbackQueueUnlessStopped:^{ + GTMSessionFetcherReceivedProgressBlock progressBlock; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + progressBlock = self->_receivedProgressBlock; + } + if (progressBlock) { + progressBlock((int64_t)bufferLength, self->_downloadedLength); + } + }]; + } + } + } // @synchronized(self) +} + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask + willCacheResponse:(NSCachedURLResponse *)proposedResponse + completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler { + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ dataTask:%@ willCacheResponse:%@ %@", + [self class], self, session, dataTask, + proposedResponse, proposedResponse.response); + GTMSessionFetcherWillCacheURLResponseBlock callback; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + callback = _willCacheURLResponseBlock; + + if (callback) { + [self invokeOnCallbackQueueAfterUserStopped:YES + block:^{ + callback(proposedResponse, completionHandler); + }]; + } + } // @synchronized(self) + if (!callback) { + completionHandler(proposedResponse); + } +} + + +- (void)URLSession:(NSURLSession *)session + downloadTask:(NSURLSessionDownloadTask *)downloadTask + didWriteData:(int64_t)bytesWritten + totalBytesWritten:(int64_t)totalBytesWritten +totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ downloadTask:%@ didWriteData:%lld" + @" bytesWritten:%lld totalBytesExpectedToWrite:%lld", + [self class], self, session, downloadTask, bytesWritten, + totalBytesWritten, totalBytesExpectedToWrite); + [self setSessionTask:downloadTask]; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if ((totalBytesExpectedToWrite != NSURLSessionTransferSizeUnknown) && + (totalBytesExpectedToWrite < totalBytesWritten)) { + // Have observed cases were bytesWritten == totalBytesExpectedToWrite, + // but totalBytesWritten > totalBytesExpectedToWrite, so setting to unkown in these cases. + totalBytesExpectedToWrite = NSURLSessionTransferSizeUnknown; + } + + GTMSessionFetcherDownloadProgressBlock progressBlock; + progressBlock = self->_downloadProgressBlock; + if (progressBlock) { + [self invokeOnCallbackQueueUnlessStopped:^{ + progressBlock(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); + }]; + } + } // @synchronized(self) +} + +- (void)URLSession:(NSURLSession *)session + downloadTask:(NSURLSessionDownloadTask *)downloadTask + didResumeAtOffset:(int64_t)fileOffset +expectedTotalBytes:(int64_t)expectedTotalBytes { + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ downloadTask:%@ didResumeAtOffset:%lld" + @" expectedTotalBytes:%lld", + [self class], self, session, downloadTask, fileOffset, + expectedTotalBytes); + [self setSessionTask:downloadTask]; +} + +- (void)URLSession:(NSURLSession *)session + downloadTask:(NSURLSessionDownloadTask *)downloadTask +didFinishDownloadingToURL:(NSURL *)downloadLocationURL { + // Download may have relaunched app, so update _sessionTask. + [self setSessionTask:downloadTask]; + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ downloadTask:%@ didFinishDownloadingToURL:%@", + [self class], self, session, downloadTask, downloadLocationURL); + NSNumber *fileSizeNum; + [downloadLocationURL getResourceValue:&fileSizeNum + forKey:NSURLFileSizeKey + error:NULL]; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSURL *destinationURL = _destinationFileURL; + + _downloadedLength = fileSizeNum.longLongValue; + + // Overwrite any previous file at the destination URL. + NSFileManager *fileMgr = [NSFileManager defaultManager]; + NSError *removeError; + if (![fileMgr removeItemAtURL:destinationURL error:&removeError] + && removeError.code != NSFileNoSuchFileError) { + GTMSESSION_LOG_DEBUG(@"Could not remove previous file at %@ due to %@", + downloadLocationURL.path, removeError); + } + + NSInteger statusCode = [self statusCodeUnsynchronized]; + if (statusCode < 200 || statusCode > 399) { + // In OS X 10.11, the response body is written to a file even on a server + // status error. For convenience of the fetcher client, we'll skip saving the + // downloaded body to the destination URL so that clients do not need to know + // to delete the file following fetch errors. + GTMSESSION_LOG_DEBUG(@"Abandoning download due to status %ld, file %@", + (long)statusCode, downloadLocationURL.path); + + // On error code, add the contents of the temporary file to _downloadTaskErrorData + // This way fetcher clients have access to error details possibly passed by the server. + if (_downloadedLength > 0 && _downloadedLength <= kMaximumDownloadErrorDataLength) { + _downloadTaskErrorData = [NSData dataWithContentsOfURL:downloadLocationURL]; + } else if (_downloadedLength > kMaximumDownloadErrorDataLength) { + GTMSESSION_LOG_DEBUG(@"Download error data for file %@ not passed to userInfo due to size " + @"%lld", downloadLocationURL.path, _downloadedLength); + } + } else { + NSError *moveError; + NSURL *destinationFolderURL = [destinationURL URLByDeletingLastPathComponent]; + BOOL didMoveDownload = NO; + if ([fileMgr createDirectoryAtURL:destinationFolderURL + withIntermediateDirectories:YES + attributes:nil + error:&moveError]) { + didMoveDownload = [fileMgr moveItemAtURL:downloadLocationURL + toURL:destinationURL + error:&moveError]; + } + if (!didMoveDownload) { + _downloadFinishedError = moveError; + } + GTM_LOG_BACKGROUND_SESSION(@"%@ %p Moved download from \"%@\" to \"%@\" %@", + [self class], self, + downloadLocationURL.path, destinationURL.path, + error ? error : @""); + } + } // @synchronized(self) +} + +/* Sent as the last message related to a specific task. Error may be + * nil, which implies that no error occurred and this task is complete. + */ +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task +didCompleteWithError:(NSError *)error { + [self setSessionTask:task]; + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ task:%@ didCompleteWithError:%@", + [self class], self, session, task, error); + + NSInteger status = self.statusCode; + BOOL forceAssumeRetry = NO; + BOOL succeeded = NO; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + +#if !GTM_DISABLE_FETCHER_TEST_BLOCK + // The task is never resumed when a testBlock is used. When the session is destroyed, + // we should ignore the callback, since the testBlock support code itself invokes + // shouldRetryNowForStatus: and finishWithError:shouldRetry: + if (_isUsingTestBlock) return; +#endif + + if (error == nil) { + error = _downloadFinishedError; + } + succeeded = (error == nil && status >= 0 && status < 300); + if (succeeded) { + // Succeeded. + _bodyLength = task.countOfBytesSent; + } + } // @synchronized(self) + + if (succeeded) { + [self finishWithError:nil shouldRetry:NO]; + return; + } + // For background redirects, no delegate method is called, so we cannot restore a stripped + // Authorization header, so if a 403 ("Forbidden") was generated due to a missing OAuth 2 header, + // set the current request's URL to the redirected URL, so we in effect restore the Authorization + // header. + if ((status == 403) && self.usingBackgroundSession) { + NSURL *redirectURL = self.response.URL; + NSURLRequest *request = self.request; + if (![request.URL isEqual:redirectURL]) { + NSString *authorizationHeader = [request.allHTTPHeaderFields objectForKey:@"Authorization"]; + if (authorizationHeader != nil) { + NSMutableURLRequest *mutableRequest = [request mutableCopy]; + mutableRequest.URL = redirectURL; + [self updateMutableRequest:mutableRequest]; + // Avoid assuming the session is still valid. + self.session = nil; + forceAssumeRetry = YES; + } + } + } + + // If invalidating the session was deferred in stopFetchReleasingCallbacks: then do it now. + NSURLSession *oldSession = self.sessionNeedingInvalidation; + if (oldSession) { + [self setSessionNeedingInvalidation:NULL]; + [oldSession finishTasksAndInvalidate]; + } + + // Failed. + [self shouldRetryNowForStatus:status + error:error + forceAssumeRetry:forceAssumeRetry + response:^(BOOL shouldRetry) { + [self finishWithError:error shouldRetry:shouldRetry]; + }]; +} + +#if TARGET_OS_IPHONE +- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session { + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSessionDidFinishEventsForBackgroundURLSession:%@", + [self class], self, session); + [self removePersistedBackgroundSessionFromDefaults]; + + GTMSessionFetcherSystemCompletionHandler handler; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + handler = self.systemCompletionHandler; + self.systemCompletionHandler = nil; + } // @synchronized(self) + if (handler) { + GTM_LOG_BACKGROUND_SESSION(@"%@ %p Calling system completionHandler", [self class], self); + handler(); + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSURLSession *oldSession = _session; + _session = nil; + if (_shouldInvalidateSession) { + [oldSession finishTasksAndInvalidate]; + } + } // @synchronized(self) + } +} +#endif + +- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(GTM_NULLABLE NSError *)error { + // This may happen repeatedly for retries. On authentication callbacks, the retry + // may begin before the prior session sends the didBecomeInvalid delegate message. + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ didBecomeInvalidWithError:%@", + [self class], self, session, error); + if (session == (NSURLSession *)self.session) { + GTM_LOG_SESSION_DELEGATE(@" Unexpected retained invalid session: %@", session); + self.session = nil; + } +} + +- (void)finishWithError:(GTM_NULLABLE NSError *)error shouldRetry:(BOOL)shouldRetry { + [self removePersistedBackgroundSessionFromDefaults]; + + BOOL shouldStopFetching = YES; + NSData *downloadedData = nil; +#if !STRIP_GTM_FETCH_LOGGING + BOOL shouldDeferLogging = NO; +#endif + BOOL shouldBeginRetryTimer = NO; + NSInteger status = [self statusCode]; + NSURL *destinationURL = self.destinationFileURL; + + BOOL fetchSucceeded = (error == nil && status >= 0 && status < 300); + +#if !STRIP_GTM_FETCH_LOGGING + if (!fetchSucceeded) { + if (!shouldDeferLogging && !self.hasLoggedError) { + [self logNowWithError:error]; + self.hasLoggedError = YES; + } + } +#endif // !STRIP_GTM_FETCH_LOGGING + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + +#if !STRIP_GTM_FETCH_LOGGING + shouldDeferLogging = _deferResponseBodyLogging; +#endif + if (fetchSucceeded) { + // Success + if ((_downloadedData.length > 0) && (destinationURL != nil)) { + // Overwrite any previous file at the destination URL. + NSFileManager *fileMgr = [NSFileManager defaultManager]; + [fileMgr removeItemAtURL:destinationURL + error:NULL]; + NSURL *destinationFolderURL = [destinationURL URLByDeletingLastPathComponent]; + BOOL didMoveDownload = NO; + if ([fileMgr createDirectoryAtURL:destinationFolderURL + withIntermediateDirectories:YES + attributes:nil + error:&error]) { + didMoveDownload = [_downloadedData writeToURL:destinationURL + options:NSDataWritingAtomic + error:&error]; + } + if (didMoveDownload) { + _downloadedData = nil; + } else { + _downloadFinishedError = error; + } + } + downloadedData = _downloadedData; + } else { + // Unsuccessful with error or status over 300. Retry or notify the delegate of failure + if (shouldRetry) { + // Retrying. + shouldBeginRetryTimer = YES; + shouldStopFetching = NO; + } else { + if (error == nil) { + // Create an error. + NSDictionary *userInfo = GTMErrorUserInfoForData( + _downloadedData.length > 0 ? _downloadedData : _downloadTaskErrorData, + [self responseHeadersUnsynchronized]); + + error = [NSError errorWithDomain:kGTMSessionFetcherStatusDomain + code:status + userInfo:userInfo]; + } else { + // If the error had resume data, and the client supplied a resume block, pass the + // data to the client. + void (^resumeBlock)(NSData *) = _resumeDataBlock; + _resumeDataBlock = nil; + if (resumeBlock) { + NSData *resumeData = [error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData]; + if (resumeData) { + [self invokeOnCallbackQueueAfterUserStopped:YES block:^{ + resumeBlock(resumeData); + }]; + } + } + } + if (_downloadedData.length > 0) { + downloadedData = _downloadedData; + } + // If the error occurred after retries, report the number and duration of the + // retries. This provides a clue to a developer looking at the error description + // that the fetcher did retry before failing with this error. + if (_retryCount > 0) { + NSMutableDictionary *userInfoWithRetries = + [NSMutableDictionary dictionaryWithDictionary:(NSDictionary *)error.userInfo]; + NSTimeInterval timeSinceInitialRequest = -[_initialRequestDate timeIntervalSinceNow]; + [userInfoWithRetries setObject:@(timeSinceInitialRequest) + forKey:kGTMSessionFetcherElapsedIntervalWithRetriesKey]; + [userInfoWithRetries setObject:@(_retryCount) + forKey:kGTMSessionFetcherNumberOfRetriesDoneKey]; + error = [NSError errorWithDomain:(NSString *)error.domain + code:error.code + userInfo:userInfoWithRetries]; + } + } + } + } // @synchronized(self) + + if (shouldBeginRetryTimer) { + [self beginRetryTimer]; + } + + // We want to send the stop notification before calling the delegate's + // callback selector, since the callback selector may release all of + // the fetcher properties that the client is using to track the fetches. + // + // We'll also stop now so that, to any observers watching the notifications, + // it doesn't look like our wait for a retry (which may be long, + // 30 seconds or more) is part of the network activity. + [self sendStopNotificationIfNeeded]; + + if (shouldStopFetching) { + [self invokeFetchCallbacksOnCallbackQueueWithData:downloadedData + error:error]; + // The upload subclass doesn't want to release callbacks until upload chunks have completed. + BOOL shouldRelease = [self shouldReleaseCallbacksUponCompletion]; + [self stopFetchReleasingCallbacks:shouldRelease]; + } + +#if !STRIP_GTM_FETCH_LOGGING + // _hasLoggedError is only set by this method + if (!shouldDeferLogging && !_hasLoggedError) { + [self logNowWithError:error]; + } +#endif +} + +- (BOOL)shouldReleaseCallbacksUponCompletion { + // A subclass can override this to keep callbacks around after the + // connection has finished successfully + return YES; +} + +- (void)logNowWithError:(GTM_NULLABLE NSError *)error { + GTMSessionCheckNotSynchronized(self); + + // If the logging category is available, then log the current request, + // response, data, and error + if ([self respondsToSelector:@selector(logFetchWithError:)]) { + [self performSelector:@selector(logFetchWithError:) withObject:error]; + } +} + +#pragma mark Retries + +- (BOOL)isRetryError:(NSError *)error { + struct RetryRecord { + __unsafe_unretained NSString *const domain; + NSInteger code; + }; + + struct RetryRecord retries[] = { + { kGTMSessionFetcherStatusDomain, 408 }, // request timeout + { kGTMSessionFetcherStatusDomain, 502 }, // failure gatewaying to another server + { kGTMSessionFetcherStatusDomain, 503 }, // service unavailable + { kGTMSessionFetcherStatusDomain, 504 }, // request timeout + { NSURLErrorDomain, NSURLErrorTimedOut }, + { NSURLErrorDomain, NSURLErrorNetworkConnectionLost }, + { nil, 0 } + }; + + // NSError's isEqual always returns false for equal but distinct instances + // of NSError, so we have to compare the domain and code values explicitly + NSString *domain = error.domain; + NSInteger code = error.code; + for (int idx = 0; retries[idx].domain != nil; idx++) { + if (code == retries[idx].code && [domain isEqual:retries[idx].domain]) { + return YES; + } + } + return NO; +} + +// shouldRetryNowForStatus:error: responds with YES if the user has enabled retries +// and the status or error is one that is suitable for retrying. "Suitable" +// means either the isRetryError:'s list contains the status or error, or the +// user's retry block is present and returns YES when called, or the +// authorizer may be able to fix. +- (void)shouldRetryNowForStatus:(NSInteger)status + error:(NSError *)error + forceAssumeRetry:(BOOL)forceAssumeRetry + response:(GTMSessionFetcherRetryResponse)response { + // Determine if a refreshed authorizer may avoid an authorization error + BOOL willRetry = NO; + + // We assume _authorizer is immutable after beginFetch, and _hasAttemptedAuthRefresh is modified + // only in this method, and this method is invoked on the serial delegate queue. + // + // We want to avoid calling the authorizer from inside a sync block. + BOOL isFirstAuthError = (_authorizer != nil + && !_hasAttemptedAuthRefresh + && status == GTMSessionFetcherStatusUnauthorized); // 401 + + BOOL hasPrimed = NO; + if (isFirstAuthError) { + if ([_authorizer respondsToSelector:@selector(primeForRefresh)]) { + hasPrimed = [_authorizer primeForRefresh]; + } + } + + BOOL shouldRetryForAuthRefresh = NO; + if (hasPrimed) { + shouldRetryForAuthRefresh = YES; + _hasAttemptedAuthRefresh = YES; + [self updateRequestValue:nil forHTTPHeaderField:@"Authorization"]; + } + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + BOOL shouldDoRetry = [self isRetryEnabledUnsynchronized]; + if (shouldDoRetry && ![self hasRetryAfterInterval]) { + + // Determine if we're doing exponential backoff retries + shouldDoRetry = [self nextRetryIntervalUnsynchronized] < _maxRetryInterval; + + if (shouldDoRetry) { + // If an explicit max retry interval was set, we expect repeated backoffs to take + // up to roughly twice that for repeated fast failures. If the initial attempt is + // already more than 3 times the max retry interval, then failures have taken a long time + // (such as from network timeouts) so don't retry again to avoid the app becoming + // unexpectedly unresponsive. + if (_maxRetryInterval > 0) { + NSTimeInterval maxAllowedIntervalBeforeRetry = _maxRetryInterval * 3; + NSTimeInterval timeSinceInitialRequest = -[_initialRequestDate timeIntervalSinceNow]; + if (timeSinceInitialRequest > maxAllowedIntervalBeforeRetry) { + shouldDoRetry = NO; + } + } + } + } + BOOL canRetry = shouldRetryForAuthRefresh || forceAssumeRetry || shouldDoRetry; + if (canRetry) { + NSDictionary *userInfo = + GTMErrorUserInfoForData(_downloadedData, [self responseHeadersUnsynchronized]); + NSError *statusError = [NSError errorWithDomain:kGTMSessionFetcherStatusDomain + code:status + userInfo:userInfo]; + if (error == nil) { + error = statusError; + } + willRetry = shouldRetryForAuthRefresh || + forceAssumeRetry || + [self isRetryError:error] || + ((error != statusError) && [self isRetryError:statusError]); + + // If the user has installed a retry callback, consult that. + GTMSessionFetcherRetryBlock retryBlock = _retryBlock; + if (retryBlock) { + [self invokeOnCallbackQueueUnlessStopped:^{ + retryBlock(willRetry, error, response); + }]; + return; + } + } + } // @synchronized(self) + response(willRetry); +} + +- (BOOL)hasRetryAfterInterval { + GTMSessionCheckSynchronized(self); + + NSDictionary *responseHeaders = [self responseHeadersUnsynchronized]; + NSString *retryAfterValue = [responseHeaders valueForKey:@"Retry-After"]; + return (retryAfterValue != nil); +} + +- (NSTimeInterval)retryAfterInterval { + GTMSessionCheckSynchronized(self); + + NSDictionary *responseHeaders = [self responseHeadersUnsynchronized]; + NSString *retryAfterValue = [responseHeaders valueForKey:@"Retry-After"]; + if (retryAfterValue == nil) { + return 0; + } + // Retry-After formatted as HTTP-date | delta-seconds + // Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html + NSDateFormatter *rfc1123DateFormatter = [[NSDateFormatter alloc] init]; + rfc1123DateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; + rfc1123DateFormatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"]; + rfc1123DateFormatter.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss z"; + NSDate *retryAfterDate = [rfc1123DateFormatter dateFromString:retryAfterValue]; + NSTimeInterval retryAfterInterval = (retryAfterDate != nil) ? + retryAfterDate.timeIntervalSinceNow : retryAfterValue.intValue; + retryAfterInterval = MAX(0, retryAfterInterval); + return retryAfterInterval; +} + +- (void)beginRetryTimer { + if (![NSThread isMainThread]) { + // Defer creating and starting the timer until we're on the main thread to ensure it has + // a run loop. + dispatch_group_async(_callbackGroup, dispatch_get_main_queue(), ^{ + [self beginRetryTimer]; + }); + return; + } + + [self destroyRetryTimer]; + +#if GTM_BACKGROUND_TASK_FETCHING + // Don't keep a background task active while awaiting retry, which can lead to the + // app exceeding the allotted time for keeping the background task open, causing the + // system to terminate the app. When the retry starts, a new background task will + // be created. + [self endBackgroundTask]; +#endif // GTM_BACKGROUND_TASK_FETCHING + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSTimeInterval nextInterval = [self nextRetryIntervalUnsynchronized]; + NSTimeInterval maxInterval = _maxRetryInterval; + NSTimeInterval newInterval = MIN(nextInterval, (maxInterval > 0 ? maxInterval : DBL_MAX)); + NSTimeInterval newIntervalTolerance = (newInterval / 10) > 1.0 ?: 1.0; + + _lastRetryInterval = newInterval; + + _retryTimer = [NSTimer timerWithTimeInterval:newInterval + target:self + selector:@selector(retryTimerFired:) + userInfo:nil + repeats:NO]; + _retryTimer.tolerance = newIntervalTolerance; + [[NSRunLoop mainRunLoop] addTimer:_retryTimer + forMode:NSDefaultRunLoopMode]; + } // @synchronized(self) + + [self postNotificationOnMainThreadWithName:kGTMSessionFetcherRetryDelayStartedNotification + userInfo:nil + requireAsync:NO]; +} + +- (void)retryTimerFired:(NSTimer *)timer { + [self destroyRetryTimer]; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _retryCount++; + } // @synchronized(self) + + NSOperationQueue *queue = self.sessionDelegateQueue; + [queue addOperationWithBlock:^{ + [self retryFetch]; + }]; +} + +- (void)destroyRetryTimer { + BOOL shouldNotify = NO; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_retryTimer) { + [_retryTimer invalidate]; + _retryTimer = nil; + shouldNotify = YES; + } + } + + if (shouldNotify) { + [self postNotificationOnMainThreadWithName:kGTMSessionFetcherRetryDelayStoppedNotification + userInfo:nil + requireAsync:NO]; + } +} + +- (NSUInteger)retryCount { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _retryCount; + } // @synchronized(self) +} + +- (NSTimeInterval)nextRetryInterval { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSTimeInterval interval = [self nextRetryIntervalUnsynchronized]; + return interval; + } // @synchronized(self) +} + +- (NSTimeInterval)nextRetryIntervalUnsynchronized { + GTMSessionCheckSynchronized(self); + + NSInteger statusCode = [self statusCodeUnsynchronized]; + if ((statusCode == 503) && [self hasRetryAfterInterval]) { + NSTimeInterval secs = [self retryAfterInterval]; + return secs; + } + // The next wait interval is the factor (2.0) times the last interval, + // but never less than the minimum interval. + NSTimeInterval secs = _lastRetryInterval * _retryFactor; + if (_maxRetryInterval > 0) { + secs = MIN(secs, _maxRetryInterval); + } + secs = MAX(secs, _minRetryInterval); + + return secs; +} + +- (NSTimer *)retryTimer { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _retryTimer; + } // @synchronized(self) +} + +- (BOOL)isRetryEnabled { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _isRetryEnabled; + } // @synchronized(self) +} + +- (BOOL)isRetryEnabledUnsynchronized { + GTMSessionCheckSynchronized(self); + + return _isRetryEnabled; +} + +- (void)setRetryEnabled:(BOOL)flag { + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (flag && !_isRetryEnabled) { + // We defer initializing these until the user calls setRetryEnabled + // to avoid using the random number generator if it's not needed. + // However, this means min and max intervals for this fetcher are reset + // as a side effect of calling setRetryEnabled. + // + // Make an initial retry interval random between 1.0 and 2.0 seconds + _minRetryInterval = InitialMinRetryInterval(); + _maxRetryInterval = kUnsetMaxRetryInterval; + _retryFactor = 2.0; + _lastRetryInterval = 0.0; + } + _isRetryEnabled = flag; + } // @synchronized(self) +}; + +- (NSTimeInterval)maxRetryInterval { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _maxRetryInterval; + } // @synchronized(self) +} + +- (void)setMaxRetryInterval:(NSTimeInterval)secs { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (secs > 0) { + _maxRetryInterval = secs; + } else { + _maxRetryInterval = kUnsetMaxRetryInterval; + } + } // @synchronized(self) +} + +- (double)minRetryInterval { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _minRetryInterval; + } // @synchronized(self) +} + +- (void)setMinRetryInterval:(NSTimeInterval)secs { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (secs > 0) { + _minRetryInterval = secs; + } else { + // Set min interval to a random value between 1.0 and 2.0 seconds + // so that if multiple clients start retrying at the same time, they'll + // repeat at different times and avoid overloading the server + _minRetryInterval = InitialMinRetryInterval(); + } + } // @synchronized(self) + +} + +#pragma mark iOS System Completion Handlers + +#if TARGET_OS_IPHONE +static NSMutableDictionary *gSystemCompletionHandlers = nil; + +- (GTM_NULLABLE GTMSessionFetcherSystemCompletionHandler)systemCompletionHandler { + return [[self class] systemCompletionHandlerForSessionIdentifier:_sessionIdentifier]; +} + +- (void)setSystemCompletionHandler:(GTM_NULLABLE GTMSessionFetcherSystemCompletionHandler)systemCompletionHandler { + [[self class] setSystemCompletionHandler:systemCompletionHandler + forSessionIdentifier:_sessionIdentifier]; +} + ++ (void)setSystemCompletionHandler:(GTM_NULLABLE GTMSessionFetcherSystemCompletionHandler)systemCompletionHandler + forSessionIdentifier:(NSString *)sessionIdentifier { + if (!sessionIdentifier) { + NSLog(@"%s with nil identifier", __PRETTY_FUNCTION__); + return; + } + + @synchronized([GTMSessionFetcher class]) { + if (gSystemCompletionHandlers == nil && systemCompletionHandler != nil) { + gSystemCompletionHandlers = [[NSMutableDictionary alloc] init]; + } + // Use setValue: to remove the object if completionHandler is nil. + [gSystemCompletionHandlers setValue:systemCompletionHandler + forKey:sessionIdentifier]; + } +} + ++ (GTM_NULLABLE GTMSessionFetcherSystemCompletionHandler)systemCompletionHandlerForSessionIdentifier:(NSString *)sessionIdentifier { + if (!sessionIdentifier) { + return nil; + } + @synchronized([GTMSessionFetcher class]) { + return [gSystemCompletionHandlers objectForKey:sessionIdentifier]; + } +} +#endif // TARGET_OS_IPHONE + +#pragma mark Getters and Setters + +@synthesize downloadResumeData = _downloadResumeData, + configuration = _configuration, + configurationBlock = _configurationBlock, + sessionTask = _sessionTask, + wasCreatedFromBackgroundSession = _wasCreatedFromBackgroundSession, + sessionUserInfo = _sessionUserInfo, + taskDescription = _taskDescription, + taskPriority = _taskPriority, + usingBackgroundSession = _usingBackgroundSession, + canShareSession = _canShareSession, + completionHandler = _completionHandler, + credential = _credential, + proxyCredential = _proxyCredential, + bodyData = _bodyData, + bodyLength = _bodyLength, + service = _service, + serviceHost = _serviceHost, + accumulateDataBlock = _accumulateDataBlock, + receivedProgressBlock = _receivedProgressBlock, + downloadProgressBlock = _downloadProgressBlock, + resumeDataBlock = _resumeDataBlock, + didReceiveResponseBlock = _didReceiveResponseBlock, + challengeBlock = _challengeBlock, + willRedirectBlock = _willRedirectBlock, + sendProgressBlock = _sendProgressBlock, + willCacheURLResponseBlock = _willCacheURLResponseBlock, + retryBlock = _retryBlock, + retryFactor = _retryFactor, + allowedInsecureSchemes = _allowedInsecureSchemes, + allowLocalhostRequest = _allowLocalhostRequest, + allowInvalidServerCertificates = _allowInvalidServerCertificates, + cookieStorage = _cookieStorage, + callbackQueue = _callbackQueue, + initialBeginFetchDate = _initialBeginFetchDate, + testBlock = _testBlock, + testBlockAccumulateDataChunkCount = _testBlockAccumulateDataChunkCount, + comment = _comment, + log = _log; + +#if !STRIP_GTM_FETCH_LOGGING +@synthesize redirectedFromURL = _redirectedFromURL, + logRequestBody = _logRequestBody, + logResponseBody = _logResponseBody, + hasLoggedError = _hasLoggedError; +#endif + +#if GTM_BACKGROUND_TASK_FETCHING +@synthesize backgroundTaskIdentifier = _backgroundTaskIdentifier, + skipBackgroundTask = _skipBackgroundTask; +#endif + +- (GTM_NULLABLE NSURLRequest *)request { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return [_request copy]; + } // @synchronized(self) +} + +- (void)setRequest:(GTM_NULLABLE NSURLRequest *)request { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (![self isFetchingUnsynchronized]) { + _request = [request mutableCopy]; + } else { + GTMSESSION_ASSERT_DEBUG(0, @"request may not be set after beginFetch has been invoked"); + } + } // @synchronized(self) +} + +- (GTM_NULLABLE NSMutableURLRequest *)mutableRequestForTesting { + // Allow tests only to modify the request, useful during retries. + return _request; +} + +// Internal method for updating the request property such as on redirects. +- (void)updateMutableRequest:(GTM_NULLABLE NSMutableURLRequest *)request { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _request = request; + } // @synchronized(self) +} + +// Set a header field value on the request. Header field value changes will not +// affect a fetch after the fetch has begun. +- (void)setRequestValue:(GTM_NULLABLE NSString *)value forHTTPHeaderField:(NSString *)field { + if (![self isFetching]) { + [self updateRequestValue:value forHTTPHeaderField:field]; + } else { + GTMSESSION_ASSERT_DEBUG(0, @"request may not be set after beginFetch has been invoked"); + } +} + +// Internal method for updating request headers. +- (void)updateRequestValue:(GTM_NULLABLE NSString *)value forHTTPHeaderField:(NSString *)field { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + [_request setValue:value forHTTPHeaderField:field]; + } // @synchronized(self) +} + +- (void)setResponse:(GTM_NULLABLE NSURLResponse *)response { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _response = response; + } // @synchronized(self) +} + +- (int64_t)bodyLength { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_bodyLength == NSURLSessionTransferSizeUnknown) { + if (_bodyData) { + _bodyLength = (int64_t)_bodyData.length; + } else if (_bodyFileURL) { + NSNumber *fileSizeNum = nil; + NSError *fileSizeError = nil; + if ([_bodyFileURL getResourceValue:&fileSizeNum + forKey:NSURLFileSizeKey + error:&fileSizeError]) { + _bodyLength = [fileSizeNum longLongValue]; + } + } + } + return _bodyLength; + } // @synchronized(self) +} + +- (BOOL)useUploadTask { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _useUploadTask; + } // @synchronized(self) +} + +- (void)setUseUploadTask:(BOOL)flag { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (flag != _useUploadTask) { + GTMSESSION_ASSERT_DEBUG(![self isFetchingUnsynchronized], + @"useUploadTask should not change after beginFetch has been invoked"); + _useUploadTask = flag; + } + } // @synchronized(self) +} + +- (GTM_NULLABLE NSURL *)bodyFileURL { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _bodyFileURL; + } // @synchronized(self) +} + +- (void)setBodyFileURL:(GTM_NULLABLE NSURL *)fileURL { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + // The comparison here is a trivial optimization and forgiveness for any client that + // repeatedly sets the property, so it just uses pointer comparison rather than isEqual:. + if (fileURL != _bodyFileURL) { + GTMSESSION_ASSERT_DEBUG(![self isFetchingUnsynchronized], + @"fileURL should not change after beginFetch has been invoked"); + + _bodyFileURL = fileURL; + } + } // @synchronized(self) +} + +- (GTM_NULLABLE GTMSessionFetcherBodyStreamProvider)bodyStreamProvider { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _bodyStreamProvider; + } // @synchronized(self) +} + +- (void)setBodyStreamProvider:(GTM_NULLABLE GTMSessionFetcherBodyStreamProvider)block { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + GTMSESSION_ASSERT_DEBUG(![self isFetchingUnsynchronized], + @"stream provider should not change after beginFetch has been invoked"); + + _bodyStreamProvider = [block copy]; + } // @synchronized(self) +} + +- (GTM_NULLABLE id)authorizer { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _authorizer; + } // @synchronized(self) +} + +- (void)setAuthorizer:(GTM_NULLABLE id)authorizer { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (authorizer != _authorizer) { + if ([self isFetchingUnsynchronized]) { + GTMSESSION_ASSERT_DEBUG(0, @"authorizer should not change after beginFetch has been invoked"); + } else { + _authorizer = authorizer; + } + } + } // @synchronized(self) +} + +- (GTM_NULLABLE NSData *)downloadedData { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _downloadedData; + } // @synchronized(self) +} + +- (void)setDownloadedData:(GTM_NULLABLE NSData *)data { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _downloadedData = [data mutableCopy]; + } // @synchronized(self) +} + +- (int64_t)downloadedLength { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _downloadedLength; + } // @synchronized(self) +} + +- (void)setDownloadedLength:(int64_t)length { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _downloadedLength = length; + } // @synchronized(self) +} + +- (dispatch_queue_t GTM_NONNULL_TYPE)callbackQueue { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _callbackQueue; + } // @synchronized(self) +} + +- (void)setCallbackQueue:(dispatch_queue_t GTM_NULLABLE_TYPE)queue { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _callbackQueue = queue ?: dispatch_get_main_queue(); + } // @synchronized(self) +} + +- (GTM_NULLABLE NSURLSession *)session { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _session; + } // @synchronized(self) +} + +- (NSInteger)servicePriority { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _servicePriority; + } // @synchronized(self) +} + +- (void)setServicePriority:(NSInteger)value { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (value != _servicePriority) { + GTMSESSION_ASSERT_DEBUG(![self isFetchingUnsynchronized], + @"servicePriority should not change after beginFetch has been invoked"); + + _servicePriority = value; + } + } // @synchronized(self) +} + + +- (void)setSession:(GTM_NULLABLE NSURLSession *)session { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _session = session; + } // @synchronized(self) +} + +- (BOOL)canShareSession { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _canShareSession; + } // @synchronized(self) +} + +- (void)setCanShareSession:(BOOL)flag { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _canShareSession = flag; + } // @synchronized(self) +} + +- (BOOL)useBackgroundSession { + // This reflects if the user requested a background session, not necessarily + // if one was created. That is tracked with _usingBackgroundSession. + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _userRequestedBackgroundSession; + } // @synchronized(self) +} + +- (void)setUseBackgroundSession:(BOOL)flag { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (flag != _userRequestedBackgroundSession) { + GTMSESSION_ASSERT_DEBUG(![self isFetchingUnsynchronized], + @"useBackgroundSession should not change after beginFetch has been invoked"); + + _userRequestedBackgroundSession = flag; + } + } // @synchronized(self) +} + +- (BOOL)isUsingBackgroundSession { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _usingBackgroundSession; + } // @synchronized(self) +} + +- (void)setUsingBackgroundSession:(BOOL)flag { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _usingBackgroundSession = flag; + } // @synchronized(self) +} + +- (GTM_NULLABLE NSURLSession *)sessionNeedingInvalidation { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _sessionNeedingInvalidation; + } // @synchronized(self) +} + +- (void)setSessionNeedingInvalidation:(GTM_NULLABLE NSURLSession *)session { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _sessionNeedingInvalidation = session; + } // @synchronized(self) +} + +- (NSOperationQueue * GTM_NONNULL_TYPE)sessionDelegateQueue { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _delegateQueue; + } // @synchronized(self) +} + +- (void)setSessionDelegateQueue:(NSOperationQueue * GTM_NULLABLE_TYPE)queue { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (queue != _delegateQueue) { + if ([self isFetchingUnsynchronized]) { + GTMSESSION_ASSERT_DEBUG(0, @"sessionDelegateQueue should not change after fetch begins"); + } else { + _delegateQueue = queue ?: [NSOperationQueue mainQueue]; + } + } + } // @synchronized(self) +} + +- (BOOL)userStoppedFetching { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _userStoppedFetching; + } // @synchronized(self) +} + +- (GTM_NULLABLE id)userData { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _userData; + } // @synchronized(self) +} + +- (void)setUserData:(GTM_NULLABLE id)theObj { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _userData = theObj; + } // @synchronized(self) +} + +- (GTM_NULLABLE NSURL *)destinationFileURL { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _destinationFileURL; + } // @synchronized(self) +} + +- (void)setDestinationFileURL:(GTM_NULLABLE NSURL *)destinationFileURL { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (((_destinationFileURL == nil) && (destinationFileURL == nil)) || + [_destinationFileURL isEqual:destinationFileURL]) { + return; + } + if (_sessionIdentifier) { + // This is something we don't expect to happen in production. + // However if it ever happen, leave a system log. + NSLog(@"%@: Destination File URL changed from (%@) to (%@) after session identifier has " + @"been created.", + [self class], _destinationFileURL, destinationFileURL); +#if DEBUG + // On both the simulator and devices, the path can change to the download file, but the name + // shouldn't change. Technically, this isn't supported in the fetcher, but the change of + // URL is expected to happen only across development runs through Xcode. + NSString *oldFilename = [_destinationFileURL lastPathComponent]; + NSString *newFilename = [destinationFileURL lastPathComponent]; + #pragma unused(oldFilename) + #pragma unused(newFilename) + GTMSESSION_ASSERT_DEBUG([oldFilename isEqualToString:newFilename], + @"Destination File URL cannot be changed after session identifier has been created"); +#endif + } + _destinationFileURL = destinationFileURL; + } // @synchronized(self) +} + +- (void)setProperties:(GTM_NULLABLE NSDictionary *)dict { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _properties = [dict mutableCopy]; + } // @synchronized(self) +} + +- (GTM_NULLABLE NSDictionary *)properties { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _properties; + } // @synchronized(self) +} + +- (void)setProperty:(GTM_NULLABLE id)obj forKey:(NSString *)key { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_properties == nil && obj != nil) { + _properties = [[NSMutableDictionary alloc] init]; + } + [_properties setValue:obj forKey:key]; + } // @synchronized(self) +} + +- (GTM_NULLABLE id)propertyForKey:(NSString *)key { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return [_properties objectForKey:key]; + } // @synchronized(self) +} + +- (void)addPropertiesFromDictionary:(NSDictionary *)dict { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_properties == nil && dict != nil) { + [self setProperties:[dict mutableCopy]]; + } else { + [_properties addEntriesFromDictionary:dict]; + } + } // @synchronized(self) +} + +- (void)setCommentWithFormat:(id)format, ... { +#if !STRIP_GTM_FETCH_LOGGING + NSString *result = format; + if (format) { + va_list argList; + va_start(argList, format); + + result = [[NSString alloc] initWithFormat:format + arguments:argList]; + va_end(argList); + } + [self setComment:result]; +#endif +} + +#if !STRIP_GTM_FETCH_LOGGING +- (NSData *)loggedStreamData { + return _loggedStreamData; +} + +- (void)appendLoggedStreamData:dataToAdd { + if (!_loggedStreamData) { + _loggedStreamData = [NSMutableData data]; + } + [_loggedStreamData appendData:dataToAdd]; +} + +- (void)clearLoggedStreamData { + _loggedStreamData = nil; +} + +- (void)setDeferResponseBodyLogging:(BOOL)deferResponseBodyLogging { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (deferResponseBodyLogging != _deferResponseBodyLogging) { + _deferResponseBodyLogging = deferResponseBodyLogging; + if (!deferResponseBodyLogging && !self.hasLoggedError) { + [_delegateQueue addOperationWithBlock:^{ + [self logNowWithError:nil]; + }]; + } + } + } // @synchronized(self) +} + +- (BOOL)deferResponseBodyLogging { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _deferResponseBodyLogging; + } // @synchronized(self) +} + +#else ++ (void)setLoggingEnabled:(BOOL)flag { +} + ++ (BOOL)isLoggingEnabled { + return NO; +} +#endif // STRIP_GTM_FETCH_LOGGING + +@end + +@implementation GTMSessionFetcher (BackwardsCompatibilityOnly) + +- (void)setCookieStorageMethod:(NSInteger)method { + // For backwards compatibility with the old fetcher, we'll support the old constants. + // + // Clients using the GTMSessionFetcher class should set the cookie storage explicitly + // themselves. + NSHTTPCookieStorage *storage = nil; + switch(method) { + case 0: // kGTMHTTPFetcherCookieStorageMethodStatic + // nil storage will use [[self class] staticCookieStorage] when the fetch begins. + break; + case 1: // kGTMHTTPFetcherCookieStorageMethodFetchHistory + // Do nothing; use whatever was set by the fetcher service. + return; + case 2: // kGTMHTTPFetcherCookieStorageMethodSystemDefault + storage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; + break; + case 3: // kGTMHTTPFetcherCookieStorageMethodNone + // Create temporary storage for this fetcher only. + storage = [[GTMSessionCookieStorage alloc] init]; + break; + default: + GTMSESSION_ASSERT_DEBUG(0, @"Invalid cookie storage method: %d", (int)method); + } + self.cookieStorage = storage; +} + +@end + +@implementation GTMSessionCookieStorage { + NSMutableArray *_cookies; + NSHTTPCookieAcceptPolicy _policy; +} + +- (id)init { + self = [super init]; + if (self != nil) { + _cookies = [[NSMutableArray alloc] init]; + } + return self; +} + +- (GTM_NULLABLE NSArray *)cookies { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return [_cookies copy]; + } // @synchronized(self) +} + +- (void)setCookie:(NSHTTPCookie *)cookie { + if (!cookie) return; + if (_policy == NSHTTPCookieAcceptPolicyNever) return; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + [self internalSetCookie:cookie]; + } // @synchronized(self) +} + +// Note: this should only be called from inside a @synchronized(self) block. +- (void)internalSetCookie:(NSHTTPCookie *)newCookie { + GTMSessionCheckSynchronized(self); + + if (_policy == NSHTTPCookieAcceptPolicyNever) return; + + BOOL isValidCookie = (newCookie.name.length > 0 + && newCookie.domain.length > 0 + && newCookie.path.length > 0); + GTMSESSION_ASSERT_DEBUG(isValidCookie, @"invalid cookie: %@", newCookie); + + if (isValidCookie) { + // Remove the cookie if it's currently in the array. + NSHTTPCookie *oldCookie = [self cookieMatchingCookie:newCookie]; + if (oldCookie) { + [_cookies removeObjectIdenticalTo:oldCookie]; + } + + if (![[self class] hasCookieExpired:newCookie]) { + [_cookies addObject:newCookie]; + } + } +} + +// Add all cookies in the new cookie array to the storage, +// replacing stored cookies as appropriate. +// +// Side effect: removes expired cookies from the storage array. +- (void)setCookies:(GTM_NULLABLE NSArray *)newCookies { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + [self removeExpiredCookies]; + + for (NSHTTPCookie *newCookie in newCookies) { + [self internalSetCookie:newCookie]; + } + } // @synchronized(self) +} + +- (void)setCookies:(NSArray *)cookies forURL:(GTM_NULLABLE NSURL *)URL mainDocumentURL:(GTM_NULLABLE NSURL *)mainDocumentURL { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_policy == NSHTTPCookieAcceptPolicyNever) { + return; + } + + if (_policy == NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain) { + NSString *mainHost = mainDocumentURL.host; + NSString *associatedHost = URL.host; + if (!mainHost || ![associatedHost hasSuffix:mainHost]) { + return; + } + } + } // @synchronized(self) + [self setCookies:cookies]; +} + +- (void)deleteCookie:(NSHTTPCookie *)cookie { + if (!cookie) return; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSHTTPCookie *foundCookie = [self cookieMatchingCookie:cookie]; + if (foundCookie) { + [_cookies removeObjectIdenticalTo:foundCookie]; + } + } // @synchronized(self) +} + +// Retrieve all cookies appropriate for the given URL, considering +// domain, path, cookie name, expiration, security setting. +// Side effect: removed expired cookies from the storage array. +- (GTM_NULLABLE NSArray *)cookiesForURL:(NSURL *)theURL { + NSMutableArray *foundCookies = nil; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + [self removeExpiredCookies]; + + // We'll prepend "." to the desired domain, since we want the + // actual domain "nytimes.com" to still match the cookie domain + // ".nytimes.com" when we check it below with hasSuffix. + NSString *host = theURL.host.lowercaseString; + NSString *path = theURL.path; + NSString *scheme = [theURL scheme]; + + NSString *requestingDomain = nil; + BOOL isLocalhostRetrieval = NO; + + if (IsLocalhost(host)) { + isLocalhostRetrieval = YES; + } else { + if (host.length > 0) { + requestingDomain = [@"." stringByAppendingString:host]; + } + } + + for (NSHTTPCookie *storedCookie in _cookies) { + NSString *cookieDomain = storedCookie.domain.lowercaseString; + NSString *cookiePath = storedCookie.path; + BOOL cookieIsSecure = [storedCookie isSecure]; + + BOOL isDomainOK; + + if (isLocalhostRetrieval) { + // Prior to 10.5.6, the domain stored into NSHTTPCookies for localhost + // is "localhost.local" + isDomainOK = (IsLocalhost(cookieDomain) + || [cookieDomain isEqual:@"localhost.local"]); + } else { + // Ensure we're matching exact domain names. We prepended a dot to the + // requesting domain, so we can also prepend one here if needed before + // checking if the request contains the cookie domain. + if (![cookieDomain hasPrefix:@"."]) { + cookieDomain = [@"." stringByAppendingString:cookieDomain]; + } + isDomainOK = [requestingDomain hasSuffix:cookieDomain]; + } + + BOOL isPathOK = [cookiePath isEqual:@"/"] || [path hasPrefix:cookiePath]; + BOOL isSecureOK = (!cookieIsSecure + || [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame); + + if (isDomainOK && isPathOK && isSecureOK) { + if (foundCookies == nil) { + foundCookies = [NSMutableArray array]; + } + [foundCookies addObject:storedCookie]; + } + } + } // @synchronized(self) + return foundCookies; +} + +// Override methods from the NSHTTPCookieStorage (NSURLSessionTaskAdditions) category. +- (void)storeCookies:(NSArray *)cookies forTask:(NSURLSessionTask *)task { + NSURLRequest *currentRequest = task.currentRequest; + [self setCookies:cookies forURL:currentRequest.URL mainDocumentURL:nil]; +} + +- (void)getCookiesForTask:(NSURLSessionTask *)task + completionHandler:(void (^)(GTM_NSArrayOf(NSHTTPCookie *) *))completionHandler { + if (completionHandler) { + NSURLRequest *currentRequest = task.currentRequest; + NSURL *currentRequestURL = currentRequest.URL; + NSArray *cookies = [self cookiesForURL:currentRequestURL]; + completionHandler(cookies); + } +} + +// Return a cookie from the array with the same name, domain, and path as the +// given cookie, or else return nil if none found. +// +// Both the cookie being tested and all cookies in the storage array should +// be valid (non-nil name, domains, paths). +// +// Note: this should only be called from inside a @synchronized(self) block +- (GTM_NULLABLE NSHTTPCookie *)cookieMatchingCookie:(NSHTTPCookie *)cookie { + GTMSessionCheckSynchronized(self); + + NSString *name = cookie.name; + NSString *domain = cookie.domain; + NSString *path = cookie.path; + + GTMSESSION_ASSERT_DEBUG(name && domain && path, + @"Invalid stored cookie (name:%@ domain:%@ path:%@)", name, domain, path); + + for (NSHTTPCookie *storedCookie in _cookies) { + if ([storedCookie.name isEqual:name] + && [storedCookie.domain isEqual:domain] + && [storedCookie.path isEqual:path]) { + return storedCookie; + } + } + return nil; +} + +// Internal routine to remove any expired cookies from the array, excluding +// cookies with nil expirations. +// +// Note: this should only be called from inside a @synchronized(self) block +- (void)removeExpiredCookies { + GTMSessionCheckSynchronized(self); + + // Count backwards since we're deleting items from the array + for (NSInteger idx = (NSInteger)_cookies.count - 1; idx >= 0; idx--) { + NSHTTPCookie *storedCookie = [_cookies objectAtIndex:(NSUInteger)idx]; + if ([[self class] hasCookieExpired:storedCookie]) { + [_cookies removeObjectAtIndex:(NSUInteger)idx]; + } + } +} + ++ (BOOL)hasCookieExpired:(NSHTTPCookie *)cookie { + NSDate *expiresDate = [cookie expiresDate]; + if (expiresDate == nil) { + // Cookies seem to have a Expires property even when the expiresDate method returns nil. + id expiresVal = [[cookie properties] objectForKey:NSHTTPCookieExpires]; + if ([expiresVal isKindOfClass:[NSDate class]]) { + expiresDate = expiresVal; + } + } + BOOL hasExpired = (expiresDate != nil && [expiresDate timeIntervalSinceNow] < 0); + return hasExpired; +} + +- (void)removeAllCookies { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + [_cookies removeAllObjects]; + } // @synchronized(self) +} + +- (NSHTTPCookieAcceptPolicy)cookieAcceptPolicy { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _policy; + } // @synchronized(self) +} + +- (void)setCookieAcceptPolicy:(NSHTTPCookieAcceptPolicy)cookieAcceptPolicy { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _policy = cookieAcceptPolicy; + } // @synchronized(self) +} + +@end + +void GTMSessionFetcherAssertValidSelector(id GTM_NULLABLE_TYPE obj, SEL GTM_NULLABLE_TYPE sel, ...) { + // Verify that the object's selector is implemented with the proper + // number and type of arguments +#if DEBUG + va_list argList; + va_start(argList, sel); + + if (obj && sel) { + // Check that the selector is implemented + if (![obj respondsToSelector:sel]) { + NSLog(@"\"%@\" selector \"%@\" is unimplemented or misnamed", + NSStringFromClass([(id)obj class]), + NSStringFromSelector((SEL)sel)); + NSCAssert(0, @"callback selector unimplemented or misnamed"); + } else { + const char *expectedArgType; + unsigned int argCount = 2; // skip self and _cmd + NSMethodSignature *sig = [obj methodSignatureForSelector:sel]; + + // Check that each expected argument is present and of the correct type + while ((expectedArgType = va_arg(argList, const char*)) != 0) { + + if ([sig numberOfArguments] > argCount) { + const char *foundArgType = [sig getArgumentTypeAtIndex:argCount]; + + if (0 != strncmp(foundArgType, expectedArgType, strlen(expectedArgType))) { + NSLog(@"\"%@\" selector \"%@\" argument %d should be type %s", + NSStringFromClass([(id)obj class]), + NSStringFromSelector((SEL)sel), (argCount - 2), expectedArgType); + NSCAssert(0, @"callback selector argument type mistake"); + } + } + argCount++; + } + + // Check that the proper number of arguments are present in the selector + if (argCount != [sig numberOfArguments]) { + NSLog(@"\"%@\" selector \"%@\" should have %d arguments", + NSStringFromClass([(id)obj class]), + NSStringFromSelector((SEL)sel), (argCount - 2)); + NSCAssert(0, @"callback selector arguments incorrect"); + } + } + } + + va_end(argList); +#endif +} + +NSString *GTMFetcherCleanedUserAgentString(NSString *str) { + // Reference http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html + // and http://www-archive.mozilla.org/build/user-agent-strings.html + + if (str == nil) return @""; + + NSMutableString *result = [NSMutableString stringWithString:str]; + + // Replace spaces and commas with underscores + [result replaceOccurrencesOfString:@" " + withString:@"_" + options:0 + range:NSMakeRange(0, result.length)]; + [result replaceOccurrencesOfString:@"," + withString:@"_" + options:0 + range:NSMakeRange(0, result.length)]; + + // Delete http token separators and remaining whitespace + static NSCharacterSet *charsToDelete = nil; + if (charsToDelete == nil) { + // Make a set of unwanted characters + NSString *const kSeparators = @"()<>@;:\\\"/[]?={}"; + + NSMutableCharacterSet *mutableChars = + [[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy]; + [mutableChars addCharactersInString:kSeparators]; + charsToDelete = [mutableChars copy]; // hang on to an immutable copy + } + + while (1) { + NSRange separatorRange = [result rangeOfCharacterFromSet:charsToDelete]; + if (separatorRange.location == NSNotFound) break; + + [result deleteCharactersInRange:separatorRange]; + }; + + return result; +} + +NSString *GTMFetcherSystemVersionString(void) { + static NSString *sSavedSystemString; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + // The Xcode 8 SDKs finally cleaned up this mess by providing TARGET_OS_OSX + // and TARGET_OS_IOS, but to build with older SDKs, those don't exist and + // instead one has to rely on TARGET_OS_MAC (which is true for iOS, watchOS, + // and tvOS) and TARGET_OS_IPHONE (which is true for iOS, watchOS, tvOS). So + // one has to order these carefully so you pick off the specific things + // first. + // If the code can ever assume Xcode 8 or higher (even when building for + // older OSes), then + // TARGET_OS_MAC -> TARGET_OS_OSX + // TARGET_OS_IPHONE -> TARGET_OS_IOS + // TARGET_IPHONE_SIMULATOR -> TARGET_OS_SIMULATOR +#if TARGET_OS_WATCH + // watchOS - WKInterfaceDevice + + WKInterfaceDevice *currentDevice = [WKInterfaceDevice currentDevice]; + + NSString *rawModel = [currentDevice model]; + NSString *model = GTMFetcherCleanedUserAgentString(rawModel); + + NSString *systemVersion = [currentDevice systemVersion]; + +#if TARGET_OS_SIMULATOR + NSString *hardwareModel = @"sim"; +#else + NSString *hardwareModel; + struct utsname unameRecord; + if (uname(&unameRecord) == 0) { + NSString *machineName = @(unameRecord.machine); + hardwareModel = GTMFetcherCleanedUserAgentString(machineName); + } + if (hardwareModel.length == 0) { + hardwareModel = @"unk"; + } +#endif + + sSavedSystemString = [[NSString alloc] initWithFormat:@"%@/%@ hw/%@", + model, systemVersion, hardwareModel]; + // Example: Apple_Watch/3.0 hw/Watch1_2 +#elif TARGET_OS_TV || TARGET_OS_IPHONE + // iOS and tvOS have UIDevice, use that. + UIDevice *currentDevice = [UIDevice currentDevice]; + + NSString *rawModel = [currentDevice model]; + NSString *model = GTMFetcherCleanedUserAgentString(rawModel); + + NSString *systemVersion = [currentDevice systemVersion]; + +#if TARGET_IPHONE_SIMULATOR || TARGET_OS_SIMULATOR + NSString *hardwareModel = @"sim"; +#else + NSString *hardwareModel; + struct utsname unameRecord; + if (uname(&unameRecord) == 0) { + NSString *machineName = @(unameRecord.machine); + hardwareModel = GTMFetcherCleanedUserAgentString(machineName); + } + if (hardwareModel.length == 0) { + hardwareModel = @"unk"; + } +#endif + + sSavedSystemString = [[NSString alloc] initWithFormat:@"%@/%@ hw/%@", + model, systemVersion, hardwareModel]; + // Example: iPod_Touch/2.2 hw/iPod1_1 + // Example: Apple_TV/9.2 hw/AppleTV5,3 +#elif TARGET_OS_MAC + // Mac build + NSProcessInfo *procInfo = [NSProcessInfo processInfo]; +#if !defined(MAC_OS_X_VERSION_10_10) + BOOL hasOperatingSystemVersion = NO; +#elif MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_10 + BOOL hasOperatingSystemVersion = + [procInfo respondsToSelector:@selector(operatingSystemVersion)]; +#else + BOOL hasOperatingSystemVersion = YES; +#endif + NSString *versString; + if (hasOperatingSystemVersion) { +#if defined(MAC_OS_X_VERSION_10_10) + // A reference to NSOperatingSystemVersion requires the 10.10 SDK. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability" +// Disable unguarded availability warning as we can't use the @availability macro until we require +// all clients to build with Xcode 9 or above. + NSOperatingSystemVersion version = procInfo.operatingSystemVersion; +#pragma clang diagnostic pop + versString = [NSString stringWithFormat:@"%ld.%ld.%ld", + (long)version.majorVersion, (long)version.minorVersion, + (long)version.patchVersion]; +#else +#pragma unused(procInfo) +#endif + } else { + // With Gestalt inexplicably deprecated in 10.8, we're reduced to reading + // the system plist file. + NSString *const kPath = @"/System/Library/CoreServices/SystemVersion.plist"; + NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:kPath]; + versString = [plist objectForKey:@"ProductVersion"]; + if (versString.length == 0) { + versString = @"10.?.?"; + } + } + + sSavedSystemString = [[NSString alloc] initWithFormat:@"MacOSX/%@", versString]; +#elif defined(_SYS_UTSNAME_H) + // Foundation-only build + struct utsname unameRecord; + uname(&unameRecord); + + sSavedSystemString = [NSString stringWithFormat:@"%s/%s", + unameRecord.sysname, unameRecord.release]; // "Darwin/8.11.1" +#else +#error No branch taken for a default user agent +#endif + }); + return sSavedSystemString; +} + +NSString *GTMFetcherStandardUserAgentString(NSBundle * GTM_NULLABLE_TYPE bundle) { + NSString *result = [NSString stringWithFormat:@"%@ %@", + GTMFetcherApplicationIdentifier(bundle), + GTMFetcherSystemVersionString()]; + return result; +} + +NSString *GTMFetcherApplicationIdentifier(NSBundle * GTM_NULLABLE_TYPE bundle) { + @synchronized([GTMSessionFetcher class]) { + static NSMutableDictionary *sAppIDMap = nil; + + // If there's a bundle ID, use that; otherwise, use the process name + if (bundle == nil) { + bundle = [NSBundle mainBundle]; + } + NSString *bundleID = [bundle bundleIdentifier]; + if (bundleID == nil) { + bundleID = @""; + } + + NSString *identifier = [sAppIDMap objectForKey:bundleID]; + if (identifier) return identifier; + + // Apps may add a string to the info.plist to uniquely identify different builds. + identifier = [bundle objectForInfoDictionaryKey:@"GTMUserAgentID"]; + if (identifier.length == 0) { + if (bundleID.length > 0) { + identifier = bundleID; + } else { + // Fall back on the procname, prefixed by "proc" to flag that it's + // autogenerated and perhaps unreliable + NSString *procName = [[NSProcessInfo processInfo] processName]; + identifier = [NSString stringWithFormat:@"proc_%@", procName]; + } + } + + // Clean up whitespace and special characters + identifier = GTMFetcherCleanedUserAgentString(identifier); + + // If there's a version number, append that + NSString *version = [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; + if (version.length == 0) { + version = [bundle objectForInfoDictionaryKey:@"CFBundleVersion"]; + } + + // Clean up whitespace and special characters + version = GTMFetcherCleanedUserAgentString(version); + + // Glue the two together (cleanup done above or else cleanup would strip the + // slash) + if (version.length > 0) { + identifier = [identifier stringByAppendingFormat:@"/%@", version]; + } + + if (sAppIDMap == nil) { + sAppIDMap = [[NSMutableDictionary alloc] init]; + } + [sAppIDMap setObject:identifier forKey:bundleID]; + return identifier; + } +} + +#if DEBUG && (!defined(NS_BLOCK_ASSERTIONS) || GTMSESSION_ASSERT_AS_LOG) +@implementation GTMSessionSyncMonitorInternal { + NSValue *_objectKey; // The synchronize target object. + const char *_functionName; // The function containing the monitored sync block. +} + +- (instancetype)initWithSynchronizationObject:(id)object + allowRecursive:(BOOL)allowRecursive + functionName:(const char *)functionName { + self = [super init]; + if (self) { + Class threadKey = [GTMSessionSyncMonitorInternal class]; + _objectKey = [NSValue valueWithNonretainedObject:object]; + _functionName = functionName; + + NSMutableDictionary *threadDict = [NSThread currentThread].threadDictionary; + NSMutableDictionary *counters = threadDict[threadKey]; + if (counters == nil) { + counters = [NSMutableDictionary dictionary]; + threadDict[(id)threadKey] = counters; + } + NSCountedSet *functionNamesCounter = counters[_objectKey]; + NSUInteger numberOfSyncingFunctions = functionNamesCounter.count; + + if (!allowRecursive) { + BOOL isTopLevelSyncScope = (numberOfSyncingFunctions == 0); + NSArray *stack = [NSThread callStackSymbols]; + GTMSESSION_ASSERT_DEBUG(isTopLevelSyncScope, + @"*** Recursive sync on %@ at %s; previous sync at %@\n%@", + [object class], functionName, functionNamesCounter.allObjects, + [stack subarrayWithRange:NSMakeRange(1, stack.count - 1)]); + } + + if (!functionNamesCounter) { + functionNamesCounter = [NSCountedSet set]; + counters[_objectKey] = functionNamesCounter; + } + [functionNamesCounter addObject:(id _Nonnull)@(functionName)]; + } + return self; +} + +- (void)dealloc { + Class threadKey = [GTMSessionSyncMonitorInternal class]; + + NSMutableDictionary *threadDict = [NSThread currentThread].threadDictionary; + NSMutableDictionary *counters = threadDict[threadKey]; + NSCountedSet *functionNamesCounter = counters[_objectKey]; + NSString *functionNameStr = @(_functionName); + NSUInteger numberOfSyncsByThisFunction = [functionNamesCounter countForObject:functionNameStr]; + NSArray *stack = [NSThread callStackSymbols]; + GTMSESSION_ASSERT_DEBUG(numberOfSyncsByThisFunction > 0, @"Sync not found on %@ at %s\n%@", + [_objectKey.nonretainedObjectValue class], _functionName, + [stack subarrayWithRange:NSMakeRange(1, stack.count - 1)]); + [functionNamesCounter removeObject:functionNameStr]; + if (functionNamesCounter.count == 0) { + [counters removeObjectForKey:_objectKey]; + } +} + ++ (NSArray *)functionsHoldingSynchronizationOnObject:(id)object { + Class threadKey = [GTMSessionSyncMonitorInternal class]; + NSValue *localObjectKey = [NSValue valueWithNonretainedObject:object]; + + NSMutableDictionary *threadDict = [NSThread currentThread].threadDictionary; + NSMutableDictionary *counters = threadDict[threadKey]; + NSCountedSet *functionNamesCounter = counters[localObjectKey]; + return functionNamesCounter.count > 0 ? functionNamesCounter.allObjects : nil; +} +@end +#endif // DEBUG && (!defined(NS_BLOCK_ASSERTIONS) || GTMSESSION_ASSERT_AS_LOG) +GTM_ASSUME_NONNULL_END diff --git a/!main project/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.h b/!main project/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.h new file mode 100644 index 0000000..5ccea78 --- /dev/null +++ b/!main project/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.h @@ -0,0 +1,112 @@ +/* Copyright 2014 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "GTMSessionFetcher.h" + +// GTM HTTP Logging +// +// All traffic using GTMSessionFetcher can be easily logged. Call +// +// [GTMSessionFetcher setLoggingEnabled:YES]; +// +// to begin generating log files. +// +// Unless explicitly set by the application using +setLoggingDirectory:, +// logs are put into a default directory, located at: +// * macOS: ~/Desktop/GTMHTTPDebugLogs +// * iOS simulator: ~/GTMHTTPDebugLogs (in application sandbox) +// * iOS device: ~/Documents/GTMHTTPDebugLogs (in application sandbox) +// +// Tip: use the Finder's "Sort By Date" to find the most recent logs. +// +// Each run of an application gets a separate set of log files. An html +// file is generated to simplify browsing the run's http transactions. +// The html file includes javascript links for inline viewing of uploaded +// and downloaded data. +// +// A symlink is created in the logs folder to simplify finding the html file +// for the latest run of the application; the symlink is called +// +// AppName_http_log_newest.html +// +// For better viewing of XML logs, use Camino or Firefox rather than Safari. +// +// Each fetcher may be given a comment to be inserted as a label in the logs, +// such as +// [fetcher setCommentWithFormat:@"retrieve item %@", itemName]; +// +// Projects may define STRIP_GTM_FETCH_LOGGING to remove logging code. + +#if !STRIP_GTM_FETCH_LOGGING + +@interface GTMSessionFetcher (GTMSessionFetcherLogging) + +// Note: on macOS the default logs directory is ~/Desktop/GTMHTTPDebugLogs; on +// iOS simulators it will be the ~/GTMHTTPDebugLogs (in the app sandbox); on +// iOS devices it will be in ~/Documents/GTMHTTPDebugLogs (in the app sandbox). +// These directories will be created as needed, and are excluded from backups +// to iCloud and iTunes. +// +// If a custom directory is set, the directory should already exist. It is +// the application's responsibility to exclude any custom directory from +// backups, if desired. ++ (void)setLoggingDirectory:(NSString *)path; ++ (NSString *)loggingDirectory; + +// client apps can turn logging on and off ++ (void)setLoggingEnabled:(BOOL)isLoggingEnabled; ++ (BOOL)isLoggingEnabled; + +// client apps can turn off logging to a file if they want to only check +// the fetcher's log property ++ (void)setLoggingToFileEnabled:(BOOL)isLoggingToFileEnabled; ++ (BOOL)isLoggingToFileEnabled; + +// client apps can optionally specify process name and date string used in +// log file names ++ (void)setLoggingProcessName:(NSString *)processName; ++ (NSString *)loggingProcessName; + ++ (void)setLoggingDateStamp:(NSString *)dateStamp; ++ (NSString *)loggingDateStamp; + +// client apps can specify the directory for the log for this specific run, +// typically to match the directory used by another fetcher class, like: +// +// [GTMSessionFetcher setLogDirectoryForCurrentRun:[GTMHTTPFetcher logDirectoryForCurrentRun]]; +// +// Setting this overrides the logging directory, process name, and date stamp when writing +// the log file. ++ (void)setLogDirectoryForCurrentRun:(NSString *)logDirectoryForCurrentRun; ++ (NSString *)logDirectoryForCurrentRun; + +// Prunes old log directories that have not been modified since the provided date. +// This will not delete the current run's log directory. ++ (void)deleteLogDirectoriesOlderThanDate:(NSDate *)date; + +// internal; called by fetcher +- (void)logFetchWithError:(NSError *)error; +- (NSInputStream *)loggedInputStreamForInputStream:(NSInputStream *)inputStream; +- (GTMSessionFetcherBodyStreamProvider)loggedStreamProviderForStreamProvider: + (GTMSessionFetcherBodyStreamProvider)streamProvider; + +// internal; accessors useful for viewing logs ++ (NSString *)processNameLogPrefix; ++ (NSString *)symlinkNameSuffix; ++ (NSString *)htmlFileName; + +@end + +#endif // !STRIP_GTM_FETCH_LOGGING diff --git a/!main project/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.m b/!main project/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.m new file mode 100644 index 0000000..cdf5c17 --- /dev/null +++ b/!main project/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.m @@ -0,0 +1,982 @@ +/* Copyright 2014 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#include +#include + +#import "GTMSessionFetcherLogging.h" + +#ifndef STRIP_GTM_FETCH_LOGGING + #error GTMSessionFetcher headers should have defaulted this if it wasn't already defined. +#endif + +#if !STRIP_GTM_FETCH_LOGGING + +// Sensitive credential strings are replaced in logs with _snip_ +// +// Apps that must see the contents of sensitive tokens can set this to 1 +#ifndef SKIP_GTM_FETCH_LOGGING_SNIPPING +#define SKIP_GTM_FETCH_LOGGING_SNIPPING 0 +#endif + +// If GTMReadMonitorInputStream is available, it can be used for +// capturing uploaded streams of data +// +// We locally declare methods of GTMReadMonitorInputStream so we +// do not need to import the header, as some projects may not have it available +#if !GTMSESSION_BUILD_COMBINED_SOURCES +@interface GTMReadMonitorInputStream : NSInputStream + ++ (instancetype)inputStreamWithStream:(NSInputStream *)input; + +@property (assign) id readDelegate; +@property (assign) SEL readSelector; + +@end +#else +@class GTMReadMonitorInputStream; +#endif // !GTMSESSION_BUILD_COMBINED_SOURCES + +@interface GTMSessionFetcher (GTMHTTPFetcherLoggingUtilities) + ++ (NSString *)headersStringForDictionary:(NSDictionary *)dict; ++ (NSString *)snipSubstringOfString:(NSString *)originalStr + betweenStartString:(NSString *)startStr + endString:(NSString *)endStr; +- (void)inputStream:(GTMReadMonitorInputStream *)stream + readIntoBuffer:(void *)buffer + length:(int64_t)length; + +@end + +@implementation GTMSessionFetcher (GTMSessionFetcherLogging) + +// fetchers come and fetchers go, but statics are forever +static BOOL gIsLoggingEnabled = NO; +static BOOL gIsLoggingToFile = YES; +static NSString *gLoggingDirectoryPath = nil; +static NSString *gLogDirectoryForCurrentRun = nil; +static NSString *gLoggingDateStamp = nil; +static NSString *gLoggingProcessName = nil; + ++ (void)setLoggingDirectory:(NSString *)path { + gLoggingDirectoryPath = [path copy]; +} + ++ (NSString *)loggingDirectory { + if (!gLoggingDirectoryPath) { + NSArray *paths = nil; +#if TARGET_IPHONE_SIMULATOR + // default to a directory called GTMHTTPDebugLogs into a sandbox-safe + // directory that a developer can find easily, the application home + paths = @[ NSHomeDirectory() ]; +#elif TARGET_OS_IPHONE + // Neither ~/Desktop nor ~/Home is writable on an actual iOS, watchOS, or tvOS device. + // Put it in ~/Documents. + paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); +#else + // default to a directory called GTMHTTPDebugLogs in the desktop folder + paths = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES); +#endif + + NSString *desktopPath = paths.firstObject; + if (desktopPath) { + NSString *const kGTMLogFolderName = @"GTMHTTPDebugLogs"; + NSString *logsFolderPath = [desktopPath stringByAppendingPathComponent:kGTMLogFolderName]; + + NSFileManager *fileMgr = [NSFileManager defaultManager]; + BOOL isDir; + BOOL doesFolderExist = [fileMgr fileExistsAtPath:logsFolderPath isDirectory:&isDir]; + if (!doesFolderExist) { + // make the directory + doesFolderExist = [fileMgr createDirectoryAtPath:logsFolderPath + withIntermediateDirectories:YES + attributes:nil + error:NULL]; + if (doesFolderExist) { + // The directory has been created. Exclude it from backups. + NSURL *pathURL = [NSURL fileURLWithPath:logsFolderPath isDirectory:YES]; + [pathURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:NULL]; + } + } + + if (doesFolderExist) { + // it's there; store it in the global + gLoggingDirectoryPath = [logsFolderPath copy]; + } + } + } + return gLoggingDirectoryPath; +} + ++ (void)setLogDirectoryForCurrentRun:(NSString *)logDirectoryForCurrentRun { + // Set the path for this run's logs. + gLogDirectoryForCurrentRun = [logDirectoryForCurrentRun copy]; +} + ++ (NSString *)logDirectoryForCurrentRun { + // make a directory for this run's logs, like SyncProto_logs_10-16_01-56-58PM + if (gLogDirectoryForCurrentRun) return gLogDirectoryForCurrentRun; + + NSString *parentDir = [self loggingDirectory]; + NSString *logNamePrefix = [self processNameLogPrefix]; + NSString *dateStamp = [self loggingDateStamp]; + NSString *dirName = [NSString stringWithFormat:@"%@%@", logNamePrefix, dateStamp]; + NSString *logDirectory = [parentDir stringByAppendingPathComponent:dirName]; + + if (gIsLoggingToFile) { + NSFileManager *fileMgr = [NSFileManager defaultManager]; + // Be sure that the first time this app runs, it's not writing to a preexisting folder + static BOOL gShouldReuseFolder = NO; + if (!gShouldReuseFolder) { + gShouldReuseFolder = YES; + NSString *origLogDir = logDirectory; + for (int ctr = 2; ctr < 20; ++ctr) { + if (![fileMgr fileExistsAtPath:logDirectory]) break; + + // append a digit + logDirectory = [origLogDir stringByAppendingFormat:@"_%d", ctr]; + } + } + if (![fileMgr createDirectoryAtPath:logDirectory + withIntermediateDirectories:YES + attributes:nil + error:NULL]) return nil; + } + gLogDirectoryForCurrentRun = logDirectory; + + return gLogDirectoryForCurrentRun; +} + ++ (void)setLoggingEnabled:(BOOL)isLoggingEnabled { + gIsLoggingEnabled = isLoggingEnabled; +} + ++ (BOOL)isLoggingEnabled { + return gIsLoggingEnabled; +} + ++ (void)setLoggingToFileEnabled:(BOOL)isLoggingToFileEnabled { + gIsLoggingToFile = isLoggingToFileEnabled; +} + ++ (BOOL)isLoggingToFileEnabled { + return gIsLoggingToFile; +} + ++ (void)setLoggingProcessName:(NSString *)processName { + gLoggingProcessName = [processName copy]; +} + ++ (NSString *)loggingProcessName { + // get the process name (once per run) replacing spaces with underscores + if (!gLoggingProcessName) { + NSString *procName = [[NSProcessInfo processInfo] processName]; + gLoggingProcessName = [procName stringByReplacingOccurrencesOfString:@" " withString:@"_"]; + } + return gLoggingProcessName; +} + ++ (void)setLoggingDateStamp:(NSString *)dateStamp { + gLoggingDateStamp = [dateStamp copy]; +} + ++ (NSString *)loggingDateStamp { + // We'll pick one date stamp per run, so a run that starts at a later second + // will get a unique results html file + if (!gLoggingDateStamp) { + // produce a string like 08-21_01-41-23PM + + NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; + [formatter setFormatterBehavior:NSDateFormatterBehavior10_4]; + [formatter setDateFormat:@"M-dd_hh-mm-ssa"]; + + gLoggingDateStamp = [formatter stringFromDate:[NSDate date]]; + } + return gLoggingDateStamp; +} + ++ (NSString *)processNameLogPrefix { + static NSString *gPrefix = nil; + if (!gPrefix) { + NSString *processName = [self loggingProcessName]; + gPrefix = [[NSString alloc] initWithFormat:@"%@_log_", processName]; + } + return gPrefix; +} + ++ (NSString *)symlinkNameSuffix { + return @"_log_newest.html"; +} + ++ (NSString *)htmlFileName { + return @"aperçu_http_log.html"; +} + ++ (void)deleteLogDirectoriesOlderThanDate:(NSDate *)cutoffDate { + NSFileManager *fileMgr = [NSFileManager defaultManager]; + NSURL *parentDir = [NSURL fileURLWithPath:[[self class] loggingDirectory]]; + NSURL *logDirectoryForCurrentRun = + [NSURL fileURLWithPath:[[self class] logDirectoryForCurrentRun]]; + NSError *error; + NSArray *contents = [fileMgr contentsOfDirectoryAtURL:parentDir + includingPropertiesForKeys:@[ NSURLContentModificationDateKey ] + options:0 + error:&error]; + for (NSURL *itemURL in contents) { + if ([itemURL isEqual:logDirectoryForCurrentRun]) continue; + + NSDate *modDate; + if ([itemURL getResourceValue:&modDate + forKey:NSURLContentModificationDateKey + error:&error]) { + if ([modDate compare:cutoffDate] == NSOrderedAscending) { + if (![fileMgr removeItemAtURL:itemURL error:&error]) { + NSLog(@"deleteLogDirectoriesOlderThanDate failed to delete %@: %@", + itemURL.path, error); + } + } + } else { + NSLog(@"deleteLogDirectoriesOlderThanDate failed to get mod date of %@: %@", + itemURL.path, error); + } + } +} + +// formattedStringFromData returns a prettyprinted string for XML or JSON input, +// and a plain string for other input data +- (NSString *)formattedStringFromData:(NSData *)inputData + contentType:(NSString *)contentType + JSON:(NSDictionary **)outJSON { + if (!inputData) return nil; + + // if the content type is JSON and we have the parsing class available, use that + if ([contentType hasPrefix:@"application/json"] && inputData.length > 5) { + // convert from JSON string to NSObjects and back to a formatted string + NSMutableDictionary *obj = [NSJSONSerialization JSONObjectWithData:inputData + options:NSJSONReadingMutableContainers + error:NULL]; + if (obj) { + if (outJSON) *outJSON = obj; + if ([obj isKindOfClass:[NSMutableDictionary class]]) { + // for security and privacy, omit OAuth 2 response access and refresh tokens + if ([obj valueForKey:@"refresh_token"] != nil) { + [obj setObject:@"_snip_" forKey:@"refresh_token"]; + } + if ([obj valueForKey:@"access_token"] != nil) { + [obj setObject:@"_snip_" forKey:@"access_token"]; + } + } + NSData *data = [NSJSONSerialization dataWithJSONObject:obj + options:NSJSONWritingPrettyPrinted + error:NULL]; + if (data) { + NSString *jsonStr = [[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding]; + return jsonStr; + } + } + } + +#if !TARGET_OS_IPHONE && !GTM_SKIP_LOG_XMLFORMAT + // verify that this data starts with the bytes indicating XML + + NSString *const kXMLLintPath = @"/usr/bin/xmllint"; + static BOOL gHasCheckedAvailability = NO; + static BOOL gIsXMLLintAvailable = NO; + + if (!gHasCheckedAvailability) { + gIsXMLLintAvailable = [[NSFileManager defaultManager] fileExistsAtPath:kXMLLintPath]; + gHasCheckedAvailability = YES; + } + if (gIsXMLLintAvailable + && inputData.length > 5 + && strncmp(inputData.bytes, " 0) { + // success + inputData = formattedData; + } + } +#else + // we can't call external tasks on the iPhone; leave the XML unformatted +#endif + + NSString *dataStr = [[NSString alloc] initWithData:inputData + encoding:NSUTF8StringEncoding]; + return dataStr; +} + +// stringFromStreamData creates a string given the supplied data +// +// If NSString can create a UTF-8 string from the data, then that is returned. +// +// Otherwise, this routine tries to find a MIME boundary at the beginning of the data block, and +// uses that to break up the data into parts. Each part will be used to try to make a UTF-8 string. +// For parts that fail, a replacement string showing the part header and <> is supplied +// in place of the binary data. + +- (NSString *)stringFromStreamData:(NSData *)data + contentType:(NSString *)contentType { + + if (!data) return nil; + + // optimistically, see if the whole data block is UTF-8 + NSString *streamDataStr = [self formattedStringFromData:data + contentType:contentType + JSON:NULL]; + if (streamDataStr) return streamDataStr; + + // Munge a buffer by replacing non-ASCII bytes with underscores, and turn that munged buffer an + // NSString. That gives us a string we can use with NSScanner. + NSMutableData *mutableData = [NSMutableData dataWithData:data]; + unsigned char *bytes = (unsigned char *)mutableData.mutableBytes; + + for (unsigned int idx = 0; idx < mutableData.length; ++idx) { + if (bytes[idx] > 0x7F || bytes[idx] == 0) { + bytes[idx] = '_'; + } + } + + NSString *mungedStr = [[NSString alloc] initWithData:mutableData + encoding:NSUTF8StringEncoding]; + if (mungedStr) { + + // scan for the boundary string + NSString *boundary = nil; + NSScanner *scanner = [NSScanner scannerWithString:mungedStr]; + + if ([scanner scanUpToString:@"\r\n" intoString:&boundary] + && [boundary hasPrefix:@"--"]) { + + // we found a boundary string; use it to divide the string into parts + NSArray *mungedParts = [mungedStr componentsSeparatedByString:boundary]; + + // look at each munged part in the original string, and try to convert those into UTF-8 + NSMutableArray *origParts = [NSMutableArray array]; + NSUInteger offset = 0; + for (NSString *mungedPart in mungedParts) { + NSUInteger partSize = mungedPart.length; + NSData *origPartData = [data subdataWithRange:NSMakeRange(offset, partSize)]; + NSString *origPartStr = [[NSString alloc] initWithData:origPartData + encoding:NSUTF8StringEncoding]; + if (origPartStr) { + // we could make this original part into UTF-8; use the string + [origParts addObject:origPartStr]; + } else { + // this part can't be made into UTF-8; scan the header, if we can + NSString *header = nil; + NSScanner *headerScanner = [NSScanner scannerWithString:mungedPart]; + if (![headerScanner scanUpToString:@"\r\n\r\n" intoString:&header]) { + // we couldn't find a header + header = @""; + } + // make a part string with the header and <> + NSString *binStr = [NSString stringWithFormat:@"\r%@\r<<%lu bytes>>\r", + header, (long)(partSize - header.length)]; + [origParts addObject:binStr]; + } + offset += partSize + boundary.length; + } + // rejoin the original parts + streamDataStr = [origParts componentsJoinedByString:boundary]; + } + } + if (!streamDataStr) { + // give up; just make a string showing the uploaded bytes + streamDataStr = [NSString stringWithFormat:@"<<%u bytes>>", (unsigned int)data.length]; + } + return streamDataStr; +} + +// logFetchWithError is called following a successful or failed fetch attempt +// +// This method does all the work for appending to and creating log files + +- (void)logFetchWithError:(NSError *)error { + if (![[self class] isLoggingEnabled]) return; + NSString *logDirectory = [[self class] logDirectoryForCurrentRun]; + if (!logDirectory) return; + NSString *processName = [[self class] loggingProcessName]; + + // TODO: add Javascript to display response data formatted in hex + + // each response's NSData goes into its own xml or txt file, though all responses for this run of + // the app share a main html file. This counter tracks all fetch responses for this app run. + // + // we'll use a local variable since this routine may be reentered while waiting for XML formatting + // to be completed by an external task + static int gResponseCounter = 0; + int responseCounter = ++gResponseCounter; + + NSURLResponse *response = [self response]; + NSDictionary *responseHeaders = [self responseHeaders]; + NSString *responseDataStr = nil; + NSDictionary *responseJSON = nil; + + // if there's response data, decide what kind of file to put it in based on the first bytes of the + // file or on the mime type supplied by the server + NSString *responseMIMEType = [response MIMEType]; + BOOL isResponseImage = NO; + + // file name for an image data file + NSString *responseDataFileName = nil; + + int64_t responseDataLength = self.downloadedLength; + if (responseDataLength > 0) { + NSData *downloadedData = self.downloadedData; + if (downloadedData == nil + && responseDataLength > 0 + && responseDataLength < 20000 + && self.destinationFileURL) { + // There's a download file that's not too big, so get the data to display from the downloaded + // file. + NSURL *destinationURL = self.destinationFileURL; + downloadedData = [NSData dataWithContentsOfURL:destinationURL]; + } + NSString *responseType = [responseHeaders valueForKey:@"Content-Type"]; + responseDataStr = [self formattedStringFromData:downloadedData + contentType:responseType + JSON:&responseJSON]; + NSString *responseDataExtn = nil; + NSData *dataToWrite = nil; + if (responseDataStr) { + // we were able to make a UTF-8 string from the response data + if ([responseMIMEType isEqual:@"application/atom+xml"] + || [responseMIMEType hasSuffix:@"/xml"]) { + responseDataExtn = @"xml"; + dataToWrite = [responseDataStr dataUsingEncoding:NSUTF8StringEncoding]; + } + } else if ([responseMIMEType isEqual:@"image/jpeg"]) { + responseDataExtn = @"jpg"; + dataToWrite = downloadedData; + isResponseImage = YES; + } else if ([responseMIMEType isEqual:@"image/gif"]) { + responseDataExtn = @"gif"; + dataToWrite = downloadedData; + isResponseImage = YES; + } else if ([responseMIMEType isEqual:@"image/png"]) { + responseDataExtn = @"png"; + dataToWrite = downloadedData; + isResponseImage = YES; + } else { + // add more non-text types here + } + // if we have an extension, save the raw data in a file with that extension + if (responseDataExtn && dataToWrite) { + // generate a response file base name like + NSString *responseBaseName = [NSString stringWithFormat:@"fetch_%d_response", responseCounter]; + responseDataFileName = [responseBaseName stringByAppendingPathExtension:responseDataExtn]; + NSString *responseDataFilePath = [logDirectory stringByAppendingPathComponent:responseDataFileName]; + + NSError *downloadedError = nil; + if (gIsLoggingToFile && ![dataToWrite writeToFile:responseDataFilePath + options:0 + error:&downloadedError]) { + NSLog(@"%@ logging write error:%@ (%@)", [self class], downloadedError, responseDataFileName); + } + } + } + // we'll have one main html file per run of the app + NSString *htmlName = [[self class] htmlFileName]; + NSString *htmlPath =[logDirectory stringByAppendingPathComponent:htmlName]; + + // if the html file exists (from logging previous fetches) we don't need + // to re-write the header or the scripts + NSFileManager *fileMgr = [NSFileManager defaultManager]; + BOOL didFileExist = [fileMgr fileExistsAtPath:htmlPath]; + + NSMutableString* outputHTML = [NSMutableString string]; + + // we need a header to say we'll have UTF-8 text + if (!didFileExist) { + [outputHTML appendFormat:@"%@ HTTP fetch log %@", + processName, [[self class] loggingDateStamp]]; + } + // now write the visible html elements + NSString *copyableFileName = [NSString stringWithFormat:@"fetch_%d.txt", responseCounter]; + + NSDate *now = [NSDate date]; + // write the date & time, the comment, and the link to the plain-text (copyable) log + [outputHTML appendFormat:@"%@      ", now]; + + NSString *comment = [self comment]; + if (comment.length > 0) { + [outputHTML appendFormat:@"%@      ", comment]; + } + [outputHTML appendFormat:@"request/response log
", copyableFileName]; + NSTimeInterval elapsed = -self.initialBeginFetchDate.timeIntervalSinceNow; + [outputHTML appendFormat:@"elapsed: %5.3fsec
", elapsed]; + + // write the request URL + NSURLRequest *request = self.request; + NSString *requestMethod = request.HTTPMethod; + NSURL *requestURL = request.URL; + + // Save the request URL for next time in case this redirects. + NSString *redirectedFromURLString = [self.redirectedFromURL absoluteString]; + self.redirectedFromURL = [requestURL copy]; + if (redirectedFromURLString) { + [outputHTML appendFormat:@"redirected from %@
", + redirectedFromURLString]; + } + [outputHTML appendFormat:@"request: %@ %@
\n", requestMethod, requestURL]; + + // write the request headers + NSDictionary *requestHeaders = request.allHTTPHeaderFields; + NSUInteger numberOfRequestHeaders = requestHeaders.count; + if (numberOfRequestHeaders > 0) { + // Indicate if the request is authorized; warn if the request is authorized but non-SSL + NSString *auth = [requestHeaders objectForKey:@"Authorization"]; + NSString *headerDetails = @""; + if (auth) { + BOOL isInsecure = [[requestURL scheme] isEqual:@"http"]; + if (isInsecure) { + // 26A0 = ⚠ + headerDetails = + @"   authorized, non-SSL "; + } else { + headerDetails = @"   authorized"; + } + } + NSString *cookiesHdr = [requestHeaders objectForKey:@"Cookie"]; + if (cookiesHdr) { + headerDetails = [headerDetails stringByAppendingString:@"   cookies"]; + } + NSString *matchHdr = [requestHeaders objectForKey:@"If-Match"]; + if (matchHdr) { + headerDetails = [headerDetails stringByAppendingString:@"   if-match"]; + } + matchHdr = [requestHeaders objectForKey:@"If-None-Match"]; + if (matchHdr) { + headerDetails = [headerDetails stringByAppendingString:@"   if-none-match"]; + } + [outputHTML appendFormat:@"   headers: %d %@
", + (int)numberOfRequestHeaders, headerDetails]; + } else { + [outputHTML appendFormat:@"   headers: none
"]; + } + // write the request post data + NSData *bodyData = nil; + NSData *loggedStreamData = self.loggedStreamData; + if (loggedStreamData) { + bodyData = loggedStreamData; + } else { + bodyData = self.bodyData; + if (bodyData == nil) { + bodyData = self.request.HTTPBody; + } + } + uint64_t bodyDataLength = bodyData.length; + + if (bodyData.length == 0) { + // If the data is in a body upload file URL, read that in if it's not huge. + NSURL *bodyFileURL = self.bodyFileURL; + if (bodyFileURL) { + NSNumber *fileSizeNum = nil; + NSError *fileSizeError = nil; + if ([bodyFileURL getResourceValue:&fileSizeNum + forKey:NSURLFileSizeKey + error:&fileSizeError]) { + bodyDataLength = [fileSizeNum unsignedLongLongValue]; + if (bodyDataLength > 0 && bodyDataLength < 50000) { + bodyData = [NSData dataWithContentsOfURL:bodyFileURL + options:NSDataReadingUncached + error:&fileSizeError]; + } + } + } + } + NSString *bodyDataStr = nil; + NSString *postType = [requestHeaders valueForKey:@"Content-Type"]; + + if (bodyDataLength > 0) { + [outputHTML appendFormat:@"   data: %llu bytes, %@
\n", + bodyDataLength, postType ? postType : @"(no type)"]; + NSString *logRequestBody = self.logRequestBody; + if (logRequestBody) { + bodyDataStr = [logRequestBody copy]; + self.logRequestBody = nil; + } else { + bodyDataStr = [self stringFromStreamData:bodyData + contentType:postType]; + if (bodyDataStr) { + // remove OAuth 2 client secret and refresh token + bodyDataStr = [[self class] snipSubstringOfString:bodyDataStr + betweenStartString:@"client_secret=" + endString:@"&"]; + bodyDataStr = [[self class] snipSubstringOfString:bodyDataStr + betweenStartString:@"refresh_token=" + endString:@"&"]; + // remove ClientLogin password + bodyDataStr = [[self class] snipSubstringOfString:bodyDataStr + betweenStartString:@"&Passwd=" + endString:@"&"]; + } + } + } else { + // no post data + } + // write the response status, MIME type, URL + NSInteger status = [self statusCode]; + if (response) { + NSString *statusString = @""; + if (status != 0) { + if (status == 200 || status == 201) { + statusString = [NSString stringWithFormat:@"%ld", (long)status]; + + // report any JSON-RPC error + if ([responseJSON isKindOfClass:[NSDictionary class]]) { + NSDictionary *jsonError = [responseJSON objectForKey:@"error"]; + if ([jsonError isKindOfClass:[NSDictionary class]]) { + NSString *jsonCode = [[jsonError valueForKey:@"code"] description]; + NSString *jsonMessage = [jsonError valueForKey:@"message"]; + if (jsonCode || jsonMessage) { + // 2691 = ⚑ + NSString *const jsonErrFmt = + @"   JSON error: %@ %@  ⚑"; + statusString = [statusString stringByAppendingFormat:jsonErrFmt, + jsonCode ? jsonCode : @"", + jsonMessage ? jsonMessage : @""]; + } + } + } + } else { + // purple for anything other than 200 or 201 + NSString *flag = status >= 400 ? @" ⚑" : @""; // 2691 = ⚑ + NSString *explanation = [NSHTTPURLResponse localizedStringForStatusCode:status]; + NSString *const statusFormat = @"%ld %@ %@"; + statusString = [NSString stringWithFormat:statusFormat, (long)status, explanation, flag]; + } + } + // show the response URL only if it's different from the request URL + NSString *responseURLStr = @""; + NSURL *responseURL = response.URL; + + if (responseURL && ![responseURL isEqual:request.URL]) { + NSString *const responseURLFormat = + @"response URL: %@
\n"; + responseURLStr = [NSString stringWithFormat:responseURLFormat, [responseURL absoluteString]]; + } + [outputHTML appendFormat:@"response:  status %@
\n%@", + statusString, responseURLStr]; + // Write the response headers + NSUInteger numberOfResponseHeaders = responseHeaders.count; + if (numberOfResponseHeaders > 0) { + // Indicate if the server is setting cookies + NSString *cookiesSet = [responseHeaders valueForKey:@"Set-Cookie"]; + NSString *cookiesStr = + cookiesSet ? @"  sets cookies" : @""; + // Indicate if the server is redirecting + NSString *location = [responseHeaders valueForKey:@"Location"]; + BOOL isRedirect = status >= 300 && status <= 399 && location != nil; + NSString *redirectsStr = + isRedirect ? @"  redirects" : @""; + [outputHTML appendFormat:@"   headers: %d %@ %@
\n", + (int)numberOfResponseHeaders, cookiesStr, redirectsStr]; + } else { + [outputHTML appendString:@"   headers: none
\n"]; + } + } + // error + if (error) { + [outputHTML appendFormat:@"Error: %@
\n", error.description]; + } + // Write the response data + if (responseDataFileName) { + if (isResponseImage) { + // Make a small inline image that links to the full image file + [outputHTML appendFormat:@"   data: %lld bytes, %@
", + responseDataLength, responseMIMEType]; + NSString *const fmt = + @"image\n"; + [outputHTML appendFormat:fmt, responseDataFileName, responseDataFileName]; + } else { + // The response data was XML; link to the xml file + NSString *const fmt = + @"   data: %lld bytes, %@   %@\n"; + [outputHTML appendFormat:fmt, responseDataLength, responseMIMEType, + responseDataFileName, [responseDataFileName pathExtension]]; + } + } else { + // The response data was not an image; just show the length and MIME type + [outputHTML appendFormat:@"   data: %lld bytes, %@\n", + responseDataLength, responseMIMEType ? responseMIMEType : @"(no response type)"]; + } + // Make a single string of the request and response, suitable for copying + // to the clipboard and pasting into a bug report + NSMutableString *copyable = [NSMutableString string]; + if (comment) { + [copyable appendFormat:@"%@\n\n", comment]; + } + [copyable appendFormat:@"%@ elapsed: %5.3fsec\n", now, elapsed]; + if (redirectedFromURLString) { + [copyable appendFormat:@"Redirected from %@\n", redirectedFromURLString]; + } + [copyable appendFormat:@"Request: %@ %@\n", requestMethod, requestURL]; + if (requestHeaders.count > 0) { + [copyable appendFormat:@"Request headers:\n%@\n", + [[self class] headersStringForDictionary:requestHeaders]]; + } + if (bodyDataLength > 0) { + [copyable appendFormat:@"Request body: (%llu bytes)\n", bodyDataLength]; + if (bodyDataStr) { + [copyable appendFormat:@"%@\n", bodyDataStr]; + } + [copyable appendString:@"\n"]; + } + if (response) { + [copyable appendFormat:@"Response: status %d\n", (int) status]; + [copyable appendFormat:@"Response headers:\n%@\n", + [[self class] headersStringForDictionary:responseHeaders]]; + [copyable appendFormat:@"Response body: (%lld bytes)\n", responseDataLength]; + if (responseDataLength > 0) { + NSString *logResponseBody = self.logResponseBody; + if (logResponseBody) { + // The user has provided the response body text. + responseDataStr = [logResponseBody copy]; + self.logResponseBody = nil; + } + if (responseDataStr != nil) { + [copyable appendFormat:@"%@\n", responseDataStr]; + } else { + // Even though it's redundant, we'll put in text to indicate that all the bytes are binary. + if (self.destinationFileURL) { + [copyable appendFormat:@"<<%lld bytes>> to file %@\n", + responseDataLength, self.destinationFileURL.path]; + } else { + [copyable appendFormat:@"<<%lld bytes>>\n", responseDataLength]; + } + } + } + } + if (error) { + [copyable appendFormat:@"Error: %@\n", error]; + } + // Save to log property before adding the separator + self.log = copyable; + + [copyable appendString:@"-----------------------------------------------------------\n"]; + + // Write the copyable version to another file (linked to at the top of the html file, above) + // + // Ideally, something to just copy this to the clipboard like + // Copy here." + // would work everywhere, but it only works in Safari as of 8/2010 + if (gIsLoggingToFile) { + NSString *parentDir = [[self class] loggingDirectory]; + NSString *copyablePath = [logDirectory stringByAppendingPathComponent:copyableFileName]; + NSError *copyableError = nil; + if (![copyable writeToFile:copyablePath + atomically:NO + encoding:NSUTF8StringEncoding + error:©ableError]) { + // Error writing to file + NSLog(@"%@ logging write error:%@ (%@)", [self class], copyableError, copyablePath); + } + [outputHTML appendString:@"

"]; + + // Append the HTML to the main output file + const char* htmlBytes = outputHTML.UTF8String; + NSOutputStream *stream = [NSOutputStream outputStreamToFileAtPath:htmlPath + append:YES]; + [stream open]; + [stream write:(const uint8_t *) htmlBytes maxLength:strlen(htmlBytes)]; + [stream close]; + + // Make a symlink to the latest html + NSString *const symlinkNameSuffix = [[self class] symlinkNameSuffix]; + NSString *symlinkName = [processName stringByAppendingString:symlinkNameSuffix]; + NSString *symlinkPath = [parentDir stringByAppendingPathComponent:symlinkName]; + + [fileMgr removeItemAtPath:symlinkPath error:NULL]; + [fileMgr createSymbolicLinkAtPath:symlinkPath + withDestinationPath:htmlPath + error:NULL]; +#if TARGET_OS_IPHONE + static BOOL gReportedLoggingPath = NO; + if (!gReportedLoggingPath) { + gReportedLoggingPath = YES; + NSLog(@"GTMSessionFetcher logging to \"%@\"", parentDir); + } +#endif + } +} + +- (NSInputStream *)loggedInputStreamForInputStream:(NSInputStream *)inputStream { + if (!inputStream) return nil; + if (![GTMSessionFetcher isLoggingEnabled]) return inputStream; + + [self clearLoggedStreamData]; // Clear any previous data. + Class monitorClass = NSClassFromString(@"GTMReadMonitorInputStream"); + if (!monitorClass) { + NSString const *str = @"<>"; + NSData *stringData = [str dataUsingEncoding:NSUTF8StringEncoding]; + [self appendLoggedStreamData:stringData]; + return inputStream; + } + inputStream = [monitorClass inputStreamWithStream:inputStream]; + + GTMReadMonitorInputStream *readMonitorInputStream = (GTMReadMonitorInputStream *)inputStream; + [readMonitorInputStream setReadDelegate:self]; + SEL readSel = @selector(inputStream:readIntoBuffer:length:); + [readMonitorInputStream setReadSelector:readSel]; + + return inputStream; +} + +- (GTMSessionFetcherBodyStreamProvider)loggedStreamProviderForStreamProvider: + (GTMSessionFetcherBodyStreamProvider)streamProvider { + if (!streamProvider) return nil; + if (![GTMSessionFetcher isLoggingEnabled]) return streamProvider; + + [self clearLoggedStreamData]; // Clear any previous data. + Class monitorClass = NSClassFromString(@"GTMReadMonitorInputStream"); + if (!monitorClass) { + NSString const *str = @"<>"; + NSData *stringData = [str dataUsingEncoding:NSUTF8StringEncoding]; + [self appendLoggedStreamData:stringData]; + return streamProvider; + } + GTMSessionFetcherBodyStreamProvider loggedStreamProvider = + ^(GTMSessionFetcherBodyStreamProviderResponse response) { + streamProvider(^(NSInputStream *bodyStream) { + bodyStream = [self loggedInputStreamForInputStream:bodyStream]; + response(bodyStream); + }); + }; + return loggedStreamProvider; +} + +@end + +@implementation GTMSessionFetcher (GTMSessionFetcherLoggingUtilities) + +- (void)inputStream:(GTMReadMonitorInputStream *)stream + readIntoBuffer:(void *)buffer + length:(int64_t)length { + // append the captured data + NSData *data = [NSData dataWithBytesNoCopy:buffer + length:(NSUInteger)length + freeWhenDone:NO]; + [self appendLoggedStreamData:data]; +} + +#pragma mark Fomatting Utilities + ++ (NSString *)snipSubstringOfString:(NSString *)originalStr + betweenStartString:(NSString *)startStr + endString:(NSString *)endStr { +#if SKIP_GTM_FETCH_LOGGING_SNIPPING + return originalStr; +#else + if (!originalStr) return nil; + + // Find the start string, and replace everything between it + // and the end string (or the end of the original string) with "_snip_" + NSRange startRange = [originalStr rangeOfString:startStr]; + if (startRange.location == NSNotFound) return originalStr; + + // We found the start string + NSUInteger originalLength = originalStr.length; + NSUInteger startOfTarget = NSMaxRange(startRange); + NSRange targetAndRest = NSMakeRange(startOfTarget, originalLength - startOfTarget); + NSRange endRange = [originalStr rangeOfString:endStr + options:0 + range:targetAndRest]; + NSRange replaceRange; + if (endRange.location == NSNotFound) { + // Found no end marker so replace to end of string + replaceRange = targetAndRest; + } else { + // Replace up to the endStr + replaceRange = NSMakeRange(startOfTarget, endRange.location - startOfTarget); + } + NSString *result = [originalStr stringByReplacingCharactersInRange:replaceRange + withString:@"_snip_"]; + return result; +#endif // SKIP_GTM_FETCH_LOGGING_SNIPPING +} + ++ (NSString *)headersStringForDictionary:(NSDictionary *)dict { + // Format the dictionary in http header style, like + // Accept: application/json + // Cache-Control: no-cache + // Content-Type: application/json; charset=utf-8 + // + // Pad the key names, but not beyond 16 chars, since long custom header + // keys just create too much whitespace + NSArray *keys = [dict.allKeys sortedArrayUsingSelector:@selector(compare:)]; + + NSMutableString *str = [NSMutableString string]; + for (NSString *key in keys) { + NSString *value = [dict valueForKey:key]; + if ([key isEqual:@"Authorization"]) { + // Remove OAuth 1 token + value = [[self class] snipSubstringOfString:value + betweenStartString:@"oauth_token=\"" + endString:@"\""]; + + // Remove OAuth 2 bearer token (draft 16, and older form) + value = [[self class] snipSubstringOfString:value + betweenStartString:@"Bearer " + endString:@"\n"]; + value = [[self class] snipSubstringOfString:value + betweenStartString:@"OAuth " + endString:@"\n"]; + + // Remove Google ClientLogin + value = [[self class] snipSubstringOfString:value + betweenStartString:@"GoogleLogin auth=" + endString:@"\n"]; + } + [str appendFormat:@" %@: %@\n", key, value]; + } + return str; +} + +@end + +#endif // !STRIP_GTM_FETCH_LOGGING diff --git a/!main project/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.h b/!main project/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.h new file mode 100644 index 0000000..fb743ca --- /dev/null +++ b/!main project/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.h @@ -0,0 +1,193 @@ +/* Copyright 2014 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// For best performance and convenient usage, fetchers should be generated by a common +// GTMSessionFetcherService instance, like +// +// _fetcherService = [[GTMSessionFetcherService alloc] init]; +// GTMSessionFetcher* myFirstFetcher = [_fetcherService fetcherWithRequest:request1]; +// GTMSessionFetcher* mySecondFetcher = [_fetcherService fetcherWithRequest:request2]; + +#import "GTMSessionFetcher.h" + +GTM_ASSUME_NONNULL_BEGIN + +// Notifications. + +// This notification indicates a reusable session has become invalid. It is intended mainly for the +// service's unit tests. +// +// The notification object is the fetcher service. +// The invalid session is provided via the userInfo kGTMSessionFetcherServiceSessionKey key. +extern NSString *const kGTMSessionFetcherServiceSessionBecameInvalidNotification; +extern NSString *const kGTMSessionFetcherServiceSessionKey; + +@interface GTMSessionFetcherService : NSObject + +// Queues of delayed and running fetchers. Each dictionary contains arrays +// of GTMSessionFetcher *fetchers, keyed by NSString *host +@property(atomic, strong, readonly, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, NSArray *) *delayedFetchersByHost; +@property(atomic, strong, readonly, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, NSArray *) *runningFetchersByHost; + +// A max value of 0 means no fetchers should be delayed. +// The default limit is 10 simultaneous fetchers targeting each host. +// This does not apply to fetchers whose useBackgroundSession property is YES. Since services are +// not resurrected on an app relaunch, delayed fetchers would effectively be abandoned. +@property(atomic, assign) NSUInteger maxRunningFetchersPerHost; + +// Properties to be applied to each fetcher; see GTMSessionFetcher.h for descriptions +@property(atomic, strong, GTM_NULLABLE) NSURLSessionConfiguration *configuration; +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherConfigurationBlock configurationBlock; +@property(atomic, strong, GTM_NULLABLE) NSHTTPCookieStorage *cookieStorage; +@property(atomic, strong, GTM_NULL_RESETTABLE) dispatch_queue_t callbackQueue; +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherChallengeBlock challengeBlock; +@property(atomic, strong, GTM_NULLABLE) NSURLCredential *credential; +@property(atomic, strong) NSURLCredential *proxyCredential; +@property(atomic, copy, GTM_NULLABLE) GTM_NSArrayOf(NSString *) *allowedInsecureSchemes; +@property(atomic, assign) BOOL allowLocalhostRequest; +@property(atomic, assign) BOOL allowInvalidServerCertificates; +@property(atomic, assign, getter=isRetryEnabled) BOOL retryEnabled; +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherRetryBlock retryBlock; +@property(atomic, assign) NSTimeInterval maxRetryInterval; +@property(atomic, assign) NSTimeInterval minRetryInterval; +@property(atomic, copy, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, id) *properties; + +#if GTM_BACKGROUND_TASK_FETCHING +@property(atomic, assign) BOOL skipBackgroundTask; +#endif + +// A default useragent of GTMFetcherStandardUserAgentString(nil) will be given to each fetcher +// created by this service unless the request already has a user-agent header set. +// This default will be added starting with builds with the SDKs for OS X 10.11 and iOS 9. +// +// To use the configuration's default user agent, set this property to nil. +@property(atomic, copy, GTM_NULLABLE) NSString *userAgent; + +// The authorizer to attach to the created fetchers. If a specific fetcher should +// not authorize its requests, the fetcher's authorizer property may be set to nil +// before the fetch begins. +@property(atomic, strong, GTM_NULLABLE) id authorizer; + +// Delegate queue used by the session when calling back to the fetcher. The default +// is the main queue. Changing this does not affect the queue used to call back to the +// application; that is specified by the callbackQueue property above. +@property(atomic, strong, GTM_NULL_RESETTABLE) NSOperationQueue *sessionDelegateQueue; + +// When enabled, indicates the same session should be used by subsequent fetchers. +// +// This is enabled by default. +@property(atomic, assign) BOOL reuseSession; + +// Sets the delay until an unused session is invalidated. +// The default interval is 60 seconds. +// +// If the interval is set to 0, then any reused session is not invalidated except by +// explicitly invoking -resetSession. Be aware that setting the interval to 0 thus +// causes the session's delegate to be retained until the session is explicitly reset. +@property(atomic, assign) NSTimeInterval unusedSessionTimeout; + +// If shouldReuseSession is enabled, this will force creation of a new session when future +// fetchers begin. +- (void)resetSession; + +// Create a fetcher +// +// These methods will return a fetcher. If successfully created, the connection +// will hold a strong reference to it for the life of the connection as well. +// So the caller doesn't have to hold onto the fetcher explicitly unless they +// want to be able to monitor or cancel it. +- (GTMSessionFetcher *)fetcherWithRequest:(NSURLRequest *)request; +- (GTMSessionFetcher *)fetcherWithURL:(NSURL *)requestURL; +- (GTMSessionFetcher *)fetcherWithURLString:(NSString *)requestURLString; + +// Common method for fetcher creation. +// +// -fetcherWithRequest:fetcherClass: may be overridden to customize creation of +// fetchers. This is the ONLY method in the GTMSessionFetcher library intended to +// be overridden. +- (id)fetcherWithRequest:(NSURLRequest *)request + fetcherClass:(Class)fetcherClass; + +- (BOOL)isDelayingFetcher:(GTMSessionFetcher *)fetcher; + +- (NSUInteger)numberOfFetchers; // running + delayed fetchers +- (NSUInteger)numberOfRunningFetchers; +- (NSUInteger)numberOfDelayedFetchers; + +// Return a list of all running or delayed fetchers. This includes fetchers created +// by the service which have been started and have not yet stopped. +// +// Returns an array of fetcher objects, or nil if none. +- (GTM_NULLABLE GTM_NSArrayOf(GTMSessionFetcher *) *)issuedFetchers; + +// Search for running or delayed fetchers with the specified URL. +// +// Returns an array of fetcher objects found, or nil if none found. +- (GTM_NULLABLE GTM_NSArrayOf(GTMSessionFetcher *) *)issuedFetchersWithRequestURL:(NSURL *)requestURL; + +- (void)stopAllFetchers; + +// Methods for use by the fetcher class only. +- (GTM_NULLABLE NSURLSession *)session; +- (GTM_NULLABLE NSURLSession *)sessionForFetcherCreation; +- (GTM_NULLABLE id)sessionDelegate; +- (GTM_NULLABLE NSDate *)stoppedAllFetchersDate; + +// The testBlock can inspect its fetcher parameter's request property to +// determine which fetcher is being faked. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherTestBlock testBlock; + +@end + +@interface GTMSessionFetcherService (TestingSupport) + +// Convenience methods to create a fetcher service for testing. +// +// Fetchers generated by this mock fetcher service will not perform any +// network operation, but will invoke callbacks and provide the supplied data +// or error to the completion handler. +// +// You can make more customized mocks by setting the test block property of the service +// or fetcher; the test block can inspect the fetcher's request or other properties. +// +// See the description of the testBlock property below. ++ (instancetype)mockFetcherServiceWithFakedData:(GTM_NULLABLE NSData *)fakedDataOrNil + fakedError:(GTM_NULLABLE NSError *)fakedErrorOrNil; ++ (instancetype)mockFetcherServiceWithFakedData:(GTM_NULLABLE NSData *)fakedDataOrNil + fakedResponse:(NSHTTPURLResponse *)fakedResponse + fakedError:(GTM_NULLABLE NSError *)fakedErrorOrNil; + +// Spin the run loop and discard events (or, if not on the main thread, just sleep the thread) +// until all running and delayed fetchers have completed. +// +// This is only for use in testing or in tools without a user interface. +// +// Synchronous fetches should never be done by shipping apps; they are +// sufficient reason for rejection from the app store. +// +// Returns NO if timed out. +- (BOOL)waitForCompletionOfAllFetchersWithTimeout:(NSTimeInterval)timeoutInSeconds; + +@end + +@interface GTMSessionFetcherService (BackwardsCompatibilityOnly) + +// Clients using GTMSessionFetcher should set the cookie storage explicitly themselves. +// This method is just for compatibility with the old fetcher. +@property(atomic, assign) NSInteger cookieStorageMethod; + +@end + +GTM_ASSUME_NONNULL_END diff --git a/!main project/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m b/!main project/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m new file mode 100644 index 0000000..bd44787 --- /dev/null +++ b/!main project/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m @@ -0,0 +1,1369 @@ +/* Copyright 2014 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "GTMSessionFetcherService.h" + +NSString *const kGTMSessionFetcherServiceSessionBecameInvalidNotification + = @"kGTMSessionFetcherServiceSessionBecameInvalidNotification"; +NSString *const kGTMSessionFetcherServiceSessionKey + = @"kGTMSessionFetcherServiceSessionKey"; + +#if !GTMSESSION_BUILD_COMBINED_SOURCES +@interface GTMSessionFetcher (ServiceMethods) +- (BOOL)beginFetchMayDelay:(BOOL)mayDelay + mayAuthorize:(BOOL)mayAuthorize; +@end +#endif // !GTMSESSION_BUILD_COMBINED_SOURCES + +@interface GTMSessionFetcherService () + +@property(atomic, strong, readwrite) NSDictionary *delayedFetchersByHost; +@property(atomic, strong, readwrite) NSDictionary *runningFetchersByHost; + +@end + +// Since NSURLSession doesn't support a separate delegate per task (!), instances of this +// class serve as a session delegate trampoline. +// +// This class maps a session's tasks to fetchers, and resends delegate messages to the task's +// fetcher. +@interface GTMSessionFetcherSessionDelegateDispatcher : NSObject + +// The session for the tasks in this dispatcher's task-to-fetcher map. +@property(atomic) NSURLSession *session; + +// The timer interval for invalidating a session that has no active tasks. +@property(atomic) NSTimeInterval discardInterval; + +// The current discard timer. +@property(atomic, readonly) NSTimer *discardTimer; + + +- (instancetype)initWithParentService:(GTMSessionFetcherService *)parentService + sessionDiscardInterval:(NSTimeInterval)discardInterval; + +- (void)setFetcher:(GTMSessionFetcher *)fetcher + forTask:(NSURLSessionTask *)task; +- (void)removeFetcher:(GTMSessionFetcher *)fetcher; + +// Before using a session, tells the delegate dispatcher to stop the discard timer. +- (void)startSessionUsage; + +// When abandoning a delegate dispatcher, we want to avoid the session retaining +// the delegate after tasks complete. +- (void)abandon; + +@end + + +@implementation GTMSessionFetcherService { + NSMutableDictionary *_delayedFetchersByHost; + NSMutableDictionary *_runningFetchersByHost; + NSUInteger _maxRunningFetchersPerHost; + + // When this ivar is nil, the service will not reuse sessions. + GTMSessionFetcherSessionDelegateDispatcher *_delegateDispatcher; + + // Fetchers will wait on this if another fetcher is creating the shared NSURLSession. + dispatch_semaphore_t _sessionCreationSemaphore; + + dispatch_queue_t _callbackQueue; + NSOperationQueue *_delegateQueue; + NSHTTPCookieStorage *_cookieStorage; + NSString *_userAgent; + NSTimeInterval _timeout; + + NSURLCredential *_credential; // Username & password. + NSURLCredential *_proxyCredential; // Credential supplied to proxy servers. + + NSInteger _cookieStorageMethod; + + id _authorizer; + + // For waitForCompletionOfAllFetchersWithTimeout: we need to wait on stopped fetchers since + // they've not yet finished invoking their queued callbacks. This array is nil except when + // waiting on fetchers. + NSMutableArray *_stoppedFetchersToWaitFor; + + // For fetchers that enqueued their callbacks before stopAllFetchers was called on the service, + // set a barrier so the callbacks know to bail out. + NSDate *_stoppedAllFetchersDate; +} + +@synthesize maxRunningFetchersPerHost = _maxRunningFetchersPerHost, + configuration = _configuration, + configurationBlock = _configurationBlock, + cookieStorage = _cookieStorage, + userAgent = _userAgent, + challengeBlock = _challengeBlock, + credential = _credential, + proxyCredential = _proxyCredential, + allowedInsecureSchemes = _allowedInsecureSchemes, + allowLocalhostRequest = _allowLocalhostRequest, + allowInvalidServerCertificates = _allowInvalidServerCertificates, + retryEnabled = _retryEnabled, + retryBlock = _retryBlock, + maxRetryInterval = _maxRetryInterval, + minRetryInterval = _minRetryInterval, + properties = _properties, + unusedSessionTimeout = _unusedSessionTimeout, + testBlock = _testBlock; + +#if GTM_BACKGROUND_TASK_FETCHING +@synthesize skipBackgroundTask = _skipBackgroundTask; +#endif + +- (instancetype)init { + self = [super init]; + if (self) { + _delayedFetchersByHost = [[NSMutableDictionary alloc] init]; + _runningFetchersByHost = [[NSMutableDictionary alloc] init]; + _maxRunningFetchersPerHost = 10; + _cookieStorageMethod = -1; + _unusedSessionTimeout = 60.0; + _delegateDispatcher = + [[GTMSessionFetcherSessionDelegateDispatcher alloc] initWithParentService:self + sessionDiscardInterval:_unusedSessionTimeout]; + _callbackQueue = dispatch_get_main_queue(); + + _delegateQueue = [[NSOperationQueue alloc] init]; + _delegateQueue.maxConcurrentOperationCount = 1; + _delegateQueue.name = @"com.google.GTMSessionFetcher.NSURLSessionDelegateQueue"; + + _sessionCreationSemaphore = dispatch_semaphore_create(1); + + // Starting with the SDKs for OS X 10.11/iOS 9, the service has a default useragent. + // Apps can remove this and get the default system "CFNetwork" useragent by setting the + // fetcher service's userAgent property to nil. +#if (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_11) \ + || (TARGET_OS_IPHONE && defined(__IPHONE_9_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0) + _userAgent = GTMFetcherStandardUserAgentString(nil); +#endif + } + return self; +} + +- (void)dealloc { + [self detachAuthorizer]; + [_delegateDispatcher abandon]; +} + +#pragma mark Generate a new fetcher + +// Clients may override this method. Clients should not override any other library methods. +- (id)fetcherWithRequest:(NSURLRequest *)request + fetcherClass:(Class)fetcherClass { + GTMSessionFetcher *fetcher = [[fetcherClass alloc] initWithRequest:request + configuration:self.configuration]; + fetcher.callbackQueue = self.callbackQueue; + fetcher.sessionDelegateQueue = self.sessionDelegateQueue; + fetcher.challengeBlock = self.challengeBlock; + fetcher.credential = self.credential; + fetcher.proxyCredential = self.proxyCredential; + fetcher.authorizer = self.authorizer; + fetcher.cookieStorage = self.cookieStorage; + fetcher.allowedInsecureSchemes = self.allowedInsecureSchemes; + fetcher.allowLocalhostRequest = self.allowLocalhostRequest; + fetcher.allowInvalidServerCertificates = self.allowInvalidServerCertificates; + fetcher.configurationBlock = self.configurationBlock; + fetcher.retryEnabled = self.retryEnabled; + fetcher.retryBlock = self.retryBlock; + fetcher.maxRetryInterval = self.maxRetryInterval; + fetcher.minRetryInterval = self.minRetryInterval; + fetcher.properties = self.properties; + fetcher.service = self; + if (self.cookieStorageMethod >= 0) { + [fetcher setCookieStorageMethod:self.cookieStorageMethod]; + } + +#if GTM_BACKGROUND_TASK_FETCHING + fetcher.skipBackgroundTask = self.skipBackgroundTask; +#endif + + NSString *userAgent = self.userAgent; + if (userAgent.length > 0 + && [request valueForHTTPHeaderField:@"User-Agent"] == nil) { + [fetcher setRequestValue:userAgent + forHTTPHeaderField:@"User-Agent"]; + } + fetcher.testBlock = self.testBlock; + + return fetcher; +} + +- (GTMSessionFetcher *)fetcherWithRequest:(NSURLRequest *)request { + return [self fetcherWithRequest:request + fetcherClass:[GTMSessionFetcher class]]; +} + +- (GTMSessionFetcher *)fetcherWithURL:(NSURL *)requestURL { + return [self fetcherWithRequest:[NSURLRequest requestWithURL:requestURL]]; +} + +- (GTMSessionFetcher *)fetcherWithURLString:(NSString *)requestURLString { + NSURL *url = [NSURL URLWithString:requestURLString]; + return [self fetcherWithURL:url]; +} + +// Returns a session for the fetcher's host, or nil. +- (NSURLSession *)session { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSURLSession *session = _delegateDispatcher.session; + return session; + } +} + +// Returns a session for the fetcher's host, or nil. For shared sessions, this +// waits on a semaphore, blocking other fetchers while the caller creates the +// session if needed. +- (NSURLSession *)sessionForFetcherCreation { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + if (!_delegateDispatcher) { + // This fetcher is creating a non-shared session, so skip the semaphore usage. + return nil; + } + } + + // Wait if another fetcher is currently creating a session; avoid waiting + // inside the @synchronized block, as that can deadlock. + dispatch_semaphore_wait(_sessionCreationSemaphore, DISPATCH_TIME_FOREVER); + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + // Before getting the NSURLSession for task creation, it is + // important to invalidate and nil out the session discard timer; otherwise + // the session can be invalidated between when it is returned to the + // fetcher, and when the fetcher attempts to create its NSURLSessionTask. + [_delegateDispatcher startSessionUsage]; + + NSURLSession *session = _delegateDispatcher.session; + if (session) { + // The calling fetcher will receive a preexisting session, so + // we can allow other fetchers to create a session. + dispatch_semaphore_signal(_sessionCreationSemaphore); + } else { + // No existing session was obtained, so the calling fetcher will create the session; + // it *must* invoke fetcherDidCreateSession: to signal the dispatcher's semaphore after + // the session has been created (or fails to be created) to avoid a hang. + } + return session; + } +} + +- (id)sessionDelegate { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _delegateDispatcher; + } +} + +#pragma mark Queue Management + +- (void)addRunningFetcher:(GTMSessionFetcher *)fetcher + forHost:(NSString *)host { + // Add to the array of running fetchers for this host, creating the array if needed. + NSMutableArray *runningForHost = [_runningFetchersByHost objectForKey:host]; + if (runningForHost == nil) { + runningForHost = [NSMutableArray arrayWithObject:fetcher]; + [_runningFetchersByHost setObject:runningForHost forKey:host]; + } else { + [runningForHost addObject:fetcher]; + } +} + +- (void)addDelayedFetcher:(GTMSessionFetcher *)fetcher + forHost:(NSString *)host { + // Add to the array of delayed fetchers for this host, creating the array if needed. + NSMutableArray *delayedForHost = [_delayedFetchersByHost objectForKey:host]; + if (delayedForHost == nil) { + delayedForHost = [NSMutableArray arrayWithObject:fetcher]; + [_delayedFetchersByHost setObject:delayedForHost forKey:host]; + } else { + [delayedForHost addObject:fetcher]; + } +} + +- (BOOL)isDelayingFetcher:(GTMSessionFetcher *)fetcher { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSString *host = fetcher.request.URL.host; + if (host == nil) { + return NO; + } + NSArray *delayedForHost = [_delayedFetchersByHost objectForKey:host]; + NSUInteger idx = [delayedForHost indexOfObjectIdenticalTo:fetcher]; + BOOL isDelayed = (delayedForHost != nil) && (idx != NSNotFound); + return isDelayed; + } +} + +- (BOOL)fetcherShouldBeginFetching:(GTMSessionFetcher *)fetcher { + // Entry point from the fetcher + NSURL *requestURL = fetcher.request.URL; + NSString *host = requestURL.host; + + // Addresses "file:///path" case where localhost is the implicit host. + if (host.length == 0 && [requestURL isFileURL]) { + host = @"localhost"; + } + + if (host.length == 0) { + // Data URIs legitimately have no host, reject other hostless URLs. + GTMSESSION_ASSERT_DEBUG([[requestURL scheme] isEqual:@"data"], @"%@ lacks host", fetcher); + return YES; + } + + BOOL shouldBeginResult; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSMutableArray *runningForHost = [_runningFetchersByHost objectForKey:host]; + if (runningForHost != nil + && [runningForHost indexOfObjectIdenticalTo:fetcher] != NSNotFound) { + GTMSESSION_ASSERT_DEBUG(NO, @"%@ was already running", fetcher); + return YES; + } + + BOOL shouldRunNow = (fetcher.usingBackgroundSession + || _maxRunningFetchersPerHost == 0 + || _maxRunningFetchersPerHost > + [[self class] numberOfNonBackgroundSessionFetchers:runningForHost]); + if (shouldRunNow) { + [self addRunningFetcher:fetcher forHost:host]; + shouldBeginResult = YES; + } else { + [self addDelayedFetcher:fetcher forHost:host]; + shouldBeginResult = NO; + } + } // @synchronized(self) + + // We'll save the host that serves as the key for this fetcher's array + // to avoid any chance of the underlying request changing, stranding + // the fetcher in the wrong array + fetcher.serviceHost = host; + + return shouldBeginResult; +} + +- (void)startFetcher:(GTMSessionFetcher *)fetcher { + [fetcher beginFetchMayDelay:NO + mayAuthorize:YES]; +} + +// Internal utility. Returns a fetcher's delegate if it's a dispatcher, or nil if the fetcher +// is its own delegate (possibly via proxy) and has no dispatcher. +- (GTMSessionFetcherSessionDelegateDispatcher *)delegateDispatcherForFetcher:(GTMSessionFetcher *)fetcher { + GTMSessionCheckNotSynchronized(self); + + NSURLSession *fetcherSession = fetcher.session; + if (fetcherSession) { + id fetcherDelegate = fetcherSession.delegate; + // If the delegate is non-nil and claims to be a GTMSessionFetcher, there is no dispatcher; + // assume the fetcher is the delegate or has been proxied (some third-party frameworks + // are known to swizzle NSURLSession to proxy its delegate). + BOOL hasDispatcher = (fetcherDelegate != nil && + ![fetcherDelegate isKindOfClass:[GTMSessionFetcher class]]); + if (hasDispatcher) { + GTMSESSION_ASSERT_DEBUG([fetcherDelegate isKindOfClass:[GTMSessionFetcherSessionDelegateDispatcher class]], + @"Fetcher delegate class: %@", [fetcherDelegate class]); + return (GTMSessionFetcherSessionDelegateDispatcher *)fetcherDelegate; + } + } + return nil; +} + +- (void)fetcherDidCreateSession:(GTMSessionFetcher *)fetcher { + if (fetcher.canShareSession) { + NSURLSession *fetcherSession = fetcher.session; + GTMSESSION_ASSERT_DEBUG(fetcherSession != nil, @"Fetcher missing its session: %@", fetcher); + + GTMSessionFetcherSessionDelegateDispatcher *delegateDispatcher = + [self delegateDispatcherForFetcher:fetcher]; + if (delegateDispatcher) { + GTMSESSION_ASSERT_DEBUG(delegateDispatcher.session == nil, + @"Fetcher made an extra session: %@", fetcher); + + // Save this fetcher's session. + delegateDispatcher.session = fetcherSession; + + // Allow other fetchers to request this session now. + dispatch_semaphore_signal(_sessionCreationSemaphore); + } + } +} + +- (void)fetcherDidBeginFetching:(GTMSessionFetcher *)fetcher { + // If this fetcher has a separate delegate with a shared session, then + // this fetcher should be added to the delegate's map of tasks to fetchers. + GTMSessionFetcherSessionDelegateDispatcher *delegateDispatcher = + [self delegateDispatcherForFetcher:fetcher]; + if (delegateDispatcher) { + GTMSESSION_ASSERT_DEBUG(fetcher.canShareSession, + @"Inappropriate shared session: %@", fetcher); + + // There should already be a session, from this or a previous fetcher. + // + // Sanity check that the fetcher's session is the delegate's shared session. + NSURLSession *sharedSession = delegateDispatcher.session; + NSURLSession *fetcherSession = fetcher.session; + GTMSESSION_ASSERT_DEBUG(sharedSession != nil, @"Missing delegate session: %@", fetcher); + GTMSESSION_ASSERT_DEBUG(fetcherSession == sharedSession, + @"Inconsistent session: %@ %@ (shared: %@)", + fetcher, fetcherSession, sharedSession); + + if (sharedSession != nil && fetcherSession == sharedSession) { + NSURLSessionTask *task = fetcher.sessionTask; + GTMSESSION_ASSERT_DEBUG(task != nil, @"Missing session task: %@", fetcher); + + if (task) { + [delegateDispatcher setFetcher:fetcher + forTask:task]; + } + } + } +} + +- (void)stopFetcher:(GTMSessionFetcher *)fetcher { + [fetcher stopFetching]; +} + +- (void)fetcherDidStop:(GTMSessionFetcher *)fetcher { + // Entry point from the fetcher + NSString *host = fetcher.serviceHost; + if (!host) { + // fetcher has been stopped previously + return; + } + + // This removeFetcher: invocation is a fallback; typically, fetchers are removed from the task + // map when the task completes. + GTMSessionFetcherSessionDelegateDispatcher *delegateDispatcher = + [self delegateDispatcherForFetcher:fetcher]; + [delegateDispatcher removeFetcher:fetcher]; + + NSMutableArray *fetchersToStart; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + // If a test is waiting for all fetchers to stop, it needs to wait for this one + // to invoke its callbacks on the callback queue. + [_stoppedFetchersToWaitFor addObject:fetcher]; + + NSMutableArray *runningForHost = [_runningFetchersByHost objectForKey:host]; + [runningForHost removeObject:fetcher]; + + NSMutableArray *delayedForHost = [_delayedFetchersByHost objectForKey:host]; + [delayedForHost removeObject:fetcher]; + + while (delayedForHost.count > 0 + && [[self class] numberOfNonBackgroundSessionFetchers:runningForHost] + < _maxRunningFetchersPerHost) { + // Start another delayed fetcher running, scanning for the minimum + // priority value, defaulting to FIFO for equal priorities + GTMSessionFetcher *nextFetcher = nil; + for (GTMSessionFetcher *delayedFetcher in delayedForHost) { + if (nextFetcher == nil + || delayedFetcher.servicePriority < nextFetcher.servicePriority) { + nextFetcher = delayedFetcher; + } + } + + if (nextFetcher) { + [self addRunningFetcher:nextFetcher forHost:host]; + runningForHost = [_runningFetchersByHost objectForKey:host]; + + [delayedForHost removeObjectIdenticalTo:nextFetcher]; + + if (!fetchersToStart) { + fetchersToStart = [NSMutableArray array]; + } + [fetchersToStart addObject:nextFetcher]; + } + } + + if (runningForHost.count == 0) { + // None left; remove the empty array + [_runningFetchersByHost removeObjectForKey:host]; + } + + if (delayedForHost.count == 0) { + [_delayedFetchersByHost removeObjectForKey:host]; + } + } // @synchronized(self) + + // Start fetchers outside of the synchronized block to avoid a deadlock. + for (GTMSessionFetcher *nextFetcher in fetchersToStart) { + [self startFetcher:nextFetcher]; + } + + // The fetcher is no longer in the running or the delayed array, + // so remove its host and thread properties + fetcher.serviceHost = nil; +} + +- (NSUInteger)numberOfFetchers { + NSUInteger running = [self numberOfRunningFetchers]; + NSUInteger delayed = [self numberOfDelayedFetchers]; + return running + delayed; +} + +- (NSUInteger)numberOfRunningFetchers { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSUInteger sum = 0; + for (NSString *host in _runningFetchersByHost) { + NSArray *fetchers = [_runningFetchersByHost objectForKey:host]; + sum += fetchers.count; + } + return sum; + } +} + +- (NSUInteger)numberOfDelayedFetchers { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSUInteger sum = 0; + for (NSString *host in _delayedFetchersByHost) { + NSArray *fetchers = [_delayedFetchersByHost objectForKey:host]; + sum += fetchers.count; + } + return sum; + } +} + +- (NSArray *)issuedFetchers { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSMutableArray *allFetchers = [NSMutableArray array]; + void (^accumulateFetchers)(id, id, BOOL *) = ^(NSString *host, + NSArray *fetchersForHost, + BOOL *stop) { + [allFetchers addObjectsFromArray:fetchersForHost]; + }; + [_runningFetchersByHost enumerateKeysAndObjectsUsingBlock:accumulateFetchers]; + [_delayedFetchersByHost enumerateKeysAndObjectsUsingBlock:accumulateFetchers]; + + GTMSESSION_ASSERT_DEBUG(allFetchers.count == [NSSet setWithArray:allFetchers].count, + @"Fetcher appears multiple times\n running: %@\n delayed: %@", + _runningFetchersByHost, _delayedFetchersByHost); + + return allFetchers.count > 0 ? allFetchers : nil; + } +} + +- (NSArray *)issuedFetchersWithRequestURL:(NSURL *)requestURL { + NSString *host = requestURL.host; + if (host.length == 0) return nil; + + NSURL *targetURL = [requestURL absoluteURL]; + + NSArray *allFetchers = [self issuedFetchers]; + NSIndexSet *indexes = [allFetchers indexesOfObjectsPassingTest:^BOOL(GTMSessionFetcher *fetcher, + NSUInteger idx, + BOOL *stop) { + NSURL *fetcherURL = [fetcher.request.URL absoluteURL]; + return [fetcherURL isEqual:targetURL]; + }]; + + NSArray *result = nil; + if (indexes.count > 0) { + result = [allFetchers objectsAtIndexes:indexes]; + } + return result; +} + +- (void)stopAllFetchers { + NSArray *delayedFetchersByHost; + NSArray *runningFetchersByHost; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + // Set the time barrier so fetchers know not to call back even if + // the stop calls below occur after the fetchers naturally + // stopped and so were removed from _runningFetchersByHost, + // but while the callbacks were already enqueued before stopAllFetchers + // was invoked. + _stoppedAllFetchersDate = [[NSDate alloc] init]; + + // Remove fetchers from the delayed list to avoid fetcherDidStop: from + // starting more fetchers running as a side effect of stopping one + delayedFetchersByHost = _delayedFetchersByHost.allValues; + [_delayedFetchersByHost removeAllObjects]; + + runningFetchersByHost = _runningFetchersByHost.allValues; + [_runningFetchersByHost removeAllObjects]; + } + + for (NSArray *delayedForHost in delayedFetchersByHost) { + for (GTMSessionFetcher *fetcher in delayedForHost) { + [self stopFetcher:fetcher]; + } + } + + for (NSArray *runningForHost in runningFetchersByHost) { + for (GTMSessionFetcher *fetcher in runningForHost) { + [self stopFetcher:fetcher]; + } + } +} + +- (NSDate *)stoppedAllFetchersDate { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _stoppedAllFetchersDate; + } +} + +#pragma mark Accessors + +- (BOOL)reuseSession { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _delegateDispatcher != nil; + } +} + +- (void)setReuseSession:(BOOL)shouldReuse { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + BOOL wasReusing = (_delegateDispatcher != nil); + if (shouldReuse != wasReusing) { + [self abandonDispatcher]; + if (shouldReuse) { + _delegateDispatcher = + [[GTMSessionFetcherSessionDelegateDispatcher alloc] initWithParentService:self + sessionDiscardInterval:_unusedSessionTimeout]; + } else { + _delegateDispatcher = nil; + } + } + } +} + +- (void)resetSession { + GTMSessionCheckNotSynchronized(self); + dispatch_semaphore_wait(_sessionCreationSemaphore, DISPATCH_TIME_FOREVER); + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + [self resetSessionInternal]; + } + + dispatch_semaphore_signal(_sessionCreationSemaphore); +} + +- (void)resetSessionInternal { + GTMSessionCheckSynchronized(self); + + // The old dispatchers may be retained as delegates of any ongoing sessions by those sessions. + if (_delegateDispatcher) { + [self abandonDispatcher]; + _delegateDispatcher = + [[GTMSessionFetcherSessionDelegateDispatcher alloc] initWithParentService:self + sessionDiscardInterval:_unusedSessionTimeout]; + } +} + +- (void)resetSessionForDispatcherDiscardTimer:(NSTimer *)timer { + GTMSessionCheckNotSynchronized(self); + + dispatch_semaphore_wait(_sessionCreationSemaphore, DISPATCH_TIME_FOREVER); + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_delegateDispatcher.discardTimer == timer) { + // If the delegate dispatcher's current discardTimer is the same object as the timer + // that fired, no fetcher has recently attempted to start using the session by calling + // startSessionUsage, which invalidates and nils out the timer. + [self resetSessionInternal]; + } else { + // A fetcher has invalidated the timer between its triggering and now, potentially + // meaning a fetcher has requested access to the NSURLSession, and may be in the process + // of starting a new task. The dispatcher should not be abandoned, as this can lead + // to a race condition between calling -finishTasksAndInvalidate on the NSURLSession + // and the fetcher attempting to create a new task. + } + } + + dispatch_semaphore_signal(_sessionCreationSemaphore); +} + +- (NSTimeInterval)unusedSessionTimeout { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _unusedSessionTimeout; + } +} + +- (void)setUnusedSessionTimeout:(NSTimeInterval)timeout { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _unusedSessionTimeout = timeout; + _delegateDispatcher.discardInterval = timeout; + } +} + +// This method should be called inside of @synchronized(self) +- (void)abandonDispatcher { + GTMSessionCheckSynchronized(self); + [_delegateDispatcher abandon]; +} + +- (NSDictionary *)runningFetchersByHost { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return [_runningFetchersByHost copy]; + } +} + +- (void)setRunningFetchersByHost:(NSDictionary *)dict { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _runningFetchersByHost = [dict mutableCopy]; + } +} + +- (NSDictionary *)delayedFetchersByHost { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return [_delayedFetchersByHost copy]; + } +} + +- (void)setDelayedFetchersByHost:(NSDictionary *)dict { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _delayedFetchersByHost = [dict mutableCopy]; + } +} + +- (id)authorizer { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _authorizer; + } +} + +- (void)setAuthorizer:(id)obj { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (obj != _authorizer) { + [self detachAuthorizer]; + } + + _authorizer = obj; + } + + // Use the fetcher service for the authorization fetches if the auth + // object supports fetcher services + if ([obj respondsToSelector:@selector(setFetcherService:)]) { +#if GTM_USE_SESSION_FETCHER + [obj setFetcherService:self]; +#else + [obj setFetcherService:(id)self]; +#endif + } +} + +// This should be called inside a @synchronized(self) block except during dealloc. +- (void)detachAuthorizer { + // This method is called by the fetcher service's dealloc and setAuthorizer: + // methods; do not override. + // + // The fetcher service retains the authorizer, and the authorizer has a + // weak pointer to the fetcher service (a non-zeroing pointer for + // compatibility with iOS 4 and Mac OS X 10.5/10.6.) + // + // When this fetcher service no longer uses the authorizer, we want to remove + // the authorizer's dependence on the fetcher service. Authorizers can still + // function without a fetcher service. + if ([_authorizer respondsToSelector:@selector(fetcherService)]) { + id authFetcherService = [_authorizer fetcherService]; + if (authFetcherService == self) { + [_authorizer setFetcherService:nil]; + } + } +} + +- (dispatch_queue_t GTM_NONNULL_TYPE)callbackQueue { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _callbackQueue; + } // @synchronized(self) +} + +- (void)setCallbackQueue:(dispatch_queue_t GTM_NULLABLE_TYPE)queue { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _callbackQueue = queue ?: dispatch_get_main_queue(); + } // @synchronized(self) +} + +- (NSOperationQueue * GTM_NONNULL_TYPE)sessionDelegateQueue { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _delegateQueue; + } // @synchronized(self) +} + +- (void)setSessionDelegateQueue:(NSOperationQueue * GTM_NULLABLE_TYPE)queue { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _delegateQueue = queue ?: [NSOperationQueue mainQueue]; + } // @synchronized(self) +} + +- (NSOperationQueue *)delegateQueue { + // Provided for compatibility with the old fetcher service. The gtm-oauth2 code respects + // any custom delegate queue for calling the app. + return nil; +} + ++ (NSUInteger)numberOfNonBackgroundSessionFetchers:(NSArray *)fetchers { + NSUInteger sum = 0; + for (GTMSessionFetcher *fetcher in fetchers) { + if (!fetcher.usingBackgroundSession) { + ++sum; + } + } + return sum; +} + +@end + +@implementation GTMSessionFetcherService (TestingSupport) + ++ (instancetype)mockFetcherServiceWithFakedData:(NSData *)fakedDataOrNil + fakedError:(NSError *)fakedErrorOrNil { +#if !GTM_DISABLE_FETCHER_TEST_BLOCK + NSURL *url = [NSURL URLWithString:@"http://example.invalid"]; + NSHTTPURLResponse *fakedResponse = + [[NSHTTPURLResponse alloc] initWithURL:url + statusCode:(fakedErrorOrNil ? 500 : 200) + HTTPVersion:@"HTTP/1.1" + headerFields:nil]; + return [self mockFetcherServiceWithFakedData:fakedDataOrNil + fakedResponse:fakedResponse + fakedError:fakedErrorOrNil]; +#else + GTMSESSION_ASSERT_DEBUG(0, @"Test blocks disabled"); + return nil; +#endif // GTM_DISABLE_FETCHER_TEST_BLOCK +} + ++ (instancetype)mockFetcherServiceWithFakedData:(NSData *)fakedDataOrNil + fakedResponse:(NSHTTPURLResponse *)fakedResponse + fakedError:(NSError *)fakedErrorOrNil { +#if !GTM_DISABLE_FETCHER_TEST_BLOCK + GTMSessionFetcherService *service = [[self alloc] init]; + service.allowedInsecureSchemes = @[ @"http" ]; + service.testBlock = ^(GTMSessionFetcher *fetcherToTest, + GTMSessionFetcherTestResponse testResponse) { + testResponse(fakedResponse, fakedDataOrNil, fakedErrorOrNil); + }; + return service; +#else + GTMSESSION_ASSERT_DEBUG(0, @"Test blocks disabled"); + return nil; +#endif // GTM_DISABLE_FETCHER_TEST_BLOCK +} + +#pragma mark Synchronous Wait for Unit Testing + +- (BOOL)waitForCompletionOfAllFetchersWithTimeout:(NSTimeInterval)timeoutInSeconds { + NSDate *giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds]; + _stoppedFetchersToWaitFor = [NSMutableArray array]; + + BOOL shouldSpinRunLoop = [NSThread isMainThread]; + const NSTimeInterval kSpinInterval = 0.001; + BOOL didTimeOut = NO; + while (([self numberOfFetchers] > 0 || _stoppedFetchersToWaitFor.count > 0)) { + didTimeOut = [giveUpDate timeIntervalSinceNow] < 0; + if (didTimeOut) break; + + GTMSessionFetcher *stoppedFetcher = _stoppedFetchersToWaitFor.firstObject; + if (stoppedFetcher) { + [_stoppedFetchersToWaitFor removeObject:stoppedFetcher]; + [stoppedFetcher waitForCompletionWithTimeout:10.0 * kSpinInterval]; + } + + if (shouldSpinRunLoop) { + NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:kSpinInterval]; + [[NSRunLoop currentRunLoop] runUntilDate:stopDate]; + } else { + [NSThread sleepForTimeInterval:kSpinInterval]; + } + } + _stoppedFetchersToWaitFor = nil; + + return !didTimeOut; +} + +@end + +@implementation GTMSessionFetcherService (BackwardsCompatibilityOnly) + +- (NSInteger)cookieStorageMethod { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _cookieStorageMethod; + } +} + +- (void)setCookieStorageMethod:(NSInteger)cookieStorageMethod { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _cookieStorageMethod = cookieStorageMethod; + } +} + +@end + +@implementation GTMSessionFetcherSessionDelegateDispatcher { + __weak GTMSessionFetcherService *_parentService; + NSURLSession *_session; + + // The task map maps NSURLSessionTasks to GTMSessionFetchers + NSMutableDictionary *_taskToFetcherMap; + // The discard timer will invalidate sessions after the session's last task completes. + NSTimer *_discardTimer; + NSTimeInterval _discardInterval; +} + +@synthesize discardInterval = _discardInterval, + session = _session; + +- (instancetype)init { + [self doesNotRecognizeSelector:_cmd]; + return nil; +} + +- (instancetype)initWithParentService:(GTMSessionFetcherService *)parentService + sessionDiscardInterval:(NSTimeInterval)discardInterval { + self = [super init]; + if (self) { + _discardInterval = discardInterval; + _parentService = parentService; + } + return self; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"%@ %p %@ %@", + [self class], self, + _session ?: @"", + _taskToFetcherMap.count > 0 ? _taskToFetcherMap : @""]; +} + +- (NSTimer *)discardTimer { + GTMSessionCheckNotSynchronized(self); + @synchronized(self) { + return _discardTimer; + } +} + +// This method should be called inside of a @synchronized(self) block. +- (void)startDiscardTimer { + GTMSessionCheckSynchronized(self); + [_discardTimer invalidate]; + _discardTimer = nil; + if (_discardInterval > 0) { + _discardTimer = [NSTimer timerWithTimeInterval:_discardInterval + target:self + selector:@selector(discardTimerFired:) + userInfo:nil + repeats:NO]; + [_discardTimer setTolerance:(_discardInterval / 10)]; + [[NSRunLoop mainRunLoop] addTimer:_discardTimer forMode:NSRunLoopCommonModes]; + } +} + +// This method should be called inside of a @synchronized(self) block. +- (void)destroyDiscardTimer { + GTMSessionCheckSynchronized(self); + [_discardTimer invalidate]; + _discardTimer = nil; +} + +- (void)discardTimerFired:(NSTimer *)timer { + GTMSessionFetcherService *service; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSUInteger numberOfTasks = _taskToFetcherMap.count; + if (numberOfTasks == 0) { + service = _parentService; + } + } + + // Inform the service that the discard timer has fired, and should check whether the + // service can abandon us. -resetSession cannot be called directly, as there is a + // race condition that must be guarded against with the NSURLSession being returned + // from sessionForFetcherCreation outside other locks. The service can take steps + // to prevent resetting the session if that has occurred. + // + // The service must be called from outside the @synchronized block. + [service resetSessionForDispatcherDiscardTimer:timer]; +} + +- (void)abandon { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + [self destroySessionAndTimer]; + } +} + +- (void)startSessionUsage { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + [self destroyDiscardTimer]; + } +} + +// This method should be called inside of a @synchronized(self) block. +- (void)destroySessionAndTimer { + GTMSessionCheckSynchronized(self); + [self destroyDiscardTimer]; + + // Break any retain cycle from the session holding the delegate. + [_session finishTasksAndInvalidate]; + + // Immediately clear the session so no new task may be issued with it. + // + // The _taskToFetcherMap needs to stay valid until the outstanding tasks finish. + _session = nil; +} + +- (void)setFetcher:(GTMSessionFetcher *)fetcher forTask:(NSURLSessionTask *)task { + GTMSESSION_ASSERT_DEBUG(fetcher != nil, @"missing fetcher"); + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_taskToFetcherMap == nil) { + _taskToFetcherMap = [[NSMutableDictionary alloc] init]; + } + + if (fetcher) { + [_taskToFetcherMap setObject:fetcher forKey:task]; + [self destroyDiscardTimer]; + } + } +} + +- (void)removeFetcher:(GTMSessionFetcher *)fetcher { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + // Typically, a fetcher should be removed when its task invokes + // URLSession:task:didCompleteWithError:. + // + // When fetching with a testBlock, though, the task completed delegate + // method may not be invoked, requiring cleanup here. + NSArray *tasks = [_taskToFetcherMap allKeysForObject:fetcher]; + GTMSESSION_ASSERT_DEBUG(tasks.count <= 1, @"fetcher task not unmapped: %@", tasks); + [_taskToFetcherMap removeObjectsForKeys:tasks]; + + if (_taskToFetcherMap.count == 0) { + [self startDiscardTimer]; + } + } +} + +// This helper method provides synchronized access to the task map for the delegate +// methods below. +- (id)fetcherForTask:(NSURLSessionTask *)task { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return [_taskToFetcherMap objectForKey:task]; + } +} + +- (void)removeTaskFromMap:(NSURLSessionTask *)task { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + [_taskToFetcherMap removeObjectForKey:task]; + } +} + +- (void)setSession:(NSURLSession *)session { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _session = session; + } +} + +- (NSURLSession *)session { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _session; + } +} + +- (NSTimeInterval)discardInterval { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _discardInterval; + } +} + +- (void)setDiscardInterval:(NSTimeInterval)interval { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _discardInterval = interval; + } +} + +// NSURLSessionDelegate protocol methods. + +// - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session; +// +// TODO(seh): How do we route this to an appropriate fetcher? + + +- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error { + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ didBecomeInvalidWithError:%@", + [self class], self, session, error); + NSDictionary *localTaskToFetcherMap; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _session = nil; + + localTaskToFetcherMap = [_taskToFetcherMap copy]; + } + + // Any "suspended" tasks may not have received callbacks from NSURLSession when the session + // completes; we'll call them now. + [localTaskToFetcherMap enumerateKeysAndObjectsUsingBlock:^(NSURLSessionTask *task, + GTMSessionFetcher *fetcher, + BOOL *stop) { + if (fetcher.session == session) { + // Our delegate method URLSession:task:didCompleteWithError: will rely on + // _taskToFetcherMap so that should still contain this fetcher. + NSError *canceledError = [NSError errorWithDomain:NSURLErrorDomain + code:NSURLErrorCancelled + userInfo:nil]; + [self URLSession:session task:task didCompleteWithError:canceledError]; + } else { + GTMSESSION_ASSERT_DEBUG(0, @"Unexpected session in fetcher: %@ has %@ (expected %@)", + fetcher, fetcher.session, session); + } + }]; + + // Our tests rely on this notification to know the session discard timer fired. + NSDictionary *userInfo = @{ kGTMSessionFetcherServiceSessionKey : session }; + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + [nc postNotificationName:kGTMSessionFetcherServiceSessionBecameInvalidNotification + object:_parentService + userInfo:userInfo]; +} + + +#pragma mark - NSURLSessionTaskDelegate + +// NSURLSessionTaskDelegate protocol methods. +// +// We won't test here if the fetcher responds to these since we only want this +// class to implement the same delegate methods the fetcher does (so NSURLSession's +// tests for respondsToSelector: will have the same result whether the session +// delegate is the fetcher or this dispatcher.) + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task +willPerformHTTPRedirection:(NSHTTPURLResponse *)response + newRequest:(NSURLRequest *)request + completionHandler:(void (^)(NSURLRequest *))completionHandler { + id fetcher = [self fetcherForTask:task]; + [fetcher URLSession:session + task:task +willPerformHTTPRedirection:response + newRequest:request + completionHandler:completionHandler]; +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task +didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge + completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))handler { + id fetcher = [self fetcherForTask:task]; + [fetcher URLSession:session + task:task + didReceiveChallenge:challenge + completionHandler:handler]; +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + needNewBodyStream:(void (^)(NSInputStream *bodyStream))handler { + id fetcher = [self fetcherForTask:task]; + [fetcher URLSession:session + task:task + needNewBodyStream:handler]; +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + didSendBodyData:(int64_t)bytesSent + totalBytesSent:(int64_t)totalBytesSent +totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { + id fetcher = [self fetcherForTask:task]; + [fetcher URLSession:session + task:task + didSendBodyData:bytesSent + totalBytesSent:totalBytesSent +totalBytesExpectedToSend:totalBytesExpectedToSend]; +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task +didCompleteWithError:(NSError *)error { + id fetcher = [self fetcherForTask:task]; + + // This is the usual way tasks are removed from the task map. + [self removeTaskFromMap:task]; + + [fetcher URLSession:session + task:task + didCompleteWithError:error]; +} + +// NSURLSessionDataDelegate protocol methods. + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask +didReceiveResponse:(NSURLResponse *)response + completionHandler:(void (^)(NSURLSessionResponseDisposition))handler { + id fetcher = [self fetcherForTask:dataTask]; + [fetcher URLSession:session + dataTask:dataTask + didReceiveResponse:response + completionHandler:handler]; +} + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask +didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask { + id fetcher = [self fetcherForTask:dataTask]; + GTMSESSION_ASSERT_DEBUG(fetcher != nil, @"Missing fetcher for %@", dataTask); + [self removeTaskFromMap:dataTask]; + if (fetcher) { + GTMSESSION_ASSERT_DEBUG([fetcher isKindOfClass:[GTMSessionFetcher class]], + @"Expecting GTMSessionFetcher"); + [self setFetcher:(GTMSessionFetcher *)fetcher forTask:downloadTask]; + } + + [fetcher URLSession:session + dataTask:dataTask +didBecomeDownloadTask:downloadTask]; +} + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask + didReceiveData:(NSData *)data { + id fetcher = [self fetcherForTask:dataTask]; + [fetcher URLSession:session + dataTask:dataTask + didReceiveData:data]; +} + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask + willCacheResponse:(NSCachedURLResponse *)proposedResponse + completionHandler:(void (^)(NSCachedURLResponse *))handler { + id fetcher = [self fetcherForTask:dataTask]; + [fetcher URLSession:session + dataTask:dataTask + willCacheResponse:proposedResponse + completionHandler:handler]; +} + +// NSURLSessionDownloadDelegate protocol methods. + +- (void)URLSession:(NSURLSession *)session + downloadTask:(NSURLSessionDownloadTask *)downloadTask +didFinishDownloadingToURL:(NSURL *)location { + id fetcher = [self fetcherForTask:downloadTask]; + [fetcher URLSession:session + downloadTask:downloadTask +didFinishDownloadingToURL:location]; +} + +- (void)URLSession:(NSURLSession *)session + downloadTask:(NSURLSessionDownloadTask *)downloadTask + didWriteData:(int64_t)bytesWritten + totalBytesWritten:(int64_t)totalWritten +totalBytesExpectedToWrite:(int64_t)totalExpected { + id fetcher = [self fetcherForTask:downloadTask]; + [fetcher URLSession:session + downloadTask:downloadTask + didWriteData:bytesWritten + totalBytesWritten:totalWritten +totalBytesExpectedToWrite:totalExpected]; +} + +- (void)URLSession:(NSURLSession *)session + downloadTask:(NSURLSessionDownloadTask *)downloadTask + didResumeAtOffset:(int64_t)fileOffset +expectedTotalBytes:(int64_t)expectedTotalBytes { + id fetcher = [self fetcherForTask:downloadTask]; + [fetcher URLSession:session + downloadTask:downloadTask + didResumeAtOffset:fileOffset + expectedTotalBytes:expectedTotalBytes]; +} + +@end diff --git a/!main project/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.h b/!main project/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.h new file mode 100644 index 0000000..2f9023a --- /dev/null +++ b/!main project/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.h @@ -0,0 +1,175 @@ +/* Copyright 2014 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// GTMSessionUploadFetcher implements Google's resumable upload protocol. + +// +// This subclass of GTMSessionFetcher simulates the series of fetches +// needed for chunked upload as a single fetch operation. +// +// Protocol document: TBD +// +// To the client, the only fetcher that exists is this class; the subsidiary +// fetchers needed for uploading chunks are not visible (though the most recent +// chunk fetcher may be accessed via the -activeFetcher or -chunkFetcher methods, and +// -responseHeaders and -statusCode reflect results from the most recent chunk +// fetcher.) +// +// Chunk fetchers are discarded as soon as they have completed. +// +// The protocol also allows for a cancellation notification request to be sent to the +// server to allow discarding of the currently uploaded data and this will be sent +// automatically upon calling stopFetching if the upload has already started. +// +// Note: Unlike the fetcher superclass, the methods of GTMSessionUploadFetcher should +// only be used from the main thread until further work is done to make this subclass +// thread-safe. + +#import "GTMSessionFetcher.h" +#import "GTMSessionFetcherService.h" + +GTM_ASSUME_NONNULL_BEGIN + +// The value to use for file size parameters when the file size is not yet known. +extern int64_t const kGTMSessionUploadFetcherUnknownFileSize; + +// Unless an application knows it needs a smaller chunk size, it should use the standard +// chunk size, which sends the entire file as a single chunk to minimize upload overhead. +// Setting an explicit chunk size that comfortably fits in memory is advisable for large +// uploads. +extern int64_t const kGTMSessionUploadFetcherStandardChunkSize; + +// When uploading requires data buffer allocations (such as uploading from an NSData or +// an NSFileHandle) this is the maximum buffer size that will be created by the fetcher. +extern int64_t const kGTMSessionUploadFetcherMaximumDemandBufferSize; + +// Notification that the upload location URL was provided by the server. +extern NSString *const kGTMSessionFetcherUploadLocationObtainedNotification; + +// Block to provide data during uploads. +// +// Response data may be allocated with dataWithBytesNoCopy:length:freeWhenDone: for efficiency, +// and released after the response block returns. +// +// If the length of the file being uploaded is unknown or already set, send +// kGTMSessionUploadFetcherUnknownFileSize for |fullUploadLength|. Otherwise, set |fullUploadLength| +// to its proper value. +// +// Pass nil as the data (and optionally an NSError) for a failure. +typedef void (^GTMSessionUploadFetcherDataProviderResponse)(NSData * GTM_NULLABLE_TYPE data, + int64_t fullUploadLength, + NSError * GTM_NULLABLE_TYPE error); +// Do not call the response with an NSData object with less data than the requested length unless +// you are passing the fullUploadLength to the fetcher for the first time and it is the last chunk +// of data in the file being uploaded. +typedef void (^GTMSessionUploadFetcherDataProvider)(int64_t offset, int64_t length, + GTMSessionUploadFetcherDataProviderResponse response); + +// Block to be notified about the final status of the cancellation request started in stopFetching. +// +// |fetcher| will be the cancel request that was sent to the server, or nil if stopFetching is not +// going to send a cancel request. If |fetcher| is provided, the other parameters correspond to the +// completion handler of the cancellation request fetcher. +typedef void (^GTMSessionUploadFetcherCancellationHandler)( + GTMSessionFetcher * GTM_NULLABLE_TYPE fetcher, + NSData * GTM_NULLABLE_TYPE data, + NSError * GTM_NULLABLE_TYPE error); + +@interface GTMSessionUploadFetcher : GTMSessionFetcher + +// Create an upload fetcher specifying either the request or the resume location URL, +// then set an upload data source using one of these: +// +// setUploadFileURL: +// setUploadDataLength:provider: +// setUploadFileHandle: +// setUploadData: + ++ (instancetype)uploadFetcherWithRequest:(NSURLRequest *)request + uploadMIMEType:(NSString *)uploadMIMEType + chunkSize:(int64_t)chunkSize + fetcherService:(GTM_NULLABLE GTMSessionFetcherService *)fetcherServiceOrNil; + +// Allows cellular access. ++ (instancetype)uploadFetcherWithLocation:(NSURL * GTM_NULLABLE_TYPE)uploadLocationURL + uploadMIMEType:(NSString *)uploadMIMEType + chunkSize:(int64_t)chunkSize + fetcherService:(GTM_NULLABLE GTMSessionFetcherService *)fetcherServiceOrNil; + ++ (instancetype)uploadFetcherWithLocation:(NSURL *GTM_NULLABLE_TYPE)uploadLocationURL + uploadMIMEType:(NSString *)uploadMIMEType + chunkSize:(int64_t)chunkSize + allowsCellularAccess:(BOOL)allowsCellularAccess + fetcherService:(GTM_NULLABLE GTMSessionFetcherService *)fetcherServiceOrNil; + +// Allows dataProviders for files of unknown length. Pass kGTMSessionUploadFetcherUnknownFileSize as +// |fullLength| if the length is unknown. +- (void)setUploadDataLength:(int64_t)fullLength + provider:(GTM_NULLABLE GTMSessionUploadFetcherDataProvider)block; + ++ (NSArray *)uploadFetchersForBackgroundSessions; ++ (GTM_NULLABLE instancetype)uploadFetcherForSessionIdentifier:(NSString *)sessionIdentifier; + +- (void)pauseFetching; +- (void)resumeFetching; +- (BOOL)isPaused; + +@property(atomic, strong, GTM_NULLABLE) NSURL *uploadLocationURL; +@property(atomic, strong, GTM_NULLABLE) NSData *uploadData; +@property(atomic, strong, GTM_NULLABLE) NSURL *uploadFileURL; +@property(atomic, strong, GTM_NULLABLE) NSFileHandle *uploadFileHandle; +@property(atomic, copy, readonly, GTM_NULLABLE) GTMSessionUploadFetcherDataProvider uploadDataProvider; +@property(atomic, copy) NSString *uploadMIMEType; +@property(atomic, readonly, assign) int64_t chunkSize; +@property(atomic, readonly, assign) int64_t currentOffset; +// Reflects the original NSURLRequest's @c allowCellularAccess property. +@property(atomic, readonly, assign) BOOL allowsCellularAccess; + +// The fetcher for the current data chunk, if any +@property(atomic, strong, GTM_NULLABLE) GTMSessionFetcher *chunkFetcher; + +// The active fetcher is the current chunk fetcher, or the upload fetcher itself +// if no chunk fetcher has yet been created. +@property(atomic, readonly) GTMSessionFetcher *activeFetcher; + +// The last request made by an active fetcher. Useful for testing. +@property(atomic, readonly, GTM_NULLABLE) NSURLRequest *lastChunkRequest; + +// The status code from the most recently-completed fetch. +@property(atomic, assign) NSInteger statusCode; + +// Invoked as part of the stop fetching process. Invoked immediately if there is no upload in +// progress, otherwise invoked with the results of the attempt to notify the server that the +// upload will not continue. +// +// Unlike other callbacks, since this is related specifically to the stopFetching flow it is not +// cleared by stopFetching. It will instead clear itself after it is invoked or if the completion +// has occured before stopFetching is called. +@property(atomic, copy, GTM_NULLABLE) GTMSessionUploadFetcherCancellationHandler + cancellationHandler; + +// Exposed for testing only. +@property(atomic, readonly, GTM_NULLABLE) dispatch_queue_t delegateCallbackQueue; +@property(atomic, readonly, GTM_NULLABLE) GTMSessionFetcherCompletionHandler delegateCompletionHandler; + +@end + +@interface GTMSessionFetcher (GTMSessionUploadFetcherMethods) + +@property(readonly, GTM_NULLABLE) GTMSessionUploadFetcher *parentUploadFetcher; + +@end + +GTM_ASSUME_NONNULL_END diff --git a/!main project/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.m b/!main project/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.m new file mode 100644 index 0000000..4ea43f6 --- /dev/null +++ b/!main project/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.m @@ -0,0 +1,1985 @@ +/* Copyright 2014 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "GTMSessionUploadFetcher.h" + +static NSString *const kGTMSessionIdentifierIsUploadChunkFetcherMetadataKey = @"_upChunk"; +static NSString *const kGTMSessionIdentifierUploadFileURLMetadataKey = @"_upFileURL"; +static NSString *const kGTMSessionIdentifierUploadFileLengthMetadataKey = @"_upFileLen"; +static NSString *const kGTMSessionIdentifierUploadLocationURLMetadataKey = @"_upLocURL"; +static NSString *const kGTMSessionIdentifierUploadMIMETypeMetadataKey = @"_uploadMIME"; +static NSString *const kGTMSessionIdentifierUploadChunkSizeMetadataKey = @"_upChSize"; +static NSString *const kGTMSessionIdentifierUploadCurrentOffsetMetadataKey = @"_upOffset"; +static NSString *const kGTMSessionIdentifierUploadAllowsCellularAccess = @"_upAllowsCellularAccess"; + +static NSString *const kGTMSessionHeaderXGoogUploadChunkGranularity = @"X-Goog-Upload-Chunk-Granularity"; +static NSString *const kGTMSessionHeaderXGoogUploadCommand = @"X-Goog-Upload-Command"; +static NSString *const kGTMSessionHeaderXGoogUploadContentLength = @"X-Goog-Upload-Content-Length"; +static NSString *const kGTMSessionHeaderXGoogUploadContentType = @"X-Goog-Upload-Content-Type"; +static NSString *const kGTMSessionHeaderXGoogUploadOffset = @"X-Goog-Upload-Offset"; +static NSString *const kGTMSessionHeaderXGoogUploadProtocol = @"X-Goog-Upload-Protocol"; +static NSString *const kGTMSessionXGoogUploadProtocolResumable = @"resumable"; +static NSString *const kGTMSessionHeaderXGoogUploadSizeReceived = @"X-Goog-Upload-Size-Received"; +static NSString *const kGTMSessionHeaderXGoogUploadStatus = @"X-Goog-Upload-Status"; +static NSString *const kGTMSessionHeaderXGoogUploadURL = @"X-Goog-Upload-URL"; + +// Property of chunk fetchers identifying the parent upload fetcher. Non-retained NSValue. +static NSString *const kGTMSessionUploadFetcherChunkParentKey = @"_uploadFetcherChunkParent"; + +int64_t const kGTMSessionUploadFetcherUnknownFileSize = -1; + +int64_t const kGTMSessionUploadFetcherStandardChunkSize = (int64_t)LLONG_MAX; + +#if TARGET_OS_IPHONE +int64_t const kGTMSessionUploadFetcherMaximumDemandBufferSize = 10 * 1024 * 1024; // 10 MB for iOS, watchOS, tvOS +#else +int64_t const kGTMSessionUploadFetcherMaximumDemandBufferSize = 100 * 1024 * 1024; // 100 MB for macOS +#endif + +typedef NS_ENUM(NSUInteger, GTMSessionUploadFetcherStatus) { + kStatusUnknown, + kStatusActive, + kStatusFinal, + kStatusCancelled, +}; + +NSString *const kGTMSessionFetcherUploadLocationObtainedNotification = + @"kGTMSessionFetcherUploadLocationObtainedNotification"; + +#if !GTMSESSION_BUILD_COMBINED_SOURCES +@interface GTMSessionFetcher (ProtectedMethods) + +// Access to non-public method on the parent fetcher class. +- (void)stopFetchReleasingCallbacks:(BOOL)shouldReleaseCallbacks; +- (void)createSessionIdentifierWithMetadata:(NSDictionary *)metadata; +- (GTMSessionFetcherCompletionHandler)completionHandlerWithTarget:(id)target + didFinishSelector:(SEL)finishedSelector; +- (void)invokeOnCallbackQueue:(dispatch_queue_t)callbackQueue + afterUserStopped:(BOOL)afterStopped + block:(void (^)(void))block; +- (NSTimer *)retryTimer; +- (void)beginFetchForRetry; + +@property(readwrite, strong) NSData *downloadedData; +- (void)releaseCallbacks; + +- (NSInteger)statusCodeUnsynchronized; + +- (BOOL)userStoppedFetching; + +@end +#endif // !GTMSESSION_BUILD_COMBINED_SOURCES + +@interface GTMSessionUploadFetcher () + +// Changing readonly to readwrite. +@property(atomic, strong, readwrite) NSURLRequest *lastChunkRequest; +@property(atomic, readwrite, assign) int64_t currentOffset; + +// Internal properties. +@property(strong, atomic, GTM_NULLABLE) GTMSessionFetcher *fetcherInFlight; // Synchronized on self. + +@property(assign, atomic, getter=isSubdataGenerating) BOOL subdataGenerating; +@property(assign, atomic) BOOL shouldInitiateOffsetQuery; +@property(assign, atomic) int64_t uploadGranularity; +@property(assign, atomic) BOOL allowsCellularAccess; + +@end + +@implementation GTMSessionUploadFetcher { + GTMSessionFetcher *_chunkFetcher; + + // We'll call through to the delegate's completion handler. + GTMSessionFetcherCompletionHandler _delegateCompletionHandler; + dispatch_queue_t _delegateCallbackQueue; + + // The initial fetch's body length and bytes actually sent are + // needed for calculating progress during subsequent chunk uploads + int64_t _initialBodyLength; + int64_t _initialBodySent; + + // The upload server address for the chunks of this upload session. + NSURL *_uploadLocationURL; + + // _uploadData, _uploadDataProvider, or _uploadFileHandle may be set, but only one. + NSData *_uploadData; + NSFileHandle *_uploadFileHandle; + GTMSessionUploadFetcherDataProvider _uploadDataProvider; + NSURL *_uploadFileURL; + int64_t _uploadFileLength; + NSString *_uploadMIMEType; + int64_t _chunkSize; + int64_t _uploadGranularity; + BOOL _isPaused; + BOOL _isRestartedUpload; + BOOL _shouldInitiateOffsetQuery; + + // Tied to useBackgroundSession property, since this property is applicable to chunk fetchers. + BOOL _useBackgroundSessionOnChunkFetchers; + + // We keep the latest offset into the upload data just for progress reporting. + int64_t _currentOffset; + + NSDictionary *_recentChunkReponseHeaders; + NSInteger _recentChunkStatusCode; + + // For waiting, we need to know the fetcher in flight, if any, and if subdata generation + // is in progress. + GTMSessionFetcher *_fetcherInFlight; + BOOL _isSubdataGenerating; + BOOL _isCancelInFlight; + + GTMSessionUploadFetcherCancellationHandler _cancellationHandler; +} + ++ (void)load { + [self uploadFetchersForBackgroundSessions]; +} + ++ (instancetype)uploadFetcherWithRequest:(NSURLRequest *)request + uploadMIMEType:(NSString *)uploadMIMEType + chunkSize:(int64_t)chunkSize + fetcherService:(GTMSessionFetcherService *)fetcherService { + GTMSessionUploadFetcher *fetcher = [self uploadFetcherWithRequest:request + fetcherService:fetcherService]; + [fetcher setLocationURL:nil + uploadMIMEType:uploadMIMEType + chunkSize:chunkSize + allowsCellularAccess:request.allowsCellularAccess]; + return fetcher; +} + ++ (instancetype)uploadFetcherWithLocation:(NSURL *GTM_NULLABLE_TYPE)uploadLocationURL + uploadMIMEType:(NSString *)uploadMIMEType + chunkSize:(int64_t)chunkSize + fetcherService:(GTM_NULLABLE GTMSessionFetcherService *)fetcherServiceOrNil { + return [self uploadFetcherWithLocation:uploadLocationURL + uploadMIMEType:uploadMIMEType + chunkSize:chunkSize + allowsCellularAccess:YES + fetcherService:fetcherServiceOrNil]; +} + ++ (instancetype)uploadFetcherWithLocation:(NSURL *GTM_NULLABLE_TYPE)uploadLocationURL + uploadMIMEType:(NSString *)uploadMIMEType + chunkSize:(int64_t)chunkSize + allowsCellularAccess:(BOOL)allowsCellularAccess + fetcherService:(GTMSessionFetcherService *)fetcherService { + GTMSessionUploadFetcher *fetcher = [self uploadFetcherWithRequest:nil + fetcherService:fetcherService]; + [fetcher setLocationURL:uploadLocationURL + uploadMIMEType:uploadMIMEType + chunkSize:chunkSize + allowsCellularAccess:allowsCellularAccess]; + return fetcher; +} + ++ (instancetype)uploadFetcherForSessionIdentifierMetadata:(NSDictionary *)metadata { + GTMSESSION_ASSERT_DEBUG( + [metadata[kGTMSessionIdentifierIsUploadChunkFetcherMetadataKey] boolValue], + @"Session identifier metadata is not for an upload fetcher: %@", metadata); + + NSNumber *uploadFileLengthNum = metadata[kGTMSessionIdentifierUploadFileLengthMetadataKey]; + GTMSESSION_ASSERT_DEBUG(uploadFileLengthNum != nil, + @"Session metadata missing an UploadFileSize"); + if (uploadFileLengthNum == nil) return nil; + + int64_t uploadFileLength = [uploadFileLengthNum longLongValue]; + GTMSESSION_ASSERT_DEBUG(uploadFileLength >= 0, @"Session metadata UploadFileSize is unknown"); + + NSString *uploadFileURLString = metadata[kGTMSessionIdentifierUploadFileURLMetadataKey]; + GTMSESSION_ASSERT_DEBUG(uploadFileURLString, @"Session metadata missing an UploadFileURL"); + if (uploadFileURLString == nil) return nil; + + NSURL *uploadFileURL = [NSURL URLWithString:uploadFileURLString]; + // There used to be a call here to NSURL checkResourceIsReachableAndReturnError: to check for the + // existence of the file (also tried NSFileManager fileExistsAtPath:). We've determined + // empirically that the check can fail at startup even when the upload file does in fact exist. + // For now, we'll go ahead and restore the background upload fetcher. If the file doesn't exist, + // it will fail later. + + NSString *uploadLocationURLString = metadata[kGTMSessionIdentifierUploadLocationURLMetadataKey]; + NSURL *uploadLocationURL = + uploadLocationURLString ? [NSURL URLWithString:uploadLocationURLString] : nil; + + NSString *uploadMIMEType = + metadata[kGTMSessionIdentifierUploadMIMETypeMetadataKey]; + int64_t uploadChunkSize = + [metadata[kGTMSessionIdentifierUploadChunkSizeMetadataKey] longLongValue]; + if (uploadChunkSize <= 0) { + uploadChunkSize = kGTMSessionUploadFetcherStandardChunkSize; + } + int64_t currentOffset = + [metadata[kGTMSessionIdentifierUploadCurrentOffsetMetadataKey] longLongValue]; + + BOOL allowsCellularAccess = YES; + if (metadata[kGTMSessionIdentifierUploadAllowsCellularAccess]) { + allowsCellularAccess = [metadata[kGTMSessionIdentifierUploadAllowsCellularAccess] boolValue]; + } + + GTMSESSION_ASSERT_DEBUG(currentOffset <= uploadFileLength, + @"CurrentOffset (%lld) exceeds UploadFileSize (%lld)", + currentOffset, uploadFileLength); + if (currentOffset > uploadFileLength) return nil; + + GTMSessionUploadFetcher *uploadFetcher = [self uploadFetcherWithLocation:uploadLocationURL + uploadMIMEType:uploadMIMEType + chunkSize:uploadChunkSize + allowsCellularAccess:allowsCellularAccess + fetcherService:nil]; + // Set the upload file length before setting the upload file URL tries to determine the length. + [uploadFetcher setUploadFileLength:uploadFileLength]; + + uploadFetcher.uploadFileURL = uploadFileURL; + uploadFetcher.sessionUserInfo = metadata; + uploadFetcher.useBackgroundSession = YES; + uploadFetcher.currentOffset = currentOffset; + uploadFetcher.delegateCallbackQueue = uploadFetcher.callbackQueue; + uploadFetcher.allowedInsecureSchemes = @[ @"http" ]; // Allowed on restored upload fetcher. + return uploadFetcher; +} + ++ (instancetype)uploadFetcherWithRequest:(NSURLRequest *)request + fetcherService:(GTMSessionFetcherService *)fetcherService { + // Internal utility method for instantiating fetchers + GTMSessionUploadFetcher *fetcher; + if ([fetcherService isKindOfClass:[GTMSessionFetcherService class]]) { + fetcher = [fetcherService fetcherWithRequest:request + fetcherClass:self]; + } else { + fetcher = [self fetcherWithRequest:request]; + } + fetcher.useBackgroundSession = YES; + return fetcher; +} + ++ (NSPointerArray *)uploadFetcherPointerArrayForBackgroundSessions { + static NSPointerArray *gUploadFetcherPointerArrayForBackgroundSessions = nil; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + gUploadFetcherPointerArrayForBackgroundSessions = [NSPointerArray weakObjectsPointerArray]; + }); + return gUploadFetcherPointerArrayForBackgroundSessions; +} + ++ (instancetype)uploadFetcherForSessionIdentifier:(NSString *)sessionIdentifier { + GTMSESSION_ASSERT_DEBUG(sessionIdentifier != nil, @"Invalid session identifier"); + NSArray *uploadFetchersForBackgroundSessions = [self uploadFetchersForBackgroundSessions]; + for (GTMSessionUploadFetcher *uploadFetcher in uploadFetchersForBackgroundSessions) { + if ([uploadFetcher.chunkFetcher.sessionIdentifier isEqual:sessionIdentifier]) { + return uploadFetcher; + } + } + return nil; +} + ++ (NSArray *)uploadFetchersForBackgroundSessions { + NSMutableSet *restoredSessionIdentifiers = [[NSMutableSet alloc] init]; + NSMutableArray *uploadFetchers = [[NSMutableArray alloc] init]; + NSPointerArray *uploadFetcherPointerArray = [self uploadFetcherPointerArrayForBackgroundSessions]; + + // Collect the background session upload fetchers that are still in memory. + @synchronized(uploadFetcherPointerArray) { + [uploadFetcherPointerArray compact]; + for (GTMSessionUploadFetcher *uploadFetcher in uploadFetcherPointerArray) { + NSString *sessionIdentifier = uploadFetcher.chunkFetcher.sessionIdentifier; + if (sessionIdentifier) { + [restoredSessionIdentifiers addObject:sessionIdentifier]; + [uploadFetchers addObject:uploadFetcher]; + } + } + } // @synchronized(uploadFetcherPointerArray) + + // The system may have other ongoing background upload sessions. Restore upload fetchers for those + // too. + NSArray *fetchers = [GTMSessionFetcher fetchersForBackgroundSessions]; + for (GTMSessionFetcher *fetcher in fetchers) { + NSString *sessionIdentifier = fetcher.sessionIdentifier; + if (!sessionIdentifier || [restoredSessionIdentifiers containsObject:sessionIdentifier]) { + continue; + } + NSDictionary *sessionIdentifierMetadata = [fetcher sessionIdentifierMetadata]; + if (sessionIdentifierMetadata == nil) { + continue; + } + if (![sessionIdentifierMetadata[kGTMSessionIdentifierIsUploadChunkFetcherMetadataKey] boolValue]) { + continue; + } + GTMSessionUploadFetcher *uploadFetcher = + [self uploadFetcherForSessionIdentifierMetadata:sessionIdentifierMetadata]; + if (uploadFetcher == nil) { + // Something went wrong with this upload fetcher, so kill the restored chunk fetcher. + [fetcher stopFetching]; + continue; + } + [uploadFetchers addObject:uploadFetcher]; + uploadFetcher->_chunkFetcher = fetcher; + uploadFetcher->_fetcherInFlight = fetcher; + [uploadFetcher attachSendProgressBlockToChunkFetcher:fetcher]; + fetcher.completionHandler = + [fetcher completionHandlerWithTarget:uploadFetcher + didFinishSelector:@selector(chunkFetcher:finishedWithData:error:)]; + + GTMSESSION_LOG_DEBUG(@"%@ restoring upload fetcher %@ for chunk fetcher %@", + [self class], uploadFetcher, fetcher); + } + return uploadFetchers; +} + +- (void)setUploadData:(NSData *)data { + BOOL changed = NO; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_uploadData != data) { + _uploadData = data; + changed = YES; + } + } + if (changed) { + [self setupRequestHeaders]; + } +} + +- (NSData *)uploadData { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _uploadData; + } +} + +- (void)setUploadFileHandle:(NSFileHandle *)fh { + BOOL changed = NO; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_uploadFileHandle != fh) { + _uploadFileHandle = fh; + changed = YES; + } + } + if (changed) { + [self setupRequestHeaders]; + } +} + +- (NSFileHandle *)uploadFileHandle { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _uploadFileHandle; + } +} + +- (void)setUploadFileURL:(NSURL *)uploadURL { + BOOL changed = NO; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_uploadFileURL != uploadURL) { + _uploadFileURL = uploadURL; + changed = YES; + } + } + if (changed) { + [self setupRequestHeaders]; + } +} + +- (NSURL *)uploadFileURL { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _uploadFileURL; + } +} + +- (void)setUploadFileLength:(int64_t)fullLength { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_uploadFileLength == kGTMSessionUploadFetcherUnknownFileSize && + fullLength != kGTMSessionUploadFetcherUnknownFileSize) { + _uploadFileLength = fullLength; + } + } +} + +- (void)setUploadDataLength:(int64_t)fullLength + provider:(GTMSessionUploadFetcherDataProvider)block { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _uploadDataProvider = [block copy]; + _uploadFileLength = fullLength; + } + [self setupRequestHeaders]; +} + +- (GTMSessionUploadFetcherDataProvider)uploadDataProvider { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _uploadDataProvider; + } +} + + +- (void)setUploadMIMEType:(NSString *)uploadMIMEType { + GTMSESSION_ASSERT_DEBUG(0, @"TODO: disallow setUploadMIMEType by making declaration readonly"); + // (and uploadMIMEType, chunksize, currentOffset) + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _uploadMIMEType = uploadMIMEType; + } +} + +- (NSString *)uploadMIMEType { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _uploadMIMEType; + } +} + +- (int64_t)chunkSize { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _chunkSize; + } +} + +- (void)setupRequestHeaders { + GTMSessionCheckNotSynchronized(self); + +#if DEBUG + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + int hasData = (_uploadData != nil) ? 1 : 0; + int hasFileHandle = (_uploadFileHandle != nil) ? 1 : 0; + int hasFileURL = (_uploadFileURL != nil) ? 1 : 0; + int hasUploadDataProvider = (_uploadDataProvider != nil) ? 1 : 0; + int numberOfSources = hasData + hasFileHandle + hasFileURL + hasUploadDataProvider; + #pragma unused(numberOfSources) + GTMSESSION_ASSERT_DEBUG(numberOfSources == 1, + @"Need just one upload source (%d)", numberOfSources); + } // @synchronized(self) +#endif + + // Add our custom headers to the initial request indicating the data + // type and total size to be delivered later in the chunk requests. + NSMutableURLRequest *mutableRequest = [self.request mutableCopy]; + + GTMSESSION_ASSERT_DEBUG((mutableRequest == nil) != (_uploadLocationURL == nil), + @"Request and location are mutually exclusive"); + if (!mutableRequest) return; + + [mutableRequest setValue:kGTMSessionXGoogUploadProtocolResumable + forHTTPHeaderField:kGTMSessionHeaderXGoogUploadProtocol]; + [mutableRequest setValue:@"start" + forHTTPHeaderField:kGTMSessionHeaderXGoogUploadCommand]; + [mutableRequest setValue:_uploadMIMEType + forHTTPHeaderField:kGTMSessionHeaderXGoogUploadContentType]; + [mutableRequest setValue:@([self fullUploadLength]).stringValue + forHTTPHeaderField:kGTMSessionHeaderXGoogUploadContentLength]; + + NSString *method = mutableRequest.HTTPMethod; + if (method == nil || [method caseInsensitiveCompare:@"GET"] == NSOrderedSame) { + [mutableRequest setHTTPMethod:@"POST"]; + } + + // Ensure the user agent header identifies this to the upload server as a + // GTMSessionUploadFetcher client. The /1 can be incremented in the unlikely circumstance + // we need to make a bug fix in the client that the server can recognize. + NSString *const kUserAgentStub = @"(GTMSUF/1)"; + NSString *userAgent = [mutableRequest valueForHTTPHeaderField:@"User-Agent"]; + if (userAgent == nil + || [userAgent rangeOfString:kUserAgentStub].location == NSNotFound) { + if (userAgent.length == 0) { + userAgent = GTMFetcherStandardUserAgentString(nil); + } + userAgent = [userAgent stringByAppendingFormat:@" %@", kUserAgentStub]; + [mutableRequest setValue:userAgent forHTTPHeaderField:@"User-Agent"]; + } + [self setRequest:mutableRequest]; +} + +- (void)setLocationURL:(NSURL *GTM_NULLABLE_TYPE)location + uploadMIMEType:(NSString *)uploadMIMEType + chunkSize:(int64_t)chunkSize + allowsCellularAccess:(BOOL)allowsCellularAccess { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + GTMSESSION_ASSERT_DEBUG(chunkSize > 0, @"chunk size is zero"); + + _allowsCellularAccess = allowsCellularAccess; + + // When resuming an upload, set the known upload target URL. + _uploadLocationURL = location; + + _uploadMIMEType = uploadMIMEType; + _chunkSize = chunkSize; + + // Indicate that we've not yet determined the file handle's length + _uploadFileLength = kGTMSessionUploadFetcherUnknownFileSize; + + // Indicate that we've not yet determined the upload fetcher status + _recentChunkStatusCode = -1; + + // If this is restarting an upload begun by another fetcher, + // the location is specified but the request is nil + _isRestartedUpload = (location != nil); + } // @synchronized(self) +} + +- (int64_t)fullUploadLength { + int64_t result; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_uploadData) { + result = (int64_t)_uploadData.length; + } else { + if (_uploadFileLength == kGTMSessionUploadFetcherUnknownFileSize) { + if (_uploadFileHandle) { + // First time through, seek to end to determine file length + _uploadFileLength = (int64_t)[_uploadFileHandle seekToEndOfFile]; + } else if (_uploadDataProvider) { + // _uploadFileLength is set when the _uploadDataProvider is set. + GTMSESSION_ASSERT_DEBUG(_uploadFileLength >= 0, @"No uploadDataProvider length set"); + } else { + NSNumber *filesizeNum; + NSError *valueError; + if ([_uploadFileURL getResourceValue:&filesizeNum + forKey:NSURLFileSizeKey + error:&valueError]) { + _uploadFileLength = filesizeNum.longLongValue; + } else { + GTMSESSION_ASSERT_DEBUG(NO, @"Cannot get file size: %@\n %@", + valueError, _uploadFileURL.path); + _uploadFileLength = 0; + } + } + } + result = _uploadFileLength; + } + } // @synchronized(self) + return result; +} + +// Make a subdata of the upload data. +- (void)generateChunkSubdataWithOffset:(int64_t)offset + length:(int64_t)length + response:(GTMSessionUploadFetcherDataProviderResponse)response { + GTMSessionUploadFetcherDataProvider uploadDataProvider = self.uploadDataProvider; + if (uploadDataProvider) { + uploadDataProvider(offset, length, response); + return; + } + + NSData *uploadData = self.uploadData; + if (uploadData) { + // NSData provided. + NSData *resultData; + if (offset == 0 && length == (int64_t)uploadData.length) { + resultData = uploadData; + } else { + int64_t dataLength = (int64_t)uploadData.length; + // Ensure our range is valid. b/18007814 + if (offset + length > dataLength) { + NSString *errorMessage = [NSString stringWithFormat: + @"Range invalid for upload data. offset: %lld\tlength: %lld\tdataLength: %lld", + offset, length, dataLength]; + GTMSESSION_ASSERT_DEBUG(NO, @"%@", errorMessage); + response(nil, + kGTMSessionUploadFetcherUnknownFileSize, + [self uploadChunkUnavailableErrorWithDescription:errorMessage]); + return; + } + NSRange range = NSMakeRange((NSUInteger)offset, (NSUInteger)length); + + @try { + resultData = [uploadData subdataWithRange:range]; + } + @catch (NSException *exception) { + NSString *errorMessage = exception.description; + GTMSESSION_ASSERT_DEBUG(NO, @"%@", errorMessage); + response(nil, + kGTMSessionUploadFetcherUnknownFileSize, + [self uploadChunkUnavailableErrorWithDescription:errorMessage]); + return; + } + } + response(resultData, kGTMSessionUploadFetcherUnknownFileSize, nil); + return; + } + NSURL *uploadFileURL = self.uploadFileURL; + if (uploadFileURL) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [self generateChunkSubdataFromFileURL:uploadFileURL + offset:offset + length:length + response:response]; + }); + return; + } + GTMSESSION_ASSERT_DEBUG(_uploadFileHandle, @"Unexpectedly missing upload data package"); + NSFileHandle *uploadFileHandle = self.uploadFileHandle; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [self generateChunkSubdataFromFileHandle:uploadFileHandle + offset:offset + length:length + response:response]; + }); +} + +- (void)generateChunkSubdataFromFileHandle:(NSFileHandle *)fileHandle + offset:(int64_t)offset + length:(int64_t)length + response:(GTMSessionUploadFetcherDataProviderResponse)response { + NSData *resultData; + NSError *error; + @try { + [fileHandle seekToFileOffset:(unsigned long long)offset]; + resultData = [fileHandle readDataOfLength:(NSUInteger)length]; + } + @catch (NSException *exception) { + GTMSESSION_ASSERT_DEBUG(NO, @"uploadFileHandle failed to read, %@", exception); + error = [self uploadChunkUnavailableErrorWithDescription:exception.description]; + } + // The response always re-dispatches to the main thread, so we skip doing that here. + response(resultData, kGTMSessionUploadFetcherUnknownFileSize, error); +} + +- (void)generateChunkSubdataFromFileURL:(NSURL *)fileURL + offset:(int64_t)offset + length:(int64_t)length + response:(GTMSessionUploadFetcherDataProviderResponse)response { + GTMSessionCheckNotSynchronized(self); + + NSData *resultData; + NSError *error; + int64_t fullUploadLength = [self fullUploadLength]; + NSData *mappedData = + [NSData dataWithContentsOfURL:fileURL + options:NSDataReadingMappedAlways + NSDataReadingUncached + error:&error]; + if (!mappedData) { + // We could not create an NSData by memory-mapping the file. +#if TARGET_IPHONE_SIMULATOR + // NSTemporaryDirectory() can differ in the simulator between app restarts, + // yet the contents for the new path remains unchanged, so try the latest temp path. + if ([error.domain isEqual:NSCocoaErrorDomain] && (error.code == NSFileReadNoSuchFileError)) { + NSString *filename = [fileURL lastPathComponent]; + NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:filename]; + NSURL *newFileURL = [NSURL fileURLWithPath:filePath]; + if (![newFileURL isEqual:fileURL]) { + [self generateChunkSubdataFromFileURL:newFileURL + offset:offset + length:length + response:response]; + return; + } + } +#endif + + // If the file is just too large to create an NSData for, or if for some other reason we can't + // map it, create an NSFileHandle instead to read a subset into an NSData. +#if DEBUG + NSNumber *fileSizeNum; + BOOL hasFileSize = [fileURL getResourceValue:&fileSizeNum forKey:NSURLFileSizeKey error:NULL]; + GTMSESSION_LOG_DEBUG(@"Note: uploadFileURL is falling back to creating upload chunks by reading" + @" an NSFileHandle since uploadFileURL failed to map the upload file," + @" file size %@, %@", + hasFileSize ? fileSizeNum : @"unknown", error); +#endif + + NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingFromURL:fileURL + error:&error]; + if (fileHandle != nil) { + [self generateChunkSubdataFromFileHandle:fileHandle + offset:offset + length:length + response:response]; + return; + } + GTMSESSION_ASSERT_DEBUG(NO, @"uploadFileURL failed to read, %@", error); + // Fall through with the error. + } else { + // Successfully created an NSData by memory-mapping the file. + if ((NSUInteger)(offset + length) > mappedData.length) { + NSString *errorMessage = [NSString stringWithFormat: + @"Range invalid for upload data. offset: %lld\tlength: %lld\tdataLength: %lld\texpected UploadLength: %lld", + offset, length, (long long)mappedData.length, fullUploadLength]; + GTMSESSION_ASSERT_DEBUG(NO, @"%@", errorMessage); + response(nil, + kGTMSessionUploadFetcherUnknownFileSize, + [self uploadChunkUnavailableErrorWithDescription:errorMessage]); + return; + } + if (offset > 0 || length < fullUploadLength) { + NSRange range = NSMakeRange((NSUInteger)offset, (NSUInteger)length); + resultData = [mappedData subdataWithRange:range]; + } else { + resultData = mappedData; + } + } + // The response always re-dispatches to the main thread, so we skip re-dispatching here. + response(resultData, kGTMSessionUploadFetcherUnknownFileSize, error); +} + +- (NSError *)uploadChunkUnavailableErrorWithDescription:(NSString *)description { + // The description in the userInfo is intended as a clue to programmers, not + // for client code to examine or rely on. + NSDictionary *userInfo = @{ @"description" : description }; + return [NSError errorWithDomain:kGTMSessionFetcherErrorDomain + code:GTMSessionFetcherErrorUploadChunkUnavailable + userInfo:userInfo]; +} + +- (NSError *)prematureFailureErrorWithUserInfo:(NSDictionary *)userInfo { + // An error for if we get an unexpected status from the upload server or + // otherwise cannot continue. This is an issue beyond the upload protocol; + // there's no way the client can do anything useful except give up. + NSError *error = [NSError errorWithDomain:kGTMSessionFetcherStatusDomain + code:501 // Not implemented + userInfo:userInfo]; + return error; +} + ++ (GTMSessionUploadFetcherStatus)uploadStatusFromResponseHeaders:(NSDictionary *)responseHeaders { + NSString *statusString = [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadStatus]; + if ([statusString isEqual:@"active"]) { + return kStatusActive; + } + if ([statusString isEqual:@"final"]) { + return kStatusFinal; + } + if ([statusString isEqual:@"cancelled"]) { + return kStatusCancelled; + } + return kStatusUnknown; +} + +#pragma mark Method overrides affecting the initial fetch only + +- (void)setCompletionHandler:(GTMSessionFetcherCompletionHandler)handler { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _delegateCompletionHandler = handler; + } +} + +- (void)setDelegateCallbackQueue:(dispatch_queue_t GTM_NULLABLE_TYPE)queue { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _delegateCallbackQueue = queue; + } +} + +- (dispatch_queue_t GTM_NULLABLE_TYPE)delegateCallbackQueue { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _delegateCallbackQueue; + } +} + +- (BOOL)isRestartedUpload { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _isRestartedUpload; + } +} + +- (GTMSessionFetcher * GTM_NULLABLE_TYPE)chunkFetcher { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _chunkFetcher; + } +} + +- (void)setChunkFetcher:(GTMSessionFetcher * GTM_NULLABLE_TYPE)fetcher { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _chunkFetcher = fetcher; + } +} + +- (void)setFetcherInFlight:(GTMSessionFetcher * GTM_NULLABLE_TYPE)fetcher { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _fetcherInFlight = fetcher; + } +} + +- (GTMSessionFetcher * GTM_NULLABLE_TYPE)fetcherInFlight { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _fetcherInFlight; + } +} + +- (void)setCancellationHandler:(GTMSessionUploadFetcherCancellationHandler GTM_NULLABLE_TYPE) + cancellationHandler { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _cancellationHandler = cancellationHandler; + } +} + +- (GTMSessionUploadFetcherCancellationHandler GTM_NULLABLE_TYPE)cancellationHandler { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _cancellationHandler; + } +} + +- (void)beginFetchForRetry { + GTMSessionCheckNotSynchronized(self); + + // Override the superclass to reset the initial body length and fetcher-in-flight, + // then call the superclass implementation. + [self setInitialBodyLength:[self bodyLength]]; + + GTMSESSION_ASSERT_DEBUG(self.fetcherInFlight == nil, @"unexpected fetcher in flight: %@", + self.fetcherInFlight); + self.fetcherInFlight = self; + [super beginFetchForRetry]; +} + +- (void)beginFetchWithCompletionHandler:(GTMSessionFetcherCompletionHandler)handler { + GTMSessionCheckNotSynchronized(self); + + [self setInitialBodyLength:[self bodyLength]]; + + // We'll hold onto the superclass's callback queue so we can invoke the handler + // even after the superclass has released the queue and its callback handler, as + // happens during auth failure. + [self setDelegateCallbackQueue:self.callbackQueue]; + self.completionHandler = handler; + + if ([self isRestartedUpload]) { + // When restarting an upload, we know the destination location for chunk fetches, + // but we need to query to find the initial offset. + if (![self isPaused]) { + [self sendQueryForUploadOffsetWithFetcherProperties:self.properties]; + } + return; + } + // We don't want to call into the client's completion block immediately + // after the finish of the initial connection (the delegate is called only + // when uploading finishes), so we substitute our own completion block to be + // called when the initial connection finishes + GTMSESSION_ASSERT_DEBUG(self.fetcherInFlight == nil, @"unexpected fetcher in flight: %@", + self.fetcherInFlight); + + self.fetcherInFlight = self; + [super beginFetchWithCompletionHandler:^(NSData *data, NSError *error) { + self.fetcherInFlight = nil; + // callback + + BOOL hasTestBlock = (self.testBlock != nil); + if (![self isRestartedUpload] && !hasTestBlock) { + if (error == nil) { + [self beginChunkFetches]; + } else { + if ([self retryTimer] == nil) { + [self invokeFinalCallbackWithData:nil + error:error + shouldInvalidateLocation:YES]; + } + } + } else { + // If there was no initial request, then this fetch is resuming some + // other uploadFetcher's initial request, and the superclass's connection + // is never used, so at this point we call the user's actual completion + // block. + if (!hasTestBlock) { + [self invokeFinalCallbackWithData:data + error:error + shouldInvalidateLocation:YES]; + } else { + // There was a test block, so we won't do chunk fetches, but we simulate obtaining + // the data to be uploaded from the upload data provider block or the file handle, + // and then call back. + [self generateChunkSubdataWithOffset:0 + length:[self fullUploadLength] + response:^(NSData *generateData, int64_t fullUploadLength, NSError *generateError) { + [self invokeFinalCallbackWithData:data + error:error + shouldInvalidateLocation:YES]; + }]; + } + } + }]; +} + +- (void)beginChunkFetches { + GTMSessionCheckNotSynchronized(self); + +#if DEBUG + // The initial response of the resumable upload protocol should have an + // empty body + // + // This assert typically happens because the upload create/edit link URL was + // not supplied with the request, and the server is thus expecting a non- + // resumable request/response. + if (self.downloadedData.length > 0) { + NSData *downloadedData = self.downloadedData; + NSString *str = [[NSString alloc] initWithData:downloadedData + encoding:NSUTF8StringEncoding]; + #pragma unused(str) + GTMSESSION_ASSERT_DEBUG(NO, @"unexpected response data (uploading to the wrong URL?)\n%@", str); + } +#endif + + // We need to get the upload URL from the location header to continue. + NSDictionary *responseHeaders = [self responseHeaders]; + + [self retrieveUploadChunkGranularityFromResponseHeaders:responseHeaders]; + + GTMSessionUploadFetcherStatus uploadStatus = + [[self class] uploadStatusFromResponseHeaders:responseHeaders]; + GTMSESSION_ASSERT_DEBUG(uploadStatus != kStatusUnknown, + @"beginChunkFetches has unexpected upload status for headers %@", responseHeaders); + + BOOL isPrematureStop = (uploadStatus == kStatusFinal) || (uploadStatus == kStatusCancelled); + + NSString *uploadLocationURLStr = [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadURL]; + BOOL hasUploadLocation = (uploadLocationURLStr.length > 0); + + if (isPrematureStop || !hasUploadLocation) { + GTMSESSION_ASSERT_DEBUG(NO, @"Premature failure: upload-status:\"%@\" location:%@", + [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadStatus], uploadLocationURLStr); + // We cannot continue since we do not know the location to use + // as our upload destination. + NSDictionary *userInfo = nil; + NSData *downloadedData = self.downloadedData; + if (downloadedData.length > 0) { + userInfo = @{ kGTMSessionFetcherStatusDataKey : downloadedData }; + } + NSError *failureError = [self prematureFailureErrorWithUserInfo:userInfo]; + [self invokeFinalCallbackWithData:nil + error:failureError + shouldInvalidateLocation:YES]; + return; + } + + self.uploadLocationURL = [NSURL URLWithString:uploadLocationURLStr]; + + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + [nc postNotificationName:kGTMSessionFetcherUploadLocationObtainedNotification + object:self]; + + // we've now sent all of the initial post body data, so we need to include + // its size in future progress indicator callbacks + [self setInitialBodySent:[self initialBodyLength]]; + + // just in case the user paused us during the initial fetch... + if (![self isPaused]) { + [self uploadNextChunkWithOffset:0]; + } +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + didSendBodyData:(int64_t)bytesSent + totalBytesSent:(int64_t)totalBytesSent + totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { + // Overrides the superclass. + [self invokeDelegateWithDidSendBytes:bytesSent + totalBytesSent:totalBytesSent + totalBytesExpectedToSend:totalBytesExpectedToSend + [self fullUploadLength]]; +} + +- (BOOL)shouldReleaseCallbacksUponCompletion { + // Overrides the superclass. + + // We don't want the superclass to release the delegate and callback + // blocks once the initial fetch has finished + // + // This is invoked for only successful completion of the connection; + // an error always will invoke and release the callbacks + return NO; +} + +- (void)invokeFinalCallbackWithData:(NSData *)data + error:(NSError *)error + shouldInvalidateLocation:(BOOL)shouldInvalidateLocation { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (shouldInvalidateLocation) { + _uploadLocationURL = nil; + } + + dispatch_queue_t queue = _delegateCallbackQueue; + GTMSessionFetcherCompletionHandler handler = _delegateCompletionHandler; + if (queue && handler) { + [self invokeOnCallbackQueue:queue + afterUserStopped:NO + block:^{ + handler(data, error); + }]; + } + } // @synchronized(self) + + [self releaseUploadAndBaseCallbacks:!self.userStoppedFetching]; +} + +- (void)releaseUploadAndBaseCallbacks:(BOOL)shouldReleaseCancellation { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _delegateCallbackQueue = nil; + _delegateCompletionHandler = nil; + _uploadDataProvider = nil; + if (shouldReleaseCancellation) { + _cancellationHandler = nil; + } + } + + // Release the base class's callbacks, too, if needed. + [self releaseCallbacks]; +} + +- (void)stopFetchReleasingCallbacks:(BOOL)shouldReleaseCallbacks { + GTMSessionCheckNotSynchronized(self); + + // Clear _fetcherInFlight when stopped. Moved from stopFetching, since that's a public method, + // where this method does the work. Fixes issue clearing value when retryBlock included. + GTMSessionFetcher *fetcherInFlight = self.fetcherInFlight; + if (fetcherInFlight == self) { + self.fetcherInFlight = nil; + } + + [super stopFetchReleasingCallbacks:shouldReleaseCallbacks]; + + if (shouldReleaseCallbacks) { + [self releaseUploadAndBaseCallbacks:NO]; + } +} + +#pragma mark Chunk fetching methods + +- (void)uploadNextChunkWithOffset:(int64_t)offset { + // use the properties in each chunk fetcher + NSDictionary *props = [self properties]; + + [self uploadNextChunkWithOffset:offset + fetcherProperties:props]; +} + +- (void)sendQueryForUploadOffsetWithFetcherProperties:(NSDictionary *)props { + GTMSessionFetcher *queryFetcher = [self uploadFetcherWithProperties:props + isQueryFetch:YES]; + queryFetcher.bodyData = [NSData data]; + + NSString *originalComment = self.comment; + [queryFetcher setCommentWithFormat:@"%@ (query offset)", + originalComment ? originalComment : @"upload"]; + + [queryFetcher setRequestValue:@"query" forHTTPHeaderField:kGTMSessionHeaderXGoogUploadCommand]; + + self.fetcherInFlight = queryFetcher; + [queryFetcher beginFetchWithDelegate:self + didFinishSelector:@selector(queryFetcher:finishedWithData:error:)]; +} + +- (void)queryFetcher:(GTMSessionFetcher *)queryFetcher + finishedWithData:(NSData *)data + error:(NSError *)error { + self.fetcherInFlight = nil; + + NSDictionary *responseHeaders = [queryFetcher responseHeaders]; + NSString *sizeReceivedHeader; + + GTMSessionUploadFetcherStatus uploadStatus = + [[self class] uploadStatusFromResponseHeaders:responseHeaders]; + GTMSESSION_ASSERT_DEBUG(uploadStatus != kStatusUnknown || error != nil, + @"query fetcher completion has unexpected upload status for headers %@", responseHeaders); + + if (error == nil) { + sizeReceivedHeader = [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadSizeReceived]; + + if (uploadStatus == kStatusCancelled || + (uploadStatus == kStatusActive && sizeReceivedHeader == nil)) { + NSDictionary *userInfo = nil; + if (data.length > 0) { + userInfo = @{ kGTMSessionFetcherStatusDataKey : data }; + } + error = [self prematureFailureErrorWithUserInfo:userInfo]; + } + } + + if (error == nil) { + int64_t offset = [sizeReceivedHeader longLongValue]; + int64_t fullUploadLength = [self fullUploadLength]; + if (uploadStatus == kStatusFinal || + (offset >= fullUploadLength && + fullUploadLength != kGTMSessionUploadFetcherUnknownFileSize)) { + // Handle we're done + [self chunkFetcher:queryFetcher finishedWithData:data error:nil]; + } else { + [self retrieveUploadChunkGranularityFromResponseHeaders:responseHeaders]; + [self uploadNextChunkWithOffset:offset]; + } + } else { + // Handle query error + [self chunkFetcher:queryFetcher finishedWithData:data error:error]; + } +} + +- (void)sendCancelUploadWithFetcherProperties:(NSDictionary *)props { + @synchronized(self) { + _isCancelInFlight = YES; + } + GTMSessionFetcher *cancelFetcher = [self uploadFetcherWithProperties:props + isQueryFetch:YES]; + cancelFetcher.bodyData = [NSData data]; + + NSString *originalComment = self.comment; + [cancelFetcher setCommentWithFormat:@"%@ (cancel)", + originalComment ? originalComment : @"upload"]; + + [cancelFetcher setRequestValue:@"cancel" forHTTPHeaderField:kGTMSessionHeaderXGoogUploadCommand]; + + self.fetcherInFlight = cancelFetcher; + [cancelFetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) { + self.fetcherInFlight = nil; + if (![self triggerCancellationHandlerForFetch:cancelFetcher data:data error:error]) { + if (error) { + GTMSESSION_LOG_DEBUG(@"cancelFetcher %@", error); + } + } + @synchronized(self) { + self->_isCancelInFlight = NO; + } + }]; +} + +- (void)uploadNextChunkWithOffset:(int64_t)offset + fetcherProperties:(NSDictionary *)props { + GTMSessionCheckNotSynchronized(self); + + // Example chunk headers: + // X-Goog-Upload-Command: upload, finalize + // X-Goog-Upload-Offset: 0 + // Content-Length: 2000000 + // Content-Type: image/jpeg + // + // {bytes 0-1999999} + + // The chunk upload URL requires no authentication header. + GTMSessionFetcher *chunkFetcher = [self uploadFetcherWithProperties:props + isQueryFetch:NO]; + [self attachSendProgressBlockToChunkFetcher:chunkFetcher]; + int64_t chunkSize = [self updateChunkFetcher:chunkFetcher + forChunkAtOffset:offset]; + BOOL isUploadingFileURL = (self.uploadFileURL != nil); + int64_t fullUploadLength = [self fullUploadLength]; + + // The chunk size may have changed, so determine again if we're uploading the full file. + BOOL isUploadingFullFile = (offset == 0 && + fullUploadLength != kGTMSessionUploadFetcherUnknownFileSize && + chunkSize >= fullUploadLength); + if (isUploadingFullFile && isUploadingFileURL) { + // The data is the full upload file URL. + chunkFetcher.bodyFileURL = self.uploadFileURL; + [self beginChunkFetcher:chunkFetcher + offset:offset]; + } else { + // Make an NSData for the subset for this upload chunk. + self.subdataGenerating = YES; + [self generateChunkSubdataWithOffset:offset + length:chunkSize + response:^(NSData *chunkData, int64_t uploadFileLength, NSError *chunkError) { + // The subdata methods may leave us on a background thread. + dispatch_async(dispatch_get_main_queue(), ^{ + self.subdataGenerating = NO; + + // dont allow the updating of fileLength for uploads not using a data provider as they + // should know the file length before the upload starts. + if (self->_uploadDataProvider != nil && uploadFileLength > 0) { + [self setUploadFileLength:uploadFileLength]; + // Update the command and content-length headers if this is the last chunk to be sent. + if (offset + chunkSize >= uploadFileLength) { + int64_t updatedChunkSize = [self updateChunkFetcher:chunkFetcher + forChunkAtOffset:offset]; + if (updatedChunkSize == 0) { + // Calling beginChunkFetcher early when there is no more data to send allows us to + // properly handle nil chunkData below without having to account for the case where + // we are just finalizing the file. + chunkFetcher.bodyData = [[NSData alloc] init]; + [self beginChunkFetcher:chunkFetcher + offset:offset]; + return; + } + } + } + + if (chunkData == nil) { + NSError *responseError = chunkError; + if (!responseError) { + responseError = [self uploadChunkUnavailableErrorWithDescription:@"chunkData is nil"]; + } + [self invokeFinalCallbackWithData:nil + error:responseError + shouldInvalidateLocation:YES]; + return; + } + + BOOL didWriteFile = NO; + if (isUploadingFileURL) { + // Make a temporary file with the data subset. + NSString *tempName = + [NSString stringWithFormat:@"GTMUpload_temp_%@", [[NSUUID UUID] UUIDString]]; + NSString *tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:tempName]; + NSError *writeError; + didWriteFile = [chunkData writeToFile:tempPath + options:NSDataWritingAtomic + error:&writeError]; + if (didWriteFile) { + chunkFetcher.bodyFileURL = [NSURL fileURLWithPath:tempPath]; + } else { + GTMSESSION_LOG_DEBUG(@"writeToFile failed: %@\n%@", writeError, tempPath); + } + } + if (!didWriteFile) { + chunkFetcher.bodyData = [chunkData copy]; + } + [self beginChunkFetcher:chunkFetcher + offset:offset]; + }); + }]; + } +} + +- (void)beginChunkFetcher:(GTMSessionFetcher *)chunkFetcher + offset:(int64_t)offset { + + // Track the current offset for progress reporting + self.currentOffset = offset; + + // Hang on to the fetcher in case we need to cancel it. We set these before beginning the + // chunk fetch so the observers notified of chunk fetches can inspect the upload fetcher to + // match to the chunk. + self.chunkFetcher = chunkFetcher; + self.fetcherInFlight = chunkFetcher; + + // Update the last chunk request, including any request headers. + self.lastChunkRequest = chunkFetcher.request; + + [chunkFetcher beginFetchWithDelegate:self + didFinishSelector:@selector(chunkFetcher:finishedWithData:error:)]; +} + +- (void)attachSendProgressBlockToChunkFetcher:(GTMSessionFetcher *)chunkFetcher { + chunkFetcher.sendProgressBlock = ^(int64_t bytesSent, int64_t totalBytesSent, + int64_t totalBytesExpectedToSend) { + // The total bytes expected include the initial body and the full chunked + // data, independent of how big this fetcher's chunk is. + int64_t initialBodySent = [self bodyLength]; // TODO(grobbins) use [self initialBodySent] + int64_t totalSent = initialBodySent + self.currentOffset + totalBytesSent; + int64_t totalExpected = initialBodySent + [self fullUploadLength]; + + [self invokeDelegateWithDidSendBytes:bytesSent + totalBytesSent:totalSent + totalBytesExpectedToSend:totalExpected]; + }; +} + +- (NSDictionary *)uploadSessionIdentifierMetadata { + NSMutableDictionary *metadata = [NSMutableDictionary dictionary]; + metadata[kGTMSessionIdentifierIsUploadChunkFetcherMetadataKey] = @YES; + GTMSESSION_ASSERT_DEBUG(self.uploadFileURL, + @"Invalid upload fetcher to create session identifier for metadata"); + metadata[kGTMSessionIdentifierUploadFileURLMetadataKey] = [self.uploadFileURL absoluteString]; + metadata[kGTMSessionIdentifierUploadFileLengthMetadataKey] = @([self fullUploadLength]); + + if (self.uploadLocationURL) { + metadata[kGTMSessionIdentifierUploadLocationURLMetadataKey] = + [self.uploadLocationURL absoluteString]; + } + if (self.uploadMIMEType) { + metadata[kGTMSessionIdentifierUploadMIMETypeMetadataKey] = self.uploadMIMEType; + } + metadata[kGTMSessionIdentifierUploadChunkSizeMetadataKey] = @(self.chunkSize); + metadata[kGTMSessionIdentifierUploadCurrentOffsetMetadataKey] = @(self.currentOffset); + metadata[kGTMSessionIdentifierUploadAllowsCellularAccess] = @(self.request.allowsCellularAccess); + + return metadata; +} + +- (GTMSessionFetcher *)uploadFetcherWithProperties:(NSDictionary *)properties + isQueryFetch:(BOOL)isQueryFetch { + GTMSessionCheckNotSynchronized(self); + + // Common code to make a request for a query command or for a chunk upload. + NSURL *uploadLocationURL = self.uploadLocationURL; + NSMutableURLRequest *chunkRequest = [NSMutableURLRequest requestWithURL:uploadLocationURL]; + [chunkRequest setHTTPMethod:@"PUT"]; + + // copy the user-agent from the original connection + // n.b. that self.request is nil for upload fetchers created with an existing upload location + // URL. + NSURLRequest *origRequest = self.request; + + chunkRequest.allowsCellularAccess = origRequest.allowsCellularAccess; + if (!origRequest) { + chunkRequest.allowsCellularAccess = _allowsCellularAccess; + } + NSString *userAgent = [origRequest valueForHTTPHeaderField:@"User-Agent"]; + if (userAgent.length > 0) { + [chunkRequest setValue:userAgent forHTTPHeaderField:@"User-Agent"]; + } + + [chunkRequest setValue:kGTMSessionXGoogUploadProtocolResumable + forHTTPHeaderField:kGTMSessionHeaderXGoogUploadProtocol]; + + // To avoid timeouts when debugging, copy the timeout of the initial fetcher. + NSTimeInterval origTimeout = [origRequest timeoutInterval]; + [chunkRequest setTimeoutInterval:origTimeout]; + + // + // Make a new chunk fetcher. + // + GTMSessionFetcher *chunkFetcher = [GTMSessionFetcher fetcherWithRequest:chunkRequest]; + chunkFetcher.callbackQueue = self.callbackQueue; + chunkFetcher.sessionUserInfo = self.sessionUserInfo; + chunkFetcher.configurationBlock = self.configurationBlock; + chunkFetcher.allowedInsecureSchemes = self.allowedInsecureSchemes; + chunkFetcher.allowLocalhostRequest = self.allowLocalhostRequest; + chunkFetcher.allowInvalidServerCertificates = self.allowInvalidServerCertificates; + chunkFetcher.useUploadTask = !isQueryFetch; + + if (self.uploadFileURL && !isQueryFetch && self.useBackgroundSession) { + [chunkFetcher createSessionIdentifierWithMetadata:[self uploadSessionIdentifierMetadata]]; + } + + // Give the chunk fetcher the same properties as the previous chunk fetcher + chunkFetcher.properties = [properties mutableCopy]; + [chunkFetcher setProperty:[NSValue valueWithNonretainedObject:self] + forKey:kGTMSessionUploadFetcherChunkParentKey]; + + // copy other fetcher settings to the new fetcher + chunkFetcher.retryEnabled = self.retryEnabled; + chunkFetcher.maxRetryInterval = self.maxRetryInterval; + + if ([self isRetryEnabled]) { + // We interpose our own retry method both so we can change the request to ask the server to + // tell us where to resume the chunk. + chunkFetcher.retryBlock = ^(BOOL suggestedWillRetry, NSError *chunkError, + GTMSessionFetcherRetryResponse response) { + void (^finish)(BOOL) = ^(BOOL shouldRetry){ + // We'll retry by sending an offset query. + if (shouldRetry) { + self.shouldInitiateOffsetQuery = !isQueryFetch; + + // We don't know what our actual offset is anymore, but the server will tell us. + self.currentOffset = 0; + } + // We don't actually want to retry this specific fetcher. + response(NO); + }; + + GTMSessionFetcherRetryBlock retryBlock = self.retryBlock; + if (retryBlock) { + // Ask the client, then call the finish block above. + retryBlock(suggestedWillRetry, chunkError, finish); + } else { + finish(suggestedWillRetry); + } + }; + } + + return chunkFetcher; +} + +- (void)chunkFetcher:(GTMSessionFetcher *)chunkFetcher + finishedWithData:(NSData *)data + error:(NSError *)error { + BOOL hasDestroyedOldChunkFetcher = NO; + self.fetcherInFlight = nil; + + NSDictionary *responseHeaders = [chunkFetcher responseHeaders]; + GTMSessionUploadFetcherStatus uploadStatus = + [[self class] uploadStatusFromResponseHeaders:responseHeaders]; + GTMSESSION_ASSERT_DEBUG(uploadStatus != kStatusUnknown + || error != nil + || self.wasCreatedFromBackgroundSession, + @"chunk fetcher completion has kStatusUnknown upload status for headers %@ fetcher %@", + responseHeaders, self); + BOOL isUploadStatusStopped = (uploadStatus == kStatusFinal || uploadStatus == kStatusCancelled); + + // Check if the fetcher was actually querying. If it failed, do not retry, + // as it would enter an infinite retry loop. + NSString *uploadCommand = + chunkFetcher.request.allHTTPHeaderFields[kGTMSessionHeaderXGoogUploadCommand]; + BOOL isQueryFetch = [uploadCommand isEqual:@"query"]; + + // TODO + // Maybe here we can check to see if the request had x goog content length set. (the file length one). + int64_t previousContentLength = + [[chunkFetcher.request valueForHTTPHeaderField:@"Content-Length"] longLongValue]; + // The Content-Length header may not be present if the chunk fetcher was recreated from + // a background session. + BOOL hasKnownChunkSize = (previousContentLength > 0); + BOOL needsQuery = (!hasKnownChunkSize && !isUploadStatusStopped); + + if (error || (needsQuery && !isQueryFetch)) { + NSInteger status = error.code; + + // Status 4xx indicates a bad offset in the Google upload protocol. However, do not retry status + // 404 per spec, nor if the upload size appears to have been zero (since the server will just + // keep asking us to retry.) + if (self.shouldInitiateOffsetQuery || + (needsQuery && !isQueryFetch) || + ([error.domain isEqual:kGTMSessionFetcherStatusDomain] && + status >= 400 && status <= 499 && + status != 404 && + uploadStatus == kStatusActive && + previousContentLength > 0)) { + self.shouldInitiateOffsetQuery = NO; + [self destroyChunkFetcher]; + hasDestroyedOldChunkFetcher = YES; + [self sendQueryForUploadOffsetWithFetcherProperties:chunkFetcher.properties]; + } else { + // Some unexpected status has occurred; handle it as we would a regular + // object fetcher failure. + [self invokeFinalCallbackWithData:data + error:error + shouldInvalidateLocation:NO]; + } + } else { + // The chunk has uploaded successfully. + int64_t newOffset = self.currentOffset + previousContentLength; +#if DEBUG + // Verify that if we think all of the uploading data has been sent, the server responded with + // the "final" upload status. + BOOL hasUploadAllData = (newOffset == [self fullUploadLength]); + BOOL isFinalStatus = (uploadStatus == kStatusFinal); + #pragma unused(hasUploadAllData,isFinalStatus) + GTMSESSION_ASSERT_DEBUG(hasUploadAllData == isFinalStatus || !hasKnownChunkSize, + @"uploadStatus:%@ newOffset:%lld (%lld + %lld) fullUploadLength:%lld" + @" chunkFetcher:%@ requestHeaders:%@ responseHeaders:%@", + [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadStatus], + newOffset, self.currentOffset, previousContentLength, + [self fullUploadLength], + chunkFetcher, chunkFetcher.request.allHTTPHeaderFields, + responseHeaders); +#endif + if (isUploadStatusStopped || (_currentOffset > _uploadFileLength && _uploadFileLength > 0)) { + // This was the last chunk. + if (error == nil && uploadStatus == kStatusCancelled) { + // Report cancelled status as an error. + NSDictionary *userInfo = nil; + if (data.length > 0) { + userInfo = @{ kGTMSessionFetcherStatusDataKey : data }; + } + data = nil; + error = [self prematureFailureErrorWithUserInfo:userInfo]; + } else { + // The upload is in final status. + // + // Take the chunk fetcher's data as the superclass data. + self.downloadedData = data; + self.statusCode = chunkFetcher.statusCode; + } + + // we're done + [self invokeFinalCallbackWithData:data + error:error + shouldInvalidateLocation:YES]; + } else { + // Start the next chunk. + self.currentOffset = newOffset; + + // We want to destroy this chunk fetcher before creating the next one, but + // we want to pass on its properties + NSDictionary *props = [chunkFetcher properties]; + + // We no longer need to be able to cancel this chunkFetcher. Destroy it + // before we create a new chunk fetcher. + [self destroyChunkFetcher]; + hasDestroyedOldChunkFetcher = YES; + + [self uploadNextChunkWithOffset:newOffset + fetcherProperties:props]; + } + } + if (!hasDestroyedOldChunkFetcher) { + [self destroyChunkFetcher]; + } +} + +- (void)destroyChunkFetcher { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_fetcherInFlight == _chunkFetcher) { + _fetcherInFlight = nil; + } + + [_chunkFetcher stopFetching]; + + NSURL *chunkFileURL = _chunkFetcher.bodyFileURL; + BOOL wasTemporaryUploadFile = ![chunkFileURL isEqual:_uploadFileURL]; + if (wasTemporaryUploadFile) { + NSError *error; + [[NSFileManager defaultManager] removeItemAtURL:chunkFileURL + error:&error]; + if (error) { + GTMSESSION_LOG_DEBUG(@"removingItemAtURL failed: %@\n%@", error, chunkFileURL); + } + } + + _recentChunkReponseHeaders = _chunkFetcher.responseHeaders; + + // To avoid retain cycles, remove all properties except the parent identifier. + _chunkFetcher.properties = + @{ kGTMSessionUploadFetcherChunkParentKey : [NSValue valueWithNonretainedObject:self] }; + + _chunkFetcher.retryBlock = nil; + _chunkFetcher.sendProgressBlock = nil; + _chunkFetcher = nil; + } // @synchronized(self) +} + +// This method calculates the proper values to pass to the client's send progress block. +// +// The actual total bytes sent include the initial body sent, plus the +// offset into the batched data prior to the current chunk fetcher + +- (void)invokeDelegateWithDidSendBytes:(int64_t)bytesSent + totalBytesSent:(int64_t)totalBytesSent + totalBytesExpectedToSend:(int64_t)totalBytesExpected { + GTMSessionCheckNotSynchronized(self); + + // Ensure the chunk fetcher survives the callback in case the user pauses the upload process. + __block GTMSessionFetcher *holdFetcher = self.chunkFetcher; + + [self invokeOnCallbackQueue:self.delegateCallbackQueue + afterUserStopped:NO + block:^{ + GTMSessionFetcherSendProgressBlock sendProgressBlock = self.sendProgressBlock; + if (sendProgressBlock) { + sendProgressBlock(bytesSent, totalBytesSent, totalBytesExpected); + } + holdFetcher = nil; + }]; +} + +- (void)retrieveUploadChunkGranularityFromResponseHeaders:(NSDictionary *)responseHeaders { + GTMSessionCheckNotSynchronized(self); + + // Standard granularity for Google uploads is 256K. + NSString *chunkGranularityHeader = + [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadChunkGranularity]; + self.uploadGranularity = chunkGranularityHeader.longLongValue; +} + +#pragma mark - + +- (BOOL)isPaused { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _isPaused; + } // @synchronized(self) +} + +- (void)pauseFetching { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _isPaused = YES; + } // @synchronized(self) + + // Pausing just means stopping the current chunk from uploading; + // when we resume, we will send a query request to the server to + // figure out what bytes to resume sending. + // + // We won't try to cancel the initial data upload, but rather will check + // for being paused in beginChunkFetches. + [self destroyChunkFetcher]; +} + +- (void)resumeFetching { + BOOL wasPaused; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + wasPaused = _isPaused; + _isPaused = NO; + } // @synchronized(self) + + if (wasPaused) { + [self sendQueryForUploadOffsetWithFetcherProperties:self.properties]; + } +} + +- (void)stopFetching { + // Overrides the superclass + [self destroyChunkFetcher]; + + // If we think the server is waiting for more data, then tell it there won't be more. + if (self.uploadLocationURL) { + [self sendCancelUploadWithFetcherProperties:[self properties]]; + self.uploadLocationURL = nil; + } else { + [self invokeOnCallbackQueue:self.callbackQueue + afterUserStopped:YES + block:^{ + // Repeated calls to stopFetching may cause this path to be reached despite having sent a real + // cancel request, check here to ensure that the cancellation handler invocation which fires + // will definitely be for the real request sent previously. + @synchronized(self) { + if (self->_isCancelInFlight) { + return; + } + } + [self triggerCancellationHandlerForFetch:nil data:nil error:nil]; + }]; + } + + [super stopFetching]; +} + +// Fires the cancellation handler, returning whether there was a handler to be fired. +- (BOOL)triggerCancellationHandlerForFetch:(GTMSessionFetcher *)fetcher + data:(NSData *)data + error:(NSError *)error { + GTMSessionUploadFetcherCancellationHandler handler = self.cancellationHandler; + if (handler) { + handler(fetcher, data, error); + self.cancellationHandler = nil; + return YES; + } + return NO; +} + +#pragma mark - + +- (int64_t)updateChunkFetcher:(GTMSessionFetcher *)chunkFetcher + forChunkAtOffset:(int64_t)offset { + BOOL isUploadingFileURL = (self.uploadFileURL != nil); + + // Upload another chunk, meeting server-required granularity. + int64_t chunkSize = self.chunkSize; + + int64_t fullUploadLength = [self fullUploadLength]; + BOOL isFileLengthKnown = fullUploadLength >= 0; + + BOOL isUploadingFullFile = (offset == 0 && isFileLengthKnown && chunkSize >= fullUploadLength); + if (!isUploadingFileURL || !isUploadingFullFile) { + // We're not uploading the entire file and given the file URL. Since we'll be + // allocating a subdata block for a chunk, we need to bound it to something that + // won't blow the process's memory. + if (chunkSize > kGTMSessionUploadFetcherMaximumDemandBufferSize) { + chunkSize = kGTMSessionUploadFetcherMaximumDemandBufferSize; + } + } + + int64_t granularity = self.uploadGranularity; + if (granularity > 0) { + if (chunkSize < granularity) { + chunkSize = granularity; + } else { + chunkSize = chunkSize - (chunkSize % granularity); + } + } + + GTMSESSION_ASSERT_DEBUG(offset < fullUploadLength || fullUploadLength == 0, + @"offset %lld exceeds data length %lld", offset, fullUploadLength); + + if (granularity > 0) { + offset = offset - (offset % granularity); + } + + // If the chunk size is bigger than the remaining data, or else + // it's close enough in size to the remaining data that we'd rather + // avoid having a whole extra http fetch for the leftover bit, then make + // this chunk size exactly match the remaining data size + NSString *command; + int64_t thisChunkSize = chunkSize; + + BOOL isChunkTooBig = (thisChunkSize >= (fullUploadLength - offset)); + BOOL isChunkAlmostBigEnough = (fullUploadLength - offset - 2500 < thisChunkSize); + BOOL isFinalChunk = (isChunkTooBig || isChunkAlmostBigEnough) && isFileLengthKnown; + if (isFinalChunk) { + thisChunkSize = fullUploadLength - offset; + if (thisChunkSize > 0) { + command = @"upload, finalize"; + } else { + command = @"finalize"; + } + } else { + command = @"upload"; + } + NSString *lengthStr = @(thisChunkSize).stringValue; + NSString *offsetStr = @(offset).stringValue; + + [chunkFetcher setRequestValue:command forHTTPHeaderField:kGTMSessionHeaderXGoogUploadCommand]; + [chunkFetcher setRequestValue:lengthStr forHTTPHeaderField:@"Content-Length"]; + [chunkFetcher setRequestValue:offsetStr forHTTPHeaderField:kGTMSessionHeaderXGoogUploadOffset]; + if (_uploadFileLength != kGTMSessionUploadFetcherUnknownFileSize) { + [chunkFetcher setRequestValue:@([self fullUploadLength]).stringValue + forHTTPHeaderField:kGTMSessionHeaderXGoogUploadContentLength]; + } + + // Append the range of bytes in this chunk to the fetcher comment. + NSString *baseComment = self.comment; + [chunkFetcher setCommentWithFormat:@"%@ (%lld-%lld)", + baseComment ? baseComment : @"upload", offset, MAX(0, offset + thisChunkSize - 1)]; + + return thisChunkSize; +} + +// Public properties. +@synthesize currentOffset = _currentOffset, + allowsCellularAccess = _allowsCellularAccess, + delegateCompletionHandler = _delegateCompletionHandler, + chunkFetcher = _chunkFetcher, + lastChunkRequest = _lastChunkRequest, + subdataGenerating = _subdataGenerating, + shouldInitiateOffsetQuery = _shouldInitiateOffsetQuery, + uploadGranularity = _uploadGranularity; + +// Internal properties. +@dynamic fetcherInFlight; +@dynamic activeFetcher; +@dynamic statusCode; +@dynamic delegateCallbackQueue; + ++ (void)removePointer:(void *)pointer fromPointerArray:(NSPointerArray *)pointerArray { + for (NSUInteger index = 0, count = pointerArray.count; index < count; ++index) { + void *pointerAtIndex = [pointerArray pointerAtIndex:index]; + if (pointerAtIndex == pointer) { + [pointerArray removePointerAtIndex:index]; + return; + } + } +} + +- (BOOL)useBackgroundSession { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _useBackgroundSessionOnChunkFetchers; + } // @synchronized(self +} + +- (void)setUseBackgroundSession:(BOOL)useBackgroundSession { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_useBackgroundSessionOnChunkFetchers != useBackgroundSession) { + _useBackgroundSessionOnChunkFetchers = useBackgroundSession; + NSPointerArray *uploadFetcherPointerArrayForBackgroundSessions = + [[self class] uploadFetcherPointerArrayForBackgroundSessions]; + @synchronized(uploadFetcherPointerArrayForBackgroundSessions) { + if (_useBackgroundSessionOnChunkFetchers) { + [uploadFetcherPointerArrayForBackgroundSessions addPointer:(__bridge void *)self]; + } else { + [[self class] removePointer:(__bridge void *)self + fromPointerArray:uploadFetcherPointerArrayForBackgroundSessions]; + } + } // @synchronized(uploadFetcherPointerArrayForBackgroundSessions) + } + } // @synchronized(self) +} + +- (BOOL)canFetchWithBackgroundSession { + // The initial upload fetcher is always a foreground session; the + // useBackgroundSession property will apply only to chunk fetchers, + // not to queries. + return NO; +} + +- (NSDictionary *)responseHeaders { + GTMSessionCheckNotSynchronized(self); + // Overrides the superclass + + // If asked for the fetcher's response, use the most recent chunk fetcher's response, + // since the original request's response lacks useful information like the actual + // Content-Type. + NSDictionary *dict = self.chunkFetcher.responseHeaders; + if (dict) { + return dict; + } + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_recentChunkReponseHeaders) { + return _recentChunkReponseHeaders; + } + } // @synchronized(self + + // No chunk fetcher yet completed, so return whatever we have from the initial fetch. + return [super responseHeaders]; +} + +- (NSInteger)statusCodeUnsynchronized { + GTMSessionCheckSynchronized(self); + + if (_recentChunkStatusCode != -1) { + // Overrides the superclass to indicate status appropriate to the initial + // or latest chunk fetch + return _recentChunkStatusCode; + } else { + return [super statusCodeUnsynchronized]; + } +} + + +- (void)setStatusCode:(NSInteger)val { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _recentChunkStatusCode = val; + } +} + +- (int64_t)initialBodyLength { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _initialBodyLength; + } +} + +- (void)setInitialBodyLength:(int64_t)length { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _initialBodyLength = length; + } +} + +- (int64_t)initialBodySent { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _initialBodySent; + } +} + +- (void)setInitialBodySent:(int64_t)length { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _initialBodySent = length; + } +} + +- (NSURL *)uploadLocationURL { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _uploadLocationURL; + } +} + +- (void)setUploadLocationURL:(NSURL *)locationURL { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _uploadLocationURL = locationURL; + } +} + +- (GTMSessionFetcher *)activeFetcher { + GTMSessionFetcher *result = self.fetcherInFlight; + if (result) return result; + + return self; +} + +- (BOOL)isFetching { + // If there is an active chunk fetcher, then the upload fetcher is considered + // to still be fetching. + if (self.fetcherInFlight != nil) return YES; + + return [super isFetching]; +} + +- (BOOL)waitForCompletionWithTimeout:(NSTimeInterval)timeoutInSeconds { + NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds]; + + while (self.fetcherInFlight || self.subdataGenerating) { + if ([timeoutDate timeIntervalSinceNow] < 0) return NO; + + if (self.subdataGenerating) { + // Allow time for subdata generation. + NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:0.001]; + [[NSRunLoop currentRunLoop] runUntilDate:stopDate]; + } else { + // Wait for any chunk or query fetchers that still have pending callbacks or + // notifications. + BOOL timedOut; + + if (self.fetcherInFlight == self) { + timedOut = ![super waitForCompletionWithTimeout:timeoutInSeconds]; + } else { + timedOut = ![self.fetcherInFlight waitForCompletionWithTimeout:timeoutInSeconds]; + } + if (timedOut) return NO; + } + } + return YES; +} + +@end + +@implementation GTMSessionFetcher (GTMSessionUploadFetcherMethods) + +- (GTMSessionUploadFetcher *)parentUploadFetcher { + NSValue *property = [self propertyForKey:kGTMSessionUploadFetcherChunkParentKey]; + if (!property) return nil; + + GTMSessionUploadFetcher *uploadFetcher = property.nonretainedObjectValue; + + GTMSESSION_ASSERT_DEBUG([uploadFetcher isKindOfClass:[GTMSessionUploadFetcher class]], + @"Unexpected parent upload fetcher class: %@", [uploadFetcher class]); + return uploadFetcher; +} + +@end diff --git a/!main project/Pods/GoogleAppMeasurement/Frameworks/GoogleAppMeasurement.framework/GoogleAppMeasurement b/!main project/Pods/GoogleAppMeasurement/Frameworks/GoogleAppMeasurement.framework/GoogleAppMeasurement new file mode 100755 index 0000000..de27b25 Binary files /dev/null and b/!main project/Pods/GoogleAppMeasurement/Frameworks/GoogleAppMeasurement.framework/GoogleAppMeasurement differ diff --git a/!main project/Pods/GoogleAppMeasurement/Frameworks/GoogleAppMeasurement.framework/Modules/module.modulemap b/!main project/Pods/GoogleAppMeasurement/Frameworks/GoogleAppMeasurement.framework/Modules/module.modulemap new file mode 100755 index 0000000..de80e9e --- /dev/null +++ b/!main project/Pods/GoogleAppMeasurement/Frameworks/GoogleAppMeasurement.framework/Modules/module.modulemap @@ -0,0 +1,11 @@ +framework module GoogleAppMeasurement { + export * + module * { export * } + link "sqlite3" + link "z" + link framework "CoreData" + link framework "Security" + link framework "StoreKit" + link framework "SystemConfiguration" + link framework "UIKit" +} diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORAssert.m b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORAssert.m new file mode 100644 index 0000000..3e5f57b --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORAssert.m @@ -0,0 +1,36 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "GDTCORLibrary/Public/GDTCORAssert.h" + +GDTCORAssertionBlock GDTCORAssertionBlockToRunInstead(void) { + // This class is only compiled in by unit tests, and this should fail quickly in optimized builds. + Class GDTCORAssertClass = NSClassFromString(@"GDTCORAssertHelper"); + if (__builtin_expect(!!GDTCORAssertClass, 0)) { + SEL assertionBlockSEL = NSSelectorFromString(@"assertionBlock"); + if (assertionBlockSEL) { + IMP assertionBlockIMP = [GDTCORAssertClass methodForSelector:assertionBlockSEL]; + if (assertionBlockIMP) { + GDTCORAssertionBlock assertionBlock = ((GDTCORAssertionBlock(*)(id, SEL))assertionBlockIMP)( + GDTCORAssertClass, assertionBlockSEL); + if (assertionBlock) { + return assertionBlock; + } + } + } + } + return NULL; +} diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORClock.m b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORClock.m new file mode 100644 index 0000000..f0ea8ab --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORClock.m @@ -0,0 +1,164 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "GDTCORLibrary/Public/GDTCORClock.h" + +#import + +// Using a monotonic clock is necessary because CFAbsoluteTimeGetCurrent(), NSDate, and related all +// are subject to drift. That it to say, multiple consecutive calls do not always result in a +// time that is in the future. Clocks may be adjusted by the user, NTP, or any number of external +// factors. This class attempts to determine the wall-clock time at the time of the event by +// capturing the kernel start and time since boot to determine a wallclock time in UTC. +// +// Timezone offsets at the time of a snapshot are also captured in order to provide local-time +// details. Other classes in this library depend on comparing times at some time in the future to +// a time captured in the past, and this class needs to provide a mechanism to do that. +// +// TL;DR: This class attempts to accomplish two things: 1. Provide accurate event times. 2. Provide +// a monotonic clock mechanism to accurately check if some clock snapshot was before or after +// by using a shared reference point (kernel boot time). +// +// Note: Much of the mach time stuff doesn't work properly in the simulator. So this class can be +// difficult to unit test. + +/** Returns the kernel boottime property from sysctl. + * + * Inspired by https://stackoverflow.com/a/40497811 + * + * @return The KERN_BOOTTIME property from sysctl, in nanoseconds. + */ +static int64_t KernelBootTimeInNanoseconds() { + // Caching the result is not possible because clock drift would not be accounted for. + struct timeval boottime; + int mib[2] = {CTL_KERN, KERN_BOOTTIME}; + size_t size = sizeof(boottime); + int rc = sysctl(mib, 2, &boottime, &size, NULL, 0); + if (rc != 0) { + return 0; + } + return (int64_t)boottime.tv_sec * NSEC_PER_MSEC + (int64_t)boottime.tv_usec; +} + +/** Returns value of gettimeofday, in nanoseconds. + * + * Inspired by https://stackoverflow.com/a/40497811 + * + * @return The value of gettimeofday, in nanoseconds. + */ +static int64_t UptimeInNanoseconds() { + int64_t before_now; + int64_t after_now; + struct timeval now; + + before_now = KernelBootTimeInNanoseconds(); + // Addresses a race condition in which the system time has updated, but the boottime has not. + do { + gettimeofday(&now, NULL); + after_now = KernelBootTimeInNanoseconds(); + } while (after_now != before_now); + return (int64_t)now.tv_sec * NSEC_PER_MSEC + (int64_t)now.tv_usec - before_now; +} + +// TODO: Consider adding a 'trustedTime' property that can be populated by the response from a BE. +@implementation GDTCORClock + +- (instancetype)init { + self = [super init]; + if (self) { + _kernelBootTime = KernelBootTimeInNanoseconds(); + _uptime = UptimeInNanoseconds(); + _timeMillis = + (int64_t)((CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) * NSEC_PER_USEC); + CFTimeZoneRef timeZoneRef = CFTimeZoneCopySystem(); + _timezoneOffsetSeconds = CFTimeZoneGetSecondsFromGMT(timeZoneRef, 0); + CFRelease(timeZoneRef); + } + return self; +} + ++ (GDTCORClock *)snapshot { + return [[GDTCORClock alloc] init]; +} + ++ (instancetype)clockSnapshotInTheFuture:(uint64_t)millisInTheFuture { + GDTCORClock *snapshot = [self snapshot]; + snapshot->_timeMillis += millisInTheFuture; + return snapshot; +} + +- (BOOL)isAfter:(GDTCORClock *)otherClock { + // These clocks are trivially comparable when they share a kernel boot time. + if (_kernelBootTime == otherClock->_kernelBootTime) { + int64_t timeDiff = (_timeMillis + _timezoneOffsetSeconds) - + (otherClock->_timeMillis + otherClock->_timezoneOffsetSeconds); + return timeDiff > 0; + } else { + int64_t kernelBootTimeDiff = otherClock->_kernelBootTime - _kernelBootTime; + // This isn't a great solution, but essentially, if the other clock's boot time is 'later', NO + // is returned. This can be altered by changing the system time and rebooting. + return kernelBootTimeDiff < 0 ? YES : NO; + } +} + +- (NSUInteger)hash { + return [@(_kernelBootTime) hash] ^ [@(_uptime) hash] ^ [@(_timeMillis) hash] ^ + [@(_timezoneOffsetSeconds) hash]; +} + +- (BOOL)isEqual:(id)object { + return [self hash] == [object hash]; +} + +#pragma mark - NSSecureCoding + +/** NSKeyedCoder key for timeMillis property. */ +static NSString *const kGDTCORClockTimeMillisKey = @"GDTCORClockTimeMillis"; + +/** NSKeyedCoder key for timezoneOffsetMillis property. */ +static NSString *const kGDTCORClockTimezoneOffsetSeconds = @"GDTCORClockTimezoneOffsetSeconds"; + +/** NSKeyedCoder key for _kernelBootTime ivar. */ +static NSString *const kGDTCORClockKernelBootTime = @"GDTCORClockKernelBootTime"; + +/** NSKeyedCoder key for _uptime ivar. */ +static NSString *const kGDTCORClockUptime = @"GDTCORClockUptime"; + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + self = [super init]; + if (self) { + // TODO: If the kernelBootTime is more recent, we need to change the kernel boot time and + // uptimeMillis ivars + _timeMillis = [aDecoder decodeInt64ForKey:kGDTCORClockTimeMillisKey]; + _timezoneOffsetSeconds = [aDecoder decodeInt64ForKey:kGDTCORClockTimezoneOffsetSeconds]; + _kernelBootTime = [aDecoder decodeInt64ForKey:kGDTCORClockKernelBootTime]; + _uptime = [aDecoder decodeInt64ForKey:kGDTCORClockUptime]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeInt64:_timeMillis forKey:kGDTCORClockTimeMillisKey]; + [aCoder encodeInt64:_timezoneOffsetSeconds forKey:kGDTCORClockTimezoneOffsetSeconds]; + [aCoder encodeInt64:_kernelBootTime forKey:kGDTCORClockKernelBootTime]; + [aCoder encodeInt64:_uptime forKey:kGDTCORClockUptime]; +} + +@end diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORConsoleLogger.m b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORConsoleLogger.m new file mode 100644 index 0000000..6df92a5 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORConsoleLogger.m @@ -0,0 +1,36 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "GDTCORLibrary/Public/GDTCORConsoleLogger.h" + +/** The console logger prefix. */ +static NSString *kGDTCORConsoleLogger = @"[GoogleDataTransport]"; + +NSString *GDTCORMessageCodeEnumToString(GDTCORMessageCode code) { + return [[NSString alloc] initWithFormat:@"I-GDTCOR%06ld", (long)code]; +} + +void GDTCORLog(GDTCORMessageCode code, NSString *format, ...) { +// Don't log anything in not debug builds. +#if !NDEBUG + NSString *logFormat = [NSString stringWithFormat:@"%@[%@] %@", kGDTCORConsoleLogger, + GDTCORMessageCodeEnumToString(code), format]; + va_list args; + va_start(args, format); + NSLogv(logFormat, args); + va_end(args); +#endif // !NDEBUG +} diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORDataFuture.m b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORDataFuture.m new file mode 100644 index 0000000..33c3f15 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORDataFuture.m @@ -0,0 +1,64 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@implementation GDTCORDataFuture + +- (instancetype)initWithFileURL:(NSURL *)fileURL { + self = [super init]; + if (self) { + _fileURL = fileURL; + } + return self; +} + +- (BOOL)isEqual:(id)object { + return [self hash] == [object hash]; +} + +- (NSUInteger)hash { + // In reality, only one of these should be populated. + return [_fileURL hash] ^ [_originalData hash]; +} + +#pragma mark - NSSecureCoding + +/** Coding key for _fileURL ivar. */ +static NSString *kGDTCORDataFutureFileURLKey = @"GDTCORDataFutureFileURLKey"; + +/** Coding key for _data ivar. */ +static NSString *kGDTCORDataFutureDataKey = @"GDTCORDataFutureDataKey"; + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (void)encodeWithCoder:(nonnull NSCoder *)aCoder { + [aCoder encodeObject:_fileURL forKey:kGDTCORDataFutureFileURLKey]; + [aCoder encodeObject:_originalData forKey:kGDTCORDataFutureDataKey]; +} + +- (nullable instancetype)initWithCoder:(nonnull NSCoder *)aDecoder { + self = [self init]; + if (self) { + _fileURL = [aDecoder decodeObjectOfClass:[NSURL class] forKey:kGDTCORDataFutureFileURLKey]; + _originalData = [aDecoder decodeObjectOfClass:[NSData class] forKey:kGDTCORDataFutureDataKey]; + } + return self; +} + +@end diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCOREvent.m b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCOREvent.m new file mode 100644 index 0000000..ed0c8e8 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCOREvent.m @@ -0,0 +1,123 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import +#import +#import + +#import "GDTCORLibrary/Private/GDTCOREvent_Private.h" + +@implementation GDTCOREvent + +- (nullable instancetype)initWithMappingID:(NSString *)mappingID target:(NSInteger)target { + GDTCORAssert(mappingID.length > 0, @"Please give a valid mapping ID"); + GDTCORAssert(target > 0, @"A target cannot be negative or 0"); + if (mappingID == nil || mappingID.length == 0 || target <= 0) { + return nil; + } + self = [super init]; + if (self) { + _mappingID = mappingID; + _target = target; + _qosTier = GDTCOREventQosDefault; + } + GDTCORLogDebug("Event %@ created. mappingID: %@ target:%ld qos:%ld", self, _mappingID, + (long)_target, (long)_qosTier); + return self; +} + +- (instancetype)copy { + GDTCOREvent *copy = [[GDTCOREvent alloc] initWithMappingID:_mappingID target:_target]; + copy.dataObject = _dataObject; + copy.dataObjectTransportBytes = _dataObjectTransportBytes; + copy.qosTier = _qosTier; + copy.clockSnapshot = _clockSnapshot; + copy.customPrioritizationParams = _customPrioritizationParams; + GDTCORLogDebug("Copying event %@ to event %@", self, copy); + return copy; +} + +- (NSUInteger)hash { + // This loses some precision, but it's probably fine. + NSUInteger mappingIDHash = [_mappingID hash]; + NSUInteger timeHash = [_clockSnapshot hash]; + NSUInteger dataObjectTransportBytesHash = [_dataObjectTransportBytes hash]; + return mappingIDHash ^ _target ^ dataObjectTransportBytesHash ^ _qosTier ^ timeHash; +} + +- (BOOL)isEqual:(id)object { + return [self hash] == [object hash]; +} + +- (void)setDataObject:(id)dataObject { + // If you're looking here because of a performance issue in -transportBytes slowing the assignment + // of -dataObject, one way to address this is to add a queue to this class, + // dispatch_(barrier_ if concurrent)async here, and implement the getter with a dispatch_sync. + if (dataObject != _dataObject) { + _dataObject = dataObject; + _dataObjectTransportBytes = [dataObject transportBytes]; + } +} + +- (GDTCORStoredEvent *)storedEventWithDataFuture:(GDTCORDataFuture *)dataFuture { + return [[GDTCORStoredEvent alloc] initWithEvent:self dataFuture:dataFuture]; +} + +#pragma mark - NSSecureCoding and NSCoding Protocols + +/** NSCoding key for mappingID property. */ +static NSString *mappingIDKey = @"_mappingID"; + +/** NSCoding key for target property. */ +static NSString *targetKey = @"_target"; + +/** NSCoding key for dataObjectTransportBytes property. */ +static NSString *dataObjectTransportBytesKey = @"_dataObjectTransportBytesKey"; + +/** NSCoding key for qosTier property. */ +static NSString *qosTierKey = @"_qosTier"; + +/** NSCoding key for clockSnapshot property. */ +static NSString *clockSnapshotKey = @"_clockSnapshot"; + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (id)initWithCoder:(NSCoder *)aDecoder { + NSString *mappingID = [aDecoder decodeObjectOfClass:[NSObject class] forKey:mappingIDKey]; + NSInteger target = [aDecoder decodeIntegerForKey:targetKey]; + self = [self initWithMappingID:mappingID target:target]; + if (self) { + _dataObjectTransportBytes = [aDecoder decodeObjectOfClass:[NSData class] + forKey:dataObjectTransportBytesKey]; + _qosTier = [aDecoder decodeIntegerForKey:qosTierKey]; + _clockSnapshot = [aDecoder decodeObjectOfClass:[GDTCORClock class] forKey:clockSnapshotKey]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_mappingID forKey:mappingIDKey]; + [aCoder encodeInteger:_target forKey:targetKey]; + [aCoder encodeObject:_dataObjectTransportBytes forKey:dataObjectTransportBytesKey]; + [aCoder encodeInteger:_qosTier forKey:qosTierKey]; + [aCoder encodeObject:_clockSnapshot forKey:clockSnapshotKey]; +} + +@end diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORLifecycle.m b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORLifecycle.m new file mode 100644 index 0000000..cd554ad --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORLifecycle.m @@ -0,0 +1,132 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "GDTCORLibrary/Public/GDTCORLifecycle.h" + +#import +#import + +#import "GDTCORLibrary/Private/GDTCORRegistrar_Private.h" +#import "GDTCORLibrary/Private/GDTCORStorage_Private.h" +#import "GDTCORLibrary/Private/GDTCORTransformer_Private.h" +#import "GDTCORLibrary/Private/GDTCORUploadCoordinator.h" + +@implementation GDTCORLifecycle + ++ (void)load { + [self sharedInstance]; +} + +/** Creates/returns the singleton instance of this class. + * + * @return The singleton instance of this class. + */ ++ (instancetype)sharedInstance { + static GDTCORLifecycle *sharedInstance; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[GDTCORLifecycle alloc] init]; + }); + return sharedInstance; +} + +- (instancetype)init { + self = [super init]; + if (self) { + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + [notificationCenter addObserver:self + selector:@selector(applicationDidEnterBackground:) + name:kGDTCORApplicationDidEnterBackgroundNotification + object:nil]; + [notificationCenter addObserver:self + selector:@selector(applicationWillEnterForeground:) + name:kGDTCORApplicationWillEnterForegroundNotification + object:nil]; + + NSString *name = kGDTCORApplicationWillTerminateNotification; + [notificationCenter addObserver:self + selector:@selector(applicationWillTerminate:) + name:name + object:nil]; + } + return self; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)applicationDidEnterBackground:(NSNotification *)notification { + GDTCORApplication *application = [GDTCORApplication sharedApplication]; + if ([[GDTCORTransformer sharedInstance] respondsToSelector:@selector(appWillBackground:)]) { + GDTCORLogDebug("%@", @"Signaling GDTCORTransformer that the app is backgrounding."); + [[GDTCORTransformer sharedInstance] appWillBackground:application]; + } + if ([[GDTCORStorage sharedInstance] respondsToSelector:@selector(appWillBackground:)]) { + GDTCORLogDebug("%@", @"Signaling GDTCORStorage that the app is backgrounding."); + [[GDTCORStorage sharedInstance] appWillBackground:application]; + } + if ([[GDTCORUploadCoordinator sharedInstance] respondsToSelector:@selector(appWillBackground:)]) { + GDTCORLogDebug("%@", @"Signaling GDTCORUploadCoordinator that the app is backgrounding."); + [[GDTCORUploadCoordinator sharedInstance] appWillBackground:application]; + } + if ([[GDTCORRegistrar sharedInstance] respondsToSelector:@selector(appWillBackground:)]) { + GDTCORLogDebug("%@", @"Signaling GDTCORRegistrar that the app is backgrounding."); + [[GDTCORRegistrar sharedInstance] appWillBackground:application]; + } +} + +- (void)applicationWillEnterForeground:(NSNotification *)notification { + GDTCORApplication *application = [GDTCORApplication sharedApplication]; + if ([[GDTCORTransformer sharedInstance] respondsToSelector:@selector(appWillForeground:)]) { + GDTCORLogDebug("%@", @"Signaling GDTCORTransformer that the app is foregrounding."); + [[GDTCORTransformer sharedInstance] appWillForeground:application]; + } + if ([[GDTCORStorage sharedInstance] respondsToSelector:@selector(appWillForeground:)]) { + GDTCORLogDebug("%@", @"Signaling GDTCORStorage that the app is foregrounding."); + [[GDTCORStorage sharedInstance] appWillForeground:application]; + } + if ([[GDTCORUploadCoordinator sharedInstance] respondsToSelector:@selector(appWillForeground:)]) { + GDTCORLogDebug("%@", @"Signaling GDTCORUploadCoordinator that the app is foregrounding."); + [[GDTCORUploadCoordinator sharedInstance] appWillForeground:application]; + } + if ([[GDTCORRegistrar sharedInstance] respondsToSelector:@selector(appWillForeground:)]) { + GDTCORLogDebug("%@", @"Signaling GDTCORRegistrar that the app is foregrounding."); + [[GDTCORRegistrar sharedInstance] appWillForeground:application]; + } +} + +- (void)applicationWillTerminate:(NSNotification *)notification { + GDTCORApplication *application = [GDTCORApplication sharedApplication]; + if ([[GDTCORTransformer sharedInstance] respondsToSelector:@selector(appWillTerminate:)]) { + GDTCORLogDebug("%@", @"Signaling GDTCORTransformer that the app is terminating."); + [[GDTCORTransformer sharedInstance] appWillTerminate:application]; + } + if ([[GDTCORStorage sharedInstance] respondsToSelector:@selector(appWillTerminate:)]) { + GDTCORLogDebug("%@", @"Signaling GDTCORStorage that the app is terminating."); + [[GDTCORStorage sharedInstance] appWillTerminate:application]; + } + if ([[GDTCORUploadCoordinator sharedInstance] respondsToSelector:@selector(appWillTerminate:)]) { + GDTCORLogDebug("%@", @"Signaling GDTCORUploadCoordinator that the app is terminating."); + [[GDTCORUploadCoordinator sharedInstance] appWillTerminate:application]; + } + if ([[GDTCORRegistrar sharedInstance] respondsToSelector:@selector(appWillTerminate:)]) { + GDTCORLogDebug("%@", @"Signaling GDTCORRegistrar that the app is terminating."); + [[GDTCORRegistrar sharedInstance] appWillTerminate:application]; + } +} + +@end diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m new file mode 100644 index 0000000..778ced9 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m @@ -0,0 +1,222 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import +#import + +#import "GDTCORLibrary/Private/GDTCORRegistrar_Private.h" + +#ifdef GDTCOR_VERSION +#define STR(x) STR_EXPAND(x) +#define STR_EXPAND(x) #x +NSString *const kGDTCORVersion = @STR(GDTCOR_VERSION); +#else +NSString *const kGDTCORVersion = @"Unknown"; +#endif // GDTCOR_VERSION + +const GDTCORBackgroundIdentifier GDTCORBackgroundIdentifierInvalid = 0; + +NSString *const kGDTCORApplicationDidEnterBackgroundNotification = + @"GDTCORApplicationDidEnterBackgroundNotification"; + +NSString *const kGDTCORApplicationWillEnterForegroundNotification = + @"GDTCORApplicationWillEnterForegroundNotification"; + +NSString *const kGDTCORApplicationWillTerminateNotification = + @"GDTCORApplicationWillTerminateNotification"; +#if !TARGET_OS_WATCH +BOOL GDTCORReachabilityFlagsContainWWAN(SCNetworkReachabilityFlags flags) { +#if TARGET_OS_IOS + return (flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN; +#else + return NO; +#endif // TARGET_OS_IOS +} +#endif // !TARGET_OS_WATCH + +@interface GDTCORApplication () +/** + Private flag to match the existing `readonly` public flag. This will be accurate for all platforms, + since we handle each platform's lifecycle notifications separately. + */ +@property(atomic, readwrite) BOOL isRunningInBackground; + +@end + +@implementation GDTCORApplication + ++ (void)load { + GDTCORLogDebug( + "%@", @"GDT is initializing. Please note that if you quit the app via the " + "debugger and not through a lifecycle event, event data will remain on disk but " + "storage won't have a reference to them since the singleton wasn't saved to disk."); +#if TARGET_OS_IOS || TARGET_OS_TV + // If this asserts, please file a bug at https://github.com/firebase/firebase-ios-sdk/issues. + GDTCORFatalAssert( + GDTCORBackgroundIdentifierInvalid == UIBackgroundTaskInvalid, + @"GDTCORBackgroundIdentifierInvalid and UIBackgroundTaskInvalid should be the same."); +#endif + [self sharedApplication]; +} + ++ (nullable GDTCORApplication *)sharedApplication { + static GDTCORApplication *application; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + application = [[GDTCORApplication alloc] init]; + }); + return application; +} + +- (instancetype)init { + self = [super init]; + if (self) { + // This class will be instantiated in the foreground. + _isRunningInBackground = NO; + +#if TARGET_OS_IOS || TARGET_OS_TV + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + [notificationCenter addObserver:self + selector:@selector(iOSApplicationDidEnterBackground:) + name:UIApplicationDidEnterBackgroundNotification + object:nil]; + [notificationCenter addObserver:self + selector:@selector(iOSApplicationWillEnterForeground:) + name:UIApplicationWillEnterForegroundNotification + object:nil]; + + NSString *name = UIApplicationWillTerminateNotification; + [notificationCenter addObserver:self + selector:@selector(iOSApplicationWillTerminate:) + name:name + object:nil]; + +#if defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 + if (@available(iOS 13, tvOS 13.0, *)) { + [notificationCenter addObserver:self + selector:@selector(iOSApplicationWillEnterForeground:) + name:UISceneWillEnterForegroundNotification + object:nil]; + [notificationCenter addObserver:self + selector:@selector(iOSApplicationDidEnterBackground:) + name:UISceneWillDeactivateNotification + object:nil]; + } +#endif // defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 + +#elif TARGET_OS_OSX + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + [notificationCenter addObserver:self + selector:@selector(macOSApplicationWillTerminate:) + name:NSApplicationWillTerminateNotification + object:nil]; +#endif // TARGET_OS_IOS || TARGET_OS_TV + } + return self; +} + +- (GDTCORBackgroundIdentifier)beginBackgroundTaskWithName:(NSString *)name + expirationHandler:(void (^)(void))handler { + GDTCORBackgroundIdentifier bgID = + [[self sharedApplicationForBackgroundTask] beginBackgroundTaskWithName:name + expirationHandler:handler]; +#if !NDEBUG + if (bgID != GDTCORBackgroundIdentifierInvalid) { + GDTCORLogDebug("Creating background task with name:%@ bgID:%ld", name, (long)bgID); + } +#endif // !NDEBUG + return bgID; +} + +- (void)endBackgroundTask:(GDTCORBackgroundIdentifier)bgID { + if (bgID != GDTCORBackgroundIdentifierInvalid) { + GDTCORLogDebug("Ending background task with ID:%ld was successful", (long)bgID); + [[self sharedApplicationForBackgroundTask] endBackgroundTask:bgID]; + return; + } +} + +#pragma mark - App environment helpers + +- (BOOL)isAppExtension { +#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH + BOOL appExtension = [[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]; + return appExtension; +#elif TARGET_OS_OSX + return NO; +#endif +} + +/** Returns a UIApplication instance if on the appropriate platform. + * + * @return The shared UIApplication if on the appropriate platform. + */ +#if TARGET_OS_IOS || TARGET_OS_TV +- (nullable UIApplication *)sharedApplicationForBackgroundTask { +#else +- (nullable id)sharedApplicationForBackgroundTask { +#endif + if ([self isAppExtension]) { + return nil; + } + id sharedApplication = nil; + Class uiApplicationClass = NSClassFromString(@"UIApplication"); + if (uiApplicationClass && + [uiApplicationClass respondsToSelector:(NSSelectorFromString(@"sharedApplication"))]) { + sharedApplication = [uiApplicationClass sharedApplication]; + } + return sharedApplication; +} + +#pragma mark - UIApplicationDelegate + +#if TARGET_OS_IOS || TARGET_OS_TV +- (void)iOSApplicationDidEnterBackground:(NSNotification *)notif { + _isRunningInBackground = YES; + + NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter]; + GDTCORLogDebug("%@", @"GDTCORPlatform is sending a notif that the app is backgrounding."); + [notifCenter postNotificationName:kGDTCORApplicationDidEnterBackgroundNotification object:nil]; +} + +- (void)iOSApplicationWillEnterForeground:(NSNotification *)notif { + _isRunningInBackground = NO; + + NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter]; + GDTCORLogDebug("%@", @"GDTCORPlatform is sending a notif that the app is foregrounding."); + [notifCenter postNotificationName:kGDTCORApplicationWillEnterForegroundNotification object:nil]; +} + +- (void)iOSApplicationWillTerminate:(NSNotification *)notif { + NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter]; + GDTCORLogDebug("%@", @"GDTCORPlatform is sending a notif that the app is terminating."); + [notifCenter postNotificationName:kGDTCORApplicationWillTerminateNotification object:nil]; +} +#endif // TARGET_OS_IOS || TARGET_OS_TV + +#pragma mark - NSApplicationDelegate + +#if TARGET_OS_OSX +- (void)macOSApplicationWillTerminate:(NSNotification *)notif { + NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter]; + GDTCORLogDebug("%@", @"GDTCORPlatform is sending a notif that the app is terminating."); + [notifCenter postNotificationName:kGDTCORApplicationWillTerminateNotification object:nil]; +} +#endif // TARGET_OS_OSX + +@end diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORReachability.m b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORReachability.m new file mode 100644 index 0000000..1c6555a --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORReachability.m @@ -0,0 +1,118 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "GDTCORLibrary/Private/GDTCORReachability.h" +#import "GDTCORLibrary/Private/GDTCORReachability_Private.h" +#if !TARGET_OS_WATCH + +#import + +#import + +/** Sets the _callbackFlag ivar whenever the network changes. + * + * @param reachability The reachability object calling back. + * @param flags The new flag values. + * @param info Any data that might be passed in by the callback. + */ +static void GDTCORReachabilityCallback(SCNetworkReachabilityRef reachability, + SCNetworkReachabilityFlags flags, + void *info); + +@implementation GDTCORReachability { + /** The reachability object. */ + SCNetworkReachabilityRef _reachabilityRef; + + /** The queue on which callbacks and all work will occur. */ + dispatch_queue_t _reachabilityQueue; + + /** Flags specified by reachability callbacks. */ + SCNetworkConnectionFlags _callbackFlags; +} + ++ (void)load { + [self sharedInstance]; +} + ++ (instancetype)sharedInstance { + static GDTCORReachability *sharedInstance; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[GDTCORReachability alloc] init]; + }); + return sharedInstance; +} + ++ (SCNetworkReachabilityFlags)currentFlags { + __block SCNetworkReachabilityFlags currentFlags; + dispatch_sync([GDTCORReachability sharedInstance] -> _reachabilityQueue, ^{ + GDTCORReachability *reachability = [GDTCORReachability sharedInstance]; + currentFlags = reachability->_flags ? reachability->_flags : reachability->_callbackFlags; + GDTCORLogDebug("Initial reachability flags determined: %d", currentFlags); + }); + return currentFlags; +} + +- (instancetype)init { + self = [super init]; + if (self) { + struct sockaddr_in zeroAddress; + bzero(&zeroAddress, sizeof(zeroAddress)); + zeroAddress.sin_len = sizeof(zeroAddress); + zeroAddress.sin_family = AF_INET; + + _reachabilityQueue = + dispatch_queue_create("com.google.GDTCORReachability", DISPATCH_QUEUE_SERIAL); + _reachabilityRef = SCNetworkReachabilityCreateWithAddress( + kCFAllocatorDefault, (const struct sockaddr *)&zeroAddress); + Boolean success = SCNetworkReachabilitySetDispatchQueue(_reachabilityRef, _reachabilityQueue); + if (!success) { + GDTCORLogWarning(GDTCORMCWReachabilityFailed, @"%@", @"The reachability queue wasn't set."); + } + success = SCNetworkReachabilitySetCallback(_reachabilityRef, GDTCORReachabilityCallback, NULL); + if (!success) { + GDTCORLogWarning(GDTCORMCWReachabilityFailed, @"%@", + @"The reachability callback wasn't set."); + } + + // Get the initial set of flags. + dispatch_async(_reachabilityQueue, ^{ + Boolean valid = SCNetworkReachabilityGetFlags(self->_reachabilityRef, &self->_flags); + if (!valid) { + GDTCORLogDebug("%@", @"Determining reachability failed."); + self->_flags = 0; + } + }); + } + return self; +} + +- (void)setCallbackFlags:(SCNetworkReachabilityFlags)flags { + if (_callbackFlags != flags) { + self->_callbackFlags = flags; + } +} + +@end + +static void GDTCORReachabilityCallback(SCNetworkReachabilityRef reachability, + SCNetworkReachabilityFlags flags, + void *info) { + GDTCORLogDebug("Reachability changed, new flags: %d", flags); + [[GDTCORReachability sharedInstance] setCallbackFlags:flags]; +} + +#endif diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m new file mode 100644 index 0000000..1440376 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m @@ -0,0 +1,142 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "GDTCORLibrary/Public/GDTCORRegistrar.h" +#import "GDTCORLibrary/Private/GDTCORRegistrar_Private.h" + +#import + +@implementation GDTCORRegistrar { + /** Backing ivar for targetToUploader property. */ + NSMutableDictionary> *_targetToUploader; + + /** Backing ivar for targetToPrioritizer property. */ + NSMutableDictionary> *_targetToPrioritizer; +} + ++ (instancetype)sharedInstance { + static GDTCORRegistrar *sharedInstance; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[GDTCORRegistrar alloc] init]; + }); + return sharedInstance; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _registrarQueue = dispatch_queue_create("com.google.GDTCORRegistrar", DISPATCH_QUEUE_SERIAL); + _targetToPrioritizer = [[NSMutableDictionary alloc] init]; + _targetToUploader = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (void)registerUploader:(id)backend target:(GDTCORTarget)target { + __weak GDTCORRegistrar *weakSelf = self; + dispatch_async(_registrarQueue, ^{ + GDTCORRegistrar *strongSelf = weakSelf; + if (strongSelf) { + GDTCORLogDebug("Registered an uploader: %@ for target:%ld", backend, (long)target); + strongSelf->_targetToUploader[@(target)] = backend; + } + }); +} + +- (void)registerPrioritizer:(id)prioritizer target:(GDTCORTarget)target { + __weak GDTCORRegistrar *weakSelf = self; + dispatch_async(_registrarQueue, ^{ + GDTCORRegistrar *strongSelf = weakSelf; + if (strongSelf) { + GDTCORLogDebug("Registered a prioritizer: %@ for target:%ld", prioritizer, (long)target); + strongSelf->_targetToPrioritizer[@(target)] = prioritizer; + } + }); +} + +- (NSMutableDictionary> *)targetToUploader { + __block NSMutableDictionary> *targetToUploader; + __weak GDTCORRegistrar *weakSelf = self; + dispatch_sync(_registrarQueue, ^{ + GDTCORRegistrar *strongSelf = weakSelf; + if (strongSelf) { + targetToUploader = strongSelf->_targetToUploader; + } + }); + return targetToUploader; +} + +- (NSMutableDictionary> *)targetToPrioritizer { + __block NSMutableDictionary> *targetToPrioritizer; + __weak GDTCORRegistrar *weakSelf = self; + dispatch_sync(_registrarQueue, ^{ + GDTCORRegistrar *strongSelf = weakSelf; + if (strongSelf) { + targetToPrioritizer = strongSelf->_targetToPrioritizer; + } + }); + return targetToPrioritizer; +} + +#pragma mark - GDTCORLifecycleProtocol + +- (void)appWillBackground:(nonnull GDTCORApplication *)app { + dispatch_async(_registrarQueue, ^{ + for (id uploader in [self->_targetToUploader allValues]) { + if ([uploader respondsToSelector:@selector(appWillBackground:)]) { + [uploader appWillBackground:app]; + } + } + for (id prioritizer in [self->_targetToPrioritizer allValues]) { + if ([prioritizer respondsToSelector:@selector(appWillBackground:)]) { + [prioritizer appWillBackground:app]; + } + } + }); +} + +- (void)appWillForeground:(nonnull GDTCORApplication *)app { + dispatch_async(_registrarQueue, ^{ + for (id uploader in [self->_targetToUploader allValues]) { + if ([uploader respondsToSelector:@selector(appWillForeground:)]) { + [uploader appWillForeground:app]; + } + } + for (id prioritizer in [self->_targetToPrioritizer allValues]) { + if ([prioritizer respondsToSelector:@selector(appWillForeground:)]) { + [prioritizer appWillForeground:app]; + } + } + }); +} + +- (void)appWillTerminate:(nonnull GDTCORApplication *)app { + dispatch_sync(_registrarQueue, ^{ + for (id uploader in [self->_targetToUploader allValues]) { + if ([uploader respondsToSelector:@selector(appWillTerminate:)]) { + [uploader appWillTerminate:app]; + } + } + for (id prioritizer in [self->_targetToPrioritizer allValues]) { + if ([prioritizer respondsToSelector:@selector(appWillTerminate:)]) { + [prioritizer appWillTerminate:app]; + } + } + }); +} + +@end diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORStorage.m b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORStorage.m new file mode 100644 index 0000000..23a5ee7 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORStorage.m @@ -0,0 +1,333 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "GDTCORLibrary/Private/GDTCORStorage.h" +#import "GDTCORLibrary/Private/GDTCORStorage_Private.h" + +#import +#import +#import +#import +#import + +#import "GDTCORLibrary/Private/GDTCOREvent_Private.h" +#import "GDTCORLibrary/Private/GDTCORRegistrar_Private.h" +#import "GDTCORLibrary/Private/GDTCORUploadCoordinator.h" + +/** Creates and/or returns a singleton NSString that is the shared storage path. + * + * @return The SDK event storage path. + */ +static NSString *GDTCORStoragePath() { + static NSString *storagePath; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSString *cachePath = + NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0]; + storagePath = [NSString stringWithFormat:@"%@/google-sdks-events", cachePath]; + GDTCORLogDebug("Events will be saved to %@", storagePath); + }); + return storagePath; +} + +@implementation GDTCORStorage + ++ (NSString *)archivePath { + static NSString *archivePath; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + archivePath = [GDTCORStoragePath() stringByAppendingPathComponent:@"GDTCORStorageArchive"]; + }); + return archivePath; +} + ++ (instancetype)sharedInstance { + static GDTCORStorage *sharedStorage; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedStorage = [[GDTCORStorage alloc] init]; + }); + return sharedStorage; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _storageQueue = dispatch_queue_create("com.google.GDTCORStorage", DISPATCH_QUEUE_SERIAL); + _targetToEventSet = [[NSMutableDictionary alloc] init]; + _storedEvents = [[NSMutableOrderedSet alloc] init]; + _uploadCoordinator = [GDTCORUploadCoordinator sharedInstance]; + } + return self; +} + +- (void)storeEvent:(GDTCOREvent *)event { + GDTCORLogDebug("Saving event: %@", event); + if (event == nil) { + GDTCORLogDebug("%@", @"The event was nil, so it was not saved."); + return; + } + + [self createEventDirectoryIfNotExists]; + + __block GDTCORBackgroundIdentifier bgID = GDTCORBackgroundIdentifierInvalid; + bgID = [[GDTCORApplication sharedApplication] + beginBackgroundTaskWithName:@"GDTStorage" + expirationHandler:^{ + // End the background task if it's still valid. + [[GDTCORApplication sharedApplication] endBackgroundTask:bgID]; + bgID = GDTCORBackgroundIdentifierInvalid; + }]; + + dispatch_async(_storageQueue, ^{ + // Check that a backend implementation is available for this target. + NSInteger target = event.target; + + // Check that a prioritizer is available for this target. + id prioritizer = + [GDTCORRegistrar sharedInstance].targetToPrioritizer[@(target)]; + GDTCORAssert(prioritizer, @"There's no prioritizer registered for the given target. Are you " + @"sure you've added the support library for the backend you need?"); + + // Write the transport bytes to disk, get a filename. + GDTCORAssert(event.dataObjectTransportBytes, @"The event should have been serialized to bytes"); + NSURL *eventFile = [self saveEventBytesToDisk:event.dataObjectTransportBytes + eventHash:event.hash]; + GDTCORLogDebug("Event saved to disk: %@", eventFile); + GDTCORDataFuture *dataFuture = [[GDTCORDataFuture alloc] initWithFileURL:eventFile]; + GDTCORStoredEvent *storedEvent = [event storedEventWithDataFuture:dataFuture]; + + // Add event to tracking collections. + [self addEventToTrackingCollections:storedEvent]; + + // Have the prioritizer prioritize the event. + [prioritizer prioritizeEvent:storedEvent]; + + // Check the QoS, if it's high priority, notify the target that it has a high priority event. + if (event.qosTier == GDTCOREventQoSFast) { + [self.uploadCoordinator forceUploadForTarget:target]; + } + + // Write state to disk if we're in the background. + if ([[GDTCORApplication sharedApplication] isRunningInBackground]) { + GDTCORLogDebug("%@", @"Saving storage state because the app is running in the background"); + if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) { + NSError *error; + NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self + requiringSecureCoding:YES + error:&error]; + [data writeToFile:[GDTCORStorage archivePath] atomically:YES]; + } else { +#if !TARGET_OS_MACCATALYST && !TARGET_OS_WATCH + [NSKeyedArchiver archiveRootObject:self toFile:[GDTCORStorage archivePath]]; +#endif + } + } + + // Cancel or end the associated background task if it's still valid. + [[GDTCORApplication sharedApplication] endBackgroundTask:bgID]; + bgID = GDTCORBackgroundIdentifierInvalid; + GDTCORLogDebug("Event %@ is stored. There are %ld events stored on disk", event, + (unsigned long)self->_storedEvents.count); + }); +} + +- (void)removeEvents:(NSSet *)events { + NSSet *eventsToRemove = [events copy]; + dispatch_async(_storageQueue, ^{ + for (GDTCORStoredEvent *event in eventsToRemove) { + // Remove from disk, first and foremost. + NSError *error; + if (event.dataFuture.fileURL) { + NSURL *fileURL = event.dataFuture.fileURL; + [[NSFileManager defaultManager] removeItemAtURL:fileURL error:&error]; + GDTCORAssert(error == nil, @"There was an error removing an event file: %@", error); + GDTCORLogDebug("Removed event from disk: %@", fileURL); + } + + // Remove from the tracking collections. + [self.storedEvents removeObject:event]; + [self.targetToEventSet[event.target] removeObject:event]; + } + }); +} + +#pragma mark - Private helper methods + +/** Creates the storage directory if it does not exist. */ +- (void)createEventDirectoryIfNotExists { + NSError *error; + BOOL result = [[NSFileManager defaultManager] createDirectoryAtPath:GDTCORStoragePath() + withIntermediateDirectories:YES + attributes:0 + error:&error]; + if (!result || error) { + GDTCORLogError(GDTCORMCEDirectoryCreationError, @"Error creating the directory: %@", error); + } +} + +/** Saves the event's dataObjectTransportBytes to a file using NSData mechanisms. + * + * @note This method should only be called from a method within a block on _storageQueue to maintain + * thread safety. + * + * @param transportBytes The transport bytes of the event. + * @param eventHash The hash value of the event. + * @return The filename + */ +- (NSURL *)saveEventBytesToDisk:(NSData *)transportBytes eventHash:(NSUInteger)eventHash { + NSString *storagePath = GDTCORStoragePath(); + NSString *event = [NSString stringWithFormat:@"event-%lu", (unsigned long)eventHash]; + NSURL *eventFilePath = [NSURL fileURLWithPath:[storagePath stringByAppendingPathComponent:event]]; + + GDTCORAssert(![[NSFileManager defaultManager] fileExistsAtPath:eventFilePath.path], + @"An event shouldn't already exist at this path: %@", eventFilePath.path); + + BOOL writingSuccess = [transportBytes writeToURL:eventFilePath atomically:YES]; + if (!writingSuccess) { + GDTCORLogError(GDTCORMCEFileWriteError, @"An event file could not be written: %@", + eventFilePath); + } + + return eventFilePath; +} + +/** Adds the event to internal tracking collections. + * + * @note This method should only be called from a method within a block on _storageQueue to maintain + * thread safety. + * + * @param event The event to track. + */ +- (void)addEventToTrackingCollections:(GDTCORStoredEvent *)event { + [_storedEvents addObject:event]; + NSMutableSet *events = self.targetToEventSet[event.target]; + events = events ? events : [[NSMutableSet alloc] init]; + [events addObject:event]; + _targetToEventSet[event.target] = events; +} + +#pragma mark - GDTCORLifecycleProtocol + +- (void)appWillForeground:(GDTCORApplication *)app { + if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) { + NSError *error; + NSData *data = [NSData dataWithContentsOfFile:[GDTCORStorage archivePath]]; + if (data) { + [NSKeyedUnarchiver unarchivedObjectOfClass:[GDTCORStorage class] fromData:data error:&error]; + } + } else { +#if !TARGET_OS_MACCATALYST && !TARGET_OS_WATCH + [NSKeyedUnarchiver unarchiveObjectWithFile:[GDTCORStorage archivePath]]; +#endif + } +} + +- (void)appWillBackground:(GDTCORApplication *)app { + dispatch_async(_storageQueue, ^{ + // Immediately request a background task to run until the end of the current queue of work, and + // cancel it once the work is done. + __block GDTCORBackgroundIdentifier bgID = + [app beginBackgroundTaskWithName:@"GDTStorage" + expirationHandler:^{ + [app endBackgroundTask:bgID]; + bgID = GDTCORBackgroundIdentifierInvalid; + }]; + + if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) { + NSError *error; + NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self + requiringSecureCoding:YES + error:&error]; + [data writeToFile:[GDTCORStorage archivePath] atomically:YES]; + } else { +#if !TARGET_OS_MACCATALYST && !TARGET_OS_WATCH + [NSKeyedArchiver archiveRootObject:self toFile:[GDTCORStorage archivePath]]; +#endif + } + + // End the background task if it's still valid. + [app endBackgroundTask:bgID]; + bgID = GDTCORBackgroundIdentifierInvalid; + }); +} + +- (void)appWillTerminate:(GDTCORApplication *)application { + if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) { + NSError *error; + NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self + requiringSecureCoding:YES + error:&error]; + [data writeToFile:[GDTCORStorage archivePath] atomically:YES]; + } else { +#if !TARGET_OS_MACCATALYST && !TARGET_OS_WATCH + [NSKeyedArchiver archiveRootObject:self toFile:[GDTCORStorage archivePath]]; +#endif + } +} + +#pragma mark - NSSecureCoding + +/** The NSKeyedCoder key for the storedEvents property. */ +static NSString *const kGDTCORStorageStoredEventsKey = @"GDTCORStorageStoredEventsKey"; + +/** The NSKeyedCoder key for the targetToEventSet property. */ +static NSString *const kGDTCORStorageTargetToEventSetKey = @"GDTCORStorageTargetToEventSetKey"; + +/** The NSKeyedCoder key for the uploadCoordinator property. */ +static NSString *const kGDTCORStorageUploadCoordinatorKey = @"GDTCORStorageUploadCoordinatorKey"; + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + // Create the singleton and populate its ivars. + GDTCORStorage *sharedInstance = [self.class sharedInstance]; + dispatch_sync(sharedInstance.storageQueue, ^{ + NSSet *classes = + [NSSet setWithObjects:[NSMutableOrderedSet class], [GDTCORStoredEvent class], nil]; + sharedInstance->_storedEvents = [aDecoder decodeObjectOfClasses:classes + forKey:kGDTCORStorageStoredEventsKey]; + classes = [NSSet setWithObjects:[NSMutableDictionary class], [NSMutableSet class], + [GDTCORStoredEvent class], nil]; + sharedInstance->_targetToEventSet = + [aDecoder decodeObjectOfClasses:classes forKey:kGDTCORStorageTargetToEventSetKey]; + sharedInstance->_uploadCoordinator = + [aDecoder decodeObjectOfClass:[GDTCORUploadCoordinator class] + forKey:kGDTCORStorageUploadCoordinatorKey]; + }); + return sharedInstance; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + GDTCORStorage *sharedInstance = [self.class sharedInstance]; + NSMutableOrderedSet *storedEvents = sharedInstance->_storedEvents; + if (storedEvents) { + [aCoder encodeObject:storedEvents forKey:kGDTCORStorageStoredEventsKey]; + } + NSMutableDictionary *> *targetToEventSet = + sharedInstance->_targetToEventSet; + if (targetToEventSet) { + [aCoder encodeObject:targetToEventSet forKey:kGDTCORStorageTargetToEventSetKey]; + } + GDTCORUploadCoordinator *uploadCoordinator = sharedInstance->_uploadCoordinator; + if (uploadCoordinator) { + [aCoder encodeObject:uploadCoordinator forKey:kGDTCORStorageUploadCoordinatorKey]; + } +} + +@end diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORStoredEvent.m b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORStoredEvent.m new file mode 100644 index 0000000..a243394 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORStoredEvent.m @@ -0,0 +1,95 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import + +#import "GDTCORLibrary/Private/GDTCORStorage_Private.h" + +@implementation GDTCORStoredEvent + +- (instancetype)initWithEvent:(GDTCOREvent *)event + dataFuture:(nonnull GDTCORDataFuture *)dataFuture { + self = [super init]; + if (self) { + _dataFuture = dataFuture; + _mappingID = event.mappingID; + _target = @(event.target); + _qosTier = event.qosTier; + _clockSnapshot = event.clockSnapshot; + _customPrioritizationParams = event.customPrioritizationParams; + } + return self; +} + +#pragma mark - NSSecureCoding + +/** Coding key for the dataFuture ivar. */ +static NSString *kDataFutureKey = @"GDTCORStoredEventDataFutureKey"; + +/** Coding key for mappingID ivar. */ +static NSString *kMappingIDKey = @"GDTCORStoredEventMappingIDKey"; + +/** Coding key for target ivar. */ +static NSString *kTargetKey = @"GDTCORStoredEventTargetKey"; + +/** Coding key for qosTier ivar. */ +static NSString *kQosTierKey = @"GDTCORStoredEventQosTierKey"; + +/** Coding key for clockSnapshot ivar. */ +static NSString *kClockSnapshotKey = @"GDTCORStoredEventClockSnapshotKey"; + +/** Coding key for customPrioritizationParams ivar. */ +static NSString *kCustomPrioritizationParamsKey = @"GDTCORStoredEventcustomPrioritizationParamsKey"; + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (void)encodeWithCoder:(nonnull NSCoder *)aCoder { + [aCoder encodeObject:_dataFuture forKey:kDataFutureKey]; + [aCoder encodeObject:_mappingID forKey:kMappingIDKey]; + [aCoder encodeObject:_target forKey:kTargetKey]; + [aCoder encodeObject:@(_qosTier) forKey:kQosTierKey]; + [aCoder encodeObject:_clockSnapshot forKey:kClockSnapshotKey]; + [aCoder encodeObject:_customPrioritizationParams forKey:kCustomPrioritizationParamsKey]; +} + +- (nullable instancetype)initWithCoder:(nonnull NSCoder *)aDecoder { + self = [self init]; + if (self) { + _dataFuture = [aDecoder decodeObjectOfClass:[GDTCORDataFuture class] forKey:kDataFutureKey]; + _mappingID = [aDecoder decodeObjectOfClass:[NSString class] forKey:kMappingIDKey]; + _target = [aDecoder decodeObjectOfClass:[NSNumber class] forKey:kTargetKey]; + NSNumber *qosTier = [aDecoder decodeObjectOfClass:[NSNumber class] forKey:kQosTierKey]; + _qosTier = [qosTier intValue]; + _clockSnapshot = [aDecoder decodeObjectOfClass:[GDTCORClock class] forKey:kClockSnapshotKey]; + _customPrioritizationParams = [aDecoder decodeObjectOfClass:[NSDictionary class] + forKey:kCustomPrioritizationParamsKey]; + } + return self; +} + +- (BOOL)isEqual:(GDTCORStoredEvent *)other { + return [self hash] == [other hash]; +} + +- (NSUInteger)hash { + return [_dataFuture hash] ^ [_mappingID hash] ^ [_target hash] ^ [_clockSnapshot hash] ^ _qosTier; +} + +@end diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORTransformer.m b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORTransformer.m new file mode 100644 index 0000000..f892040 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORTransformer.m @@ -0,0 +1,91 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "GDTCORLibrary/Private/GDTCORTransformer.h" +#import "GDTCORLibrary/Private/GDTCORTransformer_Private.h" + +#import +#import +#import +#import +#import + +#import "GDTCORLibrary/Private/GDTCORStorage.h" + +@implementation GDTCORTransformer + ++ (instancetype)sharedInstance { + static GDTCORTransformer *eventTransformer; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + eventTransformer = [[self alloc] init]; + }); + return eventTransformer; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _eventWritingQueue = + dispatch_queue_create("com.google.GDTCORTransformer", DISPATCH_QUEUE_SERIAL); + _storageInstance = [GDTCORStorage sharedInstance]; + } + return self; +} + +- (void)transformEvent:(GDTCOREvent *)event + withTransformers:(NSArray> *)transformers { + GDTCORAssert(event, @"You can't write a nil event"); + + __block GDTCORBackgroundIdentifier bgID = GDTCORBackgroundIdentifierInvalid; + bgID = [[GDTCORApplication sharedApplication] + beginBackgroundTaskWithName:@"GDTTransformer" + expirationHandler:^{ + [[GDTCORApplication sharedApplication] endBackgroundTask:bgID]; + bgID = GDTCORBackgroundIdentifierInvalid; + }]; + dispatch_async(_eventWritingQueue, ^{ + GDTCOREvent *transformedEvent = event; + for (id transformer in transformers) { + if ([transformer respondsToSelector:@selector(transform:)]) { + GDTCORLogDebug("Applying a transformer to event %@", event); + transformedEvent = [transformer transform:transformedEvent]; + if (!transformedEvent) { + return; + } + } else { + GDTCORLogError(GDTCORMCETransformerDoesntImplementTransform, + @"Transformer doesn't implement transform: %@", transformer); + return; + } + } + [self.storageInstance storeEvent:transformedEvent]; + + // The work is done, cancel the background task if it's valid. + [[GDTCORApplication sharedApplication] endBackgroundTask:bgID]; + bgID = GDTCORBackgroundIdentifierInvalid; + }); +} + +#pragma mark - GDTCORLifecycleProtocol + +- (void)appWillTerminate:(GDTCORApplication *)application { + // Flush the queue immediately. + dispatch_sync(_eventWritingQueue, ^{ + }); +} + +@end diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORTransport.m b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORTransport.m new file mode 100644 index 0000000..b095c85 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORTransport.m @@ -0,0 +1,73 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import +#import "GDTCORLibrary/Private/GDTCORTransport_Private.h" + +#import +#import +#import + +#import "GDTCORLibrary/Private/GDTCORTransformer.h" + +@implementation GDTCORTransport + +- (nullable instancetype)initWithMappingID:(NSString *)mappingID + transformers: + (nullable NSArray> *)transformers + target:(NSInteger)target { + GDTCORAssert(mappingID.length > 0, @"A mapping ID cannot be nil or empty"); + GDTCORAssert(target > 0, @"A target cannot be negative or 0"); + if (mappingID == nil || mappingID.length == 0 || target <= 0) { + return nil; + } + self = [super init]; + if (self) { + _mappingID = mappingID; + _transformers = transformers; + _target = target; + _transformerInstance = [GDTCORTransformer sharedInstance]; + } + GDTCORLogDebug("Transport object created. mappingID:%@ transformers:%@ target:%ld", _mappingID, + _transformers, (long)_target); + return self; +} + +- (void)sendTelemetryEvent:(GDTCOREvent *)event { + // TODO: Determine if sending an event before registration is allowed. + GDTCORAssert(event, @"You can't send a nil event"); + GDTCOREvent *copiedEvent = [event copy]; + copiedEvent.qosTier = GDTCOREventQoSTelemetry; + copiedEvent.clockSnapshot = [GDTCORClock snapshot]; + [self.transformerInstance transformEvent:copiedEvent withTransformers:_transformers]; + GDTCORLogDebug("Telemetry event sent: %@", event); +} + +- (void)sendDataEvent:(GDTCOREvent *)event { + // TODO: Determine if sending an event before registration is allowed. + GDTCORAssert(event, @"You can't send a nil event"); + GDTCORAssert(event.qosTier != GDTCOREventQoSTelemetry, @"Use -sendTelemetryEvent, please."); + GDTCOREvent *copiedEvent = [event copy]; + copiedEvent.clockSnapshot = [GDTCORClock snapshot]; + [self.transformerInstance transformEvent:copiedEvent withTransformers:_transformers]; + GDTCORLogDebug("Data event sent: %@", event); +} + +- (GDTCOREvent *)eventForTransport { + return [[GDTCOREvent alloc] initWithMappingID:_mappingID target:_target]; +} + +@end diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORUploadCoordinator.m b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORUploadCoordinator.m new file mode 100644 index 0000000..0dbc1b4 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORUploadCoordinator.m @@ -0,0 +1,273 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "GDTCORLibrary/Private/GDTCORUploadCoordinator.h" + +#import +#import +#import + +#import "GDTCORLibrary/Private/GDTCORReachability.h" +#import "GDTCORLibrary/Private/GDTCORRegistrar_Private.h" +#import "GDTCORLibrary/Private/GDTCORStorage.h" + +@implementation GDTCORUploadCoordinator + ++ (instancetype)sharedInstance { + static GDTCORUploadCoordinator *sharedUploader; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedUploader = [[GDTCORUploadCoordinator alloc] init]; + [sharedUploader startTimer]; + }); + return sharedUploader; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _coordinationQueue = + dispatch_queue_create("com.google.GDTCORUploadCoordinator", DISPATCH_QUEUE_SERIAL); + _registrar = [GDTCORRegistrar sharedInstance]; + _timerInterval = 30 * NSEC_PER_SEC; + _timerLeeway = 5 * NSEC_PER_SEC; + _targetToInFlightPackages = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (void)forceUploadForTarget:(GDTCORTarget)target { + dispatch_async(_coordinationQueue, ^{ + GDTCORLogDebug("Forcing an upload of target %ld", (long)target); + GDTCORUploadConditions conditions = [self uploadConditions]; + conditions |= GDTCORUploadConditionHighPriority; + [self uploadTargets:@[ @(target) ] conditions:conditions]; + }); +} + +#pragma mark - Property overrides + +// GDTCORStorage and GDTCORUploadCoordinator +sharedInstance methods call each other, so this breaks +// the loop. +- (GDTCORStorage *)storage { + if (!_storage) { + _storage = [GDTCORStorage sharedInstance]; + } + return _storage; +} + +#pragma mark - Private helper methods + +/** Starts a timer that checks whether or not events can be uploaded at regular intervals. It will + * check the next-upload clocks of all targets to determine if an upload attempt can be made. + */ +- (void)startTimer { + dispatch_sync(_coordinationQueue, ^{ + self->_timer = + dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self->_coordinationQueue); + dispatch_source_set_timer(self->_timer, DISPATCH_TIME_NOW, self->_timerInterval, + self->_timerLeeway); + dispatch_source_set_event_handler(self->_timer, ^{ + if (![[GDTCORApplication sharedApplication] isRunningInBackground]) { + GDTCORUploadConditions conditions = [self uploadConditions]; + GDTCORLogDebug("%@", @"Upload timer fired"); + [self uploadTargets:[self.registrar.targetToUploader allKeys] conditions:conditions]; + } + }); + GDTCORLogDebug("%@", @"Upload timer started"); + dispatch_resume(self->_timer); + }); +} + +/** Stops the currently running timer. */ +- (void)stopTimer { + if (_timer) { + dispatch_source_cancel(_timer); + } +} + +/** Triggers the uploader implementations for the given targets to upload. + * + * @param targets An array of targets to trigger. + * @param conditions The set of upload conditions. + */ +- (void)uploadTargets:(NSArray *)targets conditions:(GDTCORUploadConditions)conditions { + dispatch_async(_coordinationQueue, ^{ + if ((conditions & GDTCORUploadConditionNoNetwork) == GDTCORUploadConditionNoNetwork) { + return; + } + for (NSNumber *target in targets) { + // Don't trigger uploads for targets that have an in-flight package already. + if (self->_targetToInFlightPackages[target]) { + GDTCORLogDebug("Target %@ will not upload, there's an upload in flight", target); + continue; + } + // Ask the uploader if they can upload and do so, if it can. + id uploader = self.registrar.targetToUploader[target]; + if ([uploader readyToUploadWithConditions:conditions]) { + id prioritizer = self.registrar.targetToPrioritizer[target]; + GDTCORUploadPackage *package = [prioritizer uploadPackageWithConditions:conditions]; + if (package.events.count) { + self->_targetToInFlightPackages[target] = package; + GDTCORLogDebug("Package of %ld events is being handed over to an uploader", + (long)package.events.count); + [uploader uploadPackage:package]; + } else { + [package completeDelivery]; + } + } + GDTCORLogDebug("Target %@ is not ready to upload", target); + } + }); +} + +/** Returns the current upload conditions after making determinations about the network connection. + * + * @return The current upload conditions. + */ +- (GDTCORUploadConditions)uploadConditions { +#if TARGET_OS_WATCH + return GDTCORUploadConditionNoNetwork; +#else + SCNetworkReachabilityFlags currentFlags = [GDTCORReachability currentFlags]; + BOOL reachable = + (currentFlags & kSCNetworkReachabilityFlagsReachable) == kSCNetworkReachabilityFlagsReachable; + BOOL connectionRequired = (currentFlags & kSCNetworkReachabilityFlagsConnectionRequired) == + kSCNetworkReachabilityFlagsConnectionRequired; + BOOL networkConnected = reachable && !connectionRequired; + + if (!networkConnected) { + return GDTCORUploadConditionNoNetwork; + } + + BOOL isWWAN = GDTCORReachabilityFlagsContainWWAN(currentFlags); + if (isWWAN) { + return GDTCORUploadConditionMobileData; + } else { + return GDTCORUploadConditionWifiData; + } +#endif +} + +#pragma mark - NSSecureCoding support + +/** The NSKeyedCoder key for the targetToInFlightPackages property. */ +static NSString *const ktargetToInFlightPackagesKey = + @"GDTCORUploadCoordinatortargetToInFlightPackages"; + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + GDTCORUploadCoordinator *sharedCoordinator = [GDTCORUploadCoordinator sharedInstance]; + dispatch_sync(sharedCoordinator->_coordinationQueue, ^{ + @try { + sharedCoordinator->_targetToInFlightPackages = + [aDecoder decodeObjectOfClass:[NSMutableDictionary class] + forKey:ktargetToInFlightPackagesKey]; + + } @catch (NSException *exception) { + sharedCoordinator->_targetToInFlightPackages = [NSMutableDictionary dictionary]; + } + }); + return sharedCoordinator; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + dispatch_sync(_coordinationQueue, ^{ + // All packages that have been given to uploaders need to be tracked so that their expiration + // timers can be called. + if (self->_targetToInFlightPackages.count > 0) { + [aCoder encodeObject:self->_targetToInFlightPackages forKey:ktargetToInFlightPackagesKey]; + } + }); +} + +#pragma mark - GDTCORLifecycleProtocol + +- (void)appWillForeground:(GDTCORApplication *)app { + // Not entirely thread-safe, but it should be fine. + [self startTimer]; +} + +- (void)appWillBackground:(GDTCORApplication *)app { + // Should be thread-safe. If it ends up not being, put this in a dispatch_sync. + [self stopTimer]; +} + +- (void)appWillTerminate:(GDTCORApplication *)application { + dispatch_sync(_coordinationQueue, ^{ + [self stopTimer]; + }); +} + +#pragma mark - GDTCORUploadPackageProtocol + +- (void)packageDelivered:(GDTCORUploadPackage *)package successful:(BOOL)successful { + if (!_coordinationQueue) { + return; + } + dispatch_async(_coordinationQueue, ^{ + NSNumber *targetNumber = @(package.target); + NSMutableDictionary *targetToInFlightPackages = + self->_targetToInFlightPackages; + GDTCORRegistrar *registrar = self->_registrar; + if (targetToInFlightPackages) { + [targetToInFlightPackages removeObjectForKey:targetNumber]; + } + if (registrar) { + id prioritizer = registrar.targetToPrioritizer[targetNumber]; + if (!prioritizer) { + GDTCORLogError(GDTCORMCEPrioritizerError, + @"A prioritizer should be registered for this target: %@", targetNumber); + } + if ([prioritizer respondsToSelector:@selector(packageDelivered:successful:)]) { + [prioritizer packageDelivered:package successful:successful]; + } + } + if (package.events != nil) { + [self.storage removeEvents:package.events]; + } + }); +} + +- (void)packageExpired:(GDTCORUploadPackage *)package { + if (!_coordinationQueue) { + return; + } + dispatch_async(_coordinationQueue, ^{ + NSNumber *targetNumber = @(package.target); + NSMutableDictionary *targetToInFlightPackages = + self->_targetToInFlightPackages; + GDTCORRegistrar *registrar = self->_registrar; + if (targetToInFlightPackages) { + [targetToInFlightPackages removeObjectForKey:targetNumber]; + } + if (registrar) { + id prioritizer = registrar.targetToPrioritizer[targetNumber]; + id uploader = registrar.targetToUploader[targetNumber]; + if ([prioritizer respondsToSelector:@selector(packageExpired:)]) { + [prioritizer packageExpired:package]; + } + if ([uploader respondsToSelector:@selector(packageExpired:)]) { + [uploader packageExpired:package]; + } + } + }); +} + +@end diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORUploadPackage.m b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORUploadPackage.m new file mode 100644 index 0000000..6ac5f4f --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORUploadPackage.m @@ -0,0 +1,159 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import +#import +#import + +#import "GDTCORLibrary/Private/GDTCORStorage_Private.h" +#import "GDTCORLibrary/Private/GDTCORUploadCoordinator.h" +#import "GDTCORLibrary/Private/GDTCORUploadPackage_Private.h" + +@implementation GDTCORUploadPackage { + /** If YES, the package's -completeDelivery method has been called. */ + BOOL _isDelivered; + + /** If YES, is being handled by the handler. */ + BOOL _isHandled; + + /** A timer that will regularly check to see whether this package has expired or not. */ + NSTimer *_expirationTimer; +} + +- (instancetype)initWithTarget:(GDTCORTarget)target { + self = [super init]; + if (self) { + _target = target; + _storage = [GDTCORStorage sharedInstance]; + _deliverByTime = [GDTCORClock clockSnapshotInTheFuture:180000]; + _handler = [GDTCORUploadCoordinator sharedInstance]; + _expirationTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 + target:self + selector:@selector(checkIfPackageIsExpired:) + userInfo:nil + repeats:YES]; + } + GDTCORLogDebug("Upload package created %@", self); + return self; +} + +- (instancetype)copy { + GDTCORUploadPackage *newPackage = [[GDTCORUploadPackage alloc] initWithTarget:_target]; + newPackage->_events = [_events copy]; + GDTCORLogDebug("Copying UploadPackage %@ to %@", self, newPackage); + return newPackage; +} + +- (NSUInteger)hash { + return [_events hash]; +} + +- (BOOL)isEqual:(id)object { + return [self hash] == [object hash]; +} + +- (void)dealloc { + [_expirationTimer invalidate]; +} + +- (void)setStorage:(GDTCORStorage *)storage { + if (storage != _storage) { + _storage = storage; + } +} + +- (void)completeDelivery { + if (_isDelivered) { + GDTCORLogError(GDTCORMCEDeliverTwice, @"%@", + @"It's an API violation to call -completeDelivery twice."); + } + _isDelivered = YES; + if (!_isHandled && _handler && + [_handler respondsToSelector:@selector(packageDelivered:successful:)]) { + [_expirationTimer invalidate]; + _isHandled = YES; + [_handler packageDelivered:self successful:YES]; + } + GDTCORLogDebug("Upload package delivered: %@", self); +} + +- (void)retryDeliveryInTheFuture { + if (!_isHandled && _handler && + [_handler respondsToSelector:@selector(packageDelivered:successful:)]) { + [_expirationTimer invalidate]; + _isHandled = YES; + [_handler packageDelivered:self successful:NO]; + } + GDTCORLogDebug("Upload package will retry in the future: %@", self); +} + +- (void)checkIfPackageIsExpired:(NSTimer *)timer { + if ([[GDTCORClock snapshot] isAfter:_deliverByTime]) { + if (_handler && [_handler respondsToSelector:@selector(packageExpired:)]) { + _isHandled = YES; + [_expirationTimer invalidate]; + GDTCORLogDebug("Upload package expired: %@", self); + [_handler packageExpired:self]; + } + } +} + +#pragma mark - NSSecureCoding + +/** The keyed archiver key for the events property. */ +static NSString *const kEventsKey = @"GDTCORUploadPackageEventsKey"; + +/** The keyed archiver key for the _isHandled property. */ +static NSString *const kDeliverByTimeKey = @"GDTCORUploadPackageDeliveryByTimeKey"; + +/** The keyed archiver key for the _isHandled ivar. */ +static NSString *const kIsHandledKey = @"GDTCORUploadPackageIsHandledKey"; + +/** The keyed archiver key for the handler property. */ +static NSString *const kHandlerKey = @"GDTCORUploadPackageHandlerKey"; + +/** The keyed archiver key for the target property. */ +static NSString *const kTargetKey = @"GDTCORUploadPackageTargetKey"; + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (void)encodeWithCoder:(nonnull NSCoder *)aCoder { + [aCoder encodeObject:_events forKey:kEventsKey]; + [aCoder encodeObject:_deliverByTime forKey:kDeliverByTimeKey]; + [aCoder encodeBool:_isHandled forKey:kIsHandledKey]; + [aCoder encodeObject:_handler forKey:kHandlerKey]; + [aCoder encodeInteger:_target forKey:kTargetKey]; +} + +- (nullable instancetype)initWithCoder:(nonnull NSCoder *)aDecoder { + GDTCORTarget target = [aDecoder decodeIntegerForKey:kTargetKey]; + self = [self initWithTarget:target]; + if (self) { + NSSet *classes = [NSSet setWithObjects:[NSSet class], [GDTCORStoredEvent class], nil]; + _events = [aDecoder decodeObjectOfClasses:classes forKey:kEventsKey]; + _deliverByTime = [aDecoder decodeObjectOfClass:[GDTCORClock class] forKey:kDeliverByTimeKey]; + _isHandled = [aDecoder decodeBoolForKey:kIsHandledKey]; + // _handler isn't technically NSSecureCoding, because we don't know the class of this object. + // but it gets decoded anyway. + } + return self; +} + +@end diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCOREvent_Private.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCOREvent_Private.h new file mode 100644 index 0000000..f7f8a28 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCOREvent_Private.h @@ -0,0 +1,30 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface GDTCOREvent () + +/** The serialized bytes of the event data object. */ +@property(nonatomic) NSData *dataObjectTransportBytes; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORReachability.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORReachability.h new file mode 100644 index 0000000..7879b59 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORReachability.h @@ -0,0 +1,34 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#if !TARGET_OS_WATCH +#import +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** This class helps determine upload conditions by determining connectivity. */ +@interface GDTCORReachability : NSObject +#if !TARGET_OS_WATCH +/** The current set flags indicating network conditions */ ++ (SCNetworkReachabilityFlags)currentFlags; +#endif + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORReachability_Private.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORReachability_Private.h new file mode 100644 index 0000000..5d3ddaf --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORReachability_Private.h @@ -0,0 +1,32 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "GDTCORLibrary/Private/GDTCORReachability.h" + +@interface GDTCORReachability () + +#if !TARGET_OS_WATCH +/** Allows manually setting the flags for testing purposes. */ +@property(nonatomic, readwrite) SCNetworkReachabilityFlags flags; +#endif + +/** Creates/returns the singleton instance of this class. + * + * @return The singleton instance of this class. + */ ++ (instancetype)sharedInstance; + +@end diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h new file mode 100644 index 0000000..074fc11 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h @@ -0,0 +1,35 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@interface GDTCORRegistrar () + +NS_ASSUME_NONNULL_BEGIN + +/** The concurrent queue on which all registration occurs. */ +@property(nonatomic, readonly) dispatch_queue_t registrarQueue; + +/** A map of targets to backend implementations. */ +@property(atomic, readonly) NSMutableDictionary> *targetToUploader; + +/** A map of targets to prioritizer implementations. */ +@property(atomic, readonly) + NSMutableDictionary> *targetToPrioritizer; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorage.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorage.h new file mode 100644 index 0000000..008b1d9 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorage.h @@ -0,0 +1,50 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import + +@class GDTCOREvent; +@class GDTCORStoredEvent; + +NS_ASSUME_NONNULL_BEGIN + +/** Manages the storage of events. This class is thread-safe. */ +@interface GDTCORStorage : NSObject + +/** Creates and/or returns the storage singleton. + * + * @return The storage singleton. + */ ++ (instancetype)sharedInstance; + +/** Stores event.dataObjectTransportBytes into a shared on-device folder and tracks the event via + * a GDTCORStoredEvent instance. + * + * @param event The event to store. + */ +- (void)storeEvent:(GDTCOREvent *)event; + +/** Removes a set of events from storage specified by their hash. + * + * @param events The set of stored events to remove. + */ +- (void)removeEvents:(NSSet *)events; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorage_Private.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorage_Private.h new file mode 100644 index 0000000..24569fd --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorage_Private.h @@ -0,0 +1,47 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "GDTCORLibrary/Private/GDTCORStorage.h" + +@class GDTCORUploadCoordinator; + +NS_ASSUME_NONNULL_BEGIN + +@interface GDTCORStorage () + +/** The queue on which all storage work will occur. */ +@property(nonatomic) dispatch_queue_t storageQueue; + +/** A map of targets to a set of stored events. */ +@property(nonatomic) + NSMutableDictionary *> *targetToEventSet; + +/** All the events that have been stored. */ +@property(readonly, nonatomic) NSMutableOrderedSet *storedEvents; + +/** The upload coordinator instance used by this storage instance. */ +@property(nonatomic) GDTCORUploadCoordinator *uploadCoordinator; + +/** Returns the path to the keyed archive of the singleton. This is where the singleton is saved + * to disk during certain app lifecycle events. + * + * @return File path to serialized singleton. + */ ++ (NSString *)archivePath; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer.h new file mode 100644 index 0000000..9f4ec39 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer.h @@ -0,0 +1,54 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import + +@class GDTCOREvent; + +@protocol GDTCOREventTransformer; + +NS_ASSUME_NONNULL_BEGIN + +/** Manages the transforming of events. It's desirable for this to be its own class + * because running all events through a single instance ensures that transformers are thread-safe. + * Having a per-transport queue to run on isn't sufficient because transformer objects could + * maintain state (or at least, there's nothing to stop them from doing that) and the same instances + * may be used across multiple instances. + */ +@interface GDTCORTransformer : NSObject + +/** Instantiates or returns the event transformer singleton. + * + * @return The singleton instance of the event transformer. + */ ++ (instancetype)sharedInstance; + +/** Writes the result of applying the given transformers' -transform method on the given event. + * + * @note If the app is suspended, a background task will be created to complete work in-progress, + * but this method will not send any further events until the app is resumed. + * + * @param event The event to apply transformers on. + * @param transformers The list of transformers to apply. + */ +- (void)transformEvent:(GDTCOREvent *)event + withTransformers:(nullable NSArray> *)transformers; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer_Private.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer_Private.h new file mode 100644 index 0000000..fcdae34 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer_Private.h @@ -0,0 +1,33 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "GDTCORLibrary/Private/GDTCORTransformer.h" + +@class GDTCORStorage; + +NS_ASSUME_NONNULL_BEGIN + +@interface GDTCORTransformer () + +/** The queue on which all work will occur. */ +@property(nonatomic) dispatch_queue_t eventWritingQueue; + +/** The storage instance used to store events. Should only be used to inject a testing fake. */ +@property(nonatomic) GDTCORStorage *storageInstance; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransport_Private.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransport_Private.h new file mode 100644 index 0000000..71f73a6 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransport_Private.h @@ -0,0 +1,39 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class GDTCORTransformer; + +NS_ASSUME_NONNULL_BEGIN + +@interface GDTCORTransport () + +/** The mapping identifier that the target backend will use to map the transport bytes to proto. */ +@property(nonatomic) NSString *mappingID; + +/** The transformers that will operate on events sent by this transport. */ +@property(nonatomic) NSArray> *transformers; + +/** The target backend of this transport. */ +@property(nonatomic) NSInteger target; + +/** The transformer instance to used to transform events. Allows injecting a fake during testing. */ +@property(nonatomic) GDTCORTransformer *transformerInstance; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadCoordinator.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadCoordinator.h new file mode 100644 index 0000000..b1d708c --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadCoordinator.h @@ -0,0 +1,77 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import +#import + +#import "GDTCORLibrary/Private/GDTCORUploadPackage_Private.h" + +@class GDTCORClock; +@class GDTCORStorage; + +NS_ASSUME_NONNULL_BEGIN + +/** This class connects storage and uploader implementations, providing events to an uploader + * and informing the storage what events were successfully uploaded or not. + */ +@interface GDTCORUploadCoordinator + : NSObject + +/** The queue on which all upload coordination will occur. Also used by a dispatch timer. */ +/** Creates and/or returrns the singleton. + * + * @return The singleton instance of this class. + */ ++ (instancetype)sharedInstance; +@property(nonatomic, readonly) dispatch_queue_t coordinationQueue; + +/** A timer that will causes regular checks for events to upload. */ +@property(nonatomic, readonly) dispatch_source_t timer; + +/** The interval the timer will fire. */ +@property(nonatomic, readonly) uint64_t timerInterval; + +/** Some leeway given to libdispatch for the timer interval event. */ +@property(nonatomic, readonly) uint64_t timerLeeway; + +/** The map of targets to in-flight packages. */ +@property(nonatomic, readonly) + NSMutableDictionary *targetToInFlightPackages; + +/** The storage object the coordinator will use. Generally used for testing. */ +@property(nonatomic) GDTCORStorage *storage; + +/** The registrar object the coordinator will use. Generally used for testing. */ +@property(nonatomic) GDTCORRegistrar *registrar; + +/** Forces the backend specified by the target to upload the provided set of events. This should + * only ever happen when the QoS tier of an event requires it. + * + * @param target The target that should force an upload. + */ +- (void)forceUploadForTarget:(GDTCORTarget)target; + +/** Starts the upload timer. */ +- (void)startTimer; + +/** Stops the upload timer from running. */ +- (void)stopTimer; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadPackage_Private.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadPackage_Private.h new file mode 100644 index 0000000..1eb58d4 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadPackage_Private.h @@ -0,0 +1,29 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class GDTCORStorage; + +@interface GDTCORUploadPackage () + +/** The storage object this upload package will use to resolve event hashes to files. */ +@property(nonatomic) GDTCORStorage *storage; + +/** A handler that will receive callbacks for certain events. */ +@property(nonatomic) id handler; + +@end diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORAssert.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORAssert.h new file mode 100644 index 0000000..941e412 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORAssert.h @@ -0,0 +1,91 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import + +/** A block type that could be run instead of normal assertion logging. No return type, no params. + */ +typedef void (^GDTCORAssertionBlock)(void); + +/** Returns the result of executing a soft-linked method present in unit tests that allows a block + * to be run instead of normal assertion logging. This helps ameliorate issues with catching + * exceptions that occur on a dispatch_queue. + * + * @return A block that can be run instead of normal assert printing. + */ +FOUNDATION_EXPORT GDTCORAssertionBlock _Nullable GDTCORAssertionBlockToRunInstead(void); + +#if defined(NS_BLOCK_ASSERTIONS) + +#define GDTCORAssert(condition, ...) \ + do { \ + } while (0); + +#define GDTCORFatalAssert(condition, ...) \ + do { \ + } while (0); + +#else // defined(NS_BLOCK_ASSERTIONS) + +/** Asserts using a console log, unless a block was specified to be run instead. + * + * @param condition The condition you'd expect to be YES. + */ +#define GDTCORAssert(condition, ...) \ + do { \ + if (__builtin_expect(!(condition), 0)) { \ + GDTCORAssertionBlock assertionBlock = GDTCORAssertionBlockToRunInstead(); \ + if (assertionBlock) { \ + assertionBlock(); \ + } else { \ + __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \ + NSString *__assert_file__ = [NSString stringWithUTF8String:__FILE__]; \ + __assert_file__ = __assert_file__ ? __assert_file__ : @""; \ + GDTCORLogError(GDTCORMCEGeneralError, @"Assertion failed (%@:%d): %s,", __assert_file__, \ + __LINE__, ##__VA_ARGS__); \ + __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \ + } \ + } \ + } while (0); + +/** Asserts by logging to the console and throwing an exception if NS_BLOCK_ASSERTIONS is not + * defined. + * + * @param condition The condition you'd expect to be YES. + */ +#define GDTCORFatalAssert(condition, ...) \ + do { \ + __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \ + if (__builtin_expect(!(condition), 0)) { \ + NSString *__assert_file__ = [NSString stringWithUTF8String:__FILE__]; \ + __assert_file__ = __assert_file__ ? __assert_file__ : @""; \ + GDTCORLogError(GDTCORMCEFatalAssertion, \ + @"Fatal assertion encountered, please open an issue at " \ + "https://github.com/firebase/firebase-ios-sdk/issues " \ + "(%@:%d): %s,", \ + __assert_file__, __LINE__, ##__VA_ARGS__); \ + [[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd \ + object:self \ + file:__assert_file__ \ + lineNumber:__LINE__ \ + description:@"%@", ##__VA_ARGS__]; \ + } \ + __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \ + } while (0); + +#endif // defined(NS_BLOCK_ASSERTIONS) diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORClock.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORClock.h new file mode 100644 index 0000000..01de21a --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORClock.h @@ -0,0 +1,57 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** This class manages the device clock and produces snapshots of the current time. */ +@interface GDTCORClock : NSObject + +/** The wallclock time, UTC, in milliseconds. */ +@property(nonatomic, readonly) int64_t timeMillis; + +/** The offset from UTC in seconds. */ +@property(nonatomic, readonly) int64_t timezoneOffsetSeconds; + +/** The kernel boot time when this clock was created. */ +@property(nonatomic, readonly) int64_t kernelBootTime; + +/** The device uptime when this clock was created. */ +@property(nonatomic, readonly) int64_t uptime; + +/** Creates a GDTCORClock object using the current time and offsets. + * + * @return A new GDTCORClock object representing the current time state. + */ ++ (instancetype)snapshot; + +/** Creates a GDTCORClock object representing a time in the future, relative to now. + * + * @param millisInTheFuture The millis in the future from now this clock should represent. + * @return An instance representing a future time. + */ ++ (instancetype)clockSnapshotInTheFuture:(uint64_t)millisInTheFuture; + +/** Compares one clock with another, returns YES if the caller is after the parameter. + * + * @return YES if the calling clock's time is after the given clock's time. + */ +- (BOOL)isAfter:(GDTCORClock *)otherClock; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORConsoleLogger.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORConsoleLogger.h new file mode 100644 index 0000000..c70706b --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORConsoleLogger.h @@ -0,0 +1,94 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +// Set this to 1 to have the library print out as much as possible about what GDT is doing. +#define GDT_VERBOSE_LOGGING 0 + +/** A list of message codes to print in the logger that help to correspond printed messages with + * code locations. + * + * Prefixes: + * - MCW => MessageCodeWarning + * - MCE => MessageCodeError + */ +typedef NS_ENUM(NSInteger, GDTCORMessageCode) { + + /** For warning messages concerning transportBytes: not being implemented by a data object. */ + GDTCORMCWDataObjectMissingBytesImpl = 1, + + /** For warning messages concerning a failed event upload. */ + GDTCORMCWUploadFailed = 2, + + /** For warning messages concerning a forced event upload. */ + GDTCORMCWForcedUpload = 3, + + /** For warning messages concerning a failed reachability call. */ + GDTCORMCWReachabilityFailed = 4, + + /** For error messages concerning transform: not being implemented by an event transformer. */ + GDTCORMCETransformerDoesntImplementTransform = 1000, + + /** For error messages concerning the creation of a directory failing. */ + GDTCORMCEDirectoryCreationError = 1001, + + /** For error messages concerning the writing of a event file. */ + GDTCORMCEFileWriteError = 1002, + + /** For error messages concerning the lack of a prioritizer for a given backend. */ + GDTCORMCEPrioritizerError = 1003, + + /** For error messages concerning a package delivery API violation. */ + GDTCORMCEDeliverTwice = 1004, + + /** For error messages concerning an error in an implementation of -transportBytes. */ + GDTCORMCETransportBytesError = 1005, + + /** For general purpose error messages in a dependency. */ + GDTCORMCEGeneralError = 1006, + + /** For fatal errors. Please go to https://github.com/firebase/firebase-ios-sdk/issues and open + * an issue if you encounter an error with this code. + */ + GDTCORMCEFatalAssertion = 1007 +}; + +/** */ +FOUNDATION_EXPORT +void GDTCORLog(GDTCORMessageCode code, NSString *_Nonnull format, ...); + +/** Returns the string that represents some message code. + * + * @param code The code to convert to a string. + * @return The string representing the message code. + */ +FOUNDATION_EXPORT NSString *_Nonnull GDTCORMessageCodeEnumToString(GDTCORMessageCode code); + +// A define to wrap GULLogWarning with slightly more convenient usage. +#define GDTCORLogWarning(MESSAGE_CODE, MESSAGE_FORMAT, ...) \ + GDTCORLog(MESSAGE_CODE, MESSAGE_FORMAT, __VA_ARGS__); + +// A define to wrap GULLogError with slightly more convenient usage and a failing assert. +#define GDTCORLogError(MESSAGE_CODE, MESSAGE_FORMAT, ...) \ + GDTCORLog(MESSAGE_CODE, MESSAGE_FORMAT, __VA_ARGS__); + +// A define to wrap NSLog for verbose console logs only useful for local debugging. +#if GDT_VERBOSE_LOGGING == 1 +#define GDTCORLogDebug(FORMAT, ...) NSLog(@"GDT: " FORMAT, __VA_ARGS__); +#else +#define GDTCORLogDebug(...) +#endif // GDT_VERBOSE_LOGGING == 1 diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORDataFuture.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORDataFuture.h new file mode 100644 index 0000000..07f428f --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORDataFuture.h @@ -0,0 +1,42 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** This class represents a future data object, determined at instantiation time. */ +@interface GDTCORDataFuture : NSObject + +/** The data, computed on-demand, depending on the initializer. */ +@property(nullable, readonly, nonatomic) NSData *data; + +/** If not nil, this data future was instantiated with this file URL. */ +@property(nullable, readonly, nonatomic) NSURL *fileURL; + +/** If not nil, this data future was instantiated with this NSData instance. */ +@property(nullable, readonly, nonatomic) NSData *originalData; + +/** Initializes an instance with the given the fileURL. + * + * @param fileURL The fileURL containing the data to return in -data. + * @return An instance of this class. + */ +- (instancetype)initWithFileURL:(NSURL *)fileURL; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCOREvent.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCOREvent.h new file mode 100644 index 0000000..1ab55de --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCOREvent.h @@ -0,0 +1,94 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import + +@class GDTCORClock; +@class GDTCORDataFuture; +@class GDTCORStoredEvent; + +NS_ASSUME_NONNULL_BEGIN + +/** The different possible quality of service specifiers. High values indicate high priority. */ +typedef NS_ENUM(NSInteger, GDTCOREventQoS) { + /** The QoS tier wasn't set, and won't ever be sent. */ + GDTCOREventQoSUnknown = 0, + + /** This event is internal telemetry data that should not be sent on its own if possible. */ + GDTCOREventQoSTelemetry = 1, + + /** This event should be sent, but in a batch only roughly once per day. */ + GDTCOREventQoSDaily = 2, + + /** This event should be sent when requested by the uploader. */ + GDTCOREventQosDefault = 3, + + /** This event should be sent immediately along with any other data that can be batched. */ + GDTCOREventQoSFast = 4, + + /** This event should only be uploaded on wifi. */ + GDTCOREventQoSWifiOnly = 5, +}; + +@interface GDTCOREvent : NSObject + +/** The mapping identifier, to allow backends to map the transport bytes to a proto. */ +@property(readonly, nonatomic) NSString *mappingID; + +/** The identifier for the backend this event will eventually be sent to. */ +@property(readonly, nonatomic) NSInteger target; + +/** The data object encapsulated in the transport of your choice, as long as it implements + * the GDTCOREventDataObject protocol. */ +@property(nullable, nonatomic) id dataObject; + +/** The quality of service tier this event belongs to. */ +@property(nonatomic) GDTCOREventQoS qosTier; + +/** The clock snapshot at the time of the event. */ +@property(nonatomic) GDTCORClock *clockSnapshot; + +/** A dictionary provided to aid prioritizers by allowing the passing of arbitrary data. It will be + * retained by a copy in -copy, but not used for -hash. + * + * @note Ensure that classes contained therein implement NSSecureCoding to prevent loss of data. + */ +@property(nullable, nonatomic) NSDictionary *customPrioritizationParams; + +// Please use the designated initializer. +- (instancetype)init NS_UNAVAILABLE; + +/** Initializes an instance using the given mappingID. + * + * @param mappingID The mapping identifier. + * @param target The event's target identifier. + * @return An instance of this class. + */ +- (nullable instancetype)initWithMappingID:(NSString *)mappingID + target:(NSInteger)target NS_DESIGNATED_INITIALIZER; + +/** Returns the GDTCORStoredEvent equivalent of self. + * + * @param dataFuture The data future representing the transport bytes of the original event. + * @return An equivalent GDTCORStoredEvent. + */ +- (GDTCORStoredEvent *)storedEventWithDataFuture:(GDTCORDataFuture *)dataFuture; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCOREventDataObject.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCOREventDataObject.h new file mode 100644 index 0000000..34ef624 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCOREventDataObject.h @@ -0,0 +1,36 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** This protocol defines the common interface that event protos should implement regardless of the + * underlying transport technology (protobuf, nanopb, etc). + */ +@protocol GDTCOREventDataObject + +@required + +/** Returns the serialized proto bytes of the implementing event proto. + * + * @return the serialized proto bytes of the implementing event proto. + */ +- (NSData *)transportBytes; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCOREventTransformer.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCOREventTransformer.h new file mode 100644 index 0000000..29f9592 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCOREventTransformer.h @@ -0,0 +1,38 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class GDTCOREvent; + +NS_ASSUME_NONNULL_BEGIN + +/** Defines the API that event transformers must adopt. */ +@protocol GDTCOREventTransformer + +@required + +/** Transforms an event by applying some logic to it. Events returned can be nil, for example, in + * instances where the event should be sampled. + * + * @param event The event to transform. + * @return A transformed event, or nil if the transformation dropped the event. + */ +- (nullable GDTCOREvent *)transform:(GDTCOREvent *)event; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORLifecycle.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORLifecycle.h new file mode 100644 index 0000000..4d61a21 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORLifecycle.h @@ -0,0 +1,63 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import + +@class GDTCOREvent; + +NS_ASSUME_NONNULL_BEGIN + +/** A protocol defining the lifecycle events objects in the library must respond to immediately. */ +@protocol GDTCORLifecycleProtocol + +@optional + +/** Indicates an imminent app termination in the rare occurrence when -applicationWillTerminate: has + * been called. + * + * @param app The GDTCORApplication instance. + */ +- (void)appWillTerminate:(GDTCORApplication *)app; + +/** Indicates that the app is moving to background and eventual suspension or the current UIScene is + * deactivating. + * + * @param app The GDTCORApplication instance. + */ +- (void)appWillBackground:(GDTCORApplication *)app; + +/** Indicates that the app is resuming operation or a UIScene is activating. + * + * @param app The GDTCORApplication instance. + */ +- (void)appWillForeground:(GDTCORApplication *)app; + +@end + +/** This class manages the library's response to app lifecycle events. + * + * When backgrounding, the library doesn't stop processing events, it's just that several background + * tasks will end up being created for every event that's sent, and the stateful objects of the + * library (GDTCORStorage and GDTCORUploadCoordinator singletons) will deserialize themselves from + * and to disk before and after every operation, respectively. + */ +@interface GDTCORLifecycle : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORPlatform.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORPlatform.h new file mode 100644 index 0000000..d72c2c9 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORPlatform.h @@ -0,0 +1,98 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#if !TARGET_OS_WATCH +#import +#endif +#if TARGET_OS_IOS || TARGET_OS_TV +#import +#elif TARGET_OS_OSX +#import +#endif // TARGET_OS_IOS || TARGET_OS_TV + +NS_ASSUME_NONNULL_BEGIN + +/** The GoogleDataTransport library version. */ +FOUNDATION_EXPORT NSString *const kGDTCORVersion; + +/** A notification sent out if the app is backgrounding. */ +FOUNDATION_EXPORT NSString *const kGDTCORApplicationDidEnterBackgroundNotification; + +/** A notification sent out if the app is foregrounding. */ +FOUNDATION_EXPORT NSString *const kGDTCORApplicationWillEnterForegroundNotification; + +/** A notification sent out if the app is terminating. */ +FOUNDATION_EXPORT NSString *const kGDTCORApplicationWillTerminateNotification; + +#if !TARGET_OS_WATCH +/** Compares flags with the WWAN reachability flag, if available, and returns YES if present. + * + * @param flags The set of reachability flags. + * @return YES if the WWAN flag is set, NO otherwise. + */ +BOOL GDTCORReachabilityFlagsContainWWAN(SCNetworkReachabilityFlags flags); +#endif + +/** A typedef identify background identifiers. */ +typedef volatile NSUInteger GDTCORBackgroundIdentifier; + +/** A background task's invalid sentinel value. */ +FOUNDATION_EXPORT const GDTCORBackgroundIdentifier GDTCORBackgroundIdentifierInvalid; + +#if TARGET_OS_IOS || TARGET_OS_TV +/** A protocol that wraps UIApplicationDelegate or NSObject protocol, depending on the platform. */ +@protocol GDTCORApplicationDelegate +#elif TARGET_OS_OSX +@protocol GDTCORApplicationDelegate +#else +@protocol GDTCORApplicationDelegate +#endif // TARGET_OS_IOS || TARGET_OS_TV + +@end + +/** A cross-platform application class. */ +@interface GDTCORApplication : NSObject + +/** Flag to determine if the application is running in the background. */ +@property(atomic, readonly) BOOL isRunningInBackground; + +/** Creates and/or returns the shared application instance. + * + * @return The shared application instance. + */ ++ (nullable GDTCORApplication *)sharedApplication; + +/** Creates a background task with the returned identifier if on a suitable platform. + * + * @name name The name of the task, useful for debugging which background tasks are running. + * @param handler The handler block that is called if the background task expires. + * @return An identifier for the background task, or GDTCORBackgroundIdentifierInvalid if one + * couldn't be created. + */ +- (GDTCORBackgroundIdentifier)beginBackgroundTaskWithName:(NSString *)name + expirationHandler:(void (^__nullable)(void))handler; + +/** Ends the background task if the identifier is valid. + * + * @param bgID The background task to end. + */ +- (void)endBackgroundTask:(GDTCORBackgroundIdentifier)bgID; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORPrioritizer.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORPrioritizer.h new file mode 100644 index 0000000..3c0c3c6 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORPrioritizer.h @@ -0,0 +1,73 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import +#import + +@class GDTCORStoredEvent; + +NS_ASSUME_NONNULL_BEGIN + +/** Options that define a set of upload conditions. This is used to help minimize end user data + * consumption impact. + */ +typedef NS_OPTIONS(NSInteger, GDTCORUploadConditions) { + + /** An upload shouldn't be attempted, because there's no network. */ + GDTCORUploadConditionNoNetwork = 1 << 0, + + /** An upload would likely use mobile data. */ + GDTCORUploadConditionMobileData = 1 << 1, + + /** An upload would likely use wifi data. */ + GDTCORUploadConditionWifiData = 1 << 2, + + /** An upload uses some sort of network connection, but it's unclear which. */ + GDTCORUploadConditionUnclearConnection = 1 << 3, + + /** A high priority event has occurred. */ + GDTCORUploadConditionHighPriority = 1 << 4, +}; + +/** This protocol defines the common interface of event prioritization. Prioritizers are + * stateful objects that prioritize events upon insertion into storage and remain prepared to return + * a set of filenames to the storage system. + */ +@protocol GDTCORPrioritizer + +@required + +/** Accepts an event and uses the event metadata to make choices on how to prioritize the event. + * This method exists as a way to help prioritize which events should be sent, which is dependent on + * the request proto structure of your backend. + * + * @param event The event to prioritize. + */ +- (void)prioritizeEvent:(GDTCORStoredEvent *)event; + +/** Returns a set of events to upload given a set of conditions. + * + * @param conditions A bit mask specifying the current upload conditions. + * @return An object to be used by the uploader to determine file URLs to upload with respect to the + * current conditions. + */ +- (GDTCORUploadPackage *)uploadPackageWithConditions:(GDTCORUploadConditions)conditions; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORRegistrar.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORRegistrar.h new file mode 100644 index 0000000..0a8fbb0 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORRegistrar.h @@ -0,0 +1,50 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Manages the registration of targets with the transport SDK. */ +@interface GDTCORRegistrar : NSObject + +/** Creates and/or returns the singleton instance. + * + * @return The singleton instance of this class. + */ ++ (instancetype)sharedInstance; + +/** Registers a backend implementation with the GoogleDataTransport infrastructure. + * + * @param backend The backend object to register. + * @param target The target this backend object will be responsible for. + */ +- (void)registerUploader:(id)backend target:(GDTCORTarget)target; + +/** Registers a event prioritizer implementation with the GoogleDataTransport infrastructure. + * + * @param prioritizer The prioritizer object to register. + * @param target The target this prioritizer object will be responsible for. + */ +- (void)registerPrioritizer:(id)prioritizer target:(GDTCORTarget)target; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORStoredEvent.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORStoredEvent.h new file mode 100644 index 0000000..647b220 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORStoredEvent.h @@ -0,0 +1,58 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#import + +#import +#import + +@class GDTCOREvent; + +NS_ASSUME_NONNULL_BEGIN + +@interface GDTCORStoredEvent : NSObject + +/** The data future representing the original event's transport bytes. */ +@property(readonly, nonatomic) GDTCORDataFuture *dataFuture; + +/** The mapping identifier, to allow backends to map the transport bytes to a proto. */ +@property(readonly, nonatomic) NSString *mappingID; + +/** The identifier for the backend this event will eventually be sent to. */ +@property(readonly, nonatomic) NSNumber *target; + +/** The quality of service tier this event belongs to. */ +@property(readonly, nonatomic) GDTCOREventQoS qosTier; + +/** The clock snapshot at the time of the event. */ +@property(readonly, nonatomic) GDTCORClock *clockSnapshot; + +/** A dictionary provided to aid prioritizers by allowing the passing of arbitrary data. + * + * @note Ensure that custom classes in this dict implement NSSecureCoding to prevent loss of data. + */ +@property(readonly, nullable, nonatomic) NSDictionary *customPrioritizationParams; + +/** Initializes a stored event with the given URL and event. + * + * @param event The event this stored event represents. + * @param dataFuture The dataFuture this event represents. + * @return An instance of this class. + */ +- (instancetype)initWithEvent:(GDTCOREvent *)event dataFuture:(GDTCORDataFuture *)dataFuture; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORTargets.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORTargets.h new file mode 100644 index 0000000..ebd36d1 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORTargets.h @@ -0,0 +1,32 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +/** The list of targets supported by the shared transport infrastructure. If adding a new target, + * please use the previous value +1. + */ +typedef NS_ENUM(NSInteger, GDTCORTarget) { + + /** A target only used in testing. */ + kGDTCORTargetTest = 999, + + /** The CCT target. */ + kGDTCORTargetCCT = 1000, + + /** The FLL target. */ + kGDTCORTargetFLL = 1001, +}; diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORTransport.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORTransport.h new file mode 100644 index 0000000..a952240 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORTransport.h @@ -0,0 +1,69 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import + +@class GDTCOREvent; + +NS_ASSUME_NONNULL_BEGIN + +@interface GDTCORTransport : NSObject + +// Please use the designated initializer. +- (instancetype)init NS_UNAVAILABLE; + +/** Initializes a new transport that will send events to the given target backend. + * + * @param mappingID The mapping identifier used by the backend to map the data object transport + * bytes to a proto. + * @param transformers A list of transformers to be applied to events that are sent. + * @param target The target backend of this transport. + * @return A transport that will send events. + */ +- (nullable instancetype)initWithMappingID:(NSString *)mappingID + transformers: + (nullable NSArray> *)transformers + target:(NSInteger)target NS_DESIGNATED_INITIALIZER; + +/** Copies and sends an internal telemetry event. Events sent using this API are lower in priority, + * and sometimes won't be sent on their own. + * + * @note This will convert the event's data object to data and release the original event. + * + * @param event The event to send. + */ +- (void)sendTelemetryEvent:(GDTCOREvent *)event; + +/** Copies and sends an SDK service data event. Events send using this API are higher in priority, + * and will cause a network request at some point in the relative near future. + * + * @note This will convert the event's data object to data and release the original event. + * + * @param event The event to send. + */ +- (void)sendDataEvent:(GDTCOREvent *)event; + +/** Creates an event for use by this transport. + * + * @return An event that is suited for use by this transport. + */ +- (GDTCOREvent *)eventForTransport; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORUploadPackage.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORUploadPackage.h new file mode 100644 index 0000000..46a676b --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORUploadPackage.h @@ -0,0 +1,80 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import + +@class GDTCORClock; +@class GDTCORStoredEvent; +@class GDTCORUploadPackage; + +/** A protocol that allows a handler to respond to package lifecycle events. */ +@protocol GDTCORUploadPackageProtocol + +@optional + +/** Indicates that the package has expired. + * + * @note Package expiration will only be checked every 5 seconds. + * + * @param package The package that has expired. + */ +- (void)packageExpired:(GDTCORUploadPackage *)package; + +/** Indicates that the package was successfully delivered. + * + * @param package The package that was delivered. + */ +- (void)packageDelivered:(GDTCORUploadPackage *)package successful:(BOOL)successful; + +@end + +/** This class is a container that's handed off to uploaders. */ +@interface GDTCORUploadPackage : NSObject + +/** The set of stored events in this upload package. */ +@property(nonatomic) NSSet *events; + +/** The expiration time. If [[GDTCORClock snapshot] isAfter:deliverByTime] this package has expired. + * + * @note By default, the expiration time will be 3 minutes from creation. + */ +@property(nonatomic) GDTCORClock *deliverByTime; + +/** The target of this package. */ +@property(nonatomic, readonly) GDTCORTarget target; + +/** Initializes a package instance. + * + * @param target The target/destination of this package. + * @return An instance of this class. + */ +- (instancetype)initWithTarget:(GDTCORTarget)target NS_DESIGNATED_INITIALIZER; + +// Please use the designated initializer. +- (instancetype)init NS_UNAVAILABLE; + +/** Completes delivery of the package. + * + * @note This *needs* to be called by an uploader for the package to not expire. + */ +- (void)completeDelivery; + +/** Sends the package back, indicating that delivery should be attempted again in the future. */ +- (void)retryDeliveryInTheFuture; + +@end diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORUploader.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORUploader.h new file mode 100644 index 0000000..a34f8b2 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GDTCORUploader.h @@ -0,0 +1,47 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** This protocol defines the common interface for uploader implementations. */ +@protocol GDTCORUploader + +@required + +/** Returns YES if the uploader can make an upload attempt, NO otherwise. + * + * @param conditions The conditions that the upload attempt is likely to occur under. + * @return YES if the uploader can make an upload attempt, NO otherwise. + */ +- (BOOL)readyToUploadWithConditions:(GDTCORUploadConditions)conditions; + +/** Uploads events to the backend using this specific backend's chosen format. + * + * @param package The event package to upload. Make sure to call -completeDelivery. + */ +- (void)uploadPackage:(GDTCORUploadPackage *)package; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport.h b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport.h new file mode 100644 index 0000000..e46a385 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport.h @@ -0,0 +1,30 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "GDTCORClock.h" +#import "GDTCORConsoleLogger.h" +#import "GDTCORDataFuture.h" +#import "GDTCOREvent.h" +#import "GDTCOREventDataObject.h" +#import "GDTCOREventTransformer.h" +#import "GDTCORLifecycle.h" +#import "GDTCORPrioritizer.h" +#import "GDTCORRegistrar.h" +#import "GDTCORStoredEvent.h" +#import "GDTCORTargets.h" +#import "GDTCORTransport.h" +#import "GDTCORUploadPackage.h" +#import "GDTCORUploader.h" diff --git a/!main project/Pods/GoogleDataTransport/LICENSE b/!main project/Pods/GoogleDataTransport/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/!main project/Pods/GoogleDataTransport/README.md b/!main project/Pods/GoogleDataTransport/README.md new file mode 100644 index 0000000..5097a89 --- /dev/null +++ b/!main project/Pods/GoogleDataTransport/README.md @@ -0,0 +1,254 @@ +# Firebase iOS Open Source Development [![Build Status](https://travis-ci.org/firebase/firebase-ios-sdk.svg?branch=master)](https://travis-ci.org/firebase/firebase-ios-sdk) + +This repository contains a subset of the Firebase iOS SDK source. It currently +includes FirebaseCore, FirebaseABTesting, FirebaseAuth, FirebaseDatabase, +FirebaseFirestore, FirebaseFunctions, FirebaseInstanceID, FirebaseInAppMessaging, +FirebaseInAppMessagingDisplay, FirebaseMessaging, FirebaseRemoteConfig, and +FirebaseStorage. + +The repository also includes GoogleUtilities source. The +[GoogleUtilities](GoogleUtilities/README.md) pod is +a set of utilities used by Firebase and other Google products. + +Firebase is an app development platform with tools to help you build, grow and +monetize your app. More information about Firebase can be found at +[https://firebase.google.com](https://firebase.google.com). + +## Installation + +See the three subsections for details about three different installation methods. +1. [Standard pod install](README.md#standard-pod-install) +1. [Installing from the GitHub repo](README.md#installing-from-github) +1. [Experimental Carthage](README.md#carthage-ios-only) + +### Standard pod install + +Go to +[https://firebase.google.com/docs/ios/setup](https://firebase.google.com/docs/ios/setup). + +### Installing from GitHub + +For releases starting with 5.0.0, the source for each release is also deployed +to CocoaPods master and available via standard +[CocoaPods Podfile syntax](https://guides.cocoapods.org/syntax/podfile.html#pod). + +These instructions can be used to access the Firebase repo at other branches, +tags, or commits. + +#### Background + +See +[the Podfile Syntax Reference](https://guides.cocoapods.org/syntax/podfile.html#pod) +for instructions and options about overriding pod source locations. + +#### Accessing Firebase Source Snapshots + +All of the official releases are tagged in this repo and available via CocoaPods. To access a local +source snapshot or unreleased branch, use Podfile directives like the following: + +To access FirebaseFirestore via a branch: +``` +pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +``` + +To access FirebaseMessaging via a checked out version of the firebase-ios-sdk repo do: + +``` +pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk' +pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk' +``` + +### Carthage (iOS only) + +Instructions for the experimental Carthage distribution are at +[Carthage](Carthage.md). + +### Rome + +Instructions for installing binary frameworks via +[Rome](https://github.com/CocoaPods/Rome) are at [Rome](Rome.md). + +## Development + +To develop Firebase software in this repository, ensure that you have at least +the following software: + + * Xcode 10.1 (or later) + * CocoaPods 1.7.2 (or later) + * [CocoaPods generate](https://github.com/square/cocoapods-generate) + +For the pod that you want to develop: + +`pod gen Firebase{name here}.podspec --local-sources=./ --auto-open --platforms=ios` + +Note: If the CocoaPods cache is out of date, you may need to run +`pod repo update` before the `pod gen` command. + +Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for +those platforms. Since 10.2, Xcode does not properly handle multi-platform +CocoaPods workspaces. + +Firestore has a self contained Xcode project. See +[Firestore/README.md](Firestore/README.md). + +### Development for Catalyst +* `pod gen {name here}.podspec --local-sources=./ --auto-open --platforms=ios` +* Check the Mac box in the App-iOS Build Settings +* Sign the App in the Settings Signing & Capabilities tab +* Click Pods in the Project Manager +* Add Signing to the iOS host app and unit test targets +* Select the Unit-unit scheme +* Run it to build and test + +### Adding a New Firebase Pod + +See [AddNewPod.md](AddNewPod.md). + +### Code Formatting + +To ensure that the code is formatted consistently, run the script +[./scripts/style.sh](https://github.com/firebase/firebase-ios-sdk/blob/master/scripts/style.sh) +before creating a PR. + +Travis will verify that any code changes are done in a style compliant way. Install +`clang-format` and `swiftformat`. +These commands will get the right versions: + +``` +brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/e3496d9/Formula/clang-format.rb +brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/7963c3d/Formula/swiftformat.rb +``` + +Note: if you already have a newer version of these installed you may need to +`brew switch` to this version. + +To update this section, find the versions of clang-format and swiftformat.rb to +match the versions in the CI failure logs +[here](https://github.com/Homebrew/homebrew-core/tree/master/Formula). + +### Running Unit Tests + +Select a scheme and press Command-u to build a component and run its unit tests. + +#### Viewing Code Coverage + +First, make sure that [xcov](https://github.com/nakiostudio/xcov) is installed with `gem install xcov`. + +After running the `AllUnitTests_iOS` scheme in Xcode, execute +`xcov --workspace Firebase.xcworkspace --scheme AllUnitTests_iOS --output_directory xcov_output` +at Example/ in the terminal. This will aggregate the coverage, and you can run `open xcov_output/index.html` to see the results. + +### Running Sample Apps +In order to run the sample apps and integration tests, you'll need valid +`GoogleService-Info.plist` files for those samples. The Firebase Xcode project contains dummy plist +files without real values, but can be replaced with real plist files. To get your own +`GoogleService-Info.plist` files: + +1. Go to the [Firebase Console](https://console.firebase.google.com/) +2. Create a new Firebase project, if you don't already have one +3. For each sample app you want to test, create a new Firebase app with the sample app's bundle +identifier (e.g. `com.google.Database-Example`) +4. Download the resulting `GoogleService-Info.plist` and replace the appropriate dummy plist file +(e.g. in [Example/Database/App/](Example/Database/App/)); + +Some sample apps like Firebase Messaging ([Example/Messaging/App](Example/Messaging/App)) require +special Apple capabilities, and you will have to change the sample app to use a unique bundle +identifier that you can control in your own Apple Developer account. + +## Specific Component Instructions +See the sections below for any special instructions for those components. + +### Firebase Auth + +If you're doing specific Firebase Auth development, see +[the Auth Sample README](Example/Auth/README.md) for instructions about +building and running the FirebaseAuth pod along with various samples and tests. + +### Firebase Database + +To run the Database Integration tests, make your database authentication rules +[public](https://firebase.google.com/docs/database/security/quickstart). + +### Firebase Storage + +To run the Storage Integration tests, follow the instructions in +[FIRStorageIntegrationTests.m](Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m). + +#### Push Notifications + +Push notifications can only be delivered to specially provisioned App IDs in the developer portal. +In order to actually test receiving push notifications, you will need to: + +1. Change the bundle identifier of the sample app to something you own in your Apple Developer +account, and enable that App ID for push notifications. +2. You'll also need to +[upload your APNs Provider Authentication Key or certificate to the Firebase Console](https://firebase.google.com/docs/cloud-messaging/ios/certs) +at **Project Settings > Cloud Messaging > [Your Firebase App]**. +3. Ensure your iOS device is added to your Apple Developer portal as a test device. + +#### iOS Simulator + +The iOS Simulator cannot register for remote notifications, and will not receive push notifications. +In order to receive push notifications, you'll have to follow the steps above and run the app on a +physical device. + +## Community Supported Efforts + +We've seen an amazing amount of interest and contributions to improve the Firebase SDKs, and we are +very grateful! We'd like to empower as many developers as we can to be able to use Firebase and +participate in the Firebase community. + +### tvOS, macOS, and Catalyst +Thanks to contributions from the community, many of Firebase SDKs now compile, run unit tests, and work on +tvOS, macOS, and Catalyst. + +For tvOS, checkout the [Sample](Example/tvOSSample). + +Keep in mind that macOS, Catalyst and tvOS are not officially supported by Firebase, and this +repository is actively developed primarily for iOS. While we can catch basic unit test issues with +Travis, there may be some changes where the SDK no longer works as expected on macOS or tvOS. If you +encounter this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues). + +During app setup in the console, you may get to a step that mentions something like "Checking if the app +has communicated with our servers". This relies on Analytics and will not work on macOS/tvOS/Catalyst. +**It's safe to ignore the message and continue**, the rest of the SDKs will work as expected. + +To install, add a subset of the following to the Podfile: + +``` +pod 'Firebase/ABTesting' +pod 'Firebase/Auth' +pod 'Firebase/Crashlytics' +pod 'Firebase/Database' +pod 'Firebase/Firestore' +pod 'Firebase/Functions' +pod 'Firebase/Messaging' +pod 'Firebase/RemoteConfig' +pod 'Firebase/Storage' +``` + +#### Additional Catalyst Notes + +* FirebaseAuth and FirebaseMessaging require adding `Keychain Sharing Capability` +to Build Settings. +* FirebaseFirestore requires signing the +[gRPC Resource target](https://github.com/firebase/firebase-ios-sdk/issues/3500#issuecomment-518741681). + +## Roadmap + +See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source +plans and directions. + +## Contributing + +See [Contributing](CONTRIBUTING.md) for more information on contributing to the Firebase +iOS SDK. + +## License + +The contents of this repository is licensed under the +[Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0). + +Your use of Firebase is governed by the +[Terms of Service for Firebase Services](https://firebase.google.com/terms/). diff --git a/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/GDTCCTCompressionHelper.m b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/GDTCCTCompressionHelper.m new file mode 100644 index 0000000..6ae115d --- /dev/null +++ b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/GDTCCTCompressionHelper.m @@ -0,0 +1,96 @@ +/* + * Copyright 2020 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "GDTCCTLibrary/Private/GDTCCTCompressionHelper.h" + +#import + +@implementation GDTCCTCompressionHelper + ++ (nullable NSData *)gzippedData:(NSData *)data { +#if defined(__LP64__) && __LP64__ + // Don't support > 32bit length for 64 bit, see note in header. + if (data.length > UINT_MAX) { + return nil; + } +#endif + + const uint kChunkSize = 1024; + + const void *bytes = [data bytes]; + NSUInteger length = [data length]; + + int level = Z_DEFAULT_COMPRESSION; + if (!bytes || !length) { + return nil; + } + + z_stream strm; + bzero(&strm, sizeof(z_stream)); + + int memLevel = 8; // Default. + int windowBits = 15 + 16; // Enable gzip header instead of zlib header. + + int retCode; + if ((retCode = deflateInit2(&strm, level, Z_DEFLATED, windowBits, memLevel, + Z_DEFAULT_STRATEGY)) != Z_OK) { + return nil; + } + + // Hint the size at 1/4 the input size. + NSMutableData *result = [NSMutableData dataWithCapacity:(length / 4)]; + unsigned char output[kChunkSize]; + + // Setup the input. + strm.avail_in = (unsigned int)length; + strm.next_in = (unsigned char *)bytes; + + // Collect the data. + do { + // update what we're passing in + strm.avail_out = kChunkSize; + strm.next_out = output; + retCode = deflate(&strm, Z_FINISH); + if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) { + deflateEnd(&strm); + return nil; + } + // Collect what we got. + unsigned gotBack = kChunkSize - strm.avail_out; + if (gotBack > 0) { + [result appendBytes:output length:gotBack]; + } + + } while (retCode == Z_OK); + + // If the loop exits, it used all input and the stream ended. + NSAssert(strm.avail_in == 0, + @"Should have finished deflating without using all input, %u bytes left", strm.avail_in); + NSAssert(retCode == Z_STREAM_END, + @"thought we finished deflate w/o getting a result of stream end, code %d", retCode); + + // Clean up. + deflateEnd(&strm); + + return result; +} + ++ (BOOL)isGzipped:(NSData *)data { + const UInt8 *bytes = (const UInt8 *)data.bytes; + return (data.length >= 2 && bytes[0] == 0x1f && bytes[1] == 0x8b); +} + +@end diff --git a/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/GDTCCTNanopbHelpers.m b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/GDTCCTNanopbHelpers.m new file mode 100644 index 0000000..2afb823 --- /dev/null +++ b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/GDTCCTNanopbHelpers.m @@ -0,0 +1,198 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h" + +#if TARGET_OS_IOS || TARGET_OS_TV +#import +#elif TARGET_OS_OSX +#import +#endif // TARGET_OS_IOS || TARGET_OS_TV + +#import + +#import +#import +#import + +#import "GDTCCTLibrary/Private/GDTCCTPrioritizer.h" + +#pragma mark - General purpose encoders + +pb_bytes_array_t *GDTCCTEncodeString(NSString *string) { + NSData *stringBytes = [string dataUsingEncoding:NSUTF8StringEncoding]; + return GDTCCTEncodeData(stringBytes); +} + +pb_bytes_array_t *GDTCCTEncodeData(NSData *data) { + pb_bytes_array_t *pbBytes = malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(data.length)); + memcpy(pbBytes->bytes, [data bytes], data.length); + pbBytes->size = (pb_size_t)data.length; + return pbBytes; +} + +#pragma mark - CCT object constructors + +NSData *_Nullable GDTCCTEncodeBatchedLogRequest(gdt_cct_BatchedLogRequest *batchedLogRequest) { + pb_ostream_t sizestream = PB_OSTREAM_SIZING; + // Encode 1 time to determine the size. + if (!pb_encode(&sizestream, gdt_cct_BatchedLogRequest_fields, batchedLogRequest)) { + GDTCORLogError(GDTCORMCEGeneralError, @"Error in nanopb encoding for size: %s", + PB_GET_ERROR(&sizestream)); + } + + // Encode a 2nd time to actually get the bytes from it. + size_t bufferSize = sizestream.bytes_written; + CFMutableDataRef dataRef = CFDataCreateMutable(CFAllocatorGetDefault(), bufferSize); + pb_ostream_t ostream = pb_ostream_from_buffer((void *)CFDataGetBytePtr(dataRef), bufferSize); + if (!pb_encode(&ostream, gdt_cct_BatchedLogRequest_fields, batchedLogRequest)) { + GDTCORLogError(GDTCORMCEGeneralError, @"Error in nanopb encoding for bytes: %s", + PB_GET_ERROR(&ostream)); + } + CFDataSetLength(dataRef, ostream.bytes_written); + + return CFBridgingRelease(dataRef); +} + +gdt_cct_BatchedLogRequest GDTCCTConstructBatchedLogRequest( + NSDictionary *> *logMappingIDToLogSet) { + gdt_cct_BatchedLogRequest batchedLogRequest = gdt_cct_BatchedLogRequest_init_default; + NSUInteger numberOfLogRequests = logMappingIDToLogSet.count; + gdt_cct_LogRequest *logRequests = malloc(sizeof(gdt_cct_LogRequest) * numberOfLogRequests); + + __block int i = 0; + [logMappingIDToLogSet enumerateKeysAndObjectsUsingBlock:^( + NSString *_Nonnull logMappingID, + NSSet *_Nonnull logSet, BOOL *_Nonnull stop) { + int32_t logSource = [logMappingID intValue]; + gdt_cct_LogRequest logRequest = GDTCCTConstructLogRequest(logSource, logSet); + logRequests[i] = logRequest; + i++; + }]; + + batchedLogRequest.log_request = logRequests; + batchedLogRequest.log_request_count = (pb_size_t)numberOfLogRequests; + return batchedLogRequest; +} + +gdt_cct_LogRequest GDTCCTConstructLogRequest(int32_t logSource, + NSSet *_Nonnull logSet) { + if (logSet.count == 0) { + GDTCORLogError(GDTCORMCEGeneralError, @"%@", + @"An empty event set can't be serialized to proto."); + gdt_cct_LogRequest logRequest = gdt_cct_LogRequest_init_default; + return logRequest; + } + gdt_cct_LogRequest logRequest = gdt_cct_LogRequest_init_default; + logRequest.log_source = logSource; + logRequest.has_log_source = 1; + logRequest.client_info = GDTCCTConstructClientInfo(); + logRequest.has_client_info = 1; + logRequest.log_event = malloc(sizeof(gdt_cct_LogEvent) * logSet.count); + int i = 0; + for (GDTCORStoredEvent *log in logSet) { + gdt_cct_LogEvent logEvent = GDTCCTConstructLogEvent(log); + logRequest.log_event[i] = logEvent; + i++; + } + logRequest.log_event_count = (pb_size_t)logSet.count; + + GDTCORClock *currentTime = [GDTCORClock snapshot]; + logRequest.request_time_ms = currentTime.timeMillis; + logRequest.has_request_time_ms = 1; + logRequest.request_uptime_ms = currentTime.uptime; + logRequest.has_request_uptime_ms = 1; + + return logRequest; +} + +gdt_cct_LogEvent GDTCCTConstructLogEvent(GDTCORStoredEvent *event) { + gdt_cct_LogEvent logEvent = gdt_cct_LogEvent_init_default; + logEvent.event_time_ms = event.clockSnapshot.timeMillis; + logEvent.has_event_time_ms = 1; + logEvent.event_uptime_ms = event.clockSnapshot.uptime; + logEvent.has_event_uptime_ms = 1; + logEvent.timezone_offset_seconds = event.clockSnapshot.timezoneOffsetSeconds; + logEvent.has_timezone_offset_seconds = 1; + // TODO: Read network_connection_info from the custom params dict. + + NSError *error; + NSData *extensionBytes = [NSData dataWithContentsOfURL:event.dataFuture.fileURL + options:0 + error:&error]; + if (error) { + GDTCORLogError(GDTCORMCEGeneralError, + @"There was an error reading extension bytes from disk: %@", error); + return logEvent; + } + logEvent.source_extension = GDTCCTEncodeData(extensionBytes); // read bytes from the file. + return logEvent; +} + +gdt_cct_ClientInfo GDTCCTConstructClientInfo() { + gdt_cct_ClientInfo clientInfo = gdt_cct_ClientInfo_init_default; + clientInfo.client_type = gdt_cct_ClientInfo_ClientType_IOS_FIREBASE; + clientInfo.has_client_type = 1; +#if TARGET_OS_IOS || TARGET_OS_TV + clientInfo.ios_client_info = GDTCCTConstructiOSClientInfo(); + clientInfo.has_ios_client_info = 1; +#elif TARGET_OS_OSX + // TODO(mikehaney24): Expand the proto to include macOS client info. +#endif + return clientInfo; +} + +gdt_cct_IosClientInfo GDTCCTConstructiOSClientInfo() { + gdt_cct_IosClientInfo iOSClientInfo = gdt_cct_IosClientInfo_init_default; +#if TARGET_OS_IOS || TARGET_OS_TV + UIDevice *device = [UIDevice currentDevice]; + NSBundle *bundle = [NSBundle mainBundle]; + NSLocale *locale = [NSLocale currentLocale]; + iOSClientInfo.os_full_version = GDTCCTEncodeString(device.systemVersion); + NSArray *versionComponents = [device.systemVersion componentsSeparatedByString:@"."]; + iOSClientInfo.os_major_version = GDTCCTEncodeString(versionComponents[0]); + NSString *version = [bundle objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey]; + if (version) { + iOSClientInfo.application_build = GDTCCTEncodeString(version); + } + NSString *countryCode = [locale objectForKey:NSLocaleCountryCode]; + if (countryCode) { + iOSClientInfo.country = GDTCCTEncodeString([locale objectForKey:NSLocaleCountryCode]); + } + iOSClientInfo.model = GDTCCTEncodeString(device.model); + NSString *languageCode = bundle.preferredLocalizations.firstObject; + iOSClientInfo.language_code = + languageCode ? GDTCCTEncodeString(languageCode) : GDTCCTEncodeString(@"en"); + iOSClientInfo.application_bundle_id = GDTCCTEncodeString(bundle.bundleIdentifier); +#endif + return iOSClientInfo; +} + +#pragma mark - CCT Object decoders + +gdt_cct_LogResponse GDTCCTDecodeLogResponse(NSData *data, NSError **error) { + gdt_cct_LogResponse response = gdt_cct_LogResponse_init_default; + pb_istream_t istream = pb_istream_from_buffer([data bytes], [data length]); + if (!pb_decode(&istream, gdt_cct_LogResponse_fields, &response)) { + NSString *nanopb_error = [NSString stringWithFormat:@"%s", PB_GET_ERROR(&istream)]; + NSDictionary *userInfo = @{@"nanopb error:" : nanopb_error}; + if (error != NULL) { + *error = [NSError errorWithDomain:NSURLErrorDomain code:-1 userInfo:userInfo]; + } + response = (gdt_cct_LogResponse)gdt_cct_LogResponse_init_default; + } + return response; +} diff --git a/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/GDTCCTPrioritizer.m b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/GDTCCTPrioritizer.m new file mode 100644 index 0000000..0c6e879 --- /dev/null +++ b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/GDTCCTPrioritizer.m @@ -0,0 +1,199 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "GDTCCTLibrary/Private/GDTCCTPrioritizer.h" + +#import +#import +#import +#import +#import + +const static int64_t kMillisPerDay = 8.64e+7; + +@implementation GDTCCTPrioritizer + ++ (void)load { + GDTCCTPrioritizer *prioritizer = [GDTCCTPrioritizer sharedInstance]; + [[GDTCORRegistrar sharedInstance] registerPrioritizer:prioritizer target:kGDTCORTargetCCT]; +} + ++ (instancetype)sharedInstance { + static GDTCCTPrioritizer *sharedInstance; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[GDTCCTPrioritizer alloc] init]; + }); + return sharedInstance; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _queue = dispatch_queue_create("com.google.GDTCCTPrioritizer", DISPATCH_QUEUE_SERIAL); + _events = [[NSMutableSet alloc] init]; + } + return self; +} + +#pragma mark - GDTCORPrioritizer Protocol + +- (void)prioritizeEvent:(GDTCORStoredEvent *)event { + dispatch_async(_queue, ^{ + [self.events addObject:event]; + }); +} + +- (GDTCORUploadPackage *)uploadPackageWithConditions:(GDTCORUploadConditions)conditions { + GDTCORUploadPackage *package = [[GDTCORUploadPackage alloc] initWithTarget:kGDTCORTargetCCT]; + dispatch_sync(_queue, ^{ + NSSet *logEventsThatWillBeSent; + // A high priority event effectively flushes all events to be sent. + if ((conditions & GDTCORUploadConditionHighPriority) == GDTCORUploadConditionHighPriority) { + GDTCORLogDebug("%@", @"CCT: A high priority event is flushing all events."); + package.events = self.events; + GDTCORLogDebug("CCT: %lu events are in the upload package", + (unsigned long)package.events.count); + return; + } + + // If on wifi, upload logs that are ok to send on wifi. + if ((conditions & GDTCORUploadConditionWifiData) == GDTCORUploadConditionWifiData) { + logEventsThatWillBeSent = [self logEventsOkToSendOnWifi]; + GDTCORLogDebug("%@", @"CCT: events ok to send on wifi are being added to the upload package"); + } else { + logEventsThatWillBeSent = [self logEventsOkToSendOnMobileData]; + GDTCORLogDebug("%@", + @"CCT: events ok to send on mobile are being added to the upload package"); + } + + // If it's been > 24h since the last daily upload, upload logs with the daily QoS. + if (self.timeOfLastDailyUpload) { + int64_t millisSinceLastUpload = + [GDTCORClock snapshot].timeMillis - self.timeOfLastDailyUpload.timeMillis; + if (millisSinceLastUpload > kMillisPerDay) { + logEventsThatWillBeSent = + [logEventsThatWillBeSent setByAddingObjectsFromSet:[self logEventsOkToSendDaily]]; + GDTCORLogDebug("%@", @"CCT: events ok to send daily are being added to the upload package"); + } + } else { + self.timeOfLastDailyUpload = [GDTCORClock snapshot]; + logEventsThatWillBeSent = + [logEventsThatWillBeSent setByAddingObjectsFromSet:[self logEventsOkToSendDaily]]; + GDTCORLogDebug("%@", @"CCT: events ok to send daily are being added to the upload package"); + } + package.events = logEventsThatWillBeSent; + }); + GDTCORLogDebug("CCT: created an upload package with %ld events", + (unsigned long)package.events.count); + return package; +} + +#pragma mark - Private helper methods + +/** The different possible quality of service specifiers. High values indicate high priority. */ +typedef NS_ENUM(NSInteger, GDTCCTQoSTier) { + /** The QoS tier wasn't set, and won't ever be sent. */ + GDTCCTQoSDefault = 0, + + /** This event is internal telemetry data that should not be sent on its own if possible. */ + GDTCCTQoSTelemetry = 1, + + /** This event should be sent, but in a batch only roughly once per day. */ + GDTCCTQoSDaily = 2, + + /** This event should only be uploaded on wifi. */ + GDTCCTQoSWifiOnly = 5, +}; + +/** Converts a GDTCOREventQoS to a GDTCCTQoS tier. + * + * @param qosTier The GDTCOREventQoS value. + * @return A static NSNumber that represents the CCT QoS tier. + */ +FOUNDATION_STATIC_INLINE +NSNumber *GDTCCTQosTierFromGDTCOREventQosTier(GDTCOREventQoS qosTier) { + switch (qosTier) { + case GDTCOREventQoSWifiOnly: + return @(GDTCCTQoSWifiOnly); + break; + + case GDTCOREventQoSTelemetry: + // falls through. + case GDTCOREventQoSDaily: + return @(GDTCCTQoSDaily); + break; + + default: + return @(GDTCCTQoSDefault); + break; + } +} + +/** Returns a set of logs that are ok to upload whilst on mobile data. + * + * @note This should be called from a thread safe method. + * @return A set of logs that are ok to upload whilst on mobile data. + */ +- (NSSet *)logEventsOkToSendOnMobileData { + return [self.events + objectsPassingTest:^BOOL(GDTCORStoredEvent *_Nonnull event, BOOL *_Nonnull stop) { + return [GDTCCTQosTierFromGDTCOREventQosTier(event.qosTier) isEqual:@(GDTCCTQoSDefault)]; + }]; +} + +/** Returns a set of logs that are ok to upload whilst on wifi. + * + * @note This should be called from a thread safe method. + * @return A set of logs that are ok to upload whilst on wifi. + */ +- (NSSet *)logEventsOkToSendOnWifi { + return [self.events + objectsPassingTest:^BOOL(GDTCORStoredEvent *_Nonnull event, BOOL *_Nonnull stop) { + NSNumber *qosTier = GDTCCTQosTierFromGDTCOREventQosTier(event.qosTier); + return [qosTier isEqual:@(GDTCCTQoSDefault)] || [qosTier isEqual:@(GDTCCTQoSWifiOnly)] || + [qosTier isEqual:@(GDTCCTQoSDaily)]; + }]; +} + +/** Returns a set of logs that only should have a single upload attempt per day. + * + * @note This should be called from a thread safe method. + * @return A set of logs that are ok to upload only once per day. + */ +- (NSSet *)logEventsOkToSendDaily { + return [self.events + objectsPassingTest:^BOOL(GDTCORStoredEvent *_Nonnull event, BOOL *_Nonnull stop) { + return [GDTCCTQosTierFromGDTCOREventQosTier(event.qosTier) isEqual:@(GDTCCTQoSDaily)]; + }]; +} + +#pragma mark - GDTCORUploadPackageProtocol + +- (void)packageDelivered:(GDTCORUploadPackage *)package successful:(BOOL)successful { + dispatch_async(_queue, ^{ + NSSet *events = [package.events copy]; + for (GDTCORStoredEvent *event in events) { + [self.events removeObject:event]; + } + }); +} + +- (void)packageExpired:(GDTCORUploadPackage *)package { + [self packageDelivered:package successful:YES]; +} + +@end diff --git a/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/GDTCCTUploader.m b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/GDTCCTUploader.m new file mode 100644 index 0000000..532bf3b --- /dev/null +++ b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/GDTCCTUploader.m @@ -0,0 +1,278 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "GDTCCTLibrary/Private/GDTCCTUploader.h" + +#import +#import +#import + +#import +#import +#import + +#import "GDTCCTLibrary/Private/GDTCCTCompressionHelper.h" +#import "GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h" +#import "GDTCCTLibrary/Private/GDTCCTPrioritizer.h" + +#import "GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h" + +#ifdef GDTCCTSUPPORT_VERSION +#define STR(x) STR_EXPAND(x) +#define STR_EXPAND(x) #x +static NSString *const kGDTCCTSupportSDKVersion = @STR(GDTCCTSUPPORT_VERSION); +#else +static NSString *const kGDTCCTSupportSDKVersion = @"UNKNOWN"; +#endif // GDTCCTSUPPORT_VERSION + +#if !NDEBUG +NSNotificationName const GDTCCTUploadCompleteNotification = @"com.GDTCCTUploader.UploadComplete"; +#endif // #if !NDEBUG + +@interface GDTCCTUploader () + +// Redeclared as readwrite. +@property(nullable, nonatomic, readwrite) NSURLSessionUploadTask *currentTask; + +@end + +@implementation GDTCCTUploader + ++ (void)load { + GDTCCTUploader *uploader = [GDTCCTUploader sharedInstance]; + [[GDTCORRegistrar sharedInstance] registerUploader:uploader target:kGDTCORTargetCCT]; +} + ++ (instancetype)sharedInstance { + static GDTCCTUploader *sharedInstance; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[GDTCCTUploader alloc] init]; + }); + return sharedInstance; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _uploaderQueue = dispatch_queue_create("com.google.GDTCCTUploader", DISPATCH_QUEUE_SERIAL); + NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; + _uploaderSession = [NSURLSession sessionWithConfiguration:config]; + } + return self; +} + +- (NSURL *)defaultServerURL { + static NSURL *defaultServerURL; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + // These strings should be interleaved to construct the real URL. This is just to (hopefully) + // fool github URL scanning bots. + const char *p1 = "hts/frbslgiggolai.o/0clgbth"; + const char *p2 = "tp:/ieaeogn.ogepscmvc/o/ac"; + const char defaultURL[54] = { + p1[0], p2[0], p1[1], p2[1], p1[2], p2[2], p1[3], p2[3], p1[4], p2[4], p1[5], + p2[5], p1[6], p2[6], p1[7], p2[7], p1[8], p2[8], p1[9], p2[9], p1[10], p2[10], + p1[11], p2[11], p1[12], p2[12], p1[13], p2[13], p1[14], p2[14], p1[15], p2[15], p1[16], + p2[16], p1[17], p2[17], p1[18], p2[18], p1[19], p2[19], p1[20], p2[20], p1[21], p2[21], + p1[22], p2[22], p1[23], p2[23], p1[24], p2[24], p1[25], p2[25], p1[26], '\0'}; + defaultServerURL = [NSURL URLWithString:[NSString stringWithUTF8String:defaultURL]]; + }); + return defaultServerURL; +} + +- (void)uploadPackage:(GDTCORUploadPackage *)package { + __block GDTCORBackgroundIdentifier bgID = GDTCORBackgroundIdentifierInvalid; + bgID = [[GDTCORApplication sharedApplication] + beginBackgroundTaskWithName:@"GDTCCTUploader-upload" + expirationHandler:^{ + if (bgID != GDTCORBackgroundIdentifierInvalid) { + // Cancel the current upload and complete delivery. + [self.currentTask cancel]; + [self.currentUploadPackage completeDelivery]; + + // End the task. + [[GDTCORApplication sharedApplication] endBackgroundTask:bgID]; + } + }]; + + dispatch_async(_uploaderQueue, ^{ + if (self->_currentTask || self->_currentUploadPackage) { + GDTCORLogWarning(GDTCORMCWUploadFailed, @"%@", + @"An upload shouldn't be initiated with another in progress."); + return; + } + NSURL *serverURL = self.serverURL ? self.serverURL : [self defaultServerURL]; + + id completionHandler = ^(NSData *_Nullable data, NSURLResponse *_Nullable response, + NSError *_Nullable error) { + GDTCORLogDebug("%@", @"CCT: request completed"); + if (error) { + GDTCORLogWarning(GDTCORMCWUploadFailed, @"There was an error uploading events: %@", error); + } + NSError *decodingError; + if (data) { + gdt_cct_LogResponse logResponse = GDTCCTDecodeLogResponse(data, &decodingError); + if (!decodingError && logResponse.has_next_request_wait_millis) { + GDTCORLogDebug( + "CCT: The backend responded asking to not upload for %lld millis from now.", + logResponse.next_request_wait_millis); + self->_nextUploadTime = + [GDTCORClock clockSnapshotInTheFuture:logResponse.next_request_wait_millis]; + } else { + GDTCORLogDebug("%@", @"CCT: The CCT backend response failed to parse, so the next " + @"request won't occur until 15 minutes from now"); + // 15 minutes from now. + self->_nextUploadTime = [GDTCORClock clockSnapshotInTheFuture:15 * 60 * 1000]; + } + pb_release(gdt_cct_LogResponse_fields, &logResponse); + } +#if !NDEBUG + // Post a notification when in DEBUG mode to state how many packages were uploaded. Useful + // for validation during tests. + [[NSNotificationCenter defaultCenter] postNotificationName:GDTCCTUploadCompleteNotification + object:@(package.events.count)]; +#endif // #if !NDEBUG + GDTCORLogDebug("%@", @"CCT: package delivered"); + [package completeDelivery]; + + // End the background task if there was one. + if (bgID != GDTCORBackgroundIdentifierInvalid) { + [[GDTCORApplication sharedApplication] endBackgroundTask:bgID]; + bgID = GDTCORBackgroundIdentifierInvalid; + } + self.currentTask = nil; + self.currentUploadPackage = nil; + }; + self->_currentUploadPackage = package; + NSData *requestProtoData = + [self constructRequestProtoFromPackage:(GDTCORUploadPackage *)package]; + NSData *gzippedData = [GDTCCTCompressionHelper gzippedData:requestProtoData]; + BOOL usingGzipData = gzippedData != nil && gzippedData.length < requestProtoData.length; + NSData *dataToSend = usingGzipData ? gzippedData : requestProtoData; + NSURLRequest *request = [self constructRequestWithURL:serverURL data:dataToSend]; + GDTCORLogDebug("CCT: request created: %@", request); + self.currentTask = [self.uploaderSession uploadTaskWithRequest:request + fromData:dataToSend + completionHandler:completionHandler]; + GDTCORLogDebug("%@", @"CCT: The upload task is about to begin."); + [self.currentTask resume]; + }); +} + +- (BOOL)readyToUploadWithConditions:(GDTCORUploadConditions)conditions { + __block BOOL result = NO; + dispatch_sync(_uploaderQueue, ^{ + if (self->_currentUploadPackage) { + result = NO; + GDTCORLogDebug("%@", @"CCT: can't upload because a package is in flight"); + return; + } + if (self->_currentTask) { + result = NO; + GDTCORLogDebug("%@", @"CCT: can't upload because a task is in progress"); + return; + } + if ((conditions & GDTCORUploadConditionHighPriority) == GDTCORUploadConditionHighPriority) { + result = YES; + GDTCORLogDebug("%@", @"CCT: a high priority event is allowing an upload"); + return; + } else if (self->_nextUploadTime) { + result = [[GDTCORClock snapshot] isAfter:self->_nextUploadTime]; +#if !NDEBUG + if (result) { + GDTCORLogDebug("%@", @"CCT: can upload because the request wait time has transpired"); + } else { + GDTCORLogDebug("%@", @"CCT: can't upload because the backend asked to wait"); + } +#endif // !NDEBUG + return; + } + GDTCORLogDebug("%@", @"CCT: can upload because nothing is preventing it"); + result = YES; + }); + return result; +} + +#pragma mark - Private helper methods + +/** Constructs data given an upload package. + * + * @param package The upload package used to construct the request proto bytes. + * @return Proto bytes representing a gdt_cct_LogRequest object. + */ +- (nonnull NSData *)constructRequestProtoFromPackage:(GDTCORUploadPackage *)package { + // Segment the log events by log type. + NSMutableDictionary *> *logMappingIDToLogSet = + [[NSMutableDictionary alloc] init]; + [package.events + enumerateObjectsUsingBlock:^(GDTCORStoredEvent *_Nonnull event, BOOL *_Nonnull stop) { + NSMutableSet *logSet = logMappingIDToLogSet[event.mappingID]; + logSet = logSet ? logSet : [[NSMutableSet alloc] init]; + [logSet addObject:event]; + logMappingIDToLogSet[event.mappingID] = logSet; + }]; + + gdt_cct_BatchedLogRequest batchedLogRequest = + GDTCCTConstructBatchedLogRequest(logMappingIDToLogSet); + + NSData *data = GDTCCTEncodeBatchedLogRequest(&batchedLogRequest); + pb_release(gdt_cct_BatchedLogRequest_fields, &batchedLogRequest); + return data ? data : [[NSData alloc] init]; +} + +/** Constructs a request to CCT given a URL and request body data. + * + * @param URL The URL to send the request to. + * @param data The request body data. + * @return A new NSURLRequest ready to be sent to CCT. + */ +- (NSURLRequest *)constructRequestWithURL:(NSURL *)URL data:(NSData *)data { + BOOL isGzipped = [GDTCCTCompressionHelper isGzipped:data]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; + [request setValue:@"application/x-protobuf" forHTTPHeaderField:@"Content-Type"]; + if (isGzipped) { + [request setValue:@"gzip" forHTTPHeaderField:@"Content-Encoding"]; + } + [request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"]; + NSString *userAgent = [NSString stringWithFormat:@"datatransport/%@ cctsupport/%@ apple/", + kGDTCORVersion, kGDTCCTSupportSDKVersion]; + [request setValue:userAgent forHTTPHeaderField:@"User-Agent"]; + request.HTTPMethod = @"POST"; + [request setHTTPBody:data]; + return request; +} + +#pragma mark - GDTCORUploadPackageProtocol + +- (void)packageExpired:(GDTCORUploadPackage *)package { + dispatch_async(_uploaderQueue, ^{ + [self.currentTask cancel]; + self.currentTask = nil; + self.currentUploadPackage = nil; + }); +} + +#pragma mark - GDTCORLifecycleProtocol + +- (void)appWillTerminate:(GDTCORApplication *)application { + dispatch_sync(_uploaderQueue, ^{ + [self.currentTask cancel]; + [self.currentUploadPackage completeDelivery]; + }); +} + +@end diff --git a/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/GDTFLLPrioritizer.m b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/GDTFLLPrioritizer.m new file mode 100644 index 0000000..b52c10b --- /dev/null +++ b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/GDTFLLPrioritizer.m @@ -0,0 +1,199 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "GDTCCTLibrary/Private/GDTFLLPrioritizer.h" + +#import +#import +#import +#import +#import + +const static int64_t kMillisPerDay = 8.64e+7; + +@implementation GDTFLLPrioritizer + ++ (void)load { + GDTFLLPrioritizer *prioritizer = [GDTFLLPrioritizer sharedInstance]; + [[GDTCORRegistrar sharedInstance] registerPrioritizer:prioritizer target:kGDTCORTargetFLL]; +} + ++ (instancetype)sharedInstance { + static GDTFLLPrioritizer *sharedInstance; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[GDTFLLPrioritizer alloc] init]; + }); + return sharedInstance; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _queue = dispatch_queue_create("com.google.GDTFLLPrioritizer", DISPATCH_QUEUE_SERIAL); + _events = [[NSMutableSet alloc] init]; + } + return self; +} + +#pragma mark - GDTCORPrioritizer Protocol + +- (void)prioritizeEvent:(GDTCORStoredEvent *)event { + dispatch_async(_queue, ^{ + [self.events addObject:event]; + }); +} + +- (GDTCORUploadPackage *)uploadPackageWithConditions:(GDTCORUploadConditions)conditions { + GDTCORUploadPackage *package = [[GDTCORUploadPackage alloc] initWithTarget:kGDTCORTargetFLL]; + dispatch_sync(_queue, ^{ + NSSet *logEventsThatWillBeSent; + // A high priority event effectively flushes all events to be sent. + if ((conditions & GDTCORUploadConditionHighPriority) == GDTCORUploadConditionHighPriority) { + GDTCORLogDebug("%@", @"FLL: A high priority event is flushing all events."); + package.events = self.events; + GDTCORLogDebug("FLL: %lu events are in the upload package", + (unsigned long)package.events.count); + return; + } + + // If on wifi, upload logs that are ok to send on wifi. + if ((conditions & GDTCORUploadConditionWifiData) == GDTCORUploadConditionWifiData) { + logEventsThatWillBeSent = [self logEventsOkToSendOnWifi]; + GDTCORLogDebug("%@", @"FLL: events ok to send on wifi are being added to the upload package"); + } else { + logEventsThatWillBeSent = [self logEventsOkToSendOnMobileData]; + GDTCORLogDebug("%@", + @"FLL: events ok to send on mobile are being added to the upload package"); + } + + // If it's been > 24h since the last daily upload, upload logs with the daily QoS. + if (self.timeOfLastDailyUpload) { + int64_t millisSinceLastUpload = + [GDTCORClock snapshot].timeMillis - self.timeOfLastDailyUpload.timeMillis; + if (millisSinceLastUpload > kMillisPerDay) { + logEventsThatWillBeSent = + [logEventsThatWillBeSent setByAddingObjectsFromSet:[self logEventsOkToSendDaily]]; + GDTCORLogDebug("%@", @"FLL: events ok to send daily are being added to the upload package"); + } + } else { + self.timeOfLastDailyUpload = [GDTCORClock snapshot]; + logEventsThatWillBeSent = + [logEventsThatWillBeSent setByAddingObjectsFromSet:[self logEventsOkToSendDaily]]; + GDTCORLogDebug("%@", @"FLL: events ok to send daily are being added to the upload package"); + } + package.events = logEventsThatWillBeSent; + }); + GDTCORLogDebug("FLL: created an upload package with %ld events", + (unsigned long)package.events.count); + return package; +} + +#pragma mark - Private helper methods + +/** The different possible quality of service specifiers. High values indicate high priority. */ +typedef NS_ENUM(NSInteger, GDTFLLQoSTier) { + /** The QoS tier wasn't set, and won't ever be sent. */ + GDTFLLQoSDefault = 0, + + /** This event is internal telemetry data that should not be sent on its own if possible. */ + GDTFLLQoSTelemetry = 1, + + /** This event should be sent, but in a batch only roughly once per day. */ + GDTFLLQoSDaily = 2, + + /** This event should only be uploaded on wifi. */ + GDTFLLQoSWifiOnly = 5, +}; + +/** Converts a GDTCOREventQoS to a GDTFLLQoS tier. + * + * @param qosTier The GDTCOREventQoS value. + * @return A static NSNumber that represents the CCT QoS tier. + */ +FOUNDATION_STATIC_INLINE +NSNumber *GDTCCTQosTierFromGDTCOREventQosTier(GDTCOREventQoS qosTier) { + switch (qosTier) { + case GDTCOREventQoSWifiOnly: + return @(GDTFLLQoSWifiOnly); + break; + + case GDTCOREventQoSTelemetry: + // falls through. + case GDTCOREventQoSDaily: + return @(GDTFLLQoSDaily); + break; + + default: + return @(GDTFLLQoSDefault); + break; + } +} + +/** Returns a set of logs that are ok to upload whilst on mobile data. + * + * @note This should be called from a thread safe method. + * @return A set of logs that are ok to upload whilst on mobile data. + */ +- (NSSet *)logEventsOkToSendOnMobileData { + return [self.events + objectsPassingTest:^BOOL(GDTCORStoredEvent *_Nonnull event, BOOL *_Nonnull stop) { + return [GDTCCTQosTierFromGDTCOREventQosTier(event.qosTier) isEqual:@(GDTFLLQoSDefault)]; + }]; +} + +/** Returns a set of logs that are ok to upload whilst on wifi. + * + * @note This should be called from a thread safe method. + * @return A set of logs that are ok to upload whilst on wifi. + */ +- (NSSet *)logEventsOkToSendOnWifi { + return [self.events + objectsPassingTest:^BOOL(GDTCORStoredEvent *_Nonnull event, BOOL *_Nonnull stop) { + NSNumber *qosTier = GDTCCTQosTierFromGDTCOREventQosTier(event.qosTier); + return [qosTier isEqual:@(GDTFLLQoSDefault)] || [qosTier isEqual:@(GDTFLLQoSWifiOnly)] || + [qosTier isEqual:@(GDTFLLQoSDaily)]; + }]; +} + +/** Returns a set of logs that only should have a single upload attempt per day. + * + * @note This should be called from a thread safe method. + * @return A set of logs that are ok to upload only once per day. + */ +- (NSSet *)logEventsOkToSendDaily { + return [self.events + objectsPassingTest:^BOOL(GDTCORStoredEvent *_Nonnull event, BOOL *_Nonnull stop) { + return [GDTCCTQosTierFromGDTCOREventQosTier(event.qosTier) isEqual:@(GDTFLLQoSDaily)]; + }]; +} + +#pragma mark - GDTCORUploadPackageProtocol + +- (void)packageDelivered:(GDTCORUploadPackage *)package successful:(BOOL)successful { + dispatch_async(_queue, ^{ + NSSet *events = [package.events copy]; + for (GDTCORStoredEvent *event in events) { + [self.events removeObject:event]; + } + }); +} + +- (void)packageExpired:(GDTCORUploadPackage *)package { + [self packageDelivered:package successful:YES]; +} + +@end diff --git a/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/GDTFLLUploader.m b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/GDTFLLUploader.m new file mode 100644 index 0000000..6117d88 --- /dev/null +++ b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/GDTFLLUploader.m @@ -0,0 +1,328 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "GDTCCTLibrary/Private/GDTFLLUploader.h" + +#import +#import +#import + +#import +#import +#import + +#import "GDTCCTLibrary/Private/GDTCCTCompressionHelper.h" +#import "GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h" +#import "GDTCCTLibrary/Private/GDTFLLPrioritizer.h" + +#import "GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h" + +#ifdef GDTCCTSUPPORT_VERSION +#define STR(x) STR_EXPAND(x) +#define STR_EXPAND(x) #x +static NSString *const kGDTCCTSupportSDKVersion = @STR(GDTCCTSUPPORT_VERSION); +#else +static NSString *const kGDTCCTSupportSDKVersion = @"UNKNOWN"; +#endif // GDTCCTSUPPORT_VERSION + +#if !NDEBUG +NSNotificationName const GDTFLLUploadCompleteNotification = @"com.GDTFLLUploader.UploadComplete"; +#endif // #if !NDEBUG + +@interface GDTFLLUploader () + +// Redeclared as readwrite. +@property(nullable, nonatomic, readwrite) NSURLSessionUploadTask *currentTask; + +@end + +@implementation GDTFLLUploader + ++ (void)load { + GDTFLLUploader *uploader = [GDTFLLUploader sharedInstance]; + [[GDTCORRegistrar sharedInstance] registerUploader:uploader target:kGDTCORTargetFLL]; +} + ++ (instancetype)sharedInstance { + static GDTFLLUploader *sharedInstance; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[GDTFLLUploader alloc] init]; + }); + return sharedInstance; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _uploaderQueue = dispatch_queue_create("com.google.GDTFLLUploader", DISPATCH_QUEUE_SERIAL); + NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; + _uploaderSession = [NSURLSession sessionWithConfiguration:config + delegate:self + delegateQueue:nil]; + } + return self; +} + +- (NSURL *)defaultServerURL { + static NSURL *defaultServerURL; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + // These strings should be interleaved to construct the real URL. This is just to (hopefully) + // fool github URL scanning bots. + const char *p1 = "hts/frbslgigp.ogepscmv/ieo/eaybtho"; + const char *p2 = "tp:/ieaeogn-agolai.o/1frlglgc/aclg"; + const char defaultURL[69] = { + p1[0], p2[0], p1[1], p2[1], p1[2], p2[2], p1[3], p2[3], p1[4], p2[4], + p1[5], p2[5], p1[6], p2[6], p1[7], p2[7], p1[8], p2[8], p1[9], p2[9], + p1[10], p2[10], p1[11], p2[11], p1[12], p2[12], p1[13], p2[13], p1[14], p2[14], + p1[15], p2[15], p1[16], p2[16], p1[17], p2[17], p1[18], p2[18], p1[19], p2[19], + p1[20], p2[20], p1[21], p2[21], p1[22], p2[22], p1[23], p2[23], p1[24], p2[24], + p1[25], p2[25], p1[26], p2[26], p1[27], p2[27], p1[28], p2[28], p1[29], p2[29], + p1[30], p2[30], p1[31], p2[31], p1[32], p2[32], p1[33], p2[33], '\0'}; + defaultServerURL = [NSURL URLWithString:[NSString stringWithUTF8String:defaultURL]]; + }); + return defaultServerURL; +} + +- (NSString *)defaultAPIKey { + static NSString *defaultServerKey; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + // These strings should be interleaved to construct the real key. + const char *p1 = "AzSBG0honD6A-PxV5nBc"; + const char *p2 = "Iay44Iwtu2vV0AOrz1C"; + const char defaultKey[40] = {p1[0], p2[0], p1[1], p2[1], p1[2], p2[2], p1[3], p2[3], + p1[4], p2[4], p1[5], p2[5], p1[6], p2[6], p1[7], p2[7], + p1[8], p2[8], p1[9], p2[9], p1[10], p2[10], p1[11], p2[11], + p1[12], p2[12], p1[13], p2[13], p1[14], p2[14], p1[15], p2[15], + p1[16], p2[16], p1[17], p2[17], p1[18], p2[18], p1[19], '\0'}; + defaultServerKey = [NSString stringWithUTF8String:defaultKey]; + }); + return defaultServerKey; +} + +- (void)uploadPackage:(GDTCORUploadPackage *)package { + __block GDTCORBackgroundIdentifier bgID = GDTCORBackgroundIdentifierInvalid; + bgID = [[GDTCORApplication sharedApplication] + beginBackgroundTaskWithName:@"GDTFLLUploader-upload" + expirationHandler:^{ + if (bgID != GDTCORBackgroundIdentifierInvalid) { + // Cancel the upload and complete delivery. + [self.currentTask cancel]; + [self.currentUploadPackage completeDelivery]; + + // End the background task. + [[GDTCORApplication sharedApplication] endBackgroundTask:bgID]; + } + }]; + + dispatch_async(_uploaderQueue, ^{ + if (self->_currentTask || self->_currentUploadPackage) { + GDTCORLogWarning(GDTCORMCWUploadFailed, @"%@", + @"An upload shouldn't be initiated with another in progress."); + return; + } + NSURL *serverURL = self.serverURL ? self.serverURL : [self defaultServerURL]; + + id completionHandler = ^(NSData *_Nullable data, NSURLResponse *_Nullable response, + NSError *_Nullable error) { + GDTCORLogDebug("%@", @"FLL: request completed"); + if (error) { + GDTCORLogWarning(GDTCORMCWUploadFailed, @"There was an error uploading events: %@", error); + } + NSError *decodingError; + if (data) { + gdt_cct_LogResponse logResponse = GDTCCTDecodeLogResponse(data, &decodingError); + if (!decodingError && logResponse.has_next_request_wait_millis) { + GDTCORLogDebug( + "FLL: The backend responded asking to not upload for %lld millis from now.", + logResponse.next_request_wait_millis); + self->_nextUploadTime = + [GDTCORClock clockSnapshotInTheFuture:logResponse.next_request_wait_millis]; + } else { + GDTCORLogDebug("%@", @"FLL: The CCT backend response failed to parse, so the next " + @"request won't occur until 15 minutes from now"); + // 15 minutes from now. + self->_nextUploadTime = [GDTCORClock clockSnapshotInTheFuture:15 * 60 * 1000]; + } + pb_release(gdt_cct_LogResponse_fields, &logResponse); + } + + // Only retry if one of these codes is returned. + if (((NSHTTPURLResponse *)response).statusCode == 429 || + ((NSHTTPURLResponse *)response).statusCode == 503) { + [package retryDeliveryInTheFuture]; + } else { +#if !NDEBUG + // Post a notification when in DEBUG mode to state how many packages were uploaded. Useful + // for validation during tests. + [[NSNotificationCenter defaultCenter] postNotificationName:GDTFLLUploadCompleteNotification + object:@(package.events.count)]; +#endif // #if !NDEBUG + GDTCORLogDebug("%@", @"FLL: package delivered"); + [package completeDelivery]; + } + + // End the background task if there was one. + if (bgID != GDTCORBackgroundIdentifierInvalid) { + [[GDTCORApplication sharedApplication] endBackgroundTask:bgID]; + bgID = GDTCORBackgroundIdentifierInvalid; + } + self.currentTask = nil; + self.currentUploadPackage = nil; + }; + self->_currentUploadPackage = package; + NSData *requestProtoData = + [self constructRequestProtoFromPackage:(GDTCORUploadPackage *)package]; + NSData *gzippedData = [GDTCCTCompressionHelper gzippedData:requestProtoData]; + BOOL usingGzipData = gzippedData != nil && gzippedData.length < requestProtoData.length; + NSData *dataToSend = usingGzipData ? gzippedData : requestProtoData; + NSURLRequest *request = [self constructRequestWithURL:serverURL data:dataToSend]; + GDTCORLogDebug("FLL: request created: %@", request); + self.currentTask = [self.uploaderSession uploadTaskWithRequest:request + fromData:dataToSend + completionHandler:completionHandler]; + GDTCORLogDebug("%@", @"FLL: The upload task is about to begin."); + [self.currentTask resume]; + }); +} + +- (BOOL)readyToUploadWithConditions:(GDTCORUploadConditions)conditions { + __block BOOL result = NO; + dispatch_sync(_uploaderQueue, ^{ + if (self->_currentUploadPackage) { + result = NO; + GDTCORLogDebug("%@", @"FLL: can't upload because a package is in flight"); + return; + } + if (self->_currentTask) { + result = NO; + GDTCORLogDebug("%@", @"FLL: can't upload because a task is in progress"); + return; + } + if ((conditions & GDTCORUploadConditionHighPriority) == GDTCORUploadConditionHighPriority) { + result = YES; + GDTCORLogDebug("%@", @"FLL: a high priority event is allowing an upload"); + return; + } else if (self->_nextUploadTime) { + result = [[GDTCORClock snapshot] isAfter:self->_nextUploadTime]; +#if !NDEBUG + if (result) { + GDTCORLogDebug("%@", @"FLL: can upload because the request wait time has transpired"); + } else { + GDTCORLogDebug("%@", @"FLL: can't upload because the backend asked to wait"); + } +#endif // !NDEBUG + return; + } + GDTCORLogDebug("%@", @"FLL: can upload because nothing is preventing it"); + result = YES; + }); + return result; +} + +#pragma mark - Private helper methods + +/** Constructs data given an upload package. + * + * @param package The upload package used to construct the request proto bytes. + * @return Proto bytes representing a gdt_cct_LogRequest object. + */ +- (nonnull NSData *)constructRequestProtoFromPackage:(GDTCORUploadPackage *)package { + // Segment the log events by log type. + NSMutableDictionary *> *logMappingIDToLogSet = + [[NSMutableDictionary alloc] init]; + [package.events + enumerateObjectsUsingBlock:^(GDTCORStoredEvent *_Nonnull event, BOOL *_Nonnull stop) { + NSMutableSet *logSet = logMappingIDToLogSet[event.mappingID]; + logSet = logSet ? logSet : [[NSMutableSet alloc] init]; + [logSet addObject:event]; + logMappingIDToLogSet[event.mappingID] = logSet; + }]; + + gdt_cct_BatchedLogRequest batchedLogRequest = + GDTCCTConstructBatchedLogRequest(logMappingIDToLogSet); + + NSData *data = GDTCCTEncodeBatchedLogRequest(&batchedLogRequest); + pb_release(gdt_cct_BatchedLogRequest_fields, &batchedLogRequest); + return data ? data : [[NSData alloc] init]; +} + +/** Constructs a request to FLL given a URL and request body data. + * + * @param URL The URL to send the request to. + * @param data The request body data. + * @return A new NSURLRequest ready to be sent to FLL. + */ +- (NSURLRequest *)constructRequestWithURL:(NSURL *)URL data:(NSData *)data { + const UInt8 *bytes = (const UInt8 *)data.bytes; + // From https://en.wikipedia.org/wiki/Gzip, gzip's magic number is 1f 8b. + BOOL isGzipped = (data.length >= 2 && bytes[0] == 0x1f && bytes[1] == 0x8b); + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; + [request setValue:[self defaultAPIKey] forHTTPHeaderField:@"X-Goog-Api-Key"]; + [request setValue:@"application/x-protobuf" forHTTPHeaderField:@"Content-Type"]; + if (isGzipped) { + [request setValue:@"gzip" forHTTPHeaderField:@"Content-Encoding"]; + } + [request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"]; + NSString *userAgent = [NSString stringWithFormat:@"datatransport/%@ fllsupport/%@ apple/", + kGDTCORVersion, kGDTCCTSupportSDKVersion]; + [request setValue:userAgent forHTTPHeaderField:@"User-Agent"]; + request.HTTPMethod = @"POST"; + [request setHTTPBody:data]; + return request; +} + +#pragma mark - GDTCORUploadPackageProtocol + +- (void)packageExpired:(GDTCORUploadPackage *)package { + dispatch_async(_uploaderQueue, ^{ + [self.currentTask cancel]; + self.currentTask = nil; + self.currentUploadPackage = nil; + }); +} + +#pragma mark - GDTCORLifecycleProtocol + +- (void)appWillTerminate:(GDTCORApplication *)application { + dispatch_sync(_uploaderQueue, ^{ + [self.currentTask cancel]; + [self.currentUploadPackage completeDelivery]; + }); +} + +#pragma mark - NSURLSessionDelegate + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + willPerformHTTPRedirection:(NSHTTPURLResponse *)response + newRequest:(NSURLRequest *)request + completionHandler:(void (^)(NSURLRequest *_Nullable))completionHandler { + if (!completionHandler) { + return; + } + if (response.statusCode == 302 || response.statusCode == 301) { + NSURLRequest *newRequest = [self constructRequestWithURL:request.URL + data:task.originalRequest.HTTPBody]; + completionHandler(newRequest); + } else { + completionHandler(request); + } +} + +@end diff --git a/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/Private/GDTCCTCompressionHelper.h b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/Private/GDTCCTCompressionHelper.h new file mode 100644 index 0000000..08d0a4b --- /dev/null +++ b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/Private/GDTCCTCompressionHelper.h @@ -0,0 +1,40 @@ +/* + * Copyright 2020 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** A class with methods to help with gzipped data. */ +@interface GDTCCTCompressionHelper : NSObject + +/** Compresses the given data and returns a new data object. + * + * @note Reduced version from GULNSData+zlib.m of GoogleUtilities. + * @return Compressed data, or nil if there was an error. + */ ++ (nullable NSData *)gzippedData:(NSData *)data; + +/** Returns YES if the data looks like it was gzip compressed by checking for the gzip magic number. + * + * @note: From https://en.wikipedia.org/wiki/Gzip, gzip's magic number is 1f 8b. + * @return YES if the data appears gzipped, NO otherwise. + */ ++ (BOOL)isGzipped:(NSData *)data; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h new file mode 100644 index 0000000..08081cc --- /dev/null +++ b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h @@ -0,0 +1,112 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import + +#import "GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h" + +NS_ASSUME_NONNULL_BEGIN + +#pragma mark - General purpose encoders + +/** Converts an NSString* to a pb_bytes_array_t*. + * + * @note malloc is called in this method. Ensure that pb_release is called on this or the parent. + * + * @param string The string to convert. + * @return A newly allocated array of bytes representing the UTF8 encoding of the string. + */ +pb_bytes_array_t *GDTCCTEncodeString(NSString *string); + +/** Converts an NSData to a pb_bytes_array_t*. + * + * @note malloc is called in this method. Ensure that pb_release is called on this or the parent. + * + * @param data The data to convert. + * @return A newly allocated array of bytes with [data bytes] copied into it. + */ +pb_bytes_array_t *GDTCCTEncodeData(NSData *data); + +#pragma mark - CCT object constructors + +/** Encodes a batched log request. + * + * @note Ensure that pb_release is called on the batchedLogRequest param. + * + * @param batchedLogRequest A pointer to the log batch to encode to bytes. + * @return An NSData object representing the bytes of the log request batch. + */ +FOUNDATION_EXPORT +NSData *GDTCCTEncodeBatchedLogRequest(gdt_cct_BatchedLogRequest *batchedLogRequest); + +/** Constructs a gdt_cct_BatchedLogRequest given sets of events segemented by mapping ID. + * + * @note malloc is called in this method. Ensure that pb_release is called on this or the parent. + * + * @param logMappingIDToLogSet A map of mapping IDs to sets of events to convert into a batch. + * @return A newly created gdt_cct_BatchedLogRequest. + */ +FOUNDATION_EXPORT +gdt_cct_BatchedLogRequest GDTCCTConstructBatchedLogRequest( + NSDictionary *> *logMappingIDToLogSet); + +/** Constructs a log request given a log source and a set of events. + * + * @note malloc is called in this method. Ensure that pb_release is called on this or the parent. + * @param logSource The CCT log source to put into the log request. + * @param logSet The set of events to send in this log request. + */ +FOUNDATION_EXPORT +gdt_cct_LogRequest GDTCCTConstructLogRequest(int32_t logSource, NSSet *logSet); + +/** Constructs a gdt_cct_LogEvent given a GDTCORStoredEvent*. + * + * @param event The GDTCORStoredEvent to convert. + * @return The new gdt_cct_LogEvent object. + */ +FOUNDATION_EXPORT +gdt_cct_LogEvent GDTCCTConstructLogEvent(GDTCORStoredEvent *event); + +/** Constructs a gdt_cct_ClientInfo representing the client device. + * + * @return The new gdt_cct_ClientInfo object. + */ +FOUNDATION_EXPORT +gdt_cct_ClientInfo GDTCCTConstructClientInfo(void); + +/** Constructs a gdt_cct_IosClientInfo representing the client device. + * + * @return The new gdt_cct_IosClientInfo object. + */ +FOUNDATION_EXPORT +gdt_cct_IosClientInfo GDTCCTConstructiOSClientInfo(void); + +#pragma mark - CCT object decoders + +/** Decodes a gdt_cct_LogResponse given proto bytes. + * + * @note malloc is called in this method. Ensure that pb_release is called on the return value. + * + * @param data The proto bytes of the gdt_cct_LogResponse. + * @param error An error that will be populated if something went wrong during decoding. + * @return A newly allocated gdt_cct_LogResponse from the data, if the bytes decoded properly. + */ +FOUNDATION_EXPORT +gdt_cct_LogResponse GDTCCTDecodeLogResponse(NSData *data, NSError **error); + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/Private/GDTCCTPrioritizer.h b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/Private/GDTCCTPrioritizer.h new file mode 100644 index 0000000..1908a31 --- /dev/null +++ b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/Private/GDTCCTPrioritizer.h @@ -0,0 +1,44 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Manages the prioritization of events from GoogleDataTransport. */ +@interface GDTCCTPrioritizer : NSObject + +/** The queue on which this prioritizer operates. */ +@property(nonatomic) dispatch_queue_t queue; + +/** All log events that have been processed by this prioritizer. */ +@property(nonatomic) NSMutableSet *events; + +/** The most recent attempted upload of daily uploaded logs. */ +@property(nonatomic) GDTCORClock *timeOfLastDailyUpload; + +/** Creates and/or returns the singleton instance of this class. + * + * @return The singleton instance of this class. + */ ++ (instancetype)sharedInstance; + +NS_ASSUME_NONNULL_END + +@end diff --git a/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/Private/GDTCCTUploader.h b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/Private/GDTCCTUploader.h new file mode 100644 index 0000000..ba95a20 --- /dev/null +++ b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/Private/GDTCCTUploader.h @@ -0,0 +1,57 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +#if !NDEBUG +/** A notification fired when uploading is complete, detailing the number of events uploaded. */ +extern NSNotificationName const GDTCCTUploadCompleteNotification; +#endif // #if !NDEBUG + +/** Class capable of uploading events to the CCT backend. */ +@interface GDTCCTUploader : NSObject + +/** The queue on which all CCT uploading will occur. */ +@property(nonatomic, readonly) dispatch_queue_t uploaderQueue; + +/** The server URL to upload to. Look at .m for the default value. */ +@property(nonatomic) NSURL *serverURL; + +/** The URL session that will attempt upload. */ +@property(nonatomic, readonly) NSURLSession *uploaderSession; + +/** The current upload task. */ +@property(nullable, nonatomic, readonly) NSURLSessionUploadTask *currentTask; + +/** Current upload package. */ +@property(nullable, nonatomic) GDTCORUploadPackage *currentUploadPackage; + +/** The next upload time. */ +@property(nullable, nonatomic) GDTCORClock *nextUploadTime; + +/** Creates and/or returns the singleton instance of this class. + * + * @return The singleton instance of this class. + */ ++ (instancetype)sharedInstance; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/Private/GDTFLLPrioritizer.h b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/Private/GDTFLLPrioritizer.h new file mode 100644 index 0000000..b7622f2 --- /dev/null +++ b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/Private/GDTFLLPrioritizer.h @@ -0,0 +1,44 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Manages the prioritization of events from GoogleDataTransport. */ +@interface GDTFLLPrioritizer : NSObject + +/** The queue on which this prioritizer operates. */ +@property(nonatomic) dispatch_queue_t queue; + +/** All log events that have been processed by this prioritizer. */ +@property(nonatomic) NSMutableSet *events; + +/** The most recent attempted upload of daily uploaded logs. */ +@property(nonatomic) GDTCORClock *timeOfLastDailyUpload; + +/** Creates and/or returns the singleton instance of this class. + * + * @return The singleton instance of this class. + */ ++ (instancetype)sharedInstance; + +NS_ASSUME_NONNULL_END + +@end diff --git a/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/Private/GDTFLLUploader.h b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/Private/GDTFLLUploader.h new file mode 100644 index 0000000..7df08ab --- /dev/null +++ b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/Private/GDTFLLUploader.h @@ -0,0 +1,57 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +#if !NDEBUG +/** A notification fired when uploading is complete, detailing the number of events uploaded. */ +extern NSNotificationName const GDTFLLUploadCompleteNotification; +#endif // #if !NDEBUG + +/** Class capable of uploading events to the CCT backend. */ +@interface GDTFLLUploader : NSObject + +/** The queue on which all CCT uploading will occur. */ +@property(nonatomic, readonly) dispatch_queue_t uploaderQueue; + +/** The server URL to upload to. Look at .m for the default value. */ +@property(nonatomic) NSURL *serverURL; + +/** The URL session that will attempt upload. */ +@property(nonatomic, readonly) NSURLSession *uploaderSession; + +/** The current upload task. */ +@property(nullable, nonatomic, readonly) NSURLSessionUploadTask *currentTask; + +/** Current upload package. */ +@property(nullable, nonatomic) GDTCORUploadPackage *currentUploadPackage; + +/** The next upload time. */ +@property(nullable, nonatomic) GDTCORClock *nextUploadTime; + +/** Creates and/or returns the singleton instance of this class. + * + * @return The singleton instance of this class. + */ ++ (instancetype)sharedInstance; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.c b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.c new file mode 100644 index 0000000..95846e6 --- /dev/null +++ b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.c @@ -0,0 +1,128 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.3.9.3 */ + +#include "cct.nanopb.h" + +/* @@protoc_insertion_point(includes) */ +#if PB_PROTO_HEADER_VERSION != 30 +#error Regenerate this file with the current version of nanopb generator. +#endif + +const gdt_cct_NetworkConnectionInfo_NetworkType gdt_cct_NetworkConnectionInfo_network_type_default = gdt_cct_NetworkConnectionInfo_NetworkType_NONE; +const gdt_cct_NetworkConnectionInfo_MobileSubtype gdt_cct_NetworkConnectionInfo_mobile_subtype_default = gdt_cct_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE; +const gdt_cct_QosTierConfiguration_QosTier gdt_cct_LogRequest_qos_tier_default = gdt_cct_QosTierConfiguration_QosTier_DEFAULT; +const int32_t gdt_cct_QosTierConfiguration_log_source_default = 0; + + +const pb_field_t gdt_cct_LogEvent_fields[7] = { + PB_FIELD( 1, INT64 , OPTIONAL, STATIC , FIRST, gdt_cct_LogEvent, event_time_ms, event_time_ms, 0), + PB_FIELD( 6, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_LogEvent, source_extension, event_time_ms, 0), + PB_FIELD( 11, INT32 , OPTIONAL, STATIC , OTHER, gdt_cct_LogEvent, event_code, source_extension, 0), + PB_FIELD( 15, SINT64 , OPTIONAL, STATIC , OTHER, gdt_cct_LogEvent, timezone_offset_seconds, event_code, 0), + PB_FIELD( 17, INT64 , OPTIONAL, STATIC , OTHER, gdt_cct_LogEvent, event_uptime_ms, timezone_offset_seconds, 0), + PB_FIELD( 23, MESSAGE , OPTIONAL, STATIC , OTHER, gdt_cct_LogEvent, network_connection_info, event_uptime_ms, &gdt_cct_NetworkConnectionInfo_fields), + PB_LAST_FIELD +}; + +const pb_field_t gdt_cct_NetworkConnectionInfo_fields[3] = { + PB_FIELD( 1, ENUM , OPTIONAL, STATIC , FIRST, gdt_cct_NetworkConnectionInfo, network_type, network_type, &gdt_cct_NetworkConnectionInfo_network_type_default), + PB_FIELD( 2, UENUM , OPTIONAL, STATIC , OTHER, gdt_cct_NetworkConnectionInfo, mobile_subtype, network_type, &gdt_cct_NetworkConnectionInfo_mobile_subtype_default), + PB_LAST_FIELD +}; + +const pb_field_t gdt_cct_IosClientInfo_fields[8] = { + PB_FIELD( 3, BYTES , OPTIONAL, POINTER , FIRST, gdt_cct_IosClientInfo, os_major_version, os_major_version, 0), + PB_FIELD( 4, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_IosClientInfo, os_full_version, os_major_version, 0), + PB_FIELD( 5, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_IosClientInfo, application_build, os_full_version, 0), + PB_FIELD( 6, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_IosClientInfo, country, application_build, 0), + PB_FIELD( 7, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_IosClientInfo, model, country, 0), + PB_FIELD( 8, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_IosClientInfo, language_code, model, 0), + PB_FIELD( 11, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_IosClientInfo, application_bundle_id, language_code, 0), + PB_LAST_FIELD +}; + +const pb_field_t gdt_cct_ClientInfo_fields[3] = { + PB_FIELD( 1, UENUM , OPTIONAL, STATIC , FIRST, gdt_cct_ClientInfo, client_type, client_type, 0), + PB_FIELD( 4, MESSAGE , OPTIONAL, STATIC , OTHER, gdt_cct_ClientInfo, ios_client_info, client_type, &gdt_cct_IosClientInfo_fields), + PB_LAST_FIELD +}; + +const pb_field_t gdt_cct_BatchedLogRequest_fields[2] = { + PB_FIELD( 1, MESSAGE , REPEATED, POINTER , FIRST, gdt_cct_BatchedLogRequest, log_request, log_request, &gdt_cct_LogRequest_fields), + PB_LAST_FIELD +}; + +const pb_field_t gdt_cct_LogRequest_fields[7] = { + PB_FIELD( 1, MESSAGE , OPTIONAL, STATIC , FIRST, gdt_cct_LogRequest, client_info, client_info, &gdt_cct_ClientInfo_fields), + PB_FIELD( 2, INT32 , OPTIONAL, STATIC , OTHER, gdt_cct_LogRequest, log_source, client_info, 0), + PB_FIELD( 3, MESSAGE , REPEATED, POINTER , OTHER, gdt_cct_LogRequest, log_event, log_source, &gdt_cct_LogEvent_fields), + PB_FIELD( 4, INT64 , OPTIONAL, STATIC , OTHER, gdt_cct_LogRequest, request_time_ms, log_event, 0), + PB_FIELD( 8, INT64 , OPTIONAL, STATIC , OTHER, gdt_cct_LogRequest, request_uptime_ms, request_time_ms, 0), + PB_FIELD( 9, UENUM , OPTIONAL, STATIC , OTHER, gdt_cct_LogRequest, qos_tier, request_uptime_ms, &gdt_cct_LogRequest_qos_tier_default), + PB_LAST_FIELD +}; + +const pb_field_t gdt_cct_QosTierConfiguration_fields[3] = { + PB_FIELD( 2, UENUM , OPTIONAL, STATIC , FIRST, gdt_cct_QosTierConfiguration, qos_tier, qos_tier, 0), + PB_FIELD( 3, INT32 , OPTIONAL, STATIC , OTHER, gdt_cct_QosTierConfiguration, log_source, qos_tier, &gdt_cct_QosTierConfiguration_log_source_default), + PB_LAST_FIELD +}; + +const pb_field_t gdt_cct_QosTiersOverride_fields[3] = { + PB_FIELD( 1, MESSAGE , REPEATED, POINTER , FIRST, gdt_cct_QosTiersOverride, qos_tier_configuration, qos_tier_configuration, &gdt_cct_QosTierConfiguration_fields), + PB_FIELD( 2, INT64 , OPTIONAL, STATIC , OTHER, gdt_cct_QosTiersOverride, qos_tier_fingerprint, qos_tier_configuration, 0), + PB_LAST_FIELD +}; + +const pb_field_t gdt_cct_LogResponse_fields[3] = { + PB_FIELD( 1, INT64 , OPTIONAL, STATIC , FIRST, gdt_cct_LogResponse, next_request_wait_millis, next_request_wait_millis, 0), + PB_FIELD( 3, MESSAGE , OPTIONAL, STATIC , OTHER, gdt_cct_LogResponse, qos_tier, next_request_wait_millis, &gdt_cct_QosTiersOverride_fields), + PB_LAST_FIELD +}; + + + + + + +/* Check that field information fits in pb_field_t */ +#if !defined(PB_FIELD_32BIT) +/* If you get an error here, it means that you need to define PB_FIELD_32BIT + * compile-time option. You can do that in pb.h or on compiler command line. + * + * The reason you need to do this is that some of your messages contain tag + * numbers or field sizes that are larger than what can fit in 8 or 16 bit + * field descriptors. + */ +PB_STATIC_ASSERT((pb_membersize(gdt_cct_LogEvent, network_connection_info) < 65536 && pb_membersize(gdt_cct_ClientInfo, ios_client_info) < 65536 && pb_membersize(gdt_cct_LogRequest, client_info) < 65536 && pb_membersize(gdt_cct_LogResponse, qos_tier) < 65536), YOU_MUST_DEFINE_PB_FIELD_32BIT_FOR_MESSAGES_gdt_cct_LogEvent_gdt_cct_NetworkConnectionInfo_gdt_cct_IosClientInfo_gdt_cct_ClientInfo_gdt_cct_BatchedLogRequest_gdt_cct_LogRequest_gdt_cct_QosTierConfiguration_gdt_cct_QosTiersOverride_gdt_cct_LogResponse) +#endif + +#if !defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT) +/* If you get an error here, it means that you need to define PB_FIELD_16BIT + * compile-time option. You can do that in pb.h or on compiler command line. + * + * The reason you need to do this is that some of your messages contain tag + * numbers or field sizes that are larger than what can fit in the default + * 8 bit descriptors. + */ +PB_STATIC_ASSERT((pb_membersize(gdt_cct_LogEvent, network_connection_info) < 256 && pb_membersize(gdt_cct_ClientInfo, ios_client_info) < 256 && pb_membersize(gdt_cct_LogRequest, client_info) < 256 && pb_membersize(gdt_cct_LogResponse, qos_tier) < 256), YOU_MUST_DEFINE_PB_FIELD_16BIT_FOR_MESSAGES_gdt_cct_LogEvent_gdt_cct_NetworkConnectionInfo_gdt_cct_IosClientInfo_gdt_cct_ClientInfo_gdt_cct_BatchedLogRequest_gdt_cct_LogRequest_gdt_cct_QosTierConfiguration_gdt_cct_QosTiersOverride_gdt_cct_LogResponse) +#endif + + +/* @@protoc_insertion_point(eof) */ diff --git a/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h new file mode 100644 index 0000000..a6d4cfb --- /dev/null +++ b/!main project/Pods/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h @@ -0,0 +1,281 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.3.9.3 */ + +#ifndef PB_GDT_CCT_CCT_NANOPB_H_INCLUDED +#define PB_GDT_CCT_CCT_NANOPB_H_INCLUDED +#include + +/* @@protoc_insertion_point(includes) */ +#if PB_PROTO_HEADER_VERSION != 30 +#error Regenerate this file with the current version of nanopb generator. +#endif + + +/* Enum definitions */ +typedef enum _gdt_cct_NetworkConnectionInfo_NetworkType { + gdt_cct_NetworkConnectionInfo_NetworkType_NONE = -1, + gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE = 0, + gdt_cct_NetworkConnectionInfo_NetworkType_WIFI = 1, + gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE_MMS = 2, + gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE_SUPL = 3, + gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE_DUN = 4, + gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE_HIPRI = 5, + gdt_cct_NetworkConnectionInfo_NetworkType_WIMAX = 6, + gdt_cct_NetworkConnectionInfo_NetworkType_BLUETOOTH = 7, + gdt_cct_NetworkConnectionInfo_NetworkType_DUMMY = 8, + gdt_cct_NetworkConnectionInfo_NetworkType_ETHERNET = 9, + gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE_FOTA = 10, + gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE_IMS = 11, + gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE_CBS = 12, + gdt_cct_NetworkConnectionInfo_NetworkType_WIFI_P2P = 13, + gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE_IA = 14, + gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE_EMERGENCY = 15, + gdt_cct_NetworkConnectionInfo_NetworkType_PROXY = 16, + gdt_cct_NetworkConnectionInfo_NetworkType_VPN = 17 +} gdt_cct_NetworkConnectionInfo_NetworkType; +#define _gdt_cct_NetworkConnectionInfo_NetworkType_MIN gdt_cct_NetworkConnectionInfo_NetworkType_NONE +#define _gdt_cct_NetworkConnectionInfo_NetworkType_MAX gdt_cct_NetworkConnectionInfo_NetworkType_VPN +#define _gdt_cct_NetworkConnectionInfo_NetworkType_ARRAYSIZE ((gdt_cct_NetworkConnectionInfo_NetworkType)(gdt_cct_NetworkConnectionInfo_NetworkType_VPN+1)) + +typedef enum _gdt_cct_NetworkConnectionInfo_MobileSubtype { + gdt_cct_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE = 0, + gdt_cct_NetworkConnectionInfo_MobileSubtype_GPRS = 1, + gdt_cct_NetworkConnectionInfo_MobileSubtype_EDGE = 2, + gdt_cct_NetworkConnectionInfo_MobileSubtype_UMTS = 3, + gdt_cct_NetworkConnectionInfo_MobileSubtype_CDMA = 4, + gdt_cct_NetworkConnectionInfo_MobileSubtype_EVDO_0 = 5, + gdt_cct_NetworkConnectionInfo_MobileSubtype_EVDO_A = 6, + gdt_cct_NetworkConnectionInfo_MobileSubtype_RTT = 7, + gdt_cct_NetworkConnectionInfo_MobileSubtype_HSDPA = 8, + gdt_cct_NetworkConnectionInfo_MobileSubtype_HSUPA = 9, + gdt_cct_NetworkConnectionInfo_MobileSubtype_HSPA = 10, + gdt_cct_NetworkConnectionInfo_MobileSubtype_IDEN = 11, + gdt_cct_NetworkConnectionInfo_MobileSubtype_EVDO_B = 12, + gdt_cct_NetworkConnectionInfo_MobileSubtype_LTE = 13, + gdt_cct_NetworkConnectionInfo_MobileSubtype_EHRPD = 14, + gdt_cct_NetworkConnectionInfo_MobileSubtype_HSPAP = 15, + gdt_cct_NetworkConnectionInfo_MobileSubtype_GSM = 16, + gdt_cct_NetworkConnectionInfo_MobileSubtype_TD_SCDMA = 17, + gdt_cct_NetworkConnectionInfo_MobileSubtype_IWLAN = 18, + gdt_cct_NetworkConnectionInfo_MobileSubtype_LTE_CA = 19, + gdt_cct_NetworkConnectionInfo_MobileSubtype_COMBINED = 100 +} gdt_cct_NetworkConnectionInfo_MobileSubtype; +#define _gdt_cct_NetworkConnectionInfo_MobileSubtype_MIN gdt_cct_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE +#define _gdt_cct_NetworkConnectionInfo_MobileSubtype_MAX gdt_cct_NetworkConnectionInfo_MobileSubtype_COMBINED +#define _gdt_cct_NetworkConnectionInfo_MobileSubtype_ARRAYSIZE ((gdt_cct_NetworkConnectionInfo_MobileSubtype)(gdt_cct_NetworkConnectionInfo_MobileSubtype_COMBINED+1)) + +typedef enum _gdt_cct_ClientInfo_ClientType { + gdt_cct_ClientInfo_ClientType_CLIENT_UNKNOWN = 0, + gdt_cct_ClientInfo_ClientType_IOS_FIREBASE = 15 +} gdt_cct_ClientInfo_ClientType; +#define _gdt_cct_ClientInfo_ClientType_MIN gdt_cct_ClientInfo_ClientType_CLIENT_UNKNOWN +#define _gdt_cct_ClientInfo_ClientType_MAX gdt_cct_ClientInfo_ClientType_IOS_FIREBASE +#define _gdt_cct_ClientInfo_ClientType_ARRAYSIZE ((gdt_cct_ClientInfo_ClientType)(gdt_cct_ClientInfo_ClientType_IOS_FIREBASE+1)) + +typedef enum _gdt_cct_QosTierConfiguration_QosTier { + gdt_cct_QosTierConfiguration_QosTier_DEFAULT = 0, + gdt_cct_QosTierConfiguration_QosTier_UNMETERED_ONLY = 1, + gdt_cct_QosTierConfiguration_QosTier_UNMETERED_OR_DAILY = 2, + gdt_cct_QosTierConfiguration_QosTier_FAST_IF_RADIO_AWAKE = 3, + gdt_cct_QosTierConfiguration_QosTier_NEVER = 4 +} gdt_cct_QosTierConfiguration_QosTier; +#define _gdt_cct_QosTierConfiguration_QosTier_MIN gdt_cct_QosTierConfiguration_QosTier_DEFAULT +#define _gdt_cct_QosTierConfiguration_QosTier_MAX gdt_cct_QosTierConfiguration_QosTier_NEVER +#define _gdt_cct_QosTierConfiguration_QosTier_ARRAYSIZE ((gdt_cct_QosTierConfiguration_QosTier)(gdt_cct_QosTierConfiguration_QosTier_NEVER+1)) + +/* Struct definitions */ +typedef struct _gdt_cct_BatchedLogRequest { + pb_size_t log_request_count; + struct _gdt_cct_LogRequest *log_request; +/* @@protoc_insertion_point(struct:gdt_cct_BatchedLogRequest) */ +} gdt_cct_BatchedLogRequest; + +typedef struct _gdt_cct_IosClientInfo { + pb_bytes_array_t *os_major_version; + pb_bytes_array_t *os_full_version; + pb_bytes_array_t *application_build; + pb_bytes_array_t *country; + pb_bytes_array_t *model; + pb_bytes_array_t *language_code; + pb_bytes_array_t *application_bundle_id; +/* @@protoc_insertion_point(struct:gdt_cct_IosClientInfo) */ +} gdt_cct_IosClientInfo; + +typedef struct _gdt_cct_ClientInfo { + bool has_client_type; + gdt_cct_ClientInfo_ClientType client_type; + bool has_ios_client_info; + gdt_cct_IosClientInfo ios_client_info; +/* @@protoc_insertion_point(struct:gdt_cct_ClientInfo) */ +} gdt_cct_ClientInfo; + +typedef struct _gdt_cct_NetworkConnectionInfo { + bool has_network_type; + gdt_cct_NetworkConnectionInfo_NetworkType network_type; + bool has_mobile_subtype; + gdt_cct_NetworkConnectionInfo_MobileSubtype mobile_subtype; +/* @@protoc_insertion_point(struct:gdt_cct_NetworkConnectionInfo) */ +} gdt_cct_NetworkConnectionInfo; + +typedef struct _gdt_cct_QosTierConfiguration { + bool has_qos_tier; + gdt_cct_QosTierConfiguration_QosTier qos_tier; + bool has_log_source; + int32_t log_source; +/* @@protoc_insertion_point(struct:gdt_cct_QosTierConfiguration) */ +} gdt_cct_QosTierConfiguration; + +typedef struct _gdt_cct_QosTiersOverride { + pb_size_t qos_tier_configuration_count; + struct _gdt_cct_QosTierConfiguration *qos_tier_configuration; + bool has_qos_tier_fingerprint; + int64_t qos_tier_fingerprint; +/* @@protoc_insertion_point(struct:gdt_cct_QosTiersOverride) */ +} gdt_cct_QosTiersOverride; + +typedef struct _gdt_cct_LogEvent { + bool has_event_time_ms; + int64_t event_time_ms; + pb_bytes_array_t *source_extension; + bool has_event_code; + int32_t event_code; + bool has_timezone_offset_seconds; + int64_t timezone_offset_seconds; + bool has_event_uptime_ms; + int64_t event_uptime_ms; + bool has_network_connection_info; + gdt_cct_NetworkConnectionInfo network_connection_info; +/* @@protoc_insertion_point(struct:gdt_cct_LogEvent) */ +} gdt_cct_LogEvent; + +typedef struct _gdt_cct_LogRequest { + bool has_client_info; + gdt_cct_ClientInfo client_info; + bool has_log_source; + int32_t log_source; + pb_size_t log_event_count; + struct _gdt_cct_LogEvent *log_event; + bool has_request_time_ms; + int64_t request_time_ms; + bool has_request_uptime_ms; + int64_t request_uptime_ms; + bool has_qos_tier; + gdt_cct_QosTierConfiguration_QosTier qos_tier; +/* @@protoc_insertion_point(struct:gdt_cct_LogRequest) */ +} gdt_cct_LogRequest; + +typedef struct _gdt_cct_LogResponse { + bool has_next_request_wait_millis; + int64_t next_request_wait_millis; + bool has_qos_tier; + gdt_cct_QosTiersOverride qos_tier; +/* @@protoc_insertion_point(struct:gdt_cct_LogResponse) */ +} gdt_cct_LogResponse; + +/* Default values for struct fields */ +extern const gdt_cct_NetworkConnectionInfo_NetworkType gdt_cct_NetworkConnectionInfo_network_type_default; +extern const gdt_cct_NetworkConnectionInfo_MobileSubtype gdt_cct_NetworkConnectionInfo_mobile_subtype_default; +extern const gdt_cct_QosTierConfiguration_QosTier gdt_cct_LogRequest_qos_tier_default; +extern const int32_t gdt_cct_QosTierConfiguration_log_source_default; + +/* Initializer values for message structs */ +#define gdt_cct_LogEvent_init_default {false, 0, NULL, false, 0, false, 0, false, 0, false, gdt_cct_NetworkConnectionInfo_init_default} +#define gdt_cct_NetworkConnectionInfo_init_default {false, gdt_cct_NetworkConnectionInfo_NetworkType_NONE, false, gdt_cct_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE} +#define gdt_cct_IosClientInfo_init_default {NULL, NULL, NULL, NULL, NULL, NULL, NULL} +#define gdt_cct_ClientInfo_init_default {false, _gdt_cct_ClientInfo_ClientType_MIN, false, gdt_cct_IosClientInfo_init_default} +#define gdt_cct_BatchedLogRequest_init_default {0, NULL} +#define gdt_cct_LogRequest_init_default {false, gdt_cct_ClientInfo_init_default, false, 0, 0, NULL, false, 0, false, 0, false, gdt_cct_QosTierConfiguration_QosTier_DEFAULT} +#define gdt_cct_QosTierConfiguration_init_default {false, _gdt_cct_QosTierConfiguration_QosTier_MIN, false, 0} +#define gdt_cct_QosTiersOverride_init_default {0, NULL, false, 0} +#define gdt_cct_LogResponse_init_default {false, 0, false, gdt_cct_QosTiersOverride_init_default} +#define gdt_cct_LogEvent_init_zero {false, 0, NULL, false, 0, false, 0, false, 0, false, gdt_cct_NetworkConnectionInfo_init_zero} +#define gdt_cct_NetworkConnectionInfo_init_zero {false, _gdt_cct_NetworkConnectionInfo_NetworkType_MIN, false, _gdt_cct_NetworkConnectionInfo_MobileSubtype_MIN} +#define gdt_cct_IosClientInfo_init_zero {NULL, NULL, NULL, NULL, NULL, NULL, NULL} +#define gdt_cct_ClientInfo_init_zero {false, _gdt_cct_ClientInfo_ClientType_MIN, false, gdt_cct_IosClientInfo_init_zero} +#define gdt_cct_BatchedLogRequest_init_zero {0, NULL} +#define gdt_cct_LogRequest_init_zero {false, gdt_cct_ClientInfo_init_zero, false, 0, 0, NULL, false, 0, false, 0, false, _gdt_cct_QosTierConfiguration_QosTier_MIN} +#define gdt_cct_QosTierConfiguration_init_zero {false, _gdt_cct_QosTierConfiguration_QosTier_MIN, false, 0} +#define gdt_cct_QosTiersOverride_init_zero {0, NULL, false, 0} +#define gdt_cct_LogResponse_init_zero {false, 0, false, gdt_cct_QosTiersOverride_init_zero} + +/* Field tags (for use in manual encoding/decoding) */ +#define gdt_cct_BatchedLogRequest_log_request_tag 1 +#define gdt_cct_IosClientInfo_os_major_version_tag 3 +#define gdt_cct_IosClientInfo_os_full_version_tag 4 +#define gdt_cct_IosClientInfo_application_build_tag 5 +#define gdt_cct_IosClientInfo_country_tag 6 +#define gdt_cct_IosClientInfo_model_tag 7 +#define gdt_cct_IosClientInfo_language_code_tag 8 +#define gdt_cct_IosClientInfo_application_bundle_id_tag 11 +#define gdt_cct_ClientInfo_client_type_tag 1 +#define gdt_cct_ClientInfo_ios_client_info_tag 4 +#define gdt_cct_NetworkConnectionInfo_network_type_tag 1 +#define gdt_cct_NetworkConnectionInfo_mobile_subtype_tag 2 +#define gdt_cct_QosTierConfiguration_qos_tier_tag 2 +#define gdt_cct_QosTierConfiguration_log_source_tag 3 +#define gdt_cct_QosTiersOverride_qos_tier_configuration_tag 1 +#define gdt_cct_QosTiersOverride_qos_tier_fingerprint_tag 2 +#define gdt_cct_LogEvent_event_time_ms_tag 1 +#define gdt_cct_LogEvent_event_code_tag 11 +#define gdt_cct_LogEvent_event_uptime_ms_tag 17 +#define gdt_cct_LogEvent_source_extension_tag 6 +#define gdt_cct_LogEvent_timezone_offset_seconds_tag 15 +#define gdt_cct_LogEvent_network_connection_info_tag 23 +#define gdt_cct_LogRequest_request_time_ms_tag 4 +#define gdt_cct_LogRequest_request_uptime_ms_tag 8 +#define gdt_cct_LogRequest_client_info_tag 1 +#define gdt_cct_LogRequest_log_source_tag 2 +#define gdt_cct_LogRequest_log_event_tag 3 +#define gdt_cct_LogRequest_qos_tier_tag 9 +#define gdt_cct_LogResponse_next_request_wait_millis_tag 1 +#define gdt_cct_LogResponse_qos_tier_tag 3 + +/* Struct field encoding specification for nanopb */ +extern const pb_field_t gdt_cct_LogEvent_fields[7]; +extern const pb_field_t gdt_cct_NetworkConnectionInfo_fields[3]; +extern const pb_field_t gdt_cct_IosClientInfo_fields[8]; +extern const pb_field_t gdt_cct_ClientInfo_fields[3]; +extern const pb_field_t gdt_cct_BatchedLogRequest_fields[2]; +extern const pb_field_t gdt_cct_LogRequest_fields[7]; +extern const pb_field_t gdt_cct_QosTierConfiguration_fields[3]; +extern const pb_field_t gdt_cct_QosTiersOverride_fields[3]; +extern const pb_field_t gdt_cct_LogResponse_fields[3]; + +/* Maximum encoded size of messages (where known) */ +/* gdt_cct_LogEvent_size depends on runtime parameters */ +#define gdt_cct_NetworkConnectionInfo_size 13 +/* gdt_cct_IosClientInfo_size depends on runtime parameters */ +/* gdt_cct_ClientInfo_size depends on runtime parameters */ +/* gdt_cct_BatchedLogRequest_size depends on runtime parameters */ +/* gdt_cct_LogRequest_size depends on runtime parameters */ +#define gdt_cct_QosTierConfiguration_size 13 +/* gdt_cct_QosTiersOverride_size depends on runtime parameters */ +/* gdt_cct_LogResponse_size depends on runtime parameters */ + +/* Message IDs (where set with "msgid" option) */ +#ifdef PB_MSGID + +#define CCT_MESSAGES \ + + +#endif + +/* @@protoc_insertion_point(eof) */ + +#endif diff --git a/!main project/Pods/GoogleDataTransportCCTSupport/LICENSE b/!main project/Pods/GoogleDataTransportCCTSupport/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/!main project/Pods/GoogleDataTransportCCTSupport/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/!main project/Pods/GoogleDataTransportCCTSupport/README.md b/!main project/Pods/GoogleDataTransportCCTSupport/README.md new file mode 100644 index 0000000..5097a89 --- /dev/null +++ b/!main project/Pods/GoogleDataTransportCCTSupport/README.md @@ -0,0 +1,254 @@ +# Firebase iOS Open Source Development [![Build Status](https://travis-ci.org/firebase/firebase-ios-sdk.svg?branch=master)](https://travis-ci.org/firebase/firebase-ios-sdk) + +This repository contains a subset of the Firebase iOS SDK source. It currently +includes FirebaseCore, FirebaseABTesting, FirebaseAuth, FirebaseDatabase, +FirebaseFirestore, FirebaseFunctions, FirebaseInstanceID, FirebaseInAppMessaging, +FirebaseInAppMessagingDisplay, FirebaseMessaging, FirebaseRemoteConfig, and +FirebaseStorage. + +The repository also includes GoogleUtilities source. The +[GoogleUtilities](GoogleUtilities/README.md) pod is +a set of utilities used by Firebase and other Google products. + +Firebase is an app development platform with tools to help you build, grow and +monetize your app. More information about Firebase can be found at +[https://firebase.google.com](https://firebase.google.com). + +## Installation + +See the three subsections for details about three different installation methods. +1. [Standard pod install](README.md#standard-pod-install) +1. [Installing from the GitHub repo](README.md#installing-from-github) +1. [Experimental Carthage](README.md#carthage-ios-only) + +### Standard pod install + +Go to +[https://firebase.google.com/docs/ios/setup](https://firebase.google.com/docs/ios/setup). + +### Installing from GitHub + +For releases starting with 5.0.0, the source for each release is also deployed +to CocoaPods master and available via standard +[CocoaPods Podfile syntax](https://guides.cocoapods.org/syntax/podfile.html#pod). + +These instructions can be used to access the Firebase repo at other branches, +tags, or commits. + +#### Background + +See +[the Podfile Syntax Reference](https://guides.cocoapods.org/syntax/podfile.html#pod) +for instructions and options about overriding pod source locations. + +#### Accessing Firebase Source Snapshots + +All of the official releases are tagged in this repo and available via CocoaPods. To access a local +source snapshot or unreleased branch, use Podfile directives like the following: + +To access FirebaseFirestore via a branch: +``` +pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +``` + +To access FirebaseMessaging via a checked out version of the firebase-ios-sdk repo do: + +``` +pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk' +pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk' +``` + +### Carthage (iOS only) + +Instructions for the experimental Carthage distribution are at +[Carthage](Carthage.md). + +### Rome + +Instructions for installing binary frameworks via +[Rome](https://github.com/CocoaPods/Rome) are at [Rome](Rome.md). + +## Development + +To develop Firebase software in this repository, ensure that you have at least +the following software: + + * Xcode 10.1 (or later) + * CocoaPods 1.7.2 (or later) + * [CocoaPods generate](https://github.com/square/cocoapods-generate) + +For the pod that you want to develop: + +`pod gen Firebase{name here}.podspec --local-sources=./ --auto-open --platforms=ios` + +Note: If the CocoaPods cache is out of date, you may need to run +`pod repo update` before the `pod gen` command. + +Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for +those platforms. Since 10.2, Xcode does not properly handle multi-platform +CocoaPods workspaces. + +Firestore has a self contained Xcode project. See +[Firestore/README.md](Firestore/README.md). + +### Development for Catalyst +* `pod gen {name here}.podspec --local-sources=./ --auto-open --platforms=ios` +* Check the Mac box in the App-iOS Build Settings +* Sign the App in the Settings Signing & Capabilities tab +* Click Pods in the Project Manager +* Add Signing to the iOS host app and unit test targets +* Select the Unit-unit scheme +* Run it to build and test + +### Adding a New Firebase Pod + +See [AddNewPod.md](AddNewPod.md). + +### Code Formatting + +To ensure that the code is formatted consistently, run the script +[./scripts/style.sh](https://github.com/firebase/firebase-ios-sdk/blob/master/scripts/style.sh) +before creating a PR. + +Travis will verify that any code changes are done in a style compliant way. Install +`clang-format` and `swiftformat`. +These commands will get the right versions: + +``` +brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/e3496d9/Formula/clang-format.rb +brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/7963c3d/Formula/swiftformat.rb +``` + +Note: if you already have a newer version of these installed you may need to +`brew switch` to this version. + +To update this section, find the versions of clang-format and swiftformat.rb to +match the versions in the CI failure logs +[here](https://github.com/Homebrew/homebrew-core/tree/master/Formula). + +### Running Unit Tests + +Select a scheme and press Command-u to build a component and run its unit tests. + +#### Viewing Code Coverage + +First, make sure that [xcov](https://github.com/nakiostudio/xcov) is installed with `gem install xcov`. + +After running the `AllUnitTests_iOS` scheme in Xcode, execute +`xcov --workspace Firebase.xcworkspace --scheme AllUnitTests_iOS --output_directory xcov_output` +at Example/ in the terminal. This will aggregate the coverage, and you can run `open xcov_output/index.html` to see the results. + +### Running Sample Apps +In order to run the sample apps and integration tests, you'll need valid +`GoogleService-Info.plist` files for those samples. The Firebase Xcode project contains dummy plist +files without real values, but can be replaced with real plist files. To get your own +`GoogleService-Info.plist` files: + +1. Go to the [Firebase Console](https://console.firebase.google.com/) +2. Create a new Firebase project, if you don't already have one +3. For each sample app you want to test, create a new Firebase app with the sample app's bundle +identifier (e.g. `com.google.Database-Example`) +4. Download the resulting `GoogleService-Info.plist` and replace the appropriate dummy plist file +(e.g. in [Example/Database/App/](Example/Database/App/)); + +Some sample apps like Firebase Messaging ([Example/Messaging/App](Example/Messaging/App)) require +special Apple capabilities, and you will have to change the sample app to use a unique bundle +identifier that you can control in your own Apple Developer account. + +## Specific Component Instructions +See the sections below for any special instructions for those components. + +### Firebase Auth + +If you're doing specific Firebase Auth development, see +[the Auth Sample README](Example/Auth/README.md) for instructions about +building and running the FirebaseAuth pod along with various samples and tests. + +### Firebase Database + +To run the Database Integration tests, make your database authentication rules +[public](https://firebase.google.com/docs/database/security/quickstart). + +### Firebase Storage + +To run the Storage Integration tests, follow the instructions in +[FIRStorageIntegrationTests.m](Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m). + +#### Push Notifications + +Push notifications can only be delivered to specially provisioned App IDs in the developer portal. +In order to actually test receiving push notifications, you will need to: + +1. Change the bundle identifier of the sample app to something you own in your Apple Developer +account, and enable that App ID for push notifications. +2. You'll also need to +[upload your APNs Provider Authentication Key or certificate to the Firebase Console](https://firebase.google.com/docs/cloud-messaging/ios/certs) +at **Project Settings > Cloud Messaging > [Your Firebase App]**. +3. Ensure your iOS device is added to your Apple Developer portal as a test device. + +#### iOS Simulator + +The iOS Simulator cannot register for remote notifications, and will not receive push notifications. +In order to receive push notifications, you'll have to follow the steps above and run the app on a +physical device. + +## Community Supported Efforts + +We've seen an amazing amount of interest and contributions to improve the Firebase SDKs, and we are +very grateful! We'd like to empower as many developers as we can to be able to use Firebase and +participate in the Firebase community. + +### tvOS, macOS, and Catalyst +Thanks to contributions from the community, many of Firebase SDKs now compile, run unit tests, and work on +tvOS, macOS, and Catalyst. + +For tvOS, checkout the [Sample](Example/tvOSSample). + +Keep in mind that macOS, Catalyst and tvOS are not officially supported by Firebase, and this +repository is actively developed primarily for iOS. While we can catch basic unit test issues with +Travis, there may be some changes where the SDK no longer works as expected on macOS or tvOS. If you +encounter this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues). + +During app setup in the console, you may get to a step that mentions something like "Checking if the app +has communicated with our servers". This relies on Analytics and will not work on macOS/tvOS/Catalyst. +**It's safe to ignore the message and continue**, the rest of the SDKs will work as expected. + +To install, add a subset of the following to the Podfile: + +``` +pod 'Firebase/ABTesting' +pod 'Firebase/Auth' +pod 'Firebase/Crashlytics' +pod 'Firebase/Database' +pod 'Firebase/Firestore' +pod 'Firebase/Functions' +pod 'Firebase/Messaging' +pod 'Firebase/RemoteConfig' +pod 'Firebase/Storage' +``` + +#### Additional Catalyst Notes + +* FirebaseAuth and FirebaseMessaging require adding `Keychain Sharing Capability` +to Build Settings. +* FirebaseFirestore requires signing the +[gRPC Resource target](https://github.com/firebase/firebase-ios-sdk/issues/3500#issuecomment-518741681). + +## Roadmap + +See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source +plans and directions. + +## Contributing + +See [Contributing](CONTRIBUTING.md) for more information on contributing to the Firebase +iOS SDK. + +## License + +The contents of this repository is licensed under the +[Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0). + +Your use of Firebase is governed by the +[Terms of Service for Firebase Services](https://firebase.google.com/terms/). diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m b/!main project/Pods/GoogleUtilities/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m new file mode 100644 index 0000000..173a776 --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m @@ -0,0 +1,1038 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +#import +#import +#import +#import +#import "GoogleUtilities/AppDelegateSwizzler/Internal/GULAppDelegateSwizzler_Private.h" +#import "GoogleUtilities/Common/GULLoggerCodes.h" + +#import + +// Implementations need to be typed before calling the implementation directly to cast the +// arguments and the return types correctly. Otherwise, it will crash the app. +typedef BOOL (*GULRealOpenURLSourceApplicationAnnotationIMP)( + id, SEL, GULApplication *, NSURL *, NSString *, id); + +typedef BOOL (*GULRealOpenURLOptionsIMP)( + id, SEL, GULApplication *, NSURL *, NSDictionary *); + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wstrict-prototypes" +typedef void (*GULRealHandleEventsForBackgroundURLSessionIMP)( + id, SEL, GULApplication *, NSString *, void (^)()); +#pragma clang diagnostic pop + +// This is needed to for the library to be warning free on iOS versions < 8. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability" +typedef BOOL (*GULRealContinueUserActivityIMP)( + id, SEL, GULApplication *, NSUserActivity *, void (^)(NSArray *restorableObjects)); +#pragma clang diagnostic pop + +typedef void (*GULRealDidRegisterForRemoteNotificationsIMP)(id, SEL, GULApplication *, NSData *); + +typedef void (*GULRealDidFailToRegisterForRemoteNotificationsIMP)(id, + SEL, + GULApplication *, + NSError *); + +typedef void (*GULRealDidReceiveRemoteNotificationIMP)(id, SEL, GULApplication *, NSDictionary *); + +// TODO: Since we don't support iOS 7 anymore, see if we can remove the check below. +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 && !TARGET_OS_WATCH +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability" +typedef void (*GULRealDidReceiveRemoteNotificationWithCompletionIMP)( + id, SEL, GULApplication *, NSDictionary *, void (^)(UIBackgroundFetchResult)); +#pragma clang diagnostic pop +#endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 && !TARGET_OS_WATCH + +typedef void (^GULAppDelegateInterceptorCallback)(id); + +// The strings below are the keys for associated objects. +static char const *const kGULRealIMPBySelectorKey = "GUL_realIMPBySelector"; +static char const *const kGULRealClassKey = "GUL_realClass"; + +static NSString *const kGULAppDelegateKeyPath = @"delegate"; + +static GULLoggerService kGULLoggerSwizzler = @"[GoogleUtilities/AppDelegateSwizzler]"; + +// Since Firebase SDKs also use this for app delegate proxying, in order to not be a breaking change +// we disable App Delegate proxying when either of these two flags are set to NO. + +/** Plist key that allows Firebase developers to disable App and Scene Delegate Proxying. */ +static NSString *const kGULFirebaseAppDelegateProxyEnabledPlistKey = + @"FirebaseAppDelegateProxyEnabled"; + +/** Plist key that allows developers not using Firebase to disable App and Scene Delegate Proxying. + */ +static NSString *const kGULGoogleUtilitiesAppDelegateProxyEnabledPlistKey = + @"GoogleUtilitiesAppDelegateProxyEnabled"; + +/** The prefix of the App Delegate. */ +static NSString *const kGULAppDelegatePrefix = @"GUL_"; + +/** The original instance of App Delegate. */ +static id gOriginalAppDelegate; + +/** The original App Delegate class */ +static Class gOriginalAppDelegateClass; + +/** The subclass of the original App Delegate. */ +static Class gAppDelegateSubclass; + +/** Remote notification methods selectors + * + * We have to opt out of referencing APNS related App Delegate methods directly to prevent + * an Apple review warning email about missing Push Notification Entitlement + * (like here: https://github.com/firebase/firebase-ios-sdk/issues/2807). From our experience, the + * warning is triggered when any of the symbols is present in the application sent to review, even + * if the code is never executed. Because GULAppDelegateSwizzler may be used by applications that + * are not using APNS we have to refer to the methods indirectly using selector constructed from + * string. + * + * NOTE: None of the methods is proxied unless it is explicitly requested by calling the method + * +[GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods] + */ +static NSString *const kGULDidRegisterForRemoteNotificationsSEL = + @"application:didRegisterForRemoteNotificationsWithDeviceToken:"; +static NSString *const kGULDidFailToRegisterForRemoteNotificationsSEL = + @"application:didFailToRegisterForRemoteNotificationsWithError:"; +static NSString *const kGULDidReceiveRemoteNotificationSEL = + @"application:didReceiveRemoteNotification:"; +static NSString *const kGULDidReceiveRemoteNotificationWithCompletionSEL = + @"application:didReceiveRemoteNotification:fetchCompletionHandler:"; + +/** + * This class is necessary to store the delegates in an NSArray without retaining them. + * [NSValue valueWithNonRetainedObject] also provides this functionality, but does not provide a + * zeroing pointer. This will cause EXC_BAD_ACCESS when trying to access the object after it is + * dealloced. Instead, this container stores a weak, zeroing reference to the object, which + * automatically is set to nil by the runtime when the object is dealloced. + */ +@interface GULZeroingWeakContainer : NSObject + +/** Stores a weak object. */ +@property(nonatomic, weak) id object; + +@end + +@implementation GULZeroingWeakContainer +@end + +@interface GULAppDelegateObserver : NSObject +@end + +@implementation GULAppDelegateObserver { + BOOL _isObserving; +} + ++ (GULAppDelegateObserver *)sharedInstance { + static GULAppDelegateObserver *instance; + static dispatch_once_t once; + dispatch_once(&once, ^{ + instance = [[GULAppDelegateObserver alloc] init]; + }); + return instance; +} + +- (void)observeUIApplication { + if (_isObserving) { + return; + } + [[GULAppDelegateSwizzler sharedApplication] + addObserver:self + forKeyPath:kGULAppDelegateKeyPath + options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld + context:nil]; + _isObserving = YES; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context { + if ([keyPath isEqual:kGULAppDelegateKeyPath]) { + id newValue = change[NSKeyValueChangeNewKey]; + id oldValue = change[NSKeyValueChangeOldKey]; + if ([newValue isEqual:oldValue]) { + return; + } + // Free the stored app delegate instance because it has been changed to a different instance to + // avoid keeping it alive forever. + if ([oldValue isEqual:gOriginalAppDelegate]) { + gOriginalAppDelegate = nil; + // Remove the observer. Parse it to NSObject to avoid warning. + [[GULAppDelegateSwizzler sharedApplication] removeObserver:self + forKeyPath:kGULAppDelegateKeyPath]; + _isObserving = NO; + } + } +} + +@end + +@implementation GULAppDelegateSwizzler + +static dispatch_once_t sProxyAppDelegateOnceToken; +static dispatch_once_t sProxyAppDelegateRemoteNotificationOnceToken; + +#pragma mark - Public methods + ++ (BOOL)isAppDelegateProxyEnabled { + NSDictionary *infoDictionary = [NSBundle mainBundle].infoDictionary; + + id isFirebaseProxyEnabledPlistValue = infoDictionary[kGULFirebaseAppDelegateProxyEnabledPlistKey]; + id isGoogleProxyEnabledPlistValue = + infoDictionary[kGULGoogleUtilitiesAppDelegateProxyEnabledPlistKey]; + + // Enabled by default. + BOOL isFirebaseAppDelegateProxyEnabled = YES; + BOOL isGoogleUtilitiesAppDelegateProxyEnabled = YES; + + if ([isFirebaseProxyEnabledPlistValue isKindOfClass:[NSNumber class]]) { + isFirebaseAppDelegateProxyEnabled = [isFirebaseProxyEnabledPlistValue boolValue]; + } + + if ([isGoogleProxyEnabledPlistValue isKindOfClass:[NSNumber class]]) { + isGoogleUtilitiesAppDelegateProxyEnabled = [isGoogleProxyEnabledPlistValue boolValue]; + } + + // Only deactivate the proxy if it is explicitly disabled by app developers using either one of + // the plist flags. + return isFirebaseAppDelegateProxyEnabled && isGoogleUtilitiesAppDelegateProxyEnabled; +} + ++ (GULAppDelegateInterceptorID)registerAppDelegateInterceptor: + (id)interceptor { + NSAssert(interceptor, @"AppDelegateProxy cannot add nil interceptor"); + NSAssert([interceptor conformsToProtocol:@protocol(GULApplicationDelegate)], + @"AppDelegateProxy interceptor does not conform to UIApplicationDelegate"); + + if (!interceptor) { + GULLogError(kGULLoggerSwizzler, NO, + [NSString stringWithFormat:@"I-SWZ%06ld", + (long)kGULSwizzlerMessageCodeAppDelegateSwizzling000], + @"AppDelegateProxy cannot add nil interceptor."); + return nil; + } + if (![interceptor conformsToProtocol:@protocol(GULApplicationDelegate)]) { + GULLogError(kGULLoggerSwizzler, NO, + [NSString stringWithFormat:@"I-SWZ%06ld", + (long)kGULSwizzlerMessageCodeAppDelegateSwizzling001], + @"AppDelegateProxy interceptor does not conform to UIApplicationDelegate"); + return nil; + } + + // The ID should be the same given the same interceptor object. + NSString *interceptorID = [NSString stringWithFormat:@"%@%p", kGULAppDelegatePrefix, interceptor]; + if (!interceptorID.length) { + GULLogError(kGULLoggerSwizzler, NO, + [NSString stringWithFormat:@"I-SWZ%06ld", + (long)kGULSwizzlerMessageCodeAppDelegateSwizzling002], + @"AppDelegateProxy cannot create Interceptor ID."); + return nil; + } + GULZeroingWeakContainer *weakObject = [[GULZeroingWeakContainer alloc] init]; + weakObject.object = interceptor; + [GULAppDelegateSwizzler interceptors][interceptorID] = weakObject; + return interceptorID; +} + ++ (void)unregisterAppDelegateInterceptorWithID:(GULAppDelegateInterceptorID)interceptorID { + NSAssert(interceptorID, @"AppDelegateProxy cannot unregister nil interceptor ID."); + NSAssert(((NSString *)interceptorID).length != 0, + @"AppDelegateProxy cannot unregister empty interceptor ID."); + + if (!interceptorID) { + GULLogError(kGULLoggerSwizzler, NO, + [NSString stringWithFormat:@"I-SWZ%06ld", + (long)kGULSwizzlerMessageCodeAppDelegateSwizzling003], + @"AppDelegateProxy cannot unregister empty interceptor ID."); + return; + } + + GULZeroingWeakContainer *weakContainer = [GULAppDelegateSwizzler interceptors][interceptorID]; + if (!weakContainer.object) { + GULLogError(kGULLoggerSwizzler, NO, + [NSString stringWithFormat:@"I-SWZ%06ld", + (long)kGULSwizzlerMessageCodeAppDelegateSwizzling004], + @"AppDelegateProxy cannot unregister interceptor that was not registered. " + "Interceptor ID %@", + interceptorID); + return; + } + + [[GULAppDelegateSwizzler interceptors] removeObjectForKey:interceptorID]; +} + ++ (void)proxyOriginalDelegate { + if ([GULAppEnvironmentUtil isAppExtension]) { + return; + } + + dispatch_once(&sProxyAppDelegateOnceToken, ^{ + id originalDelegate = + [GULAppDelegateSwizzler sharedApplication].delegate; + [GULAppDelegateSwizzler proxyAppDelegate:originalDelegate]; + }); +} + ++ (void)proxyOriginalDelegateIncludingAPNSMethods { + if ([GULAppEnvironmentUtil isAppExtension]) { + return; + } + + [self proxyOriginalDelegate]; + + dispatch_once(&sProxyAppDelegateRemoteNotificationOnceToken, ^{ + id appDelegate = [GULAppDelegateSwizzler sharedApplication].delegate; + + NSMutableDictionary *realImplementationsBySelector = + [objc_getAssociatedObject(appDelegate, &kGULRealIMPBySelectorKey) mutableCopy]; + + [self proxyRemoteNotificationsMethodsWithAppDelegateSubClass:gAppDelegateSubclass + realClass:gOriginalAppDelegateClass + appDelegate:appDelegate + realImplementationsBySelector:realImplementationsBySelector]; + + objc_setAssociatedObject(appDelegate, &kGULRealIMPBySelectorKey, + [realImplementationsBySelector copy], OBJC_ASSOCIATION_RETAIN); + [self reassignAppDelegate]; + }); +} + +#pragma mark - Create proxy + ++ (GULApplication *)sharedApplication { + if ([GULAppEnvironmentUtil isAppExtension]) { + return nil; + } + id sharedApplication = nil; + Class uiApplicationClass = NSClassFromString(kGULApplicationClassName); + if (uiApplicationClass && + [uiApplicationClass respondsToSelector:(NSSelectorFromString(@"sharedApplication"))]) { + sharedApplication = [uiApplicationClass sharedApplication]; + } + return sharedApplication; +} + +#pragma mark - Override default methods + +/** Creates a new subclass of the class of the given object and sets the isa value of the given + * object to the new subclass. Additionally this copies methods to that new subclass that allow us + * to intercept UIApplicationDelegate methods. This is better known as isa swizzling. + * + * @param appDelegate The object to which you want to isa swizzle. This has to conform to the + * UIApplicationDelegate subclass. + * @return Returns the new subclass. + */ ++ (nullable Class)createSubclassWithObject:(id)appDelegate { + Class realClass = [appDelegate class]; + + // Create GUL__ + NSString *classNameWithPrefix = + [kGULAppDelegatePrefix stringByAppendingString:NSStringFromClass(realClass)]; + NSString *newClassName = + [NSString stringWithFormat:@"%@-%@", classNameWithPrefix, [NSUUID UUID].UUIDString]; + + if (NSClassFromString(newClassName)) { + GULLogError(kGULLoggerSwizzler, NO, + [NSString stringWithFormat:@"I-SWZ%06ld", + (long)kGULSwizzlerMessageCodeAppDelegateSwizzling005], + @"Cannot create a proxy for App Delegate. Subclass already exists. Original Class: " + @"%@, subclass: %@", + NSStringFromClass(realClass), newClassName); + return nil; + } + + // Register the new class as subclass of the real one. Do not allocate more than the real class + // size. + Class appDelegateSubClass = objc_allocateClassPair(realClass, newClassName.UTF8String, 0); + if (appDelegateSubClass == Nil) { + GULLogError(kGULLoggerSwizzler, NO, + [NSString stringWithFormat:@"I-SWZ%06ld", + (long)kGULSwizzlerMessageCodeAppDelegateSwizzling006], + @"Cannot create a proxy for App Delegate. Subclass already exists. Original Class: " + @"%@, subclass: Nil", + NSStringFromClass(realClass)); + return nil; + } + + NSMutableDictionary *realImplementationsBySelector = + [[NSMutableDictionary alloc] init]; + + // For application:continueUserActivity:restorationHandler: + SEL continueUserActivitySEL = @selector(application:continueUserActivity:restorationHandler:); + [self proxyDestinationSelector:continueUserActivitySEL + implementationsFromSourceSelector:continueUserActivitySEL + fromClass:[GULAppDelegateSwizzler class] + toClass:appDelegateSubClass + realClass:realClass + storeDestinationImplementationTo:realImplementationsBySelector]; + +#if TARGET_OS_IOS || TARGET_OS_TV + // Add the following methods from GULAppDelegate class, and store the real implementation so it + // can forward to the real one. + // For application:openURL:options: + SEL applicationOpenURLOptionsSEL = @selector(application:openURL:options:); + if ([appDelegate respondsToSelector:applicationOpenURLOptionsSEL]) { + // Only add the application:openURL:options: method if the original AppDelegate implements it. + // This fixes a bug if an app only implements application:openURL:sourceApplication:annotation: + // (if we add the `options` method, iOS sees that one exists and does not call the + // `sourceApplication` method, which in this case is the only one the app implements). + + [self proxyDestinationSelector:applicationOpenURLOptionsSEL + implementationsFromSourceSelector:applicationOpenURLOptionsSEL + fromClass:[GULAppDelegateSwizzler class] + toClass:appDelegateSubClass + realClass:realClass + storeDestinationImplementationTo:realImplementationsBySelector]; + } + + // For application:handleEventsForBackgroundURLSession:completionHandler: + SEL handleEventsForBackgroundURLSessionSEL = @selector(application: + handleEventsForBackgroundURLSession:completionHandler:); + [self proxyDestinationSelector:handleEventsForBackgroundURLSessionSEL + implementationsFromSourceSelector:handleEventsForBackgroundURLSessionSEL + fromClass:[GULAppDelegateSwizzler class] + toClass:appDelegateSubClass + realClass:realClass + storeDestinationImplementationTo:realImplementationsBySelector]; +#endif // TARGET_OS_IOS || TARGET_OS_TV + +#if TARGET_OS_IOS + // For application:openURL:sourceApplication:annotation: + SEL openURLSourceApplicationAnnotationSEL = @selector(application: + openURL:sourceApplication:annotation:); + + [self proxyDestinationSelector:openURLSourceApplicationAnnotationSEL + implementationsFromSourceSelector:openURLSourceApplicationAnnotationSEL + fromClass:[GULAppDelegateSwizzler class] + toClass:appDelegateSubClass + realClass:realClass + storeDestinationImplementationTo:realImplementationsBySelector]; +#endif // TARGET_OS_IOS + + // Override the description too so the custom class name will not show up. + [GULAppDelegateSwizzler addInstanceMethodWithDestinationSelector:@selector(description) + withImplementationFromSourceSelector:@selector(fakeDescription) + fromClass:[self class] + toClass:appDelegateSubClass]; + + // Store original implementations to a fake property of the original delegate. + objc_setAssociatedObject(appDelegate, &kGULRealIMPBySelectorKey, + [realImplementationsBySelector copy], OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(appDelegate, &kGULRealClassKey, realClass, + OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + // The subclass size has to be exactly the same size with the original class size. The subclass + // cannot have more ivars/properties than its superclass since it will cause an offset in memory + // that can lead to overwriting the isa of an object in the next frame. + if (class_getInstanceSize(realClass) != class_getInstanceSize(appDelegateSubClass)) { + GULLogError(kGULLoggerSwizzler, NO, + [NSString stringWithFormat:@"I-SWZ%06ld", + (long)kGULSwizzlerMessageCodeAppDelegateSwizzling007], + @"Cannot create subclass of App Delegate, because the created subclass is not the " + @"same size. %@", + NSStringFromClass(realClass)); + NSAssert(NO, @"Classes must be the same size to swizzle isa"); + return nil; + } + + // Make the newly created class to be the subclass of the real App Delegate class. + objc_registerClassPair(appDelegateSubClass); + if (object_setClass(appDelegate, appDelegateSubClass)) { + GULLogDebug(kGULLoggerSwizzler, NO, + [NSString stringWithFormat:@"I-SWZ%06ld", + (long)kGULSwizzlerMessageCodeAppDelegateSwizzling008], + @"Successfully created App Delegate Proxy automatically. To disable the " + @"proxy, set the flag %@ to NO (Boolean) in the Info.plist", + [GULAppDelegateSwizzler correctAppDelegateProxyKey]); + } + + return appDelegateSubClass; +} + ++ (void)proxyRemoteNotificationsMethodsWithAppDelegateSubClass:(Class)appDelegateSubClass + realClass:(Class)realClass + appDelegate:(id)appDelegate + realImplementationsBySelector: + (NSMutableDictionary *)realImplementationsBySelector { + if (realClass == nil || appDelegateSubClass == nil || appDelegate == nil || + realImplementationsBySelector == nil) { + // The App Delegate has not been swizzled. + return; + } + + // For application:didRegisterForRemoteNotificationsWithDeviceToken: + SEL didRegisterForRemoteNotificationsSEL = + NSSelectorFromString(kGULDidRegisterForRemoteNotificationsSEL); + SEL didRegisterForRemoteNotificationsDonorSEL = @selector(application: + donor_didRegisterForRemoteNotificationsWithDeviceToken:); + + [self proxyDestinationSelector:didRegisterForRemoteNotificationsSEL + implementationsFromSourceSelector:didRegisterForRemoteNotificationsDonorSEL + fromClass:[GULAppDelegateSwizzler class] + toClass:appDelegateSubClass + realClass:realClass + storeDestinationImplementationTo:realImplementationsBySelector]; + + // For application:didFailToRegisterForRemoteNotificationsWithError: + SEL didFailToRegisterForRemoteNotificationsSEL = + NSSelectorFromString(kGULDidFailToRegisterForRemoteNotificationsSEL); + SEL didFailToRegisterForRemoteNotificationsDonorSEL = @selector(application: + donor_didFailToRegisterForRemoteNotificationsWithError:); + + [self proxyDestinationSelector:didFailToRegisterForRemoteNotificationsSEL + implementationsFromSourceSelector:didFailToRegisterForRemoteNotificationsDonorSEL + fromClass:[GULAppDelegateSwizzler class] + toClass:appDelegateSubClass + realClass:realClass + storeDestinationImplementationTo:realImplementationsBySelector]; + + // For application:didReceiveRemoteNotification: + SEL didReceiveRemoteNotificationSEL = NSSelectorFromString(kGULDidReceiveRemoteNotificationSEL); + SEL didReceiveRemoteNotificationDonotSEL = @selector(application: + donor_didReceiveRemoteNotification:); + + [self proxyDestinationSelector:didReceiveRemoteNotificationSEL + implementationsFromSourceSelector:didReceiveRemoteNotificationDonotSEL + fromClass:[GULAppDelegateSwizzler class] + toClass:appDelegateSubClass + realClass:realClass + storeDestinationImplementationTo:realImplementationsBySelector]; + + // For application:didReceiveRemoteNotification:fetchCompletionHandler: +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 && !TARGET_OS_WATCH + if ([GULAppEnvironmentUtil isIOS7OrHigher]) { + SEL didReceiveRemoteNotificationWithCompletionSEL = + NSSelectorFromString(kGULDidReceiveRemoteNotificationWithCompletionSEL); + SEL didReceiveRemoteNotificationWithCompletionDonorSEL = + @selector(application:donor_didReceiveRemoteNotification:fetchCompletionHandler:); + if ([appDelegate respondsToSelector:didReceiveRemoteNotificationWithCompletionSEL]) { + // Only add the application:didReceiveRemoteNotification:fetchCompletionHandler: method if + // the original AppDelegate implements it. + // This fixes a bug if an app only implements application:didReceiveRemoteNotification: + // (if we add the method with completion, iOS sees that one exists and does not call + // the method without the completion, which in this case is the only one the app implements). + + [self proxyDestinationSelector:didReceiveRemoteNotificationWithCompletionSEL + implementationsFromSourceSelector:didReceiveRemoteNotificationWithCompletionDonorSEL + fromClass:[GULAppDelegateSwizzler class] + toClass:appDelegateSubClass + realClass:realClass + storeDestinationImplementationTo:realImplementationsBySelector]; + } + } +#endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 && !TARGET_OS_WATCH +} + +/// We have to do this to invalidate the cache that caches the original respondsToSelector of +/// openURL handlers. Without this, it won't call the default implementations because the system +/// checks and caches them. +/// Register KVO only once. Otherwise, the observing method will be called as many times as +/// being registered. ++ (void)reassignAppDelegate { +#if !TARGET_OS_WATCH + id delegate = [self sharedApplication].delegate; + [self sharedApplication].delegate = nil; + [self sharedApplication].delegate = delegate; + gOriginalAppDelegate = delegate; + [[GULAppDelegateObserver sharedInstance] observeUIApplication]; +#endif +} + +#pragma mark - Helper methods + ++ (GULMutableDictionary *)interceptors { + static dispatch_once_t onceToken; + static GULMutableDictionary *sInterceptors; + dispatch_once(&onceToken, ^{ + sInterceptors = [[GULMutableDictionary alloc] init]; + }); + return sInterceptors; +} + ++ (nullable NSValue *)originalImplementationForSelector:(SEL)selector object:(id)object { + NSDictionary *realImplementationBySelector = + objc_getAssociatedObject(object, &kGULRealIMPBySelectorKey); + return realImplementationBySelector[NSStringFromSelector(selector)]; +} + ++ (void)proxyDestinationSelector:(SEL)destinationSelector + implementationsFromSourceSelector:(SEL)sourceSelector + fromClass:(Class)sourceClass + toClass:(Class)destinationClass + realClass:(Class)realClass + storeDestinationImplementationTo: + (NSMutableDictionary *)destinationImplementationsBySelector { + [self addInstanceMethodWithDestinationSelector:destinationSelector + withImplementationFromSourceSelector:sourceSelector + fromClass:sourceClass + toClass:destinationClass]; + IMP sourceImplementation = + [GULAppDelegateSwizzler implementationOfMethodSelector:destinationSelector + fromClass:realClass]; + NSValue *sourceImplementationPointer = [NSValue valueWithPointer:sourceImplementation]; + + NSString *destinationSelectorString = NSStringFromSelector(destinationSelector); + destinationImplementationsBySelector[destinationSelectorString] = sourceImplementationPointer; +} + +/** Copies a method identified by the methodSelector from one class to the other. After this method + * is called, performing [toClassInstance methodSelector] will be similar to calling + * [fromClassInstance methodSelector]. This method does nothing if toClass already has a method + * identified by methodSelector. + * + * @param methodSelector The SEL that identifies both the method on the fromClass as well as the + * one on the toClass. + * @param fromClass The class from which a method is sourced. + * @param toClass The class to which the method is added. If the class already has a method with + * the same selector, this has no effect. + */ ++ (void)addInstanceMethodWithSelector:(SEL)methodSelector + fromClass:(Class)fromClass + toClass:(Class)toClass { + [self addInstanceMethodWithDestinationSelector:methodSelector + withImplementationFromSourceSelector:methodSelector + fromClass:fromClass + toClass:toClass]; +} + +/** Copies a method identified by the sourceSelector from the fromClass as a method for the + * destinationSelector on the toClass. After this method is called, performing + * [toClassInstance destinationSelector] will be similar to calling + * [fromClassInstance sourceSelector]. This method does nothing if toClass already has a method + * identified by destinationSelector. + * + * @param destinationSelector The SEL that identifies the method on the toClass. + * @param sourceSelector The SEL that identifies the method on the fromClass. + * @param fromClass The class from which a method is sourced. + * @param toClass The class to which the method is added. If the class already has a method with + * the same selector, this has no effect. + */ ++ (void)addInstanceMethodWithDestinationSelector:(SEL)destinationSelector + withImplementationFromSourceSelector:(SEL)sourceSelector + fromClass:(Class)fromClass + toClass:(Class)toClass { + Method method = class_getInstanceMethod(fromClass, sourceSelector); + IMP methodIMP = method_getImplementation(method); + const char *types = method_getTypeEncoding(method); + if (!class_addMethod(toClass, destinationSelector, methodIMP, types)) { + GULLogWarning(kGULLoggerSwizzler, NO, + [NSString stringWithFormat:@"I-SWZ%06ld", + (long)kGULSwizzlerMessageCodeAppDelegateSwizzling009], + @"Cannot copy method to destination selector %@ as it already exists", + NSStringFromSelector(destinationSelector)); + } +} + +/** Gets the IMP of the instance method on the class identified by the selector. + * + * @param selector The selector of which the IMP is to be fetched. + * @param aClass The class from which the IMP is to be fetched. + * @return The IMP of the instance method identified by selector and aClass. + */ ++ (IMP)implementationOfMethodSelector:(SEL)selector fromClass:(Class)aClass { + Method aMethod = class_getInstanceMethod(aClass, selector); + return method_getImplementation(aMethod); +} + +/** Enumerates through all the interceptors and if they respond to a given selector, executes a + * GULAppDelegateInterceptorCallback with the interceptor. + * + * @param methodSelector The SEL to check if an interceptor responds to. + * @param callback the GULAppDelegateInterceptorCallback. + */ ++ (void)notifyInterceptorsWithMethodSelector:(SEL)methodSelector + callback:(GULAppDelegateInterceptorCallback)callback { + if (!callback) { + return; + } + + NSDictionary *interceptors = [GULAppDelegateSwizzler interceptors].dictionary; + [interceptors enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + GULZeroingWeakContainer *interceptorContainer = obj; + id interceptor = interceptorContainer.object; + if (!interceptor) { + GULLogWarning( + kGULLoggerSwizzler, NO, + [NSString + stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeAppDelegateSwizzling010], + @"AppDelegateProxy cannot find interceptor with ID %@. Removing the interceptor.", key); + [[GULAppDelegateSwizzler interceptors] removeObjectForKey:key]; + return; + } + if ([interceptor respondsToSelector:methodSelector]) { + callback(interceptor); + } + }]; +} + +// The methods below are donor methods which are added to the dynamic subclass of the App Delegate. +// They are called within the scope of the real App Delegate so |self| does not refer to the +// GULAppDelegateSwizzler instance but the real App Delegate instance. + +#pragma mark - [Donor Methods] Overridden instance description method + +- (NSString *)fakeDescription { + Class realClass = objc_getAssociatedObject(self, &kGULRealClassKey); + return [NSString stringWithFormat:@"<%@: %p>", realClass, self]; +} + +#pragma mark - [Donor Methods] URL overridden handler methods +#if TARGET_OS_IOS || TARGET_OS_TV + +- (BOOL)application:(GULApplication *)application + openURL:(NSURL *)url + options:(NSDictionary *)options { + SEL methodSelector = @selector(application:openURL:options:); + // Call the real implementation if the real App Delegate has any. + NSValue *openURLIMPPointer = + [GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self]; + GULRealOpenURLOptionsIMP openURLOptionsIMP = [openURLIMPPointer pointerValue]; + + __block BOOL returnedValue = NO; + +// This is needed to for the library to be warning free on iOS versions < 9. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability" + [GULAppDelegateSwizzler + notifyInterceptorsWithMethodSelector:methodSelector + callback:^(id interceptor) { + returnedValue |= [interceptor application:application + openURL:url + options:options]; + }]; +#pragma clang diagnostic pop + if (openURLOptionsIMP) { + returnedValue |= openURLOptionsIMP(self, methodSelector, application, url, options); + } + return returnedValue; +} + +#endif // TARGET_OS_IOS || TARGET_OS_TV + +#if TARGET_OS_IOS + +- (BOOL)application:(GULApplication *)application + openURL:(NSURL *)url + sourceApplication:(NSString *)sourceApplication + annotation:(id)annotation { + SEL methodSelector = @selector(application:openURL:sourceApplication:annotation:); + + // Call the real implementation if the real App Delegate has any. + NSValue *openURLSourceAppAnnotationIMPPointer = + [GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self]; + GULRealOpenURLSourceApplicationAnnotationIMP openURLSourceApplicationAnnotationIMP = + [openURLSourceAppAnnotationIMPPointer pointerValue]; + + __block BOOL returnedValue = NO; + [GULAppDelegateSwizzler + notifyInterceptorsWithMethodSelector:methodSelector + callback:^(id interceptor) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + returnedValue |= [interceptor application:application + openURL:url + sourceApplication:sourceApplication + annotation:annotation]; +#pragma clang diagnostic pop + }]; + if (openURLSourceApplicationAnnotationIMP) { + returnedValue |= openURLSourceApplicationAnnotationIMP(self, methodSelector, application, url, + sourceApplication, annotation); + } + return returnedValue; +} + +#endif // TARGET_OS_IOS + +#pragma mark - [Donor Methods] Network overridden handler methods + +#if TARGET_OS_IOS || TARGET_OS_TV + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wstrict-prototypes" +- (void)application:(GULApplication *)application + handleEventsForBackgroundURLSession:(NSString *)identifier + completionHandler:(void (^)())completionHandler API_AVAILABLE(ios(7.0)) { +#pragma clang diagnostic pop + SEL methodSelector = @selector(application: + handleEventsForBackgroundURLSession:completionHandler:); + NSValue *handleBackgroundSessionPointer = + [GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self]; + GULRealHandleEventsForBackgroundURLSessionIMP handleBackgroundSessionIMP = + [handleBackgroundSessionPointer pointerValue]; + + // Notify interceptors. + [GULAppDelegateSwizzler + notifyInterceptorsWithMethodSelector:methodSelector + callback:^(id interceptor) { + [interceptor application:application + handleEventsForBackgroundURLSession:identifier + completionHandler:completionHandler]; + }]; + // Call the real implementation if the real App Delegate has any. + if (handleBackgroundSessionIMP) { + handleBackgroundSessionIMP(self, methodSelector, application, identifier, completionHandler); + } +} + +#endif // TARGET_OS_IOS || TARGET_OS_TV + +#pragma mark - [Donor Methods] User Activities overridden handler methods + +// This is needed to for the library to be warning free on iOS versions < 8. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability" +- (BOOL)application:(GULApplication *)application + continueUserActivity:(NSUserActivity *)userActivity + restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler { + SEL methodSelector = @selector(application:continueUserActivity:restorationHandler:); + NSValue *continueUserActivityIMPPointer = + [GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self]; + GULRealContinueUserActivityIMP continueUserActivityIMP = + continueUserActivityIMPPointer.pointerValue; + + __block BOOL returnedValue = NO; +#if !TARGET_OS_WATCH + [GULAppDelegateSwizzler + notifyInterceptorsWithMethodSelector:methodSelector + callback:^(id interceptor) { + returnedValue |= [interceptor application:application + continueUserActivity:userActivity + restorationHandler:restorationHandler]; + }]; +#endif + // Call the real implementation if the real App Delegate has any. + if (continueUserActivityIMP) { + returnedValue |= continueUserActivityIMP(self, methodSelector, application, userActivity, + restorationHandler); + } + return returnedValue; +} +#pragma clang diagnostic pop + +#pragma mark - [Donor Methods] Remote Notifications + +- (void)application:(GULApplication *)application + donor_didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + SEL methodSelector = NSSelectorFromString(kGULDidRegisterForRemoteNotificationsSEL); + + NSValue *didRegisterForRemoteNotificationsIMPPointer = + [GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self]; + GULRealDidRegisterForRemoteNotificationsIMP didRegisterForRemoteNotificationsIMP = + [didRegisterForRemoteNotificationsIMPPointer pointerValue]; + + // Notify interceptors. + [GULAppDelegateSwizzler + notifyInterceptorsWithMethodSelector:methodSelector + callback:^(id interceptor) { + NSInvocation *invocation = [GULAppDelegateSwizzler + appDelegateInvocationForSelector:methodSelector]; + [invocation setTarget:interceptor]; + [invocation setSelector:methodSelector]; + [invocation setArgument:(void *)(&application) atIndex:2]; + [invocation setArgument:(void *)(&deviceToken) atIndex:3]; + [invocation invoke]; + }]; + // Call the real implementation if the real App Delegate has any. + if (didRegisterForRemoteNotificationsIMP) { + didRegisterForRemoteNotificationsIMP(self, methodSelector, application, deviceToken); + } +} + +- (void)application:(GULApplication *)application + donor_didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { + SEL methodSelector = NSSelectorFromString(kGULDidFailToRegisterForRemoteNotificationsSEL); + NSValue *didFailToRegisterForRemoteNotificationsIMPPointer = + [GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self]; + GULRealDidFailToRegisterForRemoteNotificationsIMP didFailToRegisterForRemoteNotificationsIMP = + [didFailToRegisterForRemoteNotificationsIMPPointer pointerValue]; + + // Notify interceptors. + [GULAppDelegateSwizzler + notifyInterceptorsWithMethodSelector:methodSelector + callback:^(id interceptor) { + NSInvocation *invocation = [GULAppDelegateSwizzler + appDelegateInvocationForSelector:methodSelector]; + [invocation setTarget:interceptor]; + [invocation setSelector:methodSelector]; + [invocation setArgument:(void *)(&application) atIndex:2]; + [invocation setArgument:(void *)(&error) atIndex:3]; + [invocation invoke]; + }]; + // Call the real implementation if the real App Delegate has any. + if (didFailToRegisterForRemoteNotificationsIMP) { + didFailToRegisterForRemoteNotificationsIMP(self, methodSelector, application, error); + } +} + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 && !TARGET_OS_WATCH +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability" +- (void)application:(GULApplication *)application + donor_didReceiveRemoteNotification:(NSDictionary *)userInfo + fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { + SEL methodSelector = NSSelectorFromString(kGULDidReceiveRemoteNotificationWithCompletionSEL); + NSValue *didReceiveRemoteNotificationWithCompletionIMPPointer = + [GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self]; + GULRealDidReceiveRemoteNotificationWithCompletionIMP + didReceiveRemoteNotificationWithCompletionIMP = + [didReceiveRemoteNotificationWithCompletionIMPPointer pointerValue]; + + // Notify interceptors. + [GULAppDelegateSwizzler + notifyInterceptorsWithMethodSelector:methodSelector + callback:^(id interceptor) { + NSInvocation *invocation = [GULAppDelegateSwizzler + appDelegateInvocationForSelector:methodSelector]; + [invocation setTarget:interceptor]; + [invocation setSelector:methodSelector]; + [invocation setArgument:(void *)(&application) atIndex:2]; + [invocation setArgument:(void *)(&userInfo) atIndex:3]; + [invocation setArgument:(void *)(&completionHandler) atIndex:4]; + [invocation invoke]; + }]; + // Call the real implementation if the real App Delegate has any. + if (didReceiveRemoteNotificationWithCompletionIMP) { + didReceiveRemoteNotificationWithCompletionIMP(self, methodSelector, application, userInfo, + completionHandler); + } +} +#pragma clang diagnostic pop +#endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 && !TARGET_OS_WATCH + +- (void)application:(GULApplication *)application + donor_didReceiveRemoteNotification:(NSDictionary *)userInfo { + SEL methodSelector = NSSelectorFromString(kGULDidReceiveRemoteNotificationSEL); + NSValue *didReceiveRemoteNotificationIMPPointer = + [GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self]; + GULRealDidReceiveRemoteNotificationIMP didReceiveRemoteNotificationIMP = + [didReceiveRemoteNotificationIMPPointer pointerValue]; + + // Notify interceptors. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [GULAppDelegateSwizzler + notifyInterceptorsWithMethodSelector:methodSelector + callback:^(id interceptor) { + NSInvocation *invocation = [GULAppDelegateSwizzler + appDelegateInvocationForSelector:methodSelector]; + [invocation setTarget:interceptor]; + [invocation setSelector:methodSelector]; + [invocation setArgument:(void *)(&application) atIndex:2]; + [invocation setArgument:(void *)(&userInfo) atIndex:3]; + [invocation invoke]; + }]; +#pragma clang diagnostic pop + // Call the real implementation if the real App Delegate has any. + if (didReceiveRemoteNotificationIMP) { + didReceiveRemoteNotificationIMP(self, methodSelector, application, userInfo); + } +} + ++ (nullable NSInvocation *)appDelegateInvocationForSelector:(SEL)selector { + struct objc_method_description methodDescription = + protocol_getMethodDescription(@protocol(GULApplicationDelegate), selector, NO, YES); + if (methodDescription.types == NULL) { + return nil; + } + + NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:methodDescription.types]; + return [NSInvocation invocationWithMethodSignature:signature]; +} + ++ (void)proxyAppDelegate:(id)appDelegate { + if (![appDelegate conformsToProtocol:@protocol(GULApplicationDelegate)]) { + GULLogNotice( + kGULLoggerSwizzler, NO, + [NSString + stringWithFormat:@"I-SWZ%06ld", + (long)kGULSwizzlerMessageCodeAppDelegateSwizzlingInvalidAppDelegate], + @"App Delegate does not conform to UIApplicationDelegate protocol. %@", + [GULAppDelegateSwizzler correctAlternativeWhenAppDelegateProxyNotCreated]); + return; + } + + id originalDelegate = appDelegate; + // Do not create a subclass if it is not enabled. + if (![GULAppDelegateSwizzler isAppDelegateProxyEnabled]) { + GULLogNotice(kGULLoggerSwizzler, NO, + [NSString stringWithFormat:@"I-SWZ%06ld", + (long)kGULSwizzlerMessageCodeAppDelegateSwizzling011], + @"App Delegate Proxy is disabled. %@", + [GULAppDelegateSwizzler correctAlternativeWhenAppDelegateProxyNotCreated]); + return; + } + // Do not accept nil delegate. + if (!originalDelegate) { + GULLogError(kGULLoggerSwizzler, NO, + [NSString stringWithFormat:@"I-SWZ%06ld", + (long)kGULSwizzlerMessageCodeAppDelegateSwizzling012], + @"Cannot create App Delegate Proxy because App Delegate instance is nil. %@", + [GULAppDelegateSwizzler correctAlternativeWhenAppDelegateProxyNotCreated]); + return; + } + + @try { + gOriginalAppDelegateClass = [originalDelegate class]; + gAppDelegateSubclass = [self createSubclassWithObject:originalDelegate]; + [self reassignAppDelegate]; + } @catch (NSException *exception) { + GULLogError(kGULLoggerSwizzler, NO, + [NSString stringWithFormat:@"I-SWZ%06ld", + (long)kGULSwizzlerMessageCodeAppDelegateSwizzling013], + @"Cannot create App Delegate Proxy. %@", + [GULAppDelegateSwizzler correctAlternativeWhenAppDelegateProxyNotCreated]); + return; + } +} + +#pragma mark - Methods to print correct debug logs + ++ (NSString *)correctAppDelegateProxyKey { + return NSClassFromString(@"FIRCore") ? kGULFirebaseAppDelegateProxyEnabledPlistKey + : kGULGoogleUtilitiesAppDelegateProxyEnabledPlistKey; +} + ++ (NSString *)correctAlternativeWhenAppDelegateProxyNotCreated { + return NSClassFromString(@"FIRCore") + ? @"To log deep link campaigns manually, call the methods in " + @"FIRAnalytics+AppDelegate.h." + : @""; +} + +#pragma mark - Private Methods for Testing + ++ (void)clearInterceptors { + [[self interceptors] removeAllObjects]; +} + ++ (void)resetProxyOriginalDelegateOnceToken { + sProxyAppDelegateOnceToken = 0; + sProxyAppDelegateRemoteNotificationOnceToken = 0; +} + ++ (id)originalDelegate { + return gOriginalAppDelegate; +} + +@end diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/AppDelegateSwizzler/Internal/GULAppDelegateSwizzler_Private.h b/!main project/Pods/GoogleUtilities/GoogleUtilities/AppDelegateSwizzler/Internal/GULAppDelegateSwizzler_Private.h new file mode 100644 index 0000000..d7ebd86 --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/AppDelegateSwizzler/Internal/GULAppDelegateSwizzler_Private.h @@ -0,0 +1,55 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import +#import +#import + +@class GULApplication; + +NS_ASSUME_NONNULL_BEGIN + +@interface GULAppDelegateSwizzler () + +/** ISA Swizzles the given appDelegate as the original app delegate would be. + * + * @param appDelegate The object that needs to be isa swizzled. This should conform to the + * application delegate protocol. + */ ++ (void)proxyAppDelegate:(id)appDelegate; + +/** Returns a dictionary containing interceptor IDs mapped to a GULZeroingWeakContainer. + * + * @return A dictionary of the form {NSString : GULZeroingWeakContainer}, where the NSString is + * the interceptorID. + */ ++ (GULMutableDictionary *)interceptors; + +/** Deletes all the registered interceptors. */ ++ (void)clearInterceptors; + +/** Resets the token that prevents the app delegate proxy from being isa swizzled multiple times. */ ++ (void)resetProxyOriginalDelegateOnceToken; + +/** Returns the original app delegate that was proxied. + * + * @return The original app delegate instance that was proxied. + */ ++ (id)originalDelegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/AppDelegateSwizzler/Private/GULAppDelegateSwizzler.h b/!main project/Pods/GoogleUtilities/GoogleUtilities/AppDelegateSwizzler/Private/GULAppDelegateSwizzler.h new file mode 100644 index 0000000..c1b2d6e --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/AppDelegateSwizzler/Private/GULAppDelegateSwizzler.h @@ -0,0 +1,107 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef NSString *const GULAppDelegateInterceptorID; + +/** This class contains methods that isa swizzle the app delegate. */ +@interface GULAppDelegateSwizzler : NSProxy + +/** Registers an app delegate interceptor whose methods will be invoked as they're invoked on the + * original app delegate. + * + * @param interceptor An instance of a class that conforms to the application delegate protocol. + * The interceptor is NOT retained. + * @return A unique GULAppDelegateInterceptorID if interceptor was successfully registered; nil + * if it fails. + */ ++ (nullable GULAppDelegateInterceptorID)registerAppDelegateInterceptor: + (id)interceptor; + +/** Unregisters an interceptor with the given ID if it exists. + * + * @param interceptorID The object that was generated when the interceptor was registered. + */ ++ (void)unregisterAppDelegateInterceptorWithID:(GULAppDelegateInterceptorID)interceptorID; + +/** This method ensures that the original app delegate has been proxied. Call this before + * registering your interceptor. This method is safe to call multiple times (but it only proxies + * the app delegate once). + * + * This method doesn't proxy APNS related methods: + * @code + * - application:didRegisterForRemoteNotificationsWithDeviceToken: + * - application:didFailToRegisterForRemoteNotificationsWithError: + * - application:didReceiveRemoteNotification:fetchCompletionHandler: + * - application:didReceiveRemoteNotification: + * @endcode + * + * To proxy these methods use +[GULAppDelegateSwizzler + * proxyOriginalDelegateIncludingAPNSMethods]. The methods have to be proxied separately to + * avoid potential warnings from Apple review about missing Push Notification Entitlement (e.g. + * https://github.com/firebase/firebase-ios-sdk/issues/2807) + * + * The method has no effect for extensions. + * + * @see proxyOriginalDelegateIncludingAPNSMethods + */ ++ (void)proxyOriginalDelegate; + +/** This method ensures that the original app delegate has been proxied including APNS related + * methods. Call this before registering your interceptor. This method is safe to call multiple + * times (but it only proxies the app delegate once) or + * after +[GULAppDelegateSwizzler proxyOriginalDelegate] + * + * This method calls +[GULAppDelegateSwizzler proxyOriginalDelegate] under the hood. + * After calling this method the following App Delegate methods will be proxied in addition to + * the methods proxied by proxyOriginalDelegate: + * @code + * - application:didRegisterForRemoteNotificationsWithDeviceToken: + * - application:didFailToRegisterForRemoteNotificationsWithError: + * - application:didReceiveRemoteNotification:fetchCompletionHandler: + * - application:didReceiveRemoteNotification: + * @endcode + * + * The method has no effect for extensions. + * + * @see proxyOriginalDelegate + */ ++ (void)proxyOriginalDelegateIncludingAPNSMethods; + +/** Indicates whether app delegate proxy is explicitly disabled or enabled. Enabled by default. + * + * @return YES if AppDelegateProxy is Enabled, NO otherwise. + */ ++ (BOOL)isAppDelegateProxyEnabled; + +/** Returns the current sharedApplication. + * + * @return the current application instance if in an app, or nil if in extension or if it doesn't + * exist. + */ ++ (nullable GULApplication *)sharedApplication; + +/** Do not initialize this class. */ +- (instancetype)init NS_UNAVAILABLE; + +NS_ASSUME_NONNULL_END + +@end diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/AppDelegateSwizzler/Private/GULApplication.h b/!main project/Pods/GoogleUtilities/GoogleUtilities/AppDelegateSwizzler/Private/GULApplication.h new file mode 100644 index 0000000..8067212 --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/AppDelegateSwizzler/Private/GULApplication.h @@ -0,0 +1,50 @@ +/* + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#if TARGET_OS_IOS || TARGET_OS_TV + +#import + +#define GULApplication UIApplication +#define GULApplicationDelegate UIApplicationDelegate +#define GULUserActivityRestoring UIUserActivityRestoring + +static NSString *const kGULApplicationClassName = @"UIApplication"; + +#elif TARGET_OS_OSX + +#import + +#define GULApplication NSApplication +#define GULApplicationDelegate NSApplicationDelegate +#define GULUserActivityRestoring NSUserActivityRestoring + +static NSString *const kGULApplicationClassName = @"NSApplication"; + +#elif TARGET_OS_WATCH + +#import + +// We match the according watchOS API but swizzling should not work in watch +#define GULApplication WKExtension +#define GULApplicationDelegate WKExtensionDelegate +#define GULUserActivityRestoring NSUserActivityRestoring + +static NSString *const kGULApplicationClassName = @"WKExtension"; + +#endif diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/Common/GULLoggerCodes.h b/!main project/Pods/GoogleUtilities/GoogleUtilities/Common/GULLoggerCodes.h new file mode 100644 index 0000000..053ce84 --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/Common/GULLoggerCodes.h @@ -0,0 +1,56 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +typedef NS_ENUM(NSInteger, GULSwizzlerMessageCode) { + // App Delegate Swizzling. + kGULSwizzlerMessageCodeAppDelegateSwizzling000 = 1000, // I-SWZ001000 + kGULSwizzlerMessageCodeAppDelegateSwizzling001 = 1001, // I-SWZ001001 + kGULSwizzlerMessageCodeAppDelegateSwizzling002 = 1002, // I-SWZ001002 + kGULSwizzlerMessageCodeAppDelegateSwizzling003 = 1003, // I-SWZ001003 + kGULSwizzlerMessageCodeAppDelegateSwizzling004 = 1004, // I-SWZ001004 + kGULSwizzlerMessageCodeAppDelegateSwizzling005 = 1005, // I-SWZ001005 + kGULSwizzlerMessageCodeAppDelegateSwizzling006 = 1006, // I-SWZ001006 + kGULSwizzlerMessageCodeAppDelegateSwizzling007 = 1007, // I-SWZ001007 + kGULSwizzlerMessageCodeAppDelegateSwizzling008 = 1008, // I-SWZ001008 + kGULSwizzlerMessageCodeAppDelegateSwizzling009 = 1009, // I-SWZ001009 + kGULSwizzlerMessageCodeAppDelegateSwizzling010 = 1010, // I-SWZ001010 + kGULSwizzlerMessageCodeAppDelegateSwizzling011 = 1011, // I-SWZ001011 + kGULSwizzlerMessageCodeAppDelegateSwizzling012 = 1012, // I-SWZ001012 + kGULSwizzlerMessageCodeAppDelegateSwizzling013 = 1013, // I-SWZ001013 + kGULSwizzlerMessageCodeAppDelegateSwizzlingInvalidAppDelegate = 1014, // I-SWZ001014 + + // Scene Delegate Swizzling. + kGULSwizzlerMessageCodeSceneDelegateSwizzling000 = 1100, // I-SWZ001100 + kGULSwizzlerMessageCodeSceneDelegateSwizzling001 = 1101, // I-SWZ001101 + kGULSwizzlerMessageCodeSceneDelegateSwizzling002 = 1102, // I-SWZ001102 + kGULSwizzlerMessageCodeSceneDelegateSwizzling003 = 1103, // I-SWZ001103 + kGULSwizzlerMessageCodeSceneDelegateSwizzling004 = 1104, // I-SWZ001104 + kGULSwizzlerMessageCodeSceneDelegateSwizzling005 = 1105, // I-SWZ001105 + kGULSwizzlerMessageCodeSceneDelegateSwizzling006 = 1106, // I-SWZ001106 + kGULSwizzlerMessageCodeSceneDelegateSwizzling007 = 1107, // I-SWZ001107 + kGULSwizzlerMessageCodeSceneDelegateSwizzling008 = 1108, // I-SWZ001108 + kGULSwizzlerMessageCodeSceneDelegateSwizzling009 = 1109, // I-SWZ001109 + kGULSwizzlerMessageCodeSceneDelegateSwizzling010 = 1110, // I-SWZ001110 + kGULSwizzlerMessageCodeSceneDelegateSwizzling011 = 1111, // I-SWZ001111 + kGULSwizzlerMessageCodeSceneDelegateSwizzling012 = 1112, // I-SWZ001112 + kGULSwizzlerMessageCodeSceneDelegateSwizzling013 = 1113, // I-SWZ001113 + kGULSwizzlerMessageCodeSceneDelegateSwizzlingInvalidSceneDelegate = 1114, // I-SWZ001114 + + // Method Swizzling. + kGULSwizzlerMessageCodeMethodSwizzling000 = 2000, // I-SWZ002000 +}; diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/Environment/GULHeartbeatDateStorage.m b/!main project/Pods/GoogleUtilities/GoogleUtilities/Environment/GULHeartbeatDateStorage.m new file mode 100644 index 0000000..483c859 --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/Environment/GULHeartbeatDateStorage.m @@ -0,0 +1,140 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import +#import + +@interface GULHeartbeatDateStorage () +/** The storage to store the date of the last sent heartbeat. */ +@property(nonatomic, readonly) NSFileCoordinator *fileCoordinator; +@end + +@implementation GULHeartbeatDateStorage + +- (instancetype)initWithFileName:(NSString *)fileName { + if (fileName == nil) { + return nil; + } + + self = [super init]; + if (self) { + _fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil]; + NSURL *directoryURL = [[self class] directoryPathURL]; + [[self class] checkAndCreateDirectory:directoryURL fileCoordinator:_fileCoordinator]; + _fileURL = [directoryURL URLByAppendingPathComponent:fileName]; + } + return self; +} + +/** Returns the URL path of the Application Support folder. + * @return the URL path of Application Support. + */ ++ (NSURL *)directoryPathURL { + NSArray *paths = + NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); + NSArray *components = @[ paths.lastObject, @"Google/FIRApp" ]; + NSString *directoryString = [NSString pathWithComponents:components]; + NSURL *directoryURL = [NSURL fileURLWithPath:directoryString]; + return directoryURL; +} + +/** Checks and creates a directory for the directory specified by the + * directory url + * @param directoryPathURL The path to the directory which needs to be created. + * @param fileCoordinator The fileCoordinator object to coordinate writes to the directory. + */ ++ (void)checkAndCreateDirectory:(NSURL *)directoryPathURL + fileCoordinator:(NSFileCoordinator *)fileCoordinator { + NSError *fileCoordinatorError = nil; + [fileCoordinator + coordinateWritingItemAtURL:directoryPathURL + options:0 + error:&fileCoordinatorError + byAccessor:^(NSURL *writingDirectoryURL) { + NSError *error; + if (![writingDirectoryURL checkResourceIsReachableAndReturnError:&error]) { + // If fail creating the Application Support directory, log warning. + NSError *error; + [[NSFileManager defaultManager] createDirectoryAtURL:writingDirectoryURL + withIntermediateDirectories:YES + attributes:nil + error:&error]; + } + }]; +} + +- (nullable NSMutableDictionary *)heartbeatDictionaryWithFileURL:(NSURL *)readingFileURL { + NSError *error; + NSMutableDictionary *dict; + NSData *objectData = [NSData dataWithContentsOfURL:readingFileURL options:0 error:&error]; + if (objectData == nil || error != nil) { + dict = [NSMutableDictionary dictionary]; + } else { + dict = [GULSecureCoding + unarchivedObjectOfClasses:[NSSet setWithArray:@[ NSDictionary.class, NSDate.class ]] + fromData:objectData + error:&error]; + if (dict == nil || error != nil) { + dict = [NSMutableDictionary dictionary]; + } + } + return dict; +} + +- (nullable NSDate *)heartbeatDateForTag:(NSString *)tag { + __block NSMutableDictionary *dict; + NSError *error; + [self.fileCoordinator coordinateReadingItemAtURL:self.fileURL + options:0 + error:&error + byAccessor:^(NSURL *readingURL) { + dict = [self heartbeatDictionaryWithFileURL:readingURL]; + }]; + return dict[tag]; +} + +- (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag { + NSError *error; + __block BOOL isSuccess = false; + [self.fileCoordinator coordinateReadingItemAtURL:self.fileURL + options:0 + writingItemAtURL:self.fileURL + options:0 + error:&error + byAccessor:^(NSURL *readingURL, NSURL *writingURL) { + NSMutableDictionary *dictionary = + [self heartbeatDictionaryWithFileURL:readingURL]; + dictionary[tag] = date; + NSError *error; + isSuccess = [self writeDictionary:dictionary + forWritingURL:writingURL + error:&error]; + }]; + return isSuccess; +} + +- (BOOL)writeDictionary:(NSMutableDictionary *)dictionary + forWritingURL:(NSURL *)writingFileURL + error:(NSError **)outError { + NSData *data = [GULSecureCoding archivedDataWithRootObject:dictionary error:outError]; + if (*outError != nil) { + return false; + } else { + return [data writeToURL:writingFileURL atomically:YES]; + } +} + +@end diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/Environment/GULSecureCoding.m b/!main project/Pods/GoogleUtilities/GoogleUtilities/Environment/GULSecureCoding.m new file mode 100644 index 0000000..ebfb808 --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/Environment/GULSecureCoding.m @@ -0,0 +1,103 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "GoogleUtilities/Environment/Public/GULSecureCoding.h" + +NSString *const kGULSecureCodingError = @"GULSecureCodingError"; + +@implementation GULSecureCoding + ++ (nullable id)unarchivedObjectOfClasses:(NSSet *)classes + fromData:(NSData *)data + error:(NSError **)outError { + id object; +#if __has_builtin(__builtin_available) + if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) { + object = [NSKeyedUnarchiver unarchivedObjectOfClasses:classes fromData:data error:outError]; + } else +#endif // __has_builtin(__builtin_available) + { + @try { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; +#pragma clang diagnostic pop + unarchiver.requiresSecureCoding = YES; + + object = [unarchiver decodeObjectOfClasses:classes forKey:NSKeyedArchiveRootObjectKey]; + } @catch (NSException *exception) { + if (outError) { + *outError = [self archivingErrorWithException:exception]; + } + } + + if (object == nil && outError && *outError == nil) { + NSString *failureReason = @"NSKeyedUnarchiver failed to unarchive data."; + *outError = [NSError errorWithDomain:kGULSecureCodingError + code:-1 + userInfo:@{NSLocalizedFailureReasonErrorKey : failureReason}]; + } + } + + return object; +} + ++ (nullable id)unarchivedObjectOfClass:(Class)class + fromData:(NSData *)data + error:(NSError **)outError { + return [self unarchivedObjectOfClasses:[NSSet setWithObject:class] fromData:data error:outError]; +} + ++ (nullable NSData *)archivedDataWithRootObject:(id)object error:(NSError **)outError { + NSData *archiveData; +#if __has_builtin(__builtin_available) + if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) { + archiveData = [NSKeyedArchiver archivedDataWithRootObject:object + requiringSecureCoding:YES + error:outError]; + } else +#endif // __has_builtin(__builtin_available) + { + @try { + NSMutableData *data = [NSMutableData data]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; +#pragma clang diagnostic pop + archiver.requiresSecureCoding = YES; + + [archiver encodeObject:object forKey:NSKeyedArchiveRootObjectKey]; + [archiver finishEncoding]; + + archiveData = [data copy]; + } @catch (NSException *exception) { + if (outError) { + *outError = [self archivingErrorWithException:exception]; + } + } + } + + return archiveData; +} + ++ (NSError *)archivingErrorWithException:(NSException *)exception { + NSString *failureReason = [NSString + stringWithFormat:@"NSKeyedArchiver exception with name: %@, reason: %@, userInfo: %@", + exception.name, exception.reason, exception.userInfo]; + NSDictionary *errorUserInfo = @{NSLocalizedFailureReasonErrorKey : failureReason}; + + return [NSError errorWithDomain:kGULSecureCodingError code:-1 userInfo:errorUserInfo]; +} + +@end diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/Environment/Public/GULHeartbeatDateStorage.h b/!main project/Pods/GoogleUtilities/GoogleUtilities/Environment/Public/GULHeartbeatDateStorage.h new file mode 100644 index 0000000..9432dfc --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/Environment/Public/GULHeartbeatDateStorage.h @@ -0,0 +1,49 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// Stores either a date or a dictionary to a specified file. +@interface GULHeartbeatDateStorage : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +@property(nonatomic, readonly) NSURL *fileURL; + +/** + * Default initializer. + * @param fileName The name of the file to store the date information. + * exist, it will be created if needed. + */ +- (instancetype)initWithFileName:(NSString *)fileName; + +/** + * Reads the date from the specified file for the given tag. + * @return Returns date if exists, otherwise `nil`. + */ +- (nullable NSDate *)heartbeatDateForTag:(NSString *)tag; + +/** + * Saves the date for the specified tag in the specified file. + * @return YES on success, NO otherwise. + */ +- (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/Environment/Public/GULSecureCoding.h b/!main project/Pods/GoogleUtilities/GoogleUtilities/Environment/Public/GULSecureCoding.h new file mode 100644 index 0000000..8484b39 --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/Environment/Public/GULSecureCoding.h @@ -0,0 +1,36 @@ +// Copyright 2019 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** The class wraps `NSKeyedArchiver` and `NSKeyedUnarchiver` API to provide a unified secure coding + * methods for iOS versions before and after 11. + */ +@interface GULSecureCoding : NSObject + ++ (nullable id)unarchivedObjectOfClasses:(NSSet *)classes + fromData:(NSData *)data + error:(NSError **)outError; + ++ (nullable id)unarchivedObjectOfClass:(Class)class + fromData:(NSData *)data + error:(NSError **)outError; + ++ (nullable NSData *)archivedDataWithRootObject:(id)object error:(NSError **)outError; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/Environment/third_party/GULAppEnvironmentUtil.h b/!main project/Pods/GoogleUtilities/GoogleUtilities/Environment/third_party/GULAppEnvironmentUtil.h new file mode 100644 index 0000000..d550264 --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/Environment/third_party/GULAppEnvironmentUtil.h @@ -0,0 +1,46 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@interface GULAppEnvironmentUtil : NSObject + +/// Indicates whether the app is from Apple Store or not. Returns NO if the app is on simulator, +/// development environment or sideloaded. ++ (BOOL)isFromAppStore; + +/// Indicates whether the app is a Testflight app. Returns YES if the app has sandbox receipt. +/// Returns NO otherwise. ++ (BOOL)isAppStoreReceiptSandbox; + +/// Indicates whether the app is on simulator or not at runtime depending on the device +/// architecture. ++ (BOOL)isSimulator; + +/// The current device model. Returns an empty string if device model cannot be retrieved. ++ (NSString *)deviceModel; + +/// The current operating system version. Returns an empty string if the system version cannot be +/// retrieved. ++ (NSString *)systemVersion; + +/// Indicates whether it is running inside an extension or an app. ++ (BOOL)isAppExtension; + +/// @return Returns @YES when is run on iOS version greater or equal to 7.0 ++ (BOOL)isIOS7OrHigher; + +@end diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/Environment/third_party/GULAppEnvironmentUtil.m b/!main project/Pods/GoogleUtilities/GoogleUtilities/Environment/third_party/GULAppEnvironmentUtil.m new file mode 100644 index 0000000..6442941 --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/Environment/third_party/GULAppEnvironmentUtil.m @@ -0,0 +1,263 @@ +// Copyright 2017 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "GoogleUtilities/Environment/third_party/GULAppEnvironmentUtil.h" + +#import +#import +#import +#import + +#if TARGET_OS_IOS +#import +#endif + +/// The encryption info struct and constants are missing from the iPhoneSimulator SDK, but not from +/// the iPhoneOS or Mac OS X SDKs. Since one doesn't ever ship a Simulator binary, we'll just +/// provide the definitions here. +#if TARGET_OS_SIMULATOR && !defined(LC_ENCRYPTION_INFO) +#define LC_ENCRYPTION_INFO 0x21 +struct encryption_info_command { + uint32_t cmd; + uint32_t cmdsize; + uint32_t cryptoff; + uint32_t cryptsize; + uint32_t cryptid; +}; +#endif + +@implementation GULAppEnvironmentUtil + +/// A key for the Info.plist to enable or disable checking if the App Store is running in a sandbox. +/// This will affect your data integrity when using Firebase Analytics, as it will disable some +/// necessary checks. +static NSString *const kFIRAppStoreReceiptURLCheckEnabledKey = + @"FirebaseAppStoreReceiptURLCheckEnabled"; + +/// The file name of the sandbox receipt. This is available on iOS >= 8.0 +static NSString *const kFIRAIdentitySandboxReceiptFileName = @"sandboxReceipt"; + +/// The following copyright from Landon J. Fuller applies to the isAppEncrypted function. +/// +/// Copyright (c) 2017 Landon J. Fuller +/// All rights reserved. +/// +/// Permission is hereby granted, free of charge, to any person obtaining a copy of this software +/// and associated documentation files (the "Software"), to deal in the Software without +/// restriction, including without limitation the rights to use, copy, modify, merge, publish, +/// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the +/// Software is furnished to do so, subject to the following conditions: +/// +/// The above copyright notice and this permission notice shall be included in all copies or +/// substantial portions of the Software. +/// +/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING +/// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +/// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +/// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +/// +/// Comment from iPhone Dev Wiki +/// Crack Prevention: +/// App Store binaries are signed by both their developer and Apple. This encrypts the binary so +/// that decryption keys are needed in order to make the binary readable. When iOS executes the +/// binary, the decryption keys are used to decrypt the binary into a readable state where it is +/// then loaded into memory and executed. iOS can tell the encryption status of a binary via the +/// cryptid structure member of LC_ENCRYPTION_INFO MachO load command. If cryptid is a non-zero +/// value then the binary is encrypted. +/// +/// 'Cracking' works by letting the kernel decrypt the binary then siphoning the decrypted data into +/// a new binary file, resigning, and repackaging. This will only work on jailbroken devices as +/// codesignature validation has been removed. Resigning takes place because while the codesignature +/// doesn't have to be valid thanks to the jailbreak, it does have to be in place unless you have +/// AppSync or similar to disable codesignature checks. +/// +/// More information at Landon Fuller's blog +static BOOL IsAppEncrypted() { + const struct mach_header *executableHeader = NULL; + for (uint32_t i = 0; i < _dyld_image_count(); i++) { + const struct mach_header *header = _dyld_get_image_header(i); + if (header && header->filetype == MH_EXECUTE) { + executableHeader = header; + break; + } + } + + if (!executableHeader) { + return NO; + } + + BOOL is64bit = (executableHeader->magic == MH_MAGIC_64); + uintptr_t cursor = (uintptr_t)executableHeader + + (is64bit ? sizeof(struct mach_header_64) : sizeof(struct mach_header)); + const struct segment_command *segmentCommand = NULL; + uint32_t i = 0; + + while (i++ < executableHeader->ncmds) { + segmentCommand = (struct segment_command *)cursor; + + if (!segmentCommand) { + continue; + } + + if ((!is64bit && segmentCommand->cmd == LC_ENCRYPTION_INFO) || + (is64bit && segmentCommand->cmd == LC_ENCRYPTION_INFO_64)) { + if (is64bit) { + struct encryption_info_command_64 *cryptCmd = + (struct encryption_info_command_64 *)segmentCommand; + return cryptCmd && cryptCmd->cryptid != 0; + } else { + struct encryption_info_command *cryptCmd = (struct encryption_info_command *)segmentCommand; + return cryptCmd && cryptCmd->cryptid != 0; + } + } + cursor += segmentCommand->cmdsize; + } + + return NO; +} + +static BOOL HasSCInfoFolder() { +#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH + NSString *bundlePath = [NSBundle mainBundle].bundlePath; + NSString *scInfoPath = [bundlePath stringByAppendingPathComponent:@"SC_Info"]; + return [[NSFileManager defaultManager] fileExistsAtPath:scInfoPath]; +#elif TARGET_OS_OSX + return NO; +#endif +} + +static BOOL HasEmbeddedMobileProvision() { +#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH + return [[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"].length > 0; +#elif TARGET_OS_OSX + return NO; +#endif +} + ++ (BOOL)isFromAppStore { + static dispatch_once_t isEncryptedOnce; + static BOOL isEncrypted = NO; + + dispatch_once(&isEncryptedOnce, ^{ + isEncrypted = IsAppEncrypted(); + }); + + if ([GULAppEnvironmentUtil isSimulator]) { + return NO; + } + + // If an app contain the sandboxReceipt file, it means its coming from TestFlight + // This must be checked before the SCInfo Folder check below since TestFlight apps may + // also have an SCInfo folder. + if ([GULAppEnvironmentUtil isAppStoreReceiptSandbox]) { + return NO; + } + + if (HasSCInfoFolder()) { + // When iTunes downloads a .ipa, it also gets a customized .sinf file which is added to the + // main SC_Info directory. + return YES; + } + + // For iOS >= 8.0, iTunesMetadata.plist is moved outside of the sandbox. Any attempt to read + // the iTunesMetadata.plist outside of the sandbox will be rejected by Apple. + // If the app does not contain the embedded.mobileprovision which is stripped out by Apple when + // the app is submitted to store, then it is highly likely that it is from Apple Store. + return isEncrypted && !HasEmbeddedMobileProvision(); +} + ++ (BOOL)isAppStoreReceiptSandbox { + // Since checking the App Store's receipt URL can be memory intensive, check the option in the + // Info.plist if developers opted out of this check. + id enableSandboxCheck = + [[NSBundle mainBundle] objectForInfoDictionaryKey:kFIRAppStoreReceiptURLCheckEnabledKey]; + if (enableSandboxCheck && [enableSandboxCheck isKindOfClass:[NSNumber class]] && + ![enableSandboxCheck boolValue]) { + return NO; + } +// The #else is for pre Xcode 9 where @available is not yet implemented. +#if __has_builtin(__builtin_available) + if (@available(iOS 7.0, *)) { +#else + if ([[UIDevice currentDevice].systemVersion integerValue] >= 7) { +#endif + NSURL *appStoreReceiptURL = [NSBundle mainBundle].appStoreReceiptURL; + NSString *appStoreReceiptFileName = appStoreReceiptURL.lastPathComponent; + return [appStoreReceiptFileName isEqualToString:kFIRAIdentitySandboxReceiptFileName]; + } + return NO; +} + ++ (BOOL)isSimulator { +#if TARGET_OS_IOS || TARGET_OS_TV + NSString *platform = [GULAppEnvironmentUtil deviceModel]; + return [platform isEqual:@"x86_64"] || [platform isEqual:@"i386"]; +#elif TARGET_OS_OSX + return NO; +#endif + return NO; +} + ++ (NSString *)deviceModel { + static dispatch_once_t once; + static NSString *deviceModel; + + dispatch_once(&once, ^{ + struct utsname systemInfo; + if (uname(&systemInfo) == 0) { + deviceModel = [NSString stringWithUTF8String:systemInfo.machine]; + } + }); + return deviceModel; +} + ++ (NSString *)systemVersion { +#if TARGET_OS_IOS + return [UIDevice currentDevice].systemVersion; +#elif TARGET_OS_OSX || TARGET_OS_TV || TARGET_OS_WATCH + // Assemble the systemVersion, excluding the patch version if it's 0. + NSOperatingSystemVersion osVersion = [NSProcessInfo processInfo].operatingSystemVersion; + NSMutableString *versionString = [[NSMutableString alloc] + initWithFormat:@"%ld.%ld", (long)osVersion.majorVersion, (long)osVersion.minorVersion]; + if (osVersion.patchVersion != 0) { + [versionString appendFormat:@".%ld", (long)osVersion.patchVersion]; + } + return versionString; +#endif +} + ++ (BOOL)isAppExtension { +#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH + // Documented by Apple + BOOL appExtension = [[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]; + return appExtension; +#elif TARGET_OS_OSX + return NO; +#endif +} + ++ (BOOL)isIOS7OrHigher { +#if __has_builtin(__builtin_available) + if (@available(iOS 7.0, *)) { +#else + if ([[UIDevice currentDevice].systemVersion integerValue] >= 7) { +#endif + return YES; + } + + return NO; +} + +@end diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/LICENSE b/!main project/Pods/GoogleUtilities/GoogleUtilities/LICENSE new file mode 100644 index 0000000..30a8f72 --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/LICENSE @@ -0,0 +1,247 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +================================================================================ + +The following copyright from Landon J. Fuller applies to the isAppEncrypted +function in Environment/third_party/GULAppEnvironmentUtil.m. + +Copyright (c) 2017 Landon J. Fuller +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Comment from +iPhone Dev Wiki +Crack Prevention: App Store binaries are signed by both their developer +and Apple. This encrypts the binary so that decryption keys are needed in order +to make the binary readable. When iOS executes the binary, the decryption keys +are used to decrypt the binary into a readable state where it is then loaded +into memory and executed. iOS can tell the encryption status of a binary via the +cryptid structure member of LC_ENCRYPTION_INFO MachO load command. If cryptid is +a non-zero value then the binary is encrypted. + +'Cracking' works by letting the kernel decrypt the binary then siphoning the +decrypted data into a new binary file, resigning, and repackaging. This will +only work on jailbroken devices as codesignature validation has been removed. +Resigning takes place because while the codesignature doesn't have to be valid +thanks to the jailbreak, it does have to be in place unless you have AppSync or +similar to disable codesignature checks. + +More information at Landon +Fuller's blog diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/Logger/GULLogger.m b/!main project/Pods/GoogleUtilities/GoogleUtilities/Logger/GULLogger.m new file mode 100644 index 0000000..0a512da --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/Logger/GULLogger.m @@ -0,0 +1,214 @@ +// Copyright 2018 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "GoogleUtilities/Logger/Private/GULLogger.h" + +#include + +#import +#import + +/// ASL client facility name used by GULLogger. +const char *kGULLoggerASLClientFacilityName = "com.google.utilities.logger"; + +static dispatch_once_t sGULLoggerOnceToken; + +static aslclient sGULLoggerClient; + +static dispatch_queue_t sGULClientQueue; + +static BOOL sGULLoggerDebugMode; + +static GULLoggerLevel sGULLoggerMaximumLevel; + +// Allow clients to register a version to include in the log. +static const char *sVersion = ""; + +static GULLoggerService kGULLoggerLogger = @"[GULLogger]"; + +#ifdef DEBUG +/// The regex pattern for the message code. +static NSString *const kMessageCodePattern = @"^I-[A-Z]{3}[0-9]{6}$"; +static NSRegularExpression *sMessageCodeRegex; +#endif + +void GULLoggerInitializeASL(void) { + dispatch_once(&sGULLoggerOnceToken, ^{ + NSInteger majorOSVersion = [[GULAppEnvironmentUtil systemVersion] integerValue]; + uint32_t aslOptions = ASL_OPT_STDERR; +#if TARGET_OS_SIMULATOR + // The iOS 11 simulator doesn't need the ASL_OPT_STDERR flag. + if (majorOSVersion >= 11) { + aslOptions = 0; + } +#else + // Devices running iOS 10 or higher don't need the ASL_OPT_STDERR flag. + if (majorOSVersion >= 10) { + aslOptions = 0; + } +#endif // TARGET_OS_SIMULATOR + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" // asl is deprecated + // Initialize the ASL client handle. + sGULLoggerClient = asl_open(NULL, kGULLoggerASLClientFacilityName, aslOptions); + sGULLoggerMaximumLevel = GULLoggerLevelNotice; + + // Set the filter used by system/device log. Initialize in default mode. + asl_set_filter(sGULLoggerClient, ASL_FILTER_MASK_UPTO(ASL_LEVEL_NOTICE)); + + sGULClientQueue = dispatch_queue_create("GULLoggingClientQueue", DISPATCH_QUEUE_SERIAL); + dispatch_set_target_queue(sGULClientQueue, + dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)); +#ifdef DEBUG + sMessageCodeRegex = [NSRegularExpression regularExpressionWithPattern:kMessageCodePattern + options:0 + error:NULL]; +#endif + }); +} + +void GULLoggerEnableSTDERR(void) { + asl_add_log_file(sGULLoggerClient, STDERR_FILENO); +} + +void GULLoggerForceDebug(void) { + // We should enable debug mode if we're not running from App Store. + if (![GULAppEnvironmentUtil isFromAppStore]) { + sGULLoggerDebugMode = YES; + GULSetLoggerLevel(GULLoggerLevelDebug); + } +} + +__attribute__((no_sanitize("thread"))) void GULSetLoggerLevel(GULLoggerLevel loggerLevel) { + if (loggerLevel < GULLoggerLevelMin || loggerLevel > GULLoggerLevelMax) { + GULLogError(kGULLoggerLogger, NO, @"I-COR000023", @"Invalid logger level, %ld", + (long)loggerLevel); + return; + } + GULLoggerInitializeASL(); + // We should not raise the logger level if we are running from App Store. + if (loggerLevel >= GULLoggerLevelNotice && [GULAppEnvironmentUtil isFromAppStore]) { + return; + } + + sGULLoggerMaximumLevel = loggerLevel; + dispatch_async(sGULClientQueue, ^{ + asl_set_filter(sGULLoggerClient, ASL_FILTER_MASK_UPTO(loggerLevel)); + }); +} + +/** + * Check if the level is high enough to be loggable. + */ +__attribute__((no_sanitize("thread"))) BOOL GULIsLoggableLevel(GULLoggerLevel loggerLevel) { + GULLoggerInitializeASL(); + if (sGULLoggerDebugMode) { + return YES; + } + return (BOOL)(loggerLevel <= sGULLoggerMaximumLevel); +} + +#ifdef DEBUG +void GULResetLogger() { + sGULLoggerOnceToken = 0; +} + +aslclient getGULLoggerClient() { + return sGULLoggerClient; +} + +dispatch_queue_t getGULClientQueue() { + return sGULClientQueue; +} + +BOOL getGULLoggerDebugMode() { + return sGULLoggerDebugMode; +} +#endif + +void GULLoggerRegisterVersion(const char *version) { + sVersion = version; +} + +void GULLogBasic(GULLoggerLevel level, + GULLoggerService service, + BOOL forceLog, + NSString *messageCode, + NSString *message, + va_list args_ptr) { + GULLoggerInitializeASL(); + if (!(level <= sGULLoggerMaximumLevel || sGULLoggerDebugMode || forceLog)) { + return; + } + +#ifdef DEBUG + NSCAssert(messageCode.length == 11, @"Incorrect message code length."); + NSRange messageCodeRange = NSMakeRange(0, messageCode.length); + NSUInteger numberOfMatches = [sMessageCodeRegex numberOfMatchesInString:messageCode + options:0 + range:messageCodeRange]; + NSCAssert(numberOfMatches == 1, @"Incorrect message code format."); +#endif + NSString *logMsg; + if (args_ptr == NULL) { + logMsg = message; + } else { + logMsg = [[NSString alloc] initWithFormat:message arguments:args_ptr]; + } + logMsg = [NSString stringWithFormat:@"%s - %@[%@] %@", sVersion, service, messageCode, logMsg]; + dispatch_async(sGULClientQueue, ^{ + asl_log(sGULLoggerClient, NULL, (int)level, "%s", logMsg.UTF8String); + }); +} +#pragma clang diagnostic pop + +/** + * Generates the logging functions using macros. + * + * Calling GULLogError({service}, @"I-XYZ000001", @"Configure %@ failed.", @"blah") shows: + * yyyy-mm-dd hh:mm:ss.SSS sender[PID] [{service}][I-XYZ000001] Configure blah failed. + * Calling GULLogDebug({service}, @"I-XYZ000001", @"Configure succeed.") shows: + * yyyy-mm-dd hh:mm:ss.SSS sender[PID] [{service}][I-XYZ000001] Configure succeed. + */ +#define GUL_LOGGING_FUNCTION(level) \ + void GULLog##level(GULLoggerService service, BOOL force, NSString *messageCode, \ + NSString *message, ...) { \ + va_list args_ptr; \ + va_start(args_ptr, message); \ + GULLogBasic(GULLoggerLevel##level, service, force, messageCode, message, args_ptr); \ + va_end(args_ptr); \ + } + +GUL_LOGGING_FUNCTION(Error) +GUL_LOGGING_FUNCTION(Warning) +GUL_LOGGING_FUNCTION(Notice) +GUL_LOGGING_FUNCTION(Info) +GUL_LOGGING_FUNCTION(Debug) + +#undef GUL_MAKE_LOGGER + +#pragma mark - GULLoggerWrapper + +@implementation GULLoggerWrapper + ++ (void)logWithLevel:(GULLoggerLevel)level + withService:(GULLoggerService)service + withCode:(NSString *)messageCode + withMessage:(NSString *)message + withArgs:(va_list)args { + GULLogBasic(level, service, NO, messageCode, message, args); +} + +@end diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/Logger/Private/GULLogger.h b/!main project/Pods/GoogleUtilities/GoogleUtilities/Logger/Private/GULLogger.h new file mode 100644 index 0000000..ff42576 --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/Logger/Private/GULLogger.h @@ -0,0 +1,159 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * The services used in the logger. + */ +typedef NSString *const GULLoggerService; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * Initialize GULLogger. + */ +extern void GULLoggerInitializeASL(void); + +/** + * Override log level to Debug. + */ +void GULLoggerForceDebug(void); + +/** + * Turn on logging to STDERR. + */ +extern void GULLoggerEnableSTDERR(void); + +/** + * Changes the default logging level of GULLoggerLevelNotice to a user-specified level. + * The default level cannot be set above GULLoggerLevelNotice if the app is running from App Store. + * (required) log level (one of the GULLoggerLevel enum values). + */ +extern void GULSetLoggerLevel(GULLoggerLevel loggerLevel); + +/** + * Checks if the specified logger level is loggable given the current settings. + * (required) log level (one of the GULLoggerLevel enum values). + */ +extern BOOL GULIsLoggableLevel(GULLoggerLevel loggerLevel); + +/** + * Register version to include in logs. + * (required) version + */ +extern void GULLoggerRegisterVersion(const char *version); + +/** + * Logs a message to the Xcode console and the device log. If running from AppStore, will + * not log any messages with a level higher than GULLoggerLevelNotice to avoid log spamming. + * (required) log level (one of the GULLoggerLevel enum values). + * (required) service name of type GULLoggerService. + * (required) message code starting with "I-" which means iOS, followed by a capitalized + * three-character service identifier and a six digit integer message ID that is unique + * within the service. + * An example of the message code is @"I-COR000001". + * (required) message string which can be a format string. + * (optional) variable arguments list obtained from calling va_start, used when message is a format + * string. + */ +extern void GULLogBasic(GULLoggerLevel level, + GULLoggerService service, + BOOL forceLog, + NSString *messageCode, + NSString *message, +// On 64-bit simulators, va_list is not a pointer, so cannot be marked nullable +// See: http://stackoverflow.com/q/29095469 +#if __LP64__ && TARGET_OS_SIMULATOR || TARGET_OS_OSX + va_list args_ptr +#else + va_list _Nullable args_ptr +#endif +); + +/** + * The following functions accept the following parameters in order: + * (required) service name of type GULLoggerService. + * (required) message code starting from "I-" which means iOS, followed by a capitalized + * three-character service identifier and a six digit integer message ID that is unique + * within the service. + * An example of the message code is @"I-COR000001". + * See go/firebase-log-proposal for details. + * (required) message string which can be a format string. + * (optional) the list of arguments to substitute into the format string. + * Example usage: + * GULLogError(kGULLoggerCore, @"I-COR000001", @"Configuration of %@ failed.", app.name); + */ +extern void GULLogError(GULLoggerService service, + BOOL force, + NSString *messageCode, + NSString *message, + ...) NS_FORMAT_FUNCTION(4, 5); +extern void GULLogWarning(GULLoggerService service, + BOOL force, + NSString *messageCode, + NSString *message, + ...) NS_FORMAT_FUNCTION(4, 5); +extern void GULLogNotice(GULLoggerService service, + BOOL force, + NSString *messageCode, + NSString *message, + ...) NS_FORMAT_FUNCTION(4, 5); +extern void GULLogInfo(GULLoggerService service, + BOOL force, + NSString *messageCode, + NSString *message, + ...) NS_FORMAT_FUNCTION(4, 5); +extern void GULLogDebug(GULLoggerService service, + BOOL force, + NSString *messageCode, + NSString *message, + ...) NS_FORMAT_FUNCTION(4, 5); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +@interface GULLoggerWrapper : NSObject + +/** + * Objective-C wrapper for GULLogBasic to allow weak linking to GULLogger + * (required) log level (one of the GULLoggerLevel enum values). + * (required) service name of type GULLoggerService. + * (required) message code starting with "I-" which means iOS, followed by a capitalized + * three-character service identifier and a six digit integer message ID that is unique + * within the service. + * An example of the message code is @"I-COR000001". + * (required) message string which can be a format string. + * (optional) variable arguments list obtained from calling va_start, used when message is a format + * string. + */ + ++ (void)logWithLevel:(GULLoggerLevel)level + withService:(GULLoggerService)service + withCode:(NSString *)messageCode + withMessage:(NSString *)message + withArgs:(va_list)args; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/Logger/Public/GULLoggerLevel.h b/!main project/Pods/GoogleUtilities/GoogleUtilities/Logger/Public/GULLoggerLevel.h new file mode 100644 index 0000000..f0ee435 --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/Logger/Public/GULLoggerLevel.h @@ -0,0 +1,37 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +/** + * The log levels used by internal logging. + */ +typedef NS_ENUM(NSInteger, GULLoggerLevel) { + /** Error level, matches ASL_LEVEL_ERR. */ + GULLoggerLevelError = 3, + /** Warning level, matches ASL_LEVEL_WARNING. */ + GULLoggerLevelWarning = 4, + /** Notice level, matches ASL_LEVEL_NOTICE. */ + GULLoggerLevelNotice = 5, + /** Info level, matches ASL_LEVEL_INFO. */ + GULLoggerLevelInfo = 6, + /** Debug level, matches ASL_LEVEL_DEBUG. */ + GULLoggerLevelDebug = 7, + /** Minimum log level. */ + GULLoggerLevelMin = GULLoggerLevelError, + /** Maximum log level. */ + GULLoggerLevelMax = GULLoggerLevelDebug +} NS_SWIFT_NAME(GoogleLoggerLevel); diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/MethodSwizzler/GULSwizzler.m b/!main project/Pods/GoogleUtilities/GoogleUtilities/MethodSwizzler/GULSwizzler.m new file mode 100644 index 0000000..27d48bb --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/MethodSwizzler/GULSwizzler.m @@ -0,0 +1,153 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "GoogleUtilities/MethodSwizzler/Private/GULSwizzler.h" + +#import + +#ifdef DEBUG +#import +#import "GoogleUtilities/Common/GULLoggerCodes.h" + +static GULLoggerService kGULLoggerSwizzler = @"[GoogleUtilities/MethodSwizzler]"; +#endif + +dispatch_queue_t GetGULSwizzlingQueue(void) { + static dispatch_queue_t queue; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + queue = dispatch_queue_create("com.google.GULSwizzler", DISPATCH_QUEUE_SERIAL); + }); + return queue; +} + +@implementation GULSwizzler + ++ (void)swizzleClass:(Class)aClass + selector:(SEL)selector + isClassSelector:(BOOL)isClassSelector + withBlock:(nullable id)block { + dispatch_sync(GetGULSwizzlingQueue(), ^{ + NSAssert(selector, @"The selector cannot be NULL"); + NSAssert(aClass, @"The class cannot be Nil"); + Class resolvedClass = aClass; + Method method = nil; + if (isClassSelector) { + method = class_getClassMethod(aClass, selector); + resolvedClass = object_getClass(aClass); + } else { + method = class_getInstanceMethod(aClass, selector); + } + NSAssert(method, @"You're attempting to swizzle a method that doesn't exist. (%@, %@)", + NSStringFromClass(resolvedClass), NSStringFromSelector(selector)); + IMP newImp = imp_implementationWithBlock(block); +#ifdef DEBUG + IMP currentImp = class_getMethodImplementation(resolvedClass, selector); + Class class = NSClassFromString(@"GULSwizzlingCache"); + if (class) { + SEL cacheSelector = NSSelectorFromString(@"cacheCurrentIMP:forNewIMP:forClass:withSelector:"); + NSMethodSignature *methodSignature = [class methodSignatureForSelector:cacheSelector]; + if (methodSignature != nil) { + NSInvocation *inv = [NSInvocation invocationWithMethodSignature:methodSignature]; + [inv setSelector:cacheSelector]; + [inv setTarget:class]; + [inv setArgument:&(currentImp) atIndex:2]; + [inv setArgument:&(newImp) atIndex:3]; + [inv setArgument:&(resolvedClass) atIndex:4]; + [inv setArgument:(void *_Nonnull) & (selector) atIndex:5]; + [inv invoke]; + } + } +#endif + + const char *typeEncoding = method_getTypeEncoding(method); + __unused IMP originalImpOfClass = + class_replaceMethod(resolvedClass, selector, newImp, typeEncoding); + +#ifdef DEBUG + // If !originalImpOfClass, then the IMP came from a superclass. + if (originalImpOfClass) { + SEL selector = NSSelectorFromString(@"originalIMPOfCurrentIMP:"); + NSMethodSignature *methodSignature = [class methodSignatureForSelector:selector]; + if (methodSignature != nil) { + NSInvocation *inv = [NSInvocation invocationWithMethodSignature:methodSignature]; + [inv setSelector:selector]; + [inv setTarget:class]; + [inv setArgument:&(currentImp) atIndex:2]; + [inv invoke]; + IMP testOriginal; + [inv getReturnValue:&testOriginal]; + if (originalImpOfClass != testOriginal) { + GULLogWarning(kGULLoggerSwizzler, NO, + [NSString stringWithFormat:@"I-SWZ%06ld", + (long)kGULSwizzlerMessageCodeMethodSwizzling000], + @"Swizzling class: %@ SEL:%@ after it has been previously been swizzled.", + NSStringFromClass(resolvedClass), NSStringFromSelector(selector)); + } + } + } +#endif + }); +} + ++ (nullable IMP)currentImplementationForClass:(Class)aClass + selector:(SEL)selector + isClassSelector:(BOOL)isClassSelector { + NSAssert(selector, @"The selector cannot be NULL"); + NSAssert(aClass, @"The class cannot be Nil"); + if (selector == NULL || aClass == nil) { + return nil; + } + __block IMP currentIMP = nil; + dispatch_sync(GetGULSwizzlingQueue(), ^{ + Method method = nil; + if (isClassSelector) { + method = class_getClassMethod(aClass, selector); + } else { + method = class_getInstanceMethod(aClass, selector); + } + NSAssert(method, @"The Method for this class/selector combo doesn't exist (%@, %@).", + NSStringFromClass(aClass), NSStringFromSelector(selector)); + if (method == nil) { + return; + } + currentIMP = method_getImplementation(method); + NSAssert(currentIMP, @"The IMP for this class/selector combo doesn't exist (%@, %@).", + NSStringFromClass(aClass), NSStringFromSelector(selector)); + }); + return currentIMP; +} + ++ (BOOL)selector:(SEL)selector existsInClass:(Class)aClass isClassSelector:(BOOL)isClassSelector { + Method method = isClassSelector ? class_getClassMethod(aClass, selector) + : class_getInstanceMethod(aClass, selector); + return method != nil; +} + ++ (NSArray *)ivarObjectsForObject:(id)object { + NSMutableArray *array = [NSMutableArray array]; + unsigned int count; + Ivar *vars = class_copyIvarList([object class], &count); + for (NSUInteger i = 0; i < count; i++) { + const char *typeEncoding = ivar_getTypeEncoding(vars[i]); + // Check to see if the ivar is an object. + if (strncmp(typeEncoding, "@", 1) == 0) { + id ivarObject = object_getIvar(object, vars[i]); + [array addObject:ivarObject]; + } + } + free(vars); + return array; +} +@end diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/MethodSwizzler/Private/GULOriginalIMPConvenienceMacros.h b/!main project/Pods/GoogleUtilities/GoogleUtilities/MethodSwizzler/Private/GULOriginalIMPConvenienceMacros.h new file mode 100644 index 0000000..a33262a --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/MethodSwizzler/Private/GULOriginalIMPConvenienceMacros.h @@ -0,0 +1,207 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * GULOriginalIMPConvenienceMacros.h + * + * This header contains convenience macros for invoking the original IMP of a swizzled method. + */ + +/** + * Invokes original IMP when the original selector takes no arguments. + * + * @param __receivingObject The object on which the IMP is invoked. + * @param __swizzledSEL The selector used for swizzling. + * @param __returnType The return type of the original implementation. + * @param __originalIMP The original IMP. + */ +#define GUL_INVOKE_ORIGINAL_IMP0(__receivingObject, __swizzledSEL, __returnType, __originalIMP) \ + ((__returnType(*)(id, SEL))__originalIMP)(__receivingObject, __swizzledSEL) + +/** + * Invokes original IMP when the original selector takes 1 argument. + * + * @param __receivingObject The object on which the IMP is invoked. + * @param __swizzledSEL The selector used for swizzling. + * @param __returnType The return type of the original implementation. + * @param __originalIMP The original IMP. + * @param __arg1 The first argument. + */ +#define GUL_INVOKE_ORIGINAL_IMP1(__receivingObject, __swizzledSEL, __returnType, __originalIMP, \ + __arg1) \ + ((__returnType(*)(id, SEL, __typeof__(__arg1)))__originalIMP)(__receivingObject, __swizzledSEL, \ + __arg1) + +/** + * Invokes original IMP when the original selector takes 2 arguments. + * + * @param __receivingObject The object on which the IMP is invoked. + * @param __swizzledSEL The selector used for swizzling. + * @param __returnType The return type of the original implementation. + * @param __originalIMP The original IMP. + * @param __arg1 The first argument. + * @param __arg2 The second argument. + */ +#define GUL_INVOKE_ORIGINAL_IMP2(__receivingObject, __swizzledSEL, __returnType, __originalIMP, \ + __arg1, __arg2) \ + ((__returnType(*)(id, SEL, __typeof__(__arg1), __typeof__(__arg2)))__originalIMP)( \ + __receivingObject, __swizzledSEL, __arg1, __arg2) + +/** + * Invokes original IMP when the original selector takes 3 arguments. + * + * @param __receivingObject The object on which the IMP is invoked. + * @param __swizzledSEL The selector used for swizzling. + * @param __returnType The return type of the original implementation. + * @param __originalIMP The original IMP. + * @param __arg1 The first argument. + * @param __arg2 The second argument. + * @param __arg3 The third argument. + */ +#define GUL_INVOKE_ORIGINAL_IMP3(__receivingObject, __swizzledSEL, __returnType, __originalIMP, \ + __arg1, __arg2, __arg3) \ + ((__returnType(*)(id, SEL, __typeof__(__arg1), __typeof__(__arg2), \ + __typeof__(__arg3)))__originalIMP)(__receivingObject, __swizzledSEL, __arg1, \ + __arg2, __arg3) + +/** + * Invokes original IMP when the original selector takes 4 arguments. + * + * @param __receivingObject The object on which the IMP is invoked. + * @param __swizzledSEL The selector used for swizzling. + * @param __returnType The return type of the original implementation. + * @param __originalIMP The original IMP. + * @param __arg1 The first argument. + * @param __arg2 The second argument. + * @param __arg3 The third argument. + * @param __arg4 The fourth argument. + */ +#define GUL_INVOKE_ORIGINAL_IMP4(__receivingObject, __swizzledSEL, __returnType, __originalIMP, \ + __arg1, __arg2, __arg3, __arg4) \ + ((__returnType(*)(id, SEL, __typeof__(__arg1), __typeof__(__arg2), __typeof__(__arg3), \ + __typeof__(__arg4)))__originalIMP)(__receivingObject, __swizzledSEL, __arg1, \ + __arg2, __arg3, __arg4) + +/** + * Invokes original IMP when the original selector takes 5 arguments. + * + * @param __receivingObject The object on which the IMP is invoked. + * @param __swizzledSEL The selector used for swizzling. + * @param __returnType The return type of the original implementation. + * @param __originalIMP The original IMP. + * @param __arg1 The first argument. + * @param __arg2 The second argument. + * @param __arg3 The third argument. + * @param __arg4 The fourth argument. + * @param __arg5 The fifth argument. + */ +#define GUL_INVOKE_ORIGINAL_IMP5(__receivingObject, __swizzledSEL, __returnType, __originalIMP, \ + __arg1, __arg2, __arg3, __arg4, __arg5) \ + ((__returnType(*)(id, SEL, __typeof__(__arg1), __typeof__(__arg2), __typeof__(__arg3), \ + __typeof__(__arg4), __typeof__(__arg5)))__originalIMP)( \ + __receivingObject, __swizzledSEL, __arg1, __arg2, __arg3, __arg4, __arg5) + +/** + * Invokes original IMP when the original selector takes 6 arguments. + * + * @param __receivingObject The object on which the IMP is invoked. + * @param __swizzledSEL The selector used for swizzling. + * @param __returnType The return type of the original implementation. + * @param __originalIMP The original IMP. + * @param __arg1 The first argument. + * @param __arg2 The second argument. + * @param __arg3 The third argument. + * @param __arg4 The fourth argument. + * @param __arg5 The fifth argument. + * @param __arg6 The sixth argument. + */ +#define GUL_INVOKE_ORIGINAL_IMP6(__receivingObject, __swizzledSEL, __returnType, __originalIMP, \ + __arg1, __arg2, __arg3, __arg4, __arg5, __arg6) \ + ((__returnType(*)(id, SEL, __typeof__(__arg1), __typeof__(__arg2), __typeof__(__arg3), \ + __typeof__(__arg4), __typeof__(__arg5), __typeof__(__arg6)))__originalIMP)( \ + __receivingObject, __swizzledSEL, __arg1, __arg2, __arg3, __arg4, __arg5, __arg6) + +/** + * Invokes original IMP when the original selector takes 7 arguments. + * + * @param __receivingObject The object on which the IMP is invoked. + * @param __swizzledSEL The selector used for swizzling. + * @param __returnType The return type of the original implementation. + * @param __originalIMP The original IMP. + * @param __arg1 The first argument. + * @param __arg2 The second argument. + * @param __arg3 The third argument. + * @param __arg4 The fourth argument. + * @param __arg5 The fifth argument. + * @param __arg6 The sixth argument. + * @param __arg7 The seventh argument. + */ +#define GUL_INVOKE_ORIGINAL_IMP7(__receivingObject, __swizzledSEL, __returnType, __originalIMP, \ + __arg1, __arg2, __arg3, __arg4, __arg5, __arg6, __arg7) \ + ((__returnType(*)(id, SEL, __typeof__(__arg1), __typeof__(__arg2), __typeof__(__arg3), \ + __typeof__(__arg4), __typeof__(__arg5), __typeof__(__arg6), \ + __typeof__(__arg7)))__originalIMP)( \ + __receivingObject, __swizzledSEL, __arg1, __arg2, __arg3, __arg4, __arg5, __arg6, __arg7) + +/** + * Invokes original IMP when the original selector takes 8 arguments. + * + * @param __receivingObject The object on which the IMP is invoked. + * @param __swizzledSEL The selector used for swizzling. + * @param __returnType The return type of the original implementation. + * @param __originalIMP The original IMP. + * @param __arg1 The first argument. + * @param __arg2 The second argument. + * @param __arg3 The third argument. + * @param __arg4 The fourth argument. + * @param __arg5 The fifth argument. + * @param __arg6 The sixth argument. + * @param __arg7 The seventh argument. + * @param __arg8 The eighth argument. + */ +#define GUL_INVOKE_ORIGINAL_IMP8(__receivingObject, __swizzledSEL, __returnType, __originalIMP, \ + __arg1, __arg2, __arg3, __arg4, __arg5, __arg6, __arg7, __arg8) \ + ((__returnType(*)(id, SEL, __typeof__(__arg1), __typeof__(__arg2), __typeof__(__arg3), \ + __typeof__(__arg4), __typeof__(__arg5), __typeof__(__arg6), \ + __typeof__(__arg7), __typeof__(__arg8)))__originalIMP)( \ + __receivingObject, __swizzledSEL, __arg1, __arg2, __arg3, __arg4, __arg5, __arg6, __arg7, \ + __arg8) + +/** + * Invokes original IMP when the original selector takes 9 arguments. + * + * @param __receivingObject The object on which the IMP is invoked. + * @param __swizzledSEL The selector used for swizzling. + * @param __returnType The return type of the original implementation. + * @param __originalIMP The original IMP. + * @param __arg1 The first argument. + * @param __arg2 The second argument. + * @param __arg3 The third argument. + * @param __arg4 The fourth argument. + * @param __arg5 The fifth argument. + * @param __arg6 The sixth argument. + * @param __arg7 The seventh argument. + * @param __arg8 The eighth argument. + * @param __arg9 The ninth argument. + */ +#define GUL_INVOKE_ORIGINAL_IMP9(__receivingObject, __swizzledSEL, __returnType, __originalIMP, \ + __arg1, __arg2, __arg3, __arg4, __arg5, __arg6, __arg7, __arg8, \ + __arg9) \ + ((__returnType(*)(id, SEL, __typeof__(__arg1), __typeof__(__arg2), __typeof__(__arg3), \ + __typeof__(__arg4), __typeof__(__arg5), __typeof__(__arg6), \ + __typeof__(__arg7), __typeof__(__arg8), __typeof__(__arg9)))__originalIMP)( \ + __receivingObject, __swizzledSEL, __arg1, __arg2, __arg3, __arg4, __arg5, __arg6, __arg7, \ + __arg8, __arg9) diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/MethodSwizzler/Private/GULSwizzler.h b/!main project/Pods/GoogleUtilities/GoogleUtilities/MethodSwizzler/Private/GULSwizzler.h new file mode 100644 index 0000000..26949c8 --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/MethodSwizzler/Private/GULSwizzler.h @@ -0,0 +1,71 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** This class handles the runtime manipulation necessary to instrument selectors. It stores the + * classes and selectors that have been swizzled, and runs all operations on its own queue. + */ +@interface GULSwizzler : NSObject + +/** Manipulates the Objective-C runtime to replace the original IMP with the supplied block. + * + * @param aClass The class to swizzle. + * @param selector The selector of the class to swizzle. + * @param isClassSelector A BOOL specifying whether the selector is a class or instance selector. + * @param block The block that replaces the original IMP. + */ ++ (void)swizzleClass:(Class)aClass + selector:(SEL)selector + isClassSelector:(BOOL)isClassSelector + withBlock:(nullable id)block; + +/** Returns the current IMP for the given class and selector. + * + * @param aClass The class to use. + * @param selector The selector to find the implementation of. + * @param isClassSelector A BOOL specifying whether the selector is a class or instance selector. + * @return The implementation of the selector in the runtime. + */ ++ (nullable IMP)currentImplementationForClass:(Class)aClass + selector:(SEL)selector + isClassSelector:(BOOL)isClassSelector; + +/** Checks the runtime to see if a selector exists on a class. If a property is declared as + * @dynamic, we have a reverse swizzling situation, where the implementation of a method exists + * only in concrete subclasses, and NOT in the superclass. We can detect that situation using + * this helper method. Similarly, we can detect situations where a class doesn't implement a + * protocol method. + * + * @param selector The selector to check for. + * @param aClass The class to check. + * @param isClassSelector A BOOL specifying whether the selector is a class or instance selector. + * @return YES if the method was found in this selector/class combination, NO otherwise. + */ ++ (BOOL)selector:(SEL)selector existsInClass:(Class)aClass isClassSelector:(BOOL)isClassSelector; + +/** Returns a list of all Objective-C (and not primitive) ivars contained by the given object. + * + * @param object The object whose ivars will be iterated. + * @return The list of ivar objects. + */ ++ (NSArray *)ivarObjectsForObject:(id)object; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/NSData+zlib/GULNSData+zlib.h b/!main project/Pods/GoogleUtilities/GoogleUtilities/NSData+zlib/GULNSData+zlib.h new file mode 100644 index 0000000..36f94a7 --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/NSData+zlib/GULNSData+zlib.h @@ -0,0 +1,49 @@ +// Copyright 2018 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +/// This is a copy of Google Toolbox for Mac library to avoid creating an extra framework. + +// NOTE: For 64bit, none of these apis handle input sizes >32bits, they will return nil when given +// such data. To handle data of that size you really should be streaming it rather then doing it all +// in memory. + +@interface NSData (GULGzip) + +/// Returns an data as the result of decompressing the payload of |data|.The data to decompress must +/// be a gzipped payloads. ++ (NSData *)gul_dataByInflatingGzippedData:(NSData *)data error:(NSError **)error; + +/// Returns an compressed data with the result of gzipping the payload of |data|. Uses the default +/// compression level. ++ (NSData *)gul_dataByGzippingData:(NSData *)data error:(NSError **)error; + +FOUNDATION_EXPORT NSString *const GULNSDataZlibErrorDomain; +FOUNDATION_EXPORT NSString *const GULNSDataZlibErrorKey; // NSNumber +FOUNDATION_EXPORT NSString *const GULNSDataZlibRemainingBytesKey; // NSNumber + +typedef NS_ENUM(NSInteger, GULNSDataZlibError) { + GULNSDataZlibErrorGreaterThan32BitsToCompress = 1024, + // An internal zlib error. + // GULNSDataZlibErrorKey will contain the error value. + // NSLocalizedDescriptionKey may contain an error string from zlib. + // Look in zlib.h for list of errors. + GULNSDataZlibErrorInternal, + // There was left over data in the buffer that was not used. + // GULNSDataZlibRemainingBytesKey will contain number of remaining bytes. + GULNSDataZlibErrorDataRemaining +}; + +@end diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/NSData+zlib/GULNSData+zlib.m b/!main project/Pods/GoogleUtilities/GoogleUtilities/NSData+zlib/GULNSData+zlib.m new file mode 100644 index 0000000..5a77bb8 --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/NSData+zlib/GULNSData+zlib.m @@ -0,0 +1,207 @@ +// Copyright 2018 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "GoogleUtilities/NSData+zlib/GULNSData+zlib.h" + +#import + +#define kChunkSize 1024 +#define Z_DEFAULT_COMPRESSION (-1) + +NSString *const GULNSDataZlibErrorDomain = @"com.google.GULNSDataZlibErrorDomain"; +NSString *const GULNSDataZlibErrorKey = @"GULNSDataZlibErrorKey"; +NSString *const GULNSDataZlibRemainingBytesKey = @"GULNSDataZlibRemainingBytesKey"; + +@implementation NSData (GULGzip) + ++ (NSData *)gul_dataByInflatingGzippedData:(NSData *)data error:(NSError **)error { + const void *bytes = [data bytes]; + NSUInteger length = [data length]; + if (!bytes || !length) { + return nil; + } + +#if defined(__LP64__) && __LP64__ + // Don't support > 32bit length for 64 bit, see note in header. + if (length > UINT_MAX) { + return nil; + } +#endif + + z_stream strm; + bzero(&strm, sizeof(z_stream)); + + // Setup the input. + strm.avail_in = (unsigned int)length; + strm.next_in = (unsigned char *)bytes; + + int windowBits = 15; // 15 to enable any window size + windowBits += 32; // and +32 to enable zlib or gzip header detection. + + int retCode; + if ((retCode = inflateInit2(&strm, windowBits)) != Z_OK) { + if (error) { + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:retCode] + forKey:GULNSDataZlibErrorKey]; + *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain + code:GULNSDataZlibErrorInternal + userInfo:userInfo]; + } + return nil; + } + + // Hint the size at 4x the input size. + NSMutableData *result = [NSMutableData dataWithCapacity:(length * 4)]; + unsigned char output[kChunkSize]; + + // Loop to collect the data. + do { + // Update what we're passing in. + strm.avail_out = kChunkSize; + strm.next_out = output; + retCode = inflate(&strm, Z_NO_FLUSH); + if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) { + if (error) { + NSMutableDictionary *userInfo = + [NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:retCode] + forKey:GULNSDataZlibErrorKey]; + if (strm.msg) { + NSString *message = [NSString stringWithUTF8String:strm.msg]; + if (message) { + [userInfo setObject:message forKey:NSLocalizedDescriptionKey]; + } + } + *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain + code:GULNSDataZlibErrorInternal + userInfo:userInfo]; + } + inflateEnd(&strm); + return nil; + } + // Collect what we got. + unsigned gotBack = kChunkSize - strm.avail_out; + if (gotBack > 0) { + [result appendBytes:output length:gotBack]; + } + + } while (retCode == Z_OK); + + // Make sure there wasn't more data tacked onto the end of a valid compressed stream. + if (strm.avail_in != 0) { + if (error) { + NSDictionary *userInfo = + [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedInt:strm.avail_in] + forKey:GULNSDataZlibRemainingBytesKey]; + *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain + code:GULNSDataZlibErrorDataRemaining + userInfo:userInfo]; + } + result = nil; + } + // The only way out of the loop was by hitting the end of the stream. + NSAssert(retCode == Z_STREAM_END, + @"Thought we finished inflate w/o getting a result of stream end, code %d", retCode); + + // Clean up. + inflateEnd(&strm); + + return result; +} + ++ (NSData *)gul_dataByGzippingData:(NSData *)data error:(NSError **)error { + const void *bytes = [data bytes]; + NSUInteger length = [data length]; + + int level = Z_DEFAULT_COMPRESSION; + if (!bytes || !length) { + return nil; + } + +#if defined(__LP64__) && __LP64__ + // Don't support > 32bit length for 64 bit, see note in header. + if (length > UINT_MAX) { + if (error) { + *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain + code:GULNSDataZlibErrorGreaterThan32BitsToCompress + userInfo:nil]; + } + return nil; + } +#endif + + z_stream strm; + bzero(&strm, sizeof(z_stream)); + + int memLevel = 8; // Default. + int windowBits = 15 + 16; // Enable gzip header instead of zlib header. + + int retCode; + if ((retCode = deflateInit2(&strm, level, Z_DEFLATED, windowBits, memLevel, + Z_DEFAULT_STRATEGY)) != Z_OK) { + if (error) { + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:retCode] + forKey:GULNSDataZlibErrorKey]; + *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain + code:GULNSDataZlibErrorInternal + userInfo:userInfo]; + } + return nil; + } + + // Hint the size at 1/4 the input size. + NSMutableData *result = [NSMutableData dataWithCapacity:(length / 4)]; + unsigned char output[kChunkSize]; + + // Setup the input. + strm.avail_in = (unsigned int)length; + strm.next_in = (unsigned char *)bytes; + + // Collect the data. + do { + // update what we're passing in + strm.avail_out = kChunkSize; + strm.next_out = output; + retCode = deflate(&strm, Z_FINISH); + if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) { + if (error) { + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:retCode] + forKey:GULNSDataZlibErrorKey]; + *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain + code:GULNSDataZlibErrorInternal + userInfo:userInfo]; + } + deflateEnd(&strm); + return nil; + } + // Collect what we got. + unsigned gotBack = kChunkSize - strm.avail_out; + if (gotBack > 0) { + [result appendBytes:output length:gotBack]; + } + + } while (retCode == Z_OK); + + // If the loop exits, it used all input and the stream ended. + NSAssert(strm.avail_in == 0, + @"Should have finished deflating without using all input, %u bytes left", strm.avail_in); + NSAssert(retCode == Z_STREAM_END, + @"thought we finished deflate w/o getting a result of stream end, code %d", retCode); + + // Clean up. + deflateEnd(&strm); + + return result; +} + +@end diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/GULMutableDictionary.m b/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/GULMutableDictionary.m new file mode 100644 index 0000000..4389660 --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/GULMutableDictionary.m @@ -0,0 +1,101 @@ +// Copyright 2017 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "GoogleUtilities/Network/Private/GULMutableDictionary.h" + +@implementation GULMutableDictionary { + /// The mutable dictionary. + NSMutableDictionary *_objects; + + /// Serial synchronization queue. All reads should use dispatch_sync, while writes use + /// dispatch_async. + dispatch_queue_t _queue; +} + +- (instancetype)init { + self = [super init]; + + if (self) { + _objects = [[NSMutableDictionary alloc] init]; + _queue = dispatch_queue_create("GULMutableDictionary", DISPATCH_QUEUE_SERIAL); + } + + return self; +} + +- (NSString *)description { + __block NSString *description; + dispatch_sync(_queue, ^{ + description = self->_objects.description; + }); + return description; +} + +- (id)objectForKey:(id)key { + __block id object; + dispatch_sync(_queue, ^{ + object = [self->_objects objectForKey:key]; + }); + return object; +} + +- (void)setObject:(id)object forKey:(id)key { + dispatch_async(_queue, ^{ + [self->_objects setObject:object forKey:key]; + }); +} + +- (void)removeObjectForKey:(id)key { + dispatch_async(_queue, ^{ + [self->_objects removeObjectForKey:key]; + }); +} + +- (void)removeAllObjects { + dispatch_async(_queue, ^{ + [self->_objects removeAllObjects]; + }); +} + +- (NSUInteger)count { + __block NSUInteger count; + dispatch_sync(_queue, ^{ + count = self->_objects.count; + }); + return count; +} + +- (id)objectForKeyedSubscript:(id)key { + __block id object; + dispatch_sync(_queue, ^{ + object = self->_objects[key]; + }); + return object; +} + +- (void)setObject:(id)obj forKeyedSubscript:(id)key { + dispatch_async(_queue, ^{ + self->_objects[key] = obj; + }); +} + +- (NSDictionary *)dictionary { + __block NSDictionary *dictionary; + dispatch_sync(_queue, ^{ + dictionary = [self->_objects copy]; + }); + return dictionary; +} + +@end diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/GULNetwork.m b/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/GULNetwork.m new file mode 100644 index 0000000..9d78e0e --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/GULNetwork.m @@ -0,0 +1,389 @@ +// Copyright 2017 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "GoogleUtilities/Network/Private/GULNetwork.h" +#import "GoogleUtilities/Network/Private/GULNetworkMessageCode.h" + +#import +#import +#import +#import "GoogleUtilities/Network/Private/GULMutableDictionary.h" +#import "GoogleUtilities/Network/Private/GULNetworkConstants.h" + +/// Constant string for request header Content-Encoding. +static NSString *const kGULNetworkContentCompressionKey = @"Content-Encoding"; + +/// Constant string for request header Content-Encoding value. +static NSString *const kGULNetworkContentCompressionValue = @"gzip"; + +/// Constant string for request header Content-Length. +static NSString *const kGULNetworkContentLengthKey = @"Content-Length"; + +/// Constant string for request header Content-Type. +static NSString *const kGULNetworkContentTypeKey = @"Content-Type"; + +/// Constant string for request header Content-Type value. +static NSString *const kGULNetworkContentTypeValue = @"application/x-www-form-urlencoded"; + +/// Constant string for GET request method. +static NSString *const kGULNetworkGETRequestMethod = @"GET"; + +/// Constant string for POST request method. +static NSString *const kGULNetworkPOSTRequestMethod = @"POST"; + +/// Default constant string as a prefix for network logger. +static NSString *const kGULNetworkLogTag = @"Google/Utilities/Network"; + +@interface GULNetwork () +@end + +@implementation GULNetwork { + /// Network reachability. + GULReachabilityChecker *_reachability; + + /// The dictionary of requests by session IDs { NSString : id }. + GULMutableDictionary *_requests; +} + +- (instancetype)init { + return [self initWithReachabilityHost:kGULNetworkReachabilityHost]; +} + +- (instancetype)initWithReachabilityHost:(NSString *)reachabilityHost { + self = [super init]; + if (self) { + // Setup reachability. + _reachability = [[GULReachabilityChecker alloc] initWithReachabilityDelegate:self + withHost:reachabilityHost]; + if (![_reachability start]) { + return nil; + } + + _requests = [[GULMutableDictionary alloc] init]; + _timeoutInterval = kGULNetworkTimeOutInterval; + } + return self; +} + +- (void)dealloc { + _reachability.reachabilityDelegate = nil; + [_reachability stop]; +} + +#pragma mark - External Methods + ++ (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID + completionHandler:(GULNetworkSystemCompletionHandler)completionHandler { + [GULNetworkURLSession handleEventsForBackgroundURLSessionID:sessionID + completionHandler:completionHandler]; +} + +- (NSString *)postURL:(NSURL *)url + payload:(NSData *)payload + queue:(dispatch_queue_t)queue + usingBackgroundSession:(BOOL)usingBackgroundSession + completionHandler:(GULNetworkCompletionHandler)handler { + if (!url.absoluteString.length) { + [self handleErrorWithCode:GULErrorCodeNetworkInvalidURL queue:queue withHandler:handler]; + return nil; + } + + NSTimeInterval timeOutInterval = _timeoutInterval ?: kGULNetworkTimeOutInterval; + + NSMutableURLRequest *request = + [[NSMutableURLRequest alloc] initWithURL:url + cachePolicy:NSURLRequestReloadIgnoringLocalCacheData + timeoutInterval:timeOutInterval]; + + if (!request) { + [self handleErrorWithCode:GULErrorCodeNetworkSessionTaskCreation + queue:queue + withHandler:handler]; + return nil; + } + + NSError *compressError = nil; + NSData *compressedData = [NSData gul_dataByGzippingData:payload error:&compressError]; + if (!compressedData || compressError) { + if (compressError || payload.length > 0) { + // If the payload is not empty but it fails to compress the payload, something has been wrong. + [self handleErrorWithCode:GULErrorCodeNetworkPayloadCompression + queue:queue + withHandler:handler]; + return nil; + } + compressedData = [[NSData alloc] init]; + } + + NSString *postLength = @(compressedData.length).stringValue; + + // Set up the request with the compressed data. + [request setValue:postLength forHTTPHeaderField:kGULNetworkContentLengthKey]; + request.HTTPBody = compressedData; + request.HTTPMethod = kGULNetworkPOSTRequestMethod; + [request setValue:kGULNetworkContentTypeValue forHTTPHeaderField:kGULNetworkContentTypeKey]; + [request setValue:kGULNetworkContentCompressionValue + forHTTPHeaderField:kGULNetworkContentCompressionKey]; + + GULNetworkURLSession *fetcher = [[GULNetworkURLSession alloc] initWithNetworkLoggerDelegate:self]; + fetcher.backgroundNetworkEnabled = usingBackgroundSession; + + __weak GULNetwork *weakSelf = self; + NSString *requestID = [fetcher + sessionIDFromAsyncPOSTRequest:request + completionHandler:^(NSHTTPURLResponse *response, NSData *data, + NSString *sessionID, NSError *error) { + GULNetwork *strongSelf = weakSelf; + if (!strongSelf) { + return; + } + dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue(); + dispatch_async(queueToDispatch, ^{ + if (sessionID.length) { + [strongSelf->_requests removeObjectForKey:sessionID]; + } + if (handler) { + handler(response, data, error); + } + }); + }]; + if (!requestID) { + [self handleErrorWithCode:GULErrorCodeNetworkSessionTaskCreation + queue:queue + withHandler:handler]; + return nil; + } + + [self GULNetwork_logWithLevel:kGULNetworkLogLevelDebug + messageCode:kGULNetworkMessageCodeNetwork000 + message:@"Uploading data. Host" + context:url]; + _requests[requestID] = fetcher; + return requestID; +} + +- (NSString *)getURL:(NSURL *)url + headers:(NSDictionary *)headers + queue:(dispatch_queue_t)queue + usingBackgroundSession:(BOOL)usingBackgroundSession + completionHandler:(GULNetworkCompletionHandler)handler { + if (!url.absoluteString.length) { + [self handleErrorWithCode:GULErrorCodeNetworkInvalidURL queue:queue withHandler:handler]; + return nil; + } + + NSTimeInterval timeOutInterval = _timeoutInterval ?: kGULNetworkTimeOutInterval; + NSMutableURLRequest *request = + [[NSMutableURLRequest alloc] initWithURL:url + cachePolicy:NSURLRequestReloadIgnoringLocalCacheData + timeoutInterval:timeOutInterval]; + + if (!request) { + [self handleErrorWithCode:GULErrorCodeNetworkSessionTaskCreation + queue:queue + withHandler:handler]; + return nil; + } + + request.HTTPMethod = kGULNetworkGETRequestMethod; + request.allHTTPHeaderFields = headers; + + GULNetworkURLSession *fetcher = [[GULNetworkURLSession alloc] initWithNetworkLoggerDelegate:self]; + fetcher.backgroundNetworkEnabled = usingBackgroundSession; + + __weak GULNetwork *weakSelf = self; + NSString *requestID = [fetcher + sessionIDFromAsyncGETRequest:request + completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSString *sessionID, + NSError *error) { + GULNetwork *strongSelf = weakSelf; + if (!strongSelf) { + return; + } + dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue(); + dispatch_async(queueToDispatch, ^{ + if (sessionID.length) { + [strongSelf->_requests removeObjectForKey:sessionID]; + } + if (handler) { + handler(response, data, error); + } + }); + }]; + + if (!requestID) { + [self handleErrorWithCode:GULErrorCodeNetworkSessionTaskCreation + queue:queue + withHandler:handler]; + return nil; + } + + [self GULNetwork_logWithLevel:kGULNetworkLogLevelDebug + messageCode:kGULNetworkMessageCodeNetwork001 + message:@"Downloading data. Host" + context:url]; + _requests[requestID] = fetcher; + return requestID; +} + +- (BOOL)hasUploadInProgress { + return _requests.count > 0; +} + +#pragma mark - Network Reachability + +/// Tells reachability delegate to call reachabilityDidChangeToStatus: to notify the network +/// reachability has changed. +- (void)reachability:(GULReachabilityChecker *)reachability + statusChanged:(GULReachabilityStatus)status { + _networkConnected = (status == kGULReachabilityViaCellular || status == kGULReachabilityViaWifi); + [_reachabilityDelegate reachabilityDidChange]; +} + +#pragma mark - Network logger delegate + +- (void)setLoggerDelegate:(id)loggerDelegate { + // Explicitly check whether the delegate responds to the methods because conformsToProtocol does + // not work correctly even though the delegate does respond to the methods. + if (!loggerDelegate || + ![loggerDelegate respondsToSelector:@selector(GULNetwork_logWithLevel: + messageCode:message:contexts:)] || + ![loggerDelegate respondsToSelector:@selector(GULNetwork_logWithLevel: + messageCode:message:context:)] || + ![loggerDelegate respondsToSelector:@selector(GULNetwork_logWithLevel: + messageCode:message:)]) { + GULLogError(kGULLoggerNetwork, NO, + [NSString stringWithFormat:@"I-NET%06ld", (long)kGULNetworkMessageCodeNetwork002], + @"Cannot set the network logger delegate: delegate does not conform to the network " + "logger protocol."); + return; + } + _loggerDelegate = loggerDelegate; +} + +#pragma mark - Private methods + +/// Handles network error and calls completion handler with the error. +- (void)handleErrorWithCode:(NSInteger)code + queue:(dispatch_queue_t)queue + withHandler:(GULNetworkCompletionHandler)handler { + NSDictionary *userInfo = @{kGULNetworkErrorContext : @"Failed to create network request"}; + NSError *error = [[NSError alloc] initWithDomain:kGULNetworkErrorDomain + code:code + userInfo:userInfo]; + [self GULNetwork_logWithLevel:kGULNetworkLogLevelWarning + messageCode:kGULNetworkMessageCodeNetwork002 + message:@"Failed to create network request. Code, error" + contexts:@[ @(code), error ]]; + if (handler) { + dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue(); + dispatch_async(queueToDispatch, ^{ + handler(nil, nil, error); + }); + } +} + +#pragma mark - Network logger + +- (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevel + messageCode:(GULNetworkMessageCode)messageCode + message:(NSString *)message + contexts:(NSArray *)contexts { + // Let the delegate log the message if there is a valid logger delegate. Otherwise, just log + // errors/warnings/info messages to the console log. + if (_loggerDelegate) { + [_loggerDelegate GULNetwork_logWithLevel:logLevel + messageCode:messageCode + message:message + contexts:contexts]; + return; + } + if (_isDebugModeEnabled || logLevel == kGULNetworkLogLevelError || + logLevel == kGULNetworkLogLevelWarning || logLevel == kGULNetworkLogLevelInfo) { + NSString *formattedMessage = GULStringWithLogMessage(message, logLevel, contexts); + NSLog(@"%@", formattedMessage); + GULLogBasic((GULLoggerLevel)logLevel, kGULLoggerNetwork, NO, + [NSString stringWithFormat:@"I-NET%06ld", (long)messageCode], formattedMessage, + NULL); + } +} + +- (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevel + messageCode:(GULNetworkMessageCode)messageCode + message:(NSString *)message + context:(id)context { + if (_loggerDelegate) { + [_loggerDelegate GULNetwork_logWithLevel:logLevel + messageCode:messageCode + message:message + context:context]; + return; + } + NSArray *contexts = context ? @[ context ] : @[]; + [self GULNetwork_logWithLevel:logLevel messageCode:messageCode message:message contexts:contexts]; +} + +- (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevel + messageCode:(GULNetworkMessageCode)messageCode + message:(NSString *)message { + if (_loggerDelegate) { + [_loggerDelegate GULNetwork_logWithLevel:logLevel messageCode:messageCode message:message]; + return; + } + [self GULNetwork_logWithLevel:logLevel messageCode:messageCode message:message contexts:@[]]; +} + +/// Returns a string for the given log level (e.g. kGULNetworkLogLevelError -> @"ERROR"). +static NSString *GULLogLevelDescriptionFromLogLevel(GULNetworkLogLevel logLevel) { + static NSDictionary *levelNames = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + levelNames = @{ + @(kGULNetworkLogLevelError) : @"ERROR", + @(kGULNetworkLogLevelWarning) : @"WARNING", + @(kGULNetworkLogLevelInfo) : @"INFO", + @(kGULNetworkLogLevelDebug) : @"DEBUG" + }; + }); + return levelNames[@(logLevel)]; +} + +/// Returns a formatted string to be used for console logging. +static NSString *GULStringWithLogMessage(NSString *message, + GULNetworkLogLevel logLevel, + NSArray *contexts) { + if (!message) { + message = @"(Message was nil)"; + } else if (!message.length) { + message = @"(Message was empty)"; + } + NSMutableString *result = [[NSMutableString alloc] + initWithFormat:@"<%@/%@> %@", kGULNetworkLogTag, GULLogLevelDescriptionFromLogLevel(logLevel), + message]; + + if (!contexts.count) { + return result; + } + + NSMutableArray *formattedContexts = [[NSMutableArray alloc] init]; + for (id item in contexts) { + [formattedContexts addObject:(item != [NSNull null] ? item : @"(nil)")]; + } + + [result appendString:@": "]; + [result appendString:[formattedContexts componentsJoinedByString:@", "]]; + return result; +} + +@end diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/GULNetworkConstants.m b/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/GULNetworkConstants.m new file mode 100644 index 0000000..dea8dbd --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/GULNetworkConstants.m @@ -0,0 +1,40 @@ +// Copyright 2017 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "GoogleUtilities/Network/Private/GULNetworkConstants.h" + +#import + +NSString *const kGULNetworkBackgroundSessionConfigIDPrefix = @"com.gul.network.background-upload"; +NSString *const kGULNetworkApplicationSupportSubdirectory = @"GUL/Network"; +NSString *const kGULNetworkTempDirectoryName = @"GULNetworkTemporaryDirectory"; +const NSTimeInterval kGULNetworkTempFolderExpireTime = 60 * 60; // 1 hour +const NSTimeInterval kGULNetworkTimeOutInterval = 60; // 1 minute. +NSString *const kGULNetworkReachabilityHost = @"app-measurement.com"; +NSString *const kGULNetworkErrorContext = @"Context"; + +const int kGULNetworkHTTPStatusOK = 200; +const int kGULNetworkHTTPStatusNoContent = 204; +const int kGULNetworkHTTPStatusCodeMultipleChoices = 300; +const int kGULNetworkHTTPStatusCodeMovedPermanently = 301; +const int kGULNetworkHTTPStatusCodeFound = 302; +const int kGULNetworkHTTPStatusCodeNotModified = 304; +const int kGULNetworkHTTPStatusCodeMovedTemporarily = 307; +const int kGULNetworkHTTPStatusCodeNotFound = 404; +const int kGULNetworkHTTPStatusCodeCannotAcceptTraffic = 429; +const int kGULNetworkHTTPStatusCodeUnavailable = 503; + +NSString *const kGULNetworkErrorDomain = @"com.gul.network.ErrorDomain"; + +GULLoggerService kGULLoggerNetwork = @"[GULNetwork]"; diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/GULNetworkURLSession.m b/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/GULNetworkURLSession.m new file mode 100644 index 0000000..df8bf46 --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/GULNetworkURLSession.m @@ -0,0 +1,762 @@ +// Copyright 2017 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +#import "GoogleUtilities/Network/Private/GULNetworkURLSession.h" + +#import +#import "GoogleUtilities/Network/Private/GULMutableDictionary.h" +#import "GoogleUtilities/Network/Private/GULNetworkConstants.h" +#import "GoogleUtilities/Network/Private/GULNetworkMessageCode.h" + +@interface GULNetworkURLSession () +@end + +@implementation GULNetworkURLSession { + /// The handler to be called when the request completes or error has occurs. + GULNetworkURLSessionCompletionHandler _completionHandler; + + /// Session ID generated randomly with a fixed prefix. + NSString *_sessionID; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability" + /// The session configuration. NSURLSessionConfiguration' is only available on iOS 7.0 or newer. + NSURLSessionConfiguration *_sessionConfig; + + /// The current NSURLSession. + NSURLSession *__weak _Nullable _URLSession; +#pragma clang diagnostic pop + + /// The path to the directory where all temporary files are stored before uploading. + NSURL *_networkDirectoryURL; + + /// The downloaded data from fetching. + NSData *_downloadedData; + + /// The path to the temporary file which stores the uploading data. + NSURL *_uploadingFileURL; + + /// The current request. + NSURLRequest *_request; +} + +#pragma mark - Init + +- (instancetype)initWithNetworkLoggerDelegate:(id)networkLoggerDelegate { + self = [super init]; + if (self) { + // Create URL to the directory where all temporary files to upload have to be stored. + NSArray *paths = + NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); + NSString *applicationSupportDirectory = paths.firstObject; + NSArray *tempPathComponents = @[ + applicationSupportDirectory, kGULNetworkApplicationSupportSubdirectory, + kGULNetworkTempDirectoryName + ]; + _networkDirectoryURL = [NSURL fileURLWithPathComponents:tempPathComponents]; + _sessionID = [NSString stringWithFormat:@"%@-%@", kGULNetworkBackgroundSessionConfigIDPrefix, + [[NSUUID UUID] UUIDString]]; + _loggerDelegate = networkLoggerDelegate; + } + return self; +} + +#pragma mark - External Methods + +#pragma mark - To be called from AppDelegate + ++ (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID + completionHandler: + (GULNetworkSystemCompletionHandler)systemCompletionHandler { + // The session may not be Analytics background. Ignore those that do not have the prefix. + if (![sessionID hasPrefix:kGULNetworkBackgroundSessionConfigIDPrefix]) { + return; + } + GULNetworkURLSession *fetcher = [self fetcherWithSessionIdentifier:sessionID]; + if (fetcher != nil) { + [fetcher addSystemCompletionHandler:systemCompletionHandler forSession:sessionID]; + } else { + GULLogError(kGULLoggerNetwork, NO, + [NSString stringWithFormat:@"I-NET%06ld", (long)kGULNetworkMessageCodeNetwork003], + @"Failed to retrieve background session with ID %@ after app is relaunched.", + sessionID); + } +} + +#pragma mark - External Methods + +/// Sends an async POST request using NSURLSession for iOS >= 7.0, and returns an ID of the +/// connection. +- (nullable NSString *)sessionIDFromAsyncPOSTRequest:(NSURLRequest *)request + completionHandler:(GULNetworkURLSessionCompletionHandler)handler + API_AVAILABLE(ios(7.0)) { + // NSURLSessionUploadTask does not work with NSData in the background. + // To avoid this issue, write the data to a temporary file to upload it. + // Make a temporary file with the data subset. + _uploadingFileURL = [self temporaryFilePathWithSessionID:_sessionID]; + NSError *writeError; + NSURLSessionUploadTask *postRequestTask; + NSURLSession *session; + BOOL didWriteFile = NO; + + // Clean up the entire temp folder to avoid temp files that remain in case the previous session + // crashed and did not clean up. + [self maybeRemoveTempFilesAtURL:_networkDirectoryURL + expiringTime:kGULNetworkTempFolderExpireTime]; + + // If there is no background network enabled, no need to write to file. This will allow default + // network session which runs on the foreground. + if (_backgroundNetworkEnabled && [self ensureTemporaryDirectoryExists]) { + didWriteFile = [request.HTTPBody writeToFile:_uploadingFileURL.path + options:NSDataWritingAtomic + error:&writeError]; + + if (writeError) { + [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError + messageCode:kGULNetworkMessageCodeURLSession000 + message:@"Failed to write request data to file" + context:writeError]; + } + } + + if (didWriteFile) { + // Exclude this file from backing up to iTunes. There are conflicting reports that excluding + // directory from backing up does not exclude files of that directory from backing up. + [self excludeFromBackupForURL:_uploadingFileURL]; + + _sessionConfig = [self backgroundSessionConfigWithSessionID:_sessionID]; + [self populateSessionConfig:_sessionConfig withRequest:request]; + session = [NSURLSession sessionWithConfiguration:_sessionConfig + delegate:self + delegateQueue:[NSOperationQueue mainQueue]]; + postRequestTask = [session uploadTaskWithRequest:request fromFile:_uploadingFileURL]; + } else { + // If we cannot write to file, just send it in the foreground. + _sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; + [self populateSessionConfig:_sessionConfig withRequest:request]; + session = [NSURLSession sessionWithConfiguration:_sessionConfig + delegate:self + delegateQueue:[NSOperationQueue mainQueue]]; + postRequestTask = [session uploadTaskWithRequest:request fromData:request.HTTPBody]; + } + + if (!session || !postRequestTask) { + NSError *error = [[NSError alloc] + initWithDomain:kGULNetworkErrorDomain + code:GULErrorCodeNetworkRequestCreation + userInfo:@{kGULNetworkErrorContext : @"Cannot create network session"}]; + [self callCompletionHandler:handler withResponse:nil data:nil error:error]; + return nil; + } + + _URLSession = session; + + // Save the session into memory. + [[self class] setSessionInFetcherMap:self forSessionID:_sessionID]; + + _request = [request copy]; + + // Store completion handler because background session does not accept handler block but custom + // delegate. + _completionHandler = [handler copy]; + [postRequestTask resume]; + + return _sessionID; +} + +/// Sends an async GET request using NSURLSession for iOS >= 7.0, and returns an ID of the session. +- (nullable NSString *)sessionIDFromAsyncGETRequest:(NSURLRequest *)request + completionHandler:(GULNetworkURLSessionCompletionHandler)handler + API_AVAILABLE(ios(7.0)) { + if (_backgroundNetworkEnabled) { + _sessionConfig = [self backgroundSessionConfigWithSessionID:_sessionID]; + } else { + _sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; + } + + [self populateSessionConfig:_sessionConfig withRequest:request]; + + // Do not cache the GET request. + _sessionConfig.URLCache = nil; + + NSURLSession *session = [NSURLSession sessionWithConfiguration:_sessionConfig + delegate:self + delegateQueue:[NSOperationQueue mainQueue]]; + NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request]; + + if (!session || !downloadTask) { + NSError *error = [[NSError alloc] + initWithDomain:kGULNetworkErrorDomain + code:GULErrorCodeNetworkRequestCreation + userInfo:@{kGULNetworkErrorContext : @"Cannot create network session"}]; + [self callCompletionHandler:handler withResponse:nil data:nil error:error]; + return nil; + } + + _URLSession = session; + + // Save the session into memory. + [[self class] setSessionInFetcherMap:self forSessionID:_sessionID]; + + _request = [request copy]; + + _completionHandler = [handler copy]; + [downloadTask resume]; + + return _sessionID; +} + +#pragma mark - NSURLSessionDataDelegate + +/// Called by the NSURLSession when the data task has received some of the expected data. +/// Once the session is completed, URLSession:task:didCompleteWithError will be called and the +/// completion handler will be called with the downloaded data. +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask + didReceiveData:(NSData *)data { + @synchronized(self) { + NSMutableData *mutableData = [[NSMutableData alloc] init]; + if (_downloadedData) { + mutableData = _downloadedData.mutableCopy; + } + [mutableData appendData:data]; + _downloadedData = mutableData; + } +} + +#pragma mark - NSURLSessionTaskDelegate + +/// Called by the NSURLSession once the download task is completed. The file is saved in the +/// provided URL so we need to read the data and store into _downloadedData. Once the session is +/// completed, URLSession:task:didCompleteWithError will be called and the completion handler will +/// be called with the downloaded data. +- (void)URLSession:(NSURLSession *)session + downloadTask:(NSURLSessionDownloadTask *)task + didFinishDownloadingToURL:(NSURL *)url API_AVAILABLE(ios(7.0)) { + if (!url.path) { + [_loggerDelegate + GULNetwork_logWithLevel:kGULNetworkLogLevelError + messageCode:kGULNetworkMessageCodeURLSession001 + message:@"Unable to read downloaded data from empty temp path"]; + _downloadedData = nil; + return; + } + + NSError *error; + _downloadedData = [NSData dataWithContentsOfFile:url.path options:0 error:&error]; + + if (error) { + [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError + messageCode:kGULNetworkMessageCodeURLSession002 + message:@"Cannot read the content of downloaded data" + context:error]; + _downloadedData = nil; + } +} + +#if TARGET_OS_IOS || TARGET_OS_TV +- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session + API_AVAILABLE(ios(7.0)) { + [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelDebug + messageCode:kGULNetworkMessageCodeURLSession003 + message:@"Background session finished" + context:session.configuration.identifier]; + [self callSystemCompletionHandler:session.configuration.identifier]; +} +#endif + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + didCompleteWithError:(NSError *)error API_AVAILABLE(ios(7.0)) { + // Avoid any chance of recursive behavior leading to it being used repeatedly. + GULNetworkURLSessionCompletionHandler handler = _completionHandler; + _completionHandler = nil; + + if (task.response) { + // The following assertion should always be true for HTTP requests, see https://goo.gl/gVLxT7. + NSAssert([task.response isKindOfClass:[NSHTTPURLResponse class]], @"URL response must be HTTP"); + + // The server responded so ignore the error created by the system. + error = nil; + } else if (!error) { + error = [[NSError alloc] + initWithDomain:kGULNetworkErrorDomain + code:GULErrorCodeNetworkInvalidResponse + userInfo:@{kGULNetworkErrorContext : @"Network Error: Empty network response"}]; + } + + [self callCompletionHandler:handler + withResponse:(NSHTTPURLResponse *)task.response + data:_downloadedData + error:error]; + + // Remove the temp file to avoid trashing devices with lots of temp files. + [self removeTempItemAtURL:_uploadingFileURL]; + + // Try to clean up stale files again. + [self maybeRemoveTempFilesAtURL:_networkDirectoryURL + expiringTime:kGULNetworkTempFolderExpireTime]; + + // This is called without checking the sessionID here since non-background sessions + // won't have an ID. + [session finishTasksAndInvalidate]; + + // Explicitly remove the session so it won't be reused. The weak map table should + // remove the session on deallocation, but dealloc may not happen immediately after + // calling `finishTasksAndInvalidate`. + NSString *sessionID = session.configuration.identifier; + [[self class] setSessionInFetcherMap:nil forSessionID:sessionID]; +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge + completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, + NSURLCredential *credential))completionHandler + API_AVAILABLE(ios(7.0)) { + // The handling is modeled after GTMSessionFetcher. + if ([challenge.protectionSpace.authenticationMethod + isEqualToString:NSURLAuthenticationMethodServerTrust]) { + SecTrustRef serverTrust = challenge.protectionSpace.serverTrust; + if (serverTrust == NULL) { + [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelDebug + messageCode:kGULNetworkMessageCodeURLSession004 + message:@"Received empty server trust for host. Host" + context:_request.URL]; + completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); + return; + } + NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust]; + if (!credential) { + [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelWarning + messageCode:kGULNetworkMessageCodeURLSession005 + message:@"Unable to verify server identity. Host" + context:_request.URL]; + completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); + return; + } + + [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelDebug + messageCode:kGULNetworkMessageCodeURLSession006 + message:@"Received SSL challenge for host. Host" + context:_request.URL]; + + void (^callback)(BOOL) = ^(BOOL allow) { + if (allow) { + completionHandler(NSURLSessionAuthChallengeUseCredential, credential); + } else { + [self->_loggerDelegate + GULNetwork_logWithLevel:kGULNetworkLogLevelDebug + messageCode:kGULNetworkMessageCodeURLSession007 + message:@"Cancelling authentication challenge for host. Host" + context:self->_request.URL]; + completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); + } + }; + + // Retain the trust object to avoid a SecTrustEvaluate() crash on iOS 7. + CFRetain(serverTrust); + + // Evaluate the certificate chain. + // + // The delegate queue may be the main thread. Trust evaluation could cause some + // blocking network activity, so we must evaluate async, as documented at + // https://developer.apple.com/library/ios/technotes/tn2232/ + dispatch_queue_t evaluateBackgroundQueue = + dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + + dispatch_async(evaluateBackgroundQueue, ^{ + SecTrustResultType trustEval = kSecTrustResultInvalid; + BOOL shouldAllow; + OSStatus trustError; + + @synchronized([GULNetworkURLSession class]) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + trustError = SecTrustEvaluate(serverTrust, &trustEval); +#pragma clang dianostic pop + } + + if (trustError != errSecSuccess) { + [self->_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError + messageCode:kGULNetworkMessageCodeURLSession008 + message:@"Cannot evaluate server trust. Error, host" + contexts:@[ @(trustError), self->_request.URL ]]; + shouldAllow = NO; + } else { + // Having a trust level "unspecified" by the user is the usual result, described at + // https://developer.apple.com/library/mac/qa/qa1360 + shouldAllow = + (trustEval == kSecTrustResultUnspecified || trustEval == kSecTrustResultProceed); + } + + // Call the call back with the permission. + callback(shouldAllow); + + CFRelease(serverTrust); + }); + return; + } + + // Default handling for other Auth Challenges. + completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); +} + +#pragma mark - Internal Methods + +/// Stores system completion handler with session ID as key. +- (void)addSystemCompletionHandler:(GULNetworkSystemCompletionHandler)handler + forSession:(NSString *)identifier { + if (!handler) { + [_loggerDelegate + GULNetwork_logWithLevel:kGULNetworkLogLevelError + messageCode:kGULNetworkMessageCodeURLSession009 + message:@"Cannot store nil system completion handler in network"]; + return; + } + + if (!identifier.length) { + [_loggerDelegate + GULNetwork_logWithLevel:kGULNetworkLogLevelError + messageCode:kGULNetworkMessageCodeURLSession010 + message:@"Cannot store system completion handler with empty network " + "session identifier"]; + return; + } + + GULMutableDictionary *systemCompletionHandlers = + [[self class] sessionIDToSystemCompletionHandlerDictionary]; + if (systemCompletionHandlers[identifier]) { + [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelWarning + messageCode:kGULNetworkMessageCodeURLSession011 + message:@"Got multiple system handlers for a single session ID" + context:identifier]; + } + + systemCompletionHandlers[identifier] = handler; +} + +/// Calls the system provided completion handler with the session ID stored in the dictionary. +/// The handler will be removed from the dictionary after being called. +- (void)callSystemCompletionHandler:(NSString *)identifier { + GULMutableDictionary *systemCompletionHandlers = + [[self class] sessionIDToSystemCompletionHandlerDictionary]; + GULNetworkSystemCompletionHandler handler = [systemCompletionHandlers objectForKey:identifier]; + + if (handler) { + [systemCompletionHandlers removeObjectForKey:identifier]; + + dispatch_async(dispatch_get_main_queue(), ^{ + handler(); + }); + } +} + +/// Sets or updates the session ID of this session. +- (void)setSessionID:(NSString *)sessionID { + _sessionID = [sessionID copy]; +} + +/// Creates a background session configuration with the session ID using the supported method. +- (NSURLSessionConfiguration *)backgroundSessionConfigWithSessionID:(NSString *)sessionID + API_AVAILABLE(ios(7.0)) { +#if (TARGET_OS_OSX && defined(MAC_OS_X_VERSION_10_10) && \ + MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10) || \ + TARGET_OS_TV || \ + (TARGET_OS_IOS && defined(__IPHONE_8_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0) + + // iOS 8/10.10 builds require the new backgroundSessionConfiguration method name. + return [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionID]; + +#elif (TARGET_OS_OSX && defined(MAC_OS_X_VERSION_10_10) && \ + MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_10) || \ + (TARGET_OS_IOS && defined(__IPHONE_8_0) && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0) + + // Do a runtime check to avoid a deprecation warning about using + // +backgroundSessionConfiguration: on iOS 8. + if ([NSURLSessionConfiguration + respondsToSelector:@selector(backgroundSessionConfigurationWithIdentifier:)]) { + // Running on iOS 8+/OS X 10.10+. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability" + return [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionID]; +#pragma clang diagnostic pop + } else { + // Running on iOS 7/OS X 10.9. + return [NSURLSessionConfiguration backgroundSessionConfiguration:sessionID]; + } + +#else + // Building with an SDK earlier than iOS 8/OS X 10.10. + return [NSURLSessionConfiguration backgroundSessionConfiguration:sessionID]; +#endif +} + +- (void)maybeRemoveTempFilesAtURL:(NSURL *)folderURL expiringTime:(NSTimeInterval)staleTime { + if (!folderURL.absoluteString.length) { + return; + } + + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSError *error = nil; + + NSArray *properties = @[ NSURLCreationDateKey ]; + NSArray *directoryContent = + [fileManager contentsOfDirectoryAtURL:folderURL + includingPropertiesForKeys:properties + options:NSDirectoryEnumerationSkipsSubdirectoryDescendants + error:&error]; + if (error && error.code != NSFileReadNoSuchFileError) { + [_loggerDelegate + GULNetwork_logWithLevel:kGULNetworkLogLevelDebug + messageCode:kGULNetworkMessageCodeURLSession012 + message:@"Cannot get files from the temporary network folder. Error" + context:error]; + return; + } + + if (!directoryContent.count) { + return; + } + + NSTimeInterval now = [NSDate date].timeIntervalSince1970; + for (NSURL *tempFile in directoryContent) { + NSDate *creationDate; + BOOL getCreationDate = [tempFile getResourceValue:&creationDate + forKey:NSURLCreationDateKey + error:NULL]; + if (!getCreationDate) { + continue; + } + NSTimeInterval creationTimeInterval = creationDate.timeIntervalSince1970; + if (fabs(now - creationTimeInterval) > staleTime) { + [self removeTempItemAtURL:tempFile]; + } + } +} + +/// Removes the temporary file written to disk for sending the request. It has to be cleaned up +/// after the session is done. +- (void)removeTempItemAtURL:(NSURL *)fileURL { + if (!fileURL.absoluteString.length) { + return; + } + + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSError *error = nil; + + if (![fileManager removeItemAtURL:fileURL error:&error] && error.code != NSFileNoSuchFileError) { + [_loggerDelegate + GULNetwork_logWithLevel:kGULNetworkLogLevelError + messageCode:kGULNetworkMessageCodeURLSession013 + message:@"Failed to remove temporary uploading data file. Error" + context:error.localizedDescription]; + } +} + +/// Gets the fetcher with the session ID. ++ (instancetype)fetcherWithSessionIdentifier:(NSString *)sessionIdentifier { + GULNetworkURLSession *session = [self sessionFromFetcherMapForSessionID:sessionIdentifier]; + if (!session && [sessionIdentifier hasPrefix:kGULNetworkBackgroundSessionConfigIDPrefix]) { + session = [[GULNetworkURLSession alloc] initWithNetworkLoggerDelegate:nil]; + [session setSessionID:sessionIdentifier]; + [self setSessionInFetcherMap:session forSessionID:sessionIdentifier]; + } + return session; +} + +/// Returns a map of the fetcher by session ID. Creates a map if it is not created. +/// When reading and writing from/to the session map, don't use this method directly. +/// To avoid thread safety issues, use one of the helper methods at the bottom of the +/// file: setSessionInFetcherMap:forSessionID:, sessionFromFetcherMapForSessionID: ++ (NSMapTable *)sessionIDToFetcherMap { + static NSMapTable *sessionIDToFetcherMap; + + static dispatch_once_t sessionMapOnceToken; + dispatch_once(&sessionMapOnceToken, ^{ + sessionIDToFetcherMap = [NSMapTable strongToWeakObjectsMapTable]; + }); + return sessionIDToFetcherMap; +} + ++ (NSLock *)sessionIDToFetcherMapReadWriteLock { + static NSLock *lock; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + lock = [[NSLock alloc] init]; + }); + return lock; +} + +/// Returns a map of system provided completion handler by session ID. Creates a map if it is not +/// created. ++ (GULMutableDictionary *)sessionIDToSystemCompletionHandlerDictionary { + static GULMutableDictionary *systemCompletionHandlers; + + static dispatch_once_t systemCompletionHandlerOnceToken; + dispatch_once(&systemCompletionHandlerOnceToken, ^{ + systemCompletionHandlers = [[GULMutableDictionary alloc] init]; + }); + return systemCompletionHandlers; +} + +- (NSURL *)temporaryFilePathWithSessionID:(NSString *)sessionID { + NSString *tempName = [NSString stringWithFormat:@"GULUpload_temp_%@", sessionID]; + return [_networkDirectoryURL URLByAppendingPathComponent:tempName]; +} + +/// Makes sure that the directory to store temp files exists. If not, tries to create it and returns +/// YES. If there is anything wrong, returns NO. +- (BOOL)ensureTemporaryDirectoryExists { + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSError *error = nil; + + // Create a temporary directory if it does not exist or was deleted. + if ([_networkDirectoryURL checkResourceIsReachableAndReturnError:&error]) { + return YES; + } + + if (error && error.code != NSFileReadNoSuchFileError) { + [_loggerDelegate + GULNetwork_logWithLevel:kGULNetworkLogLevelWarning + messageCode:kGULNetworkMessageCodeURLSession014 + message:@"Error while trying to access Network temp folder. Error" + context:error]; + } + + NSError *writeError = nil; + + [fileManager createDirectoryAtURL:_networkDirectoryURL + withIntermediateDirectories:YES + attributes:nil + error:&writeError]; + if (writeError) { + [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError + messageCode:kGULNetworkMessageCodeURLSession015 + message:@"Cannot create temporary directory. Error" + context:writeError]; + return NO; + } + + // Set the iCloud exclusion attribute on the Documents URL. + [self excludeFromBackupForURL:_networkDirectoryURL]; + + return YES; +} + +- (void)excludeFromBackupForURL:(NSURL *)url { + if (!url.path) { + return; + } + + // Set the iCloud exclusion attribute on the Documents URL. + NSError *preventBackupError = nil; + [url setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:&preventBackupError]; + if (preventBackupError) { + [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError + messageCode:kGULNetworkMessageCodeURLSession016 + message:@"Cannot exclude temporary folder from iTunes backup"]; + } +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + willPerformHTTPRedirection:(NSHTTPURLResponse *)response + newRequest:(NSURLRequest *)request + completionHandler:(void (^)(NSURLRequest *))completionHandler API_AVAILABLE(ios(7.0)) { + NSArray *nonAllowedRedirectionCodes = @[ + @(kGULNetworkHTTPStatusCodeFound), @(kGULNetworkHTTPStatusCodeMovedPermanently), + @(kGULNetworkHTTPStatusCodeMovedTemporarily), @(kGULNetworkHTTPStatusCodeMultipleChoices) + ]; + + // Allow those not in the non allowed list to be followed. + if (![nonAllowedRedirectionCodes containsObject:@(response.statusCode)]) { + completionHandler(request); + return; + } + + // Do not allow redirection if the response code is in the non-allowed list. + NSURLRequest *newRequest = request; + + if (response) { + newRequest = nil; + } + + completionHandler(newRequest); +} + +#pragma mark - Helper Methods + ++ (void)setSessionInFetcherMap:(GULNetworkURLSession *)session forSessionID:(NSString *)sessionID { + [[self sessionIDToFetcherMapReadWriteLock] lock]; + GULNetworkURLSession *existingSession = + [[[self class] sessionIDToFetcherMap] objectForKey:sessionID]; + if (existingSession) { + if (session) { + NSString *message = [NSString stringWithFormat:@"Discarding session: %@", existingSession]; + [existingSession->_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelInfo + messageCode:kGULNetworkMessageCodeURLSession019 + message:message]; + } + [existingSession->_URLSession finishTasksAndInvalidate]; + } + if (session) { + [[[self class] sessionIDToFetcherMap] setObject:session forKey:sessionID]; + } else { + [[[self class] sessionIDToFetcherMap] removeObjectForKey:sessionID]; + } + [[self sessionIDToFetcherMapReadWriteLock] unlock]; +} + ++ (nullable GULNetworkURLSession *)sessionFromFetcherMapForSessionID:(NSString *)sessionID { + [[self sessionIDToFetcherMapReadWriteLock] lock]; + GULNetworkURLSession *session = [[[self class] sessionIDToFetcherMap] objectForKey:sessionID]; + [[self sessionIDToFetcherMapReadWriteLock] unlock]; + return session; +} + +- (void)callCompletionHandler:(GULNetworkURLSessionCompletionHandler)handler + withResponse:(NSHTTPURLResponse *)response + data:(NSData *)data + error:(NSError *)error { + if (error) { + [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError + messageCode:kGULNetworkMessageCodeURLSession017 + message:@"Encounter network error. Code, error" + contexts:@[ @(error.code), error ]]; + } + + if (handler) { + dispatch_async(dispatch_get_main_queue(), ^{ + handler(response, data, self->_sessionID, error); + }); + } +} + +// Always use the request parameters even if the default session configuration is more restrictive. +- (void)populateSessionConfig:(NSURLSessionConfiguration *)sessionConfig + withRequest:(NSURLRequest *)request API_AVAILABLE(ios(7.0)) { + sessionConfig.HTTPAdditionalHeaders = request.allHTTPHeaderFields; + sessionConfig.timeoutIntervalForRequest = request.timeoutInterval; + sessionConfig.timeoutIntervalForResource = request.timeoutInterval; + sessionConfig.requestCachePolicy = request.cachePolicy; +} + +@end diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/Private/GULMutableDictionary.h b/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/Private/GULMutableDictionary.h new file mode 100644 index 0000000..a8cc45b --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/Private/GULMutableDictionary.h @@ -0,0 +1,46 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +/// A mutable dictionary that provides atomic accessor and mutators. +@interface GULMutableDictionary : NSObject + +/// Returns an object given a key in the dictionary or nil if not found. +- (id)objectForKey:(id)key; + +/// Updates the object given its key or adds it to the dictionary if it is not in the dictionary. +- (void)setObject:(id)object forKey:(id)key; + +/// Removes the object given its session ID from the dictionary. +- (void)removeObjectForKey:(id)key; + +/// Removes all objects. +- (void)removeAllObjects; + +/// Returns the number of current objects in the dictionary. +- (NSUInteger)count; + +/// Returns an object given a key in the dictionary or nil if not found. +- (id)objectForKeyedSubscript:(id)key; + +/// Updates the object given its key or adds it to the dictionary if it is not in the dictionary. +- (void)setObject:(id)obj forKeyedSubscript:(id)key; + +/// Returns the immutable dictionary. +- (NSDictionary *)dictionary; + +@end diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/Private/GULNetwork.h b/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/Private/GULNetwork.h new file mode 100644 index 0000000..0e75ae5 --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/Private/GULNetwork.h @@ -0,0 +1,87 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "GULNetworkConstants.h" +#import "GULNetworkLoggerProtocol.h" +#import "GULNetworkURLSession.h" + +/// Delegate protocol for GULNetwork events. +@protocol GULNetworkReachabilityDelegate + +/// Tells the delegate to handle events when the network reachability changes to connected or not +/// connected. +- (void)reachabilityDidChange; + +@end + +/// The Network component that provides network status and handles network requests and responses. +/// This is not thread safe. +/// +/// NOTE: +/// User must add FIRAnalytics handleEventsForBackgroundURLSessionID:completionHandler to the +/// AppDelegate application:handleEventsForBackgroundURLSession:completionHandler: +@interface GULNetwork : NSObject + +/// Indicates if network connectivity is available. +@property(nonatomic, readonly, getter=isNetworkConnected) BOOL networkConnected; + +/// Indicates if there are any uploads in progress. +@property(nonatomic, readonly, getter=hasUploadInProgress) BOOL uploadInProgress; + +/// An optional delegate that can be used in the event when network reachability changes. +@property(nonatomic, weak) id reachabilityDelegate; + +/// An optional delegate that can be used to log messages, warnings or errors that occur in the +/// network operations. +@property(nonatomic, weak) id loggerDelegate; + +/// Indicates whether the logger should display debug messages. +@property(nonatomic, assign) BOOL isDebugModeEnabled; + +/// The time interval in seconds for the network request to timeout. +@property(nonatomic, assign) NSTimeInterval timeoutInterval; + +/// Initializes with the default reachability host. +- (instancetype)init; + +/// Initializes with a custom reachability host. +- (instancetype)initWithReachabilityHost:(NSString *)reachabilityHost; + +/// Handles events when background session with the given ID has finished. ++ (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID + completionHandler:(GULNetworkSystemCompletionHandler)completionHandler; + +/// Compresses and sends a POST request with the provided data to the URL. The session will be +/// background session if usingBackgroundSession is YES. Otherwise, the POST session is default +/// session. Returns a session ID or nil if an error occurs. +- (NSString *)postURL:(NSURL *)url + payload:(NSData *)payload + queue:(dispatch_queue_t)queue + usingBackgroundSession:(BOOL)usingBackgroundSession + completionHandler:(GULNetworkCompletionHandler)handler; + +/// Sends a GET request with the provided data to the URL. The session will be background session +/// if usingBackgroundSession is YES. Otherwise, the GET session is default session. Returns a +/// session ID or nil if an error occurs. +- (NSString *)getURL:(NSURL *)url + headers:(NSDictionary *)headers + queue:(dispatch_queue_t)queue + usingBackgroundSession:(BOOL)usingBackgroundSession + completionHandler:(GULNetworkCompletionHandler)handler; + +@end diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/Private/GULNetworkConstants.h b/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/Private/GULNetworkConstants.h new file mode 100644 index 0000000..44d440b --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/Private/GULNetworkConstants.h @@ -0,0 +1,79 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import +#import + +/// Error codes in Firebase Network error domain. +/// Note: these error codes should never change. It would make it harder to decode the errors if +/// we inadvertently altered any of these codes in a future SDK version. +typedef NS_ENUM(NSInteger, GULNetworkErrorCode) { + /// Unknown error. + GULNetworkErrorCodeUnknown = 0, + /// Error occurs when the request URL is invalid. + GULErrorCodeNetworkInvalidURL = 1, + /// Error occurs when request cannot be constructed. + GULErrorCodeNetworkRequestCreation = 2, + /// Error occurs when payload cannot be compressed. + GULErrorCodeNetworkPayloadCompression = 3, + /// Error occurs when session task cannot be created. + GULErrorCodeNetworkSessionTaskCreation = 4, + /// Error occurs when there is no response. + GULErrorCodeNetworkInvalidResponse = 5 +}; + +#pragma mark - Network constants + +/// The prefix of the ID of the background session. +extern NSString *const kGULNetworkBackgroundSessionConfigIDPrefix; + +/// The sub directory to store the files of data that is being uploaded in the background. +extern NSString *const kGULNetworkApplicationSupportSubdirectory; + +/// Name of the temporary directory that stores files for background uploading. +extern NSString *const kGULNetworkTempDirectoryName; + +/// The period when the temporary uploading file can stay. +extern const NSTimeInterval kGULNetworkTempFolderExpireTime; + +/// The default network request timeout interval. +extern const NSTimeInterval kGULNetworkTimeOutInterval; + +/// The host to check the reachability of the network. +extern NSString *const kGULNetworkReachabilityHost; + +/// The key to get the error context of the UserInfo. +extern NSString *const kGULNetworkErrorContext; + +#pragma mark - Network Status Code + +extern const int kGULNetworkHTTPStatusOK; +extern const int kGULNetworkHTTPStatusNoContent; +extern const int kGULNetworkHTTPStatusCodeMultipleChoices; +extern const int kGULNetworkHTTPStatusCodeMovedPermanently; +extern const int kGULNetworkHTTPStatusCodeFound; +extern const int kGULNetworkHTTPStatusCodeNotModified; +extern const int kGULNetworkHTTPStatusCodeMovedTemporarily; +extern const int kGULNetworkHTTPStatusCodeNotFound; +extern const int kGULNetworkHTTPStatusCodeCannotAcceptTraffic; +extern const int kGULNetworkHTTPStatusCodeUnavailable; + +#pragma mark - Error Domain + +extern NSString *const kGULNetworkErrorDomain; + +/// The logger service for GULNetwork. +extern GULLoggerService kGULLoggerNetwork; diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/Private/GULNetworkLoggerProtocol.h b/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/Private/GULNetworkLoggerProtocol.h new file mode 100644 index 0000000..f1be590 --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/Private/GULNetworkLoggerProtocol.h @@ -0,0 +1,51 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import + +#import "GULNetworkMessageCode.h" + +/// The log levels used by GULNetworkLogger. +typedef NS_ENUM(NSInteger, GULNetworkLogLevel) { + kGULNetworkLogLevelError = GULLoggerLevelError, + kGULNetworkLogLevelWarning = GULLoggerLevelWarning, + kGULNetworkLogLevelInfo = GULLoggerLevelInfo, + kGULNetworkLogLevelDebug = GULLoggerLevelDebug, +}; + +@protocol GULNetworkLoggerDelegate + +@required +/// Tells the delegate to log a message with an array of contexts and the log level. +- (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevel + messageCode:(GULNetworkMessageCode)messageCode + message:(NSString *)message + contexts:(NSArray *)contexts; + +/// Tells the delegate to log a message with a context and the log level. +- (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevel + messageCode:(GULNetworkMessageCode)messageCode + message:(NSString *)message + context:(id)context; + +/// Tells the delegate to log a message with the log level. +- (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevel + messageCode:(GULNetworkMessageCode)messageCode + message:(NSString *)message; + +@end diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/Private/GULNetworkMessageCode.h b/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/Private/GULNetworkMessageCode.h new file mode 100644 index 0000000..507bc5a --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/Private/GULNetworkMessageCode.h @@ -0,0 +1,47 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +// Make sure these codes do not overlap with any contained in the FIRAMessageCode enum. +typedef NS_ENUM(NSInteger, GULNetworkMessageCode) { + // GULNetwork.m + kGULNetworkMessageCodeNetwork000 = 900000, // I-NET900000 + kGULNetworkMessageCodeNetwork001 = 900001, // I-NET900001 + kGULNetworkMessageCodeNetwork002 = 900002, // I-NET900002 + kGULNetworkMessageCodeNetwork003 = 900003, // I-NET900003 + // GULNetworkURLSession.m + kGULNetworkMessageCodeURLSession000 = 901000, // I-NET901000 + kGULNetworkMessageCodeURLSession001 = 901001, // I-NET901001 + kGULNetworkMessageCodeURLSession002 = 901002, // I-NET901002 + kGULNetworkMessageCodeURLSession003 = 901003, // I-NET901003 + kGULNetworkMessageCodeURLSession004 = 901004, // I-NET901004 + kGULNetworkMessageCodeURLSession005 = 901005, // I-NET901005 + kGULNetworkMessageCodeURLSession006 = 901006, // I-NET901006 + kGULNetworkMessageCodeURLSession007 = 901007, // I-NET901007 + kGULNetworkMessageCodeURLSession008 = 901008, // I-NET901008 + kGULNetworkMessageCodeURLSession009 = 901009, // I-NET901009 + kGULNetworkMessageCodeURLSession010 = 901010, // I-NET901010 + kGULNetworkMessageCodeURLSession011 = 901011, // I-NET901011 + kGULNetworkMessageCodeURLSession012 = 901012, // I-NET901012 + kGULNetworkMessageCodeURLSession013 = 901013, // I-NET901013 + kGULNetworkMessageCodeURLSession014 = 901014, // I-NET901014 + kGULNetworkMessageCodeURLSession015 = 901015, // I-NET901015 + kGULNetworkMessageCodeURLSession016 = 901016, // I-NET901016 + kGULNetworkMessageCodeURLSession017 = 901017, // I-NET901017 + kGULNetworkMessageCodeURLSession018 = 901018, // I-NET901018 + kGULNetworkMessageCodeURLSession019 = 901019, // I-NET901019 +}; diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/Private/GULNetworkURLSession.h b/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/Private/GULNetworkURLSession.h new file mode 100644 index 0000000..3f9f7f9 --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/Network/Private/GULNetworkURLSession.h @@ -0,0 +1,62 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "GULNetworkLoggerProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef void (^GULNetworkCompletionHandler)(NSHTTPURLResponse *_Nullable response, + NSData *_Nullable data, + NSError *_Nullable error); +typedef void (^GULNetworkURLSessionCompletionHandler)(NSHTTPURLResponse *_Nullable response, + NSData *_Nullable data, + NSString *sessionID, + NSError *_Nullable error); +typedef void (^GULNetworkSystemCompletionHandler)(void); + +/// The protocol that uses NSURLSession for iOS >= 7.0 to handle requests and responses. +@interface GULNetworkURLSession : NSObject + +/// Indicates whether the background network is enabled. Default value is NO. +@property(nonatomic, getter=isBackgroundNetworkEnabled) BOOL backgroundNetworkEnabled; + +/// The logger delegate to log message, errors or warnings that occur during the network operations. +@property(nonatomic, weak, nullable) id loggerDelegate; + +/// Calls the system provided completion handler after the background session is finished. ++ (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID + completionHandler:(GULNetworkSystemCompletionHandler)completionHandler; + +/// Initializes with logger delegate. +- (instancetype)initWithNetworkLoggerDelegate: + (nullable id)networkLoggerDelegate NS_DESIGNATED_INITIALIZER; + +- (instancetype)init NS_UNAVAILABLE; + +/// Sends an asynchronous POST request and calls the provided completion handler when the request +/// completes or when errors occur, and returns an ID of the session/connection. +- (nullable NSString *)sessionIDFromAsyncPOSTRequest:(NSURLRequest *)request + completionHandler:(GULNetworkURLSessionCompletionHandler)handler; + +/// Sends an asynchronous GET request and calls the provided completion handler when the request +/// completes or when errors occur, and returns an ID of the session. +- (nullable NSString *)sessionIDFromAsyncGETRequest:(NSURLRequest *)request + completionHandler:(GULNetworkURLSessionCompletionHandler)handler; + +NS_ASSUME_NONNULL_END +@end diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/Reachability/GULReachabilityChecker+Internal.h b/!main project/Pods/GoogleUtilities/GoogleUtilities/Reachability/GULReachabilityChecker+Internal.h new file mode 100644 index 0000000..8aabc8a --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/Reachability/GULReachabilityChecker+Internal.h @@ -0,0 +1,47 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import +#if !TARGET_OS_WATCH +typedef SCNetworkReachabilityRef (*GULReachabilityCreateWithNameFn)(CFAllocatorRef allocator, + const char *host); + +typedef Boolean (*GULReachabilitySetCallbackFn)(SCNetworkReachabilityRef target, + SCNetworkReachabilityCallBack callback, + SCNetworkReachabilityContext *context); +typedef Boolean (*GULReachabilityScheduleWithRunLoopFn)(SCNetworkReachabilityRef target, + CFRunLoopRef runLoop, + CFStringRef runLoopMode); +typedef Boolean (*GULReachabilityUnscheduleFromRunLoopFn)(SCNetworkReachabilityRef target, + CFRunLoopRef runLoop, + CFStringRef runLoopMode); + +typedef void (*GULReachabilityReleaseFn)(CFTypeRef cf); + +struct GULReachabilityApi { + GULReachabilityCreateWithNameFn createWithNameFn; + GULReachabilitySetCallbackFn setCallbackFn; + GULReachabilityScheduleWithRunLoopFn scheduleWithRunLoopFn; + GULReachabilityUnscheduleFromRunLoopFn unscheduleFromRunLoopFn; + GULReachabilityReleaseFn releaseFn; +}; +#endif +@interface GULReachabilityChecker (Internal) + +- (const struct GULReachabilityApi *)reachabilityApi; +- (void)setReachabilityApi:(const struct GULReachabilityApi *)reachabilityApi; + +@end diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/Reachability/GULReachabilityChecker.m b/!main project/Pods/GoogleUtilities/GoogleUtilities/Reachability/GULReachabilityChecker.m new file mode 100644 index 0000000..a334c1a --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/Reachability/GULReachabilityChecker.m @@ -0,0 +1,263 @@ +// Copyright 2017 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +#import "GoogleUtilities/Reachability/GULReachabilityChecker+Internal.h" +#import "GoogleUtilities/Reachability/Private/GULReachabilityChecker.h" +#import "GoogleUtilities/Reachability/Private/GULReachabilityMessageCode.h" + +#import +#import + +static GULLoggerService kGULLoggerReachability = @"[GULReachability]"; +#if !TARGET_OS_WATCH +static void ReachabilityCallback(SCNetworkReachabilityRef reachability, + SCNetworkReachabilityFlags flags, + void *info); + +static const struct GULReachabilityApi kGULDefaultReachabilityApi = { + SCNetworkReachabilityCreateWithName, + SCNetworkReachabilitySetCallback, + SCNetworkReachabilityScheduleWithRunLoop, + SCNetworkReachabilityUnscheduleFromRunLoop, + CFRelease, +}; + +static NSString *const kGULReachabilityUnknownStatus = @"Unknown"; +static NSString *const kGULReachabilityConnectedStatus = @"Connected"; +static NSString *const kGULReachabilityDisconnectedStatus = @"Disconnected"; +#endif +@interface GULReachabilityChecker () + +@property(nonatomic, assign) const struct GULReachabilityApi *reachabilityApi; +@property(nonatomic, assign) GULReachabilityStatus reachabilityStatus; +@property(nonatomic, copy) NSString *host; +#if !TARGET_OS_WATCH +@property(nonatomic, assign) SCNetworkReachabilityRef reachability; +#endif + +@end + +@implementation GULReachabilityChecker + +@synthesize reachabilityApi = reachabilityApi_; +#if !TARGET_OS_WATCH +@synthesize reachability = reachability_; +#endif + +- (const struct GULReachabilityApi *)reachabilityApi { + return reachabilityApi_; +} + +- (void)setReachabilityApi:(const struct GULReachabilityApi *)reachabilityApi { +#if !TARGET_OS_WATCH + if (reachability_) { + GULLogError(kGULLoggerReachability, NO, + [NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode000], + @"Cannot change reachability API while reachability is running. " + @"Call stop first."); + return; + } + reachabilityApi_ = reachabilityApi; +#endif +} + +@synthesize reachabilityStatus = reachabilityStatus_; +@synthesize host = host_; +@synthesize reachabilityDelegate = reachabilityDelegate_; + +- (BOOL)isActive { +#if !TARGET_OS_WATCH + return reachability_ != nil; +#else + return NO; +#endif +} + +- (void)setReachabilityDelegate:(id)reachabilityDelegate { + if (reachabilityDelegate && + (![(NSObject *)reachabilityDelegate conformsToProtocol:@protocol(GULReachabilityDelegate)])) { + GULLogError(kGULLoggerReachability, NO, + [NSString stringWithFormat:@"I-NET%06ld", (long)kGULReachabilityMessageCode005], + @"Reachability delegate doesn't conform to Reachability protocol."); + return; + } + reachabilityDelegate_ = reachabilityDelegate; +} + +- (instancetype)initWithReachabilityDelegate:(id)reachabilityDelegate + withHost:(NSString *)host { + self = [super init]; + + if (!host || !host.length) { + GULLogError(kGULLoggerReachability, NO, + [NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode001], + @"Invalid host specified"); + return nil; + } + if (self) { +#if !TARGET_OS_WATCH + [self setReachabilityDelegate:reachabilityDelegate]; + reachabilityApi_ = &kGULDefaultReachabilityApi; + reachabilityStatus_ = kGULReachabilityUnknown; + host_ = [host copy]; + reachability_ = nil; +#endif + } + return self; +} + +- (void)dealloc { + reachabilityDelegate_ = nil; + [self stop]; +} + +- (BOOL)start { +#if TARGET_OS_WATCH + return NO; +#else + + if (!reachability_) { + reachability_ = reachabilityApi_->createWithNameFn(kCFAllocatorDefault, [host_ UTF8String]); + if (!reachability_) { + return NO; + } + SCNetworkReachabilityContext context = { + 0, /* version */ + (__bridge void *)(self), /* info (passed as last parameter to reachability callback) */ + NULL, /* retain */ + NULL, /* release */ + NULL /* copyDescription */ + }; + if (!reachabilityApi_->setCallbackFn(reachability_, ReachabilityCallback, &context) || + !reachabilityApi_->scheduleWithRunLoopFn(reachability_, CFRunLoopGetMain(), + kCFRunLoopCommonModes)) { + reachabilityApi_->releaseFn(reachability_); + reachability_ = nil; + + GULLogError(kGULLoggerReachability, NO, + [NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode002], + @"Failed to start reachability handle"); + return NO; + } + } + GULLogDebug(kGULLoggerReachability, NO, + [NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode003], + @"Monitoring the network status"); + return YES; +#endif +} + +- (void)stop { +#if !TARGET_OS_WATCH + if (reachability_) { + reachabilityStatus_ = kGULReachabilityUnknown; + reachabilityApi_->unscheduleFromRunLoopFn(reachability_, CFRunLoopGetMain(), + kCFRunLoopCommonModes); + reachabilityApi_->releaseFn(reachability_); + reachability_ = nil; + } +#endif +} + +#if !TARGET_OS_WATCH +- (GULReachabilityStatus)statusForFlags:(SCNetworkReachabilityFlags)flags { + GULReachabilityStatus status = kGULReachabilityNotReachable; + // If the Reachable flag is not set, we definitely don't have connectivity. + if (flags & kSCNetworkReachabilityFlagsReachable) { + // Reachable flag is set. Check further flags. + if (!(flags & kSCNetworkReachabilityFlagsConnectionRequired)) { +// Connection required flag is not set, so we have connectivity. +#if TARGET_OS_IOS || TARGET_OS_TV + status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kGULReachabilityViaCellular + : kGULReachabilityViaWifi; +#elif TARGET_OS_OSX + status = kGULReachabilityViaWifi; +#endif + } else if ((flags & (kSCNetworkReachabilityFlagsConnectionOnDemand | + kSCNetworkReachabilityFlagsConnectionOnTraffic)) && + !(flags & kSCNetworkReachabilityFlagsInterventionRequired)) { +// If the connection on demand or connection on traffic flag is set, and user intervention +// is not required, we have connectivity. +#if TARGET_OS_IOS || TARGET_OS_TV + status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kGULReachabilityViaCellular + : kGULReachabilityViaWifi; +#elif TARGET_OS_OSX + status = kGULReachabilityViaWifi; +#endif + } + } + return status; +} + +- (void)reachabilityFlagsChanged:(SCNetworkReachabilityFlags)flags { + GULReachabilityStatus status = [self statusForFlags:flags]; + if (reachabilityStatus_ != status) { + NSString *reachabilityStatusString; + if (status == kGULReachabilityUnknown) { + reachabilityStatusString = kGULReachabilityUnknownStatus; + } else { + reachabilityStatusString = (status == kGULReachabilityNotReachable) + ? kGULReachabilityDisconnectedStatus + : kGULReachabilityConnectedStatus; + } + + GULLogDebug(kGULLoggerReachability, NO, + [NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode004], + @"Network status has changed. Code:%@, status:%@", @(status), + reachabilityStatusString); + reachabilityStatus_ = status; + [reachabilityDelegate_ reachability:self statusChanged:reachabilityStatus_]; + } +} + +#endif +@end + +#if !TARGET_OS_WATCH +static void ReachabilityCallback(SCNetworkReachabilityRef reachability, + SCNetworkReachabilityFlags flags, + void *info) { + GULReachabilityChecker *checker = (__bridge GULReachabilityChecker *)info; + [checker reachabilityFlagsChanged:flags]; +} +#endif + +// This function used to be at the top of the file, but it was moved here +// as a workaround for a suspected compiler bug. When compiled in Release mode +// and run on an iOS device with WiFi disabled, the reachability code crashed +// when calling SCNetworkReachabilityScheduleWithRunLoop, or shortly thereafter. +// After unsuccessfully trying to diagnose the cause of the crash, it was +// discovered that moving this function to the end of the file magically fixed +// the crash. If you are going to edit this file, exercise caution and make sure +// to test thoroughly with an iOS device under various network conditions. +const NSString *GULReachabilityStatusString(GULReachabilityStatus status) { + switch (status) { + case kGULReachabilityUnknown: + return @"Reachability Unknown"; + + case kGULReachabilityNotReachable: + return @"Not reachable"; + + case kGULReachabilityViaWifi: + return @"Reachable via Wifi"; + + case kGULReachabilityViaCellular: + return @"Reachable via Cellular Data"; + + default: + return [NSString stringWithFormat:@"Invalid reachability status %d", (int)status]; + } +} diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/Reachability/Private/GULReachabilityChecker.h b/!main project/Pods/GoogleUtilities/GoogleUtilities/Reachability/Private/GULReachabilityChecker.h new file mode 100644 index 0000000..0c70c05 --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/Reachability/Private/GULReachabilityChecker.h @@ -0,0 +1,79 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import +#if !TARGET_OS_WATCH +#import +#endif + +/// Reachability Status +typedef enum { + kGULReachabilityUnknown, ///< Have not yet checked or been notified whether host is reachable. + kGULReachabilityNotReachable, ///< Host is not reachable. + kGULReachabilityViaWifi, ///< Host is reachable via Wifi. + kGULReachabilityViaCellular, ///< Host is reachable via cellular. +} GULReachabilityStatus; + +const NSString *GULReachabilityStatusString(GULReachabilityStatus status); + +@class GULReachabilityChecker; + +/// Google Analytics iOS Reachability Checker. +@protocol GULReachabilityDelegate +@required +/// Called when network status has changed. +- (void)reachability:(GULReachabilityChecker *)reachability + statusChanged:(GULReachabilityStatus)status; +@end + +/// Google Analytics iOS Network Status Checker. +@interface GULReachabilityChecker : NSObject + +/// The last known reachability status, or GULReachabilityStatusUnknown if the +/// checker is not active. +@property(nonatomic, readonly) GULReachabilityStatus reachabilityStatus; +/// The host to which reachability status is to be checked. +@property(nonatomic, copy, readonly) NSString *host; +/// The delegate to be notified of reachability status changes. +@property(nonatomic, weak) id reachabilityDelegate; +/// `YES` if the reachability checker is active, `NO` otherwise. +@property(nonatomic, readonly) BOOL isActive; + +/// Initialize the reachability checker. Note that you must call start to begin checking for and +/// receiving notifications about network status changes. +/// +/// @param reachabilityDelegate The delegate to be notified when reachability status to host +/// changes. +/// +/// @param host The name of the host. +/// +- (instancetype)initWithReachabilityDelegate:(id)reachabilityDelegate + withHost:(NSString *)host; + +- (instancetype)init NS_UNAVAILABLE; + +/// Start checking for reachability to the specified host. This has no effect if the status +/// checker is already checking for connectivity. +/// +/// @return `YES` if initiating status checking was successful or the status checking has already +/// been initiated, `NO` otherwise. +- (BOOL)start; + +/// Stop checking for reachability to the specified host. This has no effect if the status +/// checker is not checking for connectivity. +- (void)stop; + +@end diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/Reachability/Private/GULReachabilityMessageCode.h b/!main project/Pods/GoogleUtilities/GoogleUtilities/Reachability/Private/GULReachabilityMessageCode.h new file mode 100644 index 0000000..373e0af --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/Reachability/Private/GULReachabilityMessageCode.h @@ -0,0 +1,29 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +// Make sure these codes do not overlap with any contained in the FIRAMessageCode enum. +typedef NS_ENUM(NSInteger, GULReachabilityMessageCode) { + // GULReachabilityChecker.m + kGULReachabilityMessageCode000 = 902000, // I-NET902000 + kGULReachabilityMessageCode001 = 902001, // I-NET902001 + kGULReachabilityMessageCode002 = 902002, // I-NET902002 + kGULReachabilityMessageCode003 = 902003, // I-NET902003 + kGULReachabilityMessageCode004 = 902004, // I-NET902004 + kGULReachabilityMessageCode005 = 902005, // I-NET902005 + kGULReachabilityMessageCode006 = 902006, // I-NET902006 +}; diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/SceneDelegateSwizzler/GULSceneDelegateSwizzler.m b/!main project/Pods/GoogleUtilities/GoogleUtilities/SceneDelegateSwizzler/GULSceneDelegateSwizzler.m new file mode 100644 index 0000000..b80e2af --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/SceneDelegateSwizzler/GULSceneDelegateSwizzler.m @@ -0,0 +1,438 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "TargetConditionals.h" + +#import +#import +#import +#import +#import +#import "GoogleUtilities/Common/GULLoggerCodes.h" +#import "GoogleUtilities/SceneDelegateSwizzler/Internal/GULSceneDelegateSwizzler_Private.h" + +#import + +#if UISCENE_SUPPORTED +API_AVAILABLE(ios(13.0), tvos(13.0)) +typedef void (*GULOpenURLContextsIMP)(id, SEL, UIScene *, NSSet *); + +API_AVAILABLE(ios(13.0), tvos(13.0)) +typedef void (^GULSceneDelegateInterceptorCallback)(id); + +// The strings below are the keys for associated objects. +static char const *const kGULRealIMPBySelectorKey = "GUL_realIMPBySelector"; +static char const *const kGULRealClassKey = "GUL_realClass"; +#endif // UISCENE_SUPPORTED + +static GULLoggerService kGULLoggerSwizzler = @"[GoogleUtilities/SceneDelegateSwizzler]"; + +// Since Firebase SDKs also use this for app delegate proxying, in order to not be a breaking change +// we disable App Delegate proxying when either of these two flags are set to NO. + +/** Plist key that allows Firebase developers to disable App and Scene Delegate Proxying. */ +static NSString *const kGULFirebaseSceneDelegateProxyEnabledPlistKey = + @"FirebaseAppDelegateProxyEnabled"; + +/** Plist key that allows developers not using Firebase to disable App and Scene Delegate Proxying. + */ +static NSString *const kGULGoogleUtilitiesSceneDelegateProxyEnabledPlistKey = + @"GoogleUtilitiesAppDelegateProxyEnabled"; + +/** The prefix of the Scene Delegate. */ +static NSString *const kGULSceneDelegatePrefix = @"GUL_"; + +/** + * This class is necessary to store the delegates in an NSArray without retaining them. + * [NSValue valueWithNonRetainedObject] also provides this functionality, but does not provide a + * zeroing pointer. This will cause EXC_BAD_ACCESS when trying to access the object after it is + * dealloced. Instead, this container stores a weak, zeroing reference to the object, which + * automatically is set to nil by the runtime when the object is dealloced. + */ +@interface GULSceneZeroingWeakContainer : NSObject + +/** Stores a weak object. */ +@property(nonatomic, weak) id object; + +@end + +@implementation GULSceneZeroingWeakContainer +@end + +@implementation GULSceneDelegateSwizzler + +#pragma mark - Public methods + ++ (BOOL)isSceneDelegateProxyEnabled { + return [GULAppDelegateSwizzler isAppDelegateProxyEnabled]; +} + ++ (void)proxyOriginalSceneDelegate { +#if UISCENE_SUPPORTED + if ([GULAppEnvironmentUtil isAppExtension]) { + return; + } + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + if (@available(iOS 13.0, tvOS 13.0, *)) { + if (![GULSceneDelegateSwizzler isSceneDelegateProxyEnabled]) { + return; + } + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(handleSceneWillConnectToNotification:) + name:UISceneWillConnectNotification + object:nil]; + } + }); +#endif // UISCENE_SUPPORTED +} + +#if UISCENE_SUPPORTED ++ (GULSceneDelegateInterceptorID)registerSceneDelegateInterceptor:(id)interceptor { + NSAssert(interceptor, @"SceneDelegateProxy cannot add nil interceptor"); + NSAssert([interceptor conformsToProtocol:@protocol(UISceneDelegate)], + @"SceneDelegateProxy interceptor does not conform to UIApplicationDelegate"); + + if (!interceptor) { + GULLogError(kGULLoggerSwizzler, NO, + [NSString stringWithFormat:@"I-SWZ%06ld", + (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling000], + @"SceneDelegateProxy cannot add nil interceptor."); + return nil; + } + if (![interceptor conformsToProtocol:@protocol(UISceneDelegate)]) { + GULLogError(kGULLoggerSwizzler, NO, + [NSString stringWithFormat:@"I-SWZ%06ld", + (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling001], + @"SceneDelegateProxy interceptor does not conform to UIApplicationDelegate"); + return nil; + } + + // The ID should be the same given the same interceptor object. + NSString *interceptorID = + [NSString stringWithFormat:@"%@%p", kGULSceneDelegatePrefix, interceptor]; + if (!interceptorID.length) { + GULLogError(kGULLoggerSwizzler, NO, + [NSString stringWithFormat:@"I-SWZ%06ld", + (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling002], + @"SceneDelegateProxy cannot create Interceptor ID."); + return nil; + } + GULSceneZeroingWeakContainer *weakObject = [[GULSceneZeroingWeakContainer alloc] init]; + weakObject.object = interceptor; + [GULSceneDelegateSwizzler interceptors][interceptorID] = weakObject; + return interceptorID; +} + ++ (void)unregisterSceneDelegateInterceptorWithID:(GULSceneDelegateInterceptorID)interceptorID { + NSAssert(interceptorID, @"SceneDelegateProxy cannot unregister nil interceptor ID."); + NSAssert(((NSString *)interceptorID).length != 0, + @"SceneDelegateProxy cannot unregister empty interceptor ID."); + + if (!interceptorID) { + GULLogError(kGULLoggerSwizzler, NO, + [NSString stringWithFormat:@"I-SWZ%06ld", + (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling003], + @"SceneDelegateProxy cannot unregister empty interceptor ID."); + return; + } + + GULSceneZeroingWeakContainer *weakContainer = + [GULSceneDelegateSwizzler interceptors][interceptorID]; + if (!weakContainer.object) { + GULLogError(kGULLoggerSwizzler, NO, + [NSString stringWithFormat:@"I-SWZ%06ld", + (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling004], + @"SceneDelegateProxy cannot unregister interceptor that was not registered. " + "Interceptor ID %@", + interceptorID); + return; + } + + [[GULSceneDelegateSwizzler interceptors] removeObjectForKey:interceptorID]; +} + +#pragma mark - Helper methods + ++ (GULMutableDictionary *)interceptors { + static dispatch_once_t onceToken; + static GULMutableDictionary *sInterceptors; + dispatch_once(&onceToken, ^{ + sInterceptors = [[GULMutableDictionary alloc] init]; + }); + return sInterceptors; +} + ++ (void)clearInterceptors { + [[self interceptors] removeAllObjects]; +} + ++ (nullable NSValue *)originalImplementationForSelector:(SEL)selector object:(id)object { + NSDictionary *realImplementationBySelector = + objc_getAssociatedObject(object, &kGULRealIMPBySelectorKey); + return realImplementationBySelector[NSStringFromSelector(selector)]; +} + ++ (void)proxyDestinationSelector:(SEL)destinationSelector + implementationsFromSourceSelector:(SEL)sourceSelector + fromClass:(Class)sourceClass + toClass:(Class)destinationClass + realClass:(Class)realClass + storeDestinationImplementationTo: + (NSMutableDictionary *)destinationImplementationsBySelector { + [self addInstanceMethodWithDestinationSelector:destinationSelector + withImplementationFromSourceSelector:sourceSelector + fromClass:sourceClass + toClass:destinationClass]; + IMP sourceImplementation = + [GULSceneDelegateSwizzler implementationOfMethodSelector:destinationSelector + fromClass:realClass]; + NSValue *sourceImplementationPointer = [NSValue valueWithPointer:sourceImplementation]; + + NSString *destinationSelectorString = NSStringFromSelector(destinationSelector); + destinationImplementationsBySelector[destinationSelectorString] = sourceImplementationPointer; +} + +/** Copies a method identified by the methodSelector from one class to the other. After this method + * is called, performing [toClassInstance methodSelector] will be similar to calling + * [fromClassInstance methodSelector]. This method does nothing if toClass already has a method + * identified by methodSelector. + * + * @param methodSelector The SEL that identifies both the method on the fromClass as well as the + * one on the toClass. + * @param fromClass The class from which a method is sourced. + * @param toClass The class to which the method is added. If the class already has a method with + * the same selector, this has no effect. + */ ++ (void)addInstanceMethodWithSelector:(SEL)methodSelector + fromClass:(Class)fromClass + toClass:(Class)toClass { + [self addInstanceMethodWithDestinationSelector:methodSelector + withImplementationFromSourceSelector:methodSelector + fromClass:fromClass + toClass:toClass]; +} + +/** Copies a method identified by the sourceSelector from the fromClass as a method for the + * destinationSelector on the toClass. After this method is called, performing + * [toClassInstance destinationSelector] will be similar to calling + * [fromClassInstance sourceSelector]. This method does nothing if toClass already has a method + * identified by destinationSelector. + * + * @param destinationSelector The SEL that identifies the method on the toClass. + * @param sourceSelector The SEL that identifies the method on the fromClass. + * @param fromClass The class from which a method is sourced. + * @param toClass The class to which the method is added. If the class already has a method with + * the same selector, this has no effect. + */ ++ (void)addInstanceMethodWithDestinationSelector:(SEL)destinationSelector + withImplementationFromSourceSelector:(SEL)sourceSelector + fromClass:(Class)fromClass + toClass:(Class)toClass { + Method method = class_getInstanceMethod(fromClass, sourceSelector); + IMP methodIMP = method_getImplementation(method); + const char *types = method_getTypeEncoding(method); + if (!class_addMethod(toClass, destinationSelector, methodIMP, types)) { + GULLogWarning( + kGULLoggerSwizzler, NO, + [NSString + stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling009], + @"Cannot copy method to destination selector %@ as it already exists", + NSStringFromSelector(destinationSelector)); + } +} + +/** Gets the IMP of the instance method on the class identified by the selector. + * + * @param selector The selector of which the IMP is to be fetched. + * @param aClass The class from which the IMP is to be fetched. + * @return The IMP of the instance method identified by selector and aClass. + */ ++ (IMP)implementationOfMethodSelector:(SEL)selector fromClass:(Class)aClass { + Method aMethod = class_getInstanceMethod(aClass, selector); + return method_getImplementation(aMethod); +} + +/** Enumerates through all the interceptors and if they respond to a given selector, executes a + * GULSceneDelegateInterceptorCallback with the interceptor. + * + * @param methodSelector The SEL to check if an interceptor responds to. + * @param callback the GULSceneDelegateInterceptorCallback. + */ ++ (void)notifyInterceptorsWithMethodSelector:(SEL)methodSelector + callback:(GULSceneDelegateInterceptorCallback)callback + API_AVAILABLE(ios(13.0)) { + if (!callback) { + return; + } + + NSDictionary *interceptors = [GULSceneDelegateSwizzler interceptors].dictionary; + [interceptors enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + GULSceneZeroingWeakContainer *interceptorContainer = obj; + id interceptor = interceptorContainer.object; + if (!interceptor) { + GULLogWarning( + kGULLoggerSwizzler, NO, + [NSString stringWithFormat:@"I-SWZ%06ld", + (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling010], + @"SceneDelegateProxy cannot find interceptor with ID %@. Removing the interceptor.", key); + [[GULSceneDelegateSwizzler interceptors] removeObjectForKey:key]; + return; + } + if ([interceptor respondsToSelector:methodSelector]) { + callback(interceptor); + } + }]; +} + ++ (void)handleSceneWillConnectToNotification:(NSNotification *)notification { + if (@available(iOS 13.0, tvOS 13.0, *)) { + if ([notification.object isKindOfClass:[UIScene class]]) { + UIScene *scene = (UIScene *)notification.object; + [GULSceneDelegateSwizzler proxySceneDelegateIfNeeded:scene]; + } + } +} + +#pragma mark - [Donor Methods] UISceneDelegate URL handler + +- (void)scene:(UIScene *)scene + openURLContexts:(NSSet *)URLContexts API_AVAILABLE(ios(13.0), tvos(13.0)) { + if (@available(iOS 13.0, tvOS 13.0, *)) { + SEL methodSelector = @selector(scene:openURLContexts:); + // Call the real implementation if the real Scene Delegate has any. + NSValue *openURLContextsIMPPointer = + [GULSceneDelegateSwizzler originalImplementationForSelector:methodSelector object:self]; + GULOpenURLContextsIMP openURLContextsIMP = [openURLContextsIMPPointer pointerValue]; + + [GULSceneDelegateSwizzler + notifyInterceptorsWithMethodSelector:methodSelector + callback:^(id interceptor) { + if ([interceptor + conformsToProtocol:@protocol(UISceneDelegate)]) { + id sceneInterceptor = + (id)interceptor; + [sceneInterceptor scene:scene openURLContexts:URLContexts]; + } + }]; + + if (openURLContextsIMP) { + openURLContextsIMP(self, methodSelector, scene, URLContexts); + } + } +} + ++ (void)proxySceneDelegateIfNeeded:(UIScene *)scene { + Class realClass = [scene.delegate class]; + NSString *className = NSStringFromClass(realClass); + + // Skip proxying if failed to get the delegate class name for some reason (e.g. `delegate == nil`) + // or the class has a prefix of kGULAppDelegatePrefix, which means it has been proxied before. + if (className == nil || [className hasPrefix:kGULSceneDelegatePrefix]) { + return; + } + + NSString *classNameWithPrefix = [kGULSceneDelegatePrefix stringByAppendingString:className]; + NSString *newClassName = + [NSString stringWithFormat:@"%@-%@", classNameWithPrefix, [NSUUID UUID].UUIDString]; + + if (NSClassFromString(newClassName)) { + GULLogError( + kGULLoggerSwizzler, NO, + [NSString + stringWithFormat:@"I-SWZ%06ld", + (long) + kGULSwizzlerMessageCodeSceneDelegateSwizzlingInvalidSceneDelegate], + @"Cannot create a proxy for Scene Delegate. Subclass already exists. Original Class" + @": %@, subclass: %@", + className, newClassName); + return; + } + + // Register the new class as subclass of the real one. Do not allocate more than the real class + // size. + Class sceneDelegateSubClass = objc_allocateClassPair(realClass, newClassName.UTF8String, 0); + if (sceneDelegateSubClass == Nil) { + GULLogError( + kGULLoggerSwizzler, NO, + [NSString + stringWithFormat:@"I-SWZ%06ld", + (long) + kGULSwizzlerMessageCodeSceneDelegateSwizzlingInvalidSceneDelegate], + @"Cannot create a proxy for Scene Delegate. Subclass already exists. Original Class" + @": %@, subclass: Nil", + className); + return; + } + + NSMutableDictionary *realImplementationsBySelector = + [[NSMutableDictionary alloc] init]; + + // For scene:openURLContexts: + SEL openURLContextsSEL = @selector(scene:openURLContexts:); + [self proxyDestinationSelector:openURLContextsSEL + implementationsFromSourceSelector:openURLContextsSEL + fromClass:[GULSceneDelegateSwizzler class] + toClass:sceneDelegateSubClass + realClass:realClass + storeDestinationImplementationTo:realImplementationsBySelector]; + + // Store original implementations to a fake property of the original delegate. + objc_setAssociatedObject(scene.delegate, &kGULRealIMPBySelectorKey, + [realImplementationsBySelector copy], OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(scene.delegate, &kGULRealClassKey, realClass, + OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + // The subclass size has to be exactly the same size with the original class size. The subclass + // cannot have more ivars/properties than its superclass since it will cause an offset in memory + // that can lead to overwriting the isa of an object in the next frame. + if (class_getInstanceSize(realClass) != class_getInstanceSize(sceneDelegateSubClass)) { + GULLogError( + kGULLoggerSwizzler, NO, + [NSString + stringWithFormat:@"I-SWZ%06ld", + (long) + kGULSwizzlerMessageCodeSceneDelegateSwizzlingInvalidSceneDelegate], + @"Cannot create subclass of Scene Delegate, because the created subclass is not the " + @"same size. %@", + className); + NSAssert(NO, @"Classes must be the same size to swizzle isa"); + return; + } + + // Make the newly created class to be the subclass of the real Scene Delegate class. + objc_registerClassPair(sceneDelegateSubClass); + if (object_setClass(scene.delegate, sceneDelegateSubClass)) { + GULLogDebug( + kGULLoggerSwizzler, NO, + [NSString + stringWithFormat:@"I-SWZ%06ld", + (long) + kGULSwizzlerMessageCodeSceneDelegateSwizzlingInvalidSceneDelegate], + @"Successfully created Scene Delegate Proxy automatically. To disable the " + @"proxy, set the flag %@ to NO (Boolean) in the Info.plist", + [GULSceneDelegateSwizzler correctSceneDelegateProxyKey]); + } +} + ++ (NSString *)correctSceneDelegateProxyKey { + return NSClassFromString(@"FIRCore") ? kGULFirebaseSceneDelegateProxyEnabledPlistKey + : kGULGoogleUtilitiesSceneDelegateProxyEnabledPlistKey; +} + +#endif // UISCENE_SUPPORTED + +@end diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/SceneDelegateSwizzler/Internal/GULSceneDelegateSwizzler_Private.h b/!main project/Pods/GoogleUtilities/GoogleUtilities/SceneDelegateSwizzler/Internal/GULSceneDelegateSwizzler_Private.h new file mode 100644 index 0000000..a2439eb --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/SceneDelegateSwizzler/Internal/GULSceneDelegateSwizzler_Private.h @@ -0,0 +1,48 @@ +/* + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface GULSceneDelegateSwizzler () + +#if UISCENE_SUPPORTED + +/** Returns a dictionary containing interceptor IDs mapped to a GULZeroingWeakContainer. + * + * @return A dictionary of the form {NSString : GULZeroingWeakContainer}, where the NSString is + * the interceptorID. + */ ++ (GULMutableDictionary *)interceptors; + +/** Deletes all the registered interceptors. */ ++ (void)clearInterceptors; + +/** ISA Swizzles the given appDelegate as the original app delegate would be. + * + * @param scene The scene whose delegate needs to be isa swizzled. This should conform to the + * scene delegate protocol. + */ ++ (void)proxySceneDelegateIfNeeded:(UIScene *)scene API_AVAILABLE(ios(13.0), tvos(13.0)); + +#endif // UISCENE_SUPPORTED + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/SceneDelegateSwizzler/Private/GULSceneDelegateSwizzler.h b/!main project/Pods/GoogleUtilities/GoogleUtilities/SceneDelegateSwizzler/Private/GULSceneDelegateSwizzler.h new file mode 100644 index 0000000..420b3e7 --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/SceneDelegateSwizzler/Private/GULSceneDelegateSwizzler.h @@ -0,0 +1,73 @@ +/* + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if !TARGET_OS_OSX +#import +#endif // !TARGET_OS_OSX + +#if ((TARGET_OS_IOS || TARGET_OS_TV) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= 130000)) +#define UISCENE_SUPPORTED 1 +#endif + +NS_ASSUME_NONNULL_BEGIN + +typedef NSString *const GULSceneDelegateInterceptorID; + +/** This class contains methods that isa swizzle the scene delegate. */ +@interface GULSceneDelegateSwizzler : NSProxy + +#if UISCENE_SUPPORTED + +/** Registers a scene delegate interceptor whose methods will be invoked as they're invoked on the + * original scene delegate. + * + * @param interceptor An instance of a class that conforms to the application delegate protocol. + * The interceptor is NOT retained. + * @return A unique GULSceneDelegateInterceptorID if interceptor was successfully registered; nil + * if it fails. + */ ++ (nullable GULSceneDelegateInterceptorID)registerSceneDelegateInterceptor: + (id)interceptor API_AVAILABLE(ios(13.0), tvos(13.0)); + +/** Unregisters an interceptor with the given ID if it exists. + * + * @param interceptorID The object that was generated when the interceptor was registered. + */ ++ (void)unregisterSceneDelegateInterceptorWithID:(GULSceneDelegateInterceptorID)interceptorID + API_AVAILABLE(ios(13.0), tvos(13.0)); + +/** Do not initialize this class. */ +- (instancetype)init NS_UNAVAILABLE; + +#endif // UISCENE_SUPPORTED + +/** This method ensures that the original scene delegate has been proxied. Call this before + * registering your interceptor. This method is safe to call multiple times (but it only proxies + * the scene delegate once). + * + * The method has no effect for extensions. + */ ++ (void)proxyOriginalSceneDelegate; + +/** Indicates whether scene delegate proxy is explicitly disabled or enabled. Enabled by default. + * + * @return YES if SceneDelegateProxy is Enabled, NO otherwise. + */ ++ (BOOL)isSceneDelegateProxyEnabled; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/UserDefaults/GULUserDefaults.m b/!main project/Pods/GoogleUtilities/GoogleUtilities/UserDefaults/GULUserDefaults.m new file mode 100644 index 0000000..47ac35d --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/UserDefaults/GULUserDefaults.m @@ -0,0 +1,213 @@ +// Copyright 2018 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "Private/GULUserDefaults.h" + +#import + +NS_ASSUME_NONNULL_BEGIN + +static NSTimeInterval const kGULSynchronizeInterval = 1.0; + +static NSString *const kGULLogFormat = @"I-GUL%06ld"; + +static GULLoggerService kGULLogUserDefaultsService = @"[GoogleUtilities/UserDefaults]"; + +typedef NS_ENUM(NSInteger, GULUDMessageCode) { + GULUDMessageCodeInvalidKeyGet = 1, + GULUDMessageCodeInvalidKeySet = 2, + GULUDMessageCodeInvalidObjectSet = 3, + GULUDMessageCodeSynchronizeFailed = 4, +}; + +@interface GULUserDefaults () + +/// Equivalent to the suite name for NSUserDefaults. +@property(readonly) CFStringRef appNameRef; + +@property(atomic) BOOL isPreferenceFileExcluded; + +@end + +@implementation GULUserDefaults { + // The application name is the same with the suite name of the NSUserDefaults, and it is used for + // preferences. + CFStringRef _appNameRef; +} + ++ (GULUserDefaults *)standardUserDefaults { + static GULUserDefaults *standardUserDefaults; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + standardUserDefaults = [[GULUserDefaults alloc] init]; + }); + return standardUserDefaults; +} + +- (instancetype)init { + return [self initWithSuiteName:nil]; +} + +- (instancetype)initWithSuiteName:(nullable NSString *)suiteName { + self = [super init]; + + NSString *name = [suiteName copy]; + + if (self) { + // `kCFPreferencesCurrentApplication` maps to the same defaults database as + // `[NSUserDefaults standardUserDefaults]`. + _appNameRef = + name.length ? (__bridge_retained CFStringRef)name : kCFPreferencesCurrentApplication; + } + + return self; +} + +- (void)dealloc { + // If we're using a custom `_appNameRef` it needs to be released. If it's a constant, it shouldn't + // need to be released since we don't own it. + if (CFStringCompare(_appNameRef, kCFPreferencesCurrentApplication, 0) != kCFCompareEqualTo) { + CFRelease(_appNameRef); + } + + [NSObject cancelPreviousPerformRequestsWithTarget:self + selector:@selector(synchronize) + object:nil]; +} + +- (nullable id)objectForKey:(NSString *)defaultName { + NSString *key = [defaultName copy]; + if (![key isKindOfClass:[NSString class]] || !key.length) { + GULLogWarning(@"", NO, + [NSString stringWithFormat:kGULLogFormat, (long)GULUDMessageCodeInvalidKeyGet], + @"Cannot get object for invalid user default key."); + return nil; + } + return (__bridge_transfer id)CFPreferencesCopyAppValue((__bridge CFStringRef)key, _appNameRef); +} + +- (void)setObject:(nullable id)value forKey:(NSString *)defaultName { + NSString *key = [defaultName copy]; + if (![key isKindOfClass:[NSString class]] || !key.length) { + GULLogWarning(kGULLogUserDefaultsService, NO, + [NSString stringWithFormat:kGULLogFormat, (long)GULUDMessageCodeInvalidKeySet], + @"Cannot set object for invalid user default key."); + return; + } + if (!value) { + CFPreferencesSetAppValue((__bridge CFStringRef)key, NULL, _appNameRef); + [self scheduleSynchronize]; + return; + } + BOOL isAcceptableValue = + [value isKindOfClass:[NSString class]] || [value isKindOfClass:[NSNumber class]] || + [value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]] || + [value isKindOfClass:[NSDate class]] || [value isKindOfClass:[NSData class]]; + if (!isAcceptableValue) { + GULLogWarning(kGULLogUserDefaultsService, NO, + [NSString stringWithFormat:kGULLogFormat, (long)GULUDMessageCodeInvalidObjectSet], + @"Cannot set invalid object to user defaults. Must be a string, number, array, " + @"dictionary, date, or data. Value: %@", + value); + return; + } + + CFPreferencesSetAppValue((__bridge CFStringRef)key, (__bridge CFStringRef)value, _appNameRef); + [self scheduleSynchronize]; +} + +- (void)removeObjectForKey:(NSString *)key { + [self setObject:nil forKey:key]; +} + +#pragma mark - Getters + +- (NSInteger)integerForKey:(NSString *)defaultName { + NSNumber *object = [self objectForKey:defaultName]; + return object.integerValue; +} + +- (float)floatForKey:(NSString *)defaultName { + NSNumber *object = [self objectForKey:defaultName]; + return object.floatValue; +} + +- (double)doubleForKey:(NSString *)defaultName { + NSNumber *object = [self objectForKey:defaultName]; + return object.doubleValue; +} + +- (BOOL)boolForKey:(NSString *)defaultName { + NSNumber *object = [self objectForKey:defaultName]; + return object.boolValue; +} + +- (nullable NSString *)stringForKey:(NSString *)defaultName { + return [self objectForKey:defaultName]; +} + +- (nullable NSArray *)arrayForKey:(NSString *)defaultName { + return [self objectForKey:defaultName]; +} + +- (nullable NSDictionary *)dictionaryForKey:(NSString *)defaultName { + return [self objectForKey:defaultName]; +} + +#pragma mark - Setters + +- (void)setInteger:(NSInteger)integer forKey:(NSString *)defaultName { + [self setObject:@(integer) forKey:defaultName]; +} + +- (void)setFloat:(float)value forKey:(NSString *)defaultName { + [self setObject:@(value) forKey:defaultName]; +} + +- (void)setDouble:(double)doubleNumber forKey:(NSString *)defaultName { + [self setObject:@(doubleNumber) forKey:defaultName]; +} + +- (void)setBool:(BOOL)boolValue forKey:(NSString *)defaultName { + [self setObject:@(boolValue) forKey:defaultName]; +} + +#pragma mark - Save data + +- (void)synchronize { + if (!CFPreferencesAppSynchronize(_appNameRef)) { + GULLogError(kGULLogUserDefaultsService, NO, + [NSString stringWithFormat:kGULLogFormat, (long)GULUDMessageCodeSynchronizeFailed], + @"Cannot synchronize user defaults to disk"); + } +} + +#pragma mark - Private methods + +- (void)scheduleSynchronize { + // Synchronize data using a timer so that multiple set... calls can be coalesced under one + // synchronize. + [NSObject cancelPreviousPerformRequestsWithTarget:self + selector:@selector(synchronize) + object:nil]; + // This method may be called on multiple queues (due to set... methods can be called on any queue) + // synchronize can be scheduled on different queues, so make sure that it does not crash. If this + // instance goes away, self will be released also, no one will retain it and the schedule won't be + // called. + [self performSelector:@selector(synchronize) withObject:nil afterDelay:kGULSynchronizeInterval]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleUtilities/GoogleUtilities/UserDefaults/Private/GULUserDefaults.h b/!main project/Pods/GoogleUtilities/GoogleUtilities/UserDefaults/Private/GULUserDefaults.h new file mode 100644 index 0000000..0d04781 --- /dev/null +++ b/!main project/Pods/GoogleUtilities/GoogleUtilities/UserDefaults/Private/GULUserDefaults.h @@ -0,0 +1,110 @@ +// Copyright 2018 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// A thread-safe user defaults that uses C functions from CFPreferences.h instead of +/// `NSUserDefaults`. This is to avoid sending an `NSNotification` when it's changed from a +/// background thread to avoid crashing. // TODO: Insert radar number here. +@interface GULUserDefaults : NSObject + +/// A shared user defaults similar to +[NSUserDefaults standardUserDefaults] and accesses the same +/// data of the standardUserDefaults. ++ (GULUserDefaults *)standardUserDefaults; + +/// Initializes preferences with a suite name that is the same with the NSUserDefaults' suite name. +/// Both of CFPreferences and NSUserDefaults share the same plist file so their data will exactly +/// the same. +/// +/// @param suiteName The name of the suite of the user defaults. +- (instancetype)initWithSuiteName:(nullable NSString *)suiteName; + +#pragma mark - Getters + +/// Searches the receiver's search list for a default with the key 'defaultName' and return it. If +/// another process has changed defaults in the search list, NSUserDefaults will automatically +/// update to the latest values. If the key in question has been marked as ubiquitous via a Defaults +/// Configuration File, the latest value may not be immediately available, and the registered value +/// will be returned instead. +- (nullable id)objectForKey:(NSString *)defaultName; + +/// Equivalent to -objectForKey:, except that it will return nil if the value is not an NSArray. +- (nullable NSArray *)arrayForKey:(NSString *)defaultName; + +/// Equivalent to -objectForKey:, except that it will return nil if the value +/// is not an NSDictionary. +- (nullable NSDictionary *)dictionaryForKey:(NSString *)defaultName; + +/// Equivalent to -objectForKey:, except that it will convert NSNumber values to their NSString +/// representation. If a non-string non-number value is found, nil will be returned. +- (nullable NSString *)stringForKey:(NSString *)defaultName; + +/// Equivalent to -objectForKey:, except that it converts the returned value to an NSInteger. If the +/// value is an NSNumber, the result of -integerValue will be returned. If the value is an NSString, +/// it will be converted to NSInteger if possible. If the value is a boolean, it will be converted +/// to either 1 for YES or 0 for NO. If the value is absent or can't be converted to an integer, 0 +/// will be returned. +- (NSInteger)integerForKey:(NSString *)defaultName; + +/// Similar to -integerForKey:, except that it returns a float, and boolean values will not be +/// converted. +- (float)floatForKey:(NSString *)defaultName; + +/// Similar to -integerForKey:, except that it returns a double, and boolean values will not be +/// converted. +- (double)doubleForKey:(NSString *)defaultName; + +/// Equivalent to -objectForKey:, except that it converts the returned value to a BOOL. If the value +/// is an NSNumber, NO will be returned if the value is 0, YES otherwise. If the value is an +/// NSString, values of "YES" or "1" will return YES, and values of "NO", "0", or any other string +/// will return NO. If the value is absent or can't be converted to a BOOL, NO will be returned. +- (BOOL)boolForKey:(NSString *)defaultName; + +#pragma mark - Setters + +/// Immediately stores a value (or removes the value if `nil` is passed as the value) for the +/// provided key in the search list entry for the receiver's suite name in the current user and any +/// host, then asynchronously stores the value persistently, where it is made available to other +/// processes. +- (void)setObject:(nullable id)value forKey:(NSString *)defaultName; + +/// Equivalent to -setObject:forKey: except that the value is converted from a float to an NSNumber. +- (void)setFloat:(float)value forKey:(NSString *)defaultName; + +/// Equivalent to -setObject:forKey: except that the value is converted from a double to an +/// NSNumber. +- (void)setDouble:(double)value forKey:(NSString *)defaultName; + +/// Equivalent to -setObject:forKey: except that the value is converted from an NSInteger to an +/// NSNumber. +- (void)setInteger:(NSInteger)value forKey:(NSString *)defaultName; + +/// Equivalent to -setObject:forKey: except that the value is converted from a BOOL to an NSNumber. +- (void)setBool:(BOOL)value forKey:(NSString *)defaultName; + +#pragma mark - Removing Defaults + +/// Equivalent to -[... setObject:nil forKey:defaultName] +- (void)removeObjectForKey:(NSString *)defaultName; + +#pragma mark - Save data + +/// Blocks the calling thread until all in-progress set operations have completed. +- (void)synchronize; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/GoogleUtilities/README.md b/!main project/Pods/GoogleUtilities/README.md new file mode 100644 index 0000000..5097a89 --- /dev/null +++ b/!main project/Pods/GoogleUtilities/README.md @@ -0,0 +1,254 @@ +# Firebase iOS Open Source Development [![Build Status](https://travis-ci.org/firebase/firebase-ios-sdk.svg?branch=master)](https://travis-ci.org/firebase/firebase-ios-sdk) + +This repository contains a subset of the Firebase iOS SDK source. It currently +includes FirebaseCore, FirebaseABTesting, FirebaseAuth, FirebaseDatabase, +FirebaseFirestore, FirebaseFunctions, FirebaseInstanceID, FirebaseInAppMessaging, +FirebaseInAppMessagingDisplay, FirebaseMessaging, FirebaseRemoteConfig, and +FirebaseStorage. + +The repository also includes GoogleUtilities source. The +[GoogleUtilities](GoogleUtilities/README.md) pod is +a set of utilities used by Firebase and other Google products. + +Firebase is an app development platform with tools to help you build, grow and +monetize your app. More information about Firebase can be found at +[https://firebase.google.com](https://firebase.google.com). + +## Installation + +See the three subsections for details about three different installation methods. +1. [Standard pod install](README.md#standard-pod-install) +1. [Installing from the GitHub repo](README.md#installing-from-github) +1. [Experimental Carthage](README.md#carthage-ios-only) + +### Standard pod install + +Go to +[https://firebase.google.com/docs/ios/setup](https://firebase.google.com/docs/ios/setup). + +### Installing from GitHub + +For releases starting with 5.0.0, the source for each release is also deployed +to CocoaPods master and available via standard +[CocoaPods Podfile syntax](https://guides.cocoapods.org/syntax/podfile.html#pod). + +These instructions can be used to access the Firebase repo at other branches, +tags, or commits. + +#### Background + +See +[the Podfile Syntax Reference](https://guides.cocoapods.org/syntax/podfile.html#pod) +for instructions and options about overriding pod source locations. + +#### Accessing Firebase Source Snapshots + +All of the official releases are tagged in this repo and available via CocoaPods. To access a local +source snapshot or unreleased branch, use Podfile directives like the following: + +To access FirebaseFirestore via a branch: +``` +pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' +``` + +To access FirebaseMessaging via a checked out version of the firebase-ios-sdk repo do: + +``` +pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk' +pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk' +``` + +### Carthage (iOS only) + +Instructions for the experimental Carthage distribution are at +[Carthage](Carthage.md). + +### Rome + +Instructions for installing binary frameworks via +[Rome](https://github.com/CocoaPods/Rome) are at [Rome](Rome.md). + +## Development + +To develop Firebase software in this repository, ensure that you have at least +the following software: + + * Xcode 10.1 (or later) + * CocoaPods 1.7.2 (or later) + * [CocoaPods generate](https://github.com/square/cocoapods-generate) + +For the pod that you want to develop: + +`pod gen Firebase{name here}.podspec --local-sources=./ --auto-open --platforms=ios` + +Note: If the CocoaPods cache is out of date, you may need to run +`pod repo update` before the `pod gen` command. + +Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for +those platforms. Since 10.2, Xcode does not properly handle multi-platform +CocoaPods workspaces. + +Firestore has a self contained Xcode project. See +[Firestore/README.md](Firestore/README.md). + +### Development for Catalyst +* `pod gen {name here}.podspec --local-sources=./ --auto-open --platforms=ios` +* Check the Mac box in the App-iOS Build Settings +* Sign the App in the Settings Signing & Capabilities tab +* Click Pods in the Project Manager +* Add Signing to the iOS host app and unit test targets +* Select the Unit-unit scheme +* Run it to build and test + +### Adding a New Firebase Pod + +See [AddNewPod.md](AddNewPod.md). + +### Code Formatting + +To ensure that the code is formatted consistently, run the script +[./scripts/style.sh](https://github.com/firebase/firebase-ios-sdk/blob/master/scripts/style.sh) +before creating a PR. + +Travis will verify that any code changes are done in a style compliant way. Install +`clang-format` and `swiftformat`. +These commands will get the right versions: + +``` +brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/e3496d9/Formula/clang-format.rb +brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/7963c3d/Formula/swiftformat.rb +``` + +Note: if you already have a newer version of these installed you may need to +`brew switch` to this version. + +To update this section, find the versions of clang-format and swiftformat.rb to +match the versions in the CI failure logs +[here](https://github.com/Homebrew/homebrew-core/tree/master/Formula). + +### Running Unit Tests + +Select a scheme and press Command-u to build a component and run its unit tests. + +#### Viewing Code Coverage + +First, make sure that [xcov](https://github.com/nakiostudio/xcov) is installed with `gem install xcov`. + +After running the `AllUnitTests_iOS` scheme in Xcode, execute +`xcov --workspace Firebase.xcworkspace --scheme AllUnitTests_iOS --output_directory xcov_output` +at Example/ in the terminal. This will aggregate the coverage, and you can run `open xcov_output/index.html` to see the results. + +### Running Sample Apps +In order to run the sample apps and integration tests, you'll need valid +`GoogleService-Info.plist` files for those samples. The Firebase Xcode project contains dummy plist +files without real values, but can be replaced with real plist files. To get your own +`GoogleService-Info.plist` files: + +1. Go to the [Firebase Console](https://console.firebase.google.com/) +2. Create a new Firebase project, if you don't already have one +3. For each sample app you want to test, create a new Firebase app with the sample app's bundle +identifier (e.g. `com.google.Database-Example`) +4. Download the resulting `GoogleService-Info.plist` and replace the appropriate dummy plist file +(e.g. in [Example/Database/App/](Example/Database/App/)); + +Some sample apps like Firebase Messaging ([Example/Messaging/App](Example/Messaging/App)) require +special Apple capabilities, and you will have to change the sample app to use a unique bundle +identifier that you can control in your own Apple Developer account. + +## Specific Component Instructions +See the sections below for any special instructions for those components. + +### Firebase Auth + +If you're doing specific Firebase Auth development, see +[the Auth Sample README](Example/Auth/README.md) for instructions about +building and running the FirebaseAuth pod along with various samples and tests. + +### Firebase Database + +To run the Database Integration tests, make your database authentication rules +[public](https://firebase.google.com/docs/database/security/quickstart). + +### Firebase Storage + +To run the Storage Integration tests, follow the instructions in +[FIRStorageIntegrationTests.m](Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m). + +#### Push Notifications + +Push notifications can only be delivered to specially provisioned App IDs in the developer portal. +In order to actually test receiving push notifications, you will need to: + +1. Change the bundle identifier of the sample app to something you own in your Apple Developer +account, and enable that App ID for push notifications. +2. You'll also need to +[upload your APNs Provider Authentication Key or certificate to the Firebase Console](https://firebase.google.com/docs/cloud-messaging/ios/certs) +at **Project Settings > Cloud Messaging > [Your Firebase App]**. +3. Ensure your iOS device is added to your Apple Developer portal as a test device. + +#### iOS Simulator + +The iOS Simulator cannot register for remote notifications, and will not receive push notifications. +In order to receive push notifications, you'll have to follow the steps above and run the app on a +physical device. + +## Community Supported Efforts + +We've seen an amazing amount of interest and contributions to improve the Firebase SDKs, and we are +very grateful! We'd like to empower as many developers as we can to be able to use Firebase and +participate in the Firebase community. + +### tvOS, macOS, and Catalyst +Thanks to contributions from the community, many of Firebase SDKs now compile, run unit tests, and work on +tvOS, macOS, and Catalyst. + +For tvOS, checkout the [Sample](Example/tvOSSample). + +Keep in mind that macOS, Catalyst and tvOS are not officially supported by Firebase, and this +repository is actively developed primarily for iOS. While we can catch basic unit test issues with +Travis, there may be some changes where the SDK no longer works as expected on macOS or tvOS. If you +encounter this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues). + +During app setup in the console, you may get to a step that mentions something like "Checking if the app +has communicated with our servers". This relies on Analytics and will not work on macOS/tvOS/Catalyst. +**It's safe to ignore the message and continue**, the rest of the SDKs will work as expected. + +To install, add a subset of the following to the Podfile: + +``` +pod 'Firebase/ABTesting' +pod 'Firebase/Auth' +pod 'Firebase/Crashlytics' +pod 'Firebase/Database' +pod 'Firebase/Firestore' +pod 'Firebase/Functions' +pod 'Firebase/Messaging' +pod 'Firebase/RemoteConfig' +pod 'Firebase/Storage' +``` + +#### Additional Catalyst Notes + +* FirebaseAuth and FirebaseMessaging require adding `Keychain Sharing Capability` +to Build Settings. +* FirebaseFirestore requires signing the +[gRPC Resource target](https://github.com/firebase/firebase-ios-sdk/issues/3500#issuecomment-518741681). + +## Roadmap + +See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source +plans and directions. + +## Contributing + +See [Contributing](CONTRIBUTING.md) for more information on contributing to the Firebase +iOS SDK. + +## License + +The contents of this repository is licensed under the +[Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0). + +Your use of Firebase is governed by the +[Terms of Service for Firebase Services](https://firebase.google.com/terms/). diff --git a/!main project/Pods/Headers/Private/Firebase/Firebase.h b/!main project/Pods/Headers/Private/Firebase/Firebase.h new file mode 120000 index 0000000..07ac6eb --- /dev/null +++ b/!main project/Pods/Headers/Private/Firebase/Firebase.h @@ -0,0 +1 @@ +../../../Firebase/CoreOnly/Sources/Firebase.h \ No newline at end of file diff --git a/!main project/Pods/Headers/Private/FirebaseAnalyticsInterop/FIRAnalyticsInterop.h b/!main project/Pods/Headers/Private/FirebaseAnalyticsInterop/FIRAnalyticsInterop.h new file mode 120000 index 0000000..e01a43a --- /dev/null +++ b/!main project/Pods/Headers/Private/FirebaseAnalyticsInterop/FIRAnalyticsInterop.h @@ -0,0 +1 @@ +../../../FirebaseAnalyticsInterop/Interop/Analytics/Public/FIRAnalyticsInterop.h \ No newline at end of file diff --git a/!main project/Pods/Headers/Private/FirebaseAnalyticsInterop/FIRAnalyticsInteropListener.h b/!main project/Pods/Headers/Private/FirebaseAnalyticsInterop/FIRAnalyticsInteropListener.h new file mode 120000 index 0000000..d3cd097 --- /dev/null +++ b/!main project/Pods/Headers/Private/FirebaseAnalyticsInterop/FIRAnalyticsInteropListener.h @@ -0,0 +1 @@ +../../../FirebaseAnalyticsInterop/Interop/Analytics/Public/FIRAnalyticsInteropListener.h \ No newline at end of file diff --git a/!main project/Pods/Headers/Private/FirebaseAnalyticsInterop/FIRInteropEventNames.h b/!main project/Pods/Headers/Private/FirebaseAnalyticsInterop/FIRInteropEventNames.h new file mode 120000 index 0000000..dbda3ec --- /dev/null +++ b/!main project/Pods/Headers/Private/FirebaseAnalyticsInterop/FIRInteropEventNames.h @@ -0,0 +1 @@ +../../../FirebaseAnalyticsInterop/Interop/Analytics/Public/FIRInteropEventNames.h \ No newline at end of file diff --git a/!main project/Pods/Headers/Private/FirebaseAnalyticsInterop/FIRInteropParameterNames.h b/!main project/Pods/Headers/Private/FirebaseAnalyticsInterop/FIRInteropParameterNames.h new file mode 120000 index 0000000..5145843 --- /dev/null +++ b/!main project/Pods/Headers/Private/FirebaseAnalyticsInterop/FIRInteropParameterNames.h @@ -0,0 +1 @@ +../../../FirebaseAnalyticsInterop/Interop/Analytics/Public/FIRInteropParameterNames.h \ No newline at end of file diff --git a/!main project/Pods/Headers/Private/FirebaseAuthInterop/FIRAuthInterop.h b/!main project/Pods/Headers/Private/FirebaseAuthInterop/FIRAuthInterop.h new file mode 120000 index 0000000..0253e25 --- /dev/null +++ b/!main project/Pods/Headers/Private/FirebaseAuthInterop/FIRAuthInterop.h @@ -0,0 +1 @@ +../../../FirebaseAuthInterop/Interop/Auth/Public/FIRAuthInterop.h \ No newline at end of file diff --git a/!main project/Pods/Headers/Private/FirebaseCoreDiagnosticsInterop/FIRCoreDiagnosticsData.h b/!main project/Pods/Headers/Private/FirebaseCoreDiagnosticsInterop/FIRCoreDiagnosticsData.h new file mode 120000 index 0000000..ce646ec --- /dev/null +++ b/!main project/Pods/Headers/Private/FirebaseCoreDiagnosticsInterop/FIRCoreDiagnosticsData.h @@ -0,0 +1 @@ +../../../FirebaseCoreDiagnosticsInterop/Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsData.h \ No newline at end of file diff --git a/!main project/Pods/Headers/Private/FirebaseCoreDiagnosticsInterop/FIRCoreDiagnosticsInterop.h b/!main project/Pods/Headers/Private/FirebaseCoreDiagnosticsInterop/FIRCoreDiagnosticsInterop.h new file mode 120000 index 0000000..07d83d7 --- /dev/null +++ b/!main project/Pods/Headers/Private/FirebaseCoreDiagnosticsInterop/FIRCoreDiagnosticsInterop.h @@ -0,0 +1 @@ +../../../FirebaseCoreDiagnosticsInterop/Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsInterop.h \ No newline at end of file diff --git a/!main project/Pods/Headers/Public/Firebase/Firebase.h b/!main project/Pods/Headers/Public/Firebase/Firebase.h new file mode 120000 index 0000000..07ac6eb --- /dev/null +++ b/!main project/Pods/Headers/Public/Firebase/Firebase.h @@ -0,0 +1 @@ +../../../Firebase/CoreOnly/Sources/Firebase.h \ No newline at end of file diff --git a/!main project/Pods/Headers/Public/FirebaseAnalyticsInterop/FIRAnalyticsInterop.h b/!main project/Pods/Headers/Public/FirebaseAnalyticsInterop/FIRAnalyticsInterop.h new file mode 120000 index 0000000..e01a43a --- /dev/null +++ b/!main project/Pods/Headers/Public/FirebaseAnalyticsInterop/FIRAnalyticsInterop.h @@ -0,0 +1 @@ +../../../FirebaseAnalyticsInterop/Interop/Analytics/Public/FIRAnalyticsInterop.h \ No newline at end of file diff --git a/!main project/Pods/Headers/Public/FirebaseAnalyticsInterop/FIRAnalyticsInteropListener.h b/!main project/Pods/Headers/Public/FirebaseAnalyticsInterop/FIRAnalyticsInteropListener.h new file mode 120000 index 0000000..d3cd097 --- /dev/null +++ b/!main project/Pods/Headers/Public/FirebaseAnalyticsInterop/FIRAnalyticsInteropListener.h @@ -0,0 +1 @@ +../../../FirebaseAnalyticsInterop/Interop/Analytics/Public/FIRAnalyticsInteropListener.h \ No newline at end of file diff --git a/!main project/Pods/Headers/Public/FirebaseAnalyticsInterop/FIRInteropEventNames.h b/!main project/Pods/Headers/Public/FirebaseAnalyticsInterop/FIRInteropEventNames.h new file mode 120000 index 0000000..dbda3ec --- /dev/null +++ b/!main project/Pods/Headers/Public/FirebaseAnalyticsInterop/FIRInteropEventNames.h @@ -0,0 +1 @@ +../../../FirebaseAnalyticsInterop/Interop/Analytics/Public/FIRInteropEventNames.h \ No newline at end of file diff --git a/!main project/Pods/Headers/Public/FirebaseAnalyticsInterop/FIRInteropParameterNames.h b/!main project/Pods/Headers/Public/FirebaseAnalyticsInterop/FIRInteropParameterNames.h new file mode 120000 index 0000000..5145843 --- /dev/null +++ b/!main project/Pods/Headers/Public/FirebaseAnalyticsInterop/FIRInteropParameterNames.h @@ -0,0 +1 @@ +../../../FirebaseAnalyticsInterop/Interop/Analytics/Public/FIRInteropParameterNames.h \ No newline at end of file diff --git a/!main project/Pods/Headers/Public/FirebaseAuthInterop/FIRAuthInterop.h b/!main project/Pods/Headers/Public/FirebaseAuthInterop/FIRAuthInterop.h new file mode 120000 index 0000000..0253e25 --- /dev/null +++ b/!main project/Pods/Headers/Public/FirebaseAuthInterop/FIRAuthInterop.h @@ -0,0 +1 @@ +../../../FirebaseAuthInterop/Interop/Auth/Public/FIRAuthInterop.h \ No newline at end of file diff --git a/!main project/Pods/Headers/Public/FirebaseCoreDiagnosticsInterop/FIRCoreDiagnosticsData.h b/!main project/Pods/Headers/Public/FirebaseCoreDiagnosticsInterop/FIRCoreDiagnosticsData.h new file mode 120000 index 0000000..ce646ec --- /dev/null +++ b/!main project/Pods/Headers/Public/FirebaseCoreDiagnosticsInterop/FIRCoreDiagnosticsData.h @@ -0,0 +1 @@ +../../../FirebaseCoreDiagnosticsInterop/Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsData.h \ No newline at end of file diff --git a/!main project/Pods/Headers/Public/FirebaseCoreDiagnosticsInterop/FIRCoreDiagnosticsInterop.h b/!main project/Pods/Headers/Public/FirebaseCoreDiagnosticsInterop/FIRCoreDiagnosticsInterop.h new file mode 120000 index 0000000..07d83d7 --- /dev/null +++ b/!main project/Pods/Headers/Public/FirebaseCoreDiagnosticsInterop/FIRCoreDiagnosticsInterop.h @@ -0,0 +1 @@ +../../../FirebaseCoreDiagnosticsInterop/Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsInterop.h \ No newline at end of file diff --git a/!main project/Pods/Kingfisher/LICENSE b/!main project/Pods/Kingfisher/LICENSE new file mode 100644 index 0000000..80888ba --- /dev/null +++ b/!main project/Pods/Kingfisher/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2019 Wei Wang + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/!main project/Pods/Kingfisher/README.md b/!main project/Pods/Kingfisher/README.md new file mode 100644 index 0000000..b00279d --- /dev/null +++ b/!main project/Pods/Kingfisher/README.md @@ -0,0 +1,168 @@ +

+Kingfisher +

+ +

+ + + + + +
+ + + + +

+ +Kingfisher is a powerful, pure-Swift library for downloading and caching images from the web. It provides you a chance to use a pure-Swift way to work with remote images in your next app. + +## Features + +- [x] Asynchronous image downloading and caching. +- [x] Loading image from either `URLSession`-based networking or local provided data. +- [x] Useful image processors and filters provided. +- [x] Multiple-layer hybrid cache for both memory and disk. +- [x] Fine control on cache behavior. Customizable expiration date and size limit. +- [x] Cancelable downloading and auto-reusing previous downloaded content to improve performance. +- [x] Independent components. Use the downloader, caching system and image processors separately as you need. +- [x] Prefetching images and showing them from cache to boost your app. +- [x] View extensions for `UIImageView`, `NSImageView`, `NSButton` and `UIButton` to directly set an image from a URL. +- [x] Built-in transition animation when setting images. +- [x] Customizable placeholder and indicator while loading images. +- [x] Extensible image processing and image format easily. +- [x] SwiftUI support. + +### Kingfisher 101 + +The simplest use-case is setting an image to an image view with the `UIImageView` extension: + +```swift +let url = URL(string: "https://example.com/image.png") +imageView.kf.setImage(with: url) +``` + +Kingfisher will download the image from `url`, send it to both memory cache and disk cache, and display it in `imageView`. When you set with the same URL later, the image will be retrieved from cache and shown immediately. + +It also works if you use SwiftUI: + +```swift +import KingfisherSwiftUI + +var body: some View { + KFImage(URL(string: "https://example.com/image.png")!) +} +``` + +### A More Advanced Example + +With the powerful options, you can do hard tasks with Kingfisher in a simple way. For example, the code below: + +1. Downloads a high-resolution image. +2. Downsamples it to match the image view size. +3. Makes it round cornered with a given radius. +4. Shows a system indicator and a placeholder image while downloading. +5. When prepared, it animates the small thumbnail image with a "fade in" effect. +6. The original large image is also cached to disk for later use, to get rid of downloading it again in a detail view. +7. A console log is printed when the task finishes, either for success or failure. + +```swift +let url = URL(string: "https://example.com/high_resolution_image.png") +let processor = DownsamplingImageProcessor(size: imageView.size) + >> RoundCornerImageProcessor(cornerRadius: 20) +imageView.kf.indicatorType = .activity +imageView.kf.setImage( + with: url, + placeholder: UIImage(named: "placeholderImage"), + options: [ + .processor(processor), + .scaleFactor(UIScreen.main.scale), + .transition(.fade(1)), + .cacheOriginalImage + ]) +{ + result in + switch result { + case .success(let value): + print("Task done for: \(value.source.url?.absoluteString ?? "")") + case .failure(let error): + print("Job failed: \(error.localizedDescription)") + } +} +``` + +It is really a very common situation I can meet in my daily work. Think about how many lines you need to write without Kingfisher. You will fall in love with it if you give it a try! + +### Learn More + +To learn the using of Kingfisher by more examples, take a look at the [Cheat Sheet](https://github.com/onevcat/Kingfisher/wiki/Cheat-Sheet). There we summarized most common tasks in Kingfisher, you can get a better idea on what this framework can do. There are also some tips for performance in the same page, remember to check them too. + +## Requirements + +- iOS 10.0+ / macOS 10.12+ / tvOS 10.0+ / watchOS 3.0+ +- Swift 4.0+ + +[Kingfisher 5.0 Migration](https://github.com/onevcat/Kingfisher/wiki/Kingfisher-5.0-Migration-Guide) - Kingfisher 5.x is NOT fully compatible with version 4.x. However, the migration is not difficult. Depending on your use cases, it may take no effect or several minutes to modify your existing code for the new version. Please follow the [migration guide](https://github.com/onevcat/Kingfisher/wiki/Kingfisher-5.0-Migration-Guide) when you prepare to upgrade Kingfisher in your project. + +If you are using an even earlier version, see the guides below to know the steps for migrating. + +> - Kingfisher 4.0 Migration - Kingfisher 3.x should be source compatible to Kingfisher 4. The reason for a major update is that we need to specify the Swift version explicitly for Xcode. All deprecated methods in Kingfisher 3 has been removed, so please ensure you have no warning left before you migrate from Kingfisher 3 to Kingfisher 4. If you have any trouble in migrating, please open an issue to discuss. +> - [Kingfisher 3.0 Migration](https://github.com/onevcat/Kingfisher/wiki/Kingfisher-3.0-Migration-Guide) - If you are upgrading to Kingfisher 3.x from an earlier version, please read this for more information. + +## Next Steps + +We prepared a [wiki page](https://github.com/onevcat/Kingfisher/wiki). You can find tons of useful things there. + +* [Installation Guide](https://github.com/onevcat/Kingfisher/wiki/Installation-Guide) - Follow it to integrate Kingfisher into your project. +* [Cheat Sheet](https://github.com/onevcat/Kingfisher/wiki/Cheat-Sheet)- Curious about what Kingfisher could do and how would it look like when used in your project? See this page for useful code snippets. If you are already familiar with Kingfisher, you could also learn new tricks to improve the way you use Kingfisher! +* [API Reference](http://onevcat.github.io/Kingfisher/) - Lastly, please remember to read the full whenever you may need a more detailed reference. + +## Other + +### Future of Kingfisher + +I want to keep Kingfisher lightweight. This framework will focus on providing a simple solution for downloading and caching images. This doesn’t mean the framework can’t be improved. Kingfisher is far from perfect, so necessary and useful updates will be made to make it better. + +### Developments and Tests + +Any contributing and pull requests are warmly welcome. However, before you plan to implement some features or try to fix an uncertain issue, it is recommended to open a discussion first. It would be appreciated if your pull requests could build and with all tests green. :) + +### About the logo + +The logo of Kingfisher is inspired by [Tangram (七巧板)](http://en.wikipedia.org/wiki/Tangram), a dissection puzzle consisting of seven flat shapes from China. I believe she's a kingfisher bird instead of a swift, but someone insists that she is a pigeon. I guess I should give her a name. Hi, guys, do you have any suggestions? + +### Contact + +Follow and contact me on [Twitter](http://twitter.com/onevcat) or [Sina Weibo](http://weibo.com/onevcat). If you find an issue, just [open a ticket](https://github.com/onevcat/Kingfisher/issues/new). Pull requests are warmly welcome as well. + +## Contributors + +This project exists thanks to all the people who contribute. [[Contribute]](https://github.com/onevcat/Kingfisher/blob/master/CONTRIBUTING.md). + + + +## Backers + +Thank you to all our backers! Your support is really important for the project and encourages us to continue. 🙏 [[Become a backer](https://opencollective.com/kingfisher#backer)] + + + + +## Sponsors + +Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/kingfisher#sponsor)] + + + + + + + + + + + + +### License + +Kingfisher is released under the MIT license. See LICENSE for details. diff --git a/!main project/Pods/Kingfisher/Sources/Cache/CacheSerializer.swift b/!main project/Pods/Kingfisher/Sources/Cache/CacheSerializer.swift new file mode 100644 index 0000000..2290c16 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Cache/CacheSerializer.swift @@ -0,0 +1,137 @@ +// +// CacheSerializer.swift +// Kingfisher +// +// Created by Wei Wang on 2016/09/02. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation +import CoreGraphics + +/// An `CacheSerializer` is used to convert some data to an image object after +/// retrieving it from disk storage, and vice versa, to convert an image to data object +/// for storing to the disk storage. +public protocol CacheSerializer { + + /// Gets the serialized data from a provided image + /// and optional original data for caching to disk. + /// + /// - Parameters: + /// - image: The image needed to be serialized. + /// - original: The original data which is just downloaded. + /// If the image is retrieved from cache instead of + /// downloaded, it will be `nil`. + /// - Returns: The data object for storing to disk, or `nil` when no valid + /// data could be serialized. + func data(with image: KFCrossPlatformImage, original: Data?) -> Data? + + /// Gets an image from provided serialized data. + /// + /// - Parameters: + /// - data: The data from which an image should be deserialized. + /// - options: The parsed options for deserialization. + /// - Returns: An image deserialized or `nil` when no valid image + /// could be deserialized. + func image(with data: Data, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? + + /// Gets an image deserialized from provided data. + /// + /// - Parameters: + /// - data: The data from which an image should be deserialized. + /// - options: Options for deserialization. + /// - Returns: An image deserialized or `nil` when no valid image + /// could be deserialized. + /// - Note: + /// This method is deprecated. Please implement the version with + /// `KingfisherParsedOptionsInfo` as parameter instead. + @available(*, deprecated, + message: "Deprecated. Implement the method with same name but with `KingfisherParsedOptionsInfo` instead.") + func image(with data: Data, options: KingfisherOptionsInfo?) -> KFCrossPlatformImage? +} + +extension CacheSerializer { + public func image(with data: Data, options: KingfisherOptionsInfo?) -> KFCrossPlatformImage? { + return image(with: data, options: KingfisherParsedOptionsInfo(options)) + } +} + +/// Represents a basic and default `CacheSerializer` used in Kingfisher disk cache system. +/// It could serialize and deserialize images in PNG, JPEG and GIF format. For +/// image other than these formats, a normalized `pngRepresentation` will be used. +public struct DefaultCacheSerializer: CacheSerializer { + + /// The default general cache serializer used across Kingfisher's cache. + public static let `default` = DefaultCacheSerializer() + + /// The compression quality when converting image to a lossy format data. Default is 1.0. + public var compressionQuality: CGFloat = 1.0 + + /// Whether the original data should be preferred when serializing the image. + /// If `true`, the input original data will be checked first and used unless the data is `nil`. + /// In that case, the serialization will fall back to creating data from image. + public var preferCacheOriginalData: Bool = false + + /// Creates a cache serializer that serialize and deserialize images in PNG, JPEG and GIF format. + /// + /// - Note: + /// Use `DefaultCacheSerializer.default` unless you need to specify your own properties. + /// + public init() { } + + /// - Parameters: + /// - image: The image needed to be serialized. + /// - original: The original data which is just downloaded. + /// If the image is retrieved from cache instead of + /// downloaded, it will be `nil`. + /// - Returns: The data object for storing to disk, or `nil` when no valid + /// data could be serialized. + /// + /// - Note: + /// Only when `original` contains valid PNG, JPEG and GIF format data, the `image` will be + /// converted to the corresponding data type. Otherwise, if the `original` is provided but it is not + /// If `original` is `nil`, the input `image` will be encoded as PNG data. + public func data(with image: KFCrossPlatformImage, original: Data?) -> Data? { + if preferCacheOriginalData { + return original ?? + image.kf.data( + format: original?.kf.imageFormat ?? .unknown, + compressionQuality: compressionQuality + ) + } else { + return image.kf.data( + format: original?.kf.imageFormat ?? .unknown, + compressionQuality: compressionQuality + ) + } + } + + /// Gets an image deserialized from provided data. + /// + /// - Parameters: + /// - data: The data from which an image should be deserialized. + /// - options: Options for deserialization. + /// - Returns: An image deserialized or `nil` when no valid image + /// could be deserialized. + public func image(with data: Data, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + return KingfisherWrapper.image(data: data, options: options.imageCreatingOptions) + } +} diff --git a/!main project/Pods/Kingfisher/Sources/Cache/DiskStorage.swift b/!main project/Pods/Kingfisher/Sources/Cache/DiskStorage.swift new file mode 100644 index 0000000..e538bd6 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Cache/DiskStorage.swift @@ -0,0 +1,466 @@ +// +// DiskStorage.swift +// Kingfisher +// +// Created by Wei Wang on 2018/10/15. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + + +/// Represents a set of conception related to storage which stores a certain type of value in disk. +/// This is a namespace for the disk storage types. A `Backend` with a certain `Config` will be used to describe the +/// storage. See these composed types for more information. +public enum DiskStorage { + + /// Represents a storage back-end for the `DiskStorage`. The value is serialized to data + /// and stored as file in the file system under a specified location. + /// + /// You can config a `DiskStorage.Backend` in its initializer by passing a `DiskStorage.Config` value. + /// or modifying the `config` property after it being created. `DiskStorage` will use file's attributes to keep + /// track of a file for its expiration or size limitation. + public class Backend { + /// The config used for this disk storage. + public var config: Config + + // The final storage URL on disk, with `name` and `cachePathBlock` considered. + public let directoryURL: URL + + let metaChangingQueue: DispatchQueue + + /// Creates a disk storage with the given `DiskStorage.Config`. + /// + /// - Parameter config: The config used for this disk storage. + /// - Throws: An error if the folder for storage cannot be got or created. + public init(config: Config) throws { + + self.config = config + + let url: URL + if let directory = config.directory { + url = directory + } else { + url = try config.fileManager.url( + for: .cachesDirectory, + in: .userDomainMask, + appropriateFor: nil, + create: true) + } + + let cacheName = "com.onevcat.Kingfisher.ImageCache.\(config.name)" + directoryURL = config.cachePathBlock(url, cacheName) + + metaChangingQueue = DispatchQueue(label: cacheName) + + try prepareDirectory() + } + + // Creates the storage folder. + func prepareDirectory() throws { + let fileManager = config.fileManager + let path = directoryURL.path + + guard !fileManager.fileExists(atPath: path) else { return } + + do { + try fileManager.createDirectory( + atPath: path, + withIntermediateDirectories: true, + attributes: nil) + } catch { + throw KingfisherError.cacheError(reason: .cannotCreateDirectory(path: path, error: error)) + } + } + + func store( + value: T, + forKey key: String, + expiration: StorageExpiration? = nil) throws + { + let expiration = expiration ?? config.expiration + // The expiration indicates that already expired, no need to store. + guard !expiration.isExpired else { return } + + let data: Data + do { + data = try value.toData() + } catch { + throw KingfisherError.cacheError(reason: .cannotConvertToData(object: value, error: error)) + } + + let fileURL = cacheFileURL(forKey: key) + do { + try data.write(to: fileURL) + } catch { + throw KingfisherError.cacheError( + reason: .cannotCreateCacheFile(fileURL: fileURL, key: key, data: data, error: error) + ) + } + + let now = Date() + let attributes: [FileAttributeKey : Any] = [ + // The last access date. + .creationDate: now.fileAttributeDate, + // The estimated expiration date. + .modificationDate: expiration.estimatedExpirationSinceNow.fileAttributeDate + ] + do { + try config.fileManager.setAttributes(attributes, ofItemAtPath: fileURL.path) + } catch { + try? config.fileManager.removeItem(at: fileURL) + throw KingfisherError.cacheError( + reason: .cannotSetCacheFileAttribute( + filePath: fileURL.path, + attributes: attributes, + error: error + ) + ) + } + } + + func value(forKey key: String, extendingExpiration: ExpirationExtending = .cacheTime) throws -> T? { + return try value(forKey: key, referenceDate: Date(), actuallyLoad: true, extendingExpiration: extendingExpiration) + } + + func value( + forKey key: String, + referenceDate: Date, + actuallyLoad: Bool, + extendingExpiration: ExpirationExtending) throws -> T? + { + let fileManager = config.fileManager + let fileURL = cacheFileURL(forKey: key) + let filePath = fileURL.path + guard fileManager.fileExists(atPath: filePath) else { + return nil + } + + let meta: FileMeta + do { + let resourceKeys: Set = [.contentModificationDateKey, .creationDateKey] + meta = try FileMeta(fileURL: fileURL, resourceKeys: resourceKeys) + } catch { + throw KingfisherError.cacheError( + reason: .invalidURLResource(error: error, key: key, url: fileURL)) + } + + if meta.expired(referenceDate: referenceDate) { + return nil + } + if !actuallyLoad { return T.empty } + + do { + let data = try Data(contentsOf: fileURL) + let obj = try T.fromData(data) + metaChangingQueue.async { + meta.extendExpiration(with: fileManager, extendingExpiration: extendingExpiration) + } + return obj + } catch { + throw KingfisherError.cacheError(reason: .cannotLoadDataFromDisk(url: fileURL, error: error)) + } + } + + func isCached(forKey key: String) -> Bool { + return isCached(forKey: key, referenceDate: Date()) + } + + func isCached(forKey key: String, referenceDate: Date) -> Bool { + do { + let result = try value( + forKey: key, + referenceDate: referenceDate, + actuallyLoad: false, + extendingExpiration: .none + ) + return result != nil + } catch { + return false + } + } + + func remove(forKey key: String) throws { + let fileURL = cacheFileURL(forKey: key) + try removeFile(at: fileURL) + } + + func removeFile(at url: URL) throws { + try config.fileManager.removeItem(at: url) + } + + func removeAll() throws { + try removeAll(skipCreatingDirectory: false) + } + + func removeAll(skipCreatingDirectory: Bool) throws { + try config.fileManager.removeItem(at: directoryURL) + if !skipCreatingDirectory { + try prepareDirectory() + } + } + + /// The URL of the cached file with a given computed `key`. + /// + /// - Note: + /// This method does not guarantee there is an image already cached in the returned URL. It just gives your + /// the URL that the image should be if it exists in disk storage, with the give key. + /// + /// - Parameter key: The final computed key used when caching the image. Please note that usually this is not + /// the `cacheKey` of an image `Source`. It is the computed key with processor identifier considered. + public func cacheFileURL(forKey key: String) -> URL { + let fileName = cacheFileName(forKey: key) + return directoryURL.appendingPathComponent(fileName) + } + + func cacheFileName(forKey key: String) -> String { + if config.usesHashedFileName { + let hashedKey = key.kf.md5 + if let ext = config.pathExtension { + return "\(hashedKey).\(ext)" + } + return hashedKey + } else { + if let ext = config.pathExtension { + return "\(key).\(ext)" + } + return key + } + } + + func allFileURLs(for propertyKeys: [URLResourceKey]) throws -> [URL] { + let fileManager = config.fileManager + + guard let directoryEnumerator = fileManager.enumerator( + at: directoryURL, includingPropertiesForKeys: propertyKeys, options: .skipsHiddenFiles) else + { + throw KingfisherError.cacheError(reason: .fileEnumeratorCreationFailed(url: directoryURL)) + } + + guard let urls = directoryEnumerator.allObjects as? [URL] else { + throw KingfisherError.cacheError(reason: .invalidFileEnumeratorContent(url: directoryURL)) + } + return urls + } + + func removeExpiredValues(referenceDate: Date = Date()) throws -> [URL] { + let propertyKeys: [URLResourceKey] = [ + .isDirectoryKey, + .contentModificationDateKey + ] + + let urls = try allFileURLs(for: propertyKeys) + let keys = Set(propertyKeys) + let expiredFiles = urls.filter { fileURL in + do { + let meta = try FileMeta(fileURL: fileURL, resourceKeys: keys) + if meta.isDirectory { + return false + } + return meta.expired(referenceDate: referenceDate) + } catch { + return true + } + } + try expiredFiles.forEach { url in + try removeFile(at: url) + } + return expiredFiles + } + + func removeSizeExceededValues() throws -> [URL] { + + if config.sizeLimit == 0 { return [] } // Back compatible. 0 means no limit. + + var size = try totalSize() + if size < config.sizeLimit { return [] } + + let propertyKeys: [URLResourceKey] = [ + .isDirectoryKey, + .creationDateKey, + .fileSizeKey + ] + let keys = Set(propertyKeys) + + let urls = try allFileURLs(for: propertyKeys) + var pendings: [FileMeta] = urls.compactMap { fileURL in + guard let meta = try? FileMeta(fileURL: fileURL, resourceKeys: keys) else { + return nil + } + return meta + } + // Sort by last access date. Most recent file first. + pendings.sort(by: FileMeta.lastAccessDate) + + var removed: [URL] = [] + let target = config.sizeLimit / 2 + while size > target, let meta = pendings.popLast() { + size -= UInt(meta.fileSize) + try removeFile(at: meta.url) + removed.append(meta.url) + } + return removed + } + + /// Get the total file size of the folder in bytes. + func totalSize() throws -> UInt { + let propertyKeys: [URLResourceKey] = [.fileSizeKey] + let urls = try allFileURLs(for: propertyKeys) + let keys = Set(propertyKeys) + let totalSize: UInt = urls.reduce(0) { size, fileURL in + do { + let meta = try FileMeta(fileURL: fileURL, resourceKeys: keys) + return size + UInt(meta.fileSize) + } catch { + return size + } + } + return totalSize + } + } +} + +extension DiskStorage { + /// Represents the config used in a `DiskStorage`. + public struct Config { + + /// The file size limit on disk of the storage in bytes. 0 means no limit. + public var sizeLimit: UInt + + /// The `StorageExpiration` used in this disk storage. Default is `.days(7)`, + /// means that the disk cache would expire in one week. + public var expiration: StorageExpiration = .days(7) + + /// The preferred extension of cache item. It will be appended to the file name as its extension. + /// Default is `nil`, means that the cache file does not contain a file extension. + public var pathExtension: String? = nil + + /// Default is `true`, means that the cache file name will be hashed before storing. + public var usesHashedFileName = true + + let name: String + let fileManager: FileManager + let directory: URL? + + var cachePathBlock: ((_ directory: URL, _ cacheName: String) -> URL)! = { + (directory, cacheName) in + return directory.appendingPathComponent(cacheName, isDirectory: true) + } + + /// Creates a config value based on given parameters. + /// + /// - Parameters: + /// - name: The name of cache. It is used as a part of storage folder. It is used to identify the disk + /// storage. Two storages with the same `name` would share the same folder in disk, and it should + /// be prevented. + /// - sizeLimit: The size limit in bytes for all existing files in the disk storage. + /// - fileManager: The `FileManager` used to manipulate files on disk. Default is `FileManager.default`. + /// - directory: The URL where the disk storage should live. The storage will use this as the root folder, + /// and append a path which is constructed by input `name`. Default is `nil`, indicates that + /// the cache directory under user domain mask will be used. + public init( + name: String, + sizeLimit: UInt, + fileManager: FileManager = .default, + directory: URL? = nil) + { + self.name = name + self.fileManager = fileManager + self.directory = directory + self.sizeLimit = sizeLimit + } + } +} + +extension DiskStorage { + struct FileMeta { + + let url: URL + + let lastAccessDate: Date? + let estimatedExpirationDate: Date? + let isDirectory: Bool + let fileSize: Int + + static func lastAccessDate(lhs: FileMeta, rhs: FileMeta) -> Bool { + return lhs.lastAccessDate ?? .distantPast > rhs.lastAccessDate ?? .distantPast + } + + init(fileURL: URL, resourceKeys: Set) throws { + let meta = try fileURL.resourceValues(forKeys: resourceKeys) + self.init( + fileURL: fileURL, + lastAccessDate: meta.creationDate, + estimatedExpirationDate: meta.contentModificationDate, + isDirectory: meta.isDirectory ?? false, + fileSize: meta.fileSize ?? 0) + } + + init( + fileURL: URL, + lastAccessDate: Date?, + estimatedExpirationDate: Date?, + isDirectory: Bool, + fileSize: Int) + { + self.url = fileURL + self.lastAccessDate = lastAccessDate + self.estimatedExpirationDate = estimatedExpirationDate + self.isDirectory = isDirectory + self.fileSize = fileSize + } + + func expired(referenceDate: Date) -> Bool { + return estimatedExpirationDate?.isPast(referenceDate: referenceDate) ?? true + } + + func extendExpiration(with fileManager: FileManager, extendingExpiration: ExpirationExtending) { + guard let lastAccessDate = lastAccessDate, + let lastEstimatedExpiration = estimatedExpirationDate else + { + return + } + + let attributes: [FileAttributeKey : Any] + + switch extendingExpiration { + case .none: + // not extending expiration time here + return + case .cacheTime: + let originalExpiration: StorageExpiration = + .seconds(lastEstimatedExpiration.timeIntervalSince(lastAccessDate)) + attributes = [ + .creationDate: Date().fileAttributeDate, + .modificationDate: originalExpiration.estimatedExpirationSinceNow.fileAttributeDate + ] + case .expirationTime(let expirationTime): + attributes = [ + .creationDate: Date().fileAttributeDate, + .modificationDate: expirationTime.estimatedExpirationSinceNow.fileAttributeDate + ] + } + + try? fileManager.setAttributes(attributes, ofItemAtPath: url.path) + } + } +} + diff --git a/!main project/Pods/Kingfisher/Sources/Cache/FormatIndicatedCacheSerializer.swift b/!main project/Pods/Kingfisher/Sources/Cache/FormatIndicatedCacheSerializer.swift new file mode 100644 index 0000000..cdfb7c3 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Cache/FormatIndicatedCacheSerializer.swift @@ -0,0 +1,118 @@ +// +// RequestModifier.swift +// Kingfisher +// +// Created by Junyu Kuang on 5/28/17. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation +import CoreGraphics + +/// `FormatIndicatedCacheSerializer` lets you indicate an image format for serialized caches. +/// +/// It could serialize and deserialize PNG, JPEG and GIF images. For +/// image other than these formats, a normalized `pngRepresentation` will be used. +/// +/// Example: +/// ```` +/// let profileImageSize = CGSize(width: 44, height: 44) +/// +/// // A round corner image. +/// let imageProcessor = RoundCornerImageProcessor( +/// cornerRadius: profileImageSize.width / 2, targetSize: profileImageSize) +/// +/// let optionsInfo: KingfisherOptionsInfo = [ +/// .cacheSerializer(FormatIndicatedCacheSerializer.png), +/// .processor(imageProcessor)] +/// +/// A URL pointing to a JPEG image. +/// let url = URL(string: "https://example.com/image.jpg")! +/// +/// // Image will be always cached as PNG format to preserve alpha channel for round rectangle. +/// // So when you load it from cache again later, it will be still round cornered. +/// // Otherwise, the corner part would be filled by white color (since JPEG does not contain an alpha channel). +/// imageView.kf.setImage(with: url, options: optionsInfo) +/// ```` +public struct FormatIndicatedCacheSerializer: CacheSerializer { + + /// A `FormatIndicatedCacheSerializer` which converts image from and to PNG format. If the image cannot be + /// represented by PNG format, it will fallback to its real format which is determined by `original` data. + public static let png = FormatIndicatedCacheSerializer(imageFormat: .PNG, jpegCompressionQuality: nil) + + /// A `FormatIndicatedCacheSerializer` which converts image from and to JPEG format. If the image cannot be + /// represented by JPEG format, it will fallback to its real format which is determined by `original` data. + /// The compression quality is 1.0 when using this serializer. If you need to set a customized compression quality, + /// use `jpeg(compressionQuality:)`. + public static let jpeg = FormatIndicatedCacheSerializer(imageFormat: .JPEG, jpegCompressionQuality: 1.0) + + /// A `FormatIndicatedCacheSerializer` which converts image from and to JPEG format with a settable compression + /// quality. If the image cannot be represented by JPEG format, it will fallback to its real format which is + /// determined by `original` data. + /// - Parameter compressionQuality: The compression quality when converting image to JPEG data. + public static func jpeg(compressionQuality: CGFloat) -> FormatIndicatedCacheSerializer { + return FormatIndicatedCacheSerializer(imageFormat: .JPEG, jpegCompressionQuality: compressionQuality) + } + + /// A `FormatIndicatedCacheSerializer` which converts image from and to GIF format. If the image cannot be + /// represented by GIF format, it will fallback to its real format which is determined by `original` data. + public static let gif = FormatIndicatedCacheSerializer(imageFormat: .GIF, jpegCompressionQuality: nil) + + /// The indicated image format. + private let imageFormat: ImageFormat + + /// The compression quality used for loss image format (like JPEG). + private let jpegCompressionQuality: CGFloat? + + /// Creates data which represents the given `image` under a format. + public func data(with image: KFCrossPlatformImage, original: Data?) -> Data? { + + func imageData(withFormat imageFormat: ImageFormat) -> Data? { + return autoreleasepool { () -> Data? in + switch imageFormat { + case .PNG: return image.kf.pngRepresentation() + case .JPEG: return image.kf.jpegRepresentation(compressionQuality: jpegCompressionQuality ?? 1.0) + case .GIF: return image.kf.gifRepresentation() + case .unknown: return nil + } + } + } + + // generate data with indicated image format + if let data = imageData(withFormat: imageFormat) { + return data + } + + let originalFormat = original?.kf.imageFormat ?? .unknown + + // generate data with original image's format + if originalFormat != imageFormat, let data = imageData(withFormat: originalFormat) { + return data + } + + return original ?? image.kf.normalized.kf.pngRepresentation() + } + + /// Same implementation as `DefaultCacheSerializer`. + public func image(with data: Data, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + return KingfisherWrapper.image(data: data, options: options.imageCreatingOptions) + } +} diff --git a/!main project/Pods/Kingfisher/Sources/Cache/ImageCache.swift b/!main project/Pods/Kingfisher/Sources/Cache/ImageCache.swift new file mode 100644 index 0000000..fde53b0 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Cache/ImageCache.swift @@ -0,0 +1,839 @@ +// +// ImageCache.swift +// Kingfisher +// +// Created by Wei Wang on 15/4/6. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#if os(macOS) +import AppKit +#else +import UIKit +#endif + +extension Notification.Name { + /// This notification will be sent when the disk cache got cleaned either there are cached files expired or the + /// total size exceeding the max allowed size. The manually invoking of `clearDiskCache` method will not trigger + /// this notification. + /// + /// The `object` of this notification is the `ImageCache` object which sends the notification. + /// A list of removed hashes (files) could be retrieved by accessing the array under + /// `KingfisherDiskCacheCleanedHashKey` key in `userInfo` of the notification object you received. + /// By checking the array, you could know the hash codes of files are removed. + public static let KingfisherDidCleanDiskCache = + Notification.Name("com.onevcat.Kingfisher.KingfisherDidCleanDiskCache") +} + +/// Key for array of cleaned hashes in `userInfo` of `KingfisherDidCleanDiskCacheNotification`. +public let KingfisherDiskCacheCleanedHashKey = "com.onevcat.Kingfisher.cleanedHash" + +/// Cache type of a cached image. +/// - none: The image is not cached yet when retrieving it. +/// - memory: The image is cached in memory. +/// - disk: The image is cached in disk. +public enum CacheType { + /// The image is not cached yet when retrieving it. + case none + /// The image is cached in memory. + case memory + /// The image is cached in disk. + case disk + + /// Whether the cache type represents the image is already cached or not. + public var cached: Bool { + switch self { + case .memory, .disk: return true + case .none: return false + } + } +} + +/// Represents the caching operation result. +public struct CacheStoreResult { + + /// The cache result for memory cache. Caching an image to memory will never fail. + public let memoryCacheResult: Result<(), Never> + + /// The cache result for disk cache. If an error happens during caching operation, + /// you can get it from `.failure` case of this `diskCacheResult`. + public let diskCacheResult: Result<(), KingfisherError> +} + +extension KFCrossPlatformImage: CacheCostCalculable { + /// Cost of an image + public var cacheCost: Int { return kf.cost } +} + +extension Data: DataTransformable { + public func toData() throws -> Data { + return self + } + + public static func fromData(_ data: Data) throws -> Data { + return data + } + + public static let empty = Data() +} + + +/// Represents the getting image operation from the cache. +/// +/// - disk: The image can be retrieved from disk cache. +/// - memory: The image can be retrieved memory cache. +/// - none: The image does not exist in the cache. +public enum ImageCacheResult { + + /// The image can be retrieved from disk cache. + case disk(KFCrossPlatformImage) + + /// The image can be retrieved memory cache. + case memory(KFCrossPlatformImage) + + /// The image does not exist in the cache. + case none + + /// Extracts the image from cache result. It returns the associated `Image` value for + /// `.disk` and `.memory` case. For `.none` case, `nil` is returned. + public var image: KFCrossPlatformImage? { + switch self { + case .disk(let image): return image + case .memory(let image): return image + case .none: return nil + } + } + + /// Returns the corresponding `CacheType` value based on the result type of `self`. + public var cacheType: CacheType { + switch self { + case .disk: return .disk + case .memory: return .memory + case .none: return .none + } + } +} + +/// Represents a hybrid caching system which is composed by a `MemoryStorage.Backend` and a `DiskStorage.Backend`. +/// `ImageCache` is a high level abstract for storing an image as well as its data to disk memory and disk, and +/// retrieving them back. +/// +/// While a default image cache object will be used if you prefer the extension methods of Kingfisher, you can create +/// your own cache object and configure its storages as your need. This class also provide an interface for you to set +/// the memory and disk storage config. +open class ImageCache { + + // MARK: Singleton + /// The default `ImageCache` object. Kingfisher will use this cache for its related methods if there is no + /// other cache specified. The `name` of this default cache is "default", and you should not use this name + /// for any of your customize cache. + public static let `default` = ImageCache(name: "default") + + // MARK: Public Properties + /// The `MemoryStorage.Backend` object used in this cache. This storage holds loaded images in memory with a + /// reasonable expire duration and a maximum memory usage. To modify the configuration of a storage, just set + /// the storage `config` and its properties. + public let memoryStorage: MemoryStorage.Backend + + /// The `DiskStorage.Backend` object used in this cache. This storage stores loaded images in disk with a + /// reasonable expire duration and a maximum disk usage. To modify the configuration of a storage, just set + /// the storage `config` and its properties. + public let diskStorage: DiskStorage.Backend + + private let ioQueue: DispatchQueue + + /// Closure that defines the disk cache path from a given path and cacheName. + public typealias DiskCachePathClosure = (URL, String) -> URL + + // MARK: Initializers + + /// Creates an `ImageCache` from a customized `MemoryStorage` and `DiskStorage`. + /// + /// - Parameters: + /// - memoryStorage: The `MemoryStorage.Backend` object to use in the image cache. + /// - diskStorage: The `DiskStorage.Backend` object to use in the image cache. + public init( + memoryStorage: MemoryStorage.Backend, + diskStorage: DiskStorage.Backend) + { + self.memoryStorage = memoryStorage + self.diskStorage = diskStorage + let ioQueueName = "com.onevcat.Kingfisher.ImageCache.ioQueue.\(UUID().uuidString)" + ioQueue = DispatchQueue(label: ioQueueName) + + let notifications: [(Notification.Name, Selector)] + #if !os(macOS) && !os(watchOS) + #if swift(>=4.2) + notifications = [ + (UIApplication.didReceiveMemoryWarningNotification, #selector(clearMemoryCache)), + (UIApplication.willTerminateNotification, #selector(cleanExpiredDiskCache)), + (UIApplication.didEnterBackgroundNotification, #selector(backgroundCleanExpiredDiskCache)) + ] + #else + notifications = [ + (NSNotification.Name.UIApplicationDidReceiveMemoryWarning, #selector(clearMemoryCache)), + (NSNotification.Name.UIApplicationWillTerminate, #selector(cleanExpiredDiskCache)), + (NSNotification.Name.UIApplicationDidEnterBackground, #selector(backgroundCleanExpiredDiskCache)) + ] + #endif + #elseif os(macOS) + notifications = [ + (NSApplication.willResignActiveNotification, #selector(cleanExpiredDiskCache)), + ] + #else + notifications = [] + #endif + notifications.forEach { + NotificationCenter.default.addObserver(self, selector: $0.1, name: $0.0, object: nil) + } + } + + /// Creates an `ImageCache` with a given `name`. Both `MemoryStorage` and `DiskStorage` will be created + /// with a default config based on the `name`. + /// + /// - Parameter name: The name of cache object. It is used to setup disk cache directories and IO queue. + /// You should not use the same `name` for different caches, otherwise, the disk storage would + /// be conflicting to each other. The `name` should not be an empty string. + public convenience init(name: String) { + try! self.init(name: name, cacheDirectoryURL: nil, diskCachePathClosure: nil) + } + + /// Creates an `ImageCache` with a given `name`, cache directory `path` + /// and a closure to modify the cache directory. + /// + /// - Parameters: + /// - name: The name of cache object. It is used to setup disk cache directories and IO queue. + /// You should not use the same `name` for different caches, otherwise, the disk storage would + /// be conflicting to each other. + /// - cacheDirectoryURL: Location of cache directory URL on disk. It will be internally pass to the + /// initializer of `DiskStorage` as the disk cache directory. If `nil`, the cache + /// directory under user domain mask will be used. + /// - diskCachePathClosure: Closure that takes in an optional initial path string and generates + /// the final disk cache path. You could use it to fully customize your cache path. + /// - Throws: An error that happens during image cache creating, such as unable to create a directory at the given + /// path. + public convenience init( + name: String, + cacheDirectoryURL: URL?, + diskCachePathClosure: DiskCachePathClosure? = nil) throws + { + if name.isEmpty { + fatalError("[Kingfisher] You should specify a name for the cache. A cache with empty name is not permitted.") + } + + let totalMemory = ProcessInfo.processInfo.physicalMemory + let costLimit = totalMemory / 4 + let memoryStorage = MemoryStorage.Backend(config: + .init(totalCostLimit: (costLimit > Int.max) ? Int.max : Int(costLimit))) + + var diskConfig = DiskStorage.Config( + name: name, + sizeLimit: 0, + directory: cacheDirectoryURL + ) + if let closure = diskCachePathClosure { + diskConfig.cachePathBlock = closure + } + let diskStorage = try DiskStorage.Backend(config: diskConfig) + diskConfig.cachePathBlock = nil + + self.init(memoryStorage: memoryStorage, diskStorage: diskStorage) + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + // MARK: Storing Images + + open func store(_ image: KFCrossPlatformImage, + original: Data? = nil, + forKey key: String, + options: KingfisherParsedOptionsInfo, + toDisk: Bool = true, + completionHandler: ((CacheStoreResult) -> Void)? = nil) + { + let identifier = options.processor.identifier + let callbackQueue = options.callbackQueue + + let computedKey = key.computedKey(with: identifier) + // Memory storage should not throw. + memoryStorage.storeNoThrow(value: image, forKey: computedKey, expiration: options.memoryCacheExpiration) + + guard toDisk else { + if let completionHandler = completionHandler { + let result = CacheStoreResult(memoryCacheResult: .success(()), diskCacheResult: .success(())) + callbackQueue.execute { completionHandler(result) } + } + return + } + + ioQueue.async { + let serializer = options.cacheSerializer + if let data = serializer.data(with: image, original: original) { + self.syncStoreToDisk( + data, + forKey: key, + processorIdentifier: identifier, + callbackQueue: callbackQueue, + expiration: options.diskCacheExpiration, + completionHandler: completionHandler) + } else { + guard let completionHandler = completionHandler else { return } + + let diskError = KingfisherError.cacheError( + reason: .cannotSerializeImage(image: image, original: original, serializer: serializer)) + let result = CacheStoreResult( + memoryCacheResult: .success(()), + diskCacheResult: .failure(diskError)) + callbackQueue.execute { completionHandler(result) } + } + } + } + + /// Stores an image to the cache. + /// + /// - Parameters: + /// - image: The image to be stored. + /// - original: The original data of the image. This value will be forwarded to the provided `serializer` for + /// further use. By default, Kingfisher uses a `DefaultCacheSerializer` to serialize the image to + /// data for caching in disk, it checks the image format based on `original` data to determine in + /// which image format should be used. For other types of `serializer`, it depends on their + /// implementation detail on how to use this original data. + /// - key: The key used for caching the image. + /// - identifier: The identifier of processor being used for caching. If you are using a processor for the + /// image, pass the identifier of processor to this parameter. + /// - serializer: The `CacheSerializer` + /// - toDisk: Whether this image should be cached to disk or not. If `false`, the image is only cached in memory. + /// Otherwise, it is cached in both memory storage and disk storage. Default is `true`. + /// - callbackQueue: The callback queue on which `completionHandler` is invoked. Default is `.untouch`. For case + /// that `toDisk` is `false`, a `.untouch` queue means `callbackQueue` will be invoked from the + /// caller queue of this method. If `toDisk` is `true`, the `completionHandler` will be called + /// from an internal file IO queue. To change this behavior, specify another `CallbackQueue` + /// value. + /// - completionHandler: A closure which is invoked when the cache operation finishes. + open func store(_ image: KFCrossPlatformImage, + original: Data? = nil, + forKey key: String, + processorIdentifier identifier: String = "", + cacheSerializer serializer: CacheSerializer = DefaultCacheSerializer.default, + toDisk: Bool = true, + callbackQueue: CallbackQueue = .untouch, + completionHandler: ((CacheStoreResult) -> Void)? = nil) + { + struct TempProcessor: ImageProcessor { + let identifier: String + func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + return nil + } + } + + let options = KingfisherParsedOptionsInfo([ + .processor(TempProcessor(identifier: identifier)), + .cacheSerializer(serializer), + .callbackQueue(callbackQueue) + ]) + store(image, original: original, forKey: key, options: options, + toDisk: toDisk, completionHandler: completionHandler) + } + + open func storeToDisk( + _ data: Data, + forKey key: String, + processorIdentifier identifier: String = "", + expiration: StorageExpiration? = nil, + callbackQueue: CallbackQueue = .untouch, + completionHandler: ((CacheStoreResult) -> Void)? = nil) + { + ioQueue.async { + self.syncStoreToDisk( + data, + forKey: key, + processorIdentifier: identifier, + callbackQueue: callbackQueue, + expiration: expiration, + completionHandler: completionHandler) + } + } + + private func syncStoreToDisk( + _ data: Data, + forKey key: String, + processorIdentifier identifier: String = "", + callbackQueue: CallbackQueue = .untouch, + expiration: StorageExpiration? = nil, + completionHandler: ((CacheStoreResult) -> Void)? = nil) + { + let computedKey = key.computedKey(with: identifier) + let result: CacheStoreResult + do { + try self.diskStorage.store(value: data, forKey: computedKey, expiration: expiration) + result = CacheStoreResult(memoryCacheResult: .success(()), diskCacheResult: .success(())) + } catch { + let diskError: KingfisherError + if let error = error as? KingfisherError { + diskError = error + } else { + diskError = .cacheError(reason: .cannotConvertToData(object: data, error: error)) + } + + result = CacheStoreResult( + memoryCacheResult: .success(()), + diskCacheResult: .failure(diskError) + ) + } + if let completionHandler = completionHandler { + callbackQueue.execute { completionHandler(result) } + } + } + + // MARK: Removing Images + + /// Removes the image for the given key from the cache. + /// + /// - Parameters: + /// - key: The key used for caching the image. + /// - identifier: The identifier of processor being used for caching. If you are using a processor for the + /// image, pass the identifier of processor to this parameter. + /// - fromMemory: Whether this image should be removed from memory storage or not. + /// If `false`, the image won't be removed from the memory storage. Default is `true`. + /// - fromDisk: Whether this image should be removed from disk storage or not. + /// If `false`, the image won't be removed from the disk storage. Default is `true`. + /// - callbackQueue: The callback queue on which `completionHandler` is invoked. Default is `.untouch`. + /// - completionHandler: A closure which is invoked when the cache removing operation finishes. + open func removeImage(forKey key: String, + processorIdentifier identifier: String = "", + fromMemory: Bool = true, + fromDisk: Bool = true, + callbackQueue: CallbackQueue = .untouch, + completionHandler: (() -> Void)? = nil) + { + let computedKey = key.computedKey(with: identifier) + + if fromMemory { + try? memoryStorage.remove(forKey: computedKey) + } + + if fromDisk { + ioQueue.async{ + try? self.diskStorage.remove(forKey: computedKey) + if let completionHandler = completionHandler { + callbackQueue.execute { completionHandler() } + } + } + } else { + if let completionHandler = completionHandler { + callbackQueue.execute { completionHandler() } + } + } + } + + func retrieveImage(forKey key: String, + options: KingfisherParsedOptionsInfo, + callbackQueue: CallbackQueue = .mainCurrentOrAsync, + completionHandler: ((Result) -> Void)?) + { + // No completion handler. No need to start working and early return. + guard let completionHandler = completionHandler else { return } + + // Try to check the image from memory cache first. + if let image = retrieveImageInMemoryCache(forKey: key, options: options) { + let image = options.imageModifier?.modify(image) ?? image + callbackQueue.execute { completionHandler(.success(.memory(image))) } + } else if options.fromMemoryCacheOrRefresh { + callbackQueue.execute { completionHandler(.success(.none)) } + } else { + + // Begin to disk search. + self.retrieveImageInDiskCache(forKey: key, options: options, callbackQueue: callbackQueue) { + result in + switch result { + case .success(let image): + + guard let image = image else { + // No image found in disk storage. + callbackQueue.execute { completionHandler(.success(.none)) } + return + } + + let finalImage = options.imageModifier?.modify(image) ?? image + // Cache the disk image to memory. + // We are passing `false` to `toDisk`, the memory cache does not change + // callback queue, we can call `completionHandler` without another dispatch. + var cacheOptions = options + cacheOptions.callbackQueue = .untouch + self.store( + finalImage, + forKey: key, + options: cacheOptions, + toDisk: false) + { + _ in + callbackQueue.execute { completionHandler(.success(.disk(finalImage))) } + } + case .failure(let error): + callbackQueue.execute { completionHandler(.failure(error)) } + } + } + } + } + + // MARK: Getting Images + + /// Gets an image for a given key from the cache, either from memory storage or disk storage. + /// + /// - Parameters: + /// - key: The key used for caching the image. + /// - options: The `KingfisherOptionsInfo` options setting used for retrieving the image. + /// - callbackQueue: The callback queue on which `completionHandler` is invoked. Default is `.mainCurrentOrAsync`. + /// - completionHandler: A closure which is invoked when the image getting operation finishes. If the + /// image retrieving operation finishes without problem, an `ImageCacheResult` value + /// will be sent to this closure as result. Otherwise, a `KingfisherError` result + /// with detail failing reason will be sent. + open func retrieveImage(forKey key: String, + options: KingfisherOptionsInfo? = nil, + callbackQueue: CallbackQueue = .mainCurrentOrAsync, + completionHandler: ((Result) -> Void)?) + { + retrieveImage( + forKey: key, + options: KingfisherParsedOptionsInfo(options), + callbackQueue: callbackQueue, + completionHandler: completionHandler) + } + + func retrieveImageInMemoryCache( + forKey key: String, + options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? + { + let computedKey = key.computedKey(with: options.processor.identifier) + return memoryStorage.value(forKey: computedKey, extendingExpiration: options.memoryCacheAccessExtendingExpiration) + } + + /// Gets an image for a given key from the memory storage. + /// + /// - Parameters: + /// - key: The key used for caching the image. + /// - options: The `KingfisherOptionsInfo` options setting used for retrieving the image. + /// - Returns: The image stored in memory cache, if exists and valid. Otherwise, if the image does not exist or + /// has already expired, `nil` is returned. + open func retrieveImageInMemoryCache( + forKey key: String, + options: KingfisherOptionsInfo? = nil) -> KFCrossPlatformImage? + { + return retrieveImageInMemoryCache(forKey: key, options: KingfisherParsedOptionsInfo(options)) + } + + func retrieveImageInDiskCache( + forKey key: String, + options: KingfisherParsedOptionsInfo, + callbackQueue: CallbackQueue = .untouch, + completionHandler: @escaping (Result) -> Void) + { + let computedKey = key.computedKey(with: options.processor.identifier) + let loadingQueue: CallbackQueue = options.loadDiskFileSynchronously ? .untouch : .dispatch(ioQueue) + loadingQueue.execute { + do { + var image: KFCrossPlatformImage? = nil + if let data = try self.diskStorage.value(forKey: computedKey, extendingExpiration: options.diskCacheAccessExtendingExpiration) { + image = options.cacheSerializer.image(with: data, options: options) + } + callbackQueue.execute { completionHandler(.success(image)) } + } catch { + if let error = error as? KingfisherError { + callbackQueue.execute { completionHandler(.failure(error)) } + } else { + assertionFailure("The internal thrown error should be a `KingfisherError`.") + } + } + } + } + + /// Gets an image for a given key from the disk storage. + /// + /// - Parameters: + /// - key: The key used for caching the image. + /// - options: The `KingfisherOptionsInfo` options setting used for retrieving the image. + /// - callbackQueue: The callback queue on which `completionHandler` is invoked. Default is `.untouch`. + /// - completionHandler: A closure which is invoked when the operation finishes. + open func retrieveImageInDiskCache( + forKey key: String, + options: KingfisherOptionsInfo? = nil, + callbackQueue: CallbackQueue = .untouch, + completionHandler: @escaping (Result) -> Void) + { + retrieveImageInDiskCache( + forKey: key, + options: KingfisherParsedOptionsInfo(options), + callbackQueue: callbackQueue, + completionHandler: completionHandler) + } + + // MARK: Cleaning + /// Clears the memory storage of this cache. + @objc public func clearMemoryCache() { + try? memoryStorage.removeAll() + } + + /// Clears the disk storage of this cache. This is an async operation. + /// + /// - Parameter handler: A closure which is invoked when the cache clearing operation finishes. + /// This `handler` will be called from the main queue. + open func clearDiskCache(completion handler: (()->())? = nil) { + ioQueue.async { + do { + try self.diskStorage.removeAll() + } catch _ { } + if let handler = handler { + DispatchQueue.main.async { handler() } + } + } + } + + /// Clears the expired images from disk storage. This is an async operation. + open func cleanExpiredMemoryCache() { + memoryStorage.removeExpired() + } + + /// Clears the expired images from disk storage. This is an async operation. + @objc func cleanExpiredDiskCache() { + cleanExpiredDiskCache(completion: nil) + } + + /// Clears the expired images from disk storage. This is an async operation. + /// + /// - Parameter handler: A closure which is invoked when the cache clearing operation finishes. + /// This `handler` will be called from the main queue. + open func cleanExpiredDiskCache(completion handler: (() -> Void)? = nil) { + ioQueue.async { + do { + var removed: [URL] = [] + let removedExpired = try self.diskStorage.removeExpiredValues() + removed.append(contentsOf: removedExpired) + + let removedSizeExceeded = try self.diskStorage.removeSizeExceededValues() + removed.append(contentsOf: removedSizeExceeded) + + if !removed.isEmpty { + DispatchQueue.main.async { + let cleanedHashes = removed.map { $0.lastPathComponent } + NotificationCenter.default.post( + name: .KingfisherDidCleanDiskCache, + object: self, + userInfo: [KingfisherDiskCacheCleanedHashKey: cleanedHashes]) + } + } + + if let handler = handler { + DispatchQueue.main.async { handler() } + } + } catch {} + } + } + +#if !os(macOS) && !os(watchOS) + /// Clears the expired images from disk storage when app is in background. This is an async operation. + /// In most cases, you should not call this method explicitly. + /// It will be called automatically when `UIApplicationDidEnterBackgroundNotification` received. + @objc public func backgroundCleanExpiredDiskCache() { + // if 'sharedApplication()' is unavailable, then return + guard let sharedApplication = KingfisherWrapper.shared else { return } + + func endBackgroundTask(_ task: inout UIBackgroundTaskIdentifier) { + sharedApplication.endBackgroundTask(task) + #if swift(>=4.2) + task = UIBackgroundTaskIdentifier.invalid + #else + task = UIBackgroundTaskInvalid + #endif + } + + var backgroundTask: UIBackgroundTaskIdentifier! + backgroundTask = sharedApplication.beginBackgroundTask { + endBackgroundTask(&backgroundTask!) + } + + cleanExpiredDiskCache { + endBackgroundTask(&backgroundTask!) + } + } +#endif + + // MARK: Image Cache State + + /// Returns the cache type for a given `key` and `identifier` combination. + /// This method is used for checking whether an image is cached in current cache. + /// It also provides information on which kind of cache can it be found in the return value. + /// + /// - Parameters: + /// - key: The key used for caching the image. + /// - identifier: Processor identifier which used for this image. Default is the `identifier` of + /// `DefaultImageProcessor.default`. + /// - Returns: A `CacheType` instance which indicates the cache status. + /// `.none` means the image is not in cache or it is already expired. + open func imageCachedType( + forKey key: String, + processorIdentifier identifier: String = DefaultImageProcessor.default.identifier) -> CacheType + { + let computedKey = key.computedKey(with: identifier) + if memoryStorage.isCached(forKey: computedKey) { return .memory } + if diskStorage.isCached(forKey: computedKey) { return .disk } + return .none + } + + /// Returns whether the file exists in cache for a given `key` and `identifier` combination. + /// + /// - Parameters: + /// - key: The key used for caching the image. + /// - identifier: Processor identifier which used for this image. Default is the `identifier` of + /// `DefaultImageProcessor.default`. + /// - Returns: A `Bool` which indicates whether a cache could match the given `key` and `identifier` combination. + /// + /// - Note: + /// The return value does not contain information about from which kind of storage the cache matches. + /// To get the information about cache type according `CacheType`, + /// use `imageCachedType(forKey:processorIdentifier:)` instead. + public func isCached( + forKey key: String, + processorIdentifier identifier: String = DefaultImageProcessor.default.identifier) -> Bool + { + return imageCachedType(forKey: key, processorIdentifier: identifier).cached + } + + /// Gets the hash used as cache file name for the key. + /// + /// - Parameters: + /// - key: The key used for caching the image. + /// - identifier: Processor identifier which used for this image. Default is the `identifier` of + /// `DefaultImageProcessor.default`. + /// - Returns: The hash which is used as the cache file name. + /// + /// - Note: + /// By default, for a given combination of `key` and `identifier`, `ImageCache` will use the value + /// returned by this method as the cache file name. You can use this value to check and match cache file + /// if you need. + open func hash( + forKey key: String, + processorIdentifier identifier: String = DefaultImageProcessor.default.identifier) -> String + { + let computedKey = key.computedKey(with: identifier) + return diskStorage.cacheFileName(forKey: computedKey) + } + + /// Calculates the size taken by the disk storage. + /// It is the total file size of all cached files in the `diskStorage` on disk in bytes. + /// + /// - Parameter handler: Called with the size calculating finishes. This closure is invoked from the main queue. + open func calculateDiskStorageSize(completion handler: @escaping ((Result) -> Void)) { + ioQueue.async { + do { + let size = try self.diskStorage.totalSize() + DispatchQueue.main.async { handler(.success(size)) } + } catch { + if let error = error as? KingfisherError { + DispatchQueue.main.async { handler(.failure(error)) } + } else { + assertionFailure("The internal thrown error should be a `KingfisherError`.") + } + + } + } + } + + /// Gets the cache path for the key. + /// It is useful for projects with web view or anyone that needs access to the local file path. + /// + /// i.e. Replacing the `` tag in your HTML. + /// + /// - Parameters: + /// - key: The key used for caching the image. + /// - identifier: Processor identifier which used for this image. Default is the `identifier` of + /// `DefaultImageProcessor.default`. + /// - Returns: The disk path of cached image under the given `key` and `identifier`. + /// + /// - Note: + /// This method does not guarantee there is an image already cached in the returned path. It just gives your + /// the path that the image should be, if it exists in disk storage. + /// + /// You could use `isCached(forKey:)` method to check whether the image is cached under that key in disk. + open func cachePath( + forKey key: String, + processorIdentifier identifier: String = DefaultImageProcessor.default.identifier) -> String + { + let computedKey = key.computedKey(with: identifier) + return diskStorage.cacheFileURL(forKey: computedKey).path + } +} + +extension Dictionary { + func keysSortedByValue(_ isOrderedBefore: (Value, Value) -> Bool) -> [Key] { + return Array(self).sorted{ isOrderedBefore($0.1, $1.1) }.map{ $0.0 } + } +} + +#if !os(macOS) && !os(watchOS) +// MARK: - For App Extensions +extension UIApplication: KingfisherCompatible { } +extension KingfisherWrapper where Base: UIApplication { + public static var shared: UIApplication? { + let selector = NSSelectorFromString("sharedApplication") + guard Base.responds(to: selector) else { return nil } + return Base.perform(selector).takeUnretainedValue() as? UIApplication + } +} +#endif + +extension String { + func computedKey(with identifier: String) -> String { + if identifier.isEmpty { + return self + } else { + return appending("@\(identifier)") + } + } +} + +extension ImageCache { + + /// Creates an `ImageCache` with a given `name`, cache directory `path` + /// and a closure to modify the cache directory. + /// + /// - Parameters: + /// - name: The name of cache object. It is used to setup disk cache directories and IO queue. + /// You should not use the same `name` for different caches, otherwise, the disk storage would + /// be conflicting to each other. + /// - path: Location of cache URL on disk. It will be internally pass to the initializer of `DiskStorage` as the + /// disk cache directory. + /// - diskCachePathClosure: Closure that takes in an optional initial path string and generates + /// the final disk cache path. You could use it to fully customize your cache path. + /// - Throws: An error that happens during image cache creating, such as unable to create a directory at the given + /// path. + @available(*, deprecated, message: "Use `init(name:cacheDirectoryURL:diskCachePathClosure:)` instead", + renamed: "init(name:cacheDirectoryURL:diskCachePathClosure:)") + public convenience init( + name: String, + path: String?, + diskCachePathClosure: DiskCachePathClosure? = nil) throws + { + let directoryURL = path.flatMap { URL(string: $0) } + try self.init(name: name, cacheDirectoryURL: directoryURL, diskCachePathClosure: diskCachePathClosure) + } +} diff --git a/!main project/Pods/Kingfisher/Sources/Cache/MemoryStorage.swift b/!main project/Pods/Kingfisher/Sources/Cache/MemoryStorage.swift new file mode 100644 index 0000000..fd0965b --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Cache/MemoryStorage.swift @@ -0,0 +1,237 @@ +// +// MemoryStorage.swift +// Kingfisher +// +// Created by Wei Wang on 2018/10/15. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +/// Represents a set of conception related to storage which stores a certain type of value in memory. +/// This is a namespace for the memory storage types. A `Backend` with a certain `Config` will be used to describe the +/// storage. See these composed types for more information. +public enum MemoryStorage { + + /// Represents a storage which stores a certain type of value in memory. It provides fast access, + /// but limited storing size. The stored value type needs to conform to `CacheCostCalculable`, + /// and its `cacheCost` will be used to determine the cost of size for the cache item. + /// + /// You can config a `MemoryStorage.Backend` in its initializer by passing a `MemoryStorage.Config` value. + /// or modifying the `config` property after it being created. The backend of `MemoryStorage` has + /// upper limitation on cost size in memory and item count. All items in the storage has an expiration + /// date. When retrieved, if the target item is already expired, it will be recognized as it does not + /// exist in the storage. The `MemoryStorage` also contains a scheduled self clean task, to evict expired + /// items from memory. + public class Backend { + let storage = NSCache>() + + // Keys trackes the objects once inside the storage. For object removing triggered by user, the corresponding + // key would be also removed. However, for the object removing triggered by cache rule/policy of system, the + // key will be remained there until next `removeExpired` happens. + // + // Breaking the strict tracking could save additional locking behaviors. + // See https://github.com/onevcat/Kingfisher/issues/1233 + var keys = Set() + + private var cleanTimer: Timer? = nil + private let lock = NSLock() + + /// The config used in this storage. It is a value you can set and + /// use to config the storage in air. + public var config: Config { + didSet { + storage.totalCostLimit = config.totalCostLimit + storage.countLimit = config.countLimit + } + } + + /// Creates a `MemoryStorage` with a given `config`. + /// + /// - Parameter config: The config used to create the storage. It determines the max size limitation, + /// default expiration setting and more. + public init(config: Config) { + self.config = config + storage.totalCostLimit = config.totalCostLimit + storage.countLimit = config.countLimit + + cleanTimer = .scheduledTimer(withTimeInterval: config.cleanInterval, repeats: true) { [weak self] _ in + guard let self = self else { return } + self.removeExpired() + } + } + + func removeExpired() { + lock.lock() + defer { lock.unlock() } + for key in keys { + let nsKey = key as NSString + guard let object = storage.object(forKey: nsKey) else { + // This could happen if the object is moved by cache `totalCostLimit` or `countLimit` rule. + // We didn't remove the key yet until now, since we do not want to introduce additonal lock. + // See https://github.com/onevcat/Kingfisher/issues/1233 + keys.remove(key) + continue + } + if object.estimatedExpiration.isPast { + storage.removeObject(forKey: nsKey) + keys.remove(key) + } + } + } + + // Storing in memory will not throw. It is just for meeting protocol requirement and + // forwarding to no throwing method. + func store( + value: T, + forKey key: String, + expiration: StorageExpiration? = nil) throws + { + storeNoThrow(value: value, forKey: key, expiration: expiration) + } + + // The no throw version for storing value in cache. Kingfisher knows the detail so it + // could use this version to make syntax simpler internally. + func storeNoThrow( + value: T, + forKey key: String, + expiration: StorageExpiration? = nil) + { + lock.lock() + defer { lock.unlock() } + let expiration = expiration ?? config.expiration + // The expiration indicates that already expired, no need to store. + guard !expiration.isExpired else { return } + + let object = StorageObject(value, key: key, expiration: expiration) + storage.setObject(object, forKey: key as NSString, cost: value.cacheCost) + keys.insert(key) + } + + /// Use this when you actually access the memory cached item. + /// By default, this will extend the expired data for the accessed item. + /// + /// - Parameters: + /// - key: Cache Key + /// - extendingExpiration: expiration value to extend item expiration time: + /// * .none: The item expires after the original time, without extending after access. + /// * .cacheTime: The item expiration extends by the original cache time after each access. + /// * .expirationTime: The item expiration extends by the provided time after each access. + /// - Returns: cached object or nil + func value(forKey key: String, extendingExpiration: ExpirationExtending = .cacheTime) -> T? { + guard let object = storage.object(forKey: key as NSString) else { + return nil + } + if object.expired { + return nil + } + object.extendExpiration(extendingExpiration) + return object.value + } + + func isCached(forKey key: String) -> Bool { + guard let _ = value(forKey: key, extendingExpiration: .none) else { + return false + } + return true + } + + func remove(forKey key: String) throws { + lock.lock() + defer { lock.unlock() } + storage.removeObject(forKey: key as NSString) + keys.remove(key) + } + + func removeAll() throws { + lock.lock() + defer { lock.unlock() } + storage.removeAllObjects() + keys.removeAll() + } + } +} + +extension MemoryStorage { + /// Represents the config used in a `MemoryStorage`. + public struct Config { + + /// Total cost limit of the storage in bytes. + public var totalCostLimit: Int + + /// The item count limit of the memory storage. + public var countLimit: Int = .max + + /// The `StorageExpiration` used in this memory storage. Default is `.seconds(300)`, + /// means that the memory cache would expire in 5 minutes. + public var expiration: StorageExpiration = .seconds(300) + + /// The time interval between the storage do clean work for swiping expired items. + public let cleanInterval: TimeInterval + + /// Creates a config from a given `totalCostLimit` value. + /// + /// - Parameters: + /// - totalCostLimit: Total cost limit of the storage in bytes. + /// - cleanInterval: The time interval between the storage do clean work for swiping expired items. + /// Default is 120, means the auto eviction happens once per two minutes. + /// + /// - Note: + /// Other members of `MemoryStorage.Config` will use their default values when created. + public init(totalCostLimit: Int, cleanInterval: TimeInterval = 120) { + self.totalCostLimit = totalCostLimit + self.cleanInterval = cleanInterval + } + } +} + +extension MemoryStorage { + class StorageObject { + let value: T + let expiration: StorageExpiration + let key: String + + private(set) var estimatedExpiration: Date + + init(_ value: T, key: String, expiration: StorageExpiration) { + self.value = value + self.key = key + self.expiration = expiration + + self.estimatedExpiration = expiration.estimatedExpirationSinceNow + } + + func extendExpiration(_ extendingExpiration: ExpirationExtending = .cacheTime) { + switch extendingExpiration { + case .none: + return + case .cacheTime: + self.estimatedExpiration = expiration.estimatedExpirationSinceNow + case .expirationTime(let expirationTime): + self.estimatedExpiration = expirationTime.estimatedExpirationSinceNow + } + } + + var expired: Bool { + return estimatedExpiration.isPast + } + } +} diff --git a/!main project/Pods/Kingfisher/Sources/Cache/Storage.swift b/!main project/Pods/Kingfisher/Sources/Cache/Storage.swift new file mode 100644 index 0000000..fb2b423 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Cache/Storage.swift @@ -0,0 +1,113 @@ +// +// Storage.swift +// Kingfisher +// +// Created by Wei Wang on 2018/10/15. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +/// Constants for some time intervals +struct TimeConstants { + static let secondsInOneMinute = 60 + static let minutesInOneHour = 60 + static let hoursInOneDay = 24 + static let secondsInOneDay = 86_400 +} + +/// Represents the expiration strategy used in storage. +/// +/// - never: The item never expires. +/// - seconds: The item expires after a time duration of given seconds from now. +/// - days: The item expires after a time duration of given days from now. +/// - date: The item expires after a given date. +public enum StorageExpiration { + /// The item never expires. + case never + /// The item expires after a time duration of given seconds from now. + case seconds(TimeInterval) + /// The item expires after a time duration of given days from now. + case days(Int) + /// The item expires after a given date. + case date(Date) + /// Indicates the item is already expired. Use this to skip cache. + case expired + + func estimatedExpirationSince(_ date: Date) -> Date { + switch self { + case .never: return .distantFuture + case .seconds(let seconds): + return date.addingTimeInterval(seconds) + case .days(let days): + let duration = TimeInterval(TimeConstants.secondsInOneDay) * TimeInterval(days) + return date.addingTimeInterval(duration) + case .date(let ref): + return ref + case .expired: + return .distantPast + } + } + + var estimatedExpirationSinceNow: Date { + return estimatedExpirationSince(Date()) + } + + var isExpired: Bool { + return timeInterval <= 0 + } + + var timeInterval: TimeInterval { + switch self { + case .never: return .infinity + case .seconds(let seconds): return seconds + case .days(let days): return TimeInterval(TimeConstants.secondsInOneDay) * TimeInterval(days) + case .date(let ref): return ref.timeIntervalSinceNow + case .expired: return -(.infinity) + } + } +} + +/// Represents the expiration extending strategy used in storage to after access. +/// +/// - none: The item expires after the original time, without extending after access. +/// - cacheTime: The item expiration extends by the original cache time after each access. +/// - expirationTime: The item expiration extends by the provided time after each access. +public enum ExpirationExtending { + /// The item expires after the original time, without extending after access. + case none + /// The item expiration extends by the original cache time after each access. + case cacheTime + /// The item expiration extends by the provided time after each access. + case expirationTime(_ expiration: StorageExpiration) +} + +/// Represents types which cost in memory can be calculated. +public protocol CacheCostCalculable { + var cacheCost: Int { get } +} + +/// Represents types which can be converted to and from data. +public protocol DataTransformable { + func toData() throws -> Data + static func fromData(_ data: Data) throws -> Self + static var empty: Self { get } +} diff --git a/!main project/Pods/Kingfisher/Sources/Extensions/ImageView+Kingfisher.swift b/!main project/Pods/Kingfisher/Sources/Extensions/ImageView+Kingfisher.swift new file mode 100644 index 0000000..1df2a7f --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Extensions/ImageView+Kingfisher.swift @@ -0,0 +1,419 @@ +// +// ImageView+Kingfisher.swift +// Kingfisher +// +// Created by Wei Wang on 15/4/6. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#if !os(watchOS) + +#if os(macOS) +import AppKit +#else +import UIKit +#endif + +extension KingfisherWrapper where Base: KFCrossPlatformImageView { + + // MARK: Setting Image + + /// Sets an image to the image view with a `Source`. + /// + /// - Parameters: + /// - source: The `Source` object defines data information from network or a data provider. + /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. + /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. + /// - completionHandler: Called when the image retrieved and set finished. + /// - Returns: A task represents the image downloading. + /// + /// - Note: + /// This is the easiest way to use Kingfisher to boost the image setting process from a source. Since all parameters + /// have a default value except the `source`, you can set an image from a certain URL to an image view like this: + /// + /// ``` + /// // Set image from a network source. + /// let url = URL(string: "https://example.com/image.png")! + /// imageView.kf.setImage(with: .network(url)) + /// + /// // Or set image from a data provider. + /// let provider = LocalFileImageDataProvider(fileURL: fileURL) + /// imageView.kf.setImage(with: .provider(provider)) + /// ``` + /// + /// For both `.network` and `.provider` source, there are corresponding view extension methods. So the code + /// above is equivalent to: + /// + /// ``` + /// imageView.kf.setImage(with: url) + /// imageView.kf.setImage(with: provider) + /// ``` + /// + /// Internally, this method will use `KingfisherManager` to get the source. + /// Since this method will perform UI changes, you must call it from the main thread. + /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. + /// + @discardableResult + public func setImage( + with source: Source?, + placeholder: Placeholder? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + var mutatingSelf = self + guard let source = source else { + mutatingSelf.placeholder = placeholder + mutatingSelf.taskIdentifier = nil + completionHandler?(.failure(KingfisherError.imageSettingError(reason: .emptySource))) + return nil + } + + var options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions + (options ?? .empty)) + let noImageOrPlaceholderSet = base.image == nil && self.placeholder == nil + if !options.keepCurrentImageWhileLoading || noImageOrPlaceholderSet { + // Always set placeholder while there is no image/placeholder yet. + mutatingSelf.placeholder = placeholder + } + + let maybeIndicator = indicator + maybeIndicator?.startAnimatingView() + + let issuedIdentifier = Source.Identifier.next() + mutatingSelf.taskIdentifier = issuedIdentifier + + if base.shouldPreloadAllAnimation() { + options.preloadAllAnimationData = true + } + + if let block = progressBlock { + options.onDataReceived = (options.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)] + } + + if let provider = ImageProgressiveProvider(options, refresh: { image in + self.base.image = image + }) { + options.onDataReceived = (options.onDataReceived ?? []) + [provider] + } + + options.onDataReceived?.forEach { + $0.onShouldApply = { issuedIdentifier == self.taskIdentifier } + } + + let task = KingfisherManager.shared.retrieveImage( + with: source, + options: options, + downloadTaskUpdated: { mutatingSelf.imageTask = $0 }, + completionHandler: { result in + CallbackQueue.mainCurrentOrAsync.execute { + maybeIndicator?.stopAnimatingView() + guard issuedIdentifier == self.taskIdentifier else { + let reason: KingfisherError.ImageSettingErrorReason + do { + let value = try result.get() + reason = .notCurrentSourceTask(result: value, error: nil, source: source) + } catch { + reason = .notCurrentSourceTask(result: nil, error: error, source: source) + } + let error = KingfisherError.imageSettingError(reason: reason) + completionHandler?(.failure(error)) + return + } + + mutatingSelf.imageTask = nil + mutatingSelf.taskIdentifier = nil + + switch result { + case .success(let value): + guard self.needsTransition(options: options, cacheType: value.cacheType) else { + mutatingSelf.placeholder = nil + self.base.image = value.image + completionHandler?(result) + return + } + + self.makeTransition(image: value.image, transition: options.transition) { + completionHandler?(result) + } + + case .failure: + if let image = options.onFailureImage { + self.base.image = image + } + completionHandler?(result) + } + } + } + ) + mutatingSelf.imageTask = task + return task + } + + /// Sets an image to the image view with a requested resource. + /// + /// - Parameters: + /// - resource: The `Resource` object contains information about the resource. + /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. + /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. + /// - completionHandler: Called when the image retrieved and set finished. + /// - Returns: A task represents the image downloading. + /// + /// - Note: + /// This is the easiest way to use Kingfisher to boost the image setting process from network. Since all parameters + /// have a default value except the `resource`, you can set an image from a certain URL to an image view like this: + /// + /// ``` + /// let url = URL(string: "https://example.com/image.png")! + /// imageView.kf.setImage(with: url) + /// ``` + /// + /// Internally, this method will use `KingfisherManager` to get the requested resource, from either cache + /// or network. Since this method will perform UI changes, you must call it from the main thread. + /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. + /// + @discardableResult + public func setImage( + with resource: Resource?, + placeholder: Placeholder? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + return setImage( + with: resource?.convertToSource(), + placeholder: placeholder, + options: options, + progressBlock: progressBlock, + completionHandler: completionHandler) + } + + /// Sets an image to the image view with a data provider. + /// + /// - Parameters: + /// - provider: The `ImageDataProvider` object contains information about the data. + /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. + /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. + /// - completionHandler: Called when the image retrieved and set finished. + /// - Returns: A task represents the image downloading. + /// + /// Internally, this method will use `KingfisherManager` to get the image data, from either cache + /// or the data provider. Since this method will perform UI changes, you must call it from the main thread. + /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. + /// + @discardableResult + public func setImage( + with provider: ImageDataProvider?, + placeholder: Placeholder? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + return setImage( + with: provider.map { .provider($0) }, + placeholder: placeholder, + options: options, + progressBlock: progressBlock, + completionHandler: completionHandler) + } + + // MARK: Cancelling Downloading Task + + /// Cancels the image download task of the image view if it is running. + /// Nothing will happen if the downloading has already finished. + public func cancelDownloadTask() { + imageTask?.cancel() + } + + private func needsTransition(options: KingfisherParsedOptionsInfo, cacheType: CacheType) -> Bool { + switch options.transition { + case .none: + return false + #if !os(macOS) + default: + if options.forceTransition { return true } + if cacheType == .none { return true } + return false + #endif + } + } + + private func makeTransition(image: KFCrossPlatformImage, transition: ImageTransition, done: @escaping () -> Void) { + #if !os(macOS) + // Force hiding the indicator without transition first. + UIView.transition( + with: self.base, + duration: 0.0, + options: [], + animations: { self.indicator?.stopAnimatingView() }, + completion: { _ in + var mutatingSelf = self + mutatingSelf.placeholder = nil + UIView.transition( + with: self.base, + duration: transition.duration, + options: [transition.animationOptions, .allowUserInteraction], + animations: { transition.animations?(self.base, image) }, + completion: { finished in + transition.completion?(finished) + done() + } + ) + } + ) + #else + done() + #endif + } +} + +// MARK: - Associated Object +private var taskIdentifierKey: Void? +private var indicatorKey: Void? +private var indicatorTypeKey: Void? +private var placeholderKey: Void? +private var imageTaskKey: Void? + +extension KingfisherWrapper where Base: KFCrossPlatformImageView { + + // MARK: Properties + public private(set) var taskIdentifier: Source.Identifier.Value? { + get { + let box: Box? = getAssociatedObject(base, &taskIdentifierKey) + return box?.value + } + set { + let box = newValue.map { Box($0) } + setRetainedAssociatedObject(base, &taskIdentifierKey, box) + } + } + + /// Holds which indicator type is going to be used. + /// Default is `.none`, means no indicator will be shown while downloading. + public var indicatorType: IndicatorType { + get { + return getAssociatedObject(base, &indicatorTypeKey) ?? .none + } + + set { + switch newValue { + case .none: indicator = nil + case .activity: indicator = ActivityIndicator() + case .image(let data): indicator = ImageIndicator(imageData: data) + case .custom(let anIndicator): indicator = anIndicator + } + + setRetainedAssociatedObject(base, &indicatorTypeKey, newValue) + } + } + + /// Holds any type that conforms to the protocol `Indicator`. + /// The protocol `Indicator` has a `view` property that will be shown when loading an image. + /// It will be `nil` if `indicatorType` is `.none`. + public private(set) var indicator: Indicator? { + get { + let box: Box? = getAssociatedObject(base, &indicatorKey) + return box?.value + } + + set { + // Remove previous + if let previousIndicator = indicator { + previousIndicator.view.removeFromSuperview() + } + + // Add new + if let newIndicator = newValue { + // Set default indicator layout + let view = newIndicator.view + + base.addSubview(view) + view.translatesAutoresizingMaskIntoConstraints = false + view.centerXAnchor.constraint( + equalTo: base.centerXAnchor, constant: newIndicator.centerOffset.x).isActive = true + view.centerYAnchor.constraint( + equalTo: base.centerYAnchor, constant: newIndicator.centerOffset.y).isActive = true + + switch newIndicator.sizeStrategy(in: base) { + case .intrinsicSize: + break + case .full: + view.heightAnchor.constraint(equalTo: base.heightAnchor, constant: 0).isActive = true + view.widthAnchor.constraint(equalTo: base.widthAnchor, constant: 0).isActive = true + case .size(let size): + view.heightAnchor.constraint(equalToConstant: size.height).isActive = true + view.widthAnchor.constraint(equalToConstant: size.width).isActive = true + } + + newIndicator.view.isHidden = true + } + + // Save in associated object + // Wrap newValue with Box to workaround an issue that Swift does not recognize + // and casting protocol for associate object correctly. https://github.com/onevcat/Kingfisher/issues/872 + setRetainedAssociatedObject(base, &indicatorKey, newValue.map(Box.init)) + } + } + + private var imageTask: DownloadTask? { + get { return getAssociatedObject(base, &imageTaskKey) } + set { setRetainedAssociatedObject(base, &imageTaskKey, newValue)} + } + + /// Represents the `Placeholder` used for this image view. A `Placeholder` will be shown in the view while + /// it is downloading an image. + public private(set) var placeholder: Placeholder? { + get { return getAssociatedObject(base, &placeholderKey) } + set { + if let previousPlaceholder = placeholder { + previousPlaceholder.remove(from: base) + } + + if let newPlaceholder = newValue { + newPlaceholder.add(to: base) + } else { + base.image = nil + } + setRetainedAssociatedObject(base, &placeholderKey, newValue) + } + } +} + + +@objc extension KFCrossPlatformImageView { + func shouldPreloadAllAnimation() -> Bool { return true } +} + +extension KingfisherWrapper where Base: KFCrossPlatformImageView { + /// Gets the image URL bound to this image view. + @available(*, deprecated, message: "Use `taskIdentifier` instead to identify a setting task.") + public private(set) var webURL: URL? { + get { return nil } + set { } + } +} + +#endif diff --git a/!main project/Pods/Kingfisher/Sources/Extensions/NSButton+Kingfisher.swift b/!main project/Pods/Kingfisher/Sources/Extensions/NSButton+Kingfisher.swift new file mode 100644 index 0000000..79b9029 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Extensions/NSButton+Kingfisher.swift @@ -0,0 +1,355 @@ +// +// NSButton+Kingfisher.swift +// Kingfisher +// +// Created by Jie Zhang on 14/04/2016. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) + +import AppKit + +extension KingfisherWrapper where Base: NSButton { + + // MARK: Setting Image + + /// Sets an image to the button with a source. + /// + /// - Parameters: + /// - source: The `Source` object contains information about how to get the image. + /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. + /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. + /// - completionHandler: Called when the image retrieved and set finished. + /// - Returns: A task represents the image downloading. + /// + /// - Note: + /// Internally, this method will use `KingfisherManager` to get the requested source. + /// Since this method will perform UI changes, you must call it from the main thread. + /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. + /// + @discardableResult + public func setImage( + with source: Source?, + placeholder: KFCrossPlatformImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + var mutatingSelf = self + guard let source = source else { + base.image = placeholder + mutatingSelf.taskIdentifier = nil + completionHandler?(.failure(KingfisherError.imageSettingError(reason: .emptySource))) + return nil + } + + var options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions + (options ?? .empty)) + if !options.keepCurrentImageWhileLoading { + base.image = placeholder + } + + let issuedIdentifier = Source.Identifier.next() + mutatingSelf.taskIdentifier = issuedIdentifier + + if let block = progressBlock { + options.onDataReceived = (options.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)] + } + + if let provider = ImageProgressiveProvider(options, refresh: { image in + self.base.image = image + }) { + options.onDataReceived = (options.onDataReceived ?? []) + [provider] + } + + options.onDataReceived?.forEach { + $0.onShouldApply = { issuedIdentifier == self.taskIdentifier } + } + + let task = KingfisherManager.shared.retrieveImage( + with: source, + options: options, + downloadTaskUpdated: { mutatingSelf.imageTask = $0 }, + completionHandler: { result in + CallbackQueue.mainCurrentOrAsync.execute { + guard issuedIdentifier == self.taskIdentifier else { + let reason: KingfisherError.ImageSettingErrorReason + do { + let value = try result.get() + reason = .notCurrentSourceTask(result: value, error: nil, source: source) + } catch { + reason = .notCurrentSourceTask(result: nil, error: error, source: source) + } + let error = KingfisherError.imageSettingError(reason: reason) + completionHandler?(.failure(error)) + return + } + + mutatingSelf.imageTask = nil + mutatingSelf.taskIdentifier = nil + + switch result { + case .success(let value): + self.base.image = value.image + completionHandler?(result) + + case .failure: + if let image = options.onFailureImage { + self.base.image = image + } + completionHandler?(result) + } + } + } + ) + + mutatingSelf.imageTask = task + return task + } + + /// Sets an image to the button with a requested resource. + /// + /// - Parameters: + /// - resource: The `Resource` object contains information about the resource. + /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. + /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. + /// - completionHandler: Called when the image retrieved and set finished. + /// - Returns: A task represents the image downloading. + /// + /// - Note: + /// Internally, this method will use `KingfisherManager` to get the requested resource, from either cache + /// or network. Since this method will perform UI changes, you must call it from the main thread. + /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. + /// + @discardableResult + public func setImage( + with resource: Resource?, + placeholder: KFCrossPlatformImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + return setImage( + with: resource?.convertToSource(), + placeholder: placeholder, + options: options, + progressBlock: progressBlock, + completionHandler: completionHandler) + } + + // MARK: Cancelling Downloading Task + + /// Cancels the image download task of the button if it is running. + /// Nothing will happen if the downloading has already finished. + public func cancelImageDownloadTask() { + imageTask?.cancel() + } + + // MARK: Setting Alternate Image + + @discardableResult + public func setAlternateImage( + with source: Source?, + placeholder: KFCrossPlatformImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + var mutatingSelf = self + guard let source = source else { + base.alternateImage = placeholder + mutatingSelf.alternateTaskIdentifier = nil + completionHandler?(.failure(KingfisherError.imageSettingError(reason: .emptySource))) + return nil + } + + var options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions + (options ?? .empty)) + if !options.keepCurrentImageWhileLoading { + base.alternateImage = placeholder + } + + let issuedIdentifier = Source.Identifier.next() + mutatingSelf.alternateTaskIdentifier = issuedIdentifier + + if let block = progressBlock { + options.onDataReceived = (options.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)] + } + + if let provider = ImageProgressiveProvider(options, refresh: { image in + self.base.alternateImage = image + }) { + options.onDataReceived = (options.onDataReceived ?? []) + [provider] + } + + options.onDataReceived?.forEach { + $0.onShouldApply = { issuedIdentifier == self.alternateTaskIdentifier } + } + + let task = KingfisherManager.shared.retrieveImage( + with: source, + options: options, + downloadTaskUpdated: { mutatingSelf.alternateImageTask = $0 }, + completionHandler: { result in + CallbackQueue.mainCurrentOrAsync.execute { + guard issuedIdentifier == self.alternateTaskIdentifier else { + let reason: KingfisherError.ImageSettingErrorReason + do { + let value = try result.get() + reason = .notCurrentSourceTask(result: value, error: nil, source: source) + } catch { + reason = .notCurrentSourceTask(result: nil, error: error, source: source) + } + let error = KingfisherError.imageSettingError(reason: reason) + completionHandler?(.failure(error)) + return + } + + mutatingSelf.alternateImageTask = nil + mutatingSelf.alternateTaskIdentifier = nil + + switch result { + case .success(let value): + self.base.alternateImage = value.image + completionHandler?(result) + + case .failure: + if let image = options.onFailureImage { + self.base.alternateImage = image + } + completionHandler?(result) + } + } + } + ) + + mutatingSelf.alternateImageTask = task + return task + } + + /// Sets an alternate image to the button with a requested resource. + /// + /// - Parameters: + /// - resource: The `Resource` object contains information about the resource. + /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. + /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. + /// - completionHandler: Called when the image retrieved and set finished. + /// - Returns: A task represents the image downloading. + /// + /// - Note: + /// Internally, this method will use `KingfisherManager` to get the requested resource, from either cache + /// or network. Since this method will perform UI changes, you must call it from the main thread. + /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. + /// + @discardableResult + public func setAlternateImage( + with resource: Resource?, + placeholder: KFCrossPlatformImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + return setAlternateImage( + with: resource?.convertToSource(), + placeholder: placeholder, + options: options, + progressBlock: progressBlock, + completionHandler: completionHandler) + } + + // MARK: Cancelling Alternate Image Downloading Task + + /// Cancels the alternate image download task of the button if it is running. + /// Nothing will happen if the downloading has already finished. + public func cancelAlternateImageDownloadTask() { + alternateImageTask?.cancel() + } +} + + +// MARK: - Associated Object +private var taskIdentifierKey: Void? +private var imageTaskKey: Void? + +private var alternateTaskIdentifierKey: Void? +private var alternateImageTaskKey: Void? + +extension KingfisherWrapper where Base: NSButton { + + // MARK: Properties + + public private(set) var taskIdentifier: Source.Identifier.Value? { + get { + let box: Box? = getAssociatedObject(base, &taskIdentifierKey) + return box?.value + } + set { + let box = newValue.map { Box($0) } + setRetainedAssociatedObject(base, &taskIdentifierKey, box) + } + } + + private var imageTask: DownloadTask? { + get { return getAssociatedObject(base, &imageTaskKey) } + set { setRetainedAssociatedObject(base, &imageTaskKey, newValue)} + } + + public private(set) var alternateTaskIdentifier: Source.Identifier.Value? { + get { + let box: Box? = getAssociatedObject(base, &alternateTaskIdentifierKey) + return box?.value + } + set { + let box = newValue.map { Box($0) } + setRetainedAssociatedObject(base, &alternateTaskIdentifierKey, box) + } + } + + private var alternateImageTask: DownloadTask? { + get { return getAssociatedObject(base, &alternateImageTaskKey) } + set { setRetainedAssociatedObject(base, &alternateImageTaskKey, newValue)} + } +} + +extension KingfisherWrapper where Base: NSButton { + + /// Gets the image URL bound to this button. + @available(*, deprecated, message: "Use `taskIdentifier` instead to identify a setting task.") + public private(set) var webURL: URL? { + get { return nil } + set { } + } + + + /// Gets the image URL bound to this button. + @available(*, deprecated, message: "Use `alternateTaskIdentifier` instead to identify a setting task.") + public private(set) var alternateWebURL: URL? { + get { return nil } + set { } + } +} + +#endif diff --git a/!main project/Pods/Kingfisher/Sources/Extensions/UIButton+Kingfisher.swift b/!main project/Pods/Kingfisher/Sources/Extensions/UIButton+Kingfisher.swift new file mode 100644 index 0000000..3fda9a3 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Extensions/UIButton+Kingfisher.swift @@ -0,0 +1,398 @@ +// +// UIButton+Kingfisher.swift +// Kingfisher +// +// Created by Wei Wang on 15/4/13. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#if !os(watchOS) + +#if canImport(UIKit) +import UIKit + +extension KingfisherWrapper where Base: UIButton { + + // MARK: Setting Image + /// Sets an image to the button for a specified state with a source. + /// + /// - Parameters: + /// - source: The `Source` object contains information about the image. + /// - state: The button state to which the image should be set. + /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. + /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. + /// - completionHandler: Called when the image retrieved and set finished. + /// - Returns: A task represents the image downloading. + /// + /// - Note: + /// Internally, this method will use `KingfisherManager` to get the requested source, from either cache + /// or network. Since this method will perform UI changes, you must call it from the main thread. + /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. + /// + @discardableResult + public func setImage( + with source: Source?, + for state: UIControl.State, + placeholder: UIImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + guard let source = source else { + base.setImage(placeholder, for: state) + setTaskIdentifier(nil, for: state) + completionHandler?(.failure(KingfisherError.imageSettingError(reason: .emptySource))) + return nil + } + + var options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions + (options ?? .empty)) + if !options.keepCurrentImageWhileLoading { + base.setImage(placeholder, for: state) + } + + var mutatingSelf = self + let issuedIdentifier = Source.Identifier.next() + setTaskIdentifier(issuedIdentifier, for: state) + + if let block = progressBlock { + options.onDataReceived = (options.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)] + } + + if let provider = ImageProgressiveProvider(options, refresh: { image in + self.base.setImage(image, for: state) + }) { + options.onDataReceived = (options.onDataReceived ?? []) + [provider] + } + + options.onDataReceived?.forEach { + $0.onShouldApply = { issuedIdentifier == self.taskIdentifier(for: state) } + } + + let task = KingfisherManager.shared.retrieveImage( + with: source, + options: options, + downloadTaskUpdated: { mutatingSelf.imageTask = $0 }, + completionHandler: { result in + CallbackQueue.mainCurrentOrAsync.execute { + guard issuedIdentifier == self.taskIdentifier(for: state) else { + let reason: KingfisherError.ImageSettingErrorReason + do { + let value = try result.get() + reason = .notCurrentSourceTask(result: value, error: nil, source: source) + } catch { + reason = .notCurrentSourceTask(result: nil, error: error, source: source) + } + let error = KingfisherError.imageSettingError(reason: reason) + completionHandler?(.failure(error)) + return + } + + mutatingSelf.imageTask = nil + mutatingSelf.setTaskIdentifier(nil, for: state) + + switch result { + case .success(let value): + self.base.setImage(value.image, for: state) + completionHandler?(result) + + case .failure: + if let image = options.onFailureImage { + self.base.setImage(image, for: state) + } + completionHandler?(result) + } + } + } + ) + + mutatingSelf.imageTask = task + return task + } + + /// Sets an image to the button for a specified state with a requested resource. + /// + /// - Parameters: + /// - resource: The `Resource` object contains information about the resource. + /// - state: The button state to which the image should be set. + /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. + /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. + /// - completionHandler: Called when the image retrieved and set finished. + /// - Returns: A task represents the image downloading. + /// + /// - Note: + /// Internally, this method will use `KingfisherManager` to get the requested resource, from either cache + /// or network. Since this method will perform UI changes, you must call it from the main thread. + /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. + /// + @discardableResult + public func setImage( + with resource: Resource?, + for state: UIControl.State, + placeholder: UIImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + return setImage( + with: resource?.convertToSource(), + for: state, + placeholder: placeholder, + options: options, + progressBlock: progressBlock, + completionHandler: completionHandler) + } + + // MARK: Cancelling Downloading Task + + /// Cancels the image download task of the button if it is running. + /// Nothing will happen if the downloading has already finished. + public func cancelImageDownloadTask() { + imageTask?.cancel() + } + + // MARK: Setting Background Image + + /// Sets a background image to the button for a specified state with a source. + /// + /// - Parameters: + /// - source: The `Source` object contains information about the image. + /// - state: The button state to which the image should be set. + /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. + /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. + /// - completionHandler: Called when the image retrieved and set finished. + /// - Returns: A task represents the image downloading. + /// + /// - Note: + /// Internally, this method will use `KingfisherManager` to get the requested source + /// Since this method will perform UI changes, you must call it from the main thread. + /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. + /// + @discardableResult + public func setBackgroundImage( + with source: Source?, + for state: UIControl.State, + placeholder: UIImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + guard let source = source else { + base.setBackgroundImage(placeholder, for: state) + setBackgroundTaskIdentifier(nil, for: state) + completionHandler?(.failure(KingfisherError.imageSettingError(reason: .emptySource))) + return nil + } + + var options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions + (options ?? .empty)) + if !options.keepCurrentImageWhileLoading { + base.setBackgroundImage(placeholder, for: state) + } + + var mutatingSelf = self + let issuedIdentifier = Source.Identifier.next() + setBackgroundTaskIdentifier(issuedIdentifier, for: state) + + if let block = progressBlock { + options.onDataReceived = (options.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)] + } + + if let provider = ImageProgressiveProvider(options, refresh: { image in + self.base.setBackgroundImage(image, for: state) + }) { + options.onDataReceived = (options.onDataReceived ?? []) + [provider] + } + + options.onDataReceived?.forEach { + $0.onShouldApply = { issuedIdentifier == self.backgroundTaskIdentifier(for: state) } + } + + let task = KingfisherManager.shared.retrieveImage( + with: source, + options: options, + downloadTaskUpdated: { mutatingSelf.backgroundImageTask = $0 }, + completionHandler: { result in + CallbackQueue.mainCurrentOrAsync.execute { + guard issuedIdentifier == self.backgroundTaskIdentifier(for: state) else { + let reason: KingfisherError.ImageSettingErrorReason + do { + let value = try result.get() + reason = .notCurrentSourceTask(result: value, error: nil, source: source) + } catch { + reason = .notCurrentSourceTask(result: nil, error: error, source: source) + } + let error = KingfisherError.imageSettingError(reason: reason) + completionHandler?(.failure(error)) + return + } + + mutatingSelf.backgroundImageTask = nil + mutatingSelf.setBackgroundTaskIdentifier(nil, for: state) + + switch result { + case .success(let value): + self.base.setBackgroundImage(value.image, for: state) + completionHandler?(result) + + case .failure: + if let image = options.onFailureImage { + self.base.setBackgroundImage(image, for: state) + } + completionHandler?(result) + } + } + } + ) + + mutatingSelf.backgroundImageTask = task + return task + } + + /// Sets a background image to the button for a specified state with a requested resource. + /// + /// - Parameters: + /// - resource: The `Resource` object contains information about the resource. + /// - state: The button state to which the image should be set. + /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. + /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. + /// - completionHandler: Called when the image retrieved and set finished. + /// - Returns: A task represents the image downloading. + /// + /// - Note: + /// Internally, this method will use `KingfisherManager` to get the requested resource, from either cache + /// or network. Since this method will perform UI changes, you must call it from the main thread. + /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. + /// + @discardableResult + public func setBackgroundImage( + with resource: Resource?, + for state: UIControl.State, + placeholder: UIImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + return setBackgroundImage( + with: resource?.convertToSource(), + for: state, + placeholder: placeholder, + options: options, + progressBlock: progressBlock, + completionHandler: completionHandler) + } + + // MARK: Cancelling Background Downloading Task + + /// Cancels the background image download task of the button if it is running. + /// Nothing will happen if the downloading has already finished. + public func cancelBackgroundImageDownloadTask() { + backgroundImageTask?.cancel() + } +} + +// MARK: - Associated Object +private var taskIdentifierKey: Void? +private var imageTaskKey: Void? + +// MARK: Properties +extension KingfisherWrapper where Base: UIButton { + + private typealias TaskIdentifier = Box<[UInt: Source.Identifier.Value]> + + public func taskIdentifier(for state: UIControl.State) -> Source.Identifier.Value? { + return taskIdentifierInfo.value[state.rawValue] + } + + private func setTaskIdentifier(_ identifier: Source.Identifier.Value?, for state: UIControl.State) { + taskIdentifierInfo.value[state.rawValue] = identifier + } + + private var taskIdentifierInfo: TaskIdentifier { + return getAssociatedObject(base, &taskIdentifierKey) ?? { + setRetainedAssociatedObject(base, &taskIdentifierKey, $0) + return $0 + } (TaskIdentifier([:])) + } + + private var imageTask: DownloadTask? { + get { return getAssociatedObject(base, &imageTaskKey) } + set { setRetainedAssociatedObject(base, &imageTaskKey, newValue)} + } +} + + +private var backgroundTaskIdentifierKey: Void? +private var backgroundImageTaskKey: Void? + +// MARK: Background Properties +extension KingfisherWrapper where Base: UIButton { + + public func backgroundTaskIdentifier(for state: UIControl.State) -> Source.Identifier.Value? { + return backgroundTaskIdentifierInfo.value[state.rawValue] + } + + private func setBackgroundTaskIdentifier(_ identifier: Source.Identifier.Value?, for state: UIControl.State) { + backgroundTaskIdentifierInfo.value[state.rawValue] = identifier + } + + private var backgroundTaskIdentifierInfo: TaskIdentifier { + return getAssociatedObject(base, &backgroundTaskIdentifierKey) ?? { + setRetainedAssociatedObject(base, &backgroundTaskIdentifierKey, $0) + return $0 + } (TaskIdentifier([:])) + } + + private var backgroundImageTask: DownloadTask? { + get { return getAssociatedObject(base, &backgroundImageTaskKey) } + mutating set { setRetainedAssociatedObject(base, &backgroundImageTaskKey, newValue) } + } +} + +extension KingfisherWrapper where Base: UIButton { + + /// Gets the image URL of this button for a specified state. + /// + /// - Parameter state: The state that uses the specified image. + /// - Returns: Current URL for image. + @available(*, deprecated, message: "Use `taskIdentifier` instead to identify a setting task.") + public func webURL(for state: UIControl.State) -> URL? { + return nil + } + + /// Gets the background image URL of this button for a specified state. + /// + /// - Parameter state: The state that uses the specified background image. + /// - Returns: Current URL for image. + @available(*, deprecated, message: "Use `backgroundTaskIdentifier` instead to identify a setting task.") + public func backgroundWebURL(for state: UIControl.State) -> URL? { + return nil + } +} +#endif + +#endif diff --git a/!main project/Pods/Kingfisher/Sources/Extensions/WKInterfaceImage+Kingfisher.swift b/!main project/Pods/Kingfisher/Sources/Extensions/WKInterfaceImage+Kingfisher.swift new file mode 100644 index 0000000..328facb --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Extensions/WKInterfaceImage+Kingfisher.swift @@ -0,0 +1,205 @@ +// +// WKInterfaceImage+Kingfisher.swift +// Kingfisher +// +// Created by Rodrigo Borges Soares on 04/05/18. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#if canImport(WatchKit) + +import WatchKit + +extension KingfisherWrapper where Base: WKInterfaceImage { + + // MARK: Setting Image + + /// Sets an image to the image view with a source. + /// + /// - Parameters: + /// - source: The `Source` object contains information about the image. + /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. + /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. + /// - completionHandler: Called when the image retrieved and set finished. + /// - Returns: A task represents the image downloading. + /// + /// - Note: + /// + /// Internally, this method will use `KingfisherManager` to get the requested source + /// Since this method will perform UI changes, you must call it from the main thread. + /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. + /// + @discardableResult + public func setImage( + with source: Source?, + placeholder: KFCrossPlatformImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + var mutatingSelf = self + guard let source = source else { + base.setImage(placeholder) + mutatingSelf.taskIdentifier = nil + completionHandler?(.failure(KingfisherError.imageSettingError(reason: .emptySource))) + return nil + } + + var options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions + (options ?? .empty)) + if !options.keepCurrentImageWhileLoading { + base.setImage(placeholder) + } + + let issuedIdentifier = Source.Identifier.next() + mutatingSelf.taskIdentifier = issuedIdentifier + + if let block = progressBlock { + options.onDataReceived = (options.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)] + } + + if let provider = ImageProgressiveProvider(options, refresh: { image in + self.base.setImage(image) + }) { + options.onDataReceived = (options.onDataReceived ?? []) + [provider] + } + + options.onDataReceived?.forEach { + $0.onShouldApply = { issuedIdentifier == self.taskIdentifier } + } + + let task = KingfisherManager.shared.retrieveImage( + with: source, + options: options, + downloadTaskUpdated: { mutatingSelf.imageTask = $0 }, + completionHandler: { result in + CallbackQueue.mainCurrentOrAsync.execute { + guard issuedIdentifier == self.taskIdentifier else { + let reason: KingfisherError.ImageSettingErrorReason + do { + let value = try result.get() + reason = .notCurrentSourceTask(result: value, error: nil, source: source) + } catch { + reason = .notCurrentSourceTask(result: nil, error: error, source: source) + } + let error = KingfisherError.imageSettingError(reason: reason) + completionHandler?(.failure(error)) + return + } + + mutatingSelf.imageTask = nil + mutatingSelf.taskIdentifier = nil + + switch result { + case .success(let value): + self.base.setImage(value.image) + completionHandler?(result) + + case .failure: + if let image = options.onFailureImage { + self.base.setImage(image) + } + completionHandler?(result) + } + } + } + ) + + mutatingSelf.imageTask = task + return task + } + + /// Sets an image to the image view with a requested resource. + /// + /// - Parameters: + /// - resource: The `Resource` object contains information about the image. + /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. + /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. + /// - completionHandler: Called when the image retrieved and set finished. + /// - Returns: A task represents the image downloading. + /// + /// - Note: + /// + /// Internally, this method will use `KingfisherManager` to get the requested resource, from either cache + /// or network. Since this method will perform UI changes, you must call it from the main thread. + /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. + /// + @discardableResult + public func setImage( + with resource: Resource?, + placeholder: KFCrossPlatformImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + return setImage( + with: resource?.convertToSource(), + placeholder: placeholder, + options: options, + progressBlock: progressBlock, + completionHandler: completionHandler) + } + + // MARK: Cancelling Image + + /// Cancel the image download task bounded to the image view if it is running. + /// Nothing will happen if the downloading has already finished. + public func cancelDownloadTask() { + imageTask?.cancel() + } +} + +private var taskIdentifierKey: Void? +private var imageTaskKey: Void? + +// MARK: Properties +extension KingfisherWrapper where Base: WKInterfaceImage { + + public private(set) var taskIdentifier: Source.Identifier.Value? { + get { + let box: Box? = getAssociatedObject(base, &taskIdentifierKey) + return box?.value + } + set { + let box = newValue.map { Box($0) } + setRetainedAssociatedObject(base, &taskIdentifierKey, box) + } + } + + private var imageTask: DownloadTask? { + get { return getAssociatedObject(base, &imageTaskKey) } + set { setRetainedAssociatedObject(base, &imageTaskKey, newValue)} + } +} + +extension KingfisherWrapper where Base: WKInterfaceImage { + /// Gets the image URL bound to this image view. + @available(*, deprecated, message: "Use `taskIdentifier` instead to identify a setting task.") + public private(set) var webURL: URL? { + get { return nil } + set { } + } +} + +#endif diff --git a/!main project/Pods/Kingfisher/Sources/General/Deprecated.swift b/!main project/Pods/Kingfisher/Sources/General/Deprecated.swift new file mode 100644 index 0000000..e0724b5 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/General/Deprecated.swift @@ -0,0 +1,681 @@ +// +// Deprecated.swift +// Kingfisher +// +// Created by onevcat on 2018/09/28. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit +#elseif canImport(UIKit) +import UIKit +#endif + +// MARK: - Deprecated +extension KingfisherWrapper where Base: KFCrossPlatformImage { + @available(*, deprecated, message: + "Will be removed soon. Pass parameters with `ImageCreatingOptions`, use `image(with:options:)` instead.") + public static func image( + data: Data, + scale: CGFloat, + preloadAllAnimationData: Bool, + onlyFirstFrame: Bool) -> KFCrossPlatformImage? + { + let options = ImageCreatingOptions( + scale: scale, + duration: 0.0, + preloadAll: preloadAllAnimationData, + onlyFirstFrame: onlyFirstFrame) + return KingfisherWrapper.image(data: data, options: options) + } + + @available(*, deprecated, message: + "Will be removed soon. Pass parameters with `ImageCreatingOptions`, use `animatedImage(with:options:)` instead.") + public static func animated( + with data: Data, + scale: CGFloat = 1.0, + duration: TimeInterval = 0.0, + preloadAll: Bool, + onlyFirstFrame: Bool = false) -> KFCrossPlatformImage? + { + let options = ImageCreatingOptions( + scale: scale, duration: duration, preloadAll: preloadAll, onlyFirstFrame: onlyFirstFrame) + return animatedImage(data: data, options: options) + } +} + +@available(*, deprecated, message: "Will be removed soon. Use `Result` based callback instead") +public typealias CompletionHandler = + ((_ image: KFCrossPlatformImage?, _ error: NSError?, _ cacheType: CacheType, _ imageURL: URL?) -> Void) + +@available(*, deprecated, message: "Will be removed soon. Use `Result` based callback instead") +public typealias ImageDownloaderCompletionHandler = + ((_ image: KFCrossPlatformImage?, _ error: NSError?, _ url: URL?, _ originalData: Data?) -> Void) + +// MARK: - Deprecated +@available(*, deprecated, message: "Will be removed soon. Use `DownloadTask` to cancel a task.") +extension RetrieveImageTask { + @available(*, deprecated, message: "RetrieveImageTask.empty will be removed soon. Use `nil` to represent a no task.") + public static let empty = RetrieveImageTask() +} + +// MARK: - Deprecated +extension KingfisherManager { + /// Get an image with resource. + /// If `.empty` is used as `options`, Kingfisher will seek the image in memory and disk first. + /// If not found, it will download the image at `resource.downloadURL` and cache it with `resource.cacheKey`. + /// These default behaviors could be adjusted by passing different options. See `KingfisherOptions` for more. + /// + /// - Parameters: + /// - resource: Resource object contains information such as `cacheKey` and `downloadURL`. + /// - options: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called every time downloaded data changed. This could be used as a progress UI. + /// - completionHandler: Called when the whole retrieving process finished. + /// - Returns: A `RetrieveImageTask` task object. You can use this object to cancel the task. + @available(*, deprecated, message: "Use `Result` based callback instead.") + @discardableResult + public func retrieveImage(with resource: Resource, + options: KingfisherOptionsInfo?, + progressBlock: DownloadProgressBlock?, + completionHandler: CompletionHandler?) -> DownloadTask? + { + return retrieveImage(with: resource, options: options, progressBlock: progressBlock) { + result in + switch result { + case .success(let value): completionHandler?(value.image, nil, value.cacheType, value.source.url) + case .failure(let error): completionHandler?(nil, error as NSError, .none, resource.downloadURL) + } + } + } +} + +// MARK: - Deprecated +extension ImageDownloader { + @available(*, deprecated, message: "Use `Result` based callback instead.") + @discardableResult + open func downloadImage(with url: URL, + retrieveImageTask: RetrieveImageTask? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: ImageDownloaderProgressBlock? = nil, + completionHandler: ImageDownloaderCompletionHandler?) -> DownloadTask? + { + return downloadImage(with: url, options: options, progressBlock: progressBlock) { + result in + switch result { + case .success(let value): completionHandler?(value.image, nil, value.url, value.originalData) + case .failure(let error): completionHandler?(nil, error as NSError, nil, nil) + } + } + } +} + +@available(*, deprecated, message: "RetrieveImageDownloadTask is removed. Use `DownloadTask` to cancel a task.") +public struct RetrieveImageDownloadTask { +} + +@available(*, deprecated, message: "RetrieveImageTask is removed. Use `DownloadTask` to cancel a task.") +public final class RetrieveImageTask { +} + +@available(*, deprecated, message: "Use `DownloadProgressBlock` instead.", renamed: "DownloadProgressBlock") +public typealias ImageDownloaderProgressBlock = DownloadProgressBlock + +#if !os(watchOS) +// MARK: - Deprecated +extension KingfisherWrapper where Base: KFCrossPlatformImageView { + @available(*, deprecated, message: "Use `Result` based callback instead.") + @discardableResult + public func setImage(with resource: Resource?, + placeholder: Placeholder? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: CompletionHandler?) -> DownloadTask? + { + return setImage(with: resource, placeholder: placeholder, options: options, progressBlock: progressBlock) { + result in + switch result { + case .success(let value): + completionHandler?(value.image, nil, value.cacheType, value.source.url) + case .failure(let error): + completionHandler?(nil, error as NSError, .none, nil) + } + } + } +} +#endif + +#if canImport(UIKit) && !os(watchOS) +// MARK: - Deprecated +extension KingfisherWrapper where Base: UIButton { + @available(*, deprecated, message: "Use `Result` based callback instead.") + @discardableResult + public func setImage( + with resource: Resource?, + for state: UIControl.State, + placeholder: UIImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: CompletionHandler?) -> DownloadTask? + { + return setImage( + with: resource, + for: state, + placeholder: placeholder, + options: options, + progressBlock: progressBlock) + { + result in + switch result { + case .success(let value): + completionHandler?(value.image, nil, value.cacheType, value.source.url) + case .failure(let error): + completionHandler?(nil, error as NSError, .none, nil) + } + } + } + + @available(*, deprecated, message: "Use `Result` based callback instead.") + @discardableResult + public func setBackgroundImage( + with resource: Resource?, + for state: UIControl.State, + placeholder: UIImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: CompletionHandler?) -> DownloadTask? + { + return setBackgroundImage( + with: resource, + for: state, + placeholder: placeholder, + options: options, + progressBlock: progressBlock) + { + result in + switch result { + case .success(let value): + completionHandler?(value.image, nil, value.cacheType, value.source.url) + case .failure(let error): + completionHandler?(nil, error as NSError, .none, nil) + } + } + } +} +#endif + +#if os(watchOS) +import WatchKit +// MARK: - Deprecated +extension KingfisherWrapper where Base: WKInterfaceImage { + @available(*, deprecated, message: "Use `Result` based callback instead.") + @discardableResult + public func setImage(_ resource: Resource?, + placeholder: KFCrossPlatformImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: CompletionHandler?) -> DownloadTask? + { + return setImage( + with: resource, + placeholder: placeholder, + options: options, + progressBlock: progressBlock) + { + result in + switch result { + case .success(let value): + completionHandler?(value.image, nil, value.cacheType, value.source.url) + case .failure(let error): + completionHandler?(nil, error as NSError, .none, nil) + } + } + } +} +#endif + +#if os(macOS) +// MARK: - Deprecated +extension KingfisherWrapper where Base: NSButton { + @discardableResult + @available(*, deprecated, message: "Use `Result` based callback instead.") + public func setImage(with resource: Resource?, + placeholder: KFCrossPlatformImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: CompletionHandler?) -> DownloadTask? + { + return setImage( + with: resource, + placeholder: placeholder, + options: options, + progressBlock: progressBlock) + { + result in + switch result { + case .success(let value): + completionHandler?(value.image, nil, value.cacheType, value.source.url) + case .failure(let error): + completionHandler?(nil, error as NSError, .none, nil) + } + } + } + + @discardableResult + @available(*, deprecated, message: "Use `Result` based callback instead.") + public func setAlternateImage(with resource: Resource?, + placeholder: KFCrossPlatformImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: CompletionHandler?) -> DownloadTask? + { + return setAlternateImage( + with: resource, + placeholder: placeholder, + options: options, + progressBlock: progressBlock) + { + result in + switch result { + case .success(let value): + completionHandler?(value.image, nil, value.cacheType, value.source.url) + case .failure(let error): + completionHandler?(nil, error as NSError, .none, nil) + } + } + } +} +#endif + +// MARK: - Deprecated +extension ImageCache { + /// The largest cache cost of memory cache. The total cost is pixel count of + /// all cached images in memory. + /// Default is unlimited. Memory cache will be purged automatically when a + /// memory warning notification is received. + @available(*, deprecated, message: "Use `memoryStorage.config.totalCostLimit` instead.", + renamed: "memoryStorage.config.totalCostLimit") + open var maxMemoryCost: Int { + get { return memoryStorage.config.totalCostLimit } + set { memoryStorage.config.totalCostLimit = newValue } + } + + /// The default DiskCachePathClosure + @available(*, deprecated, message: "Not needed anymore.") + public final class func defaultDiskCachePathClosure(path: String?, cacheName: String) -> String { + let dstPath = path ?? NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first! + return (dstPath as NSString).appendingPathComponent(cacheName) + } + + /// The default file extension appended to cached files. + @available(*, deprecated, message: "Use `diskStorage.config.pathExtension` instead.", + renamed: "diskStorage.config.pathExtension") + open var pathExtension: String? { + get { return diskStorage.config.pathExtension } + set { diskStorage.config.pathExtension = newValue } + } + + ///The disk cache location. + @available(*, deprecated, message: "Use `diskStorage.directoryURL.absoluteString` instead.", + renamed: "diskStorage.directoryURL.absoluteString") + public var diskCachePath: String { + return diskStorage.directoryURL.absoluteString + } + + /// The largest disk size can be taken for the cache. It is the total + /// allocated size of cached files in bytes. + /// Default is no limit. + @available(*, deprecated, message: "Use `diskStorage.config.sizeLimit` instead.", + renamed: "diskStorage.config.sizeLimit") + open var maxDiskCacheSize: UInt { + get { return UInt(diskStorage.config.sizeLimit) } + set { diskStorage.config.sizeLimit = newValue } + } + + @available(*, deprecated, message: "Use `diskStorage.cacheFileURL(forKey:).path` instead.", + renamed: "diskStorage.cacheFileURL(forKey:)") + open func cachePath(forComputedKey key: String) -> String { + return diskStorage.cacheFileURL(forKey: key).path + } + + /** + Get an image for a key from disk. + + - parameter key: Key for the image. + - parameter options: Options of retrieving image. If you need to retrieve an image which was + stored with a specified `ImageProcessor`, pass the processor in the option too. + + - returns: The image object if it is cached, or `nil` if there is no such key in the cache. + */ + @available(*, deprecated, + message: "Use `Result` based `retrieveImageInDiskCache(forKey:options:callbackQueue:completionHandler:)` instead.", + renamed: "retrieveImageInDiskCache(forKey:options:callbackQueue:completionHandler:)") + open func retrieveImageInDiskCache(forKey key: String, options: KingfisherOptionsInfo? = nil) -> KFCrossPlatformImage? { + let options = KingfisherParsedOptionsInfo(options ?? .empty) + let computedKey = key.computedKey(with: options.processor.identifier) + do { + if let data = try diskStorage.value(forKey: computedKey, extendingExpiration: options.diskCacheAccessExtendingExpiration) { + return options.cacheSerializer.image(with: data, options: options) + } + } catch {} + return nil + } + + @available(*, deprecated, + message: "Use `Result` based `retrieveImage(forKey:options:callbackQueue:completionHandler:)` instead.", + renamed: "retrieveImage(forKey:options:callbackQueue:completionHandler:)") + open func retrieveImage(forKey key: String, + options: KingfisherOptionsInfo?, + completionHandler: ((KFCrossPlatformImage?, CacheType) -> Void)?) + { + retrieveImage( + forKey: key, + options: options, + callbackQueue: .dispatch((options ?? .empty).callbackDispatchQueue)) + { + result in + do { + let value = try result.get() + completionHandler?(value.image, value.cacheType) + } catch { + completionHandler?(nil, .none) + } + } + } + + /// The longest time duration in second of the cache being stored in disk. + /// Default is 1 week (60 * 60 * 24 * 7 seconds). + /// Setting this to a negative value will make the disk cache never expiring. + @available(*, deprecated, message: "Deprecated. Use `diskStorage.config.expiration` instead") + open var maxCachePeriodInSecond: TimeInterval { + get { return diskStorage.config.expiration.timeInterval } + set { diskStorage.config.expiration = newValue < 0 ? .never : .seconds(newValue) } + } + + @available(*, deprecated, message: "Use `Result` based callback instead.") + open func store(_ image: KFCrossPlatformImage, + original: Data? = nil, + forKey key: String, + processorIdentifier identifier: String = "", + cacheSerializer serializer: CacheSerializer = DefaultCacheSerializer.default, + toDisk: Bool = true, + completionHandler: (() -> Void)?) + { + store( + image, + original: original, + forKey: key, + processorIdentifier: identifier, + cacheSerializer: serializer, + toDisk: toDisk) + { + _ in + completionHandler?() + } + } + + @available(*, deprecated, message: "Use the `Result`-based `calculateDiskStorageSize` instead.") + open func calculateDiskCacheSize(completion handler: @escaping ((_ size: UInt) -> Void)) { + calculateDiskStorageSize { result in + let size: UInt? = try? result.get() + handler(size ?? 0) + } + } +} + +// MARK: - Deprecated +extension Collection where Iterator.Element == KingfisherOptionsInfoItem { + /// The queue of callbacks should happen from Kingfisher. + @available(*, deprecated, message: "Use `callbackQueue` instead.", renamed: "callbackQueue") + public var callbackDispatchQueue: DispatchQueue { + return KingfisherParsedOptionsInfo(Array(self)).callbackQueue.queue + } +} + +/// Error domain of Kingfisher +@available(*, deprecated, message: "Use `KingfisherError.domain` instead.", renamed: "KingfisherError.domain") +public let KingfisherErrorDomain = "com.onevcat.Kingfisher.Error" + +/// Key will be used in the `userInfo` of `.invalidStatusCode` +@available(*, unavailable, +message: "Use `.invalidHTTPStatusCode` or `isInvalidResponseStatusCode` of `KingfisherError` instead for the status code.") +public let KingfisherErrorStatusCodeKey = "statusCode" + +// MARK: - Deprecated +extension Collection where Iterator.Element == KingfisherOptionsInfoItem { + /// The target `ImageCache` which is used. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `targetCache` instead.") + public var targetCache: ImageCache? { + return KingfisherParsedOptionsInfo(Array(self)).targetCache + } + + /// The original `ImageCache` which is used. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `originalCache` instead.") + public var originalCache: ImageCache? { + return KingfisherParsedOptionsInfo(Array(self)).originalCache + } + + /// The `ImageDownloader` which is specified. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `downloader` instead.") + public var downloader: ImageDownloader? { + return KingfisherParsedOptionsInfo(Array(self)).downloader + } + + /// Member for animation transition when using UIImageView. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `transition` instead.") + public var transition: ImageTransition { + return KingfisherParsedOptionsInfo(Array(self)).transition + } + + /// A `Float` value set as the priority of image download task. The value for it should be + /// between 0.0~1.0. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `downloadPriority` instead.") + public var downloadPriority: Float { + return KingfisherParsedOptionsInfo(Array(self)).downloadPriority + } + + /// Whether an image will be always downloaded again or not. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `forceRefresh` instead.") + public var forceRefresh: Bool { + return KingfisherParsedOptionsInfo(Array(self)).forceRefresh + } + + /// Whether an image should be got only from memory cache or download. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `fromMemoryCacheOrRefresh` instead.") + public var fromMemoryCacheOrRefresh: Bool { + return KingfisherParsedOptionsInfo(Array(self)).fromMemoryCacheOrRefresh + } + + /// Whether the transition should always happen or not. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `forceTransition` instead.") + public var forceTransition: Bool { + return KingfisherParsedOptionsInfo(Array(self)).forceTransition + } + + /// Whether cache the image only in memory or not. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `cacheMemoryOnly` instead.") + public var cacheMemoryOnly: Bool { + return KingfisherParsedOptionsInfo(Array(self)).cacheMemoryOnly + } + + /// Whether the caching operation will be waited or not. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `waitForCache` instead.") + public var waitForCache: Bool { + return KingfisherParsedOptionsInfo(Array(self)).waitForCache + } + + /// Whether only load the images from cache or not. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `onlyFromCache` instead.") + public var onlyFromCache: Bool { + return KingfisherParsedOptionsInfo(Array(self)).onlyFromCache + } + + /// Whether the image should be decoded in background or not. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `backgroundDecode` instead.") + public var backgroundDecode: Bool { + return KingfisherParsedOptionsInfo(Array(self)).backgroundDecode + } + + /// Whether the image data should be all loaded at once if it is an animated image. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `preloadAllAnimationData` instead.") + public var preloadAllAnimationData: Bool { + return KingfisherParsedOptionsInfo(Array(self)).preloadAllAnimationData + } + + /// The `CallbackQueue` on which completion handler should be invoked. + /// If not set in the options, `.mainCurrentOrAsync` will be used. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `callbackQueue` instead.") + public var callbackQueue: CallbackQueue { + return KingfisherParsedOptionsInfo(Array(self)).callbackQueue + } + + /// The scale factor which should be used for the image. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `scaleFactor` instead.") + public var scaleFactor: CGFloat { + return KingfisherParsedOptionsInfo(Array(self)).scaleFactor + } + + /// The `ImageDownloadRequestModifier` will be used before sending a download request. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `requestModifier` instead.") + public var modifier: ImageDownloadRequestModifier? { + return KingfisherParsedOptionsInfo(Array(self)).requestModifier + } + + /// `ImageProcessor` for processing when the downloading finishes. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `processor` instead.") + public var processor: ImageProcessor { + return KingfisherParsedOptionsInfo(Array(self)).processor + } + + /// `ImageModifier` for modifying right before the image is displayed. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `imageModifier` instead.") + public var imageModifier: ImageModifier? { + return KingfisherParsedOptionsInfo(Array(self)).imageModifier + } + + /// `CacheSerializer` to convert image to data for storing in cache. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `cacheSerializer` instead.") + public var cacheSerializer: CacheSerializer { + return KingfisherParsedOptionsInfo(Array(self)).cacheSerializer + } + + /// Keep the existing image while setting another image to an image view. + /// Or the placeholder will be used while downloading. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `keepCurrentImageWhileLoading` instead.") + public var keepCurrentImageWhileLoading: Bool { + return KingfisherParsedOptionsInfo(Array(self)).keepCurrentImageWhileLoading + } + + /// Whether the options contains `.onlyLoadFirstFrame`. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `onlyLoadFirstFrame` instead.") + public var onlyLoadFirstFrame: Bool { + return KingfisherParsedOptionsInfo(Array(self)).onlyLoadFirstFrame + } + + /// Whether the options contains `.cacheOriginalImage`. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `cacheOriginalImage` instead.") + public var cacheOriginalImage: Bool { + return KingfisherParsedOptionsInfo(Array(self)).cacheOriginalImage + } + + /// The image which should be used when download image request fails. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `onFailureImage` instead.") + public var onFailureImage: Optional { + return KingfisherParsedOptionsInfo(Array(self)).onFailureImage + } + + /// Whether the `ImagePrefetcher` should load images to memory in an aggressive way or not. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `alsoPrefetchToMemory` instead.") + public var alsoPrefetchToMemory: Bool { + return KingfisherParsedOptionsInfo(Array(self)).alsoPrefetchToMemory + } + + /// Whether the disk storage file loading should happen in a synchronous behavior or not. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `loadDiskFileSynchronously` instead.") + public var loadDiskFileSynchronously: Bool { + return KingfisherParsedOptionsInfo(Array(self)).loadDiskFileSynchronously + } +} + +/// The default modifier. +/// It does nothing and returns the image as is. +@available(*, deprecated, message: "Use `nil` in KingfisherOptionsInfo to indicate no modifier.") +public struct DefaultImageModifier: ImageModifier { + + /// A default `DefaultImageModifier` which can be used everywhere. + public static let `default` = DefaultImageModifier() + private init() {} + + /// Modifies an input `Image`. See `ImageModifier` protocol for more. + public func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage { return image } +} + + +#if os(macOS) +@available(*, deprecated, message: "Use `KFCrossPlatformImage` instead.") +public typealias Image = KFCrossPlatformImage +@available(*, deprecated, message: "Use `KFCrossPlatformView` instead.") +public typealias View = KFCrossPlatformView +@available(*, deprecated, message: "Use `KFCrossPlatformColor` instead.") +public typealias Color = KFCrossPlatformColor +@available(*, deprecated, message: "Use `KFCrossPlatformImageView` instead.") +public typealias ImageView = KFCrossPlatformImageView +@available(*, deprecated, message: "Use `KFCrossPlatformButton` instead.") +public typealias Button = KFCrossPlatformButton +#else +@available(*, deprecated, message: "Use `KFCrossPlatformImage` instead.") +public typealias Image = KFCrossPlatformImage +@available(*, deprecated, message: "Use `KFCrossPlatformColor` instead.") +public typealias Color = KFCrossPlatformColor + #if !os(watchOS) + @available(*, deprecated, message: "Use `KFCrossPlatformImageView` instead.") + public typealias ImageView = KFCrossPlatformImageView + @available(*, deprecated, message: "Use `KFCrossPlatformView` instead.") + public typealias View = KFCrossPlatformView + @available(*, deprecated, message: "Use `KFCrossPlatformButton` instead.") + public typealias Button = KFCrossPlatformButton + #endif +#endif diff --git a/!main project/Pods/Kingfisher/Sources/General/ImageSource/ImageDataProvider.swift b/!main project/Pods/Kingfisher/Sources/General/ImageSource/ImageDataProvider.swift new file mode 100644 index 0000000..f303107 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/General/ImageSource/ImageDataProvider.swift @@ -0,0 +1,157 @@ +// +// ImageDataProvider.swift +// Kingfisher +// +// Created by onevcat on 2018/11/13. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +/// Represents a data provider to provide image data to Kingfisher when setting with +/// `Source.provider` source. Compared to `Source.network` member, it gives a chance +/// to load some image data in your own way, as long as you can provide the data +/// representation for the image. +public protocol ImageDataProvider { + + /// The key used in cache. + var cacheKey: String { get } + + /// Provides the data which represents image. Kingfisher uses the data you pass in the + /// handler to process images and caches it for later use. + /// + /// - Parameter handler: The handler you should call when you prepared your data. + /// If the data is loaded successfully, call the handler with + /// a `.success` with the data associated. Otherwise, call it + /// with a `.failure` and pass the error. + /// + /// - Note: + /// If the `handler` is called with a `.failure` with error, a `dataProviderError` of + /// `ImageSettingErrorReason` will be finally thrown out to you as the `KingfisherError` + /// from the framework. + func data(handler: @escaping (Result) -> Void) + + /// The content URL represents this provider, if exists. + var contentURL: URL? { get } +} + +public extension ImageDataProvider { + var contentURL: URL? { return nil } +} + +/// Represents an image data provider for loading from a local file URL on disk. +/// Uses this type for adding a disk image to Kingfisher. Compared to loading it +/// directly, you can get benefit of using Kingfisher's extension methods, as well +/// as applying `ImageProcessor`s and storing the image to `ImageCache` of Kingfisher. +public struct LocalFileImageDataProvider: ImageDataProvider { + + // MARK: Public Properties + + /// The file URL from which the image be loaded. + public let fileURL: URL + + // MARK: Initializers + + /// Creates an image data provider by supplying the target local file URL. + /// + /// - Parameters: + /// - fileURL: The file URL from which the image be loaded. + /// - cacheKey: The key is used for caching the image data. By default, + /// the `absoluteString` of `fileURL` is used. + public init(fileURL: URL, cacheKey: String? = nil) { + self.fileURL = fileURL + self.cacheKey = cacheKey ?? fileURL.absoluteString + } + + // MARK: Protocol Conforming + + /// The key used in cache. + public var cacheKey: String + + public func data(handler: (Result) -> Void) { + handler(Result(catching: { try Data(contentsOf: fileURL) })) + } + + /// The URL of the local file on the disk. + public var contentURL: URL? { + return fileURL + } +} + +/// Represents an image data provider for loading image from a given Base64 encoded string. +public struct Base64ImageDataProvider: ImageDataProvider { + + // MARK: Public Properties + /// The encoded Base64 string for the image. + public let base64String: String + + // MARK: Initializers + + /// Creates an image data provider by supplying the Base64 encoded string. + /// + /// - Parameters: + /// - base64String: The Base64 encoded string for an image. + /// - cacheKey: The key is used for caching the image data. You need a different key for any different image. + public init(base64String: String, cacheKey: String) { + self.base64String = base64String + self.cacheKey = cacheKey + } + + // MARK: Protocol Conforming + + /// The key used in cache. + public var cacheKey: String + + public func data(handler: (Result) -> Void) { + let data = Data(base64Encoded: base64String)! + handler(.success(data)) + } +} + +/// Represents an image data provider for a raw data object. +public struct RawImageDataProvider: ImageDataProvider { + + // MARK: Public Properties + + /// The raw data object to provide to Kingfisher image loader. + public let data: Data + + // MARK: Initializers + + /// Creates an image data provider by the given raw `data` value and a `cacheKey` be used in Kingfisher cache. + /// + /// - Parameters: + /// - data: The raw data reprensents an image. + /// - cacheKey: The key is used for caching the image data. You need a different key for any different image. + public init(data: Data, cacheKey: String) { + self.data = data + self.cacheKey = cacheKey + } + + // MARK: Protocol Conforming + + /// The key used in cache. + public var cacheKey: String + + public func data(handler: @escaping (Result) -> Void) { + handler(.success(data)) + } +} diff --git a/!main project/Pods/Kingfisher/Sources/General/ImageSource/Resource.swift b/!main project/Pods/Kingfisher/Sources/General/ImageSource/Resource.swift new file mode 100644 index 0000000..8514384 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/General/ImageSource/Resource.swift @@ -0,0 +1,86 @@ +// +// Resource.swift +// Kingfisher +// +// Created by Wei Wang on 15/4/6. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +/// Represents an image resource at a certain url and a given cache key. +/// Kingfisher will use a `Resource` to download a resource from network and cache it with the cache key when +/// using `Source.network` as its image setting source. +public protocol Resource { + + /// The key used in cache. + var cacheKey: String { get } + + /// The target image URL. + var downloadURL: URL { get } +} + +extension Resource { + + /// Converts `self` to a valid `Source` based on its `downloadURL` scheme. A `.provider` with + /// `LocalFileImageDataProvider` associated will be returned if the URL points to a local file. Otherwise, + /// `.network` is returned. + public func convertToSource() -> Source { + return downloadURL.isFileURL ? + .provider(LocalFileImageDataProvider(fileURL: downloadURL, cacheKey: cacheKey)) : + .network(self) + } +} + +/// ImageResource is a simple combination of `downloadURL` and `cacheKey`. +/// When passed to image view set methods, Kingfisher will try to download the target +/// image from the `downloadURL`, and then store it with the `cacheKey` as the key in cache. +public struct ImageResource: Resource { + + // MARK: - Initializers + + /// Creates an image resource. + /// + /// - Parameters: + /// - downloadURL: The target image URL from where the image can be downloaded. + /// - cacheKey: The cache key. If `nil`, Kingfisher will use the `absoluteString` of `downloadURL` as the key. + /// Default is `nil`. + public init(downloadURL: URL, cacheKey: String? = nil) { + self.downloadURL = downloadURL + self.cacheKey = cacheKey ?? downloadURL.absoluteString + } + + // MARK: Protocol Conforming + + /// The key used in cache. + public let cacheKey: String + + /// The target image URL. + public let downloadURL: URL +} + +/// URL conforms to `Resource` in Kingfisher. +/// The `absoluteString` of this URL is used as `cacheKey`. And the URL itself will be used as `downloadURL`. +/// If you need customize the url and/or cache key, use `ImageResource` instead. +extension URL: Resource { + public var cacheKey: String { return absoluteString } + public var downloadURL: URL { return self } +} diff --git a/!main project/Pods/Kingfisher/Sources/General/ImageSource/Source.swift b/!main project/Pods/Kingfisher/Sources/General/ImageSource/Source.swift new file mode 100644 index 0000000..79680ab --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/General/ImageSource/Source.swift @@ -0,0 +1,97 @@ +// +// Source.swift +// Kingfisher +// +// Created by onevcat on 2018/11/17. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +/// Represents an image setting source for Kingfisher methods. +/// +/// A `Source` value indicates the way how the target image can be retrieved and cached. +/// +/// - network: The target image should be got from network remotely. The associated `Resource` +/// value defines detail information like image URL and cache key. +/// - provider: The target image should be provided in a data format. Normally, it can be an image +/// from local storage or in any other encoding format (like Base64). +public enum Source { + + /// Represents the source task identifier when setting an image to a view with extension methods. + public enum Identifier { + + /// The underlying value type of source identifier. + public typealias Value = UInt + static var current: Value = 0 + static func next() -> Value { + current += 1 + return current + } + } + + // MARK: Member Cases + + /// The target image should be got from network remotely. The associated `Resource` + /// value defines detail information like image URL and cache key. + case network(Resource) + + /// The target image should be provided in a data format. Normally, it can be an image + /// from local storage or in any other encoding format (like Base64). + case provider(ImageDataProvider) + + // MARK: Getting Properties + + /// The cache key defined for this source value. + public var cacheKey: String { + switch self { + case .network(let resource): return resource.cacheKey + case .provider(let provider): return provider.cacheKey + } + } + + /// The URL defined for this source value. + /// + /// For a `.network` source, it is the `downloadURL` of associated `Resource` instance. + /// For a `.provider` value, it is always `nil`. + public var url: URL? { + switch self { + case .network(let resource): return resource.downloadURL + case .provider(let provider): return provider.contentURL + } + } +} + +extension Source { + var asResource: Resource? { + guard case .network(let resource) = self else { + return nil + } + return resource + } + + var asProvider: ImageDataProvider? { + guard case .provider(let provider) = self else { + return nil + } + return provider + } +} diff --git a/!main project/Pods/Kingfisher/Sources/General/Kingfisher.swift b/!main project/Pods/Kingfisher/Sources/General/Kingfisher.swift new file mode 100644 index 0000000..8143531 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/General/Kingfisher.swift @@ -0,0 +1,89 @@ +// +// Kingfisher.swift +// Kingfisher +// +// Created by Wei Wang on 16/9/14. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation +import ImageIO + +#if os(macOS) +import AppKit +public typealias KFCrossPlatformImage = NSImage +public typealias KFCrossPlatformView = NSView +public typealias KFCrossPlatformColor = NSColor +public typealias KFCrossPlatformImageView = NSImageView +public typealias KFCrossPlatformButton = NSButton +#else +import UIKit +public typealias KFCrossPlatformImage = UIImage +public typealias KFCrossPlatformColor = UIColor +#if !os(watchOS) +public typealias KFCrossPlatformImageView = UIImageView +public typealias KFCrossPlatformView = UIView +public typealias KFCrossPlatformButton = UIButton +#else +import WatchKit +#endif +#endif + +/// Wrapper for Kingfisher compatible types. This type provides an extension point for +/// connivence methods in Kingfisher. +public struct KingfisherWrapper { + public let base: Base + public init(_ base: Base) { + self.base = base + } +} + +/// Represents an object type that is compatible with Kingfisher. You can use `kf` property to get a +/// value in the namespace of Kingfisher. +public protocol KingfisherCompatible: AnyObject { } + +/// Represents a value type that is compatible with Kingfisher. You can use `kf` property to get a +/// value in the namespace of Kingfisher. +public protocol KingfisherCompatibleValue {} + +extension KingfisherCompatible { + /// Gets a namespace holder for Kingfisher compatible types. + public var kf: KingfisherWrapper { + get { return KingfisherWrapper(self) } + set { } + } +} + +extension KingfisherCompatibleValue { + /// Gets a namespace holder for Kingfisher compatible types. + public var kf: KingfisherWrapper { + get { return KingfisherWrapper(self) } + set { } + } +} + +extension KFCrossPlatformImage: KingfisherCompatible { } +#if !os(watchOS) +extension KFCrossPlatformImageView: KingfisherCompatible { } +extension KFCrossPlatformButton: KingfisherCompatible { } +#else +extension WKInterfaceImage: KingfisherCompatible { } +#endif diff --git a/!main project/Pods/Kingfisher/Sources/General/KingfisherError.swift b/!main project/Pods/Kingfisher/Sources/General/KingfisherError.swift new file mode 100644 index 0000000..a3262f4 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/General/KingfisherError.swift @@ -0,0 +1,437 @@ +// +// KingfisherError.swift +// Kingfisher +// +// Created by onevcat on 2018/09/26. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +extension Never {} + +/// Represents all the errors which can happen in Kingfisher framework. +/// Kingfisher related methods always throw a `KingfisherError` or invoke the callback with `KingfisherError` +/// as its error type. To handle errors from Kingfisher, you switch over the error to get a reason catalog, +/// then switch over the reason to know error detail. +public enum KingfisherError: Error { + + // MARK: Error Reason Types + + /// Represents the error reason during networking request phase. + /// + /// - emptyRequest: The request is empty. Code 1001. + /// - invalidURL: The URL of request is invalid. Code 1002. + /// - taskCancelled: The downloading task is cancelled by user. Code 1003. + public enum RequestErrorReason { + + /// The request is empty. Code 1001. + case emptyRequest + + /// The URL of request is invalid. Code 1002. + /// - request: The request is tend to be sent but its URL is invalid. + case invalidURL(request: URLRequest) + + /// The downloading task is cancelled by user. Code 1003. + /// - task: The session data task which is cancelled. + /// - token: The cancel token which is used for cancelling the task. + case taskCancelled(task: SessionDataTask, token: SessionDataTask.CancelToken) + } + + /// Represents the error reason during networking response phase. + /// + /// - invalidURLResponse: The response is not a valid URL response. Code 2001. + /// - invalidHTTPStatusCode: The response contains an invalid HTTP status code. Code 2002. + /// - URLSessionError: An error happens in the system URL session. Code 2003. + /// - dataModifyingFailed: Data modifying fails on returning a valid data. Code 2004. + /// - noURLResponse: The task is done but no URL response found. Code 2005. + public enum ResponseErrorReason { + + /// The response is not a valid URL response. Code 2001. + /// - response: The received invalid URL response. + /// The response is expected to be an HTTP response, but it is not. + case invalidURLResponse(response: URLResponse) + + /// The response contains an invalid HTTP status code. Code 2002. + /// - Note: + /// By default, status code 200..<400 is recognized as valid. You can override + /// this behavior by conforming to the `ImageDownloaderDelegate`. + /// - response: The received response. + case invalidHTTPStatusCode(response: HTTPURLResponse) + + /// An error happens in the system URL session. Code 2003. + /// - error: The underlying URLSession error object. + case URLSessionError(error: Error) + + /// Data modifying fails on returning a valid data. Code 2004. + /// - task: The failed task. + case dataModifyingFailed(task: SessionDataTask) + + /// The task is done but no URL response found. Code 2005. + /// - task: The failed task. + case noURLResponse(task: SessionDataTask) + } + + /// Represents the error reason during Kingfisher caching system. + /// + /// - fileEnumeratorCreationFailed: Cannot create a file enumerator for a certain disk URL. Code 3001. + /// - invalidFileEnumeratorContent: Cannot get correct file contents from a file enumerator. Code 3002. + /// - invalidURLResource: The file at target URL exists, but its URL resource is unavailable. Code 3003. + /// - cannotLoadDataFromDisk: The file at target URL exists, but the data cannot be loaded from it. Code 3004. + /// - cannotCreateDirectory: Cannot create a folder at a given path. Code 3005. + /// - imageNotExisting: The requested image does not exist in cache. Code 3006. + /// - cannotConvertToData: Cannot convert an object to data for storing. Code 3007. + /// - cannotSerializeImage: Cannot serialize an image to data for storing. Code 3008. + /// - cannotCreateCacheFile: Cannot create the cache file at a certain fileURL under a key. Code 3009. + /// - cannotSetCacheFileAttribute: Cannot set file attributes to a cached file. Code 3010. + public enum CacheErrorReason { + + /// Cannot create a file enumerator for a certain disk URL. Code 3001. + /// - url: The target disk URL from which the file enumerator should be created. + case fileEnumeratorCreationFailed(url: URL) + + /// Cannot get correct file contents from a file enumerator. Code 3002. + /// - url: The target disk URL from which the content of a file enumerator should be got. + case invalidFileEnumeratorContent(url: URL) + + /// The file at target URL exists, but its URL resource is unavailable. Code 3003. + /// - error: The underlying error thrown by file manager. + /// - key: The key used to getting the resource from cache. + /// - url: The disk URL where the target cached file exists. + case invalidURLResource(error: Error, key: String, url: URL) + + /// The file at target URL exists, but the data cannot be loaded from it. Code 3004. + /// - url: The disk URL where the target cached file exists. + /// - error: The underlying error which describes why this error happens. + case cannotLoadDataFromDisk(url: URL, error: Error) + + /// Cannot create a folder at a given path. Code 3005. + /// - path: The disk path where the directory creating operation fails. + /// - error: The underlying error which describes why this error happens. + case cannotCreateDirectory(path: String, error: Error) + + /// The requested image does not exist in cache. Code 3006. + /// - key: Key of the requested image in cache. + case imageNotExisting(key: String) + + /// Cannot convert an object to data for storing. Code 3007. + /// - object: The object which needs be convert to data. + case cannotConvertToData(object: Any, error: Error) + + /// Cannot serialize an image to data for storing. Code 3008. + /// - image: The input image needs to be serialized to cache. + /// - original: The original image data, if exists. + /// - serializer: The `CacheSerializer` used for the image serializing. + case cannotSerializeImage(image: KFCrossPlatformImage?, original: Data?, serializer: CacheSerializer) + + /// Cannot create the cache file at a certain fileURL under a key. Code 3009. + /// - fileURL: The url where the cache file should be created. + /// - key: The cache key used for the cache. When caching a file through `KingfisherManager` and Kingfisher's + /// extension method, it is the resolved cache key based on your input `Source` and the image processors. + /// - data: The data to be cached. + /// - error: The underlying error originally thrown by Foundation when writing the `data` to the disk file at + /// `fileURL`. + case cannotCreateCacheFile(fileURL: URL, key: String, data: Data, error: Error) + + /// Cannot set file attributes to a cached file. Code 3010. + /// - filePath: The path of target cache file. + /// - attributes: The file attribute to be set to the target file. + /// - error: The underlying error originally thrown by Foundation when setting the `attributes` to the disk + /// file at `filePath`. + case cannotSetCacheFileAttribute(filePath: String, attributes: [FileAttributeKey : Any], error: Error) + } + + + /// Represents the error reason during image processing phase. + /// + /// - processingFailed: Image processing fails. There is no valid output image from the processor. Code 4001. + public enum ProcessorErrorReason { + /// Image processing fails. There is no valid output image from the processor. Code 4001. + /// - processor: The `ImageProcessor` used to process the image or its data in `item`. + /// - item: The image or its data content. + case processingFailed(processor: ImageProcessor, item: ImageProcessItem) + } + + /// Represents the error reason during image setting in a view related class. + /// + /// - emptySource: The input resource is empty or `nil`. Code 5001. + /// - notCurrentSourceTask: The source task is finished, but it is not the one expected now. Code 5002. + /// - dataProviderError: An error happens during getting data from an `ImageDataProvider`. Code 5003. + public enum ImageSettingErrorReason { + + /// The input resource is empty or `nil`. Code 5001. + case emptySource + + /// The resource task is finished, but it is not the one expected now. This usually happens when you set another + /// resource on the view without cancelling the current on-going one. The previous setting task will fail with + /// this `.notCurrentSourceTask` error when a result got, regardless of it being successful or not for that task. + /// The result of this original task is contained in the associated value. + /// Code 5002. + /// - result: The `RetrieveImageResult` if the source task is finished without problem. `nil` if an error + /// happens. + /// - error: The `Error` if an issue happens during image setting task. `nil` if the task finishes without + /// problem. + /// - source: The original source value of the task. + case notCurrentSourceTask(result: RetrieveImageResult?, error: Error?, source: Source) + + /// An error happens during getting data from an `ImageDataProvider`. Code 5003. + case dataProviderError(provider: ImageDataProvider, error: Error) + + /// No more alternative `Source` can be used in current loading process. It means that the + /// `.alternativeSources` are used and Kingfisher tried to recovery from the original error, but still + /// fails for all the given alternative sources. The associated value holds all the errors encountered during + /// the load process, including the original source loading error and all the alternative sources errors. + /// Code 5004. + case alternativeSourcesExhausted([PropagationError]) + } + + // MARK: Member Cases + + /// Represents the error reason during networking request phase. + case requestError(reason: RequestErrorReason) + /// Represents the error reason during networking response phase. + case responseError(reason: ResponseErrorReason) + /// Represents the error reason during Kingfisher caching system. + case cacheError(reason: CacheErrorReason) + /// Represents the error reason during image processing phase. + case processorError(reason: ProcessorErrorReason) + /// Represents the error reason during image setting in a view related class. + case imageSettingError(reason: ImageSettingErrorReason) + + // MARK: Helper Properties & Methods + + /// Helper property to check whether this error is a `RequestErrorReason.taskCancelled` or not. + public var isTaskCancelled: Bool { + if case .requestError(reason: .taskCancelled) = self { + return true + } + return false + } + + /// Helper method to check whether this error is a `ResponseErrorReason.invalidHTTPStatusCode` and the + /// associated value is a given status code. + /// + /// - Parameter code: The given status code. + /// - Returns: If `self` is a `ResponseErrorReason.invalidHTTPStatusCode` error + /// and its status code equals to `code`, `true` is returned. Otherwise, `false`. + public func isInvalidResponseStatusCode(_ code: Int) -> Bool { + if case .responseError(reason: .invalidHTTPStatusCode(let response)) = self { + return response.statusCode == code + } + return false + } + + public var isInvalidResponseStatusCode: Bool { + if case .responseError(reason: .invalidHTTPStatusCode) = self { + return true + } + return false + } + + /// Helper property to check whether this error is a `ImageSettingErrorReason.notCurrentSourceTask` or not. + /// When a new image setting task starts while the old one is still running, the new task identifier will be + /// set and the old one is overwritten. A `.notCurrentSourceTask` error will be raised when the old task finishes + /// to let you know the setting process finishes with a certain result, but the image view or button is not set. + public var isNotCurrentTask: Bool { + if case .imageSettingError(reason: .notCurrentSourceTask(_, _, _)) = self { + return true + } + return false + } +} + +// MARK: - LocalizedError Conforming +extension KingfisherError: LocalizedError { + + /// A localized message describing what error occurred. + public var errorDescription: String? { + switch self { + case .requestError(let reason): return reason.errorDescription + case .responseError(let reason): return reason.errorDescription + case .cacheError(let reason): return reason.errorDescription + case .processorError(let reason): return reason.errorDescription + case .imageSettingError(let reason): return reason.errorDescription + } + } +} + + +// MARK: - CustomNSError Conforming +extension KingfisherError: CustomNSError { + + /// The error domain of `KingfisherError`. All errors from Kingfisher is under this domain. + public static let domain = "com.onevcat.Kingfisher.Error" + + /// The error code within the given domain. + public var errorCode: Int { + switch self { + case .requestError(let reason): return reason.errorCode + case .responseError(let reason): return reason.errorCode + case .cacheError(let reason): return reason.errorCode + case .processorError(let reason): return reason.errorCode + case .imageSettingError(let reason): return reason.errorCode + } + } +} + +extension KingfisherError.RequestErrorReason { + var errorDescription: String? { + switch self { + case .emptyRequest: + return "The request is empty or `nil`." + case .invalidURL(let request): + return "The request contains an invalid or empty URL. Request: \(request)." + case .taskCancelled(let task, let token): + return "The session task was cancelled. Task: \(task), cancel token: \(token)." + } + } + + var errorCode: Int { + switch self { + case .emptyRequest: return 1001 + case .invalidURL: return 1002 + case .taskCancelled: return 1003 + } + } +} + +extension KingfisherError.ResponseErrorReason { + var errorDescription: String? { + switch self { + case .invalidURLResponse(let response): + return "The URL response is invalid: \(response)" + case .invalidHTTPStatusCode(let response): + return "The HTTP status code in response is invalid. Code: \(response.statusCode), response: \(response)." + case .URLSessionError(let error): + return "A URL session error happened. The underlying error: \(error)" + case .dataModifyingFailed(let task): + return "The data modifying delegate returned `nil` for the downloaded data. Task: \(task)." + case .noURLResponse(let task): + return "No URL response received. Task: \(task)," + } + } + + var errorCode: Int { + switch self { + case .invalidURLResponse: return 2001 + case .invalidHTTPStatusCode: return 2002 + case .URLSessionError: return 2003 + case .dataModifyingFailed: return 2004 + case .noURLResponse: return 2005 + } + } +} + +extension KingfisherError.CacheErrorReason { + var errorDescription: String? { + switch self { + case .fileEnumeratorCreationFailed(let url): + return "Cannot create file enumerator for URL: \(url)." + case .invalidFileEnumeratorContent(let url): + return "Cannot get contents from the file enumerator at URL: \(url)." + case .invalidURLResource(let error, let key, let url): + return "Cannot get URL resource values or data for the given URL: \(url). " + + "Cache key: \(key). Underlying error: \(error)" + case .cannotLoadDataFromDisk(let url, let error): + return "Cannot load data from disk at URL: \(url). Underlying error: \(error)" + case .cannotCreateDirectory(let path, let error): + return "Cannot create directory at given path: Path: \(path). Underlying error: \(error)" + case .imageNotExisting(let key): + return "The image is not in cache, but you requires it should only be " + + "from cache by enabling the `.onlyFromCache` option. Key: \(key)." + case .cannotConvertToData(let object, let error): + return "Cannot convert the input object to a `Data` object when storing it to disk cache. " + + "Object: \(object). Underlying error: \(error)" + case .cannotSerializeImage(let image, let originalData, let serializer): + return "Cannot serialize an image due to the cache serializer returning `nil`. " + + "Image: \(String(describing:image)), original data: \(String(describing: originalData)), " + + "serializer: \(serializer)." + case .cannotCreateCacheFile(let fileURL, let key, let data, let error): + return "Cannot create cache file at url: \(fileURL), key: \(key), data length: \(data.count). " + + "Underlying foundation error: \(error)." + case .cannotSetCacheFileAttribute(let filePath, let attributes, let error): + return "Cannot set file attribute for the cache file at path: \(filePath), attributes: \(attributes)." + + "Underlying foundation error: \(error)." + } + } + + var errorCode: Int { + switch self { + case .fileEnumeratorCreationFailed: return 3001 + case .invalidFileEnumeratorContent: return 3002 + case .invalidURLResource: return 3003 + case .cannotLoadDataFromDisk: return 3004 + case .cannotCreateDirectory: return 3005 + case .imageNotExisting: return 3006 + case .cannotConvertToData: return 3007 + case .cannotSerializeImage: return 3008 + case .cannotCreateCacheFile: return 3009 + case .cannotSetCacheFileAttribute: return 3010 + } + } +} + +extension KingfisherError.ProcessorErrorReason { + var errorDescription: String? { + switch self { + case .processingFailed(let processor, let item): + return "Processing image failed. Processor: \(processor). Processing item: \(item)." + } + } + + var errorCode: Int { + switch self { + case .processingFailed: return 4001 + } + } +} + +extension KingfisherError.ImageSettingErrorReason { + var errorDescription: String? { + switch self { + case .emptySource: + return "The input resource is empty." + case .notCurrentSourceTask(let result, let error, let resource): + if let result = result { + return "Retrieving resource succeeded, but this source is " + + "not the one currently expected. Result: \(result). Resource: \(resource)." + } else if let error = error { + return "Retrieving resource failed, and this resource is " + + "not the one currently expected. Error: \(error). Resource: \(resource)." + } else { + return nil + } + case .dataProviderError(let provider, let error): + return "Image data provider fails to provide data. Provider: \(provider), error: \(error)" + case .alternativeSourcesExhausted(let errors): + return "Image setting from alternaive sources failed: \(errors)" + } + } + + var errorCode: Int { + switch self { + case .emptySource: return 5001 + case .notCurrentSourceTask: return 5002 + case .dataProviderError: return 5003 + case .alternativeSourcesExhausted: return 5004 + } + } +} diff --git a/!main project/Pods/Kingfisher/Sources/General/KingfisherManager.swift b/!main project/Pods/Kingfisher/Sources/General/KingfisherManager.swift new file mode 100644 index 0000000..f3d053a --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/General/KingfisherManager.swift @@ -0,0 +1,673 @@ +// +// KingfisherManager.swift +// Kingfisher +// +// Created by Wei Wang on 15/4/6. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + + +import Foundation + +/// The downloading progress block type. +/// The parameter value is the `receivedSize` of current response. +/// The second parameter is the total expected data length from response's "Content-Length" header. +/// If the expected length is not available, this block will not be called. +public typealias DownloadProgressBlock = ((_ receivedSize: Int64, _ totalSize: Int64) -> Void) + +/// Represents the result of a Kingfisher retrieving image task. +public struct RetrieveImageResult { + + /// Gets the image object of this result. + public let image: KFCrossPlatformImage + + /// Gets the cache source of the image. It indicates from which layer of cache this image is retrieved. + /// If the image is just downloaded from network, `.none` will be returned. + public let cacheType: CacheType + + /// The `Source` which this result is related to. This indicated where the `image` of `self` is referring. + public let source: Source + + /// The original `Source` from which the retrieve task begins. It can be different from the `source` property. + /// When an alternative source loading happened, the `source` will be the replacing loading target, while the + /// `originalSource` will be kept as the initial `source` which issued the image loading process. + public let originalSource: Source +} + +/// A struct that stores some related information of an `KingfisherError`. It provides some context information for +/// a pure error so you can identify the error easier. +public struct PropagationError { + + /// The `Source` to which current `error` is bound. + public let source: Source + + /// The actual error happens in framework. + public let error: KingfisherError +} + + +/// The downloading task updated block type. The parameter `newTask` is the updated new task of image setting process. +/// It is a `nil` if the image loading does not require an image downloading process. If an image downloading is issued, +/// this value will contain the actual `DownloadTask` for you to keep and cancel it later if you need. +public typealias DownloadTaskUpdatedBlock = ((_ newTask: DownloadTask?) -> Void) + +/// Main manager class of Kingfisher. It connects Kingfisher downloader and cache, +/// to provide a set of convenience methods to use Kingfisher for tasks. +/// You can use this class to retrieve an image via a specified URL from web or cache. +public class KingfisherManager { + + /// Represents a shared manager used across Kingfisher. + /// Use this instance for getting or storing images with Kingfisher. + public static let shared = KingfisherManager() + + // Mark: Public Properties + /// The `ImageCache` used by this manager. It is `ImageCache.default` by default. + /// If a cache is specified in `KingfisherManager.defaultOptions`, the value in `defaultOptions` will be + /// used instead. + public var cache: ImageCache + + /// The `ImageDownloader` used by this manager. It is `ImageDownloader.default` by default. + /// If a downloader is specified in `KingfisherManager.defaultOptions`, the value in `defaultOptions` will be + /// used instead. + public var downloader: ImageDownloader + + /// Default options used by the manager. This option will be used in + /// Kingfisher manager related methods, as well as all view extension methods. + /// You can also passing other options for each image task by sending an `options` parameter + /// to Kingfisher's APIs. The per image options will overwrite the default ones, + /// if the option exists in both. + public var defaultOptions = KingfisherOptionsInfo.empty + + // Use `defaultOptions` to overwrite the `downloader` and `cache`. + private var currentDefaultOptions: KingfisherOptionsInfo { + return [.downloader(downloader), .targetCache(cache)] + defaultOptions + } + + private let processingQueue: CallbackQueue + + private convenience init() { + self.init(downloader: .default, cache: .default) + } + + /// Creates an image setting manager with specified downloader and cache. + /// + /// - Parameters: + /// - downloader: The image downloader used to download images. + /// - cache: The image cache which stores memory and disk images. + public init(downloader: ImageDownloader, cache: ImageCache) { + self.downloader = downloader + self.cache = cache + + let processQueueName = "com.onevcat.Kingfisher.KingfisherManager.processQueue.\(UUID().uuidString)" + processingQueue = .dispatch(DispatchQueue(label: processQueueName)) + } + + // MARK: - Getting Images + + /// Gets an image from a given resource. + /// - Parameters: + /// - resource: The `Resource` object defines data information like key or URL. + /// - options: Options to use when creating the animated image. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. `progressBlock` is always called in + /// main queue. + /// - downloadTaskUpdated: Called when a new image downloading task is created for current image retrieving. This + /// usually happens when an alternative source is used to replace the original (failed) + /// task. You can update your reference of `DownloadTask` if you want to manually `cancel` + /// the new task. + /// - completionHandler: Called when the image retrieved and set finished. This completion handler will be invoked + /// from the `options.callbackQueue`. If not specified, the main queue will be used. + /// - Returns: A task represents the image downloading. If there is a download task starts for `.network` resource, + /// the started `DownloadTask` is returned. Otherwise, `nil` is returned. + /// + /// - Note: + /// This method will first check whether the requested `resource` is already in cache or not. If cached, + /// it returns `nil` and invoke the `completionHandler` after the cached image retrieved. Otherwise, it + /// will download the `resource`, store it in cache, then call `completionHandler`. + @discardableResult + public func retrieveImage( + with resource: Resource, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + downloadTaskUpdated: DownloadTaskUpdatedBlock? = nil, + completionHandler: ((Result) -> Void)?) -> DownloadTask? + { + return retrieveImage( + with: resource.convertToSource(), + options: options, + progressBlock: progressBlock, + downloadTaskUpdated: downloadTaskUpdated, + completionHandler: completionHandler + ) + } + + /// Gets an image from a given resource. + /// + /// - Parameters: + /// - source: The `Source` object defines data information from network or a data provider. + /// - options: Options to use when creating the animated image. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. `progressBlock` is always called in + /// main queue. + /// - downloadTaskUpdated: Called when a new image downloading task is created for current image retrieving. This + /// usually happens when an alternative source is used to replace the original (failed) + /// task. You can update your reference of `DownloadTask` if you want to manually `cancel` + /// the new task. + /// - completionHandler: Called when the image retrieved and set finished. This completion handler will be invoked + /// from the `options.callbackQueue`. If not specified, the main queue will be used. + /// - Returns: A task represents the image downloading. If there is a download task starts for `.network` resource, + /// the started `DownloadTask` is returned. Otherwise, `nil` is returned. + /// + /// - Note: + /// This method will first check whether the requested `source` is already in cache or not. If cached, + /// it returns `nil` and invoke the `completionHandler` after the cached image retrieved. Otherwise, it + /// will try to load the `source`, store it in cache, then call `completionHandler`. + /// + public func retrieveImage( + with source: Source, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + downloadTaskUpdated: DownloadTaskUpdatedBlock? = nil, + completionHandler: ((Result) -> Void)?) -> DownloadTask? + { + let options = currentDefaultOptions + (options ?? .empty) + var info = KingfisherParsedOptionsInfo(options) + if let block = progressBlock { + info.onDataReceived = (info.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)] + } + return retrieveImage( + with: source, + options: info, + downloadTaskUpdated: downloadTaskUpdated, + completionHandler: completionHandler) + } + + func retrieveImage( + with source: Source, + options: KingfisherParsedOptionsInfo, + downloadTaskUpdated: DownloadTaskUpdatedBlock? = nil, + completionHandler: ((Result) -> Void)?) -> DownloadTask? + { + var context = RetrievingContext(options: options, originalSource: source) + + func handler(currentSource: Source, result: (Result)) -> Void { + switch result { + case .success: + completionHandler?(result) + case .failure(let error): + // Skip alternative sources if the user cancelled it. + guard !error.isTaskCancelled else { + completionHandler?(.failure(error)) + return + } + if let nextSource = context.popAlternativeSource() { + context.appendError(error, to: currentSource) + let newTask = self.retrieveImage(with: nextSource, context: context) { result in + handler(currentSource: nextSource, result: result) + } + downloadTaskUpdated?(newTask) + } else { + // No other alternative source. Finish with error. + if context.propagationErrors.isEmpty { + completionHandler?(.failure(error)) + } else { + context.appendError(error, to: currentSource) + let finalError = KingfisherError.imageSettingError( + reason: .alternativeSourcesExhausted(context.propagationErrors) + ) + completionHandler?(.failure(finalError)) + } + } + } + } + + return retrieveImage( + with: source, + context: context) + { + result in + handler(currentSource: source, result: result) + } + + } + + private func retrieveImage( + with source: Source, + context: RetrievingContext, + completionHandler: ((Result) -> Void)?) -> DownloadTask? + { + let options = context.options + if options.forceRefresh { + return loadAndCacheImage( + source: source, + context: context, + completionHandler: completionHandler)?.value + + } else { + let loadedFromCache = retrieveImageFromCache( + source: source, + context: context, + completionHandler: completionHandler) + + if loadedFromCache { + return nil + } + + if options.onlyFromCache { + let error = KingfisherError.cacheError(reason: .imageNotExisting(key: source.cacheKey)) + completionHandler?(.failure(error)) + return nil + } + + return loadAndCacheImage( + source: source, + context: context, + completionHandler: completionHandler)?.value + } + } + + func provideImage( + provider: ImageDataProvider, + options: KingfisherParsedOptionsInfo, + completionHandler: ((Result) -> Void)?) + { + guard let completionHandler = completionHandler else { return } + provider.data { result in + switch result { + case .success(let data): + (options.processingQueue ?? self.processingQueue).execute { + let processor = options.processor + let processingItem = ImageProcessItem.data(data) + guard let image = processor.process(item: processingItem, options: options) else { + options.callbackQueue.execute { + let error = KingfisherError.processorError( + reason: .processingFailed(processor: processor, item: processingItem)) + completionHandler(.failure(error)) + } + return + } + + options.callbackQueue.execute { + let result = ImageLoadingResult(image: image, url: nil, originalData: data) + completionHandler(.success(result)) + } + } + case .failure(let error): + options.callbackQueue.execute { + let error = KingfisherError.imageSettingError( + reason: .dataProviderError(provider: provider, error: error)) + completionHandler(.failure(error)) + } + + } + } + } + + private func cacheImage( + source: Source, + options: KingfisherParsedOptionsInfo, + context: RetrievingContext, + result: Result, + completionHandler: ((Result) -> Void)? + ) + { + switch result { + case .success(let value): + let needToCacheOriginalImage = options.cacheOriginalImage && + options.processor != DefaultImageProcessor.default + let coordinator = CacheCallbackCoordinator( + shouldWaitForCache: options.waitForCache, shouldCacheOriginal: needToCacheOriginalImage) + // Add image to cache. + let targetCache = options.targetCache ?? self.cache + targetCache.store( + value.image, + original: value.originalData, + forKey: source.cacheKey, + options: options, + toDisk: !options.cacheMemoryOnly) + { + _ in + coordinator.apply(.cachingImage) { + let result = RetrieveImageResult( + image: value.image, + cacheType: .none, + source: source, + originalSource: context.originalSource + ) + completionHandler?(.success(result)) + } + } + + // Add original image to cache if necessary. + + if needToCacheOriginalImage { + let originalCache = options.originalCache ?? targetCache + originalCache.storeToDisk( + value.originalData, + forKey: source.cacheKey, + processorIdentifier: DefaultImageProcessor.default.identifier, + expiration: options.diskCacheExpiration) + { + _ in + coordinator.apply(.cachingOriginalImage) { + let result = RetrieveImageResult( + image: value.image, + cacheType: .none, + source: source, + originalSource: context.originalSource + ) + completionHandler?(.success(result)) + } + } + } + + coordinator.apply(.cacheInitiated) { + let result = RetrieveImageResult( + image: value.image, + cacheType: .none, + source: source, + originalSource: context.originalSource + ) + completionHandler?(.success(result)) + } + + case .failure(let error): + completionHandler?(.failure(error)) + } + } + + @discardableResult + func loadAndCacheImage( + source: Source, + context: RetrievingContext, + completionHandler: ((Result) -> Void)?) -> DownloadTask.WrappedTask? + { + let options = context.options + func _cacheImage(_ result: Result) { + cacheImage( + source: source, + options: options, + context: context, + result: result, + completionHandler: completionHandler + ) + } + + switch source { + case .network(let resource): + let downloader = options.downloader ?? self.downloader + let task = downloader.downloadImage( + with: resource.downloadURL, options: options, completionHandler: _cacheImage + ) + return task.map(DownloadTask.WrappedTask.download) + + case .provider(let provider): + provideImage(provider: provider, options: options, completionHandler: _cacheImage) + return .dataProviding + } + } + + /// Retrieves image from memory or disk cache. + /// + /// - Parameters: + /// - source: The target source from which to get image. + /// - key: The key to use when caching the image. + /// - url: Image request URL. This is not used when retrieving image from cache. It is just used for + /// `RetrieveImageResult` callback compatibility. + /// - options: Options on how to get the image from image cache. + /// - completionHandler: Called when the image retrieving finishes, either with succeeded + /// `RetrieveImageResult` or an error. + /// - Returns: `true` if the requested image or the original image before being processed is existing in cache. + /// Otherwise, this method returns `false`. + /// + /// - Note: + /// The image retrieving could happen in either memory cache or disk cache. The `.processor` option in + /// `options` will be considered when searching in the cache. If no processed image is found, Kingfisher + /// will try to check whether an original version of that image is existing or not. If there is already an + /// original, Kingfisher retrieves it from cache and processes it. Then, the processed image will be store + /// back to cache for later use. + func retrieveImageFromCache( + source: Source, + context: RetrievingContext, + completionHandler: ((Result) -> Void)?) -> Bool + { + let options = context.options + // 1. Check whether the image was already in target cache. If so, just get it. + let targetCache = options.targetCache ?? cache + let key = source.cacheKey + let targetImageCached = targetCache.imageCachedType( + forKey: key, processorIdentifier: options.processor.identifier) + + let validCache = targetImageCached.cached && + (options.fromMemoryCacheOrRefresh == false || targetImageCached == .memory) + if validCache { + targetCache.retrieveImage(forKey: key, options: options) { result in + guard let completionHandler = completionHandler else { return } + options.callbackQueue.execute { + result.match( + onSuccess: { cacheResult in + let value: Result + if let image = cacheResult.image { + value = result.map { + RetrieveImageResult( + image: image, + cacheType: $0.cacheType, + source: source, + originalSource: context.originalSource + ) + } + } else { + value = .failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key))) + } + completionHandler(value) + }, + onFailure: { _ in + completionHandler(.failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key)))) + } + ) + } + } + return true + } + + // 2. Check whether the original image exists. If so, get it, process it, save to storage and return. + let originalCache = options.originalCache ?? targetCache + // No need to store the same file in the same cache again. + if originalCache === targetCache && options.processor == DefaultImageProcessor.default { + return false + } + + // Check whether the unprocessed image existing or not. + let originalImageCacheType = originalCache.imageCachedType( + forKey: key, processorIdentifier: DefaultImageProcessor.default.identifier) + let canAcceptDiskCache = !options.fromMemoryCacheOrRefresh + + let canUseOriginalImageCache = + (canAcceptDiskCache && originalImageCacheType.cached) || + (!canAcceptDiskCache && originalImageCacheType == .memory) + + if canUseOriginalImageCache { + // Now we are ready to get found the original image from cache. We need the unprocessed image, so remove + // any processor from options first. + var optionsWithoutProcessor = options + optionsWithoutProcessor.processor = DefaultImageProcessor.default + originalCache.retrieveImage(forKey: key, options: optionsWithoutProcessor) { result in + + result.match( + onSuccess: { cacheResult in + guard let image = cacheResult.image else { + assertionFailure("The image (under key: \(key) should be existing in the original cache.") + return + } + + let processor = options.processor + (options.processingQueue ?? self.processingQueue).execute { + let item = ImageProcessItem.image(image) + guard let processedImage = processor.process(item: item, options: options) else { + let error = KingfisherError.processorError( + reason: .processingFailed(processor: processor, item: item)) + options.callbackQueue.execute { completionHandler?(.failure(error)) } + return + } + + var cacheOptions = options + cacheOptions.callbackQueue = .untouch + + let coordinator = CacheCallbackCoordinator( + shouldWaitForCache: options.waitForCache, shouldCacheOriginal: false) + + targetCache.store( + processedImage, + forKey: key, + options: cacheOptions, + toDisk: !options.cacheMemoryOnly) + { + _ in + coordinator.apply(.cachingImage) { + let value = RetrieveImageResult( + image: processedImage, + cacheType: .none, + source: source, + originalSource: context.originalSource + ) + options.callbackQueue.execute { completionHandler?(.success(value)) } + } + } + + coordinator.apply(.cacheInitiated) { + let value = RetrieveImageResult( + image: processedImage, + cacheType: .none, + source: source, + originalSource: context.originalSource + ) + options.callbackQueue.execute { completionHandler?(.success(value)) } + } + } + }, + onFailure: { _ in + // This should not happen actually, since we already confirmed `originalImageCached` is `true`. + // Just in case... + options.callbackQueue.execute { + completionHandler?( + .failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key))) + ) + } + } + ) + } + return true + } + + return false + } +} + +struct RetrievingContext { + + var options: KingfisherParsedOptionsInfo + + let originalSource: Source + var propagationErrors: [PropagationError] = [] + + init(options: KingfisherParsedOptionsInfo, originalSource: Source) { + self.originalSource = originalSource + self.options = options + } + + mutating func popAlternativeSource() -> Source? { + guard var alternativeSources = options.alternativeSources, !alternativeSources.isEmpty else { + return nil + } + let nextSource = alternativeSources.removeFirst() + options.alternativeSources = alternativeSources + return nextSource + } + + @discardableResult + mutating func appendError(_ error: KingfisherError, to source: Source) -> [PropagationError] { + let item = PropagationError(source: source, error: error) + propagationErrors.append(item) + return propagationErrors + } +} + +class CacheCallbackCoordinator { + + enum State { + case idle + case imageCached + case originalImageCached + case done + } + + enum Action { + case cacheInitiated + case cachingImage + case cachingOriginalImage + } + + private let shouldWaitForCache: Bool + private let shouldCacheOriginal: Bool + + private (set) var state: State = .idle + + init(shouldWaitForCache: Bool, shouldCacheOriginal: Bool) { + self.shouldWaitForCache = shouldWaitForCache + self.shouldCacheOriginal = shouldCacheOriginal + } + + func apply(_ action: Action, trigger: () -> Void) { + switch (state, action) { + case (.done, _): + break + + // From .idle + case (.idle, .cacheInitiated): + if !shouldWaitForCache { + state = .done + trigger() + } + case (.idle, .cachingImage): + if shouldCacheOriginal { + state = .imageCached + } else { + state = .done + trigger() + } + case (.idle, .cachingOriginalImage): + state = .originalImageCached + + // From .imageCached + case (.imageCached, .cachingOriginalImage): + state = .done + trigger() + + // From .originalImageCached + case (.originalImageCached, .cachingImage): + state = .done + trigger() + + default: + assertionFailure("This case should not happen in CacheCallbackCoordinator: \(state) - \(action)") + } + } +} diff --git a/!main project/Pods/Kingfisher/Sources/General/KingfisherOptionsInfo.swift b/!main project/Pods/Kingfisher/Sources/General/KingfisherOptionsInfo.swift new file mode 100644 index 0000000..e77793f --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/General/KingfisherOptionsInfo.swift @@ -0,0 +1,373 @@ +// +// KingfisherOptionsInfo.swift +// Kingfisher +// +// Created by Wei Wang on 15/4/23. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#if os(macOS) +import AppKit +#else +import UIKit +#endif + + +/// KingfisherOptionsInfo is a typealias for [KingfisherOptionsInfoItem]. +/// You can use the enum of option item with value to control some behaviors of Kingfisher. +public typealias KingfisherOptionsInfo = [KingfisherOptionsInfoItem] + +extension Array where Element == KingfisherOptionsInfoItem { + static let empty: KingfisherOptionsInfo = [] +} + +/// Represents the available option items could be used in `KingfisherOptionsInfo`. +public enum KingfisherOptionsInfoItem { + + /// Kingfisher will use the associated `ImageCache` object when handling related operations, + /// including trying to retrieve the cached images and store the downloaded image to it. + case targetCache(ImageCache) + + /// The `ImageCache` for storing and retrieving original images. If `originalCache` is + /// contained in the options, it will be preferred for storing and retrieving original images. + /// If there is no `.originalCache` in the options, `.targetCache` will be used to store original images. + /// + /// When using KingfisherManager to download and store an image, if `cacheOriginalImage` is + /// applied in the option, the original image will be stored to this `originalCache`. At the + /// same time, if a requested final image (with processor applied) cannot be found in `targetCache`, + /// Kingfisher will try to search the original image to check whether it is already there. If found, + /// it will be used and applied with the given processor. It is an optimization for not downloading + /// the same image for multiple times. + case originalCache(ImageCache) + + /// Kingfisher will use the associated `ImageDownloader` object to download the requested images. + case downloader(ImageDownloader) + + /// Member for animation transition when using `UIImageView`. Kingfisher will use the `ImageTransition` of + /// this enum to animate the image in if it is downloaded from web. The transition will not happen when the + /// image is retrieved from either memory or disk cache by default. If you need to do the transition even when + /// the image being retrieved from cache, set `.forceRefresh` as well. + case transition(ImageTransition) + + /// Associated `Float` value will be set as the priority of image download task. The value for it should be + /// between 0.0~1.0. If this option not set, the default value (`URLSessionTask.defaultPriority`) will be used. + case downloadPriority(Float) + + /// If set, Kingfisher will ignore the cache and try to fire a download task for the resource. + case forceRefresh + + /// If set, Kingfisher will try to retrieve the image from memory cache first. If the image is not in memory + /// cache, then it will ignore the disk cache but download the image again from network. This is useful when + /// you want to display a changeable image behind the same url at the same app session, while avoiding download + /// it for multiple times. + case fromMemoryCacheOrRefresh + + /// If set, setting the image to an image view will happen with transition even when retrieved from cache. + /// See `.transition` option for more. + case forceTransition + + /// If set, Kingfisher will only cache the value in memory but not in disk. + case cacheMemoryOnly + + /// If set, Kingfisher will wait for caching operation to be completed before calling the completion block. + case waitForCache + + /// If set, Kingfisher will only try to retrieve the image from cache, but not from network. If the image is + /// not in cache, the image retrieving will fail with an error. + case onlyFromCache + + /// Decode the image in background thread before using. It will decode the downloaded image data and do a off-screen + /// rendering to extract pixel information in background. This can speed up display, but will cost more time to + /// prepare the image for using. + case backgroundDecode + + /// The associated value of this member will be used as the target queue of dispatch callbacks when + /// retrieving images from cache. If not set, Kingfisher will use main queue for callbacks. + @available(*, deprecated, message: "Use `.callbackQueue(CallbackQueue)` instead.") + case callbackDispatchQueue(DispatchQueue?) + + /// The associated value will be used as the target queue of dispatch callbacks when retrieving images from + /// cache. If not set, Kingfisher will use `.mainCurrentOrAsync` for callbacks. + /// + /// - Note: + /// This option does not affect the callbacks for UI related extension methods. You will always get the + /// callbacks called from main queue. + case callbackQueue(CallbackQueue) + + /// The associated value will be used as the scale factor when converting retrieved data to an image. + /// Specify the image scale, instead of your screen scale. You may need to set the correct scale when you dealing + /// with 2x or 3x retina images. Otherwise, Kingfisher will convert the data to image object at `scale` 1.0. + case scaleFactor(CGFloat) + + /// Whether all the animated image data should be preloaded. Default is `false`, which means only following frames + /// will be loaded on need. If `true`, all the animated image data will be loaded and decoded into memory. + /// + /// This option is mainly used for back compatibility internally. You should not set it directly. Instead, + /// you should choose the image view class to control the GIF data loading. There are two classes in Kingfisher + /// support to display a GIF image. `AnimatedImageView` does not preload all data, it takes much less memory, but + /// uses more CPU when display. While a normal image view (`UIImageView` or `NSImageView`) loads all data at once, + /// which uses more memory but only decode image frames once. + case preloadAllAnimationData + + /// The `ImageDownloadRequestModifier` contained will be used to change the request before it being sent. + /// This is the last chance you can modify the image download request. You can modify the request for some + /// customizing purpose, such as adding auth token to the header, do basic HTTP auth or something like url mapping. + /// The original request will be sent without any modification by default. + case requestModifier(ImageDownloadRequestModifier) + + /// The `ImageDownloadRedirectHandler` contained will be used to change the request before redirection. + /// This is the possibility you can modify the image download request during redirect. You can modify the request for + /// some customizing purpose, such as adding auth token to the header, do basic HTTP auth or something like url + /// mapping. + /// The original redirection request will be sent without any modification by default. + case redirectHandler(ImageDownloadRedirectHandler) + + /// Processor for processing when the downloading finishes, a processor will convert the downloaded data to an image + /// and/or apply some filter on it. If a cache is connected to the downloader (it happens when you are using + /// KingfisherManager or any of the view extension methods), the converted image will also be sent to cache as well. + /// If not set, the `DefaultImageProcessor.default` will be used. + case processor(ImageProcessor) + + /// Supplies a `CacheSerializer` to convert some data to an image object for + /// retrieving from disk cache or vice versa for storing to disk cache. + /// If not set, the `DefaultCacheSerializer.default` will be used. + case cacheSerializer(CacheSerializer) + + /// An `ImageModifier` is for modifying an image as needed right before it is used. If the image was fetched + /// directly from the downloader, the modifier will run directly after the `ImageProcessor`. If the image is being + /// fetched from a cache, the modifier will run after the `CacheSerializer`. + /// + /// Use `ImageModifier` when you need to set properties that do not persist when caching the image on a concrete + /// type of `Image`, such as the `renderingMode` or the `alignmentInsets` of `UIImage`. + case imageModifier(ImageModifier) + + /// Keep the existing image of image view while setting another image to it. + /// By setting this option, the placeholder image parameter of image view extension method + /// will be ignored and the current image will be kept while loading or downloading the new image. + case keepCurrentImageWhileLoading + + /// If set, Kingfisher will only load the first frame from an animated image file as a single image. + /// Loading an animated images may take too much memory. It will be useful when you want to display a + /// static preview of the first frame from a animated image. + /// + /// This option will be ignored if the target image is not animated image data. + case onlyLoadFirstFrame + + /// If set and an `ImageProcessor` is used, Kingfisher will try to cache both the final result and original + /// image. Kingfisher will have a chance to use the original image when another processor is applied to the same + /// resource, instead of downloading it again. You can use `.originalCache` to specify a cache or the original + /// images if necessary. + /// + /// The original image will be only cached to disk storage. + case cacheOriginalImage + + /// If set and a downloading error occurred Kingfisher will set provided image (or empty) + /// in place of requested one. It's useful when you don't want to show placeholder + /// during loading time but wants to use some default image when requests will be failed. + case onFailureImage(KFCrossPlatformImage?) + + /// If set and used in `ImagePrefetcher`, the prefetching operation will load the images into memory storage + /// aggressively. By default this is not contained in the options, that means if the requested image is already + /// in disk cache, Kingfisher will not try to load it to memory. + case alsoPrefetchToMemory + + /// If set, the disk storage loading will happen in the same calling queue. By default, disk storage file loading + /// happens in its own queue with an asynchronous dispatch behavior. Although it provides better non-blocking disk + /// loading performance, it also causes a flickering when you reload an image from disk, if the image view already + /// has an image set. + /// + /// Set this options will stop that flickering by keeping all loading in the same queue (typically the UI queue + /// if you are using Kingfisher's extension methods to set an image), with a tradeoff of loading performance. + case loadDiskFileSynchronously + + /// The expiration setting for memory cache. By default, the underlying `MemoryStorage.Backend` uses the + /// expiration in its config for all items. If set, the `MemoryStorage.Backend` will use this associated + /// value to overwrite the config setting for this caching item. + case memoryCacheExpiration(StorageExpiration) + + /// The expiration extending setting for memory cache. The item expiration time will be incremented by this + /// value after access. + /// By default, the underlying `MemoryStorage.Backend` uses the initial cache expiration as extending + /// value: .cacheTime. + /// + /// To disable extending option at all add memoryCacheAccessExtendingExpiration(.none) to options. + case memoryCacheAccessExtendingExpiration(ExpirationExtending) + + /// The expiration setting for disk cache. By default, the underlying `DiskStorage.Backend` uses the + /// expiration in its config for all items. If set, the `DiskStorage.Backend` will use this associated + /// value to overwrite the config setting for this caching item. + case diskCacheExpiration(StorageExpiration) + + /// The expiration extending setting for disk cache. The item expiration time will be incremented by this value after access. + /// By default, the underlying `DiskStorage.Backend` uses the initial cache expiration as extending value: .cacheTime. + /// To disable extending option at all add diskCacheAccessExtendingExpiration(.none) to options. + case diskCacheAccessExtendingExpiration(ExpirationExtending) + + /// Decides on which queue the image processing should happen. By default, Kingfisher uses a pre-defined serial + /// queue to process images. Use this option to change this behavior. For example, specify a `.mainCurrentOrAsync` + /// to let the image be processed in main queue to prevent a possible flickering (but with a possibility of + /// blocking the UI, especially if the processor needs a lot of time to run). + case processingQueue(CallbackQueue) + + /// Enable progressive image loading, Kingfisher will use the `ImageProgressive` of + case progressiveJPEG(ImageProgressive) + + /// The alternative sources will be used when the original input `Source` fails. The `Source`s in the associated + /// array will be used to start a new image loading task if the previous task fails due to an error. The image + /// source loading process will stop as soon as a source is loaded successfully. If all `[Source]`s are used but + /// the loading is still failing, an `imageSettingError` with `alternativeSourcesExhausted` as its reason will be + /// thrown out. + /// + /// This option is useful if you want to implement a fallback solution for setting image. + /// + /// User cancellation will not trigger the alternative source loading. + case alternativeSources([Source]) +} + +// Improve performance by parsing the input `KingfisherOptionsInfo` (self) first. +// So we can prevent the iterating over the options array again and again. +/// The parsed options info used across Kingfisher methods. Each property in this type corresponds a case member +/// in `KingfisherOptionsInfoItem`. When a `KingfisherOptionsInfo` sent to Kingfisher related methods, it will be +/// parsed and converted to a `KingfisherParsedOptionsInfo` first, and pass through the internal methods. +public struct KingfisherParsedOptionsInfo { + + public var targetCache: ImageCache? = nil + public var originalCache: ImageCache? = nil + public var downloader: ImageDownloader? = nil + public var transition: ImageTransition = .none + public var downloadPriority: Float = URLSessionTask.defaultPriority + public var forceRefresh = false + public var fromMemoryCacheOrRefresh = false + public var forceTransition = false + public var cacheMemoryOnly = false + public var waitForCache = false + public var onlyFromCache = false + public var backgroundDecode = false + public var preloadAllAnimationData = false + public var callbackQueue: CallbackQueue = .mainCurrentOrAsync + public var scaleFactor: CGFloat = 1.0 + public var requestModifier: ImageDownloadRequestModifier? = nil + public var redirectHandler: ImageDownloadRedirectHandler? = nil + public var processor: ImageProcessor = DefaultImageProcessor.default + public var imageModifier: ImageModifier? = nil + public var cacheSerializer: CacheSerializer = DefaultCacheSerializer.default + public var keepCurrentImageWhileLoading = false + public var onlyLoadFirstFrame = false + public var cacheOriginalImage = false + public var onFailureImage: Optional = .none + public var alsoPrefetchToMemory = false + public var loadDiskFileSynchronously = false + public var memoryCacheExpiration: StorageExpiration? = nil + public var memoryCacheAccessExtendingExpiration: ExpirationExtending = .cacheTime + public var diskCacheExpiration: StorageExpiration? = nil + public var diskCacheAccessExtendingExpiration: ExpirationExtending = .cacheTime + public var processingQueue: CallbackQueue? = nil + public var progressiveJPEG: ImageProgressive? = nil + public var alternativeSources: [Source]? = nil + + var onDataReceived: [DataReceivingSideEffect]? = nil + + public init(_ info: KingfisherOptionsInfo?) { + guard let info = info else { return } + for option in info { + switch option { + case .targetCache(let value): targetCache = value + case .originalCache(let value): originalCache = value + case .downloader(let value): downloader = value + case .transition(let value): transition = value + case .downloadPriority(let value): downloadPriority = value + case .forceRefresh: forceRefresh = true + case .fromMemoryCacheOrRefresh: fromMemoryCacheOrRefresh = true + case .forceTransition: forceTransition = true + case .cacheMemoryOnly: cacheMemoryOnly = true + case .waitForCache: waitForCache = true + case .onlyFromCache: onlyFromCache = true + case .backgroundDecode: backgroundDecode = true + case .preloadAllAnimationData: preloadAllAnimationData = true + case .callbackQueue(let value): callbackQueue = value + case .scaleFactor(let value): scaleFactor = value + case .requestModifier(let value): requestModifier = value + case .redirectHandler(let value): redirectHandler = value + case .processor(let value): processor = value + case .imageModifier(let value): imageModifier = value + case .cacheSerializer(let value): cacheSerializer = value + case .keepCurrentImageWhileLoading: keepCurrentImageWhileLoading = true + case .onlyLoadFirstFrame: onlyLoadFirstFrame = true + case .cacheOriginalImage: cacheOriginalImage = true + case .onFailureImage(let value): onFailureImage = .some(value) + case .alsoPrefetchToMemory: alsoPrefetchToMemory = true + case .loadDiskFileSynchronously: loadDiskFileSynchronously = true + case .callbackDispatchQueue(let value): callbackQueue = value.map { .dispatch($0) } ?? .mainCurrentOrAsync + case .memoryCacheExpiration(let expiration): memoryCacheExpiration = expiration + case .memoryCacheAccessExtendingExpiration(let expirationExtending): memoryCacheAccessExtendingExpiration = expirationExtending + case .diskCacheExpiration(let expiration): diskCacheExpiration = expiration + case .diskCacheAccessExtendingExpiration(let expirationExtending): diskCacheAccessExtendingExpiration = expirationExtending + case .processingQueue(let queue): processingQueue = queue + case .progressiveJPEG(let value): progressiveJPEG = value + case .alternativeSources(let sources): alternativeSources = sources + } + } + + if originalCache == nil { + originalCache = targetCache + } + } +} + +extension KingfisherParsedOptionsInfo { + var imageCreatingOptions: ImageCreatingOptions { + return ImageCreatingOptions( + scale: scaleFactor, + duration: 0.0, + preloadAll: preloadAllAnimationData, + onlyFirstFrame: onlyLoadFirstFrame) + } +} + +protocol DataReceivingSideEffect: AnyObject { + var onShouldApply: () -> Bool { get set } + func onDataReceived(_ session: URLSession, task: SessionDataTask, data: Data) +} + +class ImageLoadingProgressSideEffect: DataReceivingSideEffect { + + var onShouldApply: () -> Bool = { return true } + + let block: DownloadProgressBlock + + init(_ block: @escaping DownloadProgressBlock) { + self.block = block + } + + func onDataReceived(_ session: URLSession, task: SessionDataTask, data: Data) { + DispatchQueue.main.async { + guard self.onShouldApply() else { return } + guard let expectedContentLength = task.task.response?.expectedContentLength, + expectedContentLength != -1 else + { + return + } + + let dataLength = Int64(task.mutableData.count) + self.block(dataLength, expectedContentLength) + } + } +} diff --git a/!main project/Pods/Kingfisher/Sources/Image/Filter.swift b/!main project/Pods/Kingfisher/Sources/Image/Filter.swift new file mode 100644 index 0000000..6e4b386 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Image/Filter.swift @@ -0,0 +1,146 @@ +// +// Filter.swift +// Kingfisher +// +// Created by Wei Wang on 2016/08/31. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#if !os(watchOS) + +import CoreImage + +// Reuse the same CI Context for all CI drawing. +private let ciContext = CIContext(options: nil) + +/// Represents the type of transformer method, which will be used in to provide a `Filter`. +public typealias Transformer = (CIImage) -> CIImage? + +/// Represents a processor based on a `CIImage` `Filter`. +/// It requires a filter to create an `ImageProcessor`. +public protocol CIImageProcessor: ImageProcessor { + var filter: Filter { get } +} + +extension CIImageProcessor { + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + switch item { + case .image(let image): + return image.kf.apply(filter) + case .data: + return (DefaultImageProcessor.default |> self).process(item: item, options: options) + } + } +} + +/// A wrapper struct for a `Transformer` of CIImage filters. A `Filter` +/// value could be used to create a `CIImage` processor. +public struct Filter { + + let transform: Transformer + + public init(transform: @escaping Transformer) { + self.transform = transform + } + + /// Tint filter which will apply a tint color to images. + public static var tint: (KFCrossPlatformColor) -> Filter = { + color in + Filter { + input in + + let colorFilter = CIFilter(name: "CIConstantColorGenerator")! + colorFilter.setValue(CIColor(color: color), forKey: kCIInputColorKey) + + let filter = CIFilter(name: "CISourceOverCompositing")! + + let colorImage = colorFilter.outputImage + filter.setValue(colorImage, forKey: kCIInputImageKey) + filter.setValue(input, forKey: kCIInputBackgroundImageKey) + + return filter.outputImage?.cropped(to: input.extent) + } + } + + /// Represents color control elements. It is a tuple of + /// `(brightness, contrast, saturation, inputEV)` + public typealias ColorElement = (CGFloat, CGFloat, CGFloat, CGFloat) + + /// Color control filter which will apply color control change to images. + public static var colorControl: (ColorElement) -> Filter = { arg -> Filter in + let (brightness, contrast, saturation, inputEV) = arg + return Filter { input in + let paramsColor = [kCIInputBrightnessKey: brightness, + kCIInputContrastKey: contrast, + kCIInputSaturationKey: saturation] + let blackAndWhite = input.applyingFilter("CIColorControls", parameters: paramsColor) + let paramsExposure = [kCIInputEVKey: inputEV] + return blackAndWhite.applyingFilter("CIExposureAdjust", parameters: paramsExposure) + } + } +} + +extension KingfisherWrapper where Base: KFCrossPlatformImage { + + /// Applies a `Filter` containing `CIImage` transformer to `self`. + /// + /// - Parameter filter: The filter used to transform `self`. + /// - Returns: A transformed image by input `Filter`. + /// + /// - Note: + /// Only CG-based images are supported. If any error happens + /// during transforming, `self` will be returned. + public func apply(_ filter: Filter) -> KFCrossPlatformImage { + + guard let cgImage = cgImage else { + assertionFailure("[Kingfisher] Tint image only works for CG-based image.") + return base + } + + let inputImage = CIImage(cgImage: cgImage) + guard let outputImage = filter.transform(inputImage) else { + return base + } + + guard let result = ciContext.createCGImage(outputImage, from: outputImage.extent) else { + assertionFailure("[Kingfisher] Can not make an tint image within context.") + return base + } + + #if os(macOS) + return fixedForRetinaPixel(cgImage: result, to: size) + #else + return KFCrossPlatformImage(cgImage: result, scale: base.scale, orientation: base.imageOrientation) + #endif + } + +} + +#endif diff --git a/!main project/Pods/Kingfisher/Sources/Image/GIFAnimatedImage.swift b/!main project/Pods/Kingfisher/Sources/Image/GIFAnimatedImage.swift new file mode 100644 index 0000000..4685583 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Image/GIFAnimatedImage.swift @@ -0,0 +1,121 @@ +// +// AnimatedImage.swift +// Kingfisher +// +// Created by onevcat on 2018/09/26. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation +import ImageIO + +/// Represents a set of image creating options used in Kingfisher. +public struct ImageCreatingOptions { + + /// The target scale of image needs to be created. + public let scale: CGFloat + + /// The expected animation duration if an animated image being created. + public let duration: TimeInterval + + /// For an animated image, whether or not all frames should be loaded before displaying. + public let preloadAll: Bool + + /// For an animated image, whether or not only the first image should be + /// loaded as a static image. It is useful for preview purpose of an animated image. + public let onlyFirstFrame: Bool + + /// Creates an `ImageCreatingOptions` object. + /// + /// - Parameters: + /// - scale: The target scale of image needs to be created. Default is `1.0`. + /// - duration: The expected animation duration if an animated image being created. + /// A value less or equal to `0.0` means the animated image duration will + /// be determined by the frame data. Default is `0.0`. + /// - preloadAll: For an animated image, whether or not all frames should be loaded before displaying. + /// Default is `false`. + /// - onlyFirstFrame: For an animated image, whether or not only the first image should be + /// loaded as a static image. It is useful for preview purpose of an animated image. + /// Default is `false`. + public init( + scale: CGFloat = 1.0, + duration: TimeInterval = 0.0, + preloadAll: Bool = false, + onlyFirstFrame: Bool = false) + { + self.scale = scale + self.duration = duration + self.preloadAll = preloadAll + self.onlyFirstFrame = onlyFirstFrame + } +} + +// Represents the decoding for a GIF image. This class extracts frames from an `imageSource`, then +// hold the images for later use. +class GIFAnimatedImage { + let images: [KFCrossPlatformImage] + let duration: TimeInterval + + init?(from imageSource: CGImageSource, for info: [String: Any], options: ImageCreatingOptions) { + let frameCount = CGImageSourceGetCount(imageSource) + var images = [KFCrossPlatformImage]() + var gifDuration = 0.0 + + for i in 0 ..< frameCount { + guard let imageRef = CGImageSourceCreateImageAtIndex(imageSource, i, info as CFDictionary) else { + return nil + } + + if frameCount == 1 { + gifDuration = .infinity + } else { + // Get current animated GIF frame duration + gifDuration += GIFAnimatedImage.getFrameDuration(from: imageSource, at: i) + } + images.append(KingfisherWrapper.image(cgImage: imageRef, scale: options.scale, refImage: nil)) + if options.onlyFirstFrame { break } + } + self.images = images + self.duration = gifDuration + } + + // Calculates frame duration for a gif frame out of the kCGImagePropertyGIFDictionary dictionary. + static func getFrameDuration(from gifInfo: [String: Any]?) -> TimeInterval { + let defaultFrameDuration = 0.1 + guard let gifInfo = gifInfo else { return defaultFrameDuration } + + let unclampedDelayTime = gifInfo[kCGImagePropertyGIFUnclampedDelayTime as String] as? NSNumber + let delayTime = gifInfo[kCGImagePropertyGIFDelayTime as String] as? NSNumber + let duration = unclampedDelayTime ?? delayTime + + guard let frameDuration = duration else { return defaultFrameDuration } + return frameDuration.doubleValue > 0.011 ? frameDuration.doubleValue : defaultFrameDuration + } + + // Calculates frame duration at a specific index for a gif from an `imageSource`. + static func getFrameDuration(from imageSource: CGImageSource, at index: Int) -> TimeInterval { + guard let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, index, nil) + as? [String: Any] else { return 0.0 } + + let gifInfo = properties[kCGImagePropertyGIFDictionary as String] as? [String: Any] + return getFrameDuration(from: gifInfo) + } +} diff --git a/!main project/Pods/Kingfisher/Sources/Image/Image.swift b/!main project/Pods/Kingfisher/Sources/Image/Image.swift new file mode 100644 index 0000000..da757ca --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Image/Image.swift @@ -0,0 +1,377 @@ +// +// Image.swift +// Kingfisher +// +// Created by Wei Wang on 16/1/6. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + + +#if os(macOS) +import AppKit +private var imagesKey: Void? +private var durationKey: Void? +#else +import UIKit +import MobileCoreServices +private var imageSourceKey: Void? +#endif + +#if !os(watchOS) +import CoreImage +#endif + +import CoreGraphics +import ImageIO + +private var animatedImageDataKey: Void? + +// MARK: - Image Properties +extension KingfisherWrapper where Base: KFCrossPlatformImage { + private(set) var animatedImageData: Data? { + get { return getAssociatedObject(base, &animatedImageDataKey) } + set { setRetainedAssociatedObject(base, &animatedImageDataKey, newValue) } + } + + #if os(macOS) + var cgImage: CGImage? { + return base.cgImage(forProposedRect: nil, context: nil, hints: nil) + } + + var scale: CGFloat { + return 1.0 + } + + private(set) var images: [KFCrossPlatformImage]? { + get { return getAssociatedObject(base, &imagesKey) } + set { setRetainedAssociatedObject(base, &imagesKey, newValue) } + } + + private(set) var duration: TimeInterval { + get { return getAssociatedObject(base, &durationKey) ?? 0.0 } + set { setRetainedAssociatedObject(base, &durationKey, newValue) } + } + + var size: CGSize { + return base.representations.reduce(.zero) { size, rep in + let width = max(size.width, CGFloat(rep.pixelsWide)) + let height = max(size.height, CGFloat(rep.pixelsHigh)) + return CGSize(width: width, height: height) + } + } + #else + var cgImage: CGImage? { return base.cgImage } + var scale: CGFloat { return base.scale } + var images: [KFCrossPlatformImage]? { return base.images } + var duration: TimeInterval { return base.duration } + var size: CGSize { return base.size } + + private(set) var imageSource: CGImageSource? { + get { return getAssociatedObject(base, &imageSourceKey) } + set { setRetainedAssociatedObject(base, &imageSourceKey, newValue) } + } + #endif + + // Bitmap memory cost with bytes. + var cost: Int { + let pixel = Int(size.width * size.height * scale * scale) + guard let cgImage = cgImage else { + return pixel * 4 + } + return pixel * cgImage.bitsPerPixel / 8 + } +} + +// MARK: - Image Conversion +extension KingfisherWrapper where Base: KFCrossPlatformImage { + #if os(macOS) + static func image(cgImage: CGImage, scale: CGFloat, refImage: KFCrossPlatformImage?) -> KFCrossPlatformImage { + return KFCrossPlatformImage(cgImage: cgImage, size: .zero) + } + + /// Normalize the image. This getter does nothing on macOS but return the image itself. + public var normalized: KFCrossPlatformImage { return base } + + #else + /// Creating an image from a give `CGImage` at scale and orientation for refImage. The method signature is for + /// compatibility of macOS version. + static func image(cgImage: CGImage, scale: CGFloat, refImage: KFCrossPlatformImage?) -> KFCrossPlatformImage { + return KFCrossPlatformImage(cgImage: cgImage, scale: scale, orientation: refImage?.imageOrientation ?? .up) + } + + /// Returns normalized image for current `base` image. + /// This method will try to redraw an image with orientation and scale considered. + public var normalized: KFCrossPlatformImage { + // prevent animated image (GIF) lose it's images + guard images == nil else { return base.copy() as! KFCrossPlatformImage } + // No need to do anything if already up + guard base.imageOrientation != .up else { return base.copy() as! KFCrossPlatformImage } + + return draw(to: size, inverting: true, refImage: KFCrossPlatformImage()) { + fixOrientation(in: $0) + return true + } + } + + func fixOrientation(in context: CGContext) { + + var transform = CGAffineTransform.identity + + let orientation = base.imageOrientation + + switch orientation { + case .down, .downMirrored: + transform = transform.translatedBy(x: size.width, y: size.height) + transform = transform.rotated(by: .pi) + case .left, .leftMirrored: + transform = transform.translatedBy(x: size.width, y: 0) + transform = transform.rotated(by: .pi / 2.0) + case .right, .rightMirrored: + transform = transform.translatedBy(x: 0, y: size.height) + transform = transform.rotated(by: .pi / -2.0) + case .up, .upMirrored: + break + #if compiler(>=5) + @unknown default: + break + #endif + } + + //Flip image one more time if needed to, this is to prevent flipped image + switch orientation { + case .upMirrored, .downMirrored: + transform = transform.translatedBy(x: size.width, y: 0) + transform = transform.scaledBy(x: -1, y: 1) + case .leftMirrored, .rightMirrored: + transform = transform.translatedBy(x: size.height, y: 0) + transform = transform.scaledBy(x: -1, y: 1) + case .up, .down, .left, .right: + break + #if compiler(>=5) + @unknown default: + break + #endif + } + + context.concatenate(transform) + switch orientation { + case .left, .leftMirrored, .right, .rightMirrored: + context.draw(cgImage!, in: CGRect(x: 0, y: 0, width: size.height, height: size.width)) + default: + context.draw(cgImage!, in: CGRect(x: 0, y: 0, width: size.width, height: size.height)) + } + } + #endif +} + +// MARK: - Image Representation +extension KingfisherWrapper where Base: KFCrossPlatformImage { + /// Returns PNG representation of `base` image. + /// + /// - Returns: PNG data of image. + public func pngRepresentation() -> Data? { + #if os(macOS) + guard let cgImage = cgImage else { + return nil + } + let rep = NSBitmapImageRep(cgImage: cgImage) + return rep.representation(using: .png, properties: [:]) + #else + #if swift(>=4.2) + return base.pngData() + #else + return UIImagePNGRepresentation(base) + #endif + #endif + } + + /// Returns JPEG representation of `base` image. + /// + /// - Parameter compressionQuality: The compression quality when converting image to JPEG data. + /// - Returns: JPEG data of image. + public func jpegRepresentation(compressionQuality: CGFloat) -> Data? { + #if os(macOS) + guard let cgImage = cgImage else { + return nil + } + let rep = NSBitmapImageRep(cgImage: cgImage) + return rep.representation(using:.jpeg, properties: [.compressionFactor: compressionQuality]) + #else + #if swift(>=4.2) + return base.jpegData(compressionQuality: compressionQuality) + #else + return UIImageJPEGRepresentation(base, compressionQuality) + #endif + #endif + } + + /// Returns GIF representation of `base` image. + /// + /// - Returns: Original GIF data of image. + public func gifRepresentation() -> Data? { + return animatedImageData + } + + /// Returns a data representation for `base` image, with the `format` as the format indicator. + /// + /// - Parameter format: The format in which the output data should be. If `unknown`, the `base` image will be + /// converted in the PNG representation. + /// + /// - Returns: The output data representing. + + /// Returns a data representation for `base` image, with the `format` as the format indicator. + /// - Parameters: + /// - format: The format in which the output data should be. If `unknown`, the `base` image will be + /// converted in the PNG representation. + /// - compressionQuality: The compression quality when converting image to a lossy format data. + public func data(format: ImageFormat, compressionQuality: CGFloat = 1.0) -> Data? { + return autoreleasepool { () -> Data? in + let data: Data? + switch format { + case .PNG: data = pngRepresentation() + case .JPEG: data = jpegRepresentation(compressionQuality: compressionQuality) + case .GIF: data = gifRepresentation() + case .unknown: data = normalized.kf.pngRepresentation() + } + + return data + } + } +} + +// MARK: - Creating Images +extension KingfisherWrapper where Base: KFCrossPlatformImage { + + /// Creates an animated image from a given data and options. Currently only GIF data is supported. + /// + /// - Parameters: + /// - data: The animated image data. + /// - options: Options to use when creating the animated image. + /// - Returns: An `Image` object represents the animated image. It is in form of an array of image frames with a + /// certain duration. `nil` if anything wrong when creating animated image. + public static func animatedImage(data: Data, options: ImageCreatingOptions) -> KFCrossPlatformImage? { + let info: [String: Any] = [ + kCGImageSourceShouldCache as String: true, + kCGImageSourceTypeIdentifierHint as String: kUTTypeGIF + ] + + guard let imageSource = CGImageSourceCreateWithData(data as CFData, info as CFDictionary) else { + return nil + } + + #if os(macOS) + guard let animatedImage = GIFAnimatedImage(from: imageSource, for: info, options: options) else { + return nil + } + var image: KFCrossPlatformImage? + if options.onlyFirstFrame { + image = animatedImage.images.first + } else { + image = KFCrossPlatformImage(data: data) + var kf = image?.kf + kf?.images = animatedImage.images + kf?.duration = animatedImage.duration + } + image?.kf.animatedImageData = data + return image + #else + + var image: KFCrossPlatformImage? + if options.preloadAll || options.onlyFirstFrame { + // Use `images` image if you want to preload all animated data + guard let animatedImage = GIFAnimatedImage(from: imageSource, for: info, options: options) else { + return nil + } + if options.onlyFirstFrame { + image = animatedImage.images.first + } else { + let duration = options.duration <= 0.0 ? animatedImage.duration : options.duration + image = .animatedImage(with: animatedImage.images, duration: duration) + } + image?.kf.animatedImageData = data + } else { + image = KFCrossPlatformImage(data: data, scale: options.scale) + var kf = image?.kf + kf?.imageSource = imageSource + kf?.animatedImageData = data + } + + return image + #endif + } + + /// Creates an image from a given data and options. `.JPEG`, `.PNG` or `.GIF` is supported. For other + /// image format, image initializer from system will be used. If no image object could be created from + /// the given `data`, `nil` will be returned. + /// + /// - Parameters: + /// - data: The image data representation. + /// - options: Options to use when creating the image. + /// - Returns: An `Image` object represents the image if created. If the `data` is invalid or not supported, `nil` + /// will be returned. + public static func image(data: Data, options: ImageCreatingOptions) -> KFCrossPlatformImage? { + var image: KFCrossPlatformImage? + switch data.kf.imageFormat { + case .JPEG: + image = KFCrossPlatformImage(data: data, scale: options.scale) + case .PNG: + image = KFCrossPlatformImage(data: data, scale: options.scale) + case .GIF: + image = KingfisherWrapper.animatedImage(data: data, options: options) + case .unknown: + image = KFCrossPlatformImage(data: data, scale: options.scale) + } + return image + } + + /// Creates a downsampled image from given data to a certain size and scale. + /// + /// - Parameters: + /// - data: The image data contains a JPEG or PNG image. + /// - pointSize: The target size in point to which the image should be downsampled. + /// - scale: The scale of result image. + /// - Returns: A downsampled `Image` object following the input conditions. + /// + /// - Note: + /// Different from image `resize` methods, downsampling will not render the original + /// input image in pixel format. It does downsampling from the image data, so it is much + /// more memory efficient and friendly. Choose to use downsampling as possible as you can. + /// + /// The input size should be smaller than the size of input image. If it is larger than the + /// original image size, the result image will be the same size of input without downsampling. + public static func downsampledImage(data: Data, to pointSize: CGSize, scale: CGFloat) -> KFCrossPlatformImage? { + let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary + guard let imageSource = CGImageSourceCreateWithData(data as CFData, imageSourceOptions) else { + return nil + } + + let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale + let downsampleOptions = [ + kCGImageSourceCreateThumbnailFromImageAlways: true, + kCGImageSourceShouldCacheImmediately: true, + kCGImageSourceCreateThumbnailWithTransform: true, + kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels] as CFDictionary + guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else { + return nil + } + return KingfisherWrapper.image(cgImage: downsampledImage, scale: scale, refImage: nil) + } +} diff --git a/!main project/Pods/Kingfisher/Sources/Image/ImageDrawing.swift b/!main project/Pods/Kingfisher/Sources/Image/ImageDrawing.swift new file mode 100644 index 0000000..31e890e --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Image/ImageDrawing.swift @@ -0,0 +1,544 @@ +// +// ImageDrawing.swift +// Kingfisher +// +// Created by onevcat on 2018/09/28. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Accelerate + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit +#endif +#if canImport(UIKit) +import UIKit +#endif + +// MARK: - Image Transforming +extension KingfisherWrapper where Base: KFCrossPlatformImage { + // MARK: Blend Mode + /// Create image from `base` image and apply blend mode. + /// + /// - parameter blendMode: The blend mode of creating image. + /// - parameter alpha: The alpha should be used for image. + /// - parameter backgroundColor: The background color for the output image. + /// + /// - returns: An image with blend mode applied. + /// + /// - Note: This method only works for CG-based image. + #if !os(macOS) + public func image(withBlendMode blendMode: CGBlendMode, + alpha: CGFloat = 1.0, + backgroundColor: KFCrossPlatformColor? = nil) -> KFCrossPlatformImage + { + guard let _ = cgImage else { + assertionFailure("[Kingfisher] Blend mode image only works for CG-based image.") + return base + } + + let rect = CGRect(origin: .zero, size: size) + return draw(to: rect.size) { _ in + if let backgroundColor = backgroundColor { + backgroundColor.setFill() + UIRectFill(rect) + } + + base.draw(in: rect, blendMode: blendMode, alpha: alpha) + return false + } + } + #endif + + #if os(macOS) + // MARK: Compositing + /// Creates image from `base` image and apply compositing operation. + /// + /// - Parameters: + /// - compositingOperation: The compositing operation of creating image. + /// - alpha: The alpha should be used for image. + /// - backgroundColor: The background color for the output image. + /// - Returns: An image with compositing operation applied. + /// + /// - Note: This method only works for CG-based image. For any non-CG-based image, `base` itself is returned. + public func image(withCompositingOperation compositingOperation: NSCompositingOperation, + alpha: CGFloat = 1.0, + backgroundColor: KFCrossPlatformColor? = nil) -> KFCrossPlatformImage + { + guard let _ = cgImage else { + assertionFailure("[Kingfisher] Compositing Operation image only works for CG-based image.") + return base + } + + let rect = CGRect(origin: .zero, size: size) + return draw(to: rect.size) { _ in + if let backgroundColor = backgroundColor { + backgroundColor.setFill() + rect.fill() + } + base.draw(in: rect, from: .zero, operation: compositingOperation, fraction: alpha) + return false + } + } + #endif + + // MARK: Round Corner + /// Creates a round corner image from on `base` image. + /// + /// - Parameters: + /// - radius: The round corner radius of creating image. + /// - size: The target size of creating image. + /// - corners: The target corners which will be applied rounding. + /// - backgroundColor: The background color for the output image + /// - Returns: An image with round corner of `self`. + /// + /// - Note: This method only works for CG-based image. The current image scale is kept. + /// For any non-CG-based image, `base` itself is returned. + public func image(withRoundRadius radius: CGFloat, + fit size: CGSize, + roundingCorners corners: RectCorner = .all, + backgroundColor: KFCrossPlatformColor? = nil) -> KFCrossPlatformImage + { + guard let _ = cgImage else { + assertionFailure("[Kingfisher] Round corner image only works for CG-based image.") + return base + } + + let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: size) + return draw(to: size) { _ in + #if os(macOS) + if let backgroundColor = backgroundColor { + let rectPath = NSBezierPath(rect: rect) + backgroundColor.setFill() + rectPath.fill() + } + + let path = NSBezierPath(roundedRect: rect, byRoundingCorners: corners, radius: radius) + #if swift(>=4.2) + path.windingRule = .evenOdd + #else + path.windingRule = .evenOddWindingRule + #endif + path.addClip() + base.draw(in: rect) + #else + guard let context = UIGraphicsGetCurrentContext() else { + assertionFailure("[Kingfisher] Failed to create CG context for image.") + return false + } + + if let backgroundColor = backgroundColor { + let rectPath = UIBezierPath(rect: rect) + backgroundColor.setFill() + rectPath.fill() + } + + let path = UIBezierPath( + roundedRect: rect, + byRoundingCorners: corners.uiRectCorner, + cornerRadii: CGSize(width: radius, height: radius) + ) + context.addPath(path.cgPath) + context.clip() + base.draw(in: rect) + #endif + return false + } + } + + #if os(iOS) || os(tvOS) + func resize(to size: CGSize, for contentMode: UIView.ContentMode) -> KFCrossPlatformImage { + switch contentMode { + case .scaleAspectFit: + return resize(to: size, for: .aspectFit) + case .scaleAspectFill: + return resize(to: size, for: .aspectFill) + default: + return resize(to: size) + } + } + #endif + + // MARK: Resizing + /// Resizes `base` image to an image with new size. + /// + /// - Parameter size: The target size in point. + /// - Returns: An image with new size. + /// - Note: This method only works for CG-based image. The current image scale is kept. + /// For any non-CG-based image, `base` itself is returned. + public func resize(to size: CGSize) -> KFCrossPlatformImage { + guard let _ = cgImage else { + assertionFailure("[Kingfisher] Resize only works for CG-based image.") + return base + } + + let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: size) + return draw(to: size) { _ in + #if os(macOS) + base.draw(in: rect, from: .zero, operation: .copy, fraction: 1.0) + #else + base.draw(in: rect) + #endif + return false + } + } + + /// Resizes `base` image to an image of new size, respecting the given content mode. + /// + /// - Parameters: + /// - targetSize: The target size in point. + /// - contentMode: Content mode of output image should be. + /// - Returns: An image with new size. + /// + /// - Note: This method only works for CG-based image. The current image scale is kept. + /// For any non-CG-based image, `base` itself is returned. + public func resize(to targetSize: CGSize, for contentMode: ContentMode) -> KFCrossPlatformImage { + let newSize = size.kf.resize(to: targetSize, for: contentMode) + return resize(to: newSize) + } + + // MARK: Cropping + /// Crops `base` image to a new size with a given anchor. + /// + /// - Parameters: + /// - size: The target size. + /// - anchor: The anchor point from which the size should be calculated. + /// - Returns: An image with new size. + /// + /// - Note: This method only works for CG-based image. The current image scale is kept. + /// For any non-CG-based image, `base` itself is returned. + public func crop(to size: CGSize, anchorOn anchor: CGPoint) -> KFCrossPlatformImage { + guard let cgImage = cgImage else { + assertionFailure("[Kingfisher] Crop only works for CG-based image.") + return base + } + + let rect = self.size.kf.constrainedRect(for: size, anchor: anchor) + guard let image = cgImage.cropping(to: rect.scaled(scale)) else { + assertionFailure("[Kingfisher] Cropping image failed.") + return base + } + + return KingfisherWrapper.image(cgImage: image, scale: scale, refImage: base) + } + + // MARK: Blur + /// Creates an image with blur effect based on `base` image. + /// + /// - Parameter radius: The blur radius should be used when creating blur effect. + /// - Returns: An image with blur effect applied. + /// + /// - Note: This method only works for CG-based image. The current image scale is kept. + /// For any non-CG-based image, `base` itself is returned. + public func blurred(withRadius radius: CGFloat) -> KFCrossPlatformImage { + + guard let cgImage = cgImage else { + assertionFailure("[Kingfisher] Blur only works for CG-based image.") + return base + } + + // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement + // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5) + // if d is odd, use three box-blurs of size 'd', centered on the output pixel. + let s = max(radius, 2.0) + // We will do blur on a resized image (*0.5), so the blur radius could be half as well. + + // Fix the slow compiling time for Swift 3. + // See https://github.com/onevcat/Kingfisher/issues/611 + let pi2 = 2 * CGFloat.pi + let sqrtPi2 = sqrt(pi2) + var targetRadius = floor(s * 3.0 * sqrtPi2 / 4.0 + 0.5) + + if targetRadius.isEven { targetRadius += 1 } + + // Determine necessary iteration count by blur radius. + let iterations: Int + if radius < 0.5 { + iterations = 1 + } else if radius < 1.5 { + iterations = 2 + } else { + iterations = 3 + } + + let w = Int(size.width) + let h = Int(size.height) + let rowBytes = Int(CGFloat(cgImage.bytesPerRow)) + + func createEffectBuffer(_ context: CGContext) -> vImage_Buffer { + let data = context.data + let width = vImagePixelCount(context.width) + let height = vImagePixelCount(context.height) + let rowBytes = context.bytesPerRow + + return vImage_Buffer(data: data, height: height, width: width, rowBytes: rowBytes) + } + + guard let context = beginContext(size: size, scale: scale, inverting: true) else { + assertionFailure("[Kingfisher] Failed to create CG context for blurring image.") + return base + } + context.draw(cgImage, in: CGRect(x: 0, y: 0, width: w, height: h)) + endContext() + + var inBuffer = createEffectBuffer(context) + + guard let outContext = beginContext(size: size, scale: scale, inverting: true) else { + assertionFailure("[Kingfisher] Failed to create CG context for blurring image.") + return base + } + defer { endContext() } + var outBuffer = createEffectBuffer(outContext) + + for _ in 0 ..< iterations { + let flag = vImage_Flags(kvImageEdgeExtend) + vImageBoxConvolve_ARGB8888( + &inBuffer, &outBuffer, nil, 0, 0, UInt32(targetRadius), UInt32(targetRadius), nil, flag) + // Next inBuffer should be the outButter of current iteration + (inBuffer, outBuffer) = (outBuffer, inBuffer) + } + + #if os(macOS) + let result = outContext.makeImage().flatMap { + fixedForRetinaPixel(cgImage: $0, to: size) + } + #else + let result = outContext.makeImage().flatMap { + KFCrossPlatformImage(cgImage: $0, scale: base.scale, orientation: base.imageOrientation) + } + #endif + guard let blurredImage = result else { + assertionFailure("[Kingfisher] Can not make an blurred image within this context.") + return base + } + + return blurredImage + } + + // MARK: Overlay + /// Creates an image from `base` image with a color overlay layer. + /// + /// - Parameters: + /// - color: The color should be use to overlay. + /// - fraction: Fraction of input color. From 0.0 to 1.0. 0.0 means solid color, + /// 1.0 means transparent overlay. + /// - Returns: An image with a color overlay applied. + /// + /// - Note: This method only works for CG-based image. The current image scale is kept. + /// For any non-CG-based image, `base` itself is returned. + public func overlaying(with color: KFCrossPlatformColor, fraction: CGFloat) -> KFCrossPlatformImage { + + guard let _ = cgImage else { + assertionFailure("[Kingfisher] Overlaying only works for CG-based image.") + return base + } + + let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) + return draw(to: rect.size) { context in + #if os(macOS) + base.draw(in: rect) + if fraction > 0 { + color.withAlphaComponent(1 - fraction).set() + rect.fill(using: .sourceAtop) + } + #else + color.set() + UIRectFill(rect) + base.draw(in: rect, blendMode: .destinationIn, alpha: 1.0) + + if fraction > 0 { + base.draw(in: rect, blendMode: .sourceAtop, alpha: fraction) + } + #endif + return false + } + } + + // MARK: Tint + /// Creates an image from `base` image with a color tint. + /// + /// - Parameter color: The color should be used to tint `base` + /// - Returns: An image with a color tint applied. + public func tinted(with color: KFCrossPlatformColor) -> KFCrossPlatformImage { + #if os(watchOS) + return base + #else + return apply(.tint(color)) + #endif + } + + // MARK: Color Control + + /// Create an image from `self` with color control. + /// + /// - Parameters: + /// - brightness: Brightness changing to image. + /// - contrast: Contrast changing to image. + /// - saturation: Saturation changing to image. + /// - inputEV: InputEV changing to image. + /// - Returns: An image with color control applied. + public func adjusted(brightness: CGFloat, contrast: CGFloat, saturation: CGFloat, inputEV: CGFloat) -> KFCrossPlatformImage { + #if os(watchOS) + return base + #else + return apply(.colorControl((brightness, contrast, saturation, inputEV))) + #endif + } + + /// Return an image with given scale. + /// + /// - Parameter scale: Target scale factor the new image should have. + /// - Returns: The image with target scale. If the base image is already in the scale, `base` will be returned. + public func scaled(to scale: CGFloat) -> KFCrossPlatformImage { + guard scale != self.scale else { + return base + } + guard let cgImage = cgImage else { + assertionFailure("[Kingfisher] Scaling only works for CG-based image.") + return base + } + return KingfisherWrapper.image(cgImage: cgImage, scale: scale, refImage: base) + } +} + +// MARK: - Decoding Image +extension KingfisherWrapper where Base: KFCrossPlatformImage { + + /// Returns the decoded image of the `base` image. It will draw the image in a plain context and return the data + /// from it. This could improve the drawing performance when an image is just created from data but not yet + /// displayed for the first time. + /// + /// - Note: This method only works for CG-based image. The current image scale is kept. + /// For any non-CG-based image or animated image, `base` itself is returned. + public var decoded: KFCrossPlatformImage { return decoded(scale: scale) } + + /// Returns decoded image of the `base` image at a given scale. It will draw the image in a plain context and + /// return the data from it. This could improve the drawing performance when an image is just created from + /// data but not yet displayed for the first time. + /// + /// - Parameter scale: The given scale of target image should be. + /// - Returns: The decoded image ready to be displayed. + /// + /// - Note: This method only works for CG-based image. The current image scale is kept. + /// For any non-CG-based image or animated image, `base` itself is returned. + public func decoded(scale: CGFloat) -> KFCrossPlatformImage { + // Prevent animated image (GIF) losing it's images + #if os(iOS) + if imageSource != nil { return base } + #else + if images != nil { return base } + #endif + + guard let imageRef = cgImage else { + assertionFailure("[Kingfisher] Decoding only works for CG-based image.") + return base + } + + let size = CGSize(width: CGFloat(imageRef.width) / scale, height: CGFloat(imageRef.height) / scale) + return draw(to: size, inverting: true, scale: scale) { context in + context.draw(imageRef, in: CGRect(origin: .zero, size: size)) + return true + } + } +} + +extension KingfisherWrapper where Base: KFCrossPlatformImage { + + func beginContext(size: CGSize, scale: CGFloat, inverting: Bool = false) -> CGContext? { + #if os(macOS) + guard let rep = NSBitmapImageRep( + bitmapDataPlanes: nil, + pixelsWide: Int(size.width), + pixelsHigh: Int(size.height), + bitsPerSample: cgImage?.bitsPerComponent ?? 8, + samplesPerPixel: 4, + hasAlpha: true, + isPlanar: false, + colorSpaceName: .calibratedRGB, + bytesPerRow: 0, + bitsPerPixel: 0) else + { + assertionFailure("[Kingfisher] Image representation cannot be created.") + return nil + } + rep.size = size + NSGraphicsContext.saveGraphicsState() + guard let context = NSGraphicsContext(bitmapImageRep: rep) else { + assertionFailure("[Kingfisher] Image context cannot be created.") + return nil + } + + NSGraphicsContext.current = context + return context.cgContext + #else + UIGraphicsBeginImageContextWithOptions(size, false, scale) + guard let context = UIGraphicsGetCurrentContext() else { return nil } + if inverting { // If drawing a CGImage, we need to make context flipped. + context.scaleBy(x: 1.0, y: -1.0) + context.translateBy(x: 0, y: -size.height) + } + return context + #endif + } + + func endContext() { + #if os(macOS) + NSGraphicsContext.restoreGraphicsState() + #else + UIGraphicsEndImageContext() + #endif + } + + func draw( + to size: CGSize, + inverting: Bool = false, + scale: CGFloat? = nil, + refImage: KFCrossPlatformImage? = nil, + draw: (CGContext) -> Bool // Whether use the refImage (`true`) or ignore image orientation (`false`) + ) -> KFCrossPlatformImage + { + let targetScale = scale ?? self.scale + guard let context = beginContext(size: size, scale: targetScale, inverting: inverting) else { + assertionFailure("[Kingfisher] Failed to create CG context for blurring image.") + return base + } + defer { endContext() } + let useRefImage = draw(context) + guard let cgImage = context.makeImage() else { + return base + } + let ref = useRefImage ? (refImage ?? base) : nil + return KingfisherWrapper.image(cgImage: cgImage, scale: targetScale, refImage: ref) + } + + #if os(macOS) + func fixedForRetinaPixel(cgImage: CGImage, to size: CGSize) -> KFCrossPlatformImage { + + let image = KFCrossPlatformImage(cgImage: cgImage, size: base.size) + let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: size) + + return draw(to: self.size) { context in + image.draw(in: rect, from: .zero, operation: .copy, fraction: 1.0) + return false + } + } + #endif +} diff --git a/!main project/Pods/Kingfisher/Sources/Image/ImageFormat.swift b/!main project/Pods/Kingfisher/Sources/Image/ImageFormat.swift new file mode 100644 index 0000000..14e3c7d --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Image/ImageFormat.swift @@ -0,0 +1,131 @@ +// +// ImageFormat.swift +// Kingfisher +// +// Created by onevcat on 2018/09/28. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +/// Represents image format. +/// +/// - unknown: The format cannot be recognized or not supported yet. +/// - PNG: PNG image format. +/// - JPEG: JPEG image format. +/// - GIF: GIF image format. +public enum ImageFormat { + /// The format cannot be recognized or not supported yet. + case unknown + /// PNG image format. + case PNG + /// JPEG image format. + case JPEG + /// GIF image format. + case GIF + + struct HeaderData { + static var PNG: [UInt8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A] + static var JPEG_SOI: [UInt8] = [0xFF, 0xD8] + static var JPEG_IF: [UInt8] = [0xFF] + static var GIF: [UInt8] = [0x47, 0x49, 0x46] + } + + /// https://en.wikipedia.org/wiki/JPEG + public enum JPEGMarker { + case SOF0 //baseline + case SOF2 //progressive + case DHT //Huffman Table + case DQT //Quantization Table + case DRI //Restart Interval + case SOS //Start Of Scan + case RSTn(UInt8) //Restart + case APPn //Application-specific + case COM //Comment + case EOI //End Of Image + + var bytes: [UInt8] { + switch self { + case .SOF0: return [0xFF, 0xC0] + case .SOF2: return [0xFF, 0xC2] + case .DHT: return [0xFF, 0xC4] + case .DQT: return [0xFF, 0xDB] + case .DRI: return [0xFF, 0xDD] + case .SOS: return [0xFF, 0xDA] + case .RSTn(let n): return [0xFF, 0xD0 + n] + case .APPn: return [0xFF, 0xE0] + case .COM: return [0xFF, 0xFE] + case .EOI: return [0xFF, 0xD9] + } + } + } +} + + +extension Data: KingfisherCompatibleValue {} + +// MARK: - Misc Helpers +extension KingfisherWrapper where Base == Data { + /// Gets the image format corresponding to the data. + public var imageFormat: ImageFormat { + guard base.count > 8 else { return .unknown } + + var buffer = [UInt8](repeating: 0, count: 8) + base.copyBytes(to: &buffer, count: 8) + + if buffer == ImageFormat.HeaderData.PNG { + return .PNG + + } else if buffer[0] == ImageFormat.HeaderData.JPEG_SOI[0], + buffer[1] == ImageFormat.HeaderData.JPEG_SOI[1], + buffer[2] == ImageFormat.HeaderData.JPEG_IF[0] + { + return .JPEG + + } else if buffer[0] == ImageFormat.HeaderData.GIF[0], + buffer[1] == ImageFormat.HeaderData.GIF[1], + buffer[2] == ImageFormat.HeaderData.GIF[2] + { + return .GIF + } + + return .unknown + } + + public func contains(jpeg marker: ImageFormat.JPEGMarker) -> Bool { + guard imageFormat == .JPEG else { + return false + } + + var buffer = [UInt8](repeating: 0, count: base.count) + base.copyBytes(to: &buffer, count: base.count) + for (index, item) in buffer.enumerated() { + guard + item == marker.bytes.first, + buffer.count > index + 1, + buffer[index + 1] == marker.bytes[1] else { + continue + } + return true + } + return false + } +} diff --git a/!main project/Pods/Kingfisher/Sources/Image/ImageProcessor.swift b/!main project/Pods/Kingfisher/Sources/Image/ImageProcessor.swift new file mode 100644 index 0000000..bbd1aaf --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Image/ImageProcessor.swift @@ -0,0 +1,845 @@ +// +// ImageProcessor.swift +// Kingfisher +// +// Created by Wei Wang on 2016/08/26. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation +import CoreGraphics + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit +#endif + +/// Represents an item which could be processed by an `ImageProcessor`. +/// +/// - image: Input image. The processor should provide a way to apply +/// processing on this `image` and return the result image. +/// - data: Input data. The processor should provide a way to apply +/// processing on this `image` and return the result image. +public enum ImageProcessItem { + + /// Input image. The processor should provide a way to apply + /// processing on this `image` and return the result image. + case image(KFCrossPlatformImage) + + /// Input data. The processor should provide a way to apply + /// processing on this `image` and return the result image. + case data(Data) +} + +/// An `ImageProcessor` would be used to convert some downloaded data to an image. +public protocol ImageProcessor { + /// Identifier of the processor. It will be used to identify the processor when + /// caching and retrieving an image. You might want to make sure that processors with + /// same properties/functionality have the same identifiers, so correct processed images + /// could be retrieved with proper key. + /// + /// - Note: Do not supply an empty string for a customized processor, which is already reserved by + /// the `DefaultImageProcessor`. It is recommended to use a reverse domain name notation string of + /// your own for the identifier. + var identifier: String { get } + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: The return value should be `nil` if processing failed while converting an input item to image. + /// If `nil` received by the processing caller, an error will be reported and the process flow stops. + /// If the processing flow is not critical for your flow, then when the input item is already an image + /// (`.image` case) and there is any errors in the processing, you could return the input image itself + /// to keep the processing pipeline continuing. + /// - Note: Most processor only supports CG-based images. watchOS is not supported for processors containing + /// a filter, the input image will be returned directly on watchOS. + /// - Note: + /// This method is deprecated. Please implement the version with + /// `KingfisherParsedOptionsInfo` as parameter instead. + @available(*, deprecated, + message: "Deprecated. Implement the method with same name but with `KingfisherParsedOptionsInfo` instead.") + func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> KFCrossPlatformImage? + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: The parsed options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: The return value should be `nil` if processing failed while converting an input item to image. + /// If `nil` received by the processing caller, an error will be reported and the process flow stops. + /// If the processing flow is not critical for your flow, then when the input item is already an image + /// (`.image` case) and there is any errors in the processing, you could return the input image itself + /// to keep the processing pipeline continuing. + /// - Note: Most processor only supports CG-based images. watchOS is not supported for processors containing + /// a filter, the input image will be returned directly on watchOS. + func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? +} + +extension ImageProcessor { + public func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> KFCrossPlatformImage? { + return process(item: item, options: KingfisherParsedOptionsInfo(options)) + } +} + +extension ImageProcessor { + + /// Appends an `ImageProcessor` to another. The identifier of the new `ImageProcessor` + /// will be "\(self.identifier)|>\(another.identifier)". + /// + /// - Parameter another: An `ImageProcessor` you want to append to `self`. + /// - Returns: The new `ImageProcessor` will process the image in the order + /// of the two processors concatenated. + public func append(another: ImageProcessor) -> ImageProcessor { + let newIdentifier = identifier.appending("|>\(another.identifier)") + return GeneralProcessor(identifier: newIdentifier) { + item, options in + if let image = self.process(item: item, options: options) { + return another.process(item: .image(image), options: options) + } else { + return nil + } + } + } +} + +func ==(left: ImageProcessor, right: ImageProcessor) -> Bool { + return left.identifier == right.identifier +} + +func !=(left: ImageProcessor, right: ImageProcessor) -> Bool { + return !(left == right) +} + +typealias ProcessorImp = ((ImageProcessItem, KingfisherParsedOptionsInfo) -> KFCrossPlatformImage?) +struct GeneralProcessor: ImageProcessor { + let identifier: String + let p: ProcessorImp + func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + return p(item, options) + } +} + +/// The default processor. It converts the input data to a valid image. +/// Images of .PNG, .JPEG and .GIF format are supported. +/// If an image item is given as `.image` case, `DefaultImageProcessor` will +/// do nothing on it and return the associated image. +public struct DefaultImageProcessor: ImageProcessor { + + /// A default `DefaultImageProcessor` could be used across. + public static let `default` = DefaultImageProcessor() + + /// Identifier of the processor. + /// - Note: See documentation of `ImageProcessor` protocol for more. + public let identifier = "" + + /// Creates a `DefaultImageProcessor`. Use `DefaultImageProcessor.default` to get an instance, + /// if you do not have a good reason to create your own `DefaultImageProcessor`. + public init() {} + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + switch item { + case .image(let image): + return image.kf.scaled(to: options.scaleFactor) + case .data(let data): + return KingfisherWrapper.image(data: data, options: options.imageCreatingOptions) + } + } +} + +/// Represents the rect corner setting when processing a round corner image. +public struct RectCorner: OptionSet { + + /// Raw value of the rect corner. + public let rawValue: Int + + /// Represents the top left corner. + public static let topLeft = RectCorner(rawValue: 1 << 0) + + /// Represents the top right corner. + public static let topRight = RectCorner(rawValue: 1 << 1) + + /// Represents the bottom left corner. + public static let bottomLeft = RectCorner(rawValue: 1 << 2) + + /// Represents the bottom right corner. + public static let bottomRight = RectCorner(rawValue: 1 << 3) + + /// Represents all corners. + public static let all: RectCorner = [.topLeft, .topRight, .bottomLeft, .bottomRight] + + /// Creates a `RectCorner` option set with a given value. + /// + /// - Parameter rawValue: The value represents a certain corner option. + public init(rawValue: Int) { + self.rawValue = rawValue + } + + var cornerIdentifier: String { + if self == .all { + return "" + } + return "_corner(\(rawValue))" + } +} + +#if !os(macOS) +/// Processor for adding an blend mode to images. Only CG-based images are supported. +public struct BlendImageProcessor: ImageProcessor { + + /// Identifier of the processor. + /// - Note: See documentation of `ImageProcessor` protocol for more. + public let identifier: String + + /// Blend Mode will be used to blend the input image. + public let blendMode: CGBlendMode + + /// Alpha will be used when blend image. + public let alpha: CGFloat + + /// Background color of the output image. If `nil`, it will stay transparent. + public let backgroundColor: KFCrossPlatformColor? + + /// Creates a `BlendImageProcessor`. + /// + /// - Parameters: + /// - blendMode: Blend Mode will be used to blend the input image. + /// - alpha: Alpha will be used when blend image. From 0.0 to 1.0. 1.0 means solid image, + /// 0.0 means transparent image (not visible at all). Default is 1.0. + /// - backgroundColor: Background color to apply for the output image. Default is `nil`. + public init(blendMode: CGBlendMode, alpha: CGFloat = 1.0, backgroundColor: KFCrossPlatformColor? = nil) { + self.blendMode = blendMode + self.alpha = alpha + self.backgroundColor = backgroundColor + var identifier = "com.onevcat.Kingfisher.BlendImageProcessor(\(blendMode.rawValue),\(alpha))" + if let color = backgroundColor { + identifier.append("_\(color.hex)") + } + self.identifier = identifier + } + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + switch item { + case .image(let image): + return image.kf.scaled(to: options.scaleFactor) + .kf.image(withBlendMode: blendMode, alpha: alpha, backgroundColor: backgroundColor) + case .data: + return (DefaultImageProcessor.default |> self).process(item: item, options: options) + } + } +} +#endif + +#if os(macOS) +/// Processor for adding an compositing operation to images. Only CG-based images are supported in macOS. +public struct CompositingImageProcessor: ImageProcessor { + + /// Identifier of the processor. + /// - Note: See documentation of `ImageProcessor` protocol for more. + public let identifier: String + + /// Compositing operation will be used to the input image. + public let compositingOperation: NSCompositingOperation + + /// Alpha will be used when compositing image. + public let alpha: CGFloat + + /// Background color of the output image. If `nil`, it will stay transparent. + public let backgroundColor: KFCrossPlatformColor? + + /// Creates a `CompositingImageProcessor` + /// + /// - Parameters: + /// - compositingOperation: Compositing operation will be used to the input image. + /// - alpha: Alpha will be used when compositing image. + /// From 0.0 to 1.0. 1.0 means solid image, 0.0 means transparent image. + /// Default is 1.0. + /// - backgroundColor: Background color to apply for the output image. Default is `nil`. + public init(compositingOperation: NSCompositingOperation, + alpha: CGFloat = 1.0, + backgroundColor: KFCrossPlatformColor? = nil) + { + self.compositingOperation = compositingOperation + self.alpha = alpha + self.backgroundColor = backgroundColor + var identifier = "com.onevcat.Kingfisher.CompositingImageProcessor(\(compositingOperation.rawValue),\(alpha))" + if let color = backgroundColor { + identifier.append("_\(color.hex)") + } + self.identifier = identifier + } + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + switch item { + case .image(let image): + return image.kf.scaled(to: options.scaleFactor) + .kf.image( + withCompositingOperation: compositingOperation, + alpha: alpha, + backgroundColor: backgroundColor) + case .data: + return (DefaultImageProcessor.default |> self).process(item: item, options: options) + } + } +} +#endif + +/// Processor for making round corner images. Only CG-based images are supported in macOS, +/// if a non-CG image passed in, the processor will do nothing. +/// +/// Note: The input image will be rendered with round corner pixels removed. If the image itself does not contain +/// alpha channel (for example, a JPEG image), the processed image will contain an alpha channel in memory in order +/// to show correctly. However, when cached into disk, the image format will be respected and the alpha channel will +/// be removed. That means when you load the processed image from cache again, you will lose transparent corner. +/// You could use `FormatIndicatedCacheSerializer.png` to force Kingfisher to serialize the image to PNG format in this +/// case. +public struct RoundCornerImageProcessor: ImageProcessor { + + /// Identifier of the processor. + /// - Note: See documentation of `ImageProcessor` protocol for more. + public let identifier: String + + /// Corner radius will be applied in processing. + public let cornerRadius: CGFloat + + /// The target corners which will be applied rounding. + public let roundingCorners: RectCorner + + /// Target size of output image should be. If `nil`, the image will keep its original size after processing. + public let targetSize: CGSize? + + /// Background color of the output image. If `nil`, it will use a transparent background. + public let backgroundColor: KFCrossPlatformColor? + + /// Creates a `RoundCornerImageProcessor`. + /// + /// - Parameters: + /// - cornerRadius: Corner radius will be applied in processing. + /// - targetSize: Target size of output image should be. If `nil`, + /// the image will keep its original size after processing. + /// Default is `nil`. + /// - corners: The target corners which will be applied rounding. Default is `.all`. + /// - backgroundColor: Background color to apply for the output image. Default is `nil`. + public init( + cornerRadius: CGFloat, + targetSize: CGSize? = nil, + roundingCorners corners: RectCorner = .all, + backgroundColor: KFCrossPlatformColor? = nil) + { + self.cornerRadius = cornerRadius + self.targetSize = targetSize + self.roundingCorners = corners + self.backgroundColor = backgroundColor + + self.identifier = { + var identifier = "" + + if let size = targetSize { + identifier = "com.onevcat.Kingfisher.RoundCornerImageProcessor" + + "(\(cornerRadius)_\(size)\(corners.cornerIdentifier))" + } else { + identifier = "com.onevcat.Kingfisher.RoundCornerImageProcessor" + + "(\(cornerRadius)\(corners.cornerIdentifier))" + } + if let backgroundColor = backgroundColor { + identifier += "_\(backgroundColor)" + } + + return identifier + }() + } + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + switch item { + case .image(let image): + let size = targetSize ?? image.kf.size + return image.kf.scaled(to: options.scaleFactor) + .kf.image( + withRoundRadius: cornerRadius, + fit: size, + roundingCorners: roundingCorners, + backgroundColor: backgroundColor) + case .data: + return (DefaultImageProcessor.default |> self).process(item: item, options: options) + } + } +} + + +/// Represents how a size adjusts itself to fit a target size. +/// +/// - none: Not scale the content. +/// - aspectFit: Scales the content to fit the size of the view by maintaining the aspect ratio. +/// - aspectFill: Scales the content to fill the size of the view. +public enum ContentMode { + /// Not scale the content. + case none + /// Scales the content to fit the size of the view by maintaining the aspect ratio. + case aspectFit + /// Scales the content to fill the size of the view. + case aspectFill +} + +/// Processor for resizing images. +/// If you need to resize a data represented image to a smaller size, use `DownsamplingImageProcessor` +/// instead, which is more efficient and takes less memory. +public struct ResizingImageProcessor: ImageProcessor { + + /// Identifier of the processor. + /// - Note: See documentation of `ImageProcessor` protocol for more. + public let identifier: String + + /// The reference size for resizing operation in point. + public let referenceSize: CGSize + + /// Target content mode of output image should be. + /// Default is `.none`. + public let targetContentMode: ContentMode + + /// Creates a `ResizingImageProcessor`. + /// + /// - Parameters: + /// - referenceSize: The reference size for resizing operation in point. + /// - mode: Target content mode of output image should be. + /// + /// - Note: + /// The instance of `ResizingImageProcessor` will follow its `mode` property + /// and try to resizing the input images to fit or fill the `referenceSize`. + /// That means if you are using a `mode` besides of `.none`, you may get an + /// image with its size not be the same as the `referenceSize`. + /// + /// **Example**: With input image size: {100, 200}, + /// `referenceSize`: {100, 100}, `mode`: `.aspectFit`, + /// you will get an output image with size of {50, 100}, which "fit"s + /// the `referenceSize`. + /// + /// If you need an output image exactly to be a specified size, append or use + /// a `CroppingImageProcessor`. + public init(referenceSize: CGSize, mode: ContentMode = .none) { + self.referenceSize = referenceSize + self.targetContentMode = mode + + if mode == .none { + self.identifier = "com.onevcat.Kingfisher.ResizingImageProcessor(\(referenceSize))" + } else { + self.identifier = "com.onevcat.Kingfisher.ResizingImageProcessor(\(referenceSize), \(mode))" + } + } + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + switch item { + case .image(let image): + return image.kf.scaled(to: options.scaleFactor) + .kf.resize(to: referenceSize, for: targetContentMode) + case .data: + return (DefaultImageProcessor.default |> self).process(item: item, options: options) + } + } +} + +/// Processor for adding blur effect to images. `Accelerate.framework` is used underhood for +/// a better performance. A simulated Gaussian blur with specified blur radius will be applied. +public struct BlurImageProcessor: ImageProcessor { + + /// Identifier of the processor. + /// - Note: See documentation of `ImageProcessor` protocol for more. + public let identifier: String + + /// Blur radius for the simulated Gaussian blur. + public let blurRadius: CGFloat + + /// Creates a `BlurImageProcessor` + /// + /// - parameter blurRadius: Blur radius for the simulated Gaussian blur. + public init(blurRadius: CGFloat) { + self.blurRadius = blurRadius + self.identifier = "com.onevcat.Kingfisher.BlurImageProcessor(\(blurRadius))" + } + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + switch item { + case .image(let image): + let radius = blurRadius * options.scaleFactor + return image.kf.scaled(to: options.scaleFactor) + .kf.blurred(withRadius: radius) + case .data: + return (DefaultImageProcessor.default |> self).process(item: item, options: options) + } + } +} + +/// Processor for adding an overlay to images. Only CG-based images are supported in macOS. +public struct OverlayImageProcessor: ImageProcessor { + + /// Identifier of the processor. + /// - Note: See documentation of `ImageProcessor` protocol for more. + public let identifier: String + + /// Overlay color will be used to overlay the input image. + public let overlay: KFCrossPlatformColor + + /// Fraction will be used when overlay the color to image. + public let fraction: CGFloat + + /// Creates an `OverlayImageProcessor` + /// + /// - parameter overlay: Overlay color will be used to overlay the input image. + /// - parameter fraction: Fraction will be used when overlay the color to image. + /// From 0.0 to 1.0. 0.0 means solid color, 1.0 means transparent overlay. + public init(overlay: KFCrossPlatformColor, fraction: CGFloat = 0.5) { + self.overlay = overlay + self.fraction = fraction + self.identifier = "com.onevcat.Kingfisher.OverlayImageProcessor(\(overlay.hex)_\(fraction))" + } + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + switch item { + case .image(let image): + return image.kf.scaled(to: options.scaleFactor) + .kf.overlaying(with: overlay, fraction: fraction) + case .data: + return (DefaultImageProcessor.default |> self).process(item: item, options: options) + } + } +} + +/// Processor for tint images with color. Only CG-based images are supported. +public struct TintImageProcessor: ImageProcessor { + + /// Identifier of the processor. + /// - Note: See documentation of `ImageProcessor` protocol for more. + public let identifier: String + + /// Tint color will be used to tint the input image. + public let tint: KFCrossPlatformColor + + /// Creates a `TintImageProcessor` + /// + /// - parameter tint: Tint color will be used to tint the input image. + public init(tint: KFCrossPlatformColor) { + self.tint = tint + self.identifier = "com.onevcat.Kingfisher.TintImageProcessor(\(tint.hex))" + } + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + switch item { + case .image(let image): + return image.kf.scaled(to: options.scaleFactor) + .kf.tinted(with: tint) + case .data: + return (DefaultImageProcessor.default |> self).process(item: item, options: options) + } + } +} + +/// Processor for applying some color control to images. Only CG-based images are supported. +/// watchOS is not supported. +public struct ColorControlsProcessor: ImageProcessor { + + /// Identifier of the processor. + /// - Note: See documentation of `ImageProcessor` protocol for more. + public let identifier: String + + /// Brightness changing to image. + public let brightness: CGFloat + + /// Contrast changing to image. + public let contrast: CGFloat + + /// Saturation changing to image. + public let saturation: CGFloat + + /// InputEV changing to image. + public let inputEV: CGFloat + + /// Creates a `ColorControlsProcessor` + /// + /// - Parameters: + /// - brightness: Brightness changing to image. + /// - contrast: Contrast changing to image. + /// - saturation: Saturation changing to image. + /// - inputEV: InputEV changing to image. + public init(brightness: CGFloat, contrast: CGFloat, saturation: CGFloat, inputEV: CGFloat) { + self.brightness = brightness + self.contrast = contrast + self.saturation = saturation + self.inputEV = inputEV + self.identifier = "com.onevcat.Kingfisher.ColorControlsProcessor(\(brightness)_\(contrast)_\(saturation)_\(inputEV))" + } + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + switch item { + case .image(let image): + return image.kf.scaled(to: options.scaleFactor) + .kf.adjusted(brightness: brightness, contrast: contrast, saturation: saturation, inputEV: inputEV) + case .data: + return (DefaultImageProcessor.default |> self).process(item: item, options: options) + } + } +} + +/// Processor for applying black and white effect to images. Only CG-based images are supported. +/// watchOS is not supported. +public struct BlackWhiteProcessor: ImageProcessor { + + /// Identifier of the processor. + /// - Note: See documentation of `ImageProcessor` protocol for more. + public let identifier = "com.onevcat.Kingfisher.BlackWhiteProcessor" + + /// Creates a `BlackWhiteProcessor` + public init() {} + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + return ColorControlsProcessor(brightness: 0.0, contrast: 1.0, saturation: 0.0, inputEV: 0.7) + .process(item: item, options: options) + } +} + +/// Processor for cropping an image. Only CG-based images are supported. +/// watchOS is not supported. +public struct CroppingImageProcessor: ImageProcessor { + + /// Identifier of the processor. + /// - Note: See documentation of `ImageProcessor` protocol for more. + public let identifier: String + + /// Target size of output image should be. + public let size: CGSize + + /// Anchor point from which the output size should be calculate. + /// The anchor point is consisted by two values between 0.0 and 1.0. + /// It indicates a related point in current image. + /// See `CroppingImageProcessor.init(size:anchor:)` for more. + public let anchor: CGPoint + + /// Creates a `CroppingImageProcessor`. + /// + /// - Parameters: + /// - size: Target size of output image should be. + /// - anchor: The anchor point from which the size should be calculated. + /// Default is `CGPoint(x: 0.5, y: 0.5)`, which means the center of input image. + /// - Note: + /// The anchor point is consisted by two values between 0.0 and 1.0. + /// It indicates a related point in current image, eg: (0.0, 0.0) for top-left + /// corner, (0.5, 0.5) for center and (1.0, 1.0) for bottom-right corner. + /// The `size` property of `CroppingImageProcessor` will be used along with + /// `anchor` to calculate a target rectangle in the size of image. + /// + /// The target size will be automatically calculated with a reasonable behavior. + /// For example, when you have an image size of `CGSize(width: 100, height: 100)`, + /// and a target size of `CGSize(width: 20, height: 20)`: + /// - with a (0.0, 0.0) anchor (top-left), the crop rect will be `{0, 0, 20, 20}`; + /// - with a (0.5, 0.5) anchor (center), it will be `{40, 40, 20, 20}` + /// - while with a (1.0, 1.0) anchor (bottom-right), it will be `{80, 80, 20, 20}` + public init(size: CGSize, anchor: CGPoint = CGPoint(x: 0.5, y: 0.5)) { + self.size = size + self.anchor = anchor + self.identifier = "com.onevcat.Kingfisher.CroppingImageProcessor(\(size)_\(anchor))" + } + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + switch item { + case .image(let image): + return image.kf.scaled(to: options.scaleFactor) + .kf.crop(to: size, anchorOn: anchor) + case .data: return (DefaultImageProcessor.default |> self).process(item: item, options: options) + } + } +} + +/// Processor for downsampling an image. Compared to `ResizingImageProcessor`, this processor +/// does not render the images to resize. Instead, it downsample the input data directly to an +/// image. It is a more efficient than `ResizingImageProcessor`. +/// +/// Only CG-based images are supported. Animated images (like GIF) is not supported. +public struct DownsamplingImageProcessor: ImageProcessor { + + /// Target size of output image should be. It should be smaller than the size of + /// input image. If it is larger, the result image will be the same size of input + /// data without downsampling. + public let size: CGSize + + /// Identifier of the processor. + /// - Note: See documentation of `ImageProcessor` protocol for more. + public let identifier: String + + /// Creates a `DownsamplingImageProcessor`. + /// + /// - Parameter size: The target size of the downsample operation. + public init(size: CGSize) { + self.size = size + self.identifier = "com.onevcat.Kingfisher.DownsamplingImageProcessor(\(size))" + } + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + switch item { + case .image(let image): + guard let data = image.kf.data(format: .unknown) else { + return nil + } + return KingfisherWrapper.downsampledImage(data: data, to: size, scale: options.scaleFactor) + case .data(let data): + return KingfisherWrapper.downsampledImage(data: data, to: size, scale: options.scaleFactor) + } + } +} + +/// Concatenates two `ImageProcessor`s. `ImageProcessor.append(another:)` is used internally. +/// +/// - Parameters: +/// - left: The first processor. +/// - right: The second processor. +/// - Returns: The concatenated processor. +@available(*, deprecated, +message: "Will be removed soon. Use `|>` instead.", +renamed: "|>") +public func >>(left: ImageProcessor, right: ImageProcessor) -> ImageProcessor { + return left.append(another: right) +} + +infix operator |>: AdditionPrecedence +public func |>(left: ImageProcessor, right: ImageProcessor) -> ImageProcessor { + return left.append(another: right) +} + +extension KFCrossPlatformColor { + var hex: String { + var r: CGFloat = 0 + var g: CGFloat = 0 + var b: CGFloat = 0 + var a: CGFloat = 0 + + #if os(macOS) + (usingColorSpace(.sRGB) ?? self).getRed(&r, green: &g, blue: &b, alpha: &a) + #else + getRed(&r, green: &g, blue: &b, alpha: &a) + #endif + + let rInt = Int(r * 255) << 24 + let gInt = Int(g * 255) << 16 + let bInt = Int(b * 255) << 8 + let aInt = Int(a * 255) + + let rgba = rInt | gInt | bInt | aInt + + return String(format:"#%08x", rgba) + } +} diff --git a/!main project/Pods/Kingfisher/Sources/Image/ImageProgressive.swift b/!main project/Pods/Kingfisher/Sources/Image/ImageProgressive.swift new file mode 100644 index 0000000..2baa5e3 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Image/ImageProgressive.swift @@ -0,0 +1,321 @@ +// +// ImageProgressive.swift +// Kingfisher +// +// Created by lixiang on 2019/5/10. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation +import CoreGraphics + +private let sharedProcessingQueue: CallbackQueue = + .dispatch(DispatchQueue(label: "com.onevcat.Kingfisher.ImageDownloader.Process")) + +public struct ImageProgressive { + + /// A default `ImageProgressive` could be used across. + public static let `default` = ImageProgressive( + isBlur: true, + isFastestScan: true, + scanInterval: 0 + ) + + /// Whether to enable blur effect processing + let isBlur: Bool + /// Whether to enable the fastest scan + let isFastestScan: Bool + /// Minimum time interval for each scan + let scanInterval: TimeInterval + + public init(isBlur: Bool, + isFastestScan: Bool, + scanInterval: TimeInterval) { + self.isBlur = isBlur + self.isFastestScan = isFastestScan + self.scanInterval = scanInterval + } +} + +protocol ImageSettable: AnyObject { + var image: KFCrossPlatformImage? { get set } +} + +final class ImageProgressiveProvider: DataReceivingSideEffect { + + var onShouldApply: () -> Bool = { return true } + + func onDataReceived(_ session: URLSession, task: SessionDataTask, data: Data) { + + DispatchQueue.main.async { + guard self.onShouldApply() else { return } + self.update(data: task.mutableData, with: task.callbacks) + } + } + + private let option: ImageProgressive + private let refresh: (KFCrossPlatformImage) -> Void + + private let decoder: ImageProgressiveDecoder + private let queue = ImageProgressiveSerialQueue() + + init?(_ options: KingfisherParsedOptionsInfo, + refresh: @escaping (KFCrossPlatformImage) -> Void) { + guard let option = options.progressiveJPEG else { return nil } + + self.option = option + self.refresh = refresh + self.decoder = ImageProgressiveDecoder( + option, + processingQueue: options.processingQueue ?? sharedProcessingQueue, + creatingOptions: options.imageCreatingOptions + ) + } + + func update(data: Data, with callbacks: [SessionDataTask.TaskCallback]) { + guard !data.isEmpty else { return } + + queue.add(minimum: option.scanInterval) { completion in + + func decode(_ data: Data) { + self.decoder.decode(data, with: callbacks) { image in + defer { completion() } + guard self.onShouldApply() else { return } + guard let image = image else { return } + self.refresh(image) + } + } + + let semaphore = DispatchSemaphore(value: 0) + var onShouldApply: Bool = false + + CallbackQueue.mainAsync.execute { + onShouldApply = self.onShouldApply() + semaphore.signal() + } + semaphore.wait() + guard onShouldApply else { + self.queue.clean() + completion() + return + } + + if self.option.isFastestScan { + decode(self.decoder.scanning(data) ?? Data()) + } else { + self.decoder.scanning(data).forEach { decode($0) } + } + } + } +} + +private final class ImageProgressiveDecoder { + + private let option: ImageProgressive + private let processingQueue: CallbackQueue + private let creatingOptions: ImageCreatingOptions + private(set) var scannedCount = 0 + private(set) var scannedIndex = -1 + + init(_ option: ImageProgressive, + processingQueue: CallbackQueue, + creatingOptions: ImageCreatingOptions) { + self.option = option + self.processingQueue = processingQueue + self.creatingOptions = creatingOptions + } + + func scanning(_ data: Data) -> [Data] { + guard data.kf.contains(jpeg: .SOF2) else { + return [] + } + guard scannedIndex + 1 < data.count else { + return [] + } + + var datas: [Data] = [] + var index = scannedIndex + 1 + var count = scannedCount + + while index < data.count - 1 { + scannedIndex = index + // 0xFF, 0xDA - Start Of Scan + let SOS = ImageFormat.JPEGMarker.SOS.bytes + if data[index] == SOS[0], data[index + 1] == SOS[1] { + if count > 0 { + datas.append(data[0 ..< index]) + } + count += 1 + } + index += 1 + } + + // Found more scans this the previous time + guard count > scannedCount else { return [] } + scannedCount = count + + // `> 1` checks that we've received a first scan (SOS) and then received + // and also received a second scan (SOS). This way we know that we have + // at least one full scan available. + guard count > 1 else { return [] } + return datas + } + + func scanning(_ data: Data) -> Data? { + guard data.kf.contains(jpeg: .SOF2) else { + return nil + } + guard scannedIndex + 1 < data.count else { + return nil + } + + var index = scannedIndex + 1 + var count = scannedCount + var lastSOSIndex = 0 + + while index < data.count - 1 { + scannedIndex = index + // 0xFF, 0xDA - Start Of Scan + let SOS = ImageFormat.JPEGMarker.SOS.bytes + if data[index] == SOS[0], data[index + 1] == SOS[1] { + lastSOSIndex = index + count += 1 + } + index += 1 + } + + // Found more scans this the previous time + guard count > scannedCount else { return nil } + scannedCount = count + + // `> 1` checks that we've received a first scan (SOS) and then received + // and also received a second scan (SOS). This way we know that we have + // at least one full scan available. + guard count > 1 && lastSOSIndex > 0 else { return nil } + return data[0 ..< lastSOSIndex] + } + + func decode(_ data: Data, + with callbacks: [SessionDataTask.TaskCallback], + completion: @escaping (KFCrossPlatformImage?) -> Void) { + guard data.kf.contains(jpeg: .SOF2) else { + CallbackQueue.mainCurrentOrAsync.execute { completion(nil) } + return + } + + func processing(_ data: Data) { + let processor = ImageDataProcessor( + data: data, + callbacks: callbacks, + processingQueue: processingQueue + ) + processor.onImageProcessed.delegate(on: self) { (self, result) in + guard let image = try? result.0.get() else { + CallbackQueue.mainCurrentOrAsync.execute { completion(nil) } + return + } + + CallbackQueue.mainCurrentOrAsync.execute { completion(image) } + } + processor.process() + } + + // Blur partial images. + let count = scannedCount + + if option.isBlur, count < 6 { + processingQueue.execute { + // Progressively reduce blur as we load more scans. + let image = KingfisherWrapper.image( + data: data, + options: self.creatingOptions + ) + let radius = max(2, 14 - count * 4) + let temp = image?.kf.blurred(withRadius: CGFloat(radius)) + processing(temp?.kf.data(format: .JPEG) ?? data) + } + + } else { + processing(data) + } + } +} + +private final class ImageProgressiveSerialQueue { + typealias ClosureCallback = ((@escaping () -> Void)) -> Void + + private let queue: DispatchQueue = .init(label: "com.onevcat.Kingfisher.ImageProgressive.SerialQueue") + private var items: [DispatchWorkItem] = [] + private var notify: (() -> Void)? + private var lastTime: TimeInterval? + var count: Int { return items.count } + + func add(minimum interval: TimeInterval, closure: @escaping ClosureCallback) { + let completion = { [weak self] in + guard let self = self else { return } + + self.queue.async { [weak self] in + guard let self = self else { return } + guard !self.items.isEmpty else { return } + + self.items.removeFirst() + + if let next = self.items.first { + self.queue.asyncAfter( + deadline: .now() + interval, + execute: next + ) + + } else { + self.lastTime = Date().timeIntervalSince1970 + self.notify?() + self.notify = nil + } + } + } + + queue.async { [weak self] in + guard let self = self else { return } + + let item = DispatchWorkItem { + closure(completion) + } + if self.items.isEmpty { + let difference = Date().timeIntervalSince1970 - (self.lastTime ?? 0) + let delay = difference < interval ? interval - difference : 0 + self.queue.asyncAfter(deadline: .now() + delay, execute: item) + } + self.items.append(item) + } + } + + func notify(_ closure: @escaping () -> Void) { + self.notify = closure + } + + func clean() { + queue.async { [weak self] in + guard let self = self else { return } + self.items.forEach { $0.cancel() } + self.items.removeAll() + } + } +} diff --git a/!main project/Pods/Kingfisher/Sources/Image/ImageTransition.swift b/!main project/Pods/Kingfisher/Sources/Image/ImageTransition.swift new file mode 100644 index 0000000..c13a9d2 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Image/ImageTransition.swift @@ -0,0 +1,115 @@ +// +// ImageTransition.swift +// Kingfisher +// +// Created by Wei Wang on 15/9/18. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#if os(iOS) || os(tvOS) +import UIKit + +/// Transition effect which will be used when an image downloaded and set by `UIImageView` +/// extension API in Kingfisher. You can assign an enum value with transition duration as +/// an item in `KingfisherOptionsInfo` to enable the animation transition. +/// +/// Apple's UIViewAnimationOptions is used under the hood. +/// For custom transition, you should specified your own transition options, animations and +/// completion handler as well. +/// +/// - none: No animation transition. +/// - fade: Fade in the loaded image in a given duration. +/// - flipFromLeft: Flip from left transition. +/// - flipFromRight: Flip from right transition. +/// - flipFromTop: Flip from top transition. +/// - flipFromBottom: Flip from bottom transition. +/// - custom: Custom transition. +public enum ImageTransition { + /// No animation transition. + case none + /// Fade in the loaded image in a given duration. + case fade(TimeInterval) + /// Flip from left transition. + case flipFromLeft(TimeInterval) + /// Flip from right transition. + case flipFromRight(TimeInterval) + /// Flip from top transition. + case flipFromTop(TimeInterval) + /// Flip from bottom transition. + case flipFromBottom(TimeInterval) + /// Custom transition defined by a general animation block. + /// - duration: The time duration of this custom transition. + /// - options: `UIView.AnimationOptions` should be used in the transition. + /// - animations: The animation block will be applied when setting image. + /// - completion: A block called when the transition animation finishes. + case custom(duration: TimeInterval, + options: UIView.AnimationOptions, + animations: ((UIImageView, UIImage) -> Void)?, + completion: ((Bool) -> Void)?) + + var duration: TimeInterval { + switch self { + case .none: return 0 + case .fade(let duration): return duration + + case .flipFromLeft(let duration): return duration + case .flipFromRight(let duration): return duration + case .flipFromTop(let duration): return duration + case .flipFromBottom(let duration): return duration + + case .custom(let duration, _, _, _): return duration + } + } + + var animationOptions: UIView.AnimationOptions { + switch self { + case .none: return [] + case .fade: return .transitionCrossDissolve + + case .flipFromLeft: return .transitionFlipFromLeft + case .flipFromRight: return .transitionFlipFromRight + case .flipFromTop: return .transitionFlipFromTop + case .flipFromBottom: return .transitionFlipFromBottom + + case .custom(_, let options, _, _): return options + } + } + + var animations: ((UIImageView, UIImage) -> Void)? { + switch self { + case .custom(_, _, let animations, _): return animations + default: return { $0.image = $1 } + } + } + + var completion: ((Bool) -> Void)? { + switch self { + case .custom(_, _, _, let completion): return completion + default: return nil + } + } +} +#else +// Just a placeholder for compiling on macOS. +public enum ImageTransition { + case none +} +#endif diff --git a/!main project/Pods/Kingfisher/Sources/Image/Placeholder.swift b/!main project/Pods/Kingfisher/Sources/Image/Placeholder.swift new file mode 100644 index 0000000..1179815 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Image/Placeholder.swift @@ -0,0 +1,80 @@ +// +// Placeholder.swift +// Kingfisher +// +// Created by Tieme van Veen on 28/08/2017. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#if !os(watchOS) + +#if os(macOS) +import AppKit +#else +import UIKit +#endif + +/// Represents a placeholder type which could be set while loading as well as +/// loading finished without getting an image. +public protocol Placeholder { + + /// How the placeholder should be added to a given image view. + func add(to imageView: KFCrossPlatformImageView) + + /// How the placeholder should be removed from a given image view. + func remove(from imageView: KFCrossPlatformImageView) +} + +/// Default implementation of an image placeholder. The image will be set or +/// reset directly for `image` property of the image view. +extension KFCrossPlatformImage: Placeholder { + /// How the placeholder should be added to a given image view. + public func add(to imageView: KFCrossPlatformImageView) { imageView.image = self } + + /// How the placeholder should be removed from a given image view. + public func remove(from imageView: KFCrossPlatformImageView) { imageView.image = nil } +} + +/// Default implementation of an arbitrary view as placeholder. The view will be +/// added as a subview when adding and be removed from its super view when removing. +/// +/// To use your customize View type as placeholder, simply let it conforming to +/// `Placeholder` by `extension MyView: Placeholder {}`. +extension Placeholder where Self: KFCrossPlatformView { + + /// How the placeholder should be added to a given image view. + public func add(to imageView: KFCrossPlatformImageView) { + imageView.addSubview(self) + translatesAutoresizingMaskIntoConstraints = false + + centerXAnchor.constraint(equalTo: imageView.centerXAnchor).isActive = true + centerYAnchor.constraint(equalTo: imageView.centerYAnchor).isActive = true + heightAnchor.constraint(equalTo: imageView.heightAnchor).isActive = true + widthAnchor.constraint(equalTo: imageView.widthAnchor).isActive = true + } + + /// How the placeholder should be removed from a given image view. + public func remove(from imageView: KFCrossPlatformImageView) { + removeFromSuperview() + } +} + +#endif diff --git a/!main project/Pods/Kingfisher/Sources/Kingfisher.h b/!main project/Pods/Kingfisher/Sources/Kingfisher.h new file mode 100644 index 0000000..356adde --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Kingfisher.h @@ -0,0 +1,37 @@ +// +// Kingfisher.h +// Kingfisher +// +// Created by Wei Wang on 15/4/6. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +//! Project version number for Kingfisher. +FOUNDATION_EXPORT double KingfisherVersionNumber; + +//! Project version string for Kingfisher. +FOUNDATION_EXPORT const unsigned char KingfisherVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/!main project/Pods/Kingfisher/Sources/Networking/AuthenticationChallengeResponsable.swift b/!main project/Pods/Kingfisher/Sources/Networking/AuthenticationChallengeResponsable.swift new file mode 100644 index 0000000..5f6fc57 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Networking/AuthenticationChallengeResponsable.swift @@ -0,0 +1,91 @@ +// +// AuthenticationChallengeResponsable.swift +// Kingfisher +// +// Created by Wei Wang on 2018/10/11. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +/// Protocol indicates that an authentication challenge could be handled. +public protocol AuthenticationChallengeResponsable: AnyObject { + + /// Called when a session level authentication challenge is received. + /// This method provide a chance to handle and response to the authentication + /// challenge before downloading could start. + /// + /// - Parameters: + /// - downloader: The downloader which receives this challenge. + /// - challenge: An object that contains the request for authentication. + /// - completionHandler: A handler that your delegate method must call. + /// + /// - Note: This method is a forward from `URLSessionDelegate.urlSession(:didReceiveChallenge:completionHandler:)`. + /// Please refer to the document of it in `URLSessionDelegate`. + func downloader( + _ downloader: ImageDownloader, + didReceive challenge: URLAuthenticationChallenge, + completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) + + /// Called when a task level authentication challenge is received. + /// This method provide a chance to handle and response to the authentication + /// challenge before downloading could start. + /// + /// - Parameters: + /// - downloader: The downloader which receives this challenge. + /// - task: The task whose request requires authentication. + /// - challenge: An object that contains the request for authentication. + /// - completionHandler: A handler that your delegate method must call. + func downloader( + _ downloader: ImageDownloader, + task: URLSessionTask, + didReceive challenge: URLAuthenticationChallenge, + completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) +} + +extension AuthenticationChallengeResponsable { + + public func downloader( + _ downloader: ImageDownloader, + didReceive challenge: URLAuthenticationChallenge, + completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) + { + if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust { + if let trustedHosts = downloader.trustedHosts, trustedHosts.contains(challenge.protectionSpace.host) { + let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!) + completionHandler(.useCredential, credential) + return + } + } + + completionHandler(.performDefaultHandling, nil) + } + + public func downloader( + _ downloader: ImageDownloader, + task: URLSessionTask, + didReceive challenge: URLAuthenticationChallenge, + completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) + { + completionHandler(.performDefaultHandling, nil) + } + +} diff --git a/!main project/Pods/Kingfisher/Sources/Networking/ImageDataProcessor.swift b/!main project/Pods/Kingfisher/Sources/Networking/ImageDataProcessor.swift new file mode 100644 index 0000000..51e9705 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Networking/ImageDataProcessor.swift @@ -0,0 +1,80 @@ +// +// ImageDataProcessor.swift +// Kingfisher +// +// Created by Wei Wang on 2018/10/11. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +private let sharedProcessingQueue: CallbackQueue = + .dispatch(DispatchQueue(label: "com.onevcat.Kingfisher.ImageDownloader.Process")) + +// Handles image processing work on an own process queue. +class ImageDataProcessor { + let data: Data + let callbacks: [SessionDataTask.TaskCallback] + let queue: CallbackQueue + + // Note: We have an optimization choice there, to reduce queue dispatch by checking callback + // queue settings in each option... + let onImageProcessed = Delegate<(Result, SessionDataTask.TaskCallback), Void>() + + init(data: Data, callbacks: [SessionDataTask.TaskCallback], processingQueue: CallbackQueue?) { + self.data = data + self.callbacks = callbacks + self.queue = processingQueue ?? sharedProcessingQueue + } + + func process() { + queue.execute(doProcess) + } + + private func doProcess() { + var processedImages = [String: KFCrossPlatformImage]() + for callback in callbacks { + let processor = callback.options.processor + var image = processedImages[processor.identifier] + if image == nil { + image = processor.process(item: .data(data), options: callback.options) + processedImages[processor.identifier] = image + } + + let result: Result + if let image = image { + var finalImage = image + if let imageModifier = callback.options.imageModifier { + finalImage = imageModifier.modify(image) + } + if callback.options.backgroundDecode { + finalImage = finalImage.kf.decoded + } + result = .success(finalImage) + } else { + let error = KingfisherError.processorError( + reason: .processingFailed(processor: processor, item: .data(data))) + result = .failure(error) + } + onImageProcessed.call((result, callback)) + } + } +} diff --git a/!main project/Pods/Kingfisher/Sources/Networking/ImageDownloader.swift b/!main project/Pods/Kingfisher/Sources/Networking/ImageDownloader.swift new file mode 100644 index 0000000..74bdaca --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Networking/ImageDownloader.swift @@ -0,0 +1,377 @@ +// +// ImageDownloader.swift +// Kingfisher +// +// Created by Wei Wang on 15/4/6. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#if os(macOS) +import AppKit +#else +import UIKit +#endif + +/// Represents a success result of an image downloading progress. +public struct ImageLoadingResult { + + /// The downloaded image. + public let image: KFCrossPlatformImage + + /// Original URL of the image request. + public let url: URL? + + /// The raw data received from downloader. + public let originalData: Data +} + +/// Represents a task of an image downloading process. +public struct DownloadTask { + + /// The `SessionDataTask` object bounded to this download task. Multiple `DownloadTask`s could refer + /// to a same `sessionTask`. This is an optimization in Kingfisher to prevent multiple downloading task + /// for the same URL resource at the same time. + /// + /// When you `cancel` a `DownloadTask`, this `SessionDataTask` and its cancel token will be pass through. + /// You can use them to identify the cancelled task. + public let sessionTask: SessionDataTask + + /// The cancel token which is used to cancel the task. This is only for identify the task when it is cancelled. + /// To cancel a `DownloadTask`, use `cancel` instead. + public let cancelToken: SessionDataTask.CancelToken + + /// Cancel this task if it is running. It will do nothing if this task is not running. + /// + /// - Note: + /// In Kingfisher, there is an optimization to prevent starting another download task if the target URL is being + /// downloading. However, even when internally no new session task created, a `DownloadTask` will be still created + /// and returned when you call related methods, but it will share the session downloading task with a previous task. + /// In this case, if multiple `DownloadTask`s share a single session download task, cancelling a `DownloadTask` + /// does not affect other `DownloadTask`s. + /// + /// If you need to cancel all `DownloadTask`s of a url, use `ImageDownloader.cancel(url:)`. If you need to cancel + /// all downloading tasks of an `ImageDownloader`, use `ImageDownloader.cancelAll()`. + public func cancel() { + sessionTask.cancel(token: cancelToken) + } +} + +extension DownloadTask { + enum WrappedTask { + case download(DownloadTask) + case dataProviding + + func cancel() { + switch self { + case .download(let task): task.cancel() + case .dataProviding: break + } + } + + var value: DownloadTask? { + switch self { + case .download(let task): return task + case .dataProviding: return nil + } + } + } +} + +/// Represents a downloading manager for requesting the image with a URL from server. +open class ImageDownloader { + + // MARK: Singleton + /// The default downloader. + public static let `default` = ImageDownloader(name: "default") + + // MARK: Public Properties + /// The duration before the downloading is timeout. Default is 15 seconds. + open var downloadTimeout: TimeInterval = 15.0 + + /// A set of trusted hosts when receiving server trust challenges. A challenge with host name contained in this + /// set will be ignored. You can use this set to specify the self-signed site. It only will be used if you don't + /// specify the `authenticationChallengeResponder`. + /// + /// If `authenticationChallengeResponder` is set, this property will be ignored and the implementation of + /// `authenticationChallengeResponder` will be used instead. + open var trustedHosts: Set? + + /// Use this to set supply a configuration for the downloader. By default, + /// NSURLSessionConfiguration.ephemeralSessionConfiguration() will be used. + /// + /// You could change the configuration before a downloading task starts. + /// A configuration without persistent storage for caches is requested for downloader working correctly. + open var sessionConfiguration = URLSessionConfiguration.ephemeral { + didSet { + session.invalidateAndCancel() + session = URLSession(configuration: sessionConfiguration, delegate: sessionDelegate, delegateQueue: nil) + } + } + + /// Whether the download requests should use pipeline or not. Default is false. + open var requestsUsePipelining = false + + /// Delegate of this `ImageDownloader` object. See `ImageDownloaderDelegate` protocol for more. + open weak var delegate: ImageDownloaderDelegate? + + /// A responder for authentication challenge. + /// Downloader will forward the received authentication challenge for the downloading session to this responder. + open weak var authenticationChallengeResponder: AuthenticationChallengeResponsable? + + private let name: String + private let sessionDelegate: SessionDelegate + private var session: URLSession + + // MARK: Initializers + + /// Creates a downloader with name. + /// + /// - Parameter name: The name for the downloader. It should not be empty. + public init(name: String) { + if name.isEmpty { + fatalError("[Kingfisher] You should specify a name for the downloader. " + + "A downloader with empty name is not permitted.") + } + + self.name = name + + sessionDelegate = SessionDelegate() + session = URLSession( + configuration: sessionConfiguration, + delegate: sessionDelegate, + delegateQueue: nil) + + authenticationChallengeResponder = self + setupSessionHandler() + } + + deinit { session.invalidateAndCancel() } + + private func setupSessionHandler() { + sessionDelegate.onReceiveSessionChallenge.delegate(on: self) { (self, invoke) in + self.authenticationChallengeResponder?.downloader(self, didReceive: invoke.1, completionHandler: invoke.2) + } + sessionDelegate.onReceiveSessionTaskChallenge.delegate(on: self) { (self, invoke) in + self.authenticationChallengeResponder?.downloader( + self, task: invoke.1, didReceive: invoke.2, completionHandler: invoke.3) + } + sessionDelegate.onValidStatusCode.delegate(on: self) { (self, code) in + return (self.delegate ?? self).isValidStatusCode(code, for: self) + } + sessionDelegate.onDownloadingFinished.delegate(on: self) { (self, value) in + let (url, result) = value + do { + let value = try result.get() + self.delegate?.imageDownloader(self, didFinishDownloadingImageForURL: url, with: value, error: nil) + } catch { + self.delegate?.imageDownloader(self, didFinishDownloadingImageForURL: url, with: nil, error: error) + } + } + sessionDelegate.onDidDownloadData.delegate(on: self) { (self, task) in + guard let url = task.task.originalRequest?.url else { + return task.mutableData + } + return (self.delegate ?? self).imageDownloader(self, didDownload: task.mutableData, for: url) + } + } + + // MARK: Dowloading Task + /// Downloads an image with a URL and option. Invoked internally by Kingfisher. Subclasses must invoke super. + /// + /// - Parameters: + /// - url: Target URL. + /// - options: The options could control download behavior. See `KingfisherOptionsInfo`. + /// - completionHandler: Called when the download progress finishes. This block will be called in the queue + /// defined in `.callbackQueue` in `options` parameter. + /// - Returns: A downloading task. You could call `cancel` on it to stop the download task. + @discardableResult + open func downloadImage( + with url: URL, + options: KingfisherParsedOptionsInfo, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + // Creates default request. + var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: downloadTimeout) + request.httpShouldUsePipelining = requestsUsePipelining + + if let requestModifier = options.requestModifier { + // Modifies request before sending. + guard let r = requestModifier.modified(for: request) else { + options.callbackQueue.execute { + completionHandler?(.failure(KingfisherError.requestError(reason: .emptyRequest))) + } + return nil + } + request = r + } + + // There is a possibility that request modifier changed the url to `nil` or empty. + // In this case, throw an error. + guard let url = request.url, !url.absoluteString.isEmpty else { + options.callbackQueue.execute { + completionHandler?(.failure(KingfisherError.requestError(reason: .invalidURL(request: request)))) + } + return nil + } + + // Wraps `completionHandler` to `onCompleted` respectively. + + let onCompleted = completionHandler.map { + block -> Delegate, Void> in + let delegate = Delegate, Void>() + delegate.delegate(on: self) { (_, callback) in + block(callback) + } + return delegate + } + + // SessionDataTask.TaskCallback is a wrapper for `onCompleted` and `options` (for processor info) + let callback = SessionDataTask.TaskCallback( + onCompleted: onCompleted, + options: options + ) + + // Ready to start download. Add it to session task manager (`sessionHandler`) + + let downloadTask: DownloadTask + if let existingTask = sessionDelegate.task(for: url) { + downloadTask = sessionDelegate.append(existingTask, url: url, callback: callback) + } else { + let sessionDataTask = session.dataTask(with: request) + sessionDataTask.priority = options.downloadPriority + downloadTask = sessionDelegate.add(sessionDataTask, url: url, callback: callback) + } + + let sessionTask = downloadTask.sessionTask + + // Start the session task if not started yet. + if !sessionTask.started { + sessionTask.onTaskDone.delegate(on: self) { (self, done) in + // Underlying downloading finishes. + // result: Result<(Data, URLResponse?)>, callbacks: [TaskCallback] + let (result, callbacks) = done + + // Before processing the downloaded data. + do { + let value = try result.get() + self.delegate?.imageDownloader( + self, + didFinishDownloadingImageForURL: url, + with: value.1, + error: nil + ) + } catch { + self.delegate?.imageDownloader( + self, + didFinishDownloadingImageForURL: url, + with: nil, + error: error + ) + } + + switch result { + // Download finished. Now process the data to an image. + case .success(let (data, response)): + let processor = ImageDataProcessor( + data: data, callbacks: callbacks, processingQueue: options.processingQueue) + processor.onImageProcessed.delegate(on: self) { (self, result) in + // `onImageProcessed` will be called for `callbacks.count` times, with each + // `SessionDataTask.TaskCallback` as the input parameter. + // result: Result, callback: SessionDataTask.TaskCallback + let (result, callback) = result + + if let image = try? result.get() { + self.delegate?.imageDownloader(self, didDownload: image, for: url, with: response) + } + + let imageResult = result.map { ImageLoadingResult(image: $0, url: url, originalData: data) } + let queue = callback.options.callbackQueue + queue.execute { callback.onCompleted?.call(imageResult) } + } + processor.process() + + case .failure(let error): + callbacks.forEach { callback in + let queue = callback.options.callbackQueue + queue.execute { callback.onCompleted?.call(.failure(error)) } + } + } + } + delegate?.imageDownloader(self, willDownloadImageForURL: url, with: request) + sessionTask.resume() + } + return downloadTask + } + + /// Downloads an image with a URL and option. + /// + /// - Parameters: + /// - url: Target URL. + /// - options: The options could control download behavior. See `KingfisherOptionsInfo`. + /// - progressBlock: Called when the download progress updated. This block will be always be called in main queue. + /// - completionHandler: Called when the download progress finishes. This block will be called in the queue + /// defined in `.callbackQueue` in `options` parameter. + /// - Returns: A downloading task. You could call `cancel` on it to stop the download task. + @discardableResult + open func downloadImage( + with url: URL, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + var info = KingfisherParsedOptionsInfo(options) + if let block = progressBlock { + info.onDataReceived = (info.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)] + } + return downloadImage( + with: url, + options: info, + completionHandler: completionHandler) + } +} + +// MARK: Cancelling Task +extension ImageDownloader { + + /// Cancel all downloading tasks for this `ImageDownloader`. It will trigger the completion handlers + /// for all not-yet-finished downloading tasks. + /// + /// If you need to only cancel a certain task, call `cancel()` on the `DownloadTask` + /// returned by the downloading methods. If you need to cancel all `DownloadTask`s of a certain url, + /// use `ImageDownloader.cancel(url:)`. + public func cancelAll() { + sessionDelegate.cancelAll() + } + + /// Cancel all downloading tasks for a given URL. It will trigger the completion handlers for + /// all not-yet-finished downloading tasks for the URL. + /// + /// - Parameter url: The URL which you want to cancel downloading. + public func cancel(url: URL) { + sessionDelegate.cancel(url: url) + } +} + +// Use the default implementation from extension of `AuthenticationChallengeResponsable`. +extension ImageDownloader: AuthenticationChallengeResponsable {} + +// Use the default implementation from extension of `ImageDownloaderDelegate`. +extension ImageDownloader: ImageDownloaderDelegate {} diff --git a/!main project/Pods/Kingfisher/Sources/Networking/ImageDownloaderDelegate.swift b/!main project/Pods/Kingfisher/Sources/Networking/ImageDownloaderDelegate.swift new file mode 100644 index 0000000..2cc9102 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Networking/ImageDownloaderDelegate.swift @@ -0,0 +1,127 @@ +// +// ImageDownloaderDelegate.swift +// Kingfisher +// +// Created by Wei Wang on 2018/10/11. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +/// Protocol of `ImageDownloader`. This protocol provides a set of methods which are related to image downloader +/// working stages and rules. +public protocol ImageDownloaderDelegate: AnyObject { + + /// Called when the `ImageDownloader` object will start downloading an image from a specified URL. + /// + /// - Parameters: + /// - downloader: The `ImageDownloader` object which is used for the downloading operation. + /// - url: URL of the starting request. + /// - request: The request object for the download process. + /// + func imageDownloader(_ downloader: ImageDownloader, willDownloadImageForURL url: URL, with request: URLRequest?) + + /// Called when the `ImageDownloader` completes a downloading request with success or failure. + /// + /// - Parameters: + /// - downloader: The `ImageDownloader` object which is used for the downloading operation. + /// - url: URL of the original request URL. + /// - response: The response object of the downloading process. + /// - error: The error in case of failure. + /// + func imageDownloader( + _ downloader: ImageDownloader, + didFinishDownloadingImageForURL url: URL, + with response: URLResponse?, + error: Error?) + + /// Called when the `ImageDownloader` object successfully downloaded image data from specified URL. This is + /// your last chance to verify or modify the downloaded data before Kingfisher tries to perform addition + /// processing on the image data. + /// + /// - Parameters: + /// - downloader: The `ImageDownloader` object which is used for the downloading operation. + /// - data: The original downloaded data. + /// - url: The URL of the original request URL. + /// - Returns: The data from which Kingfisher should use to create an image. You need to provide valid data + /// which content is one of the supported image file format. Kingfisher will perform process on this + /// data and try to convert it to an image object. + /// - Note: + /// This can be used to pre-process raw image data before creation of `Image` instance (i.e. + /// decrypting or verification). If `nil` returned, the processing is interrupted and a `KingfisherError` with + /// `ResponseErrorReason.dataModifyingFailed` will be raised. You could use this fact to stop the image + /// processing flow if you find the data is corrupted or malformed. + func imageDownloader(_ downloader: ImageDownloader, didDownload data: Data, for url: URL) -> Data? + + /// Called when the `ImageDownloader` object successfully downloads and processes an image from specified URL. + /// + /// - Parameters: + /// - downloader: The `ImageDownloader` object which is used for the downloading operation. + /// - image: The downloaded and processed image. + /// - url: URL of the original request URL. + /// - response: The original response object of the downloading process. + /// + func imageDownloader( + _ downloader: ImageDownloader, + didDownload image: KFCrossPlatformImage, + for url: URL, + with response: URLResponse?) + + /// Checks if a received HTTP status code is valid or not. + /// By default, a status code in range 200..<400 is considered as valid. + /// If an invalid code is received, the downloader will raise an `KingfisherError` with + /// `ResponseErrorReason.invalidHTTPStatusCode` as its reason. + /// + /// - Parameters: + /// - code: The received HTTP status code. + /// - downloader: The `ImageDownloader` object asks for validate status code. + /// - Returns: Returns a value to indicate whether this HTTP status code is valid or not. + /// - Note: If the default 200 to 400 valid code does not suit your need, + /// you can implement this method to change that behavior. + func isValidStatusCode(_ code: Int, for downloader: ImageDownloader) -> Bool +} + +// Default implementation for `ImageDownloaderDelegate`. +extension ImageDownloaderDelegate { + public func imageDownloader( + _ downloader: ImageDownloader, + willDownloadImageForURL url: URL, + with request: URLRequest?) {} + + public func imageDownloader( + _ downloader: ImageDownloader, + didFinishDownloadingImageForURL url: URL, + with response: URLResponse?, + error: Error?) {} + + public func imageDownloader( + _ downloader: ImageDownloader, + didDownload image: KFCrossPlatformImage, + for url: URL, + with response: URLResponse?) {} + + public func isValidStatusCode(_ code: Int, for downloader: ImageDownloader) -> Bool { + return (200..<400).contains(code) + } + public func imageDownloader(_ downloader: ImageDownloader, didDownload data: Data, for url: URL) -> Data? { + return data + } +} diff --git a/!main project/Pods/Kingfisher/Sources/Networking/ImageModifier.swift b/!main project/Pods/Kingfisher/Sources/Networking/ImageModifier.swift new file mode 100644 index 0000000..5ef0722 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Networking/ImageModifier.swift @@ -0,0 +1,116 @@ +// +// ImageModifier.swift +// Kingfisher +// +// Created by Ethan Gill on 2017/11/28. +// +// Copyright (c) 2019 Ethan Gill +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +/// An `ImageModifier` can be used to change properties on an image in between +/// cache serialization and use of the image. The modified returned image will be +/// only used for current rendering purpose, the serialization data will not contain +/// the changes applied by the `ImageModifier`. +public protocol ImageModifier { + /// Modify an input `Image`. + /// + /// - parameter image: Image which will be modified by `self` + /// + /// - returns: The modified image. + /// + /// - Note: The return value will be unmodified if modifying is not possible on + /// the current platform. + /// - Note: Most modifiers support UIImage or NSImage, but not CGImage. + func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage +} + +/// A wrapper for creating an `ImageModifier` easier. +/// This type conforms to `ImageModifier` and wraps an image modify block. +/// If the `block` throws an error, the original image will be used. +public struct AnyImageModifier: ImageModifier { + + /// A block which modifies images, or returns the original image + /// if modification cannot be performed with an error. + let block: (KFCrossPlatformImage) throws -> KFCrossPlatformImage + + /// Creates an `AnyImageModifier` with a given `modify` block. + public init(modify: @escaping (KFCrossPlatformImage) throws -> KFCrossPlatformImage) { + block = modify + } + + /// Modify an input `Image`. See `ImageModifier` protocol for more. + public func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage { + return (try? block(image)) ?? image + } +} + +#if os(iOS) || os(tvOS) || os(watchOS) +import UIKit + +/// Modifier for setting the rendering mode of images. +public struct RenderingModeImageModifier: ImageModifier { + + /// The rendering mode to apply to the image. + public let renderingMode: UIImage.RenderingMode + + /// Creates a `RenderingModeImageModifier`. + /// + /// - Parameter renderingMode: The rendering mode to apply to the image. Default is `.automatic`. + public init(renderingMode: UIImage.RenderingMode = .automatic) { + self.renderingMode = renderingMode + } + + /// Modify an input `Image`. See `ImageModifier` protocol for more. + public func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage { + return image.withRenderingMode(renderingMode) + } +} + +/// Modifier for setting the `flipsForRightToLeftLayoutDirection` property of images. +public struct FlipsForRightToLeftLayoutDirectionImageModifier: ImageModifier { + + /// Creates a `FlipsForRightToLeftLayoutDirectionImageModifier`. + public init() {} + + /// Modify an input `Image`. See `ImageModifier` protocol for more. + public func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage { + return image.imageFlippedForRightToLeftLayoutDirection() + } +} + +/// Modifier for setting the `alignmentRectInsets` property of images. +public struct AlignmentRectInsetsImageModifier: ImageModifier { + + /// The alignment insets to apply to the image + public let alignmentInsets: UIEdgeInsets + + /// Creates an `AlignmentRectInsetsImageModifier`. + public init(alignmentInsets: UIEdgeInsets) { + self.alignmentInsets = alignmentInsets + } + + /// Modify an input `Image`. See `ImageModifier` protocol for more. + public func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage { + return image.withAlignmentRectInsets(alignmentInsets) + } +} +#endif diff --git a/!main project/Pods/Kingfisher/Sources/Networking/ImagePrefetcher.swift b/!main project/Pods/Kingfisher/Sources/Networking/ImagePrefetcher.swift new file mode 100644 index 0000000..c2d5ff1 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Networking/ImagePrefetcher.swift @@ -0,0 +1,372 @@ +// +// ImagePrefetcher.swift +// Kingfisher +// +// Created by Claire Knight on 24/02/2016 +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + + +#if os(macOS) +import AppKit +#else +import UIKit +#endif + +/// Progress update block of prefetcher when initialized with a list of resources. +/// +/// - `skippedResources`: An array of resources that are already cached before the prefetching starting. +/// - `failedResources`: An array of resources that fail to be downloaded. It could because of being cancelled while +/// downloading, encountered an error when downloading or the download not being started at all. +/// - `completedResources`: An array of resources that are downloaded and cached successfully. +public typealias PrefetcherProgressBlock = + ((_ skippedResources: [Resource], _ failedResources: [Resource], _ completedResources: [Resource]) -> Void) + +/// Progress update block of prefetcher when initialized with a list of resources. +/// +/// - `skippedSources`: An array of sources that are already cached before the prefetching starting. +/// - `failedSources`: An array of sources that fail to be fetched. +/// - `completedResources`: An array of sources that are fetched and cached successfully. +public typealias PrefetcherSourceProgressBlock = + ((_ skippedSources: [Source], _ failedSources: [Source], _ completedSources: [Source]) -> Void) + +/// Completion block of prefetcher when initialized with a list of sources. +/// +/// - `skippedResources`: An array of resources that are already cached before the prefetching starting. +/// - `failedResources`: An array of resources that fail to be downloaded. It could because of being cancelled while +/// downloading, encountered an error when downloading or the download not being started at all. +/// - `completedResources`: An array of resources that are downloaded and cached successfully. +public typealias PrefetcherCompletionHandler = + ((_ skippedResources: [Resource], _ failedResources: [Resource], _ completedResources: [Resource]) -> Void) + +/// Completion block of prefetcher when initialized with a list of sources. +/// +/// - `skippedSources`: An array of sources that are already cached before the prefetching starting. +/// - `failedSources`: An array of sources that fail to be fetched. +/// - `completedSources`: An array of sources that are fetched and cached successfully. +public typealias PrefetcherSourceCompletionHandler = + ((_ skippedSources: [Source], _ failedSources: [Source], _ completedSources: [Source]) -> Void) + +/// `ImagePrefetcher` represents a downloading manager for requesting many images via URLs, then caching them. +/// This is useful when you know a list of image resources and want to download them before showing. It also works with +/// some Cocoa prefetching mechanism like table view or collection view `prefetchDataSource`, to start image downloading +/// and caching before they display on screen. +public class ImagePrefetcher: CustomStringConvertible { + + public var description: String { + return "\(Unmanaged.passUnretained(self).toOpaque())" + } + + /// The maximum concurrent downloads to use when prefetching images. Default is 5. + public var maxConcurrentDownloads = 5 + + private let prefetchSources: [Source] + private let optionsInfo: KingfisherParsedOptionsInfo + + private var progressBlock: PrefetcherProgressBlock? + private var completionHandler: PrefetcherCompletionHandler? + + private var progressSourceBlock: PrefetcherSourceProgressBlock? + private var completionSourceHandler: PrefetcherSourceCompletionHandler? + + private var tasks = [String: DownloadTask.WrappedTask]() + + private var pendingSources: ArraySlice + private var skippedSources = [Source]() + private var completedSources = [Source]() + private var failedSources = [Source]() + + private var stopped = false + + // A manager used for prefetching. We will use the helper methods in manager. + private let manager: KingfisherManager + + private let pretchQueue = DispatchQueue(label: "com.onevcat.Kingfisher.ImagePrefetcher.pretchQueue") + private static let requestingQueue = DispatchQueue(label: "com.onevcat.Kingfisher.ImagePrefetcher.requestingQueue") + + private var finished: Bool { + let totalFinished: Int = failedSources.count + skippedSources.count + completedSources.count + return totalFinished == prefetchSources.count && tasks.isEmpty + } + + /// Creates an image prefetcher with an array of URLs. + /// + /// The prefetcher should be initiated with a list of prefetching targets. The URLs list is immutable. + /// After you get a valid `ImagePrefetcher` object, you call `start()` on it to begin the prefetching process. + /// The images which are already cached will be skipped without downloading again. + /// + /// - Parameters: + /// - urls: The URLs which should be prefetched. + /// - options: Options could control some behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called every time an resource is downloaded, skipped or cancelled. + /// - completionHandler: Called when the whole prefetching process finished. + /// + /// - Note: + /// By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as + /// the downloader and cache target respectively. You can specify another downloader or cache by using + /// a customized `KingfisherOptionsInfo`. Both the progress and completion block will be invoked in + /// main thread. The `.callbackQueue` value in `optionsInfo` will be ignored in this method. + public convenience init( + urls: [URL], + options: KingfisherOptionsInfo? = nil, + progressBlock: PrefetcherProgressBlock? = nil, + completionHandler: PrefetcherCompletionHandler? = nil) + { + let resources: [Resource] = urls.map { $0 } + self.init( + resources: resources, + options: options, + progressBlock: progressBlock, + completionHandler: completionHandler) + } + + /// Creates an image prefetcher with an array of resources. + /// + /// - Parameters: + /// - resources: The resources which should be prefetched. See `Resource` type for more. + /// - options: Options could control some behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called every time an resource is downloaded, skipped or cancelled. + /// - completionHandler: Called when the whole prefetching process finished. + /// + /// - Note: + /// By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as + /// the downloader and cache target respectively. You can specify another downloader or cache by using + /// a customized `KingfisherOptionsInfo`. Both the progress and completion block will be invoked in + /// main thread. The `.callbackQueue` value in `optionsInfo` will be ignored in this method. + public convenience init( + resources: [Resource], + options: KingfisherOptionsInfo? = nil, + progressBlock: PrefetcherProgressBlock? = nil, + completionHandler: PrefetcherCompletionHandler? = nil) + { + self.init(sources: resources.map { $0.convertToSource() }, options: options) + self.progressBlock = progressBlock + self.completionHandler = completionHandler + } + + /// Creates an image prefetcher with an array of sources. + /// + /// - Parameters: + /// - sources: The sources which should be prefetched. See `Source` type for more. + /// - options: Options could control some behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called every time an source fetching successes, fails, is skipped. + /// - completionHandler: Called when the whole prefetching process finished. + /// + /// - Note: + /// By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as + /// the downloader and cache target respectively. You can specify another downloader or cache by using + /// a customized `KingfisherOptionsInfo`. Both the progress and completion block will be invoked in + /// main thread. The `.callbackQueue` value in `optionsInfo` will be ignored in this method. + public convenience init(sources: [Source], + options: KingfisherOptionsInfo? = nil, + progressBlock: PrefetcherSourceProgressBlock? = nil, + completionHandler: PrefetcherSourceCompletionHandler? = nil) + { + self.init(sources: sources, options: options) + self.progressSourceBlock = progressBlock + self.completionSourceHandler = completionHandler + } + + init(sources: [Source], options: KingfisherOptionsInfo?) { + var options = KingfisherParsedOptionsInfo(options) + prefetchSources = sources + pendingSources = ArraySlice(sources) + + // We want all callbacks from our prefetch queue, so we should ignore the callback queue in options. + // Add our own callback dispatch queue to make sure all internal callbacks are + // coming back in our expected queue. + options.callbackQueue = .dispatch(pretchQueue) + optionsInfo = options + + let cache = optionsInfo.targetCache ?? .default + let downloader = optionsInfo.downloader ?? .default + manager = KingfisherManager(downloader: downloader, cache: cache) + } + + /// Starts to download the resources and cache them. This can be useful for background downloading + /// of assets that are required for later use in an app. This code will not try and update any UI + /// with the results of the process. + public func start() { + pretchQueue.async { + guard !self.stopped else { + assertionFailure("You can not restart the same prefetcher. Try to create a new prefetcher.") + self.handleComplete() + return + } + + guard self.maxConcurrentDownloads > 0 else { + assertionFailure("There should be concurrent downloads value should be at least 1.") + self.handleComplete() + return + } + + // Empty case. + guard self.prefetchSources.count > 0 else { + self.handleComplete() + return + } + + let initialConcurrentDownloads = min(self.prefetchSources.count, self.maxConcurrentDownloads) + for _ in 0 ..< initialConcurrentDownloads { + if let resource = self.pendingSources.popFirst() { + self.startPrefetching(resource) + } + } + } + } + + /// Stops current downloading progress, and cancel any future prefetching activity that might be occuring. + public func stop() { + pretchQueue.async { + if self.finished { return } + self.stopped = true + self.tasks.values.forEach { $0.cancel() } + } + } + + private func downloadAndCache(_ source: Source) { + + let downloadTaskCompletionHandler: ((Result) -> Void) = { result in + self.tasks.removeValue(forKey: source.cacheKey) + do { + let _ = try result.get() + self.completedSources.append(source) + } catch { + self.failedSources.append(source) + } + + self.reportProgress() + if self.stopped { + if self.tasks.isEmpty { + self.failedSources.append(contentsOf: self.pendingSources) + self.handleComplete() + } + } else { + self.reportCompletionOrStartNext() + } + } + + var downloadTask: DownloadTask.WrappedTask? + ImagePrefetcher.requestingQueue.sync { + let context = RetrievingContext( + options: optionsInfo, originalSource: source + ) + downloadTask = manager.loadAndCacheImage( + source: source, + context: context, + completionHandler: downloadTaskCompletionHandler) + } + + if let downloadTask = downloadTask { + tasks[source.cacheKey] = downloadTask + } + } + + private func append(cached source: Source) { + skippedSources.append(source) + + reportProgress() + reportCompletionOrStartNext() + } + + private func startPrefetching(_ source: Source) + { + if optionsInfo.forceRefresh { + downloadAndCache(source) + return + } + + let cacheType = manager.cache.imageCachedType( + forKey: source.cacheKey, + processorIdentifier: optionsInfo.processor.identifier) + switch cacheType { + case .memory: + append(cached: source) + case .disk: + if optionsInfo.alsoPrefetchToMemory { + let context = RetrievingContext(options: optionsInfo, originalSource: source) + _ = manager.retrieveImageFromCache( + source: source, + context: context) + { + _ in + self.append(cached: source) + } + } else { + append(cached: source) + } + case .none: + downloadAndCache(source) + } + } + + private func reportProgress() { + + if progressBlock == nil && progressSourceBlock == nil { + return + } + + let skipped = self.skippedSources + let failed = self.failedSources + let completed = self.completedSources + CallbackQueue.mainCurrentOrAsync.execute { + self.progressSourceBlock?(skipped, failed, completed) + self.progressBlock?( + skipped.compactMap { $0.asResource }, + failed.compactMap { $0.asResource }, + completed.compactMap { $0.asResource } + ) + } + } + + private func reportCompletionOrStartNext() { + if let resource = self.pendingSources.popFirst() { + // Loose call stack for huge ammount of sources. + pretchQueue.async { self.startPrefetching(resource) } + } else { + guard allFinished else { return } + self.handleComplete() + } + } + + var allFinished: Bool { + return skippedSources.count + failedSources.count + completedSources.count == prefetchSources.count + } + + private func handleComplete() { + + if completionHandler == nil && completionSourceHandler == nil { + return + } + + // The completion handler should be called on the main thread + CallbackQueue.mainCurrentOrAsync.execute { + self.completionSourceHandler?(self.skippedSources, self.failedSources, self.completedSources) + self.completionHandler?( + self.skippedSources.compactMap { $0.asResource }, + self.failedSources.compactMap { $0.asResource }, + self.completedSources.compactMap { $0.asResource } + ) + self.completionHandler = nil + self.progressBlock = nil + } + } +} diff --git a/!main project/Pods/Kingfisher/Sources/Networking/RedirectHandler.swift b/!main project/Pods/Kingfisher/Sources/Networking/RedirectHandler.swift new file mode 100644 index 0000000..aaf0f7f --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Networking/RedirectHandler.swift @@ -0,0 +1,76 @@ +// +// RedirectHandler.swift +// Kingfisher +// +// Created by Roman Maidanovych on 2018/12/10. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +/// Represents and wraps a method for modifying request during an image download request redirection. +public protocol ImageDownloadRedirectHandler { + + /// The `ImageDownloadRedirectHandler` contained will be used to change the request before redirection. + /// This is the posibility you can modify the image download request during redirection. You can modify the + /// request for some customizing purpose, such as adding auth token to the header, do basic HTTP auth or + /// something like url mapping. + /// + /// Usually, you pass an `ImageDownloadRedirectHandler` as the associated value of + /// `KingfisherOptionsInfoItem.redirectHandler` and use it as the `options` parameter in related methods. + /// + /// If you do nothing with the input `request` and return it as is, a downloading process will redirect with it. + /// + /// - Parameters: + /// - task: The current `SessionDataTask` which triggers this redirect. + /// - response: The response received during redirection. + /// - newRequest: The request for redirection which can be modified. + /// - completionHandler: A closure for being called with modified request. + func handleHTTPRedirection( + for task: SessionDataTask, + response: HTTPURLResponse, + newRequest: URLRequest, + completionHandler: @escaping (URLRequest?) -> Void) +} + +/// A wrapper for creating an `ImageDownloadRedirectHandler` easier. +/// This type conforms to `ImageDownloadRedirectHandler` and wraps a redirect request modify block. +public struct AnyRedirectHandler: ImageDownloadRedirectHandler { + + let block: (SessionDataTask, HTTPURLResponse, URLRequest, (URLRequest?) -> Void) -> Void + + public func handleHTTPRedirection( + for task: SessionDataTask, + response: HTTPURLResponse, + newRequest: URLRequest, + completionHandler: @escaping (URLRequest?) -> Void) + { + block(task, response, newRequest, completionHandler) + } + + /// Creates a value of `ImageDownloadRedirectHandler` which runs `modify` block. + /// + /// - Parameter modify: The request modifying block runs when a request modifying task comes. + /// + public init(handle: @escaping (SessionDataTask, HTTPURLResponse, URLRequest, (URLRequest?) -> Void) -> Void) { + block = handle + } +} diff --git a/!main project/Pods/Kingfisher/Sources/Networking/RequestModifier.swift b/!main project/Pods/Kingfisher/Sources/Networking/RequestModifier.swift new file mode 100644 index 0000000..06b062a --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Networking/RequestModifier.swift @@ -0,0 +1,69 @@ +// +// RequestModifier.swift +// Kingfisher +// +// Created by Wei Wang on 2016/09/05. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +/// Represents and wraps a method for modifying request before an image download request starts. +public protocol ImageDownloadRequestModifier { + + /// A method will be called just before the `request` being sent. + /// This is the last chance you can modify the image download request. You can modify the request for some + /// customizing purpose, such as adding auth token to the header, do basic HTTP auth or something like url mapping. + /// + /// Usually, you pass an `ImageDownloadRequestModifier` as the associated value of + /// `KingfisherOptionsInfoItem.requestModifier` and use it as the `options` parameter in related methods. + /// + /// If you do nothing with the input `request` and return it as is, a downloading process will start with it. + /// + /// - Parameter request: The input request contains necessary information like `url`. This request is generated + /// according to your resource url as a GET request. + /// - Returns: A modified version of request, which you wish to use for downloading an image. If `nil` returned, + /// a `KingfisherError.requestError` with `.emptyRequest` as its reason will occur. + /// + func modified(for request: URLRequest) -> URLRequest? +} + +/// A wrapper for creating an `ImageDownloadRequestModifier` easier. +/// This type conforms to `ImageDownloadRequestModifier` and wraps an image modify block. +public struct AnyModifier: ImageDownloadRequestModifier { + + let block: (URLRequest) -> URLRequest? + + /// For `ImageDownloadRequestModifier` conformation. + public func modified(for request: URLRequest) -> URLRequest? { + return block(request) + } + + /// Creates a value of `ImageDownloadRequestModifier` which runs `modify` block. + /// + /// - Parameter modify: The request modifying block runs when a request modifying task comes. + /// The return `URLRequest?` value of this block will be used as the image download request. + /// If `nil` returned, a `KingfisherError.requestError` with `.emptyRequest` as its + /// reason will occur. + public init(modify: @escaping (URLRequest) -> URLRequest?) { + block = modify + } +} diff --git a/!main project/Pods/Kingfisher/Sources/Networking/SessionDataTask.swift b/!main project/Pods/Kingfisher/Sources/Networking/SessionDataTask.swift new file mode 100644 index 0000000..2fcfbf0 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Networking/SessionDataTask.swift @@ -0,0 +1,119 @@ +// +// SessionDataTask.swift +// Kingfisher +// +// Created by Wei Wang on 2018/11/1. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +/// Represents a session data task in `ImageDownloader`. It consists of an underlying `URLSessionDataTask` and +/// an array of `TaskCallback`. Multiple `TaskCallback`s could be added for a single downloading data task. +public class SessionDataTask { + + /// Represents the type of token which used for cancelling a task. + public typealias CancelToken = Int + + struct TaskCallback { + let onCompleted: Delegate, Void>? + let options: KingfisherParsedOptionsInfo + } + + /// Downloaded raw data of current task. + public private(set) var mutableData: Data + + /// The underlying download task. It is only for debugging purpose when you encountered an error. You should not + /// modify the content of this task or start it yourself. + public let task: URLSessionDataTask + private var callbacksStore = [CancelToken: TaskCallback]() + + var callbacks: [SessionDataTask.TaskCallback] { + lock.lock() + defer { lock.unlock() } + return Array(callbacksStore.values) + } + + private var currentToken = 0 + private let lock = NSLock() + + let onTaskDone = Delegate<(Result<(Data, URLResponse?), KingfisherError>, [TaskCallback]), Void>() + let onCallbackCancelled = Delegate<(CancelToken, TaskCallback), Void>() + + var started = false + var containsCallbacks: Bool { + // We should be able to use `task.state != .running` to check it. + // However, in some rare cases, cancelling the task does not change + // task state to `.cancelling` immediately, but still in `.running`. + // So we need to check callbacks count to for sure that it is safe to remove the + // task in delegate. + return !callbacks.isEmpty + } + + init(task: URLSessionDataTask) { + self.task = task + mutableData = Data() + } + + func addCallback(_ callback: TaskCallback) -> CancelToken { + lock.lock() + defer { lock.unlock() } + callbacksStore[currentToken] = callback + defer { currentToken += 1 } + return currentToken + } + + func removeCallback(_ token: CancelToken) -> TaskCallback? { + lock.lock() + defer { lock.unlock() } + if let callback = callbacksStore[token] { + callbacksStore[token] = nil + return callback + } + return nil + } + + func resume() { + guard !started else { return } + started = true + task.resume() + } + + func cancel(token: CancelToken) { + guard let callback = removeCallback(token) else { + return + } + if callbacksStore.count == 0 { + task.cancel() + } + onCallbackCancelled.call((token, callback)) + } + + func forceCancel() { + for token in callbacksStore.keys { + cancel(token: token) + } + } + + func didReceiveData(_ data: Data) { + mutableData.append(data) + } +} diff --git a/!main project/Pods/Kingfisher/Sources/Networking/SessionDelegate.swift b/!main project/Pods/Kingfisher/Sources/Networking/SessionDelegate.swift new file mode 100644 index 0000000..dde757b --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Networking/SessionDelegate.swift @@ -0,0 +1,251 @@ +// +// SessionDelegate.swift +// Kingfisher +// +// Created by Wei Wang on 2018/11/1. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +// Represents the delegate object of downloader session. It also behave like a task manager for downloading. +class SessionDelegate: NSObject { + + typealias SessionChallengeFunc = ( + URLSession, + URLAuthenticationChallenge, + (URLSession.AuthChallengeDisposition, URLCredential?) -> Void + ) + + typealias SessionTaskChallengeFunc = ( + URLSession, + URLSessionTask, + URLAuthenticationChallenge, + (URLSession.AuthChallengeDisposition, URLCredential?) -> Void + ) + + private var tasks: [URL: SessionDataTask] = [:] + private let lock = NSLock() + + let onValidStatusCode = Delegate() + let onDownloadingFinished = Delegate<(URL, Result), Void>() + let onDidDownloadData = Delegate() + + let onReceiveSessionChallenge = Delegate() + let onReceiveSessionTaskChallenge = Delegate() + + func add( + _ dataTask: URLSessionDataTask, + url: URL, + callback: SessionDataTask.TaskCallback) -> DownloadTask + { + lock.lock() + defer { lock.unlock() } + + // Create a new task if necessary. + let task = SessionDataTask(task: dataTask) + task.onCallbackCancelled.delegate(on: self) { [unowned task] (self, value) in + let (token, callback) = value + + let error = KingfisherError.requestError(reason: .taskCancelled(task: task, token: token)) + task.onTaskDone.call((.failure(error), [callback])) + // No other callbacks waiting, we can clear the task now. + if !task.containsCallbacks { + let dataTask = task.task + self.remove(dataTask) + } + } + let token = task.addCallback(callback) + tasks[url] = task + return DownloadTask(sessionTask: task, cancelToken: token) + } + + func append( + _ task: SessionDataTask, + url: URL, + callback: SessionDataTask.TaskCallback) -> DownloadTask + { + let token = task.addCallback(callback) + return DownloadTask(sessionTask: task, cancelToken: token) + } + + private func remove(_ task: URLSessionTask) { + guard let url = task.originalRequest?.url else { + return + } + lock.lock() + defer {lock.unlock()} + tasks[url] = nil + } + + private func task(for task: URLSessionTask) -> SessionDataTask? { + + guard let url = task.originalRequest?.url else { + return nil + } + + lock.lock() + defer { lock.unlock() } + guard let sessionTask = tasks[url] else { + return nil + } + guard sessionTask.task.taskIdentifier == task.taskIdentifier else { + return nil + } + return sessionTask + } + + func task(for url: URL) -> SessionDataTask? { + lock.lock() + defer { lock.unlock() } + return tasks[url] + } + + func cancelAll() { + lock.lock() + let taskValues = tasks.values + lock.unlock() + for task in taskValues { + task.forceCancel() + } + } + + func cancel(url: URL) { + lock.lock() + let task = tasks[url] + lock.unlock() + task?.forceCancel() + } +} + +extension SessionDelegate: URLSessionDataDelegate { + + func urlSession( + _ session: URLSession, + dataTask: URLSessionDataTask, + didReceive response: URLResponse, + completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) + { + guard let httpResponse = response as? HTTPURLResponse else { + let error = KingfisherError.responseError(reason: .invalidURLResponse(response: response)) + onCompleted(task: dataTask, result: .failure(error)) + completionHandler(.cancel) + return + } + + let httpStatusCode = httpResponse.statusCode + guard onValidStatusCode.call(httpStatusCode) == true else { + let error = KingfisherError.responseError(reason: .invalidHTTPStatusCode(response: httpResponse)) + onCompleted(task: dataTask, result: .failure(error)) + completionHandler(.cancel) + return + } + completionHandler(.allow) + } + + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + guard let task = self.task(for: dataTask) else { + return + } + + task.didReceiveData(data) + + task.callbacks.forEach { callback in + callback.options.onDataReceived?.forEach { sideEffect in + sideEffect.onDataReceived(session, task: task, data: data) + } + } + } + + func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + guard let sessionTask = self.task(for: task) else { return } + + if let url = task.originalRequest?.url { + let result: Result + if let error = error { + result = .failure(KingfisherError.responseError(reason: .URLSessionError(error: error))) + } else if let response = task.response { + result = .success(response) + } else { + result = .failure(KingfisherError.responseError(reason: .noURLResponse(task: sessionTask))) + } + onDownloadingFinished.call((url, result)) + } + + let result: Result<(Data, URLResponse?), KingfisherError> + if let error = error { + result = .failure(KingfisherError.responseError(reason: .URLSessionError(error: error))) + } else { + if let data = onDidDownloadData.call(sessionTask), let finalData = data { + result = .success((finalData, task.response)) + } else { + result = .failure(KingfisherError.responseError(reason: .dataModifyingFailed(task: sessionTask))) + } + } + onCompleted(task: task, result: result) + } + + func urlSession( + _ session: URLSession, + didReceive challenge: URLAuthenticationChallenge, + completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) + { + onReceiveSessionChallenge.call((session, challenge, completionHandler)) + } + + func urlSession( + _ session: URLSession, + task: URLSessionTask, + didReceive challenge: URLAuthenticationChallenge, + completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) + { + onReceiveSessionTaskChallenge.call((session, task, challenge, completionHandler)) + } + + func urlSession( + _ session: URLSession, + task: URLSessionTask, + willPerformHTTPRedirection response: HTTPURLResponse, + newRequest request: URLRequest, + completionHandler: @escaping (URLRequest?) -> Void) + { + guard let sessionDataTask = self.task(for: task), + let redirectHandler = Array(sessionDataTask.callbacks).last?.options.redirectHandler else + { + completionHandler(request) + return + } + + redirectHandler.handleHTTPRedirection( + for: sessionDataTask, + response: response, + newRequest: request, + completionHandler: completionHandler) + } + + private func onCompleted(task: URLSessionTask, result: Result<(Data, URLResponse?), KingfisherError>) { + guard let sessionTask = self.task(for: task) else { + return + } + remove(task) + sessionTask.onTaskDone.call((result, sessionTask.callbacks)) + } +} diff --git a/!main project/Pods/Kingfisher/Sources/Utility/Box.swift b/!main project/Pods/Kingfisher/Sources/Utility/Box.swift new file mode 100644 index 0000000..0303a6e --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Utility/Box.swift @@ -0,0 +1,34 @@ +// +// Box.swift +// Kingfisher +// +// Created by Wei Wang on 2018/3/17. +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +class Box { + var value: T + + init(_ value: T) { + self.value = value + } +} diff --git a/!main project/Pods/Kingfisher/Sources/Utility/CallbackQueue.swift b/!main project/Pods/Kingfisher/Sources/Utility/CallbackQueue.swift new file mode 100644 index 0000000..fa67f14 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Utility/CallbackQueue.swift @@ -0,0 +1,81 @@ +// +// CallbackQueue.swift +// Kingfisher +// +// Created by onevcat on 2018/10/15. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +/// Represents callback queue behaviors when an calling of closure be dispatched. +/// +/// - asyncMain: Dispatch the calling to `DispatchQueue.main` with an `async` behavior. +/// - currentMainOrAsync: Dispatch the calling to `DispatchQueue.main` with an `async` behavior if current queue is not +/// `.main`. Otherwise, call the closure immediately in current main queue. +/// - untouch: Do not change the calling queue for closure. +/// - dispatch: Dispatches to a specified `DispatchQueue`. +public enum CallbackQueue { + /// Dispatch the calling to `DispatchQueue.main` with an `async` behavior. + case mainAsync + /// Dispatch the calling to `DispatchQueue.main` with an `async` behavior if current queue is not + /// `.main`. Otherwise, call the closure immediately in current main queue. + case mainCurrentOrAsync + /// Do not change the calling queue for closure. + case untouch + /// Dispatches to a specified `DispatchQueue`. + case dispatch(DispatchQueue) + + public func execute(_ block: @escaping () -> Void) { + switch self { + case .mainAsync: + DispatchQueue.main.async { block() } + case .mainCurrentOrAsync: + DispatchQueue.main.safeAsync { block() } + case .untouch: + block() + case .dispatch(let queue): + queue.async { block() } + } + } + + var queue: DispatchQueue { + switch self { + case .mainAsync: return .main + case .mainCurrentOrAsync: return .main + case .untouch: return OperationQueue.current?.underlyingQueue ?? .main + case .dispatch(let queue): return queue + } + } +} + +extension DispatchQueue { + // This method will dispatch the `block` to self. + // If `self` is the main queue, and current thread is main thread, the block + // will be invoked immediately instead of being dispatched. + func safeAsync(_ block: @escaping ()->()) { + if self === DispatchQueue.main && Thread.isMainThread { + block() + } else { + async { block() } + } + } +} diff --git a/!main project/Pods/Kingfisher/Sources/Utility/Delegate.swift b/!main project/Pods/Kingfisher/Sources/Utility/Delegate.swift new file mode 100644 index 0000000..15915c9 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Utility/Delegate.swift @@ -0,0 +1,53 @@ +// +// Delegate.swift +// Kingfisher +// +// Created by onevcat on 2018/10/10. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +/// A delegate helper type to "shadow" weak `self`, to prevent creating an unexpected retain cycle. +class Delegate { + init() {} + + private var block: ((Input) -> Output?)? + + func delegate(on target: T, block: ((T, Input) -> Output)?) { + // The `target` is weak inside block, so you do not need to worry about it in the caller side. + self.block = { [weak target] input in + guard let target = target else { return nil } + return block?(target, input) + } + } + + func call(_ input: Input) -> Output? { + return block?(input) + } +} + +extension Delegate where Input == Void { + // To make syntax better for `Void` input. + func call() -> Output? { + return call(()) + } +} diff --git a/!main project/Pods/Kingfisher/Sources/Utility/ExtensionHelpers.swift b/!main project/Pods/Kingfisher/Sources/Utility/ExtensionHelpers.swift new file mode 100644 index 0000000..f1e1e8b --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Utility/ExtensionHelpers.swift @@ -0,0 +1,125 @@ +// +// ExtensionHelpers.swift +// Kingfisher +// +// Created by onevcat on 2018/09/28. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +extension CGFloat { + var isEven: Bool { + return truncatingRemainder(dividingBy: 2.0) == 0 + } +} + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit +extension NSBezierPath { + convenience init(roundedRect rect: NSRect, topLeftRadius: CGFloat, topRightRadius: CGFloat, + bottomLeftRadius: CGFloat, bottomRightRadius: CGFloat) + { + self.init() + + let maxCorner = min(rect.width, rect.height) / 2 + + let radiusTopLeft = min(maxCorner, max(0, topLeftRadius)) + let radiusTopRight = min(maxCorner, max(0, topRightRadius)) + let radiusBottomLeft = min(maxCorner, max(0, bottomLeftRadius)) + let radiusBottomRight = min(maxCorner, max(0, bottomRightRadius)) + + guard !rect.isEmpty else { + return + } + + let topLeft = NSPoint(x: rect.minX, y: rect.maxY) + let topRight = NSPoint(x: rect.maxX, y: rect.maxY) + let bottomRight = NSPoint(x: rect.maxX, y: rect.minY) + + move(to: NSPoint(x: rect.midX, y: rect.maxY)) + appendArc(from: topLeft, to: rect.origin, radius: radiusTopLeft) + appendArc(from: rect.origin, to: bottomRight, radius: radiusBottomLeft) + appendArc(from: bottomRight, to: topRight, radius: radiusBottomRight) + appendArc(from: topRight, to: topLeft, radius: radiusTopRight) + close() + } + + convenience init(roundedRect rect: NSRect, byRoundingCorners corners: RectCorner, radius: CGFloat) { + let radiusTopLeft = corners.contains(.topLeft) ? radius : 0 + let radiusTopRight = corners.contains(.topRight) ? radius : 0 + let radiusBottomLeft = corners.contains(.bottomLeft) ? radius : 0 + let radiusBottomRight = corners.contains(.bottomRight) ? radius : 0 + + self.init(roundedRect: rect, topLeftRadius: radiusTopLeft, topRightRadius: radiusTopRight, + bottomLeftRadius: radiusBottomLeft, bottomRightRadius: radiusBottomRight) + } +} + +extension KFCrossPlatformImage { + // macOS does not support scale. This is just for code compatibility across platforms. + convenience init?(data: Data, scale: CGFloat) { + self.init(data: data) + } +} +#endif + +#if canImport(UIKit) +import UIKit +extension RectCorner { + var uiRectCorner: UIRectCorner { + + var result: UIRectCorner = [] + + if contains(.topLeft) { result.insert(.topLeft) } + if contains(.topRight) { result.insert(.topRight) } + if contains(.bottomLeft) { result.insert(.bottomLeft) } + if contains(.bottomRight) { result.insert(.bottomRight) } + + return result + } +} +#endif + +extension Date { + var isPast: Bool { + return isPast(referenceDate: Date()) + } + + var isFuture: Bool { + return !isPast + } + + func isPast(referenceDate: Date) -> Bool { + return timeIntervalSince(referenceDate) <= 0 + } + + func isFuture(referenceDate: Date) -> Bool { + return !isPast(referenceDate: referenceDate) + } + + // `Date` in memory is a wrap for `TimeInterval`. But in file attribute it can only accept `Int` number. + // By default the system will `round` it. But it is not friendly for testing purpose. + // So we always `ceil` the value when used for file attributes. + var fileAttributeDate: Date { + return Date(timeIntervalSince1970: ceil(timeIntervalSince1970)) + } +} diff --git a/!main project/Pods/Kingfisher/Sources/Utility/Result.swift b/!main project/Pods/Kingfisher/Sources/Utility/Result.swift new file mode 100644 index 0000000..8b9c5fd --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Utility/Result.swift @@ -0,0 +1,239 @@ +// +// Result.swift +// Kingfisher +// +// Created by onevcat on 2018/09/22. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +#if swift(>=4.3) +/// Result type already built-in +#else +/// A value that represents either a success or failure, capturing associated +/// values in both cases. +public enum Result { + /// A success, storing a `Value`. + case success(Success) + + /// A failure, storing an `Error`. + case failure(Failure) + + /// Evaluates the given transform closure when this `Result` instance is + /// `.success`, passing the value as a parameter. + /// + /// Use the `map` method with a closure that returns a non-`Result` value. + /// + /// - Parameter transform: A closure that takes the successful value of the + /// instance. + /// - Returns: A new `Result` instance with the result of the transform, if + /// it was applied. + public func map( + _ transform: (Success) -> NewSuccess + ) -> Result { + switch self { + case let .success(success): + return .success(transform(success)) + case let .failure(failure): + return .failure(failure) + } + } + + /// Evaluates the given transform closure when this `Result` instance is + /// `.failure`, passing the error as a parameter. + /// + /// Use the `mapError` method with a closure that returns a non-`Result` + /// value. + /// + /// - Parameter transform: A closure that takes the failure value of the + /// instance. + /// - Returns: A new `Result` instance with the result of the transform, if + /// it was applied. + public func mapError( + _ transform: (Failure) -> NewFailure + ) -> Result { + switch self { + case let .success(success): + return .success(success) + case let .failure(failure): + return .failure(transform(failure)) + } + } + + /// Evaluates the given transform closure when this `Result` instance is + /// `.success`, passing the value as a parameter and flattening the result. + /// + /// - Parameter transform: A closure that takes the successful value of the + /// instance. + /// - Returns: A new `Result` instance, either from the transform or from + /// the previous error value. + public func flatMap( + _ transform: (Success) -> Result + ) -> Result { + switch self { + case let .success(success): + return transform(success) + case let .failure(failure): + return .failure(failure) + } + } + + /// Evaluates the given transform closure when this `Result` instance is + /// `.failure`, passing the error as a parameter and flattening the result. + /// + /// - Parameter transform: A closure that takes the error value of the + /// instance. + /// - Returns: A new `Result` instance, either from the transform or from + /// the previous success value. + public func flatMapError( + _ transform: (Failure) -> Result + ) -> Result { + switch self { + case let .success(success): + return .success(success) + case let .failure(failure): + return transform(failure) + } + } +} + +extension Result where Failure: Error { + /// Returns the success value as a throwing expression. + /// + /// Use this method to retrieve the value of this result if it represents a + /// success, or to catch the value if it represents a failure. + /// + /// let integerResult: Result = .success(5) + /// do { + /// let value = try integerResult.get() + /// print("The value is \(value).") + /// } catch error { + /// print("Error retrieving the value: \(error)") + /// } + /// // Prints "The value is 5." + /// + /// - Returns: The success value, if the instance represents a success. + /// - Throws: The failure value, if the instance represents a failure. + public func get() throws -> Success { + switch self { + case let .success(success): + return success + case let .failure(failure): + throw failure + } + } + + /// Unwraps the `Result` into a throwing expression. + /// + /// - Returns: The success value, if the instance is a success. + /// - Throws: The error value, if the instance is a failure. + @available(*, deprecated, message: "This method will be removed soon. Use `get() throws -> Success` instead.") + public func unwrapped() throws -> Success { + switch self { + case let .success(value): + return value + case let .failure(error): + throw error + } + } +} + +extension Result where Failure == Swift.Error { + /// Creates a new result by evaluating a throwing closure, capturing the + /// returned value as a success, or any thrown error as a failure. + /// + /// - Parameter body: A throwing closure to evaluate. + @_transparent + public init(catching body: () throws -> Success) { + do { + self = .success(try body()) + } catch { + self = .failure(error) + } + } +} + +extension Result : Equatable where Success : Equatable, Failure: Equatable { } + +extension Result : Hashable where Success : Hashable, Failure : Hashable { } + +extension Result : CustomDebugStringConvertible { + public var debugDescription: String { + var output = "Result." + switch self { + case let .success(value): + output += "success(" + debugPrint(value, terminator: "", to: &output) + case let .failure(error): + output += "failure(" + debugPrint(error, terminator: "", to: &output) + } + output += ")" + + return output + } +} +#endif + +// These helper methods are not public since we do not want them to be exposed or cause any conflicting. +// However, they are just wrapper of `ResultUtil` static methods. +extension Result where Failure: Error { + + /// Evaluates the given transform closures to create a single output value. + /// + /// - Parameters: + /// - onSuccess: A closure that transforms the success value. + /// - onFailure: A closure that transforms the error value. + /// - Returns: A single `Output` value. + func match( + onSuccess: (Success) -> Output, + onFailure: (Failure) -> Output) -> Output + { + switch self { + case let .success(value): + return onSuccess(value) + case let .failure(error): + return onFailure(error) + } + } + + func matchSuccess(with folder: (Success?) -> Output) -> Output { + return match( + onSuccess: { value in return folder(value) }, + onFailure: { _ in return folder(nil) } + ) + } + + func matchFailure(with folder: (Error?) -> Output) -> Output { + return match( + onSuccess: { _ in return folder(nil) }, + onFailure: { error in return folder(error) } + ) + } + + func match(with folder: (Success?, Error?) -> Output) -> Output { + return match( + onSuccess: { return folder($0, nil) }, + onFailure: { return folder(nil, $0) } + ) + } +} diff --git a/!main project/Pods/Kingfisher/Sources/Utility/Runtime.swift b/!main project/Pods/Kingfisher/Sources/Utility/Runtime.swift new file mode 100644 index 0000000..d5818e2 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Utility/Runtime.swift @@ -0,0 +1,35 @@ +// +// Runtime.swift +// Kingfisher +// +// Created by Wei Wang on 2018/10/12. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +func getAssociatedObject(_ object: Any, _ key: UnsafeRawPointer) -> T? { + return objc_getAssociatedObject(object, key) as? T +} + +func setRetainedAssociatedObject(_ object: Any, _ key: UnsafeRawPointer, _ value: T) { + objc_setAssociatedObject(object, key, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) +} diff --git a/!main project/Pods/Kingfisher/Sources/Utility/SizeExtensions.swift b/!main project/Pods/Kingfisher/Sources/Utility/SizeExtensions.swift new file mode 100644 index 0000000..19d05d6 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Utility/SizeExtensions.swift @@ -0,0 +1,110 @@ +// +// SizeExtensions.swift +// Kingfisher +// +// Created by onevcat on 2018/09/28. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import CoreGraphics + +extension CGSize: KingfisherCompatibleValue {} +extension KingfisherWrapper where Base == CGSize { + + /// Returns a size by resizing the `base` size to a target size under a given content mode. + /// + /// - Parameters: + /// - size: The target size to resize to. + /// - contentMode: Content mode of the target size should be when resizing. + /// - Returns: The resized size under the given `ContentMode`. + public func resize(to size: CGSize, for contentMode: ContentMode) -> CGSize { + switch contentMode { + case .aspectFit: + return constrained(size) + case .aspectFill: + return filling(size) + case .none: + return size + } + } + + /// Returns a size by resizing the `base` size by making it aspect fitting the given `size`. + /// + /// - Parameter size: The size in which the `base` should fit in. + /// - Returns: The size fitted in by the input `size`, while keeps `base` aspect. + public func constrained(_ size: CGSize) -> CGSize { + let aspectWidth = round(aspectRatio * size.height) + let aspectHeight = round(size.width / aspectRatio) + + return aspectWidth > size.width ? + CGSize(width: size.width, height: aspectHeight) : + CGSize(width: aspectWidth, height: size.height) + } + + /// Returns a size by resizing the `base` size by making it aspect filling the given `size`. + /// + /// - Parameter size: The size in which the `base` should fill. + /// - Returns: The size be filled by the input `size`, while keeps `base` aspect. + public func filling(_ size: CGSize) -> CGSize { + let aspectWidth = round(aspectRatio * size.height) + let aspectHeight = round(size.width / aspectRatio) + + return aspectWidth < size.width ? + CGSize(width: size.width, height: aspectHeight) : + CGSize(width: aspectWidth, height: size.height) + } + + /// Returns a `CGRect` for which the `base` size is constrained to an input `size` at a given `anchor` point. + /// + /// - Parameters: + /// - size: The size in which the `base` should be constrained to. + /// - anchor: An anchor point in which the size constraint should happen. + /// - Returns: The result `CGRect` for the constraint operation. + public func constrainedRect(for size: CGSize, anchor: CGPoint) -> CGRect { + + let unifiedAnchor = CGPoint(x: anchor.x.clamped(to: 0.0...1.0), + y: anchor.y.clamped(to: 0.0...1.0)) + + let x = unifiedAnchor.x * base.width - unifiedAnchor.x * size.width + let y = unifiedAnchor.y * base.height - unifiedAnchor.y * size.height + let r = CGRect(x: x, y: y, width: size.width, height: size.height) + + let ori = CGRect(origin: .zero, size: base) + return ori.intersection(r) + } + + private var aspectRatio: CGFloat { + return base.height == 0.0 ? 1.0 : base.width / base.height + } +} + +extension CGRect { + func scaled(_ scale: CGFloat) -> CGRect { + return CGRect(x: origin.x * scale, y: origin.y * scale, + width: size.width * scale, height: size.height * scale) + } +} + +extension Comparable { + func clamped(to limits: ClosedRange) -> Self { + return min(max(self, limits.lowerBound), limits.upperBound) + } +} diff --git a/!main project/Pods/Kingfisher/Sources/Utility/String+MD5.swift b/!main project/Pods/Kingfisher/Sources/Utility/String+MD5.swift new file mode 100644 index 0000000..07eaaf2 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Utility/String+MD5.swift @@ -0,0 +1,291 @@ +// +// String+MD5.swift +// Kingfisher +// +// Created by Wei Wang on 18/09/25. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation +import CommonCrypto + +extension String: KingfisherCompatibleValue { } +extension KingfisherWrapper where Base == String { + var md5: String { + guard let data = base.data(using: .utf8) else { + return base + } + + #if swift(>=5.0) + let message = data.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) in + return [UInt8](bytes) + } + #else + let message = data.withUnsafeBytes { bytes in + return [UInt8](UnsafeBufferPointer(start: bytes, count: data.count)) + } + #endif + + let MD5Calculator = MD5(message) + let MD5Data = MD5Calculator.calculate() + + var MD5String = String() + for c in MD5Data { + MD5String += String(format: "%02x", c) + } + return MD5String + } +} + +// array of bytes, little-endian representation +func arrayOfBytes(_ value: T, length: Int? = nil) -> [UInt8] { + let totalBytes = length ?? (MemoryLayout.size * 8) + + let valuePointer = UnsafeMutablePointer.allocate(capacity: 1) + valuePointer.pointee = value + + let bytes = valuePointer.withMemoryRebound(to: UInt8.self, capacity: totalBytes) { (bytesPointer) -> [UInt8] in + var bytes = [UInt8](repeating: 0, count: totalBytes) + for j in 0...size, totalBytes) { + bytes[totalBytes - 1 - j] = (bytesPointer + j).pointee + } + return bytes + } + + #if swift(>=4.1) + valuePointer.deinitialize(count: 1) + valuePointer.deallocate() + #else + valuePointer.deinitialize() + valuePointer.deallocate(capacity: 1) + #endif + + return bytes +} + +extension Int { + // Array of bytes with optional padding (little-endian) + func bytes(_ totalBytes: Int = MemoryLayout.size) -> [UInt8] { + return arrayOfBytes(self, length: totalBytes) + } + +} + +extension NSMutableData { + + // Convenient way to append bytes + func appendBytes(_ arrayOfBytes: [UInt8]) { + append(arrayOfBytes, length: arrayOfBytes.count) + } + +} + +protocol HashProtocol { + var message: [UInt8] { get } + // Common part for hash calculation. Prepare header data. + func prepare(_ len: Int) -> [UInt8] +} + +extension HashProtocol { + + func prepare(_ len: Int) -> [UInt8] { + var tmpMessage = message + + // Step 1. Append Padding Bits + tmpMessage.append(0x80) // append one bit (UInt8 with one bit) to message + + // append "0" bit until message length in bits ≡ 448 (mod 512) + var msgLength = tmpMessage.count + var counter = 0 + + while msgLength % len != (len - 8) { + counter += 1 + msgLength += 1 + } + + tmpMessage += [UInt8](repeating: 0, count: counter) + return tmpMessage + } +} + +func toUInt32Array(_ slice: ArraySlice) -> [UInt32] { + var result = [UInt32]() + result.reserveCapacity(16) + + for idx in stride(from: slice.startIndex, to: slice.endIndex, by: MemoryLayout.size) { + let d0 = UInt32(slice[idx.advanced(by: 3)]) << 24 + let d1 = UInt32(slice[idx.advanced(by: 2)]) << 16 + let d2 = UInt32(slice[idx.advanced(by: 1)]) << 8 + let d3 = UInt32(slice[idx]) + let val: UInt32 = d0 | d1 | d2 | d3 + + result.append(val) + } + return result +} + +struct BytesIterator: IteratorProtocol { + + let chunkSize: Int + let data: [UInt8] + + init(chunkSize: Int, data: [UInt8]) { + self.chunkSize = chunkSize + self.data = data + } + + var offset = 0 + + mutating func next() -> ArraySlice? { + let end = min(chunkSize, data.count - offset) + let result = data[offset.. 0 ? result : nil + } +} + +struct BytesSequence: Sequence { + let chunkSize: Int + let data: [UInt8] + + func makeIterator() -> BytesIterator { + return BytesIterator(chunkSize: chunkSize, data: data) + } +} + +func rotateLeft(_ value: UInt32, bits: UInt32) -> UInt32 { + return ((value << bits) & 0xFFFFFFFF) | (value >> (32 - bits)) +} + +class MD5: HashProtocol { + + static let size = 16 // 128 / 8 + let message: [UInt8] + + init (_ message: [UInt8]) { + self.message = message + } + + // specifies the per-round shift amounts + private let shifts: [UInt32] = [7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21] + + // binary integer part of the sines of integers (Radians) + private let sines: [UInt32] = [0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, + 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391] + + private let hashes: [UInt32] = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476] + + func calculate() -> [UInt8] { + var tmpMessage = prepare(64) + tmpMessage.reserveCapacity(tmpMessage.count + 4) + + // hash values + var hh = hashes + + // Step 2. Append Length a 64-bit representation of lengthInBits + let lengthInBits = (message.count * 8) + let lengthBytes = lengthInBits.bytes(64 / 8) + tmpMessage += lengthBytes.reversed() + + // Process the message in successive 512-bit chunks: + let chunkSizeBytes = 512 / 8 // 64 + + for chunk in BytesSequence(chunkSize: chunkSizeBytes, data: tmpMessage) { + // break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15 + let M = toUInt32Array(chunk) + assert(M.count == 16, "Invalid array") + + // Initialize hash value for this chunk: + var A: UInt32 = hh[0] + var B: UInt32 = hh[1] + var C: UInt32 = hh[2] + var D: UInt32 = hh[3] + + var dTemp: UInt32 = 0 + + // Main loop + for j in 0 ..< sines.count { + var g = 0 + var F: UInt32 = 0 + + switch j { + case 0...15: + F = (B & C) | ((~B) & D) + g = j + break + case 16...31: + F = (D & B) | (~D & C) + g = (5 * j + 1) % 16 + break + case 32...47: + F = B ^ C ^ D + g = (3 * j + 5) % 16 + break + case 48...63: + F = C ^ (B | (~D)) + g = (7 * j) % 16 + break + default: + break + } + dTemp = D + D = C + C = B + B = B &+ rotateLeft((A &+ F &+ sines[j] &+ M[g]), bits: shifts[j]) + A = dTemp + } + + hh[0] = hh[0] &+ A + hh[1] = hh[1] &+ B + hh[2] = hh[2] &+ C + hh[3] = hh[3] &+ D + } + var result = [UInt8]() + result.reserveCapacity(hh.count / 4) + + hh.forEach { + let itemLE = $0.littleEndian + let r1 = UInt8(itemLE & 0xff) + let r2 = UInt8((itemLE >> 8) & 0xff) + let r3 = UInt8((itemLE >> 16) & 0xff) + let r4 = UInt8((itemLE >> 24) & 0xff) + result += [r1, r2, r3, r4] + } + return result + } +} diff --git a/!main project/Pods/Kingfisher/Sources/Views/AnimatedImageView.swift b/!main project/Pods/Kingfisher/Sources/Views/AnimatedImageView.swift new file mode 100644 index 0000000..e32a1f5 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Views/AnimatedImageView.swift @@ -0,0 +1,615 @@ +// +// AnimatableImageView.swift +// Kingfisher +// +// Created by bl4ckra1sond3tre on 4/22/16. +// +// The AnimatableImageView, AnimatedFrame and Animator is a modified version of +// some classes from kaishin's Gifu project (https://github.com/kaishin/Gifu) +// +// The MIT License (MIT) +// +// Copyright (c) 2019 Reda Lemeden. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// The name and characters used in the demo of this software are property of their +// respective owners. + +#if !os(watchOS) +#if canImport(UIKit) +import UIKit +import ImageIO + +/// Protocol of `AnimatedImageView`. +public protocol AnimatedImageViewDelegate: AnyObject { + + /// Called after the animatedImageView has finished each animation loop. + /// + /// - Parameters: + /// - imageView: The `AnimatedImageView` that is being animated. + /// - count: The looped count. + func animatedImageView(_ imageView: AnimatedImageView, didPlayAnimationLoops count: UInt) + + /// Called after the `AnimatedImageView` has reached the max repeat count. + /// + /// - Parameter imageView: The `AnimatedImageView` that is being animated. + func animatedImageViewDidFinishAnimating(_ imageView: AnimatedImageView) +} + +extension AnimatedImageViewDelegate { + public func animatedImageView(_ imageView: AnimatedImageView, didPlayAnimationLoops count: UInt) {} + public func animatedImageViewDidFinishAnimating(_ imageView: AnimatedImageView) {} +} + +#if swift(>=4.2) +let KFRunLoopModeCommon = RunLoop.Mode.common +#else +let KFRunLoopModeCommon = RunLoopMode.commonModes +#endif + +/// Represents a subclass of `UIImageView` for displaying animated image. +/// Different from showing animated image in a normal `UIImageView` (which load all frames at one time), +/// `AnimatedImageView` only tries to load several frames (defined by `framePreloadCount`) to reduce memory usage. +/// It provides a tradeoff between memory usage and CPU time. If you have a memory issue when using a normal image +/// view to load GIF data, you could give this class a try. +/// +/// Kingfisher supports setting GIF animated data to either `UIImageView` and `AnimatedImageView` out of box. So +/// it would be fairly easy to switch between them. +open class AnimatedImageView: UIImageView { + + /// Proxy object for preventing a reference cycle between the `CADDisplayLink` and `AnimatedImageView`. + class TargetProxy { + private weak var target: AnimatedImageView? + + init(target: AnimatedImageView) { + self.target = target + } + + @objc func onScreenUpdate() { + target?.updateFrameIfNeeded() + } + } + + /// Enumeration that specifies repeat count of GIF + public enum RepeatCount: Equatable { + case once + case finite(count: UInt) + case infinite + + public static func ==(lhs: RepeatCount, rhs: RepeatCount) -> Bool { + switch (lhs, rhs) { + case let (.finite(l), .finite(r)): + return l == r + case (.once, .once), + (.infinite, .infinite): + return true + case (.once, .finite(let count)), + (.finite(let count), .once): + return count == 1 + case (.once, _), + (.infinite, _), + (.finite, _): + return false + } + } + } + + // MARK: - Public property + /// Whether automatically play the animation when the view become visible. Default is `true`. + public var autoPlayAnimatedImage = true + + /// The count of the frames should be preloaded before shown. + public var framePreloadCount = 10 + + /// Specifies whether the GIF frames should be pre-scaled to the image view's size or not. + /// If the downloaded image is larger than the image view's size, it will help to reduce some memory use. + /// Default is `true`. + public var needsPrescaling = true + + /// Decode the GIF frames in background thread before using. It will decode frames data and do a off-screen + /// rendering to extract pixel information in background. This can reduce the main thread CPU usage. + public var backgroundDecode = true + + /// The animation timer's run loop mode. Default is `RunLoop.Mode.common`. + /// Set this property to `RunLoop.Mode.default` will make the animation pause during UIScrollView scrolling. + public var runLoopMode = KFRunLoopModeCommon { + willSet { + guard runLoopMode != newValue else { return } + stopAnimating() + displayLink.remove(from: .main, forMode: runLoopMode) + displayLink.add(to: .main, forMode: newValue) + startAnimating() + } + } + + /// The repeat count. The animated image will keep animate until it the loop count reaches this value. + /// Setting this value to another one will reset current animation. + /// + /// Default is `.infinite`, which means the animation will last forever. + public var repeatCount = RepeatCount.infinite { + didSet { + if oldValue != repeatCount { + reset() + setNeedsDisplay() + layer.setNeedsDisplay() + } + } + } + + /// Delegate of this `AnimatedImageView` object. See `AnimatedImageViewDelegate` protocol for more. + public weak var delegate: AnimatedImageViewDelegate? + + // MARK: - Private property + /// `Animator` instance that holds the frames of a specific image in memory. + private var animator: Animator? + + // Dispatch queue used for preloading images. + private lazy var preloadQueue: DispatchQueue = { + return DispatchQueue(label: "com.onevcat.Kingfisher.Animator.preloadQueue") + }() + + // A flag to avoid invalidating the displayLink on deinit if it was never created, because displayLink is so lazy. + private var isDisplayLinkInitialized: Bool = false + + // A display link that keeps calling the `updateFrame` method on every screen refresh. + private lazy var displayLink: CADisplayLink = { + isDisplayLinkInitialized = true + let displayLink = CADisplayLink( + target: TargetProxy(target: self), selector: #selector(TargetProxy.onScreenUpdate)) + displayLink.add(to: .main, forMode: runLoopMode) + displayLink.isPaused = true + return displayLink + }() + + // MARK: - Override + override open var image: KFCrossPlatformImage? { + didSet { + if image != oldValue { + reset() + } + setNeedsDisplay() + layer.setNeedsDisplay() + } + } + + deinit { + if isDisplayLinkInitialized { + displayLink.invalidate() + } + } + + override open var isAnimating: Bool { + if isDisplayLinkInitialized { + return !displayLink.isPaused + } else { + return super.isAnimating + } + } + + /// Starts the animation. + override open func startAnimating() { + guard !isAnimating else { return } + if animator?.isReachMaxRepeatCount ?? false { + return + } + + displayLink.isPaused = false + } + + /// Stops the animation. + override open func stopAnimating() { + super.stopAnimating() + if isDisplayLinkInitialized { + displayLink.isPaused = true + } + } + + override open func display(_ layer: CALayer) { + if let currentFrame = animator?.currentFrameImage { + layer.contents = currentFrame.cgImage + } else { + layer.contents = image?.cgImage + } + } + + override open func didMoveToWindow() { + super.didMoveToWindow() + didMove() + } + + override open func didMoveToSuperview() { + super.didMoveToSuperview() + didMove() + } + + // This is for back compatibility that using regular `UIImageView` to show animated image. + override func shouldPreloadAllAnimation() -> Bool { + return false + } + + // Reset the animator. + private func reset() { + animator = nil + if let imageSource = image?.kf.imageSource { + let targetSize = bounds.scaled(UIScreen.main.scale).size + let animator = Animator( + imageSource: imageSource, + contentMode: contentMode, + size: targetSize, + framePreloadCount: framePreloadCount, + repeatCount: repeatCount, + preloadQueue: preloadQueue) + animator.delegate = self + animator.needsPrescaling = needsPrescaling + animator.backgroundDecode = backgroundDecode + animator.prepareFramesAsynchronously() + self.animator = animator + } + didMove() + } + + private func didMove() { + if autoPlayAnimatedImage && animator != nil { + if let _ = superview, let _ = window { + startAnimating() + } else { + stopAnimating() + } + } + } + + /// Update the current frame with the displayLink duration. + private func updateFrameIfNeeded() { + guard let animator = animator else { + return + } + + guard !animator.isFinished else { + stopAnimating() + delegate?.animatedImageViewDidFinishAnimating(self) + return + } + + let duration: CFTimeInterval + + // CA based display link is opt-out from ProMotion by default. + // So the duration and its FPS might not match. + // See [#718](https://github.com/onevcat/Kingfisher/issues/718) + // By setting CADisableMinimumFrameDuration to YES in Info.plist may + // cause the preferredFramesPerSecond being 0 + let preferredFramesPerSecond = displayLink.preferredFramesPerSecond + if preferredFramesPerSecond == 0 { + duration = displayLink.duration + } else { + // Some devices (like iPad Pro 10.5) will have a different FPS. + duration = 1.0 / TimeInterval(preferredFramesPerSecond) + } + + animator.shouldChangeFrame(with: duration) { [weak self] hasNewFrame in + if hasNewFrame { + self?.layer.setNeedsDisplay() + } + } + } +} + +protocol AnimatorDelegate: AnyObject { + func animator(_ animator: AnimatedImageView.Animator, didPlayAnimationLoops count: UInt) +} + +extension AnimatedImageView: AnimatorDelegate { + func animator(_ animator: Animator, didPlayAnimationLoops count: UInt) { + delegate?.animatedImageView(self, didPlayAnimationLoops: count) + } +} + +extension AnimatedImageView { + + // Represents a single frame in a GIF. + struct AnimatedFrame { + + // The image to display for this frame. Its value is nil when the frame is removed from the buffer. + let image: UIImage? + + // The duration that this frame should remain active. + let duration: TimeInterval + + // A placeholder frame with no image assigned. + // Used to replace frames that are no longer needed in the animation. + var placeholderFrame: AnimatedFrame { + return AnimatedFrame(image: nil, duration: duration) + } + + // Whether this frame instance contains an image or not. + var isPlaceholder: Bool { + return image == nil + } + + // Returns a new instance from an optional image. + // + // - parameter image: An optional `UIImage` instance to be assigned to the new frame. + // - returns: An `AnimatedFrame` instance. + func makeAnimatedFrame(image: UIImage?) -> AnimatedFrame { + return AnimatedFrame(image: image, duration: duration) + } + } +} + +extension AnimatedImageView { + + // MARK: - Animator + class Animator { + private let size: CGSize + private let maxFrameCount: Int + private let imageSource: CGImageSource + private let maxRepeatCount: RepeatCount + + private let maxTimeStep: TimeInterval = 1.0 + private let animatedFrames = SafeArray() + private var frameCount = 0 + private var timeSinceLastFrameChange: TimeInterval = 0.0 + private var currentRepeatCount: UInt = 0 + + var isFinished: Bool = false + + var needsPrescaling = true + + var backgroundDecode = true + + weak var delegate: AnimatorDelegate? + + // Total duration of one animation loop + var loopDuration: TimeInterval = 0 + + // Current active frame image + var currentFrameImage: UIImage? { + return frame(at: currentFrameIndex) + } + + // Current active frame duration + var currentFrameDuration: TimeInterval { + return duration(at: currentFrameIndex) + } + + // The index of the current GIF frame. + var currentFrameIndex = 0 { + didSet { + previousFrameIndex = oldValue + } + } + + var previousFrameIndex = 0 { + didSet { + preloadQueue.async { + self.updatePreloadedFrames() + } + } + } + + var isReachMaxRepeatCount: Bool { + switch maxRepeatCount { + case .once: + return currentRepeatCount >= 1 + case .finite(let maxCount): + return currentRepeatCount >= maxCount + case .infinite: + return false + } + } + + var isLastFrame: Bool { + return currentFrameIndex == frameCount - 1 + } + + var preloadingIsNeeded: Bool { + return maxFrameCount < frameCount - 1 + } + + var contentMode = UIView.ContentMode.scaleToFill + + private lazy var preloadQueue: DispatchQueue = { + return DispatchQueue(label: "com.onevcat.Kingfisher.Animator.preloadQueue") + }() + + /// Creates an animator with image source reference. + /// + /// - Parameters: + /// - source: The reference of animated image. + /// - mode: Content mode of the `AnimatedImageView`. + /// - size: Size of the `AnimatedImageView`. + /// - count: Count of frames needed to be preloaded. + /// - repeatCount: The repeat count should this animator uses. + init(imageSource source: CGImageSource, + contentMode mode: UIView.ContentMode, + size: CGSize, + framePreloadCount count: Int, + repeatCount: RepeatCount, + preloadQueue: DispatchQueue) { + self.imageSource = source + self.contentMode = mode + self.size = size + self.maxFrameCount = count + self.maxRepeatCount = repeatCount + self.preloadQueue = preloadQueue + } + + func frame(at index: Int) -> KFCrossPlatformImage? { + return animatedFrames[index]?.image + } + + func duration(at index: Int) -> TimeInterval { + return animatedFrames[index]?.duration ?? .infinity + } + + func prepareFramesAsynchronously() { + frameCount = Int(CGImageSourceGetCount(imageSource)) + animatedFrames.reserveCapacity(frameCount) + preloadQueue.async { [weak self] in + self?.setupAnimatedFrames() + } + } + + func shouldChangeFrame(with duration: CFTimeInterval, handler: (Bool) -> Void) { + incrementTimeSinceLastFrameChange(with: duration) + + if currentFrameDuration > timeSinceLastFrameChange { + handler(false) + } else { + resetTimeSinceLastFrameChange() + incrementCurrentFrameIndex() + handler(true) + } + } + + private func setupAnimatedFrames() { + resetAnimatedFrames() + + var duration: TimeInterval = 0 + + (0.. maxFrameCount { return } + animatedFrames[index] = animatedFrames[index]?.makeAnimatedFrame(image: loadFrame(at: index)) + } + + self.loopDuration = duration + } + + private func resetAnimatedFrames() { + animatedFrames.removeAll() + } + + private func loadFrame(at index: Int) -> UIImage? { + let options: [CFString: Any] = [ + kCGImageSourceCreateThumbnailFromImageIfAbsent: true, + kCGImageSourceCreateThumbnailWithTransform: true, + kCGImageSourceShouldCacheImmediately: true, + kCGImageSourceThumbnailMaxPixelSize: max(size.width, size.height) + ] + + let resize = needsPrescaling && size != .zero + guard let cgImage = CGImageSourceCreateImageAtIndex(imageSource, + index, + resize ? options as CFDictionary : nil) else { + return nil + } + + let image = KFCrossPlatformImage(cgImage: cgImage) + return backgroundDecode ? image.kf.decoded : image + } + + private func updatePreloadedFrames() { + guard preloadingIsNeeded else { + return + } + + animatedFrames[previousFrameIndex] = animatedFrames[previousFrameIndex]?.placeholderFrame + + preloadIndexes(start: currentFrameIndex).forEach { index in + guard let currentAnimatedFrame = animatedFrames[index] else { return } + if !currentAnimatedFrame.isPlaceholder { return } + animatedFrames[index] = currentAnimatedFrame.makeAnimatedFrame(image: loadFrame(at: index)) + } + } + + private func incrementCurrentFrameIndex() { + currentFrameIndex = increment(frameIndex: currentFrameIndex) + if isLastFrame { + currentRepeatCount += 1 + if isReachMaxRepeatCount { + isFinished = true + } + delegate?.animator(self, didPlayAnimationLoops: currentRepeatCount) + } + } + + private func incrementTimeSinceLastFrameChange(with duration: TimeInterval) { + timeSinceLastFrameChange += min(maxTimeStep, duration) + } + + private func resetTimeSinceLastFrameChange() { + timeSinceLastFrameChange -= currentFrameDuration + } + + private func increment(frameIndex: Int, by value: Int = 1) -> Int { + return (frameIndex + value) % frameCount + } + + private func preloadIndexes(start index: Int) -> [Int] { + let nextIndex = increment(frameIndex: index) + let lastIndex = increment(frameIndex: index, by: maxFrameCount) + + if lastIndex >= nextIndex { + return [Int](nextIndex...lastIndex) + } else { + return [Int](nextIndex.. { + private var array: Array = [] + private let lock = NSLock() + + subscript(index: Int) -> Element? { + get { + lock.lock() + defer { lock.unlock() } + return array.indices ~= index ? array[index] : nil + } + + set { + lock.lock() + defer { lock.unlock() } + if let newValue = newValue, array.indices ~= index { + array[index] = newValue + } + } + } + + var count : Int { + lock.lock() + defer { lock.unlock() } + return array.count + } + + func reserveCapacity(_ count: Int) { + lock.lock() + defer { lock.unlock() } + array.reserveCapacity(count) + } + + func append(_ element: Element) { + lock.lock() + defer { lock.unlock() } + array += [element] + } + + func removeAll() { + lock.lock() + defer { lock.unlock() } + array = [] + } +} +#endif +#endif diff --git a/!main project/Pods/Kingfisher/Sources/Views/Indicator.swift b/!main project/Pods/Kingfisher/Sources/Views/Indicator.swift new file mode 100644 index 0000000..9ec9547 --- /dev/null +++ b/!main project/Pods/Kingfisher/Sources/Views/Indicator.swift @@ -0,0 +1,236 @@ +// +// Indicator.swift +// Kingfisher +// +// Created by João D. Moreira on 30/08/16. +// +// Copyright (c) 2019 Wei Wang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#if !os(watchOS) + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit +public typealias IndicatorView = NSView +#else +import UIKit +public typealias IndicatorView = UIView +#endif + +/// Represents the activity indicator type which should be added to +/// an image view when an image is being downloaded. +/// +/// - none: No indicator. +/// - activity: Uses the system activity indicator. +/// - image: Uses an image as indicator. GIF is supported. +/// - custom: Uses a custom indicator. The type of associated value should conform to the `Indicator` protocol. +public enum IndicatorType { + /// No indicator. + case none + /// Uses the system activity indicator. + case activity + /// Uses an image as indicator. GIF is supported. + case image(imageData: Data) + /// Uses a custom indicator. The type of associated value should conform to the `Indicator` protocol. + case custom(indicator: Indicator) +} + +/// An indicator type which can be used to show the download task is in progress. +public protocol Indicator { + + /// Called when the indicator should start animating. + func startAnimatingView() + + /// Called when the indicator should stop animating. + func stopAnimatingView() + + /// Center offset of the indicator. Kingfisher will use this value to determine the position of + /// indicator in the super view. + var centerOffset: CGPoint { get } + + /// The indicator view which would be added to the super view. + var view: IndicatorView { get } + + /// The size strategy used when adding the indicator to image view. + /// - Parameter imageView: The super view of indicator. + func sizeStrategy(in imageView: KFCrossPlatformImageView) -> IndicatorSizeStrategy +} + +public enum IndicatorSizeStrategy { + case intrinsicSize + case full + case size(CGSize) +} + +extension Indicator { + + /// Default implementation of `centerOffset` of `Indicator`. The default value is `.zero`, means that there is + /// no offset for the indicator view. + public var centerOffset: CGPoint { return .zero } + + + /// Default implementation of `centerOffset` of `Indicator`. The default value is `.full`, means that the indicator + /// will pin to the same height and width as the image view. + public func sizeStrategy(in imageView: KFCrossPlatformImageView) -> IndicatorSizeStrategy { + return .full + } +} + +// Displays a NSProgressIndicator / UIActivityIndicatorView +final class ActivityIndicator: Indicator { + + #if os(macOS) + private let activityIndicatorView: NSProgressIndicator + #else + private let activityIndicatorView: UIActivityIndicatorView + #endif + private var animatingCount = 0 + + var view: IndicatorView { + return activityIndicatorView + } + + func startAnimatingView() { + if animatingCount == 0 { + #if os(macOS) + activityIndicatorView.startAnimation(nil) + #else + activityIndicatorView.startAnimating() + #endif + activityIndicatorView.isHidden = false + } + animatingCount += 1 + } + + func stopAnimatingView() { + animatingCount = max(animatingCount - 1, 0) + if animatingCount == 0 { + #if os(macOS) + activityIndicatorView.stopAnimation(nil) + #else + activityIndicatorView.stopAnimating() + #endif + activityIndicatorView.isHidden = true + } + } + + func sizeStrategy(in imageView: KFCrossPlatformImageView) -> IndicatorSizeStrategy { + return .intrinsicSize + } + + init() { + #if os(macOS) + activityIndicatorView = NSProgressIndicator(frame: CGRect(x: 0, y: 0, width: 16, height: 16)) + activityIndicatorView.controlSize = .small + activityIndicatorView.style = .spinning + #else + let indicatorStyle: UIActivityIndicatorView.Style + + #if os(tvOS) + if #available(tvOS 13.0, *) { + indicatorStyle = UIActivityIndicatorView.Style.large + } else { + indicatorStyle = UIActivityIndicatorView.Style.white + } + #else + if #available(iOS 13.0, * ) { + indicatorStyle = UIActivityIndicatorView.Style.medium + } else { + indicatorStyle = UIActivityIndicatorView.Style.gray + } + #endif + + #if swift(>=4.2) + activityIndicatorView = UIActivityIndicatorView(style: indicatorStyle) + #else + activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: indicatorStyle) + #endif + #endif + } +} + +#if canImport(UIKit) +extension UIActivityIndicatorView.Style { + #if compiler(>=5.1) + #else + static let large = UIActivityIndicatorView.Style.white + #if !os(tvOS) + static let medium = UIActivityIndicatorView.Style.gray + #endif + #endif +} +#endif + +// MARK: - ImageIndicator +// Displays an ImageView. Supports gif +final class ImageIndicator: Indicator { + private let animatedImageIndicatorView: KFCrossPlatformImageView + + var view: IndicatorView { + return animatedImageIndicatorView + } + + init?( + imageData data: Data, + processor: ImageProcessor = DefaultImageProcessor.default, + options: KingfisherParsedOptionsInfo? = nil) + { + var options = options ?? KingfisherParsedOptionsInfo(nil) + // Use normal image view to show animations, so we need to preload all animation data. + if !options.preloadAllAnimationData { + options.preloadAllAnimationData = true + } + + guard let image = processor.process(item: .data(data), options: options) else { + return nil + } + + animatedImageIndicatorView = KFCrossPlatformImageView() + animatedImageIndicatorView.image = image + + #if os(macOS) + // Need for gif to animate on macOS + animatedImageIndicatorView.imageScaling = .scaleNone + animatedImageIndicatorView.canDrawSubviewsIntoLayer = true + #else + animatedImageIndicatorView.contentMode = .center + #endif + } + + func startAnimatingView() { + #if os(macOS) + animatedImageIndicatorView.animates = true + #else + animatedImageIndicatorView.startAnimating() + #endif + animatedImageIndicatorView.isHidden = false + } + + func stopAnimatingView() { + #if os(macOS) + animatedImageIndicatorView.animates = false + #else + animatedImageIndicatorView.stopAnimating() + #endif + animatedImageIndicatorView.isHidden = true + } +} + +#endif diff --git a/!main project/Pods/Manifest.lock b/!main project/Pods/Manifest.lock new file mode 100644 index 0000000..3fc9492 --- /dev/null +++ b/!main project/Pods/Manifest.lock @@ -0,0 +1,175 @@ +PODS: + - Alamofire (4.9.1) + - Firebase/Analytics (6.16.0): + - Firebase/Core + - Firebase/Auth (6.16.0): + - Firebase/CoreOnly + - FirebaseAuth (~> 6.4.3) + - Firebase/Core (6.16.0): + - Firebase/CoreOnly + - FirebaseAnalytics (= 6.2.2) + - Firebase/CoreOnly (6.16.0): + - FirebaseCore (= 6.6.1) + - Firebase/Crashlytics (6.16.0): + - Firebase/CoreOnly + - FirebaseCrashlytics (~> 4.0.0-beta.2) + - Firebase/Database (6.16.0): + - Firebase/CoreOnly + - FirebaseDatabase (~> 6.1.4) + - FirebaseAnalytics (6.2.2): + - FirebaseCore (~> 6.6) + - FirebaseInstanceID (~> 4.3) + - GoogleAppMeasurement (= 6.2.2) + - GoogleUtilities/AppDelegateSwizzler (~> 6.0) + - GoogleUtilities/MethodSwizzler (~> 6.0) + - GoogleUtilities/Network (~> 6.0) + - "GoogleUtilities/NSData+zlib (~> 6.0)" + - nanopb (= 0.3.9011) + - FirebaseAnalyticsInterop (1.5.0) + - FirebaseAuth (6.4.3): + - FirebaseAuthInterop (~> 1.0) + - FirebaseCore (~> 6.6) + - GoogleUtilities/AppDelegateSwizzler (~> 6.5) + - GoogleUtilities/Environment (~> 6.5) + - GTMSessionFetcher/Core (~> 1.1) + - FirebaseAuthInterop (1.0.0) + - FirebaseCore (6.6.1): + - FirebaseCoreDiagnostics (~> 1.2) + - FirebaseCoreDiagnosticsInterop (~> 1.2) + - GoogleUtilities/Environment (~> 6.5) + - GoogleUtilities/Logger (~> 6.5) + - FirebaseCoreDiagnostics (1.2.0): + - FirebaseCoreDiagnosticsInterop (~> 1.2) + - GoogleDataTransportCCTSupport (~> 1.3) + - GoogleUtilities/Environment (~> 6.5) + - GoogleUtilities/Logger (~> 6.5) + - nanopb (~> 0.3.901) + - FirebaseCoreDiagnosticsInterop (1.2.0) + - FirebaseCrashlytics (4.0.0-beta.3): + - FirebaseAnalyticsInterop (~> 1.2) + - FirebaseCore (~> 6.6) + - FirebaseInstanceID (~> 4.3) + - PromisesObjC (~> 1.2) + - FirebaseDatabase (6.1.4): + - FirebaseAuthInterop (~> 1.0) + - FirebaseCore (~> 6.0) + - leveldb-library (~> 1.22) + - FirebaseInstallations (1.1.0): + - FirebaseCore (~> 6.6) + - GoogleUtilities/UserDefaults (~> 6.5) + - PromisesObjC (~> 1.2) + - FirebaseInstanceID (4.3.0): + - FirebaseCore (~> 6.6) + - FirebaseInstallations (~> 1.0) + - GoogleUtilities/Environment (~> 6.5) + - GoogleUtilities/UserDefaults (~> 6.5) + - GoogleAppMeasurement (6.2.2): + - GoogleUtilities/AppDelegateSwizzler (~> 6.0) + - GoogleUtilities/MethodSwizzler (~> 6.0) + - GoogleUtilities/Network (~> 6.0) + - "GoogleUtilities/NSData+zlib (~> 6.0)" + - nanopb (= 0.3.9011) + - GoogleDataTransport (3.3.1) + - GoogleDataTransportCCTSupport (1.3.1): + - GoogleDataTransport (~> 3.3) + - nanopb (~> 0.3.901) + - GoogleUtilities/AppDelegateSwizzler (6.5.1): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Environment (6.5.1) + - GoogleUtilities/Logger (6.5.1): + - GoogleUtilities/Environment + - GoogleUtilities/MethodSwizzler (6.5.1): + - GoogleUtilities/Logger + - GoogleUtilities/Network (6.5.1): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (6.5.1)" + - GoogleUtilities/Reachability (6.5.1): + - GoogleUtilities/Logger + - GoogleUtilities/UserDefaults (6.5.1): + - GoogleUtilities/Logger + - GTMSessionFetcher/Core (1.3.1) + - Kingfisher (5.13.0): + - Kingfisher/Core (= 5.13.0) + - Kingfisher/Core (5.13.0) + - leveldb-library (1.22) + - nanopb (0.3.9011): + - nanopb/decode (= 0.3.9011) + - nanopb/encode (= 0.3.9011) + - nanopb/decode (0.3.9011) + - nanopb/encode (0.3.9011) + - PromisesObjC (1.2.8) + - Realm (4.3.1): + - Realm/Headers (= 4.3.1) + - Realm/Headers (4.3.1) + - RealmSwift (4.3.1): + - Realm (= 4.3.1) + +DEPENDENCIES: + - Alamofire + - Firebase/Analytics + - Firebase/Auth + - Firebase/Crashlytics + - Firebase/Database + - Kingfisher (~> 5.0) + - RealmSwift + +SPEC REPOS: + trunk: + - Alamofire + - Firebase + - FirebaseAnalytics + - FirebaseAnalyticsInterop + - FirebaseAuth + - FirebaseAuthInterop + - FirebaseCore + - FirebaseCoreDiagnostics + - FirebaseCoreDiagnosticsInterop + - FirebaseCrashlytics + - FirebaseDatabase + - FirebaseInstallations + - FirebaseInstanceID + - GoogleAppMeasurement + - GoogleDataTransport + - GoogleDataTransportCCTSupport + - GoogleUtilities + - GTMSessionFetcher + - Kingfisher + - leveldb-library + - nanopb + - PromisesObjC + - Realm + - RealmSwift + +SPEC CHECKSUMS: + Alamofire: 85e8a02c69d6020a0d734f6054870d7ecb75cf18 + Firebase: 497158b816d0a86fc31babbd05546fcd7e6083ff + FirebaseAnalytics: cf95d3aab897612783020fbd98401d5366f135ee + FirebaseAnalyticsInterop: 3f86269c38ae41f47afeb43ebf32a001f58fcdae + FirebaseAuth: 5ce2b03a3d7fe56b7a6e4c5ec7ff1522890b1d6f + FirebaseAuthInterop: 0ffa57668be100582bb7643d4fcb7615496c41fc + FirebaseCore: 85064903ed6c28e47fec9c7bd149d94ba1b6b6e7 + FirebaseCoreDiagnostics: 5e78803ab276bc5b50340e3c539c06c3de35c649 + FirebaseCoreDiagnosticsInterop: 296e2c5f5314500a850ad0b83e9e7c10b011a850 + FirebaseCrashlytics: 81ac4950302353dc6b5f8d5cc287397725b54c97 + FirebaseDatabase: 0144e0706a4761f1b0e8679572eba8095ddb59be + FirebaseInstallations: 575cd32f2aec0feeb0e44f5d0110a09e5e60b47b + FirebaseInstanceID: 6668efc1655a4052c083f287a7141f1ead12f9c2 + GoogleAppMeasurement: d0560d915abf15e692e8538ba1d58442217b6aff + GoogleDataTransport: 0048df6388dab1c254799f2a30365b1dffe20422 + GoogleDataTransportCCTSupport: f880d70972efa2ed1be4e9173a0f4c5f3dc2d176 + GoogleUtilities: 06eb53bb579efe7099152735900dd04bf09e7275 + GTMSessionFetcher: cea130bbfe5a7edc8d06d3f0d17288c32ffe9925 + Kingfisher: 0d334cada987fcddbe9b5cec516406e1ee9d4748 + leveldb-library: 55d93ee664b4007aac644a782d11da33fba316f7 + nanopb: 18003b5e52dab79db540fe93fe9579f399bd1ccd + PromisesObjC: c119f3cd559f50b7ae681fa59dc1acd19173b7e6 + Realm: 31dc40934081ef740f60feedc46d88473cbfeb40 + RealmSwift: b5199e9a41f67b99f51acf6874a6166f5fbe93d1 + +PODFILE CHECKSUM: b74a28c3a66700466c956a4451099909423c4a27 + +COCOAPODS: 1.8.4 diff --git a/!main project/Pods/Pods.xcodeproj/project.pbxproj b/!main project/Pods/Pods.xcodeproj/project.pbxproj new file mode 100644 index 0000000..b19af2c --- /dev/null +++ b/!main project/Pods/Pods.xcodeproj/project.pbxproj @@ -0,0 +1,9833 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXAggregateTarget section */ + 072CEA044D2EF26F03496D5996BBF59F /* Firebase */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 61E400600BC3D57ED87E094438ED32B3 /* Build configuration list for PBXAggregateTarget "Firebase" */; + buildPhases = ( + ); + dependencies = ( + 392C8E30155BB885FD34DB42F4D2F71A /* PBXTargetDependency */, + 05A0551F8A1D8AD97AFC2B0BCAB2EB88 /* PBXTargetDependency */, + 2FCE7DA6B2B795AA927052F731A13289 /* PBXTargetDependency */, + 36E1DFA2B32651F86C10E61074C30919 /* PBXTargetDependency */, + 700FF6B24F804D742CDB062FE6379C20 /* PBXTargetDependency */, + ); + name = Firebase; + }; + 5EB4B0B6DA6D5C0C3365733BEAA1C485 /* FirebaseCoreDiagnosticsInterop */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 41BB255B9390F3173EA80D74E50ADA81 /* Build configuration list for PBXAggregateTarget "FirebaseCoreDiagnosticsInterop" */; + buildPhases = ( + ); + dependencies = ( + ); + name = FirebaseCoreDiagnosticsInterop; + }; + 8EC0F2618965C875A96BFDBEE5D9734C /* FirebaseAuthInterop */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 048F3456D8B6D09E4223203A2B3483BA /* Build configuration list for PBXAggregateTarget "FirebaseAuthInterop" */; + buildPhases = ( + ); + dependencies = ( + ); + name = FirebaseAuthInterop; + }; + B53D977A951AFC38B21751B706C1DF83 /* GoogleAppMeasurement */ = { + isa = PBXAggregateTarget; + buildConfigurationList = AADDC00E893C2C74062874D0780A3D35 /* Build configuration list for PBXAggregateTarget "GoogleAppMeasurement" */; + buildPhases = ( + ); + dependencies = ( + 4087E8787FCE3268E8AA78C79269F7C8 /* PBXTargetDependency */, + 79A464178DFA35E8023E00C69B3A4748 /* PBXTargetDependency */, + ); + name = GoogleAppMeasurement; + }; + C49E7A4D59E5C8BE8DE9FB1EFB150185 /* FirebaseAnalytics */ = { + isa = PBXAggregateTarget; + buildConfigurationList = F7B6A2C25C58E401CC0123EEDFC62D1D /* Build configuration list for PBXAggregateTarget "FirebaseAnalytics" */; + buildPhases = ( + ); + dependencies = ( + 2A43784F73C5BB2A1F17517D4638761C /* PBXTargetDependency */, + D3A0FC56FD2A358D381B58B6BB665637 /* PBXTargetDependency */, + 0FF5D7B75F419BBCA71A1A03FE2B15D0 /* PBXTargetDependency */, + E1F058D92ABF497DDB714C894B3C044F /* PBXTargetDependency */, + B6870CB8F706887EE0DDB4C76EC831CA /* PBXTargetDependency */, + ); + name = FirebaseAnalytics; + }; + D372E53E2E8FEAA06A0439FB85E65767 /* FirebaseAnalyticsInterop */ = { + isa = PBXAggregateTarget; + buildConfigurationList = C84C02119D44D6E45D81B9190AFAA86F /* Build configuration list for PBXAggregateTarget "FirebaseAnalyticsInterop" */; + buildPhases = ( + ); + dependencies = ( + ); + name = FirebaseAnalyticsInterop; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 0047B13AE63FA26AAA8DB8F11B430C02 /* work_queue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C9DBBF81986552C984F047D58DD511CF /* work_queue.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 005277412A118E72336AA68F490CCAD8 /* FIRSignInWithGameCenterResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = AAB15C35792D73E4FA31853F7AE525AA /* FIRSignInWithGameCenterResponse.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 007401F7CE172EB9B8DDF081F6E09387 /* FTupleFirebase.m in Sources */ = {isa = PBXBuildFile; fileRef = 29051F2C16CCD3952E36E6E505E5A87D /* FTupleFirebase.m */; }; + 00C1ADFC99A069C522547330009F4E19 /* GDTCCTCompressionHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = F40ABB1387B024B0A59D03206A41F30D /* GDTCCTCompressionHelper.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 00E4FBEFC7AC22B61C48D083089F08D5 /* GULLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 33EBA20CB596F5C5EC3DD2C203BF1399 /* GULLogger.m */; }; + 0108306551C9465CE4A9084EF7B0D848 /* FirebaseCore-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = BF3AF58142843EDD7EB3EFB72810E7B9 /* FirebaseCore-dummy.m */; }; + 0111886BCBA7E203D31D37D93130988C /* RLMSyncSubscription.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = C8D9FF6584BEA3EB2DDF9561F2ABC8D1 /* RLMSyncSubscription.h */; }; + 011B88631D31B307CC55C071F34B14AD /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64025D1D442A26509C94DDDE4BCBF577 /* Foundation.framework */; }; + 01594852414E2C06EAF0A7A14DDF1F07 /* FIRAuthStoredUserManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 36801A35862CCA94D4DAC7925FC64064 /* FIRAuthStoredUserManager.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 015A0369EB034ABD53223DCC1697046C /* RLMRealmConfiguration+Sync.h in Headers */ = {isa = PBXBuildFile; fileRef = BBDE0947F81FE23D7DC2157383181911 /* RLMRealmConfiguration+Sync.h */; }; + 019D8E0751E60E9EDA6F292D0AFE5A4C /* FViewProcessorResult.m in Sources */ = {isa = PBXBuildFile; fileRef = A78CA57199BE1F05CB6F7E0160B11399 /* FViewProcessorResult.m */; }; + 01AFCBEBAA6285E88424E06CA0278F02 /* GTMSessionFetcherService.h in Headers */ = {isa = PBXBuildFile; fileRef = 380BA422DBB8CB845C91F2DDE5BDEA5D /* GTMSessionFetcherService.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 01ED47BBFC63F9DEA81CC6FF3B9FF121 /* FIRAuthAPNSTokenManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 2AE67C3CC542BA36613ADD23D682E7E8 /* FIRAuthAPNSTokenManager.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 01F9A5A90232973B6DA54678118DB3C0 /* GDTCORLifecycle.h in Headers */ = {isa = PBXBuildFile; fileRef = 1C248628CE055C1521E3853C48F9FF24 /* GDTCORLifecycle.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 02341086F0808232DE7D1D7523BB1F7C /* FBLPromise+Any.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 51377F1CD083FC389612859E450139CD /* FBLPromise+Any.h */; }; + 0250F77A3345CB64BCB6492E9D8D3FD3 /* RLMManagedArray.mm in Sources */ = {isa = PBXBuildFile; fileRef = 411E765A494550268DE9F0C35C384549 /* RLMManagedArray.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 0259FF9014F6BA08462866A3482B661D /* FKeepSyncedEventRegistration.m in Sources */ = {isa = PBXBuildFile; fileRef = BF0DD77C260C2BCB9B283657354A812B /* FKeepSyncedEventRegistration.m */; }; + 027CE3ABE68D3D82B881B68880524357 /* port_example.h in Headers */ = {isa = PBXBuildFile; fileRef = C2CA5484C1CAEBAFEB82928E0B911DBD /* port_example.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 0287026B8FB380A3A97DFE227460A76B /* FirebaseCore.h in Headers */ = {isa = PBXBuildFile; fileRef = 9CF19D6F9D47FE0C30F70D0326CE6B31 /* FirebaseCore.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 02D58066DFAC1A2411864057A27DB2F4 /* GULNSData+zlib.m in Sources */ = {isa = PBXBuildFile; fileRef = 7042F28BE74E0B121E4BA63670CEA317 /* GULNSData+zlib.m */; }; + 034ACA0333A3E6E9A643122B066DDD22 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64025D1D442A26509C94DDDE4BCBF577 /* Foundation.framework */; }; + 038F688C7F0D0D535C62FFD508408C2E /* GULNetworkLoggerProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = FCA095FF7A7E8830ED5933A9B581B3A0 /* GULNetworkLoggerProtocol.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 03CDDBD74599BBA80DB73792A5A71E09 /* FMaxNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 8805ED6DFABB525362EE02596FE7AF24 /* FMaxNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 03D062393118C4BCC22F3CFEA63E8A54 /* GDTCORReachability.h in Headers */ = {isa = PBXBuildFile; fileRef = 1DCEABFFFDA249FC0F0D5AEAEC0472FA /* GDTCORReachability.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 03D2B29AEF2411851C43BEE76B6A4089 /* FViewProcessorResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 02BB0C52246D55DDDA24DD966F29EE7D /* FViewProcessorResult.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 03F30DDECC0FD27449A26E75331B6930 /* FBLPromise+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = 8298C3985C462DB1351E14FFA5281BB5 /* FBLPromise+Testing.m */; }; + 0404A69C5B52AED3106A3BC8882E3DEC /* FChange.m in Sources */ = {isa = PBXBuildFile; fileRef = C381B0F4DB89513D7C09360B109ED622 /* FChange.m */; }; + 04086C82027AB6A24CA1E2747BA4B8BE /* FBLPromiseError.m in Sources */ = {isa = PBXBuildFile; fileRef = 882A1805C739601B36DDBE8199808EA7 /* FBLPromiseError.m */; }; + 049F23964BF99F1E034E600749DF4778 /* RLMSyncUtil.mm in Sources */ = {isa = PBXBuildFile; fileRef = BFADFD13907771FB22980C06CDA26146 /* RLMSyncUtil.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 04DB958F843BC476B49669F0BC17A24C /* status.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3676FCA755C86DD19676A81765B989FB /* status.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 05180304313D99D502131180CEA8ABB9 /* FTreeSortedDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 71D39A54D987F3A5B517EF69C6F97FA8 /* FTreeSortedDictionary.m */; }; + 05228D229EF9D77E6FB7D6531EF1AD19 /* FIRVerifyClientRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = C5B9B9A3D348238704B96F1CFC1C0CF3 /* FIRVerifyClientRequest.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 053FEEE0BA88BE2CF6A5FEC455F61C6D /* FIRInstallationsAPIService.h in Headers */ = {isa = PBXBuildFile; fileRef = 59C49C4C92798FC3200909C43B9F8857 /* FIRInstallationsAPIService.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 05A98670EAFC266DD9605C109C50C6FA /* FIRCreateAuthURIResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = DA888CEEA972A2D0195C006DEAFB5D7A /* FIRCreateAuthURIResponse.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 05DCF6EAAE187AC79572C87A0B56DCEA /* FIRInstanceIDTokenDeleteOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = EE76699AE0009E7286DB0C500CDEA36B /* FIRInstanceIDTokenDeleteOperation.m */; }; + 05E537D6954BE54C14B0C2D0BD748B27 /* RLMObjectStore.h in Headers */ = {isa = PBXBuildFile; fileRef = D0F7E8BBE321F64C9CCE4D3BF4DBC66F /* RLMObjectStore.h */; }; + 05E61A3ADD15D7CB6C969A76E95EAAF9 /* RLMAnalytics.mm in Sources */ = {isa = PBXBuildFile; fileRef = 45918AC6C3F376881E09B1CAAC467EF3 /* RLMAnalytics.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 05EEAE84294B4B1584603A70515B54D7 /* FTupleBoolBlock.h in Headers */ = {isa = PBXBuildFile; fileRef = 03D2D50AE298301CE92CC1C9CD0CF6B8 /* FTupleBoolBlock.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 060F29EAF6E34726F4A52995A1C31F96 /* FIRIdentityToolkitRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = B287553C4687458B4F94CD2691ECD117 /* FIRIdentityToolkitRequest.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 0624DA01ECA96AE03CA4CD86AE0DD698 /* RLMRealm+Sync.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D928597FB30E7EF35AAC58AE3A428C9 /* RLMRealm+Sync.h */; }; + 066893C78B248DE542B8229D75F2040A /* RLMSyncUser.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 930FF3B062A8B447DFC07EE99A07EE33 /* RLMSyncUser.h */; }; + 0691B681221DC47A2198606B36370CF5 /* FIRLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = C7D050C1C76B3FB438035A590FBF3984 /* FIRLogger.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 069E8403A49DE5921F142696F2518EBF /* thread_safe_reference.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E9EA6FB992769B58776BCBD623683FC3 /* thread_safe_reference.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 06F583BBED27B047350AD11D07832FC5 /* FIRUser_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 51895595CC0B4F2941F4790FCE674D1A /* FIRUser_Internal.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 0774BB819595848EFE186648456232D3 /* FIRActionCodeSettings.h in Headers */ = {isa = PBXBuildFile; fileRef = F984ED7661040651F50DFED42E8FD41F /* FIRActionCodeSettings.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 07B4A1D98B402705C9EA29D9D227E8E1 /* table_cache.cc in Sources */ = {isa = PBXBuildFile; fileRef = 94ACE21C6A33E6195E748EA160A13204 /* table_cache.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 07CFEDA472792676E80DC36DBA6B42FF /* FValidation.m in Sources */ = {isa = PBXBuildFile; fileRef = C033070806227FC7EBEF70D50A23C75B /* FValidation.m */; }; + 07F0805184FB3045E749367BC52FFE1D /* FCancelEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = BC607F184AD720438FF6F68C03102FCF /* FCancelEvent.m */; }; + 080883B43705BD3A2602CF23123D9C20 /* FirebaseAuth-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = BA2AD92B599641752DCBD8F1FE784B96 /* FirebaseAuth-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 080D03ECB8EB85A8973418C1029F8232 /* FIRCLSURLSession_PrivateMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 96DF39B293090E7DD0FB3B272833B75B /* FIRCLSURLSession_PrivateMethods.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 083E477E9DE03853989A81135EE7B8A7 /* FIRSecureStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = A384F92A096C490D8F388733A9524664 /* FIRSecureStorage.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 086644D14E2C73B1B6548DFC6291716A /* FIRCLSProcessReportOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 10D97BB079FDEB279746D6D296CE39AF /* FIRCLSProcessReportOperation.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 089C37D47E1D50644531ECECF38A4CE9 /* FTupleSetIdPath.h in Headers */ = {isa = PBXBuildFile; fileRef = 7EEC129EFA1105150FB920A946CD1280 /* FTupleSetIdPath.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 09433CD8C4A2D4227DF98AB78E246011 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967AC3B13881F4CA2CB6CE9C55146F69 /* Image.swift */; }; + 09759A3CD181CC07D8E69462D2E9424C /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 091DE8E84139D89633B550EF9E316271 /* CFNetwork.framework */; }; + 0993FE761A44E69AA2E72DFADC02CB98 /* FEmptyNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 49944035B0ED6067E46A755A89EA18AB /* FEmptyNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 09AD74027097E5A8FF25FC162F2803CE /* FIREmailPasswordAuthCredential.m in Sources */ = {isa = PBXBuildFile; fileRef = 080D58C9DE83B7518E5BB38070E78A6B /* FIREmailPasswordAuthCredential.m */; }; + 09BA8C8065B7F9FCBD4AAA69DB439C67 /* version_set.h in Headers */ = {isa = PBXBuildFile; fileRef = 163306A052CA3BBD80F4181480FB67FD /* version_set.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 0A047AEB1DCDC9C1FFE81071DBD4B239 /* FIRResetPasswordRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 14943783D6765B9A756C91C50D324D57 /* FIRResetPasswordRequest.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 0A30AF984C7DDEF87C22604B8BD8E3B8 /* FirebaseCoreDiagnostics-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 3C5B12E4F847E35CA6D5536264282E44 /* FirebaseCoreDiagnostics-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0A3E14559DE2D3FFF9A20C7A7A26A659 /* FIRPhoneAuthCredential.m in Sources */ = {isa = PBXBuildFile; fileRef = 2357030ACCEDF3D048244D1CD81A2FE5 /* FIRPhoneAuthCredential.m */; }; + 0A4B8057E841831E20481E7270E30F12 /* FBLPromise+Reduce.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = AEF66CD8B1BCBC0A3B148DF00A6113A8 /* FBLPromise+Reduce.h */; }; + 0A7197C9F569351D2CF42161DB976DCC /* FIndexedNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 98336D712A53939111F8856BC622B48F /* FIndexedNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 0ABD1A69D54921D4230DA47F703B47C8 /* RLMCollection_Private.h in Copy . Private Headers */ = {isa = PBXBuildFile; fileRef = B49E2673664F75854392D9A0F630AC28 /* RLMCollection_Private.h */; }; + 0ACC49E6050325926CBF6FF882E6BB5C /* FIRIMessageCode.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CED633A28DB53CEBA59E9F81F542E4C /* FIRIMessageCode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 0B0792B6C13A4DC546A8B215A139E05C /* FTupleTSN.m in Sources */ = {isa = PBXBuildFile; fileRef = EAF94A59F9E00336CEF40DFC2A0A2F5E /* FTupleTSN.m */; }; + 0B2280895259CB93BEB07CD87198298F /* pb_encode.c in Sources */ = {isa = PBXBuildFile; fileRef = 03196D38FF2956BE3A63693601EAFC6E /* pb_encode.c */; settings = {COMPILER_FLAGS = "-fno-objc-arc -fno-objc-arc"; }; }; + 0B4482C3DE927EB64F461B7D140A30C6 /* FTrackedQuery.h in Headers */ = {isa = PBXBuildFile; fileRef = 73F65244F2C613D2061EAC8DD493459D /* FTrackedQuery.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 0B7BB2E29393260190CF1C0F924A5C0E /* FIRVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = 7CF5F9A072CD16920377817F5691A403 /* FIRVersion.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 0B8814B2572EB8DC25293DEC91B7B998 /* FIRInstanceID+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A9E34FE56E60AFC7C356826A0D0E8E8 /* FIRInstanceID+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 0BB372001943A3F7DAB760F0B36069C5 /* FIRInstanceIDTokenManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1FD48142CA1F24926D4436B30CFC3443 /* FIRInstanceIDTokenManager.m */; }; + 0BDB2ABF3673EE91183602A632E110DD /* FIRCLSURLSession.h in Headers */ = {isa = PBXBuildFile; fileRef = F895732C10786779FB3B5D978024B17B /* FIRCLSURLSession.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 0BFF24D5C16257D168CB59FEAE373808 /* system_configuration.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 57ECB80AD960D18878792A5FCE8B29A9 /* system_configuration.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 0C099081D0FA771263A448DA11A080F3 /* FIRAuthWebView.h in Headers */ = {isa = PBXBuildFile; fileRef = F66BF470805C0E607FE8D9913E9213D7 /* FIRAuthWebView.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 0C5E11DE24DAA737704B355F5F2F3426 /* ParameterEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC09B19CF882809F37562A3B14201EF /* ParameterEncoding.swift */; }; + 0CEA55146F62CF4426A02BE2EAF227B3 /* FStorageEngine.h in Headers */ = {isa = PBXBuildFile; fileRef = 23DF59E8BD83BA3C2517751E2094EB69 /* FStorageEngine.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 0D1BB5FB93FCE7F5AF755977C54919DA /* FIRInstallationsHTTPError.m in Sources */ = {isa = PBXBuildFile; fileRef = E74E8ABB69B7B8765F18F4A590BF5085 /* FIRInstallationsHTTPError.m */; }; + 0D7DF325BBCE5DFE5BB5715AFB53BB3D /* FIRCLSUtility.h in Headers */ = {isa = PBXBuildFile; fileRef = 16FA90EBB6ED4FCC27E0FB0DEACA5A7C /* FIRCLSUtility.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 0DA0270BE8D0FACFB22430EC574707C0 /* FBLPromise+Timeout.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 14F0259C7BD472223481E9A31DC96485 /* FBLPromise+Timeout.h */; }; + 0DE126AAB6E7597E4F19374B740E994E /* FTupleStringNode.h in Headers */ = {isa = PBXBuildFile; fileRef = CDA8666A9B50BF19DF40C0F386F748B5 /* FTupleStringNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 0E031C031301B192EA4337E12804CC70 /* binding_callback_thread_observer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 61F59F538C624FC9A76C7C50D4FFAA8B /* binding_callback_thread_observer.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 0E3D41F20FB1D10DAA5FD79F3920F9AA /* arena.cc in Sources */ = {isa = PBXBuildFile; fileRef = 463D13D8E2BAA12948556D3B5ADD54FA /* arena.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 0E5A7F170A0CF1873865213D0799CC5E /* FIRInstanceIDTokenStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 37CA0488689836CFBCF7D357059D5DF2 /* FIRInstanceIDTokenStore.m */; }; + 0E71AF5DB4674C92E082BC8DAED13192 /* FIRPhoneAuthCredential_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 4129260763BBAB48DD6DB625D6A7C462 /* FIRPhoneAuthCredential_Internal.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 0E9649E3134E8BC6C9380EC78E376481 /* RLMQueryUtil.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8F9F3C4BF4F891B92DA1E5A57FDA4B2E /* RLMQueryUtil.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 0E9DCA2ECBEFCBC65211F7C911043B8D /* FRepo.h in Headers */ = {isa = PBXBuildFile; fileRef = 8CC50146E48C132CD4FDE4797E7E3763 /* FRepo.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 0EC3A2761F6C42CDFC6CAF132CB0B83F /* collection_notifications.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FDF2E4BDCD2F7C590FFDC8CA02E033F8 /* collection_notifications.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 0EC6311DC3EAD8B84EBB3FF2FCF3971C /* FPathIndex.h in Headers */ = {isa = PBXBuildFile; fileRef = B1826037AF90ACAC8537C39DF02EC8BF /* FPathIndex.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 0F203DA2CF19D58A573B79C55EF989A9 /* FIRCLSInternalLogging.c in Sources */ = {isa = PBXBuildFile; fileRef = DBFDE7C1CA6BDD86C3C60C3DF7D84759 /* FIRCLSInternalLogging.c */; }; + 0F23D7CD822DFF8983212AB0181CDDDF /* FIRCoreDiagnosticsConnector.m in Sources */ = {isa = PBXBuildFile; fileRef = AA80B18F608CC7FFEF860AA39A50114F /* FIRCoreDiagnosticsConnector.m */; }; + 0F2FB338CA6AD8975B1D9D86FCADDA40 /* FDataEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 27319C0EE4A55D23AA99620BCD4D9635 /* FDataEvent.m */; }; + 0F75CFE17869AABBA645828F7BB91096 /* log_reader.cc in Sources */ = {isa = PBXBuildFile; fileRef = BBD644E2ADD3C185AE9CD03FF0CD495B /* log_reader.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 0FEACBA97148B598AA27162DDE4C7E4F /* RLMRealmUtil.mm in Sources */ = {isa = PBXBuildFile; fileRef = C2E4BB2E9C451A12AFA6ED8B18E0674F /* RLMRealmUtil.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 1020BE6BB459DB896BCE7B16F6BB0301 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64025D1D442A26509C94DDDE4BCBF577 /* Foundation.framework */; }; + 1053C5196B318F0B760D3B59DDFA1745 /* GULNetwork.m in Sources */ = {isa = PBXBuildFile; fileRef = 13F91BDD6A43D7C3C3BA79F3B3F57353 /* GULNetwork.m */; }; + 106227B3E6239B6E36478FA17B60490E /* GDTCCTPrioritizer.h in Headers */ = {isa = PBXBuildFile; fileRef = 196645E8D0648C57FB0C6929B7E45FF3 /* GDTCCTPrioritizer.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 1077F3BF04E76193AE0399A3AFB5149B /* FIRAuth_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 5C4BD82707AB6BB86675E6BC6E48F081 /* FIRAuth_Internal.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 1079C105550BB33719C4BCF5328E59D6 /* FIRCLSSerializeSymbolicatedFramesOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = EB9E162ED5D86B7F2EB30AE42FAEE0BB /* FIRCLSSerializeSymbolicatedFramesOperation.m */; }; + 10D660D7941990575531682340C2A0EE /* FPersistenceManager.h in Headers */ = {isa = PBXBuildFile; fileRef = F62BE4EC547C3E79D8346DF6F8F50669 /* FPersistenceManager.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 1124DA5AA0690636691F8C16B54BE187 /* RLMRealm_Dynamic.h in Headers */ = {isa = PBXBuildFile; fileRef = 03E058B6C2458630160C0A65EB9FD24F /* RLMRealm_Dynamic.h */; }; + 11ADB7B4F9ED12EFBA865B78F904F354 /* FIRAdditionalUserInfo_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A0042AA567D40B19AEB176865699923 /* FIRAdditionalUserInfo_Internal.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 122559F2B179231A54AB928B00AB712C /* FIRInstanceIDTokenManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 4911B687FA7409D98B78D92E8FDD1F59 /* FIRInstanceIDTokenManager.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 1258E3F349832C216AAF9543AA8FBA3C /* FLimitedFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 415B7669334DDA6ABC48A6A59B119A62 /* FLimitedFilter.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 125F621BBE4D4D6B8EADBF317A92A9B7 /* FIRUser.h in Headers */ = {isa = PBXBuildFile; fileRef = 95D54C32914D8C5745A86BD2ABBBACD6 /* FIRUser.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 12A94B085B960FFF94A98BCF9F6E27E0 /* ObjectiveCSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948C362190D80D0FAD6202C60E1B479F /* ObjectiveCSupport.swift */; }; + 12C6D391DADBAF1A3A0E52464544ACDB /* GoogleUtilities-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 0DA64D4B686E9AD555646D96BBA0EF44 /* GoogleUtilities-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1323A3863AE9037767A2A0FCC0F474A1 /* FConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F6D5F74532AA44009E25B4EF7564210 /* FConstants.m */; }; + 132E0F619E4338E5D1B27E4C72076B3F /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCAB7E316CA26642BFF8CC2BDB62A008 /* Notifications.swift */; }; + 13781217103B9C7248945110C8179D28 /* FIRNoopAuthTokenProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = DA284A344F51526CCFFC122A20891948 /* FIRNoopAuthTokenProvider.m */; }; + 139654D56832D51AA66DC55C847675B6 /* FPersistentConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 803101B9EF5EE3DE6588D22EE440EF16 /* FPersistentConnection.m */; }; + 13AA8418142F07AAFF784A61648BC738 /* FIRCLSUserDefaults_private.h in Headers */ = {isa = PBXBuildFile; fileRef = 07C192291C6A1B6CCFBA7391244C026D /* FIRCLSUserDefaults_private.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 13AA9F89B18BDC2687A40F18CB1CE6C8 /* transact_log_handler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3E2AB05A3BCDC8A6EE6F25EB3C50D4BD /* transact_log_handler.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 13F1B40D631C98E3056EA237C952976B /* FIRInstallationsItem+RegisterInstallationAPI.h in Headers */ = {isa = PBXBuildFile; fileRef = 58B38973A7F5AAF8DEB3EEC6F846C4DD /* FIRInstallationsItem+RegisterInstallationAPI.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 1400F5BE398901D1D563CBAD0FD491E0 /* GTMSessionUploadFetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 09E99A891DB19775D655521322BA574D /* GTMSessionUploadFetcher.m */; }; + 14137207C53B6BD8BA759E1201DBBE4D /* FIRNoopAuthTokenProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = B8A5983F620EA151F5229275D6DF1BE2 /* FIRNoopAuthTokenProvider.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 1425348D1B5AE1D313645B48C1809E8C /* FIRCrashlytics.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BA2CDB5CB3429FAB9DDA4F6DC6C5283 /* FIRCrashlytics.m */; }; + 1426E7D4526CCF2C32D559E2B538580B /* schema.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 11C9E2292880FD53065259BE912CA70F /* schema.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 145308AEE5F108115EA81C2449AF5E7A /* FIROAuthProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 53143A7A200C31CAE42A2C9E2612AB3D /* FIROAuthProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 14672662061590430AF4801C432020A6 /* SessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74471D3212EED130E3D5DD72463B542D /* SessionDelegate.swift */; }; + 1496182EA04508D6FDE7C6A34B093049 /* FBLPromise+Retry.h in Headers */ = {isa = PBXBuildFile; fileRef = 7BC112B0B8DC7D7A50DFF38A18C9056B /* FBLPromise+Retry.h */; }; + 149DFEA17780F138C2D65F734477EC74 /* RLMThreadSafeReference.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1EDAF1D55B69A9C4D024EFA96DDE2D33 /* RLMThreadSafeReference.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 14F7AFD9FFD7EB0B3CB02DA66BEE976F /* keychain_helper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A87AF3EEB6D8137F65A401B485906C8D /* keychain_helper.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 1528AD86DB13AF6FD139D29AD8F17999 /* RLMCollection.h in Headers */ = {isa = PBXBuildFile; fileRef = 11CB3AD7E80C556AAEC53C4F0C55E1C2 /* RLMCollection.h */; }; + 154AF0870F0AB9C567F756A82DDBB75D /* FViewProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = 9213CDC4C57E01385FA2CED32D12D1B0 /* FViewProcessor.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 1623320157383ACACCCC8A4FBA35F553 /* APLevelDB.mm in Sources */ = {isa = PBXBuildFile; fileRef = 234795C49420633C02D08BCF4C26DEFE /* APLevelDB.mm */; }; + 163DF9C4EF2939A35285E5487D8AC718 /* RLMRealmConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 434EA703D1FB868AB9B838002A4ED5B6 /* RLMRealmConfiguration.h */; }; + 164553A0AACB1FB27D68230B7439AB7B /* RLMObjectBase_Dynamic.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 86A9C0C738B55C104584CA8477DBF065 /* RLMObjectBase_Dynamic.h */; }; + 16CA59A199E971DD9F88867955385A7D /* FIRCLSURLSessionTask_PrivateMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F10BD67D2D9EA34B8A06A9EA2DCE036 /* FIRCLSURLSessionTask_PrivateMethods.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 16F8832C1E288DDF643EC94A9D99B932 /* FIRInstallationsIIDStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 70B162E996982D9DC5A9E028820DA000 /* FIRInstallationsIIDStore.m */; }; + 171CFEBFD0A93CFF6DC40E85EBF3BF8A /* CacheSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62F021E6B2E996937A7F48891975F654 /* CacheSerializer.swift */; }; + 1788D2AB1B052F07DC95420DFE355407 /* GULReachabilityChecker.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C548F54FE1BE778FB826F39F15D7349 /* GULReachabilityChecker.m */; }; + 178CFCFBC312ACCB07E75D8F50D2C499 /* FCacheNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 683557C62E339ED3A54534F38BFE56E6 /* FCacheNode.m */; }; + 17905B6DC347427DCCDBC1AF49EF9809 /* FIRCreateAuthURIResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = CD8D438942C8F90B8D167852A4A7EEAC /* FIRCreateAuthURIResponse.m */; }; + 17CE12E3F777B243FCE5AC6201AEF303 /* RealmConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3D17B0B57A0B5950C0C15451C711B2A /* RealmConfiguration.swift */; }; + 17E74A0C302C627A1374F313FA16E7EE /* SwiftVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09933BBB67B866ED8F91402FCD732796 /* SwiftVersion.swift */; }; + 1856BEBADAA6691FD5C43D4D737029EA /* RLMObject_Private.h in Copy . Private Headers */ = {isa = PBXBuildFile; fileRef = DB20C35765E54E044401445CD25ADB73 /* RLMObject_Private.h */; }; + 18CFC529A456F15AA21FC1F0386188E7 /* FIRInstallationsAPIService.m in Sources */ = {isa = PBXBuildFile; fileRef = A9E64A8A8B133D02DAB3421C08C97BDE /* FIRInstallationsAPIService.m */; }; + 1945CD5D63A1C164AEAAA9A33E85571E /* Alamofire-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = AE7D406AE43833497C38783D7B7BB688 /* Alamofire-dummy.m */; }; + 1953358E528DC00445ED451B46B8FD20 /* GDTCORStoredEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = ECCB91EDC5AA7BAC3EDD3F05AF82568A /* GDTCORStoredEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1986B50C74F1697EA43F68335C93CEB3 /* MultipartFormData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 823EEAF21299E8D98B01448277270E70 /* MultipartFormData.swift */; }; + 198915A277429DF37346B2BC177E0E15 /* FIRDatabaseComponent.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C45C494D97CAD85D2802B100A2AC056 /* FIRDatabaseComponent.m */; }; + 19BC4C0A75C8CC089542AA269B3750B4 /* FIRApp.m in Sources */ = {isa = PBXBuildFile; fileRef = FF2A8B581BE6C14669CB135994E53F23 /* FIRApp.m */; }; + 19C89AD61B5C218AF3A83907C9034AFC /* FIRCLSURLSessionDownloadTask_PrivateMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 3ADEC01442125E8A31963729EC8049FB /* FIRCLSURLSessionDownloadTask_PrivateMethods.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 1A0810DFA1F7C0B0EF561B476BAD0D1D /* GDTCORAssert.m in Sources */ = {isa = PBXBuildFile; fileRef = 2020EB583038820A8F046C0F7333E258 /* GDTCORAssert.m */; }; + 1A6FAC76E6834F1EDF069FF9FB4A189B /* RLMSyncConfiguration.mm in Sources */ = {isa = PBXBuildFile; fileRef = 87442EC5866C801D59CF010F92961448 /* RLMSyncConfiguration.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 1A73F39CDDEEADB24723A3F6CBC359AE /* ImageView+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F705824450B4EFCB691B62F12F6B58D /* ImageView+Kingfisher.swift */; }; + 1A8834B447E36558097D854BDCA3CE1B /* FIRInstanceIDConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 2B416EEE307E63FA4BE4FE698193B180 /* FIRInstanceIDConstants.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 1A9D0B3BCFD7DDE552F1F6B3C2F89286 /* slice.h in Headers */ = {isa = PBXBuildFile; fileRef = 3C44A9357C6BC090C6B509C35F23A97C /* slice.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1AD8CF4941548205727FEC94CE09B5F4 /* FBLPromise+All.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DCE3C53BB9884A39FABEBD81671136A /* FBLPromise+All.m */; }; + 1BC0485BA033FFAB83486043111FD39D /* FIRSignUpNewUserRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 67AD62195DDA0F8E519B11169135F319 /* FIRSignUpNewUserRequest.m */; }; + 1C00496148C58C27B1A7B63E3DCF1F91 /* GDTCORTransport_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = BB72DEB463D368287CFC5EFD8FF55244 /* GDTCORTransport_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 1C2B4E586A1F2B4C5322D513467DF7BD /* FIRCLSReportManager_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 535BC6C89C9E415076459A3A026CB580 /* FIRCLSReportManager_Private.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 1C5ACABC25D19849875C4EED96392522 /* FIRMutableData.h in Headers */ = {isa = PBXBuildFile; fileRef = 82364B5E57B1EF839045A66A8EAD905A /* FIRMutableData.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1CB5C6760130C03AE56AD4870F1C1457 /* RLMCollection.mm in Sources */ = {isa = PBXBuildFile; fileRef = 24E5A5CFF7D16B79BC71F15A786A9A1D /* RLMCollection.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 1D27A15F9E29252B7B03469286EDEE22 /* FIRInstallationsLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 4678982B233A0BCD199D2DF9A374B3D2 /* FIRInstallationsLogger.m */; }; + 1D60D240B194D801E1A97ADA41D84C88 /* GULHeartbeatDateStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = AF1A7341BE52513E878BD1EFF619C285 /* GULHeartbeatDateStorage.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 1D8133277751BE31DAA2BAEDA39DDC02 /* filter_block.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6663078E0C1CBF1E77E6BA770700875A /* filter_block.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 1D9531B1B584741132D9EE4B8CCC8B1F /* no_destructor.h in Headers */ = {isa = PBXBuildFile; fileRef = CDEC22C4B877A3988603237A07AD8B98 /* no_destructor.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 1DB95DC1386E858190A46C9E55AB6DCF /* FIRUserInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 4FA95262E393CB9DD325CB12247F48B1 /* FIRUserInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1DF7E56E4C5D29BE699142FA9CA911DA /* FirebaseDatabase.h in Headers */ = {isa = PBXBuildFile; fileRef = F46A4EDD00D706566C8479D0068E3FAC /* FirebaseDatabase.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1E0B6B2B25216B9DCA864124C4407FD0 /* logging.cc in Sources */ = {isa = PBXBuildFile; fileRef = 81B9FD9FC925AE5303A6C5DEDCAF092E /* logging.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 1E3C13069482107A2782E9B72D92B4C3 /* FIRInstallationsStoredItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 846E4DE7CE26B7FBA379AFC0956A4BD3 /* FIRInstallationsStoredItem.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 1E50F74C67958D399D7EC529A10F1516 /* FIRCoreDiagnostics.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A61D8F6B2637A077A773A4BC2C231B6 /* FIRCoreDiagnostics.m */; }; + 1E68DAAF9FA67C987AED67B4DA41F855 /* RLMSchema.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 18A4FA38D0EC7379DB125F600EA014CA /* RLMSchema.h */; }; + 1E772ADD36B31B3EE965F1DF6563392E /* FIRInstanceIDConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 09304335678AA1BA258043917B4E23D4 /* FIRInstanceIDConstants.m */; }; + 1ECFB49A4B9AFCCDFB9EA2E50376C9FA /* log_writer.h in Headers */ = {isa = PBXBuildFile; fileRef = D2EE16DD66F48B7F88F4E9660B90099D /* log_writer.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 1F2DF5FD3EFFDFFBB8AC3EC9095DA457 /* FSRWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 20A3257A6A273B703A8672D312E810BC /* FSRWebSocket.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 1F2F06D3F5D78980F67821B553F0883B /* NSError+RLMSync.h in Headers */ = {isa = PBXBuildFile; fileRef = DF17CAE04A5A105011249086FB2ECCCA /* NSError+RLMSync.h */; }; + 1FA52DEEF9744E3977F2625A2A061F4A /* FIRCLSDwarfExpressionMachine.h in Headers */ = {isa = PBXBuildFile; fileRef = 80F193A96FCC90BD0DCA5A2A7ADC9F83 /* FIRCLSDwarfExpressionMachine.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 1FB90CB335C7F408C1A3B50C60260790 /* FImmutableTree.h in Headers */ = {isa = PBXBuildFile; fileRef = 97AB7716BA5097897D8CE33414A2157D /* FImmutableTree.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 2011162FA6962E236CC9FDEF170D5A65 /* FIRCLSUUID.m in Sources */ = {isa = PBXBuildFile; fileRef = C1411B2CA010177EB738082A892A6DD2 /* FIRCLSUUID.m */; }; + 202A4A731CA3A5E339451C2BA27FE99F /* filename.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9C0079468DE4A96EA35409206515BE79 /* filename.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 20364BF3F0F08AF21345FEBC48CA40B2 /* FTupleRemovedQueriesEvents.h in Headers */ = {isa = PBXBuildFile; fileRef = D7C27A7ADE5866F1D997C55EA66528BB /* FTupleRemovedQueriesEvents.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 20A4282A9E48189F614BF804F634953E /* GDTFLLUploader.h in Headers */ = {isa = PBXBuildFile; fileRef = A020B761979C1B61A16487D3C2922FF5 /* GDTFLLUploader.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 210D1B21D54F300E4999D636A4F5233E /* GDTCORRegistrar.h in Headers */ = {isa = PBXBuildFile; fileRef = C9FDAAB6458C9C7A42D77B11BF0B3F75 /* GDTCORRegistrar.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 21762AFD3A4E0D28D9EF9A8606BDB10C /* FBLPromisePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 30B0C1A58B0B3ABC8A2A09E6B26FB29A /* FBLPromisePrivate.h */; }; + 21AC9E91D06D23EAF3FE05BEBF2343F2 /* FIRCLSReportManager.h in Headers */ = {isa = PBXBuildFile; fileRef = BF4725A1936C6270B4AB0B1DBB7B4A41 /* FIRCLSReportManager.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 21E34559B19EED2D49C04D51B8BD5856 /* FIRCLSUtility.m in Sources */ = {isa = PBXBuildFile; fileRef = 1293543EA5A1A924D5F8338EBA12CC16 /* FIRCLSUtility.m */; }; + 223AB247898DEDBD7F5FFC940C7C2F11 /* FIRCLSReportUploader_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = EEF4CC836E0FC4BCEB6ACE36C4028389 /* FIRCLSReportUploader_Private.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 224D8D1B421C842A8B9D33546666D7E7 /* GTMSessionFetcherService.m in Sources */ = {isa = PBXBuildFile; fileRef = 8CD746F873450F3A4F322F45F227EA7C /* GTMSessionFetcherService.m */; }; + 224DB4DCE7A500F2C3F2508C4AA5AD8F /* FIRCLSFABHost.m in Sources */ = {isa = PBXBuildFile; fileRef = BB8555579C24393A6EB509F5D1FBD10A /* FIRCLSFABHost.m */; }; + 2281CAC912E936EA234D600D4164E6FF /* FIRCLSFABAsyncOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = EF852E414686D1C085AA9A455576B845 /* FIRCLSFABAsyncOperation.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 2290E64D712562D6C1C58ABABB4410F5 /* FIRInstallationsIIDStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D5F1AEA9CDB1B1C269E91CB1B3F3445 /* FIRInstallationsIIDStore.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 22BB761259397129B43938FB60B72694 /* dwarf.h in Headers */ = {isa = PBXBuildFile; fileRef = 709693C460EEE74D5CD70B2C864907E7 /* dwarf.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 22D8A47FCBC0B4C91500E90E4B7DBFC8 /* GDTCORStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C6F01CEA86B55CC5A4FD7FEEDA27AF0 /* GDTCORStorage.m */; }; + 22F963CEA7CC2CA57EBEAEE4DA490029 /* list_notifier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 44F6C89B42122E6D3B49FF370FAE179E /* list_notifier.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 23270FFD41B6451E9AC772ECE3CEB506 /* FBLPromise+All.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 7706CC3470134134EB96FF63B96D49DC /* FBLPromise+All.h */; }; + 232B8FD55E5AE573FBC1240327F0531E /* FIRDatabaseConfig_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = C22F2A5F3A5274251E53C399BA270681 /* FIRDatabaseConfig_Private.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 233A62E1F210AFA9D29F7155962C45CE /* FIRCLSCrashedMarkerFile.h in Headers */ = {isa = PBXBuildFile; fileRef = 8690E87E0C6FB58DBA5D4B6476077A82 /* FIRCLSCrashedMarkerFile.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 235101E93897970283CD9042C3C0DC80 /* memtable.cc in Sources */ = {isa = PBXBuildFile; fileRef = 20FC90F276553AA05ECB6EF16C5A6B91 /* memtable.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 240D9275D135B705EB767D5585554862 /* FBLPromise+Then.m in Sources */ = {isa = PBXBuildFile; fileRef = B8D3A408F55DEF3F90B05828E9484BD0 /* FBLPromise+Then.m */; }; + 2417A9EEC50FA31DD6F764CE4A64762F /* RLMMigration.h in Headers */ = {isa = PBXBuildFile; fileRef = C4A5068B4F002455620D242C20110490 /* RLMMigration.h */; }; + 244138140167BCD8E48878E493DB2432 /* cct.nanopb.h in Headers */ = {isa = PBXBuildFile; fileRef = 09B51F4D1459A875BA0930AF94EA5C0B /* cct.nanopb.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 244EF976B1E9D16D77191D1A9419CB6D /* cache.cc in Sources */ = {isa = PBXBuildFile; fileRef = 10332F50392D7C5CB440A2718EBCAF21 /* cache.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 244F5C35E9F8D1591D80C44E21E62A41 /* FIRAuthExceptionUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = D9056436C38C221324AD2173B94F7FA3 /* FIRAuthExceptionUtils.m */; }; + 248A5E543858A01EC7294FC8E6AF1A10 /* FIRCLSHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = C29418203303D3DD410EE8C349774807 /* FIRCLSHandler.m */; }; + 24C320179674BD41CB7F3A3414D73F07 /* FIRAuthURLPresenter.h in Headers */ = {isa = PBXBuildFile; fileRef = 80E5536A228B8DDE0EBBAFAD13275DA8 /* FIRAuthURLPresenter.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 24F7392348EAB0ED45B6B1DCA4391914 /* RLMSyncSession.mm in Sources */ = {isa = PBXBuildFile; fileRef = 598F4D17D3BB845BB5FDDAFDE6D2A423 /* RLMSyncSession.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 250A5FB958EB8889F6C644955EAD89D0 /* FIRTransactionResult_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 86ABF70E1FE2CB545EBEE6228D4296AB /* FIRTransactionResult_Private.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 254065603D8242A442C726A47E3A8333 /* FIRCLSThreadState.h in Headers */ = {isa = PBXBuildFile; fileRef = A528EB78B55C3F9AB1A8B7B2CF02DE85 /* FIRCLSThreadState.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 25C28324FCF66DEE65204B577286014A /* FIRCLSConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 6509B500C8E4B0D9AA9C1F9A8E6E9991 /* FIRCLSConstants.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 25F907EB20256BD810D4CE4CBA9CE48F /* FIRSignInWithGameCenterRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = B3C87CC09FB0D7BFBC1445D0533D25DB /* FIRSignInWithGameCenterRequest.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 26047293FEF6FBF2586A4CAD44B5BA8B /* FOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = A8A8721CAE2B4D781F5B1CF133CA52F3 /* FOperation.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 267984A3CFED0ECA80A4FF1CDCD73A39 /* FIRInstallationsSingleOperationPromiseCache.h in Headers */ = {isa = PBXBuildFile; fileRef = C8BC987527B0F350D97D5F43DE756CE6 /* FIRInstallationsSingleOperationPromiseCache.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 268A0739FE706CAD204F61702EEA9CB6 /* RLMRealmConfiguration.mm in Sources */ = {isa = PBXBuildFile; fileRef = CD0CB15E468AF2B83D47464CEDFA7E79 /* RLMRealmConfiguration.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 26A684F2CE11B7E0C722C1FF3405A455 /* FIRErrors.m in Sources */ = {isa = PBXBuildFile; fileRef = 292DEE854FE9B92BC4022FE701F7F7BB /* FIRErrors.m */; }; + 26F0BB95414AA62ACBB0983D8385C6A3 /* RLMObjectSchema.h in Headers */ = {isa = PBXBuildFile; fileRef = 3CF201389ACD1CAF85EDD83AC49F3F48 /* RLMObjectSchema.h */; }; + 27426BDEE639040D91F43848108A18D0 /* RLMObjectBase.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 9B76CD581D8B96669C1D56E482DA40E3 /* RLMObjectBase.h */; }; + 279E0EC139619D4D3065DA723F73185A /* FImmutableTree.m in Sources */ = {isa = PBXBuildFile; fileRef = 05F1DD910E0B70B4E0DF0A7C3FA329D0 /* FImmutableTree.m */; }; + 27A79532731B7482EBD56538CA239C3F /* FIRCLSUserDefaults.h in Headers */ = {isa = PBXBuildFile; fileRef = CAE52D190FDACA01E3E8E100CCCFFB71 /* FIRCLSUserDefaults.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 27AA309DA4D85879F3D1D12EE6AA9431 /* FIRCLSUnwind_x86.c in Sources */ = {isa = PBXBuildFile; fileRef = 145CEB14F38B1828A56B5E6681B5162C /* FIRCLSUnwind_x86.c */; }; + 27F1D8D418B9B4712AE32093474C532C /* FIRCLSMachO.m in Sources */ = {isa = PBXBuildFile; fileRef = B7B3506EDC4CA84B41DC6B9FE7582C58 /* FIRCLSMachO.m */; }; + 2851B27D236B727CA68014E27964260F /* FBLPromises.h in Headers */ = {isa = PBXBuildFile; fileRef = FCF20827CAA2AC54FFFECCB9B75F8DA7 /* FBLPromises.h */; }; + 286807D3C2B82C0C52156AC968CFA068 /* RLMObject.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 0E543475F05FA2541F2CCB52DBEF8927 /* RLMObject.h */; }; + 28A980AD36AF039DF377CA478BE79964 /* FirebaseInstanceID-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = EE508C10A062219D8B859F9DD9C6C6DC /* FirebaseInstanceID-dummy.m */; }; + 28B2C4457C674C218002B0E56C6A9D38 /* pb_decode.h in Headers */ = {isa = PBXBuildFile; fileRef = A8A9312F7F8DE00674B65C966FA1D0F3 /* pb_decode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 28D4F3F5E6A3A3CCA9FEE73090868E7D /* FIRAnalyticsConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 2E35C429DC58D57B84D7B20DF3B4C1C0 /* FIRAnalyticsConfiguration.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 28F4214019DBD32E528DA588AA8880E9 /* GDTCORStorage_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 7060A51F91294EA617A21BB7F724F288 /* GDTCORStorage_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 290A2994852FC010AEB3CCD50C496AB4 /* FIRAuthAPNSToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 471FB2C8149FECE847C73CCCD34D3622 /* FIRAuthAPNSToken.m */; }; + 294E1309A8B8C008248D3C83D700563A /* FBLPromise+Validate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1F51719C18D03B91659D1E93CF7F391D /* FBLPromise+Validate.m */; }; + 2976C5737E255E1C6DDD2189BC8C0842 /* FIRCLSException.mm in Sources */ = {isa = PBXBuildFile; fileRef = B3C87A46F96166E181D907BACBDF1D34 /* FIRCLSException.mm */; }; + 297DC19B1446D9164EF9071599A32D75 /* FIRSendVerificationCodeRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 461E3BF8391B34CA8ECC0B7327B86304 /* FIRSendVerificationCodeRequest.m */; }; + 2985A0CBB53D75B845CF4B69A151DD45 /* FIRAuthSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = B0BD9B5CDB9392F69987B56C2D80D447 /* FIRAuthSettings.m */; }; + 2A3AFA6428A5736852667E746A9B4965 /* FIRCLSCompoundOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 76E8349BE330B381533D03B9E06C740B /* FIRCLSCompoundOperation.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 2A43BF434B4113E7712C60013404C5BE /* FNextPushId.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C37D59F16CFBD0BD12D98127608CC9 /* FNextPushId.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 2AC8BBF043B238F945C0EB0540E15245 /* FIRInstallationsErrors.h in Headers */ = {isa = PBXBuildFile; fileRef = 8E79B063CE064DA7B1933274A8DDE290 /* FIRInstallationsErrors.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2AED03ADF9C4EF5EA42A703B6A309CC9 /* GULHeartbeatDateStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 14007804F652C026CE4B746B53D6F3A8 /* GULHeartbeatDateStorage.m */; }; + 2B227E31966121C9308DC3C26AE58B01 /* FChange.h in Headers */ = {isa = PBXBuildFile; fileRef = 6F6646222EB1681CC5AD46638F5CFC6A /* FChange.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 2B2DCB61E3D680229390836AD870B4D4 /* filter_policy.h in Headers */ = {isa = PBXBuildFile; fileRef = D56281FE2EA16A3BA938732F37A34424 /* filter_policy.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2B374445507BAC21E6FDCE6318EB1023 /* RealmCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA9EE67D70795C4E48FEAD85D644585B /* RealmCollection.swift */; }; + 2B3B6A1F3CE5A236CF1B97B26E355E7D /* RLMUtil.mm in Sources */ = {isa = PBXBuildFile; fileRef = 10723D0293FDDA883CBD050B8BC02938 /* RLMUtil.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 2B5C0732132B06D6118AE72184AEA30C /* nanopb-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 95C5F958FAA51CD9AFACEAFD6323D4F1 /* nanopb-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2BD7BB425103F53E71DCAE5BE3930BD9 /* cct.nanopb.c in Sources */ = {isa = PBXBuildFile; fileRef = CF8433969CF929D29996C79B904999D4 /* cct.nanopb.c */; }; + 2BF4961A85867BF2B3296356CA18B89B /* FIRInstanceIDVersionUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 4BBC28B17DDA8EBA784B3D214C810B32 /* FIRInstanceIDVersionUtilities.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 2BFAA0475C1469645E06C94D9FB6A030 /* FIRCLSNetworkClient.m in Sources */ = {isa = PBXBuildFile; fileRef = DA4EBA778B22B26ECA6257D01119579E /* FIRCLSNetworkClient.m */; }; + 2C236456F289C2B42BE0D5C341686537 /* FIRInstanceIDURLQueryItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 724B4A64760807F69FE610EA5DB525A7 /* FIRInstanceIDURLQueryItem.m */; }; + 2C61B040BA6A9A7AE66C4D9BA26D5520 /* SessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E525CD0D67BB1B0F3B7F54506731294 /* SessionDelegate.swift */; }; + 2CE3B5EB3E415B5467CC027FC85D9F99 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA0F982B217F5CD21D3B5D280E93EDBF /* Result.swift */; }; + 2CEAEFD70CD74376767EA7D85FA8831E /* sync_file.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A963882D700987FDF81DE9A1A58D906F /* sync_file.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 2CF5274BF25904B65013C30868BEF7A2 /* FIRGoogleAuthCredential.h in Headers */ = {isa = PBXBuildFile; fileRef = B0E2D9DCC2D578F3C955E2D3D20D732E /* FIRGoogleAuthCredential.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 2D22FBD03C50829975252B9B61140698 /* FIRInstallationsStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 98F2168BA4030E0EABD5F6681F96B79E /* FIRInstallationsStore.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 2D88FEA4D706A65AFE4C710CFA49903F /* FIRDataSnapshot.m in Sources */ = {isa = PBXBuildFile; fileRef = 33B783F133658E23EE462D0FE56B550F /* FIRDataSnapshot.m */; }; + 2DB7AE4C7C8112A665C254846A827842 /* FMerge.h in Headers */ = {isa = PBXBuildFile; fileRef = EAF035ADBF22E759B690C1A46370000B /* FMerge.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 2E638CEA645E715DB0E123AD6A08647D /* RLMArray.mm in Sources */ = {isa = PBXBuildFile; fileRef = 976C54C9CC751F84860286147F3226AA /* RLMArray.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 2E7D83859CEC3D469A12BBB2E4D45C90 /* GTMSessionFetcher.h in Headers */ = {isa = PBXBuildFile; fileRef = 2A91D478CC110936C320DEC2AC5D6C4A /* GTMSessionFetcher.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2EB6B24C910FCC26849E9C2E953CAAD6 /* FBLPromise+Async.m in Sources */ = {isa = PBXBuildFile; fileRef = 780E38B21E5B8075DD47973628FCEF89 /* FBLPromise+Async.m */; }; + 2ED393E3128B673358126B3490A6B2CB /* FIRResetPasswordResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 1DD18769760DADB55736DAD2D92847B3 /* FIRResetPasswordResponse.m */; }; + 2F09DA594401C8DA2D3E40E0FB830491 /* FTree.h in Headers */ = {isa = PBXBuildFile; fileRef = ED308881D7E1D2F55E74AAF0E548A9C2 /* FTree.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 2F4E04FEAA590B129BEF1D1E1CC9877E /* FIRComponent.h in Headers */ = {isa = PBXBuildFile; fileRef = B68AAF3A0D973EE14F332611717BF3E0 /* FIRComponent.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 2FAAB421A3DAB5159D3037ED784F5A1C /* FIRCLSDataCollectionArbiter.h in Headers */ = {isa = PBXBuildFile; fileRef = AC0C18B2A33D69724E42FCD30CF08905 /* FIRCLSDataCollectionArbiter.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 2FC3F41D2A6B4DBD308B274E348AE5D3 /* FWriteTreeRef.h in Headers */ = {isa = PBXBuildFile; fileRef = FB3B80F0D5095AE87661533BF8DE50F3 /* FWriteTreeRef.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 30133270A7F1F7046628EEBB67EEDD25 /* filter_policy.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3C777B4EE7D85558581EF1A3FC747D8F /* filter_policy.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 30196B49A59B0E468EA80F41F7AE1BF0 /* FPruneForest.h in Headers */ = {isa = PBXBuildFile; fileRef = F3BD52E227C6798238630C8878860D83 /* FPruneForest.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 301B53ECB6A895D5AD7239EDB5B3B9B3 /* FIRVerifyCustomTokenRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DC9B08796CD1B484EC6E692D733DA40 /* FIRVerifyCustomTokenRequest.m */; }; + 3030D09F41A0B87FBB650A149BCD0633 /* FIRAppInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = A45D338C1940FDB1302AB9CB81DA0A8B /* FIRAppInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 304577D08B32B8EEF1E0A3F4EB4C398D /* FEventRaiser.h in Headers */ = {isa = PBXBuildFile; fileRef = C25B246DBE48DFAE91A83DFD9A0CBE77 /* FEventRaiser.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 304C19806FBC2ABCA90C96A46D029392 /* GULApplication.h in Headers */ = {isa = PBXBuildFile; fileRef = 5DA795C79D20C355B2589B1B2F35081F /* GULApplication.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 30E21E73F7A1CA0DF62A74ED75B37A01 /* hash.cc in Sources */ = {isa = PBXBuildFile; fileRef = 290E99182B80CBF746F852D5E031EBE8 /* hash.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 3105E36C781D76C21BDC63365A8BF957 /* FIRVerifyPhoneNumberRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 1517B8A647EFBB22C37521AD2B638C49 /* FIRVerifyPhoneNumberRequest.m */; }; + 310E60A10CEF7E92552CC18842913A28 /* FirebaseAuthVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = 8C04E194687BA66B98EDC15D43B979B5 /* FirebaseAuthVersion.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3137683088C1F6A8B6D69F4DE97E2616 /* FIRInstanceIDTokenStore.h in Headers */ = {isa = PBXBuildFile; fileRef = DD1AA8BF76E48EA74EDCCB561AE71A0A /* FIRInstanceIDTokenStore.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 3192B3017FBA533E4B4C3FA4DB56BC33 /* FIRCLSFABNetworkClient.h in Headers */ = {isa = PBXBuildFile; fileRef = 81A7B76B62951984CC65B19C4A945977 /* FIRCLSFABNetworkClient.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 31D25C55E797A85B79DED25437A4EC66 /* firebasecore.nanopb.c in Sources */ = {isa = PBXBuildFile; fileRef = 0E742B2B5CF247EEBCFF0C8BD42B9113 /* firebasecore.nanopb.c */; }; + 32674BEAF94B3C0FD01633091D9A34E6 /* FIRAuthBackend.h in Headers */ = {isa = PBXBuildFile; fileRef = F42E571CAB78A61F579A7D8196D8DE05 /* FIRAuthBackend.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 327D2D48C2BCC571AA41F4A2C58BB56D /* FIRCLSApplicationIdentifierModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 912B6075A0BB9F3FC276D8ACE49F2D74 /* FIRCLSApplicationIdentifierModel.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 32962F880321BC804B0623A8524F5B6B /* FIRCLSExecutionIdentifierModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 98533E594EC57133B3C68EF150266FDD /* FIRCLSExecutionIdentifierModel.m */; }; + 329FC3E722DE36B8B00674D23CCB031E /* RLMSyncUser.h in Headers */ = {isa = PBXBuildFile; fileRef = 930FF3B062A8B447DFC07EE99A07EE33 /* RLMSyncUser.h */; }; + 32DEBD42E7FAB50D08E7DE1FD7F5DF48 /* FIRCLSContext.m in Sources */ = {isa = PBXBuildFile; fileRef = D69EC7F7067D0D537E187FD578991C9B /* FIRCLSContext.m */; }; + 32F6C157E45DB0ABD6E1A207BC3EB20F /* FTupleUserCallback.m in Sources */ = {isa = PBXBuildFile; fileRef = EB8D442EDD47F4C18B9E23F08BC4F999 /* FTupleUserCallback.m */; }; + 33856C91E3F10AE9C71072821D7549D0 /* FIRVerifyPasswordResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = C4B5B407BB54A7E00EFC6217B9057C28 /* FIRVerifyPasswordResponse.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 33FED9F98332B8339C1D056B51E97B4C /* RLMPredicateUtil.mm in Sources */ = {isa = PBXBuildFile; fileRef = 82EB3C9142DB8F0A22E3C30F14B3E806 /* RLMPredicateUtil.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 3452371D8065303C909398C7D16C91D8 /* FBLPromise+Wrap.h in Headers */ = {isa = PBXBuildFile; fileRef = 1D1B6EB1EB1CC689CA6DC51E84C01C2A /* FBLPromise+Wrap.h */; }; + 345A70B5409ED930BC794A87185A9ECE /* FIRCLSMachException.h in Headers */ = {isa = PBXBuildFile; fileRef = 02725477E4502DA51C0693E502CE5CD8 /* FIRCLSMachException.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 349186DDCB8B4F9CE5ED3466B2E7CF6B /* FIRCLSHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = C136D124912241858A8E583D0415F25E /* FIRCLSHandler.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 34F6547C4C8518A900222A72B4C007E5 /* FRepoInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 3CEFA5A5C281A74704488F114ED873F9 /* FRepoInfo.m */; }; + 3540E608AC053E95EB553582B3B53B8C /* FIRVerifyCustomTokenResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = AD9911FF901182BFF3E6388EE68455A4 /* FIRVerifyCustomTokenResponse.m */; }; + 354BE05906E0CB457569A8E29F9D8532 /* Realm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02231003098E086547B4CA839A2739B8 /* Realm.swift */; }; + 35644F3829BA4F595F4FC2089BEFB725 /* FIRFederatedAuthProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = AFC0E06A82E7604B1321E10A65210FC7 /* FIRFederatedAuthProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3571F958A3907B3A806E62D50C2550D4 /* NetworkReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5914E005B72AC06DA8A3F4F516C69DC0 /* NetworkReachabilityManager.swift */; }; + 35C26719E55743DACF7DF3C6B61D025D /* FArraySortedDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 0ED8B0B84A275F2D02C176BEE7BDCAA5 /* FArraySortedDictionary.m */; }; + 35E83FC343783EF6CC38C99F69A67F4A /* FIRCLSProcess.c in Sources */ = {isa = PBXBuildFile; fileRef = F5C89F11ADEAD50F827C26186992AEA2 /* FIRCLSProcess.c */; }; + 362A46839457D12ADDCCCE83713F4D58 /* FIRCLSCrashedMarkerFile.c in Sources */ = {isa = PBXBuildFile; fileRef = 7C5D1ABA49A70E905CD66FC7B1E85BFD /* FIRCLSCrashedMarkerFile.c */; }; + 36511455249A7349287D5F0256C74819 /* bloom.cc in Sources */ = {isa = PBXBuildFile; fileRef = 50DD131FEF677E8FF47C059527C19566 /* bloom.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 36AD77974C22C933CC24993C789FB85C /* FIRCLSSignal.c in Sources */ = {isa = PBXBuildFile; fileRef = 2F472D69D8352A62D70BBD53662D3F22 /* FIRCLSSignal.c */; }; + 36B9102AD5454BEA6CD15177C379061E /* FIROAuthProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 8AF7AEE28A597BE8C090298E2EBC0B1D /* FIROAuthProvider.m */; }; + 3735567A5FBE87CBEB5424CD517078EE /* FTupleTransaction.m in Sources */ = {isa = PBXBuildFile; fileRef = A0CB71AF07C955B047A38074046DD5F2 /* FTupleTransaction.m */; }; + 3772051821C60BB4D97BCDEF7CAC6918 /* FIRInstanceIDKeychain.m in Sources */ = {isa = PBXBuildFile; fileRef = FC8F7429129DDE3FE3CD5587FC76366F /* FIRInstanceIDKeychain.m */; }; + 3793E9E56166DDF584DE3D1ED3F44BA8 /* FIRCLSSettingsOnboardingManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 3BE0225CF21C0F1263F67167183C57B7 /* FIRCLSSettingsOnboardingManager.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 3820FCCABF659770A7A74440C3001C87 /* RLMSyncUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = 5292DA815D495D19C1091AD168722BFA /* RLMSyncUtil.h */; }; + 38484609856819860F2552EFF7262C68 /* iterator_wrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = A6119970BBD3C7ABD6672B5D27F2607A /* iterator_wrapper.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 384CFB5FF97037843BC45F477B4EEBCB /* FIRResetPasswordRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = AD01636391685B8E33F32E033B2F16B3 /* FIRResetPasswordRequest.m */; }; + 38A3A814BF4126D07A208A4DA5E6E240 /* FIROAuthCredential_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = FABA7194CA09589599F926D3C76789BF /* FIROAuthCredential_Internal.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 38ECB82C9A053349A8928D026F437792 /* ImageDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0791472EB1551B2208F3B22E2A0E9F04 /* ImageDataProvider.swift */; }; + 395BF823FA954E78D5CD404F4C4BB53C /* FBLPromise+Do.m in Sources */ = {isa = PBXBuildFile; fileRef = DB143F302A037FF79A4C3DF6FE9C3839 /* FBLPromise+Do.m */; }; + 3966218A30FDC3F688679A7103EE2E40 /* NSData+FIRBase64.m in Sources */ = {isa = PBXBuildFile; fileRef = 1FC141C04FBFB3AD1B37DD2AD9485844 /* NSData+FIRBase64.m */; }; + 3996B5F4074563E877612862AEC993B1 /* GULUserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A03882E9613B12780B58BA0DBDEADAA /* GULUserDefaults.m */; }; + 39B5C86A530DE569BA00411901FAF65C /* FIRAuthWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = BEDABE10B5025C79318A8CF7B1925844 /* FIRAuthWebView.m */; }; + 39C37BF824F49AD6E5899219C0733868 /* FIRInstallationsAuthTokenResultInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = A84C72B92CB67258C3060F4E4AF55F33 /* FIRInstallationsAuthTokenResultInternal.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 39EB68E40A752463B5C6199CE1FCACA5 /* FIRFacebookAuthProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 33EAA367E623B3A0DE2D79620F85FE4A /* FIRFacebookAuthProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3A43E46338D78FD720DC9A186EFB806B /* FIRInstanceIDUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D15BFCCA59443379D0E59B6CD009FE3 /* FIRInstanceIDUtilities.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 3A67AE71FF530880D928EE0D3A6FAD6C /* GDTCCTUploader.m in Sources */ = {isa = PBXBuildFile; fileRef = B53846D10D47CDE2D30C4DD425E97808 /* GDTCCTUploader.m */; }; + 3AC0D89BD349522248D74FAC83923DC8 /* FIRTwitterAuthProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 387531976064EAD3DDF02EBEB38F7D41 /* FIRTwitterAuthProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3B10809E3E6FDABC215367D684ADA5F2 /* FIRUserInfoImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = DC6628ED8CAEEDBD7E69EE5FBDF8A882 /* FIRUserInfoImpl.m */; }; + 3B34F6F45C02F259DD4516D4047C5AA7 /* write_batch.cc in Sources */ = {isa = PBXBuildFile; fileRef = DD457201EEAF6D1D95DCA33AD2EDBF9F /* write_batch.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 3B6316AC0DA1373E767C9B0209B41061 /* FIRAuthTokenResult_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 66653077665BFE63195D56DF6B94989E /* FIRAuthTokenResult_Internal.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 3B6EE6F748F891E92E1AE63029F3D4DB /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A9D78AA155958A76A77ACF07046CD17 /* Box.swift */; }; + 3B733C714C595A3401B312D2D30F092D /* GDTCORPlatform.m in Sources */ = {isa = PBXBuildFile; fileRef = DFD7E84C43890AE1CDFF3965BD2EA669 /* GDTCORPlatform.m */; }; + 3BD063008F14A0D06586F954907110D9 /* Sync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3628F98B2E091B183A6B8A870F66D7E5 /* Sync.swift */; }; + 3BDEFBCEEB2C0B16056BA157F03A1A30 /* table.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DA81CC3DB932CD235271CE085F91D47 /* table.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3BDF75744A39DAB17C513036D239E620 /* FListenComplete.m in Sources */ = {isa = PBXBuildFile; fileRef = 6804BF7FE62BF9D2FE3ED2E836DCC825 /* FListenComplete.m */; }; + 3C3FFD62DC33C9071947E05EDE02D6EA /* FTreeSortedDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = ECA7865B913CA6A76D0C52F4AD411F8D /* FTreeSortedDictionary.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 3C52CE0A60C1071B0A1586B57CC4E2FD /* KingfisherError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 667D42BF455C022A7D8EFF143376525C /* KingfisherError.swift */; }; + 3C6CE7E7BE2DEDE4C26537B856C67B6A /* FIndexedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = FEFDB553310806FE52AA299503F0117E /* FIndexedNode.m */; }; + 3C9DC7E31F8C0FCD9E0191FE4AF81284 /* FBLPromise+Await.h in Headers */ = {isa = PBXBuildFile; fileRef = 926F3482C65B41D6727B7F69D5725417 /* FBLPromise+Await.h */; }; + 3CA959955A0D5CA51EDD9511F4EAC44C /* FAckUserWrite.m in Sources */ = {isa = PBXBuildFile; fileRef = 0592320608C8F3CCF80D574E10C7E91C /* FAckUserWrite.m */; }; + 3CAC503A5DAA8D2DFB6895C2F4F46591 /* LinkingObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89DDA8DBE011A6C4ACF28382FA07CBD1 /* LinkingObjects.swift */; }; + 3CB321C64C92C8F669CD8B5ADBA0AC08 /* fbase64.c in Sources */ = {isa = PBXBuildFile; fileRef = 64C2554DC46237514E52F9B969B9C72C /* fbase64.c */; }; + 3CC45C52DF050591BAA36EB1EE71035E /* FChildEventRegistration.h in Headers */ = {isa = PBXBuildFile; fileRef = 6B34E88940F4E64CA1A6C9902CB11198 /* FChildEventRegistration.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 3D142725B97ADD7225570C26B8FD1096 /* FRepoManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B5542975A6E66F371A3C9A81B897E76 /* FRepoManager.m */; }; + 3D226A55716C60293080D0A4373425A6 /* FEventRegistration.h in Headers */ = {isa = PBXBuildFile; fileRef = 809D9326143CAC79986E116D1A06E548 /* FEventRegistration.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 3D373AD4EBC1B845DEAF2EB03603D113 /* GDTCORUploadPackage.m in Sources */ = {isa = PBXBuildFile; fileRef = 712C3E1D09C9F2171D435E81DC50FF87 /* GDTCORUploadPackage.m */; }; + 3D42868BA350D47478AF7E3C85A5618E /* FIREmailAuthProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 358E651244C190AC57D4A02BC264D248 /* FIREmailAuthProvider.m */; }; + 3D536533D07F957C1565AB1064E27B99 /* FIRInstallations.m in Sources */ = {isa = PBXBuildFile; fileRef = 79565DC1642A3650F2C7D67B6325765D /* FIRInstallations.m */; }; + 3D80FB2906F73C0A9A38EEEA4C0E14F7 /* FOperationSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 797A37AD003EE04FB5F70E2DE7E434DE /* FOperationSource.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 3E05EFB380BB32DD4E9D12038BB6A6C7 /* RLMMigration.mm in Sources */ = {isa = PBXBuildFile; fileRef = 05B52350B0B17B2722AC405A40762DA7 /* RLMMigration.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 3EA606087C8FE2CCF75F04F011D9C2C8 /* FNamedNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B72C47749C5D7545714AB0DF04FBBFC /* FNamedNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 3EE7A161A8B5A88E1B0F87885E8F1FE8 /* FNode.h in Headers */ = {isa = PBXBuildFile; fileRef = CC4EB592A6575DA0569604F8EF9D9EA3 /* FNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 3F135F4BDC16B309D9296FAC7A1522B0 /* table_cache.h in Headers */ = {isa = PBXBuildFile; fileRef = B1EA1D27F97E2F73EFBCE2703BCE7E0D /* table_cache.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 3F8153B107171B193E1327275298F08D /* FirebaseAuth-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DF7BA27509E8E936610AD1EC2F69E0E /* FirebaseAuth-dummy.m */; }; + 3F8531662DDBAC0735862ABB42B87261 /* FIRCLSByteUtility.h in Headers */ = {isa = PBXBuildFile; fileRef = 7D397FD2F6AAD22EA8990CEB78CA0371 /* FIRCLSByteUtility.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 3FACBFC2E3AF73AD392579229D274694 /* GDTCOREvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 52E21E6ED4BA9DF24FAB3428B4CA51D7 /* GDTCOREvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4041C2939AD1934FE3E208D5FC0BB8A0 /* FIRGetProjectConfigRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = B71683F1B20F1053C4E78AF2ABFE7637 /* FIRGetProjectConfigRequest.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 406488DB5CD66387440E94CEE22D373F /* GDTCORStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = 77D06B4DBC29D02E02343A8FD4AC7C03 /* GDTCORStorage.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 40B124A7DC4EB5174454DB82C1FC33EF /* random.h in Headers */ = {isa = PBXBuildFile; fileRef = 32EA256C170EBC175572FBECB2D4E5BA /* random.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 40C27F629E9FC14CFD215250DB7E813B /* FTupleTransaction.h in Headers */ = {isa = PBXBuildFile; fileRef = C0B95A37E920E65E5E78E2632195B47E /* FTupleTransaction.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 40D08C873299148205820E3707DA6BDC /* FIRAnalyticsConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = BD000B66CA3DAE39C9254B98F356AD5E /* FIRAnalyticsConfiguration.m */; }; + 4115DF99DB04C8C8E7D39D0805D4E87D /* FPersistenceManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C0A3508439074258FC89A3CBFB18B819 /* FPersistenceManager.m */; }; + 41239C845E63547347B6C4ED3958C4E2 /* NSData+SRB64Additions.h in Headers */ = {isa = PBXBuildFile; fileRef = 8E76D2B09059C28B04E6F10E68D35F5F /* NSData+SRB64Additions.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 41941557EBAA39CE6B86EB7D42180EC5 /* FBLPromise+All.h in Headers */ = {isa = PBXBuildFile; fileRef = 7706CC3470134134EB96FF63B96D49DC /* FBLPromise+All.h */; }; + 41C1EFE7A51F9566C1D092CF143D8C48 /* FIRDataSnapshot.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F739610BEEAB0E57F2B0D16064B644E /* FIRDataSnapshot.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4211BBD8C4DDD32A77957D46BAC8D255 /* FIRUserMetadata.h in Headers */ = {isa = PBXBuildFile; fileRef = 32FA6F057E064B56ED5DDA9F8F32E1E2 /* FIRUserMetadata.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 421BF2AA16068673704B71CCB2752678 /* FIRCrashlytics.h in Headers */ = {isa = PBXBuildFile; fileRef = 747AF31DA5C0F0B63B2BAA9946DE6A54 /* FIRCrashlytics.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 421E395A098392CADE58AE7E797192A5 /* FIRAuthRPCResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = FDAF8C09000A98E72DF3001961842E96 /* FIRAuthRPCResponse.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 42243F8F8A3A333A3543A3798E0BB9B2 /* ObjectiveCSupport+Sync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C003E341EAC1C5F8D5C1F825A03BC6 /* ObjectiveCSupport+Sync.swift */; }; + 424FD6C5EF45451E5B6A44D5F0812D02 /* GoogleDataTransport.h in Headers */ = {isa = PBXBuildFile; fileRef = CF5F329FA138691313AFB64FB1006F57 /* GoogleDataTransport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 42EF7F079DC719E8E215101D1097070A /* filename.h in Headers */ = {isa = PBXBuildFile; fileRef = A641EFE5534FE8DDEA5D903FD7216933 /* filename.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 42F1937DE5C9F20409E98F556239C14C /* FIRInstanceIDTokenOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 192E47A30A68811FE88255BE422DA582 /* FIRInstanceIDTokenOperation.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 430FBA64DC0FC3E07D39B0DAF4657C81 /* RLMNetworkClient.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2BABED548D8BFF82EB08193CC4A45DAD /* RLMNetworkClient.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 4327BC053CFE77040AE8E4FFB638AC64 /* FIRCLSDwarfUnwind.h in Headers */ = {isa = PBXBuildFile; fileRef = 88D5ED9C4C466DC197E025B5D6B9BEC4 /* FIRCLSDwarfUnwind.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 432848A52E6A7EC1F084299032907A29 /* ImageTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA7C19E231F44F4302565226ECB05515 /* ImageTransition.swift */; }; + 436803A8CCC81A15C5EA20B71B444217 /* RealmSwift-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ABE65F669873B77346F0A4C50731AFD /* RealmSwift-dummy.m */; }; + 4373C08B60E643CD133C67158060BE6A /* merger.h in Headers */ = {isa = PBXBuildFile; fileRef = 8F2C8DAE66279CFF4D15F682106D761B /* merger.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 43A191D74DF7A75AB58657DB8E29A740 /* env_windows_test_helper.h in Headers */ = {isa = PBXBuildFile; fileRef = A9D04B5851A9758EDBE1DA3F0572ED7F /* env_windows_test_helper.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 43B3C466C7CC8BE2BA0C3EED7ED64D66 /* FIRCLSDownloadAndSaveSettingsOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 202FECE0B01AE375A5581D0AF0CD65DC /* FIRCLSDownloadAndSaveSettingsOperation.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 43E33E09B21DB7B98B1023C3A9FFFBF3 /* GoogleUtilities-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 71721A7E88B754EEDA47EBAC240BDA55 /* GoogleUtilities-dummy.m */; }; + 440D721D141CD331A4F96417FA520F4C /* FIRCLSNetworkResponseHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 7BEFF599A831C23E9164164D5AE2DC64 /* FIRCLSNetworkResponseHandler.m */; }; + 4474F193DA6D18B47AF0D15FAEC9F482 /* FCompoundWrite.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F0622B5942605ACE1CD6F484496A943 /* FCompoundWrite.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 447FCEA8BB2DFACD60B6B8ECECC027B9 /* CallbackQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EFCCC9B6FCB73B30D97FD7D962050D3 /* CallbackQueue.swift */; }; + 44880169C9C9ED91088E113D9DE078D9 /* FIRCLSDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 62FF18EBA9F70278FDEB4F9759A9D445 /* FIRCLSDefines.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 44D75E78971EDCED0668823B4680B810 /* FIRAuthErrorUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 7EE0B27C3EE3C18B5068838918F4F384 /* FIRAuthErrorUtils.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 44ECEF3C6FD2EFECEB4C6A8E172F7C35 /* thread_annotations.h in Headers */ = {isa = PBXBuildFile; fileRef = 3FBDB654C4BF020215028800CA80C5AB /* thread_annotations.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 4506F8AB5C48F06BC5A23A8896FA3B88 /* GDTCORPlatform.h in Headers */ = {isa = PBXBuildFile; fileRef = AB0134FF9D73541F1CAAD70ADF3FE8E4 /* GDTCORPlatform.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4516012F99A1A9B2EA4313B79E29A580 /* RLMObjectSchema.mm in Sources */ = {isa = PBXBuildFile; fileRef = A6BBD745161E87221061671555FCE237 /* RLMObjectSchema.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 451A14C1427555EDBE77B22E6248A5A8 /* FIRInstallationsAuthTokenResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 842BD4B331F68A36CEB4311001E2EFD6 /* FIRInstallationsAuthTokenResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4557D52F1C5BDFFD4D7DFC0201735460 /* FIRCLSStackFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = BFFCEAEC4E3AEC71E8ED88F86C899DA1 /* FIRCLSStackFrame.m */; }; + 45656090825D8B8006F6CF3F7C953513 /* FEventEmitter.m in Sources */ = {isa = PBXBuildFile; fileRef = EA4701731BC4DA180E5A715A5505C01E /* FEventEmitter.m */; }; + 4572A9AA4ED20FC74F97FF25F61A1CAE /* FIRDatabaseConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = EAB1F081D87EA12C99FE8E742DCCA8E9 /* FIRDatabaseConfig.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 45792F139DDBCE8090EA0D61771C5A9E /* external_commit_helper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C78202D4B72FA2DC0E1F9DC84DE41760 /* external_commit_helper.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 459458B16079A5914156FE854B31580E /* FIRDatabaseQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C41758045A7C6E1ABE7A948D9112321 /* FIRDatabaseQuery.m */; }; + 45A7A6C69C576E122C6AA586B871D9C2 /* FTupleNodePath.h in Headers */ = {isa = PBXBuildFile; fileRef = 2E927F00B9D8E3AC6775F6EF61528A7B /* FTupleNodePath.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 45EDE7CCF5471B181EA12B3C8FC66F4D /* Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1EFC8129C762D0986B402BF4835DAB3 /* Util.swift */; }; + 4617CB956B78F75EAAE1989392DC3AD4 /* FirebaseCrashlytics.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D756894BE4E37E0CA32A46F1BB2FDD9 /* FirebaseCrashlytics.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 461D7C2C38AA15C6B0B9B967BA1C1397 /* env_posix_test_helper.h in Headers */ = {isa = PBXBuildFile; fileRef = 506EAE82BD3E4008D6B467A84A76A492 /* env_posix_test_helper.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 4620663A93EA36D5C393F65AD22815C4 /* FBLPromise+Wrap.m in Sources */ = {isa = PBXBuildFile; fileRef = 5115577DA06DFFCE6071348C83697F2E /* FBLPromise+Wrap.m */; }; + 4628F1BE250F88479FEEFD9C6113A488 /* FIRInstanceIDCheckinPreferences_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 34E1813A0BA2F284FD8E92D086FCDB79 /* FIRInstanceIDCheckinPreferences_Private.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 46847931BEE1B519DDC7D7628E96CDE1 /* pb_decode.c in Sources */ = {isa = PBXBuildFile; fileRef = 98F73D30C06A348EFFDC85B721BFC501 /* pb_decode.c */; settings = {COMPILER_FLAGS = "-fno-objc-arc -fno-objc-arc"; }; }; + 4702C63F91C7967ECD570C5CD8D092E3 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFC56E517F177CBDCE7A445BE95B4E4C /* Security.framework */; }; + 477B4E25FBF8AA81BF15887C25986417 /* arena.h in Headers */ = {isa = PBXBuildFile; fileRef = 28EBE76BF2FAC20F0C4AF4D2B258B996 /* arena.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 4781CFEA0881C7AFD1539C12F074632D /* NSButton+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565075BC42365FE68F251EE460368715 /* NSButton+Kingfisher.swift */; }; + 47A6BE227E061383B1F7DB598999FB41 /* FMaxNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C6417652D217907B3854C35FBC3BEF5 /* FMaxNode.m */; }; + 47AC2BED04DD7F7F819E9BCDEFB0317E /* FTuplePathValue.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C05131A2732A28192011811FF7609CE /* FTuplePathValue.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 480689C1DDB28753482E28AC9FF63766 /* FIRCLSSymbolicationOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 95B4BB665D979FEF54288EC9FB68B57F /* FIRCLSSymbolicationOperation.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 485A2AE62FA6D80EE8813531ED9B5202 /* FirebaseInstallations-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 27F5D34050A15487B1386AFF70C4D37F /* FirebaseInstallations-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 487D53153D55CE66F0EB58DD50702B51 /* RLMObjectBase_Dynamic.h in Headers */ = {isa = PBXBuildFile; fileRef = 86A9C0C738B55C104584CA8477DBF065 /* RLMObjectBase_Dynamic.h */; }; + 487F3249D460766985F47DF853C7B122 /* FIRInstanceIDTokenFetchOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 548A09288743BC6316E5BF2D9B619111 /* FIRInstanceIDTokenFetchOperation.m */; }; + 4880A102EF70151AC12B34233941AD98 /* FEventGenerator.h in Headers */ = {isa = PBXBuildFile; fileRef = 341FA0A9A90666DFB068B02CF77FC23D /* FEventGenerator.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 48BF3751E3092C0C24D0378706396092 /* GULSecureCoding.m in Sources */ = {isa = PBXBuildFile; fileRef = BE79B0FA5EB399EAC4CE00E28D3EC116 /* GULSecureCoding.m */; }; + 48C5AEF00EEE8F7222DEE7E3217F58CC /* FIRCLSUserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = F50E4E22F95A26E70C681B16D0148E77 /* FIRCLSUserDefaults.m */; }; + 48E4A08C0D897078ECCA0D914DEB647F /* FIRInstanceIDAuthKeyChain.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D77224971A73956E499DBACD9269CC0 /* FIRInstanceIDAuthKeyChain.m */; }; + 4920F481DD8EA8FDD77D27961F8AD863 /* FTrackedQueryManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E8447705007CCB88C9E6571A0E8E19F /* FTrackedQueryManager.m */; }; + 4933911483C97AE1C3763B7125D705D4 /* FIRInstallationsStatus.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A60C4735224475581B5618F0141BFDC /* FIRInstallationsStatus.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 4965BB761B0F772E219CCB324F49308B /* FIRBundleUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = 0176A732FDA7EF1260FECC4B3C3B16AF /* FIRBundleUtil.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 499B63D6AB0BC5F71DCDA86B3515E083 /* FIRSecureTokenResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B01724F721919F7D7A74590E67DC81D /* FIRSecureTokenResponse.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 49C2074E8EE7E853D47A8153491F5846 /* FKeyIndex.h in Headers */ = {isa = PBXBuildFile; fileRef = 1477BB8C1DE91F3AB64B8F2FF2A5A077 /* FKeyIndex.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 49CA65BA27EEC63A5904CAEEFA8D1FB7 /* mutexlock.h in Headers */ = {isa = PBXBuildFile; fileRef = 883D7550CA14C459AC0B859D454D150C /* mutexlock.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 4A1A01DEDBD2D150E3852CC02330CC27 /* FIRAuthNotificationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = FAB3710980A18B4F81EB86053E0394C5 /* FIRAuthNotificationManager.m */; }; + 4A1CD1D2408868B2F10F4E7B1F3CDC7A /* FIRInstallationsVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = 53FF4D24482305D95F31AEAE76AABAF1 /* FIRInstallationsVersion.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4A228C12D280C60F1472305383A091F6 /* FTrackedQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = 00D81FAAE360D000EE90A6008438A716 /* FTrackedQuery.m */; }; + 4A4BD18B37AE610488A6EB64348BF100 /* FIRCLSProcess.h in Headers */ = {isa = PBXBuildFile; fileRef = E9A7DEA3E358EF81F1F1348372BEB733 /* FIRCLSProcess.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 4A954E1410CC831251D1883F3341A975 /* RLMSyncConfiguration_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 279EF69F64F33BF43FDC19F468B28D41 /* RLMSyncConfiguration_Private.h */; }; + 4B1C14991FCEB01E9E05EF8F1FCD5671 /* ImagePrefetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2173032F4DC6AB031930F5AEED697F0 /* ImagePrefetcher.swift */; }; + 4B288AC81506A29EC002A33979797B27 /* Deprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE9D69EB6F70F361D33FF4517C4151F6 /* Deprecated.swift */; }; + 4B2BAF4BEAD936890EBF224BEABE6AEA /* FIRVerifyPhoneNumberRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = D39DEBFBB8F078D08F1BE9B07342568E /* FIRVerifyPhoneNumberRequest.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 4B51C5D0CC6C7D5D6643929AF2A55E73 /* FIRCLSAsyncOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = C9A9708F1BC72EBB6C42F9D332BAD6D8 /* FIRCLSAsyncOperation.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 4BA4E26D3223A9FE843FC8311848FB28 /* FOperationSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 067870D7BE43590F2E93FD4B3FDB5646 /* FOperationSource.m */; }; + 4C14BCCCDF4F682E80B46433B59A27F6 /* FNextPushId.m in Sources */ = {isa = PBXBuildFile; fileRef = 0351264261053739411827F81B601E59 /* FNextPushId.m */; }; + 4C3F544366A4B9CEBA290756E28570F3 /* FIRAuthAppCredentialManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 309A0F8D2A82297BB270F70596692677 /* FIRAuthAppCredentialManager.m */; }; + 4C481897FD8E8E52957F30B1B89195DE /* FSnapshotUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 120F846FAC83BF1EB295CC2FB54137E0 /* FSnapshotUtilities.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 4C673A60C12195835BF34E011C152027 /* GDTCORTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 3E8366B108065E2E1A6A3144FEEEA1BE /* GDTCORTransformer.m */; }; + 4CBAACF939AA13A4400C8DCA3410CA0B /* FBLPromise+Validate.h in Headers */ = {isa = PBXBuildFile; fileRef = 9EA4A8CC145EB727B389E41E6BF8B35C /* FBLPromise+Validate.h */; }; + 4D3895C9DBFC2D68DB50FA7E1A386533 /* FIRInstallationsStore.m in Sources */ = {isa = PBXBuildFile; fileRef = E2B1E88FB83B19CC7E6207BDD91BCE75 /* FIRInstallationsStore.m */; }; + 4DCC0459009E8F4468F83EFDFCFCEA0A /* FIRInstallationsLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 7C7A1E38C56A5310BDCCF9B80FF7BAF1 /* FIRInstallationsLogger.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 4E19A1F970E4B153B9E86E6915089892 /* FLeafNode.h in Headers */ = {isa = PBXBuildFile; fileRef = CDDA260CC13C2C314506C956FB571FED /* FLeafNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 4E5FBCDF894A910BCF3BF7B1FB4242F9 /* GULNetworkConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 5ECEECF341CFC266359914D96F0FCCAB /* GULNetworkConstants.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 4E7485B29692E8BF88A9EA104764AF1A /* log_reader.h in Headers */ = {isa = PBXBuildFile; fileRef = 486997BAA7094533C8CDDD157CFF96BB /* log_reader.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 4E7DBC98260A502FF65D170CAFF96D23 /* FIRCLSFABNetworkClient.m in Sources */ = {isa = PBXBuildFile; fileRef = A573EBB34B32D9E475532628683BF8CF /* FIRCLSFABNetworkClient.m */; }; + 4EB2ADCF5984C9875D11787D992A0972 /* FIRGetAccountInfoResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 11803A8C45EB3F5F3B529FB885798EB9 /* FIRGetAccountInfoResponse.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 4EF883370AD6BDEE1324F53B3AA355C4 /* FIRCLSdSYM.m in Sources */ = {isa = PBXBuildFile; fileRef = 30E22880820C5CA49AE883F198B59C88 /* FIRCLSdSYM.m */; }; + 4EFD0A30A914F22731EE4A40DC326D7F /* FIRCLSInstallIdentifierModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 03BD84CE35C53C22848C4F37AF990917 /* FIRCLSInstallIdentifierModel.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 4F26D76BA4E3F80903FB68333F925101 /* FIRCLSHost.m in Sources */ = {isa = PBXBuildFile; fileRef = A73AC529EF63101E9F604F56970CF8A2 /* FIRCLSHost.m */; }; + 4F5B95A2204438513D7171915037CBD4 /* histogram.h in Headers */ = {isa = PBXBuildFile; fileRef = AB546DEDD26660C09E3E334FEC21D8CB /* histogram.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 4F812E33FAB867558FB394250389F4DC /* GDTCOREvent_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 10B3B837E0F6835E55DE59E541E92996 /* GDTCOREvent_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 4FFB9654C81EB2AEF0DE0819BCFA7D24 /* FTreeNode.h in Headers */ = {isa = PBXBuildFile; fileRef = FD66CF40520DC57E058636DDAD608536 /* FTreeNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 4FFF5D9D67F39D2CF4D4C016EECACB00 /* RLMPlatform.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 22F2A4295E515E6562FF43CA3D56B0C5 /* RLMPlatform.h */; }; + 506015C21605384EBD4F685744C398D0 /* FIRServerValue.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C881435CE9E0783A5644B934C4B16C5 /* FIRServerValue.m */; }; + 50A8B48A98FC0D65D138AB7D030497AA /* RLMObjectBase_Private.h in Copy . Private Headers */ = {isa = PBXBuildFile; fileRef = 05A33B164E057EE24CF3636BBB916A28 /* RLMObjectBase_Private.h */; }; + 50B7D08F90D2D6435BBE891E1547332E /* RLMAccessor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 53006D0A08EF8EA62C89F6BBCD7F0023 /* RLMAccessor.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 510B3DE29FD0BCDA4B6746D07116E1BF /* FCachePolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = 0AE01923DCFF0C1BBC52D813E212BF6E /* FCachePolicy.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 5120291734D77494B3D96DFCC2EE9857 /* RLMMigration.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = C4A5068B4F002455620D242C20110490 /* RLMMigration.h */; }; + 517622BCB83ED4177EBC3AF9A1CE8E8F /* FBLPromise+Catch.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F79BB540DD5F3B0F0DD4C789A65EB79 /* FBLPromise+Catch.m */; }; + 51CE50887778105AB03E435628175C9A /* FEmptyNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 09125E2EA0588202813B2CDFFEEFD93F /* FEmptyNode.m */; }; + 51D6A5F1CD7E28681592199C70361D15 /* FIRCLSByteUtility.m in Sources */ = {isa = PBXBuildFile; fileRef = A542BD074662CB4F26AFC7358EE58123 /* FIRCLSByteUtility.m */; }; + 5200FB78D3889E5F72EF3EAA5B9C71FD /* FIRAuthGlobalWorkQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 6FFE6D0828D5AC252CA221F054787EFE /* FIRAuthGlobalWorkQueue.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 521651EE93DB4D74C6D0A42A4C7FB555 /* FIRFacebookAuthProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 6D60CD4074EEBE50EB3EAB68AEF7AB2A /* FIRFacebookAuthProvider.m */; }; + 526D4A6E90525A42DEEE53B2EAD2C5E8 /* Kingfisher-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 5908FBDB35CB84240FA819996966DCDC /* Kingfisher-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5290971CDFF4B6DDA038BB155C4A89C3 /* NSError+FIRInstanceID.m in Sources */ = {isa = PBXBuildFile; fileRef = AB6E896A6AB3A2C9129B37B768597237 /* NSError+FIRInstanceID.m */; }; + 5290BB41346D30D8DAF45533FB141177 /* results.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E0BFE3FA489687282F85C22151BD1515 /* results.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 52D6FD585EA8DD0B61907A5C9AA3DBF9 /* FirebaseCoreDiagnostics-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 1425FAB07EFDBF97C7BA299BD0A460E8 /* FirebaseCoreDiagnostics-dummy.m */; }; + 52D87F5622DCE379129F8F6A2E49ED48 /* FIRMutableData_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = D9BBAF84118A47ED29561F403710BAD4 /* FIRMutableData_Private.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 53090D8A8A453CB8981AA4942DDB6316 /* FValueIndex.h in Headers */ = {isa = PBXBuildFile; fileRef = 4859B5C57D1455BEDCFD395EC04ECFF2 /* FValueIndex.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 530EC7E20DB092A84F13DFAF89701619 /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 372C5DBFC4877032EA5292324F62B37A /* Realm.framework */; }; + 5321CA455C7EBD046B2798EFEE05A176 /* FIRCLSReportUploader.h in Headers */ = {isa = PBXBuildFile; fileRef = E501EC074F41F40181C1B9420DF549C9 /* FIRCLSReportUploader.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 53451DD04E547BF568F92CABA14407E3 /* FArraySortedDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = C6CBD394561897F97580F99C0927661C /* FArraySortedDictionary.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 53791F5E5F07400F92CFDFC89A432305 /* Validation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BAD9B7E3A3D8E9AA3D878E3A56DEB4 /* Validation.swift */; }; + 537EB58F1BC35B91565A0841B860C223 /* FIRInstallationsAuthTokenResult.m in Sources */ = {isa = PBXBuildFile; fileRef = A6CA61A608F721178720B5C7620B2CB0 /* FIRInstallationsAuthTokenResult.m */; }; + 53FC0A1355D80EB46E589948D032E22C /* FIRSetAccountInfoResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 61C01EB057182563C6FDADA78D82D50B /* FIRSetAccountInfoResponse.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 542E3259A98FBBB48F64604947D97F67 /* GDTCORPrioritizer.h in Headers */ = {isa = PBXBuildFile; fileRef = 71C8E228FEBA45B0E3FA9EA1E8E6F19A /* GDTCORPrioritizer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 54309C957347C1C0C28E0A9E1AEF4BE7 /* FIndexedFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 56BAA01862A49A9C10732D4D3648B154 /* FIndexedFilter.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 54630A53305112536BA94296A1425F39 /* FBLPromiseError.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 037723208A972F6D4CD565FBB23FEDBC /* FBLPromiseError.h */; }; + 548969E05ABE30268724380D5DC6E884 /* FIRSendVerificationCodeRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CD214470D6041E540B899AA24488E398 /* FIRSendVerificationCodeRequest.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 54C874E6783EA1E008E2AEE83DAAB1EA /* FConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 4877558843115B5DDB96AA54123D8AFB /* FConnection.m */; }; + 54F5F4C0D1B17878CB7483057DF28DC6 /* FIRCLSMachO.h in Headers */ = {isa = PBXBuildFile; fileRef = 14C0953A445D4BE60522EB22E077A192 /* FIRCLSMachO.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 5505CF55838E236C8016022D4036FCF9 /* FIRCLSSettings.h in Headers */ = {isa = PBXBuildFile; fileRef = D62356771945F7BF821F0BB694EC7623 /* FIRCLSSettings.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 5566EE2A5696C5683168F1310B8B6A66 /* GTMSessionUploadFetcher.h in Headers */ = {isa = PBXBuildFile; fileRef = 3AB5615A6CD7B79D890755A1CFA31206 /* GTMSessionUploadFetcher.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 55D4605016980ADF1D950E15524E11BC /* FIRInstanceIDCheckinPreferences.h in Headers */ = {isa = PBXBuildFile; fileRef = D06B3B97E4AAD489DE7ACD5A5CB33145 /* FIRInstanceIDCheckinPreferences.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 56D79D21489B99536E56D11613733DD3 /* RLMSyncManager.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 82EE9EA75ED100D0A1D4A04F26EF09A7 /* RLMSyncManager.h */; }; + 5734197CA26453B55461E0137DF89CFC /* FIRDatabaseConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 11DCEEEC472C556014277C5511D1CE3B /* FIRDatabaseConfig.m */; }; + 5740D7EB95C08F8102D86F911A11BECF /* FIRAdditionalUserInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = C190906AA7599B5C3B1EDD595BD50AE2 /* FIRAdditionalUserInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 57A9284BFD43EE2DF4CB37CC729CC59D /* GDTFLLPrioritizer.h in Headers */ = {isa = PBXBuildFile; fileRef = 635DDB80F0882C70D7DD08F88EFA542B /* GDTFLLPrioritizer.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 57B3FEF620AA9F256E6DFC810DF30C04 /* RLMArray_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = D8C6EFDD935E4915732937E2FCB4ECA1 /* RLMArray_Private.h */; }; + 57EF73A0F4CE8B7F9AB9430158ED0926 /* FAckUserWrite.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B80609DA7F3835D3B6FF1855935465F /* FAckUserWrite.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 5810F68ECE7F75FB25A2278E914BE44B /* FIROptions.m in Sources */ = {isa = PBXBuildFile; fileRef = D2B0710A146E5C9037FCA9FC8BC68189 /* FIROptions.m */; }; + 5861161136ED8784C63A95CF9EE918C8 /* shared_realm.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 106AA954E1BAE8847B35D6285830BF68 /* shared_realm.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 58A79EDF924A2F3AEE3C72935CC15180 /* FIRCLSNetworkClient.h in Headers */ = {isa = PBXBuildFile; fileRef = F90DF5F8EEF6B3A7BF300E04E72545DD /* FIRCLSNetworkClient.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 58C0A90A823422A9564655C81B134FCD /* GULSwizzler.m in Sources */ = {isa = PBXBuildFile; fileRef = F73338EC087F60071670E31C61FF3E46 /* GULSwizzler.m */; }; + 5944A240523DD38BA0E6F1298F7704D9 /* FIRAuthWebUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 57ADEE7E78BA08FD1D671853FF64AE36 /* FIRAuthWebUtils.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 599469B52AB1CE4E1B583BD80F195D0E /* FPendingPut.m in Sources */ = {isa = PBXBuildFile; fileRef = F5C03C055BA2A1C83052C9BC2AF326E2 /* FPendingPut.m */; }; + 59C1E9DE3068ED6FC3A98B5DDA432D60 /* FIRAuthUserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = A6DA917FC21F7D46345AD1B5798813F4 /* FIRAuthUserDefaults.m */; }; + 59EAF48CDE5B2776073636EF02FABB06 /* RLMSwiftSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C4E9A16C87A417E65CAB458B461017 /* RLMSwiftSupport.m */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 5A07AF42772F614C2A1945E8CA1B57ED /* firebasecore.nanopb.h in Headers */ = {isa = PBXBuildFile; fileRef = 7C9ED60FEEB27E4F77013A3307337492 /* firebasecore.nanopb.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5A353C8A03A13E3792A133931141EAC6 /* FBLPromise+Race.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 0D52282B453D45A43B76626275E73CED /* FBLPromise+Race.h */; }; + 5A78AFB9C7CC4E57E636F49470157E4A /* GDTCCTNanopbHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 33BF8AF65FFDF2C266CD299113C4B977 /* GDTCCTNanopbHelpers.m */; }; + 5A8AF4171936AD01CB9C20D850F69369 /* FirebaseAuthVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = F32515F6A7AF5C93942F5CC9477AEF66 /* FirebaseAuthVersion.m */; }; + 5AAD8DB30DDD23AEB92406F89174D7EF /* sync_config.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0FA9A704FC322DCBCFB439E7E5DA997C /* sync_config.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 5AD5EE0D42721F7E45DAEB5D7D9FA3C4 /* ExtensionHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF5F75352A2E6D56BCE252ED1AA1321 /* ExtensionHelpers.swift */; }; + 5B35E48760312B860F68C7472603D686 /* RLMObject.mm in Sources */ = {isa = PBXBuildFile; fileRef = 07D6C440C42DA5C3A063CE7C34D1303F /* RLMObject.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 5B41D96B08D4E606479415747D056CBE /* FIROptions.h in Headers */ = {isa = PBXBuildFile; fileRef = EA55512BFD1ED5E8550C4D99040DC2D6 /* FIROptions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5B96D0F75CB99CD9B63EED98E14591BA /* RLMUpdateChecker.mm in Sources */ = {isa = PBXBuildFile; fileRef = 295A4F158D05314703424E49E9949C24 /* RLMUpdateChecker.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 5B96F14532C3F92112D2CD0943D704C7 /* options.h in Headers */ = {isa = PBXBuildFile; fileRef = D50A2867A6F3524C5FE7F132004E6F3F /* options.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5BCC2F8C37FEA8348E4B87A2A2F76380 /* dbformat.h in Headers */ = {isa = PBXBuildFile; fileRef = 403BD7A51021AE9EB9B6974BC33D6FC5 /* dbformat.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 5C91FF6E29F542B2B7D54CED4D09B311 /* GDTCORTargets.h in Headers */ = {isa = PBXBuildFile; fileRef = 24447E8A1A1FD7DA7430C702460DFBA5 /* GDTCORTargets.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5CA745D6F24598B881ED85918BBE6697 /* Indicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118B6DE2F27EAA4902B2FBB1EA159C28 /* Indicator.swift */; }; + 5D12D94AD0689D694E5690B1B123690C /* FIRAuthAPNSTokenManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FC6B6785F984F1BA0AF1DDE3867F66B /* FIRAuthAPNSTokenManager.m */; }; + 5D152B277EA59399C0DFEE9B9E32C04B /* FIRCLSDataParsing.h in Headers */ = {isa = PBXBuildFile; fileRef = 1943EA82083528978789B90143A825F6 /* FIRCLSDataParsing.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 5D70B4B9CF146C0C463E7E718486C1AE /* FIndex.h in Headers */ = {isa = PBXBuildFile; fileRef = 717F6CB3DBCA25D802C9F51D5CA322DA /* FIndex.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 5DDB8E1D061F3977ED25AFE1A308594E /* FIRCLSMultipartMimeStreamEncoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B38D7E5D75C23FEADEF10B01ABF6096 /* FIRCLSMultipartMimeStreamEncoder.m */; }; + 5DEABA5DE651EE7AC516ACF42AF1AE86 /* FIRInstallationsKeychainUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = ED93F5CEB5984D2C7820803FB14E33B9 /* FIRInstallationsKeychainUtils.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 5DED512C698C3A42354FD859134ECC7D /* FIRGetAccountInfoResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 8864B72940AE9A531D5110372EF4C995 /* FIRGetAccountInfoResponse.m */; }; + 5E1482CEFE1A697AAD9BD680ECFD4802 /* FIRSecureTokenRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 1EACEFAD63F0542FB6C9630CFE587A37 /* FIRSecureTokenRequest.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 5E284513BFA66CBF2623AC3F67DF9ACD /* FIRCLSApplicationIdentifierModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 4813F514E16E244D3B40BBF1CB195098 /* FIRCLSApplicationIdentifierModel.m */; }; + 5EC665EB843D0977D69CC03F0D1F5E62 /* c.cc in Sources */ = {isa = PBXBuildFile; fileRef = EC876B68418410B69BAB194A19D284BA /* c.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 5EC78868E70B324291E267A562A96E79 /* FIRGetProjectConfigRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 04E600FE62CF56D14578FC67BB598272 /* FIRGetProjectConfigRequest.m */; }; + 5ECD300B3A6A5A4492AD8DC9BC3DFEAD /* FIRTransactionResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 8288B40378984693C731D002FB16AFC0 /* FIRTransactionResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5EFCDFE0C52B734B271CB7DEEBB66EFF /* FIRDeleteAccountRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = A6AF2ADD5C91F05DCB60BF5366727A7F /* FIRDeleteAccountRequest.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 5F32D66997FAFF2003B51D3DCA0084C7 /* FIRCLSInternalReport.h in Headers */ = {isa = PBXBuildFile; fileRef = 63CD3209EBE09936927BED09C9BAD0E7 /* FIRCLSInternalReport.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 5F3FCDFFFC373AD319D8099087EA355E /* FMerge.m in Sources */ = {isa = PBXBuildFile; fileRef = A9F13426A62CA85A9857717AABA68113 /* FMerge.m */; }; + 5F67644DC0C813771596556D5FE72469 /* FIRCLSReportUploader.m in Sources */ = {isa = PBXBuildFile; fileRef = 32C8B2FFBD9350F82EB2C05DD3F08DE1 /* FIRCLSReportUploader.m */; }; + 5F9C606B3BD1513683DCD968D368AE9B /* RLMSyncSubscription.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9A6F4A2FF77FD9938CE6ACD8F6295900 /* RLMSyncSubscription.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 5FECBE42D09EAB254BDCD0AE50C17AB8 /* FIRAuthDispatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 8593D09BBFA2FD018D9DF24500A16D38 /* FIRAuthDispatcher.m */; }; + 5FF61C0EE3635BA0CF2652F83857E694 /* FIROAuthCredential.h in Headers */ = {isa = PBXBuildFile; fileRef = 4CE85E49B4DFEF79F939FA8F668F45EB /* FIROAuthCredential.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5FFAAF8051EC966A14DCAA2549B0B208 /* FIRVerifyAssertionResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 5FF094A51019D603B58A07B6AFB78F0B /* FIRVerifyAssertionResponse.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 6076BCE61AD993AF111BE9939D334B99 /* FirebaseInstallations-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FFCA9DA3D1B73A86D7B03012553EFB1 /* FirebaseInstallations-dummy.m */; }; + 60784C1324A0D53E8A876EF08007A2F3 /* db_iter.h in Headers */ = {isa = PBXBuildFile; fileRef = 9023E9E655C90030C8031B6B2F2B677F /* db_iter.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 60AC5890112A4E3C7FF841D91AAC7AA0 /* options.cc in Sources */ = {isa = PBXBuildFile; fileRef = B2F28140F42539EF0AE6EC82CF67C0F1 /* options.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 610A300B1FA5A7BAA63BD7E2221083B1 /* FIRInstanceIDCheckinPreferences+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 3C32659A8FC6851EE6D06CCC7EAAC1D2 /* FIRInstanceIDCheckinPreferences+Internal.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 6136A64EF5388D965E6D6F0AC917E506 /* FIRFacebookAuthCredential.m in Sources */ = {isa = PBXBuildFile; fileRef = 96FD8EDB3F384930F0CCFC2E512842C5 /* FIRFacebookAuthCredential.m */; }; + 615BB4C1722E9019DB212D73FACE7780 /* RLMSyncCredentials.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 6420FC4F23214E5130FCE3DC8AE70810 /* RLMSyncCredentials.h */; }; + 617EFBCBD5DBD38653E7D1F1803905F0 /* FSnapshotUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 82E33DFCC4FCBB43EA18919761787820 /* FSnapshotUtilities.m */; }; + 6210FC41AC5325F115ABB34BB7FC4268 /* GDTCOREventDataObject.h in Headers */ = {isa = PBXBuildFile; fileRef = DABF9587A613FBB9B7C37F170B33790F /* GDTCOREventDataObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 621FE21D5739E76C42632C66EB852EDA /* GULReachabilityMessageCode.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D87F6D56345BB13EF2DB61F88CDFD0 /* GULReachabilityMessageCode.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 62453455CFB5678D90CB44A7971B3834 /* FIRInstanceIDURLQueryItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 5F5B97326C121B9F44A5F24C0E2D338D /* FIRInstanceIDURLQueryItem.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 62AD7D42C96A04828EEA3CC151AA5E38 /* RedirectHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41F3CA4CED72C78893F58B11B23859E2 /* RedirectHandler.swift */; }; + 62BCBA0AD187DA6F823C18AFE24679E4 /* FIRComponentContainerInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 24687D124A95BE37510B66E9E894EE66 /* FIRComponentContainerInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 630641270C9011D10EC4286A054B1C41 /* FIRInstanceIDAPNSInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = AC6DF125BF38E2E84E505D8CA813A0D1 /* FIRInstanceIDAPNSInfo.m */; }; + 630A4F7D2B8EEA3B4B71626F16A1B277 /* dumpfile.h in Headers */ = {isa = PBXBuildFile; fileRef = BC7F50EAF94496196A249D25CCA491B3 /* dumpfile.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 6339332668CBC70103B6DEE9FB028DB6 /* FRangeMerge.h in Headers */ = {isa = PBXBuildFile; fileRef = E8AE1089DED6CEA4D8ADF84B83D458B4 /* FRangeMerge.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 63A047238792804F506E0FBCE39F78FF /* RLMSyncUtil_Private.h in Copy . Private Headers */ = {isa = PBXBuildFile; fileRef = E7285138962EBA09D3BBBB0A162D6047 /* RLMSyncUtil_Private.h */; }; + 63B7890E2F887AB3E20D90ED2A090ADB /* FIRAuthUIDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = DD4B31E84CB4D8ACAB16B52B1A55D2F6 /* FIRAuthUIDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 63C2BA763C69F183DDB32456D7201543 /* Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36981A91C6866AF9E85E651837C03BF0 /* Filter.swift */; }; + 64744C911253C3E01461FAD7C935C8D7 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C84AB0BC56B89E39E4251B7DF1DBF4F /* Result.swift */; }; + 6481156B4BBB9D9DA83FAB5FF0287410 /* FBLPromise+Timeout.m in Sources */ = {isa = PBXBuildFile; fileRef = 639630C84534366EB2295D520602724B /* FBLPromise+Timeout.m */; }; + 648C561ABA670900DCC70034ACB901C5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64025D1D442A26509C94DDDE4BCBF577 /* Foundation.framework */; }; + 64A77A3C89F5824F2A1E855AFE15EB83 /* builder.cc in Sources */ = {isa = PBXBuildFile; fileRef = E1596E076CB1F83CFD19CA48605055E3 /* builder.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 64EE328A122407554BB19632C6114035 /* FIRInstallationsIDController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BFD56E0F5FBE4CD874A18EBA3C08777 /* FIRInstallationsIDController.m */; }; + 651432860BF0B5CDF76C3315A7D4A0A7 /* write_batch.h in Headers */ = {isa = PBXBuildFile; fileRef = 91ADC399A3333A4199D859622DC339A6 /* write_batch.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 655154DB907EF05CED9DA1664D45DC05 /* RLMRealm.mm in Sources */ = {isa = PBXBuildFile; fileRef = 54BCAF9B5A0B2D03CAC1FE8411689C95 /* RLMRealm.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 659341F1ACE71F90C2FB866F7151D646 /* GoogleDataTransport-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E7B05D506EF8041DBC2C686CB769C56 /* GoogleDataTransport-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 665FFAEB9248C6D0998E934BB75D3F75 /* RLMSyncCredentials.m in Sources */ = {isa = PBXBuildFile; fileRef = A920315F7646D262B91C4778403C67DD /* RLMSyncCredentials.m */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 6695F09A37394B2A0080FEAC06C90687 /* FIRCLSStackFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BE53798496284F7898DAF699207435F /* FIRCLSStackFrame.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 6696154B02D797B399EFF91994EC3ACE /* FIRInstallationsIIDTokenStore.h in Headers */ = {isa = PBXBuildFile; fileRef = FA4874F0784D0B918BB0F130BDE7FDBE /* FIRInstallationsIIDTokenStore.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 66BFB50E598AE2DB94E02CF609038DA0 /* FIRAuthCredential_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 277703D6A54E896B59FCD81720BC2B6C /* FIRAuthCredential_Internal.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 66FA6CB1658BA3121449E44D65AB4E49 /* FIRAuthWebUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 8711C8CB8DF3A374B7C9DCA86A93C7A2 /* FIRAuthWebUtils.m */; }; + 671CD8B463F59407A51B7C18F8DBD7E1 /* RLMSchema.h in Headers */ = {isa = PBXBuildFile; fileRef = 18A4FA38D0EC7379DB125F600EA014CA /* RLMSchema.h */; }; + 67307FEADFCE1DF75197602D93192AB3 /* FIRGetOOBConfirmationCodeResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 004C39F6E46A82C849CD59CD24686A01 /* FIRGetOOBConfirmationCodeResponse.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 675D7E47FDE4372E26F9345E57F2A50C /* FIRInstanceID.m in Sources */ = {isa = PBXBuildFile; fileRef = D3A92C87E49467B77970C7C21A6EF7D9 /* FIRInstanceID.m */; }; + 67797AC62117F9AD1487B46E758CD715 /* FTupleOnDisconnect.h in Headers */ = {isa = PBXBuildFile; fileRef = B8380284D178779DBD01394689C2E592 /* FTupleOnDisconnect.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 6789CA553001FF0FB165638C1D4E4CF8 /* merger.cc in Sources */ = {isa = PBXBuildFile; fileRef = F55DF28B45C0DC7EECDF3F579442FD58 /* merger.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 67A7DCEFFDB8053818542209031CCF66 /* FIRCLSURLSessionConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 1DF3C20AD9061C27A056CA943603808B /* FIRCLSURLSessionConfiguration.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 67AB6A2540052673DDB030ED4BB1409C /* RLMRealm.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = E05BD9106CFD3B0749792B5578D1D3B9 /* RLMRealm.h */; }; + 67C971B770407C49947DAA30E026498C /* FIRSignUpNewUserResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = A8F11A451EB6026D35C48E8E172E5EF7 /* FIRSignUpNewUserResponse.m */; }; + 67F2CEE4FD0F6B6E90818AEE5A7ED613 /* FIRDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = E5DE47C2C76346BB6B746EC071ADB3CA /* FIRDatabase.m */; }; + 6804393EA55473086BC9AC4FE08F33F5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64025D1D442A26509C94DDDE4BCBF577 /* Foundation.framework */; }; + 6808C5BEF3D1556A431CD453E63E7E38 /* NSError+RLMSync.m in Sources */ = {isa = PBXBuildFile; fileRef = 4357D6FFBA0D73ACFEF6882C53C1892B /* NSError+RLMSync.m */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 683B81361E1659216FDC3A756843E856 /* FValueEventRegistration.m in Sources */ = {isa = PBXBuildFile; fileRef = 8085970F3E66135CB37870E7D93C4126 /* FValueEventRegistration.m */; }; + 683DF482E4E5C25C03F3729230C63CE4 /* RLMCollection.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 11CB3AD7E80C556AAEC53C4F0C55E1C2 /* RLMCollection.h */; }; + 684C72B4AE51B22358D0CC199FA21F01 /* FIRUserMetadata.m in Sources */ = {isa = PBXBuildFile; fileRef = FCC777E5D845A6615BDD85C43BB2CDDE /* FIRUserMetadata.m */; }; + 686FF933FB5A33BA7A11CF986A916A06 /* Placeholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40DBA65B3D00E4A32BBE97886A532D4F /* Placeholder.swift */; }; + 688EE044B4D96D5DA2E7B700CCD3B2BF /* object_schema.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F7994B149D97C10B5E161C5B776F54EE /* object_schema.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 68C57DF30A9CA33AACD78464E1AFF476 /* RLMObjectSchema_Private.h in Copy . Private Headers */ = {isa = PBXBuildFile; fileRef = 030FB2759BA9D7978C425338F088B8B5 /* RLMObjectSchema_Private.h */; }; + 694DDEC8CDE30E341E633E6FFF8873A2 /* FIRGetAccountInfoRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 107BF9874255036D813C9C53A899ABC8 /* FIRGetAccountInfoRequest.m */; }; + 6961951C1531CD769B6D4F4B075C3CAB /* port_stdcxx.h in Headers */ = {isa = PBXBuildFile; fileRef = 500E2C11C74FFB9F16D95F36DB1892FE /* port_stdcxx.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 69CF2BCA3714260383CF82730B197E72 /* FIRAuthOperationType.h in Headers */ = {isa = PBXBuildFile; fileRef = 10CEF8E8921B937AD4E0A3EC0B204026 /* FIRAuthOperationType.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 6AB97FC38C60F471CFD764389E50AB4D /* GDTCCTUploader.h in Headers */ = {isa = PBXBuildFile; fileRef = 184038DACE9B255B375E91EB2FA49ADE /* GDTCCTUploader.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 6B52FC21C63FF610CE64BEF2AFBADA3D /* FIRCLSDownloadAndSaveSettingsOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 758A2BB7C2B851B748A469A9E8B11F8B /* FIRCLSDownloadAndSaveSettingsOperation.m */; }; + 6B9F21EBBA5B966F47635AFE9DC875CE /* FUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A750E0A59FE2F41846AA2DBD149EB42 /* FUtilities.m */; }; + 6BE3AEBCB6975AA917A267E0A35A0022 /* skiplist.h in Headers */ = {isa = PBXBuildFile; fileRef = 9E80A96424126BD73FA1DC045768406A /* skiplist.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 6C021D9AEAC1F0237AB0F69234B0E5B1 /* FIRSignInWithGameCenterResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 0007AC450179188F2E427F0B89A073E8 /* FIRSignInWithGameCenterResponse.m */; }; + 6C317F07567F7B4A089E56F02A8A1CD9 /* db_impl.h in Headers */ = {isa = PBXBuildFile; fileRef = 810334097CD688DCF361B56C4A75E9B1 /* db_impl.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 6CBD701AB21073F9E488666F7C1520D9 /* FIRCLSFCRAnalytics.m in Sources */ = {isa = PBXBuildFile; fileRef = 2FA369AC0E257B40E40EA47FBE09AF93 /* FIRCLSFCRAnalytics.m */; }; + 6D5B3184C64DDFC0DB5C83B496D58BA9 /* pb_encode.h in Headers */ = {isa = PBXBuildFile; fileRef = 2A90F3C5CA0BC7204B47164345A8A9FD /* pb_encode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 6DDD3EAC7634C6B8EBD88EB32F758913 /* RLMAccessor.h in Copy . Private Headers */ = {isa = PBXBuildFile; fileRef = 9495BA679912E002DD2FC5B70555A0D8 /* RLMAccessor.h */; }; + 6E2AE42031E6C611C2AAAD2FE1B12BAD /* Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F7A3106176C2E7089A28829FF0BE584 /* Delegate.swift */; }; + 6EA7C741EE40891E939673A78AE4AD5B /* FTupleCallbackStatus.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8CF5E3F7A1A02D334DF95AAA519EB2 /* FTupleCallbackStatus.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 6ED4FCF22261293DDA44CE9E0A1D4404 /* FImmutableSortedSet.m in Sources */ = {isa = PBXBuildFile; fileRef = 7B022C06F6C82798E3957F914136EE12 /* FImmutableSortedSet.m */; }; + 6EEE38CA0E8563AFC04F9FB6F8B79B48 /* FIRSecureTokenService.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F02C49F70C721CD4A62B36B7DD111DC /* FIRSecureTokenService.m */; }; + 6F14D38D87A8128A18AFCA43192EF494 /* FIRAuthTokenResult.m in Sources */ = {isa = PBXBuildFile; fileRef = C86D75013FDEE444275D5CE51FF59D68 /* FIRAuthTokenResult.m */; }; + 6F2CA0DF2C0510393356D83F01723AB4 /* FIRGitHubAuthCredential.h in Headers */ = {isa = PBXBuildFile; fileRef = AD28552BB9D41F42225DD045C6430D45 /* FIRGitHubAuthCredential.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 6F60CAAEFE61F18132F9668EA2E508BF /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64025D1D442A26509C94DDDE4BCBF577 /* Foundation.framework */; }; + 6FA627809D4B7555ACB813FB89E67BC3 /* FIRSignUpNewUserResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CF5AFAF7139E4C1022247757F7538D2 /* FIRSignUpNewUserResponse.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 6FC528DFE6BAEA42D1373B2658656903 /* RLMArray_Private.h in Copy . Private Headers */ = {isa = PBXBuildFile; fileRef = D8C6EFDD935E4915732937E2FCB4ECA1 /* RLMArray_Private.h */; }; + 7019C1A0EA2B7CAE1C35554D38B890EC /* GDTCORConsoleLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AC16AD6A932F1CAB1FD8F22CD2467E9 /* GDTCORConsoleLogger.m */; }; + 705BF74D108DA85C7C1376CB329BEA82 /* nanopb-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B189F0E4B0777516E298F26F55897F2 /* nanopb-dummy.m */; }; + 70B0B87B78BFDEADABD02EC1425B7C0E /* sync_manager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EC3317430C617069EE52653414A6E8F4 /* sync_manager.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 70B449AEA3817C2E349AB5807E827B9B /* FIRAuthSerialTaskQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = C05B5D9CCA2A7B1A5E03AA8CC4DE1E5F /* FIRAuthSerialTaskQueue.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 70BCE5BACB52D99796645EE3A451B783 /* Object.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96D052C928B9E11706A2CF4EA9096B59 /* Object.swift */; }; + 70C43A5A473230007F3E1E11997B0CE9 /* FTreeSortedDictionaryEnumerator.m in Sources */ = {isa = PBXBuildFile; fileRef = C23E7FFC35B7C0C36475EA8E367B1573 /* FTreeSortedDictionaryEnumerator.m */; }; + 713643837AE7E542FFD9595143E0AA39 /* ImageDrawing.swift in Sources */ = {isa = PBXBuildFile; fileRef = C64A992EF6C1A053145E1FE49B94CBA5 /* ImageDrawing.swift */; }; + 714DC87A2F1CD33A729B676F7B61603B /* FSRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DC79D7BFDA8E6A84FEDA85A96D893DA /* FSRWebSocket.m */; }; + 717C581962354033F5A82D15D6F676D5 /* WKInterfaceImage+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB59EF111D16A4E566C8249C4094FEE /* WKInterfaceImage+Kingfisher.swift */; }; + 71FC49E11979945D0516185D10F12725 /* FKeepSyncedEventRegistration.h in Headers */ = {isa = PBXBuildFile; fileRef = A7B71AD4C1D52D6BF67F016BF704E18C /* FKeepSyncedEventRegistration.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 72A2FC7AD0CD16833194F80943B40477 /* FIRAuthStoredUserManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 02DF1A33535A813CC741C2ED921D4DD6 /* FIRAuthStoredUserManager.m */; }; + 72E6D3E9999959F9349C5C86C91FD7E6 /* FListenProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 220A25CEC1AFF54BC96AF07DDC87C9D1 /* FListenProvider.m */; }; + 732CE8A85FD61C5EEC22FA9413066075 /* FIROptionsInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A4EDAEBE0ACD7112E19375E06DEF681 /* FIROptionsInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 734298FD7737180F4034C7FE1B33BC22 /* FIREmailPasswordAuthCredential.h in Headers */ = {isa = PBXBuildFile; fileRef = 5FED4C233DF99D6E196D993E5E3C7ADD /* FIREmailPasswordAuthCredential.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 73661FB7A51FD66D282DF2890DCF45F6 /* FEventRaiser.m in Sources */ = {isa = PBXBuildFile; fileRef = C58E2455DB5216CAC525E50775B466B0 /* FEventRaiser.m */; }; + 736E1003827BA8BAD28AF12533293C1C /* FIRAuthAppCredentialManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 71047F985E5491DAA031A643F38EB063 /* FIRAuthAppCredentialManager.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 73AC76F3FEC279A4AF42D33F3743EFB1 /* RLMObjectBase.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B76CD581D8B96669C1D56E482DA40E3 /* RLMObjectBase.h */; }; + 73D9FC189AC789A45BD64C21607DC5C6 /* GDTCORLifecycle.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FB1BE5072D4E905A26D9822DFBEE39F /* GDTCORLifecycle.m */; }; + 7443CC690F70BD1568A1F43D4D364B4B /* FIRVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = 01184D2BB8F9472E8D48666C0DF61D38 /* FIRVersion.m */; }; + 746CB640FA22213EEFDC668E514B1EC2 /* FIRAuthExceptionUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 45DABEADF709E0735D0285EC28D630BF /* FIRAuthExceptionUtils.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 74C6EA6ABD3894D4CB1DA0BC7C568949 /* FIRInstallationsHTTPError.h in Headers */ = {isa = PBXBuildFile; fileRef = 3824719E1E7D4113D6F9CEC8E3DC27E9 /* FIRInstallationsHTTPError.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 74C75A86C46C6DB37D54089C10C52214 /* FSyncTree.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C95068A8D4719F258EC259BA37386C9 /* FSyncTree.m */; }; + 74D1BCC4040DFA283D5FEBD9639EC490 /* version_edit.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C4CA078D9BE63A8B15F98F02748007D /* version_edit.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 74F16EAC797A560DA6EE944A8CEBEC04 /* Kingfisher.h in Headers */ = {isa = PBXBuildFile; fileRef = F6AED0E2005671F584BB2AEDF2B27BCF /* Kingfisher.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7562A36B958058693943932E86972DAD /* FIRCLSReport.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F2ED39C151AD216F2DA816B638F2A59 /* FIRCLSReport.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 76342A2C712675E9E4F9934491A47B4C /* GDTCORClock.m in Sources */ = {isa = PBXBuildFile; fileRef = 87A652F4124484CB76A1F4E60241AABE /* GDTCORClock.m */; }; + 7664FD4F59A0C00866CBAE60B0A3AD64 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64025D1D442A26509C94DDDE4BCBF577 /* Foundation.framework */; }; + 767925B99CC34D22E127120952AC715D /* Runtime.swift in Sources */ = {isa = PBXBuildFile; fileRef = B95848C5B6C50139C9D280F109737601 /* Runtime.swift */; }; + 767D2474031804B5E37F6556F2FDFE07 /* db.h in Headers */ = {isa = PBXBuildFile; fileRef = 05DA9AF2D109C39E2B3E4D0151FDEA03 /* db.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 768ACAAB9337C49D8D6603FB687B758B /* FIRCLSMachOSlice.h in Headers */ = {isa = PBXBuildFile; fileRef = 5432F4FAF7753183AD5FE130957921B2 /* FIRCLSMachOSlice.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 7692216000F15A8ECB12D661C9B53313 /* FIRLoggerLevel.h in Headers */ = {isa = PBXBuildFile; fileRef = ABCCA286340954F0DD058E63A55A397B /* FIRLoggerLevel.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 76AF55F995E98C3136D9EF5EEB5183C5 /* FIRCLSUnwind_arch.h in Headers */ = {isa = PBXBuildFile; fileRef = 1266E3968D29717728D769C77E8A3954 /* FIRCLSUnwind_arch.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 76CB8E6F7A230499A2EE885D58DA9516 /* FRepoManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 39662E3477B966D0CC14DFA1C666B797 /* FRepoManager.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 76D2D6B5E2C94652E0C9B28EDA050369 /* FIRCLSCompactUnwind.c in Sources */ = {isa = PBXBuildFile; fileRef = 2FB0B6C28B89775B2C2465949315371C /* FIRCLSCompactUnwind.c */; }; + 76D4CFA5E0BA0CC40F412FD18D775E02 /* FSnapshotHolder.m in Sources */ = {isa = PBXBuildFile; fileRef = 201546695CF3D52D60210122BDFF3E7E /* FSnapshotHolder.m */; }; + 774F1AF1B8C143D1FB94291504059A0A /* GULNetworkURLSession.m in Sources */ = {isa = PBXBuildFile; fileRef = BB4B1F93EE7A1E409F89108816B92EBF /* GULNetworkURLSession.m */; }; + 77A9A262E4D6C548BD1D5E09ACFDDA86 /* FIRErrors.h in Headers */ = {isa = PBXBuildFile; fileRef = E4443BE035B78D51F2F8EE85A7C2B16D /* FIRErrors.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 77CC78A99044D218130DA7539AB9B9A7 /* leveldb-library-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EAB78CDB261A7BD311754265ADA8C06 /* leveldb-library-dummy.m */; }; + 77CE5C7DE5029D3EF418E5DDB4B798F3 /* FTupleObjectNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 22E9080549317A0B9D3BC1664D5BF3A1 /* FTupleObjectNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 77F81ADB7EB8B8F4685B1D221C479B53 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFC56E517F177CBDCE7A445BE95B4E4C /* Security.framework */; }; + 78027788AF0FBC47704C258C159C7923 /* FIRInstanceIDTokenOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 469C011AA04EEACFF286EA5E284DE545 /* FIRInstanceIDTokenOperation.m */; }; + 782FF27221451E8222CE8A82D40031F4 /* FIRAuthSerialTaskQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CDDAB47030D2788EB6FE8C833C8F44E /* FIRAuthSerialTaskQueue.m */; }; + 78EA39899C0BABD9026C8DDB3B70A77F /* FIRCLSFileManager.h in Headers */ = {isa = PBXBuildFile; fileRef = FB06D20089CCA78A808F637BF749DF6F /* FIRCLSFileManager.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 79120D7382A3564038E796B8872271AC /* Property.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B77B047C6A76B835BE1FCB7549381AC /* Property.swift */; }; + 7912D79016C638A8B5C9221D6B637B1D /* RLMSyncPermission.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 887624AC0D176DFBC69C78021A0CF147 /* RLMSyncPermission.h */; }; + 793929EC869818B425DBE85F6447FA84 /* RLMProperty.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 8FA7E3B9378F3A764B377F83723247B7 /* RLMProperty.h */; }; + 79430F0BED1A1C23B5BE850519FDB48F /* FIRCLSDemangleOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 6CABE765FC1C335EF38A758518B37CD0 /* FIRCLSDemangleOperation.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 794DF462C0642134849F63A26680481A /* FIRInstallationsItem+RegisterInstallationAPI.m in Sources */ = {isa = PBXBuildFile; fileRef = 892F390C60B74AF78829AB20BBB9CC5D /* FIRInstallationsItem+RegisterInstallationAPI.m */; }; + 79A802BDE4CDD966154AB840A0BE97AD /* FIRCLSBinaryImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 0089CEA96A6224BCF47F57FCBAACD204 /* FIRCLSBinaryImage.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 79ACE432B9BF42AD901E5ED21221B847 /* GULSceneDelegateSwizzler.m in Sources */ = {isa = PBXBuildFile; fileRef = 51610B5A284CCD1C06EF04C565381EFA /* GULSceneDelegateSwizzler.m */; }; + 7A1EEAAAE641C0A6B99490F23EBCFC69 /* FIRSignUpNewUserRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 2BCA06E9D725F8CE559CBBD8AE84C237 /* FIRSignUpNewUserRequest.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 7A2C7D86EEC730D0860FDBCCFA634D19 /* RLMObjectBase_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 05A33B164E057EE24CF3636BBB916A28 /* RLMObjectBase_Private.h */; }; + 7AD3773DB32F9D67568EAE09E78B9A9E /* FIRCreateAuthURIRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A10D68E59112CCF8B91F5994B13A897 /* FIRCreateAuthURIRequest.m */; }; + 7B2A35912DD812339B005D28B049B31D /* FBLPromise+Testing.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 70EAFF747CBD33292EEE920054AD38F0 /* FBLPromise+Testing.h */; }; + 7B3161F2A225550C77AFEA5B4376A50B /* RLMSyncCredentials.h in Headers */ = {isa = PBXBuildFile; fileRef = 6420FC4F23214E5130FCE3DC8AE70810 /* RLMSyncCredentials.h */; }; + 7B6535A609288EAB34642AB7821FFDF8 /* FIRInstanceIDStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 163DDF9C945F142199DA4A6A64290EF3 /* FIRInstanceIDStore.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 7B690AB74CFC0D7CAC202696AF4A5BBD /* FIRCLSURLSession.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A42E18B44FC16B56095378382D15E84 /* FIRCLSURLSession.m */; }; + 7BA43849B3489E6F374CE05935022921 /* FIRCLSSymbolResolver.m in Sources */ = {isa = PBXBuildFile; fileRef = E268FACC2A09D695FA542D9C9D6B902C /* FIRCLSSymbolResolver.m */; }; + 7BA9821F063AAE8C008CFC3DCFF07142 /* FIRAuthCredential.m in Sources */ = {isa = PBXBuildFile; fileRef = FE4E65C4715C57BABDE76E223BEF6080 /* FIRAuthCredential.m */; }; + 7BF8487A9361BF2D8624D4B310E90454 /* FIRCLSProfiling.c in Sources */ = {isa = PBXBuildFile; fileRef = DC07A8DDEDC9E2EED7ADB58A9B11CAB7 /* FIRCLSProfiling.c */; }; + 7BFCB643156C73EDB574F8B7EB07E195 /* FIRCLSThreadArrayOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 77737B379095DF2CD503F701F0598089 /* FIRCLSThreadArrayOperation.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 7C1849B5942EB9756522B0B190685E1D /* FIRCLSSymbolicationOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 6233E271ED15D5C5A037C7BC902D9AA2 /* FIRCLSSymbolicationOperation.m */; }; + 7C30CD766CA9F0AADA2791BD5CB4EAB8 /* FTupleTSN.h in Headers */ = {isa = PBXBuildFile; fileRef = 6D3CD3AF92AE99DC686D0D1EED4D7456 /* FTupleTSN.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 7C7C13AF1D848ABE24E87CC5A1BC576F /* FCompoundWrite.m in Sources */ = {isa = PBXBuildFile; fileRef = 821CFA72A0DA71F03695F649274F8E78 /* FCompoundWrite.m */; }; + 7CF7FFD0A2669F0E5730BBF0B64B7E6F /* FPendingPut.h in Headers */ = {isa = PBXBuildFile; fileRef = 939962F7D0C89D0540EBE4A81F7DBB8A /* FPendingPut.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 7CF9E22AABDAF5B34430D5B22493BBDC /* FIRAuthRPCRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = DDA6A6FE1B2441688DA0C4A2AF45CAA2 /* FIRAuthRPCRequest.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 7D3519B7BD29773910B8E46594044E4E /* FirebaseInstanceID.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B32D929B50BFC710411BE8A3002A85A /* FirebaseInstanceID.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7D437253D8A80D734C1CAE3B09CDF00D /* FBLPromise+Always.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E19A3D63F5C2BBA363FC9457BC537F4 /* FBLPromise+Always.m */; }; + 7D468FA6413DFA8873F8672B6CBF2468 /* sync_permission.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 179923EADFEB236A00F5EB34E838D46B /* sync_permission.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 7D54DBB90B8519E22A0B7E277A4C0405 /* FIRTransactionResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 2251A9A4EAB8404DBDC603AD34F4C0C3 /* FIRTransactionResult.m */; }; + 7DF65A5E411826F06D1474FA83681523 /* FIRGoogleAuthCredential.m in Sources */ = {isa = PBXBuildFile; fileRef = 2651B13DEBB7991B322A3A7FB2910F41 /* FIRGoogleAuthCredential.m */; }; + 7E5C791ED2054058E01939AA1C7B827F /* FListenProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = E151626A34AF77210FB5BFC4C7D8567D /* FListenProvider.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 7EA9F5B7A97A4CEB63FCDD7CA540CBA8 /* iterator.cc in Sources */ = {isa = PBXBuildFile; fileRef = 49258A7388501C116D60F7B0F6E09199 /* iterator.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 7EABD043D0EB8CCF2E5FABF2125AF33D /* FIROAuthCredential.m in Sources */ = {isa = PBXBuildFile; fileRef = C8242370427DCF96728DAA60AC4746F2 /* FIROAuthCredential.m */; }; + 7EC9F94A0049BFFDFD0B8795F08B4A37 /* RLMProperty.mm in Sources */ = {isa = PBXBuildFile; fileRef = CB2C765DBA7E08155DA539BA14E76E0A /* RLMProperty.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 7ED9728B8FDE98CAAA531D19854AFA4A /* RLMProperty.h in Headers */ = {isa = PBXBuildFile; fileRef = 8FA7E3B9378F3A764B377F83723247B7 /* RLMProperty.h */; }; + 7F2B6F5A4496F2408F5DCE66C31549E9 /* FIRCLSNetworkOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 3AC054B5DF290D1F23F402CB55328FE6 /* FIRCLSNetworkOperation.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 7F5FB39EB957A65F199D45D50E7944F4 /* FIRCLSInternalLogging.h in Headers */ = {isa = PBXBuildFile; fileRef = 20F65E754FAE83D2F6BC09ECC325D367 /* FIRCLSInternalLogging.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 7F874EBC49645DD7B15D183549613968 /* FBLPromise+Any.h in Headers */ = {isa = PBXBuildFile; fileRef = 51377F1CD083FC389612859E450139CD /* FBLPromise+Any.h */; }; + 7FE1D9C8A8C25D367EEA42987989907F /* RLMResults.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 72DF4F0A77FF5AB0CD50A0CBFC8009A5 /* RLMResults.h */; }; + 802AC0115A5C395520D07502A77E8C26 /* FIRUser.m in Sources */ = {isa = PBXBuildFile; fileRef = 3261405AB6EB845D9659614D600F2F86 /* FIRUser.m */; }; + 8039392FCF5EE2317427478930926EDB /* snapshot.h in Headers */ = {isa = PBXBuildFile; fileRef = CC4528AA746B4ECE9B1A6B9E30F2528A /* snapshot.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 803CFFA2A6074F5752B3AF8878F4BA50 /* FIRCLSDataParsing.c in Sources */ = {isa = PBXBuildFile; fileRef = D94D8E190DDC32833CD32F9665486387 /* FIRCLSDataParsing.c */; }; + 8069CAE326CAF7B3FBAB1DAADDB0A15F /* RLMSyncUtil.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 5292DA815D495D19C1091AD168722BFA /* RLMSyncUtil.h */; }; + 80BC6B428C2BACE14A74E2D8D0FD96C7 /* GULAppDelegateSwizzler.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A540E35BC1F914EDFFB3FC921C77F85 /* GULAppDelegateSwizzler.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 80C6D096746ACC90E3B399A339A9BB3D /* FIRCreateAuthURIRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 5614F6A681D5A4E43CEE7570A1512BE2 /* FIRCreateAuthURIRequest.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 80F1CE5131DA37F9DF6516F927C78F16 /* KingfisherManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B060D6A0ACB153C3A6519940E3A2F8C /* KingfisherManager.swift */; }; + 811AB52D64C3E050CC2E53299EE69DBA /* APLevelDB.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C23EB632CADB2EE2B872554F93C995E /* APLevelDB.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 8131D2B4BE795DC28CE35BE77D080066 /* FIRIdentityToolkitRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 945E2AD872C86F66E2B79E020BBA5FF6 /* FIRIdentityToolkitRequest.m */; }; + 814503A3FEEBEE71A94C06CD679D7A70 /* FIRDiagnosticsData.h in Headers */ = {isa = PBXBuildFile; fileRef = D0373682AE1E96F2853B6452AB74C190 /* FIRDiagnosticsData.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 815288201DBCEBCE3125960F2D48236D /* GDTCORTransport.m in Sources */ = {isa = PBXBuildFile; fileRef = A92F8FEB290B291E79FE5A612537F990 /* GDTCORTransport.m */; }; + 820F023B4F1DA3B081BE889D507FEAF5 /* FIRInstanceIDBackupExcludedPlist.h in Headers */ = {isa = PBXBuildFile; fileRef = B9DA578BE4C64C070DD8C98AAA98E52F /* FIRInstanceIDBackupExcludedPlist.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 829ED10227DFAD882608CF831E54A96B /* FIRAuthInternalErrors.h in Headers */ = {isa = PBXBuildFile; fileRef = 5EB53EDD70E5582A5BDAC4B127DB2C90 /* FIRAuthInternalErrors.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 834D23E6926B2CF9235B496D9978DD73 /* FIRVerifyAssertionRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F717187275F3B68D7A159A0D95761C9 /* FIRVerifyAssertionRequest.m */; }; + 835362E41AB4806250CF2E626DB11604 /* FBLPromise+Async.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 75833FCC7F6F716545752300B81D0B93 /* FBLPromise+Async.h */; }; + 8376370153BF4F3147F7C7D5BD8B7084 /* FIRCLSInstallIdentifierModel.m in Sources */ = {isa = PBXBuildFile; fileRef = C9FD65E8C3CE0766CDC9923BD322155C /* FIRCLSInstallIdentifierModel.m */; }; + 83956E20859CDBBE7BC38ABADE0170FB /* AFError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35668BE5CB78FFDA28FD2AE333563DCA /* AFError.swift */; }; + 83F551270A9AEBBB05D87F670759B969 /* FIRComponentContainer.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F21900E3E54F11BE2C2FDCF11D66AD8 /* FIRComponentContainer.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 842D4F41D72EA4378FC7CA2514C6B848 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64025D1D442A26509C94DDDE4BCBF577 /* Foundation.framework */; }; + 84D3379972A0DB708CFE91D19B92D4E5 /* FIREmailLinkSignInRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 3064795A5FC37862FBA863669AE9B292 /* FIREmailLinkSignInRequest.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 84D41AE367A7099CE2B616FCE173A021 /* FIRGameCenterAuthProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 81E81A25B22FC7FC5627CB5A203EE004 /* FIRGameCenterAuthProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 84E1FE5DD6DE9A0C7764A29FC9F11155 /* RLMResults.h in Headers */ = {isa = PBXBuildFile; fileRef = 72DF4F0A77FF5AB0CD50A0CBFC8009A5 /* RLMResults.h */; }; + 84E8B926FACE4FA809F931BFB6CEF96D /* RLMSyncSubscription.h in Headers */ = {isa = PBXBuildFile; fileRef = C8D9FF6584BEA3EB2DDF9561F2ABC8D1 /* RLMSyncSubscription.h */; }; + 84FB7295031BEDF0073A52A61AF7129A /* Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = BABC8EB442B76A4399A427E8932DB865 /* Kingfisher.swift */; }; + 856650522D4FF67C5310C5886EC3ACD2 /* FCacheNode.h in Headers */ = {isa = PBXBuildFile; fileRef = A8956802366B6DEDE559931F69944AF6 /* FCacheNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 85A47A05438D20A7C1D8555A2A1685DA /* FIRInstallationsErrorUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = EB90112817267C3AF191E87188202873 /* FIRInstallationsErrorUtil.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 85A7198577FC80CF67D88D62AC1FBB91 /* FIRAuthUserDefaults.h in Headers */ = {isa = PBXBuildFile; fileRef = C82C90DD882D46D8FD21ADDF3E9F9D5B /* FIRAuthUserDefaults.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 85CE24D4F1B606F6C96753E7B14F0BCC /* FIRBundleUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 5553476A2B706AC8C40BDB00B6C8F0E7 /* FIRBundleUtil.m */; }; + 85D7F31EA14943CF035284A625DEDC42 /* FCancelEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = C4BA68AE47EFEC1A5A8AC627ABC9399C /* FCancelEvent.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 868CE4BA62D5DEDEB9B0B9CCE59CE8AE /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64025D1D442A26509C94DDDE4BCBF577 /* Foundation.framework */; }; + 86C28A1FA2ADAF175EB56237CFD9FB36 /* uuid.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 31779D461F51001529ECAEC1E271F0ED /* uuid.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 86CE574CADD6EAE3E385F152C74EA78C /* FormatIndicatedCacheSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BFAE93D92AAB0A65643967B49DA3AFE /* FormatIndicatedCacheSerializer.swift */; }; + 86FFEA663980E456156E8DC515E4E197 /* pb_common.h in Headers */ = {isa = PBXBuildFile; fileRef = 8194113AA335A2AA29ADC0BA26764681 /* pb_common.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 872974D912930AF2A0F3A2552A06A283 /* GULNetworkURLSession.h in Headers */ = {isa = PBXBuildFile; fileRef = 36D9D83D36A50A8C08DC14EB474F9405 /* GULNetworkURLSession.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 873FD0306FBE41D1A631FC003615264D /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ABEEBDCE85E4EB501A37D92EF28C16DB /* SystemConfiguration.framework */; }; + 8747AC80E2E4E6B96FDC0AEFF0E9F989 /* FIRInstallationsIIDTokenStore.m in Sources */ = {isa = PBXBuildFile; fileRef = E8857EFCE5796CF7471091CA1188C488 /* FIRInstallationsIIDTokenStore.m */; }; + 874AD1931253070ADC03F5043FE8C505 /* GDTFLLPrioritizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ADC6F364C16F8DBEFB7ECB280A76B7C /* GDTFLLPrioritizer.m */; }; + 87664AA7641EF3AFFA269A7E98879059 /* FIRVerifyClientResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 0920FF74941ED10798F1EF6E8D15ADB4 /* FIRVerifyClientResponse.m */; }; + 877818355EA815AA57D6802280D5ADF9 /* FIRDependency.m in Sources */ = {isa = PBXBuildFile; fileRef = FF724624F7FF1D8DCBFDB4F4778D3A64 /* FIRDependency.m */; }; + 87ABCBDB371F23895FB5BC5014D7F6F3 /* FIRAuthCredential.h in Headers */ = {isa = PBXBuildFile; fileRef = B22A5F3E02894F9C5BA92CDF2FED5C7B /* FIRAuthCredential.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 87B23A6C7190531AD4546209E6728EFA /* weak_realm_notifier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 06A412221485D514638E45C0C443BE74 /* weak_realm_notifier.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 8803A474D24AAF4D7727A0B0F18969C7 /* FStringUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = B5E3722A6289EC400D61D12B9478747F /* FStringUtilities.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 882D1048BD130932559D6C1F5E835FFC /* FIRDiagnosticsData.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3882078B4863B39FE8686F20D749E1 /* FIRDiagnosticsData.m */; }; + 883A3CEC354F2A6B1F8D3BCD3517780A /* FIRCLSAsyncOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = C0730316DE3700CB55969541D5468F24 /* FIRCLSAsyncOperation.m */; }; + 8869ED06E1AF6CC297327A90FD461E12 /* FBLPromise+Async.h in Headers */ = {isa = PBXBuildFile; fileRef = 75833FCC7F6F716545752300B81D0B93 /* FBLPromise+Async.h */; }; + 894B02C50A0C6F1E0FE73F30D4FCF06D /* GDTCCTCompressionHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = FC5D41ACD07F6EC9BF42CC87E28FD338 /* GDTCCTCompressionHelper.m */; }; + 898118C4029CB252DE5EED0F8D653505 /* FTransformedEnumerator.m in Sources */ = {isa = PBXBuildFile; fileRef = C35101BD88B8B352E5C9084099ED1C91 /* FTransformedEnumerator.m */; }; + 89A9375A72E68E2D7A0FEE9F09B9DB10 /* AuthenticationChallengeResponsable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 572DD60020066485B722152AB1526E6C /* AuthenticationChallengeResponsable.swift */; }; + 89AE4BFDC48D2DE67DD9352F8A13E10D /* FViewProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = C78A0274733FB2CB614752BB28DDFDB5 /* FViewProcessor.m */; }; + 89C03071BA28C58414C5F9A4B8614B90 /* sync_metadata.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 49BAE7C9C1895B04BCBF3BD4937DE479 /* sync_metadata.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 89DE4BDBF92376984DFFA4D6B9A789E7 /* RLMCollection_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = B49E2673664F75854392D9A0F630AC28 /* RLMCollection_Private.h */; }; + 89E4EE2B0E41C8EF8C8F5D1B2D8FAAE8 /* FIRAuthWebViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 15ADC80EB9B79579806F9F7AD89C2C94 /* FIRAuthWebViewController.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 8A21F54667CE642C7117C19A17DF34C5 /* dbformat.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4519A8667149020DB9E7BF440C7C99AE /* dbformat.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 8A50CF3AAB5BD9BF949142DEB8E5050A /* ImageModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6E362D77314A21F10D9232913CED419 /* ImageModifier.swift */; }; + 8A5BAA1F5335EA52F45BD9D05DE8424B /* two_level_iterator.cc in Sources */ = {isa = PBXBuildFile; fileRef = EB5B000A9E1BCA491B41490FC070ACD2 /* two_level_iterator.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 8A8CFA91C3682C90FA691E06935632C5 /* FIRCLSApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = E08E083442F40E8D8E3D2C7EAA72A775 /* FIRCLSApplication.m */; }; + 8ACA2F82307FBE898481DB5EBE90A520 /* FImmutableSortedDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = AA682680D3C44D8FCA0361A76F028A8F /* FImmutableSortedDictionary.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 8B4FC46A3DB2BD43F4046DAFA4FC8F8F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64025D1D442A26509C94DDDE4BCBF577 /* Foundation.framework */; }; + 8B5D0524983DB7EE6205C22D9C45D450 /* log_writer.cc in Sources */ = {isa = PBXBuildFile; fileRef = 53E222268FFB208D7AE83FD0A67262EF /* log_writer.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 8B6673BE0DB957F63D9B89E1A52210A8 /* FTupleStringNode.m in Sources */ = {isa = PBXBuildFile; fileRef = B9752B9BF07F882163DE4B93D48E07CC /* FTupleStringNode.m */; }; + 8B6B7B2DE1FB4B8D0E3E6405369E0175 /* FIRCLSAsyncOperation_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = F8F4FD2169C43EF716DF13A6017EC76E /* FIRCLSAsyncOperation_Private.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 8C08C025E8106626D57AB11ABD47A985 /* GULMutableDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = BAB03F1501D0A639B1B5A638411CBD82 /* GULMutableDictionary.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 8C5FB0D2A15DB1DBAC08EFA9E20F678E /* FBLPromise+Race.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D52282B453D45A43B76626275E73CED /* FBLPromise+Race.h */; }; + 8C941FE6752DAFE773636D80DD1B0403 /* FIRCLSUnwind.h in Headers */ = {isa = PBXBuildFile; fileRef = 03925A4568C20F619E76B1D91ACDC297 /* FIRCLSUnwind.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 8CA9CDD12D48029F0C9E7462355D567C /* block.h in Headers */ = {isa = PBXBuildFile; fileRef = 77EC52A10EB37C45E98D83D7BEC7D6DD /* block.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 8CB2369C1B0EEAF2B9E6C62BBD3FAD03 /* FIRCLSUserLogging.m in Sources */ = {isa = PBXBuildFile; fileRef = 96A2C040669D3F47F52262A721860C1F /* FIRCLSUserLogging.m */; }; + 8CDF924F391EAA98CC8BBEE05E78B861 /* FIRGameCenterAuthCredential.m in Sources */ = {isa = PBXBuildFile; fileRef = D088B0E1F77C694FC23D3A21D5BA93E9 /* FIRGameCenterAuthCredential.m */; }; + 8D042A683B5426587885AFB74C97ABD5 /* FIREmailLinkSignInResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B63C4D38C3294E634556027B2ABB2D0 /* FIREmailLinkSignInResponse.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 8D4B51F2A6E57273E12A76C160B2CC4F /* RLMObjectBase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8C3A66B5A5D384170FFA25C394542A3B /* RLMObjectBase.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 8D588CC2FA017E6A6C3571555C0D9BA4 /* FTupleUserCallback.h in Headers */ = {isa = PBXBuildFile; fileRef = C47E486A0F18A6B1AE7940348FC7D3E3 /* FTupleUserCallback.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 8D803A4306D271786D18C7C65F3EDC2E /* table_builder.cc in Sources */ = {isa = PBXBuildFile; fileRef = D48039498B3E59506A01E97FC84AADA0 /* table_builder.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 8D8D0D39219AAB655FF64828B2C3C466 /* FIRPhoneAuthProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 2E99648ED1451094B62427E18BF42988 /* FIRPhoneAuthProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8DBB2BB97A17DCEAE003D2116E709489 /* sync_session.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2119A43FC2055B4CAD9C9366757C2F8D /* sync_session.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 8E0CB520C8D4CD8BFB5D845B649DE95F /* FIRCLSSymbolResolver.h in Headers */ = {isa = PBXBuildFile; fileRef = 12F784E5C9AC8C284E63D4AF3B6F2A19 /* FIRCLSSymbolResolver.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 8E1E6EA52ED6AEDCD7B273517124FC0A /* FIRInstallationsItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 1269A06F7EF7679BC8BCC0C2B590766C /* FIRInstallationsItem.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 8E2FD15B48ABCD18FD53564E93634858 /* FIRCLSDwarfUnwindRegisters.h in Headers */ = {isa = PBXBuildFile; fileRef = 313BFED252E82ED3C22F7B4C5A62C8E3 /* FIRCLSDwarfUnwindRegisters.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 8E67945E119289DFF7141D8929414509 /* FIRCLSNetworkOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 0775707B0243939007657C36EC7F3688 /* FIRCLSNetworkOperation.m */; }; + 8EB401DC59ED357AFE37BAF90CD90B3A /* GIFAnimatedImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EACA8C55A037208CD7F7723FD488F2CD /* GIFAnimatedImage.swift */; }; + 8EBBF96A0C8E33AD826BB5E969077FEA /* FIRLibrary.h in Headers */ = {isa = PBXBuildFile; fileRef = BC0CEF1C2D64AB51593E134566463379 /* FIRLibrary.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 8EF1035B1CA556B60DED3ABD697080D2 /* FBLPromise+Do.h in Headers */ = {isa = PBXBuildFile; fileRef = 5E7434C839FCA37203C0A13316CB7E88 /* FBLPromise+Do.h */; }; + 8FA859EBEF23AE00795BDA824C71104B /* FIRInstanceIDUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = B8201CFA57F59444698C37217A4ABEA8 /* FIRInstanceIDUtilities.m */; }; + 9018A316C9EAF23C95D14A54C56A4FAF /* FIRCLSUUID.h in Headers */ = {isa = PBXBuildFile; fileRef = D0EEAEE838DEDF959DD3927587F000F9 /* FIRCLSUUID.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 90713ABF85048A803FBE61FC84C305DF /* FIRAppAssociationRegistration.h in Headers */ = {isa = PBXBuildFile; fileRef = C4F33E933CEDD03D5D071150E4688160 /* FIRAppAssociationRegistration.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 909688EFA405A9BED947BB049EF38ECA /* FIRCLSBinaryImage.m in Sources */ = {isa = PBXBuildFile; fileRef = FC3C0FB46A22497DE200AAD3C51B3F5D /* FIRCLSBinaryImage.m */; }; + 909A08C1D8A52499BA5B99546F8CEF13 /* FIRInstanceIDAPNSInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = BEAF03C0C4D7B2341CC75989723A9911 /* FIRInstanceIDAPNSInfo.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 910DAAF63F747D7F5DC9F9D77C4D3D94 /* FIRDeleteAccountRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = AD877C2DBA0A621AD258A010A5E54B2F /* FIRDeleteAccountRequest.m */; }; + 911C5E96ADAF0DCA6C1FAA27189FF0E2 /* FIRInstanceIDCheckinPreferences.m in Sources */ = {isa = PBXBuildFile; fileRef = F07D4A9232C809E948120E7102776882 /* FIRInstanceIDCheckinPreferences.m */; }; + 914D5A3BFCA9E5225BC71B39042EC967 /* FIRDatabaseReference.h in Headers */ = {isa = PBXBuildFile; fileRef = F7E041DEA67993EB831E297B08E34740 /* FIRDatabaseReference.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 92142DB3CEF7BA2BB8942694C45D3065 /* RLMRealmConfiguration+Sync.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = BBDE0947F81FE23D7DC2157383181911 /* RLMRealmConfiguration+Sync.h */; }; + 9255B03E885F7CB7D52D684EE7DA5342 /* FIRCLSLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 454E843E89166D83EA808A0C6441D07D /* FIRCLSLogger.m */; }; + 92B30710112104E0AA19A4B4AD94320C /* NSData+FIRBase64.h in Headers */ = {isa = PBXBuildFile; fileRef = 73646F0031DD6F6B9E6CD16A204ED450 /* NSData+FIRBase64.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 931BBB8230A25161D5C37528A8F9FECF /* SessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AD0CE2461D5D5215E0EC8F0E909A922 /* SessionManager.swift */; }; + 93384ACDB8289E3459F686C2C7DD27A9 /* dumpfile.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3B2C03F3C9EBC5D74F0DC6F8340D952F /* dumpfile.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 933FDA5970AA525D6CB92BFEBA2BAB4A /* Timeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E6A13DEC67A77C4FE36FE85C0086A25 /* Timeline.swift */; }; + 93A6A9EBD95BEB2036B9649889CB2376 /* FIRCLSCompoundOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = E6DD941D98E1FE39ECB7E30424EB0AF6 /* FIRCLSCompoundOperation.m */; }; + 94068437A6A57F0C86AF5EB7A62A81F9 /* FIRAdditionalUserInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E942E4D8362BD9FF9206F0F0224F63B /* FIRAdditionalUserInfo.m */; }; + 94146FAB7109DC1FABB60023ADA00173 /* FIRAuthDataResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 1231D552B10EFFFBC08D63ABA4FC43FF /* FIRAuthDataResult.m */; }; + 941496067C95BDC2F69580981E8278AC /* FIRHeartbeatInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 9735E1BB66BA1A1805C81F7022B52CEE /* FIRHeartbeatInfo.m */; }; + 94200C7828DCC86231B65AFD64536D0E /* FBLPromise+Always.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = F1A72EEFE9C788BE1B755C160E95C8F2 /* FBLPromise+Always.h */; }; + 9455D8654426BC7A04358EC97E10D9E9 /* FIRSecureTokenResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 0708BFDF3D8644771BD7287AB060DD32 /* FIRSecureTokenResponse.m */; }; + 948003424F77D78D8311A9424B25C5DC /* FIRCLSException.h in Headers */ = {isa = PBXBuildFile; fileRef = 4EB7F0FA43F7178842857FB5902D9EF6 /* FIRCLSException.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 9496FE2515AA1457B6BD957F4D686F8E /* FOverwrite.m in Sources */ = {isa = PBXBuildFile; fileRef = 60B44DC898C5948A7EA0035AC015B100 /* FOverwrite.m */; }; + 94F67C435C6D518BE0E21321878F758A /* FIRUserInfoImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = 2AAA54DE66F1A6513DBC8807276C372A /* FIRUserInfoImpl.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 953F2798667B586AFEF26C1BD2B05FEE /* FLLRBEmptyNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 8AE09E001BB86416A1D26AB609A0AEA3 /* FLLRBEmptyNode.m */; }; + 95453B89E56A3605CE44BCA8F4AA9C4F /* FIRInstanceIDKeychain.h in Headers */ = {isa = PBXBuildFile; fileRef = 92CE3C73F6820C05E0B7AD90181B0666 /* FIRInstanceIDKeychain.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 95548E94080B3374B45B92FDFB463F27 /* FBLPromise+Delay.m in Sources */ = {isa = PBXBuildFile; fileRef = 81BD9FFB63D5FA9EF33EFC33EA7597C4 /* FBLPromise+Delay.m */; }; + 95A28903392D43C9D339D4AD55728D72 /* FIRComponentType.h in Headers */ = {isa = PBXBuildFile; fileRef = 78FF096304C2AD7526682458C7C2731C /* FIRComponentType.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 95FC3F4277B5A49D0B0BC6ED0FDEC222 /* FPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 250253DBF9928DBD2E9DC314A05278B6 /* FPath.m */; }; + 96035782A092B0F6C2306836CAC7A093 /* memtable.h in Headers */ = {isa = PBXBuildFile; fileRef = 23C8057C3DC87D78E48F5584770EFD16 /* memtable.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 9607D0F6FF5A6E0856F3842C11F67987 /* block_builder.h in Headers */ = {isa = PBXBuildFile; fileRef = EA7F566FC329D8F1AEBA2463FA2B876F /* block_builder.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 960DC0390A32D0C6497616F30519A3A8 /* FIRCLSLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 7C0E929DD992CE13605482A3504B4A32 /* FIRCLSLogger.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 9638D2EDC4A07E858D8266D2FF8F5AED /* FIRCLSURLSessionConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = F99C1F5CF233E9ACD488CF17BF244A10 /* FIRCLSURLSessionConfiguration.m */; }; + 96495C1142B6C7D4CF0A6DE0EC1649D3 /* FCompleteChildSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 64E603DF7C2E7F872F1E29A9869AD2E3 /* FCompleteChildSource.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 96514FE01DB7DD791CE0F9E257046C82 /* FTupleObjects.h in Headers */ = {isa = PBXBuildFile; fileRef = 7EC8360EB2198F697C257E552137581A /* FTupleObjects.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 967A9F26556882DDA842A8313E8FAA1E /* FIRDatabaseReference_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 6930AFD3D022185F6C9FE8A6AC2D6E1C /* FIRDatabaseReference_Private.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 96AF0A80D25BBA2EE44E9EAF11B6997B /* FConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 437157BAE39DA22356AB037E9CBFD59D /* FConstants.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 970E7215DF6BFD7D77304406D8F10AF0 /* FValueIndex.m in Sources */ = {isa = PBXBuildFile; fileRef = 462F3DF1EE8A1A326EFB498FFE50BA2E /* FValueIndex.m */; }; + 974071500397CA88E104EB9828047CF9 /* RLMConstants.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 9021419F0DD8F28569985527C8E622B5 /* RLMConstants.h */; }; + 975429A6D0E9F5E96FCBE23D5FCECAF7 /* FIRCLSSerializeSymbolicatedFramesOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = EED5147D9610B2B66EC2AAD7285E96B5 /* FIRCLSSerializeSymbolicatedFramesOperation.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 976461D74F0D8B518CD0AC18FE16E2FE /* FSyncPoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 9024896D2537C44DEC820307835C4C89 /* FSyncPoint.m */; }; + 977C462E588D9085193D50156CEF2D5A /* Resource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A411B5DA7E914F2C40F92038EA7A436 /* Resource.swift */; }; + 97CCBA353255E11BADCA18806B27FD90 /* FParsedUrl.h in Headers */ = {isa = PBXBuildFile; fileRef = 6253723D977FDDB11DE98CCA90A7FA7F /* FParsedUrl.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 97D7D4ECF0164108DD2F04D8727393C7 /* FTree.m in Sources */ = {isa = PBXBuildFile; fileRef = 998AA9BB7AA0BEAEAF95FE9BB2E20DDE /* FTree.m */; }; + 97F26585AD4B634C86136CEF0E5B1EF2 /* RLMSchema.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5D72F806127CD4AB5276FB13A330E7F0 /* RLMSchema.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 9807C661B5E65E69632BB99A6E069013 /* GULUserDefaults.h in Headers */ = {isa = PBXBuildFile; fileRef = 1D2CEE38973DEDB4BC7B4AB471D4E205 /* GULUserDefaults.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 9820B3A65D65CCE7A683E5126C5100A8 /* SizeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41FB9FC4D66CF7E99640E00C16D6C174 /* SizeExtensions.swift */; }; + 9863271EEAB94EEC9EF6CE83FFC36C8E /* FIRAuthSettings.h in Headers */ = {isa = PBXBuildFile; fileRef = E9FAC3EC9C794C3593C728CF3E4EDAC1 /* FIRAuthSettings.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9898062DAF98F529BACA9EEA8EB04EF0 /* FChildrenNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D2B159BBE380986ACF63E8FCA676F50 /* FChildrenNode.m */; }; + 98A929C8E9012AB167672714FFD2113C /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AB5C720F820C5225F158C693B9DA549 /* Request.swift */; }; + 98D39B35A84835ADD3A85F9F885A38D6 /* FIRAuthRequestConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E45A7C637B9EF8C87CF12C4387A2E17 /* FIRAuthRequestConfiguration.m */; }; + 99198F67ECBC6E0E02F30B593703656E /* FIRCLSURLSessionDataTask.m in Sources */ = {isa = PBXBuildFile; fileRef = DAF1673F2959F3DA04D64CD47E7140F2 /* FIRCLSURLSessionDataTask.m */; }; + 991D278E60FF49563DA3DFAE0CB6AC64 /* GULSceneDelegateSwizzler_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D8E5D0FF01FC3B2B7A13D625B836394 /* GULSceneDelegateSwizzler_Private.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 99B32893D710CB8D9401C3AB9FF82504 /* GDTCOREventTransformer.h in Headers */ = {isa = PBXBuildFile; fileRef = 56AE672BAC2AB1B75187E390E306EFF9 /* GDTCOREventTransformer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9A074410CBBCE8C6BE663C80F235DC89 /* ImageFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = DABD5D4024C849A5EE75514C6854A95B /* ImageFormat.swift */; }; + 9A0AAED2AB0733F90A24FFBEA9E6D207 /* FIRCLSReport_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = E22ABAF13C46FCAA209FA07662CABF93 /* FIRCLSReport_Private.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 9A46766FC7639FEC0E4334173B8B08C4 /* FIRApp.h in Headers */ = {isa = PBXBuildFile; fileRef = AE3CCA0A95C2DB398DA83E48466C7954 /* FIRApp.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9A55B8614F7B2857DC4CF7116D99CD0A /* FIRInstanceIDDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 2563B7FA0AFA76EF66CE71EF56FCCD68 /* FIRInstanceIDDefines.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 9A931745F67444443E64C7C0DA782C9F /* FIRCLSReportManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 051905C1D8206444341EEDFEDE3E5B02 /* FIRCLSReportManager.m */; }; + 9AA6F9C191B6A1D5BD7A1A1965E6663B /* FIRLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = C57F75D4E0E0E4D75CBAB5644F1CF5ED /* FIRLogger.m */; }; + 9AF62C2B24B3B17186602A0A057B4819 /* FIRSecureTokenRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 524B6D68BA7AD2123C9FADC2891CFA91 /* FIRSecureTokenRequest.m */; }; + 9B1AA11B1A1BB17258314DFE69D3BAC1 /* FIRInstanceIDBackupExcludedPlist.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AB2E2F6F5F01C6A4F17BFCBA264DAA9 /* FIRInstanceIDBackupExcludedPlist.m */; }; + 9B326941BA608661D368F757E22804AA /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64025D1D442A26509C94DDDE4BCBF577 /* Foundation.framework */; }; + 9B5D2DEB300AF8CE182AB0A1360DFC49 /* FSyncTree.h in Headers */ = {isa = PBXBuildFile; fileRef = 890533AECEC39179FE6E6B2169233974 /* FSyncTree.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 9B9F7B42DC215BD6004E43E690CB18B7 /* GDTCORDataFuture.m in Sources */ = {isa = PBXBuildFile; fileRef = A7317777EA2818DD08E00424D0C9AA43 /* GDTCORDataFuture.m */; }; + 9BAED39893E3A38E86EC0CB7F1B4664D /* RealmSwift-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 18C11A47DA70BE69B373BF2ED8069CC8 /* RealmSwift-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9BB219421A0ABB05BE8E916BC3438A91 /* FDataEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = BED0BA47CB4A60F6FA1559076AE57EC5 /* FDataEvent.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 9BC5056A045FCBCDCAD6A431A65BC6A7 /* testharness.h in Headers */ = {isa = PBXBuildFile; fileRef = CAB3A4110D3D69279A6A92082D511C2A /* testharness.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 9C0DED0B53B311D880B84EAD42A5C367 /* RLMArray.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 1D0510411AB5C0B1659FA25995973E70 /* RLMArray.h */; }; + 9C5DFFE29827A2D989C2DE6910ACA270 /* RLMRealm+Sync.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 4D928597FB30E7EF35AAC58AE3A428C9 /* RLMRealm+Sync.h */; }; + 9CAE1A659435D39578BC67CAF04E6B33 /* FIRGetOOBConfirmationCodeResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = D64E47D2DC7764FF21C3B04F333D7607 /* FIRGetOOBConfirmationCodeResponse.m */; }; + 9D23B927D2A2FE8FA48C3791E493E08E /* GDTCORAssert.h in Headers */ = {isa = PBXBuildFile; fileRef = 714E27F8EAD4CFB19AC1A66F7AC98A5E /* GDTCORAssert.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D283658709A170CDA416FFDB5596637 /* FIRDatabase.h in Headers */ = {isa = PBXBuildFile; fileRef = C4EB340CC90499FE586FFAD55EA2C4C2 /* FIRDatabase.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D50881D7C3F48E650D48C42F642D025 /* GTMSessionFetcher-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCB3DAD6C9EDF1C934977898099ED3A /* GTMSessionFetcher-dummy.m */; }; + 9D89150563C25286D022E024EAAE0224 /* RLMSyncManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = FB69992458A89E18A6BF4FB596E6E397 /* RLMSyncManager.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + 9D9656D879FBF3A3046B8FB782190BFC /* GULSecureCoding.h in Headers */ = {isa = PBXBuildFile; fileRef = 263E917FB64CD4F1F2F9FF7E460337EE /* GULSecureCoding.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 9DA2D6CB3F354CD7BD716893BF59D434 /* Pods-GeekbrainsUI-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = FAFAA71EB419B9C1A78847DD7C78B876 /* Pods-GeekbrainsUI-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9DABF8DC7C2B45BDBB28DAC5F9304D27 /* FChildChangeAccumulator.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D691679E072ED98D8E9280883868E4E /* FChildChangeAccumulator.m */; }; + 9DF37CEF36132C74D8524966D7D856B7 /* FIRInstanceIDTokenInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 710AF0CF26954951B96F5B4D3FB61552 /* FIRInstanceIDTokenInfo.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 9DF37E2384BBDB8D801BEDAF8A735642 /* GoogleDataTransportCCTSupport-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 0DD29CAF1F48BADB4D360100F91E3CAD /* GoogleDataTransportCCTSupport-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9DF541C86B5E71B2135BB0A498B14FA5 /* FIRCLSURLSessionDownloadTask.h in Headers */ = {isa = PBXBuildFile; fileRef = EDF84E0100FAE3C3031F23D19DC20C35 /* FIRCLSURLSessionDownloadTask.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 9E70496AB08A15ED951EA85593776B00 /* FLLRBEmptyNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 6DE985270C413E3DC01DBB9E906093D2 /* FLLRBEmptyNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 9EC32265D0C5AD456B853E60C1FB710D /* FIRMutableData.m in Sources */ = {isa = PBXBuildFile; fileRef = F03BCB4DD187BEE1AB4313919D286422 /* FIRMutableData.m */; }; + 9EF0AE42CCA202AD453F8F2DC19B23C0 /* FIRCLSDataCollectionToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 303B212D272E5495BC2D7E46FA8627EC /* FIRCLSDataCollectionToken.m */; }; + 9F3E07DB01C10E27306BE87A68C8F953 /* FIRInstanceID_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 12A1667F60BAFB5EFB620E87EF4CA3B1 /* FIRInstanceID_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 9F52D72B9ACF0B32AC5F4F08C91A7584 /* FIRSetAccountInfoRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = F61594A2D9EB829D8F70CE39F7EA8F1B /* FIRSetAccountInfoRequest.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 9FD4240AD8305E3225CE1BA8602D0F41 /* FWebSocketConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 51EED64CBA9532DACC7881BBFE117AE7 /* FWebSocketConnection.m */; }; + A01F697294096A02C4C3EC24ECB1FAEC /* FBLPromise+Recover.h in Headers */ = {isa = PBXBuildFile; fileRef = 4C6616311432B3C00DD556941EC5A1CA /* FBLPromise+Recover.h */; }; + A0E4097BD898F34C0F9443D658EFF513 /* FIRGitHubAuthCredential.m in Sources */ = {isa = PBXBuildFile; fileRef = 38739C86586A2293AE424FDEB62F9E2D /* FIRGitHubAuthCredential.m */; }; + A0FBEF8CB22A6E168C80E221D0DF786E /* FBLPromise+Testing.h in Headers */ = {isa = PBXBuildFile; fileRef = 70EAFF747CBD33292EEE920054AD38F0 /* FBLPromise+Testing.h */; }; + A13B9060873282FF5A1D8C5868A5AE40 /* FTrackedQueryManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DC9CB71FE83B367C573761ED0E254D12 /* FTrackedQueryManager.h */; settings = {ATTRIBUTES = (Project, ); }; }; + A14FC4211C2611BCE6035EE54583B7CB /* PromisesObjC-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = CDE235CB054F3FE398E19E47A6CD214E /* PromisesObjC-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A167E0848DA6BE9E0A355B46D2BD99FA /* FBLPromisePrivate.h in Copy . Private Headers */ = {isa = PBXBuildFile; fileRef = 30B0C1A58B0B3ABC8A2A09E6B26FB29A /* FBLPromisePrivate.h */; }; + A19C9C58F87FAF5DB267AE064023DF30 /* Kingfisher-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D7BF465F8E021B4E73DECFEAD4EDC95 /* Kingfisher-dummy.m */; }; + A1A129C4F8AE9ED176B267739322F1F2 /* FIRAuthURLPresenter.m in Sources */ = {isa = PBXBuildFile; fileRef = C93A659F3C396F363AA9EAC8E6D63480 /* FIRAuthURLPresenter.m */; }; + A21E886D390FE0F02976716825A43860 /* FClock.h in Headers */ = {isa = PBXBuildFile; fileRef = D4BD915552AA4F71792B9C08A29C66C7 /* FClock.h */; settings = {ATTRIBUTES = (Project, ); }; }; + A22F7A299FED7582527E93101293142C /* FKeyIndex.m in Sources */ = {isa = PBXBuildFile; fileRef = E507F9B4360841CA0E8C7A0F2039A90C /* FKeyIndex.m */; }; + A238E096D398756700024A69B3FF87E5 /* FIRCLSHost.h in Headers */ = {isa = PBXBuildFile; fileRef = 82269596064B52D6E7E7F1BFFC8FEC78 /* FIRCLSHost.h */; settings = {ATTRIBUTES = (Project, ); }; }; + A291FD3F0355831975D0B4A59E73C598 /* FLLRBNode.h in Headers */ = {isa = PBXBuildFile; fileRef = C40CC2964E8729E9F7F77F7626636D13 /* FLLRBNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + A2CAC7C46044633BA5B3A258FE1B4311 /* GULOriginalIMPConvenienceMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = C3DF5ABA2B431B97F394AF435C9865A8 /* GULOriginalIMPConvenienceMacros.h */; settings = {ATTRIBUTES = (Private, ); }; }; + A2ED5D0609EE3AC21E31B7731951F5EE /* FTransformedEnumerator.h in Headers */ = {isa = PBXBuildFile; fileRef = 9FCC44446EB1279EA6225E7F3C5D4B38 /* FTransformedEnumerator.h */; settings = {ATTRIBUTES = (Project, ); }; }; + A2F591F45DBBFB19EFBBC5CCF8BA2793 /* RLMObservation.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5F3B738EDC7663C20ECDA7758B5F3CC1 /* RLMObservation.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + A3322FBABBDB335E5CF7AAF320C2E304 /* GTMSessionFetcherLogging.h in Headers */ = {isa = PBXBuildFile; fileRef = ED3AA8DB0FFB3C66316742F2C335F0A0 /* GTMSessionFetcherLogging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A37C8A256CF2D62172B63CA986743735 /* FRangedFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = A6395739D39ED1B02D46AFEC682B3639 /* FRangedFilter.m */; }; + A385787A0345125B9EF535AB655D6A22 /* GDTCORReachability_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 4ACA40A65A478DF9FA2F1A6435A82040 /* GDTCORReachability_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + A3BE570EEACBCD6B8624A9633E458ABF /* FIRInstallationsStoredItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 5651E58B832E9102932FB169B9827F12 /* FIRInstallationsStoredItem.m */; }; + A3C8CA56DDB997836D307ADBA8A7F26F /* FIRGetOOBConfirmationCodeRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 6D40ADFA16F2F4D5A0E378EEC660466C /* FIRGetOOBConfirmationCodeRequest.h */; settings = {ATTRIBUTES = (Project, ); }; }; + A3F37516CDD7B7C6E9E67F7937775CB8 /* RLMThreadSafeReference.h in Headers */ = {isa = PBXBuildFile; fileRef = 8F76B1EE6D2328DF902BCFC3CEFFDFF9 /* RLMThreadSafeReference.h */; }; + A3FCD4884D44DFC98D96789BAE66444C /* FTupleFirebase.h in Headers */ = {isa = PBXBuildFile; fileRef = 14AF50D2F7BD3AC68ED686CAB08FBCF4 /* FTupleFirebase.h */; settings = {ATTRIBUTES = (Project, ); }; }; + A4269A5F9CE2E4C5FE12474685FE3B19 /* PromisesObjC-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 7C5B268E1B3ED259F849D41750BBC9B0 /* PromisesObjC-dummy.m */; }; + A45B07EB5CC9B04E0BEE83B3F27C1D4D /* windows_logger.h in Headers */ = {isa = PBXBuildFile; fileRef = D70A8461139B1C76F2ED9387DEE66DFA /* windows_logger.h */; settings = {ATTRIBUTES = (Project, ); }; }; + A49A7E4B375094243610F437ACA5B30D /* FTupleCallbackStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B20983BE4C3A3AF1F2CC902B2FAD738 /* FTupleCallbackStatus.m */; }; + A4C007C8EBD401459E7BB06952BD25D4 /* FWriteRecord.m in Sources */ = {isa = PBXBuildFile; fileRef = 64DE1BE55CA40E16D2F37E836704D8C4 /* FWriteRecord.m */; }; + A4C0B68390755E8C48C99AF53F4D9984 /* FIRAuthAPNSTokenType.h in Headers */ = {isa = PBXBuildFile; fileRef = 74AAAB1D03D40969A48E2FC429D8855E /* FIRAuthAPNSTokenType.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A4DB7F899CD3EB9B94D334EA74349171 /* FIRCLSApplication.h in Headers */ = {isa = PBXBuildFile; fileRef = 2C1F67FAB1701104A2037545DAF700E1 /* FIRCLSApplication.h */; settings = {ATTRIBUTES = (Project, ); }; }; + A524B1A8F063D71F7854055E86AEA72F /* RLMListBase.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = AEFBD0F180EED7215C8DA83E8590612E /* RLMListBase.h */; }; + A54A7362B9B6588F39EC8EE04A298DA2 /* FIRCLSDataCollectionArbiter.m in Sources */ = {isa = PBXBuildFile; fileRef = D16E167B93D721F81CD63CB4768D0DD3 /* FIRCLSDataCollectionArbiter.m */; }; + A5E675465CFF1FBA0DD01AA4E2BA5F77 /* GULNetworkMessageCode.h in Headers */ = {isa = PBXBuildFile; fileRef = 9565729856E35DE36A92958374DF72C2 /* GULNetworkMessageCode.h */; settings = {ATTRIBUTES = (Private, ); }; }; + A6129D460285F6DC87E1F32287FE30F0 /* FParsedUrl.m in Sources */ = {isa = PBXBuildFile; fileRef = EF5C63E6374A65708B5F98DE5057F562 /* FParsedUrl.m */; }; + A6182BE2556FBF0EDE3418D2AF9EE4DF /* FServerValues.h in Headers */ = {isa = PBXBuildFile; fileRef = 52EF11B4BCFC895E51708A2429C5961D /* FServerValues.h */; settings = {ATTRIBUTES = (Project, ); }; }; + A6330243D6FFC19FA6CCBA406C85969C /* FIRAuthDefaultUIDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4580E4AEE938A2AD447D44D7A2136CD4 /* FIRAuthDefaultUIDelegate.m */; }; + A6645ABF85B1F4050B50D4DD62851C62 /* RLMObjectStore.h in Copy . Private Headers */ = {isa = PBXBuildFile; fileRef = D0F7E8BBE321F64C9CCE4D3BF4DBC66F /* RLMObjectStore.h */; }; + A6918169718169EBAFE493362576B4B0 /* FIRResetPasswordResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 25870563374C629A654D4D9F67C26E56 /* FIRResetPasswordResponse.h */; settings = {ATTRIBUTES = (Project, ); }; }; + A6FEF91B7336C02C1EB319E148369FC7 /* crc32c.cc in Sources */ = {isa = PBXBuildFile; fileRef = CB0A9640A0C659E27B59C16EF296B350 /* crc32c.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + A7052F1E043995467877D756C5E3B11F /* FIRCLSFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 8AEBF9A44F2485C9B53763766349031A /* FIRCLSFile.m */; }; + A7201850468A114846F7404F74A73155 /* FConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = BA5D4D31F808F2F3EB3D5D55B36F1070 /* FConnection.h */; settings = {ATTRIBUTES = (Project, ); }; }; + A77D7E76C94952348F06FECAC8EF0B14 /* FIRSetAccountInfoRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 826604233AB70EBDA731C537C73AC4CE /* FIRSetAccountInfoRequest.m */; }; + A7A8426CA84F3CCBC9FE1DD46AB9FF2F /* FOverwrite.h in Headers */ = {isa = PBXBuildFile; fileRef = A20C88EE3EEA866A57E002E2CE2CB90C /* FOverwrite.h */; settings = {ATTRIBUTES = (Project, ); }; }; + A838B922272BA807C59A0444234CB076 /* FIRDatabaseQuery.h in Headers */ = {isa = PBXBuildFile; fileRef = D45AD6314AAB709350FCD1F48E30D8B4 /* FIRDatabaseQuery.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A84DDB4FED63439996ADE951D64C5B3E /* FIRAuthProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 2633BB2A259FF573B8C32BA48394F9EA /* FIRAuthProvider.m */; }; + A84E3E054DE421ACF71E2B61ED22D2DA /* FIRAuthDataResult.h in Headers */ = {isa = PBXBuildFile; fileRef = D060DA57254EB3D36CE5BAE298D8ED96 /* FIRAuthDataResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A857D6202E2290264F544DDD8627F432 /* FIRSignInWithGameCenterRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = B0869227B77B723D090AA044C73DC03D /* FIRSignInWithGameCenterRequest.m */; }; + A86AE83C5975FDED91EFE6D15D76CD5F /* FIRCLSDwarfUnwind.c in Sources */ = {isa = PBXBuildFile; fileRef = 7638558D1BBBB6500FC2DB44CEEC0BE0 /* FIRCLSDwarfUnwind.c */; }; + A8779CABCF0CDABF0912B36B7F218AF1 /* FIRInstanceIDAuthKeyChain.h in Headers */ = {isa = PBXBuildFile; fileRef = 8F1BF833B6F67406AF5AEE71A69B09A7 /* FIRInstanceIDAuthKeyChain.h */; settings = {ATTRIBUTES = (Project, ); }; }; + A89184ED30C5CCFA36582907765E8C30 /* FTypedefs_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = B137C04F7C027EC1981824188E584EA9 /* FTypedefs_Private.h */; settings = {ATTRIBUTES = (Project, ); }; }; + A8A39094945930EF940D31A4B1D0883B /* FIRInstanceIDTokenFetchOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 807D7C288E07C2ADE943E1B2DB35B461 /* FIRInstanceIDTokenFetchOperation.h */; settings = {ATTRIBUTES = (Project, ); }; }; + A8C13C35155FB4D9B6C630D6F3E20520 /* FIRAuthAppCredential.h in Headers */ = {isa = PBXBuildFile; fileRef = C6ABC1E67AA88DD64EDA72189FFA1268 /* FIRAuthAppCredential.h */; settings = {ATTRIBUTES = (Project, ); }; }; + A8D337E9933B9BF0529DD065D188889F /* FLLRBValueNode.m in Sources */ = {isa = PBXBuildFile; fileRef = DAF1EF806E8081A6E6FC9DFDF24CB30D /* FLLRBValueNode.m */; }; + A8E82498433F11FB31D93E4F8DE0AE6F /* RLMObjectStore.mm in Sources */ = {isa = PBXBuildFile; fileRef = 91F88A0114DB504C3CB311BD28930021 /* RLMObjectStore.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + A8F8D6EF5E9C9B620F578A3B89E4D4AE /* FChildEventRegistration.m in Sources */ = {isa = PBXBuildFile; fileRef = C0D2FC3564EF6F5762A6F5D48E4136CA /* FChildEventRegistration.m */; }; + A94541B39798ECD528D6A10852CEE444 /* GDTCOREvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 8639270B60188F3F1AF6A037E6730148 /* GDTCOREvent.m */; }; + A97A33A98BC7B9331F3BF49BEC9A28B3 /* FIRVerifyCustomTokenResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = F363C40B536805228088BCBFE0735F66 /* FIRVerifyCustomTokenResponse.h */; settings = {ATTRIBUTES = (Project, ); }; }; + A9CA323F9BF2ED7EB3D185DAAE6DA350 /* FBLPromise+Any.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BFBF1D2DBD09D1AB8932EDA355D05 /* FBLPromise+Any.m */; }; + A9F0A5B1941617A83DD5A5CE55E920A0 /* FIRCLSOnboardingOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 3EBCEDAB2FDE3140557CB10ACEB94398 /* FIRCLSOnboardingOperation.h */; settings = {ATTRIBUTES = (Project, ); }; }; + A9F434D093BEFF605C24C2D0B71B4767 /* RLMRealm_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 675DAF0FD379B5DE1611353667173D80 /* RLMRealm_Private.h */; }; + AA27FF3EAAF39A3DC4EBF936EB325755 /* FIREmailLinkSignInResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 3ABD1B08F068B8BFA864424DE777BBAC /* FIREmailLinkSignInResponse.m */; }; + AA38386A8BE18F2678EF806839CCCC9E /* fbase64.h in Headers */ = {isa = PBXBuildFile; fileRef = 046EB39BD4F132031A238FB3AC461A85 /* fbase64.h */; settings = {ATTRIBUTES = (Project, ); }; }; + AA8CB96B506F6AF065AB1F8C9F9495CB /* sync_user.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F695AC3ADBB98EC47D8E223652761F16 /* sync_user.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + AABEE951C89957EB5E1FCD3D25CD45B4 /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4469C2241975FA8DD82B8AB70834A77 /* Schema.swift */; }; + AACEBC8596A841C7B61CD7E1A9AE2DE4 /* collection_notifier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BB5AA820C529D5333970A049ACDA2A36 /* collection_notifier.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + AADBCD1B21CD4A8302788D60CBA914EB /* RLMSyncManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 82EE9EA75ED100D0A1D4A04F26EF09A7 /* RLMSyncManager.h */; }; + AAFCABDE8D0D395FC602D2F61A4B45AE /* FView.m in Sources */ = {isa = PBXBuildFile; fileRef = B7AF0BB63342B990F1F06546893BE592 /* FView.m */; }; + AB202C54DCAE5F9C50CDEDA01F0F476A /* FIRInstanceIDAuthService.m in Sources */ = {isa = PBXBuildFile; fileRef = DB56C958965C0D1A15235E6596186AA8 /* FIRInstanceIDAuthService.m */; }; + AB378EF4F418F0A00D68FA7B37A60491 /* RLMOptionalBase.h in Headers */ = {isa = PBXBuildFile; fileRef = C44C40D4B3AF4238A0A8521E9F1C7F77 /* RLMOptionalBase.h */; }; + AB51B81AC32B9F0D44E93AD97A2F3C25 /* coding.h in Headers */ = {isa = PBXBuildFile; fileRef = 46889AB3C5F944AB30292A698C5E2ADB /* coding.h */; settings = {ATTRIBUTES = (Project, ); }; }; + AB8B45111A4E958CF8D633D8C6231689 /* ImageProgressive.swift in Sources */ = {isa = PBXBuildFile; fileRef = A288181DC60985C963066396CF083E72 /* ImageProgressive.swift */; }; + ABAA34A6945F8A11D5FC2F27DC905DFB /* FIRCLSOnboardingOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = D46D547E6B2FE36F4774C2E5480EBA7C /* FIRCLSOnboardingOperation.m */; }; + AC0F5BE9969AFF9FC4DBF4D3115917ED /* ObjectSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E69A7D068230FF36FEBEBF212AD9F60 /* ObjectSchema.swift */; }; + AC60C199FDE1163D1C5421CCBB11DABD /* FBLPromise+Catch.h in Headers */ = {isa = PBXBuildFile; fileRef = EA285255E24140F4364ADC9C02ADAAE5 /* FBLPromise+Catch.h */; }; + AC706F37790D84D0F35827772B6F3F06 /* filter_block.h in Headers */ = {isa = PBXBuildFile; fileRef = 2ABDD21C27AB2B0A39AF2EF7DDC29B9B /* filter_block.h */; settings = {ATTRIBUTES = (Project, ); }; }; + AC7D415C826932227566EB2B916A7C26 /* SortDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66265AC21D95957F5CF5467CDF63C31D /* SortDescriptor.swift */; }; + ACD5B442C2A2400AF828DD9220F842FD /* FIRAuthTokenResult.h in Headers */ = {isa = PBXBuildFile; fileRef = AA6E836D46E455FD474EDF1905E109AB /* FIRAuthTokenResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + ACEB271844FF98CDAAB96B5234E7952B /* FIRDataEventType.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C725C066DA25373EC76D559B25677B0 /* FIRDataEventType.h */; settings = {ATTRIBUTES = (Public, ); }; }; + ACF45AC901564746590D62BCBF2C7BF4 /* FIRAuthBackend.m in Sources */ = {isa = PBXBuildFile; fileRef = 331CF223F983590C6FA72BA0697801EE /* FIRAuthBackend.m */; }; + ACFF567479382FABD5FC6C08A619D15D /* FNodeFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 10D8F107AA7F4E07CBE300F4F7CB10C6 /* FNodeFilter.h */; settings = {ATTRIBUTES = (Project, ); }; }; + AD4F9FF4317B2FCDB80BA901DCA8BEC7 /* db_iter.cc in Sources */ = {isa = PBXBuildFile; fileRef = BAFE031523B5978048756FE2BCED2DA4 /* db_iter.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + AD99E8BF4017497083E998DFDA4FFB79 /* FBLPromise+Reduce.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B325D27B8A5F2CA76E7C81532F652F8 /* FBLPromise+Reduce.m */; }; + AD9F6231FF2F80713036CD764A91B01F /* FIRCLSMachOSlice.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E2522A9B4A9665409FB203B0AEEC844 /* FIRCLSMachOSlice.m */; }; + AD9F7734183A222D01D193BB380CEBE2 /* GTMSessionFetcherLogging.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E0433FE9144F9A78BCF219EF77AF2A0 /* GTMSessionFetcherLogging.m */; }; + ADA239945F5E2E231D1539919E169049 /* FIRDatabaseReference.m in Sources */ = {isa = PBXBuildFile; fileRef = AF62A3F1196ABE198A8D9FAD67E52A57 /* FIRDatabaseReference.m */; }; + ADC590829802B94CE186B3E5DBC57D7B /* RLMConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BDED9776D9CD4DD8E2AAEA8FEE02214 /* RLMConstants.m */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + ADF1076FF0CE456B5E0E046EEC33CD97 /* ThreadSafeReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAD407F4EF0402CE18C8A3FB16136CE1 /* ThreadSafeReference.swift */; }; + AE1CF61A21BA2BA67C5A18A00CC4DC99 /* GULLoggerCodes.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B83ED9467FD61F44F6C95DCF79C6280 /* GULLoggerCodes.h */; settings = {ATTRIBUTES = (Public, ); }; }; + AE44EA35803EB2B25C30AE3E4D513E12 /* table_builder.h in Headers */ = {isa = PBXBuildFile; fileRef = 2C87B89758929819681BF0D49D90466A /* table_builder.h */; settings = {ATTRIBUTES = (Public, ); }; }; + AE46D7CF9158C9D77CEEE8D80DDE2638 /* cache.h in Headers */ = {isa = PBXBuildFile; fileRef = 89EF68696031BDC544F7C211C49A190E /* cache.h */; settings = {ATTRIBUTES = (Public, ); }; }; + AE671A9E49E0910D1FF01B1C3FD2D4DF /* FIRInstanceID+Private.m in Sources */ = {isa = PBXBuildFile; fileRef = 1474818897D4EF99A548A3FE8D382197 /* FIRInstanceID+Private.m */; }; + AE96328EC72F4BC41BD1A496D45C9325 /* RLMSyncSession.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 9D06E960379196A78A8272C36A69AC21 /* RLMSyncSession.h */; }; + AEA43D492F0B4430F26EDE8EC151335E /* FIndexedFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B484F345ABB94B6FDED330218918FCE /* FIndexedFilter.m */; }; + AFB5D797E8DDEEA898DB6884031B40D8 /* status.h in Headers */ = {isa = PBXBuildFile; fileRef = AE6D328B3C537003183B73C76D907C10 /* status.h */; settings = {ATTRIBUTES = (Public, ); }; }; + AFC64B1097F7355FF423D6A73E9C7210 /* TaskDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9966DDAE4F25C29B629BF069C18E98F2 /* TaskDelegate.swift */; }; + AFDC58FF42E02745753EE6C5A502C3BB /* RLMSyncUser.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5F28695C975FEEBF7604AEAA004C04A7 /* RLMSyncUser.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + B0136F14A953A409C7F2B4CA854218BA /* FIRCLSExecutionIdentifierModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 21901FB1986D506361381CD549E3C54C /* FIRCLSExecutionIdentifierModel.h */; settings = {ATTRIBUTES = (Project, ); }; }; + B02C18CC0F1ED9C7B222D3922EB803DB /* FServerValues.m in Sources */ = {isa = PBXBuildFile; fileRef = 533BFCBA4D346ADED0C0CB42B1E4516B /* FServerValues.m */; }; + B070D09AF06FFBE14295C3B45211F7F9 /* FIRTwitterAuthProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 155F8EFAE7A4D4D041B3D8F2511E9D4C /* FIRTwitterAuthProvider.m */; }; + B07A918EDF41DD7B6F921908885A4019 /* GULLoggerLevel.h in Headers */ = {isa = PBXBuildFile; fileRef = 18A2A34965EE1BF82E73C4BBE109FF6B /* GULLoggerLevel.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B0A29143188784E2E0005739431C2462 /* FIRInstanceIDCheckinPreferences+Internal.m in Sources */ = {isa = PBXBuildFile; fileRef = 7409DF4877493F28BE503A9565A47E4F /* FIRInstanceIDCheckinPreferences+Internal.m */; }; + B0F1E4473381EF323D63A39B680160C3 /* env_posix.cc in Sources */ = {isa = PBXBuildFile; fileRef = 981D3489F3D5048257A7FBB1CD338281 /* env_posix.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + B0FE783C53D7E421BB90198A18CCE4E0 /* GDTCORUploadCoordinator.h in Headers */ = {isa = PBXBuildFile; fileRef = 1E0D87AAB7FCF9615A4CE2C179F88E7D /* GDTCORUploadCoordinator.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B13AFF3CB01EDDD79D5CF4062A348A01 /* FIRSetAccountInfoResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BEB9AB4B41C5EA0FC8F77768519921A /* FIRSetAccountInfoResponse.m */; }; + B13C163DAC18295ECDD0C3B6C7307793 /* FIRCLSProcessReportOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 76B8D4AFE9CA027E2EFC28B18F209340 /* FIRCLSProcessReportOperation.m */; }; + B1471D98878942FB226D2F3F625A2487 /* FIRComponent.m in Sources */ = {isa = PBXBuildFile; fileRef = B8B62D08C4562CDA545B1A9CE08B8BE1 /* FIRComponent.m */; }; + B225C132BE2F4FB6DB5948FFFF045CD3 /* FIRCLSURLSessionTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 40E7FAFFEE1A98D4288EFF0823FE7396 /* FIRCLSURLSessionTask.m */; }; + B243301AC8E71DC52E6FDC10E7249280 /* leveldb-library-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = FE1E9033C4E91571AFD9184C9A387A9F /* leveldb-library-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B2720D8994A4C764E428120DCE1111EB /* FIRConfigurationInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E81AB8B58F049ABA4528A79F1F344F7 /* FIRConfigurationInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B2D8F9D8B91DE8C26D5C2355F1FB941A /* KingfisherOptionsInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D7092FB504C43E51EBF941876B70D43 /* KingfisherOptionsInfo.swift */; }; + B2EE93107EB7EB0A704C678ED2CE8F48 /* GULSceneDelegateSwizzler.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D3459C02176B8A9124D0772B38C6178 /* GULSceneDelegateSwizzler.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B2FEC6C211B6518BE1B3BF255D831447 /* FBLPromise+Delay.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D9F16D73D1B59742905EDD0C2966E3F /* FBLPromise+Delay.h */; }; + B34A0A6D659D5D2F79FEEEAF83B87B43 /* RLMObjectSchema_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 030FB2759BA9D7978C425338F088B8B5 /* RLMObjectSchema_Private.h */; }; + B34EC46CAF117EEB42A946ED4597D64A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64025D1D442A26509C94DDDE4BCBF577 /* Foundation.framework */; }; + B37338B7164D136BB318B355CF9E2955 /* FImmutableSortedDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D83116F9227F8C545FC7F83B31C6F56 /* FImmutableSortedDictionary.m */; }; + B37F8E1C0FC44207F42540B5E5DFFE0D /* FIRInstallationsStoredAuthToken.m in Sources */ = {isa = PBXBuildFile; fileRef = EC3131B267B53BBFA83377DC278EFCE6 /* FIRInstallationsStoredAuthToken.m */; }; + B39DCEF9E3BCD040654935354C1615F7 /* GDTCORUploadPackage.h in Headers */ = {isa = PBXBuildFile; fileRef = A8D86AF676E653B84DD1CB1594DCB2BB /* GDTCORUploadPackage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B3D636D90E51741683F376016D836355 /* partial_sync.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7313A8ADB88E86743525E015300B75B0 /* partial_sync.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + B3F8717860CC04A746EC58EAEE94AEEC /* FIRCLSAllocate.c in Sources */ = {isa = PBXBuildFile; fileRef = FC47FF55CCA6DB295945AE33E25407CA /* FIRCLSAllocate.c */; }; + B42D1B76645FDC0DB698913B44E3FD31 /* FIRCLSMachOBinary.h in Headers */ = {isa = PBXBuildFile; fileRef = 619E5249FFEF1993E7929824BF208BBC /* FIRCLSMachOBinary.h */; settings = {ATTRIBUTES = (Project, ); }; }; + B4305EE7A47284C4EE66AF85D1CA9429 /* FEventEmitter.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E1F295C21957B24F285D48A4D11B95C /* FEventEmitter.h */; settings = {ATTRIBUTES = (Project, ); }; }; + B44ECB680C7291BEFF0468F5F76272E8 /* FLimitedFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 713C78044C95A18155B81728F5CCD2F3 /* FLimitedFilter.m */; }; + B4A23C2A4315131EBC064AA3E48E028B /* FIRInstanceIDAuthService.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F73B89D31C30727BAC5F06912071FD9 /* FIRInstanceIDAuthService.h */; settings = {ATTRIBUTES = (Project, ); }; }; + B4A64C9136805CB6BE283579A76C7658 /* RLMListBase.h in Headers */ = {isa = PBXBuildFile; fileRef = AEFBD0F180EED7215C8DA83E8590612E /* RLMListBase.h */; }; + B4ACBEE17C3E66A67EB74B25C4E2EECC /* GDTFLLUploader.m in Sources */ = {isa = PBXBuildFile; fileRef = 0FCAEFD4A55B7DC2D8CD018B48D09F1D /* GDTFLLUploader.m */; }; + B50E079B4CDC981505AA4AE2A18598CF /* crc32c.h in Headers */ = {isa = PBXBuildFile; fileRef = 26CA7503642099111938AB974E6A6646 /* crc32c.h */; settings = {ATTRIBUTES = (Project, ); }; }; + B5AD279F634D06859F4F4FB1E02D3FF0 /* network_reachability_observer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C699EDB3BC3205372C8415289EA8D2C4 /* network_reachability_observer.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + B5E761475F5F8C3FEBA0627317C9B87A /* FIRInstanceIDStore.m in Sources */ = {isa = PBXBuildFile; fileRef = DEB0EF341F77551CC9EE09C1A7E3863D /* FIRInstanceIDStore.m */; }; + B5FF707FEE8A4B13B59255108E0151E7 /* object_notifier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DF8DDDA82781E362677B1A61776BCD7E /* object_notifier.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + B65F33E26B5FB71C678700D5F5A7545D /* FIRCLSCompactUnwind_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = B9DBE0FD749B655F84CDF7BE54E13699 /* FIRCLSCompactUnwind_Private.h */; settings = {ATTRIBUTES = (Project, ); }; }; + B662C3D30661957B6266CCA850A0D88A /* FirebaseCore-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 28BB76344ECE64A25E705BF2FE69B522 /* FirebaseCore-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B67C4B5C715A1AFB214C8342B1B537BD /* FRangeMerge.m in Sources */ = {isa = PBXBuildFile; fileRef = CA2805B2729BA1A43D194462C60F5179 /* FRangeMerge.m */; }; + B69FBAAECADD405ABC185DFFFF9E68A0 /* FUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = F34CD699384BE03207942625DA31B7D7 /* FUtilities.h */; settings = {ATTRIBUTES = (Project, ); }; }; + B6DAA4FE8AFA5CD4BF68AF52102E0355 /* RLMObjectSchema.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 3CF201389ACD1CAF85EDD83AC49F3F48 /* RLMObjectSchema.h */; }; + B70D1176DB6DAA6F3CB7880C8F144E0C /* FIRInstanceIDTokenInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = C0D584A02FC056531E03BF78F097310D /* FIRInstanceIDTokenInfo.m */; }; + B7242EFBDD964132F28A45D5DC515C20 /* FBLPromise+Retry.m in Sources */ = {isa = PBXBuildFile; fileRef = 76B451276F7D9E31176301E5DE296E35 /* FBLPromise+Retry.m */; }; + B77D3B6F22B7617B8894DBCE83B03A76 /* GDTCORTransport.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F1D4E308F55A3F56D63433F341967EC /* GDTCORTransport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B79361F232FB79297A27EDACA5934061 /* table.cc in Sources */ = {isa = PBXBuildFile; fileRef = D044378201B375C7DF9D01AD857B77AD /* table.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + B7C9B8EA1E3EC6BC5FE0D321F0D344D0 /* FIRInstanceID.h in Headers */ = {isa = PBXBuildFile; fileRef = 4BF75C24DD34A3FE44821FA4D298BC83 /* FIRInstanceID.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B7CC2CEBDCF42DB4BCF7FDCA67991C6F /* RLMOptionalBase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3E74C743B156DCE4E62F27FFCD7EF37C /* RLMOptionalBase.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + B80080DF80150A02D9E529B72E8630C0 /* FIRInstallationsVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = D02AD4C8F8482C50EB8A6DF81E4B7DA6 /* FIRInstallationsVersion.m */; }; + B8242942F7B8DF87CD3AA1B3144AC793 /* FIRCLSURLSessionUploadTask.h in Headers */ = {isa = PBXBuildFile; fileRef = 9828526EFD32EFD531EBBB2E9D6DB8B3 /* FIRCLSURLSessionUploadTask.h */; settings = {ATTRIBUTES = (Project, ); }; }; + B86917BC204271414FD379A9EE1E6F7F /* testutil.h in Headers */ = {isa = PBXBuildFile; fileRef = 36D67A48DF56131EF94BFE935D7A514F /* testutil.h */; settings = {ATTRIBUTES = (Project, ); }; }; + B911E85427B18A240F27ECB3FD87B9ED /* FWriteTree.h in Headers */ = {isa = PBXBuildFile; fileRef = E53B2F3C7FE2FEDA489481B2B799798B /* FWriteTree.h */; settings = {ATTRIBUTES = (Project, ); }; }; + B97A8885829D76823CEF3D8F8B6DAECA /* FIRVerifyPhoneNumberResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = FAFB9BE1C3FB89125BBF7F38D1BB238C /* FIRVerifyPhoneNumberResponse.h */; settings = {ATTRIBUTES = (Project, ); }; }; + B9BEF5B410021DCC139E4F15660A32B7 /* FBLPromise+Then.h in Headers */ = {isa = PBXBuildFile; fileRef = A994DEA929EC73A3FECB99814A26BF0F /* FBLPromise+Then.h */; }; + BA0599424BA4A35E771B0FA54920D3C7 /* FIRCLSDemangleOperation.mm in Sources */ = {isa = PBXBuildFile; fileRef = 36FE3A34041E5EC9A58F9DCF49A0AC21 /* FIRCLSDemangleOperation.mm */; }; + BA27BAF6118E47DF5075FBD88CAE1A14 /* FAuthTokenProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 8FF8C4F4BA47732C4BB31B381E8CCD36 /* FAuthTokenProvider.h */; settings = {ATTRIBUTES = (Project, ); }; }; + BA545614B134ABFCE6AE4311BE1EDAE4 /* FIRCLSSettingsOnboardingManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1366E4DEDA780B6EDEB38EFC163BF5E1 /* FIRCLSSettingsOnboardingManager.m */; }; + BA8A37E646FA398521BFE762D1DEB3D0 /* FIRAuthDispatcher.h in Headers */ = {isa = PBXBuildFile; fileRef = 6411A605F957CB41FE1FC8AF5218ADD0 /* FIRAuthDispatcher.h */; settings = {ATTRIBUTES = (Project, ); }; }; + BAAC10B16ADBC408AB110F8CF4321175 /* GULAppEnvironmentUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = D18F97ACB446742A827DE3D7F31A9761 /* GULAppEnvironmentUtil.m */; }; + BB55DA90F8B3A543595F1693EDBF9288 /* FIRCLSGlobals.h in Headers */ = {isa = PBXBuildFile; fileRef = 2B92BE5EC3F82D429268873BAFBA0062 /* FIRCLSGlobals.h */; settings = {ATTRIBUTES = (Project, ); }; }; + BB5CCD24865B991A4DE72182ECC3C47E /* GDTCORConsoleLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 4EE1BC1AF3606BB8B6FF9CF50F00E04F /* GDTCORConsoleLogger.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BB6EEDA0E565A940475C5DD52D69EC16 /* FIRCLSFile.h in Headers */ = {isa = PBXBuildFile; fileRef = 02DC9AC9F992D94B7D771EF46B451D7D /* FIRCLSFile.h */; settings = {ATTRIBUTES = (Project, ); }; }; + BB6F9FA0BD72C79C63B8587B17141A9B /* FIRAuthKeychainServices.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B938CCEBEB19930A5B085AFF34B90CB /* FIRAuthKeychainServices.m */; }; + BBCC6DDC1E888668E9DB4E11FDECBE76 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AAD0BBA966E05AF48C696B1C8088665 /* Error.swift */; }; + BBD71C89D7C0D636B54C769774CB987F /* FIRTwitterAuthCredential.m in Sources */ = {isa = PBXBuildFile; fileRef = 23B445FBF7E9205D356BA3B046D1B560 /* FIRTwitterAuthCredential.m */; }; + BC7C183345CA9AA57583EF2F87343967 /* FirebaseAuth.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E2B962F4C08A3AE087F4EEE5D28146B /* FirebaseAuth.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BC92E3E5D9BF0269D170E7ED8EAAB4FA /* FSnapshotHolder.h in Headers */ = {isa = PBXBuildFile; fileRef = B4630EC4BD28C7D9F743C6494833B4F3 /* FSnapshotHolder.h */; settings = {ATTRIBUTES = (Project, ); }; }; + BCC77EA6D45B9055FA945ADCB1122EBF /* GULAppDelegateSwizzler.m in Sources */ = {isa = PBXBuildFile; fileRef = FBF030098618CE3A844110FF41D52710 /* GULAppDelegateSwizzler.m */; }; + BCF68AD207127F823C99CEB4AD4AAE5D /* RLMArray.h in Headers */ = {isa = PBXBuildFile; fileRef = 1D0510411AB5C0B1659FA25995973E70 /* RLMArray.h */; }; + BD2C3D1F84AE9307C8C7EBBEEC434288 /* FirebaseInstallations.h in Headers */ = {isa = PBXBuildFile; fileRef = F0B703AD9011F08B3C8BC98CD60D5E12 /* FirebaseInstallations.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BDF2F031400EB6019056A3F76C769748 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64025D1D442A26509C94DDDE4BCBF577 /* Foundation.framework */; }; + BE02564DC828FBB8B5ADA941B8B7EBB1 /* GDTCORTransformer_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = BF60CBDE6804E7AEE2F0511331C15B8D /* GDTCORTransformer_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + BE17BE872A2C7D4C8C4AAA51B08AA206 /* FirebaseDatabase-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 25F7B3F02D143776CB94A26EBDFF748C /* FirebaseDatabase-dummy.m */; }; + BE33F3D2B1E4AA74939221284D92652C /* placeholder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 930044AA0BC41A1A935CF3B6450322CC /* placeholder.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + BE5BEFEEC9617E6F2ECA81E0B0ED9928 /* RLMRealmConfiguration_Private.h in Copy . Private Headers */ = {isa = PBXBuildFile; fileRef = D31A28FF5B008109A4F47D9CE15E58B2 /* RLMRealmConfiguration_Private.h */; }; + BECD261B295C2FD449F9D814CA88C33A /* FValueEventRegistration.h in Headers */ = {isa = PBXBuildFile; fileRef = 459B070289EE1BA904BD423482CA91B1 /* FValueEventRegistration.h */; settings = {ATTRIBUTES = (Project, ); }; }; + BEDA31B8992C066AC6EBB699E0710776 /* FIRConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 820BAF23204DD2ADE6997AD1D0B201CE /* FIRConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BEE6B677416CA71C981D1D3F60B18C96 /* Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CDFD241B0E81B85EB547ADEBDB5D6B5 /* Alamofire.swift */; }; + BF08EB397775EFE40ABE7B19BA55B672 /* RLMSyncUtil_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = E7285138962EBA09D3BBBB0A162D6047 /* RLMSyncUtil_Private.h */; }; + BF44407CEAAB1DCAD39E6BB84840FCA5 /* FViewCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 2AA97EC8B857DC53B769FDB0770E3780 /* FViewCache.h */; settings = {ATTRIBUTES = (Project, ); }; }; + BF94B90FD2842F742883F06961FBEFE9 /* FIRConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = C68934F7EF2D6B6AA3755B9F60EBA98F /* FIRConfiguration.m */; }; + BFF27381EC19183915E8328A74EB172A /* FBLPromise+Reduce.h in Headers */ = {isa = PBXBuildFile; fileRef = AEF66CD8B1BCBC0A3B148DF00A6113A8 /* FBLPromise+Reduce.h */; }; + C00B8C8A40084064F570B8FA5672CFC3 /* block_builder.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7E1B6BA18F688452F1DB59F99647172A /* block_builder.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + C03DD9A25A37D39D1E51219B4A32CC65 /* FIRInstallationsItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 40E5AB8EBAC57F183B9FCD5346C75EE8 /* FIRInstallationsItem.m */; }; + C04D9D88E8823DBDBE93FABF90F1CF6E /* FIRDatabaseComponent.h in Headers */ = {isa = PBXBuildFile; fileRef = 8C505EBD827CC2D18CD9D3E74CD39B8A /* FIRDatabaseComponent.h */; settings = {ATTRIBUTES = (Project, ); }; }; + C08DDEB645515373444873D641E9F2E7 /* FIRCLSCodeMapping.m in Sources */ = {isa = PBXBuildFile; fileRef = 012F3963A2B15DDF0634F334AD78F02C /* FIRCLSCodeMapping.m */; }; + C0A52F5EED1C7BB3E724772F27C2107B /* FIRCLSMultipartMimeStreamEncoder.h in Headers */ = {isa = PBXBuildFile; fileRef = AFFD295CAD2BDB5B43E29F61E1D4D7BC /* FIRCLSMultipartMimeStreamEncoder.h */; settings = {ATTRIBUTES = (Project, ); }; }; + C0B775769787C3E19FB6356CD7B8DBAF /* FIRGetOOBConfirmationCodeRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 139EC6653CEB5B25C26FAD976A2CEE80 /* FIRGetOOBConfirmationCodeRequest.m */; }; + C0D9B7374FC725F11F5E6DA8E7382199 /* FIRCLSThreadArrayOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 19B99E5574E2EFC09D9C01055646B8C9 /* FIRCLSThreadArrayOperation.m */; }; + C0FFA57E39D99B6AC85FEF60CA966EE4 /* Source.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF25EF9B5A1097A37C5DCCE3ACBDFE0 /* Source.swift */; }; + C1147050F9B9901A377C3A9B6E253612 /* RLMPlatform.h in Headers */ = {isa = PBXBuildFile; fileRef = 22F2A4295E515E6562FF43CA3D56B0C5 /* RLMPlatform.h */; }; + C1305C8A3CE868521D0B46E40DF78ECC /* FIRVerifyPasswordRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 4485A1A98C3F3A02896CC18D5D8181BC /* FIRVerifyPasswordRequest.m */; }; + C13E04BEC90D13ECC2131CC4D80B64BC /* FEventGenerator.m in Sources */ = {isa = PBXBuildFile; fileRef = 26D38478C1F46C17F95B881A52894550 /* FEventGenerator.m */; }; + C1759BE35FF699F96A9860C9AE9A6D54 /* FIRGameCenterAuthProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = DE34E08AD80E531D47F7FF62F05C9DBA /* FIRGameCenterAuthProvider.m */; }; + C1DC996199ED5D451E07ED27FE3232DF /* FValidation.h in Headers */ = {isa = PBXBuildFile; fileRef = DD0946B579009574C042E72621AC1BF5 /* FValidation.h */; settings = {ATTRIBUTES = (Project, ); }; }; + C1F1C70CEE7F80A8BBECBF05FCF79142 /* primitive_list_notifier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FB428CC5D255E1711E6656967E9FDF8A /* primitive_list_notifier.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + C2213EAF4C8F8FE723E3A32C7EC8D73F /* FIRCLSDwarfExpressionMachine.c in Sources */ = {isa = PBXBuildFile; fileRef = BCAA4BD8CF79CDFBE112EEAB21E22806 /* FIRCLSDwarfExpressionMachine.c */; }; + C2285F0CF7A81ACC446E039E2548F699 /* FIRAuthGlobalWorkQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = E0361FDBB1C8218671C579D62D9C5EF1 /* FIRAuthGlobalWorkQueue.m */; }; + C26E44D2FA1813006202861296AD674A /* GTMSessionFetcher-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 9E2D7FC56B1BA97E8C68CC09D1771342 /* GTMSessionFetcher-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C29517E8F8EE04EFC24A2B3101496685 /* FBLPromise.m in Sources */ = {isa = PBXBuildFile; fileRef = BD0D11232D26F0FF3FDC5D549A76DE13 /* FBLPromise.m */; }; + C2A9D7E2A7356B9BD179D726A2418F4B /* RLMAccessor.h in Headers */ = {isa = PBXBuildFile; fileRef = 9495BA679912E002DD2FC5B70555A0D8 /* RLMAccessor.h */; }; + C2CDC860BA74B75C0504A55B02E072F6 /* AnimatedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DADE1727943B7611BB868DDDB6DEEEE /* AnimatedImageView.swift */; }; + C3278A315F4C3470DE2C205BCCE6FF61 /* FIRCLSNetworkResponseHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = B9589DD728D797408CD139CE3274709B /* FIRCLSNetworkResponseHandler.h */; settings = {ATTRIBUTES = (Project, ); }; }; + C344A5F89FD89526BADE0FC9A30DC244 /* FIRAuthDataResult_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = EDC084CC75C40792DFD2D7287EFD6467 /* FIRAuthDataResult_Internal.h */; settings = {ATTRIBUTES = (Project, ); }; }; + C3A5ACE1DC23B4872EC85665FA380BE5 /* FLLRBValueNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 3C6AA9924912484D21FE7B82B8982939 /* FLLRBValueNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + C3DC3E5EB0E222AF71D2CFD37AC296AB /* FIRInstanceIDTokenDeleteOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BB32016F6107456B9585FD31C1C63BB /* FIRInstanceIDTokenDeleteOperation.h */; settings = {ATTRIBUTES = (Project, ); }; }; + C409433F2328566B78FA477EB56CA377 /* DiskStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE5C36747E6121753BC7D0C5E556FADF /* DiskStorage.swift */; }; + C45DBE54266A6BAA1894DE42346CD56F /* FChildrenNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 7CD3E8B3D40C26292D916375AE737C9D /* FChildrenNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + C45DC8EAFB904B6EE9CF7C71AB20FD32 /* FIREmailAuthProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C980FD69AD83CB2FF5D34DC23B46A97 /* FIREmailAuthProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C49FEFB601CD36B3BE61FB6D4494D733 /* Realm-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E01D63CDAC12AFE0048E676F37EB6DA /* Realm-dummy.m */; }; + C4E325C2C2A0651CCFF2F40DCA5561C5 /* NSError+FIRInstanceID.h in Headers */ = {isa = PBXBuildFile; fileRef = 03123A90B6E96B5F3F945E9576200603 /* NSError+FIRInstanceID.h */; settings = {ATTRIBUTES = (Project, ); }; }; + C51B895EA2FAA594CE42154B2D31E5A9 /* version_edit.cc in Sources */ = {isa = PBXBuildFile; fileRef = 861AFA8FF5218ADA37F19EC29EA86D5F /* version_edit.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + C544DE1F643064552AC40F3947DC47A0 /* FTupleObjectNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 96BD12B47B380FF0B4B732A7A3C7F913 /* FTupleObjectNode.m */; }; + C5686FA13350E54E7D8A0CD0A4129BDB /* coding.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6924D9564695354749C2D1227F4B69A /* coding.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + C58645B1248EBB3255B4E590C2A4D017 /* FIRRetryHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = BC115140A96B7DA133E8F98C90D95FD0 /* FIRRetryHelper.m */; }; + C58AC3B6AC4597752277700E774B0927 /* ImageDownloaderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19C52BF106E4431FAE1B6E68ED7F69ED /* ImageDownloaderDelegate.swift */; }; + C6193624E995284C7BA2CBDED8140BCC /* FIRCLSCodeMapping.h in Headers */ = {isa = PBXBuildFile; fileRef = D4E14494BF96294FC9B9D9A09C188386 /* FIRCLSCodeMapping.h */; settings = {ATTRIBUTES = (Project, ); }; }; + C70905767B4B45E148E005795BCE5EC4 /* FBLPromise+Validate.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 9EA4A8CC145EB727B389E41E6BF8B35C /* FBLPromise+Validate.h */; }; + C740E549026AE383D979EC61223EE3A1 /* FIRCLSFileManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A9E802A74840CABA570E678E66E944C1 /* FIRCLSFileManager.m */; }; + C7E4252DFE965F0E39ADC24E1AA6F6FE /* FWriteTree.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B528ABCAC79A8EE228A8B0D1B8A80D8 /* FWriteTree.m */; }; + C80C9724725CDA4618CF75DD13EE5E35 /* FIRInstanceIDCheckinService.m in Sources */ = {isa = PBXBuildFile; fileRef = C2EADE68D084997E314F18E639061AD1 /* FIRInstanceIDCheckinService.m */; }; + C81A970A0C95B15D031D606D0F987BA7 /* list.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EEFE02DEFA8B914696B221BDB1E55875 /* list.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + C81F1787CA4DA2DC531ABCAC4E219DAC /* FLevelDBStorageEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 868FC26600B87CCCCB054FD66B2EA15E /* FLevelDBStorageEngine.m */; }; + C87F621017CD4FB079BB063451F24613 /* write_batch_internal.h in Headers */ = {isa = PBXBuildFile; fileRef = ACDE8B7DDA72C544A091534E5D36CC99 /* write_batch_internal.h */; settings = {ATTRIBUTES = (Project, ); }; }; + C900AF92B70CCC35CF46BE3DEF231564 /* RLMListBase.h in Copy . Private Headers */ = {isa = PBXBuildFile; fileRef = AEFBD0F180EED7215C8DA83E8590612E /* RLMListBase.h */; }; + C97C148C5BD94CE8C55EDB39F27B5281 /* FSparseSnapshotTree.m in Sources */ = {isa = PBXBuildFile; fileRef = 948D6FAC68230EC8AC23DC6649DB1BB3 /* FSparseSnapshotTree.m */; }; + C99FF6D0D7178FD5C92A3F07F06F7354 /* FIRCLSFCRAnalytics.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A88EA3ED2B76186B85C1671B3D3CB89 /* FIRCLSFCRAnalytics.h */; settings = {ATTRIBUTES = (Project, ); }; }; + C9CFCB620C528E09054C987ACF9C41CB /* port.h in Headers */ = {isa = PBXBuildFile; fileRef = 025A8978E9E1BA201E9B325D59D34DA4 /* port.h */; settings = {ATTRIBUTES = (Project, ); }; }; + C9E00ABDE9FCF4D436FC6D24872DFB75 /* FBLPromise.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 66B0B6220CDA636E37A9440D71E8C1F4 /* FBLPromise.h */; }; + CA0FD29B02CA5E7ED61547AD5D9AE105 /* FIRCLSFABAsyncOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = C0EE0F9DF8F3330249489D1E4F82FAFC /* FIRCLSFABAsyncOperation.m */; }; + CA2E5C7FC7863E3DBFF0DED0AC553E90 /* FIRDatabase_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 1D5FBD103DC3CF41681635FC640E23FC /* FIRDatabase_Private.h */; settings = {ATTRIBUTES = (Project, ); }; }; + CA784FB6D280003577399FC6357DF9E5 /* RLMRealm_Private.h in Copy . Private Headers */ = {isa = PBXBuildFile; fileRef = 675DAF0FD379B5DE1611353667173D80 /* RLMRealm_Private.h */; }; + CB13468076A0A131A014609BB9D08196 /* FIRInstallationsSingleOperationPromiseCache.m in Sources */ = {isa = PBXBuildFile; fileRef = D6B1422B8EBEE11D856B66B0E96F117F /* FIRInstallationsSingleOperationPromiseCache.m */; }; + CB2B572644D56110576B961927A43B1E /* index_set.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A193EF96B2067363B9A36502ACD87C75 /* index_set.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + CB33B3BF38557E32B50F3A9A23CF1960 /* FIRInstallationsIDController.h in Headers */ = {isa = PBXBuildFile; fileRef = B2C3C0B84E27EFD7128F49252167B459 /* FIRInstallationsIDController.h */; settings = {ATTRIBUTES = (Project, ); }; }; + CB40C58EF0AFEC7A5076DA07A08179C1 /* RLMSyncSession.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D06E960379196A78A8272C36A69AC21 /* RLMSyncSession.h */; }; + CB76EA65EE8382206C7881717DCF0F3D /* GDTCCTNanopbHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 8232B5CC32D2C64C6DAA70A7C7593250 /* GDTCCTNanopbHelpers.h */; settings = {ATTRIBUTES = (Private, ); }; }; + CBA7618534F6AFF89B291137C1E563DE /* FIRCLSProfiling.h in Headers */ = {isa = PBXBuildFile; fileRef = 783508FA1E09FE34ED2409D5E4E1B1C4 /* FIRCLSProfiling.h */; settings = {ATTRIBUTES = (Project, ); }; }; + CBC81EB12308BD5B374929EA148FAE11 /* FIRCLSSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = 86DB482A924060A7B03BF8E7289A0C85 /* FIRCLSSettings.m */; }; + CBD382124C7E260E04DE76E2D47E5D49 /* FIRCLSDataCollectionToken.h in Headers */ = {isa = PBXBuildFile; fileRef = 97C63B265EC2B20EAE69CA93369383DF /* FIRCLSDataCollectionToken.h */; settings = {ATTRIBUTES = (Project, ); }; }; + CC03D0496C1DF9129E965459D54AA882 /* FIRCLSUnwind.c in Sources */ = {isa = PBXBuildFile; fileRef = C4077AE6A29830CC9E7134E0563DC629 /* FIRCLSUnwind.c */; }; + CC272BD40CB17460F25E41C5F1604501 /* RLMProperty_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = C74A86C48C177012E0045DC846302FED /* RLMProperty_Private.h */; }; + CCF90603A2462A8652387889024C1C47 /* FLevelDBStorageEngine.h in Headers */ = {isa = PBXBuildFile; fileRef = DF82CE7658F608F7684A4348FE783371 /* FLevelDBStorageEngine.h */; settings = {ATTRIBUTES = (Project, ); }; }; + CD19EBB20252CC326563A92DF6098DC5 /* RLMSyncConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 1B855D092D33E62CC9867576BF1B3E8D /* RLMSyncConfiguration.h */; }; + CD698D8D49A0DB3669D8D58B4571268E /* ImageProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27BE1A976504CC23970F20939A322FB /* ImageProcessor.swift */; }; + CD704E36BE63DAD07D996AFEEE41B300 /* FTreeSortedDictionaryEnumerator.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F49F567A0C73278FD58192F5C783B3F /* FTreeSortedDictionaryEnumerator.h */; settings = {ATTRIBUTES = (Project, ); }; }; + CD8E52755C165618D3E233E1D725D5BC /* GDTCORUploadCoordinator.m in Sources */ = {isa = PBXBuildFile; fileRef = 57654FCDEAC6AA86CFE3FA58FC422D05 /* GDTCORUploadCoordinator.m */; }; + CDD42E25D201469CAFEA9E6181525FA8 /* RequestModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16238A64917CEB3E86D222482A856898 /* RequestModifier.swift */; }; + CE318AFB78D1DF9C0CAA303BE8E9C716 /* format.h in Headers */ = {isa = PBXBuildFile; fileRef = 91D4B4C08E11E17B827575DBF312C72D /* format.h */; settings = {ATTRIBUTES = (Project, ); }; }; + CE4D180DFA3FAC29062CEE93ECEFBB00 /* FTupleBoolBlock.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D9C52A5FE9F767DD058F89205254B18 /* FTupleBoolBlock.m */; }; + CE619FA76DEE02BAB42AC11A21CD03DE /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64025D1D442A26509C94DDDE4BCBF577 /* Foundation.framework */; }; + CE6AA463E98F7F834B40D3BF93F7941B /* FBLPromise+Retry.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 7BC112B0B8DC7D7A50DFF38A18C9056B /* FBLPromise+Retry.h */; }; + CE7C368EFBCE1A52886E2FC8F284D4A8 /* FIRCLSReport.m in Sources */ = {isa = PBXBuildFile; fileRef = A87FC113ACCF64E5B68D982516EAE67A /* FIRCLSReport.m */; }; + CE964515884B6095B2D6B1F07525212D /* GULNetworkConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 5AD79BA8B34169A833C65779487B1402 /* GULNetworkConstants.m */; }; + CEEF6FA2450E3D32FCFF2F63D2ED4D42 /* GULReachabilityChecker+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = DE86873942CD0E37980CB898FD57838D /* GULReachabilityChecker+Internal.h */; settings = {ATTRIBUTES = (Project, ); }; }; + CF069321882F9D790761DF7ACE0A0482 /* FIRVerifyClientResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = FA930458D628F0B45262C06E2F5FF5EE /* FIRVerifyClientResponse.h */; settings = {ATTRIBUTES = (Project, ); }; }; + CF585AA54281BB45C03037C4A4C56AED /* RLMJSONModels.m in Sources */ = {isa = PBXBuildFile; fileRef = D655C0E680BC5067C7934A5A0E42A882 /* RLMJSONModels.m */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + CF672281761B01B1FF71989FB9CF4939 /* FStringUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 3005F5102DB183A0E49E1772309D4254 /* FStringUtilities.m */; }; + CF7670D1D231DF38056A47214361E2D7 /* FTupleObjects.m in Sources */ = {isa = PBXBuildFile; fileRef = A6816E6F407C12392DC39456D0E4BB2B /* FTupleObjects.m */; }; + CFC09D6B4E3E7CA61E653731A0054477 /* FIRAuthDefaultUIDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ACAD9BF8FD9E6CC233C2B8DB317F5C0 /* FIRAuthDefaultUIDelegate.h */; settings = {ATTRIBUTES = (Project, ); }; }; + CFD3076A3783F41ED3FFB2C5335BA2CB /* FIRAuth.m in Sources */ = {isa = PBXBuildFile; fileRef = 25599B221CFAA5E8FA3F0627E7ECED5B /* FIRAuth.m */; }; + D02A8F3A322426429314853341237059 /* RLMConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 9021419F0DD8F28569985527C8E622B5 /* RLMConstants.h */; }; + D04919918C4E858CB048BCB2E2CA99C0 /* RLMSchema_Private.h in Copy . Private Headers */ = {isa = PBXBuildFile; fileRef = 67C9D0962115956B0C47D1FD9F0B6F61 /* RLMSchema_Private.h */; }; + D0508A5E7A4DF1129A99994EFC3AACEE /* SessionDataTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDB84EA8E14223D9B5C902DB4D179F57 /* SessionDataTask.swift */; }; + D05741401F1A40A4AA8DAE8053928E78 /* FPriorityIndex.m in Sources */ = {isa = PBXBuildFile; fileRef = DBFCE01EA61613CE83B1513D2DB54FCA /* FPriorityIndex.m */; }; + D05EFB6867DC63834A926B4E3969B421 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFC56E517F177CBDCE7A445BE95B4E4C /* Security.framework */; }; + D09F17559EECB37D3423EB487EB98CED /* FIRInstanceIDStringEncoding.m in Sources */ = {isa = PBXBuildFile; fileRef = 018A67795D376839684483C5DE57E63F /* FIRInstanceIDStringEncoding.m */; }; + D0FFA07135B6B357BF7FB4097C2ED5B0 /* NSError+RLMSync.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = DF17CAE04A5A105011249086FB2ECCCA /* NSError+RLMSync.h */; }; + D13E571CFDFA478398231DD5D0F4EB52 /* RLMOptionalBase.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = C44C40D4B3AF4238A0A8521E9F1C7F77 /* RLMOptionalBase.h */; }; + D1989D8FC6F8B5392579A0043C507FFE /* FIRCLSUnwind_arm.c in Sources */ = {isa = PBXBuildFile; fileRef = F8ED8EDB4332628051191DB6D91A761D /* FIRCLSUnwind_arm.c */; }; + D19BBDCA93F064A433A26461781A2D85 /* FIRGameCenterAuthCredential.h in Headers */ = {isa = PBXBuildFile; fileRef = 64863531D302B03363FA66046E691FB2 /* FIRGameCenterAuthCredential.h */; settings = {ATTRIBUTES = (Project, ); }; }; + D1B4079BB427AA2EA1955C02EA34E7EC /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63EDC4B1811281A589A7D31C6DB07ED6 /* ImageCache.swift */; }; + D1D68F0EE3EBB60A3D8584EFD87E7775 /* FBLPromise+Timeout.h in Headers */ = {isa = PBXBuildFile; fileRef = 14F0259C7BD472223481E9A31DC96485 /* FBLPromise+Timeout.h */; }; + D1E16D8B852B793DC73D69F98267636C /* Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47BAF825718E41F6EEECFADCA8414AD /* Migration.swift */; }; + D1E277C1682CF0248A33755597AF5D2E /* env.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1E0181084EF4AB7551CEDA5C117A0DE3 /* env.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + D1F33CABBE11F58572D38B71D2E8B37E /* GULReachabilityChecker.h in Headers */ = {isa = PBXBuildFile; fileRef = 96BD2F13CC711654EDB640D31C70F50E /* GULReachabilityChecker.h */; settings = {ATTRIBUTES = (Private, ); }; }; + D2162566ABE8F0D7B775E272FBDF6BC2 /* FIRGitHubAuthProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 4CAD8C29BDA630B438644EEE766E02BE /* FIRGitHubAuthProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D24EC58C2B3EBBD4D939E35F95761487 /* FIRSendVerificationCodeResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C8DC147EFC61F95DA3FC13996255591 /* FIRSendVerificationCodeResponse.h */; settings = {ATTRIBUTES = (Project, ); }; }; + D28C130F5F3012F587CC16763CBD3DBE /* collection_change_builder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9F85C1C2A16FC96066DC6B0C91D73A50 /* collection_change_builder.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + D2FF6C9EB0E30EA6651632519832710B /* FBLPromiseError.h in Headers */ = {isa = PBXBuildFile; fileRef = 037723208A972F6D4CD565FBB23FEDBC /* FBLPromiseError.h */; }; + D32191E7AD6C6BB010E5F4C81D7395DE /* FIRCLSCompactUnwind.h in Headers */ = {isa = PBXBuildFile; fileRef = 63E633213C15674BF6EE752DA839F4D0 /* FIRCLSCompactUnwind.h */; settings = {ATTRIBUTES = (Project, ); }; }; + D331917270761ACCF9DBDCCF1EA3F4D9 /* comparator.h in Headers */ = {isa = PBXBuildFile; fileRef = 9734D99905E6B8202876C55E05FC392E /* comparator.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D3D8C379C6E4FB487E5ABD6800AD7B7E /* DispatchQueue+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C312DD383F490C02B5A71E38CF3802D /* DispatchQueue+Alamofire.swift */; }; + D3DB9F1DF95D7732960C4A55972C115E /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6133E402BA8E16E6642B59393592D3F4 /* Accelerate.framework */; }; + D3E8B5F3E1D4D7A8A24E95349E6FFC3D /* log_format.h in Headers */ = {isa = PBXBuildFile; fileRef = 975D50E6136B6E412EDD8DBEC3D92B2E /* log_format.h */; settings = {ATTRIBUTES = (Project, ); }; }; + D3EB776ADD095A481488388B2899EB6E /* posix_logger.h in Headers */ = {isa = PBXBuildFile; fileRef = 84A39F6F4D4BDBC8FC579CADBC222BD4 /* posix_logger.h */; settings = {ATTRIBUTES = (Project, ); }; }; + D428922198BA1D5F1D0EDE1CEA10F0D1 /* FIRVerifyAssertionRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 8D8898271E7DCFDA5AD4581F1709420C /* FIRVerifyAssertionRequest.h */; settings = {ATTRIBUTES = (Project, ); }; }; + D42D6D95E98658FDEC52B0B4EAC66DE2 /* FIRComponentContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 305EA5D5EE4C80D4090DBE62176EEF93 /* FIRComponentContainer.m */; }; + D4677C1E658CE5A1A343D784BAA5DE65 /* FIRCLSPackageReportOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = E3E60FB6268D822CE2681F2A2F1CF1CB /* FIRCLSPackageReportOperation.m */; }; + D49D62E7098A8AA1E441B9EE644FD4D8 /* FSyncPoint.h in Headers */ = {isa = PBXBuildFile; fileRef = F1DB0044569C7F340371466F6853621C /* FSyncPoint.h */; settings = {ATTRIBUTES = (Project, ); }; }; + D4E973125B9FBDCF6C99FE00C47B7B40 /* FIRCLSInternalReport.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BB5E55A11E483CC60C9D5718C2B62FB /* FIRCLSInternalReport.m */; }; + D51D187A0FF24AA5F7B8E60B4F9B74D7 /* FIRInstanceIDTokenOperation+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = B8FAB6CC1DDAAF761AAE56B6FCF360D4 /* FIRInstanceIDTokenOperation+Private.h */; settings = {ATTRIBUTES = (Project, ); }; }; + D550A661B3494A4B35F2C5D80AE9347F /* GDTCORTransformer.h in Headers */ = {isa = PBXBuildFile; fileRef = 5B5835EC2334D400F3E828323AF7B311 /* GDTCORTransformer.h */; settings = {ATTRIBUTES = (Private, ); }; }; + D5AC6BE3804A9DABDBAC1A056ED2D206 /* Pods-GeekbrainsUI-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 43556C7CE86F4F7EA7D42D92CC4B0A64 /* Pods-GeekbrainsUI-dummy.m */; }; + D5DBB44B8AAAB56BE183604E59B34639 /* FIRVerifyAssertionResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = A0828F754A3C7E4871AC36A1ED72B713 /* FIRVerifyAssertionResponse.m */; }; + D659C1F36249732CD1FF09E719F78D84 /* format.cc in Sources */ = {isa = PBXBuildFile; fileRef = F30DA53A86EA3F366531DA93930A18D1 /* format.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + D65C254F5ABF2CB5ECEE50FE8F8E1A80 /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F76C10D403D26FC1854D798DD54CFD5 /* Response.swift */; }; + D667753105D7C0FFD7C1E073421BEE42 /* FBLPromise+Always.h in Headers */ = {isa = PBXBuildFile; fileRef = F1A72EEFE9C788BE1B755C160E95C8F2 /* FBLPromise+Always.h */; }; + D6ABCD9AD8E627F3D5DB77F54A1DD905 /* FirebaseCrashlytics-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 17866E99D6426B8B2E450403A6E5FFD2 /* FirebaseCrashlytics-dummy.m */; }; + D6BDC66B20876EEA2063D3839B9BC2AC /* FIRCLSUserLogging.h in Headers */ = {isa = PBXBuildFile; fileRef = 19B43B6463DEB5A47027B4084F472659 /* FIRCLSUserLogging.h */; settings = {ATTRIBUTES = (Project, ); }; }; + D6F9569A0CFAB7D4C0A34EFDA3C41D88 /* FIRDeleteAccountResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 19FB754EB4099F557531F6709EAC9E10 /* FIRDeleteAccountResponse.h */; settings = {ATTRIBUTES = (Project, ); }; }; + D6F9AB919063E6A05BD96C089D8F4212 /* FWriteRecord.h in Headers */ = {isa = PBXBuildFile; fileRef = 67CAF2E79A38802580080D85D7F4314D /* FWriteRecord.h */; settings = {ATTRIBUTES = (Project, ); }; }; + D791B8B87509DE26CC906CB5C9142DB6 /* FIRInstanceIDCheckinStore.h in Headers */ = {isa = PBXBuildFile; fileRef = EF5376E76A1797CA02135B3CB20439F9 /* FIRInstanceIDCheckinStore.h */; settings = {ATTRIBUTES = (Project, ); }; }; + D79F2EC9B39EAF1BF9BD42D340E88054 /* FTreeNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 5649E1776CB127F532FB761BCE7C3A02 /* FTreeNode.m */; }; + D7C855A8C3AFE0D93B5BEF9445155203 /* FView.h in Headers */ = {isa = PBXBuildFile; fileRef = E1926F498A9AA6FE52584068A889ABF3 /* FView.h */; settings = {ATTRIBUTES = (Project, ); }; }; + D8019DAAAF812FE45A5A4F7BF18F210D /* FAtomicNumber.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AA4910C2C27CDBE0B5098825D5EF432 /* FAtomicNumber.m */; }; + D81B8330CB76689DC9D2BB45BA91B7FE /* GDTCORDataFuture.h in Headers */ = {isa = PBXBuildFile; fileRef = B830D4D6C294ED178AD87E57C9ADA7FF /* GDTCORDataFuture.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D822B3AE340BD0DCE67AD0F0AC648305 /* GDTCORRegistrar_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 2AAA2976A5267694F455FD69D8CF7288 /* GDTCORRegistrar_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + D844CF4AE7817A9A17D4BEC4BC39598E /* RLMSyncPermission.h in Headers */ = {isa = PBXBuildFile; fileRef = 887624AC0D176DFBC69C78021A0CF147 /* RLMSyncPermission.h */; }; + D844F95AE779A6741600EBF37CF3A0FC /* FCompoundHash.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A84B7F161963279BD754667535C4183 /* FCompoundHash.m */; }; + D8641688AC7D4A3A10DA3D566CE57B35 /* FIRAuthRequestConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 1E13C163D44E2F284341E396BB0A7707 /* FIRAuthRequestConfiguration.h */; settings = {ATTRIBUTES = (Project, ); }; }; + D87888C7A3F26BFAD3127B56D8AB474E /* object_store.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 18D0105B5764541095FBD8092705172C /* object_store.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + D8F0096D7A2922E9AAFB6BFAE7B14915 /* FIREmailLinkSignInRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 18FF45BE694A4ACF5279DDC418ED779A /* FIREmailLinkSignInRequest.m */; }; + D9600126A179DC4CC2B7298CD6471FA0 /* c.h in Headers */ = {isa = PBXBuildFile; fileRef = 9ACB6E37C7A61A4D3362558603C83B0C /* c.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D9626CA178F66A577FED650042D40066 /* FIRCLSMachOBinary.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B33D0223995B0E6C58FB40F012E2D1F /* FIRCLSMachOBinary.m */; }; + D966EB20806995F4E2E24B57783A6BB0 /* iterator.h in Headers */ = {isa = PBXBuildFile; fileRef = CAB84098B3AFF0DFFCDFDC38B55F2DDF /* iterator.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D9A562AFDD4F455C8E535CFF572DB932 /* FIRGetProjectConfigResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = D9351244ADF4359E07B688F626D2B5AE /* FIRGetProjectConfigResponse.h */; settings = {ATTRIBUTES = (Project, ); }; }; + DA1C37B83E539D74EA57667414446203 /* GoogleDataTransport-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 2FAEFF7F1001278848CAD0FC8200F1D0 /* GoogleDataTransport-dummy.m */; }; + DA783A36FD46D9A3D8A0CC6DBA44665E /* FBLPromise+Do.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 5E7434C839FCA37203C0A13316CB7E88 /* FBLPromise+Do.h */; }; + DA84AF88D7E5CD43D8BDE2CBC55296BD /* RLMRealmConfiguration+Sync.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2E665B2C77520F4B9C4C1FC99B6178F3 /* RLMRealmConfiguration+Sync.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + DABC0FC6B581CBDD00B12B5AB7EDAE2F /* FIRVerifyPhoneNumberResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = FB5DEF08A4DBF5EFC56AE6DEFA7825A6 /* FIRVerifyPhoneNumberResponse.m */; }; + DAEC3CBE7296D885165AF09571FD1500 /* FIRCLSMachException.c in Sources */ = {isa = PBXBuildFile; fileRef = 0137250CDC9EDE8A2D5EBBA6FD7658DA /* FIRCLSMachException.c */; }; + DB06C563A10D7BEE7495AB680C1D0243 /* FIRVerifyCustomTokenRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 7E579F8382EEFCD134AD0E3538EB38EC /* FIRVerifyCustomTokenRequest.h */; settings = {ATTRIBUTES = (Project, ); }; }; + DB7596FCF437169251CFE9FEAE439569 /* FBLPromise.h in Headers */ = {isa = PBXBuildFile; fileRef = 66B0B6220CDA636E37A9440D71E8C1F4 /* FBLPromise.h */; }; + DB94BFB21436F7294C66D5CC40307397 /* RLMRealm.h in Headers */ = {isa = PBXBuildFile; fileRef = E05BD9106CFD3B0749792B5578D1D3B9 /* RLMRealm.h */; }; + DB9B30A26FCC2B91EF7A13E52D57B736 /* GULMutableDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F5D5F1A5915467DCA49F80BEBDA35D7 /* GULMutableDictionary.m */; }; + DC240DCD9D261F3CF700BCB3683E229E /* FIRAuthAppCredential.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F5C590A93C8398FAFCB14C57DD38BE4 /* FIRAuthAppCredential.m */; }; + DC7AFA4C9D4910DF09D0D17F9E7A1299 /* FListenComplete.h in Headers */ = {isa = PBXBuildFile; fileRef = 77FF98C591A214884B7A2823DD3E695B /* FListenComplete.h */; settings = {ATTRIBUTES = (Project, ); }; }; + DC7BB1AE84A079521289E8D2DD19A2ED /* FIRInstallationsKeychainUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = B2B9CFF9F6610ED23E661A8837C20DA3 /* FIRInstallationsKeychainUtils.m */; }; + DCB11F88D276324F226C7BD60A394FF6 /* FRepo_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DFB179009B0825D8D02E8E8104ADBAD /* FRepo_Private.h */; settings = {ATTRIBUTES = (Project, ); }; }; + DCBF0C3F62459C8DA2ACA422A9ACB164 /* FIRSecureTokenService.h in Headers */ = {isa = PBXBuildFile; fileRef = C80B8C0BA49A935A8C34747C21AD782F /* FIRSecureTokenService.h */; settings = {ATTRIBUTES = (Project, ); }; }; + DD42E8C0D491D9E66325270BE17AB506 /* FIRVerifyPasswordResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = CC5276A017618A24E6C85A878E76F4BA /* FIRVerifyPasswordResponse.m */; }; + DD43604C29F189E84FD25A3FB7EF5938 /* FIRCLSConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 9534287083E1F8DC40D023CAC0E0F310 /* FIRCLSConstants.m */; }; + DD57A8FDAB3C4E21A2D8178E615DBC2B /* GDTCORUploadPackage_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D45C50A9735924375C923A4C204802D /* GDTCORUploadPackage_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + DD76BA97F4C10B0FD53483BC8356C895 /* RLMSyncPermission.mm in Sources */ = {isa = PBXBuildFile; fileRef = B1259AB3BC80BACF8D0BC98DD0EAD51E /* RLMSyncPermission.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + DD9084988260C41FB166F565F0BE7C8C /* FIRCLSURLSessionDataTask_PrivateMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 6DDD8062FCA61B54A3D2C4FDBBB036D0 /* FIRCLSURLSessionDataTask_PrivateMethods.h */; settings = {ATTRIBUTES = (Project, ); }; }; + DD9A1E9F01CC5B146C6D2A3B269FBECD /* FIRAuthErrors.h in Headers */ = {isa = PBXBuildFile; fileRef = B46827FEC6AC5CD82B5F7C24704F9936 /* FIRAuthErrors.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DDC90A2A4E9893B1AC13091CDDB71F42 /* export.h in Headers */ = {isa = PBXBuildFile; fileRef = 8C6F145D9591E35C4B366950416B55E8 /* export.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DE152BBBB34FB2781BC240CAF46D6D9C /* FIRCLSURLSessionAvailability.h in Headers */ = {isa = PBXBuildFile; fileRef = 5EC5794E1FB56C2379167138CEA77AA2 /* FIRCLSURLSessionAvailability.h */; settings = {ATTRIBUTES = (Project, ); }; }; + DEDC2DF805E7A33039F50B4CF5385B03 /* FIRCLSFABAsyncOperation_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 280E81411122E10A4A03FF10AD9C6A26 /* FIRCLSFABAsyncOperation_Private.h */; settings = {ATTRIBUTES = (Project, ); }; }; + DEFBA93DF8FAAA413E7CC482B34A13F1 /* FIRCLSURLSessionDownloadTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 6275266DDE055A2E1BCB1626FD4239DF /* FIRCLSURLSessionDownloadTask.m */; }; + DF0B93C62AB59CB66B51B26CABF146D7 /* FRepoInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 5144D239598058957529301947B65E99 /* FRepoInfo.h */; settings = {ATTRIBUTES = (Project, ); }; }; + DF5026C235F0454969EAF0557BBBBDB3 /* FAtomicNumber.h in Headers */ = {isa = PBXBuildFile; fileRef = 9ECA05096F49D5C2C0D3D1C2A3130DA5 /* FAtomicNumber.h */; settings = {ATTRIBUTES = (Project, ); }; }; + DF9717EF87618FDD7C66DD28E1C60337 /* FIRCLSSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = 44C335C2BC7873351D63E1C641F7DA5E /* FIRCLSSignal.h */; settings = {ATTRIBUTES = (Project, ); }; }; + DFBC1547B874C0E0749B2A669FE5F668 /* FBLPromise+Race.m in Sources */ = {isa = PBXBuildFile; fileRef = F8CEA8779408A7E78CCCCBDD04414385 /* FBLPromise+Race.m */; }; + DFCD6C774A03896A6DC1A360602AC905 /* testharness.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5CBDE172A494EEE7A9545B91E85F13F1 /* testharness.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + DFF0D5F0FE43F08D2CD0AF3013D46207 /* FIRAuthKeychainServices.h in Headers */ = {isa = PBXBuildFile; fileRef = 3AB877196209774BF84D1C73F0BB5CDB /* FIRAuthKeychainServices.h */; settings = {ATTRIBUTES = (Project, ); }; }; + E04BEC6F14A0260CE4638DE000E9CBC8 /* RLMResults_Private.h in Copy . Private Headers */ = {isa = PBXBuildFile; fileRef = 1D593AEC5637E10E09C61C612DADEF44 /* RLMResults_Private.h */; }; + E05C21DED9A65A758A143126C6D5D149 /* FIRDataSnapshot_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 239AF3F89B458885B216E48410EAE11F /* FIRDataSnapshot_Private.h */; settings = {ATTRIBUTES = (Project, ); }; }; + E0977780511A3EF63ECEDCB1301BCF1D /* GULNetwork.h in Headers */ = {isa = PBXBuildFile; fileRef = F7FB56B436AA1F5E67E74AAD1646C026 /* GULNetwork.h */; settings = {ATTRIBUTES = (Private, ); }; }; + E0C6DE864BA14F2541627635DA3C9A7F /* FPath.h in Headers */ = {isa = PBXBuildFile; fileRef = 983140C99DED04F2EAC250131C4833B2 /* FPath.h */; settings = {ATTRIBUTES = (Project, ); }; }; + E17CF26F8F461FEE9FE9247D7EFAE10F /* FIRRetryHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = FFD782C9E7773E607256D36F128C9C31 /* FIRRetryHelper.h */; settings = {ATTRIBUTES = (Project, ); }; }; + E1AEB6F788D7310B9494846551BBF386 /* FLeafNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D7BD54C8A226B56A4E8F990B0F498279 /* FLeafNode.m */; }; + E1C04EBBCE44BD3141F4EB0A81118085 /* Realm.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 8C227158CEFB05EFA49A1D7A4A68EF82 /* Realm.h */; }; + E1D49C144D54C64E4343428EDCB10AB9 /* FIRDatabaseQuery_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = C7972622EF19B74B3BFA5118DEA5FE5F /* FIRDatabaseQuery_Private.h */; settings = {ATTRIBUTES = (Project, ); }; }; + E1ED363967D9BBBD54C23B37A0EE43B7 /* version_set.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2014970201009EBB1DD94C5CE41A7FF0 /* version_set.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + E22BB3552C77566B1B6955CE66218BCD /* Results.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A78F3E5F4F2D135399B1A15D5C933D8 /* Results.swift */; }; + E232D39A8F88B368FC05D31638BD99D5 /* FIRVerifyPasswordRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 67D6E1E49E9266C174C79BE4873DEE72 /* FIRVerifyPasswordRequest.h */; settings = {ATTRIBUTES = (Project, ); }; }; + E2A888098F34332DDA3D6D348A8339C9 /* FTupleSetIdPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EACD418D36D7A8783BD11A9111EF64B /* FTupleSetIdPath.m */; }; + E2C824B5BD5A338F7F02002AEA350F65 /* FIRInstallations.h in Headers */ = {isa = PBXBuildFile; fileRef = 57B386B4636728846C6CC917511CD61B /* FIRInstallations.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E351C5B3CD9C831D1472B45FD6028CA6 /* GoogleDataTransportCCTSupport-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C6BC7FD320F562050D0FF3A098FC56F /* GoogleDataTransportCCTSupport-dummy.m */; }; + E365FD26B9B14AD0B39CF8067DC83D39 /* FBLPromise+Recover.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 4C6616311432B3C00DD556941EC5A1CA /* FBLPromise+Recover.h */; }; + E372C5DB9505173CE808FAE38016D9D5 /* RLMProperty_Private.h in Copy . Private Headers */ = {isa = PBXBuildFile; fileRef = C74A86C48C177012E0045DC846302FED /* RLMProperty_Private.h */; }; + E3747EC31FCCA97D75A81FC700CF7E24 /* ResponseSerialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE96C47F2560AA0D7B152AAB26C9E931 /* ResponseSerialization.swift */; }; + E3853D2F683556A120B54A229C7BF185 /* pb.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EA2FE6D1C42613A812FBAE3318FA87 /* pb.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E387ADCB6A1B0D5A79BC68C0EDF5D5F1 /* GULNSData+zlib.h in Headers */ = {isa = PBXBuildFile; fileRef = 70440E94BFBD04E9127CB904A9F0BB6A /* GULNSData+zlib.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E3A82EF1BFEB4A449FFE7FC26AEC1FB9 /* RLMSyncSessionRefreshHandle.mm in Sources */ = {isa = PBXBuildFile; fileRef = 12A29852C18DA5FA1CE5E3A8ECA65A55 /* RLMSyncSessionRefreshHandle.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + E3C9D247024140F47BE6C2E78BE4A8A1 /* FIRCLSContext.h in Headers */ = {isa = PBXBuildFile; fileRef = ACB46B34666BE45BFC53AC7D3C99A144 /* FIRCLSContext.h */; settings = {ATTRIBUTES = (Project, ); }; }; + E40BDD639B905C0F84E5FEDFF8A06489 /* FIRCLSFABHost.h in Headers */ = {isa = PBXBuildFile; fileRef = 066EF248F340E2465710C78E9B311ADE /* FIRCLSFABHost.h */; settings = {ATTRIBUTES = (Project, ); }; }; + E47688CA299B6C59B4D64550B09CFCAB /* FIRSecureStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = BAB0E9E2A5876C4EF0CF337514EDCF06 /* FIRSecureStorage.m */; }; + E57EBC2383EE0C6A61D1D0A46FE47383 /* RLMThreadSafeReference.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 8F76B1EE6D2328DF902BCFC3CEFFDFF9 /* RLMThreadSafeReference.h */; }; + E5D04DC36C728FBFA7556441F0C9F671 /* FIRAEvent+Internal.m in Sources */ = {isa = PBXBuildFile; fileRef = 0ADFAE923FC1E34FC913A0E76EC991AE /* FIRAEvent+Internal.m */; }; + E60769759BCF14F38093666B8C874CF3 /* FIRInstanceIDVersionUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B12B0B698ECE39D5E6EDB89687CCC6D /* FIRInstanceIDVersionUtilities.m */; }; + E61211F04C7ED15A8E89A7E6795BA17D /* FIRInstanceIDLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = B4BC511ECBBFA8ECB40030E727A947D3 /* FIRInstanceIDLogger.m */; }; + E6255999FD8D4E7F135B69ED9AD241B2 /* FPriorityIndex.h in Headers */ = {isa = PBXBuildFile; fileRef = C9A08033F18CE85182378A9E946680D8 /* FPriorityIndex.h */; settings = {ATTRIBUTES = (Project, ); }; }; + E62B4E6ED2B28C9C115E924910A9B324 /* FIRCLSURLSessionUploadTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 504885B54A8E74421A4A94921F685393 /* FIRCLSURLSessionUploadTask.m */; }; + E673B58F6FFC7F910AE4162B1C5B3AB6 /* RLMOptionalBase.h in Copy . Private Headers */ = {isa = PBXBuildFile; fileRef = C44C40D4B3AF4238A0A8521E9F1C7F77 /* RLMOptionalBase.h */; }; + E677D8CB0C348F332D6034E697843702 /* FImmutableSortedSet.h in Headers */ = {isa = PBXBuildFile; fileRef = DF9A521F94DF28771A2F643845C79C17 /* FImmutableSortedSet.h */; settings = {ATTRIBUTES = (Project, ); }; }; + E6A59169AB160B828757961CC391334C /* List.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1C30EE032FA32667014346FEB96DAA /* List.swift */; }; + E6A9D9A43516535E9F51FB609AB6584A /* FIRPhoneAuthProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 51ABE87E1BDF51DA9905E18093D794E7 /* FIRPhoneAuthProvider.m */; }; + E6C4A27C6EF10EDE432A71D21CAFE003 /* FPersistentConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = C673995A49F14FBF2E4228285E650AAE /* FPersistentConnection.h */; settings = {ATTRIBUTES = (Project, ); }; }; + E6F2BD6EA9241D8C1D1D57B41E81A38B /* FIRPhoneAuthCredential.h in Headers */ = {isa = PBXBuildFile; fileRef = ECA7E4BA5A6CDB154033CC0AF23ACF68 /* FIRPhoneAuthCredential.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E7089B09922ED63DBA188CCD14ACCCFF /* FCachePolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = 62FD01C83412004B1C76E346EAEB52FD /* FCachePolicy.m */; }; + E77A43B269C786167E976A13F3CB71D3 /* GULLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 889D76680F2283660A9FD4C0257665FC /* GULLogger.h */; settings = {ATTRIBUTES = (Private, ); }; }; + E79F105F571CA41CD85847D8E87051AC /* FIRVerifyClientRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = A827193BF78DD12B4786B06D1EA13EF8 /* FIRVerifyClientRequest.m */; }; + E7A47E2A004C4E692E3708A2C25650A1 /* FBLPromise+Wrap.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 1D1B6EB1EB1CC689CA6DC51E84C01C2A /* FBLPromise+Wrap.h */; }; + E7D7D875688EDFBCB8F4C2C1F4F5B5E5 /* String+MD5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E9ED28562D1435FA27EF70F70D9AAE0 /* String+MD5.swift */; }; + E7F3104033356EB1C7CF50CF9F50BA2D /* FIRCLSURLSessionTask.h in Headers */ = {isa = PBXBuildFile; fileRef = A80776476F1BB3131E33DC6C63A170C8 /* FIRCLSURLSessionTask.h */; settings = {ATTRIBUTES = (Project, ); }; }; + E83D2896CD2930EB85C3343E33752B47 /* RLMResults.mm in Sources */ = {isa = PBXBuildFile; fileRef = 89B30EF9BFCEE26E9674E5CAA2473156 /* RLMResults.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + E85BA92055A041888C8CA920D402335B /* FRepo.m in Sources */ = {isa = PBXBuildFile; fileRef = 87961C9BE2777F5B6B0C58DBBDFA3324 /* FRepo.m */; }; + E86ACBB0F434E9F789171A39E6E6B4CA /* FIRAEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 6277C8B46289F5D129CC3124B79552D8 /* FIRAEvent.h */; settings = {ATTRIBUTES = (Project, ); }; }; + E88471D186D133AB49FA4BA3177DA6B6 /* GDTCORRegistrar.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AA8410864480F8EAE29609C2480122F /* GDTCORRegistrar.m */; }; + E8A754B37F44784D8B78A872DE6B2C92 /* Realm.h in Headers */ = {isa = PBXBuildFile; fileRef = 8C227158CEFB05EFA49A1D7A4A68EF82 /* Realm.h */; }; + E8E3D470C5EC22993A18AA6D4A1885E3 /* FCompoundHash.h in Headers */ = {isa = PBXBuildFile; fileRef = FD7DE28F1856D146FBFABE964B3B5C47 /* FCompoundHash.h */; settings = {ATTRIBUTES = (Project, ); }; }; + E8F7AF6D02AC6BF3C0981306D8098D34 /* FIRGoogleAuthProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 2FF6795513CF63D818090FDBBBEA2E17 /* FIRGoogleAuthProvider.m */; }; + E9607A6223E3F82761A85937A89561ED /* two_level_iterator.h in Headers */ = {isa = PBXBuildFile; fileRef = 7128EBFC506481FE5CC45958E38FE753 /* two_level_iterator.h */; settings = {ATTRIBUTES = (Project, ); }; }; + EA00EE88744C5881291DC07EB5D3F878 /* FIRCLSPackageReportOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 10B86ED0811ED153ABD059C908466AF0 /* FIRCLSPackageReportOperation.h */; settings = {ATTRIBUTES = (Project, ); }; }; + EA04EECEF9A81CD4BAF55CA40AA102EC /* GULSwizzler.h in Headers */ = {isa = PBXBuildFile; fileRef = 29C7D31341E23D15A2CE7EFACFF94955 /* GULSwizzler.h */; settings = {ATTRIBUTES = (Private, ); }; }; + EA27021598F9B99B1C8F672B3C90A429 /* FIRComponentType.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C4470BA20E3DEEAF93A0FFAC0CFEFA6 /* FIRComponentType.m */; }; + EA986A7DD96309B973A756B7AFFBE78C /* FIRInstanceIDLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED3622365FA02BAA65DEF9DB35A358C /* FIRInstanceIDLogger.h */; settings = {ATTRIBUTES = (Project, ); }; }; + EABFBF6EE7D500715C1FFF7A39D6275F /* FIRTwitterAuthCredential.h in Headers */ = {isa = PBXBuildFile; fileRef = D1F49BB693EFE972B666A13C45CD1B9D /* FIRTwitterAuthCredential.h */; settings = {ATTRIBUTES = (Project, ); }; }; + EACC292557AC609ACE5385077CDC55A5 /* FWriteTreeRef.m in Sources */ = {isa = PBXBuildFile; fileRef = EA10D39A174D32B831D388D410967DBA /* FWriteTreeRef.m */; }; + EAF8DAA936BD292598594A4BFFBE8761 /* FPruneForest.m in Sources */ = {isa = PBXBuildFile; fileRef = AE57CCAA2CE76A131F0045A0DEE4BF61 /* FPruneForest.m */; }; + EB3BB9186B5EF80F3CD901B7CC71E10C /* FClock.m in Sources */ = {isa = PBXBuildFile; fileRef = FE97F7C3CA00403626ACF85E09BE6F0C /* FClock.m */; }; + EB514A44D99F637EF888B37A8C35EDAC /* FIRActionCodeSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E3AE9D51BAC10905A4242A2853D4BC7 /* FIRActionCodeSettings.m */; }; + EB806E157E83941EAF544783625C08FF /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64025D1D442A26509C94DDDE4BCBF577 /* Foundation.framework */; }; + EB86DF3DFB2AF93D4DB8CDD6F7295F73 /* RLMObject_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DB20C35765E54E044401445CD25ADB73 /* RLMObject_Private.h */; }; + EB8B9327BA306669979C106B3679A79B /* FWebSocketConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = 2C2E2C159D6A12D606AEC93F92C6A9B4 /* FWebSocketConnection.h */; settings = {ATTRIBUTES = (Project, ); }; }; + EBA9B582E506A1697DE30B29B1A7E1A3 /* GULAppEnvironmentUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = CE591618BB2FD1CDAF280E66FE7F3F6D /* GULAppEnvironmentUtil.h */; settings = {ATTRIBUTES = (Private, ); }; }; + EBE6A5953B78D5F511FE2506014C4147 /* FIRGitHubAuthProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ED92C57899F320C729B4357A050A1A2 /* FIRGitHubAuthProvider.m */; }; + EC4DCA3FB03E03F9EE2C2A51BD70E35B /* FChildChangeAccumulator.h in Headers */ = {isa = PBXBuildFile; fileRef = 4AA009C94CE5B49342CE6C02D1B216B0 /* FChildChangeAccumulator.h */; settings = {ATTRIBUTES = (Project, ); }; }; + EC5EC3B78CC53AEBBB9A04FBD76708C6 /* FBLPromise+Delay.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 5D9F16D73D1B59742905EDD0C2966E3F /* FBLPromise+Delay.h */; }; + ECC12B7E36C9B220FB6395086BFC6FB8 /* NSData+SRB64Additions.m in Sources */ = {isa = PBXBuildFile; fileRef = 30C26803AF33201B20BEF6D6C4B3644A /* NSData+SRB64Additions.m */; }; + ECF337E4480792C1847DDFFA941624D4 /* FIRCLSURLBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = 29B851296EE421C0D17845278B4F85D9 /* FIRCLSURLBuilder.h */; settings = {ATTRIBUTES = (Project, ); }; }; + ECFAC50FD1F24FE502F6BC5C9A861FCE /* GDTCORStoredEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 066EA1C1FF6676A0B35DE90DB17930AB /* GDTCORStoredEvent.m */; }; + ED02A0F74AF2BFB5107CEDE59EEC9538 /* db_impl.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9E9D59523D289EC7F7D99A7F21CFE2BD /* db_impl.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + ED3035ABEBCD639F75DDC5C472795706 /* realm_coordinator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F87CA2091793D8973D13571079E16DCA /* realm_coordinator.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + EE19F864673AF83DB4C21CA8464455AC /* object.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 271C3E4D470D1279F3BDF15337AC90A3 /* object.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + EE2A5AE21E43B6A17BFFFE1695AF66FE /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64025D1D442A26509C94DDDE4BCBF577 /* Foundation.framework */; }; + EE530EDDE074E7E18356530C0D3F86BC /* FIRCLSAllocate.h in Headers */ = {isa = PBXBuildFile; fileRef = DFE1824C7288A210B76120880BFD42B5 /* FIRCLSAllocate.h */; settings = {ATTRIBUTES = (Project, ); }; }; + EE84A10723C774FD833000C2D65279EB /* FBLPromises.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = FCF20827CAA2AC54FFFECCB9B75F8DA7 /* FBLPromises.h */; }; + EE9F328D814934285A7D9283665DC603 /* FIRGoogleAuthProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 9E3DB5044C4A852A56DFBEC9210F6936 /* FIRGoogleAuthProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EEBFEC47FF999D55385F032EFE094C56 /* FIRInstallationsStoredAuthToken.h in Headers */ = {isa = PBXBuildFile; fileRef = 00D096F4988C4E2FC88C5A60A39A8359 /* FIRInstallationsStoredAuthToken.h */; settings = {ATTRIBUTES = (Project, ); }; }; + EEC5F93435E158CD75D96D711D85AE4B /* FIRServerValue.h in Headers */ = {isa = PBXBuildFile; fileRef = 684CC63082E6E3184BF9143A7929145F /* FIRServerValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EEF71627FA396EB479F77B2736DEE8A2 /* FIRAppAssociationRegistration.m in Sources */ = {isa = PBXBuildFile; fileRef = 424932417FD4CF653498EA1D5A70693F /* FIRAppAssociationRegistration.m */; }; + EEFB9D707C02D1770D9207FC55EFF4AD /* FTypedefs.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C0079799AE2339A85F7E62D4780C436 /* FTypedefs.h */; settings = {ATTRIBUTES = (Project, ); }; }; + EF11B740F9921F0BA68479755F55BE21 /* FIRUserMetadata_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = C8D360BB5DCD315E5A9DA1EE683CED0D /* FIRUserMetadata_Internal.h */; settings = {ATTRIBUTES = (Project, ); }; }; + EF1EA1700F8ED0FA06173C1AA5EF3936 /* FIRAuth.h in Headers */ = {isa = PBXBuildFile; fileRef = E657566D8C5E7C37652B9A892838BEF7 /* FIRAuth.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EF4FD948D37328E05C32D034A17FA6FC /* FIRGetAccountInfoRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 242F1FE16F59BBC9DA78B2379E82B553 /* FIRGetAccountInfoRequest.h */; settings = {ATTRIBUTES = (Project, ); }; }; + EF557B93D962C1C7D46AEB190528582D /* FIRInstallationsErrorUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 8994F6A0E3A70E01B98277557180A156 /* FIRInstallationsErrorUtil.m */; }; + EF6E8253DCFF30720547FCBAC9522CA0 /* FEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = D518F40A869BBAB3A4155E1BB9275EF8 /* FEvent.h */; settings = {ATTRIBUTES = (Project, ); }; }; + EFA6867F1BDDBE37A93ECF9AE99AD090 /* FIRErrorCode.h in Headers */ = {isa = PBXBuildFile; fileRef = 6363B094F036CD16B0446525FFE072C7 /* FIRErrorCode.h */; settings = {ATTRIBUTES = (Private, ); }; }; + EFC69E6D0A56DDC4350AD536565B1678 /* FBLPromise+Await.m in Sources */ = {isa = PBXBuildFile; fileRef = CBC0A66973B374C28F35611394D76F62 /* FBLPromise+Await.m */; }; + F03298753DA84C04036A6A3D24D96950 /* RLMClassInfo.mm in Sources */ = {isa = PBXBuildFile; fileRef = B537E201A244683EF2200A1913CA591C /* RLMClassInfo.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + F03C8054C64501A4E7CD4177C8A66E5D /* histogram.cc in Sources */ = {isa = PBXBuildFile; fileRef = 06BB1B8E0794DA761F9BD7359D2036F7 /* histogram.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + F0709DCF413ACE7F337DFAC0BA3B874B /* MemoryStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80D84EB3AF30412F46680999C05D159D /* MemoryStorage.swift */; }; + F070B603DA8F5E7BA55B688D183E0B03 /* RLMRealmConfiguration.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 434EA703D1FB868AB9B838002A4ED5B6 /* RLMRealmConfiguration.h */; }; + F0954D903D841D60F77422167F1D73E3 /* FIRInstanceIDStringEncoding.h in Headers */ = {isa = PBXBuildFile; fileRef = 8F50523F75B8500F411C5A032D73DE46 /* FIRInstanceIDStringEncoding.h */; settings = {ATTRIBUTES = (Project, ); }; }; + F0E89F7017249BCB3B2C1320F9413B2C /* ImageDataProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7434D3830778A43B297F00140DEDBB3 /* ImageDataProcessor.swift */; }; + F115628BFC2BC53DC023959AC44CF63E /* FirebaseInstanceID-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 8CF6F9311132EDF0F6BD49DE3F5FA5E1 /* FirebaseInstanceID-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F11957624E05D92BBA9303C81898DB72 /* FNamedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 840F659E9BCA424EB43A2CC83E1E8404 /* FNamedNode.m */; }; + F12A4DEA7641F0A7142779EC9836D2D1 /* FAuthTokenProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 739B2D7FD0A87C1FAE63F41227CA2D46 /* FAuthTokenProvider.m */; }; + F133A864D149BE7E3B8ED12A0FE0DC23 /* GDTCORClock.h in Headers */ = {isa = PBXBuildFile; fileRef = 6065ADD321D1A7A79F4A25057175A3E4 /* GDTCORClock.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F13F2AA7F2E6D95A181CAB99B900D531 /* ServerTrustPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13ADE9639DE325D6DB8F9E46AA6FCCD3 /* ServerTrustPolicy.swift */; }; + F15001312B4A1AFC5C0C39DB4B63962A /* RLMResults_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 1D593AEC5637E10E09C61C612DADEF44 /* RLMResults_Private.h */; }; + F1503B963287A68AD0D5BBEDA7CF4074 /* FPathIndex.m in Sources */ = {isa = PBXBuildFile; fileRef = 53773438319C44A00E27C7B5EC550A5D /* FPathIndex.m */; }; + F1D14A94564CA46BB81E40133C5A2434 /* FTupleNodePath.m in Sources */ = {isa = PBXBuildFile; fileRef = E847EEF4096BD3BCF215C1386938844E /* FTupleNodePath.m */; }; + F28BFCB7597D463ECD8CF3DF3B596211 /* Aliases.swift in Sources */ = {isa = PBXBuildFile; fileRef = F970D1755FF2BF74A11AA024C79EA2E0 /* Aliases.swift */; }; + F2B2B5E5B963145D27E6F7F0B97278B3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64025D1D442A26509C94DDDE4BCBF577 /* Foundation.framework */; }; + F3365FB46BF0E869D92A464B07BB9948 /* RLMRealmConfiguration_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = D31A28FF5B008109A4F47D9CE15E58B2 /* RLMRealmConfiguration_Private.h */; }; + F37356E1CB7F1FB29DCADBF6A7DAED23 /* FIRHeartbeatInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 2A82FCB7D1E389CB7FB62D54B1074343 /* FIRHeartbeatInfo.h */; settings = {ATTRIBUTES = (Private, ); }; }; + F381F5B962BAF56292D75C8CBE72CDF9 /* pb_common.c in Sources */ = {isa = PBXBuildFile; fileRef = DF5999D73CC6280F3CC4FCA82134CA7A /* pb_common.c */; settings = {COMPILER_FLAGS = "-fno-objc-arc -fno-objc-arc -fno-objc-arc"; }; }; + F44A9799A444E69EA9323A6760CF72EE /* FSparseSnapshotTree.h in Headers */ = {isa = PBXBuildFile; fileRef = A31792D94CAC5470CE07710F88771788 /* FSparseSnapshotTree.h */; settings = {ATTRIBUTES = (Project, ); }; }; + F4852C9980C392297EB26AB2134F20BF /* FIRInstanceIDCheckinService.h in Headers */ = {isa = PBXBuildFile; fileRef = 8ACDCCAD213FD226B322D9CCFFCAAA09 /* FIRInstanceIDCheckinService.h */; settings = {ATTRIBUTES = (Project, ); }; }; + F489AF2C3A4BAA41260095DAB13D5C17 /* RLMObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E543475F05FA2541F2CCB52DBEF8927 /* RLMObject.h */; }; + F4AC35F4E29621B3EB9C8FC5520E7BF6 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64025D1D442A26509C94DDDE4BCBF577 /* Foundation.framework */; }; + F4E1B21EA4746CAAA2951C1E4817F876 /* FIRDependency.h in Headers */ = {isa = PBXBuildFile; fileRef = A043B78C92D8D079E567AA643953AC40 /* FIRDependency.h */; settings = {ATTRIBUTES = (Private, ); }; }; + F4EAFFED2B6CCC28F1350707AE648818 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64025D1D442A26509C94DDDE4BCBF577 /* Foundation.framework */; }; + F4EDBD322437E1760123F3049920A076 /* FIRInstanceIDCombinedHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = DC7FFB1CAD9385389EC1B5E7A719010B /* FIRInstanceIDCombinedHandler.h */; settings = {ATTRIBUTES = (Project, ); }; }; + F4F6D9AD12A4CE730FAB880378FD7C60 /* GTMSessionFetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = EC48BC4E52678E214C5B571535F0FD8E /* GTMSessionFetcher.m */; }; + F527855C636F5EA01DDB5E43F986DD44 /* FIRCLSURLBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 742340252C9F30970E96B4A993368D3A /* FIRCLSURLBuilder.m */; }; + F52D7BC8DCF75C138925790FE62FBB6C /* FIndex.m in Sources */ = {isa = PBXBuildFile; fileRef = 59BAE894F428C0EBEC29AFC6A19B8F4D /* FIndex.m */; }; + F578887CD8E4D6C5C3EEEC9C36267E4D /* FIRAEvent+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B7352E913168EC86E1833303D5A8E3F /* FIRAEvent+Internal.h */; settings = {ATTRIBUTES = (Project, ); }; }; + F5C87E584986486407916F755C18FD05 /* FRangedFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 72B3FC42C46A789BA9EA8C45EFDEE3C5 /* FRangedFilter.h */; settings = {ATTRIBUTES = (Project, ); }; }; + F66ED581D2EBD170ABAD4EC0206B6C5C /* UIButton+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EAA16DB197D1E1AF196BDAF5D6EE86 /* UIButton+Kingfisher.swift */; }; + F6C053B5EC1EAFAF1F600DED4C8D3C51 /* FIRCoreDiagnosticsConnector.h in Headers */ = {isa = PBXBuildFile; fileRef = 5B61095D647294428C62980474737F22 /* FIRCoreDiagnosticsConnector.h */; settings = {ATTRIBUTES = (Private, ); }; }; + F6C22FF9F12BD6D6FC4C4892222F82E5 /* FTupleRemovedQueriesEvents.m in Sources */ = {isa = PBXBuildFile; fileRef = FFF2198200840B5706276A535E8A8B77 /* FTupleRemovedQueriesEvents.m */; }; + F6D77FD5A27B7709E861CA48E7BFD558 /* FIRGetProjectConfigResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 7348D3BA6708FE3622CA4B947A986519 /* FIRGetProjectConfigResponse.m */; }; + F6ED8BB10A3715184C077FFD5EAFD71A /* FIRFacebookAuthCredential.h in Headers */ = {isa = PBXBuildFile; fileRef = F4EE656688A0BDEB67D52D6B4C0F0150 /* FIRFacebookAuthCredential.h */; settings = {ATTRIBUTES = (Project, ); }; }; + F72E035551089237524CB10F8480E5D3 /* FIRCLSThreadState.c in Sources */ = {isa = PBXBuildFile; fileRef = 3F348EE51DA0A7FF10B6342F43FBB6E4 /* FIRCLSThreadState.c */; }; + F744615A5209D686B6C139D583B28B12 /* GDTCORReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = D507ADEDF13DE43ACCC74E7E20D6EB12 /* GDTCORReachability.m */; }; + F76444CD589D114686BB510A6066CE67 /* results_notifier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 29F6FF2DD8EE8092E26BF602E06784B1 /* results_notifier.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + F780F31334A5B069C756121AE82F008B /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F0C336156B678EF35851FE016FEAC04 /* Storage.swift */; }; + F789758B5B0731A48D4D83D933407363 /* FTuplePathValue.m in Sources */ = {isa = PBXBuildFile; fileRef = AFEAE24FA3CA9EDA209B5B2086A1836E /* FTuplePathValue.m */; }; + F78D3CFED096A10C6F48356B3125E7ED /* FIRAuthNotificationManager.h in Headers */ = {isa = PBXBuildFile; fileRef = B9166509EBF18D0943E4D8562A3C3CAA /* FIRAuthNotificationManager.h */; settings = {ATTRIBUTES = (Project, ); }; }; + F7B39B893327E7F2AEE270E4C6CA861B /* logging.h in Headers */ = {isa = PBXBuildFile; fileRef = BFAAA08675F94727874A6F041299FEBD /* logging.h */; settings = {ATTRIBUTES = (Project, ); }; }; + F7B74645E07C39771156A1FA413B98D4 /* Alamofire-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 51C104BFA0ECC362AD5D0AECB29D661A /* Alamofire-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F7C4C0980A7CD46EDCAAF366A722FFCD /* RLMRealm+Sync.mm in Sources */ = {isa = PBXBuildFile; fileRef = 330910696891AFD35E57ECB357CA3C1A /* RLMRealm+Sync.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + F8116420B37CA79D3BFACFFA8B4E4A15 /* RLMSyncConfiguration.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 1B855D092D33E62CC9867576BF1B3E8D /* RLMSyncConfiguration.h */; }; + F816D3A9EBBC2EB16DB0CEE0DCAAE776 /* FIRDeleteAccountResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 26FB8101B9FB466D0751B3CB5748F260 /* FIRDeleteAccountResponse.m */; }; + F8358EDA462C9CFD16619BE2AF438B5C /* FTupleOnDisconnect.m in Sources */ = {isa = PBXBuildFile; fileRef = AE4FF34D0DFF78A8C05317D4A10C2A36 /* FTupleOnDisconnect.m */; }; + F8A82726B7A671622749B22605941E29 /* FIRCLSOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 379C4299BB9ED1CBA0A5405CD9449C81 /* FIRCLSOperation.h */; settings = {ATTRIBUTES = (Project, ); }; }; + F8AD365E58CB0EFB79FBF9DBB2CD7EE3 /* FQuerySpec.h in Headers */ = {isa = PBXBuildFile; fileRef = 6EE6635D48F8FE1C35FB9578EAF4776B /* FQuerySpec.h */; settings = {ATTRIBUTES = (Project, ); }; }; + F8DEC63926F444A3C55A03FAB1A7E0E9 /* FIRCLSFeatures.h in Headers */ = {isa = PBXBuildFile; fileRef = 8FE9A7290B315D1210F54B1A597278EC /* FIRCLSFeatures.h */; settings = {ATTRIBUTES = (Project, ); }; }; + F90FE141FD6DA62DBEDC1D6C17E2DB7B /* FQueryParams.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FFDC32A91D6222B62D35ACE38DF0183 /* FQueryParams.m */; }; + F92BD30DFB4218BCD136C73A3EE04927 /* RLMRealm_Dynamic.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 03E058B6C2458630160C0A65EB9FD24F /* RLMRealm_Dynamic.h */; }; + F94C9281B3897CF5A1DAC4462E80D980 /* hash.h in Headers */ = {isa = PBXBuildFile; fileRef = 2E738E61D112BE0639284CE1910475F6 /* hash.h */; settings = {ATTRIBUTES = (Project, ); }; }; + F98C8818B05607F8507F1B47724C8E43 /* repair.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0BF9423C4AE3C584BD68D6E8F6E19B7C /* repair.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + F9CEDE306E79AB2DFD9744ECD1DAE87A /* RLMSyncConfiguration_Private.h in Copy . Private Headers */ = {isa = PBXBuildFile; fileRef = 279EF69F64F33BF43FDC19F468B28D41 /* RLMSyncConfiguration_Private.h */; }; + F9E1E2832DE5DDFCD116BD7A9D47A714 /* FIRCLSURLSessionDataTask.h in Headers */ = {isa = PBXBuildFile; fileRef = 28E542ED56611F4DE0D324E090838C02 /* FIRCLSURLSessionDataTask.h */; settings = {ATTRIBUTES = (Project, ); }; }; + FA3B86D66C5A29BFB22D4E06D170008E /* FIRCLSdSYM.h in Headers */ = {isa = PBXBuildFile; fileRef = 67593AB87D75C2524D1512196BCDF77C /* FIRCLSdSYM.h */; settings = {ATTRIBUTES = (Project, ); }; }; + FA731AA062F9E319CB39B8E1F719E538 /* FIRAuthAPNSToken.h in Headers */ = {isa = PBXBuildFile; fileRef = BC2992AF3809C2883734EBB8D3EC5609 /* FIRAuthAPNSToken.h */; settings = {ATTRIBUTES = (Project, ); }; }; + FA94BEB13D3E5EDDD4C0BFD5F11775F2 /* FViewCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 251BC7C8DC3F888A067DA6D70D010182 /* FViewCache.m */; }; + FB16D14072713E368969885E4ECD099B /* Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6091A1DCE0E3278DD93874739E79047 /* Optional.swift */; }; + FB1DC2E9C1E29C6641CF63A061C7717A /* RLMListBase.mm in Sources */ = {isa = PBXBuildFile; fileRef = EF14E699204FD227CC2454D8DC1680A4 /* RLMListBase.mm */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + FB78F741ECCF1E6DFEBE446BC390C1D2 /* FIRSendVerificationCodeResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F3F57166D63D9449D3C19EAE3B68788 /* FIRSendVerificationCodeResponse.m */; }; + FB985C4ECA7AC6D97912D7867E189B8C /* FQuerySpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 1DED2ACF11680CBA13D4FD04C0523775 /* FQuerySpec.m */; }; + FBE49255F27DF8F87C0B0833C3EC1A94 /* FirebaseDatabase-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = FF3EFA6069C2BF16A62F27DB2D7D7BDF /* FirebaseDatabase-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FC0BB1F18B568792F3990847ECC385E6 /* FIRAuthErrorUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = DFA4132E43065C4BC9B519AA54A5F287 /* FIRAuthErrorUtils.m */; }; + FC825F33466362D6389D57C108DCE054 /* FBLPromise+Then.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = A994DEA929EC73A3FECB99814A26BF0F /* FBLPromise+Then.h */; }; + FC99D9DC765D1D98CAA34C953111B8DD /* FIRCLSUnwind_x86.h in Headers */ = {isa = PBXBuildFile; fileRef = CEE91D635068604C4F9104A22986350B /* FIRCLSUnwind_x86.h */; settings = {ATTRIBUTES = (Project, ); }; }; + FD4FEA2A5FC79BE8E0039862906BCD34 /* builder.h in Headers */ = {isa = PBXBuildFile; fileRef = 0FF88F56210C6841FFB4F15504D58897 /* builder.h */; settings = {ATTRIBUTES = (Project, ); }; }; + FD62C5FE0B45A77753550EE79E30158B /* FIRAValue.h in Headers */ = {isa = PBXBuildFile; fileRef = 844026B8D96F62721AEEB3A2FD9310E2 /* FIRAValue.h */; settings = {ATTRIBUTES = (Project, ); }; }; + FD9914E68C106CC2C9A6D56DC5DC319C /* GDTCCTPrioritizer.m in Sources */ = {isa = PBXBuildFile; fileRef = E4D91F6E2EBB8D83AD58A27589C0C21F /* GDTCCTPrioritizer.m */; }; + FDA1A7CF340E7ED18F9BD24A8159A727 /* FirebaseCrashlytics-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = C2B7043CEF622A670BF8F6991A15AE7C /* FirebaseCrashlytics-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FDD7FDF9B50336F15AE17AE06AE8E680 /* FBLPromise+Catch.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = EA285255E24140F4364ADC9C02ADAAE5 /* FBLPromise+Catch.h */; }; + FE47EBDC1AED67B754D72FEDB3048AFA /* FIRInstanceIDCheckinStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F55AA6C4C4C66BDF9695A3801D2CAC0 /* FIRInstanceIDCheckinStore.m */; }; + FE54A3EFB4C58A133DEED7B9C0885FD9 /* comparator.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8E3ACF464DE946E7977DFFA1EEF5246A /* comparator.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + FE562041EE4B54BED07C5CE029019AC8 /* env.h in Headers */ = {isa = PBXBuildFile; fileRef = B9007A7BAAAE5B47A2A31441FDEDFD3E /* env.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FEAB0E502EA1A6C4F758823B3B4BDFFF /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1FDE17F9BA2AE72C1C1EFE70DC080EA /* ImageDownloader.swift */; }; + FEB1D452D618BF841E0C6D17EECD2489 /* FQueryParams.h in Headers */ = {isa = PBXBuildFile; fileRef = B9B6DDC21B935230DCFEFED3D43FD220 /* FQueryParams.h */; settings = {ATTRIBUTES = (Project, ); }; }; + FEC148147D381D36ABDEAB648CFDB90A /* RLMSchema_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 67C9D0962115956B0C47D1FD9F0B6F61 /* RLMSchema_Private.h */; }; + FEC6200CE6E46935071A783AFB805CC2 /* FIRInstanceIDCombinedHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = D2C9C3DA33A45CC253454B3C3D103093 /* FIRInstanceIDCombinedHandler.m */; }; + FF272F1B57CA44105BA30250A3206769 /* GDTCORUploader.h in Headers */ = {isa = PBXBuildFile; fileRef = CEC50CD22C78836638CBC19D7524122C /* GDTCORUploader.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FF27D379E021FB0C6E58343FAF9AE541 /* FIRAuthWebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3E43572942B487228C2EFDD3DBD087F6 /* FIRAuthWebViewController.m */; }; + FF312C27C7AB6D005FA7178A39B57DDD /* FBLPromise+Recover.m in Sources */ = {isa = PBXBuildFile; fileRef = 931030B1DBC02E1B5C039ACC9CEA2E9C /* FBLPromise+Recover.m */; }; + FF4B2BB0EF29444776166A439A9D4E30 /* async_open_task.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FE9B49FD53A34A6AB6186DA2B177EED9 /* async_open_task.cpp */; settings = {COMPILER_FLAGS = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"4.3.1\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC"; }; }; + FF7B730055D70EDBCB00F8595902151C /* GULAppDelegateSwizzler_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 0543F2CD37528CCEBC7A49CEB7FAC5D1 /* GULAppDelegateSwizzler_Private.h */; settings = {ATTRIBUTES = (Project, ); }; }; + FFA702EE09C59823F2FDF43C14CA0EB3 /* FBLPromise+Await.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 926F3482C65B41D6727B7F69D5725417 /* FBLPromise+Await.h */; }; + FFBB428073FC98895EDCE1BCF7E82DBF /* block.cc in Sources */ = {isa = PBXBuildFile; fileRef = CFD9FB653049C45B8E552C3BE27FDDAD /* block.cc */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 0B84150A0245B5BD277181C4173889EC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F4F25FCAC51B51FD5F986EB939BF1F87; + remoteInfo = GoogleDataTransportCCTSupport; + }; + 0C08E0DDA386FA7291C5CA39885F1AB1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 782725687624F8665247B84AB581BEB1; + remoteInfo = RealmSwift; + }; + 0C3A96F5C5F6E8CB1BBF34F043327B90 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D2B5E7DCCBBFB32341D857D01211A1A3; + remoteInfo = nanopb; + }; + 0E20D46E27234329076FE3950BBB7F99 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = B53D977A951AFC38B21751B706C1DF83; + remoteInfo = GoogleAppMeasurement; + }; + 0FF9A1E233A4125115F3EFBB2613C4E3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 5EB4B0B6DA6D5C0C3365733BEAA1C485; + remoteInfo = FirebaseCoreDiagnosticsInterop; + }; + 12DE0300AD3C264AE854729394E38954 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8D7F5D5DD528D21A72DC87ADA5B12E2D; + remoteInfo = GoogleUtilities; + }; + 208CC7E3E9A345211A226470ED36BF4C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 5EB4B0B6DA6D5C0C3365733BEAA1C485; + remoteInfo = FirebaseCoreDiagnosticsInterop; + }; + 2241096BF52D154C2390A33A7C459082 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 68494F30B4A13F8E5E88BCCAEC25B0A4; + remoteInfo = Realm; + }; + 25578E010FB0A6B99D69EB60D0AE8D8A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D676E21115185671D7258A56944ABE98; + remoteInfo = GTMSessionFetcher; + }; + 28698797F671D059112FD255CF577103 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = EAAA1AD3A8A1B59AB91319EE40752C6D; + remoteInfo = Alamofire; + }; + 28B025518E6FEA532380F4D63416552E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8D7F5D5DD528D21A72DC87ADA5B12E2D; + remoteInfo = GoogleUtilities; + }; + 3079E51A1863435C4700CAAD4289A707 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 2BBF7206D7FAC92C82A042A99C4A98F8; + remoteInfo = PromisesObjC; + }; + 31C009FEBEC375BDEBDB204259CB5706 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F4F25FCAC51B51FD5F986EB939BF1F87; + remoteInfo = GoogleDataTransportCCTSupport; + }; + 33BF6BAF5E852DDC34D143C5957B9BBB /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8EC0F2618965C875A96BFDBEE5D9734C; + remoteInfo = FirebaseAuthInterop; + }; + 3A85B25BE7EB3700AA42FFDB67F0FA65 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9E25537BF40D1A3B30CF43FD3E6ACD94; + remoteInfo = FirebaseInstanceID; + }; + 3D3DF074B6E5C632278A4252E418E2BD /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 6AE4A3D573DED275B034E20506596C62; + remoteInfo = FirebaseAuth; + }; + 423C0534BC19A64EE3AA747A6D910657 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 5EB4B0B6DA6D5C0C3365733BEAA1C485; + remoteInfo = FirebaseCoreDiagnosticsInterop; + }; + 43DBEC04E774DB1B29AF53D5C0CB2523 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8D7F5D5DD528D21A72DC87ADA5B12E2D; + remoteInfo = GoogleUtilities; + }; + 49F44A479BA8C7527515F5A698DD3969 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8D7F5D5DD528D21A72DC87ADA5B12E2D; + remoteInfo = GoogleUtilities; + }; + 4D44D5091BB420DEBE7212F778025EDE /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9307B7A119490930CF70393AB529AAC1; + remoteInfo = "leveldb-library"; + }; + 4D75B7D4E6D768ABBB5F00CA2812978B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8D7F5D5DD528D21A72DC87ADA5B12E2D; + remoteInfo = GoogleUtilities; + }; + 53C5D8C1E4E0BEE3C755797ACCD3426F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 4402AFF83DBDC4DD07E198685FDC2DF2; + remoteInfo = FirebaseCore; + }; + 58C19545ACFD96C76933EC2A1357ECF6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C49E7A4D59E5C8BE8DE9FB1EFB150185; + remoteInfo = FirebaseAnalytics; + }; + 5EB4367F6ABD0987B932719C700300E1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D372E53E2E8FEAA06A0439FB85E65767; + remoteInfo = FirebaseAnalyticsInterop; + }; + 5FB9279A7FB8B977A0C39AEF6B0842B6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8D7F5D5DD528D21A72DC87ADA5B12E2D; + remoteInfo = GoogleUtilities; + }; + 68988C3BA8D3738F378BE0B646187745 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D2B5E7DCCBBFB32341D857D01211A1A3; + remoteInfo = nanopb; + }; + 6C711276394E0714ABB7DADD6126DB09 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 68494F30B4A13F8E5E88BCCAEC25B0A4; + remoteInfo = Realm; + }; + 6D62F55BA23CBBC7B8F0F3ED7F7A0605 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 4402AFF83DBDC4DD07E198685FDC2DF2; + remoteInfo = FirebaseCore; + }; + 6D8F1E2DE2577840678D69579DCDFC6B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 4402AFF83DBDC4DD07E198685FDC2DF2; + remoteInfo = FirebaseCore; + }; + 6F141F40B8C4BD18CAF62DEDBC7D6A4B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 4402AFF83DBDC4DD07E198685FDC2DF2; + remoteInfo = FirebaseCore; + }; + 71FFA97981BB67C0EE8F5C5DE3763A40 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9E25537BF40D1A3B30CF43FD3E6ACD94; + remoteInfo = FirebaseInstanceID; + }; + 722DE636EF754E309E2377A2F2EC3B5B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 87803597EB3F20FC46472B85392EC4FD; + remoteInfo = FirebaseInstallations; + }; + 761399FB2F0D1E24856374B3D390B6D8 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8D7F5D5DD528D21A72DC87ADA5B12E2D; + remoteInfo = GoogleUtilities; + }; + 77FE2E7A53F20F84921FFA3DA3654782 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 5C0371EE948D0357B8EE0E34ABB44BF0; + remoteInfo = GoogleDataTransport; + }; + 79D989F96ADE69298F8884DEA8A83127 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 4402AFF83DBDC4DD07E198685FDC2DF2; + remoteInfo = FirebaseCore; + }; + 875EBEBC003673A6638AF33B98A68DEA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 620E05868772C10B4920DC7E324F2C87; + remoteInfo = FirebaseCoreDiagnostics; + }; + 8B18908728291153F46A2A4F421678E1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 072CEA044D2EF26F03496D5996BBF59F; + remoteInfo = Firebase; + }; + 8D09750C2082B22B85460095A9EF6663 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 2BBF7206D7FAC92C82A042A99C4A98F8; + remoteInfo = PromisesObjC; + }; + 8F2C2DA35D98C6D1928B3A8CE87C64E8 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 4402AFF83DBDC4DD07E198685FDC2DF2; + remoteInfo = FirebaseCore; + }; + 92AD2EC939B1E85F4FA30EC8E0A22654 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 4402AFF83DBDC4DD07E198685FDC2DF2; + remoteInfo = FirebaseCore; + }; + A2A702E58E50D0E6D66E484489B8E5C6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D2B5E7DCCBBFB32341D857D01211A1A3; + remoteInfo = nanopb; + }; + B0712C553EBA597C8935FFE5090FB915 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = E8022D22FAA6690B5E1C379C1BCE1491; + remoteInfo = Kingfisher; + }; + B266392E7C66A902BB2DC6B7CFC16FAE /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8D7F5D5DD528D21A72DC87ADA5B12E2D; + remoteInfo = GoogleUtilities; + }; + B4783EBCF939E1B6AD979918DF38F4D6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 736AF68F6527ACF6B4A4C54728824A1C; + remoteInfo = FirebaseDatabase; + }; + C1A9D1B82ED4DA51A08A5188FE6D031D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 526C4398D095B3704EB933DADBC30093; + remoteInfo = FirebaseCrashlytics; + }; + C8F705C2ADECD9202619F29B92B1B2B6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D2B5E7DCCBBFB32341D857D01211A1A3; + remoteInfo = nanopb; + }; + C9E956840CF76BE73AF3AA5E05BFD667 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 6AE4A3D573DED275B034E20506596C62; + remoteInfo = FirebaseAuth; + }; + CF5AB4129369903D3B934C30F92A82B1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8EC0F2618965C875A96BFDBEE5D9734C; + remoteInfo = FirebaseAuthInterop; + }; + D4922007713A402C8F9AB02C9AE0C6B2 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9E25537BF40D1A3B30CF43FD3E6ACD94; + remoteInfo = FirebaseInstanceID; + }; + D701690C588F6289EBF41CED6C2C4B9D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8EC0F2618965C875A96BFDBEE5D9734C; + remoteInfo = FirebaseAuthInterop; + }; + D7B0BFA47504549D7C0E2A4FECFF23DC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D372E53E2E8FEAA06A0439FB85E65767; + remoteInfo = FirebaseAnalyticsInterop; + }; + D7D6BF55D7A451D0E3B4CF82B8D8767C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D676E21115185671D7258A56944ABE98; + remoteInfo = GTMSessionFetcher; + }; + DAF995436B3F053E0ACB29B301ED2C9D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9307B7A119490930CF70393AB529AAC1; + remoteInfo = "leveldb-library"; + }; + DC00028F97358D0F7A7F571E33138E29 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 736AF68F6527ACF6B4A4C54728824A1C; + remoteInfo = FirebaseDatabase; + }; + DF30F9E34C851B4F019CA15C657FC22D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 2BBF7206D7FAC92C82A042A99C4A98F8; + remoteInfo = PromisesObjC; + }; + DF6ABEA55AE5B3CAD5D69ACB6E1524E5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D2B5E7DCCBBFB32341D857D01211A1A3; + remoteInfo = nanopb; + }; + EB15912C2AE1C6FF2B28D9521ED37B32 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 5C0371EE948D0357B8EE0E34ABB44BF0; + remoteInfo = GoogleDataTransport; + }; + ECD11A5E7A7957DCB33D02882249EA61 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 87803597EB3F20FC46472B85392EC4FD; + remoteInfo = FirebaseInstallations; + }; + EECBEC4A33C15CC6755B6CF200F8EB3F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C49E7A4D59E5C8BE8DE9FB1EFB150185; + remoteInfo = FirebaseAnalytics; + }; + F7954583DBB6A2D653793368CD5A8154 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 620E05868772C10B4920DC7E324F2C87; + remoteInfo = FirebaseCoreDiagnostics; + }; + FCDD41BE02B61E1DBF7A7FB991C36C70 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 526C4398D095B3704EB933DADBC30093; + remoteInfo = FirebaseCrashlytics; + }; + FEEC4DE9036942D7D3042235493B79A4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = B53D977A951AFC38B21751B706C1DF83; + remoteInfo = GoogleAppMeasurement; + }; + FEFB51F8A80B30E44D7D4FF289B3966B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 4402AFF83DBDC4DD07E198685FDC2DF2; + remoteInfo = FirebaseCore; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 1F16170A859A60D821655356EE33494F /* Copy . Public Headers */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(PUBLIC_HEADERS_FOLDER_PATH)/."; + dstSubfolderSpec = 16; + files = ( + D0FFA07135B6B357BF7FB4097C2ED5B0 /* NSError+RLMSync.h in Copy . Public Headers */, + E1C04EBBCE44BD3141F4EB0A81118085 /* Realm.h in Copy . Public Headers */, + 9C0DED0B53B311D880B84EAD42A5C367 /* RLMArray.h in Copy . Public Headers */, + 683DF482E4E5C25C03F3729230C63CE4 /* RLMCollection.h in Copy . Public Headers */, + 974071500397CA88E104EB9828047CF9 /* RLMConstants.h in Copy . Public Headers */, + A524B1A8F063D71F7854055E86AEA72F /* RLMListBase.h in Copy . Public Headers */, + 5120291734D77494B3D96DFCC2EE9857 /* RLMMigration.h in Copy . Public Headers */, + 286807D3C2B82C0C52156AC968CFA068 /* RLMObject.h in Copy . Public Headers */, + 27426BDEE639040D91F43848108A18D0 /* RLMObjectBase.h in Copy . Public Headers */, + 164553A0AACB1FB27D68230B7439AB7B /* RLMObjectBase_Dynamic.h in Copy . Public Headers */, + B6DAA4FE8AFA5CD4BF68AF52102E0355 /* RLMObjectSchema.h in Copy . Public Headers */, + D13E571CFDFA478398231DD5D0F4EB52 /* RLMOptionalBase.h in Copy . Public Headers */, + 4FFF5D9D67F39D2CF4D4C016EECACB00 /* RLMPlatform.h in Copy . Public Headers */, + 793929EC869818B425DBE85F6447FA84 /* RLMProperty.h in Copy . Public Headers */, + 9C5DFFE29827A2D989C2DE6910ACA270 /* RLMRealm+Sync.h in Copy . Public Headers */, + 67AB6A2540052673DDB030ED4BB1409C /* RLMRealm.h in Copy . Public Headers */, + F92BD30DFB4218BCD136C73A3EE04927 /* RLMRealm_Dynamic.h in Copy . Public Headers */, + 92142DB3CEF7BA2BB8942694C45D3065 /* RLMRealmConfiguration+Sync.h in Copy . Public Headers */, + F070B603DA8F5E7BA55B688D183E0B03 /* RLMRealmConfiguration.h in Copy . Public Headers */, + 7FE1D9C8A8C25D367EEA42987989907F /* RLMResults.h in Copy . Public Headers */, + 1E68DAAF9FA67C987AED67B4DA41F855 /* RLMSchema.h in Copy . Public Headers */, + F8116420B37CA79D3BFACFFA8B4E4A15 /* RLMSyncConfiguration.h in Copy . Public Headers */, + 615BB4C1722E9019DB212D73FACE7780 /* RLMSyncCredentials.h in Copy . Public Headers */, + 56D79D21489B99536E56D11613733DD3 /* RLMSyncManager.h in Copy . Public Headers */, + 7912D79016C638A8B5C9221D6B637B1D /* RLMSyncPermission.h in Copy . Public Headers */, + AE96328EC72F4BC41BD1A496D45C9325 /* RLMSyncSession.h in Copy . Public Headers */, + 0111886BCBA7E203D31D37D93130988C /* RLMSyncSubscription.h in Copy . Public Headers */, + 066893C78B248DE542B8229D75F2040A /* RLMSyncUser.h in Copy . Public Headers */, + 8069CAE326CAF7B3FBAB1DAADDB0A15F /* RLMSyncUtil.h in Copy . Public Headers */, + E57EBC2383EE0C6A61D1D0A46FE47383 /* RLMThreadSafeReference.h in Copy . Public Headers */, + ); + name = "Copy . Public Headers"; + runOnlyForDeploymentPostprocessing = 0; + }; + 31E1852A7D8B204006F453FB1209492D /* Copy . Private Headers */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(PRIVATE_HEADERS_FOLDER_PATH)/."; + dstSubfolderSpec = 16; + files = ( + 6DDD3EAC7634C6B8EBD88EB32F758913 /* RLMAccessor.h in Copy . Private Headers */, + 6FC528DFE6BAEA42D1373B2658656903 /* RLMArray_Private.h in Copy . Private Headers */, + 0ABD1A69D54921D4230DA47F703B47C8 /* RLMCollection_Private.h in Copy . Private Headers */, + C900AF92B70CCC35CF46BE3DEF231564 /* RLMListBase.h in Copy . Private Headers */, + 1856BEBADAA6691FD5C43D4D737029EA /* RLMObject_Private.h in Copy . Private Headers */, + 50A8B48A98FC0D65D138AB7D030497AA /* RLMObjectBase_Private.h in Copy . Private Headers */, + 68C57DF30A9CA33AACD78464E1AFF476 /* RLMObjectSchema_Private.h in Copy . Private Headers */, + A6645ABF85B1F4050B50D4DD62851C62 /* RLMObjectStore.h in Copy . Private Headers */, + E673B58F6FFC7F910AE4162B1C5B3AB6 /* RLMOptionalBase.h in Copy . Private Headers */, + E372C5DB9505173CE808FAE38016D9D5 /* RLMProperty_Private.h in Copy . Private Headers */, + CA784FB6D280003577399FC6357DF9E5 /* RLMRealm_Private.h in Copy . Private Headers */, + BE5BEFEEC9617E6F2ECA81E0B0ED9928 /* RLMRealmConfiguration_Private.h in Copy . Private Headers */, + E04BEC6F14A0260CE4638DE000E9CBC8 /* RLMResults_Private.h in Copy . Private Headers */, + D04919918C4E858CB048BCB2E2CA99C0 /* RLMSchema_Private.h in Copy . Private Headers */, + F9CEDE306E79AB2DFD9744ECD1DAE87A /* RLMSyncConfiguration_Private.h in Copy . Private Headers */, + 63A047238792804F506E0FBCE39F78FF /* RLMSyncUtil_Private.h in Copy . Private Headers */, + ); + name = "Copy . Private Headers"; + runOnlyForDeploymentPostprocessing = 0; + }; + 42CA76F9B31888F4140DDB3FDCE6D0DF /* Copy . Public Headers */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(PUBLIC_HEADERS_FOLDER_PATH)/."; + dstSubfolderSpec = 16; + files = ( + 23270FFD41B6451E9AC772ECE3CEB506 /* FBLPromise+All.h in Copy . Public Headers */, + 94200C7828DCC86231B65AFD64536D0E /* FBLPromise+Always.h in Copy . Public Headers */, + 02341086F0808232DE7D1D7523BB1F7C /* FBLPromise+Any.h in Copy . Public Headers */, + 835362E41AB4806250CF2E626DB11604 /* FBLPromise+Async.h in Copy . Public Headers */, + FFA702EE09C59823F2FDF43C14CA0EB3 /* FBLPromise+Await.h in Copy . Public Headers */, + FDD7FDF9B50336F15AE17AE06AE8E680 /* FBLPromise+Catch.h in Copy . Public Headers */, + EC5EC3B78CC53AEBBB9A04FBD76708C6 /* FBLPromise+Delay.h in Copy . Public Headers */, + DA783A36FD46D9A3D8A0CC6DBA44665E /* FBLPromise+Do.h in Copy . Public Headers */, + 5A353C8A03A13E3792A133931141EAC6 /* FBLPromise+Race.h in Copy . Public Headers */, + E365FD26B9B14AD0B39CF8067DC83D39 /* FBLPromise+Recover.h in Copy . Public Headers */, + 0A4B8057E841831E20481E7270E30F12 /* FBLPromise+Reduce.h in Copy . Public Headers */, + CE6AA463E98F7F834B40D3BF93F7941B /* FBLPromise+Retry.h in Copy . Public Headers */, + 7B2A35912DD812339B005D28B049B31D /* FBLPromise+Testing.h in Copy . Public Headers */, + FC825F33466362D6389D57C108DCE054 /* FBLPromise+Then.h in Copy . Public Headers */, + 0DA0270BE8D0FACFB22430EC574707C0 /* FBLPromise+Timeout.h in Copy . Public Headers */, + C70905767B4B45E148E005795BCE5EC4 /* FBLPromise+Validate.h in Copy . Public Headers */, + E7A47E2A004C4E692E3708A2C25650A1 /* FBLPromise+Wrap.h in Copy . Public Headers */, + C9E00ABDE9FCF4D436FC6D24872DFB75 /* FBLPromise.h in Copy . Public Headers */, + 54630A53305112536BA94296A1425F39 /* FBLPromiseError.h in Copy . Public Headers */, + EE84A10723C774FD833000C2D65279EB /* FBLPromises.h in Copy . Public Headers */, + ); + name = "Copy . Public Headers"; + runOnlyForDeploymentPostprocessing = 0; + }; + 6A61B315E33CF5864E2B7603DBA4CA7B /* Copy . Private Headers */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(PRIVATE_HEADERS_FOLDER_PATH)/."; + dstSubfolderSpec = 16; + files = ( + A167E0848DA6BE9E0A355B46D2BD99FA /* FBLPromisePrivate.h in Copy . Private Headers */, + ); + name = "Copy . Private Headers"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0007AC450179188F2E427F0B89A073E8 /* FIRSignInWithGameCenterResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRSignInWithGameCenterResponse.m; path = Firebase/Auth/Source/Backend/RPC/FIRSignInWithGameCenterResponse.m; sourceTree = ""; }; + 004C39F6E46A82C849CD59CD24686A01 /* FIRGetOOBConfirmationCodeResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRGetOOBConfirmationCodeResponse.h; path = Firebase/Auth/Source/Backend/RPC/FIRGetOOBConfirmationCodeResponse.h; sourceTree = ""; }; + 0089CEA96A6224BCF47F57FCBAACD204 /* FIRCLSBinaryImage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSBinaryImage.h; path = Crashlytics/Crashlytics/Components/FIRCLSBinaryImage.h; sourceTree = ""; }; + 00D096F4988C4E2FC88C5A60A39A8359 /* FIRInstallationsStoredAuthToken.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsStoredAuthToken.h; path = FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.h; sourceTree = ""; }; + 00D81FAAE360D000EE90A6008438A716 /* FTrackedQuery.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FTrackedQuery.m; path = Firebase/Database/Persistence/FTrackedQuery.m; sourceTree = ""; }; + 01184D2BB8F9472E8D48666C0DF61D38 /* FIRVersion.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRVersion.m; path = FirebaseCore/Sources/FIRVersion.m; sourceTree = ""; }; + 012F3963A2B15DDF0634F334AD78F02C /* FIRCLSCodeMapping.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSCodeMapping.m; path = Crashlytics/Shared/FIRCLSMachO/FIRCLSCodeMapping.m; sourceTree = ""; }; + 0137250CDC9EDE8A2D5EBBA6FD7658DA /* FIRCLSMachException.c */ = {isa = PBXFileReference; includeInIndex = 1; name = FIRCLSMachException.c; path = Crashlytics/Crashlytics/Handlers/FIRCLSMachException.c; sourceTree = ""; }; + 0176A732FDA7EF1260FECC4B3C3B16AF /* FIRBundleUtil.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRBundleUtil.h; path = FirebaseCore/Sources/FIRBundleUtil.h; sourceTree = ""; }; + 018A67795D376839684483C5DE57E63F /* FIRInstanceIDStringEncoding.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstanceIDStringEncoding.m; path = Firebase/InstanceID/FIRInstanceIDStringEncoding.m; sourceTree = ""; }; + 02231003098E086547B4CA839A2739B8 /* Realm.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Realm.swift; path = RealmSwift/Realm.swift; sourceTree = ""; }; + 025A8978E9E1BA201E9B325D59D34DA4 /* port.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = port.h; path = port/port.h; sourceTree = ""; }; + 02725477E4502DA51C0693E502CE5CD8 /* FIRCLSMachException.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSMachException.h; path = Crashlytics/Crashlytics/Handlers/FIRCLSMachException.h; sourceTree = ""; }; + 02BB0C52246D55DDDA24DD966F29EE7D /* FViewProcessorResult.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FViewProcessorResult.h; path = Firebase/Database/FViewProcessorResult.h; sourceTree = ""; }; + 02DC9AC9F992D94B7D771EF46B451D7D /* FIRCLSFile.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSFile.h; path = Crashlytics/Crashlytics/Helpers/FIRCLSFile.h; sourceTree = ""; }; + 02DF1A33535A813CC741C2ED921D4DD6 /* FIRAuthStoredUserManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAuthStoredUserManager.m; path = Firebase/Auth/Source/SystemService/FIRAuthStoredUserManager.m; sourceTree = ""; }; + 030FB2759BA9D7978C425338F088B8B5 /* RLMObjectSchema_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMObjectSchema_Private.h; path = include/RLMObjectSchema_Private.h; sourceTree = ""; }; + 03123A90B6E96B5F3F945E9576200603 /* NSError+FIRInstanceID.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSError+FIRInstanceID.h"; path = "Firebase/InstanceID/NSError+FIRInstanceID.h"; sourceTree = ""; }; + 03196D38FF2956BE3A63693601EAFC6E /* pb_encode.c */ = {isa = PBXFileReference; includeInIndex = 1; path = pb_encode.c; sourceTree = ""; }; + 0351264261053739411827F81B601E59 /* FNextPushId.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FNextPushId.m; path = Firebase/Database/Utilities/FNextPushId.m; sourceTree = ""; }; + 037723208A972F6D4CD565FBB23FEDBC /* FBLPromiseError.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FBLPromiseError.h; path = Sources/FBLPromises/include/FBLPromiseError.h; sourceTree = ""; }; + 03925A4568C20F619E76B1D91ACDC297 /* FIRCLSUnwind.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSUnwind.h; path = Crashlytics/Crashlytics/Unwind/FIRCLSUnwind.h; sourceTree = ""; }; + 03BD84CE35C53C22848C4F37AF990917 /* FIRCLSInstallIdentifierModel.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSInstallIdentifierModel.h; path = Crashlytics/Crashlytics/Models/FIRCLSInstallIdentifierModel.h; sourceTree = ""; }; + 03D2D50AE298301CE92CC1C9CD0CF6B8 /* FTupleBoolBlock.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FTupleBoolBlock.h; path = Firebase/Database/Utilities/Tuples/FTupleBoolBlock.h; sourceTree = ""; }; + 03E058B6C2458630160C0A65EB9FD24F /* RLMRealm_Dynamic.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMRealm_Dynamic.h; path = include/RLMRealm_Dynamic.h; sourceTree = ""; }; + 046EB39BD4F132031A238FB3AC461A85 /* fbase64.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = fbase64.h; path = Firebase/Database/third_party/SocketRocket/fbase64.h; sourceTree = ""; }; + 04E600FE62CF56D14578FC67BB598272 /* FIRGetProjectConfigRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRGetProjectConfigRequest.m; path = Firebase/Auth/Source/Backend/RPC/FIRGetProjectConfigRequest.m; sourceTree = ""; }; + 051905C1D8206444341EEDFEDE3E5B02 /* FIRCLSReportManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSReportManager.m; path = Crashlytics/Crashlytics/Controllers/FIRCLSReportManager.m; sourceTree = ""; }; + 0543F2CD37528CCEBC7A49CEB7FAC5D1 /* GULAppDelegateSwizzler_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULAppDelegateSwizzler_Private.h; path = GoogleUtilities/AppDelegateSwizzler/Internal/GULAppDelegateSwizzler_Private.h; sourceTree = ""; }; + 057C3F5AF0B6B7F47AB4D32D9FCD9421 /* FirebaseAuth.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FirebaseAuth.xcconfig; sourceTree = ""; }; + 0592320608C8F3CCF80D574E10C7E91C /* FAckUserWrite.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FAckUserWrite.m; path = Firebase/Database/Core/Operation/FAckUserWrite.m; sourceTree = ""; }; + 05A33B164E057EE24CF3636BBB916A28 /* RLMObjectBase_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMObjectBase_Private.h; path = include/RLMObjectBase_Private.h; sourceTree = ""; }; + 05B52350B0B17B2722AC405A40762DA7 /* RLMMigration.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMMigration.mm; path = Realm/RLMMigration.mm; sourceTree = ""; }; + 05DA9AF2D109C39E2B3E4D0151FDEA03 /* db.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = db.h; path = include/leveldb/db.h; sourceTree = ""; }; + 05F1DD910E0B70B4E0DF0A7C3FA329D0 /* FImmutableTree.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FImmutableTree.m; path = Firebase/Database/Core/Utilities/FImmutableTree.m; sourceTree = ""; }; + 066EA1C1FF6676A0B35DE90DB17930AB /* GDTCORStoredEvent.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORStoredEvent.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORStoredEvent.m; sourceTree = ""; }; + 066EF248F340E2465710C78E9B311ADE /* FIRCLSFABHost.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSFABHost.h; path = Crashlytics/Shared/FIRCLSFABHost.h; sourceTree = ""; }; + 067870D7BE43590F2E93FD4B3FDB5646 /* FOperationSource.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FOperationSource.m; path = Firebase/Database/Core/Operation/FOperationSource.m; sourceTree = ""; }; + 06A412221485D514638E45C0C443BE74 /* weak_realm_notifier.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = weak_realm_notifier.cpp; path = Realm/ObjectStore/src/impl/weak_realm_notifier.cpp; sourceTree = ""; }; + 06BB1B8E0794DA761F9BD7359D2036F7 /* histogram.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = histogram.cc; path = util/histogram.cc; sourceTree = ""; }; + 06FC5C9CF96D60C50FCD47D339C91951 /* nanopb.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = nanopb.framework; path = nanopb.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 0708BFDF3D8644771BD7287AB060DD32 /* FIRSecureTokenResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRSecureTokenResponse.m; path = Firebase/Auth/Source/Backend/RPC/FIRSecureTokenResponse.m; sourceTree = ""; }; + 0775707B0243939007657C36EC7F3688 /* FIRCLSNetworkOperation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSNetworkOperation.m; path = Crashlytics/Crashlytics/Settings/Operations/FIRCLSNetworkOperation.m; sourceTree = ""; }; + 0791472EB1551B2208F3B22E2A0E9F04 /* ImageDataProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageDataProvider.swift; path = Sources/General/ImageSource/ImageDataProvider.swift; sourceTree = ""; }; + 07C192291C6A1B6CCFBA7391244C026D /* FIRCLSUserDefaults_private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSUserDefaults_private.h; path = Crashlytics/Crashlytics/FIRCLSUserDefaults/FIRCLSUserDefaults_private.h; sourceTree = ""; }; + 07D6C440C42DA5C3A063CE7C34D1303F /* RLMObject.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMObject.mm; path = Realm/RLMObject.mm; sourceTree = ""; }; + 080D58C9DE83B7518E5BB38070E78A6B /* FIREmailPasswordAuthCredential.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIREmailPasswordAuthCredential.m; path = Firebase/Auth/Source/AuthProvider/Email/FIREmailPasswordAuthCredential.m; sourceTree = ""; }; + 09125E2EA0588202813B2CDFFEEFD93F /* FEmptyNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FEmptyNode.m; path = Firebase/Database/Snapshot/FEmptyNode.m; sourceTree = ""; }; + 091DE8E84139D89633B550EF9E316271 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/CFNetwork.framework; sourceTree = DEVELOPER_DIR; }; + 0920FF74941ED10798F1EF6E8D15ADB4 /* FIRVerifyClientResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRVerifyClientResponse.m; path = Firebase/Auth/Source/Backend/RPC/FIRVerifyClientResponse.m; sourceTree = ""; }; + 09304335678AA1BA258043917B4E23D4 /* FIRInstanceIDConstants.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstanceIDConstants.m; path = Firebase/InstanceID/FIRInstanceIDConstants.m; sourceTree = ""; }; + 09933BBB67B866ED8F91402FCD732796 /* SwiftVersion.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SwiftVersion.swift; path = RealmSwift/SwiftVersion.swift; sourceTree = ""; }; + 09B51F4D1459A875BA0930AF94EA5C0B /* cct.nanopb.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = cct.nanopb.h; path = GoogleDataTransportCCTSupport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h; sourceTree = ""; }; + 09E99A891DB19775D655521322BA574D /* GTMSessionUploadFetcher.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMSessionUploadFetcher.m; path = Source/GTMSessionUploadFetcher.m; sourceTree = ""; }; + 0A9F46A999C47653013D3AD854352507 /* leveldb.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = leveldb.framework; path = "leveldb-library.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; + 0AD0CE2461D5D5215E0EC8F0E909A922 /* SessionManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SessionManager.swift; path = Source/SessionManager.swift; sourceTree = ""; }; + 0ADFAE923FC1E34FC913A0E76EC991AE /* FIRAEvent+Internal.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FIRAEvent+Internal.m"; path = "Crashlytics/Crashlytics/Helpers/FIRAEvent+Internal.m"; sourceTree = ""; }; + 0AE01923DCFF0C1BBC52D813E212BF6E /* FCachePolicy.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FCachePolicy.h; path = Firebase/Database/Persistence/FCachePolicy.h; sourceTree = ""; }; + 0B80609DA7F3835D3B6FF1855935465F /* FAckUserWrite.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FAckUserWrite.h; path = Firebase/Database/Core/Operation/FAckUserWrite.h; sourceTree = ""; }; + 0BEB9AB4B41C5EA0FC8F77768519921A /* FIRSetAccountInfoResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRSetAccountInfoResponse.m; path = Firebase/Auth/Source/Backend/RPC/FIRSetAccountInfoResponse.m; sourceTree = ""; }; + 0BF9423C4AE3C584BD68D6E8F6E19B7C /* repair.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = repair.cc; path = db/repair.cc; sourceTree = ""; }; + 0BFAE93D92AAB0A65643967B49DA3AFE /* FormatIndicatedCacheSerializer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FormatIndicatedCacheSerializer.swift; path = Sources/Cache/FormatIndicatedCacheSerializer.swift; sourceTree = ""; }; + 0C0079799AE2339A85F7E62D4780C436 /* FTypedefs.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FTypedefs.h; path = Firebase/Database/Utilities/FTypedefs.h; sourceTree = ""; }; + 0C23EB632CADB2EE2B872554F93C995E /* APLevelDB.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = APLevelDB.h; path = "Firebase/Database/third_party/Wrap-leveldb/APLevelDB.h"; sourceTree = ""; }; + 0C4CA078D9BE63A8B15F98F02748007D /* version_edit.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = version_edit.h; path = db/version_edit.h; sourceTree = ""; }; + 0C548F54FE1BE778FB826F39F15D7349 /* GULReachabilityChecker.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULReachabilityChecker.m; path = GoogleUtilities/Reachability/GULReachabilityChecker.m; sourceTree = ""; }; + 0C725C066DA25373EC76D559B25677B0 /* FIRDataEventType.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRDataEventType.h; path = Firebase/Database/Public/FIRDataEventType.h; sourceTree = ""; }; + 0C8DC147EFC61F95DA3FC13996255591 /* FIRSendVerificationCodeResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRSendVerificationCodeResponse.h; path = Firebase/Auth/Source/Backend/RPC/FIRSendVerificationCodeResponse.h; sourceTree = ""; }; + 0CDDAB47030D2788EB6FE8C833C8F44E /* FIRAuthSerialTaskQueue.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAuthSerialTaskQueue.m; path = Firebase/Auth/Source/Auth/FIRAuthSerialTaskQueue.m; sourceTree = ""; }; + 0D15BFCCA59443379D0E59B6CD009FE3 /* FIRInstanceIDUtilities.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstanceIDUtilities.h; path = Firebase/InstanceID/FIRInstanceIDUtilities.h; sourceTree = ""; }; + 0D52282B453D45A43B76626275E73CED /* FBLPromise+Race.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Race.h"; path = "Sources/FBLPromises/include/FBLPromise+Race.h"; sourceTree = ""; }; + 0D691679E072ED98D8E9280883868E4E /* FChildChangeAccumulator.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FChildChangeAccumulator.m; path = Firebase/Database/Core/View/Filter/FChildChangeAccumulator.m; sourceTree = ""; }; + 0D77224971A73956E499DBACD9269CC0 /* FIRInstanceIDAuthKeyChain.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstanceIDAuthKeyChain.m; path = Firebase/InstanceID/FIRInstanceIDAuthKeyChain.m; sourceTree = ""; }; + 0D8E5D0FF01FC3B2B7A13D625B836394 /* GULSceneDelegateSwizzler_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULSceneDelegateSwizzler_Private.h; path = GoogleUtilities/SceneDelegateSwizzler/Internal/GULSceneDelegateSwizzler_Private.h; sourceTree = ""; }; + 0D9C52A5FE9F767DD058F89205254B18 /* FTupleBoolBlock.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FTupleBoolBlock.m; path = Firebase/Database/Utilities/Tuples/FTupleBoolBlock.m; sourceTree = ""; }; + 0DA64D4B686E9AD555646D96BBA0EF44 /* GoogleUtilities-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "GoogleUtilities-umbrella.h"; sourceTree = ""; }; + 0DCB3DAD6C9EDF1C934977898099ED3A /* GTMSessionFetcher-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "GTMSessionFetcher-dummy.m"; sourceTree = ""; }; + 0DD29CAF1F48BADB4D360100F91E3CAD /* GoogleDataTransportCCTSupport-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "GoogleDataTransportCCTSupport-umbrella.h"; sourceTree = ""; }; + 0E1F295C21957B24F285D48A4D11B95C /* FEventEmitter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FEventEmitter.h; path = Firebase/Database/Utilities/FEventEmitter.h; sourceTree = ""; }; + 0E2522A9B4A9665409FB203B0AEEC844 /* FIRCLSMachOSlice.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSMachOSlice.m; path = Crashlytics/Shared/FIRCLSMachO/FIRCLSMachOSlice.m; sourceTree = ""; }; + 0E543475F05FA2541F2CCB52DBEF8927 /* RLMObject.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMObject.h; path = include/RLMObject.h; sourceTree = ""; }; + 0E742B2B5CF247EEBCFF0C8BD42B9113 /* firebasecore.nanopb.c */ = {isa = PBXFileReference; includeInIndex = 1; name = firebasecore.nanopb.c; path = Firebase/CoreDiagnostics/FIRCDLibrary/Protogen/nanopb/firebasecore.nanopb.c; sourceTree = ""; }; + 0E7B05D506EF8041DBC2C686CB769C56 /* GoogleDataTransport-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "GoogleDataTransport-umbrella.h"; sourceTree = ""; }; + 0E942E4D8362BD9FF9206F0F0224F63B /* FIRAdditionalUserInfo.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAdditionalUserInfo.m; path = Firebase/Auth/Source/User/FIRAdditionalUserInfo.m; sourceTree = ""; }; + 0EAECCDB2F410D60877EEAE86A6621F9 /* FIRAuthInterop.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthInterop.h; path = Interop/Auth/Public/FIRAuthInterop.h; sourceTree = ""; }; + 0ED8B0B84A275F2D02C176BEE7BDCAA5 /* FArraySortedDictionary.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FArraySortedDictionary.m; path = Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FArraySortedDictionary.m; sourceTree = ""; }; + 0F0C336156B678EF35851FE016FEAC04 /* Storage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Storage.swift; path = Sources/Cache/Storage.swift; sourceTree = ""; }; + 0F1D4E308F55A3F56D63433F341967EC /* GDTCORTransport.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORTransport.h; path = GoogleDataTransport/GDTCORLibrary/Public/GDTCORTransport.h; sourceTree = ""; }; + 0F739610BEEAB0E57F2B0D16064B644E /* FIRDataSnapshot.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRDataSnapshot.h; path = Firebase/Database/Public/FIRDataSnapshot.h; sourceTree = ""; }; + 0FA9A704FC322DCBCFB439E7E5DA997C /* sync_config.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = sync_config.cpp; path = Realm/ObjectStore/src/sync/sync_config.cpp; sourceTree = ""; }; + 0FCAEFD4A55B7DC2D8CD018B48D09F1D /* GDTFLLUploader.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTFLLUploader.m; path = GoogleDataTransportCCTSupport/GDTCCTLibrary/GDTFLLUploader.m; sourceTree = ""; }; + 0FF88F56210C6841FFB4F15504D58897 /* builder.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = builder.h; path = db/builder.h; sourceTree = ""; }; + 10332F50392D7C5CB440A2718EBCAF21 /* cache.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = cache.cc; path = util/cache.cc; sourceTree = ""; }; + 106AA954E1BAE8847B35D6285830BF68 /* shared_realm.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = shared_realm.cpp; path = Realm/ObjectStore/src/shared_realm.cpp; sourceTree = ""; }; + 10723D0293FDDA883CBD050B8BC02938 /* RLMUtil.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMUtil.mm; path = Realm/RLMUtil.mm; sourceTree = ""; }; + 107BF9874255036D813C9C53A899ABC8 /* FIRGetAccountInfoRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRGetAccountInfoRequest.m; path = Firebase/Auth/Source/Backend/RPC/FIRGetAccountInfoRequest.m; sourceTree = ""; }; + 10B3B837E0F6835E55DE59E541E92996 /* GDTCOREvent_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCOREvent_Private.h; path = GoogleDataTransport/GDTCORLibrary/Private/GDTCOREvent_Private.h; sourceTree = ""; }; + 10B86ED0811ED153ABD059C908466AF0 /* FIRCLSPackageReportOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSPackageReportOperation.h; path = Crashlytics/Crashlytics/Operations/Reports/FIRCLSPackageReportOperation.h; sourceTree = ""; }; + 10C8E24EE36C598C85FB7EDE24E70ED8 /* RealmSwift-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "RealmSwift-prefix.pch"; sourceTree = ""; }; + 10CEF8E8921B937AD4E0A3EC0B204026 /* FIRAuthOperationType.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthOperationType.h; path = Firebase/Auth/Source/Auth/FIRAuthOperationType.h; sourceTree = ""; }; + 10D8F107AA7F4E07CBE300F4F7CB10C6 /* FNodeFilter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FNodeFilter.h; path = Firebase/Database/Core/View/Filter/FNodeFilter.h; sourceTree = ""; }; + 10D97BB079FDEB279746D6D296CE39AF /* FIRCLSProcessReportOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSProcessReportOperation.h; path = Crashlytics/Crashlytics/Operations/Reports/FIRCLSProcessReportOperation.h; sourceTree = ""; }; + 117B8C3BABB7D834D0BD574509E76FA2 /* GoogleDataTransport-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "GoogleDataTransport-Info.plist"; sourceTree = ""; }; + 11803A8C45EB3F5F3B529FB885798EB9 /* FIRGetAccountInfoResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRGetAccountInfoResponse.h; path = Firebase/Auth/Source/Backend/RPC/FIRGetAccountInfoResponse.h; sourceTree = ""; }; + 118B6DE2F27EAA4902B2FBB1EA159C28 /* Indicator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Indicator.swift; path = Sources/Views/Indicator.swift; sourceTree = ""; }; + 11C9E2292880FD53065259BE912CA70F /* schema.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = schema.cpp; path = Realm/ObjectStore/src/schema.cpp; sourceTree = ""; }; + 11CB3AD7E80C556AAEC53C4F0C55E1C2 /* RLMCollection.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMCollection.h; path = include/RLMCollection.h; sourceTree = ""; }; + 11DCEEEC472C556014277C5511D1CE3B /* FIRDatabaseConfig.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRDatabaseConfig.m; path = Firebase/Database/Api/FIRDatabaseConfig.m; sourceTree = ""; }; + 120F846FAC83BF1EB295CC2FB54137E0 /* FSnapshotUtilities.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FSnapshotUtilities.h; path = Firebase/Database/Snapshot/FSnapshotUtilities.h; sourceTree = ""; }; + 1231D552B10EFFFBC08D63ABA4FC43FF /* FIRAuthDataResult.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAuthDataResult.m; path = Firebase/Auth/Source/Auth/FIRAuthDataResult.m; sourceTree = ""; }; + 1266E3968D29717728D769C77E8A3954 /* FIRCLSUnwind_arch.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSUnwind_arch.h; path = Crashlytics/Crashlytics/Unwind/FIRCLSUnwind_arch.h; sourceTree = ""; }; + 1269A06F7EF7679BC8BCC0C2B590766C /* FIRInstallationsItem.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsItem.h; path = FirebaseInstallations/Source/Library/FIRInstallationsItem.h; sourceTree = ""; }; + 1293543EA5A1A924D5F8338EBA12CC16 /* FIRCLSUtility.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSUtility.m; path = Crashlytics/Crashlytics/Helpers/FIRCLSUtility.m; sourceTree = ""; }; + 12A1667F60BAFB5EFB620E87EF4CA3B1 /* FIRInstanceID_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstanceID_Private.h; path = Firebase/InstanceID/Private/FIRInstanceID_Private.h; sourceTree = ""; }; + 12A29852C18DA5FA1CE5E3A8ECA65A55 /* RLMSyncSessionRefreshHandle.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMSyncSessionRefreshHandle.mm; path = Realm/RLMSyncSessionRefreshHandle.mm; sourceTree = ""; }; + 12F784E5C9AC8C284E63D4AF3B6F2A19 /* FIRCLSSymbolResolver.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSSymbolResolver.h; path = Crashlytics/Crashlytics/Models/FIRCLSSymbolResolver.h; sourceTree = ""; }; + 1366E4DEDA780B6EDEB38EFC163BF5E1 /* FIRCLSSettingsOnboardingManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSSettingsOnboardingManager.m; path = Crashlytics/Crashlytics/Settings/FIRCLSSettingsOnboardingManager.m; sourceTree = ""; }; + 139EC6653CEB5B25C26FAD976A2CEE80 /* FIRGetOOBConfirmationCodeRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRGetOOBConfirmationCodeRequest.m; path = Firebase/Auth/Source/Backend/RPC/FIRGetOOBConfirmationCodeRequest.m; sourceTree = ""; }; + 13ADE9639DE325D6DB8F9E46AA6FCCD3 /* ServerTrustPolicy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ServerTrustPolicy.swift; path = Source/ServerTrustPolicy.swift; sourceTree = ""; }; + 13C8C8B254851998F9289F71229B28A2 /* FirebaseInstallations.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = FirebaseInstallations.framework; path = FirebaseInstallations.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 13F91BDD6A43D7C3C3BA79F3B3F57353 /* GULNetwork.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULNetwork.m; path = GoogleUtilities/Network/GULNetwork.m; sourceTree = ""; }; + 14007804F652C026CE4B746B53D6F3A8 /* GULHeartbeatDateStorage.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULHeartbeatDateStorage.m; path = GoogleUtilities/Environment/GULHeartbeatDateStorage.m; sourceTree = ""; }; + 1425FAB07EFDBF97C7BA299BD0A460E8 /* FirebaseCoreDiagnostics-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "FirebaseCoreDiagnostics-dummy.m"; sourceTree = ""; }; + 145CEB14F38B1828A56B5E6681B5162C /* FIRCLSUnwind_x86.c */ = {isa = PBXFileReference; includeInIndex = 1; name = FIRCLSUnwind_x86.c; path = Crashlytics/Crashlytics/Unwind/FIRCLSUnwind_x86.c; sourceTree = ""; }; + 1474818897D4EF99A548A3FE8D382197 /* FIRInstanceID+Private.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FIRInstanceID+Private.m"; path = "Firebase/InstanceID/FIRInstanceID+Private.m"; sourceTree = ""; }; + 1477BB8C1DE91F3AB64B8F2FF2A5A077 /* FKeyIndex.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FKeyIndex.h; path = Firebase/Database/FKeyIndex.h; sourceTree = ""; }; + 14943783D6765B9A756C91C50D324D57 /* FIRResetPasswordRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRResetPasswordRequest.h; path = Firebase/Auth/Source/Backend/RPC/FIRResetPasswordRequest.h; sourceTree = ""; }; + 14AF50D2F7BD3AC68ED686CAB08FBCF4 /* FTupleFirebase.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FTupleFirebase.h; path = Firebase/Database/Utilities/Tuples/FTupleFirebase.h; sourceTree = ""; }; + 14C0953A445D4BE60522EB22E077A192 /* FIRCLSMachO.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSMachO.h; path = Crashlytics/Shared/FIRCLSMachO/FIRCLSMachO.h; sourceTree = ""; }; + 14EA242FEEBE0CD01C6BDDB10511FD39 /* FirebaseInstallations-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "FirebaseInstallations-Info.plist"; sourceTree = ""; }; + 14F0259C7BD472223481E9A31DC96485 /* FBLPromise+Timeout.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Timeout.h"; path = "Sources/FBLPromises/include/FBLPromise+Timeout.h"; sourceTree = ""; }; + 1517B8A647EFBB22C37521AD2B638C49 /* FIRVerifyPhoneNumberRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRVerifyPhoneNumberRequest.m; path = Firebase/Auth/Source/Backend/RPC/FIRVerifyPhoneNumberRequest.m; sourceTree = ""; }; + 155F8EFAE7A4D4D041B3D8F2511E9D4C /* FIRTwitterAuthProvider.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRTwitterAuthProvider.m; path = Firebase/Auth/Source/AuthProvider/Twitter/FIRTwitterAuthProvider.m; sourceTree = ""; }; + 15ADC80EB9B79579806F9F7AD89C2C94 /* FIRAuthWebViewController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthWebViewController.h; path = Firebase/Auth/Source/Utilities/FIRAuthWebViewController.h; sourceTree = ""; }; + 16238A64917CEB3E86D222482A856898 /* RequestModifier.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RequestModifier.swift; path = Sources/Networking/RequestModifier.swift; sourceTree = ""; }; + 163306A052CA3BBD80F4181480FB67FD /* version_set.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = version_set.h; path = db/version_set.h; sourceTree = ""; }; + 163DDF9C945F142199DA4A6A64290EF3 /* FIRInstanceIDStore.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstanceIDStore.h; path = Firebase/InstanceID/FIRInstanceIDStore.h; sourceTree = ""; }; + 16FA90EBB6ED4FCC27E0FB0DEACA5A7C /* FIRCLSUtility.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSUtility.h; path = Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h; sourceTree = ""; }; + 17866E99D6426B8B2E450403A6E5FFD2 /* FirebaseCrashlytics-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "FirebaseCrashlytics-dummy.m"; sourceTree = ""; }; + 179923EADFEB236A00F5EB34E838D46B /* sync_permission.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = sync_permission.cpp; path = Realm/ObjectStore/src/sync/sync_permission.cpp; sourceTree = ""; }; + 17C37D59F16CFBD0BD12D98127608CC9 /* FNextPushId.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FNextPushId.h; path = Firebase/Database/Utilities/FNextPushId.h; sourceTree = ""; }; + 184038DACE9B255B375E91EB2FA49ADE /* GDTCCTUploader.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCCTUploader.h; path = GoogleDataTransportCCTSupport/GDTCCTLibrary/Private/GDTCCTUploader.h; sourceTree = ""; }; + 18A2A34965EE1BF82E73C4BBE109FF6B /* GULLoggerLevel.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULLoggerLevel.h; path = GoogleUtilities/Logger/Public/GULLoggerLevel.h; sourceTree = ""; }; + 18A4FA38D0EC7379DB125F600EA014CA /* RLMSchema.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMSchema.h; path = include/RLMSchema.h; sourceTree = ""; }; + 18C003E341EAC1C5F8D5C1F825A03BC6 /* ObjectiveCSupport+Sync.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ObjectiveCSupport+Sync.swift"; path = "RealmSwift/ObjectiveCSupport+Sync.swift"; sourceTree = ""; }; + 18C11A47DA70BE69B373BF2ED8069CC8 /* RealmSwift-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "RealmSwift-umbrella.h"; sourceTree = ""; }; + 18D0105B5764541095FBD8092705172C /* object_store.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = object_store.cpp; path = Realm/ObjectStore/src/object_store.cpp; sourceTree = ""; }; + 18FF45BE694A4ACF5279DDC418ED779A /* FIREmailLinkSignInRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIREmailLinkSignInRequest.m; path = Firebase/Auth/Source/Backend/RPC/FIREmailLinkSignInRequest.m; sourceTree = ""; }; + 192E47A30A68811FE88255BE422DA582 /* FIRInstanceIDTokenOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstanceIDTokenOperation.h; path = Firebase/InstanceID/FIRInstanceIDTokenOperation.h; sourceTree = ""; }; + 1943EA82083528978789B90143A825F6 /* FIRCLSDataParsing.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSDataParsing.h; path = Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDataParsing.h; sourceTree = ""; }; + 196645E8D0648C57FB0C6929B7E45FF3 /* GDTCCTPrioritizer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCCTPrioritizer.h; path = GoogleDataTransportCCTSupport/GDTCCTLibrary/Private/GDTCCTPrioritizer.h; sourceTree = ""; }; + 19A7929975E4DB7365AB0B4BEF5873BF /* FIRInteropEventNames.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInteropEventNames.h; path = Interop/Analytics/Public/FIRInteropEventNames.h; sourceTree = ""; }; + 19B43B6463DEB5A47027B4084F472659 /* FIRCLSUserLogging.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSUserLogging.h; path = Crashlytics/Crashlytics/Components/FIRCLSUserLogging.h; sourceTree = ""; }; + 19B99E5574E2EFC09D9C01055646B8C9 /* FIRCLSThreadArrayOperation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSThreadArrayOperation.m; path = Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSThreadArrayOperation.m; sourceTree = ""; }; + 19C52BF106E4431FAE1B6E68ED7F69ED /* ImageDownloaderDelegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageDownloaderDelegate.swift; path = Sources/Networking/ImageDownloaderDelegate.swift; sourceTree = ""; }; + 19FB754EB4099F557531F6709EAC9E10 /* FIRDeleteAccountResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRDeleteAccountResponse.h; path = Firebase/Auth/Source/Backend/RPC/FIRDeleteAccountResponse.h; sourceTree = ""; }; + 1AB2E2F6F5F01C6A4F17BFCBA264DAA9 /* FIRInstanceIDBackupExcludedPlist.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstanceIDBackupExcludedPlist.m; path = Firebase/InstanceID/FIRInstanceIDBackupExcludedPlist.m; sourceTree = ""; }; + 1AC16AD6A932F1CAB1FD8F22CD2467E9 /* GDTCORConsoleLogger.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORConsoleLogger.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORConsoleLogger.m; sourceTree = ""; }; + 1B855D092D33E62CC9867576BF1B3E8D /* RLMSyncConfiguration.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMSyncConfiguration.h; path = include/RLMSyncConfiguration.h; sourceTree = ""; }; + 1C248628CE055C1521E3853C48F9FF24 /* GDTCORLifecycle.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORLifecycle.h; path = GoogleDataTransport/GDTCORLibrary/Public/GDTCORLifecycle.h; sourceTree = ""; }; + 1C4470BA20E3DEEAF93A0FFAC0CFEFA6 /* FIRComponentType.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRComponentType.m; path = FirebaseCore/Sources/FIRComponentType.m; sourceTree = ""; }; + 1C45C494D97CAD85D2802B100A2AC056 /* FIRDatabaseComponent.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRDatabaseComponent.m; path = Firebase/Database/Api/FIRDatabaseComponent.m; sourceTree = ""; }; + 1C6417652D217907B3854C35FBC3BEF5 /* FMaxNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FMaxNode.m; path = Firebase/Database/FMaxNode.m; sourceTree = ""; }; + 1D0510411AB5C0B1659FA25995973E70 /* RLMArray.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMArray.h; path = include/RLMArray.h; sourceTree = ""; }; + 1D1956DDD8EB33F9F30CB04B5893A01F /* FirebaseInstallations.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = FirebaseInstallations.modulemap; sourceTree = ""; }; + 1D1B6EB1EB1CC689CA6DC51E84C01C2A /* FBLPromise+Wrap.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Wrap.h"; path = "Sources/FBLPromises/include/FBLPromise+Wrap.h"; sourceTree = ""; }; + 1D2CEE38973DEDB4BC7B4AB471D4E205 /* GULUserDefaults.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULUserDefaults.h; path = GoogleUtilities/UserDefaults/Private/GULUserDefaults.h; sourceTree = ""; }; + 1D593AEC5637E10E09C61C612DADEF44 /* RLMResults_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMResults_Private.h; path = include/RLMResults_Private.h; sourceTree = ""; }; + 1D5FBD103DC3CF41681635FC640E23FC /* FIRDatabase_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRDatabase_Private.h; path = Firebase/Database/Api/Private/FIRDatabase_Private.h; sourceTree = ""; }; + 1DCEABFFFDA249FC0F0D5AEAEC0472FA /* GDTCORReachability.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORReachability.h; path = GoogleDataTransport/GDTCORLibrary/Private/GDTCORReachability.h; sourceTree = ""; }; + 1DD18769760DADB55736DAD2D92847B3 /* FIRResetPasswordResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRResetPasswordResponse.m; path = Firebase/Auth/Source/Backend/RPC/FIRResetPasswordResponse.m; sourceTree = ""; }; + 1DED2ACF11680CBA13D4FD04C0523775 /* FQuerySpec.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FQuerySpec.m; path = Firebase/Database/Core/FQuerySpec.m; sourceTree = ""; }; + 1DF3C20AD9061C27A056CA943603808B /* FIRCLSURLSessionConfiguration.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSURLSessionConfiguration.h; path = Crashlytics/Crashlytics/FIRCLSURLSession/FIRCLSURLSessionConfiguration.h; sourceTree = ""; }; + 1E0181084EF4AB7551CEDA5C117A0DE3 /* env.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = env.cc; path = util/env.cc; sourceTree = ""; }; + 1E0D87AAB7FCF9615A4CE2C179F88E7D /* GDTCORUploadCoordinator.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORUploadCoordinator.h; path = GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadCoordinator.h; sourceTree = ""; }; + 1E13C163D44E2F284341E396BB0A7707 /* FIRAuthRequestConfiguration.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthRequestConfiguration.h; path = Firebase/Auth/Source/Backend/FIRAuthRequestConfiguration.h; sourceTree = ""; }; + 1E45A7C637B9EF8C87CF12C4387A2E17 /* FIRAuthRequestConfiguration.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAuthRequestConfiguration.m; path = Firebase/Auth/Source/Backend/FIRAuthRequestConfiguration.m; sourceTree = ""; }; + 1EAB78CDB261A7BD311754265ADA8C06 /* leveldb-library-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "leveldb-library-dummy.m"; sourceTree = ""; }; + 1EACD418D36D7A8783BD11A9111EF64B /* FTupleSetIdPath.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FTupleSetIdPath.m; path = Firebase/Database/Utilities/Tuples/FTupleSetIdPath.m; sourceTree = ""; }; + 1EACEFAD63F0542FB6C9630CFE587A37 /* FIRSecureTokenRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRSecureTokenRequest.h; path = Firebase/Auth/Source/Backend/RPC/FIRSecureTokenRequest.h; sourceTree = ""; }; + 1EDAF1D55B69A9C4D024EFA96DDE2D33 /* RLMThreadSafeReference.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMThreadSafeReference.mm; path = Realm/RLMThreadSafeReference.mm; sourceTree = ""; }; + 1F10BD67D2D9EA34B8A06A9EA2DCE036 /* FIRCLSURLSessionTask_PrivateMethods.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSURLSessionTask_PrivateMethods.h; path = Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionTask_PrivateMethods.h; sourceTree = ""; }; + 1F2ED39C151AD216F2DA816B638F2A59 /* FIRCLSReport.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSReport.h; path = Crashlytics/Crashlytics/Models/FIRCLSReport.h; sourceTree = ""; }; + 1F51719C18D03B91659D1E93CF7F391D /* FBLPromise+Validate.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Validate.m"; path = "Sources/FBLPromises/FBLPromise+Validate.m"; sourceTree = ""; }; + 1F705824450B4EFCB691B62F12F6B58D /* ImageView+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ImageView+Kingfisher.swift"; path = "Sources/Extensions/ImageView+Kingfisher.swift"; sourceTree = ""; }; + 1F73B89D31C30727BAC5F06912071FD9 /* FIRInstanceIDAuthService.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstanceIDAuthService.h; path = Firebase/InstanceID/FIRInstanceIDAuthService.h; sourceTree = ""; }; + 1FC141C04FBFB3AD1B37DD2AD9485844 /* NSData+FIRBase64.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSData+FIRBase64.m"; path = "Firebase/Auth/Source/Utilities/NSData+FIRBase64.m"; sourceTree = ""; }; + 1FD48142CA1F24926D4436B30CFC3443 /* FIRInstanceIDTokenManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstanceIDTokenManager.m; path = Firebase/InstanceID/FIRInstanceIDTokenManager.m; sourceTree = ""; }; + 2014970201009EBB1DD94C5CE41A7FF0 /* version_set.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = version_set.cc; path = db/version_set.cc; sourceTree = ""; }; + 201546695CF3D52D60210122BDFF3E7E /* FSnapshotHolder.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FSnapshotHolder.m; path = Firebase/Database/Core/FSnapshotHolder.m; sourceTree = ""; }; + 2020EB583038820A8F046C0F7333E258 /* GDTCORAssert.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORAssert.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORAssert.m; sourceTree = ""; }; + 202FECE0B01AE375A5581D0AF0CD65DC /* FIRCLSDownloadAndSaveSettingsOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSDownloadAndSaveSettingsOperation.h; path = Crashlytics/Crashlytics/Settings/Operations/FIRCLSDownloadAndSaveSettingsOperation.h; sourceTree = ""; }; + 20A3257A6A273B703A8672D312E810BC /* FSRWebSocket.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FSRWebSocket.h; path = Firebase/Database/third_party/SocketRocket/FSRWebSocket.h; sourceTree = ""; }; + 20F65E754FAE83D2F6BC09ECC325D367 /* FIRCLSInternalLogging.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSInternalLogging.h; path = Crashlytics/Crashlytics/Helpers/FIRCLSInternalLogging.h; sourceTree = ""; }; + 20FC90F276553AA05ECB6EF16C5A6B91 /* memtable.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = memtable.cc; path = db/memtable.cc; sourceTree = ""; }; + 2119A43FC2055B4CAD9C9366757C2F8D /* sync_session.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = sync_session.cpp; path = Realm/ObjectStore/src/sync/sync_session.cpp; sourceTree = ""; }; + 21901FB1986D506361381CD549E3C54C /* FIRCLSExecutionIdentifierModel.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSExecutionIdentifierModel.h; path = Crashlytics/Crashlytics/Models/FIRCLSExecutionIdentifierModel.h; sourceTree = ""; }; + 220A25CEC1AFF54BC96AF07DDC87C9D1 /* FListenProvider.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FListenProvider.m; path = Firebase/Database/Core/FListenProvider.m; sourceTree = ""; }; + 2251A9A4EAB8404DBDC603AD34F4C0C3 /* FIRTransactionResult.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRTransactionResult.m; path = Firebase/Database/Api/FIRTransactionResult.m; sourceTree = ""; }; + 22E9080549317A0B9D3BC1664D5BF3A1 /* FTupleObjectNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FTupleObjectNode.h; path = Firebase/Database/Utilities/Tuples/FTupleObjectNode.h; sourceTree = ""; }; + 22F2A4295E515E6562FF43CA3D56B0C5 /* RLMPlatform.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMPlatform.h; path = include/RLMPlatform.h; sourceTree = ""; }; + 234795C49420633C02D08BCF4C26DEFE /* APLevelDB.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = APLevelDB.mm; path = "Firebase/Database/third_party/Wrap-leveldb/APLevelDB.mm"; sourceTree = ""; }; + 2357030ACCEDF3D048244D1CD81A2FE5 /* FIRPhoneAuthCredential.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRPhoneAuthCredential.m; path = Firebase/Auth/Source/AuthProvider/Phone/FIRPhoneAuthCredential.m; sourceTree = ""; }; + 239AF3F89B458885B216E48410EAE11F /* FIRDataSnapshot_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRDataSnapshot_Private.h; path = Firebase/Database/Api/Private/FIRDataSnapshot_Private.h; sourceTree = ""; }; + 23B445FBF7E9205D356BA3B046D1B560 /* FIRTwitterAuthCredential.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRTwitterAuthCredential.m; path = Firebase/Auth/Source/AuthProvider/Twitter/FIRTwitterAuthCredential.m; sourceTree = ""; }; + 23C8057C3DC87D78E48F5584770EFD16 /* memtable.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = memtable.h; path = db/memtable.h; sourceTree = ""; }; + 23DF59E8BD83BA3C2517751E2094EB69 /* FStorageEngine.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FStorageEngine.h; path = Firebase/Database/Persistence/FStorageEngine.h; sourceTree = ""; }; + 242F1FE16F59BBC9DA78B2379E82B553 /* FIRGetAccountInfoRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRGetAccountInfoRequest.h; path = Firebase/Auth/Source/Backend/RPC/FIRGetAccountInfoRequest.h; sourceTree = ""; }; + 24447E8A1A1FD7DA7430C702460DFBA5 /* GDTCORTargets.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORTargets.h; path = GoogleDataTransport/GDTCORLibrary/Public/GDTCORTargets.h; sourceTree = ""; }; + 24527F70FC49CD3DC18B482D54CEBBCC /* nanopb.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = nanopb.xcconfig; sourceTree = ""; }; + 24687D124A95BE37510B66E9E894EE66 /* FIRComponentContainerInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRComponentContainerInternal.h; path = FirebaseCore/Sources/Private/FIRComponentContainerInternal.h; sourceTree = ""; }; + 24D9D48BC5E4BA52E3C33B89E2D8E97D /* Pods-GeekbrainsUI-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-GeekbrainsUI-acknowledgements.plist"; sourceTree = ""; }; + 24E5A5CFF7D16B79BC71F15A786A9A1D /* RLMCollection.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMCollection.mm; path = Realm/RLMCollection.mm; sourceTree = ""; }; + 250253DBF9928DBD2E9DC314A05278B6 /* FPath.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FPath.m; path = Firebase/Database/Core/Utilities/FPath.m; sourceTree = ""; }; + 251BC7C8DC3F888A067DA6D70D010182 /* FViewCache.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FViewCache.m; path = Firebase/Database/Core/View/FViewCache.m; sourceTree = ""; }; + 25599B221CFAA5E8FA3F0627E7ECED5B /* FIRAuth.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAuth.m; path = Firebase/Auth/Source/Auth/FIRAuth.m; sourceTree = ""; }; + 2563B7FA0AFA76EF66CE71EF56FCCD68 /* FIRInstanceIDDefines.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstanceIDDefines.h; path = Firebase/InstanceID/FIRInstanceIDDefines.h; sourceTree = ""; }; + 25870563374C629A654D4D9F67C26E56 /* FIRResetPasswordResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRResetPasswordResponse.h; path = Firebase/Auth/Source/Backend/RPC/FIRResetPasswordResponse.h; sourceTree = ""; }; + 25F7B3F02D143776CB94A26EBDFF748C /* FirebaseDatabase-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "FirebaseDatabase-dummy.m"; sourceTree = ""; }; + 2633BB2A259FF573B8C32BA48394F9EA /* FIRAuthProvider.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAuthProvider.m; path = Firebase/Auth/Source/AuthProvider/FIRAuthProvider.m; sourceTree = ""; }; + 263E917FB64CD4F1F2F9FF7E460337EE /* GULSecureCoding.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULSecureCoding.h; path = GoogleUtilities/Environment/Public/GULSecureCoding.h; sourceTree = ""; }; + 2651B13DEBB7991B322A3A7FB2910F41 /* FIRGoogleAuthCredential.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRGoogleAuthCredential.m; path = Firebase/Auth/Source/AuthProvider/Google/FIRGoogleAuthCredential.m; sourceTree = ""; }; + 26CA7503642099111938AB974E6A6646 /* crc32c.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = crc32c.h; path = util/crc32c.h; sourceTree = ""; }; + 26D38478C1F46C17F95B881A52894550 /* FEventGenerator.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FEventGenerator.m; path = Firebase/Database/FEventGenerator.m; sourceTree = ""; }; + 26FB8101B9FB466D0751B3CB5748F260 /* FIRDeleteAccountResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRDeleteAccountResponse.m; path = Firebase/Auth/Source/Backend/RPC/FIRDeleteAccountResponse.m; sourceTree = ""; }; + 271C3E4D470D1279F3BDF15337AC90A3 /* object.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = object.cpp; path = Realm/ObjectStore/src/object.cpp; sourceTree = ""; }; + 27319C0EE4A55D23AA99620BCD4D9635 /* FDataEvent.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FDataEvent.m; path = Firebase/Database/Core/View/FDataEvent.m; sourceTree = ""; }; + 277703D6A54E896B59FCD81720BC2B6C /* FIRAuthCredential_Internal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthCredential_Internal.h; path = Firebase/Auth/Source/AuthProvider/FIRAuthCredential_Internal.h; sourceTree = ""; }; + 279EF69F64F33BF43FDC19F468B28D41 /* RLMSyncConfiguration_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMSyncConfiguration_Private.h; path = include/RLMSyncConfiguration_Private.h; sourceTree = ""; }; + 27F5D34050A15487B1386AFF70C4D37F /* FirebaseInstallations-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "FirebaseInstallations-umbrella.h"; sourceTree = ""; }; + 280E81411122E10A4A03FF10AD9C6A26 /* FIRCLSFABAsyncOperation_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSFABAsyncOperation_Private.h; path = Crashlytics/Shared/FIRCLSOperation/FIRCLSFABAsyncOperation_Private.h; sourceTree = ""; }; + 28BB76344ECE64A25E705BF2FE69B522 /* FirebaseCore-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "FirebaseCore-umbrella.h"; sourceTree = ""; }; + 28E542ED56611F4DE0D324E090838C02 /* FIRCLSURLSessionDataTask.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSURLSessionDataTask.h; path = Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionDataTask.h; sourceTree = ""; }; + 28EBE76BF2FAC20F0C4AF4D2B258B996 /* arena.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = arena.h; path = util/arena.h; sourceTree = ""; }; + 29051F2C16CCD3952E36E6E505E5A87D /* FTupleFirebase.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FTupleFirebase.m; path = Firebase/Database/Utilities/Tuples/FTupleFirebase.m; sourceTree = ""; }; + 290E99182B80CBF746F852D5E031EBE8 /* hash.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = hash.cc; path = util/hash.cc; sourceTree = ""; }; + 292DEE854FE9B92BC4022FE701F7F7BB /* FIRErrors.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRErrors.m; path = FirebaseCore/Sources/FIRErrors.m; sourceTree = ""; }; + 295A4F158D05314703424E49E9949C24 /* RLMUpdateChecker.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMUpdateChecker.mm; path = Realm/RLMUpdateChecker.mm; sourceTree = ""; }; + 29B851296EE421C0D17845278B4F85D9 /* FIRCLSURLBuilder.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSURLBuilder.h; path = Crashlytics/Shared/FIRCLSNetworking/FIRCLSURLBuilder.h; sourceTree = ""; }; + 29C7D31341E23D15A2CE7EFACFF94955 /* GULSwizzler.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULSwizzler.h; path = GoogleUtilities/MethodSwizzler/Private/GULSwizzler.h; sourceTree = ""; }; + 29F6FF2DD8EE8092E26BF602E06784B1 /* results_notifier.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = results_notifier.cpp; path = Realm/ObjectStore/src/impl/results_notifier.cpp; sourceTree = ""; }; + 2A42E18B44FC16B56095378382D15E84 /* FIRCLSURLSession.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSURLSession.m; path = Crashlytics/Crashlytics/FIRCLSURLSession/FIRCLSURLSession.m; sourceTree = ""; }; + 2A4392999CE0325AA7FA065483BFAEC0 /* Kingfisher.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = Kingfisher.modulemap; sourceTree = ""; }; + 2A750E0A59FE2F41846AA2DBD149EB42 /* FUtilities.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FUtilities.m; path = Firebase/Database/Utilities/FUtilities.m; sourceTree = ""; }; + 2A78F3E5F4F2D135399B1A15D5C933D8 /* Results.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Results.swift; path = RealmSwift/Results.swift; sourceTree = ""; }; + 2A82FCB7D1E389CB7FB62D54B1074343 /* FIRHeartbeatInfo.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRHeartbeatInfo.h; path = FirebaseCore/Sources/Private/FIRHeartbeatInfo.h; sourceTree = ""; }; + 2A90F3C5CA0BC7204B47164345A8A9FD /* pb_encode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = pb_encode.h; sourceTree = ""; }; + 2A91D478CC110936C320DEC2AC5D6C4A /* GTMSessionFetcher.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMSessionFetcher.h; path = Source/GTMSessionFetcher.h; sourceTree = ""; }; + 2AA4910C2C27CDBE0B5098825D5EF432 /* FAtomicNumber.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FAtomicNumber.m; path = Firebase/Database/Utilities/FAtomicNumber.m; sourceTree = ""; }; + 2AA8410864480F8EAE29609C2480122F /* GDTCORRegistrar.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORRegistrar.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m; sourceTree = ""; }; + 2AA97EC8B857DC53B769FDB0770E3780 /* FViewCache.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FViewCache.h; path = Firebase/Database/Core/View/FViewCache.h; sourceTree = ""; }; + 2AAA2976A5267694F455FD69D8CF7288 /* GDTCORRegistrar_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORRegistrar_Private.h; path = GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h; sourceTree = ""; }; + 2AAA54DE66F1A6513DBC8807276C372A /* FIRUserInfoImpl.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRUserInfoImpl.h; path = Firebase/Auth/Source/User/FIRUserInfoImpl.h; sourceTree = ""; }; + 2AB5C720F820C5225F158C693B9DA549 /* Request.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Request.swift; path = Source/Request.swift; sourceTree = ""; }; + 2ABDD21C27AB2B0A39AF2EF7DDC29B9B /* filter_block.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = filter_block.h; path = table/filter_block.h; sourceTree = ""; }; + 2AE67C3CC542BA36613ADD23D682E7E8 /* FIRAuthAPNSTokenManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthAPNSTokenManager.h; path = Firebase/Auth/Source/SystemService/FIRAuthAPNSTokenManager.h; sourceTree = ""; }; + 2B416EEE307E63FA4BE4FE698193B180 /* FIRInstanceIDConstants.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstanceIDConstants.h; path = Firebase/InstanceID/FIRInstanceIDConstants.h; sourceTree = ""; }; + 2B92BE5EC3F82D429268873BAFBA0062 /* FIRCLSGlobals.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSGlobals.h; path = Crashlytics/Crashlytics/Components/FIRCLSGlobals.h; sourceTree = ""; }; + 2BABED548D8BFF82EB08193CC4A45DAD /* RLMNetworkClient.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMNetworkClient.mm; path = Realm/RLMNetworkClient.mm; sourceTree = ""; }; + 2BCA06E9D725F8CE559CBBD8AE84C237 /* FIRSignUpNewUserRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRSignUpNewUserRequest.h; path = Firebase/Auth/Source/Backend/RPC/FIRSignUpNewUserRequest.h; sourceTree = ""; }; + 2C1F67FAB1701104A2037545DAF700E1 /* FIRCLSApplication.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSApplication.h; path = Crashlytics/Crashlytics/Components/FIRCLSApplication.h; sourceTree = ""; }; + 2C2E2C159D6A12D606AEC93F92C6A9B4 /* FWebSocketConnection.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FWebSocketConnection.h; path = Firebase/Database/Realtime/FWebSocketConnection.h; sourceTree = ""; }; + 2C4D3D5179372BBE9C5DB7F166F2A132 /* GTMSessionFetcher.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = GTMSessionFetcher.modulemap; sourceTree = ""; }; + 2C87B89758929819681BF0D49D90466A /* table_builder.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = table_builder.h; path = include/leveldb/table_builder.h; sourceTree = ""; }; + 2D2540B39435F5F149667EA4419EA0E1 /* FirebaseDatabase.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FirebaseDatabase.xcconfig; sourceTree = ""; }; + 2D2B159BBE380986ACF63E8FCA676F50 /* FChildrenNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FChildrenNode.m; path = Firebase/Database/Snapshot/FChildrenNode.m; sourceTree = ""; }; + 2D5F1AEA9CDB1B1C269E91CB1B3F3445 /* FIRInstallationsIIDStore.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsIIDStore.h; path = FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDStore.h; sourceTree = ""; }; + 2DA0D814DFCB860D31D7BCD63D795858 /* FirebaseInstanceID.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = FirebaseInstanceID.framework; path = FirebaseInstanceID.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 2DF7BA27509E8E936610AD1EC2F69E0E /* FirebaseAuth-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "FirebaseAuth-dummy.m"; sourceTree = ""; }; + 2E2E8CF85E48F9328C0DF12B83D1C95C /* nanopb.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = nanopb.modulemap; sourceTree = ""; }; + 2E35C429DC58D57B84D7B20DF3B4C1C0 /* FIRAnalyticsConfiguration.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAnalyticsConfiguration.h; path = FirebaseCore/Sources/Private/FIRAnalyticsConfiguration.h; sourceTree = ""; }; + 2E665B2C77520F4B9C4C1FC99B6178F3 /* RLMRealmConfiguration+Sync.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = "RLMRealmConfiguration+Sync.mm"; path = "Realm/RLMRealmConfiguration+Sync.mm"; sourceTree = ""; }; + 2E738E61D112BE0639284CE1910475F6 /* hash.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = hash.h; path = util/hash.h; sourceTree = ""; }; + 2E927F00B9D8E3AC6775F6EF61528A7B /* FTupleNodePath.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FTupleNodePath.h; path = Firebase/Database/Utilities/Tuples/FTupleNodePath.h; sourceTree = ""; }; + 2E99648ED1451094B62427E18BF42988 /* FIRPhoneAuthProvider.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRPhoneAuthProvider.h; path = Firebase/Auth/Source/Public/FIRPhoneAuthProvider.h; sourceTree = ""; }; + 2ED92C57899F320C729B4357A050A1A2 /* FIRGitHubAuthProvider.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRGitHubAuthProvider.m; path = Firebase/Auth/Source/AuthProvider/GitHub/FIRGitHubAuthProvider.m; sourceTree = ""; }; + 2F0622B5942605ACE1CD6F484496A943 /* FCompoundWrite.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FCompoundWrite.h; path = Firebase/Database/Snapshot/FCompoundWrite.h; sourceTree = ""; }; + 2F21900E3E54F11BE2C2FDCF11D66AD8 /* FIRComponentContainer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRComponentContainer.h; path = FirebaseCore/Sources/Private/FIRComponentContainer.h; sourceTree = ""; }; + 2F472D69D8352A62D70BBD53662D3F22 /* FIRCLSSignal.c */ = {isa = PBXFileReference; includeInIndex = 1; name = FIRCLSSignal.c; path = Crashlytics/Crashlytics/Handlers/FIRCLSSignal.c; sourceTree = ""; }; + 2F49F567A0C73278FD58192F5C783B3F /* FTreeSortedDictionaryEnumerator.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FTreeSortedDictionaryEnumerator.h; path = Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FTreeSortedDictionaryEnumerator.h; sourceTree = ""; }; + 2F55AA6C4C4C66BDF9695A3801D2CAC0 /* FIRInstanceIDCheckinStore.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstanceIDCheckinStore.m; path = Firebase/InstanceID/FIRInstanceIDCheckinStore.m; sourceTree = ""; }; + 2F5D5F1A5915467DCA49F80BEBDA35D7 /* GULMutableDictionary.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULMutableDictionary.m; path = GoogleUtilities/Network/GULMutableDictionary.m; sourceTree = ""; }; + 2F6D5F74532AA44009E25B4EF7564210 /* FConstants.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FConstants.m; path = Firebase/Database/Constants/FConstants.m; sourceTree = ""; }; + 2FA369AC0E257B40E40EA47FBE09AF93 /* FIRCLSFCRAnalytics.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSFCRAnalytics.m; path = Crashlytics/Crashlytics/Helpers/FIRCLSFCRAnalytics.m; sourceTree = ""; }; + 2FAEFF7F1001278848CAD0FC8200F1D0 /* GoogleDataTransport-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "GoogleDataTransport-dummy.m"; sourceTree = ""; }; + 2FB0B6C28B89775B2C2465949315371C /* FIRCLSCompactUnwind.c */ = {isa = PBXFileReference; includeInIndex = 1; name = FIRCLSCompactUnwind.c; path = Crashlytics/Crashlytics/Unwind/Compact/FIRCLSCompactUnwind.c; sourceTree = ""; }; + 2FF6795513CF63D818090FDBBBEA2E17 /* FIRGoogleAuthProvider.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRGoogleAuthProvider.m; path = Firebase/Auth/Source/AuthProvider/Google/FIRGoogleAuthProvider.m; sourceTree = ""; }; + 3005F5102DB183A0E49E1772309D4254 /* FStringUtilities.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FStringUtilities.m; path = Firebase/Database/Utilities/FStringUtilities.m; sourceTree = ""; }; + 3022CC4D2BD5802E1A96A2789D9BE909 /* PromisesObjC.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = PromisesObjC.xcconfig; sourceTree = ""; }; + 303B212D272E5495BC2D7E46FA8627EC /* FIRCLSDataCollectionToken.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSDataCollectionToken.m; path = Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionToken.m; sourceTree = ""; }; + 305EA5D5EE4C80D4090DBE62176EEF93 /* FIRComponentContainer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRComponentContainer.m; path = FirebaseCore/Sources/FIRComponentContainer.m; sourceTree = ""; }; + 3064795A5FC37862FBA863669AE9B292 /* FIREmailLinkSignInRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIREmailLinkSignInRequest.h; path = Firebase/Auth/Source/Backend/RPC/FIREmailLinkSignInRequest.h; sourceTree = ""; }; + 309A0F8D2A82297BB270F70596692677 /* FIRAuthAppCredentialManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAuthAppCredentialManager.m; path = Firebase/Auth/Source/SystemService/FIRAuthAppCredentialManager.m; sourceTree = ""; }; + 30B0C1A58B0B3ABC8A2A09E6B26FB29A /* FBLPromisePrivate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FBLPromisePrivate.h; path = Sources/FBLPromises/include/FBLPromisePrivate.h; sourceTree = ""; }; + 30C26803AF33201B20BEF6D6C4B3644A /* NSData+SRB64Additions.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSData+SRB64Additions.m"; path = "Firebase/Database/third_party/SocketRocket/NSData+SRB64Additions.m"; sourceTree = ""; }; + 30E22880820C5CA49AE883F198B59C88 /* FIRCLSdSYM.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSdSYM.m; path = Crashlytics/Shared/FIRCLSMachO/FIRCLSdSYM.m; sourceTree = ""; }; + 313BFED252E82ED3C22F7B4C5A62C8E3 /* FIRCLSDwarfUnwindRegisters.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSDwarfUnwindRegisters.h; path = Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDwarfUnwindRegisters.h; sourceTree = ""; }; + 31779D461F51001529ECAEC1E271F0ED /* uuid.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = uuid.cpp; path = Realm/ObjectStore/src/util/uuid.cpp; sourceTree = ""; }; + 3261405AB6EB845D9659614D600F2F86 /* FIRUser.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRUser.m; path = Firebase/Auth/Source/User/FIRUser.m; sourceTree = ""; }; + 32C8B2FFBD9350F82EB2C05DD3F08DE1 /* FIRCLSReportUploader.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSReportUploader.m; path = Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader.m; sourceTree = ""; }; + 32EA256C170EBC175572FBECB2D4E5BA /* random.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = random.h; path = util/random.h; sourceTree = ""; }; + 32FA6F057E064B56ED5DDA9F8F32E1E2 /* FIRUserMetadata.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRUserMetadata.h; path = Firebase/Auth/Source/Public/FIRUserMetadata.h; sourceTree = ""; }; + 330910696891AFD35E57ECB357CA3C1A /* RLMRealm+Sync.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = "RLMRealm+Sync.mm"; path = "Realm/RLMRealm+Sync.mm"; sourceTree = ""; }; + 331CF223F983590C6FA72BA0697801EE /* FIRAuthBackend.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAuthBackend.m; path = Firebase/Auth/Source/Backend/FIRAuthBackend.m; sourceTree = ""; }; + 3347A1AB6546F0A3977529B8F199DC41 /* FBLPromises.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = FBLPromises.framework; path = PromisesObjC.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 33B783F133658E23EE462D0FE56B550F /* FIRDataSnapshot.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRDataSnapshot.m; path = Firebase/Database/Api/FIRDataSnapshot.m; sourceTree = ""; }; + 33BF8AF65FFDF2C266CD299113C4B977 /* GDTCCTNanopbHelpers.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCCTNanopbHelpers.m; path = GoogleDataTransportCCTSupport/GDTCCTLibrary/GDTCCTNanopbHelpers.m; sourceTree = ""; }; + 33EAA367E623B3A0DE2D79620F85FE4A /* FIRFacebookAuthProvider.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRFacebookAuthProvider.h; path = Firebase/Auth/Source/Public/FIRFacebookAuthProvider.h; sourceTree = ""; }; + 33EBA20CB596F5C5EC3DD2C203BF1399 /* GULLogger.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULLogger.m; path = GoogleUtilities/Logger/GULLogger.m; sourceTree = ""; }; + 341FA0A9A90666DFB068B02CF77FC23D /* FEventGenerator.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FEventGenerator.h; path = Firebase/Database/FEventGenerator.h; sourceTree = ""; }; + 342EE17D12E4A25729726E220C30E625 /* leveldb-library.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "leveldb-library.modulemap"; sourceTree = ""; }; + 34E1813A0BA2F284FD8E92D086FCDB79 /* FIRInstanceIDCheckinPreferences_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstanceIDCheckinPreferences_Private.h; path = Firebase/InstanceID/FIRInstanceIDCheckinPreferences_Private.h; sourceTree = ""; }; + 35668BE5CB78FFDA28FD2AE333563DCA /* AFError.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AFError.swift; path = Source/AFError.swift; sourceTree = ""; }; + 358E651244C190AC57D4A02BC264D248 /* FIREmailAuthProvider.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIREmailAuthProvider.m; path = Firebase/Auth/Source/AuthProvider/Email/FIREmailAuthProvider.m; sourceTree = ""; }; + 3628F98B2E091B183A6B8A870F66D7E5 /* Sync.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Sync.swift; path = RealmSwift/Sync.swift; sourceTree = ""; }; + 3676FCA755C86DD19676A81765B989FB /* status.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = status.cc; path = util/status.cc; sourceTree = ""; }; + 36801A35862CCA94D4DAC7925FC64064 /* FIRAuthStoredUserManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthStoredUserManager.h; path = Firebase/Auth/Source/SystemService/FIRAuthStoredUserManager.h; sourceTree = ""; }; + 36981A91C6866AF9E85E651837C03BF0 /* Filter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Filter.swift; path = Sources/Image/Filter.swift; sourceTree = ""; }; + 36AFC1106DADF5E8344D92559B57CF5E /* Pods-GeekbrainsUI-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-GeekbrainsUI-acknowledgements.markdown"; sourceTree = ""; }; + 36D67A48DF56131EF94BFE935D7A514F /* testutil.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = testutil.h; path = util/testutil.h; sourceTree = ""; }; + 36D9D83D36A50A8C08DC14EB474F9405 /* GULNetworkURLSession.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULNetworkURLSession.h; path = GoogleUtilities/Network/Private/GULNetworkURLSession.h; sourceTree = ""; }; + 36FE3A34041E5EC9A58F9DCF49A0AC21 /* FIRCLSDemangleOperation.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = FIRCLSDemangleOperation.mm; path = Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSDemangleOperation.mm; sourceTree = ""; }; + 372C5DBFC4877032EA5292324F62B37A /* Realm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Realm.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 379C4299BB9ED1CBA0A5405CD9449C81 /* FIRCLSOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSOperation.h; path = Crashlytics/Shared/FIRCLSOperation/FIRCLSOperation.h; sourceTree = ""; }; + 37BAD9B7E3A3D8E9AA3D878E3A56DEB4 /* Validation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Validation.swift; path = Source/Validation.swift; sourceTree = ""; }; + 37CA0488689836CFBCF7D357059D5DF2 /* FIRInstanceIDTokenStore.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstanceIDTokenStore.m; path = Firebase/InstanceID/FIRInstanceIDTokenStore.m; sourceTree = ""; }; + 380BA422DBB8CB845C91F2DDE5BDEA5D /* GTMSessionFetcherService.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMSessionFetcherService.h; path = Source/GTMSessionFetcherService.h; sourceTree = ""; }; + 3824719E1E7D4113D6F9CEC8E3DC27E9 /* FIRInstallationsHTTPError.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsHTTPError.h; path = FirebaseInstallations/Source/Library/Errors/FIRInstallationsHTTPError.h; sourceTree = ""; }; + 38739C86586A2293AE424FDEB62F9E2D /* FIRGitHubAuthCredential.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRGitHubAuthCredential.m; path = Firebase/Auth/Source/AuthProvider/GitHub/FIRGitHubAuthCredential.m; sourceTree = ""; }; + 387531976064EAD3DDF02EBEB38F7D41 /* FIRTwitterAuthProvider.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRTwitterAuthProvider.h; path = Firebase/Auth/Source/Public/FIRTwitterAuthProvider.h; sourceTree = ""; }; + 39662E3477B966D0CC14DFA1C666B797 /* FRepoManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FRepoManager.h; path = Firebase/Database/Core/FRepoManager.h; sourceTree = ""; }; + 39C548D8FA8FED7C63B92834D761E16A /* GoogleUtilities.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GoogleUtilities.xcconfig; sourceTree = ""; }; + 39E0145EC88B5CC77C8D6E175B178068 /* FirebaseDatabase.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = FirebaseDatabase.modulemap; sourceTree = ""; }; + 3A0042AA567D40B19AEB176865699923 /* FIRAdditionalUserInfo_Internal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAdditionalUserInfo_Internal.h; path = Firebase/Auth/Source/User/FIRAdditionalUserInfo_Internal.h; sourceTree = ""; }; + 3A10D68E59112CCF8B91F5994B13A897 /* FIRCreateAuthURIRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCreateAuthURIRequest.m; path = Firebase/Auth/Source/Backend/RPC/FIRCreateAuthURIRequest.m; sourceTree = ""; }; + 3A540E35BC1F914EDFFB3FC921C77F85 /* GULAppDelegateSwizzler.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULAppDelegateSwizzler.h; path = GoogleUtilities/AppDelegateSwizzler/Private/GULAppDelegateSwizzler.h; sourceTree = ""; }; + 3A9D78AA155958A76A77ACF07046CD17 /* Box.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Box.swift; path = Sources/Utility/Box.swift; sourceTree = ""; }; + 3AB5615A6CD7B79D890755A1CFA31206 /* GTMSessionUploadFetcher.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMSessionUploadFetcher.h; path = Source/GTMSessionUploadFetcher.h; sourceTree = ""; }; + 3AB877196209774BF84D1C73F0BB5CDB /* FIRAuthKeychainServices.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthKeychainServices.h; path = Firebase/Auth/Source/Storage/FIRAuthKeychainServices.h; sourceTree = ""; }; + 3ABD1B08F068B8BFA864424DE777BBAC /* FIREmailLinkSignInResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIREmailLinkSignInResponse.m; path = Firebase/Auth/Source/Backend/RPC/FIREmailLinkSignInResponse.m; sourceTree = ""; }; + 3AC054B5DF290D1F23F402CB55328FE6 /* FIRCLSNetworkOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSNetworkOperation.h; path = Crashlytics/Crashlytics/Settings/Operations/FIRCLSNetworkOperation.h; sourceTree = ""; }; + 3AC09B19CF882809F37562A3B14201EF /* ParameterEncoding.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ParameterEncoding.swift; path = Source/ParameterEncoding.swift; sourceTree = ""; }; + 3ADEC01442125E8A31963729EC8049FB /* FIRCLSURLSessionDownloadTask_PrivateMethods.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSURLSessionDownloadTask_PrivateMethods.h; path = Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionDownloadTask_PrivateMethods.h; sourceTree = ""; }; + 3B12B0B698ECE39D5E6EDB89687CCC6D /* FIRInstanceIDVersionUtilities.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstanceIDVersionUtilities.m; path = Firebase/InstanceID/FIRInstanceIDVersionUtilities.m; sourceTree = ""; }; + 3B2C03F3C9EBC5D74F0DC6F8340D952F /* dumpfile.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = dumpfile.cc; path = db/dumpfile.cc; sourceTree = ""; }; + 3B72C47749C5D7545714AB0DF04FBBFC /* FNamedNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FNamedNode.h; path = Firebase/Database/FNamedNode.h; sourceTree = ""; }; + 3B938CCEBEB19930A5B085AFF34B90CB /* FIRAuthKeychainServices.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAuthKeychainServices.m; path = Firebase/Auth/Source/Storage/FIRAuthKeychainServices.m; sourceTree = ""; }; + 3BE0225CF21C0F1263F67167183C57B7 /* FIRCLSSettingsOnboardingManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSSettingsOnboardingManager.h; path = Crashlytics/Crashlytics/Settings/FIRCLSSettingsOnboardingManager.h; sourceTree = ""; }; + 3C32659A8FC6851EE6D06CCC7EAAC1D2 /* FIRInstanceIDCheckinPreferences+Internal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FIRInstanceIDCheckinPreferences+Internal.h"; path = "Firebase/InstanceID/FIRInstanceIDCheckinPreferences+Internal.h"; sourceTree = ""; }; + 3C44A9357C6BC090C6B509C35F23A97C /* slice.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = slice.h; path = include/leveldb/slice.h; sourceTree = ""; }; + 3C55C132C630B644EE7681983CD71CBE /* FirebaseCoreDiagnosticsInterop.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FirebaseCoreDiagnosticsInterop.xcconfig; sourceTree = ""; }; + 3C5B12E4F847E35CA6D5536264282E44 /* FirebaseCoreDiagnostics-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "FirebaseCoreDiagnostics-umbrella.h"; sourceTree = ""; }; + 3C6AA9924912484D21FE7B82B8982939 /* FLLRBValueNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FLLRBValueNode.h; path = Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FLLRBValueNode.h; sourceTree = ""; }; + 3C777B4EE7D85558581EF1A3FC747D8F /* filter_policy.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = filter_policy.cc; path = util/filter_policy.cc; sourceTree = ""; }; + 3CEFA5A5C281A74704488F114ED873F9 /* FRepoInfo.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FRepoInfo.m; path = Firebase/Database/Core/FRepoInfo.m; sourceTree = ""; }; + 3CF201389ACD1CAF85EDD83AC49F3F48 /* RLMObjectSchema.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMObjectSchema.h; path = include/RLMObjectSchema.h; sourceTree = ""; }; + 3E2AB05A3BCDC8A6EE6F25EB3C50D4BD /* transact_log_handler.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = transact_log_handler.cpp; path = Realm/ObjectStore/src/impl/transact_log_handler.cpp; sourceTree = ""; }; + 3E43572942B487228C2EFDD3DBD087F6 /* FIRAuthWebViewController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAuthWebViewController.m; path = Firebase/Auth/Source/Utilities/FIRAuthWebViewController.m; sourceTree = ""; }; + 3E606567B7AC74BAC31708ED387EF522 /* Realm.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Realm.xcconfig; sourceTree = ""; }; + 3E69A7D068230FF36FEBEBF212AD9F60 /* ObjectSchema.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ObjectSchema.swift; path = RealmSwift/ObjectSchema.swift; sourceTree = ""; }; + 3E74C743B156DCE4E62F27FFCD7EF37C /* RLMOptionalBase.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMOptionalBase.mm; path = Realm/RLMOptionalBase.mm; sourceTree = ""; }; + 3E8366B108065E2E1A6A3144FEEEA1BE /* GDTCORTransformer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORTransformer.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORTransformer.m; sourceTree = ""; }; + 3EBCEDAB2FDE3140557CB10ACEB94398 /* FIRCLSOnboardingOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSOnboardingOperation.h; path = Crashlytics/Crashlytics/Settings/Operations/FIRCLSOnboardingOperation.h; sourceTree = ""; }; + 3EE9D117F16778F5DEE15B2DDD50E85C /* FirebaseCore-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "FirebaseCore-Info.plist"; sourceTree = ""; }; + 3F348EE51DA0A7FF10B6342F43FBB6E4 /* FIRCLSThreadState.c */ = {isa = PBXFileReference; includeInIndex = 1; name = FIRCLSThreadState.c; path = Crashlytics/Crashlytics/Helpers/FIRCLSThreadState.c; sourceTree = ""; }; + 3F5C590A93C8398FAFCB14C57DD38BE4 /* FIRAuthAppCredential.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAuthAppCredential.m; path = Firebase/Auth/Source/SystemService/FIRAuthAppCredential.m; sourceTree = ""; }; + 3F717187275F3B68D7A159A0D95761C9 /* FIRVerifyAssertionRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRVerifyAssertionRequest.m; path = Firebase/Auth/Source/Backend/RPC/FIRVerifyAssertionRequest.m; sourceTree = ""; }; + 3FBDB654C4BF020215028800CA80C5AB /* thread_annotations.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = thread_annotations.h; path = port/thread_annotations.h; sourceTree = ""; }; + 403BD7A51021AE9EB9B6974BC33D6FC5 /* dbformat.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = dbformat.h; path = db/dbformat.h; sourceTree = ""; }; + 40DBA65B3D00E4A32BBE97886A532D4F /* Placeholder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Placeholder.swift; path = Sources/Image/Placeholder.swift; sourceTree = ""; }; + 40E5AB8EBAC57F183B9FCD5346C75EE8 /* FIRInstallationsItem.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsItem.m; path = FirebaseInstallations/Source/Library/FIRInstallationsItem.m; sourceTree = ""; }; + 40E7FAFFEE1A98D4288EFF0823FE7396 /* FIRCLSURLSessionTask.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSURLSessionTask.m; path = Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionTask.m; sourceTree = ""; }; + 4103DB404A4E635F97770345F2161B76 /* FirebaseCore.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = FirebaseCore.modulemap; sourceTree = ""; }; + 411E765A494550268DE9F0C35C384549 /* RLMManagedArray.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMManagedArray.mm; path = Realm/RLMManagedArray.mm; sourceTree = ""; }; + 4129260763BBAB48DD6DB625D6A7C462 /* FIRPhoneAuthCredential_Internal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRPhoneAuthCredential_Internal.h; path = Firebase/Auth/Source/AuthProvider/Phone/FIRPhoneAuthCredential_Internal.h; sourceTree = ""; }; + 415B7669334DDA6ABC48A6A59B119A62 /* FLimitedFilter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FLimitedFilter.h; path = Firebase/Database/Core/View/Filter/FLimitedFilter.h; sourceTree = ""; }; + 41F3CA4CED72C78893F58B11B23859E2 /* RedirectHandler.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RedirectHandler.swift; path = Sources/Networking/RedirectHandler.swift; sourceTree = ""; }; + 41FB9FC4D66CF7E99640E00C16D6C174 /* SizeExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SizeExtensions.swift; path = Sources/Utility/SizeExtensions.swift; sourceTree = ""; }; + 424932417FD4CF653498EA1D5A70693F /* FIRAppAssociationRegistration.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAppAssociationRegistration.m; path = FirebaseCore/Sources/FIRAppAssociationRegistration.m; sourceTree = ""; }; + 42C609BAD1B799B524A14D497B26FE5A /* Realm.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = Realm.modulemap; sourceTree = ""; }; + 434EA703D1FB868AB9B838002A4ED5B6 /* RLMRealmConfiguration.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMRealmConfiguration.h; path = include/RLMRealmConfiguration.h; sourceTree = ""; }; + 43556C7CE86F4F7EA7D42D92CC4B0A64 /* Pods-GeekbrainsUI-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-GeekbrainsUI-dummy.m"; sourceTree = ""; }; + 4357D6FFBA0D73ACFEF6882C53C1892B /* NSError+RLMSync.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSError+RLMSync.m"; path = "Realm/NSError+RLMSync.m"; sourceTree = ""; }; + 437157BAE39DA22356AB037E9CBFD59D /* FConstants.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FConstants.h; path = Firebase/Database/Constants/FConstants.h; sourceTree = ""; }; + 437919EE08EC6BFCCBAC3BD346309742 /* RealmSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = RealmSwift.framework; path = RealmSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 43B1E4CD7B30B9FD278100133C2AC788 /* FirebaseAuth.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = FirebaseAuth.framework; path = FirebaseAuth.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4485A1A98C3F3A02896CC18D5D8181BC /* FIRVerifyPasswordRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRVerifyPasswordRequest.m; path = Firebase/Auth/Source/Backend/RPC/FIRVerifyPasswordRequest.m; sourceTree = ""; }; + 44C335C2BC7873351D63E1C641F7DA5E /* FIRCLSSignal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSSignal.h; path = Crashlytics/Crashlytics/Handlers/FIRCLSSignal.h; sourceTree = ""; }; + 44F6C89B42122E6D3B49FF370FAE179E /* list_notifier.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = list_notifier.cpp; path = Realm/ObjectStore/src/impl/list_notifier.cpp; sourceTree = ""; }; + 4519A8667149020DB9E7BF440C7C99AE /* dbformat.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = dbformat.cc; path = db/dbformat.cc; sourceTree = ""; }; + 454E843E89166D83EA808A0C6441D07D /* FIRCLSLogger.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSLogger.m; path = Crashlytics/Crashlytics/Helpers/FIRCLSLogger.m; sourceTree = ""; }; + 4580E4AEE938A2AD447D44D7A2136CD4 /* FIRAuthDefaultUIDelegate.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAuthDefaultUIDelegate.m; path = Firebase/Auth/Source/Utilities/FIRAuthDefaultUIDelegate.m; sourceTree = ""; }; + 45918AC6C3F376881E09B1CAAC467EF3 /* RLMAnalytics.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMAnalytics.mm; path = Realm/RLMAnalytics.mm; sourceTree = ""; }; + 459B070289EE1BA904BD423482CA91B1 /* FValueEventRegistration.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FValueEventRegistration.h; path = Firebase/Database/Core/View/FValueEventRegistration.h; sourceTree = ""; }; + 45DABEADF709E0735D0285EC28D630BF /* FIRAuthExceptionUtils.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthExceptionUtils.h; path = Firebase/Auth/Source/Utilities/FIRAuthExceptionUtils.h; sourceTree = ""; }; + 461E3BF8391B34CA8ECC0B7327B86304 /* FIRSendVerificationCodeRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRSendVerificationCodeRequest.m; path = Firebase/Auth/Source/Backend/RPC/FIRSendVerificationCodeRequest.m; sourceTree = ""; }; + 462F3DF1EE8A1A326EFB498FFE50BA2E /* FValueIndex.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FValueIndex.m; path = Firebase/Database/FValueIndex.m; sourceTree = ""; }; + 463D13D8E2BAA12948556D3B5ADD54FA /* arena.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = arena.cc; path = util/arena.cc; sourceTree = ""; }; + 4678982B233A0BCD199D2DF9A374B3D2 /* FIRInstallationsLogger.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsLogger.m; path = FirebaseInstallations/Source/Library/FIRInstallationsLogger.m; sourceTree = ""; }; + 46889AB3C5F944AB30292A698C5E2ADB /* coding.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = coding.h; path = util/coding.h; sourceTree = ""; }; + 469C011AA04EEACFF286EA5E284DE545 /* FIRInstanceIDTokenOperation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstanceIDTokenOperation.m; path = Firebase/InstanceID/FIRInstanceIDTokenOperation.m; sourceTree = ""; }; + 471FB2C8149FECE847C73CCCD34D3622 /* FIRAuthAPNSToken.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAuthAPNSToken.m; path = Firebase/Auth/Source/SystemService/FIRAuthAPNSToken.m; sourceTree = ""; }; + 4813F514E16E244D3B40BBF1CB195098 /* FIRCLSApplicationIdentifierModel.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSApplicationIdentifierModel.m; path = Crashlytics/Crashlytics/Settings/Models/FIRCLSApplicationIdentifierModel.m; sourceTree = ""; }; + 48340EDA59FB01AE26CE1757E9B8002C /* nanopb-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "nanopb-prefix.pch"; sourceTree = ""; }; + 4859B5C57D1455BEDCFD395EC04ECFF2 /* FValueIndex.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FValueIndex.h; path = Firebase/Database/FValueIndex.h; sourceTree = ""; }; + 485C5BE10BC126E11DD2D60E0EA4588F /* FirebaseCoreDiagnostics.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = FirebaseCoreDiagnostics.modulemap; sourceTree = ""; }; + 486997BAA7094533C8CDDD157CFF96BB /* log_reader.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = log_reader.h; path = db/log_reader.h; sourceTree = ""; }; + 4877558843115B5DDB96AA54123D8AFB /* FConnection.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FConnection.m; path = Firebase/Database/Realtime/FConnection.m; sourceTree = ""; }; + 4911B687FA7409D98B78D92E8FDD1F59 /* FIRInstanceIDTokenManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstanceIDTokenManager.h; path = Firebase/InstanceID/FIRInstanceIDTokenManager.h; sourceTree = ""; }; + 49258A7388501C116D60F7B0F6E09199 /* iterator.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = iterator.cc; path = table/iterator.cc; sourceTree = ""; }; + 49944035B0ED6067E46A755A89EA18AB /* FEmptyNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FEmptyNode.h; path = Firebase/Database/Snapshot/FEmptyNode.h; sourceTree = ""; }; + 49BAE7C9C1895B04BCBF3BD4937DE479 /* sync_metadata.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = sync_metadata.cpp; path = Realm/ObjectStore/src/sync/impl/sync_metadata.cpp; sourceTree = ""; }; + 4A4EDAEBE0ACD7112E19375E06DEF681 /* FIROptionsInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIROptionsInternal.h; path = FirebaseCore/Sources/Private/FIROptionsInternal.h; sourceTree = ""; }; + 4A60C4735224475581B5618F0141BFDC /* FIRInstallationsStatus.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsStatus.h; path = FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsStatus.h; sourceTree = ""; }; + 4A88EA3ED2B76186B85C1671B3D3CB89 /* FIRCLSFCRAnalytics.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSFCRAnalytics.h; path = Crashlytics/Crashlytics/Helpers/FIRCLSFCRAnalytics.h; sourceTree = ""; }; + 4AA009C94CE5B49342CE6C02D1B216B0 /* FChildChangeAccumulator.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FChildChangeAccumulator.h; path = Firebase/Database/Core/View/Filter/FChildChangeAccumulator.h; sourceTree = ""; }; + 4ABE65F669873B77346F0A4C50731AFD /* RealmSwift-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "RealmSwift-dummy.m"; sourceTree = ""; }; + 4ACA40A65A478DF9FA2F1A6435A82040 /* GDTCORReachability_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORReachability_Private.h; path = GoogleDataTransport/GDTCORLibrary/Private/GDTCORReachability_Private.h; sourceTree = ""; }; + 4ADC6F364C16F8DBEFB7ECB280A76B7C /* GDTFLLPrioritizer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTFLLPrioritizer.m; path = GoogleDataTransportCCTSupport/GDTCCTLibrary/GDTFLLPrioritizer.m; sourceTree = ""; }; + 4B325D27B8A5F2CA76E7C81532F652F8 /* FBLPromise+Reduce.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Reduce.m"; path = "Sources/FBLPromises/FBLPromise+Reduce.m"; sourceTree = ""; }; + 4B38D7E5D75C23FEADEF10B01ABF6096 /* FIRCLSMultipartMimeStreamEncoder.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSMultipartMimeStreamEncoder.m; path = Crashlytics/Shared/FIRCLSNetworking/FIRCLSMultipartMimeStreamEncoder.m; sourceTree = ""; }; + 4B484F345ABB94B6FDED330218918FCE /* FIndexedFilter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIndexedFilter.m; path = Firebase/Database/Core/View/Filter/FIndexedFilter.m; sourceTree = ""; }; + 4B528ABCAC79A8EE228A8B0D1B8A80D8 /* FWriteTree.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FWriteTree.m; path = Firebase/Database/Core/FWriteTree.m; sourceTree = ""; }; + 4B7BFBF1D2DBD09D1AB8932EDA355D05 /* FBLPromise+Any.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Any.m"; path = "Sources/FBLPromises/FBLPromise+Any.m"; sourceTree = ""; }; + 4BA2CDB5CB3429FAB9DDA4F6DC6C5283 /* FIRCrashlytics.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCrashlytics.m; path = Crashlytics/Crashlytics/FIRCrashlytics.m; sourceTree = ""; }; + 4BBC28B17DDA8EBA784B3D214C810B32 /* FIRInstanceIDVersionUtilities.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstanceIDVersionUtilities.h; path = Firebase/InstanceID/FIRInstanceIDVersionUtilities.h; sourceTree = ""; }; + 4BF75C24DD34A3FE44821FA4D298BC83 /* FIRInstanceID.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstanceID.h; path = Firebase/InstanceID/Public/FIRInstanceID.h; sourceTree = ""; }; + 4BFD56E0F5FBE4CD874A18EBA3C08777 /* FIRInstallationsIDController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsIDController.m; path = FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsIDController.m; sourceTree = ""; }; + 4C6616311432B3C00DD556941EC5A1CA /* FBLPromise+Recover.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Recover.h"; path = "Sources/FBLPromises/include/FBLPromise+Recover.h"; sourceTree = ""; }; + 4C6F01CEA86B55CC5A4FD7FEEDA27AF0 /* GDTCORStorage.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORStorage.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORStorage.m; sourceTree = ""; }; + 4CAD8C29BDA630B438644EEE766E02BE /* FIRGitHubAuthProvider.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRGitHubAuthProvider.h; path = Firebase/Auth/Source/Public/FIRGitHubAuthProvider.h; sourceTree = ""; }; + 4CE85E49B4DFEF79F939FA8F668F45EB /* FIROAuthCredential.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIROAuthCredential.h; path = Firebase/Auth/Source/Public/FIROAuthCredential.h; sourceTree = ""; }; + 4D45C50A9735924375C923A4C204802D /* GDTCORUploadPackage_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORUploadPackage_Private.h; path = GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadPackage_Private.h; sourceTree = ""; }; + 4D7092FB504C43E51EBF941876B70D43 /* KingfisherOptionsInfo.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KingfisherOptionsInfo.swift; path = Sources/General/KingfisherOptionsInfo.swift; sourceTree = ""; }; + 4D756894BE4E37E0CA32A46F1BB2FDD9 /* FirebaseCrashlytics.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FirebaseCrashlytics.h; path = Crashlytics/Crashlytics/Public/FirebaseCrashlytics.h; sourceTree = ""; }; + 4D928597FB30E7EF35AAC58AE3A428C9 /* RLMRealm+Sync.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "RLMRealm+Sync.h"; path = "include/RLMRealm+Sync.h"; sourceTree = ""; }; + 4DA5BCC58F028330E799E8CD4AC48BC8 /* FirebaseInstanceID-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "FirebaseInstanceID-Info.plist"; sourceTree = ""; }; + 4DA81CC3DB932CD235271CE085F91D47 /* table.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = table.h; path = include/leveldb/table.h; sourceTree = ""; }; + 4DCE3C53BB9884A39FABEBD81671136A /* FBLPromise+All.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+All.m"; path = "Sources/FBLPromises/FBLPromise+All.m"; sourceTree = ""; }; + 4DFB179009B0825D8D02E8E8104ADBAD /* FRepo_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FRepo_Private.h; path = Firebase/Database/Core/FRepo_Private.h; sourceTree = ""; }; + 4E01D63CDAC12AFE0048E676F37EB6DA /* Realm-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Realm-dummy.m"; sourceTree = ""; }; + 4E8447705007CCB88C9E6571A0E8E19F /* FTrackedQueryManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FTrackedQueryManager.m; path = Firebase/Database/Persistence/FTrackedQueryManager.m; sourceTree = ""; }; + 4EB7F0FA43F7178842857FB5902D9EF6 /* FIRCLSException.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSException.h; path = Crashlytics/Crashlytics/Handlers/FIRCLSException.h; sourceTree = ""; }; + 4EE1BC1AF3606BB8B6FF9CF50F00E04F /* GDTCORConsoleLogger.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORConsoleLogger.h; path = GoogleDataTransport/GDTCORLibrary/Public/GDTCORConsoleLogger.h; sourceTree = ""; }; + 4F76C10D403D26FC1854D798DD54CFD5 /* Response.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Response.swift; path = Source/Response.swift; sourceTree = ""; }; + 4FA95262E393CB9DD325CB12247F48B1 /* FIRUserInfo.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRUserInfo.h; path = Firebase/Auth/Source/Public/FIRUserInfo.h; sourceTree = ""; }; + 500E2C11C74FFB9F16D95F36DB1892FE /* port_stdcxx.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = port_stdcxx.h; path = port/port_stdcxx.h; sourceTree = ""; }; + 504885B54A8E74421A4A94921F685393 /* FIRCLSURLSessionUploadTask.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSURLSessionUploadTask.m; path = Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionUploadTask.m; sourceTree = ""; }; + 506EAE82BD3E4008D6B467A84A76A492 /* env_posix_test_helper.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = env_posix_test_helper.h; path = util/env_posix_test_helper.h; sourceTree = ""; }; + 50DD131FEF677E8FF47C059527C19566 /* bloom.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = bloom.cc; path = util/bloom.cc; sourceTree = ""; }; + 50FA543897C7A9B21B74C83B4F812945 /* Pods-GeekbrainsUI.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-GeekbrainsUI.release.xcconfig"; sourceTree = ""; }; + 5115577DA06DFFCE6071348C83697F2E /* FBLPromise+Wrap.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Wrap.m"; path = "Sources/FBLPromises/FBLPromise+Wrap.m"; sourceTree = ""; }; + 51377F1CD083FC389612859E450139CD /* FBLPromise+Any.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Any.h"; path = "Sources/FBLPromises/include/FBLPromise+Any.h"; sourceTree = ""; }; + 5144D239598058957529301947B65E99 /* FRepoInfo.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FRepoInfo.h; path = Firebase/Database/Core/FRepoInfo.h; sourceTree = ""; }; + 51610B5A284CCD1C06EF04C565381EFA /* GULSceneDelegateSwizzler.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULSceneDelegateSwizzler.m; path = GoogleUtilities/SceneDelegateSwizzler/GULSceneDelegateSwizzler.m; sourceTree = ""; }; + 51671C73F008B5C0C3751B3855999213 /* FirebaseDatabase.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = FirebaseDatabase.framework; path = FirebaseDatabase.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 51895595CC0B4F2941F4790FCE674D1A /* FIRUser_Internal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRUser_Internal.h; path = Firebase/Auth/Source/User/FIRUser_Internal.h; sourceTree = ""; }; + 51ABE87E1BDF51DA9905E18093D794E7 /* FIRPhoneAuthProvider.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRPhoneAuthProvider.m; path = Firebase/Auth/Source/AuthProvider/Phone/FIRPhoneAuthProvider.m; sourceTree = ""; }; + 51C104BFA0ECC362AD5D0AECB29D661A /* Alamofire-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Alamofire-umbrella.h"; sourceTree = ""; }; + 51EAA16DB197D1E1AF196BDAF5D6EE86 /* UIButton+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UIButton+Kingfisher.swift"; path = "Sources/Extensions/UIButton+Kingfisher.swift"; sourceTree = ""; }; + 51EED64CBA9532DACC7881BBFE117AE7 /* FWebSocketConnection.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FWebSocketConnection.m; path = Firebase/Database/Realtime/FWebSocketConnection.m; sourceTree = ""; }; + 524B6D68BA7AD2123C9FADC2891CFA91 /* FIRSecureTokenRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRSecureTokenRequest.m; path = Firebase/Auth/Source/Backend/RPC/FIRSecureTokenRequest.m; sourceTree = ""; }; + 5292DA815D495D19C1091AD168722BFA /* RLMSyncUtil.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMSyncUtil.h; path = include/RLMSyncUtil.h; sourceTree = ""; }; + 52E21E6ED4BA9DF24FAB3428B4CA51D7 /* GDTCOREvent.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCOREvent.h; path = GoogleDataTransport/GDTCORLibrary/Public/GDTCOREvent.h; sourceTree = ""; }; + 52EF11B4BCFC895E51708A2429C5961D /* FServerValues.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FServerValues.h; path = Firebase/Database/Core/FServerValues.h; sourceTree = ""; }; + 53006D0A08EF8EA62C89F6BBCD7F0023 /* RLMAccessor.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMAccessor.mm; path = Realm/RLMAccessor.mm; sourceTree = ""; }; + 53143A7A200C31CAE42A2C9E2612AB3D /* FIROAuthProvider.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIROAuthProvider.h; path = Firebase/Auth/Source/Public/FIROAuthProvider.h; sourceTree = ""; }; + 533BFCBA4D346ADED0C0CB42B1E4516B /* FServerValues.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FServerValues.m; path = Firebase/Database/Core/FServerValues.m; sourceTree = ""; }; + 535BC6C89C9E415076459A3A026CB580 /* FIRCLSReportManager_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSReportManager_Private.h; path = Crashlytics/Crashlytics/Controllers/FIRCLSReportManager_Private.h; sourceTree = ""; }; + 53773438319C44A00E27C7B5EC550A5D /* FPathIndex.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FPathIndex.m; path = Firebase/Database/FPathIndex.m; sourceTree = ""; }; + 53E222268FFB208D7AE83FD0A67262EF /* log_writer.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = log_writer.cc; path = db/log_writer.cc; sourceTree = ""; }; + 53FF4D24482305D95F31AEAE76AABAF1 /* FIRInstallationsVersion.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsVersion.h; path = FirebaseInstallations/Source/Library/Public/FIRInstallationsVersion.h; sourceTree = ""; }; + 5432F4FAF7753183AD5FE130957921B2 /* FIRCLSMachOSlice.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSMachOSlice.h; path = Crashlytics/Shared/FIRCLSMachO/FIRCLSMachOSlice.h; sourceTree = ""; }; + 548A09288743BC6316E5BF2D9B619111 /* FIRInstanceIDTokenFetchOperation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstanceIDTokenFetchOperation.m; path = Firebase/InstanceID/FIRInstanceIDTokenFetchOperation.m; sourceTree = ""; }; + 54BCAF9B5A0B2D03CAC1FE8411689C95 /* RLMRealm.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMRealm.mm; path = Realm/RLMRealm.mm; sourceTree = ""; }; + 5553476A2B706AC8C40BDB00B6C8F0E7 /* FIRBundleUtil.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRBundleUtil.m; path = FirebaseCore/Sources/FIRBundleUtil.m; sourceTree = ""; }; + 558D32172C384450CA880936CFFD6474 /* Realm-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Realm-prefix.pch"; sourceTree = ""; }; + 5614F6A681D5A4E43CEE7570A1512BE2 /* FIRCreateAuthURIRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCreateAuthURIRequest.h; path = Firebase/Auth/Source/Backend/RPC/FIRCreateAuthURIRequest.h; sourceTree = ""; }; + 5649E1776CB127F532FB761BCE7C3A02 /* FTreeNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FTreeNode.m; path = Firebase/Database/Core/Utilities/FTreeNode.m; sourceTree = ""; }; + 565075BC42365FE68F251EE460368715 /* NSButton+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "NSButton+Kingfisher.swift"; path = "Sources/Extensions/NSButton+Kingfisher.swift"; sourceTree = ""; }; + 5651E58B832E9102932FB169B9827F12 /* FIRInstallationsStoredItem.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsStoredItem.m; path = FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredItem.m; sourceTree = ""; }; + 56AE672BAC2AB1B75187E390E306EFF9 /* GDTCOREventTransformer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCOREventTransformer.h; path = GoogleDataTransport/GDTCORLibrary/Public/GDTCOREventTransformer.h; sourceTree = ""; }; + 56BAA01862A49A9C10732D4D3648B154 /* FIndexedFilter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIndexedFilter.h; path = Firebase/Database/Core/View/Filter/FIndexedFilter.h; sourceTree = ""; }; + 572DD60020066485B722152AB1526E6C /* AuthenticationChallengeResponsable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AuthenticationChallengeResponsable.swift; path = Sources/Networking/AuthenticationChallengeResponsable.swift; sourceTree = ""; }; + 57654FCDEAC6AA86CFE3FA58FC422D05 /* GDTCORUploadCoordinator.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORUploadCoordinator.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORUploadCoordinator.m; sourceTree = ""; }; + 57ADEE7E78BA08FD1D671853FF64AE36 /* FIRAuthWebUtils.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthWebUtils.h; path = Firebase/Auth/Source/Utilities/FIRAuthWebUtils.h; sourceTree = ""; }; + 57B386B4636728846C6CC917511CD61B /* FIRInstallations.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallations.h; path = FirebaseInstallations/Source/Library/Public/FIRInstallations.h; sourceTree = ""; }; + 57ECB80AD960D18878792A5FCE8B29A9 /* system_configuration.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = system_configuration.cpp; path = Realm/ObjectStore/src/sync/impl/apple/system_configuration.cpp; sourceTree = ""; }; + 58B38973A7F5AAF8DEB3EEC6F846C4DD /* FIRInstallationsItem+RegisterInstallationAPI.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FIRInstallationsItem+RegisterInstallationAPI.h"; path = "FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsItem+RegisterInstallationAPI.h"; sourceTree = ""; }; + 58C58D0B86BC10D15B6E228B4E5AE4E0 /* FirebaseInstanceID.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = FirebaseInstanceID.modulemap; sourceTree = ""; }; + 5908FBDB35CB84240FA819996966DCDC /* Kingfisher-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Kingfisher-umbrella.h"; sourceTree = ""; }; + 5914E005B72AC06DA8A3F4F516C69DC0 /* NetworkReachabilityManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NetworkReachabilityManager.swift; path = Source/NetworkReachabilityManager.swift; sourceTree = ""; }; + 598F4D17D3BB845BB5FDDAFDE6D2A423 /* RLMSyncSession.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMSyncSession.mm; path = Realm/RLMSyncSession.mm; sourceTree = ""; }; + 59BAE894F428C0EBEC29AFC6A19B8F4D /* FIndex.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIndex.m; path = Firebase/Database/FIndex.m; sourceTree = ""; }; + 59C49C4C92798FC3200909C43B9F8857 /* FIRInstallationsAPIService.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsAPIService.h; path = FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsAPIService.h; sourceTree = ""; }; + 5AB59EF111D16A4E566C8249C4094FEE /* WKInterfaceImage+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "WKInterfaceImage+Kingfisher.swift"; path = "Sources/Extensions/WKInterfaceImage+Kingfisher.swift"; sourceTree = ""; }; + 5AD79BA8B34169A833C65779487B1402 /* GULNetworkConstants.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULNetworkConstants.m; path = GoogleUtilities/Network/GULNetworkConstants.m; sourceTree = ""; }; + 5B5835EC2334D400F3E828323AF7B311 /* GDTCORTransformer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORTransformer.h; path = GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer.h; sourceTree = ""; }; + 5B61095D647294428C62980474737F22 /* FIRCoreDiagnosticsConnector.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCoreDiagnosticsConnector.h; path = FirebaseCore/Sources/Private/FIRCoreDiagnosticsConnector.h; sourceTree = ""; }; + 5C41758045A7C6E1ABE7A948D9112321 /* FIRDatabaseQuery.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRDatabaseQuery.m; path = Firebase/Database/Api/FIRDatabaseQuery.m; sourceTree = ""; }; + 5C4BD82707AB6BB86675E6BC6E48F081 /* FIRAuth_Internal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuth_Internal.h; path = Firebase/Auth/Source/Auth/FIRAuth_Internal.h; sourceTree = ""; }; + 5CBDE172A494EEE7A9545B91E85F13F1 /* testharness.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = testharness.cc; path = util/testharness.cc; sourceTree = ""; }; + 5CED633A28DB53CEBA59E9F81F542E4C /* FIRIMessageCode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRIMessageCode.h; path = Firebase/InstanceID/FIRIMessageCode.h; sourceTree = ""; }; + 5CF5AFAF7139E4C1022247757F7538D2 /* FIRSignUpNewUserResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRSignUpNewUserResponse.h; path = Firebase/Auth/Source/Backend/RPC/FIRSignUpNewUserResponse.h; sourceTree = ""; }; + 5D2293CC1383BBA537B11200CD92E5A9 /* GoogleDataTransportCCTSupport-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "GoogleDataTransportCCTSupport-Info.plist"; sourceTree = ""; }; + 5D72F806127CD4AB5276FB13A330E7F0 /* RLMSchema.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMSchema.mm; path = Realm/RLMSchema.mm; sourceTree = ""; }; + 5D797E9A5C5782CE845840781FA1CC81 /* Alamofire.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Alamofire.framework; path = Alamofire.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 5D9F16D73D1B59742905EDD0C2966E3F /* FBLPromise+Delay.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Delay.h"; path = "Sources/FBLPromises/include/FBLPromise+Delay.h"; sourceTree = ""; }; + 5DA795C79D20C355B2589B1B2F35081F /* GULApplication.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULApplication.h; path = GoogleUtilities/AppDelegateSwizzler/Private/GULApplication.h; sourceTree = ""; }; + 5DADE1727943B7611BB868DDDB6DEEEE /* AnimatedImageView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnimatedImageView.swift; path = Sources/Views/AnimatedImageView.swift; sourceTree = ""; }; + 5DC79D7BFDA8E6A84FEDA85A96D893DA /* FSRWebSocket.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FSRWebSocket.m; path = Firebase/Database/third_party/SocketRocket/FSRWebSocket.m; sourceTree = ""; }; + 5DC9B08796CD1B484EC6E692D733DA40 /* FIRVerifyCustomTokenRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRVerifyCustomTokenRequest.m; path = Firebase/Auth/Source/Backend/RPC/FIRVerifyCustomTokenRequest.m; sourceTree = ""; }; + 5DF25EF9B5A1097A37C5DCCE3ACBDFE0 /* Source.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Source.swift; path = Sources/General/ImageSource/Source.swift; sourceTree = ""; }; + 5E0433FE9144F9A78BCF219EF77AF2A0 /* GTMSessionFetcherLogging.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMSessionFetcherLogging.m; path = Source/GTMSessionFetcherLogging.m; sourceTree = ""; }; + 5E19A3D63F5C2BBA363FC9457BC537F4 /* FBLPromise+Always.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Always.m"; path = "Sources/FBLPromises/FBLPromise+Always.m"; sourceTree = ""; }; + 5E6A13DEC67A77C4FE36FE85C0086A25 /* Timeline.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Timeline.swift; path = Source/Timeline.swift; sourceTree = ""; }; + 5E7434C839FCA37203C0A13316CB7E88 /* FBLPromise+Do.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Do.h"; path = "Sources/FBLPromises/include/FBLPromise+Do.h"; sourceTree = ""; }; + 5EB53EDD70E5582A5BDAC4B127DB2C90 /* FIRAuthInternalErrors.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthInternalErrors.h; path = Firebase/Auth/Source/Utilities/FIRAuthInternalErrors.h; sourceTree = ""; }; + 5EC5794E1FB56C2379167138CEA77AA2 /* FIRCLSURLSessionAvailability.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSURLSessionAvailability.h; path = Crashlytics/Crashlytics/FIRCLSURLSession/FIRCLSURLSessionAvailability.h; sourceTree = ""; }; + 5ECEECF341CFC266359914D96F0FCCAB /* GULNetworkConstants.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULNetworkConstants.h; path = GoogleUtilities/Network/Private/GULNetworkConstants.h; sourceTree = ""; }; + 5EFCCC9B6FCB73B30D97FD7D962050D3 /* CallbackQueue.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CallbackQueue.swift; path = Sources/Utility/CallbackQueue.swift; sourceTree = ""; }; + 5F02C49F70C721CD4A62B36B7DD111DC /* FIRSecureTokenService.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRSecureTokenService.m; path = Firebase/Auth/Source/SystemService/FIRSecureTokenService.m; sourceTree = ""; }; + 5F28695C975FEEBF7604AEAA004C04A7 /* RLMSyncUser.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMSyncUser.mm; path = Realm/RLMSyncUser.mm; sourceTree = ""; }; + 5F3B738EDC7663C20ECDA7758B5F3CC1 /* RLMObservation.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMObservation.mm; path = Realm/RLMObservation.mm; sourceTree = ""; }; + 5F5B97326C121B9F44A5F24C0E2D338D /* FIRInstanceIDURLQueryItem.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstanceIDURLQueryItem.h; path = Firebase/InstanceID/FIRInstanceIDURLQueryItem.h; sourceTree = ""; }; + 5FC6B6785F984F1BA0AF1DDE3867F66B /* FIRAuthAPNSTokenManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAuthAPNSTokenManager.m; path = Firebase/Auth/Source/SystemService/FIRAuthAPNSTokenManager.m; sourceTree = ""; }; + 5FE8768E83C4E59BEA71F5CC6DAEF45B /* leveldb-library-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "leveldb-library-Info.plist"; sourceTree = ""; }; + 5FED4C233DF99D6E196D993E5E3C7ADD /* FIREmailPasswordAuthCredential.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIREmailPasswordAuthCredential.h; path = Firebase/Auth/Source/AuthProvider/Email/FIREmailPasswordAuthCredential.h; sourceTree = ""; }; + 5FF094A51019D603B58A07B6AFB78F0B /* FIRVerifyAssertionResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRVerifyAssertionResponse.h; path = Firebase/Auth/Source/Backend/RPC/FIRVerifyAssertionResponse.h; sourceTree = ""; }; + 6065ADD321D1A7A79F4A25057175A3E4 /* GDTCORClock.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORClock.h; path = GoogleDataTransport/GDTCORLibrary/Public/GDTCORClock.h; sourceTree = ""; }; + 60B44DC898C5948A7EA0035AC015B100 /* FOverwrite.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FOverwrite.m; path = Firebase/Database/Core/Operation/FOverwrite.m; sourceTree = ""; }; + 6133E402BA8E16E6642B59393592D3F4 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/Accelerate.framework; sourceTree = DEVELOPER_DIR; }; + 619E5249FFEF1993E7929824BF208BBC /* FIRCLSMachOBinary.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSMachOBinary.h; path = Crashlytics/Shared/FIRCLSMachO/FIRCLSMachOBinary.h; sourceTree = ""; }; + 61C01EB057182563C6FDADA78D82D50B /* FIRSetAccountInfoResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRSetAccountInfoResponse.h; path = Firebase/Auth/Source/Backend/RPC/FIRSetAccountInfoResponse.h; sourceTree = ""; }; + 61F59F538C624FC9A76C7C50D4FFAA8B /* binding_callback_thread_observer.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = binding_callback_thread_observer.cpp; path = Realm/ObjectStore/src/binding_callback_thread_observer.cpp; sourceTree = ""; }; + 6233E271ED15D5C5A037C7BC902D9AA2 /* FIRCLSSymbolicationOperation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSSymbolicationOperation.m; path = Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSSymbolicationOperation.m; sourceTree = ""; }; + 6253723D977FDDB11DE98CCA90A7FA7F /* FParsedUrl.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FParsedUrl.h; path = Firebase/Database/Utilities/FParsedUrl.h; sourceTree = ""; }; + 6275266DDE055A2E1BCB1626FD4239DF /* FIRCLSURLSessionDownloadTask.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSURLSessionDownloadTask.m; path = Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionDownloadTask.m; sourceTree = ""; }; + 6277C8B46289F5D129CC3124B79552D8 /* FIRAEvent.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAEvent.h; path = Crashlytics/Crashlytics/Helpers/FIRAEvent.h; sourceTree = ""; }; + 62ED77C4EA723E5D0037503EEE89A489 /* Firebase.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = Firebase.h; path = CoreOnly/Sources/Firebase.h; sourceTree = ""; }; + 62F021E6B2E996937A7F48891975F654 /* CacheSerializer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CacheSerializer.swift; path = Sources/Cache/CacheSerializer.swift; sourceTree = ""; }; + 62FD01C83412004B1C76E346EAEB52FD /* FCachePolicy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FCachePolicy.m; path = Firebase/Database/Persistence/FCachePolicy.m; sourceTree = ""; }; + 62FF18EBA9F70278FDEB4F9759A9D445 /* FIRCLSDefines.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSDefines.h; path = Crashlytics/Crashlytics/Helpers/FIRCLSDefines.h; sourceTree = ""; }; + 635DDB80F0882C70D7DD08F88EFA542B /* GDTFLLPrioritizer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTFLLPrioritizer.h; path = GoogleDataTransportCCTSupport/GDTCCTLibrary/Private/GDTFLLPrioritizer.h; sourceTree = ""; }; + 6363B094F036CD16B0446525FFE072C7 /* FIRErrorCode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRErrorCode.h; path = FirebaseCore/Sources/Private/FIRErrorCode.h; sourceTree = ""; }; + 639630C84534366EB2295D520602724B /* FBLPromise+Timeout.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Timeout.m"; path = "Sources/FBLPromises/FBLPromise+Timeout.m"; sourceTree = ""; }; + 63A43B2B8A0078E69FE67E3A1D21ADF4 /* FirebaseCrashlytics.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FirebaseCrashlytics.xcconfig; sourceTree = ""; }; + 63CD3209EBE09936927BED09C9BAD0E7 /* FIRCLSInternalReport.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSInternalReport.h; path = Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h; sourceTree = ""; }; + 63E633213C15674BF6EE752DA839F4D0 /* FIRCLSCompactUnwind.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSCompactUnwind.h; path = Crashlytics/Crashlytics/Unwind/Compact/FIRCLSCompactUnwind.h; sourceTree = ""; }; + 63EDC4B1811281A589A7D31C6DB07ED6 /* ImageCache.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageCache.swift; path = Sources/Cache/ImageCache.swift; sourceTree = ""; }; + 64025D1D442A26509C94DDDE4BCBF577 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; + 6411A605F957CB41FE1FC8AF5218ADD0 /* FIRAuthDispatcher.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthDispatcher.h; path = Firebase/Auth/Source/Auth/FIRAuthDispatcher.h; sourceTree = ""; }; + 6420FC4F23214E5130FCE3DC8AE70810 /* RLMSyncCredentials.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMSyncCredentials.h; path = include/RLMSyncCredentials.h; sourceTree = ""; }; + 64863531D302B03363FA66046E691FB2 /* FIRGameCenterAuthCredential.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRGameCenterAuthCredential.h; path = Firebase/Auth/Source/AuthProvider/GameCenter/FIRGameCenterAuthCredential.h; sourceTree = ""; }; + 64C2554DC46237514E52F9B969B9C72C /* fbase64.c */ = {isa = PBXFileReference; includeInIndex = 1; name = fbase64.c; path = Firebase/Database/third_party/SocketRocket/fbase64.c; sourceTree = ""; }; + 64DE1BE55CA40E16D2F37E836704D8C4 /* FWriteRecord.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FWriteRecord.m; path = Firebase/Database/Core/FWriteRecord.m; sourceTree = ""; }; + 64E603DF7C2E7F872F1E29A9869AD2E3 /* FCompleteChildSource.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FCompleteChildSource.h; path = Firebase/Database/Core/View/Filter/FCompleteChildSource.h; sourceTree = ""; }; + 6509B500C8E4B0D9AA9C1F9A8E6E9991 /* FIRCLSConstants.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSConstants.h; path = Crashlytics/Shared/FIRCLSConstants.h; sourceTree = ""; }; + 66265AC21D95957F5CF5467CDF63C31D /* SortDescriptor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SortDescriptor.swift; path = RealmSwift/SortDescriptor.swift; sourceTree = ""; }; + 6663078E0C1CBF1E77E6BA770700875A /* filter_block.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = filter_block.cc; path = table/filter_block.cc; sourceTree = ""; }; + 66653077665BFE63195D56DF6B94989E /* FIRAuthTokenResult_Internal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthTokenResult_Internal.h; path = Firebase/Auth/Source/Auth/FIRAuthTokenResult_Internal.h; sourceTree = ""; }; + 667D42BF455C022A7D8EFF143376525C /* KingfisherError.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KingfisherError.swift; path = Sources/General/KingfisherError.swift; sourceTree = ""; }; + 66B0B6220CDA636E37A9440D71E8C1F4 /* FBLPromise.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FBLPromise.h; path = Sources/FBLPromises/include/FBLPromise.h; sourceTree = ""; }; + 67593AB87D75C2524D1512196BCDF77C /* FIRCLSdSYM.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSdSYM.h; path = Crashlytics/Shared/FIRCLSMachO/FIRCLSdSYM.h; sourceTree = ""; }; + 675DAF0FD379B5DE1611353667173D80 /* RLMRealm_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMRealm_Private.h; path = include/RLMRealm_Private.h; sourceTree = ""; }; + 67A7FD255D12F065146BDAC2B8A4E080 /* leveldb-library-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "leveldb-library-prefix.pch"; sourceTree = ""; }; + 67AD62195DDA0F8E519B11169135F319 /* FIRSignUpNewUserRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRSignUpNewUserRequest.m; path = Firebase/Auth/Source/Backend/RPC/FIRSignUpNewUserRequest.m; sourceTree = ""; }; + 67AFD064168467D4279B84030F4A6890 /* FirebaseAnalyticsInterop.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FirebaseAnalyticsInterop.xcconfig; sourceTree = ""; }; + 67C9D0962115956B0C47D1FD9F0B6F61 /* RLMSchema_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMSchema_Private.h; path = include/RLMSchema_Private.h; sourceTree = ""; }; + 67CAF2E79A38802580080D85D7F4314D /* FWriteRecord.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FWriteRecord.h; path = Firebase/Database/Core/FWriteRecord.h; sourceTree = ""; }; + 67D6E1E49E9266C174C79BE4873DEE72 /* FIRVerifyPasswordRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRVerifyPasswordRequest.h; path = Firebase/Auth/Source/Backend/RPC/FIRVerifyPasswordRequest.h; sourceTree = ""; }; + 67DA492C3336C7C3ACE453F59A59590B /* GoogleDataTransportCCTSupport.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GoogleDataTransportCCTSupport.xcconfig; sourceTree = ""; }; + 6804BF7FE62BF9D2FE3ED2E836DCC825 /* FListenComplete.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FListenComplete.m; path = Firebase/Database/FListenComplete.m; sourceTree = ""; }; + 683557C62E339ED3A54534F38BFE56E6 /* FCacheNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FCacheNode.m; path = Firebase/Database/Core/View/FCacheNode.m; sourceTree = ""; }; + 684CC63082E6E3184BF9143A7929145F /* FIRServerValue.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRServerValue.h; path = Firebase/Database/Public/FIRServerValue.h; sourceTree = ""; }; + 6930AFD3D022185F6C9FE8A6AC2D6E1C /* FIRDatabaseReference_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRDatabaseReference_Private.h; path = Firebase/Database/Api/Private/FIRDatabaseReference_Private.h; sourceTree = ""; }; + 6942351307BC1F54575D9853307EAE0E /* GoogleDataTransportCCTSupport.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = GoogleDataTransportCCTSupport.framework; path = GoogleDataTransportCCTSupport.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6B33D0223995B0E6C58FB40F012E2D1F /* FIRCLSMachOBinary.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSMachOBinary.m; path = Crashlytics/Shared/FIRCLSMachO/FIRCLSMachOBinary.m; sourceTree = ""; }; + 6B3481856C6C6A45C6EC74F5EAED6BA7 /* FirebaseCrashlytics.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = FirebaseCrashlytics.modulemap; sourceTree = ""; }; + 6B34E88940F4E64CA1A6C9902CB11198 /* FChildEventRegistration.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FChildEventRegistration.h; path = Firebase/Database/Core/View/FChildEventRegistration.h; sourceTree = ""; }; + 6B850B1D5595EAAB345EAA2B4D0367A2 /* RealmSwift.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = RealmSwift.modulemap; sourceTree = ""; }; + 6BB5E55A11E483CC60C9D5718C2B62FB /* FIRCLSInternalReport.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSInternalReport.m; path = Crashlytics/Crashlytics/Models/FIRCLSInternalReport.m; sourceTree = ""; }; + 6BDED9776D9CD4DD8E2AAEA8FEE02214 /* RLMConstants.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RLMConstants.m; path = Realm/RLMConstants.m; sourceTree = ""; }; + 6BE53798496284F7898DAF699207435F /* FIRCLSStackFrame.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSStackFrame.h; path = Crashlytics/Crashlytics/Models/FIRCLSStackFrame.h; sourceTree = ""; }; + 6CABE765FC1C335EF38A758518B37CD0 /* FIRCLSDemangleOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSDemangleOperation.h; path = Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSDemangleOperation.h; sourceTree = ""; }; + 6D3CD3AF92AE99DC686D0D1EED4D7456 /* FTupleTSN.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FTupleTSN.h; path = Firebase/Database/Utilities/Tuples/FTupleTSN.h; sourceTree = ""; }; + 6D40ADFA16F2F4D5A0E378EEC660466C /* FIRGetOOBConfirmationCodeRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRGetOOBConfirmationCodeRequest.h; path = Firebase/Auth/Source/Backend/RPC/FIRGetOOBConfirmationCodeRequest.h; sourceTree = ""; }; + 6D60CD4074EEBE50EB3EAB68AEF7AB2A /* FIRFacebookAuthProvider.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRFacebookAuthProvider.m; path = Firebase/Auth/Source/AuthProvider/Facebook/FIRFacebookAuthProvider.m; sourceTree = ""; }; + 6DDD8062FCA61B54A3D2C4FDBBB036D0 /* FIRCLSURLSessionDataTask_PrivateMethods.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSURLSessionDataTask_PrivateMethods.h; path = Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionDataTask_PrivateMethods.h; sourceTree = ""; }; + 6DE985270C413E3DC01DBB9E906093D2 /* FLLRBEmptyNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FLLRBEmptyNode.h; path = Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FLLRBEmptyNode.h; sourceTree = ""; }; + 6E2B962F4C08A3AE087F4EEE5D28146B /* FirebaseAuth.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FirebaseAuth.h; path = Firebase/Auth/Source/Public/FirebaseAuth.h; sourceTree = ""; }; + 6E3AE9D51BAC10905A4242A2853D4BC7 /* FIRActionCodeSettings.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRActionCodeSettings.m; path = Firebase/Auth/Source/Auth/FIRActionCodeSettings.m; sourceTree = ""; }; + 6E81AB8B58F049ABA4528A79F1F344F7 /* FIRConfigurationInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRConfigurationInternal.h; path = FirebaseCore/Sources/Private/FIRConfigurationInternal.h; sourceTree = ""; }; + 6EB2F4D80081F8E6098855995CD7BFC8 /* Alamofire-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Alamofire-Info.plist"; sourceTree = ""; }; + 6EE6635D48F8FE1C35FB9578EAF4776B /* FQuerySpec.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FQuerySpec.h; path = Firebase/Database/Core/FQuerySpec.h; sourceTree = ""; }; + 6F4E1314226380D0DA7464528964096B /* GoogleDataTransportCCTSupport.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = GoogleDataTransportCCTSupport.modulemap; sourceTree = ""; }; + 6F6646222EB1681CC5AD46638F5CFC6A /* FChange.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FChange.h; path = Firebase/Database/Core/View/FChange.h; sourceTree = ""; }; + 6FFE6D0828D5AC252CA221F054787EFE /* FIRAuthGlobalWorkQueue.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthGlobalWorkQueue.h; path = Firebase/Auth/Source/Auth/FIRAuthGlobalWorkQueue.h; sourceTree = ""; }; + 7042F28BE74E0B121E4BA63670CEA317 /* GULNSData+zlib.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "GULNSData+zlib.m"; path = "GoogleUtilities/NSData+zlib/GULNSData+zlib.m"; sourceTree = ""; }; + 70440E94BFBD04E9127CB904A9F0BB6A /* GULNSData+zlib.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "GULNSData+zlib.h"; path = "GoogleUtilities/NSData+zlib/GULNSData+zlib.h"; sourceTree = ""; }; + 7060A51F91294EA617A21BB7F724F288 /* GDTCORStorage_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORStorage_Private.h; path = GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorage_Private.h; sourceTree = ""; }; + 709693C460EEE74D5CD70B2C864907E7 /* dwarf.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = dwarf.h; path = Crashlytics/third_party/libunwind/dwarf.h; sourceTree = ""; }; + 70B162E996982D9DC5A9E028820DA000 /* FIRInstallationsIIDStore.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsIIDStore.m; path = FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDStore.m; sourceTree = ""; }; + 70EAFF747CBD33292EEE920054AD38F0 /* FBLPromise+Testing.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Testing.h"; path = "Sources/FBLPromises/include/FBLPromise+Testing.h"; sourceTree = ""; }; + 71047F985E5491DAA031A643F38EB063 /* FIRAuthAppCredentialManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthAppCredentialManager.h; path = Firebase/Auth/Source/SystemService/FIRAuthAppCredentialManager.h; sourceTree = ""; }; + 710AF0CF26954951B96F5B4D3FB61552 /* FIRInstanceIDTokenInfo.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstanceIDTokenInfo.h; path = Firebase/InstanceID/FIRInstanceIDTokenInfo.h; sourceTree = ""; }; + 7128EBFC506481FE5CC45958E38FE753 /* two_level_iterator.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = two_level_iterator.h; path = table/two_level_iterator.h; sourceTree = ""; }; + 712C3E1D09C9F2171D435E81DC50FF87 /* GDTCORUploadPackage.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORUploadPackage.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORUploadPackage.m; sourceTree = ""; }; + 713C78044C95A18155B81728F5CCD2F3 /* FLimitedFilter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FLimitedFilter.m; path = Firebase/Database/Core/View/Filter/FLimitedFilter.m; sourceTree = ""; }; + 714E27F8EAD4CFB19AC1A66F7AC98A5E /* GDTCORAssert.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORAssert.h; path = GoogleDataTransport/GDTCORLibrary/Public/GDTCORAssert.h; sourceTree = ""; }; + 71721A7E88B754EEDA47EBAC240BDA55 /* GoogleUtilities-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "GoogleUtilities-dummy.m"; sourceTree = ""; }; + 717F6CB3DBCA25D802C9F51D5CA322DA /* FIndex.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIndex.h; path = Firebase/Database/FIndex.h; sourceTree = ""; }; + 71C8E228FEBA45B0E3FA9EA1E8E6F19A /* GDTCORPrioritizer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORPrioritizer.h; path = GoogleDataTransport/GDTCORLibrary/Public/GDTCORPrioritizer.h; sourceTree = ""; }; + 71D39A54D987F3A5B517EF69C6F97FA8 /* FTreeSortedDictionary.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FTreeSortedDictionary.m; path = Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FTreeSortedDictionary.m; sourceTree = ""; }; + 724B4A64760807F69FE610EA5DB525A7 /* FIRInstanceIDURLQueryItem.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstanceIDURLQueryItem.m; path = Firebase/InstanceID/FIRInstanceIDURLQueryItem.m; sourceTree = ""; }; + 72B3FC42C46A789BA9EA8C45EFDEE3C5 /* FRangedFilter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FRangedFilter.h; path = Firebase/Database/FRangedFilter.h; sourceTree = ""; }; + 72DF4F0A77FF5AB0CD50A0CBFC8009A5 /* RLMResults.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMResults.h; path = include/RLMResults.h; sourceTree = ""; }; + 7313A8ADB88E86743525E015300B75B0 /* partial_sync.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = partial_sync.cpp; path = Realm/ObjectStore/src/sync/partial_sync.cpp; sourceTree = ""; }; + 7348D3BA6708FE3622CA4B947A986519 /* FIRGetProjectConfigResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRGetProjectConfigResponse.m; path = Firebase/Auth/Source/Backend/RPC/FIRGetProjectConfigResponse.m; sourceTree = ""; }; + 73646F0031DD6F6B9E6CD16A204ED450 /* NSData+FIRBase64.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSData+FIRBase64.h"; path = "Firebase/Auth/Source/Utilities/NSData+FIRBase64.h"; sourceTree = ""; }; + 739B2D7FD0A87C1FAE63F41227CA2D46 /* FAuthTokenProvider.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FAuthTokenProvider.m; path = Firebase/Database/Login/FAuthTokenProvider.m; sourceTree = ""; }; + 73CFA238FD606E2E699DB4E8281F155B /* FirebaseCoreDiagnostics.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FirebaseCoreDiagnostics.xcconfig; sourceTree = ""; }; + 73F65244F2C613D2061EAC8DD493459D /* FTrackedQuery.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FTrackedQuery.h; path = Firebase/Database/Persistence/FTrackedQuery.h; sourceTree = ""; }; + 73F6AA37B87E12A6A1DE1ADB047E47B3 /* Pods-GeekbrainsUI-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-GeekbrainsUI-frameworks.sh"; sourceTree = ""; }; + 7409DF4877493F28BE503A9565A47E4F /* FIRInstanceIDCheckinPreferences+Internal.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FIRInstanceIDCheckinPreferences+Internal.m"; path = "Firebase/InstanceID/FIRInstanceIDCheckinPreferences+Internal.m"; sourceTree = ""; }; + 742340252C9F30970E96B4A993368D3A /* FIRCLSURLBuilder.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSURLBuilder.m; path = Crashlytics/Shared/FIRCLSNetworking/FIRCLSURLBuilder.m; sourceTree = ""; }; + 74471D3212EED130E3D5DD72463B542D /* SessionDelegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SessionDelegate.swift; path = Sources/Networking/SessionDelegate.swift; sourceTree = ""; }; + 747AF31DA5C0F0B63B2BAA9946DE6A54 /* FIRCrashlytics.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCrashlytics.h; path = Crashlytics/Crashlytics/Public/FIRCrashlytics.h; sourceTree = ""; }; + 74AAAB1D03D40969A48E2FC429D8855E /* FIRAuthAPNSTokenType.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthAPNSTokenType.h; path = Firebase/Auth/Source/Public/FIRAuthAPNSTokenType.h; sourceTree = ""; }; + 75833FCC7F6F716545752300B81D0B93 /* FBLPromise+Async.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Async.h"; path = "Sources/FBLPromises/include/FBLPromise+Async.h"; sourceTree = ""; }; + 758A2BB7C2B851B748A469A9E8B11F8B /* FIRCLSDownloadAndSaveSettingsOperation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSDownloadAndSaveSettingsOperation.m; path = Crashlytics/Crashlytics/Settings/Operations/FIRCLSDownloadAndSaveSettingsOperation.m; sourceTree = ""; }; + 76264B404DD9DCD2936938912B89838C /* Realm-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Realm-Info.plist"; sourceTree = ""; }; + 7638558D1BBBB6500FC2DB44CEEC0BE0 /* FIRCLSDwarfUnwind.c */ = {isa = PBXFileReference; includeInIndex = 1; name = FIRCLSDwarfUnwind.c; path = Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDwarfUnwind.c; sourceTree = ""; }; + 76B451276F7D9E31176301E5DE296E35 /* FBLPromise+Retry.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Retry.m"; path = "Sources/FBLPromises/FBLPromise+Retry.m"; sourceTree = ""; }; + 76B8D4AFE9CA027E2EFC28B18F209340 /* FIRCLSProcessReportOperation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSProcessReportOperation.m; path = Crashlytics/Crashlytics/Operations/Reports/FIRCLSProcessReportOperation.m; sourceTree = ""; }; + 76E8349BE330B381533D03B9E06C740B /* FIRCLSCompoundOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSCompoundOperation.h; path = Crashlytics/Shared/FIRCLSOperation/FIRCLSCompoundOperation.h; sourceTree = ""; }; + 7706CC3470134134EB96FF63B96D49DC /* FBLPromise+All.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+All.h"; path = "Sources/FBLPromises/include/FBLPromise+All.h"; sourceTree = ""; }; + 77737B379095DF2CD503F701F0598089 /* FIRCLSThreadArrayOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSThreadArrayOperation.h; path = Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSThreadArrayOperation.h; sourceTree = ""; }; + 77D06B4DBC29D02E02343A8FD4AC7C03 /* GDTCORStorage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORStorage.h; path = GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorage.h; sourceTree = ""; }; + 77EC52A10EB37C45E98D83D7BEC7D6DD /* block.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = block.h; path = table/block.h; sourceTree = ""; }; + 77FF98C591A214884B7A2823DD3E695B /* FListenComplete.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FListenComplete.h; path = Firebase/Database/FListenComplete.h; sourceTree = ""; }; + 780E38B21E5B8075DD47973628FCEF89 /* FBLPromise+Async.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Async.m"; path = "Sources/FBLPromises/FBLPromise+Async.m"; sourceTree = ""; }; + 783508FA1E09FE34ED2409D5E4E1B1C4 /* FIRCLSProfiling.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSProfiling.h; path = Crashlytics/Crashlytics/Helpers/FIRCLSProfiling.h; sourceTree = ""; }; + 78FF096304C2AD7526682458C7C2731C /* FIRComponentType.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRComponentType.h; path = FirebaseCore/Sources/Private/FIRComponentType.h; sourceTree = ""; }; + 79565DC1642A3650F2C7D67B6325765D /* FIRInstallations.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallations.m; path = FirebaseInstallations/Source/Library/FIRInstallations.m; sourceTree = ""; }; + 797A37AD003EE04FB5F70E2DE7E434DE /* FOperationSource.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FOperationSource.h; path = Firebase/Database/Core/Operation/FOperationSource.h; sourceTree = ""; }; + 7A411B5DA7E914F2C40F92038EA7A436 /* Resource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Resource.swift; path = Sources/General/ImageSource/Resource.swift; sourceTree = ""; }; + 7A61D8F6B2637A077A773A4BC2C231B6 /* FIRCoreDiagnostics.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCoreDiagnostics.m; path = Firebase/CoreDiagnostics/FIRCDLibrary/FIRCoreDiagnostics.m; sourceTree = ""; }; + 7A9E34FE56E60AFC7C356826A0D0E8E8 /* FIRInstanceID+Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FIRInstanceID+Private.h"; path = "Firebase/InstanceID/Private/FIRInstanceID+Private.h"; sourceTree = ""; }; + 7ACAD9BF8FD9E6CC233C2B8DB317F5C0 /* FIRAuthDefaultUIDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthDefaultUIDelegate.h; path = Firebase/Auth/Source/Utilities/FIRAuthDefaultUIDelegate.h; sourceTree = ""; }; + 7B01724F721919F7D7A74590E67DC81D /* FIRSecureTokenResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRSecureTokenResponse.h; path = Firebase/Auth/Source/Backend/RPC/FIRSecureTokenResponse.h; sourceTree = ""; }; + 7B022C06F6C82798E3957F914136EE12 /* FImmutableSortedSet.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FImmutableSortedSet.m; path = Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FImmutableSortedSet.m; sourceTree = ""; }; + 7B32D929B50BFC710411BE8A3002A85A /* FirebaseInstanceID.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FirebaseInstanceID.h; path = Firebase/InstanceID/Public/FirebaseInstanceID.h; sourceTree = ""; }; + 7BC112B0B8DC7D7A50DFF38A18C9056B /* FBLPromise+Retry.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Retry.h"; path = "Sources/FBLPromises/include/FBLPromise+Retry.h"; sourceTree = ""; }; + 7BEFF599A831C23E9164164D5AE2DC64 /* FIRCLSNetworkResponseHandler.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSNetworkResponseHandler.m; path = Crashlytics/Shared/FIRCLSNetworking/FIRCLSNetworkResponseHandler.m; sourceTree = ""; }; + 7C0E929DD992CE13605482A3504B4A32 /* FIRCLSLogger.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSLogger.h; path = Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h; sourceTree = ""; }; + 7C312DD383F490C02B5A71E38CF3802D /* DispatchQueue+Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DispatchQueue+Alamofire.swift"; path = "Source/DispatchQueue+Alamofire.swift"; sourceTree = ""; }; + 7C5B268E1B3ED259F849D41750BBC9B0 /* PromisesObjC-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "PromisesObjC-dummy.m"; sourceTree = ""; }; + 7C5D1ABA49A70E905CD66FC7B1E85BFD /* FIRCLSCrashedMarkerFile.c */ = {isa = PBXFileReference; includeInIndex = 1; name = FIRCLSCrashedMarkerFile.c; path = Crashlytics/Crashlytics/Components/FIRCLSCrashedMarkerFile.c; sourceTree = ""; }; + 7C7A1E38C56A5310BDCCF9B80FF7BAF1 /* FIRInstallationsLogger.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsLogger.h; path = FirebaseInstallations/Source/Library/FIRInstallationsLogger.h; sourceTree = ""; }; + 7C84AB0BC56B89E39E4251B7DF1DBF4F /* Result.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Result.swift; path = Source/Result.swift; sourceTree = ""; }; + 7C9ED60FEEB27E4F77013A3307337492 /* firebasecore.nanopb.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = firebasecore.nanopb.h; path = Firebase/CoreDiagnostics/FIRCDLibrary/Protogen/nanopb/firebasecore.nanopb.h; sourceTree = ""; }; + 7CD3E8B3D40C26292D916375AE737C9D /* FChildrenNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FChildrenNode.h; path = Firebase/Database/Snapshot/FChildrenNode.h; sourceTree = ""; }; + 7CF5F9A072CD16920377817F5691A403 /* FIRVersion.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRVersion.h; path = FirebaseCore/Sources/FIRVersion.h; sourceTree = ""; }; + 7D397FD2F6AAD22EA8990CEB78CA0371 /* FIRCLSByteUtility.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSByteUtility.h; path = Crashlytics/Shared/FIRCLSByteUtility.h; sourceTree = ""; }; + 7D7BF465F8E021B4E73DECFEAD4EDC95 /* Kingfisher-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Kingfisher-dummy.m"; sourceTree = ""; }; + 7D83116F9227F8C545FC7F83B31C6F56 /* FImmutableSortedDictionary.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FImmutableSortedDictionary.m; path = Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FImmutableSortedDictionary.m; sourceTree = ""; }; + 7E1B6BA18F688452F1DB59F99647172A /* block_builder.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = block_builder.cc; path = table/block_builder.cc; sourceTree = ""; }; + 7E276D794AB23DA07712CC60A0F1C6EC /* GoogleDataTransport.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = GoogleDataTransport.modulemap; sourceTree = ""; }; + 7E579F8382EEFCD134AD0E3538EB38EC /* FIRVerifyCustomTokenRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRVerifyCustomTokenRequest.h; path = Firebase/Auth/Source/Backend/RPC/FIRVerifyCustomTokenRequest.h; sourceTree = ""; }; + 7EC8360EB2198F697C257E552137581A /* FTupleObjects.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FTupleObjects.h; path = Firebase/Database/Utilities/Tuples/FTupleObjects.h; sourceTree = ""; }; + 7ED3622365FA02BAA65DEF9DB35A358C /* FIRInstanceIDLogger.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstanceIDLogger.h; path = Firebase/InstanceID/FIRInstanceIDLogger.h; sourceTree = ""; }; + 7EE0B27C3EE3C18B5068838918F4F384 /* FIRAuthErrorUtils.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthErrorUtils.h; path = Firebase/Auth/Source/Utilities/FIRAuthErrorUtils.h; sourceTree = ""; }; + 7EEC129EFA1105150FB920A946CD1280 /* FTupleSetIdPath.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FTupleSetIdPath.h; path = Firebase/Database/Utilities/Tuples/FTupleSetIdPath.h; sourceTree = ""; }; + 7F3F57166D63D9449D3C19EAE3B68788 /* FIRSendVerificationCodeResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRSendVerificationCodeResponse.m; path = Firebase/Auth/Source/Backend/RPC/FIRSendVerificationCodeResponse.m; sourceTree = ""; }; + 7FB1BE5072D4E905A26D9822DFBEE39F /* GDTCORLifecycle.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORLifecycle.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORLifecycle.m; sourceTree = ""; }; + 803101B9EF5EE3DE6588D22EE440EF16 /* FPersistentConnection.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FPersistentConnection.m; path = Firebase/Database/Core/FPersistentConnection.m; sourceTree = ""; }; + 807D7C288E07C2ADE943E1B2DB35B461 /* FIRInstanceIDTokenFetchOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstanceIDTokenFetchOperation.h; path = Firebase/InstanceID/FIRInstanceIDTokenFetchOperation.h; sourceTree = ""; }; + 8085970F3E66135CB37870E7D93C4126 /* FValueEventRegistration.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FValueEventRegistration.m; path = Firebase/Database/Core/View/FValueEventRegistration.m; sourceTree = ""; }; + 809D9326143CAC79986E116D1A06E548 /* FEventRegistration.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FEventRegistration.h; path = Firebase/Database/Core/View/FEventRegistration.h; sourceTree = ""; }; + 80D84EB3AF30412F46680999C05D159D /* MemoryStorage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MemoryStorage.swift; path = Sources/Cache/MemoryStorage.swift; sourceTree = ""; }; + 80E5536A228B8DDE0EBBAFAD13275DA8 /* FIRAuthURLPresenter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthURLPresenter.h; path = Firebase/Auth/Source/Utilities/FIRAuthURLPresenter.h; sourceTree = ""; }; + 80F193A96FCC90BD0DCA5A2A7ADC9F83 /* FIRCLSDwarfExpressionMachine.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSDwarfExpressionMachine.h; path = Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDwarfExpressionMachine.h; sourceTree = ""; }; + 810334097CD688DCF361B56C4A75E9B1 /* db_impl.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = db_impl.h; path = db/db_impl.h; sourceTree = ""; }; + 8194113AA335A2AA29ADC0BA26764681 /* pb_common.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = pb_common.h; sourceTree = ""; }; + 81A7B76B62951984CC65B19C4A945977 /* FIRCLSFABNetworkClient.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSFABNetworkClient.h; path = Crashlytics/Shared/FIRCLSNetworking/FIRCLSFABNetworkClient.h; sourceTree = ""; }; + 81B9FD9FC925AE5303A6C5DEDCAF092E /* logging.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = logging.cc; path = util/logging.cc; sourceTree = ""; }; + 81BD9FFB63D5FA9EF33EFC33EA7597C4 /* FBLPromise+Delay.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Delay.m"; path = "Sources/FBLPromises/FBLPromise+Delay.m"; sourceTree = ""; }; + 81E81A25B22FC7FC5627CB5A203EE004 /* FIRGameCenterAuthProvider.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRGameCenterAuthProvider.h; path = Firebase/Auth/Source/Public/FIRGameCenterAuthProvider.h; sourceTree = ""; }; + 81EA2FE6D1C42613A812FBAE3318FA87 /* pb.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = pb.h; sourceTree = ""; }; + 820BAF23204DD2ADE6997AD1D0B201CE /* FIRConfiguration.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRConfiguration.h; path = FirebaseCore/Sources/Public/FIRConfiguration.h; sourceTree = ""; }; + 821CFA72A0DA71F03695F649274F8E78 /* FCompoundWrite.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FCompoundWrite.m; path = Firebase/Database/Snapshot/FCompoundWrite.m; sourceTree = ""; }; + 82269596064B52D6E7E7F1BFFC8FEC78 /* FIRCLSHost.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSHost.h; path = Crashlytics/Crashlytics/Components/FIRCLSHost.h; sourceTree = ""; }; + 8232B5CC32D2C64C6DAA70A7C7593250 /* GDTCCTNanopbHelpers.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCCTNanopbHelpers.h; path = GoogleDataTransportCCTSupport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h; sourceTree = ""; }; + 82364B5E57B1EF839045A66A8EAD905A /* FIRMutableData.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRMutableData.h; path = Firebase/Database/Public/FIRMutableData.h; sourceTree = ""; }; + 823EEAF21299E8D98B01448277270E70 /* MultipartFormData.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MultipartFormData.swift; path = Source/MultipartFormData.swift; sourceTree = ""; }; + 826604233AB70EBDA731C537C73AC4CE /* FIRSetAccountInfoRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRSetAccountInfoRequest.m; path = Firebase/Auth/Source/Backend/RPC/FIRSetAccountInfoRequest.m; sourceTree = ""; }; + 8288B40378984693C731D002FB16AFC0 /* FIRTransactionResult.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRTransactionResult.h; path = Firebase/Database/Public/FIRTransactionResult.h; sourceTree = ""; }; + 8298C3985C462DB1351E14FFA5281BB5 /* FBLPromise+Testing.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Testing.m"; path = "Sources/FBLPromises/FBLPromise+Testing.m"; sourceTree = ""; }; + 82E33DFCC4FCBB43EA18919761787820 /* FSnapshotUtilities.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FSnapshotUtilities.m; path = Firebase/Database/Snapshot/FSnapshotUtilities.m; sourceTree = ""; }; + 82EB3C9142DB8F0A22E3C30F14B3E806 /* RLMPredicateUtil.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMPredicateUtil.mm; path = Realm/RLMPredicateUtil.mm; sourceTree = ""; }; + 82EE9EA75ED100D0A1D4A04F26EF09A7 /* RLMSyncManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMSyncManager.h; path = include/RLMSyncManager.h; sourceTree = ""; }; + 840F659E9BCA424EB43A2CC83E1E8404 /* FNamedNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FNamedNode.m; path = Firebase/Database/FNamedNode.m; sourceTree = ""; }; + 842BD4B331F68A36CEB4311001E2EFD6 /* FIRInstallationsAuthTokenResult.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsAuthTokenResult.h; path = FirebaseInstallations/Source/Library/Public/FIRInstallationsAuthTokenResult.h; sourceTree = ""; }; + 844026B8D96F62721AEEB3A2FD9310E2 /* FIRAValue.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAValue.h; path = Crashlytics/Crashlytics/Helpers/FIRAValue.h; sourceTree = ""; }; + 846E4DE7CE26B7FBA379AFC0956A4BD3 /* FIRInstallationsStoredItem.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsStoredItem.h; path = FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredItem.h; sourceTree = ""; }; + 84A39F6F4D4BDBC8FC579CADBC222BD4 /* posix_logger.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = posix_logger.h; path = util/posix_logger.h; sourceTree = ""; }; + 856B5CD56F194FAD26EA91620B66D614 /* GoogleDataTransport.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = GoogleDataTransport.framework; path = GoogleDataTransport.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8593D09BBFA2FD018D9DF24500A16D38 /* FIRAuthDispatcher.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAuthDispatcher.m; path = Firebase/Auth/Source/Auth/FIRAuthDispatcher.m; sourceTree = ""; }; + 861AFA8FF5218ADA37F19EC29EA86D5F /* version_edit.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = version_edit.cc; path = db/version_edit.cc; sourceTree = ""; }; + 86375444C196BA272DDBB8165BF64A15 /* FirebaseCrashlytics.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = FirebaseCrashlytics.framework; path = FirebaseCrashlytics.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8639270B60188F3F1AF6A037E6730148 /* GDTCOREvent.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCOREvent.m; path = GoogleDataTransport/GDTCORLibrary/GDTCOREvent.m; sourceTree = ""; }; + 867B6C8244092F35079D098F2AD0327A /* FirebaseAnalytics.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FirebaseAnalytics.xcconfig; sourceTree = ""; }; + 868FC26600B87CCCCB054FD66B2EA15E /* FLevelDBStorageEngine.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FLevelDBStorageEngine.m; path = Firebase/Database/Persistence/FLevelDBStorageEngine.m; sourceTree = ""; }; + 8690E87E0C6FB58DBA5D4B6476077A82 /* FIRCLSCrashedMarkerFile.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSCrashedMarkerFile.h; path = Crashlytics/Crashlytics/Components/FIRCLSCrashedMarkerFile.h; sourceTree = ""; }; + 86A9C0C738B55C104584CA8477DBF065 /* RLMObjectBase_Dynamic.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMObjectBase_Dynamic.h; path = include/RLMObjectBase_Dynamic.h; sourceTree = ""; }; + 86ABF70E1FE2CB545EBEE6228D4296AB /* FIRTransactionResult_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRTransactionResult_Private.h; path = Firebase/Database/Api/Private/FIRTransactionResult_Private.h; sourceTree = ""; }; + 86C4E9A16C87A417E65CAB458B461017 /* RLMSwiftSupport.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RLMSwiftSupport.m; path = Realm/RLMSwiftSupport.m; sourceTree = ""; }; + 86DB482A924060A7B03BF8E7289A0C85 /* FIRCLSSettings.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSSettings.m; path = Crashlytics/Crashlytics/Models/FIRCLSSettings.m; sourceTree = ""; }; + 8711C8CB8DF3A374B7C9DCA86A93C7A2 /* FIRAuthWebUtils.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAuthWebUtils.m; path = Firebase/Auth/Source/Utilities/FIRAuthWebUtils.m; sourceTree = ""; }; + 87442EC5866C801D59CF010F92961448 /* RLMSyncConfiguration.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMSyncConfiguration.mm; path = Realm/RLMSyncConfiguration.mm; sourceTree = ""; }; + 87961C9BE2777F5B6B0C58DBBDFA3324 /* FRepo.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FRepo.m; path = Firebase/Database/Core/FRepo.m; sourceTree = ""; }; + 87A652F4124484CB76A1F4E60241AABE /* GDTCORClock.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORClock.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORClock.m; sourceTree = ""; }; + 8805ED6DFABB525362EE02596FE7AF24 /* FMaxNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FMaxNode.h; path = Firebase/Database/FMaxNode.h; sourceTree = ""; }; + 882A1805C739601B36DDBE8199808EA7 /* FBLPromiseError.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FBLPromiseError.m; path = Sources/FBLPromises/FBLPromiseError.m; sourceTree = ""; }; + 883D7550CA14C459AC0B859D454D150C /* mutexlock.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = mutexlock.h; path = util/mutexlock.h; sourceTree = ""; }; + 8864B72940AE9A531D5110372EF4C995 /* FIRGetAccountInfoResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRGetAccountInfoResponse.m; path = Firebase/Auth/Source/Backend/RPC/FIRGetAccountInfoResponse.m; sourceTree = ""; }; + 887624AC0D176DFBC69C78021A0CF147 /* RLMSyncPermission.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMSyncPermission.h; path = include/RLMSyncPermission.h; sourceTree = ""; }; + 889D76680F2283660A9FD4C0257665FC /* GULLogger.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULLogger.h; path = GoogleUtilities/Logger/Private/GULLogger.h; sourceTree = ""; }; + 88ABB3A4A0002798B71EE2408272DA01 /* Pods-GeekbrainsUI.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-GeekbrainsUI.debug.xcconfig"; sourceTree = ""; }; + 88D5ED9C4C466DC197E025B5D6B9BEC4 /* FIRCLSDwarfUnwind.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSDwarfUnwind.h; path = Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDwarfUnwind.h; sourceTree = ""; }; + 890533AECEC39179FE6E6B2169233974 /* FSyncTree.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FSyncTree.h; path = Firebase/Database/Core/FSyncTree.h; sourceTree = ""; }; + 892F390C60B74AF78829AB20BBB9CC5D /* FIRInstallationsItem+RegisterInstallationAPI.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FIRInstallationsItem+RegisterInstallationAPI.m"; path = "FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsItem+RegisterInstallationAPI.m"; sourceTree = ""; }; + 8994F6A0E3A70E01B98277557180A156 /* FIRInstallationsErrorUtil.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsErrorUtil.m; path = FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.m; sourceTree = ""; }; + 89B30EF9BFCEE26E9674E5CAA2473156 /* RLMResults.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMResults.mm; path = Realm/RLMResults.mm; sourceTree = ""; }; + 89DDA8DBE011A6C4ACF28382FA07CBD1 /* LinkingObjects.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LinkingObjects.swift; path = RealmSwift/LinkingObjects.swift; sourceTree = ""; }; + 89EF68696031BDC544F7C211C49A190E /* cache.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = cache.h; path = include/leveldb/cache.h; sourceTree = ""; }; + 8A03882E9613B12780B58BA0DBDEADAA /* GULUserDefaults.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULUserDefaults.m; path = GoogleUtilities/UserDefaults/GULUserDefaults.m; sourceTree = ""; }; + 8A1C30EE032FA32667014346FEB96DAA /* List.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = List.swift; path = RealmSwift/List.swift; sourceTree = ""; }; + 8A84B7F161963279BD754667535C4183 /* FCompoundHash.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FCompoundHash.m; path = Firebase/Database/Core/FCompoundHash.m; sourceTree = ""; }; + 8AAD0BBA966E05AF48C696B1C8088665 /* Error.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Error.swift; path = RealmSwift/Error.swift; sourceTree = ""; }; + 8ACDCCAD213FD226B322D9CCFFCAAA09 /* FIRInstanceIDCheckinService.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstanceIDCheckinService.h; path = Firebase/InstanceID/FIRInstanceIDCheckinService.h; sourceTree = ""; }; + 8AE09E001BB86416A1D26AB609A0AEA3 /* FLLRBEmptyNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FLLRBEmptyNode.m; path = Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FLLRBEmptyNode.m; sourceTree = ""; }; + 8AEBF9A44F2485C9B53763766349031A /* FIRCLSFile.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSFile.m; path = Crashlytics/Crashlytics/Helpers/FIRCLSFile.m; sourceTree = ""; }; + 8AF7AEE28A597BE8C090298E2EBC0B1D /* FIROAuthProvider.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIROAuthProvider.m; path = Firebase/Auth/Source/AuthProvider/OAuth/FIROAuthProvider.m; sourceTree = ""; }; + 8B060D6A0ACB153C3A6519940E3A2F8C /* KingfisherManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KingfisherManager.swift; path = Sources/General/KingfisherManager.swift; sourceTree = ""; }; + 8B20983BE4C3A3AF1F2CC902B2FAD738 /* FTupleCallbackStatus.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FTupleCallbackStatus.m; path = Firebase/Database/Utilities/Tuples/FTupleCallbackStatus.m; sourceTree = ""; }; + 8B5542975A6E66F371A3C9A81B897E76 /* FRepoManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FRepoManager.m; path = Firebase/Database/Core/FRepoManager.m; sourceTree = ""; }; + 8B63C4D38C3294E634556027B2ABB2D0 /* FIREmailLinkSignInResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIREmailLinkSignInResponse.h; path = Firebase/Auth/Source/Backend/RPC/FIREmailLinkSignInResponse.h; sourceTree = ""; }; + 8B7352E913168EC86E1833303D5A8E3F /* FIRAEvent+Internal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FIRAEvent+Internal.h"; path = "Crashlytics/Crashlytics/Helpers/FIRAEvent+Internal.h"; sourceTree = ""; }; + 8B83ED9467FD61F44F6C95DCF79C6280 /* GULLoggerCodes.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULLoggerCodes.h; path = GoogleUtilities/Common/GULLoggerCodes.h; sourceTree = ""; }; + 8BB32016F6107456B9585FD31C1C63BB /* FIRInstanceIDTokenDeleteOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstanceIDTokenDeleteOperation.h; path = Firebase/InstanceID/FIRInstanceIDTokenDeleteOperation.h; sourceTree = ""; }; + 8BCA6AFA09C705E39ECD5316A4472114 /* librealmcore-ios.a */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = archive.ar; name = "librealmcore-ios.a"; path = "core/librealmcore-ios.a"; sourceTree = ""; }; + 8C04E194687BA66B98EDC15D43B979B5 /* FirebaseAuthVersion.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FirebaseAuthVersion.h; path = Firebase/Auth/Source/Public/FirebaseAuthVersion.h; sourceTree = ""; }; + 8C227158CEFB05EFA49A1D7A4A68EF82 /* Realm.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = Realm.h; path = include/Realm.h; sourceTree = ""; }; + 8C3A66B5A5D384170FFA25C394542A3B /* RLMObjectBase.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMObjectBase.mm; path = Realm/RLMObjectBase.mm; sourceTree = ""; }; + 8C505EBD827CC2D18CD9D3E74CD39B8A /* FIRDatabaseComponent.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRDatabaseComponent.h; path = Firebase/Database/Api/FIRDatabaseComponent.h; sourceTree = ""; }; + 8C6F145D9591E35C4B366950416B55E8 /* export.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = export.h; path = include/leveldb/export.h; sourceTree = ""; }; + 8CC50146E48C132CD4FDE4797E7E3763 /* FRepo.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FRepo.h; path = Firebase/Database/Core/FRepo.h; sourceTree = ""; }; + 8CC9178C366942FD6FF6A115604EAD58 /* FirebaseCoreDiagnostics.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = FirebaseCoreDiagnostics.framework; path = FirebaseCoreDiagnostics.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8CD746F873450F3A4F322F45F227EA7C /* GTMSessionFetcherService.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMSessionFetcherService.m; path = Source/GTMSessionFetcherService.m; sourceTree = ""; }; + 8CF6F9311132EDF0F6BD49DE3F5FA5E1 /* FirebaseInstanceID-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "FirebaseInstanceID-umbrella.h"; sourceTree = ""; }; + 8D2EFA3A80904C2A2AB08FDA578F8844 /* GoogleDataTransport.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GoogleDataTransport.xcconfig; sourceTree = ""; }; + 8D8898271E7DCFDA5AD4581F1709420C /* FIRVerifyAssertionRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRVerifyAssertionRequest.h; path = Firebase/Auth/Source/Backend/RPC/FIRVerifyAssertionRequest.h; sourceTree = ""; }; + 8E3ACF464DE946E7977DFFA1EEF5246A /* comparator.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = comparator.cc; path = util/comparator.cc; sourceTree = ""; }; + 8E525CD0D67BB1B0F3B7F54506731294 /* SessionDelegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SessionDelegate.swift; path = Source/SessionDelegate.swift; sourceTree = ""; }; + 8E76D2B09059C28B04E6F10E68D35F5F /* NSData+SRB64Additions.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSData+SRB64Additions.h"; path = "Firebase/Database/third_party/SocketRocket/NSData+SRB64Additions.h"; sourceTree = ""; }; + 8E79B063CE064DA7B1933274A8DDE290 /* FIRInstallationsErrors.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsErrors.h; path = FirebaseInstallations/Source/Library/Public/FIRInstallationsErrors.h; sourceTree = ""; }; + 8E97CBBD9152BB8C531E2C2C75C24E29 /* FirebaseDatabase-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "FirebaseDatabase-Info.plist"; sourceTree = ""; }; + 8E9ED28562D1435FA27EF70F70D9AAE0 /* String+MD5.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "String+MD5.swift"; path = "Sources/Utility/String+MD5.swift"; sourceTree = ""; }; + 8F1BF833B6F67406AF5AEE71A69B09A7 /* FIRInstanceIDAuthKeyChain.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstanceIDAuthKeyChain.h; path = Firebase/InstanceID/FIRInstanceIDAuthKeyChain.h; sourceTree = ""; }; + 8F2C8DAE66279CFF4D15F682106D761B /* merger.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = merger.h; path = table/merger.h; sourceTree = ""; }; + 8F50523F75B8500F411C5A032D73DE46 /* FIRInstanceIDStringEncoding.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstanceIDStringEncoding.h; path = Firebase/InstanceID/FIRInstanceIDStringEncoding.h; sourceTree = ""; }; + 8F76B1EE6D2328DF902BCFC3CEFFDFF9 /* RLMThreadSafeReference.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMThreadSafeReference.h; path = include/RLMThreadSafeReference.h; sourceTree = ""; }; + 8F79BB540DD5F3B0F0DD4C789A65EB79 /* FBLPromise+Catch.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Catch.m"; path = "Sources/FBLPromises/FBLPromise+Catch.m"; sourceTree = ""; }; + 8F7A3106176C2E7089A28829FF0BE584 /* Delegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Delegate.swift; path = Sources/Utility/Delegate.swift; sourceTree = ""; }; + 8F9F3C4BF4F891B92DA1E5A57FDA4B2E /* RLMQueryUtil.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMQueryUtil.mm; path = Realm/RLMQueryUtil.mm; sourceTree = ""; }; + 8FA7E3B9378F3A764B377F83723247B7 /* RLMProperty.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMProperty.h; path = include/RLMProperty.h; sourceTree = ""; }; + 8FE9A7290B315D1210F54B1A597278EC /* FIRCLSFeatures.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSFeatures.h; path = Crashlytics/Crashlytics/Helpers/FIRCLSFeatures.h; sourceTree = ""; }; + 8FF8C4F4BA47732C4BB31B381E8CCD36 /* FAuthTokenProvider.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FAuthTokenProvider.h; path = Firebase/Database/Login/FAuthTokenProvider.h; sourceTree = ""; }; + 8FFDC32A91D6222B62D35ACE38DF0183 /* FQueryParams.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FQueryParams.m; path = Firebase/Database/Core/FQueryParams.m; sourceTree = ""; }; + 9021419F0DD8F28569985527C8E622B5 /* RLMConstants.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMConstants.h; path = include/RLMConstants.h; sourceTree = ""; }; + 9023E9E655C90030C8031B6B2F2B677F /* db_iter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = db_iter.h; path = db/db_iter.h; sourceTree = ""; }; + 9024896D2537C44DEC820307835C4C89 /* FSyncPoint.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FSyncPoint.m; path = Firebase/Database/Core/FSyncPoint.m; sourceTree = ""; }; + 912B6075A0BB9F3FC276D8ACE49F2D74 /* FIRCLSApplicationIdentifierModel.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSApplicationIdentifierModel.h; path = Crashlytics/Crashlytics/Settings/Models/FIRCLSApplicationIdentifierModel.h; sourceTree = ""; }; + 91ADC399A3333A4199D859622DC339A6 /* write_batch.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = write_batch.h; path = include/leveldb/write_batch.h; sourceTree = ""; }; + 91D4B4C08E11E17B827575DBF312C72D /* format.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = format.h; path = table/format.h; sourceTree = ""; }; + 91F88A0114DB504C3CB311BD28930021 /* RLMObjectStore.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMObjectStore.mm; path = Realm/RLMObjectStore.mm; sourceTree = ""; }; + 9213CDC4C57E01385FA2CED32D12D1B0 /* FViewProcessor.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FViewProcessor.h; path = Firebase/Database/FViewProcessor.h; sourceTree = ""; }; + 921BE4A82C4A7A5C72A0C6F8B8FEF200 /* Realm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Realm.framework; path = Realm.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 926F3482C65B41D6727B7F69D5725417 /* FBLPromise+Await.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Await.h"; path = "Sources/FBLPromises/include/FBLPromise+Await.h"; sourceTree = ""; }; + 92CE3C73F6820C05E0B7AD90181B0666 /* FIRInstanceIDKeychain.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstanceIDKeychain.h; path = Firebase/InstanceID/FIRInstanceIDKeychain.h; sourceTree = ""; }; + 930044AA0BC41A1A935CF3B6450322CC /* placeholder.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = placeholder.cpp; path = Realm/ObjectStore/src/placeholder.cpp; sourceTree = ""; }; + 930FF3B062A8B447DFC07EE99A07EE33 /* RLMSyncUser.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMSyncUser.h; path = include/RLMSyncUser.h; sourceTree = ""; }; + 931030B1DBC02E1B5C039ACC9CEA2E9C /* FBLPromise+Recover.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Recover.m"; path = "Sources/FBLPromises/FBLPromise+Recover.m"; sourceTree = ""; }; + 939962F7D0C89D0540EBE4A81F7DBB8A /* FPendingPut.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FPendingPut.h; path = Firebase/Database/Persistence/FPendingPut.h; sourceTree = ""; }; + 945E2AD872C86F66E2B79E020BBA5FF6 /* FIRIdentityToolkitRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRIdentityToolkitRequest.m; path = Firebase/Auth/Source/Backend/FIRIdentityToolkitRequest.m; sourceTree = ""; }; + 948C362190D80D0FAD6202C60E1B479F /* ObjectiveCSupport.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ObjectiveCSupport.swift; path = RealmSwift/ObjectiveCSupport.swift; sourceTree = ""; }; + 948D6FAC68230EC8AC23DC6649DB1BB3 /* FSparseSnapshotTree.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FSparseSnapshotTree.m; path = Firebase/Database/Core/FSparseSnapshotTree.m; sourceTree = ""; }; + 9495BA679912E002DD2FC5B70555A0D8 /* RLMAccessor.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMAccessor.h; path = include/RLMAccessor.h; sourceTree = ""; }; + 94ACE21C6A33E6195E748EA160A13204 /* table_cache.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = table_cache.cc; path = db/table_cache.cc; sourceTree = ""; }; + 9534287083E1F8DC40D023CAC0E0F310 /* FIRCLSConstants.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSConstants.m; path = Crashlytics/Shared/FIRCLSConstants.m; sourceTree = ""; }; + 9565729856E35DE36A92958374DF72C2 /* GULNetworkMessageCode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULNetworkMessageCode.h; path = GoogleUtilities/Network/Private/GULNetworkMessageCode.h; sourceTree = ""; }; + 95B4BB665D979FEF54288EC9FB68B57F /* FIRCLSSymbolicationOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSSymbolicationOperation.h; path = Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSSymbolicationOperation.h; sourceTree = ""; }; + 95C5F958FAA51CD9AFACEAFD6323D4F1 /* nanopb-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "nanopb-umbrella.h"; sourceTree = ""; }; + 95D54C32914D8C5745A86BD2ABBBACD6 /* FIRUser.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRUser.h; path = Firebase/Auth/Source/Public/FIRUser.h; sourceTree = ""; }; + 967AC3B13881F4CA2CB6CE9C55146F69 /* Image.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Image.swift; path = Sources/Image/Image.swift; sourceTree = ""; }; + 96A2C040669D3F47F52262A721860C1F /* FIRCLSUserLogging.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSUserLogging.m; path = Crashlytics/Crashlytics/Components/FIRCLSUserLogging.m; sourceTree = ""; }; + 96BD12B47B380FF0B4B732A7A3C7F913 /* FTupleObjectNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FTupleObjectNode.m; path = Firebase/Database/Utilities/Tuples/FTupleObjectNode.m; sourceTree = ""; }; + 96BD2F13CC711654EDB640D31C70F50E /* GULReachabilityChecker.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULReachabilityChecker.h; path = GoogleUtilities/Reachability/Private/GULReachabilityChecker.h; sourceTree = ""; }; + 96D052C928B9E11706A2CF4EA9096B59 /* Object.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Object.swift; path = RealmSwift/Object.swift; sourceTree = ""; }; + 96DF39B293090E7DD0FB3B272833B75B /* FIRCLSURLSession_PrivateMethods.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSURLSession_PrivateMethods.h; path = Crashlytics/Crashlytics/FIRCLSURLSession/FIRCLSURLSession_PrivateMethods.h; sourceTree = ""; }; + 96FD8EDB3F384930F0CCFC2E512842C5 /* FIRFacebookAuthCredential.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRFacebookAuthCredential.m; path = Firebase/Auth/Source/AuthProvider/Facebook/FIRFacebookAuthCredential.m; sourceTree = ""; }; + 9734D99905E6B8202876C55E05FC392E /* comparator.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = comparator.h; path = include/leveldb/comparator.h; sourceTree = ""; }; + 9735E1BB66BA1A1805C81F7022B52CEE /* FIRHeartbeatInfo.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRHeartbeatInfo.m; path = FirebaseCore/Sources/FIRHeartbeatInfo.m; sourceTree = ""; }; + 975D50E6136B6E412EDD8DBEC3D92B2E /* log_format.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = log_format.h; path = db/log_format.h; sourceTree = ""; }; + 976C54C9CC751F84860286147F3226AA /* RLMArray.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMArray.mm; path = Realm/RLMArray.mm; sourceTree = ""; }; + 97AB7716BA5097897D8CE33414A2157D /* FImmutableTree.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FImmutableTree.h; path = Firebase/Database/Core/Utilities/FImmutableTree.h; sourceTree = ""; }; + 97C63B265EC2B20EAE69CA93369383DF /* FIRCLSDataCollectionToken.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSDataCollectionToken.h; path = Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionToken.h; sourceTree = ""; }; + 981D3489F3D5048257A7FBB1CD338281 /* env_posix.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = env_posix.cc; path = util/env_posix.cc; sourceTree = ""; }; + 9828526EFD32EFD531EBBB2E9D6DB8B3 /* FIRCLSURLSessionUploadTask.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSURLSessionUploadTask.h; path = Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionUploadTask.h; sourceTree = ""; }; + 983140C99DED04F2EAC250131C4833B2 /* FPath.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FPath.h; path = Firebase/Database/Core/Utilities/FPath.h; sourceTree = ""; }; + 98336D712A53939111F8856BC622B48F /* FIndexedNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIndexedNode.h; path = Firebase/Database/Snapshot/FIndexedNode.h; sourceTree = ""; }; + 98533E594EC57133B3C68EF150266FDD /* FIRCLSExecutionIdentifierModel.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSExecutionIdentifierModel.m; path = Crashlytics/Crashlytics/Models/FIRCLSExecutionIdentifierModel.m; sourceTree = ""; }; + 98F2168BA4030E0EABD5F6681F96B79E /* FIRInstallationsStore.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsStore.h; path = FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStore.h; sourceTree = ""; }; + 98F73D30C06A348EFFDC85B721BFC501 /* pb_decode.c */ = {isa = PBXFileReference; includeInIndex = 1; path = pb_decode.c; sourceTree = ""; }; + 9966DDAE4F25C29B629BF069C18E98F2 /* TaskDelegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TaskDelegate.swift; path = Source/TaskDelegate.swift; sourceTree = ""; }; + 998AA9BB7AA0BEAEAF95FE9BB2E20DDE /* FTree.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FTree.m; path = Firebase/Database/Core/Utilities/FTree.m; sourceTree = ""; }; + 9A6F4A2FF77FD9938CE6ACD8F6295900 /* RLMSyncSubscription.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMSyncSubscription.mm; path = Realm/RLMSyncSubscription.mm; sourceTree = ""; }; + 9AA0E16A090681028EE0681EE3F25288 /* GoogleAppMeasurement.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GoogleAppMeasurement.xcconfig; sourceTree = ""; }; + 9ACB6E37C7A61A4D3362558603C83B0C /* c.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = c.h; path = include/leveldb/c.h; sourceTree = ""; }; + 9B189F0E4B0777516E298F26F55897F2 /* nanopb-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "nanopb-dummy.m"; sourceTree = ""; }; + 9B76CD581D8B96669C1D56E482DA40E3 /* RLMObjectBase.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMObjectBase.h; path = include/RLMObjectBase.h; sourceTree = ""; }; + 9B77B047C6A76B835BE1FCB7549381AC /* Property.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Property.swift; path = RealmSwift/Property.swift; sourceTree = ""; }; + 9C0079468DE4A96EA35409206515BE79 /* filename.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = filename.cc; path = db/filename.cc; sourceTree = ""; }; + 9C05131A2732A28192011811FF7609CE /* FTuplePathValue.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FTuplePathValue.h; path = Firebase/Database/Utilities/Tuples/FTuplePathValue.h; sourceTree = ""; }; + 9C6BC7FD320F562050D0FF3A098FC56F /* GoogleDataTransportCCTSupport-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "GoogleDataTransportCCTSupport-dummy.m"; sourceTree = ""; }; + 9C881435CE9E0783A5644B934C4B16C5 /* FIRServerValue.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRServerValue.m; path = Firebase/Database/Api/FIRServerValue.m; sourceTree = ""; }; + 9C95068A8D4719F258EC259BA37386C9 /* FSyncTree.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FSyncTree.m; path = Firebase/Database/Core/FSyncTree.m; sourceTree = ""; }; + 9C980FD69AD83CB2FF5D34DC23B46A97 /* FIREmailAuthProvider.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIREmailAuthProvider.h; path = Firebase/Auth/Source/Public/FIREmailAuthProvider.h; sourceTree = ""; }; + 9CDFD241B0E81B85EB547ADEBDB5D6B5 /* Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Alamofire.swift; path = Source/Alamofire.swift; sourceTree = ""; }; + 9CF19D6F9D47FE0C30F70D0326CE6B31 /* FirebaseCore.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FirebaseCore.h; path = FirebaseCore/Sources/Public/FirebaseCore.h; sourceTree = ""; }; + 9D06E960379196A78A8272C36A69AC21 /* RLMSyncSession.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMSyncSession.h; path = include/RLMSyncSession.h; sourceTree = ""; }; + 9D3459C02176B8A9124D0772B38C6178 /* GULSceneDelegateSwizzler.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULSceneDelegateSwizzler.h; path = GoogleUtilities/SceneDelegateSwizzler/Private/GULSceneDelegateSwizzler.h; sourceTree = ""; }; + 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + 9E2D7FC56B1BA97E8C68CC09D1771342 /* GTMSessionFetcher-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "GTMSessionFetcher-umbrella.h"; sourceTree = ""; }; + 9E3DB5044C4A852A56DFBEC9210F6936 /* FIRGoogleAuthProvider.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRGoogleAuthProvider.h; path = Firebase/Auth/Source/Public/FIRGoogleAuthProvider.h; sourceTree = ""; }; + 9E80A96424126BD73FA1DC045768406A /* skiplist.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = skiplist.h; path = db/skiplist.h; sourceTree = ""; }; + 9E9D59523D289EC7F7D99A7F21CFE2BD /* db_impl.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = db_impl.cc; path = db/db_impl.cc; sourceTree = ""; }; + 9EA4A8CC145EB727B389E41E6BF8B35C /* FBLPromise+Validate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Validate.h"; path = "Sources/FBLPromises/include/FBLPromise+Validate.h"; sourceTree = ""; }; + 9ECA05096F49D5C2C0D3D1C2A3130DA5 /* FAtomicNumber.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FAtomicNumber.h; path = Firebase/Database/Utilities/FAtomicNumber.h; sourceTree = ""; }; + 9EF5F75352A2E6D56BCE252ED1AA1321 /* ExtensionHelpers.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExtensionHelpers.swift; path = Sources/Utility/ExtensionHelpers.swift; sourceTree = ""; }; + 9F85C1C2A16FC96066DC6B0C91D73A50 /* collection_change_builder.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = collection_change_builder.cpp; path = Realm/ObjectStore/src/impl/collection_change_builder.cpp; sourceTree = ""; }; + 9FCC44446EB1279EA6225E7F3C5D4B38 /* FTransformedEnumerator.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FTransformedEnumerator.h; path = Firebase/Database/FTransformedEnumerator.h; sourceTree = ""; }; + 9FFCA9DA3D1B73A86D7B03012553EFB1 /* FirebaseInstallations-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "FirebaseInstallations-dummy.m"; sourceTree = ""; }; + A020B761979C1B61A16487D3C2922FF5 /* GDTFLLUploader.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTFLLUploader.h; path = GoogleDataTransportCCTSupport/GDTCCTLibrary/Private/GDTFLLUploader.h; sourceTree = ""; }; + A043B78C92D8D079E567AA643953AC40 /* FIRDependency.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRDependency.h; path = FirebaseCore/Sources/Private/FIRDependency.h; sourceTree = ""; }; + A0828F754A3C7E4871AC36A1ED72B713 /* FIRVerifyAssertionResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRVerifyAssertionResponse.m; path = Firebase/Auth/Source/Backend/RPC/FIRVerifyAssertionResponse.m; sourceTree = ""; }; + A0CB71AF07C955B047A38074046DD5F2 /* FTupleTransaction.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FTupleTransaction.m; path = Firebase/Database/Utilities/Tuples/FTupleTransaction.m; sourceTree = ""; }; + A193EF96B2067363B9A36502ACD87C75 /* index_set.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = index_set.cpp; path = Realm/ObjectStore/src/index_set.cpp; sourceTree = ""; }; + A20C88EE3EEA866A57E002E2CE2CB90C /* FOverwrite.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FOverwrite.h; path = Firebase/Database/Core/Operation/FOverwrite.h; sourceTree = ""; }; + A288181DC60985C963066396CF083E72 /* ImageProgressive.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageProgressive.swift; path = Sources/Image/ImageProgressive.swift; sourceTree = ""; }; + A30207A678F529D1338CCE50C15A7D1E /* FirebaseCoreDiagnostics-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "FirebaseCoreDiagnostics-Info.plist"; sourceTree = ""; }; + A31792D94CAC5470CE07710F88771788 /* FSparseSnapshotTree.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FSparseSnapshotTree.h; path = Firebase/Database/Core/FSparseSnapshotTree.h; sourceTree = ""; }; + A384F92A096C490D8F388733A9524664 /* FIRSecureStorage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRSecureStorage.h; path = FirebaseInstallations/Source/Library/SecureStorage/FIRSecureStorage.h; sourceTree = ""; }; + A45D338C1940FDB1302AB9CB81DA0A8B /* FIRAppInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAppInternal.h; path = FirebaseCore/Sources/Private/FIRAppInternal.h; sourceTree = ""; }; + A528EB78B55C3F9AB1A8B7B2CF02DE85 /* FIRCLSThreadState.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSThreadState.h; path = Crashlytics/Crashlytics/Helpers/FIRCLSThreadState.h; sourceTree = ""; }; + A542BD074662CB4F26AFC7358EE58123 /* FIRCLSByteUtility.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSByteUtility.m; path = Crashlytics/Shared/FIRCLSByteUtility.m; sourceTree = ""; }; + A573EBB34B32D9E475532628683BF8CF /* FIRCLSFABNetworkClient.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSFABNetworkClient.m; path = Crashlytics/Shared/FIRCLSNetworking/FIRCLSFABNetworkClient.m; sourceTree = ""; }; + A6119970BBD3C7ABD6672B5D27F2607A /* iterator_wrapper.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = iterator_wrapper.h; path = table/iterator_wrapper.h; sourceTree = ""; }; + A6395739D39ED1B02D46AFEC682B3639 /* FRangedFilter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FRangedFilter.m; path = Firebase/Database/FRangedFilter.m; sourceTree = ""; }; + A641EFE5534FE8DDEA5D903FD7216933 /* filename.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = filename.h; path = db/filename.h; sourceTree = ""; }; + A6816E6F407C12392DC39456D0E4BB2B /* FTupleObjects.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FTupleObjects.m; path = Firebase/Database/Utilities/Tuples/FTupleObjects.m; sourceTree = ""; }; + A6AF2ADD5C91F05DCB60BF5366727A7F /* FIRDeleteAccountRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRDeleteAccountRequest.h; path = Firebase/Auth/Source/Backend/RPC/FIRDeleteAccountRequest.h; sourceTree = ""; }; + A6BBD745161E87221061671555FCE237 /* RLMObjectSchema.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMObjectSchema.mm; path = Realm/RLMObjectSchema.mm; sourceTree = ""; }; + A6CA61A608F721178720B5C7620B2CB0 /* FIRInstallationsAuthTokenResult.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsAuthTokenResult.m; path = FirebaseInstallations/Source/Library/FIRInstallationsAuthTokenResult.m; sourceTree = ""; }; + A6DA917FC21F7D46345AD1B5798813F4 /* FIRAuthUserDefaults.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAuthUserDefaults.m; path = Firebase/Auth/Source/Storage/FIRAuthUserDefaults.m; sourceTree = ""; }; + A7317777EA2818DD08E00424D0C9AA43 /* GDTCORDataFuture.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORDataFuture.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORDataFuture.m; sourceTree = ""; }; + A73AC529EF63101E9F604F56970CF8A2 /* FIRCLSHost.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSHost.m; path = Crashlytics/Crashlytics/Components/FIRCLSHost.m; sourceTree = ""; }; + A78CA57199BE1F05CB6F7E0160B11399 /* FViewProcessorResult.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FViewProcessorResult.m; path = Firebase/Database/FViewProcessorResult.m; sourceTree = ""; }; + A7B71AD4C1D52D6BF67F016BF704E18C /* FKeepSyncedEventRegistration.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FKeepSyncedEventRegistration.h; path = Firebase/Database/Core/View/FKeepSyncedEventRegistration.h; sourceTree = ""; }; + A7D87F6D56345BB13EF2DB61F88CDFD0 /* GULReachabilityMessageCode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULReachabilityMessageCode.h; path = GoogleUtilities/Reachability/Private/GULReachabilityMessageCode.h; sourceTree = ""; }; + A80776476F1BB3131E33DC6C63A170C8 /* FIRCLSURLSessionTask.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSURLSessionTask.h; path = Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionTask.h; sourceTree = ""; }; + A827193BF78DD12B4786B06D1EA13EF8 /* FIRVerifyClientRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRVerifyClientRequest.m; path = Firebase/Auth/Source/Backend/RPC/FIRVerifyClientRequest.m; sourceTree = ""; }; + A84C72B92CB67258C3060F4E4AF55F33 /* FIRInstallationsAuthTokenResultInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsAuthTokenResultInternal.h; path = FirebaseInstallations/Source/Library/FIRInstallationsAuthTokenResultInternal.h; sourceTree = ""; }; + A87AF3EEB6D8137F65A401B485906C8D /* keychain_helper.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = keychain_helper.cpp; path = Realm/ObjectStore/src/impl/apple/keychain_helper.cpp; sourceTree = ""; }; + A87FC113ACCF64E5B68D982516EAE67A /* FIRCLSReport.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSReport.m; path = Crashlytics/Crashlytics/Models/FIRCLSReport.m; sourceTree = ""; }; + A8956802366B6DEDE559931F69944AF6 /* FCacheNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FCacheNode.h; path = Firebase/Database/Core/View/FCacheNode.h; sourceTree = ""; }; + A8A8721CAE2B4D781F5B1CF133CA52F3 /* FOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FOperation.h; path = Firebase/Database/Core/Operation/FOperation.h; sourceTree = ""; }; + A8A9312F7F8DE00674B65C966FA1D0F3 /* pb_decode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = pb_decode.h; sourceTree = ""; }; + A8D86AF676E653B84DD1CB1594DCB2BB /* GDTCORUploadPackage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORUploadPackage.h; path = GoogleDataTransport/GDTCORLibrary/Public/GDTCORUploadPackage.h; sourceTree = ""; }; + A8F11A451EB6026D35C48E8E172E5EF7 /* FIRSignUpNewUserResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRSignUpNewUserResponse.m; path = Firebase/Auth/Source/Backend/RPC/FIRSignUpNewUserResponse.m; sourceTree = ""; }; + A920315F7646D262B91C4778403C67DD /* RLMSyncCredentials.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RLMSyncCredentials.m; path = Realm/RLMSyncCredentials.m; sourceTree = ""; }; + A92F8FEB290B291E79FE5A612537F990 /* GDTCORTransport.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORTransport.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORTransport.m; sourceTree = ""; }; + A963882D700987FDF81DE9A1A58D906F /* sync_file.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = sync_file.cpp; path = Realm/ObjectStore/src/sync/impl/sync_file.cpp; sourceTree = ""; }; + A96DE2BFC9945024D2655D331499248F /* GoogleUtilities-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "GoogleUtilities-Info.plist"; sourceTree = ""; }; + A994DEA929EC73A3FECB99814A26BF0F /* FBLPromise+Then.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Then.h"; path = "Sources/FBLPromises/include/FBLPromise+Then.h"; sourceTree = ""; }; + A9D04B5851A9758EDBE1DA3F0572ED7F /* env_windows_test_helper.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = env_windows_test_helper.h; path = util/env_windows_test_helper.h; sourceTree = ""; }; + A9E64A8A8B133D02DAB3421C08C97BDE /* FIRInstallationsAPIService.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsAPIService.m; path = FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsAPIService.m; sourceTree = ""; }; + A9E802A74840CABA570E678E66E944C1 /* FIRCLSFileManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSFileManager.m; path = Crashlytics/Crashlytics/Models/FIRCLSFileManager.m; sourceTree = ""; }; + A9F13426A62CA85A9857717AABA68113 /* FMerge.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FMerge.m; path = Firebase/Database/Core/Operation/FMerge.m; sourceTree = ""; }; + AA682680D3C44D8FCA0361A76F028A8F /* FImmutableSortedDictionary.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FImmutableSortedDictionary.h; path = Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FImmutableSortedDictionary.h; sourceTree = ""; }; + AA6E836D46E455FD474EDF1905E109AB /* FIRAuthTokenResult.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthTokenResult.h; path = Firebase/Auth/Source/Public/FIRAuthTokenResult.h; sourceTree = ""; }; + AA80B18F608CC7FFEF860AA39A50114F /* FIRCoreDiagnosticsConnector.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCoreDiagnosticsConnector.m; path = FirebaseCore/Sources/FIRCoreDiagnosticsConnector.m; sourceTree = ""; }; + AAB15C35792D73E4FA31853F7AE525AA /* FIRSignInWithGameCenterResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRSignInWithGameCenterResponse.h; path = Firebase/Auth/Source/Backend/RPC/FIRSignInWithGameCenterResponse.h; sourceTree = ""; }; + AAFCE2DE8FB9E0DFF93F37DFC211EEE5 /* RealmSwift-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "RealmSwift-Info.plist"; sourceTree = ""; }; + AB0134FF9D73541F1CAAD70ADF3FE8E4 /* GDTCORPlatform.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORPlatform.h; path = GoogleDataTransport/GDTCORLibrary/Public/GDTCORPlatform.h; sourceTree = ""; }; + AB546DEDD26660C09E3E334FEC21D8CB /* histogram.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = histogram.h; path = util/histogram.h; sourceTree = ""; }; + AB6E896A6AB3A2C9129B37B768597237 /* NSError+FIRInstanceID.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSError+FIRInstanceID.m"; path = "Firebase/InstanceID/NSError+FIRInstanceID.m"; sourceTree = ""; }; + ABCCA286340954F0DD058E63A55A397B /* FIRLoggerLevel.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRLoggerLevel.h; path = FirebaseCore/Sources/Public/FIRLoggerLevel.h; sourceTree = ""; }; + ABEEBDCE85E4EB501A37D92EF28C16DB /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/SystemConfiguration.framework; sourceTree = DEVELOPER_DIR; }; + AC0C18B2A33D69724E42FCD30CF08905 /* FIRCLSDataCollectionArbiter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSDataCollectionArbiter.h; path = Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionArbiter.h; sourceTree = ""; }; + AC6DF125BF38E2E84E505D8CA813A0D1 /* FIRInstanceIDAPNSInfo.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstanceIDAPNSInfo.m; path = Firebase/InstanceID/FIRInstanceIDAPNSInfo.m; sourceTree = ""; }; + ACB46B34666BE45BFC53AC7D3C99A144 /* FIRCLSContext.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSContext.h; path = Crashlytics/Crashlytics/Components/FIRCLSContext.h; sourceTree = ""; }; + ACDE8B7DDA72C544A091534E5D36CC99 /* write_batch_internal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = write_batch_internal.h; path = db/write_batch_internal.h; sourceTree = ""; }; + AD01636391685B8E33F32E033B2F16B3 /* FIRResetPasswordRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRResetPasswordRequest.m; path = Firebase/Auth/Source/Backend/RPC/FIRResetPasswordRequest.m; sourceTree = ""; }; + AD28552BB9D41F42225DD045C6430D45 /* FIRGitHubAuthCredential.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRGitHubAuthCredential.h; path = Firebase/Auth/Source/AuthProvider/GitHub/FIRGitHubAuthCredential.h; sourceTree = ""; }; + AD877C2DBA0A621AD258A010A5E54B2F /* FIRDeleteAccountRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRDeleteAccountRequest.m; path = Firebase/Auth/Source/Backend/RPC/FIRDeleteAccountRequest.m; sourceTree = ""; }; + AD9911FF901182BFF3E6388EE68455A4 /* FIRVerifyCustomTokenResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRVerifyCustomTokenResponse.m; path = Firebase/Auth/Source/Backend/RPC/FIRVerifyCustomTokenResponse.m; sourceTree = ""; }; + ADABD7A2BBCAE81134FFE12A19B5DE02 /* Firebase.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Firebase.xcconfig; sourceTree = ""; }; + AE1090589F6CFBD5A3E3BD3C0DDDDCA2 /* FIRAnalyticsInterop.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAnalyticsInterop.h; path = Interop/Analytics/Public/FIRAnalyticsInterop.h; sourceTree = ""; }; + AE3CCA0A95C2DB398DA83E48466C7954 /* FIRApp.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRApp.h; path = FirebaseCore/Sources/Public/FIRApp.h; sourceTree = ""; }; + AE4FF34D0DFF78A8C05317D4A10C2A36 /* FTupleOnDisconnect.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FTupleOnDisconnect.m; path = Firebase/Database/Utilities/Tuples/FTupleOnDisconnect.m; sourceTree = ""; }; + AE57CCAA2CE76A131F0045A0DEE4BF61 /* FPruneForest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FPruneForest.m; path = Firebase/Database/Persistence/FPruneForest.m; sourceTree = ""; }; + AE5C36747E6121753BC7D0C5E556FADF /* DiskStorage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DiskStorage.swift; path = Sources/Cache/DiskStorage.swift; sourceTree = ""; }; + AE6D328B3C537003183B73C76D907C10 /* status.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = status.h; path = include/leveldb/status.h; sourceTree = ""; }; + AE7D406AE43833497C38783D7B7BB688 /* Alamofire-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Alamofire-dummy.m"; sourceTree = ""; }; + AEF66CD8B1BCBC0A3B148DF00A6113A8 /* FBLPromise+Reduce.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Reduce.h"; path = "Sources/FBLPromises/include/FBLPromise+Reduce.h"; sourceTree = ""; }; + AEFBD0F180EED7215C8DA83E8590612E /* RLMListBase.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMListBase.h; path = include/RLMListBase.h; sourceTree = ""; }; + AF1A7341BE52513E878BD1EFF619C285 /* GULHeartbeatDateStorage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULHeartbeatDateStorage.h; path = GoogleUtilities/Environment/Public/GULHeartbeatDateStorage.h; sourceTree = ""; }; + AF62A3F1196ABE198A8D9FAD67E52A57 /* FIRDatabaseReference.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRDatabaseReference.m; path = Firebase/Database/FIRDatabaseReference.m; sourceTree = ""; }; + AFC0E06A82E7604B1321E10A65210FC7 /* FIRFederatedAuthProvider.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRFederatedAuthProvider.h; path = Firebase/Auth/Source/Public/FIRFederatedAuthProvider.h; sourceTree = ""; }; + AFEAE24FA3CA9EDA209B5B2086A1836E /* FTuplePathValue.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FTuplePathValue.m; path = Firebase/Database/Utilities/Tuples/FTuplePathValue.m; sourceTree = ""; }; + AFFD295CAD2BDB5B43E29F61E1D4D7BC /* FIRCLSMultipartMimeStreamEncoder.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSMultipartMimeStreamEncoder.h; path = Crashlytics/Shared/FIRCLSNetworking/FIRCLSMultipartMimeStreamEncoder.h; sourceTree = ""; }; + B0702C27C0C68A75145BD0D9C2F4CEA7 /* GTMSessionFetcher-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "GTMSessionFetcher-Info.plist"; sourceTree = ""; }; + B0869227B77B723D090AA044C73DC03D /* FIRSignInWithGameCenterRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRSignInWithGameCenterRequest.m; path = Firebase/Auth/Source/Backend/RPC/FIRSignInWithGameCenterRequest.m; sourceTree = ""; }; + B0BD9B5CDB9392F69987B56C2D80D447 /* FIRAuthSettings.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAuthSettings.m; path = Firebase/Auth/Source/Auth/FIRAuthSettings.m; sourceTree = ""; }; + B0E2D9DCC2D578F3C955E2D3D20D732E /* FIRGoogleAuthCredential.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRGoogleAuthCredential.h; path = Firebase/Auth/Source/AuthProvider/Google/FIRGoogleAuthCredential.h; sourceTree = ""; }; + B1259AB3BC80BACF8D0BC98DD0EAD51E /* RLMSyncPermission.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMSyncPermission.mm; path = Realm/RLMSyncPermission.mm; sourceTree = ""; }; + B137C04F7C027EC1981824188E584EA9 /* FTypedefs_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FTypedefs_Private.h; path = Firebase/Database/Api/Private/FTypedefs_Private.h; sourceTree = ""; }; + B1826037AF90ACAC8537C39DF02EC8BF /* FPathIndex.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FPathIndex.h; path = Firebase/Database/FPathIndex.h; sourceTree = ""; }; + B1EA1D27F97E2F73EFBCE2703BCE7E0D /* table_cache.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = table_cache.h; path = db/table_cache.h; sourceTree = ""; }; + B1EFC8129C762D0986B402BF4835DAB3 /* Util.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Util.swift; path = RealmSwift/Util.swift; sourceTree = ""; }; + B22A5F3E02894F9C5BA92CDF2FED5C7B /* FIRAuthCredential.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthCredential.h; path = Firebase/Auth/Source/Public/FIRAuthCredential.h; sourceTree = ""; }; + B287553C4687458B4F94CD2691ECD117 /* FIRIdentityToolkitRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRIdentityToolkitRequest.h; path = Firebase/Auth/Source/Backend/FIRIdentityToolkitRequest.h; sourceTree = ""; }; + B2B9CFF9F6610ED23E661A8837C20DA3 /* FIRInstallationsKeychainUtils.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsKeychainUtils.m; path = FirebaseInstallations/Source/Library/SecureStorage/FIRInstallationsKeychainUtils.m; sourceTree = ""; }; + B2C3C0B84E27EFD7128F49252167B459 /* FIRInstallationsIDController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsIDController.h; path = FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsIDController.h; sourceTree = ""; }; + B2F28140F42539EF0AE6EC82CF67C0F1 /* options.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = options.cc; path = util/options.cc; sourceTree = ""; }; + B3691C4DC1B24C1A062A1D4EDD79DAC0 /* FIRAnalyticsInteropListener.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAnalyticsInteropListener.h; path = Interop/Analytics/Public/FIRAnalyticsInteropListener.h; sourceTree = ""; }; + B3C87A46F96166E181D907BACBDF1D34 /* FIRCLSException.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = FIRCLSException.mm; path = Crashlytics/Crashlytics/Handlers/FIRCLSException.mm; sourceTree = ""; }; + B3C87CC09FB0D7BFBC1445D0533D25DB /* FIRSignInWithGameCenterRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRSignInWithGameCenterRequest.h; path = Firebase/Auth/Source/Backend/RPC/FIRSignInWithGameCenterRequest.h; sourceTree = ""; }; + B3D17B0B57A0B5950C0C15451C711B2A /* RealmConfiguration.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RealmConfiguration.swift; path = RealmSwift/RealmConfiguration.swift; sourceTree = ""; }; + B43874C6CBB50E7134FBEC24BABFE14F /* GoogleUtilities.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = GoogleUtilities.framework; path = GoogleUtilities.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B4630EC4BD28C7D9F743C6494833B4F3 /* FSnapshotHolder.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FSnapshotHolder.h; path = Firebase/Database/Core/FSnapshotHolder.h; sourceTree = ""; }; + B46827FEC6AC5CD82B5F7C24704F9936 /* FIRAuthErrors.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthErrors.h; path = Firebase/Auth/Source/Public/FIRAuthErrors.h; sourceTree = ""; }; + B49E2673664F75854392D9A0F630AC28 /* RLMCollection_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMCollection_Private.h; path = include/RLMCollection_Private.h; sourceTree = ""; }; + B4BC511ECBBFA8ECB40030E727A947D3 /* FIRInstanceIDLogger.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstanceIDLogger.m; path = Firebase/InstanceID/FIRInstanceIDLogger.m; sourceTree = ""; }; + B536523F3D888E0367D79E755046FCD1 /* PromisesObjC.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = PromisesObjC.modulemap; sourceTree = ""; }; + B537E201A244683EF2200A1913CA591C /* RLMClassInfo.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMClassInfo.mm; path = Realm/RLMClassInfo.mm; sourceTree = ""; }; + B53846D10D47CDE2D30C4DD425E97808 /* GDTCCTUploader.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCCTUploader.m; path = GoogleDataTransportCCTSupport/GDTCCTLibrary/GDTCCTUploader.m; sourceTree = ""; }; + B5E3722A6289EC400D61D12B9478747F /* FStringUtilities.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FStringUtilities.h; path = Firebase/Database/Utilities/FStringUtilities.h; sourceTree = ""; }; + B68AAF3A0D973EE14F332611717BF3E0 /* FIRComponent.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRComponent.h; path = FirebaseCore/Sources/Private/FIRComponent.h; sourceTree = ""; }; + B6A2F43C1606C657E86CFC6EC8FF1F7F /* FIRAnalyticsConnector.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FIRAnalyticsConnector.framework; path = Frameworks/FIRAnalyticsConnector.framework; sourceTree = ""; }; + B71683F1B20F1053C4E78AF2ABFE7637 /* FIRGetProjectConfigRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRGetProjectConfigRequest.h; path = Firebase/Auth/Source/Backend/RPC/FIRGetProjectConfigRequest.h; sourceTree = ""; }; + B7AF0BB63342B990F1F06546893BE592 /* FView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FView.m; path = Firebase/Database/Core/View/FView.m; sourceTree = ""; }; + B7B3506EDC4CA84B41DC6B9FE7582C58 /* FIRCLSMachO.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSMachO.m; path = Crashlytics/Shared/FIRCLSMachO/FIRCLSMachO.m; sourceTree = ""; }; + B8201CFA57F59444698C37217A4ABEA8 /* FIRInstanceIDUtilities.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstanceIDUtilities.m; path = Firebase/InstanceID/FIRInstanceIDUtilities.m; sourceTree = ""; }; + B830D4D6C294ED178AD87E57C9ADA7FF /* GDTCORDataFuture.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORDataFuture.h; path = GoogleDataTransport/GDTCORLibrary/Public/GDTCORDataFuture.h; sourceTree = ""; }; + B8380284D178779DBD01394689C2E592 /* FTupleOnDisconnect.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FTupleOnDisconnect.h; path = Firebase/Database/Utilities/Tuples/FTupleOnDisconnect.h; sourceTree = ""; }; + B8A5983F620EA151F5229275D6DF1BE2 /* FIRNoopAuthTokenProvider.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRNoopAuthTokenProvider.h; path = Firebase/Database/Login/FIRNoopAuthTokenProvider.h; sourceTree = ""; }; + B8B62D08C4562CDA545B1A9CE08B8BE1 /* FIRComponent.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRComponent.m; path = FirebaseCore/Sources/FIRComponent.m; sourceTree = ""; }; + B8D3A408F55DEF3F90B05828E9484BD0 /* FBLPromise+Then.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Then.m"; path = "Sources/FBLPromises/FBLPromise+Then.m"; sourceTree = ""; }; + B8FAB6CC1DDAAF761AAE56B6FCF360D4 /* FIRInstanceIDTokenOperation+Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FIRInstanceIDTokenOperation+Private.h"; path = "Firebase/InstanceID/FIRInstanceIDTokenOperation+Private.h"; sourceTree = ""; }; + B9007A7BAAAE5B47A2A31441FDEDFD3E /* env.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = env.h; path = include/leveldb/env.h; sourceTree = ""; }; + B9166509EBF18D0943E4D8562A3C3CAA /* FIRAuthNotificationManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthNotificationManager.h; path = Firebase/Auth/Source/SystemService/FIRAuthNotificationManager.h; sourceTree = ""; }; + B95848C5B6C50139C9D280F109737601 /* Runtime.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Runtime.swift; path = Sources/Utility/Runtime.swift; sourceTree = ""; }; + B9589DD728D797408CD139CE3274709B /* FIRCLSNetworkResponseHandler.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSNetworkResponseHandler.h; path = Crashlytics/Shared/FIRCLSNetworking/FIRCLSNetworkResponseHandler.h; sourceTree = ""; }; + B9752B9BF07F882163DE4B93D48E07CC /* FTupleStringNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FTupleStringNode.m; path = Firebase/Database/Utilities/Tuples/FTupleStringNode.m; sourceTree = ""; }; + B9B6DDC21B935230DCFEFED3D43FD220 /* FQueryParams.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FQueryParams.h; path = Firebase/Database/Core/FQueryParams.h; sourceTree = ""; }; + B9DA578BE4C64C070DD8C98AAA98E52F /* FIRInstanceIDBackupExcludedPlist.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstanceIDBackupExcludedPlist.h; path = Firebase/InstanceID/FIRInstanceIDBackupExcludedPlist.h; sourceTree = ""; }; + B9DBE0FD749B655F84CDF7BE54E13699 /* FIRCLSCompactUnwind_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSCompactUnwind_Private.h; path = Crashlytics/Crashlytics/Unwind/Compact/FIRCLSCompactUnwind_Private.h; sourceTree = ""; }; + BA2AD92B599641752DCBD8F1FE784B96 /* FirebaseAuth-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "FirebaseAuth-umbrella.h"; sourceTree = ""; }; + BA5D4D31F808F2F3EB3D5D55B36F1070 /* FConnection.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FConnection.h; path = Firebase/Database/Realtime/FConnection.h; sourceTree = ""; }; + BA9EE67D70795C4E48FEAD85D644585B /* RealmCollection.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RealmCollection.swift; path = RealmSwift/RealmCollection.swift; sourceTree = ""; }; + BAB03F1501D0A639B1B5A638411CBD82 /* GULMutableDictionary.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULMutableDictionary.h; path = GoogleUtilities/Network/Private/GULMutableDictionary.h; sourceTree = ""; }; + BAB0E9E2A5876C4EF0CF337514EDCF06 /* FIRSecureStorage.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRSecureStorage.m; path = FirebaseInstallations/Source/Library/SecureStorage/FIRSecureStorage.m; sourceTree = ""; }; + BABC8EB442B76A4399A427E8932DB865 /* Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Kingfisher.swift; path = Sources/General/Kingfisher.swift; sourceTree = ""; }; + BAFE031523B5978048756FE2BCED2DA4 /* db_iter.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = db_iter.cc; path = db/db_iter.cc; sourceTree = ""; }; + BB4B1F93EE7A1E409F89108816B92EBF /* GULNetworkURLSession.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULNetworkURLSession.m; path = GoogleUtilities/Network/GULNetworkURLSession.m; sourceTree = ""; }; + BB5AA820C529D5333970A049ACDA2A36 /* collection_notifier.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = collection_notifier.cpp; path = Realm/ObjectStore/src/impl/collection_notifier.cpp; sourceTree = ""; }; + BB72DEB463D368287CFC5EFD8FF55244 /* GDTCORTransport_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORTransport_Private.h; path = GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransport_Private.h; sourceTree = ""; }; + BB8555579C24393A6EB509F5D1FBD10A /* FIRCLSFABHost.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSFABHost.m; path = Crashlytics/Shared/FIRCLSFABHost.m; sourceTree = ""; }; + BBD644E2ADD3C185AE9CD03FF0CD495B /* log_reader.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = log_reader.cc; path = db/log_reader.cc; sourceTree = ""; }; + BBDE0947F81FE23D7DC2157383181911 /* RLMRealmConfiguration+Sync.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "RLMRealmConfiguration+Sync.h"; path = "include/RLMRealmConfiguration+Sync.h"; sourceTree = ""; }; + BC0CEF1C2D64AB51593E134566463379 /* FIRLibrary.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRLibrary.h; path = FirebaseCore/Sources/Private/FIRLibrary.h; sourceTree = ""; }; + BC115140A96B7DA133E8F98C90D95FD0 /* FIRRetryHelper.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRRetryHelper.m; path = Firebase/Database/Core/Utilities/FIRRetryHelper.m; sourceTree = ""; }; + BC2992AF3809C2883734EBB8D3EC5609 /* FIRAuthAPNSToken.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthAPNSToken.h; path = Firebase/Auth/Source/SystemService/FIRAuthAPNSToken.h; sourceTree = ""; }; + BC607F184AD720438FF6F68C03102FCF /* FCancelEvent.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FCancelEvent.m; path = Firebase/Database/Core/View/FCancelEvent.m; sourceTree = ""; }; + BC7F50EAF94496196A249D25CCA491B3 /* dumpfile.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = dumpfile.h; path = include/leveldb/dumpfile.h; sourceTree = ""; }; + BCAA4BD8CF79CDFBE112EEAB21E22806 /* FIRCLSDwarfExpressionMachine.c */ = {isa = PBXFileReference; includeInIndex = 1; name = FIRCLSDwarfExpressionMachine.c; path = Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDwarfExpressionMachine.c; sourceTree = ""; }; + BD000B66CA3DAE39C9254B98F356AD5E /* FIRAnalyticsConfiguration.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAnalyticsConfiguration.m; path = FirebaseCore/Sources/FIRAnalyticsConfiguration.m; sourceTree = ""; }; + BD0D11232D26F0FF3FDC5D549A76DE13 /* FBLPromise.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FBLPromise.m; path = Sources/FBLPromises/FBLPromise.m; sourceTree = ""; }; + BE194B2DC76B95274DE3365E8F0C6072 /* Pods-GeekbrainsUI-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-GeekbrainsUI-Info.plist"; sourceTree = ""; }; + BE79B0FA5EB399EAC4CE00E28D3EC116 /* GULSecureCoding.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULSecureCoding.m; path = GoogleUtilities/Environment/GULSecureCoding.m; sourceTree = ""; }; + BE9D69EB6F70F361D33FF4517C4151F6 /* Deprecated.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Deprecated.swift; path = Sources/General/Deprecated.swift; sourceTree = ""; }; + BEAF03C0C4D7B2341CC75989723A9911 /* FIRInstanceIDAPNSInfo.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstanceIDAPNSInfo.h; path = Firebase/InstanceID/FIRInstanceIDAPNSInfo.h; sourceTree = ""; }; + BED0BA47CB4A60F6FA1559076AE57EC5 /* FDataEvent.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FDataEvent.h; path = Firebase/Database/Core/View/FDataEvent.h; sourceTree = ""; }; + BEDABE10B5025C79318A8CF7B1925844 /* FIRAuthWebView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAuthWebView.m; path = Firebase/Auth/Source/Utilities/FIRAuthWebView.m; sourceTree = ""; }; + BF0DD77C260C2BCB9B283657354A812B /* FKeepSyncedEventRegistration.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FKeepSyncedEventRegistration.m; path = Firebase/Database/Core/View/FKeepSyncedEventRegistration.m; sourceTree = ""; }; + BF3AF58142843EDD7EB3EFB72810E7B9 /* FirebaseCore-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "FirebaseCore-dummy.m"; sourceTree = ""; }; + BF4725A1936C6270B4AB0B1DBB7B4A41 /* FIRCLSReportManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSReportManager.h; path = Crashlytics/Crashlytics/Controllers/FIRCLSReportManager.h; sourceTree = ""; }; + BF60CBDE6804E7AEE2F0511331C15B8D /* GDTCORTransformer_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORTransformer_Private.h; path = GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer_Private.h; sourceTree = ""; }; + BF6C758A52AEA6F30D091E5D3D99C459 /* Kingfisher.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Kingfisher.xcconfig; sourceTree = ""; }; + BF7958D5FE36B4E8E4EDAD3709AF04BA /* FirebaseAuth.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = FirebaseAuth.modulemap; sourceTree = ""; }; + BF8CF5E3F7A1A02D334DF95AAA519EB2 /* FTupleCallbackStatus.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FTupleCallbackStatus.h; path = Firebase/Database/Utilities/Tuples/FTupleCallbackStatus.h; sourceTree = ""; }; + BFAAA08675F94727874A6F041299FEBD /* logging.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = logging.h; path = util/logging.h; sourceTree = ""; }; + BFADFD13907771FB22980C06CDA26146 /* RLMSyncUtil.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMSyncUtil.mm; path = Realm/RLMSyncUtil.mm; sourceTree = ""; }; + BFC56E517F177CBDCE7A445BE95B4E4C /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/Security.framework; sourceTree = DEVELOPER_DIR; }; + BFFCEAEC4E3AEC71E8ED88F86C899DA1 /* FIRCLSStackFrame.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSStackFrame.m; path = Crashlytics/Crashlytics/Models/FIRCLSStackFrame.m; sourceTree = ""; }; + C033070806227FC7EBEF70D50A23C75B /* FValidation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FValidation.m; path = Firebase/Database/Utilities/FValidation.m; sourceTree = ""; }; + C05B5D9CCA2A7B1A5E03AA8CC4DE1E5F /* FIRAuthSerialTaskQueue.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthSerialTaskQueue.h; path = Firebase/Auth/Source/Auth/FIRAuthSerialTaskQueue.h; sourceTree = ""; }; + C0730316DE3700CB55969541D5468F24 /* FIRCLSAsyncOperation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSAsyncOperation.m; path = Crashlytics/Crashlytics/Operations/FIRCLSAsyncOperation.m; sourceTree = ""; }; + C0A3508439074258FC89A3CBFB18B819 /* FPersistenceManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FPersistenceManager.m; path = Firebase/Database/Persistence/FPersistenceManager.m; sourceTree = ""; }; + C0B68F31335F12A48404BBD938C49152 /* RealmSwift.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = RealmSwift.xcconfig; sourceTree = ""; }; + C0B95A37E920E65E5E78E2632195B47E /* FTupleTransaction.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FTupleTransaction.h; path = Firebase/Database/Utilities/Tuples/FTupleTransaction.h; sourceTree = ""; }; + C0D2FC3564EF6F5762A6F5D48E4136CA /* FChildEventRegistration.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FChildEventRegistration.m; path = Firebase/Database/Core/View/FChildEventRegistration.m; sourceTree = ""; }; + C0D584A02FC056531E03BF78F097310D /* FIRInstanceIDTokenInfo.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstanceIDTokenInfo.m; path = Firebase/InstanceID/FIRInstanceIDTokenInfo.m; sourceTree = ""; }; + C0EE0F9DF8F3330249489D1E4F82FAFC /* FIRCLSFABAsyncOperation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSFABAsyncOperation.m; path = Crashlytics/Shared/FIRCLSOperation/FIRCLSFABAsyncOperation.m; sourceTree = ""; }; + C113D215D4DE09C283B8502C4C1FC7B8 /* FirebaseAuth-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "FirebaseAuth-Info.plist"; sourceTree = ""; }; + C136D124912241858A8E583D0415F25E /* FIRCLSHandler.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSHandler.h; path = Crashlytics/Crashlytics/Handlers/FIRCLSHandler.h; sourceTree = ""; }; + C1411B2CA010177EB738082A892A6DD2 /* FIRCLSUUID.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSUUID.m; path = Crashlytics/Shared/FIRCLSUUID.m; sourceTree = ""; }; + C190906AA7599B5C3B1EDD595BD50AE2 /* FIRAdditionalUserInfo.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAdditionalUserInfo.h; path = Firebase/Auth/Source/Public/FIRAdditionalUserInfo.h; sourceTree = ""; }; + C1998E0D8085221AD87F89B614C10E52 /* GTMSessionFetcher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = GTMSessionFetcher.framework; path = GTMSessionFetcher.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C2173032F4DC6AB031930F5AEED697F0 /* ImagePrefetcher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImagePrefetcher.swift; path = Sources/Networking/ImagePrefetcher.swift; sourceTree = ""; }; + C22F2A5F3A5274251E53C399BA270681 /* FIRDatabaseConfig_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRDatabaseConfig_Private.h; path = Firebase/Database/FIRDatabaseConfig_Private.h; sourceTree = ""; }; + C23E7FFC35B7C0C36475EA8E367B1573 /* FTreeSortedDictionaryEnumerator.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FTreeSortedDictionaryEnumerator.m; path = Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FTreeSortedDictionaryEnumerator.m; sourceTree = ""; }; + C25B246DBE48DFAE91A83DFD9A0CBE77 /* FEventRaiser.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FEventRaiser.h; path = Firebase/Database/Core/View/FEventRaiser.h; sourceTree = ""; }; + C29418203303D3DD410EE8C349774807 /* FIRCLSHandler.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSHandler.m; path = Crashlytics/Crashlytics/Handlers/FIRCLSHandler.m; sourceTree = ""; }; + C2B57CC70F0024AC3039D5A76E0C6B33 /* FirebaseInstanceID.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FirebaseInstanceID.xcconfig; sourceTree = ""; }; + C2B7043CEF622A670BF8F6991A15AE7C /* FirebaseCrashlytics-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "FirebaseCrashlytics-umbrella.h"; sourceTree = ""; }; + C2CA5484C1CAEBAFEB82928E0B911DBD /* port_example.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = port_example.h; path = port/port_example.h; sourceTree = ""; }; + C2E4BB2E9C451A12AFA6ED8B18E0674F /* RLMRealmUtil.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMRealmUtil.mm; path = Realm/RLMRealmUtil.mm; sourceTree = ""; }; + C2EADE68D084997E314F18E639061AD1 /* FIRInstanceIDCheckinService.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstanceIDCheckinService.m; path = Firebase/InstanceID/FIRInstanceIDCheckinService.m; sourceTree = ""; }; + C3460D05DC30E134CA7B463814A904EE /* leveldb-library.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "leveldb-library.xcconfig"; sourceTree = ""; }; + C35101BD88B8B352E5C9084099ED1C91 /* FTransformedEnumerator.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FTransformedEnumerator.m; path = Firebase/Database/FTransformedEnumerator.m; sourceTree = ""; }; + C381B0F4DB89513D7C09360B109ED622 /* FChange.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FChange.m; path = Firebase/Database/Core/View/FChange.m; sourceTree = ""; }; + C3DF5ABA2B431B97F394AF435C9865A8 /* GULOriginalIMPConvenienceMacros.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULOriginalIMPConvenienceMacros.h; path = GoogleUtilities/MethodSwizzler/Private/GULOriginalIMPConvenienceMacros.h; sourceTree = ""; }; + C3F44C782D64D7EB20B61CE3844EBFAD /* Kingfisher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Kingfisher.framework; path = Kingfisher.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C3FD6D23D7CE39B3F05FDFD14293FFC7 /* GoogleUtilities.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = GoogleUtilities.modulemap; sourceTree = ""; }; + C4077AE6A29830CC9E7134E0563DC629 /* FIRCLSUnwind.c */ = {isa = PBXFileReference; includeInIndex = 1; name = FIRCLSUnwind.c; path = Crashlytics/Crashlytics/Unwind/FIRCLSUnwind.c; sourceTree = ""; }; + C40CC2964E8729E9F7F77F7626636D13 /* FLLRBNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FLLRBNode.h; path = Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FLLRBNode.h; sourceTree = ""; }; + C44C40D4B3AF4238A0A8521E9F1C7F77 /* RLMOptionalBase.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMOptionalBase.h; path = include/RLMOptionalBase.h; sourceTree = ""; }; + C47E486A0F18A6B1AE7940348FC7D3E3 /* FTupleUserCallback.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FTupleUserCallback.h; path = Firebase/Database/Utilities/Tuples/FTupleUserCallback.h; sourceTree = ""; }; + C4A5068B4F002455620D242C20110490 /* RLMMigration.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMMigration.h; path = include/RLMMigration.h; sourceTree = ""; }; + C4B5B407BB54A7E00EFC6217B9057C28 /* FIRVerifyPasswordResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRVerifyPasswordResponse.h; path = Firebase/Auth/Source/Backend/RPC/FIRVerifyPasswordResponse.h; sourceTree = ""; }; + C4BA68AE47EFEC1A5A8AC627ABC9399C /* FCancelEvent.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FCancelEvent.h; path = Firebase/Database/Core/View/FCancelEvent.h; sourceTree = ""; }; + C4EB340CC90499FE586FFAD55EA2C4C2 /* FIRDatabase.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRDatabase.h; path = Firebase/Database/Public/FIRDatabase.h; sourceTree = ""; }; + C4F33E933CEDD03D5D071150E4688160 /* FIRAppAssociationRegistration.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAppAssociationRegistration.h; path = FirebaseCore/Sources/Private/FIRAppAssociationRegistration.h; sourceTree = ""; }; + C57F75D4E0E0E4D75CBAB5644F1CF5ED /* FIRLogger.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRLogger.m; path = FirebaseCore/Sources/FIRLogger.m; sourceTree = ""; }; + C58E2455DB5216CAC525E50775B466B0 /* FEventRaiser.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FEventRaiser.m; path = Firebase/Database/Core/View/FEventRaiser.m; sourceTree = ""; }; + C5B9B9A3D348238704B96F1CFC1C0CF3 /* FIRVerifyClientRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRVerifyClientRequest.h; path = Firebase/Auth/Source/Backend/RPC/FIRVerifyClientRequest.h; sourceTree = ""; }; + C64A992EF6C1A053145E1FE49B94CBA5 /* ImageDrawing.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageDrawing.swift; path = Sources/Image/ImageDrawing.swift; sourceTree = ""; }; + C673995A49F14FBF2E4228285E650AAE /* FPersistentConnection.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FPersistentConnection.h; path = Firebase/Database/Core/FPersistentConnection.h; sourceTree = ""; }; + C68934F7EF2D6B6AA3755B9F60EBA98F /* FIRConfiguration.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRConfiguration.m; path = FirebaseCore/Sources/FIRConfiguration.m; sourceTree = ""; }; + C699EDB3BC3205372C8415289EA8D2C4 /* network_reachability_observer.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = network_reachability_observer.cpp; path = Realm/ObjectStore/src/sync/impl/apple/network_reachability_observer.cpp; sourceTree = ""; }; + C6ABC1E67AA88DD64EDA72189FFA1268 /* FIRAuthAppCredential.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthAppCredential.h; path = Firebase/Auth/Source/SystemService/FIRAuthAppCredential.h; sourceTree = ""; }; + C6CBD394561897F97580F99C0927661C /* FArraySortedDictionary.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FArraySortedDictionary.h; path = Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FArraySortedDictionary.h; sourceTree = ""; }; + C74A86C48C177012E0045DC846302FED /* RLMProperty_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMProperty_Private.h; path = include/RLMProperty_Private.h; sourceTree = ""; }; + C78202D4B72FA2DC0E1F9DC84DE41760 /* external_commit_helper.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = external_commit_helper.cpp; path = Realm/ObjectStore/src/impl/apple/external_commit_helper.cpp; sourceTree = ""; }; + C78A0274733FB2CB614752BB28DDFDB5 /* FViewProcessor.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FViewProcessor.m; path = Firebase/Database/FViewProcessor.m; sourceTree = ""; }; + C7972622EF19B74B3BFA5118DEA5FE5F /* FIRDatabaseQuery_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRDatabaseQuery_Private.h; path = Firebase/Database/Api/Private/FIRDatabaseQuery_Private.h; sourceTree = ""; }; + C7D050C1C76B3FB438035A590FBF3984 /* FIRLogger.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRLogger.h; path = FirebaseCore/Sources/Private/FIRLogger.h; sourceTree = ""; }; + C80B8C0BA49A935A8C34747C21AD782F /* FIRSecureTokenService.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRSecureTokenService.h; path = Firebase/Auth/Source/SystemService/FIRSecureTokenService.h; sourceTree = ""; }; + C8242370427DCF96728DAA60AC4746F2 /* FIROAuthCredential.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIROAuthCredential.m; path = Firebase/Auth/Source/AuthProvider/OAuth/FIROAuthCredential.m; sourceTree = ""; }; + C82C90DD882D46D8FD21ADDF3E9F9D5B /* FIRAuthUserDefaults.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthUserDefaults.h; path = Firebase/Auth/Source/Storage/FIRAuthUserDefaults.h; sourceTree = ""; }; + C83BBC3BBB374809A02DED4A80A2F97F /* Alamofire-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Alamofire-prefix.pch"; sourceTree = ""; }; + C86D75013FDEE444275D5CE51FF59D68 /* FIRAuthTokenResult.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAuthTokenResult.m; path = Firebase/Auth/Source/Auth/FIRAuthTokenResult.m; sourceTree = ""; }; + C8BC987527B0F350D97D5F43DE756CE6 /* FIRInstallationsSingleOperationPromiseCache.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsSingleOperationPromiseCache.h; path = FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsSingleOperationPromiseCache.h; sourceTree = ""; }; + C8D360BB5DCD315E5A9DA1EE683CED0D /* FIRUserMetadata_Internal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRUserMetadata_Internal.h; path = Firebase/Auth/Source/User/FIRUserMetadata_Internal.h; sourceTree = ""; }; + C8D9FF6584BEA3EB2DDF9561F2ABC8D1 /* RLMSyncSubscription.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMSyncSubscription.h; path = include/RLMSyncSubscription.h; sourceTree = ""; }; + C93A659F3C396F363AA9EAC8E6D63480 /* FIRAuthURLPresenter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAuthURLPresenter.m; path = Firebase/Auth/Source/Utilities/FIRAuthURLPresenter.m; sourceTree = ""; }; + C9A08033F18CE85182378A9E946680D8 /* FPriorityIndex.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FPriorityIndex.h; path = Firebase/Database/FPriorityIndex.h; sourceTree = ""; }; + C9A9708F1BC72EBB6C42F9D332BAD6D8 /* FIRCLSAsyncOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSAsyncOperation.h; path = Crashlytics/Crashlytics/Operations/FIRCLSAsyncOperation.h; sourceTree = ""; }; + C9DBBF81986552C984F047D58DD511CF /* work_queue.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = work_queue.cpp; path = Realm/ObjectStore/src/sync/impl/work_queue.cpp; sourceTree = ""; }; + C9FD65E8C3CE0766CDC9923BD322155C /* FIRCLSInstallIdentifierModel.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSInstallIdentifierModel.m; path = Crashlytics/Crashlytics/Models/FIRCLSInstallIdentifierModel.m; sourceTree = ""; }; + C9FDAAB6458C9C7A42D77B11BF0B3F75 /* GDTCORRegistrar.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORRegistrar.h; path = GoogleDataTransport/GDTCORLibrary/Public/GDTCORRegistrar.h; sourceTree = ""; }; + CA2805B2729BA1A43D194462C60F5179 /* FRangeMerge.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FRangeMerge.m; path = Firebase/Database/Core/FRangeMerge.m; sourceTree = ""; }; + CA7C19E231F44F4302565226ECB05515 /* ImageTransition.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageTransition.swift; path = Sources/Image/ImageTransition.swift; sourceTree = ""; }; + CAB3A4110D3D69279A6A92082D511C2A /* testharness.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = testharness.h; path = util/testharness.h; sourceTree = ""; }; + CAB84098B3AFF0DFFCDFDC38B55F2DDF /* iterator.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = iterator.h; path = include/leveldb/iterator.h; sourceTree = ""; }; + CAE52D190FDACA01E3E8E100CCCFFB71 /* FIRCLSUserDefaults.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSUserDefaults.h; path = Crashlytics/Crashlytics/FIRCLSUserDefaults/FIRCLSUserDefaults.h; sourceTree = ""; }; + CB0A9640A0C659E27B59C16EF296B350 /* crc32c.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = crc32c.cc; path = util/crc32c.cc; sourceTree = ""; }; + CB2C765DBA7E08155DA539BA14E76E0A /* RLMProperty.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMProperty.mm; path = Realm/RLMProperty.mm; sourceTree = ""; }; + CBC0A66973B374C28F35611394D76F62 /* FBLPromise+Await.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Await.m"; path = "Sources/FBLPromises/FBLPromise+Await.m"; sourceTree = ""; }; + CC3882078B4863B39FE8686F20D749E1 /* FIRDiagnosticsData.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRDiagnosticsData.m; path = FirebaseCore/Sources/FIRDiagnosticsData.m; sourceTree = ""; }; + CC4528AA746B4ECE9B1A6B9E30F2528A /* snapshot.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = snapshot.h; path = db/snapshot.h; sourceTree = ""; }; + CC4EB592A6575DA0569604F8EF9D9EA3 /* FNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FNode.h; path = Firebase/Database/Snapshot/FNode.h; sourceTree = ""; }; + CC5276A017618A24E6C85A878E76F4BA /* FIRVerifyPasswordResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRVerifyPasswordResponse.m; path = Firebase/Auth/Source/Backend/RPC/FIRVerifyPasswordResponse.m; sourceTree = ""; }; + CCAB7E316CA26642BFF8CC2BDB62A008 /* Notifications.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Notifications.swift; path = Source/Notifications.swift; sourceTree = ""; }; + CD0CB15E468AF2B83D47464CEDFA7E79 /* RLMRealmConfiguration.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMRealmConfiguration.mm; path = Realm/RLMRealmConfiguration.mm; sourceTree = ""; }; + CD214470D6041E540B899AA24488E398 /* FIRSendVerificationCodeRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRSendVerificationCodeRequest.h; path = Firebase/Auth/Source/Backend/RPC/FIRSendVerificationCodeRequest.h; sourceTree = ""; }; + CD8D438942C8F90B8D167852A4A7EEAC /* FIRCreateAuthURIResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCreateAuthURIResponse.m; path = Firebase/Auth/Source/Backend/RPC/FIRCreateAuthURIResponse.m; sourceTree = ""; }; + CDA8666A9B50BF19DF40C0F386F748B5 /* FTupleStringNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FTupleStringNode.h; path = Firebase/Database/Utilities/Tuples/FTupleStringNode.h; sourceTree = ""; }; + CDB84EA8E14223D9B5C902DB4D179F57 /* SessionDataTask.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SessionDataTask.swift; path = Sources/Networking/SessionDataTask.swift; sourceTree = ""; }; + CDDA260CC13C2C314506C956FB571FED /* FLeafNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FLeafNode.h; path = Firebase/Database/Snapshot/FLeafNode.h; sourceTree = ""; }; + CDE235CB054F3FE398E19E47A6CD214E /* PromisesObjC-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "PromisesObjC-umbrella.h"; sourceTree = ""; }; + CDEC22C4B877A3988603237A07AD8B98 /* no_destructor.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = no_destructor.h; path = util/no_destructor.h; sourceTree = ""; }; + CE591618BB2FD1CDAF280E66FE7F3F6D /* GULAppEnvironmentUtil.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULAppEnvironmentUtil.h; path = GoogleUtilities/Environment/third_party/GULAppEnvironmentUtil.h; sourceTree = ""; }; + CEC50CD22C78836638CBC19D7524122C /* GDTCORUploader.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORUploader.h; path = GoogleDataTransport/GDTCORLibrary/Public/GDTCORUploader.h; sourceTree = ""; }; + CEE91D635068604C4F9104A22986350B /* FIRCLSUnwind_x86.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSUnwind_x86.h; path = Crashlytics/Crashlytics/Unwind/FIRCLSUnwind_x86.h; sourceTree = ""; }; + CF5F329FA138691313AFB64FB1006F57 /* GoogleDataTransport.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GoogleDataTransport.h; path = GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport.h; sourceTree = ""; }; + CF8433969CF929D29996C79B904999D4 /* cct.nanopb.c */ = {isa = PBXFileReference; includeInIndex = 1; name = cct.nanopb.c; path = GoogleDataTransportCCTSupport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.c; sourceTree = ""; }; + CFD9FB653049C45B8E552C3BE27FDDAD /* block.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = block.cc; path = table/block.cc; sourceTree = ""; }; + D02AD4C8F8482C50EB8A6DF81E4B7DA6 /* FIRInstallationsVersion.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsVersion.m; path = FirebaseInstallations/Source/Library/FIRInstallationsVersion.m; sourceTree = ""; }; + D0373682AE1E96F2853B6452AB74C190 /* FIRDiagnosticsData.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRDiagnosticsData.h; path = FirebaseCore/Sources/Private/FIRDiagnosticsData.h; sourceTree = ""; }; + D044378201B375C7DF9D01AD857B77AD /* table.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = table.cc; path = table/table.cc; sourceTree = ""; }; + D060DA57254EB3D36CE5BAE298D8ED96 /* FIRAuthDataResult.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthDataResult.h; path = Firebase/Auth/Source/Public/FIRAuthDataResult.h; sourceTree = ""; }; + D06B3B97E4AAD489DE7ACD5A5CB33145 /* FIRInstanceIDCheckinPreferences.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstanceIDCheckinPreferences.h; path = Firebase/InstanceID/Private/FIRInstanceIDCheckinPreferences.h; sourceTree = ""; }; + D088B0E1F77C694FC23D3A21D5BA93E9 /* FIRGameCenterAuthCredential.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRGameCenterAuthCredential.m; path = Firebase/Auth/Source/AuthProvider/GameCenter/FIRGameCenterAuthCredential.m; sourceTree = ""; }; + D0EEAEE838DEDF959DD3927587F000F9 /* FIRCLSUUID.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSUUID.h; path = Crashlytics/Shared/FIRCLSUUID.h; sourceTree = ""; }; + D0F7E8BBE321F64C9CCE4D3BF4DBC66F /* RLMObjectStore.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMObjectStore.h; path = include/RLMObjectStore.h; sourceTree = ""; }; + D16E167B93D721F81CD63CB4768D0DD3 /* FIRCLSDataCollectionArbiter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSDataCollectionArbiter.m; path = Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionArbiter.m; sourceTree = ""; }; + D18F97ACB446742A827DE3D7F31A9761 /* GULAppEnvironmentUtil.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULAppEnvironmentUtil.m; path = GoogleUtilities/Environment/third_party/GULAppEnvironmentUtil.m; sourceTree = ""; }; + D1F49BB693EFE972B666A13C45CD1B9D /* FIRTwitterAuthCredential.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRTwitterAuthCredential.h; path = Firebase/Auth/Source/AuthProvider/Twitter/FIRTwitterAuthCredential.h; sourceTree = ""; }; + D1FDE17F9BA2AE72C1C1EFE70DC080EA /* ImageDownloader.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageDownloader.swift; path = Sources/Networking/ImageDownloader.swift; sourceTree = ""; }; + D27BE1A976504CC23970F20939A322FB /* ImageProcessor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageProcessor.swift; path = Sources/Image/ImageProcessor.swift; sourceTree = ""; }; + D2B0710A146E5C9037FCA9FC8BC68189 /* FIROptions.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIROptions.m; path = FirebaseCore/Sources/FIROptions.m; sourceTree = ""; }; + D2C9C3DA33A45CC253454B3C3D103093 /* FIRInstanceIDCombinedHandler.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstanceIDCombinedHandler.m; path = Firebase/InstanceID/FIRInstanceIDCombinedHandler.m; sourceTree = ""; }; + D2EE16DD66F48B7F88F4E9660B90099D /* log_writer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = log_writer.h; path = db/log_writer.h; sourceTree = ""; }; + D31A28FF5B008109A4F47D9CE15E58B2 /* RLMRealmConfiguration_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMRealmConfiguration_Private.h; path = include/RLMRealmConfiguration_Private.h; sourceTree = ""; }; + D39DEBFBB8F078D08F1BE9B07342568E /* FIRVerifyPhoneNumberRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRVerifyPhoneNumberRequest.h; path = Firebase/Auth/Source/Backend/RPC/FIRVerifyPhoneNumberRequest.h; sourceTree = ""; }; + D3A92C87E49467B77970C7C21A6EF7D9 /* FIRInstanceID.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstanceID.m; path = Firebase/InstanceID/FIRInstanceID.m; sourceTree = ""; }; + D45AD6314AAB709350FCD1F48E30D8B4 /* FIRDatabaseQuery.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRDatabaseQuery.h; path = Firebase/Database/Public/FIRDatabaseQuery.h; sourceTree = ""; }; + D46D547E6B2FE36F4774C2E5480EBA7C /* FIRCLSOnboardingOperation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSOnboardingOperation.m; path = Crashlytics/Crashlytics/Settings/Operations/FIRCLSOnboardingOperation.m; sourceTree = ""; }; + D48039498B3E59506A01E97FC84AADA0 /* table_builder.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = table_builder.cc; path = table/table_builder.cc; sourceTree = ""; }; + D4BD915552AA4F71792B9C08A29C66C7 /* FClock.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FClock.h; path = Firebase/Database/FClock.h; sourceTree = ""; }; + D4E14494BF96294FC9B9D9A09C188386 /* FIRCLSCodeMapping.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSCodeMapping.h; path = Crashlytics/Shared/FIRCLSMachO/FIRCLSCodeMapping.h; sourceTree = ""; }; + D507ADEDF13DE43ACCC74E7E20D6EB12 /* GDTCORReachability.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORReachability.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORReachability.m; sourceTree = ""; }; + D50A2867A6F3524C5FE7F132004E6F3F /* options.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = options.h; path = include/leveldb/options.h; sourceTree = ""; }; + D518F40A869BBAB3A4155E1BB9275EF8 /* FEvent.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FEvent.h; path = Firebase/Database/Core/View/FEvent.h; sourceTree = ""; }; + D56281FE2EA16A3BA938732F37A34424 /* filter_policy.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = filter_policy.h; path = include/leveldb/filter_policy.h; sourceTree = ""; }; + D6153BA7DF29AA46B25DAD360E51B86B /* GTMSessionFetcher.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GTMSessionFetcher.xcconfig; sourceTree = ""; }; + D62356771945F7BF821F0BB694EC7623 /* FIRCLSSettings.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSSettings.h; path = Crashlytics/Crashlytics/Models/FIRCLSSettings.h; sourceTree = ""; }; + D64E47D2DC7764FF21C3B04F333D7607 /* FIRGetOOBConfirmationCodeResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRGetOOBConfirmationCodeResponse.m; path = Firebase/Auth/Source/Backend/RPC/FIRGetOOBConfirmationCodeResponse.m; sourceTree = ""; }; + D655C0E680BC5067C7934A5A0E42A882 /* RLMJSONModels.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RLMJSONModels.m; path = Realm/RLMJSONModels.m; sourceTree = ""; }; + D6924D9564695354749C2D1227F4B69A /* coding.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = coding.cc; path = util/coding.cc; sourceTree = ""; }; + D69EC7F7067D0D537E187FD578991C9B /* FIRCLSContext.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSContext.m; path = Crashlytics/Crashlytics/Components/FIRCLSContext.m; sourceTree = ""; }; + D6B1422B8EBEE11D856B66B0E96F117F /* FIRInstallationsSingleOperationPromiseCache.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsSingleOperationPromiseCache.m; path = FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsSingleOperationPromiseCache.m; sourceTree = ""; }; + D70A8461139B1C76F2ED9387DEE66DFA /* windows_logger.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = windows_logger.h; path = util/windows_logger.h; sourceTree = ""; }; + D7434D3830778A43B297F00140DEDBB3 /* ImageDataProcessor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageDataProcessor.swift; path = Sources/Networking/ImageDataProcessor.swift; sourceTree = ""; }; + D7BD54C8A226B56A4E8F990B0F498279 /* FLeafNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FLeafNode.m; path = Firebase/Database/Snapshot/FLeafNode.m; sourceTree = ""; }; + D7C27A7ADE5866F1D997C55EA66528BB /* FTupleRemovedQueriesEvents.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FTupleRemovedQueriesEvents.h; path = Firebase/Database/Utilities/Tuples/FTupleRemovedQueriesEvents.h; sourceTree = ""; }; + D8C6EFDD935E4915732937E2FCB4ECA1 /* RLMArray_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMArray_Private.h; path = include/RLMArray_Private.h; sourceTree = ""; }; + D9056436C38C221324AD2173B94F7FA3 /* FIRAuthExceptionUtils.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAuthExceptionUtils.m; path = Firebase/Auth/Source/Utilities/FIRAuthExceptionUtils.m; sourceTree = ""; }; + D9351244ADF4359E07B688F626D2B5AE /* FIRGetProjectConfigResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRGetProjectConfigResponse.h; path = Firebase/Auth/Source/Backend/RPC/FIRGetProjectConfigResponse.h; sourceTree = ""; }; + D94D8E190DDC32833CD32F9665486387 /* FIRCLSDataParsing.c */ = {isa = PBXFileReference; includeInIndex = 1; name = FIRCLSDataParsing.c; path = Crashlytics/Crashlytics/Unwind/Dwarf/FIRCLSDataParsing.c; sourceTree = ""; }; + D9BBAF84118A47ED29561F403710BAD4 /* FIRMutableData_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRMutableData_Private.h; path = Firebase/Database/Api/Private/FIRMutableData_Private.h; sourceTree = ""; }; + DA0F982B217F5CD21D3B5D280E93EDBF /* Result.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Result.swift; path = Sources/Utility/Result.swift; sourceTree = ""; }; + DA284A344F51526CCFFC122A20891948 /* FIRNoopAuthTokenProvider.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRNoopAuthTokenProvider.m; path = Firebase/Database/Login/FIRNoopAuthTokenProvider.m; sourceTree = ""; }; + DA4EBA778B22B26ECA6257D01119579E /* FIRCLSNetworkClient.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSNetworkClient.m; path = Crashlytics/Crashlytics/Controllers/FIRCLSNetworkClient.m; sourceTree = ""; }; + DA7348C611F9C4656B90C46695F7806B /* nanopb-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "nanopb-Info.plist"; sourceTree = ""; }; + DA888CEEA972A2D0195C006DEAFB5D7A /* FIRCreateAuthURIResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCreateAuthURIResponse.h; path = Firebase/Auth/Source/Backend/RPC/FIRCreateAuthURIResponse.h; sourceTree = ""; }; + DABD5D4024C849A5EE75514C6854A95B /* ImageFormat.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageFormat.swift; path = Sources/Image/ImageFormat.swift; sourceTree = ""; }; + DABF9587A613FBB9B7C37F170B33790F /* GDTCOREventDataObject.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCOREventDataObject.h; path = GoogleDataTransport/GDTCORLibrary/Public/GDTCOREventDataObject.h; sourceTree = ""; }; + DAF1673F2959F3DA04D64CD47E7140F2 /* FIRCLSURLSessionDataTask.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSURLSessionDataTask.m; path = Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionDataTask.m; sourceTree = ""; }; + DAF1EF806E8081A6E6FC9DFDF24CB30D /* FLLRBValueNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FLLRBValueNode.m; path = Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FLLRBValueNode.m; sourceTree = ""; }; + DB143F302A037FF79A4C3DF6FE9C3839 /* FBLPromise+Do.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Do.m"; path = "Sources/FBLPromises/FBLPromise+Do.m"; sourceTree = ""; }; + DB20C35765E54E044401445CD25ADB73 /* RLMObject_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMObject_Private.h; path = include/RLMObject_Private.h; sourceTree = ""; }; + DB56C958965C0D1A15235E6596186AA8 /* FIRInstanceIDAuthService.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstanceIDAuthService.m; path = Firebase/InstanceID/FIRInstanceIDAuthService.m; sourceTree = ""; }; + DB704E196F2277D9E0EABED098A080D5 /* Kingfisher-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Kingfisher-Info.plist"; sourceTree = ""; }; + DB74B9A6B10A8AA948A01B99D9D9CDFF /* FirebaseInstallations.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FirebaseInstallations.xcconfig; sourceTree = ""; }; + DBFCE01EA61613CE83B1513D2DB54FCA /* FPriorityIndex.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FPriorityIndex.m; path = Firebase/Database/FPriorityIndex.m; sourceTree = ""; }; + DBFD6E4F516B23B77B63EE29F3B443C1 /* FirebaseCrashlytics-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "FirebaseCrashlytics-Info.plist"; sourceTree = ""; }; + DBFDE7C1CA6BDD86C3C60C3DF7D84759 /* FIRCLSInternalLogging.c */ = {isa = PBXFileReference; includeInIndex = 1; name = FIRCLSInternalLogging.c; path = Crashlytics/Crashlytics/Helpers/FIRCLSInternalLogging.c; sourceTree = ""; }; + DC07A8DDEDC9E2EED7ADB58A9B11CAB7 /* FIRCLSProfiling.c */ = {isa = PBXFileReference; includeInIndex = 1; name = FIRCLSProfiling.c; path = Crashlytics/Crashlytics/Helpers/FIRCLSProfiling.c; sourceTree = ""; }; + DC6628ED8CAEEDBD7E69EE5FBDF8A882 /* FIRUserInfoImpl.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRUserInfoImpl.m; path = Firebase/Auth/Source/User/FIRUserInfoImpl.m; sourceTree = ""; }; + DC7FFB1CAD9385389EC1B5E7A719010B /* FIRInstanceIDCombinedHandler.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstanceIDCombinedHandler.h; path = Firebase/InstanceID/FIRInstanceIDCombinedHandler.h; sourceTree = ""; }; + DC9CB71FE83B367C573761ED0E254D12 /* FTrackedQueryManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FTrackedQueryManager.h; path = Firebase/Database/Persistence/FTrackedQueryManager.h; sourceTree = ""; }; + DD0946B579009574C042E72621AC1BF5 /* FValidation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FValidation.h; path = Firebase/Database/Utilities/FValidation.h; sourceTree = ""; }; + DD1AA8BF76E48EA74EDCCB561AE71A0A /* FIRInstanceIDTokenStore.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstanceIDTokenStore.h; path = Firebase/InstanceID/FIRInstanceIDTokenStore.h; sourceTree = ""; }; + DD457201EEAF6D1D95DCA33AD2EDBF9F /* write_batch.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = write_batch.cc; path = db/write_batch.cc; sourceTree = ""; }; + DD4B31E84CB4D8ACAB16B52B1A55D2F6 /* FIRAuthUIDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthUIDelegate.h; path = Firebase/Auth/Source/Public/FIRAuthUIDelegate.h; sourceTree = ""; }; + DDA6A6FE1B2441688DA0C4A2AF45CAA2 /* FIRAuthRPCRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthRPCRequest.h; path = Firebase/Auth/Source/Backend/FIRAuthRPCRequest.h; sourceTree = ""; }; + DE34E08AD80E531D47F7FF62F05C9DBA /* FIRGameCenterAuthProvider.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRGameCenterAuthProvider.m; path = Firebase/Auth/Source/AuthProvider/GameCenter/FIRGameCenterAuthProvider.m; sourceTree = ""; }; + DE86873942CD0E37980CB898FD57838D /* GULReachabilityChecker+Internal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "GULReachabilityChecker+Internal.h"; path = "GoogleUtilities/Reachability/GULReachabilityChecker+Internal.h"; sourceTree = ""; }; + DEB0EF341F77551CC9EE09C1A7E3863D /* FIRInstanceIDStore.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstanceIDStore.m; path = Firebase/InstanceID/FIRInstanceIDStore.m; sourceTree = ""; }; + DF17CAE04A5A105011249086FB2ECCCA /* NSError+RLMSync.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSError+RLMSync.h"; path = "include/NSError+RLMSync.h"; sourceTree = ""; }; + DF5999D73CC6280F3CC4FCA82134CA7A /* pb_common.c */ = {isa = PBXFileReference; includeInIndex = 1; path = pb_common.c; sourceTree = ""; }; + DF82CE7658F608F7684A4348FE783371 /* FLevelDBStorageEngine.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FLevelDBStorageEngine.h; path = Firebase/Database/Persistence/FLevelDBStorageEngine.h; sourceTree = ""; }; + DF8DDDA82781E362677B1A61776BCD7E /* object_notifier.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = object_notifier.cpp; path = Realm/ObjectStore/src/impl/object_notifier.cpp; sourceTree = ""; }; + DF9A521F94DF28771A2F643845C79C17 /* FImmutableSortedSet.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FImmutableSortedSet.h; path = Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FImmutableSortedSet.h; sourceTree = ""; }; + DFA4132E43065C4BC9B519AA54A5F287 /* FIRAuthErrorUtils.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAuthErrorUtils.m; path = Firebase/Auth/Source/Utilities/FIRAuthErrorUtils.m; sourceTree = ""; }; + DFD7E84C43890AE1CDFF3965BD2EA669 /* GDTCORPlatform.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORPlatform.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m; sourceTree = ""; }; + DFDF0BE506231221E57B8D863D8C9820 /* FIRInteropParameterNames.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInteropParameterNames.h; path = Interop/Analytics/Public/FIRInteropParameterNames.h; sourceTree = ""; }; + DFE1824C7288A210B76120880BFD42B5 /* FIRCLSAllocate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSAllocate.h; path = Crashlytics/Crashlytics/Helpers/FIRCLSAllocate.h; sourceTree = ""; }; + E0361FDBB1C8218671C579D62D9C5EF1 /* FIRAuthGlobalWorkQueue.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAuthGlobalWorkQueue.m; path = Firebase/Auth/Source/Auth/FIRAuthGlobalWorkQueue.m; sourceTree = ""; }; + E05AE6A00C585CABBD21A96927641401 /* Pods_GeekbrainsUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_GeekbrainsUI.framework; path = "Pods-GeekbrainsUI.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; + E05BD9106CFD3B0749792B5578D1D3B9 /* RLMRealm.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMRealm.h; path = include/RLMRealm.h; sourceTree = ""; }; + E08E083442F40E8D8E3D2C7EAA72A775 /* FIRCLSApplication.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSApplication.m; path = Crashlytics/Crashlytics/Components/FIRCLSApplication.m; sourceTree = ""; }; + E0BFE3FA489687282F85C22151BD1515 /* results.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = results.cpp; path = Realm/ObjectStore/src/results.cpp; sourceTree = ""; }; + E151626A34AF77210FB5BFC4C7D8567D /* FListenProvider.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FListenProvider.h; path = Firebase/Database/Core/FListenProvider.h; sourceTree = ""; }; + E1596E076CB1F83CFD19CA48605055E3 /* builder.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = builder.cc; path = db/builder.cc; sourceTree = ""; }; + E181357E02598A486F35973C9C68CC76 /* Alamofire.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Alamofire.xcconfig; sourceTree = ""; }; + E1926F498A9AA6FE52584068A889ABF3 /* FView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FView.h; path = Firebase/Database/Core/View/FView.h; sourceTree = ""; }; + E22ABAF13C46FCAA209FA07662CABF93 /* FIRCLSReport_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSReport_Private.h; path = Crashlytics/Crashlytics/Models/FIRCLSReport_Private.h; sourceTree = ""; }; + E268FACC2A09D695FA542D9C9D6B902C /* FIRCLSSymbolResolver.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSSymbolResolver.m; path = Crashlytics/Crashlytics/Models/FIRCLSSymbolResolver.m; sourceTree = ""; }; + E2B1E88FB83B19CC7E6207BDD91BCE75 /* FIRInstallationsStore.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsStore.m; path = FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStore.m; sourceTree = ""; }; + E2B63D462DB7F827C4B11FD51E4F8E2D /* FirebaseCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = FirebaseCore.framework; path = FirebaseCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E3E60FB6268D822CE2681F2A2F1CF1CB /* FIRCLSPackageReportOperation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSPackageReportOperation.m; path = Crashlytics/Crashlytics/Operations/Reports/FIRCLSPackageReportOperation.m; sourceTree = ""; }; + E4443BE035B78D51F2F8EE85A7C2B16D /* FIRErrors.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRErrors.h; path = FirebaseCore/Sources/Private/FIRErrors.h; sourceTree = ""; }; + E46F7D2E9ABF567418D928FA0D4CF0B2 /* GoogleUtilities-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "GoogleUtilities-prefix.pch"; sourceTree = ""; }; + E47BAF825718E41F6EEECFADCA8414AD /* Migration.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Migration.swift; path = RealmSwift/Migration.swift; sourceTree = ""; }; + E4D91F6E2EBB8D83AD58A27589C0C21F /* GDTCCTPrioritizer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCCTPrioritizer.m; path = GoogleDataTransportCCTSupport/GDTCCTLibrary/GDTCCTPrioritizer.m; sourceTree = ""; }; + E501EC074F41F40181C1B9420DF549C9 /* FIRCLSReportUploader.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSReportUploader.h; path = Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader.h; sourceTree = ""; }; + E507F9B4360841CA0E8C7A0F2039A90C /* FKeyIndex.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FKeyIndex.m; path = Firebase/Database/FKeyIndex.m; sourceTree = ""; }; + E53B2F3C7FE2FEDA489481B2B799798B /* FWriteTree.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FWriteTree.h; path = Firebase/Database/Core/FWriteTree.h; sourceTree = ""; }; + E5DE47C2C76346BB6B746EC071ADB3CA /* FIRDatabase.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRDatabase.m; path = Firebase/Database/Api/FIRDatabase.m; sourceTree = ""; }; + E657566D8C5E7C37652B9A892838BEF7 /* FIRAuth.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuth.h; path = Firebase/Auth/Source/Public/FIRAuth.h; sourceTree = ""; }; + E6DD941D98E1FE39ECB7E30424EB0AF6 /* FIRCLSCompoundOperation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSCompoundOperation.m; path = Crashlytics/Shared/FIRCLSOperation/FIRCLSCompoundOperation.m; sourceTree = ""; }; + E6E362D77314A21F10D9232913CED419 /* ImageModifier.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageModifier.swift; path = Sources/Networking/ImageModifier.swift; sourceTree = ""; }; + E7285138962EBA09D3BBBB0A162D6047 /* RLMSyncUtil_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RLMSyncUtil_Private.h; path = include/RLMSyncUtil_Private.h; sourceTree = ""; }; + E74E8ABB69B7B8765F18F4A590BF5085 /* FIRInstallationsHTTPError.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsHTTPError.m; path = FirebaseInstallations/Source/Library/Errors/FIRInstallationsHTTPError.m; sourceTree = ""; }; + E7DEDF40806419E0CEA62BE4111E28C4 /* FirebaseCore.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FirebaseCore.xcconfig; sourceTree = ""; }; + E847EEF4096BD3BCF215C1386938844E /* FTupleNodePath.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FTupleNodePath.m; path = Firebase/Database/Utilities/Tuples/FTupleNodePath.m; sourceTree = ""; }; + E8857EFCE5796CF7471091CA1188C488 /* FIRInstallationsIIDTokenStore.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsIIDTokenStore.m; path = FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDTokenStore.m; sourceTree = ""; }; + E8AE1089DED6CEA4D8ADF84B83D458B4 /* FRangeMerge.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FRangeMerge.h; path = Firebase/Database/Core/FRangeMerge.h; sourceTree = ""; }; + E9A7DEA3E358EF81F1F1348372BEB733 /* FIRCLSProcess.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSProcess.h; path = Crashlytics/Crashlytics/Components/FIRCLSProcess.h; sourceTree = ""; }; + E9EA6FB992769B58776BCBD623683FC3 /* thread_safe_reference.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = thread_safe_reference.cpp; path = Realm/ObjectStore/src/thread_safe_reference.cpp; sourceTree = ""; }; + E9FAC3EC9C794C3593C728CF3E4EDAC1 /* FIRAuthSettings.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthSettings.h; path = Firebase/Auth/Source/Public/FIRAuthSettings.h; sourceTree = ""; }; + EA10D39A174D32B831D388D410967DBA /* FWriteTreeRef.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FWriteTreeRef.m; path = Firebase/Database/Core/FWriteTreeRef.m; sourceTree = ""; }; + EA285255E24140F4364ADC9C02ADAAE5 /* FBLPromise+Catch.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Catch.h"; path = "Sources/FBLPromises/include/FBLPromise+Catch.h"; sourceTree = ""; }; + EA4701731BC4DA180E5A715A5505C01E /* FEventEmitter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FEventEmitter.m; path = Firebase/Database/Utilities/FEventEmitter.m; sourceTree = ""; }; + EA55512BFD1ED5E8550C4D99040DC2D6 /* FIROptions.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIROptions.h; path = FirebaseCore/Sources/Public/FIROptions.h; sourceTree = ""; }; + EA7F566FC329D8F1AEBA2463FA2B876F /* block_builder.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = block_builder.h; path = table/block_builder.h; sourceTree = ""; }; + EAB1F081D87EA12C99FE8E742DCCA8E9 /* FIRDatabaseConfig.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRDatabaseConfig.h; path = Firebase/Database/Api/FIRDatabaseConfig.h; sourceTree = ""; }; + EACA8C55A037208CD7F7723FD488F2CD /* GIFAnimatedImage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = GIFAnimatedImage.swift; path = Sources/Image/GIFAnimatedImage.swift; sourceTree = ""; }; + EAF035ADBF22E759B690C1A46370000B /* FMerge.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FMerge.h; path = Firebase/Database/Core/Operation/FMerge.h; sourceTree = ""; }; + EAF94A59F9E00336CEF40DFC2A0A2F5E /* FTupleTSN.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FTupleTSN.m; path = Firebase/Database/Utilities/Tuples/FTupleTSN.m; sourceTree = ""; }; + EB5B000A9E1BCA491B41490FC070ACD2 /* two_level_iterator.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = two_level_iterator.cc; path = table/two_level_iterator.cc; sourceTree = ""; }; + EB8C58733A2EFA5B582FEAC12CC9781C /* FirebaseAnalytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FirebaseAnalytics.framework; path = Frameworks/FirebaseAnalytics.framework; sourceTree = ""; }; + EB8D442EDD47F4C18B9E23F08BC4F999 /* FTupleUserCallback.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FTupleUserCallback.m; path = Firebase/Database/Utilities/Tuples/FTupleUserCallback.m; sourceTree = ""; }; + EB90112817267C3AF191E87188202873 /* FIRInstallationsErrorUtil.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsErrorUtil.h; path = FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.h; sourceTree = ""; }; + EB9E162ED5D86B7F2EB30AE42FAEE0BB /* FIRCLSSerializeSymbolicatedFramesOperation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSSerializeSymbolicatedFramesOperation.m; path = Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSSerializeSymbolicatedFramesOperation.m; sourceTree = ""; }; + EBA1C61A579712645308088DD63C8302 /* GoogleAppMeasurement.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GoogleAppMeasurement.framework; path = Frameworks/GoogleAppMeasurement.framework; sourceTree = ""; }; + EC173B1FCB6DCDA143020621B8183EC3 /* FirebaseAuthInterop.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FirebaseAuthInterop.xcconfig; sourceTree = ""; }; + EC3131B267B53BBFA83377DC278EFCE6 /* FIRInstallationsStoredAuthToken.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsStoredAuthToken.m; path = FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.m; sourceTree = ""; }; + EC3317430C617069EE52653414A6E8F4 /* sync_manager.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = sync_manager.cpp; path = Realm/ObjectStore/src/sync/sync_manager.cpp; sourceTree = ""; }; + EC48BC4E52678E214C5B571535F0FD8E /* GTMSessionFetcher.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMSessionFetcher.m; path = Source/GTMSessionFetcher.m; sourceTree = ""; }; + EC876B68418410B69BAB194A19D284BA /* c.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = c.cc; path = db/c.cc; sourceTree = ""; }; + ECA7865B913CA6A76D0C52F4AD411F8D /* FTreeSortedDictionary.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FTreeSortedDictionary.h; path = Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FTreeSortedDictionary.h; sourceTree = ""; }; + ECA7E4BA5A6CDB154033CC0AF23ACF68 /* FIRPhoneAuthCredential.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRPhoneAuthCredential.h; path = Firebase/Auth/Source/Public/FIRPhoneAuthCredential.h; sourceTree = ""; }; + ECCB91EDC5AA7BAC3EDD3F05AF82568A /* GDTCORStoredEvent.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORStoredEvent.h; path = GoogleDataTransport/GDTCORLibrary/Public/GDTCORStoredEvent.h; sourceTree = ""; }; + ED308881D7E1D2F55E74AAF0E548A9C2 /* FTree.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FTree.h; path = Firebase/Database/Core/Utilities/FTree.h; sourceTree = ""; }; + ED3AA8DB0FFB3C66316742F2C335F0A0 /* GTMSessionFetcherLogging.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMSessionFetcherLogging.h; path = Source/GTMSessionFetcherLogging.h; sourceTree = ""; }; + ED93F5CEB5984D2C7820803FB14E33B9 /* FIRInstallationsKeychainUtils.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsKeychainUtils.h; path = FirebaseInstallations/Source/Library/SecureStorage/FIRInstallationsKeychainUtils.h; sourceTree = ""; }; + EDC084CC75C40792DFD2D7287EFD6467 /* FIRAuthDataResult_Internal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthDataResult_Internal.h; path = Firebase/Auth/Source/Auth/FIRAuthDataResult_Internal.h; sourceTree = ""; }; + EDF84E0100FAE3C3031F23D19DC20C35 /* FIRCLSURLSessionDownloadTask.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSURLSessionDownloadTask.h; path = Crashlytics/Crashlytics/FIRCLSURLSession/Tasks/FIRCLSURLSessionDownloadTask.h; sourceTree = ""; }; + EE508C10A062219D8B859F9DD9C6C6DC /* FirebaseInstanceID-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "FirebaseInstanceID-dummy.m"; sourceTree = ""; }; + EE76699AE0009E7286DB0C500CDEA36B /* FIRInstanceIDTokenDeleteOperation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstanceIDTokenDeleteOperation.m; path = Firebase/InstanceID/FIRInstanceIDTokenDeleteOperation.m; sourceTree = ""; }; + EED5147D9610B2B66EC2AAD7285E96B5 /* FIRCLSSerializeSymbolicatedFramesOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSSerializeSymbolicatedFramesOperation.h; path = Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSSerializeSymbolicatedFramesOperation.h; sourceTree = ""; }; + EEF4CC836E0FC4BCEB6ACE36C4028389 /* FIRCLSReportUploader_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSReportUploader_Private.h; path = Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader_Private.h; sourceTree = ""; }; + EEFE02DEFA8B914696B221BDB1E55875 /* list.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = list.cpp; path = Realm/ObjectStore/src/list.cpp; sourceTree = ""; }; + EF14E699204FD227CC2454D8DC1680A4 /* RLMListBase.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMListBase.mm; path = Realm/RLMListBase.mm; sourceTree = ""; }; + EF5376E76A1797CA02135B3CB20439F9 /* FIRInstanceIDCheckinStore.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstanceIDCheckinStore.h; path = Firebase/InstanceID/FIRInstanceIDCheckinStore.h; sourceTree = ""; }; + EF5C63E6374A65708B5F98DE5057F562 /* FParsedUrl.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FParsedUrl.m; path = Firebase/Database/Utilities/FParsedUrl.m; sourceTree = ""; }; + EF852E414686D1C085AA9A455576B845 /* FIRCLSFABAsyncOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSFABAsyncOperation.h; path = Crashlytics/Shared/FIRCLSOperation/FIRCLSFABAsyncOperation.h; sourceTree = ""; }; + F03BCB4DD187BEE1AB4313919D286422 /* FIRMutableData.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRMutableData.m; path = Firebase/Database/Api/FIRMutableData.m; sourceTree = ""; }; + F07D4A9232C809E948120E7102776882 /* FIRInstanceIDCheckinPreferences.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstanceIDCheckinPreferences.m; path = Firebase/InstanceID/FIRInstanceIDCheckinPreferences.m; sourceTree = ""; }; + F0B703AD9011F08B3C8BC98CD60D5E12 /* FirebaseInstallations.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FirebaseInstallations.h; path = FirebaseInstallations/Source/Library/Public/FirebaseInstallations.h; sourceTree = ""; }; + F1A72EEFE9C788BE1B755C160E95C8F2 /* FBLPromise+Always.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Always.h"; path = "Sources/FBLPromises/include/FBLPromise+Always.h"; sourceTree = ""; }; + F1DB0044569C7F340371466F6853621C /* FSyncPoint.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FSyncPoint.h; path = Firebase/Database/Core/FSyncPoint.h; sourceTree = ""; }; + F2941D69BA8BB7F1732F3A5E6C6087B6 /* Alamofire.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = Alamofire.modulemap; sourceTree = ""; }; + F30DA53A86EA3F366531DA93930A18D1 /* format.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = format.cc; path = table/format.cc; sourceTree = ""; }; + F32515F6A7AF5C93942F5CC9477AEF66 /* FirebaseAuthVersion.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FirebaseAuthVersion.m; path = Firebase/Auth/Source/FirebaseAuthVersion.m; sourceTree = ""; }; + F34228DF413EBF2A6E96A822D548A2E7 /* FIRCoreDiagnosticsData.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCoreDiagnosticsData.h; path = Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsData.h; sourceTree = ""; }; + F34CD699384BE03207942625DA31B7D7 /* FUtilities.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FUtilities.h; path = Firebase/Database/Utilities/FUtilities.h; sourceTree = ""; }; + F363C40B536805228088BCBFE0735F66 /* FIRVerifyCustomTokenResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRVerifyCustomTokenResponse.h; path = Firebase/Auth/Source/Backend/RPC/FIRVerifyCustomTokenResponse.h; sourceTree = ""; }; + F3BD52E227C6798238630C8878860D83 /* FPruneForest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FPruneForest.h; path = Firebase/Database/Persistence/FPruneForest.h; sourceTree = ""; }; + F40ABB1387B024B0A59D03206A41F30D /* GDTCCTCompressionHelper.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCCTCompressionHelper.h; path = GoogleDataTransportCCTSupport/GDTCCTLibrary/Private/GDTCCTCompressionHelper.h; sourceTree = ""; }; + F42E571CAB78A61F579A7D8196D8DE05 /* FIRAuthBackend.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthBackend.h; path = Firebase/Auth/Source/Backend/FIRAuthBackend.h; sourceTree = ""; }; + F4469C2241975FA8DD82B8AB70834A77 /* Schema.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Schema.swift; path = RealmSwift/Schema.swift; sourceTree = ""; }; + F46A4EDD00D706566C8479D0068E3FAC /* FirebaseDatabase.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FirebaseDatabase.h; path = Firebase/Database/Public/FirebaseDatabase.h; sourceTree = ""; }; + F4C35D3B830DD9704ACF0BC841B6A465 /* FIRCoreDiagnosticsInterop.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCoreDiagnosticsInterop.h; path = Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsInterop.h; sourceTree = ""; }; + F4EE656688A0BDEB67D52D6B4C0F0150 /* FIRFacebookAuthCredential.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRFacebookAuthCredential.h; path = Firebase/Auth/Source/AuthProvider/Facebook/FIRFacebookAuthCredential.h; sourceTree = ""; }; + F50E4E22F95A26E70C681B16D0148E77 /* FIRCLSUserDefaults.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSUserDefaults.m; path = Crashlytics/Crashlytics/FIRCLSUserDefaults/FIRCLSUserDefaults.m; sourceTree = ""; }; + F55DF28B45C0DC7EECDF3F579442FD58 /* merger.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = merger.cc; path = table/merger.cc; sourceTree = ""; }; + F5C03C055BA2A1C83052C9BC2AF326E2 /* FPendingPut.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FPendingPut.m; path = Firebase/Database/Persistence/FPendingPut.m; sourceTree = ""; }; + F5C89F11ADEAD50F827C26186992AEA2 /* FIRCLSProcess.c */ = {isa = PBXFileReference; includeInIndex = 1; name = FIRCLSProcess.c; path = Crashlytics/Crashlytics/Components/FIRCLSProcess.c; sourceTree = ""; }; + F6091A1DCE0E3278DD93874739E79047 /* Optional.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Optional.swift; path = RealmSwift/Optional.swift; sourceTree = ""; }; + F61594A2D9EB829D8F70CE39F7EA8F1B /* FIRSetAccountInfoRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRSetAccountInfoRequest.h; path = Firebase/Auth/Source/Backend/RPC/FIRSetAccountInfoRequest.h; sourceTree = ""; }; + F62BE4EC547C3E79D8346DF6F8F50669 /* FPersistenceManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FPersistenceManager.h; path = Firebase/Database/Persistence/FPersistenceManager.h; sourceTree = ""; }; + F66BF470805C0E607FE8D9913E9213D7 /* FIRAuthWebView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthWebView.h; path = Firebase/Auth/Source/Utilities/FIRAuthWebView.h; sourceTree = ""; }; + F695AC3ADBB98EC47D8E223652761F16 /* sync_user.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = sync_user.cpp; path = Realm/ObjectStore/src/sync/sync_user.cpp; sourceTree = ""; }; + F6AED0E2005671F584BB2AEDF2B27BCF /* Kingfisher.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = Kingfisher.h; path = Sources/Kingfisher.h; sourceTree = ""; }; + F6E7A6CA87859BB2D0DF76AF27B092B8 /* Pods-GeekbrainsUI.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-GeekbrainsUI.modulemap"; sourceTree = ""; }; + F73338EC087F60071670E31C61FF3E46 /* GULSwizzler.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULSwizzler.m; path = GoogleUtilities/MethodSwizzler/GULSwizzler.m; sourceTree = ""; }; + F7994B149D97C10B5E161C5B776F54EE /* object_schema.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = object_schema.cpp; path = Realm/ObjectStore/src/object_schema.cpp; sourceTree = ""; }; + F7D12F7E036176E8378124C2C53E714B /* GTMSessionFetcher-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "GTMSessionFetcher-prefix.pch"; sourceTree = ""; }; + F7E041DEA67993EB831E297B08E34740 /* FIRDatabaseReference.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRDatabaseReference.h; path = Firebase/Database/Public/FIRDatabaseReference.h; sourceTree = ""; }; + F7FB56B436AA1F5E67E74AAD1646C026 /* GULNetwork.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULNetwork.h; path = GoogleUtilities/Network/Private/GULNetwork.h; sourceTree = ""; }; + F87CA2091793D8973D13571079E16DCA /* realm_coordinator.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = realm_coordinator.cpp; path = Realm/ObjectStore/src/impl/realm_coordinator.cpp; sourceTree = ""; }; + F895732C10786779FB3B5D978024B17B /* FIRCLSURLSession.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSURLSession.h; path = Crashlytics/Crashlytics/FIRCLSURLSession/FIRCLSURLSession.h; sourceTree = ""; }; + F8CEA8779408A7E78CCCCBDD04414385 /* FBLPromise+Race.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Race.m"; path = "Sources/FBLPromises/FBLPromise+Race.m"; sourceTree = ""; }; + F8ED8EDB4332628051191DB6D91A761D /* FIRCLSUnwind_arm.c */ = {isa = PBXFileReference; includeInIndex = 1; name = FIRCLSUnwind_arm.c; path = Crashlytics/Crashlytics/Unwind/FIRCLSUnwind_arm.c; sourceTree = ""; }; + F8F4FD2169C43EF716DF13A6017EC76E /* FIRCLSAsyncOperation_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSAsyncOperation_Private.h; path = Crashlytics/Crashlytics/Operations/FIRCLSAsyncOperation_Private.h; sourceTree = ""; }; + F90DF5F8EEF6B3A7BF300E04E72545DD /* FIRCLSNetworkClient.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSNetworkClient.h; path = Crashlytics/Crashlytics/Controllers/FIRCLSNetworkClient.h; sourceTree = ""; }; + F970D1755FF2BF74A11AA024C79EA2E0 /* Aliases.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Aliases.swift; path = RealmSwift/Aliases.swift; sourceTree = ""; }; + F984ED7661040651F50DFED42E8FD41F /* FIRActionCodeSettings.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRActionCodeSettings.h; path = Firebase/Auth/Source/Public/FIRActionCodeSettings.h; sourceTree = ""; }; + F99C1F5CF233E9ACD488CF17BF244A10 /* FIRCLSURLSessionConfiguration.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSURLSessionConfiguration.m; path = Crashlytics/Crashlytics/FIRCLSURLSession/FIRCLSURLSessionConfiguration.m; sourceTree = ""; }; + FA4874F0784D0B918BB0F130BDE7FDBE /* FIRInstallationsIIDTokenStore.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsIIDTokenStore.h; path = FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDTokenStore.h; sourceTree = ""; }; + FA930458D628F0B45262C06E2F5FF5EE /* FIRVerifyClientResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRVerifyClientResponse.h; path = Firebase/Auth/Source/Backend/RPC/FIRVerifyClientResponse.h; sourceTree = ""; }; + FAB3710980A18B4F81EB86053E0394C5 /* FIRAuthNotificationManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAuthNotificationManager.m; path = Firebase/Auth/Source/SystemService/FIRAuthNotificationManager.m; sourceTree = ""; }; + FABA7194CA09589599F926D3C76789BF /* FIROAuthCredential_Internal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIROAuthCredential_Internal.h; path = Firebase/Auth/Source/AuthProvider/OAuth/FIROAuthCredential_Internal.h; sourceTree = ""; }; + FAD407F4EF0402CE18C8A3FB16136CE1 /* ThreadSafeReference.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ThreadSafeReference.swift; path = RealmSwift/ThreadSafeReference.swift; sourceTree = ""; }; + FAFAA71EB419B9C1A78847DD7C78B876 /* Pods-GeekbrainsUI-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-GeekbrainsUI-umbrella.h"; sourceTree = ""; }; + FAFB9BE1C3FB89125BBF7F38D1BB238C /* FIRVerifyPhoneNumberResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRVerifyPhoneNumberResponse.h; path = Firebase/Auth/Source/Backend/RPC/FIRVerifyPhoneNumberResponse.h; sourceTree = ""; }; + FB06D20089CCA78A808F637BF749DF6F /* FIRCLSFileManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCLSFileManager.h; path = Crashlytics/Crashlytics/Models/FIRCLSFileManager.h; sourceTree = ""; }; + FB3B80F0D5095AE87661533BF8DE50F3 /* FWriteTreeRef.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FWriteTreeRef.h; path = Firebase/Database/Core/FWriteTreeRef.h; sourceTree = ""; }; + FB428CC5D255E1711E6656967E9FDF8A /* primitive_list_notifier.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = primitive_list_notifier.cpp; path = Realm/ObjectStore/src/impl/primitive_list_notifier.cpp; sourceTree = ""; }; + FB5DEF08A4DBF5EFC56AE6DEFA7825A6 /* FIRVerifyPhoneNumberResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRVerifyPhoneNumberResponse.m; path = Firebase/Auth/Source/Backend/RPC/FIRVerifyPhoneNumberResponse.m; sourceTree = ""; }; + FB69992458A89E18A6BF4FB596E6E397 /* RLMSyncManager.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = RLMSyncManager.mm; path = Realm/RLMSyncManager.mm; sourceTree = ""; }; + FB7E53AD0B30ECA07B0628DDB6BA3DE9 /* PromisesObjC-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "PromisesObjC-Info.plist"; sourceTree = ""; }; + FBF030098618CE3A844110FF41D52710 /* GULAppDelegateSwizzler.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULAppDelegateSwizzler.m; path = GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m; sourceTree = ""; }; + FC3C0FB46A22497DE200AAD3C51B3F5D /* FIRCLSBinaryImage.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCLSBinaryImage.m; path = Crashlytics/Crashlytics/Components/FIRCLSBinaryImage.m; sourceTree = ""; }; + FC47FF55CCA6DB295945AE33E25407CA /* FIRCLSAllocate.c */ = {isa = PBXFileReference; includeInIndex = 1; name = FIRCLSAllocate.c; path = Crashlytics/Crashlytics/Helpers/FIRCLSAllocate.c; sourceTree = ""; }; + FC5D41ACD07F6EC9BF42CC87E28FD338 /* GDTCCTCompressionHelper.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCCTCompressionHelper.m; path = GoogleDataTransportCCTSupport/GDTCCTLibrary/GDTCCTCompressionHelper.m; sourceTree = ""; }; + FC8F7429129DDE3FE3CD5587FC76366F /* FIRInstanceIDKeychain.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstanceIDKeychain.m; path = Firebase/InstanceID/FIRInstanceIDKeychain.m; sourceTree = ""; }; + FCA095FF7A7E8830ED5933A9B581B3A0 /* GULNetworkLoggerProtocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULNetworkLoggerProtocol.h; path = GoogleUtilities/Network/Private/GULNetworkLoggerProtocol.h; sourceTree = ""; }; + FCC777E5D845A6615BDD85C43BB2CDDE /* FIRUserMetadata.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRUserMetadata.m; path = Firebase/Auth/Source/User/FIRUserMetadata.m; sourceTree = ""; }; + FCF20827CAA2AC54FFFECCB9B75F8DA7 /* FBLPromises.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FBLPromises.h; path = Sources/FBLPromises/include/FBLPromises.h; sourceTree = ""; }; + FD66CF40520DC57E058636DDAD608536 /* FTreeNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FTreeNode.h; path = Firebase/Database/Core/Utilities/FTreeNode.h; sourceTree = ""; }; + FD7DE28F1856D146FBFABE964B3B5C47 /* FCompoundHash.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FCompoundHash.h; path = Firebase/Database/Core/FCompoundHash.h; sourceTree = ""; }; + FDAF8C09000A98E72DF3001961842E96 /* FIRAuthRPCResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAuthRPCResponse.h; path = Firebase/Auth/Source/Backend/FIRAuthRPCResponse.h; sourceTree = ""; }; + FDF2E4BDCD2F7C590FFDC8CA02E033F8 /* collection_notifications.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = collection_notifications.cpp; path = Realm/ObjectStore/src/collection_notifications.cpp; sourceTree = ""; }; + FE1E9033C4E91571AFD9184C9A387A9F /* leveldb-library-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "leveldb-library-umbrella.h"; sourceTree = ""; }; + FE4E65C4715C57BABDE76E223BEF6080 /* FIRAuthCredential.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAuthCredential.m; path = Firebase/Auth/Source/AuthProvider/FIRAuthCredential.m; sourceTree = ""; }; + FE7FF026441377784DFFF679E101E733 /* Kingfisher-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Kingfisher-prefix.pch"; sourceTree = ""; }; + FE96C47F2560AA0D7B152AAB26C9E931 /* ResponseSerialization.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ResponseSerialization.swift; path = Source/ResponseSerialization.swift; sourceTree = ""; }; + FE97F7C3CA00403626ACF85E09BE6F0C /* FClock.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FClock.m; path = Firebase/Database/FClock.m; sourceTree = ""; }; + FE9B49FD53A34A6AB6186DA2B177EED9 /* async_open_task.cpp */ = {isa = PBXFileReference; includeInIndex = 1; name = async_open_task.cpp; path = Realm/ObjectStore/src/sync/async_open_task.cpp; sourceTree = ""; }; + FEFDB553310806FE52AA299503F0117E /* FIndexedNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIndexedNode.m; path = Firebase/Database/Snapshot/FIndexedNode.m; sourceTree = ""; }; + FF2A8B581BE6C14669CB135994E53F23 /* FIRApp.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRApp.m; path = FirebaseCore/Sources/FIRApp.m; sourceTree = ""; }; + FF3EFA6069C2BF16A62F27DB2D7D7BDF /* FirebaseDatabase-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "FirebaseDatabase-umbrella.h"; sourceTree = ""; }; + FF724624F7FF1D8DCBFDB4F4778D3A64 /* FIRDependency.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRDependency.m; path = FirebaseCore/Sources/FIRDependency.m; sourceTree = ""; }; + FFD782C9E7773E607256D36F128C9C31 /* FIRRetryHelper.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRRetryHelper.h; path = Firebase/Database/Core/Utilities/FIRRetryHelper.h; sourceTree = ""; }; + FFF2198200840B5706276A535E8A8B77 /* FTupleRemovedQueriesEvents.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FTupleRemovedQueriesEvents.m; path = Firebase/Database/Utilities/Tuples/FTupleRemovedQueriesEvents.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 090C1D63463ACF622287EFF9D5C9392D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 7664FD4F59A0C00866CBAE60B0A3AD64 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0A0EC028F171B534584F7108AED6844E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 011B88631D31B307CC55C071F34B14AD /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0A9310DB6FFD83927822EA5BA8DA328E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9B326941BA608661D368F757E22804AA /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0FAAD34EDB8CC19F56256C8B0AEDE8D2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + B34EC46CAF117EEB42A946ED4597D64A /* Foundation.framework in Frameworks */, + 77F81ADB7EB8B8F4685B1D221C479B53 /* Security.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1521E8D7F31158212B2C92D4C9245A3A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 842D4F41D72EA4378FC7CA2514C6B848 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1B477DC8426D3E658A0142FA77DFE476 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6F60CAAEFE61F18132F9668EA2E508BF /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 20F01FC2A668C7364CCD6554A62A03AF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + EB806E157E83941EAF544783625C08FF /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 35816E06BF9027638E501B13C6D866FF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 868CE4BA62D5DEDEB9B0B9CCE59CE8AE /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4CC2326B5F6FEE9B19AA593985C6F5DF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 648C561ABA670900DCC70034ACB901C5 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 57E714585194E469459E9A1FDA3471BA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + EE2A5AE21E43B6A17BFFFE1695AF66FE /* Foundation.framework in Frameworks */, + 530EC7E20DB092A84F13DFAF89701619 /* Realm.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 61AF6B13CCC2E38FDA0B9BAB05581FD5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D3DB9F1DF95D7732960C4A55972C115E /* Accelerate.framework in Frameworks */, + 09759A3CD181CC07D8E69462D2E9424C /* CFNetwork.framework in Frameworks */, + 6804393EA55473086BC9AC4FE08F33F5 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 814CE1FDC9DE4A46D13F0A053EA8EC97 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8B4FC46A3DB2BD43F4046DAFA4FC8F8F /* Foundation.framework in Frameworks */, + 4702C63F91C7967ECD570C5CD8D092E3 /* Security.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8683E0DD8C10842EA9FCC664D0751B75 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F2B2B5E5B963145D27E6F7F0B97278B3 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8DEE514BD8CE3E97E8CC92DEA525A51D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1020BE6BB459DB896BCE7B16F6BB0301 /* Foundation.framework in Frameworks */, + D05EFB6867DC63834A926B4E3969B421 /* Security.framework in Frameworks */, + 873FD0306FBE41D1A631FC003615264D /* SystemConfiguration.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9C2FBF4B5898F99ECA284FAE9E2ECAEC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + CE619FA76DEE02BAB42AC11A21CD03DE /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A5C09836B521F2F7A804EF587BBDB094 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F4AC35F4E29621B3EB9C8FC5520E7BF6 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D7ECF3CC1DB7624F6B120A2E231894FD /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + BDF2F031400EB6019056A3F76C769748 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DDE9346B48CC26C3A9142F24B9020835 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F4EAFFED2B6CCC28F1350707AE648818 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DEFC5A6DE8C7777B605CEB1ED61C8064 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 034ACA0333A3E6E9A643122B066DDD22 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 00B0D4C452B8A2AB1DB31D1796C22BC3 /* Support Files */ = { + isa = PBXGroup; + children = ( + 342EE17D12E4A25729726E220C30E625 /* leveldb-library.modulemap */, + C3460D05DC30E134CA7B463814A904EE /* leveldb-library.xcconfig */, + 1EAB78CDB261A7BD311754265ADA8C06 /* leveldb-library-dummy.m */, + 5FE8768E83C4E59BEA71F5CC6DAEF45B /* leveldb-library-Info.plist */, + 67A7FD255D12F065146BDAC2B8A4E080 /* leveldb-library-prefix.pch */, + FE1E9033C4E91571AFD9184C9A387A9F /* leveldb-library-umbrella.h */, + ); + name = "Support Files"; + path = "../Target Support Files/leveldb-library"; + sourceTree = ""; + }; + 0C5DF683956864DCF16BAADC6CE074AC /* GoogleDataTransportCCTSupport */ = { + isa = PBXGroup; + children = ( + CF8433969CF929D29996C79B904999D4 /* cct.nanopb.c */, + 09B51F4D1459A875BA0930AF94EA5C0B /* cct.nanopb.h */, + F40ABB1387B024B0A59D03206A41F30D /* GDTCCTCompressionHelper.h */, + FC5D41ACD07F6EC9BF42CC87E28FD338 /* GDTCCTCompressionHelper.m */, + 8232B5CC32D2C64C6DAA70A7C7593250 /* GDTCCTNanopbHelpers.h */, + 33BF8AF65FFDF2C266CD299113C4B977 /* GDTCCTNanopbHelpers.m */, + 196645E8D0648C57FB0C6929B7E45FF3 /* GDTCCTPrioritizer.h */, + E4D91F6E2EBB8D83AD58A27589C0C21F /* GDTCCTPrioritizer.m */, + 184038DACE9B255B375E91EB2FA49ADE /* GDTCCTUploader.h */, + B53846D10D47CDE2D30C4DD425E97808 /* GDTCCTUploader.m */, + 635DDB80F0882C70D7DD08F88EFA542B /* GDTFLLPrioritizer.h */, + 4ADC6F364C16F8DBEFB7ECB280A76B7C /* GDTFLLPrioritizer.m */, + A020B761979C1B61A16487D3C2922FF5 /* GDTFLLUploader.h */, + 0FCAEFD4A55B7DC2D8CD018B48D09F1D /* GDTFLLUploader.m */, + 34D69A181591FBEA896C58DAFD2B3F50 /* Support Files */, + ); + name = GoogleDataTransportCCTSupport; + path = GoogleDataTransportCCTSupport; + sourceTree = ""; + }; + 0D9621AA8115A2711643004E81184664 /* Pods */ = { + isa = PBXGroup; + children = ( + 8669867928EF515F10AD3EF6D10FDA1D /* Alamofire */, + 26C1B19059A4F0C408F2539D85BB5500 /* Firebase */, + 61AC7AE57B526478BDF66DE67247830F /* FirebaseAnalytics */, + 8CF9017241A27FFB9D0B76BF1A4A77BD /* FirebaseAnalyticsInterop */, + FD44FA58D684D38F710E910567F3C616 /* FirebaseAuth */, + D7F986D41C22AC6421B54DFA8F10865D /* FirebaseAuthInterop */, + E1CEE1322400810C9330F0708C2CA187 /* FirebaseCore */, + F5A35BBCD0E52F68825A21889AEE8A15 /* FirebaseCoreDiagnostics */, + 9400A0F5F0D5C5EFE436637FB5730866 /* FirebaseCoreDiagnosticsInterop */, + 7CC70DDEEAD5258DAEE93D10FCCB62EE /* FirebaseCrashlytics */, + 66758874D3DAB5283F3C84301B66670E /* FirebaseDatabase */, + 8DE53F4B5146A76A81008C5A5A02266E /* FirebaseInstallations */, + 76DB5A3569D503488AB1F7EA3CF78813 /* FirebaseInstanceID */, + 2625630E833431A0078DDB2C8DFC72FC /* GoogleAppMeasurement */, + 675A3B8C67B178B7FC54670F74ED81B0 /* GoogleDataTransport */, + 0C5DF683956864DCF16BAADC6CE074AC /* GoogleDataTransportCCTSupport */, + C5FC32B18D83BA32C067409E3BB62F40 /* GoogleUtilities */, + A9277EC637D3D19485D29C21AEA5B5B1 /* GTMSessionFetcher */, + 38B1904947A4F0B6D8CC99926447FF01 /* Kingfisher */, + 0DA51B226A4BC5E527AADC87065D8F3B /* leveldb-library */, + C224DE7B7BFCEF1EEB0236BEA1CA241E /* nanopb */, + D52AE0BFC7257D68819DA54C8E24C24A /* PromisesObjC */, + 761E3C039316C71879C9D539183A8A06 /* Realm */, + 591050684A2DEB56DCA89B38D292B478 /* RealmSwift */, + ); + name = Pods; + sourceTree = ""; + }; + 0DA51B226A4BC5E527AADC87065D8F3B /* leveldb-library */ = { + isa = PBXGroup; + children = ( + 463D13D8E2BAA12948556D3B5ADD54FA /* arena.cc */, + 28EBE76BF2FAC20F0C4AF4D2B258B996 /* arena.h */, + CFD9FB653049C45B8E552C3BE27FDDAD /* block.cc */, + 77EC52A10EB37C45E98D83D7BEC7D6DD /* block.h */, + 7E1B6BA18F688452F1DB59F99647172A /* block_builder.cc */, + EA7F566FC329D8F1AEBA2463FA2B876F /* block_builder.h */, + 50DD131FEF677E8FF47C059527C19566 /* bloom.cc */, + E1596E076CB1F83CFD19CA48605055E3 /* builder.cc */, + 0FF88F56210C6841FFB4F15504D58897 /* builder.h */, + EC876B68418410B69BAB194A19D284BA /* c.cc */, + 9ACB6E37C7A61A4D3362558603C83B0C /* c.h */, + 10332F50392D7C5CB440A2718EBCAF21 /* cache.cc */, + 89EF68696031BDC544F7C211C49A190E /* cache.h */, + D6924D9564695354749C2D1227F4B69A /* coding.cc */, + 46889AB3C5F944AB30292A698C5E2ADB /* coding.h */, + 8E3ACF464DE946E7977DFFA1EEF5246A /* comparator.cc */, + 9734D99905E6B8202876C55E05FC392E /* comparator.h */, + CB0A9640A0C659E27B59C16EF296B350 /* crc32c.cc */, + 26CA7503642099111938AB974E6A6646 /* crc32c.h */, + 05DA9AF2D109C39E2B3E4D0151FDEA03 /* db.h */, + 9E9D59523D289EC7F7D99A7F21CFE2BD /* db_impl.cc */, + 810334097CD688DCF361B56C4A75E9B1 /* db_impl.h */, + BAFE031523B5978048756FE2BCED2DA4 /* db_iter.cc */, + 9023E9E655C90030C8031B6B2F2B677F /* db_iter.h */, + 4519A8667149020DB9E7BF440C7C99AE /* dbformat.cc */, + 403BD7A51021AE9EB9B6974BC33D6FC5 /* dbformat.h */, + 3B2C03F3C9EBC5D74F0DC6F8340D952F /* dumpfile.cc */, + BC7F50EAF94496196A249D25CCA491B3 /* dumpfile.h */, + 1E0181084EF4AB7551CEDA5C117A0DE3 /* env.cc */, + B9007A7BAAAE5B47A2A31441FDEDFD3E /* env.h */, + 981D3489F3D5048257A7FBB1CD338281 /* env_posix.cc */, + 506EAE82BD3E4008D6B467A84A76A492 /* env_posix_test_helper.h */, + A9D04B5851A9758EDBE1DA3F0572ED7F /* env_windows_test_helper.h */, + 8C6F145D9591E35C4B366950416B55E8 /* export.h */, + 9C0079468DE4A96EA35409206515BE79 /* filename.cc */, + A641EFE5534FE8DDEA5D903FD7216933 /* filename.h */, + 6663078E0C1CBF1E77E6BA770700875A /* filter_block.cc */, + 2ABDD21C27AB2B0A39AF2EF7DDC29B9B /* filter_block.h */, + 3C777B4EE7D85558581EF1A3FC747D8F /* filter_policy.cc */, + D56281FE2EA16A3BA938732F37A34424 /* filter_policy.h */, + F30DA53A86EA3F366531DA93930A18D1 /* format.cc */, + 91D4B4C08E11E17B827575DBF312C72D /* format.h */, + 290E99182B80CBF746F852D5E031EBE8 /* hash.cc */, + 2E738E61D112BE0639284CE1910475F6 /* hash.h */, + 06BB1B8E0794DA761F9BD7359D2036F7 /* histogram.cc */, + AB546DEDD26660C09E3E334FEC21D8CB /* histogram.h */, + 49258A7388501C116D60F7B0F6E09199 /* iterator.cc */, + CAB84098B3AFF0DFFCDFDC38B55F2DDF /* iterator.h */, + A6119970BBD3C7ABD6672B5D27F2607A /* iterator_wrapper.h */, + 975D50E6136B6E412EDD8DBEC3D92B2E /* log_format.h */, + BBD644E2ADD3C185AE9CD03FF0CD495B /* log_reader.cc */, + 486997BAA7094533C8CDDD157CFF96BB /* log_reader.h */, + 53E222268FFB208D7AE83FD0A67262EF /* log_writer.cc */, + D2EE16DD66F48B7F88F4E9660B90099D /* log_writer.h */, + 81B9FD9FC925AE5303A6C5DEDCAF092E /* logging.cc */, + BFAAA08675F94727874A6F041299FEBD /* logging.h */, + 20FC90F276553AA05ECB6EF16C5A6B91 /* memtable.cc */, + 23C8057C3DC87D78E48F5584770EFD16 /* memtable.h */, + F55DF28B45C0DC7EECDF3F579442FD58 /* merger.cc */, + 8F2C8DAE66279CFF4D15F682106D761B /* merger.h */, + 883D7550CA14C459AC0B859D454D150C /* mutexlock.h */, + CDEC22C4B877A3988603237A07AD8B98 /* no_destructor.h */, + B2F28140F42539EF0AE6EC82CF67C0F1 /* options.cc */, + D50A2867A6F3524C5FE7F132004E6F3F /* options.h */, + 025A8978E9E1BA201E9B325D59D34DA4 /* port.h */, + C2CA5484C1CAEBAFEB82928E0B911DBD /* port_example.h */, + 500E2C11C74FFB9F16D95F36DB1892FE /* port_stdcxx.h */, + 84A39F6F4D4BDBC8FC579CADBC222BD4 /* posix_logger.h */, + 32EA256C170EBC175572FBECB2D4E5BA /* random.h */, + 0BF9423C4AE3C584BD68D6E8F6E19B7C /* repair.cc */, + 9E80A96424126BD73FA1DC045768406A /* skiplist.h */, + 3C44A9357C6BC090C6B509C35F23A97C /* slice.h */, + CC4528AA746B4ECE9B1A6B9E30F2528A /* snapshot.h */, + 3676FCA755C86DD19676A81765B989FB /* status.cc */, + AE6D328B3C537003183B73C76D907C10 /* status.h */, + D044378201B375C7DF9D01AD857B77AD /* table.cc */, + 4DA81CC3DB932CD235271CE085F91D47 /* table.h */, + D48039498B3E59506A01E97FC84AADA0 /* table_builder.cc */, + 2C87B89758929819681BF0D49D90466A /* table_builder.h */, + 94ACE21C6A33E6195E748EA160A13204 /* table_cache.cc */, + B1EA1D27F97E2F73EFBCE2703BCE7E0D /* table_cache.h */, + 5CBDE172A494EEE7A9545B91E85F13F1 /* testharness.cc */, + CAB3A4110D3D69279A6A92082D511C2A /* testharness.h */, + 36D67A48DF56131EF94BFE935D7A514F /* testutil.h */, + 3FBDB654C4BF020215028800CA80C5AB /* thread_annotations.h */, + EB5B000A9E1BCA491B41490FC070ACD2 /* two_level_iterator.cc */, + 7128EBFC506481FE5CC45958E38FE753 /* two_level_iterator.h */, + 861AFA8FF5218ADA37F19EC29EA86D5F /* version_edit.cc */, + 0C4CA078D9BE63A8B15F98F02748007D /* version_edit.h */, + 2014970201009EBB1DD94C5CE41A7FF0 /* version_set.cc */, + 163306A052CA3BBD80F4181480FB67FD /* version_set.h */, + D70A8461139B1C76F2ED9387DEE66DFA /* windows_logger.h */, + DD457201EEAF6D1D95DCA33AD2EDBF9F /* write_batch.cc */, + 91ADC399A3333A4199D859622DC339A6 /* write_batch.h */, + ACDE8B7DDA72C544A091534E5D36CC99 /* write_batch_internal.h */, + 00B0D4C452B8A2AB1DB31D1796C22BC3 /* Support Files */, + ); + name = "leveldb-library"; + path = "leveldb-library"; + sourceTree = ""; + }; + 14819703D41633BDAF2956E0595FF87E /* Frameworks */ = { + isa = PBXGroup; + children = ( + 372C5DBFC4877032EA5292324F62B37A /* Realm.framework */, + 5F3CB061F26F76255F4D2A550EB35D9E /* iOS */, + ); + name = Frameworks; + sourceTree = ""; + }; + 170FAE5FE958E80E2FF9F7B1247AB0C3 /* Core */ = { + isa = PBXGroup; + children = ( + 2A91D478CC110936C320DEC2AC5D6C4A /* GTMSessionFetcher.h */, + EC48BC4E52678E214C5B571535F0FD8E /* GTMSessionFetcher.m */, + ED3AA8DB0FFB3C66316742F2C335F0A0 /* GTMSessionFetcherLogging.h */, + 5E0433FE9144F9A78BCF219EF77AF2A0 /* GTMSessionFetcherLogging.m */, + 380BA422DBB8CB845C91F2DDE5BDEA5D /* GTMSessionFetcherService.h */, + 8CD746F873450F3A4F322F45F227EA7C /* GTMSessionFetcherService.m */, + 3AB5615A6CD7B79D890755A1CFA31206 /* GTMSessionUploadFetcher.h */, + 09E99A891DB19775D655521322BA574D /* GTMSessionUploadFetcher.m */, + ); + name = Core; + sourceTree = ""; + }; + 1A807C24671F934A291305C4632DBAD0 /* Support Files */ = { + isa = PBXGroup; + children = ( + 6B3481856C6C6A45C6EC74F5EAED6BA7 /* FirebaseCrashlytics.modulemap */, + 63A43B2B8A0078E69FE67E3A1D21ADF4 /* FirebaseCrashlytics.xcconfig */, + 17866E99D6426B8B2E450403A6E5FFD2 /* FirebaseCrashlytics-dummy.m */, + DBFD6E4F516B23B77B63EE29F3B443C1 /* FirebaseCrashlytics-Info.plist */, + C2B7043CEF622A670BF8F6991A15AE7C /* FirebaseCrashlytics-umbrella.h */, + ); + name = "Support Files"; + path = "../Target Support Files/FirebaseCrashlytics"; + sourceTree = ""; + }; + 1A8A2220B58D0D6D6B95EB1151907316 /* decode */ = { + isa = PBXGroup; + children = ( + ); + name = decode; + sourceTree = ""; + }; + 1BB0D8AEDF21B07E769D2D8B9401DEF0 /* Support Files */ = { + isa = PBXGroup; + children = ( + 39E0145EC88B5CC77C8D6E175B178068 /* FirebaseDatabase.modulemap */, + 2D2540B39435F5F149667EA4419EA0E1 /* FirebaseDatabase.xcconfig */, + 25F7B3F02D143776CB94A26EBDFF748C /* FirebaseDatabase-dummy.m */, + 8E97CBBD9152BB8C531E2C2C75C24E29 /* FirebaseDatabase-Info.plist */, + FF3EFA6069C2BF16A62F27DB2D7D7BDF /* FirebaseDatabase-umbrella.h */, + ); + name = "Support Files"; + path = "../Target Support Files/FirebaseDatabase"; + sourceTree = ""; + }; + 1CF1A11FEE61CCFED99ACE0F0AFA3E6F /* MethodSwizzler */ = { + isa = PBXGroup; + children = ( + C3DF5ABA2B431B97F394AF435C9865A8 /* GULOriginalIMPConvenienceMacros.h */, + 29C7D31341E23D15A2CE7EFACFF94955 /* GULSwizzler.h */, + F73338EC087F60071670E31C61FF3E46 /* GULSwizzler.m */, + ); + name = MethodSwizzler; + sourceTree = ""; + }; + 21542D429C646826C73DA3DAE223E209 /* Network */ = { + isa = PBXGroup; + children = ( + BAB03F1501D0A639B1B5A638411CBD82 /* GULMutableDictionary.h */, + 2F5D5F1A5915467DCA49F80BEBDA35D7 /* GULMutableDictionary.m */, + F7FB56B436AA1F5E67E74AAD1646C026 /* GULNetwork.h */, + 13F91BDD6A43D7C3C3BA79F3B3F57353 /* GULNetwork.m */, + 5ECEECF341CFC266359914D96F0FCCAB /* GULNetworkConstants.h */, + 5AD79BA8B34169A833C65779487B1402 /* GULNetworkConstants.m */, + FCA095FF7A7E8830ED5933A9B581B3A0 /* GULNetworkLoggerProtocol.h */, + 9565729856E35DE36A92958374DF72C2 /* GULNetworkMessageCode.h */, + 36D9D83D36A50A8C08DC14EB474F9405 /* GULNetworkURLSession.h */, + BB4B1F93EE7A1E409F89108816B92EBF /* GULNetworkURLSession.m */, + ); + name = Network; + sourceTree = ""; + }; + 21ACD3C375EECCD245260672B6D56415 /* Support Files */ = { + isa = PBXGroup; + children = ( + 2C4D3D5179372BBE9C5DB7F166F2A132 /* GTMSessionFetcher.modulemap */, + D6153BA7DF29AA46B25DAD360E51B86B /* GTMSessionFetcher.xcconfig */, + 0DCB3DAD6C9EDF1C934977898099ED3A /* GTMSessionFetcher-dummy.m */, + B0702C27C0C68A75145BD0D9C2F4CEA7 /* GTMSessionFetcher-Info.plist */, + F7D12F7E036176E8378124C2C53E714B /* GTMSessionFetcher-prefix.pch */, + 9E2D7FC56B1BA97E8C68CC09D1771342 /* GTMSessionFetcher-umbrella.h */, + ); + name = "Support Files"; + path = "../Target Support Files/GTMSessionFetcher"; + sourceTree = ""; + }; + 2625630E833431A0078DDB2C8DFC72FC /* GoogleAppMeasurement */ = { + isa = PBXGroup; + children = ( + DB89F80DEACBDADF96D40EA7201A4B57 /* Frameworks */, + F3CC0027D3D62E3E30BBC5AA07FAC215 /* Support Files */, + ); + name = GoogleAppMeasurement; + path = GoogleAppMeasurement; + sourceTree = ""; + }; + 267C6F6C48D3BA8001B0DD2841E9B133 /* Headers */ = { + isa = PBXGroup; + children = ( + DF17CAE04A5A105011249086FB2ECCCA /* NSError+RLMSync.h */, + 8C227158CEFB05EFA49A1D7A4A68EF82 /* Realm.h */, + 1D0510411AB5C0B1659FA25995973E70 /* RLMArray.h */, + 11CB3AD7E80C556AAEC53C4F0C55E1C2 /* RLMCollection.h */, + 9021419F0DD8F28569985527C8E622B5 /* RLMConstants.h */, + C4A5068B4F002455620D242C20110490 /* RLMMigration.h */, + 0E543475F05FA2541F2CCB52DBEF8927 /* RLMObject.h */, + 9B76CD581D8B96669C1D56E482DA40E3 /* RLMObjectBase.h */, + 86A9C0C738B55C104584CA8477DBF065 /* RLMObjectBase_Dynamic.h */, + 3CF201389ACD1CAF85EDD83AC49F3F48 /* RLMObjectSchema.h */, + 22F2A4295E515E6562FF43CA3D56B0C5 /* RLMPlatform.h */, + 8FA7E3B9378F3A764B377F83723247B7 /* RLMProperty.h */, + E05BD9106CFD3B0749792B5578D1D3B9 /* RLMRealm.h */, + 4D928597FB30E7EF35AAC58AE3A428C9 /* RLMRealm+Sync.h */, + 03E058B6C2458630160C0A65EB9FD24F /* RLMRealm_Dynamic.h */, + 434EA703D1FB868AB9B838002A4ED5B6 /* RLMRealmConfiguration.h */, + BBDE0947F81FE23D7DC2157383181911 /* RLMRealmConfiguration+Sync.h */, + 72DF4F0A77FF5AB0CD50A0CBFC8009A5 /* RLMResults.h */, + 18A4FA38D0EC7379DB125F600EA014CA /* RLMSchema.h */, + 1B855D092D33E62CC9867576BF1B3E8D /* RLMSyncConfiguration.h */, + 6420FC4F23214E5130FCE3DC8AE70810 /* RLMSyncCredentials.h */, + 82EE9EA75ED100D0A1D4A04F26EF09A7 /* RLMSyncManager.h */, + 887624AC0D176DFBC69C78021A0CF147 /* RLMSyncPermission.h */, + 9D06E960379196A78A8272C36A69AC21 /* RLMSyncSession.h */, + C8D9FF6584BEA3EB2DDF9561F2ABC8D1 /* RLMSyncSubscription.h */, + 930FF3B062A8B447DFC07EE99A07EE33 /* RLMSyncUser.h */, + 5292DA815D495D19C1091AD168722BFA /* RLMSyncUtil.h */, + 8F76B1EE6D2328DF902BCFC3CEFFDFF9 /* RLMThreadSafeReference.h */, + ); + name = Headers; + sourceTree = ""; + }; + 26C1B19059A4F0C408F2539D85BB5500 /* Firebase */ = { + isa = PBXGroup; + children = ( + B7DB13C36802DC70783D6E63ED91C1ED /* CoreOnly */, + 673FA0EA6A8DFC6BA4A963A835F09EA7 /* Support Files */, + ); + name = Firebase; + path = Firebase; + sourceTree = ""; + }; + 272A429EA9A78C869EA92148ADC97951 /* Support Files */ = { + isa = PBXGroup; + children = ( + 2A4392999CE0325AA7FA065483BFAEC0 /* Kingfisher.modulemap */, + BF6C758A52AEA6F30D091E5D3D99C459 /* Kingfisher.xcconfig */, + 7D7BF465F8E021B4E73DECFEAD4EDC95 /* Kingfisher-dummy.m */, + DB704E196F2277D9E0EABED098A080D5 /* Kingfisher-Info.plist */, + FE7FF026441377784DFFF679E101E733 /* Kingfisher-prefix.pch */, + 5908FBDB35CB84240FA819996966DCDC /* Kingfisher-umbrella.h */, + ); + name = "Support Files"; + path = "../Target Support Files/Kingfisher"; + sourceTree = ""; + }; + 34D69A181591FBEA896C58DAFD2B3F50 /* Support Files */ = { + isa = PBXGroup; + children = ( + 6F4E1314226380D0DA7464528964096B /* GoogleDataTransportCCTSupport.modulemap */, + 67DA492C3336C7C3ACE453F59A59590B /* GoogleDataTransportCCTSupport.xcconfig */, + 9C6BC7FD320F562050D0FF3A098FC56F /* GoogleDataTransportCCTSupport-dummy.m */, + 5D2293CC1383BBA537B11200CD92E5A9 /* GoogleDataTransportCCTSupport-Info.plist */, + 0DD29CAF1F48BADB4D360100F91E3CAD /* GoogleDataTransportCCTSupport-umbrella.h */, + ); + name = "Support Files"; + path = "../Target Support Files/GoogleDataTransportCCTSupport"; + sourceTree = ""; + }; + 358E92AC09F5448C6D0D82A3356DC9A6 /* Support Files */ = { + isa = PBXGroup; + children = ( + 1D1956DDD8EB33F9F30CB04B5893A01F /* FirebaseInstallations.modulemap */, + DB74B9A6B10A8AA948A01B99D9D9CDFF /* FirebaseInstallations.xcconfig */, + 9FFCA9DA3D1B73A86D7B03012553EFB1 /* FirebaseInstallations-dummy.m */, + 14EA242FEEBE0CD01C6BDDB10511FD39 /* FirebaseInstallations-Info.plist */, + 27F5D34050A15487B1386AFF70C4D37F /* FirebaseInstallations-umbrella.h */, + ); + name = "Support Files"; + path = "../Target Support Files/FirebaseInstallations"; + sourceTree = ""; + }; + 38B1904947A4F0B6D8CC99926447FF01 /* Kingfisher */ = { + isa = PBXGroup; + children = ( + 7FDCDF4E7F02823D751D57947B57EB65 /* Core */, + 272A429EA9A78C869EA92148ADC97951 /* Support Files */, + ); + name = Kingfisher; + path = Kingfisher; + sourceTree = ""; + }; + 412D37632F4367E05CF71D1CA1B50258 /* Logger */ = { + isa = PBXGroup; + children = ( + 889D76680F2283660A9FD4C0257665FC /* GULLogger.h */, + 33EBA20CB596F5C5EC3DD2C203BF1399 /* GULLogger.m */, + 18A2A34965EE1BF82E73C4BBE109FF6B /* GULLoggerLevel.h */, + ); + name = Logger; + sourceTree = ""; + }; + 4B8DE53DD08F4A88CE56E64E95F1062F /* Support Files */ = { + isa = PBXGroup; + children = ( + 58C58D0B86BC10D15B6E228B4E5AE4E0 /* FirebaseInstanceID.modulemap */, + C2B57CC70F0024AC3039D5A76E0C6B33 /* FirebaseInstanceID.xcconfig */, + EE508C10A062219D8B859F9DD9C6C6DC /* FirebaseInstanceID-dummy.m */, + 4DA5BCC58F028330E799E8CD4AC48BC8 /* FirebaseInstanceID-Info.plist */, + 8CF6F9311132EDF0F6BD49DE3F5FA5E1 /* FirebaseInstanceID-umbrella.h */, + ); + name = "Support Files"; + path = "../Target Support Files/FirebaseInstanceID"; + sourceTree = ""; + }; + 4C75395F2313EB6AB9C6C4DCADCC4188 /* Products */ = { + isa = PBXGroup; + children = ( + 5D797E9A5C5782CE845840781FA1CC81 /* Alamofire.framework */, + 3347A1AB6546F0A3977529B8F199DC41 /* FBLPromises.framework */, + 43B1E4CD7B30B9FD278100133C2AC788 /* FirebaseAuth.framework */, + E2B63D462DB7F827C4B11FD51E4F8E2D /* FirebaseCore.framework */, + 8CC9178C366942FD6FF6A115604EAD58 /* FirebaseCoreDiagnostics.framework */, + 86375444C196BA272DDBB8165BF64A15 /* FirebaseCrashlytics.framework */, + 51671C73F008B5C0C3751B3855999213 /* FirebaseDatabase.framework */, + 13C8C8B254851998F9289F71229B28A2 /* FirebaseInstallations.framework */, + 2DA0D814DFCB860D31D7BCD63D795858 /* FirebaseInstanceID.framework */, + 856B5CD56F194FAD26EA91620B66D614 /* GoogleDataTransport.framework */, + 6942351307BC1F54575D9853307EAE0E /* GoogleDataTransportCCTSupport.framework */, + B43874C6CBB50E7134FBEC24BABFE14F /* GoogleUtilities.framework */, + C1998E0D8085221AD87F89B614C10E52 /* GTMSessionFetcher.framework */, + C3F44C782D64D7EB20B61CE3844EBFAD /* Kingfisher.framework */, + 0A9F46A999C47653013D3AD854352507 /* leveldb.framework */, + 06FC5C9CF96D60C50FCD47D339C91951 /* nanopb.framework */, + E05AE6A00C585CABBD21A96927641401 /* Pods_GeekbrainsUI.framework */, + 921BE4A82C4A7A5C72A0C6F8B8FEF200 /* Realm.framework */, + 437919EE08EC6BFCCBAC3BD346309742 /* RealmSwift.framework */, + ); + name = Products; + sourceTree = ""; + }; + 591050684A2DEB56DCA89B38D292B478 /* RealmSwift */ = { + isa = PBXGroup; + children = ( + F970D1755FF2BF74A11AA024C79EA2E0 /* Aliases.swift */, + 8AAD0BBA966E05AF48C696B1C8088665 /* Error.swift */, + 89DDA8DBE011A6C4ACF28382FA07CBD1 /* LinkingObjects.swift */, + 8A1C30EE032FA32667014346FEB96DAA /* List.swift */, + E47BAF825718E41F6EEECFADCA8414AD /* Migration.swift */, + 96D052C928B9E11706A2CF4EA9096B59 /* Object.swift */, + 948C362190D80D0FAD6202C60E1B479F /* ObjectiveCSupport.swift */, + 18C003E341EAC1C5F8D5C1F825A03BC6 /* ObjectiveCSupport+Sync.swift */, + 3E69A7D068230FF36FEBEBF212AD9F60 /* ObjectSchema.swift */, + F6091A1DCE0E3278DD93874739E79047 /* Optional.swift */, + 9B77B047C6A76B835BE1FCB7549381AC /* Property.swift */, + 02231003098E086547B4CA839A2739B8 /* Realm.swift */, + BA9EE67D70795C4E48FEAD85D644585B /* RealmCollection.swift */, + B3D17B0B57A0B5950C0C15451C711B2A /* RealmConfiguration.swift */, + 2A78F3E5F4F2D135399B1A15D5C933D8 /* Results.swift */, + F4469C2241975FA8DD82B8AB70834A77 /* Schema.swift */, + 66265AC21D95957F5CF5467CDF63C31D /* SortDescriptor.swift */, + 09933BBB67B866ED8F91402FCD732796 /* SwiftVersion.swift */, + 3628F98B2E091B183A6B8A870F66D7E5 /* Sync.swift */, + FAD407F4EF0402CE18C8A3FB16136CE1 /* ThreadSafeReference.swift */, + B1EFC8129C762D0986B402BF4835DAB3 /* Util.swift */, + B4FEE2E926CE7FBD9A52E435790D1356 /* Support Files */, + ); + name = RealmSwift; + path = RealmSwift; + sourceTree = ""; + }; + 5AC0AF63B9D04538CEE6A0E83FC7DB5C /* Support Files */ = { + isa = PBXGroup; + children = ( + 7E276D794AB23DA07712CC60A0F1C6EC /* GoogleDataTransport.modulemap */, + 8D2EFA3A80904C2A2AB08FDA578F8844 /* GoogleDataTransport.xcconfig */, + 2FAEFF7F1001278848CAD0FC8200F1D0 /* GoogleDataTransport-dummy.m */, + 117B8C3BABB7D834D0BD574509E76FA2 /* GoogleDataTransport-Info.plist */, + 0E7B05D506EF8041DBC2C686CB769C56 /* GoogleDataTransport-umbrella.h */, + ); + name = "Support Files"; + path = "../Target Support Files/GoogleDataTransport"; + sourceTree = ""; + }; + 5BD04E5F54905FBBD9276DFBBCD09380 /* Pods-GeekbrainsUI */ = { + isa = PBXGroup; + children = ( + F6E7A6CA87859BB2D0DF76AF27B092B8 /* Pods-GeekbrainsUI.modulemap */, + 36AFC1106DADF5E8344D92559B57CF5E /* Pods-GeekbrainsUI-acknowledgements.markdown */, + 24D9D48BC5E4BA52E3C33B89E2D8E97D /* Pods-GeekbrainsUI-acknowledgements.plist */, + 43556C7CE86F4F7EA7D42D92CC4B0A64 /* Pods-GeekbrainsUI-dummy.m */, + 73F6AA37B87E12A6A1DE1ADB047E47B3 /* Pods-GeekbrainsUI-frameworks.sh */, + BE194B2DC76B95274DE3365E8F0C6072 /* Pods-GeekbrainsUI-Info.plist */, + FAFAA71EB419B9C1A78847DD7C78B876 /* Pods-GeekbrainsUI-umbrella.h */, + 88ABB3A4A0002798B71EE2408272DA01 /* Pods-GeekbrainsUI.debug.xcconfig */, + 50FA543897C7A9B21B74C83B4F812945 /* Pods-GeekbrainsUI.release.xcconfig */, + ); + name = "Pods-GeekbrainsUI"; + path = "Target Support Files/Pods-GeekbrainsUI"; + sourceTree = ""; + }; + 5E1A439E03E6E4802A6857EC10B439AE /* Support Files */ = { + isa = PBXGroup; + children = ( + B536523F3D888E0367D79E755046FCD1 /* PromisesObjC.modulemap */, + 3022CC4D2BD5802E1A96A2789D9BE909 /* PromisesObjC.xcconfig */, + 7C5B268E1B3ED259F849D41750BBC9B0 /* PromisesObjC-dummy.m */, + FB7E53AD0B30ECA07B0628DDB6BA3DE9 /* PromisesObjC-Info.plist */, + CDE235CB054F3FE398E19E47A6CD214E /* PromisesObjC-umbrella.h */, + ); + name = "Support Files"; + path = "../Target Support Files/PromisesObjC"; + sourceTree = ""; + }; + 5F3CB061F26F76255F4D2A550EB35D9E /* iOS */ = { + isa = PBXGroup; + children = ( + 6133E402BA8E16E6642B59393592D3F4 /* Accelerate.framework */, + 091DE8E84139D89633B550EF9E316271 /* CFNetwork.framework */, + 64025D1D442A26509C94DDDE4BCBF577 /* Foundation.framework */, + BFC56E517F177CBDCE7A445BE95B4E4C /* Security.framework */, + ABEEBDCE85E4EB501A37D92EF28C16DB /* SystemConfiguration.framework */, + ); + name = iOS; + sourceTree = ""; + }; + 61AC7AE57B526478BDF66DE67247830F /* FirebaseAnalytics */ = { + isa = PBXGroup; + children = ( + B1274572BE26CFAE4D5D6677E8C18310 /* Frameworks */, + 76030F48A7BC49CED96E6254AB27F6D5 /* Support Files */, + ); + name = FirebaseAnalytics; + path = FirebaseAnalytics; + sourceTree = ""; + }; + 66758874D3DAB5283F3C84301B66670E /* FirebaseDatabase */ = { + isa = PBXGroup; + children = ( + 0C23EB632CADB2EE2B872554F93C995E /* APLevelDB.h */, + 234795C49420633C02D08BCF4C26DEFE /* APLevelDB.mm */, + 0B80609DA7F3835D3B6FF1855935465F /* FAckUserWrite.h */, + 0592320608C8F3CCF80D574E10C7E91C /* FAckUserWrite.m */, + C6CBD394561897F97580F99C0927661C /* FArraySortedDictionary.h */, + 0ED8B0B84A275F2D02C176BEE7BDCAA5 /* FArraySortedDictionary.m */, + 9ECA05096F49D5C2C0D3D1C2A3130DA5 /* FAtomicNumber.h */, + 2AA4910C2C27CDBE0B5098825D5EF432 /* FAtomicNumber.m */, + 8FF8C4F4BA47732C4BB31B381E8CCD36 /* FAuthTokenProvider.h */, + 739B2D7FD0A87C1FAE63F41227CA2D46 /* FAuthTokenProvider.m */, + 64C2554DC46237514E52F9B969B9C72C /* fbase64.c */, + 046EB39BD4F132031A238FB3AC461A85 /* fbase64.h */, + A8956802366B6DEDE559931F69944AF6 /* FCacheNode.h */, + 683557C62E339ED3A54534F38BFE56E6 /* FCacheNode.m */, + 0AE01923DCFF0C1BBC52D813E212BF6E /* FCachePolicy.h */, + 62FD01C83412004B1C76E346EAEB52FD /* FCachePolicy.m */, + C4BA68AE47EFEC1A5A8AC627ABC9399C /* FCancelEvent.h */, + BC607F184AD720438FF6F68C03102FCF /* FCancelEvent.m */, + 6F6646222EB1681CC5AD46638F5CFC6A /* FChange.h */, + C381B0F4DB89513D7C09360B109ED622 /* FChange.m */, + 4AA009C94CE5B49342CE6C02D1B216B0 /* FChildChangeAccumulator.h */, + 0D691679E072ED98D8E9280883868E4E /* FChildChangeAccumulator.m */, + 6B34E88940F4E64CA1A6C9902CB11198 /* FChildEventRegistration.h */, + C0D2FC3564EF6F5762A6F5D48E4136CA /* FChildEventRegistration.m */, + 7CD3E8B3D40C26292D916375AE737C9D /* FChildrenNode.h */, + 2D2B159BBE380986ACF63E8FCA676F50 /* FChildrenNode.m */, + D4BD915552AA4F71792B9C08A29C66C7 /* FClock.h */, + FE97F7C3CA00403626ACF85E09BE6F0C /* FClock.m */, + 64E603DF7C2E7F872F1E29A9869AD2E3 /* FCompleteChildSource.h */, + FD7DE28F1856D146FBFABE964B3B5C47 /* FCompoundHash.h */, + 8A84B7F161963279BD754667535C4183 /* FCompoundHash.m */, + 2F0622B5942605ACE1CD6F484496A943 /* FCompoundWrite.h */, + 821CFA72A0DA71F03695F649274F8E78 /* FCompoundWrite.m */, + BA5D4D31F808F2F3EB3D5D55B36F1070 /* FConnection.h */, + 4877558843115B5DDB96AA54123D8AFB /* FConnection.m */, + 437157BAE39DA22356AB037E9CBFD59D /* FConstants.h */, + 2F6D5F74532AA44009E25B4EF7564210 /* FConstants.m */, + BED0BA47CB4A60F6FA1559076AE57EC5 /* FDataEvent.h */, + 27319C0EE4A55D23AA99620BCD4D9635 /* FDataEvent.m */, + 49944035B0ED6067E46A755A89EA18AB /* FEmptyNode.h */, + 09125E2EA0588202813B2CDFFEEFD93F /* FEmptyNode.m */, + D518F40A869BBAB3A4155E1BB9275EF8 /* FEvent.h */, + 0E1F295C21957B24F285D48A4D11B95C /* FEventEmitter.h */, + EA4701731BC4DA180E5A715A5505C01E /* FEventEmitter.m */, + 341FA0A9A90666DFB068B02CF77FC23D /* FEventGenerator.h */, + 26D38478C1F46C17F95B881A52894550 /* FEventGenerator.m */, + C25B246DBE48DFAE91A83DFD9A0CBE77 /* FEventRaiser.h */, + C58E2455DB5216CAC525E50775B466B0 /* FEventRaiser.m */, + 809D9326143CAC79986E116D1A06E548 /* FEventRegistration.h */, + AA682680D3C44D8FCA0361A76F028A8F /* FImmutableSortedDictionary.h */, + 7D83116F9227F8C545FC7F83B31C6F56 /* FImmutableSortedDictionary.m */, + DF9A521F94DF28771A2F643845C79C17 /* FImmutableSortedSet.h */, + 7B022C06F6C82798E3957F914136EE12 /* FImmutableSortedSet.m */, + 97AB7716BA5097897D8CE33414A2157D /* FImmutableTree.h */, + 05F1DD910E0B70B4E0DF0A7C3FA329D0 /* FImmutableTree.m */, + 717F6CB3DBCA25D802C9F51D5CA322DA /* FIndex.h */, + 59BAE894F428C0EBEC29AFC6A19B8F4D /* FIndex.m */, + 56BAA01862A49A9C10732D4D3648B154 /* FIndexedFilter.h */, + 4B484F345ABB94B6FDED330218918FCE /* FIndexedFilter.m */, + 98336D712A53939111F8856BC622B48F /* FIndexedNode.h */, + FEFDB553310806FE52AA299503F0117E /* FIndexedNode.m */, + C4EB340CC90499FE586FFAD55EA2C4C2 /* FIRDatabase.h */, + E5DE47C2C76346BB6B746EC071ADB3CA /* FIRDatabase.m */, + 1D5FBD103DC3CF41681635FC640E23FC /* FIRDatabase_Private.h */, + 8C505EBD827CC2D18CD9D3E74CD39B8A /* FIRDatabaseComponent.h */, + 1C45C494D97CAD85D2802B100A2AC056 /* FIRDatabaseComponent.m */, + EAB1F081D87EA12C99FE8E742DCCA8E9 /* FIRDatabaseConfig.h */, + 11DCEEEC472C556014277C5511D1CE3B /* FIRDatabaseConfig.m */, + C22F2A5F3A5274251E53C399BA270681 /* FIRDatabaseConfig_Private.h */, + D45AD6314AAB709350FCD1F48E30D8B4 /* FIRDatabaseQuery.h */, + 5C41758045A7C6E1ABE7A948D9112321 /* FIRDatabaseQuery.m */, + C7972622EF19B74B3BFA5118DEA5FE5F /* FIRDatabaseQuery_Private.h */, + F7E041DEA67993EB831E297B08E34740 /* FIRDatabaseReference.h */, + AF62A3F1196ABE198A8D9FAD67E52A57 /* FIRDatabaseReference.m */, + 6930AFD3D022185F6C9FE8A6AC2D6E1C /* FIRDatabaseReference_Private.h */, + 0C725C066DA25373EC76D559B25677B0 /* FIRDataEventType.h */, + 0F739610BEEAB0E57F2B0D16064B644E /* FIRDataSnapshot.h */, + 33B783F133658E23EE462D0FE56B550F /* FIRDataSnapshot.m */, + 239AF3F89B458885B216E48410EAE11F /* FIRDataSnapshot_Private.h */, + F46A4EDD00D706566C8479D0068E3FAC /* FirebaseDatabase.h */, + 82364B5E57B1EF839045A66A8EAD905A /* FIRMutableData.h */, + F03BCB4DD187BEE1AB4313919D286422 /* FIRMutableData.m */, + D9BBAF84118A47ED29561F403710BAD4 /* FIRMutableData_Private.h */, + B8A5983F620EA151F5229275D6DF1BE2 /* FIRNoopAuthTokenProvider.h */, + DA284A344F51526CCFFC122A20891948 /* FIRNoopAuthTokenProvider.m */, + FFD782C9E7773E607256D36F128C9C31 /* FIRRetryHelper.h */, + BC115140A96B7DA133E8F98C90D95FD0 /* FIRRetryHelper.m */, + 684CC63082E6E3184BF9143A7929145F /* FIRServerValue.h */, + 9C881435CE9E0783A5644B934C4B16C5 /* FIRServerValue.m */, + 8288B40378984693C731D002FB16AFC0 /* FIRTransactionResult.h */, + 2251A9A4EAB8404DBDC603AD34F4C0C3 /* FIRTransactionResult.m */, + 86ABF70E1FE2CB545EBEE6228D4296AB /* FIRTransactionResult_Private.h */, + A7B71AD4C1D52D6BF67F016BF704E18C /* FKeepSyncedEventRegistration.h */, + BF0DD77C260C2BCB9B283657354A812B /* FKeepSyncedEventRegistration.m */, + 1477BB8C1DE91F3AB64B8F2FF2A5A077 /* FKeyIndex.h */, + E507F9B4360841CA0E8C7A0F2039A90C /* FKeyIndex.m */, + CDDA260CC13C2C314506C956FB571FED /* FLeafNode.h */, + D7BD54C8A226B56A4E8F990B0F498279 /* FLeafNode.m */, + DF82CE7658F608F7684A4348FE783371 /* FLevelDBStorageEngine.h */, + 868FC26600B87CCCCB054FD66B2EA15E /* FLevelDBStorageEngine.m */, + 415B7669334DDA6ABC48A6A59B119A62 /* FLimitedFilter.h */, + 713C78044C95A18155B81728F5CCD2F3 /* FLimitedFilter.m */, + 77FF98C591A214884B7A2823DD3E695B /* FListenComplete.h */, + 6804BF7FE62BF9D2FE3ED2E836DCC825 /* FListenComplete.m */, + E151626A34AF77210FB5BFC4C7D8567D /* FListenProvider.h */, + 220A25CEC1AFF54BC96AF07DDC87C9D1 /* FListenProvider.m */, + 6DE985270C413E3DC01DBB9E906093D2 /* FLLRBEmptyNode.h */, + 8AE09E001BB86416A1D26AB609A0AEA3 /* FLLRBEmptyNode.m */, + C40CC2964E8729E9F7F77F7626636D13 /* FLLRBNode.h */, + 3C6AA9924912484D21FE7B82B8982939 /* FLLRBValueNode.h */, + DAF1EF806E8081A6E6FC9DFDF24CB30D /* FLLRBValueNode.m */, + 8805ED6DFABB525362EE02596FE7AF24 /* FMaxNode.h */, + 1C6417652D217907B3854C35FBC3BEF5 /* FMaxNode.m */, + EAF035ADBF22E759B690C1A46370000B /* FMerge.h */, + A9F13426A62CA85A9857717AABA68113 /* FMerge.m */, + 3B72C47749C5D7545714AB0DF04FBBFC /* FNamedNode.h */, + 840F659E9BCA424EB43A2CC83E1E8404 /* FNamedNode.m */, + 17C37D59F16CFBD0BD12D98127608CC9 /* FNextPushId.h */, + 0351264261053739411827F81B601E59 /* FNextPushId.m */, + CC4EB592A6575DA0569604F8EF9D9EA3 /* FNode.h */, + 10D8F107AA7F4E07CBE300F4F7CB10C6 /* FNodeFilter.h */, + A8A8721CAE2B4D781F5B1CF133CA52F3 /* FOperation.h */, + 797A37AD003EE04FB5F70E2DE7E434DE /* FOperationSource.h */, + 067870D7BE43590F2E93FD4B3FDB5646 /* FOperationSource.m */, + A20C88EE3EEA866A57E002E2CE2CB90C /* FOverwrite.h */, + 60B44DC898C5948A7EA0035AC015B100 /* FOverwrite.m */, + 6253723D977FDDB11DE98CCA90A7FA7F /* FParsedUrl.h */, + EF5C63E6374A65708B5F98DE5057F562 /* FParsedUrl.m */, + 983140C99DED04F2EAC250131C4833B2 /* FPath.h */, + 250253DBF9928DBD2E9DC314A05278B6 /* FPath.m */, + B1826037AF90ACAC8537C39DF02EC8BF /* FPathIndex.h */, + 53773438319C44A00E27C7B5EC550A5D /* FPathIndex.m */, + 939962F7D0C89D0540EBE4A81F7DBB8A /* FPendingPut.h */, + F5C03C055BA2A1C83052C9BC2AF326E2 /* FPendingPut.m */, + F62BE4EC547C3E79D8346DF6F8F50669 /* FPersistenceManager.h */, + C0A3508439074258FC89A3CBFB18B819 /* FPersistenceManager.m */, + C673995A49F14FBF2E4228285E650AAE /* FPersistentConnection.h */, + 803101B9EF5EE3DE6588D22EE440EF16 /* FPersistentConnection.m */, + C9A08033F18CE85182378A9E946680D8 /* FPriorityIndex.h */, + DBFCE01EA61613CE83B1513D2DB54FCA /* FPriorityIndex.m */, + F3BD52E227C6798238630C8878860D83 /* FPruneForest.h */, + AE57CCAA2CE76A131F0045A0DEE4BF61 /* FPruneForest.m */, + B9B6DDC21B935230DCFEFED3D43FD220 /* FQueryParams.h */, + 8FFDC32A91D6222B62D35ACE38DF0183 /* FQueryParams.m */, + 6EE6635D48F8FE1C35FB9578EAF4776B /* FQuerySpec.h */, + 1DED2ACF11680CBA13D4FD04C0523775 /* FQuerySpec.m */, + 72B3FC42C46A789BA9EA8C45EFDEE3C5 /* FRangedFilter.h */, + A6395739D39ED1B02D46AFEC682B3639 /* FRangedFilter.m */, + E8AE1089DED6CEA4D8ADF84B83D458B4 /* FRangeMerge.h */, + CA2805B2729BA1A43D194462C60F5179 /* FRangeMerge.m */, + 8CC50146E48C132CD4FDE4797E7E3763 /* FRepo.h */, + 87961C9BE2777F5B6B0C58DBBDFA3324 /* FRepo.m */, + 4DFB179009B0825D8D02E8E8104ADBAD /* FRepo_Private.h */, + 5144D239598058957529301947B65E99 /* FRepoInfo.h */, + 3CEFA5A5C281A74704488F114ED873F9 /* FRepoInfo.m */, + 39662E3477B966D0CC14DFA1C666B797 /* FRepoManager.h */, + 8B5542975A6E66F371A3C9A81B897E76 /* FRepoManager.m */, + 52EF11B4BCFC895E51708A2429C5961D /* FServerValues.h */, + 533BFCBA4D346ADED0C0CB42B1E4516B /* FServerValues.m */, + B4630EC4BD28C7D9F743C6494833B4F3 /* FSnapshotHolder.h */, + 201546695CF3D52D60210122BDFF3E7E /* FSnapshotHolder.m */, + 120F846FAC83BF1EB295CC2FB54137E0 /* FSnapshotUtilities.h */, + 82E33DFCC4FCBB43EA18919761787820 /* FSnapshotUtilities.m */, + A31792D94CAC5470CE07710F88771788 /* FSparseSnapshotTree.h */, + 948D6FAC68230EC8AC23DC6649DB1BB3 /* FSparseSnapshotTree.m */, + 20A3257A6A273B703A8672D312E810BC /* FSRWebSocket.h */, + 5DC79D7BFDA8E6A84FEDA85A96D893DA /* FSRWebSocket.m */, + 23DF59E8BD83BA3C2517751E2094EB69 /* FStorageEngine.h */, + B5E3722A6289EC400D61D12B9478747F /* FStringUtilities.h */, + 3005F5102DB183A0E49E1772309D4254 /* FStringUtilities.m */, + F1DB0044569C7F340371466F6853621C /* FSyncPoint.h */, + 9024896D2537C44DEC820307835C4C89 /* FSyncPoint.m */, + 890533AECEC39179FE6E6B2169233974 /* FSyncTree.h */, + 9C95068A8D4719F258EC259BA37386C9 /* FSyncTree.m */, + 73F65244F2C613D2061EAC8DD493459D /* FTrackedQuery.h */, + 00D81FAAE360D000EE90A6008438A716 /* FTrackedQuery.m */, + DC9CB71FE83B367C573761ED0E254D12 /* FTrackedQueryManager.h */, + 4E8447705007CCB88C9E6571A0E8E19F /* FTrackedQueryManager.m */, + 9FCC44446EB1279EA6225E7F3C5D4B38 /* FTransformedEnumerator.h */, + C35101BD88B8B352E5C9084099ED1C91 /* FTransformedEnumerator.m */, + ED308881D7E1D2F55E74AAF0E548A9C2 /* FTree.h */, + 998AA9BB7AA0BEAEAF95FE9BB2E20DDE /* FTree.m */, + FD66CF40520DC57E058636DDAD608536 /* FTreeNode.h */, + 5649E1776CB127F532FB761BCE7C3A02 /* FTreeNode.m */, + ECA7865B913CA6A76D0C52F4AD411F8D /* FTreeSortedDictionary.h */, + 71D39A54D987F3A5B517EF69C6F97FA8 /* FTreeSortedDictionary.m */, + 2F49F567A0C73278FD58192F5C783B3F /* FTreeSortedDictionaryEnumerator.h */, + C23E7FFC35B7C0C36475EA8E367B1573 /* FTreeSortedDictionaryEnumerator.m */, + 03D2D50AE298301CE92CC1C9CD0CF6B8 /* FTupleBoolBlock.h */, + 0D9C52A5FE9F767DD058F89205254B18 /* FTupleBoolBlock.m */, + BF8CF5E3F7A1A02D334DF95AAA519EB2 /* FTupleCallbackStatus.h */, + 8B20983BE4C3A3AF1F2CC902B2FAD738 /* FTupleCallbackStatus.m */, + 14AF50D2F7BD3AC68ED686CAB08FBCF4 /* FTupleFirebase.h */, + 29051F2C16CCD3952E36E6E505E5A87D /* FTupleFirebase.m */, + 2E927F00B9D8E3AC6775F6EF61528A7B /* FTupleNodePath.h */, + E847EEF4096BD3BCF215C1386938844E /* FTupleNodePath.m */, + 22E9080549317A0B9D3BC1664D5BF3A1 /* FTupleObjectNode.h */, + 96BD12B47B380FF0B4B732A7A3C7F913 /* FTupleObjectNode.m */, + 7EC8360EB2198F697C257E552137581A /* FTupleObjects.h */, + A6816E6F407C12392DC39456D0E4BB2B /* FTupleObjects.m */, + B8380284D178779DBD01394689C2E592 /* FTupleOnDisconnect.h */, + AE4FF34D0DFF78A8C05317D4A10C2A36 /* FTupleOnDisconnect.m */, + 9C05131A2732A28192011811FF7609CE /* FTuplePathValue.h */, + AFEAE24FA3CA9EDA209B5B2086A1836E /* FTuplePathValue.m */, + D7C27A7ADE5866F1D997C55EA66528BB /* FTupleRemovedQueriesEvents.h */, + FFF2198200840B5706276A535E8A8B77 /* FTupleRemovedQueriesEvents.m */, + 7EEC129EFA1105150FB920A946CD1280 /* FTupleSetIdPath.h */, + 1EACD418D36D7A8783BD11A9111EF64B /* FTupleSetIdPath.m */, + CDA8666A9B50BF19DF40C0F386F748B5 /* FTupleStringNode.h */, + B9752B9BF07F882163DE4B93D48E07CC /* FTupleStringNode.m */, + C0B95A37E920E65E5E78E2632195B47E /* FTupleTransaction.h */, + A0CB71AF07C955B047A38074046DD5F2 /* FTupleTransaction.m */, + 6D3CD3AF92AE99DC686D0D1EED4D7456 /* FTupleTSN.h */, + EAF94A59F9E00336CEF40DFC2A0A2F5E /* FTupleTSN.m */, + C47E486A0F18A6B1AE7940348FC7D3E3 /* FTupleUserCallback.h */, + EB8D442EDD47F4C18B9E23F08BC4F999 /* FTupleUserCallback.m */, + 0C0079799AE2339A85F7E62D4780C436 /* FTypedefs.h */, + B137C04F7C027EC1981824188E584EA9 /* FTypedefs_Private.h */, + F34CD699384BE03207942625DA31B7D7 /* FUtilities.h */, + 2A750E0A59FE2F41846AA2DBD149EB42 /* FUtilities.m */, + DD0946B579009574C042E72621AC1BF5 /* FValidation.h */, + C033070806227FC7EBEF70D50A23C75B /* FValidation.m */, + 459B070289EE1BA904BD423482CA91B1 /* FValueEventRegistration.h */, + 8085970F3E66135CB37870E7D93C4126 /* FValueEventRegistration.m */, + 4859B5C57D1455BEDCFD395EC04ECFF2 /* FValueIndex.h */, + 462F3DF1EE8A1A326EFB498FFE50BA2E /* FValueIndex.m */, + E1926F498A9AA6FE52584068A889ABF3 /* FView.h */, + B7AF0BB63342B990F1F06546893BE592 /* FView.m */, + 2AA97EC8B857DC53B769FDB0770E3780 /* FViewCache.h */, + 251BC7C8DC3F888A067DA6D70D010182 /* FViewCache.m */, + 9213CDC4C57E01385FA2CED32D12D1B0 /* FViewProcessor.h */, + C78A0274733FB2CB614752BB28DDFDB5 /* FViewProcessor.m */, + 02BB0C52246D55DDDA24DD966F29EE7D /* FViewProcessorResult.h */, + A78CA57199BE1F05CB6F7E0160B11399 /* FViewProcessorResult.m */, + 2C2E2C159D6A12D606AEC93F92C6A9B4 /* FWebSocketConnection.h */, + 51EED64CBA9532DACC7881BBFE117AE7 /* FWebSocketConnection.m */, + 67CAF2E79A38802580080D85D7F4314D /* FWriteRecord.h */, + 64DE1BE55CA40E16D2F37E836704D8C4 /* FWriteRecord.m */, + E53B2F3C7FE2FEDA489481B2B799798B /* FWriteTree.h */, + 4B528ABCAC79A8EE228A8B0D1B8A80D8 /* FWriteTree.m */, + FB3B80F0D5095AE87661533BF8DE50F3 /* FWriteTreeRef.h */, + EA10D39A174D32B831D388D410967DBA /* FWriteTreeRef.m */, + 8E76D2B09059C28B04E6F10E68D35F5F /* NSData+SRB64Additions.h */, + 30C26803AF33201B20BEF6D6C4B3644A /* NSData+SRB64Additions.m */, + 1BB0D8AEDF21B07E769D2D8B9401DEF0 /* Support Files */, + ); + name = FirebaseDatabase; + path = FirebaseDatabase; + sourceTree = ""; + }; + 673FA0EA6A8DFC6BA4A963A835F09EA7 /* Support Files */ = { + isa = PBXGroup; + children = ( + ADABD7A2BBCAE81134FFE12A19B5DE02 /* Firebase.xcconfig */, + ); + name = "Support Files"; + path = "../Target Support Files/Firebase"; + sourceTree = ""; + }; + 675A3B8C67B178B7FC54670F74ED81B0 /* GoogleDataTransport */ = { + isa = PBXGroup; + children = ( + 714E27F8EAD4CFB19AC1A66F7AC98A5E /* GDTCORAssert.h */, + 2020EB583038820A8F046C0F7333E258 /* GDTCORAssert.m */, + 6065ADD321D1A7A79F4A25057175A3E4 /* GDTCORClock.h */, + 87A652F4124484CB76A1F4E60241AABE /* GDTCORClock.m */, + 4EE1BC1AF3606BB8B6FF9CF50F00E04F /* GDTCORConsoleLogger.h */, + 1AC16AD6A932F1CAB1FD8F22CD2467E9 /* GDTCORConsoleLogger.m */, + B830D4D6C294ED178AD87E57C9ADA7FF /* GDTCORDataFuture.h */, + A7317777EA2818DD08E00424D0C9AA43 /* GDTCORDataFuture.m */, + 52E21E6ED4BA9DF24FAB3428B4CA51D7 /* GDTCOREvent.h */, + 8639270B60188F3F1AF6A037E6730148 /* GDTCOREvent.m */, + 10B3B837E0F6835E55DE59E541E92996 /* GDTCOREvent_Private.h */, + DABF9587A613FBB9B7C37F170B33790F /* GDTCOREventDataObject.h */, + 56AE672BAC2AB1B75187E390E306EFF9 /* GDTCOREventTransformer.h */, + 1C248628CE055C1521E3853C48F9FF24 /* GDTCORLifecycle.h */, + 7FB1BE5072D4E905A26D9822DFBEE39F /* GDTCORLifecycle.m */, + AB0134FF9D73541F1CAAD70ADF3FE8E4 /* GDTCORPlatform.h */, + DFD7E84C43890AE1CDFF3965BD2EA669 /* GDTCORPlatform.m */, + 71C8E228FEBA45B0E3FA9EA1E8E6F19A /* GDTCORPrioritizer.h */, + 1DCEABFFFDA249FC0F0D5AEAEC0472FA /* GDTCORReachability.h */, + D507ADEDF13DE43ACCC74E7E20D6EB12 /* GDTCORReachability.m */, + 4ACA40A65A478DF9FA2F1A6435A82040 /* GDTCORReachability_Private.h */, + C9FDAAB6458C9C7A42D77B11BF0B3F75 /* GDTCORRegistrar.h */, + 2AA8410864480F8EAE29609C2480122F /* GDTCORRegistrar.m */, + 2AAA2976A5267694F455FD69D8CF7288 /* GDTCORRegistrar_Private.h */, + 77D06B4DBC29D02E02343A8FD4AC7C03 /* GDTCORStorage.h */, + 4C6F01CEA86B55CC5A4FD7FEEDA27AF0 /* GDTCORStorage.m */, + 7060A51F91294EA617A21BB7F724F288 /* GDTCORStorage_Private.h */, + ECCB91EDC5AA7BAC3EDD3F05AF82568A /* GDTCORStoredEvent.h */, + 066EA1C1FF6676A0B35DE90DB17930AB /* GDTCORStoredEvent.m */, + 24447E8A1A1FD7DA7430C702460DFBA5 /* GDTCORTargets.h */, + 5B5835EC2334D400F3E828323AF7B311 /* GDTCORTransformer.h */, + 3E8366B108065E2E1A6A3144FEEEA1BE /* GDTCORTransformer.m */, + BF60CBDE6804E7AEE2F0511331C15B8D /* GDTCORTransformer_Private.h */, + 0F1D4E308F55A3F56D63433F341967EC /* GDTCORTransport.h */, + A92F8FEB290B291E79FE5A612537F990 /* GDTCORTransport.m */, + BB72DEB463D368287CFC5EFD8FF55244 /* GDTCORTransport_Private.h */, + 1E0D87AAB7FCF9615A4CE2C179F88E7D /* GDTCORUploadCoordinator.h */, + 57654FCDEAC6AA86CFE3FA58FC422D05 /* GDTCORUploadCoordinator.m */, + CEC50CD22C78836638CBC19D7524122C /* GDTCORUploader.h */, + A8D86AF676E653B84DD1CB1594DCB2BB /* GDTCORUploadPackage.h */, + 712C3E1D09C9F2171D435E81DC50FF87 /* GDTCORUploadPackage.m */, + 4D45C50A9735924375C923A4C204802D /* GDTCORUploadPackage_Private.h */, + CF5F329FA138691313AFB64FB1006F57 /* GoogleDataTransport.h */, + 5AC0AF63B9D04538CEE6A0E83FC7DB5C /* Support Files */, + ); + name = GoogleDataTransport; + path = GoogleDataTransport; + sourceTree = ""; + }; + 68152EF3D7279F32665233358A209D0C /* Support Files */ = { + isa = PBXGroup; + children = ( + C3FD6D23D7CE39B3F05FDFD14293FFC7 /* GoogleUtilities.modulemap */, + 39C548D8FA8FED7C63B92834D761E16A /* GoogleUtilities.xcconfig */, + 71721A7E88B754EEDA47EBAC240BDA55 /* GoogleUtilities-dummy.m */, + A96DE2BFC9945024D2655D331499248F /* GoogleUtilities-Info.plist */, + E46F7D2E9ABF567418D928FA0D4CF0B2 /* GoogleUtilities-prefix.pch */, + 0DA64D4B686E9AD555646D96BBA0EF44 /* GoogleUtilities-umbrella.h */, + ); + name = "Support Files"; + path = "../Target Support Files/GoogleUtilities"; + sourceTree = ""; + }; + 6A00FD0646FAC06C56634B3BFFA5B6A7 /* Support Files */ = { + isa = PBXGroup; + children = ( + 67AFD064168467D4279B84030F4A6890 /* FirebaseAnalyticsInterop.xcconfig */, + ); + name = "Support Files"; + path = "../Target Support Files/FirebaseAnalyticsInterop"; + sourceTree = ""; + }; + 6B9393A4CA4A305C3484A3A17B2C64B2 /* Reachability */ = { + isa = PBXGroup; + children = ( + 96BD2F13CC711654EDB640D31C70F50E /* GULReachabilityChecker.h */, + 0C548F54FE1BE778FB826F39F15D7349 /* GULReachabilityChecker.m */, + DE86873942CD0E37980CB898FD57838D /* GULReachabilityChecker+Internal.h */, + A7D87F6D56345BB13EF2DB61F88CDFD0 /* GULReachabilityMessageCode.h */, + ); + name = Reachability; + sourceTree = ""; + }; + 6BF6E048A3DC10A6EFD4C7DF43594D80 /* encode */ = { + isa = PBXGroup; + children = ( + ); + name = encode; + sourceTree = ""; + }; + 6EFCAACBED3BD0D1EE620CDDD9345097 /* Support Files */ = { + isa = PBXGroup; + children = ( + 42C609BAD1B799B524A14D497B26FE5A /* Realm.modulemap */, + 3E606567B7AC74BAC31708ED387EF522 /* Realm.xcconfig */, + 4E01D63CDAC12AFE0048E676F37EB6DA /* Realm-dummy.m */, + 76264B404DD9DCD2936938912B89838C /* Realm-Info.plist */, + 558D32172C384450CA880936CFFD6474 /* Realm-prefix.pch */, + ); + name = "Support Files"; + path = "../Target Support Files/Realm"; + sourceTree = ""; + }; + 76030F48A7BC49CED96E6254AB27F6D5 /* Support Files */ = { + isa = PBXGroup; + children = ( + 867B6C8244092F35079D098F2AD0327A /* FirebaseAnalytics.xcconfig */, + ); + name = "Support Files"; + path = "../Target Support Files/FirebaseAnalytics"; + sourceTree = ""; + }; + 761E3C039316C71879C9D539183A8A06 /* Realm */ = { + isa = PBXGroup; + children = ( + FE9B49FD53A34A6AB6186DA2B177EED9 /* async_open_task.cpp */, + 61F59F538C624FC9A76C7C50D4FFAA8B /* binding_callback_thread_observer.cpp */, + 9F85C1C2A16FC96066DC6B0C91D73A50 /* collection_change_builder.cpp */, + FDF2E4BDCD2F7C590FFDC8CA02E033F8 /* collection_notifications.cpp */, + BB5AA820C529D5333970A049ACDA2A36 /* collection_notifier.cpp */, + C78202D4B72FA2DC0E1F9DC84DE41760 /* external_commit_helper.cpp */, + A193EF96B2067363B9A36502ACD87C75 /* index_set.cpp */, + A87AF3EEB6D8137F65A401B485906C8D /* keychain_helper.cpp */, + EEFE02DEFA8B914696B221BDB1E55875 /* list.cpp */, + 44F6C89B42122E6D3B49FF370FAE179E /* list_notifier.cpp */, + C699EDB3BC3205372C8415289EA8D2C4 /* network_reachability_observer.cpp */, + 4357D6FFBA0D73ACFEF6882C53C1892B /* NSError+RLMSync.m */, + 271C3E4D470D1279F3BDF15337AC90A3 /* object.cpp */, + DF8DDDA82781E362677B1A61776BCD7E /* object_notifier.cpp */, + F7994B149D97C10B5E161C5B776F54EE /* object_schema.cpp */, + 18D0105B5764541095FBD8092705172C /* object_store.cpp */, + 7313A8ADB88E86743525E015300B75B0 /* partial_sync.cpp */, + 930044AA0BC41A1A935CF3B6450322CC /* placeholder.cpp */, + FB428CC5D255E1711E6656967E9FDF8A /* primitive_list_notifier.cpp */, + F87CA2091793D8973D13571079E16DCA /* realm_coordinator.cpp */, + E0BFE3FA489687282F85C22151BD1515 /* results.cpp */, + 29F6FF2DD8EE8092E26BF602E06784B1 /* results_notifier.cpp */, + 9495BA679912E002DD2FC5B70555A0D8 /* RLMAccessor.h */, + 53006D0A08EF8EA62C89F6BBCD7F0023 /* RLMAccessor.mm */, + 45918AC6C3F376881E09B1CAAC467EF3 /* RLMAnalytics.mm */, + 976C54C9CC751F84860286147F3226AA /* RLMArray.mm */, + D8C6EFDD935E4915732937E2FCB4ECA1 /* RLMArray_Private.h */, + B537E201A244683EF2200A1913CA591C /* RLMClassInfo.mm */, + 24E5A5CFF7D16B79BC71F15A786A9A1D /* RLMCollection.mm */, + B49E2673664F75854392D9A0F630AC28 /* RLMCollection_Private.h */, + 6BDED9776D9CD4DD8E2AAEA8FEE02214 /* RLMConstants.m */, + D655C0E680BC5067C7934A5A0E42A882 /* RLMJSONModels.m */, + AEFBD0F180EED7215C8DA83E8590612E /* RLMListBase.h */, + EF14E699204FD227CC2454D8DC1680A4 /* RLMListBase.mm */, + 411E765A494550268DE9F0C35C384549 /* RLMManagedArray.mm */, + 05B52350B0B17B2722AC405A40762DA7 /* RLMMigration.mm */, + 2BABED548D8BFF82EB08193CC4A45DAD /* RLMNetworkClient.mm */, + 07D6C440C42DA5C3A063CE7C34D1303F /* RLMObject.mm */, + DB20C35765E54E044401445CD25ADB73 /* RLMObject_Private.h */, + 8C3A66B5A5D384170FFA25C394542A3B /* RLMObjectBase.mm */, + 05A33B164E057EE24CF3636BBB916A28 /* RLMObjectBase_Private.h */, + A6BBD745161E87221061671555FCE237 /* RLMObjectSchema.mm */, + 030FB2759BA9D7978C425338F088B8B5 /* RLMObjectSchema_Private.h */, + D0F7E8BBE321F64C9CCE4D3BF4DBC66F /* RLMObjectStore.h */, + 91F88A0114DB504C3CB311BD28930021 /* RLMObjectStore.mm */, + 5F3B738EDC7663C20ECDA7758B5F3CC1 /* RLMObservation.mm */, + C44C40D4B3AF4238A0A8521E9F1C7F77 /* RLMOptionalBase.h */, + 3E74C743B156DCE4E62F27FFCD7EF37C /* RLMOptionalBase.mm */, + 82EB3C9142DB8F0A22E3C30F14B3E806 /* RLMPredicateUtil.mm */, + CB2C765DBA7E08155DA539BA14E76E0A /* RLMProperty.mm */, + C74A86C48C177012E0045DC846302FED /* RLMProperty_Private.h */, + 8F9F3C4BF4F891B92DA1E5A57FDA4B2E /* RLMQueryUtil.mm */, + 54BCAF9B5A0B2D03CAC1FE8411689C95 /* RLMRealm.mm */, + 330910696891AFD35E57ECB357CA3C1A /* RLMRealm+Sync.mm */, + 675DAF0FD379B5DE1611353667173D80 /* RLMRealm_Private.h */, + CD0CB15E468AF2B83D47464CEDFA7E79 /* RLMRealmConfiguration.mm */, + 2E665B2C77520F4B9C4C1FC99B6178F3 /* RLMRealmConfiguration+Sync.mm */, + D31A28FF5B008109A4F47D9CE15E58B2 /* RLMRealmConfiguration_Private.h */, + C2E4BB2E9C451A12AFA6ED8B18E0674F /* RLMRealmUtil.mm */, + 89B30EF9BFCEE26E9674E5CAA2473156 /* RLMResults.mm */, + 1D593AEC5637E10E09C61C612DADEF44 /* RLMResults_Private.h */, + 5D72F806127CD4AB5276FB13A330E7F0 /* RLMSchema.mm */, + 67C9D0962115956B0C47D1FD9F0B6F61 /* RLMSchema_Private.h */, + 86C4E9A16C87A417E65CAB458B461017 /* RLMSwiftSupport.m */, + 87442EC5866C801D59CF010F92961448 /* RLMSyncConfiguration.mm */, + 279EF69F64F33BF43FDC19F468B28D41 /* RLMSyncConfiguration_Private.h */, + A920315F7646D262B91C4778403C67DD /* RLMSyncCredentials.m */, + FB69992458A89E18A6BF4FB596E6E397 /* RLMSyncManager.mm */, + B1259AB3BC80BACF8D0BC98DD0EAD51E /* RLMSyncPermission.mm */, + 598F4D17D3BB845BB5FDDAFDE6D2A423 /* RLMSyncSession.mm */, + 12A29852C18DA5FA1CE5E3A8ECA65A55 /* RLMSyncSessionRefreshHandle.mm */, + 9A6F4A2FF77FD9938CE6ACD8F6295900 /* RLMSyncSubscription.mm */, + 5F28695C975FEEBF7604AEAA004C04A7 /* RLMSyncUser.mm */, + BFADFD13907771FB22980C06CDA26146 /* RLMSyncUtil.mm */, + E7285138962EBA09D3BBBB0A162D6047 /* RLMSyncUtil_Private.h */, + 1EDAF1D55B69A9C4D024EFA96DDE2D33 /* RLMThreadSafeReference.mm */, + 295A4F158D05314703424E49E9949C24 /* RLMUpdateChecker.mm */, + 10723D0293FDDA883CBD050B8BC02938 /* RLMUtil.mm */, + 11C9E2292880FD53065259BE912CA70F /* schema.cpp */, + 106AA954E1BAE8847B35D6285830BF68 /* shared_realm.cpp */, + 0FA9A704FC322DCBCFB439E7E5DA997C /* sync_config.cpp */, + A963882D700987FDF81DE9A1A58D906F /* sync_file.cpp */, + EC3317430C617069EE52653414A6E8F4 /* sync_manager.cpp */, + 49BAE7C9C1895B04BCBF3BD4937DE479 /* sync_metadata.cpp */, + 179923EADFEB236A00F5EB34E838D46B /* sync_permission.cpp */, + 2119A43FC2055B4CAD9C9366757C2F8D /* sync_session.cpp */, + F695AC3ADBB98EC47D8E223652761F16 /* sync_user.cpp */, + 57ECB80AD960D18878792A5FCE8B29A9 /* system_configuration.cpp */, + E9EA6FB992769B58776BCBD623683FC3 /* thread_safe_reference.cpp */, + 3E2AB05A3BCDC8A6EE6F25EB3C50D4BD /* transact_log_handler.cpp */, + 31779D461F51001529ECAEC1E271F0ED /* uuid.cpp */, + 06A412221485D514638E45C0C443BE74 /* weak_realm_notifier.cpp */, + C9DBBF81986552C984F047D58DD511CF /* work_queue.cpp */, + E2306AC79AA970C03A51906F98B92E94 /* Frameworks */, + 267C6F6C48D3BA8001B0DD2841E9B133 /* Headers */, + 6EFCAACBED3BD0D1EE620CDDD9345097 /* Support Files */, + ); + name = Realm; + path = Realm; + sourceTree = ""; + }; + 76DB5A3569D503488AB1F7EA3CF78813 /* FirebaseInstanceID */ = { + isa = PBXGroup; + children = ( + 7B32D929B50BFC710411BE8A3002A85A /* FirebaseInstanceID.h */, + 5CED633A28DB53CEBA59E9F81F542E4C /* FIRIMessageCode.h */, + 4BF75C24DD34A3FE44821FA4D298BC83 /* FIRInstanceID.h */, + D3A92C87E49467B77970C7C21A6EF7D9 /* FIRInstanceID.m */, + 7A9E34FE56E60AFC7C356826A0D0E8E8 /* FIRInstanceID+Private.h */, + 1474818897D4EF99A548A3FE8D382197 /* FIRInstanceID+Private.m */, + 12A1667F60BAFB5EFB620E87EF4CA3B1 /* FIRInstanceID_Private.h */, + BEAF03C0C4D7B2341CC75989723A9911 /* FIRInstanceIDAPNSInfo.h */, + AC6DF125BF38E2E84E505D8CA813A0D1 /* FIRInstanceIDAPNSInfo.m */, + 8F1BF833B6F67406AF5AEE71A69B09A7 /* FIRInstanceIDAuthKeyChain.h */, + 0D77224971A73956E499DBACD9269CC0 /* FIRInstanceIDAuthKeyChain.m */, + 1F73B89D31C30727BAC5F06912071FD9 /* FIRInstanceIDAuthService.h */, + DB56C958965C0D1A15235E6596186AA8 /* FIRInstanceIDAuthService.m */, + B9DA578BE4C64C070DD8C98AAA98E52F /* FIRInstanceIDBackupExcludedPlist.h */, + 1AB2E2F6F5F01C6A4F17BFCBA264DAA9 /* FIRInstanceIDBackupExcludedPlist.m */, + D06B3B97E4AAD489DE7ACD5A5CB33145 /* FIRInstanceIDCheckinPreferences.h */, + F07D4A9232C809E948120E7102776882 /* FIRInstanceIDCheckinPreferences.m */, + 3C32659A8FC6851EE6D06CCC7EAAC1D2 /* FIRInstanceIDCheckinPreferences+Internal.h */, + 7409DF4877493F28BE503A9565A47E4F /* FIRInstanceIDCheckinPreferences+Internal.m */, + 34E1813A0BA2F284FD8E92D086FCDB79 /* FIRInstanceIDCheckinPreferences_Private.h */, + 8ACDCCAD213FD226B322D9CCFFCAAA09 /* FIRInstanceIDCheckinService.h */, + C2EADE68D084997E314F18E639061AD1 /* FIRInstanceIDCheckinService.m */, + EF5376E76A1797CA02135B3CB20439F9 /* FIRInstanceIDCheckinStore.h */, + 2F55AA6C4C4C66BDF9695A3801D2CAC0 /* FIRInstanceIDCheckinStore.m */, + DC7FFB1CAD9385389EC1B5E7A719010B /* FIRInstanceIDCombinedHandler.h */, + D2C9C3DA33A45CC253454B3C3D103093 /* FIRInstanceIDCombinedHandler.m */, + 2B416EEE307E63FA4BE4FE698193B180 /* FIRInstanceIDConstants.h */, + 09304335678AA1BA258043917B4E23D4 /* FIRInstanceIDConstants.m */, + 2563B7FA0AFA76EF66CE71EF56FCCD68 /* FIRInstanceIDDefines.h */, + 92CE3C73F6820C05E0B7AD90181B0666 /* FIRInstanceIDKeychain.h */, + FC8F7429129DDE3FE3CD5587FC76366F /* FIRInstanceIDKeychain.m */, + 7ED3622365FA02BAA65DEF9DB35A358C /* FIRInstanceIDLogger.h */, + B4BC511ECBBFA8ECB40030E727A947D3 /* FIRInstanceIDLogger.m */, + 163DDF9C945F142199DA4A6A64290EF3 /* FIRInstanceIDStore.h */, + DEB0EF341F77551CC9EE09C1A7E3863D /* FIRInstanceIDStore.m */, + 8F50523F75B8500F411C5A032D73DE46 /* FIRInstanceIDStringEncoding.h */, + 018A67795D376839684483C5DE57E63F /* FIRInstanceIDStringEncoding.m */, + 8BB32016F6107456B9585FD31C1C63BB /* FIRInstanceIDTokenDeleteOperation.h */, + EE76699AE0009E7286DB0C500CDEA36B /* FIRInstanceIDTokenDeleteOperation.m */, + 807D7C288E07C2ADE943E1B2DB35B461 /* FIRInstanceIDTokenFetchOperation.h */, + 548A09288743BC6316E5BF2D9B619111 /* FIRInstanceIDTokenFetchOperation.m */, + 710AF0CF26954951B96F5B4D3FB61552 /* FIRInstanceIDTokenInfo.h */, + C0D584A02FC056531E03BF78F097310D /* FIRInstanceIDTokenInfo.m */, + 4911B687FA7409D98B78D92E8FDD1F59 /* FIRInstanceIDTokenManager.h */, + 1FD48142CA1F24926D4436B30CFC3443 /* FIRInstanceIDTokenManager.m */, + 192E47A30A68811FE88255BE422DA582 /* FIRInstanceIDTokenOperation.h */, + 469C011AA04EEACFF286EA5E284DE545 /* FIRInstanceIDTokenOperation.m */, + B8FAB6CC1DDAAF761AAE56B6FCF360D4 /* FIRInstanceIDTokenOperation+Private.h */, + DD1AA8BF76E48EA74EDCCB561AE71A0A /* FIRInstanceIDTokenStore.h */, + 37CA0488689836CFBCF7D357059D5DF2 /* FIRInstanceIDTokenStore.m */, + 5F5B97326C121B9F44A5F24C0E2D338D /* FIRInstanceIDURLQueryItem.h */, + 724B4A64760807F69FE610EA5DB525A7 /* FIRInstanceIDURLQueryItem.m */, + 0D15BFCCA59443379D0E59B6CD009FE3 /* FIRInstanceIDUtilities.h */, + B8201CFA57F59444698C37217A4ABEA8 /* FIRInstanceIDUtilities.m */, + 4BBC28B17DDA8EBA784B3D214C810B32 /* FIRInstanceIDVersionUtilities.h */, + 3B12B0B698ECE39D5E6EDB89687CCC6D /* FIRInstanceIDVersionUtilities.m */, + 03123A90B6E96B5F3F945E9576200603 /* NSError+FIRInstanceID.h */, + AB6E896A6AB3A2C9129B37B768597237 /* NSError+FIRInstanceID.m */, + 4B8DE53DD08F4A88CE56E64E95F1062F /* Support Files */, + ); + name = FirebaseInstanceID; + path = FirebaseInstanceID; + sourceTree = ""; + }; + 7CC70DDEEAD5258DAEE93D10FCCB62EE /* FirebaseCrashlytics */ = { + isa = PBXGroup; + children = ( + 709693C460EEE74D5CD70B2C864907E7 /* dwarf.h */, + 6277C8B46289F5D129CC3124B79552D8 /* FIRAEvent.h */, + 8B7352E913168EC86E1833303D5A8E3F /* FIRAEvent+Internal.h */, + 0ADFAE923FC1E34FC913A0E76EC991AE /* FIRAEvent+Internal.m */, + 844026B8D96F62721AEEB3A2FD9310E2 /* FIRAValue.h */, + FC47FF55CCA6DB295945AE33E25407CA /* FIRCLSAllocate.c */, + DFE1824C7288A210B76120880BFD42B5 /* FIRCLSAllocate.h */, + 2C1F67FAB1701104A2037545DAF700E1 /* FIRCLSApplication.h */, + E08E083442F40E8D8E3D2C7EAA72A775 /* FIRCLSApplication.m */, + 912B6075A0BB9F3FC276D8ACE49F2D74 /* FIRCLSApplicationIdentifierModel.h */, + 4813F514E16E244D3B40BBF1CB195098 /* FIRCLSApplicationIdentifierModel.m */, + C9A9708F1BC72EBB6C42F9D332BAD6D8 /* FIRCLSAsyncOperation.h */, + C0730316DE3700CB55969541D5468F24 /* FIRCLSAsyncOperation.m */, + F8F4FD2169C43EF716DF13A6017EC76E /* FIRCLSAsyncOperation_Private.h */, + 0089CEA96A6224BCF47F57FCBAACD204 /* FIRCLSBinaryImage.h */, + FC3C0FB46A22497DE200AAD3C51B3F5D /* FIRCLSBinaryImage.m */, + 7D397FD2F6AAD22EA8990CEB78CA0371 /* FIRCLSByteUtility.h */, + A542BD074662CB4F26AFC7358EE58123 /* FIRCLSByteUtility.m */, + D4E14494BF96294FC9B9D9A09C188386 /* FIRCLSCodeMapping.h */, + 012F3963A2B15DDF0634F334AD78F02C /* FIRCLSCodeMapping.m */, + 2FB0B6C28B89775B2C2465949315371C /* FIRCLSCompactUnwind.c */, + 63E633213C15674BF6EE752DA839F4D0 /* FIRCLSCompactUnwind.h */, + B9DBE0FD749B655F84CDF7BE54E13699 /* FIRCLSCompactUnwind_Private.h */, + 76E8349BE330B381533D03B9E06C740B /* FIRCLSCompoundOperation.h */, + E6DD941D98E1FE39ECB7E30424EB0AF6 /* FIRCLSCompoundOperation.m */, + 6509B500C8E4B0D9AA9C1F9A8E6E9991 /* FIRCLSConstants.h */, + 9534287083E1F8DC40D023CAC0E0F310 /* FIRCLSConstants.m */, + ACB46B34666BE45BFC53AC7D3C99A144 /* FIRCLSContext.h */, + D69EC7F7067D0D537E187FD578991C9B /* FIRCLSContext.m */, + 7C5D1ABA49A70E905CD66FC7B1E85BFD /* FIRCLSCrashedMarkerFile.c */, + 8690E87E0C6FB58DBA5D4B6476077A82 /* FIRCLSCrashedMarkerFile.h */, + AC0C18B2A33D69724E42FCD30CF08905 /* FIRCLSDataCollectionArbiter.h */, + D16E167B93D721F81CD63CB4768D0DD3 /* FIRCLSDataCollectionArbiter.m */, + 97C63B265EC2B20EAE69CA93369383DF /* FIRCLSDataCollectionToken.h */, + 303B212D272E5495BC2D7E46FA8627EC /* FIRCLSDataCollectionToken.m */, + D94D8E190DDC32833CD32F9665486387 /* FIRCLSDataParsing.c */, + 1943EA82083528978789B90143A825F6 /* FIRCLSDataParsing.h */, + 62FF18EBA9F70278FDEB4F9759A9D445 /* FIRCLSDefines.h */, + 6CABE765FC1C335EF38A758518B37CD0 /* FIRCLSDemangleOperation.h */, + 36FE3A34041E5EC9A58F9DCF49A0AC21 /* FIRCLSDemangleOperation.mm */, + 202FECE0B01AE375A5581D0AF0CD65DC /* FIRCLSDownloadAndSaveSettingsOperation.h */, + 758A2BB7C2B851B748A469A9E8B11F8B /* FIRCLSDownloadAndSaveSettingsOperation.m */, + 67593AB87D75C2524D1512196BCDF77C /* FIRCLSdSYM.h */, + 30E22880820C5CA49AE883F198B59C88 /* FIRCLSdSYM.m */, + BCAA4BD8CF79CDFBE112EEAB21E22806 /* FIRCLSDwarfExpressionMachine.c */, + 80F193A96FCC90BD0DCA5A2A7ADC9F83 /* FIRCLSDwarfExpressionMachine.h */, + 7638558D1BBBB6500FC2DB44CEEC0BE0 /* FIRCLSDwarfUnwind.c */, + 88D5ED9C4C466DC197E025B5D6B9BEC4 /* FIRCLSDwarfUnwind.h */, + 313BFED252E82ED3C22F7B4C5A62C8E3 /* FIRCLSDwarfUnwindRegisters.h */, + 4EB7F0FA43F7178842857FB5902D9EF6 /* FIRCLSException.h */, + B3C87A46F96166E181D907BACBDF1D34 /* FIRCLSException.mm */, + 21901FB1986D506361381CD549E3C54C /* FIRCLSExecutionIdentifierModel.h */, + 98533E594EC57133B3C68EF150266FDD /* FIRCLSExecutionIdentifierModel.m */, + EF852E414686D1C085AA9A455576B845 /* FIRCLSFABAsyncOperation.h */, + C0EE0F9DF8F3330249489D1E4F82FAFC /* FIRCLSFABAsyncOperation.m */, + 280E81411122E10A4A03FF10AD9C6A26 /* FIRCLSFABAsyncOperation_Private.h */, + 066EF248F340E2465710C78E9B311ADE /* FIRCLSFABHost.h */, + BB8555579C24393A6EB509F5D1FBD10A /* FIRCLSFABHost.m */, + 81A7B76B62951984CC65B19C4A945977 /* FIRCLSFABNetworkClient.h */, + A573EBB34B32D9E475532628683BF8CF /* FIRCLSFABNetworkClient.m */, + 4A88EA3ED2B76186B85C1671B3D3CB89 /* FIRCLSFCRAnalytics.h */, + 2FA369AC0E257B40E40EA47FBE09AF93 /* FIRCLSFCRAnalytics.m */, + 8FE9A7290B315D1210F54B1A597278EC /* FIRCLSFeatures.h */, + 02DC9AC9F992D94B7D771EF46B451D7D /* FIRCLSFile.h */, + 8AEBF9A44F2485C9B53763766349031A /* FIRCLSFile.m */, + FB06D20089CCA78A808F637BF749DF6F /* FIRCLSFileManager.h */, + A9E802A74840CABA570E678E66E944C1 /* FIRCLSFileManager.m */, + 2B92BE5EC3F82D429268873BAFBA0062 /* FIRCLSGlobals.h */, + C136D124912241858A8E583D0415F25E /* FIRCLSHandler.h */, + C29418203303D3DD410EE8C349774807 /* FIRCLSHandler.m */, + 82269596064B52D6E7E7F1BFFC8FEC78 /* FIRCLSHost.h */, + A73AC529EF63101E9F604F56970CF8A2 /* FIRCLSHost.m */, + 03BD84CE35C53C22848C4F37AF990917 /* FIRCLSInstallIdentifierModel.h */, + C9FD65E8C3CE0766CDC9923BD322155C /* FIRCLSInstallIdentifierModel.m */, + DBFDE7C1CA6BDD86C3C60C3DF7D84759 /* FIRCLSInternalLogging.c */, + 20F65E754FAE83D2F6BC09ECC325D367 /* FIRCLSInternalLogging.h */, + 63CD3209EBE09936927BED09C9BAD0E7 /* FIRCLSInternalReport.h */, + 6BB5E55A11E483CC60C9D5718C2B62FB /* FIRCLSInternalReport.m */, + 7C0E929DD992CE13605482A3504B4A32 /* FIRCLSLogger.h */, + 454E843E89166D83EA808A0C6441D07D /* FIRCLSLogger.m */, + 0137250CDC9EDE8A2D5EBBA6FD7658DA /* FIRCLSMachException.c */, + 02725477E4502DA51C0693E502CE5CD8 /* FIRCLSMachException.h */, + 14C0953A445D4BE60522EB22E077A192 /* FIRCLSMachO.h */, + B7B3506EDC4CA84B41DC6B9FE7582C58 /* FIRCLSMachO.m */, + 619E5249FFEF1993E7929824BF208BBC /* FIRCLSMachOBinary.h */, + 6B33D0223995B0E6C58FB40F012E2D1F /* FIRCLSMachOBinary.m */, + 5432F4FAF7753183AD5FE130957921B2 /* FIRCLSMachOSlice.h */, + 0E2522A9B4A9665409FB203B0AEEC844 /* FIRCLSMachOSlice.m */, + AFFD295CAD2BDB5B43E29F61E1D4D7BC /* FIRCLSMultipartMimeStreamEncoder.h */, + 4B38D7E5D75C23FEADEF10B01ABF6096 /* FIRCLSMultipartMimeStreamEncoder.m */, + F90DF5F8EEF6B3A7BF300E04E72545DD /* FIRCLSNetworkClient.h */, + DA4EBA778B22B26ECA6257D01119579E /* FIRCLSNetworkClient.m */, + 3AC054B5DF290D1F23F402CB55328FE6 /* FIRCLSNetworkOperation.h */, + 0775707B0243939007657C36EC7F3688 /* FIRCLSNetworkOperation.m */, + B9589DD728D797408CD139CE3274709B /* FIRCLSNetworkResponseHandler.h */, + 7BEFF599A831C23E9164164D5AE2DC64 /* FIRCLSNetworkResponseHandler.m */, + 3EBCEDAB2FDE3140557CB10ACEB94398 /* FIRCLSOnboardingOperation.h */, + D46D547E6B2FE36F4774C2E5480EBA7C /* FIRCLSOnboardingOperation.m */, + 379C4299BB9ED1CBA0A5405CD9449C81 /* FIRCLSOperation.h */, + 10B86ED0811ED153ABD059C908466AF0 /* FIRCLSPackageReportOperation.h */, + E3E60FB6268D822CE2681F2A2F1CF1CB /* FIRCLSPackageReportOperation.m */, + F5C89F11ADEAD50F827C26186992AEA2 /* FIRCLSProcess.c */, + E9A7DEA3E358EF81F1F1348372BEB733 /* FIRCLSProcess.h */, + 10D97BB079FDEB279746D6D296CE39AF /* FIRCLSProcessReportOperation.h */, + 76B8D4AFE9CA027E2EFC28B18F209340 /* FIRCLSProcessReportOperation.m */, + DC07A8DDEDC9E2EED7ADB58A9B11CAB7 /* FIRCLSProfiling.c */, + 783508FA1E09FE34ED2409D5E4E1B1C4 /* FIRCLSProfiling.h */, + 1F2ED39C151AD216F2DA816B638F2A59 /* FIRCLSReport.h */, + A87FC113ACCF64E5B68D982516EAE67A /* FIRCLSReport.m */, + E22ABAF13C46FCAA209FA07662CABF93 /* FIRCLSReport_Private.h */, + BF4725A1936C6270B4AB0B1DBB7B4A41 /* FIRCLSReportManager.h */, + 051905C1D8206444341EEDFEDE3E5B02 /* FIRCLSReportManager.m */, + 535BC6C89C9E415076459A3A026CB580 /* FIRCLSReportManager_Private.h */, + E501EC074F41F40181C1B9420DF549C9 /* FIRCLSReportUploader.h */, + 32C8B2FFBD9350F82EB2C05DD3F08DE1 /* FIRCLSReportUploader.m */, + EEF4CC836E0FC4BCEB6ACE36C4028389 /* FIRCLSReportUploader_Private.h */, + EED5147D9610B2B66EC2AAD7285E96B5 /* FIRCLSSerializeSymbolicatedFramesOperation.h */, + EB9E162ED5D86B7F2EB30AE42FAEE0BB /* FIRCLSSerializeSymbolicatedFramesOperation.m */, + D62356771945F7BF821F0BB694EC7623 /* FIRCLSSettings.h */, + 86DB482A924060A7B03BF8E7289A0C85 /* FIRCLSSettings.m */, + 3BE0225CF21C0F1263F67167183C57B7 /* FIRCLSSettingsOnboardingManager.h */, + 1366E4DEDA780B6EDEB38EFC163BF5E1 /* FIRCLSSettingsOnboardingManager.m */, + 2F472D69D8352A62D70BBD53662D3F22 /* FIRCLSSignal.c */, + 44C335C2BC7873351D63E1C641F7DA5E /* FIRCLSSignal.h */, + 6BE53798496284F7898DAF699207435F /* FIRCLSStackFrame.h */, + BFFCEAEC4E3AEC71E8ED88F86C899DA1 /* FIRCLSStackFrame.m */, + 95B4BB665D979FEF54288EC9FB68B57F /* FIRCLSSymbolicationOperation.h */, + 6233E271ED15D5C5A037C7BC902D9AA2 /* FIRCLSSymbolicationOperation.m */, + 12F784E5C9AC8C284E63D4AF3B6F2A19 /* FIRCLSSymbolResolver.h */, + E268FACC2A09D695FA542D9C9D6B902C /* FIRCLSSymbolResolver.m */, + 77737B379095DF2CD503F701F0598089 /* FIRCLSThreadArrayOperation.h */, + 19B99E5574E2EFC09D9C01055646B8C9 /* FIRCLSThreadArrayOperation.m */, + 3F348EE51DA0A7FF10B6342F43FBB6E4 /* FIRCLSThreadState.c */, + A528EB78B55C3F9AB1A8B7B2CF02DE85 /* FIRCLSThreadState.h */, + C4077AE6A29830CC9E7134E0563DC629 /* FIRCLSUnwind.c */, + 03925A4568C20F619E76B1D91ACDC297 /* FIRCLSUnwind.h */, + 1266E3968D29717728D769C77E8A3954 /* FIRCLSUnwind_arch.h */, + F8ED8EDB4332628051191DB6D91A761D /* FIRCLSUnwind_arm.c */, + 145CEB14F38B1828A56B5E6681B5162C /* FIRCLSUnwind_x86.c */, + CEE91D635068604C4F9104A22986350B /* FIRCLSUnwind_x86.h */, + 29B851296EE421C0D17845278B4F85D9 /* FIRCLSURLBuilder.h */, + 742340252C9F30970E96B4A993368D3A /* FIRCLSURLBuilder.m */, + F895732C10786779FB3B5D978024B17B /* FIRCLSURLSession.h */, + 2A42E18B44FC16B56095378382D15E84 /* FIRCLSURLSession.m */, + 96DF39B293090E7DD0FB3B272833B75B /* FIRCLSURLSession_PrivateMethods.h */, + 5EC5794E1FB56C2379167138CEA77AA2 /* FIRCLSURLSessionAvailability.h */, + 1DF3C20AD9061C27A056CA943603808B /* FIRCLSURLSessionConfiguration.h */, + F99C1F5CF233E9ACD488CF17BF244A10 /* FIRCLSURLSessionConfiguration.m */, + 28E542ED56611F4DE0D324E090838C02 /* FIRCLSURLSessionDataTask.h */, + DAF1673F2959F3DA04D64CD47E7140F2 /* FIRCLSURLSessionDataTask.m */, + 6DDD8062FCA61B54A3D2C4FDBBB036D0 /* FIRCLSURLSessionDataTask_PrivateMethods.h */, + EDF84E0100FAE3C3031F23D19DC20C35 /* FIRCLSURLSessionDownloadTask.h */, + 6275266DDE055A2E1BCB1626FD4239DF /* FIRCLSURLSessionDownloadTask.m */, + 3ADEC01442125E8A31963729EC8049FB /* FIRCLSURLSessionDownloadTask_PrivateMethods.h */, + A80776476F1BB3131E33DC6C63A170C8 /* FIRCLSURLSessionTask.h */, + 40E7FAFFEE1A98D4288EFF0823FE7396 /* FIRCLSURLSessionTask.m */, + 1F10BD67D2D9EA34B8A06A9EA2DCE036 /* FIRCLSURLSessionTask_PrivateMethods.h */, + 9828526EFD32EFD531EBBB2E9D6DB8B3 /* FIRCLSURLSessionUploadTask.h */, + 504885B54A8E74421A4A94921F685393 /* FIRCLSURLSessionUploadTask.m */, + CAE52D190FDACA01E3E8E100CCCFFB71 /* FIRCLSUserDefaults.h */, + F50E4E22F95A26E70C681B16D0148E77 /* FIRCLSUserDefaults.m */, + 07C192291C6A1B6CCFBA7391244C026D /* FIRCLSUserDefaults_private.h */, + 19B43B6463DEB5A47027B4084F472659 /* FIRCLSUserLogging.h */, + 96A2C040669D3F47F52262A721860C1F /* FIRCLSUserLogging.m */, + 16FA90EBB6ED4FCC27E0FB0DEACA5A7C /* FIRCLSUtility.h */, + 1293543EA5A1A924D5F8338EBA12CC16 /* FIRCLSUtility.m */, + D0EEAEE838DEDF959DD3927587F000F9 /* FIRCLSUUID.h */, + C1411B2CA010177EB738082A892A6DD2 /* FIRCLSUUID.m */, + 747AF31DA5C0F0B63B2BAA9946DE6A54 /* FIRCrashlytics.h */, + 4BA2CDB5CB3429FAB9DDA4F6DC6C5283 /* FIRCrashlytics.m */, + 4D756894BE4E37E0CA32A46F1BB2FDD9 /* FirebaseCrashlytics.h */, + 1A807C24671F934A291305C4632DBAD0 /* Support Files */, + ); + name = FirebaseCrashlytics; + path = FirebaseCrashlytics; + sourceTree = ""; + }; + 7FDCDF4E7F02823D751D57947B57EB65 /* Core */ = { + isa = PBXGroup; + children = ( + 5DADE1727943B7611BB868DDDB6DEEEE /* AnimatedImageView.swift */, + 572DD60020066485B722152AB1526E6C /* AuthenticationChallengeResponsable.swift */, + 3A9D78AA155958A76A77ACF07046CD17 /* Box.swift */, + 62F021E6B2E996937A7F48891975F654 /* CacheSerializer.swift */, + 5EFCCC9B6FCB73B30D97FD7D962050D3 /* CallbackQueue.swift */, + 8F7A3106176C2E7089A28829FF0BE584 /* Delegate.swift */, + BE9D69EB6F70F361D33FF4517C4151F6 /* Deprecated.swift */, + AE5C36747E6121753BC7D0C5E556FADF /* DiskStorage.swift */, + 9EF5F75352A2E6D56BCE252ED1AA1321 /* ExtensionHelpers.swift */, + 36981A91C6866AF9E85E651837C03BF0 /* Filter.swift */, + 0BFAE93D92AAB0A65643967B49DA3AFE /* FormatIndicatedCacheSerializer.swift */, + EACA8C55A037208CD7F7723FD488F2CD /* GIFAnimatedImage.swift */, + 967AC3B13881F4CA2CB6CE9C55146F69 /* Image.swift */, + 63EDC4B1811281A589A7D31C6DB07ED6 /* ImageCache.swift */, + D7434D3830778A43B297F00140DEDBB3 /* ImageDataProcessor.swift */, + 0791472EB1551B2208F3B22E2A0E9F04 /* ImageDataProvider.swift */, + D1FDE17F9BA2AE72C1C1EFE70DC080EA /* ImageDownloader.swift */, + 19C52BF106E4431FAE1B6E68ED7F69ED /* ImageDownloaderDelegate.swift */, + C64A992EF6C1A053145E1FE49B94CBA5 /* ImageDrawing.swift */, + DABD5D4024C849A5EE75514C6854A95B /* ImageFormat.swift */, + E6E362D77314A21F10D9232913CED419 /* ImageModifier.swift */, + C2173032F4DC6AB031930F5AEED697F0 /* ImagePrefetcher.swift */, + D27BE1A976504CC23970F20939A322FB /* ImageProcessor.swift */, + A288181DC60985C963066396CF083E72 /* ImageProgressive.swift */, + CA7C19E231F44F4302565226ECB05515 /* ImageTransition.swift */, + 1F705824450B4EFCB691B62F12F6B58D /* ImageView+Kingfisher.swift */, + 118B6DE2F27EAA4902B2FBB1EA159C28 /* Indicator.swift */, + F6AED0E2005671F584BB2AEDF2B27BCF /* Kingfisher.h */, + BABC8EB442B76A4399A427E8932DB865 /* Kingfisher.swift */, + 667D42BF455C022A7D8EFF143376525C /* KingfisherError.swift */, + 8B060D6A0ACB153C3A6519940E3A2F8C /* KingfisherManager.swift */, + 4D7092FB504C43E51EBF941876B70D43 /* KingfisherOptionsInfo.swift */, + 80D84EB3AF30412F46680999C05D159D /* MemoryStorage.swift */, + 565075BC42365FE68F251EE460368715 /* NSButton+Kingfisher.swift */, + 40DBA65B3D00E4A32BBE97886A532D4F /* Placeholder.swift */, + 41F3CA4CED72C78893F58B11B23859E2 /* RedirectHandler.swift */, + 16238A64917CEB3E86D222482A856898 /* RequestModifier.swift */, + 7A411B5DA7E914F2C40F92038EA7A436 /* Resource.swift */, + DA0F982B217F5CD21D3B5D280E93EDBF /* Result.swift */, + B95848C5B6C50139C9D280F109737601 /* Runtime.swift */, + CDB84EA8E14223D9B5C902DB4D179F57 /* SessionDataTask.swift */, + 74471D3212EED130E3D5DD72463B542D /* SessionDelegate.swift */, + 41FB9FC4D66CF7E99640E00C16D6C174 /* SizeExtensions.swift */, + 5DF25EF9B5A1097A37C5DCCE3ACBDFE0 /* Source.swift */, + 0F0C336156B678EF35851FE016FEAC04 /* Storage.swift */, + 8E9ED28562D1435FA27EF70F70D9AAE0 /* String+MD5.swift */, + 51EAA16DB197D1E1AF196BDAF5D6EE86 /* UIButton+Kingfisher.swift */, + 5AB59EF111D16A4E566C8249C4094FEE /* WKInterfaceImage+Kingfisher.swift */, + ); + name = Core; + sourceTree = ""; + }; + 84CC859D3297B8C5CDC447AC53FCF163 /* Support Files */ = { + isa = PBXGroup; + children = ( + 2E2E8CF85E48F9328C0DF12B83D1C95C /* nanopb.modulemap */, + 24527F70FC49CD3DC18B482D54CEBBCC /* nanopb.xcconfig */, + 9B189F0E4B0777516E298F26F55897F2 /* nanopb-dummy.m */, + DA7348C611F9C4656B90C46695F7806B /* nanopb-Info.plist */, + 48340EDA59FB01AE26CE1757E9B8002C /* nanopb-prefix.pch */, + 95C5F958FAA51CD9AFACEAFD6323D4F1 /* nanopb-umbrella.h */, + ); + name = "Support Files"; + path = "../Target Support Files/nanopb"; + sourceTree = ""; + }; + 85F5D6EA7349B0BB71C997E2C5FC91D3 /* Support Files */ = { + isa = PBXGroup; + children = ( + 3C55C132C630B644EE7681983CD71CBE /* FirebaseCoreDiagnosticsInterop.xcconfig */, + ); + name = "Support Files"; + path = "../Target Support Files/FirebaseCoreDiagnosticsInterop"; + sourceTree = ""; + }; + 8669867928EF515F10AD3EF6D10FDA1D /* Alamofire */ = { + isa = PBXGroup; + children = ( + 35668BE5CB78FFDA28FD2AE333563DCA /* AFError.swift */, + 9CDFD241B0E81B85EB547ADEBDB5D6B5 /* Alamofire.swift */, + 7C312DD383F490C02B5A71E38CF3802D /* DispatchQueue+Alamofire.swift */, + 823EEAF21299E8D98B01448277270E70 /* MultipartFormData.swift */, + 5914E005B72AC06DA8A3F4F516C69DC0 /* NetworkReachabilityManager.swift */, + CCAB7E316CA26642BFF8CC2BDB62A008 /* Notifications.swift */, + 3AC09B19CF882809F37562A3B14201EF /* ParameterEncoding.swift */, + 2AB5C720F820C5225F158C693B9DA549 /* Request.swift */, + 4F76C10D403D26FC1854D798DD54CFD5 /* Response.swift */, + FE96C47F2560AA0D7B152AAB26C9E931 /* ResponseSerialization.swift */, + 7C84AB0BC56B89E39E4251B7DF1DBF4F /* Result.swift */, + 13ADE9639DE325D6DB8F9E46AA6FCCD3 /* ServerTrustPolicy.swift */, + 8E525CD0D67BB1B0F3B7F54506731294 /* SessionDelegate.swift */, + 0AD0CE2461D5D5215E0EC8F0E909A922 /* SessionManager.swift */, + 9966DDAE4F25C29B629BF069C18E98F2 /* TaskDelegate.swift */, + 5E6A13DEC67A77C4FE36FE85C0086A25 /* Timeline.swift */, + 37BAD9B7E3A3D8E9AA3D878E3A56DEB4 /* Validation.swift */, + B5D5A0154650483FA77C00BB29C09F32 /* Support Files */, + ); + name = Alamofire; + path = Alamofire; + sourceTree = ""; + }; + 8CF9017241A27FFB9D0B76BF1A4A77BD /* FirebaseAnalyticsInterop */ = { + isa = PBXGroup; + children = ( + AE1090589F6CFBD5A3E3BD3C0DDDDCA2 /* FIRAnalyticsInterop.h */, + B3691C4DC1B24C1A062A1D4EDD79DAC0 /* FIRAnalyticsInteropListener.h */, + 19A7929975E4DB7365AB0B4BEF5873BF /* FIRInteropEventNames.h */, + DFDF0BE506231221E57B8D863D8C9820 /* FIRInteropParameterNames.h */, + 6A00FD0646FAC06C56634B3BFFA5B6A7 /* Support Files */, + ); + name = FirebaseAnalyticsInterop; + path = FirebaseAnalyticsInterop; + sourceTree = ""; + }; + 8DE53F4B5146A76A81008C5A5A02266E /* FirebaseInstallations */ = { + isa = PBXGroup; + children = ( + F0B703AD9011F08B3C8BC98CD60D5E12 /* FirebaseInstallations.h */, + 57B386B4636728846C6CC917511CD61B /* FIRInstallations.h */, + 79565DC1642A3650F2C7D67B6325765D /* FIRInstallations.m */, + 59C49C4C92798FC3200909C43B9F8857 /* FIRInstallationsAPIService.h */, + A9E64A8A8B133D02DAB3421C08C97BDE /* FIRInstallationsAPIService.m */, + 842BD4B331F68A36CEB4311001E2EFD6 /* FIRInstallationsAuthTokenResult.h */, + A6CA61A608F721178720B5C7620B2CB0 /* FIRInstallationsAuthTokenResult.m */, + A84C72B92CB67258C3060F4E4AF55F33 /* FIRInstallationsAuthTokenResultInternal.h */, + 8E79B063CE064DA7B1933274A8DDE290 /* FIRInstallationsErrors.h */, + EB90112817267C3AF191E87188202873 /* FIRInstallationsErrorUtil.h */, + 8994F6A0E3A70E01B98277557180A156 /* FIRInstallationsErrorUtil.m */, + 3824719E1E7D4113D6F9CEC8E3DC27E9 /* FIRInstallationsHTTPError.h */, + E74E8ABB69B7B8765F18F4A590BF5085 /* FIRInstallationsHTTPError.m */, + B2C3C0B84E27EFD7128F49252167B459 /* FIRInstallationsIDController.h */, + 4BFD56E0F5FBE4CD874A18EBA3C08777 /* FIRInstallationsIDController.m */, + 2D5F1AEA9CDB1B1C269E91CB1B3F3445 /* FIRInstallationsIIDStore.h */, + 70B162E996982D9DC5A9E028820DA000 /* FIRInstallationsIIDStore.m */, + FA4874F0784D0B918BB0F130BDE7FDBE /* FIRInstallationsIIDTokenStore.h */, + E8857EFCE5796CF7471091CA1188C488 /* FIRInstallationsIIDTokenStore.m */, + 1269A06F7EF7679BC8BCC0C2B590766C /* FIRInstallationsItem.h */, + 40E5AB8EBAC57F183B9FCD5346C75EE8 /* FIRInstallationsItem.m */, + 58B38973A7F5AAF8DEB3EEC6F846C4DD /* FIRInstallationsItem+RegisterInstallationAPI.h */, + 892F390C60B74AF78829AB20BBB9CC5D /* FIRInstallationsItem+RegisterInstallationAPI.m */, + ED93F5CEB5984D2C7820803FB14E33B9 /* FIRInstallationsKeychainUtils.h */, + B2B9CFF9F6610ED23E661A8837C20DA3 /* FIRInstallationsKeychainUtils.m */, + 7C7A1E38C56A5310BDCCF9B80FF7BAF1 /* FIRInstallationsLogger.h */, + 4678982B233A0BCD199D2DF9A374B3D2 /* FIRInstallationsLogger.m */, + C8BC987527B0F350D97D5F43DE756CE6 /* FIRInstallationsSingleOperationPromiseCache.h */, + D6B1422B8EBEE11D856B66B0E96F117F /* FIRInstallationsSingleOperationPromiseCache.m */, + 4A60C4735224475581B5618F0141BFDC /* FIRInstallationsStatus.h */, + 98F2168BA4030E0EABD5F6681F96B79E /* FIRInstallationsStore.h */, + E2B1E88FB83B19CC7E6207BDD91BCE75 /* FIRInstallationsStore.m */, + 00D096F4988C4E2FC88C5A60A39A8359 /* FIRInstallationsStoredAuthToken.h */, + EC3131B267B53BBFA83377DC278EFCE6 /* FIRInstallationsStoredAuthToken.m */, + 846E4DE7CE26B7FBA379AFC0956A4BD3 /* FIRInstallationsStoredItem.h */, + 5651E58B832E9102932FB169B9827F12 /* FIRInstallationsStoredItem.m */, + 53FF4D24482305D95F31AEAE76AABAF1 /* FIRInstallationsVersion.h */, + D02AD4C8F8482C50EB8A6DF81E4B7DA6 /* FIRInstallationsVersion.m */, + A384F92A096C490D8F388733A9524664 /* FIRSecureStorage.h */, + BAB0E9E2A5876C4EF0CF337514EDCF06 /* FIRSecureStorage.m */, + 358E92AC09F5448C6D0D82A3356DC9A6 /* Support Files */, + ); + name = FirebaseInstallations; + path = FirebaseInstallations; + sourceTree = ""; + }; + 9400A0F5F0D5C5EFE436637FB5730866 /* FirebaseCoreDiagnosticsInterop */ = { + isa = PBXGroup; + children = ( + F34228DF413EBF2A6E96A822D548A2E7 /* FIRCoreDiagnosticsData.h */, + F4C35D3B830DD9704ACF0BC841B6A465 /* FIRCoreDiagnosticsInterop.h */, + 85F5D6EA7349B0BB71C997E2C5FC91D3 /* Support Files */, + ); + name = FirebaseCoreDiagnosticsInterop; + path = FirebaseCoreDiagnosticsInterop; + sourceTree = ""; + }; + 9464A874822CFC90DF13B65CC91B8DBE /* Support Files */ = { + isa = PBXGroup; + children = ( + 4103DB404A4E635F97770345F2161B76 /* FirebaseCore.modulemap */, + E7DEDF40806419E0CEA62BE4111E28C4 /* FirebaseCore.xcconfig */, + BF3AF58142843EDD7EB3EFB72810E7B9 /* FirebaseCore-dummy.m */, + 3EE9D117F16778F5DEE15B2DDD50E85C /* FirebaseCore-Info.plist */, + 28BB76344ECE64A25E705BF2FE69B522 /* FirebaseCore-umbrella.h */, + ); + name = "Support Files"; + path = "../Target Support Files/FirebaseCore"; + sourceTree = ""; + }; + 966A61D858171579195F2FD378AD3F2D /* UserDefaults */ = { + isa = PBXGroup; + children = ( + 1D2CEE38973DEDB4BC7B4AB471D4E205 /* GULUserDefaults.h */, + 8A03882E9613B12780B58BA0DBDEADAA /* GULUserDefaults.m */, + ); + name = UserDefaults; + sourceTree = ""; + }; + 9A9BE3999F5C1D84482851166AD38E5C /* Targets Support Files */ = { + isa = PBXGroup; + children = ( + 5BD04E5F54905FBBD9276DFBBCD09380 /* Pods-GeekbrainsUI */, + ); + name = "Targets Support Files"; + sourceTree = ""; + }; + A9277EC637D3D19485D29C21AEA5B5B1 /* GTMSessionFetcher */ = { + isa = PBXGroup; + children = ( + 170FAE5FE958E80E2FF9F7B1247AB0C3 /* Core */, + 21ACD3C375EECCD245260672B6D56415 /* Support Files */, + ); + name = GTMSessionFetcher; + path = GTMSessionFetcher; + sourceTree = ""; + }; + B1274572BE26CFAE4D5D6677E8C18310 /* Frameworks */ = { + isa = PBXGroup; + children = ( + B6A2F43C1606C657E86CFC6EC8FF1F7F /* FIRAnalyticsConnector.framework */, + EB8C58733A2EFA5B582FEAC12CC9781C /* FirebaseAnalytics.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + B4FEE2E926CE7FBD9A52E435790D1356 /* Support Files */ = { + isa = PBXGroup; + children = ( + 6B850B1D5595EAAB345EAA2B4D0367A2 /* RealmSwift.modulemap */, + C0B68F31335F12A48404BBD938C49152 /* RealmSwift.xcconfig */, + 4ABE65F669873B77346F0A4C50731AFD /* RealmSwift-dummy.m */, + AAFCE2DE8FB9E0DFF93F37DFC211EEE5 /* RealmSwift-Info.plist */, + 10C8E24EE36C598C85FB7EDE24E70ED8 /* RealmSwift-prefix.pch */, + 18C11A47DA70BE69B373BF2ED8069CC8 /* RealmSwift-umbrella.h */, + ); + name = "Support Files"; + path = "../Target Support Files/RealmSwift"; + sourceTree = ""; + }; + B5D5A0154650483FA77C00BB29C09F32 /* Support Files */ = { + isa = PBXGroup; + children = ( + F2941D69BA8BB7F1732F3A5E6C6087B6 /* Alamofire.modulemap */, + E181357E02598A486F35973C9C68CC76 /* Alamofire.xcconfig */, + AE7D406AE43833497C38783D7B7BB688 /* Alamofire-dummy.m */, + 6EB2F4D80081F8E6098855995CD7BFC8 /* Alamofire-Info.plist */, + C83BBC3BBB374809A02DED4A80A2F97F /* Alamofire-prefix.pch */, + 51C104BFA0ECC362AD5D0AECB29D661A /* Alamofire-umbrella.h */, + ); + name = "Support Files"; + path = "../Target Support Files/Alamofire"; + sourceTree = ""; + }; + B7DB13C36802DC70783D6E63ED91C1ED /* CoreOnly */ = { + isa = PBXGroup; + children = ( + 62ED77C4EA723E5D0037503EEE89A489 /* Firebase.h */, + ); + name = CoreOnly; + sourceTree = ""; + }; + C224DE7B7BFCEF1EEB0236BEA1CA241E /* nanopb */ = { + isa = PBXGroup; + children = ( + 81EA2FE6D1C42613A812FBAE3318FA87 /* pb.h */, + DF5999D73CC6280F3CC4FCA82134CA7A /* pb_common.c */, + 8194113AA335A2AA29ADC0BA26764681 /* pb_common.h */, + 98F73D30C06A348EFFDC85B721BFC501 /* pb_decode.c */, + A8A9312F7F8DE00674B65C966FA1D0F3 /* pb_decode.h */, + 03196D38FF2956BE3A63693601EAFC6E /* pb_encode.c */, + 2A90F3C5CA0BC7204B47164345A8A9FD /* pb_encode.h */, + 1A8A2220B58D0D6D6B95EB1151907316 /* decode */, + 6BF6E048A3DC10A6EFD4C7DF43594D80 /* encode */, + 84CC859D3297B8C5CDC447AC53FCF163 /* Support Files */, + ); + name = nanopb; + path = nanopb; + sourceTree = ""; + }; + C2282507DAFB334A0B428C4D37F0750E /* Support Files */ = { + isa = PBXGroup; + children = ( + 485C5BE10BC126E11DD2D60E0EA4588F /* FirebaseCoreDiagnostics.modulemap */, + 73CFA238FD606E2E699DB4E8281F155B /* FirebaseCoreDiagnostics.xcconfig */, + 1425FAB07EFDBF97C7BA299BD0A460E8 /* FirebaseCoreDiagnostics-dummy.m */, + A30207A678F529D1338CCE50C15A7D1E /* FirebaseCoreDiagnostics-Info.plist */, + 3C5B12E4F847E35CA6D5536264282E44 /* FirebaseCoreDiagnostics-umbrella.h */, + ); + name = "Support Files"; + path = "../Target Support Files/FirebaseCoreDiagnostics"; + sourceTree = ""; + }; + C5FC32B18D83BA32C067409E3BB62F40 /* GoogleUtilities */ = { + isa = PBXGroup; + children = ( + ED142C0AFFB6A00A4A7323C2A29B7F90 /* AppDelegateSwizzler */, + DAA88B85AF71E4127D521BC5F8B63942 /* Environment */, + 412D37632F4367E05CF71D1CA1B50258 /* Logger */, + 1CF1A11FEE61CCFED99ACE0F0AFA3E6F /* MethodSwizzler */, + 21542D429C646826C73DA3DAE223E209 /* Network */, + D4A026278412F1D23C31970CFF5D4740 /* NSData+zlib */, + 6B9393A4CA4A305C3484A3A17B2C64B2 /* Reachability */, + 68152EF3D7279F32665233358A209D0C /* Support Files */, + 966A61D858171579195F2FD378AD3F2D /* UserDefaults */, + ); + name = GoogleUtilities; + path = GoogleUtilities; + sourceTree = ""; + }; + CF1408CF629C7361332E53B88F7BD30C = { + isa = PBXGroup; + children = ( + 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */, + 14819703D41633BDAF2956E0595FF87E /* Frameworks */, + 0D9621AA8115A2711643004E81184664 /* Pods */, + 4C75395F2313EB6AB9C6C4DCADCC4188 /* Products */, + 9A9BE3999F5C1D84482851166AD38E5C /* Targets Support Files */, + ); + sourceTree = ""; + }; + D4A026278412F1D23C31970CFF5D4740 /* NSData+zlib */ = { + isa = PBXGroup; + children = ( + 70440E94BFBD04E9127CB904A9F0BB6A /* GULNSData+zlib.h */, + 7042F28BE74E0B121E4BA63670CEA317 /* GULNSData+zlib.m */, + ); + name = "NSData+zlib"; + sourceTree = ""; + }; + D52AE0BFC7257D68819DA54C8E24C24A /* PromisesObjC */ = { + isa = PBXGroup; + children = ( + 66B0B6220CDA636E37A9440D71E8C1F4 /* FBLPromise.h */, + BD0D11232D26F0FF3FDC5D549A76DE13 /* FBLPromise.m */, + 7706CC3470134134EB96FF63B96D49DC /* FBLPromise+All.h */, + 4DCE3C53BB9884A39FABEBD81671136A /* FBLPromise+All.m */, + F1A72EEFE9C788BE1B755C160E95C8F2 /* FBLPromise+Always.h */, + 5E19A3D63F5C2BBA363FC9457BC537F4 /* FBLPromise+Always.m */, + 51377F1CD083FC389612859E450139CD /* FBLPromise+Any.h */, + 4B7BFBF1D2DBD09D1AB8932EDA355D05 /* FBLPromise+Any.m */, + 75833FCC7F6F716545752300B81D0B93 /* FBLPromise+Async.h */, + 780E38B21E5B8075DD47973628FCEF89 /* FBLPromise+Async.m */, + 926F3482C65B41D6727B7F69D5725417 /* FBLPromise+Await.h */, + CBC0A66973B374C28F35611394D76F62 /* FBLPromise+Await.m */, + EA285255E24140F4364ADC9C02ADAAE5 /* FBLPromise+Catch.h */, + 8F79BB540DD5F3B0F0DD4C789A65EB79 /* FBLPromise+Catch.m */, + 5D9F16D73D1B59742905EDD0C2966E3F /* FBLPromise+Delay.h */, + 81BD9FFB63D5FA9EF33EFC33EA7597C4 /* FBLPromise+Delay.m */, + 5E7434C839FCA37203C0A13316CB7E88 /* FBLPromise+Do.h */, + DB143F302A037FF79A4C3DF6FE9C3839 /* FBLPromise+Do.m */, + 0D52282B453D45A43B76626275E73CED /* FBLPromise+Race.h */, + F8CEA8779408A7E78CCCCBDD04414385 /* FBLPromise+Race.m */, + 4C6616311432B3C00DD556941EC5A1CA /* FBLPromise+Recover.h */, + 931030B1DBC02E1B5C039ACC9CEA2E9C /* FBLPromise+Recover.m */, + AEF66CD8B1BCBC0A3B148DF00A6113A8 /* FBLPromise+Reduce.h */, + 4B325D27B8A5F2CA76E7C81532F652F8 /* FBLPromise+Reduce.m */, + 7BC112B0B8DC7D7A50DFF38A18C9056B /* FBLPromise+Retry.h */, + 76B451276F7D9E31176301E5DE296E35 /* FBLPromise+Retry.m */, + 70EAFF747CBD33292EEE920054AD38F0 /* FBLPromise+Testing.h */, + 8298C3985C462DB1351E14FFA5281BB5 /* FBLPromise+Testing.m */, + A994DEA929EC73A3FECB99814A26BF0F /* FBLPromise+Then.h */, + B8D3A408F55DEF3F90B05828E9484BD0 /* FBLPromise+Then.m */, + 14F0259C7BD472223481E9A31DC96485 /* FBLPromise+Timeout.h */, + 639630C84534366EB2295D520602724B /* FBLPromise+Timeout.m */, + 9EA4A8CC145EB727B389E41E6BF8B35C /* FBLPromise+Validate.h */, + 1F51719C18D03B91659D1E93CF7F391D /* FBLPromise+Validate.m */, + 1D1B6EB1EB1CC689CA6DC51E84C01C2A /* FBLPromise+Wrap.h */, + 5115577DA06DFFCE6071348C83697F2E /* FBLPromise+Wrap.m */, + 037723208A972F6D4CD565FBB23FEDBC /* FBLPromiseError.h */, + 882A1805C739601B36DDBE8199808EA7 /* FBLPromiseError.m */, + 30B0C1A58B0B3ABC8A2A09E6B26FB29A /* FBLPromisePrivate.h */, + FCF20827CAA2AC54FFFECCB9B75F8DA7 /* FBLPromises.h */, + 5E1A439E03E6E4802A6857EC10B439AE /* Support Files */, + ); + name = PromisesObjC; + path = PromisesObjC; + sourceTree = ""; + }; + D7F986D41C22AC6421B54DFA8F10865D /* FirebaseAuthInterop */ = { + isa = PBXGroup; + children = ( + 0EAECCDB2F410D60877EEAE86A6621F9 /* FIRAuthInterop.h */, + D840ABE6F5EA9C6FE43091E1A01893DF /* Support Files */, + ); + name = FirebaseAuthInterop; + path = FirebaseAuthInterop; + sourceTree = ""; + }; + D840ABE6F5EA9C6FE43091E1A01893DF /* Support Files */ = { + isa = PBXGroup; + children = ( + EC173B1FCB6DCDA143020621B8183EC3 /* FirebaseAuthInterop.xcconfig */, + ); + name = "Support Files"; + path = "../Target Support Files/FirebaseAuthInterop"; + sourceTree = ""; + }; + DAA88B85AF71E4127D521BC5F8B63942 /* Environment */ = { + isa = PBXGroup; + children = ( + CE591618BB2FD1CDAF280E66FE7F3F6D /* GULAppEnvironmentUtil.h */, + D18F97ACB446742A827DE3D7F31A9761 /* GULAppEnvironmentUtil.m */, + AF1A7341BE52513E878BD1EFF619C285 /* GULHeartbeatDateStorage.h */, + 14007804F652C026CE4B746B53D6F3A8 /* GULHeartbeatDateStorage.m */, + 263E917FB64CD4F1F2F9FF7E460337EE /* GULSecureCoding.h */, + BE79B0FA5EB399EAC4CE00E28D3EC116 /* GULSecureCoding.m */, + ); + name = Environment; + sourceTree = ""; + }; + DB89F80DEACBDADF96D40EA7201A4B57 /* Frameworks */ = { + isa = PBXGroup; + children = ( + EBA1C61A579712645308088DD63C8302 /* GoogleAppMeasurement.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + E1CEE1322400810C9330F0708C2CA187 /* FirebaseCore */ = { + isa = PBXGroup; + children = ( + 2E35C429DC58D57B84D7B20DF3B4C1C0 /* FIRAnalyticsConfiguration.h */, + BD000B66CA3DAE39C9254B98F356AD5E /* FIRAnalyticsConfiguration.m */, + AE3CCA0A95C2DB398DA83E48466C7954 /* FIRApp.h */, + FF2A8B581BE6C14669CB135994E53F23 /* FIRApp.m */, + C4F33E933CEDD03D5D071150E4688160 /* FIRAppAssociationRegistration.h */, + 424932417FD4CF653498EA1D5A70693F /* FIRAppAssociationRegistration.m */, + A45D338C1940FDB1302AB9CB81DA0A8B /* FIRAppInternal.h */, + 0176A732FDA7EF1260FECC4B3C3B16AF /* FIRBundleUtil.h */, + 5553476A2B706AC8C40BDB00B6C8F0E7 /* FIRBundleUtil.m */, + B68AAF3A0D973EE14F332611717BF3E0 /* FIRComponent.h */, + B8B62D08C4562CDA545B1A9CE08B8BE1 /* FIRComponent.m */, + 2F21900E3E54F11BE2C2FDCF11D66AD8 /* FIRComponentContainer.h */, + 305EA5D5EE4C80D4090DBE62176EEF93 /* FIRComponentContainer.m */, + 24687D124A95BE37510B66E9E894EE66 /* FIRComponentContainerInternal.h */, + 78FF096304C2AD7526682458C7C2731C /* FIRComponentType.h */, + 1C4470BA20E3DEEAF93A0FFAC0CFEFA6 /* FIRComponentType.m */, + 820BAF23204DD2ADE6997AD1D0B201CE /* FIRConfiguration.h */, + C68934F7EF2D6B6AA3755B9F60EBA98F /* FIRConfiguration.m */, + 6E81AB8B58F049ABA4528A79F1F344F7 /* FIRConfigurationInternal.h */, + 5B61095D647294428C62980474737F22 /* FIRCoreDiagnosticsConnector.h */, + AA80B18F608CC7FFEF860AA39A50114F /* FIRCoreDiagnosticsConnector.m */, + A043B78C92D8D079E567AA643953AC40 /* FIRDependency.h */, + FF724624F7FF1D8DCBFDB4F4778D3A64 /* FIRDependency.m */, + D0373682AE1E96F2853B6452AB74C190 /* FIRDiagnosticsData.h */, + CC3882078B4863B39FE8686F20D749E1 /* FIRDiagnosticsData.m */, + 9CF19D6F9D47FE0C30F70D0326CE6B31 /* FirebaseCore.h */, + 6363B094F036CD16B0446525FFE072C7 /* FIRErrorCode.h */, + E4443BE035B78D51F2F8EE85A7C2B16D /* FIRErrors.h */, + 292DEE854FE9B92BC4022FE701F7F7BB /* FIRErrors.m */, + 2A82FCB7D1E389CB7FB62D54B1074343 /* FIRHeartbeatInfo.h */, + 9735E1BB66BA1A1805C81F7022B52CEE /* FIRHeartbeatInfo.m */, + BC0CEF1C2D64AB51593E134566463379 /* FIRLibrary.h */, + C7D050C1C76B3FB438035A590FBF3984 /* FIRLogger.h */, + C57F75D4E0E0E4D75CBAB5644F1CF5ED /* FIRLogger.m */, + ABCCA286340954F0DD058E63A55A397B /* FIRLoggerLevel.h */, + EA55512BFD1ED5E8550C4D99040DC2D6 /* FIROptions.h */, + D2B0710A146E5C9037FCA9FC8BC68189 /* FIROptions.m */, + 4A4EDAEBE0ACD7112E19375E06DEF681 /* FIROptionsInternal.h */, + 7CF5F9A072CD16920377817F5691A403 /* FIRVersion.h */, + 01184D2BB8F9472E8D48666C0DF61D38 /* FIRVersion.m */, + 9464A874822CFC90DF13B65CC91B8DBE /* Support Files */, + ); + name = FirebaseCore; + path = FirebaseCore; + sourceTree = ""; + }; + E2306AC79AA970C03A51906F98B92E94 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 8BCA6AFA09C705E39ECD5316A4472114 /* librealmcore-ios.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + E24F0D8E02BF257145BFC2D2F040879A /* Support Files */ = { + isa = PBXGroup; + children = ( + BF7958D5FE36B4E8E4EDAD3709AF04BA /* FirebaseAuth.modulemap */, + 057C3F5AF0B6B7F47AB4D32D9FCD9421 /* FirebaseAuth.xcconfig */, + 2DF7BA27509E8E936610AD1EC2F69E0E /* FirebaseAuth-dummy.m */, + C113D215D4DE09C283B8502C4C1FC7B8 /* FirebaseAuth-Info.plist */, + BA2AD92B599641752DCBD8F1FE784B96 /* FirebaseAuth-umbrella.h */, + ); + name = "Support Files"; + path = "../Target Support Files/FirebaseAuth"; + sourceTree = ""; + }; + ED142C0AFFB6A00A4A7323C2A29B7F90 /* AppDelegateSwizzler */ = { + isa = PBXGroup; + children = ( + 3A540E35BC1F914EDFFB3FC921C77F85 /* GULAppDelegateSwizzler.h */, + FBF030098618CE3A844110FF41D52710 /* GULAppDelegateSwizzler.m */, + 0543F2CD37528CCEBC7A49CEB7FAC5D1 /* GULAppDelegateSwizzler_Private.h */, + 5DA795C79D20C355B2589B1B2F35081F /* GULApplication.h */, + 8B83ED9467FD61F44F6C95DCF79C6280 /* GULLoggerCodes.h */, + 9D3459C02176B8A9124D0772B38C6178 /* GULSceneDelegateSwizzler.h */, + 51610B5A284CCD1C06EF04C565381EFA /* GULSceneDelegateSwizzler.m */, + 0D8E5D0FF01FC3B2B7A13D625B836394 /* GULSceneDelegateSwizzler_Private.h */, + ); + name = AppDelegateSwizzler; + sourceTree = ""; + }; + F3CC0027D3D62E3E30BBC5AA07FAC215 /* Support Files */ = { + isa = PBXGroup; + children = ( + 9AA0E16A090681028EE0681EE3F25288 /* GoogleAppMeasurement.xcconfig */, + ); + name = "Support Files"; + path = "../Target Support Files/GoogleAppMeasurement"; + sourceTree = ""; + }; + F5A35BBCD0E52F68825A21889AEE8A15 /* FirebaseCoreDiagnostics */ = { + isa = PBXGroup; + children = ( + 7A61D8F6B2637A077A773A4BC2C231B6 /* FIRCoreDiagnostics.m */, + 0E742B2B5CF247EEBCFF0C8BD42B9113 /* firebasecore.nanopb.c */, + 7C9ED60FEEB27E4F77013A3307337492 /* firebasecore.nanopb.h */, + C2282507DAFB334A0B428C4D37F0750E /* Support Files */, + ); + name = FirebaseCoreDiagnostics; + path = FirebaseCoreDiagnostics; + sourceTree = ""; + }; + FD44FA58D684D38F710E910567F3C616 /* FirebaseAuth */ = { + isa = PBXGroup; + children = ( + F984ED7661040651F50DFED42E8FD41F /* FIRActionCodeSettings.h */, + 6E3AE9D51BAC10905A4242A2853D4BC7 /* FIRActionCodeSettings.m */, + C190906AA7599B5C3B1EDD595BD50AE2 /* FIRAdditionalUserInfo.h */, + 0E942E4D8362BD9FF9206F0F0224F63B /* FIRAdditionalUserInfo.m */, + 3A0042AA567D40B19AEB176865699923 /* FIRAdditionalUserInfo_Internal.h */, + E657566D8C5E7C37652B9A892838BEF7 /* FIRAuth.h */, + 25599B221CFAA5E8FA3F0627E7ECED5B /* FIRAuth.m */, + 5C4BD82707AB6BB86675E6BC6E48F081 /* FIRAuth_Internal.h */, + BC2992AF3809C2883734EBB8D3EC5609 /* FIRAuthAPNSToken.h */, + 471FB2C8149FECE847C73CCCD34D3622 /* FIRAuthAPNSToken.m */, + 2AE67C3CC542BA36613ADD23D682E7E8 /* FIRAuthAPNSTokenManager.h */, + 5FC6B6785F984F1BA0AF1DDE3867F66B /* FIRAuthAPNSTokenManager.m */, + 74AAAB1D03D40969A48E2FC429D8855E /* FIRAuthAPNSTokenType.h */, + C6ABC1E67AA88DD64EDA72189FFA1268 /* FIRAuthAppCredential.h */, + 3F5C590A93C8398FAFCB14C57DD38BE4 /* FIRAuthAppCredential.m */, + 71047F985E5491DAA031A643F38EB063 /* FIRAuthAppCredentialManager.h */, + 309A0F8D2A82297BB270F70596692677 /* FIRAuthAppCredentialManager.m */, + F42E571CAB78A61F579A7D8196D8DE05 /* FIRAuthBackend.h */, + 331CF223F983590C6FA72BA0697801EE /* FIRAuthBackend.m */, + B22A5F3E02894F9C5BA92CDF2FED5C7B /* FIRAuthCredential.h */, + FE4E65C4715C57BABDE76E223BEF6080 /* FIRAuthCredential.m */, + 277703D6A54E896B59FCD81720BC2B6C /* FIRAuthCredential_Internal.h */, + D060DA57254EB3D36CE5BAE298D8ED96 /* FIRAuthDataResult.h */, + 1231D552B10EFFFBC08D63ABA4FC43FF /* FIRAuthDataResult.m */, + EDC084CC75C40792DFD2D7287EFD6467 /* FIRAuthDataResult_Internal.h */, + 7ACAD9BF8FD9E6CC233C2B8DB317F5C0 /* FIRAuthDefaultUIDelegate.h */, + 4580E4AEE938A2AD447D44D7A2136CD4 /* FIRAuthDefaultUIDelegate.m */, + 6411A605F957CB41FE1FC8AF5218ADD0 /* FIRAuthDispatcher.h */, + 8593D09BBFA2FD018D9DF24500A16D38 /* FIRAuthDispatcher.m */, + B46827FEC6AC5CD82B5F7C24704F9936 /* FIRAuthErrors.h */, + 7EE0B27C3EE3C18B5068838918F4F384 /* FIRAuthErrorUtils.h */, + DFA4132E43065C4BC9B519AA54A5F287 /* FIRAuthErrorUtils.m */, + 45DABEADF709E0735D0285EC28D630BF /* FIRAuthExceptionUtils.h */, + D9056436C38C221324AD2173B94F7FA3 /* FIRAuthExceptionUtils.m */, + 6FFE6D0828D5AC252CA221F054787EFE /* FIRAuthGlobalWorkQueue.h */, + E0361FDBB1C8218671C579D62D9C5EF1 /* FIRAuthGlobalWorkQueue.m */, + 5EB53EDD70E5582A5BDAC4B127DB2C90 /* FIRAuthInternalErrors.h */, + 3AB877196209774BF84D1C73F0BB5CDB /* FIRAuthKeychainServices.h */, + 3B938CCEBEB19930A5B085AFF34B90CB /* FIRAuthKeychainServices.m */, + B9166509EBF18D0943E4D8562A3C3CAA /* FIRAuthNotificationManager.h */, + FAB3710980A18B4F81EB86053E0394C5 /* FIRAuthNotificationManager.m */, + 10CEF8E8921B937AD4E0A3EC0B204026 /* FIRAuthOperationType.h */, + 2633BB2A259FF573B8C32BA48394F9EA /* FIRAuthProvider.m */, + 1E13C163D44E2F284341E396BB0A7707 /* FIRAuthRequestConfiguration.h */, + 1E45A7C637B9EF8C87CF12C4387A2E17 /* FIRAuthRequestConfiguration.m */, + DDA6A6FE1B2441688DA0C4A2AF45CAA2 /* FIRAuthRPCRequest.h */, + FDAF8C09000A98E72DF3001961842E96 /* FIRAuthRPCResponse.h */, + C05B5D9CCA2A7B1A5E03AA8CC4DE1E5F /* FIRAuthSerialTaskQueue.h */, + 0CDDAB47030D2788EB6FE8C833C8F44E /* FIRAuthSerialTaskQueue.m */, + E9FAC3EC9C794C3593C728CF3E4EDAC1 /* FIRAuthSettings.h */, + B0BD9B5CDB9392F69987B56C2D80D447 /* FIRAuthSettings.m */, + 36801A35862CCA94D4DAC7925FC64064 /* FIRAuthStoredUserManager.h */, + 02DF1A33535A813CC741C2ED921D4DD6 /* FIRAuthStoredUserManager.m */, + AA6E836D46E455FD474EDF1905E109AB /* FIRAuthTokenResult.h */, + C86D75013FDEE444275D5CE51FF59D68 /* FIRAuthTokenResult.m */, + 66653077665BFE63195D56DF6B94989E /* FIRAuthTokenResult_Internal.h */, + DD4B31E84CB4D8ACAB16B52B1A55D2F6 /* FIRAuthUIDelegate.h */, + 80E5536A228B8DDE0EBBAFAD13275DA8 /* FIRAuthURLPresenter.h */, + C93A659F3C396F363AA9EAC8E6D63480 /* FIRAuthURLPresenter.m */, + C82C90DD882D46D8FD21ADDF3E9F9D5B /* FIRAuthUserDefaults.h */, + A6DA917FC21F7D46345AD1B5798813F4 /* FIRAuthUserDefaults.m */, + 57ADEE7E78BA08FD1D671853FF64AE36 /* FIRAuthWebUtils.h */, + 8711C8CB8DF3A374B7C9DCA86A93C7A2 /* FIRAuthWebUtils.m */, + F66BF470805C0E607FE8D9913E9213D7 /* FIRAuthWebView.h */, + BEDABE10B5025C79318A8CF7B1925844 /* FIRAuthWebView.m */, + 15ADC80EB9B79579806F9F7AD89C2C94 /* FIRAuthWebViewController.h */, + 3E43572942B487228C2EFDD3DBD087F6 /* FIRAuthWebViewController.m */, + 5614F6A681D5A4E43CEE7570A1512BE2 /* FIRCreateAuthURIRequest.h */, + 3A10D68E59112CCF8B91F5994B13A897 /* FIRCreateAuthURIRequest.m */, + DA888CEEA972A2D0195C006DEAFB5D7A /* FIRCreateAuthURIResponse.h */, + CD8D438942C8F90B8D167852A4A7EEAC /* FIRCreateAuthURIResponse.m */, + A6AF2ADD5C91F05DCB60BF5366727A7F /* FIRDeleteAccountRequest.h */, + AD877C2DBA0A621AD258A010A5E54B2F /* FIRDeleteAccountRequest.m */, + 19FB754EB4099F557531F6709EAC9E10 /* FIRDeleteAccountResponse.h */, + 26FB8101B9FB466D0751B3CB5748F260 /* FIRDeleteAccountResponse.m */, + 6E2B962F4C08A3AE087F4EEE5D28146B /* FirebaseAuth.h */, + 8C04E194687BA66B98EDC15D43B979B5 /* FirebaseAuthVersion.h */, + F32515F6A7AF5C93942F5CC9477AEF66 /* FirebaseAuthVersion.m */, + 9C980FD69AD83CB2FF5D34DC23B46A97 /* FIREmailAuthProvider.h */, + 358E651244C190AC57D4A02BC264D248 /* FIREmailAuthProvider.m */, + 3064795A5FC37862FBA863669AE9B292 /* FIREmailLinkSignInRequest.h */, + 18FF45BE694A4ACF5279DDC418ED779A /* FIREmailLinkSignInRequest.m */, + 8B63C4D38C3294E634556027B2ABB2D0 /* FIREmailLinkSignInResponse.h */, + 3ABD1B08F068B8BFA864424DE777BBAC /* FIREmailLinkSignInResponse.m */, + 5FED4C233DF99D6E196D993E5E3C7ADD /* FIREmailPasswordAuthCredential.h */, + 080D58C9DE83B7518E5BB38070E78A6B /* FIREmailPasswordAuthCredential.m */, + F4EE656688A0BDEB67D52D6B4C0F0150 /* FIRFacebookAuthCredential.h */, + 96FD8EDB3F384930F0CCFC2E512842C5 /* FIRFacebookAuthCredential.m */, + 33EAA367E623B3A0DE2D79620F85FE4A /* FIRFacebookAuthProvider.h */, + 6D60CD4074EEBE50EB3EAB68AEF7AB2A /* FIRFacebookAuthProvider.m */, + AFC0E06A82E7604B1321E10A65210FC7 /* FIRFederatedAuthProvider.h */, + 64863531D302B03363FA66046E691FB2 /* FIRGameCenterAuthCredential.h */, + D088B0E1F77C694FC23D3A21D5BA93E9 /* FIRGameCenterAuthCredential.m */, + 81E81A25B22FC7FC5627CB5A203EE004 /* FIRGameCenterAuthProvider.h */, + DE34E08AD80E531D47F7FF62F05C9DBA /* FIRGameCenterAuthProvider.m */, + 242F1FE16F59BBC9DA78B2379E82B553 /* FIRGetAccountInfoRequest.h */, + 107BF9874255036D813C9C53A899ABC8 /* FIRGetAccountInfoRequest.m */, + 11803A8C45EB3F5F3B529FB885798EB9 /* FIRGetAccountInfoResponse.h */, + 8864B72940AE9A531D5110372EF4C995 /* FIRGetAccountInfoResponse.m */, + 6D40ADFA16F2F4D5A0E378EEC660466C /* FIRGetOOBConfirmationCodeRequest.h */, + 139EC6653CEB5B25C26FAD976A2CEE80 /* FIRGetOOBConfirmationCodeRequest.m */, + 004C39F6E46A82C849CD59CD24686A01 /* FIRGetOOBConfirmationCodeResponse.h */, + D64E47D2DC7764FF21C3B04F333D7607 /* FIRGetOOBConfirmationCodeResponse.m */, + B71683F1B20F1053C4E78AF2ABFE7637 /* FIRGetProjectConfigRequest.h */, + 04E600FE62CF56D14578FC67BB598272 /* FIRGetProjectConfigRequest.m */, + D9351244ADF4359E07B688F626D2B5AE /* FIRGetProjectConfigResponse.h */, + 7348D3BA6708FE3622CA4B947A986519 /* FIRGetProjectConfigResponse.m */, + AD28552BB9D41F42225DD045C6430D45 /* FIRGitHubAuthCredential.h */, + 38739C86586A2293AE424FDEB62F9E2D /* FIRGitHubAuthCredential.m */, + 4CAD8C29BDA630B438644EEE766E02BE /* FIRGitHubAuthProvider.h */, + 2ED92C57899F320C729B4357A050A1A2 /* FIRGitHubAuthProvider.m */, + B0E2D9DCC2D578F3C955E2D3D20D732E /* FIRGoogleAuthCredential.h */, + 2651B13DEBB7991B322A3A7FB2910F41 /* FIRGoogleAuthCredential.m */, + 9E3DB5044C4A852A56DFBEC9210F6936 /* FIRGoogleAuthProvider.h */, + 2FF6795513CF63D818090FDBBBEA2E17 /* FIRGoogleAuthProvider.m */, + B287553C4687458B4F94CD2691ECD117 /* FIRIdentityToolkitRequest.h */, + 945E2AD872C86F66E2B79E020BBA5FF6 /* FIRIdentityToolkitRequest.m */, + 4CE85E49B4DFEF79F939FA8F668F45EB /* FIROAuthCredential.h */, + C8242370427DCF96728DAA60AC4746F2 /* FIROAuthCredential.m */, + FABA7194CA09589599F926D3C76789BF /* FIROAuthCredential_Internal.h */, + 53143A7A200C31CAE42A2C9E2612AB3D /* FIROAuthProvider.h */, + 8AF7AEE28A597BE8C090298E2EBC0B1D /* FIROAuthProvider.m */, + ECA7E4BA5A6CDB154033CC0AF23ACF68 /* FIRPhoneAuthCredential.h */, + 2357030ACCEDF3D048244D1CD81A2FE5 /* FIRPhoneAuthCredential.m */, + 4129260763BBAB48DD6DB625D6A7C462 /* FIRPhoneAuthCredential_Internal.h */, + 2E99648ED1451094B62427E18BF42988 /* FIRPhoneAuthProvider.h */, + 51ABE87E1BDF51DA9905E18093D794E7 /* FIRPhoneAuthProvider.m */, + 14943783D6765B9A756C91C50D324D57 /* FIRResetPasswordRequest.h */, + AD01636391685B8E33F32E033B2F16B3 /* FIRResetPasswordRequest.m */, + 25870563374C629A654D4D9F67C26E56 /* FIRResetPasswordResponse.h */, + 1DD18769760DADB55736DAD2D92847B3 /* FIRResetPasswordResponse.m */, + 1EACEFAD63F0542FB6C9630CFE587A37 /* FIRSecureTokenRequest.h */, + 524B6D68BA7AD2123C9FADC2891CFA91 /* FIRSecureTokenRequest.m */, + 7B01724F721919F7D7A74590E67DC81D /* FIRSecureTokenResponse.h */, + 0708BFDF3D8644771BD7287AB060DD32 /* FIRSecureTokenResponse.m */, + C80B8C0BA49A935A8C34747C21AD782F /* FIRSecureTokenService.h */, + 5F02C49F70C721CD4A62B36B7DD111DC /* FIRSecureTokenService.m */, + CD214470D6041E540B899AA24488E398 /* FIRSendVerificationCodeRequest.h */, + 461E3BF8391B34CA8ECC0B7327B86304 /* FIRSendVerificationCodeRequest.m */, + 0C8DC147EFC61F95DA3FC13996255591 /* FIRSendVerificationCodeResponse.h */, + 7F3F57166D63D9449D3C19EAE3B68788 /* FIRSendVerificationCodeResponse.m */, + F61594A2D9EB829D8F70CE39F7EA8F1B /* FIRSetAccountInfoRequest.h */, + 826604233AB70EBDA731C537C73AC4CE /* FIRSetAccountInfoRequest.m */, + 61C01EB057182563C6FDADA78D82D50B /* FIRSetAccountInfoResponse.h */, + 0BEB9AB4B41C5EA0FC8F77768519921A /* FIRSetAccountInfoResponse.m */, + B3C87CC09FB0D7BFBC1445D0533D25DB /* FIRSignInWithGameCenterRequest.h */, + B0869227B77B723D090AA044C73DC03D /* FIRSignInWithGameCenterRequest.m */, + AAB15C35792D73E4FA31853F7AE525AA /* FIRSignInWithGameCenterResponse.h */, + 0007AC450179188F2E427F0B89A073E8 /* FIRSignInWithGameCenterResponse.m */, + 2BCA06E9D725F8CE559CBBD8AE84C237 /* FIRSignUpNewUserRequest.h */, + 67AD62195DDA0F8E519B11169135F319 /* FIRSignUpNewUserRequest.m */, + 5CF5AFAF7139E4C1022247757F7538D2 /* FIRSignUpNewUserResponse.h */, + A8F11A451EB6026D35C48E8E172E5EF7 /* FIRSignUpNewUserResponse.m */, + D1F49BB693EFE972B666A13C45CD1B9D /* FIRTwitterAuthCredential.h */, + 23B445FBF7E9205D356BA3B046D1B560 /* FIRTwitterAuthCredential.m */, + 387531976064EAD3DDF02EBEB38F7D41 /* FIRTwitterAuthProvider.h */, + 155F8EFAE7A4D4D041B3D8F2511E9D4C /* FIRTwitterAuthProvider.m */, + 95D54C32914D8C5745A86BD2ABBBACD6 /* FIRUser.h */, + 3261405AB6EB845D9659614D600F2F86 /* FIRUser.m */, + 51895595CC0B4F2941F4790FCE674D1A /* FIRUser_Internal.h */, + 4FA95262E393CB9DD325CB12247F48B1 /* FIRUserInfo.h */, + 2AAA54DE66F1A6513DBC8807276C372A /* FIRUserInfoImpl.h */, + DC6628ED8CAEEDBD7E69EE5FBDF8A882 /* FIRUserInfoImpl.m */, + 32FA6F057E064B56ED5DDA9F8F32E1E2 /* FIRUserMetadata.h */, + FCC777E5D845A6615BDD85C43BB2CDDE /* FIRUserMetadata.m */, + C8D360BB5DCD315E5A9DA1EE683CED0D /* FIRUserMetadata_Internal.h */, + 8D8898271E7DCFDA5AD4581F1709420C /* FIRVerifyAssertionRequest.h */, + 3F717187275F3B68D7A159A0D95761C9 /* FIRVerifyAssertionRequest.m */, + 5FF094A51019D603B58A07B6AFB78F0B /* FIRVerifyAssertionResponse.h */, + A0828F754A3C7E4871AC36A1ED72B713 /* FIRVerifyAssertionResponse.m */, + C5B9B9A3D348238704B96F1CFC1C0CF3 /* FIRVerifyClientRequest.h */, + A827193BF78DD12B4786B06D1EA13EF8 /* FIRVerifyClientRequest.m */, + FA930458D628F0B45262C06E2F5FF5EE /* FIRVerifyClientResponse.h */, + 0920FF74941ED10798F1EF6E8D15ADB4 /* FIRVerifyClientResponse.m */, + 7E579F8382EEFCD134AD0E3538EB38EC /* FIRVerifyCustomTokenRequest.h */, + 5DC9B08796CD1B484EC6E692D733DA40 /* FIRVerifyCustomTokenRequest.m */, + F363C40B536805228088BCBFE0735F66 /* FIRVerifyCustomTokenResponse.h */, + AD9911FF901182BFF3E6388EE68455A4 /* FIRVerifyCustomTokenResponse.m */, + 67D6E1E49E9266C174C79BE4873DEE72 /* FIRVerifyPasswordRequest.h */, + 4485A1A98C3F3A02896CC18D5D8181BC /* FIRVerifyPasswordRequest.m */, + C4B5B407BB54A7E00EFC6217B9057C28 /* FIRVerifyPasswordResponse.h */, + CC5276A017618A24E6C85A878E76F4BA /* FIRVerifyPasswordResponse.m */, + D39DEBFBB8F078D08F1BE9B07342568E /* FIRVerifyPhoneNumberRequest.h */, + 1517B8A647EFBB22C37521AD2B638C49 /* FIRVerifyPhoneNumberRequest.m */, + FAFB9BE1C3FB89125BBF7F38D1BB238C /* FIRVerifyPhoneNumberResponse.h */, + FB5DEF08A4DBF5EFC56AE6DEFA7825A6 /* FIRVerifyPhoneNumberResponse.m */, + 73646F0031DD6F6B9E6CD16A204ED450 /* NSData+FIRBase64.h */, + 1FC141C04FBFB3AD1B37DD2AD9485844 /* NSData+FIRBase64.m */, + E24F0D8E02BF257145BFC2D2F040879A /* Support Files */, + ); + name = FirebaseAuth; + path = FirebaseAuth; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 077EB81DD4C9C876EC7020918A99C2E9 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 2B5C0732132B06D6118AE72184AEA30C /* nanopb-umbrella.h in Headers */, + E3853D2F683556A120B54A229C7BF185 /* pb.h in Headers */, + 86FFEA663980E456156E8DC515E4E197 /* pb_common.h in Headers */, + 28B2C4457C674C218002B0E56C6A9D38 /* pb_decode.h in Headers */, + 6D5B3184C64DDFC0DB5C83B496D58BA9 /* pb_encode.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 12936760E1153D2E8EB70500E0D7B965 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 9BAED39893E3A38E86EC0CB7F1B4664D /* RealmSwift-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 12FB7CC94DCFFF5DB81E9D9C6B2081CF /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 477B4E25FBF8AA81BF15887C25986417 /* arena.h in Headers */, + 8CA9CDD12D48029F0C9E7462355D567C /* block.h in Headers */, + 9607D0F6FF5A6E0856F3842C11F67987 /* block_builder.h in Headers */, + FD4FEA2A5FC79BE8E0039862906BCD34 /* builder.h in Headers */, + D9600126A179DC4CC2B7298CD6471FA0 /* c.h in Headers */, + AE46D7CF9158C9D77CEEE8D80DDE2638 /* cache.h in Headers */, + AB51B81AC32B9F0D44E93AD97A2F3C25 /* coding.h in Headers */, + D331917270761ACCF9DBDCCF1EA3F4D9 /* comparator.h in Headers */, + B50E079B4CDC981505AA4AE2A18598CF /* crc32c.h in Headers */, + 767D2474031804B5E37F6556F2FDFE07 /* db.h in Headers */, + 6C317F07567F7B4A089E56F02A8A1CD9 /* db_impl.h in Headers */, + 60784C1324A0D53E8A876EF08007A2F3 /* db_iter.h in Headers */, + 5BCC2F8C37FEA8348E4B87A2A2F76380 /* dbformat.h in Headers */, + 630A4F7D2B8EEA3B4B71626F16A1B277 /* dumpfile.h in Headers */, + FE562041EE4B54BED07C5CE029019AC8 /* env.h in Headers */, + 461D7C2C38AA15C6B0B9B967BA1C1397 /* env_posix_test_helper.h in Headers */, + 43A191D74DF7A75AB58657DB8E29A740 /* env_windows_test_helper.h in Headers */, + DDC90A2A4E9893B1AC13091CDDB71F42 /* export.h in Headers */, + 42EF7F079DC719E8E215101D1097070A /* filename.h in Headers */, + AC706F37790D84D0F35827772B6F3F06 /* filter_block.h in Headers */, + 2B2DCB61E3D680229390836AD870B4D4 /* filter_policy.h in Headers */, + CE318AFB78D1DF9C0CAA303BE8E9C716 /* format.h in Headers */, + F94C9281B3897CF5A1DAC4462E80D980 /* hash.h in Headers */, + 4F5B95A2204438513D7171915037CBD4 /* histogram.h in Headers */, + D966EB20806995F4E2E24B57783A6BB0 /* iterator.h in Headers */, + 38484609856819860F2552EFF7262C68 /* iterator_wrapper.h in Headers */, + B243301AC8E71DC52E6FDC10E7249280 /* leveldb-library-umbrella.h in Headers */, + D3E8B5F3E1D4D7A8A24E95349E6FFC3D /* log_format.h in Headers */, + 4E7485B29692E8BF88A9EA104764AF1A /* log_reader.h in Headers */, + 1ECFB49A4B9AFCCDFB9EA2E50376C9FA /* log_writer.h in Headers */, + F7B39B893327E7F2AEE270E4C6CA861B /* logging.h in Headers */, + 96035782A092B0F6C2306836CAC7A093 /* memtable.h in Headers */, + 4373C08B60E643CD133C67158060BE6A /* merger.h in Headers */, + 49CA65BA27EEC63A5904CAEEFA8D1FB7 /* mutexlock.h in Headers */, + 1D9531B1B584741132D9EE4B8CCC8B1F /* no_destructor.h in Headers */, + 5B96F14532C3F92112D2CD0943D704C7 /* options.h in Headers */, + C9CFCB620C528E09054C987ACF9C41CB /* port.h in Headers */, + 027CE3ABE68D3D82B881B68880524357 /* port_example.h in Headers */, + 6961951C1531CD769B6D4F4B075C3CAB /* port_stdcxx.h in Headers */, + D3EB776ADD095A481488388B2899EB6E /* posix_logger.h in Headers */, + 40B124A7DC4EB5174454DB82C1FC33EF /* random.h in Headers */, + 6BE3AEBCB6975AA917A267E0A35A0022 /* skiplist.h in Headers */, + 1A9D0B3BCFD7DDE552F1F6B3C2F89286 /* slice.h in Headers */, + 8039392FCF5EE2317427478930926EDB /* snapshot.h in Headers */, + AFB5D797E8DDEEA898DB6884031B40D8 /* status.h in Headers */, + 3BDEFBCEEB2C0B16056BA157F03A1A30 /* table.h in Headers */, + AE44EA35803EB2B25C30AE3E4D513E12 /* table_builder.h in Headers */, + 3F135F4BDC16B309D9296FAC7A1522B0 /* table_cache.h in Headers */, + 9BC5056A045FCBCDCAD6A431A65BC6A7 /* testharness.h in Headers */, + B86917BC204271414FD379A9EE1E6F7F /* testutil.h in Headers */, + 44ECEF3C6FD2EFECEB4C6A8E172F7C35 /* thread_annotations.h in Headers */, + E9607A6223E3F82761A85937A89561ED /* two_level_iterator.h in Headers */, + 74D1BCC4040DFA283D5FEBD9639EC490 /* version_edit.h in Headers */, + 09BA8C8065B7F9FCBD4AAA69DB439C67 /* version_set.h in Headers */, + A45B07EB5CC9B04E0BEE83B3F27C1D4D /* windows_logger.h in Headers */, + 651432860BF0B5CDF76C3315A7D4A0A7 /* write_batch.h in Headers */, + C87F621017CD4FB079BB063451F24613 /* write_batch_internal.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 16CEA5ACD246CC0CBC31EE29385A1736 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 41941557EBAA39CE6B86EB7D42180EC5 /* FBLPromise+All.h in Headers */, + D667753105D7C0FFD7C1E073421BEE42 /* FBLPromise+Always.h in Headers */, + 7F874EBC49645DD7B15D183549613968 /* FBLPromise+Any.h in Headers */, + 8869ED06E1AF6CC297327A90FD461E12 /* FBLPromise+Async.h in Headers */, + 3C9DC7E31F8C0FCD9E0191FE4AF81284 /* FBLPromise+Await.h in Headers */, + AC60C199FDE1163D1C5421CCBB11DABD /* FBLPromise+Catch.h in Headers */, + B2FEC6C211B6518BE1B3BF255D831447 /* FBLPromise+Delay.h in Headers */, + 8EF1035B1CA556B60DED3ABD697080D2 /* FBLPromise+Do.h in Headers */, + 8C5FB0D2A15DB1DBAC08EFA9E20F678E /* FBLPromise+Race.h in Headers */, + A01F697294096A02C4C3EC24ECB1FAEC /* FBLPromise+Recover.h in Headers */, + BFF27381EC19183915E8328A74EB172A /* FBLPromise+Reduce.h in Headers */, + 1496182EA04508D6FDE7C6A34B093049 /* FBLPromise+Retry.h in Headers */, + A0FBEF8CB22A6E168C80E221D0DF786E /* FBLPromise+Testing.h in Headers */, + B9BEF5B410021DCC139E4F15660A32B7 /* FBLPromise+Then.h in Headers */, + D1D68F0EE3EBB60A3D8584EFD87E7775 /* FBLPromise+Timeout.h in Headers */, + 4CBAACF939AA13A4400C8DCA3410CA0B /* FBLPromise+Validate.h in Headers */, + 3452371D8065303C909398C7D16C91D8 /* FBLPromise+Wrap.h in Headers */, + DB7596FCF437169251CFE9FEAE439569 /* FBLPromise.h in Headers */, + D2FF6C9EB0E30EA6651632519832710B /* FBLPromiseError.h in Headers */, + 21762AFD3A4E0D28D9EF9A8606BDB10C /* FBLPromisePrivate.h in Headers */, + 2851B27D236B727CA68014E27964260F /* FBLPromises.h in Headers */, + A14FC4211C2611BCE6035EE54583B7CB /* PromisesObjC-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2582784E4FA6A1AC5D23FC53AC3F6EE2 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + F7B74645E07C39771156A1FA413B98D4 /* Alamofire-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4EAAFEEDADDF447480DB32E54C21E5A5 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 12C6D391DADBAF1A3A0E52464544ACDB /* GoogleUtilities-umbrella.h in Headers */, + 80BC6B428C2BACE14A74E2D8D0FD96C7 /* GULAppDelegateSwizzler.h in Headers */, + FF7B730055D70EDBCB00F8595902151C /* GULAppDelegateSwizzler_Private.h in Headers */, + EBA9B582E506A1697DE30B29B1A7E1A3 /* GULAppEnvironmentUtil.h in Headers */, + 304C19806FBC2ABCA90C96A46D029392 /* GULApplication.h in Headers */, + 1D60D240B194D801E1A97ADA41D84C88 /* GULHeartbeatDateStorage.h in Headers */, + E77A43B269C786167E976A13F3CB71D3 /* GULLogger.h in Headers */, + AE1CF61A21BA2BA67C5A18A00CC4DC99 /* GULLoggerCodes.h in Headers */, + B07A918EDF41DD7B6F921908885A4019 /* GULLoggerLevel.h in Headers */, + 8C08C025E8106626D57AB11ABD47A985 /* GULMutableDictionary.h in Headers */, + E0977780511A3EF63ECEDCB1301BCF1D /* GULNetwork.h in Headers */, + 4E5FBCDF894A910BCF3BF7B1FB4242F9 /* GULNetworkConstants.h in Headers */, + 038F688C7F0D0D535C62FFD508408C2E /* GULNetworkLoggerProtocol.h in Headers */, + A5E675465CFF1FBA0DD01AA4E2BA5F77 /* GULNetworkMessageCode.h in Headers */, + 872974D912930AF2A0F3A2552A06A283 /* GULNetworkURLSession.h in Headers */, + E387ADCB6A1B0D5A79BC68C0EDF5D5F1 /* GULNSData+zlib.h in Headers */, + A2CAC7C46044633BA5B3A258FE1B4311 /* GULOriginalIMPConvenienceMacros.h in Headers */, + CEEF6FA2450E3D32FCFF2F63D2ED4D42 /* GULReachabilityChecker+Internal.h in Headers */, + D1F33CABBE11F58572D38B71D2E8B37E /* GULReachabilityChecker.h in Headers */, + 621FE21D5739E76C42632C66EB852EDA /* GULReachabilityMessageCode.h in Headers */, + B2EE93107EB7EB0A704C678ED2CE8F48 /* GULSceneDelegateSwizzler.h in Headers */, + 991D278E60FF49563DA3DFAE0CB6AC64 /* GULSceneDelegateSwizzler_Private.h in Headers */, + 9D9656D879FBF3A3046B8FB782190BFC /* GULSecureCoding.h in Headers */, + EA04EECEF9A81CD4BAF55CA40AA102EC /* GULSwizzler.h in Headers */, + 9807C661B5E65E69632BB99A6E069013 /* GULUserDefaults.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 643FB09BAF8E56C0EDD87D962643680A /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 9D23B927D2A2FE8FA48C3791E493E08E /* GDTCORAssert.h in Headers */, + F133A864D149BE7E3B8ED12A0FE0DC23 /* GDTCORClock.h in Headers */, + BB5CCD24865B991A4DE72182ECC3C47E /* GDTCORConsoleLogger.h in Headers */, + D81B8330CB76689DC9D2BB45BA91B7FE /* GDTCORDataFuture.h in Headers */, + 3FACBFC2E3AF73AD392579229D274694 /* GDTCOREvent.h in Headers */, + 4F812E33FAB867558FB394250389F4DC /* GDTCOREvent_Private.h in Headers */, + 6210FC41AC5325F115ABB34BB7FC4268 /* GDTCOREventDataObject.h in Headers */, + 99B32893D710CB8D9401C3AB9FF82504 /* GDTCOREventTransformer.h in Headers */, + 01F9A5A90232973B6DA54678118DB3C0 /* GDTCORLifecycle.h in Headers */, + 4506F8AB5C48F06BC5A23A8896FA3B88 /* GDTCORPlatform.h in Headers */, + 542E3259A98FBBB48F64604947D97F67 /* GDTCORPrioritizer.h in Headers */, + 03D062393118C4BCC22F3CFEA63E8A54 /* GDTCORReachability.h in Headers */, + A385787A0345125B9EF535AB655D6A22 /* GDTCORReachability_Private.h in Headers */, + 210D1B21D54F300E4999D636A4F5233E /* GDTCORRegistrar.h in Headers */, + D822B3AE340BD0DCE67AD0F0AC648305 /* GDTCORRegistrar_Private.h in Headers */, + 406488DB5CD66387440E94CEE22D373F /* GDTCORStorage.h in Headers */, + 28F4214019DBD32E528DA588AA8880E9 /* GDTCORStorage_Private.h in Headers */, + 1953358E528DC00445ED451B46B8FD20 /* GDTCORStoredEvent.h in Headers */, + 5C91FF6E29F542B2B7D54CED4D09B311 /* GDTCORTargets.h in Headers */, + D550A661B3494A4B35F2C5D80AE9347F /* GDTCORTransformer.h in Headers */, + BE02564DC828FBB8B5ADA941B8B7EBB1 /* GDTCORTransformer_Private.h in Headers */, + B77D3B6F22B7617B8894DBCE83B03A76 /* GDTCORTransport.h in Headers */, + 1C00496148C58C27B1A7B63E3DCF1F91 /* GDTCORTransport_Private.h in Headers */, + B0FE783C53D7E421BB90198A18CCE4E0 /* GDTCORUploadCoordinator.h in Headers */, + FF272F1B57CA44105BA30250A3206769 /* GDTCORUploader.h in Headers */, + B39DCEF9E3BCD040654935354C1615F7 /* GDTCORUploadPackage.h in Headers */, + DD57A8FDAB3C4E21A2D8178E615DBC2B /* GDTCORUploadPackage_Private.h in Headers */, + 659341F1ACE71F90C2FB866F7151D646 /* GoogleDataTransport-umbrella.h in Headers */, + 424FD6C5EF45451E5B6A44D5F0812D02 /* GoogleDataTransport.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 65BE8C0B0A47682E627326AB2F9ED961 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 0774BB819595848EFE186648456232D3 /* FIRActionCodeSettings.h in Headers */, + 5740D7EB95C08F8102D86F911A11BECF /* FIRAdditionalUserInfo.h in Headers */, + 11ADB7B4F9ED12EFBA865B78F904F354 /* FIRAdditionalUserInfo_Internal.h in Headers */, + EF1EA1700F8ED0FA06173C1AA5EF3936 /* FIRAuth.h in Headers */, + 1077F3BF04E76193AE0399A3AFB5149B /* FIRAuth_Internal.h in Headers */, + FA731AA062F9E319CB39B8E1F719E538 /* FIRAuthAPNSToken.h in Headers */, + 01ED47BBFC63F9DEA81CC6FF3B9FF121 /* FIRAuthAPNSTokenManager.h in Headers */, + A4C0B68390755E8C48C99AF53F4D9984 /* FIRAuthAPNSTokenType.h in Headers */, + A8C13C35155FB4D9B6C630D6F3E20520 /* FIRAuthAppCredential.h in Headers */, + 736E1003827BA8BAD28AF12533293C1C /* FIRAuthAppCredentialManager.h in Headers */, + 32674BEAF94B3C0FD01633091D9A34E6 /* FIRAuthBackend.h in Headers */, + 87ABCBDB371F23895FB5BC5014D7F6F3 /* FIRAuthCredential.h in Headers */, + 66BFB50E598AE2DB94E02CF609038DA0 /* FIRAuthCredential_Internal.h in Headers */, + A84E3E054DE421ACF71E2B61ED22D2DA /* FIRAuthDataResult.h in Headers */, + C344A5F89FD89526BADE0FC9A30DC244 /* FIRAuthDataResult_Internal.h in Headers */, + CFC09D6B4E3E7CA61E653731A0054477 /* FIRAuthDefaultUIDelegate.h in Headers */, + BA8A37E646FA398521BFE762D1DEB3D0 /* FIRAuthDispatcher.h in Headers */, + DD9A1E9F01CC5B146C6D2A3B269FBECD /* FIRAuthErrors.h in Headers */, + 44D75E78971EDCED0668823B4680B810 /* FIRAuthErrorUtils.h in Headers */, + 746CB640FA22213EEFDC668E514B1EC2 /* FIRAuthExceptionUtils.h in Headers */, + 5200FB78D3889E5F72EF3EAA5B9C71FD /* FIRAuthGlobalWorkQueue.h in Headers */, + 829ED10227DFAD882608CF831E54A96B /* FIRAuthInternalErrors.h in Headers */, + DFF0D5F0FE43F08D2CD0AF3013D46207 /* FIRAuthKeychainServices.h in Headers */, + F78D3CFED096A10C6F48356B3125E7ED /* FIRAuthNotificationManager.h in Headers */, + 69CF2BCA3714260383CF82730B197E72 /* FIRAuthOperationType.h in Headers */, + D8641688AC7D4A3A10DA3D566CE57B35 /* FIRAuthRequestConfiguration.h in Headers */, + 7CF9E22AABDAF5B34430D5B22493BBDC /* FIRAuthRPCRequest.h in Headers */, + 421E395A098392CADE58AE7E797192A5 /* FIRAuthRPCResponse.h in Headers */, + 70B449AEA3817C2E349AB5807E827B9B /* FIRAuthSerialTaskQueue.h in Headers */, + 9863271EEAB94EEC9EF6CE83FFC36C8E /* FIRAuthSettings.h in Headers */, + 01594852414E2C06EAF0A7A14DDF1F07 /* FIRAuthStoredUserManager.h in Headers */, + ACD5B442C2A2400AF828DD9220F842FD /* FIRAuthTokenResult.h in Headers */, + 3B6316AC0DA1373E767C9B0209B41061 /* FIRAuthTokenResult_Internal.h in Headers */, + 63B7890E2F887AB3E20D90ED2A090ADB /* FIRAuthUIDelegate.h in Headers */, + 24C320179674BD41CB7F3A3414D73F07 /* FIRAuthURLPresenter.h in Headers */, + 85A7198577FC80CF67D88D62AC1FBB91 /* FIRAuthUserDefaults.h in Headers */, + 5944A240523DD38BA0E6F1298F7704D9 /* FIRAuthWebUtils.h in Headers */, + 0C099081D0FA771263A448DA11A080F3 /* FIRAuthWebView.h in Headers */, + 89E4EE2B0E41C8EF8C8F5D1B2D8FAAE8 /* FIRAuthWebViewController.h in Headers */, + 80C6D096746ACC90E3B399A339A9BB3D /* FIRCreateAuthURIRequest.h in Headers */, + 05A98670EAFC266DD9605C109C50C6FA /* FIRCreateAuthURIResponse.h in Headers */, + 5EFCDFE0C52B734B271CB7DEEBB66EFF /* FIRDeleteAccountRequest.h in Headers */, + D6F9569A0CFAB7D4C0A34EFDA3C41D88 /* FIRDeleteAccountResponse.h in Headers */, + 080883B43705BD3A2602CF23123D9C20 /* FirebaseAuth-umbrella.h in Headers */, + BC7C183345CA9AA57583EF2F87343967 /* FirebaseAuth.h in Headers */, + 310E60A10CEF7E92552CC18842913A28 /* FirebaseAuthVersion.h in Headers */, + C45DC8EAFB904B6EE9CF7C71AB20FD32 /* FIREmailAuthProvider.h in Headers */, + 84D3379972A0DB708CFE91D19B92D4E5 /* FIREmailLinkSignInRequest.h in Headers */, + 8D042A683B5426587885AFB74C97ABD5 /* FIREmailLinkSignInResponse.h in Headers */, + 734298FD7737180F4034C7FE1B33BC22 /* FIREmailPasswordAuthCredential.h in Headers */, + F6ED8BB10A3715184C077FFD5EAFD71A /* FIRFacebookAuthCredential.h in Headers */, + 39EB68E40A752463B5C6199CE1FCACA5 /* FIRFacebookAuthProvider.h in Headers */, + 35644F3829BA4F595F4FC2089BEFB725 /* FIRFederatedAuthProvider.h in Headers */, + D19BBDCA93F064A433A26461781A2D85 /* FIRGameCenterAuthCredential.h in Headers */, + 84D41AE367A7099CE2B616FCE173A021 /* FIRGameCenterAuthProvider.h in Headers */, + EF4FD948D37328E05C32D034A17FA6FC /* FIRGetAccountInfoRequest.h in Headers */, + 4EB2ADCF5984C9875D11787D992A0972 /* FIRGetAccountInfoResponse.h in Headers */, + A3C8CA56DDB997836D307ADBA8A7F26F /* FIRGetOOBConfirmationCodeRequest.h in Headers */, + 67307FEADFCE1DF75197602D93192AB3 /* FIRGetOOBConfirmationCodeResponse.h in Headers */, + 4041C2939AD1934FE3E208D5FC0BB8A0 /* FIRGetProjectConfigRequest.h in Headers */, + D9A562AFDD4F455C8E535CFF572DB932 /* FIRGetProjectConfigResponse.h in Headers */, + 6F2CA0DF2C0510393356D83F01723AB4 /* FIRGitHubAuthCredential.h in Headers */, + D2162566ABE8F0D7B775E272FBDF6BC2 /* FIRGitHubAuthProvider.h in Headers */, + 2CF5274BF25904B65013C30868BEF7A2 /* FIRGoogleAuthCredential.h in Headers */, + EE9F328D814934285A7D9283665DC603 /* FIRGoogleAuthProvider.h in Headers */, + 060F29EAF6E34726F4A52995A1C31F96 /* FIRIdentityToolkitRequest.h in Headers */, + 5FF61C0EE3635BA0CF2652F83857E694 /* FIROAuthCredential.h in Headers */, + 38A3A814BF4126D07A208A4DA5E6E240 /* FIROAuthCredential_Internal.h in Headers */, + 145308AEE5F108115EA81C2449AF5E7A /* FIROAuthProvider.h in Headers */, + E6F2BD6EA9241D8C1D1D57B41E81A38B /* FIRPhoneAuthCredential.h in Headers */, + 0E71AF5DB4674C92E082BC8DAED13192 /* FIRPhoneAuthCredential_Internal.h in Headers */, + 8D8D0D39219AAB655FF64828B2C3C466 /* FIRPhoneAuthProvider.h in Headers */, + 0A047AEB1DCDC9C1FFE81071DBD4B239 /* FIRResetPasswordRequest.h in Headers */, + A6918169718169EBAFE493362576B4B0 /* FIRResetPasswordResponse.h in Headers */, + 5E1482CEFE1A697AAD9BD680ECFD4802 /* FIRSecureTokenRequest.h in Headers */, + 499B63D6AB0BC5F71DCDA86B3515E083 /* FIRSecureTokenResponse.h in Headers */, + DCBF0C3F62459C8DA2ACA422A9ACB164 /* FIRSecureTokenService.h in Headers */, + 548969E05ABE30268724380D5DC6E884 /* FIRSendVerificationCodeRequest.h in Headers */, + D24EC58C2B3EBBD4D939E35F95761487 /* FIRSendVerificationCodeResponse.h in Headers */, + 9F52D72B9ACF0B32AC5F4F08C91A7584 /* FIRSetAccountInfoRequest.h in Headers */, + 53FC0A1355D80EB46E589948D032E22C /* FIRSetAccountInfoResponse.h in Headers */, + 25F907EB20256BD810D4CE4CBA9CE48F /* FIRSignInWithGameCenterRequest.h in Headers */, + 005277412A118E72336AA68F490CCAD8 /* FIRSignInWithGameCenterResponse.h in Headers */, + 7A1EEAAAE641C0A6B99490F23EBCFC69 /* FIRSignUpNewUserRequest.h in Headers */, + 6FA627809D4B7555ACB813FB89E67BC3 /* FIRSignUpNewUserResponse.h in Headers */, + EABFBF6EE7D500715C1FFF7A39D6275F /* FIRTwitterAuthCredential.h in Headers */, + 3AC0D89BD349522248D74FAC83923DC8 /* FIRTwitterAuthProvider.h in Headers */, + 125F621BBE4D4D6B8EADBF317A92A9B7 /* FIRUser.h in Headers */, + 06F583BBED27B047350AD11D07832FC5 /* FIRUser_Internal.h in Headers */, + 1DB95DC1386E858190A46C9E55AB6DCF /* FIRUserInfo.h in Headers */, + 94F67C435C6D518BE0E21321878F758A /* FIRUserInfoImpl.h in Headers */, + 4211BBD8C4DDD32A77957D46BAC8D255 /* FIRUserMetadata.h in Headers */, + EF11B740F9921F0BA68479755F55BE21 /* FIRUserMetadata_Internal.h in Headers */, + D428922198BA1D5F1D0EDE1CEA10F0D1 /* FIRVerifyAssertionRequest.h in Headers */, + 5FFAAF8051EC966A14DCAA2549B0B208 /* FIRVerifyAssertionResponse.h in Headers */, + 05228D229EF9D77E6FB7D6531EF1AD19 /* FIRVerifyClientRequest.h in Headers */, + CF069321882F9D790761DF7ACE0A0482 /* FIRVerifyClientResponse.h in Headers */, + DB06C563A10D7BEE7495AB680C1D0243 /* FIRVerifyCustomTokenRequest.h in Headers */, + A97A33A98BC7B9331F3BF49BEC9A28B3 /* FIRVerifyCustomTokenResponse.h in Headers */, + E232D39A8F88B368FC05D31638BD99D5 /* FIRVerifyPasswordRequest.h in Headers */, + 33856C91E3F10AE9C71072821D7549D0 /* FIRVerifyPasswordResponse.h in Headers */, + 4B2BAF4BEAD936890EBF224BEABE6AEA /* FIRVerifyPhoneNumberRequest.h in Headers */, + B97A8885829D76823CEF3D8F8B6DAECA /* FIRVerifyPhoneNumberResponse.h in Headers */, + 92B30710112104E0AA19A4B4AD94320C /* NSData+FIRBase64.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 729AFFBCCEB94343B7ED7A41583E2047 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 811AB52D64C3E050CC2E53299EE69DBA /* APLevelDB.h in Headers */, + 57EF73A0F4CE8B7F9AB9430158ED0926 /* FAckUserWrite.h in Headers */, + 53451DD04E547BF568F92CABA14407E3 /* FArraySortedDictionary.h in Headers */, + DF5026C235F0454969EAF0557BBBBDB3 /* FAtomicNumber.h in Headers */, + BA27BAF6118E47DF5075FBD88CAE1A14 /* FAuthTokenProvider.h in Headers */, + AA38386A8BE18F2678EF806839CCCC9E /* fbase64.h in Headers */, + 856650522D4FF67C5310C5886EC3ACD2 /* FCacheNode.h in Headers */, + 510B3DE29FD0BCDA4B6746D07116E1BF /* FCachePolicy.h in Headers */, + 85D7F31EA14943CF035284A625DEDC42 /* FCancelEvent.h in Headers */, + 2B227E31966121C9308DC3C26AE58B01 /* FChange.h in Headers */, + EC4DCA3FB03E03F9EE2C2A51BD70E35B /* FChildChangeAccumulator.h in Headers */, + 3CC45C52DF050591BAA36EB1EE71035E /* FChildEventRegistration.h in Headers */, + C45DBE54266A6BAA1894DE42346CD56F /* FChildrenNode.h in Headers */, + A21E886D390FE0F02976716825A43860 /* FClock.h in Headers */, + 96495C1142B6C7D4CF0A6DE0EC1649D3 /* FCompleteChildSource.h in Headers */, + E8E3D470C5EC22993A18AA6D4A1885E3 /* FCompoundHash.h in Headers */, + 4474F193DA6D18B47AF0D15FAEC9F482 /* FCompoundWrite.h in Headers */, + A7201850468A114846F7404F74A73155 /* FConnection.h in Headers */, + 96AF0A80D25BBA2EE44E9EAF11B6997B /* FConstants.h in Headers */, + 9BB219421A0ABB05BE8E916BC3438A91 /* FDataEvent.h in Headers */, + 0993FE761A44E69AA2E72DFADC02CB98 /* FEmptyNode.h in Headers */, + EF6E8253DCFF30720547FCBAC9522CA0 /* FEvent.h in Headers */, + B4305EE7A47284C4EE66AF85D1CA9429 /* FEventEmitter.h in Headers */, + 4880A102EF70151AC12B34233941AD98 /* FEventGenerator.h in Headers */, + 304577D08B32B8EEF1E0A3F4EB4C398D /* FEventRaiser.h in Headers */, + 3D226A55716C60293080D0A4373425A6 /* FEventRegistration.h in Headers */, + 8ACA2F82307FBE898481DB5EBE90A520 /* FImmutableSortedDictionary.h in Headers */, + E677D8CB0C348F332D6034E697843702 /* FImmutableSortedSet.h in Headers */, + 1FB90CB335C7F408C1A3B50C60260790 /* FImmutableTree.h in Headers */, + 5D70B4B9CF146C0C463E7E718486C1AE /* FIndex.h in Headers */, + 54309C957347C1C0C28E0A9E1AEF4BE7 /* FIndexedFilter.h in Headers */, + 0A7197C9F569351D2CF42161DB976DCC /* FIndexedNode.h in Headers */, + 9D283658709A170CDA416FFDB5596637 /* FIRDatabase.h in Headers */, + CA2E5C7FC7863E3DBFF0DED0AC553E90 /* FIRDatabase_Private.h in Headers */, + C04D9D88E8823DBDBE93FABF90F1CF6E /* FIRDatabaseComponent.h in Headers */, + 4572A9AA4ED20FC74F97FF25F61A1CAE /* FIRDatabaseConfig.h in Headers */, + 232B8FD55E5AE573FBC1240327F0531E /* FIRDatabaseConfig_Private.h in Headers */, + A838B922272BA807C59A0444234CB076 /* FIRDatabaseQuery.h in Headers */, + E1D49C144D54C64E4343428EDCB10AB9 /* FIRDatabaseQuery_Private.h in Headers */, + 914D5A3BFCA9E5225BC71B39042EC967 /* FIRDatabaseReference.h in Headers */, + 967A9F26556882DDA842A8313E8FAA1E /* FIRDatabaseReference_Private.h in Headers */, + ACEB271844FF98CDAAB96B5234E7952B /* FIRDataEventType.h in Headers */, + 41C1EFE7A51F9566C1D092CF143D8C48 /* FIRDataSnapshot.h in Headers */, + E05C21DED9A65A758A143126C6D5D149 /* FIRDataSnapshot_Private.h in Headers */, + FBE49255F27DF8F87C0B0833C3EC1A94 /* FirebaseDatabase-umbrella.h in Headers */, + 1DF7E56E4C5D29BE699142FA9CA911DA /* FirebaseDatabase.h in Headers */, + 1C5ACABC25D19849875C4EED96392522 /* FIRMutableData.h in Headers */, + 52D87F5622DCE379129F8F6A2E49ED48 /* FIRMutableData_Private.h in Headers */, + 14137207C53B6BD8BA759E1201DBBE4D /* FIRNoopAuthTokenProvider.h in Headers */, + E17CF26F8F461FEE9FE9247D7EFAE10F /* FIRRetryHelper.h in Headers */, + EEC5F93435E158CD75D96D711D85AE4B /* FIRServerValue.h in Headers */, + 5ECD300B3A6A5A4492AD8DC9BC3DFEAD /* FIRTransactionResult.h in Headers */, + 250A5FB958EB8889F6C644955EAD89D0 /* FIRTransactionResult_Private.h in Headers */, + 71FC49E11979945D0516185D10F12725 /* FKeepSyncedEventRegistration.h in Headers */, + 49C2074E8EE7E853D47A8153491F5846 /* FKeyIndex.h in Headers */, + 4E19A1F970E4B153B9E86E6915089892 /* FLeafNode.h in Headers */, + CCF90603A2462A8652387889024C1C47 /* FLevelDBStorageEngine.h in Headers */, + 1258E3F349832C216AAF9543AA8FBA3C /* FLimitedFilter.h in Headers */, + DC7AFA4C9D4910DF09D0D17F9E7A1299 /* FListenComplete.h in Headers */, + 7E5C791ED2054058E01939AA1C7B827F /* FListenProvider.h in Headers */, + 9E70496AB08A15ED951EA85593776B00 /* FLLRBEmptyNode.h in Headers */, + A291FD3F0355831975D0B4A59E73C598 /* FLLRBNode.h in Headers */, + C3A5ACE1DC23B4872EC85665FA380BE5 /* FLLRBValueNode.h in Headers */, + 03CDDBD74599BBA80DB73792A5A71E09 /* FMaxNode.h in Headers */, + 2DB7AE4C7C8112A665C254846A827842 /* FMerge.h in Headers */, + 3EA606087C8FE2CCF75F04F011D9C2C8 /* FNamedNode.h in Headers */, + 2A43BF434B4113E7712C60013404C5BE /* FNextPushId.h in Headers */, + 3EE7A161A8B5A88E1B0F87885E8F1FE8 /* FNode.h in Headers */, + ACFF567479382FABD5FC6C08A619D15D /* FNodeFilter.h in Headers */, + 26047293FEF6FBF2586A4CAD44B5BA8B /* FOperation.h in Headers */, + 3D80FB2906F73C0A9A38EEEA4C0E14F7 /* FOperationSource.h in Headers */, + A7A8426CA84F3CCBC9FE1DD46AB9FF2F /* FOverwrite.h in Headers */, + 97CCBA353255E11BADCA18806B27FD90 /* FParsedUrl.h in Headers */, + E0C6DE864BA14F2541627635DA3C9A7F /* FPath.h in Headers */, + 0EC6311DC3EAD8B84EBB3FF2FCF3971C /* FPathIndex.h in Headers */, + 7CF7FFD0A2669F0E5730BBF0B64B7E6F /* FPendingPut.h in Headers */, + 10D660D7941990575531682340C2A0EE /* FPersistenceManager.h in Headers */, + E6C4A27C6EF10EDE432A71D21CAFE003 /* FPersistentConnection.h in Headers */, + E6255999FD8D4E7F135B69ED9AD241B2 /* FPriorityIndex.h in Headers */, + 30196B49A59B0E468EA80F41F7AE1BF0 /* FPruneForest.h in Headers */, + FEB1D452D618BF841E0C6D17EECD2489 /* FQueryParams.h in Headers */, + F8AD365E58CB0EFB79FBF9DBB2CD7EE3 /* FQuerySpec.h in Headers */, + F5C87E584986486407916F755C18FD05 /* FRangedFilter.h in Headers */, + 6339332668CBC70103B6DEE9FB028DB6 /* FRangeMerge.h in Headers */, + 0E9DCA2ECBEFCBC65211F7C911043B8D /* FRepo.h in Headers */, + DCB11F88D276324F226C7BD60A394FF6 /* FRepo_Private.h in Headers */, + DF0B93C62AB59CB66B51B26CABF146D7 /* FRepoInfo.h in Headers */, + 76CB8E6F7A230499A2EE885D58DA9516 /* FRepoManager.h in Headers */, + A6182BE2556FBF0EDE3418D2AF9EE4DF /* FServerValues.h in Headers */, + BC92E3E5D9BF0269D170E7ED8EAAB4FA /* FSnapshotHolder.h in Headers */, + 4C481897FD8E8E52957F30B1B89195DE /* FSnapshotUtilities.h in Headers */, + F44A9799A444E69EA9323A6760CF72EE /* FSparseSnapshotTree.h in Headers */, + 1F2DF5FD3EFFDFFBB8AC3EC9095DA457 /* FSRWebSocket.h in Headers */, + 0CEA55146F62CF4426A02BE2EAF227B3 /* FStorageEngine.h in Headers */, + 8803A474D24AAF4D7727A0B0F18969C7 /* FStringUtilities.h in Headers */, + D49D62E7098A8AA1E441B9EE644FD4D8 /* FSyncPoint.h in Headers */, + 9B5D2DEB300AF8CE182AB0A1360DFC49 /* FSyncTree.h in Headers */, + 0B4482C3DE927EB64F461B7D140A30C6 /* FTrackedQuery.h in Headers */, + A13B9060873282FF5A1D8C5868A5AE40 /* FTrackedQueryManager.h in Headers */, + A2ED5D0609EE3AC21E31B7731951F5EE /* FTransformedEnumerator.h in Headers */, + 2F09DA594401C8DA2D3E40E0FB830491 /* FTree.h in Headers */, + 4FFB9654C81EB2AEF0DE0819BCFA7D24 /* FTreeNode.h in Headers */, + 3C3FFD62DC33C9071947E05EDE02D6EA /* FTreeSortedDictionary.h in Headers */, + CD704E36BE63DAD07D996AFEEE41B300 /* FTreeSortedDictionaryEnumerator.h in Headers */, + 05EEAE84294B4B1584603A70515B54D7 /* FTupleBoolBlock.h in Headers */, + 6EA7C741EE40891E939673A78AE4AD5B /* FTupleCallbackStatus.h in Headers */, + A3FCD4884D44DFC98D96789BAE66444C /* FTupleFirebase.h in Headers */, + 45A7A6C69C576E122C6AA586B871D9C2 /* FTupleNodePath.h in Headers */, + 77CE5C7DE5029D3EF418E5DDB4B798F3 /* FTupleObjectNode.h in Headers */, + 96514FE01DB7DD791CE0F9E257046C82 /* FTupleObjects.h in Headers */, + 67797AC62117F9AD1487B46E758CD715 /* FTupleOnDisconnect.h in Headers */, + 47AC2BED04DD7F7F819E9BCDEFB0317E /* FTuplePathValue.h in Headers */, + 20364BF3F0F08AF21345FEBC48CA40B2 /* FTupleRemovedQueriesEvents.h in Headers */, + 089C37D47E1D50644531ECECF38A4CE9 /* FTupleSetIdPath.h in Headers */, + 0DE126AAB6E7597E4F19374B740E994E /* FTupleStringNode.h in Headers */, + 40C27F629E9FC14CFD215250DB7E813B /* FTupleTransaction.h in Headers */, + 7C30CD766CA9F0AADA2791BD5CB4EAB8 /* FTupleTSN.h in Headers */, + 8D588CC2FA017E6A6C3571555C0D9BA4 /* FTupleUserCallback.h in Headers */, + EEFB9D707C02D1770D9207FC55EFF4AD /* FTypedefs.h in Headers */, + A89184ED30C5CCFA36582907765E8C30 /* FTypedefs_Private.h in Headers */, + B69FBAAECADD405ABC185DFFFF9E68A0 /* FUtilities.h in Headers */, + C1DC996199ED5D451E07ED27FE3232DF /* FValidation.h in Headers */, + BECD261B295C2FD449F9D814CA88C33A /* FValueEventRegistration.h in Headers */, + 53090D8A8A453CB8981AA4942DDB6316 /* FValueIndex.h in Headers */, + D7C855A8C3AFE0D93B5BEF9445155203 /* FView.h in Headers */, + BF44407CEAAB1DCAD39E6BB84840FCA5 /* FViewCache.h in Headers */, + 154AF0870F0AB9C567F756A82DDBB75D /* FViewProcessor.h in Headers */, + 03D2B29AEF2411851C43BEE76B6A4089 /* FViewProcessorResult.h in Headers */, + EB8B9327BA306669979C106B3679A79B /* FWebSocketConnection.h in Headers */, + D6F9AB919063E6A05BD96C089D8F4212 /* FWriteRecord.h in Headers */, + B911E85427B18A240F27ECB3FD87B9ED /* FWriteTree.h in Headers */, + 2FC3F41D2A6B4DBD308B274E348AE5D3 /* FWriteTreeRef.h in Headers */, + 41239C845E63547347B6C4ED3958C4E2 /* NSData+SRB64Additions.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 78715B5E1A1D5B389DD7D32D1D6A10C9 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 485A2AE62FA6D80EE8813531ED9B5202 /* FirebaseInstallations-umbrella.h in Headers */, + BD2C3D1F84AE9307C8C7EBBEEC434288 /* FirebaseInstallations.h in Headers */, + E2C824B5BD5A338F7F02002AEA350F65 /* FIRInstallations.h in Headers */, + 053FEEE0BA88BE2CF6A5FEC455F61C6D /* FIRInstallationsAPIService.h in Headers */, + 451A14C1427555EDBE77B22E6248A5A8 /* FIRInstallationsAuthTokenResult.h in Headers */, + 39C37BF824F49AD6E5899219C0733868 /* FIRInstallationsAuthTokenResultInternal.h in Headers */, + 2AC8BBF043B238F945C0EB0540E15245 /* FIRInstallationsErrors.h in Headers */, + 85A47A05438D20A7C1D8555A2A1685DA /* FIRInstallationsErrorUtil.h in Headers */, + 74C6EA6ABD3894D4CB1DA0BC7C568949 /* FIRInstallationsHTTPError.h in Headers */, + CB33B3BF38557E32B50F3A9A23CF1960 /* FIRInstallationsIDController.h in Headers */, + 2290E64D712562D6C1C58ABABB4410F5 /* FIRInstallationsIIDStore.h in Headers */, + 6696154B02D797B399EFF91994EC3ACE /* FIRInstallationsIIDTokenStore.h in Headers */, + 13F1B40D631C98E3056EA237C952976B /* FIRInstallationsItem+RegisterInstallationAPI.h in Headers */, + 8E1E6EA52ED6AEDCD7B273517124FC0A /* FIRInstallationsItem.h in Headers */, + 5DEABA5DE651EE7AC516ACF42AF1AE86 /* FIRInstallationsKeychainUtils.h in Headers */, + 4DCC0459009E8F4468F83EFDFCFCEA0A /* FIRInstallationsLogger.h in Headers */, + 267984A3CFED0ECA80A4FF1CDCD73A39 /* FIRInstallationsSingleOperationPromiseCache.h in Headers */, + 4933911483C97AE1C3763B7125D705D4 /* FIRInstallationsStatus.h in Headers */, + 2D22FBD03C50829975252B9B61140698 /* FIRInstallationsStore.h in Headers */, + EEBFEC47FF999D55385F032EFE094C56 /* FIRInstallationsStoredAuthToken.h in Headers */, + 1E3C13069482107A2782E9B72D92B4C3 /* FIRInstallationsStoredItem.h in Headers */, + 4A1CD1D2408868B2F10F4E7B1F3CDC7A /* FIRInstallationsVersion.h in Headers */, + 083E477E9DE03853989A81135EE7B8A7 /* FIRSecureStorage.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7FC08E0DC08BDE1007A09DECCD6D5D0A /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 28D4F3F5E6A3A3CCA9FEE73090868E7D /* FIRAnalyticsConfiguration.h in Headers */, + 9A46766FC7639FEC0E4334173B8B08C4 /* FIRApp.h in Headers */, + 90713ABF85048A803FBE61FC84C305DF /* FIRAppAssociationRegistration.h in Headers */, + 3030D09F41A0B87FBB650A149BCD0633 /* FIRAppInternal.h in Headers */, + 4965BB761B0F772E219CCB324F49308B /* FIRBundleUtil.h in Headers */, + 2F4E04FEAA590B129BEF1D1E1CC9877E /* FIRComponent.h in Headers */, + 83F551270A9AEBBB05D87F670759B969 /* FIRComponentContainer.h in Headers */, + 62BCBA0AD187DA6F823C18AFE24679E4 /* FIRComponentContainerInternal.h in Headers */, + 95A28903392D43C9D339D4AD55728D72 /* FIRComponentType.h in Headers */, + BEDA31B8992C066AC6EBB699E0710776 /* FIRConfiguration.h in Headers */, + B2720D8994A4C764E428120DCE1111EB /* FIRConfigurationInternal.h in Headers */, + F6C053B5EC1EAFAF1F600DED4C8D3C51 /* FIRCoreDiagnosticsConnector.h in Headers */, + F4E1B21EA4746CAAA2951C1E4817F876 /* FIRDependency.h in Headers */, + 814503A3FEEBEE71A94C06CD679D7A70 /* FIRDiagnosticsData.h in Headers */, + B662C3D30661957B6266CCA850A0D88A /* FirebaseCore-umbrella.h in Headers */, + 0287026B8FB380A3A97DFE227460A76B /* FirebaseCore.h in Headers */, + EFA6867F1BDDBE37A93ECF9AE99AD090 /* FIRErrorCode.h in Headers */, + 77A9A262E4D6C548BD1D5E09ACFDDA86 /* FIRErrors.h in Headers */, + F37356E1CB7F1FB29DCADBF6A7DAED23 /* FIRHeartbeatInfo.h in Headers */, + 8EBBF96A0C8E33AD826BB5E969077FEA /* FIRLibrary.h in Headers */, + 0691B681221DC47A2198606B36370CF5 /* FIRLogger.h in Headers */, + 7692216000F15A8ECB12D661C9B53313 /* FIRLoggerLevel.h in Headers */, + 5B41D96B08D4E606479415747D056CBE /* FIROptions.h in Headers */, + 732CE8A85FD61C5EEC22FA9413066075 /* FIROptionsInternal.h in Headers */, + 0B7BB2E29393260190CF1C0F924A5C0E /* FIRVersion.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8A0AD7019F6480A5E1308447F085DD12 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 244138140167BCD8E48878E493DB2432 /* cct.nanopb.h in Headers */, + 00C1ADFC99A069C522547330009F4E19 /* GDTCCTCompressionHelper.h in Headers */, + CB76EA65EE8382206C7881717DCF0F3D /* GDTCCTNanopbHelpers.h in Headers */, + 106227B3E6239B6E36478FA17B60490E /* GDTCCTPrioritizer.h in Headers */, + 6AB97FC38C60F471CFD764389E50AB4D /* GDTCCTUploader.h in Headers */, + 57A9284BFD43EE2DF4CB37CC729CC59D /* GDTFLLPrioritizer.h in Headers */, + 20A4282A9E48189F614BF804F634953E /* GDTFLLUploader.h in Headers */, + 9DF37E2384BBDB8D801BEDAF8A735642 /* GoogleDataTransportCCTSupport-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 93AAA6E3EB2241BD545A3DAF71A2F378 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 1F2F06D3F5D78980F67821B553F0883B /* NSError+RLMSync.h in Headers */, + E8A754B37F44784D8B78A872DE6B2C92 /* Realm.h in Headers */, + C2A9D7E2A7356B9BD179D726A2418F4B /* RLMAccessor.h in Headers */, + BCF68AD207127F823C99CEB4AD4AAE5D /* RLMArray.h in Headers */, + 57B3FEF620AA9F256E6DFC810DF30C04 /* RLMArray_Private.h in Headers */, + 1528AD86DB13AF6FD139D29AD8F17999 /* RLMCollection.h in Headers */, + 89DE4BDBF92376984DFFA4D6B9A789E7 /* RLMCollection_Private.h in Headers */, + D02A8F3A322426429314853341237059 /* RLMConstants.h in Headers */, + B4A64C9136805CB6BE283579A76C7658 /* RLMListBase.h in Headers */, + 2417A9EEC50FA31DD6F764CE4A64762F /* RLMMigration.h in Headers */, + F489AF2C3A4BAA41260095DAB13D5C17 /* RLMObject.h in Headers */, + EB86DF3DFB2AF93D4DB8CDD6F7295F73 /* RLMObject_Private.h in Headers */, + 73AC76F3FEC279A4AF42D33F3743EFB1 /* RLMObjectBase.h in Headers */, + 487D53153D55CE66F0EB58DD50702B51 /* RLMObjectBase_Dynamic.h in Headers */, + 7A2C7D86EEC730D0860FDBCCFA634D19 /* RLMObjectBase_Private.h in Headers */, + 26F0BB95414AA62ACBB0983D8385C6A3 /* RLMObjectSchema.h in Headers */, + B34A0A6D659D5D2F79FEEEAF83B87B43 /* RLMObjectSchema_Private.h in Headers */, + 05E537D6954BE54C14B0C2D0BD748B27 /* RLMObjectStore.h in Headers */, + AB378EF4F418F0A00D68FA7B37A60491 /* RLMOptionalBase.h in Headers */, + C1147050F9B9901A377C3A9B6E253612 /* RLMPlatform.h in Headers */, + 7ED9728B8FDE98CAAA531D19854AFA4A /* RLMProperty.h in Headers */, + CC272BD40CB17460F25E41C5F1604501 /* RLMProperty_Private.h in Headers */, + 0624DA01ECA96AE03CA4CD86AE0DD698 /* RLMRealm+Sync.h in Headers */, + DB94BFB21436F7294C66D5CC40307397 /* RLMRealm.h in Headers */, + 1124DA5AA0690636691F8C16B54BE187 /* RLMRealm_Dynamic.h in Headers */, + A9F434D093BEFF605C24C2D0B71B4767 /* RLMRealm_Private.h in Headers */, + 015A0369EB034ABD53223DCC1697046C /* RLMRealmConfiguration+Sync.h in Headers */, + 163DF9C4EF2939A35285E5487D8AC718 /* RLMRealmConfiguration.h in Headers */, + F3365FB46BF0E869D92A464B07BB9948 /* RLMRealmConfiguration_Private.h in Headers */, + 84E1FE5DD6DE9A0C7764A29FC9F11155 /* RLMResults.h in Headers */, + F15001312B4A1AFC5C0C39DB4B63962A /* RLMResults_Private.h in Headers */, + 671CD8B463F59407A51B7C18F8DBD7E1 /* RLMSchema.h in Headers */, + FEC148147D381D36ABDEAB648CFDB90A /* RLMSchema_Private.h in Headers */, + CD19EBB20252CC326563A92DF6098DC5 /* RLMSyncConfiguration.h in Headers */, + 4A954E1410CC831251D1883F3341A975 /* RLMSyncConfiguration_Private.h in Headers */, + 7B3161F2A225550C77AFEA5B4376A50B /* RLMSyncCredentials.h in Headers */, + AADBCD1B21CD4A8302788D60CBA914EB /* RLMSyncManager.h in Headers */, + D844CF4AE7817A9A17D4BEC4BC39598E /* RLMSyncPermission.h in Headers */, + CB40C58EF0AFEC7A5076DA07A08179C1 /* RLMSyncSession.h in Headers */, + 84E8B926FACE4FA809F931BFB6CEF96D /* RLMSyncSubscription.h in Headers */, + 329FC3E722DE36B8B00674D23CCB031E /* RLMSyncUser.h in Headers */, + 3820FCCABF659770A7A74440C3001C87 /* RLMSyncUtil.h in Headers */, + BF08EB397775EFE40ABE7B19BA55B672 /* RLMSyncUtil_Private.h in Headers */, + A3F37516CDD7B7C6E9E67F7937775CB8 /* RLMThreadSafeReference.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 942D1A99CD2EB4117FD8364A88A18F94 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + C26E44D2FA1813006202861296AD674A /* GTMSessionFetcher-umbrella.h in Headers */, + 2E7D83859CEC3D469A12BBB2E4D45C90 /* GTMSessionFetcher.h in Headers */, + A3322FBABBDB335E5CF7AAF320C2E304 /* GTMSessionFetcherLogging.h in Headers */, + 01AFCBEBAA6285E88424E06CA0278F02 /* GTMSessionFetcherService.h in Headers */, + 5566EE2A5696C5683168F1310B8B6A66 /* GTMSessionUploadFetcher.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AB42636B01CE99818F443BE0FBF58216 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 9DA2D6CB3F354CD7BD716893BF59D434 /* Pods-GeekbrainsUI-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B66C99A5DCAA527C8DC517F3F1ABC10A /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 22BB761259397129B43938FB60B72694 /* dwarf.h in Headers */, + F578887CD8E4D6C5C3EEEC9C36267E4D /* FIRAEvent+Internal.h in Headers */, + E86ACBB0F434E9F789171A39E6E6B4CA /* FIRAEvent.h in Headers */, + FD62C5FE0B45A77753550EE79E30158B /* FIRAValue.h in Headers */, + EE530EDDE074E7E18356530C0D3F86BC /* FIRCLSAllocate.h in Headers */, + A4DB7F899CD3EB9B94D334EA74349171 /* FIRCLSApplication.h in Headers */, + 327D2D48C2BCC571AA41F4A2C58BB56D /* FIRCLSApplicationIdentifierModel.h in Headers */, + 4B51C5D0CC6C7D5D6643929AF2A55E73 /* FIRCLSAsyncOperation.h in Headers */, + 8B6B7B2DE1FB4B8D0E3E6405369E0175 /* FIRCLSAsyncOperation_Private.h in Headers */, + 79A802BDE4CDD966154AB840A0BE97AD /* FIRCLSBinaryImage.h in Headers */, + 3F8531662DDBAC0735862ABB42B87261 /* FIRCLSByteUtility.h in Headers */, + C6193624E995284C7BA2CBDED8140BCC /* FIRCLSCodeMapping.h in Headers */, + D32191E7AD6C6BB010E5F4C81D7395DE /* FIRCLSCompactUnwind.h in Headers */, + B65F33E26B5FB71C678700D5F5A7545D /* FIRCLSCompactUnwind_Private.h in Headers */, + 2A3AFA6428A5736852667E746A9B4965 /* FIRCLSCompoundOperation.h in Headers */, + 25C28324FCF66DEE65204B577286014A /* FIRCLSConstants.h in Headers */, + E3C9D247024140F47BE6C2E78BE4A8A1 /* FIRCLSContext.h in Headers */, + 233A62E1F210AFA9D29F7155962C45CE /* FIRCLSCrashedMarkerFile.h in Headers */, + 2FAAB421A3DAB5159D3037ED784F5A1C /* FIRCLSDataCollectionArbiter.h in Headers */, + CBD382124C7E260E04DE76E2D47E5D49 /* FIRCLSDataCollectionToken.h in Headers */, + 5D152B277EA59399C0DFEE9B9E32C04B /* FIRCLSDataParsing.h in Headers */, + 44880169C9C9ED91088E113D9DE078D9 /* FIRCLSDefines.h in Headers */, + 79430F0BED1A1C23B5BE850519FDB48F /* FIRCLSDemangleOperation.h in Headers */, + 43B3C466C7CC8BE2BA0C3EED7ED64D66 /* FIRCLSDownloadAndSaveSettingsOperation.h in Headers */, + FA3B86D66C5A29BFB22D4E06D170008E /* FIRCLSdSYM.h in Headers */, + 1FA52DEEF9744E3977F2625A2A061F4A /* FIRCLSDwarfExpressionMachine.h in Headers */, + 4327BC053CFE77040AE8E4FFB638AC64 /* FIRCLSDwarfUnwind.h in Headers */, + 8E2FD15B48ABCD18FD53564E93634858 /* FIRCLSDwarfUnwindRegisters.h in Headers */, + 948003424F77D78D8311A9424B25C5DC /* FIRCLSException.h in Headers */, + B0136F14A953A409C7F2B4CA854218BA /* FIRCLSExecutionIdentifierModel.h in Headers */, + 2281CAC912E936EA234D600D4164E6FF /* FIRCLSFABAsyncOperation.h in Headers */, + DEDC2DF805E7A33039F50B4CF5385B03 /* FIRCLSFABAsyncOperation_Private.h in Headers */, + E40BDD639B905C0F84E5FEDFF8A06489 /* FIRCLSFABHost.h in Headers */, + 3192B3017FBA533E4B4C3FA4DB56BC33 /* FIRCLSFABNetworkClient.h in Headers */, + C99FF6D0D7178FD5C92A3F07F06F7354 /* FIRCLSFCRAnalytics.h in Headers */, + F8DEC63926F444A3C55A03FAB1A7E0E9 /* FIRCLSFeatures.h in Headers */, + BB6EEDA0E565A940475C5DD52D69EC16 /* FIRCLSFile.h in Headers */, + 78EA39899C0BABD9026C8DDB3B70A77F /* FIRCLSFileManager.h in Headers */, + BB55DA90F8B3A543595F1693EDBF9288 /* FIRCLSGlobals.h in Headers */, + 349186DDCB8B4F9CE5ED3466B2E7CF6B /* FIRCLSHandler.h in Headers */, + A238E096D398756700024A69B3FF87E5 /* FIRCLSHost.h in Headers */, + 4EFD0A30A914F22731EE4A40DC326D7F /* FIRCLSInstallIdentifierModel.h in Headers */, + 7F5FB39EB957A65F199D45D50E7944F4 /* FIRCLSInternalLogging.h in Headers */, + 5F32D66997FAFF2003B51D3DCA0084C7 /* FIRCLSInternalReport.h in Headers */, + 960DC0390A32D0C6497616F30519A3A8 /* FIRCLSLogger.h in Headers */, + 345A70B5409ED930BC794A87185A9ECE /* FIRCLSMachException.h in Headers */, + 54F5F4C0D1B17878CB7483057DF28DC6 /* FIRCLSMachO.h in Headers */, + B42D1B76645FDC0DB698913B44E3FD31 /* FIRCLSMachOBinary.h in Headers */, + 768ACAAB9337C49D8D6603FB687B758B /* FIRCLSMachOSlice.h in Headers */, + C0A52F5EED1C7BB3E724772F27C2107B /* FIRCLSMultipartMimeStreamEncoder.h in Headers */, + 58A79EDF924A2F3AEE3C72935CC15180 /* FIRCLSNetworkClient.h in Headers */, + 7F2B6F5A4496F2408F5DCE66C31549E9 /* FIRCLSNetworkOperation.h in Headers */, + C3278A315F4C3470DE2C205BCCE6FF61 /* FIRCLSNetworkResponseHandler.h in Headers */, + A9F0A5B1941617A83DD5A5CE55E920A0 /* FIRCLSOnboardingOperation.h in Headers */, + F8A82726B7A671622749B22605941E29 /* FIRCLSOperation.h in Headers */, + EA00EE88744C5881291DC07EB5D3F878 /* FIRCLSPackageReportOperation.h in Headers */, + 4A4BD18B37AE610488A6EB64348BF100 /* FIRCLSProcess.h in Headers */, + 086644D14E2C73B1B6548DFC6291716A /* FIRCLSProcessReportOperation.h in Headers */, + CBA7618534F6AFF89B291137C1E563DE /* FIRCLSProfiling.h in Headers */, + 7562A36B958058693943932E86972DAD /* FIRCLSReport.h in Headers */, + 9A0AAED2AB0733F90A24FFBEA9E6D207 /* FIRCLSReport_Private.h in Headers */, + 21AC9E91D06D23EAF3FE05BEBF2343F2 /* FIRCLSReportManager.h in Headers */, + 1C2B4E586A1F2B4C5322D513467DF7BD /* FIRCLSReportManager_Private.h in Headers */, + 5321CA455C7EBD046B2798EFEE05A176 /* FIRCLSReportUploader.h in Headers */, + 223AB247898DEDBD7F5FFC940C7C2F11 /* FIRCLSReportUploader_Private.h in Headers */, + 975429A6D0E9F5E96FCBE23D5FCECAF7 /* FIRCLSSerializeSymbolicatedFramesOperation.h in Headers */, + 5505CF55838E236C8016022D4036FCF9 /* FIRCLSSettings.h in Headers */, + 3793E9E56166DDF584DE3D1ED3F44BA8 /* FIRCLSSettingsOnboardingManager.h in Headers */, + DF9717EF87618FDD7C66DD28E1C60337 /* FIRCLSSignal.h in Headers */, + 6695F09A37394B2A0080FEAC06C90687 /* FIRCLSStackFrame.h in Headers */, + 480689C1DDB28753482E28AC9FF63766 /* FIRCLSSymbolicationOperation.h in Headers */, + 8E0CB520C8D4CD8BFB5D845B649DE95F /* FIRCLSSymbolResolver.h in Headers */, + 7BFCB643156C73EDB574F8B7EB07E195 /* FIRCLSThreadArrayOperation.h in Headers */, + 254065603D8242A442C726A47E3A8333 /* FIRCLSThreadState.h in Headers */, + 8C941FE6752DAFE773636D80DD1B0403 /* FIRCLSUnwind.h in Headers */, + 76AF55F995E98C3136D9EF5EEB5183C5 /* FIRCLSUnwind_arch.h in Headers */, + FC99D9DC765D1D98CAA34C953111B8DD /* FIRCLSUnwind_x86.h in Headers */, + ECF337E4480792C1847DDFFA941624D4 /* FIRCLSURLBuilder.h in Headers */, + 0BDB2ABF3673EE91183602A632E110DD /* FIRCLSURLSession.h in Headers */, + 080D03ECB8EB85A8973418C1029F8232 /* FIRCLSURLSession_PrivateMethods.h in Headers */, + DE152BBBB34FB2781BC240CAF46D6D9C /* FIRCLSURLSessionAvailability.h in Headers */, + 67A7DCEFFDB8053818542209031CCF66 /* FIRCLSURLSessionConfiguration.h in Headers */, + F9E1E2832DE5DDFCD116BD7A9D47A714 /* FIRCLSURLSessionDataTask.h in Headers */, + DD9084988260C41FB166F565F0BE7C8C /* FIRCLSURLSessionDataTask_PrivateMethods.h in Headers */, + 9DF541C86B5E71B2135BB0A498B14FA5 /* FIRCLSURLSessionDownloadTask.h in Headers */, + 19C89AD61B5C218AF3A83907C9034AFC /* FIRCLSURLSessionDownloadTask_PrivateMethods.h in Headers */, + E7F3104033356EB1C7CF50CF9F50BA2D /* FIRCLSURLSessionTask.h in Headers */, + 16CA59A199E971DD9F88867955385A7D /* FIRCLSURLSessionTask_PrivateMethods.h in Headers */, + B8242942F7B8DF87CD3AA1B3144AC793 /* FIRCLSURLSessionUploadTask.h in Headers */, + 27A79532731B7482EBD56538CA239C3F /* FIRCLSUserDefaults.h in Headers */, + 13AA8418142F07AAFF784A61648BC738 /* FIRCLSUserDefaults_private.h in Headers */, + D6BDC66B20876EEA2063D3839B9BC2AC /* FIRCLSUserLogging.h in Headers */, + 0D7DF325BBCE5DFE5BB5715AFB53BB3D /* FIRCLSUtility.h in Headers */, + 9018A316C9EAF23C95D14A54C56A4FAF /* FIRCLSUUID.h in Headers */, + 421BF2AA16068673704B71CCB2752678 /* FIRCrashlytics.h in Headers */, + FDA1A7CF340E7ED18F9BD24A8159A727 /* FirebaseCrashlytics-umbrella.h in Headers */, + 4617CB956B78F75EAAE1989392DC3AD4 /* FirebaseCrashlytics.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + EEA9A07530D934CC2168662DA90FF16D /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + F115628BFC2BC53DC023959AC44CF63E /* FirebaseInstanceID-umbrella.h in Headers */, + 7D3519B7BD29773910B8E46594044E4E /* FirebaseInstanceID.h in Headers */, + 0ACC49E6050325926CBF6FF882E6BB5C /* FIRIMessageCode.h in Headers */, + 0B8814B2572EB8DC25293DEC91B7B998 /* FIRInstanceID+Private.h in Headers */, + B7C9B8EA1E3EC6BC5FE0D321F0D344D0 /* FIRInstanceID.h in Headers */, + 9F3E07DB01C10E27306BE87A68C8F953 /* FIRInstanceID_Private.h in Headers */, + 909A08C1D8A52499BA5B99546F8CEF13 /* FIRInstanceIDAPNSInfo.h in Headers */, + A8779CABCF0CDABF0912B36B7F218AF1 /* FIRInstanceIDAuthKeyChain.h in Headers */, + B4A23C2A4315131EBC064AA3E48E028B /* FIRInstanceIDAuthService.h in Headers */, + 820F023B4F1DA3B081BE889D507FEAF5 /* FIRInstanceIDBackupExcludedPlist.h in Headers */, + 610A300B1FA5A7BAA63BD7E2221083B1 /* FIRInstanceIDCheckinPreferences+Internal.h in Headers */, + 55D4605016980ADF1D950E15524E11BC /* FIRInstanceIDCheckinPreferences.h in Headers */, + 4628F1BE250F88479FEEFD9C6113A488 /* FIRInstanceIDCheckinPreferences_Private.h in Headers */, + F4852C9980C392297EB26AB2134F20BF /* FIRInstanceIDCheckinService.h in Headers */, + D791B8B87509DE26CC906CB5C9142DB6 /* FIRInstanceIDCheckinStore.h in Headers */, + F4EDBD322437E1760123F3049920A076 /* FIRInstanceIDCombinedHandler.h in Headers */, + 1A8834B447E36558097D854BDCA3CE1B /* FIRInstanceIDConstants.h in Headers */, + 9A55B8614F7B2857DC4CF7116D99CD0A /* FIRInstanceIDDefines.h in Headers */, + 95453B89E56A3605CE44BCA8F4AA9C4F /* FIRInstanceIDKeychain.h in Headers */, + EA986A7DD96309B973A756B7AFFBE78C /* FIRInstanceIDLogger.h in Headers */, + 7B6535A609288EAB34642AB7821FFDF8 /* FIRInstanceIDStore.h in Headers */, + F0954D903D841D60F77422167F1D73E3 /* FIRInstanceIDStringEncoding.h in Headers */, + C3DC3E5EB0E222AF71D2CFD37AC296AB /* FIRInstanceIDTokenDeleteOperation.h in Headers */, + A8A39094945930EF940D31A4B1D0883B /* FIRInstanceIDTokenFetchOperation.h in Headers */, + 9DF37CEF36132C74D8524966D7D856B7 /* FIRInstanceIDTokenInfo.h in Headers */, + 122559F2B179231A54AB928B00AB712C /* FIRInstanceIDTokenManager.h in Headers */, + D51D187A0FF24AA5F7B8E60B4F9B74D7 /* FIRInstanceIDTokenOperation+Private.h in Headers */, + 42F1937DE5C9F20409E98F556239C14C /* FIRInstanceIDTokenOperation.h in Headers */, + 3137683088C1F6A8B6D69F4DE97E2616 /* FIRInstanceIDTokenStore.h in Headers */, + 62453455CFB5678D90CB44A7971B3834 /* FIRInstanceIDURLQueryItem.h in Headers */, + 3A43E46338D78FD720DC9A186EFB806B /* FIRInstanceIDUtilities.h in Headers */, + 2BF4961A85867BF2B3296356CA18B89B /* FIRInstanceIDVersionUtilities.h in Headers */, + C4E325C2C2A0651CCFF2F40DCA5561C5 /* NSError+FIRInstanceID.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FCA903FBC6FCC19FBFF772C5B82479CE /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 526D4A6E90525A42DEEE53B2EAD2C5E8 /* Kingfisher-umbrella.h in Headers */, + 74F16EAC797A560DA6EE944A8CEBEC04 /* Kingfisher.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FDEFE72E4C42ABB8F0B52B69377EDED7 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 5A07AF42772F614C2A1945E8CA1B57ED /* firebasecore.nanopb.h in Headers */, + 0A30AF984C7DDEF87C22604B8BD8E3B8 /* FirebaseCoreDiagnostics-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 150CF0BF528D1824CF07A27D3513D10E /* Pods-GeekbrainsUI */ = { + isa = PBXNativeTarget; + buildConfigurationList = 375D202C856DE3999522590304F667FD /* Build configuration list for PBXNativeTarget "Pods-GeekbrainsUI" */; + buildPhases = ( + AB42636B01CE99818F443BE0FBF58216 /* Headers */, + FA58A512B6AA6B8C15C6C6FA37A72013 /* Sources */, + DEFC5A6DE8C7777B605CEB1ED61C8064 /* Frameworks */, + DD9B7677CAF38EB3188071013D3B1DF9 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + F3E52C14FC1EDF7D7B2A641852AE94B7 /* PBXTargetDependency */, + 93F64BACFD59E8C1EBE8870A6F9BB1C1 /* PBXTargetDependency */, + 354F4196A3C64F7E9180204EEA112FE4 /* PBXTargetDependency */, + CB40789F78AC2DF2FA8AE45D1BAE2147 /* PBXTargetDependency */, + 0709C227FCA648E1B27A554B31AD14C9 /* PBXTargetDependency */, + C3EACFC9CA6028D6F606298EAE22AF91 /* PBXTargetDependency */, + 57D25E0CD95447EAA304C21B8CAA14A2 /* PBXTargetDependency */, + A05EDAFEA754F095FB80282832B52E06 /* PBXTargetDependency */, + AAF9ACD44059C396BB32C91452A948F3 /* PBXTargetDependency */, + 638210AC0EDABFD0A6DFFD9B2E972A00 /* PBXTargetDependency */, + CC6DC633F22EFA8A07C08516F05FBBF4 /* PBXTargetDependency */, + 83BE3790A7E016D11AE8BC46AB5CEDD0 /* PBXTargetDependency */, + F6802ACE8F744B4C89934305521D6666 /* PBXTargetDependency */, + 54BBD33BAFC8EA14B7F49677BAC261DE /* PBXTargetDependency */, + 349F05A2AEC17E9D92FE6B8520EE5D61 /* PBXTargetDependency */, + A450B05F74819E087335E1B622481147 /* PBXTargetDependency */, + 6E1AD25281BA3E9568151D27C726C065 /* PBXTargetDependency */, + A3959E97FDB930F5D29E4012D45111A7 /* PBXTargetDependency */, + 53C766FD17017572499CFD5BFC6E0A93 /* PBXTargetDependency */, + B19591BF175558D9D5B01FB5E9D7C18F /* PBXTargetDependency */, + E75C1481C7E14B1061F64ACCBD9970E3 /* PBXTargetDependency */, + E39FA4393FD88879E749D9E109835E26 /* PBXTargetDependency */, + 2A4AE265C5D4CE6F443EC05F98E07036 /* PBXTargetDependency */, + 0DC11DAFCEAE58BDB6363229AEC2DC4D /* PBXTargetDependency */, + ); + name = "Pods-GeekbrainsUI"; + productName = "Pods-GeekbrainsUI"; + productReference = E05AE6A00C585CABBD21A96927641401 /* Pods_GeekbrainsUI.framework */; + productType = "com.apple.product-type.framework"; + }; + 2BBF7206D7FAC92C82A042A99C4A98F8 /* PromisesObjC */ = { + isa = PBXNativeTarget; + buildConfigurationList = 51DE3347D9C3728AF991BCBE24CCF522 /* Build configuration list for PBXNativeTarget "PromisesObjC" */; + buildPhases = ( + 16CEA5ACD246CC0CBC31EE29385A1736 /* Headers */, + B8317F5C34576C4D38F48590B4D7065D /* Sources */, + 1521E8D7F31158212B2C92D4C9245A3A /* Frameworks */, + 3665B73FD774F7A0CF41D8D25F37340B /* Resources */, + 42CA76F9B31888F4140DDB3FDCE6D0DF /* Copy . Public Headers */, + 6A61B315E33CF5864E2B7603DBA4CA7B /* Copy . Private Headers */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = PromisesObjC; + productName = PromisesObjC; + productReference = 3347A1AB6546F0A3977529B8F199DC41 /* FBLPromises.framework */; + productType = "com.apple.product-type.framework"; + }; + 4402AFF83DBDC4DD07E198685FDC2DF2 /* FirebaseCore */ = { + isa = PBXNativeTarget; + buildConfigurationList = BECB003B49DC8868FC475E308A9E71DF /* Build configuration list for PBXNativeTarget "FirebaseCore" */; + buildPhases = ( + 7FC08E0DC08BDE1007A09DECCD6D5D0A /* Headers */, + 289A5A2E0E1583593915AC7312ABEB45 /* Sources */, + 35816E06BF9027638E501B13C6D866FF /* Frameworks */, + 97471AD804F611A317A6FFE154745B8E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 985E4B160C2E2AF9CE29BC9F4F2B6914 /* PBXTargetDependency */, + B24AF135B12811E72517CE2850B829ED /* PBXTargetDependency */, + 7D3621E950BF56E3ADDF18616C62CE6A /* PBXTargetDependency */, + ); + name = FirebaseCore; + productName = FirebaseCore; + productReference = E2B63D462DB7F827C4B11FD51E4F8E2D /* FirebaseCore.framework */; + productType = "com.apple.product-type.framework"; + }; + 526C4398D095B3704EB933DADBC30093 /* FirebaseCrashlytics */ = { + isa = PBXNativeTarget; + buildConfigurationList = F039405F0AD035E8228C259D7DAA585B /* Build configuration list for PBXNativeTarget "FirebaseCrashlytics" */; + buildPhases = ( + B66C99A5DCAA527C8DC517F3F1ABC10A /* Headers */, + 122112C736A194222C53FFEE48B4B238 /* Sources */, + 8683E0DD8C10842EA9FCC664D0751B75 /* Frameworks */, + C7CDFD8A22AB2AB817A691A3F1491EB5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + B91018D8AFA9C999719E25CCA71FF9E5 /* PBXTargetDependency */, + C10D33DFDBF0043972BD3D6D102DA0D3 /* PBXTargetDependency */, + E9E86E60D1518546315DAF496F26E5E4 /* PBXTargetDependency */, + 2FD65BDDC1696731952B9BA2BF65CB97 /* PBXTargetDependency */, + ); + name = FirebaseCrashlytics; + productName = FirebaseCrashlytics; + productReference = 86375444C196BA272DDBB8165BF64A15 /* FirebaseCrashlytics.framework */; + productType = "com.apple.product-type.framework"; + }; + 5C0371EE948D0357B8EE0E34ABB44BF0 /* GoogleDataTransport */ = { + isa = PBXNativeTarget; + buildConfigurationList = B2AC4D5B812BD64F70849561DAA7E63B /* Build configuration list for PBXNativeTarget "GoogleDataTransport" */; + buildPhases = ( + 643FB09BAF8E56C0EDD87D962643680A /* Headers */, + 224456F98FCD5927A1D5E33503C66BDF /* Sources */, + 4CC2326B5F6FEE9B19AA593985C6F5DF /* Frameworks */, + 21CED80E9A54C3F740758B939C8D5935 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = GoogleDataTransport; + productName = GoogleDataTransport; + productReference = 856B5CD56F194FAD26EA91620B66D614 /* GoogleDataTransport.framework */; + productType = "com.apple.product-type.framework"; + }; + 620E05868772C10B4920DC7E324F2C87 /* FirebaseCoreDiagnostics */ = { + isa = PBXNativeTarget; + buildConfigurationList = 83DA204DD006948939BC645580D82964 /* Build configuration list for PBXNativeTarget "FirebaseCoreDiagnostics" */; + buildPhases = ( + FDEFE72E4C42ABB8F0B52B69377EDED7 /* Headers */, + AC5D189C6E068650FDF69522302DF46F /* Sources */, + 0A9310DB6FFD83927822EA5BA8DA328E /* Frameworks */, + 78AC34B59909170C77565968C952AFEA /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 40243F513FD0FE6049FC111B682AD860 /* PBXTargetDependency */, + 3E20CB0FCE844A7370DD8B614DD9DFD0 /* PBXTargetDependency */, + 021255C3B6B349F5930B4797E5CEE7E0 /* PBXTargetDependency */, + BE72D292DD246CC3DA7ADD84D10F05DA /* PBXTargetDependency */, + ); + name = FirebaseCoreDiagnostics; + productName = FirebaseCoreDiagnostics; + productReference = 8CC9178C366942FD6FF6A115604EAD58 /* FirebaseCoreDiagnostics.framework */; + productType = "com.apple.product-type.framework"; + }; + 68494F30B4A13F8E5E88BCCAEC25B0A4 /* Realm */ = { + isa = PBXNativeTarget; + buildConfigurationList = C78C1F585AF9F23F8E5BE58C65DFB1B0 /* Build configuration list for PBXNativeTarget "Realm" */; + buildPhases = ( + 93AAA6E3EB2241BD545A3DAF71A2F378 /* Headers */, + 19B5DC80E1527C4C9FB77505BB071140 /* Sources */, + 814CE1FDC9DE4A46D13F0A053EA8EC97 /* Frameworks */, + 76BE90AD3450229977577041A0B3137B /* Resources */, + 31E1852A7D8B204006F453FB1209492D /* Copy . Private Headers */, + 1F16170A859A60D821655356EE33494F /* Copy . Public Headers */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Realm; + productName = Realm; + productReference = 921BE4A82C4A7A5C72A0C6F8B8FEF200 /* Realm.framework */; + productType = "com.apple.product-type.framework"; + }; + 6AE4A3D573DED275B034E20506596C62 /* FirebaseAuth */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5BCD2A01F1CF558807A1EF939E4799E4 /* Build configuration list for PBXNativeTarget "FirebaseAuth" */; + buildPhases = ( + 65BE8C0B0A47682E627326AB2F9ED961 /* Headers */, + EE14955F08A36108EEFAD0452066FA4E /* Sources */, + DDE9346B48CC26C3A9142F24B9020835 /* Frameworks */, + 8D3C3FB56D2DFDF19AC2731E4CB99C16 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + F5BB79D84F09016BF84F5EF526665541 /* PBXTargetDependency */, + 574DFF66C1711EE8E8DEA48C494170A8 /* PBXTargetDependency */, + 62EB8B3F617A68FD45785CC9BC94E60C /* PBXTargetDependency */, + 01143B005AFD35FF51FC688BC2955CD1 /* PBXTargetDependency */, + ); + name = FirebaseAuth; + productName = FirebaseAuth; + productReference = 43B1E4CD7B30B9FD278100133C2AC788 /* FirebaseAuth.framework */; + productType = "com.apple.product-type.framework"; + }; + 736AF68F6527ACF6B4A4C54728824A1C /* FirebaseDatabase */ = { + isa = PBXNativeTarget; + buildConfigurationList = 422CDDC2F642E931AE5FEE0AB55DEE66 /* Build configuration list for PBXNativeTarget "FirebaseDatabase" */; + buildPhases = ( + 729AFFBCCEB94343B7ED7A41583E2047 /* Headers */, + F0777CE53E3CA07D945E1BCEF18CCEE1 /* Sources */, + 1B477DC8426D3E658A0142FA77DFE476 /* Frameworks */, + DBC2792C7FBF5A6AA385F4E714A686F8 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 3D23A510C1F39E85C12770A44EF20426 /* PBXTargetDependency */, + FA72C7E2D80B66734E84ECC6BD4101B4 /* PBXTargetDependency */, + 7FEE2242DF64783E5EC3BA920131F292 /* PBXTargetDependency */, + ); + name = FirebaseDatabase; + productName = FirebaseDatabase; + productReference = 51671C73F008B5C0C3751B3855999213 /* FirebaseDatabase.framework */; + productType = "com.apple.product-type.framework"; + }; + 782725687624F8665247B84AB581BEB1 /* RealmSwift */ = { + isa = PBXNativeTarget; + buildConfigurationList = 22886CEE8A1196C43C2AD1112FC689C4 /* Build configuration list for PBXNativeTarget "RealmSwift" */; + buildPhases = ( + 12936760E1153D2E8EB70500E0D7B965 /* Headers */, + 02332DDABEC84D721F48118D5F43BFAA /* Sources */, + 57E714585194E469459E9A1FDA3471BA /* Frameworks */, + 13B653C20CC1D0B28162EEB3B2D27AE7 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + B0F2CB59E7E3549DFC145C2E0973B40E /* PBXTargetDependency */, + ); + name = RealmSwift; + productName = RealmSwift; + productReference = 437919EE08EC6BFCCBAC3BD346309742 /* RealmSwift.framework */; + productType = "com.apple.product-type.framework"; + }; + 87803597EB3F20FC46472B85392EC4FD /* FirebaseInstallations */ = { + isa = PBXNativeTarget; + buildConfigurationList = E764F986CA22AE287CF37FAF5822F40C /* Build configuration list for PBXNativeTarget "FirebaseInstallations" */; + buildPhases = ( + 78715B5E1A1D5B389DD7D32D1D6A10C9 /* Headers */, + CF6D1E434C01E128457474A2EA23123C /* Sources */, + D7ECF3CC1DB7624F6B120A2E231894FD /* Frameworks */, + 8718C757C43A2E1E1D6CE7D8EF48A284 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 48E7C2C891AC768D575FED3B8B85240F /* PBXTargetDependency */, + 0E0268C916D08E877764597D45FC0C5B /* PBXTargetDependency */, + 40D7E5DDFEBFB2B51E86289AA6F805CE /* PBXTargetDependency */, + ); + name = FirebaseInstallations; + productName = FirebaseInstallations; + productReference = 13C8C8B254851998F9289F71229B28A2 /* FirebaseInstallations.framework */; + productType = "com.apple.product-type.framework"; + }; + 8D7F5D5DD528D21A72DC87ADA5B12E2D /* GoogleUtilities */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0B9A4D705F70B9BA2316A19C5CFA153F /* Build configuration list for PBXNativeTarget "GoogleUtilities" */; + buildPhases = ( + 4EAAFEEDADDF447480DB32E54C21E5A5 /* Headers */, + FE80622A6C6ABD9C16D77071311098B1 /* Sources */, + 8DEE514BD8CE3E97E8CC92DEA525A51D /* Frameworks */, + 4BBB3A141341753328C970E22CEB0A20 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = GoogleUtilities; + productName = GoogleUtilities; + productReference = B43874C6CBB50E7134FBEC24BABFE14F /* GoogleUtilities.framework */; + productType = "com.apple.product-type.framework"; + }; + 9307B7A119490930CF70393AB529AAC1 /* leveldb-library */ = { + isa = PBXNativeTarget; + buildConfigurationList = AA3ABA7985FD8E40F9E3432F446E4B5D /* Build configuration list for PBXNativeTarget "leveldb-library" */; + buildPhases = ( + 12FB7CC94DCFFF5DB81E9D9C6B2081CF /* Headers */, + 4BF334F614B06CDAE9695BF0AED151CA /* Sources */, + A5C09836B521F2F7A804EF587BBDB094 /* Frameworks */, + 095C5A455C626E9DE42EF400AD0D7DE1 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "leveldb-library"; + productName = "leveldb-library"; + productReference = 0A9F46A999C47653013D3AD854352507 /* leveldb.framework */; + productType = "com.apple.product-type.framework"; + }; + 9E25537BF40D1A3B30CF43FD3E6ACD94 /* FirebaseInstanceID */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9242B65F6853164F7A63A53CB7012901 /* Build configuration list for PBXNativeTarget "FirebaseInstanceID" */; + buildPhases = ( + EEA9A07530D934CC2168662DA90FF16D /* Headers */, + 00565AEDEB4EFD3C4FC69E3FA44E7AA8 /* Sources */, + 9C2FBF4B5898F99ECA284FAE9E2ECAEC /* Frameworks */, + C6D6D5A64C476C0BE4956CE00099E694 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + F7ED5670FC5982FCDE94012638063E39 /* PBXTargetDependency */, + F074433D00394DC8883F7FECF3B4B68B /* PBXTargetDependency */, + 324578F6DA9690C2D36F5FF7A15B7F1A /* PBXTargetDependency */, + ); + name = FirebaseInstanceID; + productName = FirebaseInstanceID; + productReference = 2DA0D814DFCB860D31D7BCD63D795858 /* FirebaseInstanceID.framework */; + productType = "com.apple.product-type.framework"; + }; + D2B5E7DCCBBFB32341D857D01211A1A3 /* nanopb */ = { + isa = PBXNativeTarget; + buildConfigurationList = D005677E54324E7E9FD9E4881C4035B3 /* Build configuration list for PBXNativeTarget "nanopb" */; + buildPhases = ( + 077EB81DD4C9C876EC7020918A99C2E9 /* Headers */, + 6A4BB432E7541C5619AC52A2EA340D19 /* Sources */, + 0A0EC028F171B534584F7108AED6844E /* Frameworks */, + C1E2AB7F9D4E8F76CA9C3C3FAF200E85 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = nanopb; + productName = nanopb; + productReference = 06FC5C9CF96D60C50FCD47D339C91951 /* nanopb.framework */; + productType = "com.apple.product-type.framework"; + }; + D676E21115185671D7258A56944ABE98 /* GTMSessionFetcher */ = { + isa = PBXNativeTarget; + buildConfigurationList = D9C6481EA4A7A51B8D39B3FE857B5A82 /* Build configuration list for PBXNativeTarget "GTMSessionFetcher" */; + buildPhases = ( + 942D1A99CD2EB4117FD8364A88A18F94 /* Headers */, + C4E592B61D1A6CC8F28F7442F05F5352 /* Sources */, + 0FAAD34EDB8CC19F56256C8B0AEDE8D2 /* Frameworks */, + A0C6EC78FB2197919C3E9BF4213334EA /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = GTMSessionFetcher; + productName = GTMSessionFetcher; + productReference = C1998E0D8085221AD87F89B614C10E52 /* GTMSessionFetcher.framework */; + productType = "com.apple.product-type.framework"; + }; + E8022D22FAA6690B5E1C379C1BCE1491 /* Kingfisher */ = { + isa = PBXNativeTarget; + buildConfigurationList = F474713E7462A344D5557EB151CFA07E /* Build configuration list for PBXNativeTarget "Kingfisher" */; + buildPhases = ( + FCA903FBC6FCC19FBFF772C5B82479CE /* Headers */, + FDCA7CDF3C4EB63B09F959386061D67C /* Sources */, + 61AF6B13CCC2E38FDA0B9BAB05581FD5 /* Frameworks */, + 5291070C390A4C8318BCD0577D8D7E20 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Kingfisher; + productName = Kingfisher; + productReference = C3F44C782D64D7EB20B61CE3844EBFAD /* Kingfisher.framework */; + productType = "com.apple.product-type.framework"; + }; + EAAA1AD3A8A1B59AB91319EE40752C6D /* Alamofire */ = { + isa = PBXNativeTarget; + buildConfigurationList = E4A5194ABAF7A4780609E0E581DA6B54 /* Build configuration list for PBXNativeTarget "Alamofire" */; + buildPhases = ( + 2582784E4FA6A1AC5D23FC53AC3F6EE2 /* Headers */, + 2DDFD9AC10F181CD7130BDF5F9E0502B /* Sources */, + 090C1D63463ACF622287EFF9D5C9392D /* Frameworks */, + 473D3E892ABB6C798CFF290644259B34 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Alamofire; + productName = Alamofire; + productReference = 5D797E9A5C5782CE845840781FA1CC81 /* Alamofire.framework */; + productType = "com.apple.product-type.framework"; + }; + F4F25FCAC51B51FD5F986EB939BF1F87 /* GoogleDataTransportCCTSupport */ = { + isa = PBXNativeTarget; + buildConfigurationList = EFFBBEC058839ADEF2EE6CF4A7B89A81 /* Build configuration list for PBXNativeTarget "GoogleDataTransportCCTSupport" */; + buildPhases = ( + 8A0AD7019F6480A5E1308447F085DD12 /* Headers */, + D334137B47C394A133DE153F81442F83 /* Sources */, + 20F01FC2A668C7364CCD6554A62A03AF /* Frameworks */, + 8FB8998B98F8351B2BAE4C2CED68F296 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 70AD386F01723A04963573668F544FF5 /* PBXTargetDependency */, + 5771B563572B13F25B00332ECE517169 /* PBXTargetDependency */, + ); + name = GoogleDataTransportCCTSupport; + productName = GoogleDataTransportCCTSupport; + productReference = 6942351307BC1F54575D9853307EAE0E /* GoogleDataTransportCCTSupport.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + BFDFE7DC352907FC980B868725387E98 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1100; + LastUpgradeCheck = 1100; + }; + buildConfigurationList = 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */; + compatibilityVersion = "Xcode 10.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = CF1408CF629C7361332E53B88F7BD30C; + productRefGroup = 4C75395F2313EB6AB9C6C4DCADCC4188 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + EAAA1AD3A8A1B59AB91319EE40752C6D /* Alamofire */, + 072CEA044D2EF26F03496D5996BBF59F /* Firebase */, + C49E7A4D59E5C8BE8DE9FB1EFB150185 /* FirebaseAnalytics */, + D372E53E2E8FEAA06A0439FB85E65767 /* FirebaseAnalyticsInterop */, + 6AE4A3D573DED275B034E20506596C62 /* FirebaseAuth */, + 8EC0F2618965C875A96BFDBEE5D9734C /* FirebaseAuthInterop */, + 4402AFF83DBDC4DD07E198685FDC2DF2 /* FirebaseCore */, + 620E05868772C10B4920DC7E324F2C87 /* FirebaseCoreDiagnostics */, + 5EB4B0B6DA6D5C0C3365733BEAA1C485 /* FirebaseCoreDiagnosticsInterop */, + 526C4398D095B3704EB933DADBC30093 /* FirebaseCrashlytics */, + 736AF68F6527ACF6B4A4C54728824A1C /* FirebaseDatabase */, + 87803597EB3F20FC46472B85392EC4FD /* FirebaseInstallations */, + 9E25537BF40D1A3B30CF43FD3E6ACD94 /* FirebaseInstanceID */, + B53D977A951AFC38B21751B706C1DF83 /* GoogleAppMeasurement */, + 5C0371EE948D0357B8EE0E34ABB44BF0 /* GoogleDataTransport */, + F4F25FCAC51B51FD5F986EB939BF1F87 /* GoogleDataTransportCCTSupport */, + 8D7F5D5DD528D21A72DC87ADA5B12E2D /* GoogleUtilities */, + D676E21115185671D7258A56944ABE98 /* GTMSessionFetcher */, + E8022D22FAA6690B5E1C379C1BCE1491 /* Kingfisher */, + 9307B7A119490930CF70393AB529AAC1 /* leveldb-library */, + D2B5E7DCCBBFB32341D857D01211A1A3 /* nanopb */, + 150CF0BF528D1824CF07A27D3513D10E /* Pods-GeekbrainsUI */, + 2BBF7206D7FAC92C82A042A99C4A98F8 /* PromisesObjC */, + 68494F30B4A13F8E5E88BCCAEC25B0A4 /* Realm */, + 782725687624F8665247B84AB581BEB1 /* RealmSwift */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 095C5A455C626E9DE42EF400AD0D7DE1 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B653C20CC1D0B28162EEB3B2D27AE7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 21CED80E9A54C3F740758B939C8D5935 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3665B73FD774F7A0CF41D8D25F37340B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 473D3E892ABB6C798CFF290644259B34 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4BBB3A141341753328C970E22CEB0A20 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5291070C390A4C8318BCD0577D8D7E20 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 76BE90AD3450229977577041A0B3137B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 78AC34B59909170C77565968C952AFEA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8718C757C43A2E1E1D6CE7D8EF48A284 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8D3C3FB56D2DFDF19AC2731E4CB99C16 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8FB8998B98F8351B2BAE4C2CED68F296 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97471AD804F611A317A6FFE154745B8E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A0C6EC78FB2197919C3E9BF4213334EA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C1E2AB7F9D4E8F76CA9C3C3FAF200E85 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C6D6D5A64C476C0BE4956CE00099E694 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C7CDFD8A22AB2AB817A691A3F1491EB5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DBC2792C7FBF5A6AA385F4E714A686F8 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DD9B7677CAF38EB3188071013D3B1DF9 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 00565AEDEB4EFD3C4FC69E3FA44E7AA8 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 28A980AD36AF039DF377CA478BE79964 /* FirebaseInstanceID-dummy.m in Sources */, + AE671A9E49E0910D1FF01B1C3FD2D4DF /* FIRInstanceID+Private.m in Sources */, + 675D7E47FDE4372E26F9345E57F2A50C /* FIRInstanceID.m in Sources */, + 630641270C9011D10EC4286A054B1C41 /* FIRInstanceIDAPNSInfo.m in Sources */, + 48E4A08C0D897078ECCA0D914DEB647F /* FIRInstanceIDAuthKeyChain.m in Sources */, + AB202C54DCAE5F9C50CDEDA01F0F476A /* FIRInstanceIDAuthService.m in Sources */, + 9B1AA11B1A1BB17258314DFE69D3BAC1 /* FIRInstanceIDBackupExcludedPlist.m in Sources */, + B0A29143188784E2E0005739431C2462 /* FIRInstanceIDCheckinPreferences+Internal.m in Sources */, + 911C5E96ADAF0DCA6C1FAA27189FF0E2 /* FIRInstanceIDCheckinPreferences.m in Sources */, + C80C9724725CDA4618CF75DD13EE5E35 /* FIRInstanceIDCheckinService.m in Sources */, + FE47EBDC1AED67B754D72FEDB3048AFA /* FIRInstanceIDCheckinStore.m in Sources */, + FEC6200CE6E46935071A783AFB805CC2 /* FIRInstanceIDCombinedHandler.m in Sources */, + 1E772ADD36B31B3EE965F1DF6563392E /* FIRInstanceIDConstants.m in Sources */, + 3772051821C60BB4D97BCDEF7CAC6918 /* FIRInstanceIDKeychain.m in Sources */, + E61211F04C7ED15A8E89A7E6795BA17D /* FIRInstanceIDLogger.m in Sources */, + B5E761475F5F8C3FEBA0627317C9B87A /* FIRInstanceIDStore.m in Sources */, + D09F17559EECB37D3423EB487EB98CED /* FIRInstanceIDStringEncoding.m in Sources */, + 05DCF6EAAE187AC79572C87A0B56DCEA /* FIRInstanceIDTokenDeleteOperation.m in Sources */, + 487F3249D460766985F47DF853C7B122 /* FIRInstanceIDTokenFetchOperation.m in Sources */, + B70D1176DB6DAA6F3CB7880C8F144E0C /* FIRInstanceIDTokenInfo.m in Sources */, + 0BB372001943A3F7DAB760F0B36069C5 /* FIRInstanceIDTokenManager.m in Sources */, + 78027788AF0FBC47704C258C159C7923 /* FIRInstanceIDTokenOperation.m in Sources */, + 0E5A7F170A0CF1873865213D0799CC5E /* FIRInstanceIDTokenStore.m in Sources */, + 2C236456F289C2B42BE0D5C341686537 /* FIRInstanceIDURLQueryItem.m in Sources */, + 8FA859EBEF23AE00795BDA824C71104B /* FIRInstanceIDUtilities.m in Sources */, + E60769759BCF14F38093666B8C874CF3 /* FIRInstanceIDVersionUtilities.m in Sources */, + 5290971CDFF4B6DDA038BB155C4A89C3 /* NSError+FIRInstanceID.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 02332DDABEC84D721F48118D5F43BFAA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F28BFCB7597D463ECD8CF3DF3B596211 /* Aliases.swift in Sources */, + BBCC6DDC1E888668E9DB4E11FDECBE76 /* Error.swift in Sources */, + 3CAC503A5DAA8D2DFB6895C2F4F46591 /* LinkingObjects.swift in Sources */, + E6A59169AB160B828757961CC391334C /* List.swift in Sources */, + D1E16D8B852B793DC73D69F98267636C /* Migration.swift in Sources */, + 70BCE5BACB52D99796645EE3A451B783 /* Object.swift in Sources */, + 42243F8F8A3A333A3543A3798E0BB9B2 /* ObjectiveCSupport+Sync.swift in Sources */, + 12A94B085B960FFF94A98BCF9F6E27E0 /* ObjectiveCSupport.swift in Sources */, + AC0F5BE9969AFF9FC4DBF4D3115917ED /* ObjectSchema.swift in Sources */, + FB16D14072713E368969885E4ECD099B /* Optional.swift in Sources */, + 79120D7382A3564038E796B8872271AC /* Property.swift in Sources */, + 354BE05906E0CB457569A8E29F9D8532 /* Realm.swift in Sources */, + 2B374445507BAC21E6FDCE6318EB1023 /* RealmCollection.swift in Sources */, + 17CE12E3F777B243FCE5AC6201AEF303 /* RealmConfiguration.swift in Sources */, + 436803A8CCC81A15C5EA20B71B444217 /* RealmSwift-dummy.m in Sources */, + E22BB3552C77566B1B6955CE66218BCD /* Results.swift in Sources */, + AABEE951C89957EB5E1FCD3D25CD45B4 /* Schema.swift in Sources */, + AC7D415C826932227566EB2B916A7C26 /* SortDescriptor.swift in Sources */, + 17E74A0C302C627A1374F313FA16E7EE /* SwiftVersion.swift in Sources */, + 3BD063008F14A0D06586F954907110D9 /* Sync.swift in Sources */, + ADF1076FF0CE456B5E0E046EEC33CD97 /* ThreadSafeReference.swift in Sources */, + 45EDE7CCF5471B181EA12B3C8FC66F4D /* Util.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 122112C736A194222C53FFEE48B4B238 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E5D04DC36C728FBFA7556441F0C9F671 /* FIRAEvent+Internal.m in Sources */, + B3F8717860CC04A746EC58EAEE94AEEC /* FIRCLSAllocate.c in Sources */, + 8A8CFA91C3682C90FA691E06935632C5 /* FIRCLSApplication.m in Sources */, + 5E284513BFA66CBF2623AC3F67DF9ACD /* FIRCLSApplicationIdentifierModel.m in Sources */, + 883A3CEC354F2A6B1F8D3BCD3517780A /* FIRCLSAsyncOperation.m in Sources */, + 909688EFA405A9BED947BB049EF38ECA /* FIRCLSBinaryImage.m in Sources */, + 51D6A5F1CD7E28681592199C70361D15 /* FIRCLSByteUtility.m in Sources */, + C08DDEB645515373444873D641E9F2E7 /* FIRCLSCodeMapping.m in Sources */, + 76D2D6B5E2C94652E0C9B28EDA050369 /* FIRCLSCompactUnwind.c in Sources */, + 93A6A9EBD95BEB2036B9649889CB2376 /* FIRCLSCompoundOperation.m in Sources */, + DD43604C29F189E84FD25A3FB7EF5938 /* FIRCLSConstants.m in Sources */, + 32DEBD42E7FAB50D08E7DE1FD7F5DF48 /* FIRCLSContext.m in Sources */, + 362A46839457D12ADDCCCE83713F4D58 /* FIRCLSCrashedMarkerFile.c in Sources */, + A54A7362B9B6588F39EC8EE04A298DA2 /* FIRCLSDataCollectionArbiter.m in Sources */, + 9EF0AE42CCA202AD453F8F2DC19B23C0 /* FIRCLSDataCollectionToken.m in Sources */, + 803CFFA2A6074F5752B3AF8878F4BA50 /* FIRCLSDataParsing.c in Sources */, + BA0599424BA4A35E771B0FA54920D3C7 /* FIRCLSDemangleOperation.mm in Sources */, + 6B52FC21C63FF610CE64BEF2AFBADA3D /* FIRCLSDownloadAndSaveSettingsOperation.m in Sources */, + 4EF883370AD6BDEE1324F53B3AA355C4 /* FIRCLSdSYM.m in Sources */, + C2213EAF4C8F8FE723E3A32C7EC8D73F /* FIRCLSDwarfExpressionMachine.c in Sources */, + A86AE83C5975FDED91EFE6D15D76CD5F /* FIRCLSDwarfUnwind.c in Sources */, + 2976C5737E255E1C6DDD2189BC8C0842 /* FIRCLSException.mm in Sources */, + 32962F880321BC804B0623A8524F5B6B /* FIRCLSExecutionIdentifierModel.m in Sources */, + CA0FD29B02CA5E7ED61547AD5D9AE105 /* FIRCLSFABAsyncOperation.m in Sources */, + 224DB4DCE7A500F2C3F2508C4AA5AD8F /* FIRCLSFABHost.m in Sources */, + 4E7DBC98260A502FF65D170CAFF96D23 /* FIRCLSFABNetworkClient.m in Sources */, + 6CBD701AB21073F9E488666F7C1520D9 /* FIRCLSFCRAnalytics.m in Sources */, + A7052F1E043995467877D756C5E3B11F /* FIRCLSFile.m in Sources */, + C740E549026AE383D979EC61223EE3A1 /* FIRCLSFileManager.m in Sources */, + 248A5E543858A01EC7294FC8E6AF1A10 /* FIRCLSHandler.m in Sources */, + 4F26D76BA4E3F80903FB68333F925101 /* FIRCLSHost.m in Sources */, + 8376370153BF4F3147F7C7D5BD8B7084 /* FIRCLSInstallIdentifierModel.m in Sources */, + 0F203DA2CF19D58A573B79C55EF989A9 /* FIRCLSInternalLogging.c in Sources */, + D4E973125B9FBDCF6C99FE00C47B7B40 /* FIRCLSInternalReport.m in Sources */, + 9255B03E885F7CB7D52D684EE7DA5342 /* FIRCLSLogger.m in Sources */, + DAEC3CBE7296D885165AF09571FD1500 /* FIRCLSMachException.c in Sources */, + 27F1D8D418B9B4712AE32093474C532C /* FIRCLSMachO.m in Sources */, + D9626CA178F66A577FED650042D40066 /* FIRCLSMachOBinary.m in Sources */, + AD9F6231FF2F80713036CD764A91B01F /* FIRCLSMachOSlice.m in Sources */, + 5DDB8E1D061F3977ED25AFE1A308594E /* FIRCLSMultipartMimeStreamEncoder.m in Sources */, + 2BFAA0475C1469645E06C94D9FB6A030 /* FIRCLSNetworkClient.m in Sources */, + 8E67945E119289DFF7141D8929414509 /* FIRCLSNetworkOperation.m in Sources */, + 440D721D141CD331A4F96417FA520F4C /* FIRCLSNetworkResponseHandler.m in Sources */, + ABAA34A6945F8A11D5FC2F27DC905DFB /* FIRCLSOnboardingOperation.m in Sources */, + D4677C1E658CE5A1A343D784BAA5DE65 /* FIRCLSPackageReportOperation.m in Sources */, + 35E83FC343783EF6CC38C99F69A67F4A /* FIRCLSProcess.c in Sources */, + B13C163DAC18295ECDD0C3B6C7307793 /* FIRCLSProcessReportOperation.m in Sources */, + 7BF8487A9361BF2D8624D4B310E90454 /* FIRCLSProfiling.c in Sources */, + CE7C368EFBCE1A52886E2FC8F284D4A8 /* FIRCLSReport.m in Sources */, + 9A931745F67444443E64C7C0DA782C9F /* FIRCLSReportManager.m in Sources */, + 5F67644DC0C813771596556D5FE72469 /* FIRCLSReportUploader.m in Sources */, + 1079C105550BB33719C4BCF5328E59D6 /* FIRCLSSerializeSymbolicatedFramesOperation.m in Sources */, + CBC81EB12308BD5B374929EA148FAE11 /* FIRCLSSettings.m in Sources */, + BA545614B134ABFCE6AE4311BE1EDAE4 /* FIRCLSSettingsOnboardingManager.m in Sources */, + 36AD77974C22C933CC24993C789FB85C /* FIRCLSSignal.c in Sources */, + 4557D52F1C5BDFFD4D7DFC0201735460 /* FIRCLSStackFrame.m in Sources */, + 7C1849B5942EB9756522B0B190685E1D /* FIRCLSSymbolicationOperation.m in Sources */, + 7BA43849B3489E6F374CE05935022921 /* FIRCLSSymbolResolver.m in Sources */, + C0D9B7374FC725F11F5E6DA8E7382199 /* FIRCLSThreadArrayOperation.m in Sources */, + F72E035551089237524CB10F8480E5D3 /* FIRCLSThreadState.c in Sources */, + CC03D0496C1DF9129E965459D54AA882 /* FIRCLSUnwind.c in Sources */, + D1989D8FC6F8B5392579A0043C507FFE /* FIRCLSUnwind_arm.c in Sources */, + 27AA309DA4D85879F3D1D12EE6AA9431 /* FIRCLSUnwind_x86.c in Sources */, + F527855C636F5EA01DDB5E43F986DD44 /* FIRCLSURLBuilder.m in Sources */, + 7B690AB74CFC0D7CAC202696AF4A5BBD /* FIRCLSURLSession.m in Sources */, + 9638D2EDC4A07E858D8266D2FF8F5AED /* FIRCLSURLSessionConfiguration.m in Sources */, + 99198F67ECBC6E0E02F30B593703656E /* FIRCLSURLSessionDataTask.m in Sources */, + DEFBA93DF8FAAA413E7CC482B34A13F1 /* FIRCLSURLSessionDownloadTask.m in Sources */, + B225C132BE2F4FB6DB5948FFFF045CD3 /* FIRCLSURLSessionTask.m in Sources */, + E62B4E6ED2B28C9C115E924910A9B324 /* FIRCLSURLSessionUploadTask.m in Sources */, + 48C5AEF00EEE8F7222DEE7E3217F58CC /* FIRCLSUserDefaults.m in Sources */, + 8CB2369C1B0EEAF2B9E6C62BBD3FAD03 /* FIRCLSUserLogging.m in Sources */, + 21E34559B19EED2D49C04D51B8BD5856 /* FIRCLSUtility.m in Sources */, + 2011162FA6962E236CC9FDEF170D5A65 /* FIRCLSUUID.m in Sources */, + 1425348D1B5AE1D313645B48C1809E8C /* FIRCrashlytics.m in Sources */, + D6ABCD9AD8E627F3D5DB77F54A1DD905 /* FirebaseCrashlytics-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 19B5DC80E1527C4C9FB77505BB071140 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FF4B2BB0EF29444776166A439A9D4E30 /* async_open_task.cpp in Sources */, + 0E031C031301B192EA4337E12804CC70 /* binding_callback_thread_observer.cpp in Sources */, + D28C130F5F3012F587CC16763CBD3DBE /* collection_change_builder.cpp in Sources */, + 0EC3A2761F6C42CDFC6CAF132CB0B83F /* collection_notifications.cpp in Sources */, + AACEBC8596A841C7B61CD7E1A9AE2DE4 /* collection_notifier.cpp in Sources */, + 45792F139DDBCE8090EA0D61771C5A9E /* external_commit_helper.cpp in Sources */, + CB2B572644D56110576B961927A43B1E /* index_set.cpp in Sources */, + 14F7AFD9FFD7EB0B3CB02DA66BEE976F /* keychain_helper.cpp in Sources */, + C81A970A0C95B15D031D606D0F987BA7 /* list.cpp in Sources */, + 22F963CEA7CC2CA57EBEAEE4DA490029 /* list_notifier.cpp in Sources */, + B5AD279F634D06859F4F4FB1E02D3FF0 /* network_reachability_observer.cpp in Sources */, + 6808C5BEF3D1556A431CD453E63E7E38 /* NSError+RLMSync.m in Sources */, + EE19F864673AF83DB4C21CA8464455AC /* object.cpp in Sources */, + B5FF707FEE8A4B13B59255108E0151E7 /* object_notifier.cpp in Sources */, + 688EE044B4D96D5DA2E7B700CCD3B2BF /* object_schema.cpp in Sources */, + D87888C7A3F26BFAD3127B56D8AB474E /* object_store.cpp in Sources */, + B3D636D90E51741683F376016D836355 /* partial_sync.cpp in Sources */, + BE33F3D2B1E4AA74939221284D92652C /* placeholder.cpp in Sources */, + C1F1C70CEE7F80A8BBECBF05FCF79142 /* primitive_list_notifier.cpp in Sources */, + C49FEFB601CD36B3BE61FB6D4494D733 /* Realm-dummy.m in Sources */, + ED3035ABEBCD639F75DDC5C472795706 /* realm_coordinator.cpp in Sources */, + 5290BB41346D30D8DAF45533FB141177 /* results.cpp in Sources */, + F76444CD589D114686BB510A6066CE67 /* results_notifier.cpp in Sources */, + 50B7D08F90D2D6435BBE891E1547332E /* RLMAccessor.mm in Sources */, + 05E61A3ADD15D7CB6C969A76E95EAAF9 /* RLMAnalytics.mm in Sources */, + 2E638CEA645E715DB0E123AD6A08647D /* RLMArray.mm in Sources */, + F03298753DA84C04036A6A3D24D96950 /* RLMClassInfo.mm in Sources */, + 1CB5C6760130C03AE56AD4870F1C1457 /* RLMCollection.mm in Sources */, + ADC590829802B94CE186B3E5DBC57D7B /* RLMConstants.m in Sources */, + CF585AA54281BB45C03037C4A4C56AED /* RLMJSONModels.m in Sources */, + FB1DC2E9C1E29C6641CF63A061C7717A /* RLMListBase.mm in Sources */, + 0250F77A3345CB64BCB6492E9D8D3FD3 /* RLMManagedArray.mm in Sources */, + 3E05EFB380BB32DD4E9D12038BB6A6C7 /* RLMMigration.mm in Sources */, + 430FBA64DC0FC3E07D39B0DAF4657C81 /* RLMNetworkClient.mm in Sources */, + 5B35E48760312B860F68C7472603D686 /* RLMObject.mm in Sources */, + 8D4B51F2A6E57273E12A76C160B2CC4F /* RLMObjectBase.mm in Sources */, + 4516012F99A1A9B2EA4313B79E29A580 /* RLMObjectSchema.mm in Sources */, + A8E82498433F11FB31D93E4F8DE0AE6F /* RLMObjectStore.mm in Sources */, + A2F591F45DBBFB19EFBBC5CCF8BA2793 /* RLMObservation.mm in Sources */, + B7CC2CEBDCF42DB4BCF7FDCA67991C6F /* RLMOptionalBase.mm in Sources */, + 33FED9F98332B8339C1D056B51E97B4C /* RLMPredicateUtil.mm in Sources */, + 7EC9F94A0049BFFDFD0B8795F08B4A37 /* RLMProperty.mm in Sources */, + 0E9649E3134E8BC6C9380EC78E376481 /* RLMQueryUtil.mm in Sources */, + F7C4C0980A7CD46EDCAAF366A722FFCD /* RLMRealm+Sync.mm in Sources */, + 655154DB907EF05CED9DA1664D45DC05 /* RLMRealm.mm in Sources */, + DA84AF88D7E5CD43D8BDE2CBC55296BD /* RLMRealmConfiguration+Sync.mm in Sources */, + 268A0739FE706CAD204F61702EEA9CB6 /* RLMRealmConfiguration.mm in Sources */, + 0FEACBA97148B598AA27162DDE4C7E4F /* RLMRealmUtil.mm in Sources */, + E83D2896CD2930EB85C3343E33752B47 /* RLMResults.mm in Sources */, + 97F26585AD4B634C86136CEF0E5B1EF2 /* RLMSchema.mm in Sources */, + 59EAF48CDE5B2776073636EF02FABB06 /* RLMSwiftSupport.m in Sources */, + 1A6FAC76E6834F1EDF069FF9FB4A189B /* RLMSyncConfiguration.mm in Sources */, + 665FFAEB9248C6D0998E934BB75D3F75 /* RLMSyncCredentials.m in Sources */, + 9D89150563C25286D022E024EAAE0224 /* RLMSyncManager.mm in Sources */, + DD76BA97F4C10B0FD53483BC8356C895 /* RLMSyncPermission.mm in Sources */, + 24F7392348EAB0ED45B6B1DCA4391914 /* RLMSyncSession.mm in Sources */, + E3A82EF1BFEB4A449FFE7FC26AEC1FB9 /* RLMSyncSessionRefreshHandle.mm in Sources */, + 5F9C606B3BD1513683DCD968D368AE9B /* RLMSyncSubscription.mm in Sources */, + AFDC58FF42E02745753EE6C5A502C3BB /* RLMSyncUser.mm in Sources */, + 049F23964BF99F1E034E600749DF4778 /* RLMSyncUtil.mm in Sources */, + 149DFEA17780F138C2D65F734477EC74 /* RLMThreadSafeReference.mm in Sources */, + 5B96D0F75CB99CD9B63EED98E14591BA /* RLMUpdateChecker.mm in Sources */, + 2B3B6A1F3CE5A236CF1B97B26E355E7D /* RLMUtil.mm in Sources */, + 1426E7D4526CCF2C32D559E2B538580B /* schema.cpp in Sources */, + 5861161136ED8784C63A95CF9EE918C8 /* shared_realm.cpp in Sources */, + 5AAD8DB30DDD23AEB92406F89174D7EF /* sync_config.cpp in Sources */, + 2CEAEFD70CD74376767EA7D85FA8831E /* sync_file.cpp in Sources */, + 70B0B87B78BFDEADABD02EC1425B7C0E /* sync_manager.cpp in Sources */, + 89C03071BA28C58414C5F9A4B8614B90 /* sync_metadata.cpp in Sources */, + 7D468FA6413DFA8873F8672B6CBF2468 /* sync_permission.cpp in Sources */, + 8DBB2BB97A17DCEAE003D2116E709489 /* sync_session.cpp in Sources */, + AA8CB96B506F6AF065AB1F8C9F9495CB /* sync_user.cpp in Sources */, + 0BFF24D5C16257D168CB59FEAE373808 /* system_configuration.cpp in Sources */, + 069E8403A49DE5921F142696F2518EBF /* thread_safe_reference.cpp in Sources */, + 13AA9F89B18BDC2687A40F18CB1CE6C8 /* transact_log_handler.cpp in Sources */, + 86C28A1FA2ADAF175EB56237CFD9FB36 /* uuid.cpp in Sources */, + 87B23A6C7190531AD4546209E6728EFA /* weak_realm_notifier.cpp in Sources */, + 0047B13AE63FA26AAA8DB8F11B430C02 /* work_queue.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 224456F98FCD5927A1D5E33503C66BDF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1A0810DFA1F7C0B0EF561B476BAD0D1D /* GDTCORAssert.m in Sources */, + 76342A2C712675E9E4F9934491A47B4C /* GDTCORClock.m in Sources */, + 7019C1A0EA2B7CAE1C35554D38B890EC /* GDTCORConsoleLogger.m in Sources */, + 9B9F7B42DC215BD6004E43E690CB18B7 /* GDTCORDataFuture.m in Sources */, + A94541B39798ECD528D6A10852CEE444 /* GDTCOREvent.m in Sources */, + 73D9FC189AC789A45BD64C21607DC5C6 /* GDTCORLifecycle.m in Sources */, + 3B733C714C595A3401B312D2D30F092D /* GDTCORPlatform.m in Sources */, + F744615A5209D686B6C139D583B28B12 /* GDTCORReachability.m in Sources */, + E88471D186D133AB49FA4BA3177DA6B6 /* GDTCORRegistrar.m in Sources */, + 22D8A47FCBC0B4C91500E90E4B7DBFC8 /* GDTCORStorage.m in Sources */, + ECFAC50FD1F24FE502F6BC5C9A861FCE /* GDTCORStoredEvent.m in Sources */, + 4C673A60C12195835BF34E011C152027 /* GDTCORTransformer.m in Sources */, + 815288201DBCEBCE3125960F2D48236D /* GDTCORTransport.m in Sources */, + CD8E52755C165618D3E233E1D725D5BC /* GDTCORUploadCoordinator.m in Sources */, + 3D373AD4EBC1B845DEAF2EB03603D113 /* GDTCORUploadPackage.m in Sources */, + DA1C37B83E539D74EA57667414446203 /* GoogleDataTransport-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 289A5A2E0E1583593915AC7312ABEB45 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 40D08C873299148205820E3707DA6BDC /* FIRAnalyticsConfiguration.m in Sources */, + 19BC4C0A75C8CC089542AA269B3750B4 /* FIRApp.m in Sources */, + EEF71627FA396EB479F77B2736DEE8A2 /* FIRAppAssociationRegistration.m in Sources */, + 85CE24D4F1B606F6C96753E7B14F0BCC /* FIRBundleUtil.m in Sources */, + B1471D98878942FB226D2F3F625A2487 /* FIRComponent.m in Sources */, + D42D6D95E98658FDEC52B0B4EAC66DE2 /* FIRComponentContainer.m in Sources */, + EA27021598F9B99B1C8F672B3C90A429 /* FIRComponentType.m in Sources */, + BF94B90FD2842F742883F06961FBEFE9 /* FIRConfiguration.m in Sources */, + 0F23D7CD822DFF8983212AB0181CDDDF /* FIRCoreDiagnosticsConnector.m in Sources */, + 877818355EA815AA57D6802280D5ADF9 /* FIRDependency.m in Sources */, + 882D1048BD130932559D6C1F5E835FFC /* FIRDiagnosticsData.m in Sources */, + 0108306551C9465CE4A9084EF7B0D848 /* FirebaseCore-dummy.m in Sources */, + 26A684F2CE11B7E0C722C1FF3405A455 /* FIRErrors.m in Sources */, + 941496067C95BDC2F69580981E8278AC /* FIRHeartbeatInfo.m in Sources */, + 9AA6F9C191B6A1D5BD7A1A1965E6663B /* FIRLogger.m in Sources */, + 5810F68ECE7F75FB25A2278E914BE44B /* FIROptions.m in Sources */, + 7443CC690F70BD1568A1F43D4D364B4B /* FIRVersion.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2DDFD9AC10F181CD7130BDF5F9E0502B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 83956E20859CDBBE7BC38ABADE0170FB /* AFError.swift in Sources */, + 1945CD5D63A1C164AEAAA9A33E85571E /* Alamofire-dummy.m in Sources */, + BEE6B677416CA71C981D1D3F60B18C96 /* Alamofire.swift in Sources */, + D3D8C379C6E4FB487E5ABD6800AD7B7E /* DispatchQueue+Alamofire.swift in Sources */, + 1986B50C74F1697EA43F68335C93CEB3 /* MultipartFormData.swift in Sources */, + 3571F958A3907B3A806E62D50C2550D4 /* NetworkReachabilityManager.swift in Sources */, + 132E0F619E4338E5D1B27E4C72076B3F /* Notifications.swift in Sources */, + 0C5E11DE24DAA737704B355F5F2F3426 /* ParameterEncoding.swift in Sources */, + 98A929C8E9012AB167672714FFD2113C /* Request.swift in Sources */, + D65C254F5ABF2CB5ECEE50FE8F8E1A80 /* Response.swift in Sources */, + E3747EC31FCCA97D75A81FC700CF7E24 /* ResponseSerialization.swift in Sources */, + 64744C911253C3E01461FAD7C935C8D7 /* Result.swift in Sources */, + F13F2AA7F2E6D95A181CAB99B900D531 /* ServerTrustPolicy.swift in Sources */, + 2C61B040BA6A9A7AE66C4D9BA26D5520 /* SessionDelegate.swift in Sources */, + 931BBB8230A25161D5C37528A8F9FECF /* SessionManager.swift in Sources */, + AFC64B1097F7355FF423D6A73E9C7210 /* TaskDelegate.swift in Sources */, + 933FDA5970AA525D6CB92BFEBA2BAB4A /* Timeline.swift in Sources */, + 53791F5E5F07400F92CFDFC89A432305 /* Validation.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4BF334F614B06CDAE9695BF0AED151CA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0E3D41F20FB1D10DAA5FD79F3920F9AA /* arena.cc in Sources */, + FFBB428073FC98895EDCE1BCF7E82DBF /* block.cc in Sources */, + C00B8C8A40084064F570B8FA5672CFC3 /* block_builder.cc in Sources */, + 36511455249A7349287D5F0256C74819 /* bloom.cc in Sources */, + 64A77A3C89F5824F2A1E855AFE15EB83 /* builder.cc in Sources */, + 5EC665EB843D0977D69CC03F0D1F5E62 /* c.cc in Sources */, + 244EF976B1E9D16D77191D1A9419CB6D /* cache.cc in Sources */, + C5686FA13350E54E7D8A0CD0A4129BDB /* coding.cc in Sources */, + FE54A3EFB4C58A133DEED7B9C0885FD9 /* comparator.cc in Sources */, + A6FEF91B7336C02C1EB319E148369FC7 /* crc32c.cc in Sources */, + ED02A0F74AF2BFB5107CEDE59EEC9538 /* db_impl.cc in Sources */, + AD4F9FF4317B2FCDB80BA901DCA8BEC7 /* db_iter.cc in Sources */, + 8A21F54667CE642C7117C19A17DF34C5 /* dbformat.cc in Sources */, + 93384ACDB8289E3459F686C2C7DD27A9 /* dumpfile.cc in Sources */, + D1E277C1682CF0248A33755597AF5D2E /* env.cc in Sources */, + B0F1E4473381EF323D63A39B680160C3 /* env_posix.cc in Sources */, + 202A4A731CA3A5E339451C2BA27FE99F /* filename.cc in Sources */, + 1D8133277751BE31DAA2BAEDA39DDC02 /* filter_block.cc in Sources */, + 30133270A7F1F7046628EEBB67EEDD25 /* filter_policy.cc in Sources */, + D659C1F36249732CD1FF09E719F78D84 /* format.cc in Sources */, + 30E21E73F7A1CA0DF62A74ED75B37A01 /* hash.cc in Sources */, + F03C8054C64501A4E7CD4177C8A66E5D /* histogram.cc in Sources */, + 7EA9F5B7A97A4CEB63FCDD7CA540CBA8 /* iterator.cc in Sources */, + 77CC78A99044D218130DA7539AB9B9A7 /* leveldb-library-dummy.m in Sources */, + 0F75CFE17869AABBA645828F7BB91096 /* log_reader.cc in Sources */, + 8B5D0524983DB7EE6205C22D9C45D450 /* log_writer.cc in Sources */, + 1E0B6B2B25216B9DCA864124C4407FD0 /* logging.cc in Sources */, + 235101E93897970283CD9042C3C0DC80 /* memtable.cc in Sources */, + 6789CA553001FF0FB165638C1D4E4CF8 /* merger.cc in Sources */, + 60AC5890112A4E3C7FF841D91AAC7AA0 /* options.cc in Sources */, + F98C8818B05607F8507F1B47724C8E43 /* repair.cc in Sources */, + 04DB958F843BC476B49669F0BC17A24C /* status.cc in Sources */, + B79361F232FB79297A27EDACA5934061 /* table.cc in Sources */, + 8D803A4306D271786D18C7C65F3EDC2E /* table_builder.cc in Sources */, + 07B4A1D98B402705C9EA29D9D227E8E1 /* table_cache.cc in Sources */, + DFCD6C774A03896A6DC1A360602AC905 /* testharness.cc in Sources */, + 8A5BAA1F5335EA52F45BD9D05DE8424B /* two_level_iterator.cc in Sources */, + C51B895EA2FAA594CE42154B2D31E5A9 /* version_edit.cc in Sources */, + E1ED363967D9BBBD54C23B37A0EE43B7 /* version_set.cc in Sources */, + 3B34F6F45C02F259DD4516D4047C5AA7 /* write_batch.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 6A4BB432E7541C5619AC52A2EA340D19 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 705BF74D108DA85C7C1376CB329BEA82 /* nanopb-dummy.m in Sources */, + F381F5B962BAF56292D75C8CBE72CDF9 /* pb_common.c in Sources */, + 46847931BEE1B519DDC7D7628E96CDE1 /* pb_decode.c in Sources */, + 0B2280895259CB93BEB07CD87198298F /* pb_encode.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AC5D189C6E068650FDF69522302DF46F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1E50F74C67958D399D7EC529A10F1516 /* FIRCoreDiagnostics.m in Sources */, + 31D25C55E797A85B79DED25437A4EC66 /* firebasecore.nanopb.c in Sources */, + 52D6FD585EA8DD0B61907A5C9AA3DBF9 /* FirebaseCoreDiagnostics-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B8317F5C34576C4D38F48590B4D7065D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1AD8CF4941548205727FEC94CE09B5F4 /* FBLPromise+All.m in Sources */, + 7D437253D8A80D734C1CAE3B09CDF00D /* FBLPromise+Always.m in Sources */, + A9CA323F9BF2ED7EB3D185DAAE6DA350 /* FBLPromise+Any.m in Sources */, + 2EB6B24C910FCC26849E9C2E953CAAD6 /* FBLPromise+Async.m in Sources */, + EFC69E6D0A56DDC4350AD536565B1678 /* FBLPromise+Await.m in Sources */, + 517622BCB83ED4177EBC3AF9A1CE8E8F /* FBLPromise+Catch.m in Sources */, + 95548E94080B3374B45B92FDFB463F27 /* FBLPromise+Delay.m in Sources */, + 395BF823FA954E78D5CD404F4C4BB53C /* FBLPromise+Do.m in Sources */, + DFBC1547B874C0E0749B2A669FE5F668 /* FBLPromise+Race.m in Sources */, + FF312C27C7AB6D005FA7178A39B57DDD /* FBLPromise+Recover.m in Sources */, + AD99E8BF4017497083E998DFDA4FFB79 /* FBLPromise+Reduce.m in Sources */, + B7242EFBDD964132F28A45D5DC515C20 /* FBLPromise+Retry.m in Sources */, + 03F30DDECC0FD27449A26E75331B6930 /* FBLPromise+Testing.m in Sources */, + 240D9275D135B705EB767D5585554862 /* FBLPromise+Then.m in Sources */, + 6481156B4BBB9D9DA83FAB5FF0287410 /* FBLPromise+Timeout.m in Sources */, + 294E1309A8B8C008248D3C83D700563A /* FBLPromise+Validate.m in Sources */, + 4620663A93EA36D5C393F65AD22815C4 /* FBLPromise+Wrap.m in Sources */, + C29517E8F8EE04EFC24A2B3101496685 /* FBLPromise.m in Sources */, + 04086C82027AB6A24CA1E2747BA4B8BE /* FBLPromiseError.m in Sources */, + A4269A5F9CE2E4C5FE12474685FE3B19 /* PromisesObjC-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C4E592B61D1A6CC8F28F7442F05F5352 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9D50881D7C3F48E650D48C42F642D025 /* GTMSessionFetcher-dummy.m in Sources */, + F4F6D9AD12A4CE730FAB880378FD7C60 /* GTMSessionFetcher.m in Sources */, + AD9F7734183A222D01D193BB380CEBE2 /* GTMSessionFetcherLogging.m in Sources */, + 224D8D1B421C842A8B9D33546666D7E7 /* GTMSessionFetcherService.m in Sources */, + 1400F5BE398901D1D563CBAD0FD491E0 /* GTMSessionUploadFetcher.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + CF6D1E434C01E128457474A2EA23123C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6076BCE61AD993AF111BE9939D334B99 /* FirebaseInstallations-dummy.m in Sources */, + 3D536533D07F957C1565AB1064E27B99 /* FIRInstallations.m in Sources */, + 18CFC529A456F15AA21FC1F0386188E7 /* FIRInstallationsAPIService.m in Sources */, + 537EB58F1BC35B91565A0841B860C223 /* FIRInstallationsAuthTokenResult.m in Sources */, + EF557B93D962C1C7D46AEB190528582D /* FIRInstallationsErrorUtil.m in Sources */, + 0D1BB5FB93FCE7F5AF755977C54919DA /* FIRInstallationsHTTPError.m in Sources */, + 64EE328A122407554BB19632C6114035 /* FIRInstallationsIDController.m in Sources */, + 16F8832C1E288DDF643EC94A9D99B932 /* FIRInstallationsIIDStore.m in Sources */, + 8747AC80E2E4E6B96FDC0AEFF0E9F989 /* FIRInstallationsIIDTokenStore.m in Sources */, + 794DF462C0642134849F63A26680481A /* FIRInstallationsItem+RegisterInstallationAPI.m in Sources */, + C03DD9A25A37D39D1E51219B4A32CC65 /* FIRInstallationsItem.m in Sources */, + DC7BB1AE84A079521289E8D2DD19A2ED /* FIRInstallationsKeychainUtils.m in Sources */, + 1D27A15F9E29252B7B03469286EDEE22 /* FIRInstallationsLogger.m in Sources */, + CB13468076A0A131A014609BB9D08196 /* FIRInstallationsSingleOperationPromiseCache.m in Sources */, + 4D3895C9DBFC2D68DB50FA7E1A386533 /* FIRInstallationsStore.m in Sources */, + B37F8E1C0FC44207F42540B5E5DFFE0D /* FIRInstallationsStoredAuthToken.m in Sources */, + A3BE570EEACBCD6B8624A9633E458ABF /* FIRInstallationsStoredItem.m in Sources */, + B80080DF80150A02D9E529B72E8630C0 /* FIRInstallationsVersion.m in Sources */, + E47688CA299B6C59B4D64550B09CFCAB /* FIRSecureStorage.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D334137B47C394A133DE153F81442F83 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2BD7BB425103F53E71DCAE5BE3930BD9 /* cct.nanopb.c in Sources */, + 894B02C50A0C6F1E0FE73F30D4FCF06D /* GDTCCTCompressionHelper.m in Sources */, + 5A78AFB9C7CC4E57E636F49470157E4A /* GDTCCTNanopbHelpers.m in Sources */, + FD9914E68C106CC2C9A6D56DC5DC319C /* GDTCCTPrioritizer.m in Sources */, + 3A67AE71FF530880D928EE0D3A6FAD6C /* GDTCCTUploader.m in Sources */, + 874AD1931253070ADC03F5043FE8C505 /* GDTFLLPrioritizer.m in Sources */, + B4ACBEE17C3E66A67EB74B25C4E2EECC /* GDTFLLUploader.m in Sources */, + E351C5B3CD9C831D1472B45FD6028CA6 /* GoogleDataTransportCCTSupport-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + EE14955F08A36108EEFAD0452066FA4E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EB514A44D99F637EF888B37A8C35EDAC /* FIRActionCodeSettings.m in Sources */, + 94068437A6A57F0C86AF5EB7A62A81F9 /* FIRAdditionalUserInfo.m in Sources */, + CFD3076A3783F41ED3FFB2C5335BA2CB /* FIRAuth.m in Sources */, + 290A2994852FC010AEB3CCD50C496AB4 /* FIRAuthAPNSToken.m in Sources */, + 5D12D94AD0689D694E5690B1B123690C /* FIRAuthAPNSTokenManager.m in Sources */, + DC240DCD9D261F3CF700BCB3683E229E /* FIRAuthAppCredential.m in Sources */, + 4C3F544366A4B9CEBA290756E28570F3 /* FIRAuthAppCredentialManager.m in Sources */, + ACF45AC901564746590D62BCBF2C7BF4 /* FIRAuthBackend.m in Sources */, + 7BA9821F063AAE8C008CFC3DCFF07142 /* FIRAuthCredential.m in Sources */, + 94146FAB7109DC1FABB60023ADA00173 /* FIRAuthDataResult.m in Sources */, + A6330243D6FFC19FA6CCBA406C85969C /* FIRAuthDefaultUIDelegate.m in Sources */, + 5FECBE42D09EAB254BDCD0AE50C17AB8 /* FIRAuthDispatcher.m in Sources */, + FC0BB1F18B568792F3990847ECC385E6 /* FIRAuthErrorUtils.m in Sources */, + 244F5C35E9F8D1591D80C44E21E62A41 /* FIRAuthExceptionUtils.m in Sources */, + C2285F0CF7A81ACC446E039E2548F699 /* FIRAuthGlobalWorkQueue.m in Sources */, + BB6F9FA0BD72C79C63B8587B17141A9B /* FIRAuthKeychainServices.m in Sources */, + 4A1A01DEDBD2D150E3852CC02330CC27 /* FIRAuthNotificationManager.m in Sources */, + A84DDB4FED63439996ADE951D64C5B3E /* FIRAuthProvider.m in Sources */, + 98D39B35A84835ADD3A85F9F885A38D6 /* FIRAuthRequestConfiguration.m in Sources */, + 782FF27221451E8222CE8A82D40031F4 /* FIRAuthSerialTaskQueue.m in Sources */, + 2985A0CBB53D75B845CF4B69A151DD45 /* FIRAuthSettings.m in Sources */, + 72A2FC7AD0CD16833194F80943B40477 /* FIRAuthStoredUserManager.m in Sources */, + 6F14D38D87A8128A18AFCA43192EF494 /* FIRAuthTokenResult.m in Sources */, + A1A129C4F8AE9ED176B267739322F1F2 /* FIRAuthURLPresenter.m in Sources */, + 59C1E9DE3068ED6FC3A98B5DDA432D60 /* FIRAuthUserDefaults.m in Sources */, + 66FA6CB1658BA3121449E44D65AB4E49 /* FIRAuthWebUtils.m in Sources */, + 39B5C86A530DE569BA00411901FAF65C /* FIRAuthWebView.m in Sources */, + FF27D379E021FB0C6E58343FAF9AE541 /* FIRAuthWebViewController.m in Sources */, + 7AD3773DB32F9D67568EAE09E78B9A9E /* FIRCreateAuthURIRequest.m in Sources */, + 17905B6DC347427DCCDBC1AF49EF9809 /* FIRCreateAuthURIResponse.m in Sources */, + 910DAAF63F747D7F5DC9F9D77C4D3D94 /* FIRDeleteAccountRequest.m in Sources */, + F816D3A9EBBC2EB16DB0CEE0DCAAE776 /* FIRDeleteAccountResponse.m in Sources */, + 3F8153B107171B193E1327275298F08D /* FirebaseAuth-dummy.m in Sources */, + 5A8AF4171936AD01CB9C20D850F69369 /* FirebaseAuthVersion.m in Sources */, + 3D42868BA350D47478AF7E3C85A5618E /* FIREmailAuthProvider.m in Sources */, + D8F0096D7A2922E9AAFB6BFAE7B14915 /* FIREmailLinkSignInRequest.m in Sources */, + AA27FF3EAAF39A3DC4EBF936EB325755 /* FIREmailLinkSignInResponse.m in Sources */, + 09AD74027097E5A8FF25FC162F2803CE /* FIREmailPasswordAuthCredential.m in Sources */, + 6136A64EF5388D965E6D6F0AC917E506 /* FIRFacebookAuthCredential.m in Sources */, + 521651EE93DB4D74C6D0A42A4C7FB555 /* FIRFacebookAuthProvider.m in Sources */, + 8CDF924F391EAA98CC8BBEE05E78B861 /* FIRGameCenterAuthCredential.m in Sources */, + C1759BE35FF699F96A9860C9AE9A6D54 /* FIRGameCenterAuthProvider.m in Sources */, + 694DDEC8CDE30E341E633E6FFF8873A2 /* FIRGetAccountInfoRequest.m in Sources */, + 5DED512C698C3A42354FD859134ECC7D /* FIRGetAccountInfoResponse.m in Sources */, + C0B775769787C3E19FB6356CD7B8DBAF /* FIRGetOOBConfirmationCodeRequest.m in Sources */, + 9CAE1A659435D39578BC67CAF04E6B33 /* FIRGetOOBConfirmationCodeResponse.m in Sources */, + 5EC78868E70B324291E267A562A96E79 /* FIRGetProjectConfigRequest.m in Sources */, + F6D77FD5A27B7709E861CA48E7BFD558 /* FIRGetProjectConfigResponse.m in Sources */, + A0E4097BD898F34C0F9443D658EFF513 /* FIRGitHubAuthCredential.m in Sources */, + EBE6A5953B78D5F511FE2506014C4147 /* FIRGitHubAuthProvider.m in Sources */, + 7DF65A5E411826F06D1474FA83681523 /* FIRGoogleAuthCredential.m in Sources */, + E8F7AF6D02AC6BF3C0981306D8098D34 /* FIRGoogleAuthProvider.m in Sources */, + 8131D2B4BE795DC28CE35BE77D080066 /* FIRIdentityToolkitRequest.m in Sources */, + 7EABD043D0EB8CCF2E5FABF2125AF33D /* FIROAuthCredential.m in Sources */, + 36B9102AD5454BEA6CD15177C379061E /* FIROAuthProvider.m in Sources */, + 0A3E14559DE2D3FFF9A20C7A7A26A659 /* FIRPhoneAuthCredential.m in Sources */, + E6A9D9A43516535E9F51FB609AB6584A /* FIRPhoneAuthProvider.m in Sources */, + 384CFB5FF97037843BC45F477B4EEBCB /* FIRResetPasswordRequest.m in Sources */, + 2ED393E3128B673358126B3490A6B2CB /* FIRResetPasswordResponse.m in Sources */, + 9AF62C2B24B3B17186602A0A057B4819 /* FIRSecureTokenRequest.m in Sources */, + 9455D8654426BC7A04358EC97E10D9E9 /* FIRSecureTokenResponse.m in Sources */, + 6EEE38CA0E8563AFC04F9FB6F8B79B48 /* FIRSecureTokenService.m in Sources */, + 297DC19B1446D9164EF9071599A32D75 /* FIRSendVerificationCodeRequest.m in Sources */, + FB78F741ECCF1E6DFEBE446BC390C1D2 /* FIRSendVerificationCodeResponse.m in Sources */, + A77D7E76C94952348F06FECAC8EF0B14 /* FIRSetAccountInfoRequest.m in Sources */, + B13AFF3CB01EDDD79D5CF4062A348A01 /* FIRSetAccountInfoResponse.m in Sources */, + A857D6202E2290264F544DDD8627F432 /* FIRSignInWithGameCenterRequest.m in Sources */, + 6C021D9AEAC1F0237AB0F69234B0E5B1 /* FIRSignInWithGameCenterResponse.m in Sources */, + 1BC0485BA033FFAB83486043111FD39D /* FIRSignUpNewUserRequest.m in Sources */, + 67C971B770407C49947DAA30E026498C /* FIRSignUpNewUserResponse.m in Sources */, + BBD71C89D7C0D636B54C769774CB987F /* FIRTwitterAuthCredential.m in Sources */, + B070D09AF06FFBE14295C3B45211F7F9 /* FIRTwitterAuthProvider.m in Sources */, + 802AC0115A5C395520D07502A77E8C26 /* FIRUser.m in Sources */, + 3B10809E3E6FDABC215367D684ADA5F2 /* FIRUserInfoImpl.m in Sources */, + 684C72B4AE51B22358D0CC199FA21F01 /* FIRUserMetadata.m in Sources */, + 834D23E6926B2CF9235B496D9978DD73 /* FIRVerifyAssertionRequest.m in Sources */, + D5DBB44B8AAAB56BE183604E59B34639 /* FIRVerifyAssertionResponse.m in Sources */, + E79F105F571CA41CD85847D8E87051AC /* FIRVerifyClientRequest.m in Sources */, + 87664AA7641EF3AFFA269A7E98879059 /* FIRVerifyClientResponse.m in Sources */, + 301B53ECB6A895D5AD7239EDB5B3B9B3 /* FIRVerifyCustomTokenRequest.m in Sources */, + 3540E608AC053E95EB553582B3B53B8C /* FIRVerifyCustomTokenResponse.m in Sources */, + C1305C8A3CE868521D0B46E40DF78ECC /* FIRVerifyPasswordRequest.m in Sources */, + DD42E8C0D491D9E66325270BE17AB506 /* FIRVerifyPasswordResponse.m in Sources */, + 3105E36C781D76C21BDC63365A8BF957 /* FIRVerifyPhoneNumberRequest.m in Sources */, + DABC0FC6B581CBDD00B12B5AB7EDAE2F /* FIRVerifyPhoneNumberResponse.m in Sources */, + 3966218A30FDC3F688679A7103EE2E40 /* NSData+FIRBase64.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F0777CE53E3CA07D945E1BCEF18CCEE1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1623320157383ACACCCC8A4FBA35F553 /* APLevelDB.mm in Sources */, + 3CA959955A0D5CA51EDD9511F4EAC44C /* FAckUserWrite.m in Sources */, + 35C26719E55743DACF7DF3C6B61D025D /* FArraySortedDictionary.m in Sources */, + D8019DAAAF812FE45A5A4F7BF18F210D /* FAtomicNumber.m in Sources */, + F12A4DEA7641F0A7142779EC9836D2D1 /* FAuthTokenProvider.m in Sources */, + 3CB321C64C92C8F669CD8B5ADBA0AC08 /* fbase64.c in Sources */, + 178CFCFBC312ACCB07E75D8F50D2C499 /* FCacheNode.m in Sources */, + E7089B09922ED63DBA188CCD14ACCCFF /* FCachePolicy.m in Sources */, + 07F0805184FB3045E749367BC52FFE1D /* FCancelEvent.m in Sources */, + 0404A69C5B52AED3106A3BC8882E3DEC /* FChange.m in Sources */, + 9DABF8DC7C2B45BDBB28DAC5F9304D27 /* FChildChangeAccumulator.m in Sources */, + A8F8D6EF5E9C9B620F578A3B89E4D4AE /* FChildEventRegistration.m in Sources */, + 9898062DAF98F529BACA9EEA8EB04EF0 /* FChildrenNode.m in Sources */, + EB3BB9186B5EF80F3CD901B7CC71E10C /* FClock.m in Sources */, + D844F95AE779A6741600EBF37CF3A0FC /* FCompoundHash.m in Sources */, + 7C7C13AF1D848ABE24E87CC5A1BC576F /* FCompoundWrite.m in Sources */, + 54C874E6783EA1E008E2AEE83DAAB1EA /* FConnection.m in Sources */, + 1323A3863AE9037767A2A0FCC0F474A1 /* FConstants.m in Sources */, + 0F2FB338CA6AD8975B1D9D86FCADDA40 /* FDataEvent.m in Sources */, + 51CE50887778105AB03E435628175C9A /* FEmptyNode.m in Sources */, + 45656090825D8B8006F6CF3F7C953513 /* FEventEmitter.m in Sources */, + C13E04BEC90D13ECC2131CC4D80B64BC /* FEventGenerator.m in Sources */, + 73661FB7A51FD66D282DF2890DCF45F6 /* FEventRaiser.m in Sources */, + B37338B7164D136BB318B355CF9E2955 /* FImmutableSortedDictionary.m in Sources */, + 6ED4FCF22261293DDA44CE9E0A1D4404 /* FImmutableSortedSet.m in Sources */, + 279E0EC139619D4D3065DA723F73185A /* FImmutableTree.m in Sources */, + F52D7BC8DCF75C138925790FE62FBB6C /* FIndex.m in Sources */, + AEA43D492F0B4430F26EDE8EC151335E /* FIndexedFilter.m in Sources */, + 3C6CE7E7BE2DEDE4C26537B856C67B6A /* FIndexedNode.m in Sources */, + 67F2CEE4FD0F6B6E90818AEE5A7ED613 /* FIRDatabase.m in Sources */, + 198915A277429DF37346B2BC177E0E15 /* FIRDatabaseComponent.m in Sources */, + 5734197CA26453B55461E0137DF89CFC /* FIRDatabaseConfig.m in Sources */, + 459458B16079A5914156FE854B31580E /* FIRDatabaseQuery.m in Sources */, + ADA239945F5E2E231D1539919E169049 /* FIRDatabaseReference.m in Sources */, + 2D88FEA4D706A65AFE4C710CFA49903F /* FIRDataSnapshot.m in Sources */, + BE17BE872A2C7D4C8C4AAA51B08AA206 /* FirebaseDatabase-dummy.m in Sources */, + 9EC32265D0C5AD456B853E60C1FB710D /* FIRMutableData.m in Sources */, + 13781217103B9C7248945110C8179D28 /* FIRNoopAuthTokenProvider.m in Sources */, + C58645B1248EBB3255B4E590C2A4D017 /* FIRRetryHelper.m in Sources */, + 506015C21605384EBD4F685744C398D0 /* FIRServerValue.m in Sources */, + 7D54DBB90B8519E22A0B7E277A4C0405 /* FIRTransactionResult.m in Sources */, + 0259FF9014F6BA08462866A3482B661D /* FKeepSyncedEventRegistration.m in Sources */, + A22F7A299FED7582527E93101293142C /* FKeyIndex.m in Sources */, + E1AEB6F788D7310B9494846551BBF386 /* FLeafNode.m in Sources */, + C81F1787CA4DA2DC531ABCAC4E219DAC /* FLevelDBStorageEngine.m in Sources */, + B44ECB680C7291BEFF0468F5F76272E8 /* FLimitedFilter.m in Sources */, + 3BDF75744A39DAB17C513036D239E620 /* FListenComplete.m in Sources */, + 72E6D3E9999959F9349C5C86C91FD7E6 /* FListenProvider.m in Sources */, + 953F2798667B586AFEF26C1BD2B05FEE /* FLLRBEmptyNode.m in Sources */, + A8D337E9933B9BF0529DD065D188889F /* FLLRBValueNode.m in Sources */, + 47A6BE227E061383B1F7DB598999FB41 /* FMaxNode.m in Sources */, + 5F3FCDFFFC373AD319D8099087EA355E /* FMerge.m in Sources */, + F11957624E05D92BBA9303C81898DB72 /* FNamedNode.m in Sources */, + 4C14BCCCDF4F682E80B46433B59A27F6 /* FNextPushId.m in Sources */, + 4BA4E26D3223A9FE843FC8311848FB28 /* FOperationSource.m in Sources */, + 9496FE2515AA1457B6BD957F4D686F8E /* FOverwrite.m in Sources */, + A6129D460285F6DC87E1F32287FE30F0 /* FParsedUrl.m in Sources */, + 95FC3F4277B5A49D0B0BC6ED0FDEC222 /* FPath.m in Sources */, + F1503B963287A68AD0D5BBEDA7CF4074 /* FPathIndex.m in Sources */, + 599469B52AB1CE4E1B583BD80F195D0E /* FPendingPut.m in Sources */, + 4115DF99DB04C8C8E7D39D0805D4E87D /* FPersistenceManager.m in Sources */, + 139654D56832D51AA66DC55C847675B6 /* FPersistentConnection.m in Sources */, + D05741401F1A40A4AA8DAE8053928E78 /* FPriorityIndex.m in Sources */, + EAF8DAA936BD292598594A4BFFBE8761 /* FPruneForest.m in Sources */, + F90FE141FD6DA62DBEDC1D6C17E2DB7B /* FQueryParams.m in Sources */, + FB985C4ECA7AC6D97912D7867E189B8C /* FQuerySpec.m in Sources */, + A37C8A256CF2D62172B63CA986743735 /* FRangedFilter.m in Sources */, + B67C4B5C715A1AFB214C8342B1B537BD /* FRangeMerge.m in Sources */, + E85BA92055A041888C8CA920D402335B /* FRepo.m in Sources */, + 34F6547C4C8518A900222A72B4C007E5 /* FRepoInfo.m in Sources */, + 3D142725B97ADD7225570C26B8FD1096 /* FRepoManager.m in Sources */, + B02C18CC0F1ED9C7B222D3922EB803DB /* FServerValues.m in Sources */, + 76D4CFA5E0BA0CC40F412FD18D775E02 /* FSnapshotHolder.m in Sources */, + 617EFBCBD5DBD38653E7D1F1803905F0 /* FSnapshotUtilities.m in Sources */, + C97C148C5BD94CE8C55EDB39F27B5281 /* FSparseSnapshotTree.m in Sources */, + 714DC87A2F1CD33A729B676F7B61603B /* FSRWebSocket.m in Sources */, + CF672281761B01B1FF71989FB9CF4939 /* FStringUtilities.m in Sources */, + 976461D74F0D8B518CD0AC18FE16E2FE /* FSyncPoint.m in Sources */, + 74C75A86C46C6DB37D54089C10C52214 /* FSyncTree.m in Sources */, + 4A228C12D280C60F1472305383A091F6 /* FTrackedQuery.m in Sources */, + 4920F481DD8EA8FDD77D27961F8AD863 /* FTrackedQueryManager.m in Sources */, + 898118C4029CB252DE5EED0F8D653505 /* FTransformedEnumerator.m in Sources */, + 97D7D4ECF0164108DD2F04D8727393C7 /* FTree.m in Sources */, + D79F2EC9B39EAF1BF9BD42D340E88054 /* FTreeNode.m in Sources */, + 05180304313D99D502131180CEA8ABB9 /* FTreeSortedDictionary.m in Sources */, + 70C43A5A473230007F3E1E11997B0CE9 /* FTreeSortedDictionaryEnumerator.m in Sources */, + CE4D180DFA3FAC29062CEE93ECEFBB00 /* FTupleBoolBlock.m in Sources */, + A49A7E4B375094243610F437ACA5B30D /* FTupleCallbackStatus.m in Sources */, + 007401F7CE172EB9B8DDF081F6E09387 /* FTupleFirebase.m in Sources */, + F1D14A94564CA46BB81E40133C5A2434 /* FTupleNodePath.m in Sources */, + C544DE1F643064552AC40F3947DC47A0 /* FTupleObjectNode.m in Sources */, + CF7670D1D231DF38056A47214361E2D7 /* FTupleObjects.m in Sources */, + F8358EDA462C9CFD16619BE2AF438B5C /* FTupleOnDisconnect.m in Sources */, + F789758B5B0731A48D4D83D933407363 /* FTuplePathValue.m in Sources */, + F6C22FF9F12BD6D6FC4C4892222F82E5 /* FTupleRemovedQueriesEvents.m in Sources */, + E2A888098F34332DDA3D6D348A8339C9 /* FTupleSetIdPath.m in Sources */, + 8B6673BE0DB957F63D9B89E1A52210A8 /* FTupleStringNode.m in Sources */, + 3735567A5FBE87CBEB5424CD517078EE /* FTupleTransaction.m in Sources */, + 0B0792B6C13A4DC546A8B215A139E05C /* FTupleTSN.m in Sources */, + 32F6C157E45DB0ABD6E1A207BC3EB20F /* FTupleUserCallback.m in Sources */, + 6B9F21EBBA5B966F47635AFE9DC875CE /* FUtilities.m in Sources */, + 07CFEDA472792676E80DC36DBA6B42FF /* FValidation.m in Sources */, + 683B81361E1659216FDC3A756843E856 /* FValueEventRegistration.m in Sources */, + 970E7215DF6BFD7D77304406D8F10AF0 /* FValueIndex.m in Sources */, + AAFCABDE8D0D395FC602D2F61A4B45AE /* FView.m in Sources */, + FA94BEB13D3E5EDDD4C0BFD5F11775F2 /* FViewCache.m in Sources */, + 89AE4BFDC48D2DE67DD9352F8A13E10D /* FViewProcessor.m in Sources */, + 019D8E0751E60E9EDA6F292D0AFE5A4C /* FViewProcessorResult.m in Sources */, + 9FD4240AD8305E3225CE1BA8602D0F41 /* FWebSocketConnection.m in Sources */, + A4C007C8EBD401459E7BB06952BD25D4 /* FWriteRecord.m in Sources */, + C7E4252DFE965F0E39ADC24E1AA6F6FE /* FWriteTree.m in Sources */, + EACC292557AC609ACE5385077CDC55A5 /* FWriteTreeRef.m in Sources */, + ECC12B7E36C9B220FB6395086BFC6FB8 /* NSData+SRB64Additions.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FA58A512B6AA6B8C15C6C6FA37A72013 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D5AC6BE3804A9DABDBAC1A056ED2D206 /* Pods-GeekbrainsUI-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FDCA7CDF3C4EB63B09F959386061D67C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C2CDC860BA74B75C0504A55B02E072F6 /* AnimatedImageView.swift in Sources */, + 89A9375A72E68E2D7A0FEE9F09B9DB10 /* AuthenticationChallengeResponsable.swift in Sources */, + 3B6EE6F748F891E92E1AE63029F3D4DB /* Box.swift in Sources */, + 171CFEBFD0A93CFF6DC40E85EBF3BF8A /* CacheSerializer.swift in Sources */, + 447FCEA8BB2DFACD60B6B8ECECC027B9 /* CallbackQueue.swift in Sources */, + 6E2AE42031E6C611C2AAAD2FE1B12BAD /* Delegate.swift in Sources */, + 4B288AC81506A29EC002A33979797B27 /* Deprecated.swift in Sources */, + C409433F2328566B78FA477EB56CA377 /* DiskStorage.swift in Sources */, + 5AD5EE0D42721F7E45DAEB5D7D9FA3C4 /* ExtensionHelpers.swift in Sources */, + 63C2BA763C69F183DDB32456D7201543 /* Filter.swift in Sources */, + 86CE574CADD6EAE3E385F152C74EA78C /* FormatIndicatedCacheSerializer.swift in Sources */, + 8EB401DC59ED357AFE37BAF90CD90B3A /* GIFAnimatedImage.swift in Sources */, + 09433CD8C4A2D4227DF98AB78E246011 /* Image.swift in Sources */, + D1B4079BB427AA2EA1955C02EA34E7EC /* ImageCache.swift in Sources */, + F0E89F7017249BCB3B2C1320F9413B2C /* ImageDataProcessor.swift in Sources */, + 38ECB82C9A053349A8928D026F437792 /* ImageDataProvider.swift in Sources */, + FEAB0E502EA1A6C4F758823B3B4BDFFF /* ImageDownloader.swift in Sources */, + C58AC3B6AC4597752277700E774B0927 /* ImageDownloaderDelegate.swift in Sources */, + 713643837AE7E542FFD9595143E0AA39 /* ImageDrawing.swift in Sources */, + 9A074410CBBCE8C6BE663C80F235DC89 /* ImageFormat.swift in Sources */, + 8A50CF3AAB5BD9BF949142DEB8E5050A /* ImageModifier.swift in Sources */, + 4B1C14991FCEB01E9E05EF8F1FCD5671 /* ImagePrefetcher.swift in Sources */, + CD698D8D49A0DB3669D8D58B4571268E /* ImageProcessor.swift in Sources */, + AB8B45111A4E958CF8D633D8C6231689 /* ImageProgressive.swift in Sources */, + 432848A52E6A7EC1F084299032907A29 /* ImageTransition.swift in Sources */, + 1A73F39CDDEEADB24723A3F6CBC359AE /* ImageView+Kingfisher.swift in Sources */, + 5CA745D6F24598B881ED85918BBE6697 /* Indicator.swift in Sources */, + A19C9C58F87FAF5DB267AE064023DF30 /* Kingfisher-dummy.m in Sources */, + 84FB7295031BEDF0073A52A61AF7129A /* Kingfisher.swift in Sources */, + 3C52CE0A60C1071B0A1586B57CC4E2FD /* KingfisherError.swift in Sources */, + 80F1CE5131DA37F9DF6516F927C78F16 /* KingfisherManager.swift in Sources */, + B2D8F9D8B91DE8C26D5C2355F1FB941A /* KingfisherOptionsInfo.swift in Sources */, + F0709DCF413ACE7F337DFAC0BA3B874B /* MemoryStorage.swift in Sources */, + 4781CFEA0881C7AFD1539C12F074632D /* NSButton+Kingfisher.swift in Sources */, + 686FF933FB5A33BA7A11CF986A916A06 /* Placeholder.swift in Sources */, + 62AD7D42C96A04828EEA3CC151AA5E38 /* RedirectHandler.swift in Sources */, + CDD42E25D201469CAFEA9E6181525FA8 /* RequestModifier.swift in Sources */, + 977C462E588D9085193D50156CEF2D5A /* Resource.swift in Sources */, + 2CE3B5EB3E415B5467CC027FC85D9F99 /* Result.swift in Sources */, + 767925B99CC34D22E127120952AC715D /* Runtime.swift in Sources */, + D0508A5E7A4DF1129A99994EFC3AACEE /* SessionDataTask.swift in Sources */, + 14672662061590430AF4801C432020A6 /* SessionDelegate.swift in Sources */, + 9820B3A65D65CCE7A683E5126C5100A8 /* SizeExtensions.swift in Sources */, + C0FFA57E39D99B6AC85FEF60CA966EE4 /* Source.swift in Sources */, + F780F31334A5B069C756121AE82F008B /* Storage.swift in Sources */, + E7D7D875688EDFBCB8F4C2C1F4F5B5E5 /* String+MD5.swift in Sources */, + F66ED581D2EBD170ABAD4EC0206B6C5C /* UIButton+Kingfisher.swift in Sources */, + 717C581962354033F5A82D15D6F676D5 /* WKInterfaceImage+Kingfisher.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FE80622A6C6ABD9C16D77071311098B1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 43E33E09B21DB7B98B1023C3A9FFFBF3 /* GoogleUtilities-dummy.m in Sources */, + BCC77EA6D45B9055FA945ADCB1122EBF /* GULAppDelegateSwizzler.m in Sources */, + BAAC10B16ADBC408AB110F8CF4321175 /* GULAppEnvironmentUtil.m in Sources */, + 2AED03ADF9C4EF5EA42A703B6A309CC9 /* GULHeartbeatDateStorage.m in Sources */, + 00E4FBEFC7AC22B61C48D083089F08D5 /* GULLogger.m in Sources */, + DB9B30A26FCC2B91EF7A13E52D57B736 /* GULMutableDictionary.m in Sources */, + 1053C5196B318F0B760D3B59DDFA1745 /* GULNetwork.m in Sources */, + CE964515884B6095B2D6B1F07525212D /* GULNetworkConstants.m in Sources */, + 774F1AF1B8C143D1FB94291504059A0A /* GULNetworkURLSession.m in Sources */, + 02D58066DFAC1A2411864057A27DB2F4 /* GULNSData+zlib.m in Sources */, + 1788D2AB1B052F07DC95420DFE355407 /* GULReachabilityChecker.m in Sources */, + 79ACE432B9BF42AD901E5ED21221B847 /* GULSceneDelegateSwizzler.m in Sources */, + 48BF3751E3092C0C24D0378706396092 /* GULSecureCoding.m in Sources */, + 58C0A90A823422A9564655C81B134FCD /* GULSwizzler.m in Sources */, + 3996B5F4074563E877612862AEC993B1 /* GULUserDefaults.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 01143B005AFD35FF51FC688BC2955CD1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GoogleUtilities; + target = 8D7F5D5DD528D21A72DC87ADA5B12E2D /* GoogleUtilities */; + targetProxy = 4D75B7D4E6D768ABBB5F00CA2812978B /* PBXContainerItemProxy */; + }; + 021255C3B6B349F5930B4797E5CEE7E0 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GoogleUtilities; + target = 8D7F5D5DD528D21A72DC87ADA5B12E2D /* GoogleUtilities */; + targetProxy = 761399FB2F0D1E24856374B3D390B6D8 /* PBXContainerItemProxy */; + }; + 05A0551F8A1D8AD97AFC2B0BCAB2EB88 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseAuth; + target = 6AE4A3D573DED275B034E20506596C62 /* FirebaseAuth */; + targetProxy = 3D3DF074B6E5C632278A4252E418E2BD /* PBXContainerItemProxy */; + }; + 0709C227FCA648E1B27A554B31AD14C9 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseAuth; + target = 6AE4A3D573DED275B034E20506596C62 /* FirebaseAuth */; + targetProxy = C9E956840CF76BE73AF3AA5E05BFD667 /* PBXContainerItemProxy */; + }; + 0DC11DAFCEAE58BDB6363229AEC2DC4D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = nanopb; + target = D2B5E7DCCBBFB32341D857D01211A1A3 /* nanopb */; + targetProxy = C8F705C2ADECD9202619F29B92B1B2B6 /* PBXContainerItemProxy */; + }; + 0E0268C916D08E877764597D45FC0C5B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GoogleUtilities; + target = 8D7F5D5DD528D21A72DC87ADA5B12E2D /* GoogleUtilities */; + targetProxy = 43DBEC04E774DB1B29AF53D5C0CB2523 /* PBXContainerItemProxy */; + }; + 0FF5D7B75F419BBCA71A1A03FE2B15D0 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GoogleAppMeasurement; + target = B53D977A951AFC38B21751B706C1DF83 /* GoogleAppMeasurement */; + targetProxy = FEEC4DE9036942D7D3042235493B79A4 /* PBXContainerItemProxy */; + }; + 2A43784F73C5BB2A1F17517D4638761C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseCore; + target = 4402AFF83DBDC4DD07E198685FDC2DF2 /* FirebaseCore */; + targetProxy = 8F2C2DA35D98C6D1928B3A8CE87C64E8 /* PBXContainerItemProxy */; + }; + 2A4AE265C5D4CE6F443EC05F98E07036 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "leveldb-library"; + target = 9307B7A119490930CF70393AB529AAC1 /* leveldb-library */; + targetProxy = DAF995436B3F053E0ACB29B301ED2C9D /* PBXContainerItemProxy */; + }; + 2FCE7DA6B2B795AA927052F731A13289 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseCore; + target = 4402AFF83DBDC4DD07E198685FDC2DF2 /* FirebaseCore */; + targetProxy = 92AD2EC939B1E85F4FA30EC8E0A22654 /* PBXContainerItemProxy */; + }; + 2FD65BDDC1696731952B9BA2BF65CB97 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = PromisesObjC; + target = 2BBF7206D7FAC92C82A042A99C4A98F8 /* PromisesObjC */; + targetProxy = 3079E51A1863435C4700CAAD4289A707 /* PBXContainerItemProxy */; + }; + 324578F6DA9690C2D36F5FF7A15B7F1A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GoogleUtilities; + target = 8D7F5D5DD528D21A72DC87ADA5B12E2D /* GoogleUtilities */; + targetProxy = 49F44A479BA8C7527515F5A698DD3969 /* PBXContainerItemProxy */; + }; + 349F05A2AEC17E9D92FE6B8520EE5D61 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GoogleAppMeasurement; + target = B53D977A951AFC38B21751B706C1DF83 /* GoogleAppMeasurement */; + targetProxy = 0E20D46E27234329076FE3950BBB7F99 /* PBXContainerItemProxy */; + }; + 354F4196A3C64F7E9180204EEA112FE4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseAnalytics; + target = C49E7A4D59E5C8BE8DE9FB1EFB150185 /* FirebaseAnalytics */; + targetProxy = EECBEC4A33C15CC6755B6CF200F8EB3F /* PBXContainerItemProxy */; + }; + 36E1DFA2B32651F86C10E61074C30919 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseCrashlytics; + target = 526C4398D095B3704EB933DADBC30093 /* FirebaseCrashlytics */; + targetProxy = FCDD41BE02B61E1DBF7A7FB991C36C70 /* PBXContainerItemProxy */; + }; + 392C8E30155BB885FD34DB42F4D2F71A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseAnalytics; + target = C49E7A4D59E5C8BE8DE9FB1EFB150185 /* FirebaseAnalytics */; + targetProxy = 58C19545ACFD96C76933EC2A1357ECF6 /* PBXContainerItemProxy */; + }; + 3D23A510C1F39E85C12770A44EF20426 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseAuthInterop; + target = 8EC0F2618965C875A96BFDBEE5D9734C /* FirebaseAuthInterop */; + targetProxy = 33BF6BAF5E852DDC34D143C5957B9BBB /* PBXContainerItemProxy */; + }; + 3E20CB0FCE844A7370DD8B614DD9DFD0 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GoogleDataTransportCCTSupport; + target = F4F25FCAC51B51FD5F986EB939BF1F87 /* GoogleDataTransportCCTSupport */; + targetProxy = 0B84150A0245B5BD277181C4173889EC /* PBXContainerItemProxy */; + }; + 40243F513FD0FE6049FC111B682AD860 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseCoreDiagnosticsInterop; + target = 5EB4B0B6DA6D5C0C3365733BEAA1C485 /* FirebaseCoreDiagnosticsInterop */; + targetProxy = 423C0534BC19A64EE3AA747A6D910657 /* PBXContainerItemProxy */; + }; + 4087E8787FCE3268E8AA78C79269F7C8 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GoogleUtilities; + target = 8D7F5D5DD528D21A72DC87ADA5B12E2D /* GoogleUtilities */; + targetProxy = 12DE0300AD3C264AE854729394E38954 /* PBXContainerItemProxy */; + }; + 40D7E5DDFEBFB2B51E86289AA6F805CE /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = PromisesObjC; + target = 2BBF7206D7FAC92C82A042A99C4A98F8 /* PromisesObjC */; + targetProxy = 8D09750C2082B22B85460095A9EF6663 /* PBXContainerItemProxy */; + }; + 48E7C2C891AC768D575FED3B8B85240F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseCore; + target = 4402AFF83DBDC4DD07E198685FDC2DF2 /* FirebaseCore */; + targetProxy = 79D989F96ADE69298F8884DEA8A83127 /* PBXContainerItemProxy */; + }; + 53C766FD17017572499CFD5BFC6E0A93 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Kingfisher; + target = E8022D22FAA6690B5E1C379C1BCE1491 /* Kingfisher */; + targetProxy = B0712C553EBA597C8935FFE5090FB915 /* PBXContainerItemProxy */; + }; + 54BBD33BAFC8EA14B7F49677BAC261DE /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GTMSessionFetcher; + target = D676E21115185671D7258A56944ABE98 /* GTMSessionFetcher */; + targetProxy = 25578E010FB0A6B99D69EB60D0AE8D8A /* PBXContainerItemProxy */; + }; + 574DFF66C1711EE8E8DEA48C494170A8 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseCore; + target = 4402AFF83DBDC4DD07E198685FDC2DF2 /* FirebaseCore */; + targetProxy = 6D62F55BA23CBBC7B8F0F3ED7F7A0605 /* PBXContainerItemProxy */; + }; + 5771B563572B13F25B00332ECE517169 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = nanopb; + target = D2B5E7DCCBBFB32341D857D01211A1A3 /* nanopb */; + targetProxy = 0C3A96F5C5F6E8CB1BBF34F043327B90 /* PBXContainerItemProxy */; + }; + 57D25E0CD95447EAA304C21B8CAA14A2 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseCore; + target = 4402AFF83DBDC4DD07E198685FDC2DF2 /* FirebaseCore */; + targetProxy = 53C5D8C1E4E0BEE3C755797ACCD3426F /* PBXContainerItemProxy */; + }; + 62EB8B3F617A68FD45785CC9BC94E60C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GTMSessionFetcher; + target = D676E21115185671D7258A56944ABE98 /* GTMSessionFetcher */; + targetProxy = D7D6BF55D7A451D0E3B4CF82B8D8767C /* PBXContainerItemProxy */; + }; + 638210AC0EDABFD0A6DFFD9B2E972A00 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseCrashlytics; + target = 526C4398D095B3704EB933DADBC30093 /* FirebaseCrashlytics */; + targetProxy = C1A9D1B82ED4DA51A08A5188FE6D031D /* PBXContainerItemProxy */; + }; + 6E1AD25281BA3E9568151D27C726C065 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GoogleDataTransportCCTSupport; + target = F4F25FCAC51B51FD5F986EB939BF1F87 /* GoogleDataTransportCCTSupport */; + targetProxy = 31C009FEBEC375BDEBDB204259CB5706 /* PBXContainerItemProxy */; + }; + 700FF6B24F804D742CDB062FE6379C20 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseDatabase; + target = 736AF68F6527ACF6B4A4C54728824A1C /* FirebaseDatabase */; + targetProxy = DC00028F97358D0F7A7F571E33138E29 /* PBXContainerItemProxy */; + }; + 70AD386F01723A04963573668F544FF5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GoogleDataTransport; + target = 5C0371EE948D0357B8EE0E34ABB44BF0 /* GoogleDataTransport */; + targetProxy = EB15912C2AE1C6FF2B28D9521ED37B32 /* PBXContainerItemProxy */; + }; + 79A464178DFA35E8023E00C69B3A4748 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = nanopb; + target = D2B5E7DCCBBFB32341D857D01211A1A3 /* nanopb */; + targetProxy = 68988C3BA8D3738F378BE0B646187745 /* PBXContainerItemProxy */; + }; + 7D3621E950BF56E3ADDF18616C62CE6A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GoogleUtilities; + target = 8D7F5D5DD528D21A72DC87ADA5B12E2D /* GoogleUtilities */; + targetProxy = 28B025518E6FEA532380F4D63416552E /* PBXContainerItemProxy */; + }; + 7FEE2242DF64783E5EC3BA920131F292 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "leveldb-library"; + target = 9307B7A119490930CF70393AB529AAC1 /* leveldb-library */; + targetProxy = 4D44D5091BB420DEBE7212F778025EDE /* PBXContainerItemProxy */; + }; + 83BE3790A7E016D11AE8BC46AB5CEDD0 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseInstallations; + target = 87803597EB3F20FC46472B85392EC4FD /* FirebaseInstallations */; + targetProxy = 722DE636EF754E309E2377A2F2EC3B5B /* PBXContainerItemProxy */; + }; + 93F64BACFD59E8C1EBE8870A6F9BB1C1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Firebase; + target = 072CEA044D2EF26F03496D5996BBF59F /* Firebase */; + targetProxy = 8B18908728291153F46A2A4F421678E1 /* PBXContainerItemProxy */; + }; + 985E4B160C2E2AF9CE29BC9F4F2B6914 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseCoreDiagnostics; + target = 620E05868772C10B4920DC7E324F2C87 /* FirebaseCoreDiagnostics */; + targetProxy = 875EBEBC003673A6638AF33B98A68DEA /* PBXContainerItemProxy */; + }; + A05EDAFEA754F095FB80282832B52E06 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseCoreDiagnostics; + target = 620E05868772C10B4920DC7E324F2C87 /* FirebaseCoreDiagnostics */; + targetProxy = F7954583DBB6A2D653793368CD5A8154 /* PBXContainerItemProxy */; + }; + A3959E97FDB930F5D29E4012D45111A7 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GoogleUtilities; + target = 8D7F5D5DD528D21A72DC87ADA5B12E2D /* GoogleUtilities */; + targetProxy = B266392E7C66A902BB2DC6B7CFC16FAE /* PBXContainerItemProxy */; + }; + A450B05F74819E087335E1B622481147 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GoogleDataTransport; + target = 5C0371EE948D0357B8EE0E34ABB44BF0 /* GoogleDataTransport */; + targetProxy = 77FE2E7A53F20F84921FFA3DA3654782 /* PBXContainerItemProxy */; + }; + AAF9ACD44059C396BB32C91452A948F3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseCoreDiagnosticsInterop; + target = 5EB4B0B6DA6D5C0C3365733BEAA1C485 /* FirebaseCoreDiagnosticsInterop */; + targetProxy = 0FF9A1E233A4125115F3EFBB2613C4E3 /* PBXContainerItemProxy */; + }; + B0F2CB59E7E3549DFC145C2E0973B40E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Realm; + target = 68494F30B4A13F8E5E88BCCAEC25B0A4 /* Realm */; + targetProxy = 2241096BF52D154C2390A33A7C459082 /* PBXContainerItemProxy */; + }; + B19591BF175558D9D5B01FB5E9D7C18F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = PromisesObjC; + target = 2BBF7206D7FAC92C82A042A99C4A98F8 /* PromisesObjC */; + targetProxy = DF30F9E34C851B4F019CA15C657FC22D /* PBXContainerItemProxy */; + }; + B24AF135B12811E72517CE2850B829ED /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseCoreDiagnosticsInterop; + target = 5EB4B0B6DA6D5C0C3365733BEAA1C485 /* FirebaseCoreDiagnosticsInterop */; + targetProxy = 208CC7E3E9A345211A226470ED36BF4C /* PBXContainerItemProxy */; + }; + B6870CB8F706887EE0DDB4C76EC831CA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = nanopb; + target = D2B5E7DCCBBFB32341D857D01211A1A3 /* nanopb */; + targetProxy = A2A702E58E50D0E6D66E484489B8E5C6 /* PBXContainerItemProxy */; + }; + B91018D8AFA9C999719E25CCA71FF9E5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseAnalyticsInterop; + target = D372E53E2E8FEAA06A0439FB85E65767 /* FirebaseAnalyticsInterop */; + targetProxy = D7B0BFA47504549D7C0E2A4FECFF23DC /* PBXContainerItemProxy */; + }; + BE72D292DD246CC3DA7ADD84D10F05DA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = nanopb; + target = D2B5E7DCCBBFB32341D857D01211A1A3 /* nanopb */; + targetProxy = DF6ABEA55AE5B3CAD5D69ACB6E1524E5 /* PBXContainerItemProxy */; + }; + C10D33DFDBF0043972BD3D6D102DA0D3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseCore; + target = 4402AFF83DBDC4DD07E198685FDC2DF2 /* FirebaseCore */; + targetProxy = 6D8F1E2DE2577840678D69579DCDFC6B /* PBXContainerItemProxy */; + }; + C3EACFC9CA6028D6F606298EAE22AF91 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseAuthInterop; + target = 8EC0F2618965C875A96BFDBEE5D9734C /* FirebaseAuthInterop */; + targetProxy = D701690C588F6289EBF41CED6C2C4B9D /* PBXContainerItemProxy */; + }; + CB40789F78AC2DF2FA8AE45D1BAE2147 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseAnalyticsInterop; + target = D372E53E2E8FEAA06A0439FB85E65767 /* FirebaseAnalyticsInterop */; + targetProxy = 5EB4367F6ABD0987B932719C700300E1 /* PBXContainerItemProxy */; + }; + CC6DC633F22EFA8A07C08516F05FBBF4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseDatabase; + target = 736AF68F6527ACF6B4A4C54728824A1C /* FirebaseDatabase */; + targetProxy = B4783EBCF939E1B6AD979918DF38F4D6 /* PBXContainerItemProxy */; + }; + D3A0FC56FD2A358D381B58B6BB665637 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseInstanceID; + target = 9E25537BF40D1A3B30CF43FD3E6ACD94 /* FirebaseInstanceID */; + targetProxy = 71FFA97981BB67C0EE8F5C5DE3763A40 /* PBXContainerItemProxy */; + }; + E1F058D92ABF497DDB714C894B3C044F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GoogleUtilities; + target = 8D7F5D5DD528D21A72DC87ADA5B12E2D /* GoogleUtilities */; + targetProxy = 5FB9279A7FB8B977A0C39AEF6B0842B6 /* PBXContainerItemProxy */; + }; + E39FA4393FD88879E749D9E109835E26 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = RealmSwift; + target = 782725687624F8665247B84AB581BEB1 /* RealmSwift */; + targetProxy = 0C08E0DDA386FA7291C5CA39885F1AB1 /* PBXContainerItemProxy */; + }; + E75C1481C7E14B1061F64ACCBD9970E3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Realm; + target = 68494F30B4A13F8E5E88BCCAEC25B0A4 /* Realm */; + targetProxy = 6C711276394E0714ABB7DADD6126DB09 /* PBXContainerItemProxy */; + }; + E9E86E60D1518546315DAF496F26E5E4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseInstanceID; + target = 9E25537BF40D1A3B30CF43FD3E6ACD94 /* FirebaseInstanceID */; + targetProxy = 3A85B25BE7EB3700AA42FFDB67F0FA65 /* PBXContainerItemProxy */; + }; + F074433D00394DC8883F7FECF3B4B68B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseInstallations; + target = 87803597EB3F20FC46472B85392EC4FD /* FirebaseInstallations */; + targetProxy = ECD11A5E7A7957DCB33D02882249EA61 /* PBXContainerItemProxy */; + }; + F3E52C14FC1EDF7D7B2A641852AE94B7 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Alamofire; + target = EAAA1AD3A8A1B59AB91319EE40752C6D /* Alamofire */; + targetProxy = 28698797F671D059112FD255CF577103 /* PBXContainerItemProxy */; + }; + F5BB79D84F09016BF84F5EF526665541 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseAuthInterop; + target = 8EC0F2618965C875A96BFDBEE5D9734C /* FirebaseAuthInterop */; + targetProxy = CF5AB4129369903D3B934C30F92A82B1 /* PBXContainerItemProxy */; + }; + F6802ACE8F744B4C89934305521D6666 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseInstanceID; + target = 9E25537BF40D1A3B30CF43FD3E6ACD94 /* FirebaseInstanceID */; + targetProxy = D4922007713A402C8F9AB02C9AE0C6B2 /* PBXContainerItemProxy */; + }; + F7ED5670FC5982FCDE94012638063E39 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseCore; + target = 4402AFF83DBDC4DD07E198685FDC2DF2 /* FirebaseCore */; + targetProxy = FEFB51F8A80B30E44D7D4FF289B3966B /* PBXContainerItemProxy */; + }; + FA72C7E2D80B66734E84ECC6BD4101B4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FirebaseCore; + target = 4402AFF83DBDC4DD07E198685FDC2DF2 /* FirebaseCore */; + targetProxy = 6F141F40B8C4BD18CAF62DEDBC7D6A4B /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 04C735CBA622053583A0CF8ADD40F5F1 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 24527F70FC49CD3DC18B482D54CEBBCC /* nanopb.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/nanopb/nanopb-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/nanopb/nanopb-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/nanopb/nanopb.modulemap"; + PRODUCT_MODULE_NAME = nanopb; + PRODUCT_NAME = nanopb; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 0D39FC230BE07CE3325C380B86D1E458 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 867B6C8244092F35079D098F2AD0327A /* FirebaseAnalytics.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 0E3AB9FE856931FDF3579D2831EBEB8F /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D6153BA7DF29AA46B25DAD360E51B86B /* GTMSessionFetcher.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/GTMSessionFetcher/GTMSessionFetcher-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/GTMSessionFetcher/GTMSessionFetcher-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/GTMSessionFetcher/GTMSessionFetcher.modulemap"; + PRODUCT_MODULE_NAME = GTMSessionFetcher; + PRODUCT_NAME = GTMSessionFetcher; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 12BF405C79DF208F1E7BC486AFE40F35 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E7DEDF40806419E0CEA62BE4111E28C4 /* FirebaseCore.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/FirebaseCore/FirebaseCore-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/FirebaseCore/FirebaseCore.modulemap"; + PRODUCT_MODULE_NAME = FirebaseCore; + PRODUCT_NAME = FirebaseCore; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 232469F9ED17F6ADDE8C8BB225676CDD /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E7DEDF40806419E0CEA62BE4111E28C4 /* FirebaseCore.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/FirebaseCore/FirebaseCore-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/FirebaseCore/FirebaseCore.modulemap"; + PRODUCT_MODULE_NAME = FirebaseCore; + PRODUCT_NAME = FirebaseCore; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 2561401C2769271EDF5B6AB4F1300230 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 88ABB3A4A0002798B71EE2408272DA01 /* Pods-GeekbrainsUI.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/Pods-GeekbrainsUI/Pods-GeekbrainsUI-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-GeekbrainsUI/Pods-GeekbrainsUI.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 25B159D0A2423E81173FAB0A18AE8DB2 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = EC173B1FCB6DCDA143020621B8183EC3 /* FirebaseAuthInterop.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 25BCB10DC5C37001BA46A93575944C91 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 057C3F5AF0B6B7F47AB4D32D9FCD9421 /* FirebaseAuth.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/FirebaseAuth/FirebaseAuth-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/FirebaseAuth/FirebaseAuth.modulemap"; + PRODUCT_MODULE_NAME = FirebaseAuth; + PRODUCT_NAME = FirebaseAuth; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 2A9C82AC677FBE865F082A976F32AEA0 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 67DA492C3336C7C3ACE453F59A59590B /* GoogleDataTransportCCTSupport.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport.modulemap"; + PRODUCT_MODULE_NAME = GoogleDataTransportCCTSupport; + PRODUCT_NAME = GoogleDataTransportCCTSupport; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 2D001A65DD41252A3739BAE288D885E8 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D6153BA7DF29AA46B25DAD360E51B86B /* GTMSessionFetcher.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/GTMSessionFetcher/GTMSessionFetcher-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/GTMSessionFetcher/GTMSessionFetcher-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/GTMSessionFetcher/GTMSessionFetcher.modulemap"; + PRODUCT_MODULE_NAME = GTMSessionFetcher; + PRODUCT_NAME = GTMSessionFetcher; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 3657217B49B8CA5A78839AC01DEE89F0 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 67AFD064168467D4279B84030F4A6890 /* FirebaseAnalyticsInterop.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 369B98281735BAC2219FD2C5CDBC1FE1 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 057C3F5AF0B6B7F47AB4D32D9FCD9421 /* FirebaseAuth.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/FirebaseAuth/FirebaseAuth-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/FirebaseAuth/FirebaseAuth.modulemap"; + PRODUCT_MODULE_NAME = FirebaseAuth; + PRODUCT_NAME = FirebaseAuth; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 39F85C83E7A7DCC9F8A3E3C991970B96 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 67AFD064168467D4279B84030F4A6890 /* FirebaseAnalyticsInterop.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 3C9172E4286B886980BF3AE29637CC20 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = ADABD7A2BBCAE81134FFE12A19B5DE02 /* Firebase.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 3DE2C4A3A68C76D798374D77D7644459 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C0B68F31335F12A48404BBD938C49152 /* RealmSwift.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/RealmSwift/RealmSwift-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/RealmSwift/RealmSwift-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/RealmSwift/RealmSwift.modulemap"; + PRODUCT_MODULE_NAME = RealmSwift; + PRODUCT_NAME = RealmSwift; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 4592A658BFE5AA153429BDDDEACE8A91 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 63A43B2B8A0078E69FE67E3A1D21ADF4 /* FirebaseCrashlytics.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/FirebaseCrashlytics/FirebaseCrashlytics-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/FirebaseCrashlytics/FirebaseCrashlytics.modulemap"; + PRODUCT_MODULE_NAME = FirebaseCrashlytics; + PRODUCT_NAME = FirebaseCrashlytics; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 467209F2B478496F5E53A5939039B29E /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3E606567B7AC74BAC31708ED387EF522 /* Realm.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/Realm/Realm-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/Realm/Realm-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/Realm/Realm.modulemap"; + PRODUCT_MODULE_NAME = Realm; + PRODUCT_NAME = Realm; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 472D128FA54EB4B32161125C2F2EBCC2 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 24527F70FC49CD3DC18B482D54CEBBCC /* nanopb.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/nanopb/nanopb-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/nanopb/nanopb-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/nanopb/nanopb.modulemap"; + PRODUCT_MODULE_NAME = nanopb; + PRODUCT_NAME = nanopb; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 47BB16E00A4C070F92355A3D117D2027 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3C55C132C630B644EE7681983CD71CBE /* FirebaseCoreDiagnosticsInterop.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 514C3CA9637E2073CEFB594C45083CE6 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DB74B9A6B10A8AA948A01B99D9D9CDFF /* FirebaseInstallations.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/FirebaseInstallations/FirebaseInstallations-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/FirebaseInstallations/FirebaseInstallations.modulemap"; + PRODUCT_MODULE_NAME = FirebaseInstallations; + PRODUCT_NAME = FirebaseInstallations; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 5273D6577DD5F7A2FBDD5D7FD58FE256 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 39C548D8FA8FED7C63B92834D761E16A /* GoogleUtilities.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/GoogleUtilities/GoogleUtilities-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/GoogleUtilities/GoogleUtilities-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/GoogleUtilities/GoogleUtilities.modulemap"; + PRODUCT_MODULE_NAME = GoogleUtilities; + PRODUCT_NAME = GoogleUtilities; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 66D9B471A39F438884F13C5FD7575767 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = EC173B1FCB6DCDA143020621B8183EC3 /* FirebaseAuthInterop.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 685C7399720396677319FCAE9F7931BD /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = BF6C758A52AEA6F30D091E5D3D99C459 /* Kingfisher.xcconfig */; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/Kingfisher/Kingfisher-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/Kingfisher/Kingfisher-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/Kingfisher/Kingfisher.modulemap"; + PRODUCT_MODULE_NAME = Kingfisher; + PRODUCT_NAME = Kingfisher; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 6AA888D29791BA1B1FF2555702220AC9 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3C55C132C630B644EE7681983CD71CBE /* FirebaseCoreDiagnosticsInterop.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 75BBB760DFD877E344322B54D29DFE64 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E181357E02598A486F35973C9C68CC76 /* Alamofire.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/Alamofire/Alamofire-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/Alamofire/Alamofire-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/Alamofire/Alamofire.modulemap"; + PRODUCT_MODULE_NAME = Alamofire; + PRODUCT_NAME = Alamofire; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.1; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 7689F37542CE9FB09B099BD855644446 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2D2540B39435F5F149667EA4419EA0E1 /* FirebaseDatabase.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/FirebaseDatabase/FirebaseDatabase-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/FirebaseDatabase/FirebaseDatabase.modulemap"; + PRODUCT_MODULE_NAME = FirebaseDatabase; + PRODUCT_NAME = FirebaseDatabase; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 76B04784E199F9E057C51FBC85B40AF9 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3E606567B7AC74BAC31708ED387EF522 /* Realm.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/Realm/Realm-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/Realm/Realm-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/Realm/Realm.modulemap"; + PRODUCT_MODULE_NAME = Realm; + PRODUCT_NAME = Realm; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 7C8AF119368E52F2C55D30BA6712D159 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = ADABD7A2BBCAE81134FFE12A19B5DE02 /* Firebase.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 87CAE5582F2C084ACE4C4F8A4F3DD309 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C2B57CC70F0024AC3039D5A76E0C6B33 /* FirebaseInstanceID.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/FirebaseInstanceID/FirebaseInstanceID-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/FirebaseInstanceID/FirebaseInstanceID.modulemap"; + PRODUCT_MODULE_NAME = FirebaseInstanceID; + PRODUCT_NAME = FirebaseInstanceID; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 88CEA30C537F9FDD71C672375B8FAAE1 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 63A43B2B8A0078E69FE67E3A1D21ADF4 /* FirebaseCrashlytics.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/FirebaseCrashlytics/FirebaseCrashlytics-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/FirebaseCrashlytics/FirebaseCrashlytics.modulemap"; + PRODUCT_MODULE_NAME = FirebaseCrashlytics; + PRODUCT_NAME = FirebaseCrashlytics; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 8FFB63B52C2DF6D4CCBE1C9EFB42888F /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C3460D05DC30E134CA7B463814A904EE /* leveldb-library.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/leveldb-library/leveldb-library-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/leveldb-library/leveldb-library-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/leveldb-library/leveldb-library.modulemap"; + PRODUCT_MODULE_NAME = leveldb; + PRODUCT_NAME = leveldb; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 939F1A8F6BCEB7F9F64D9EEE50CAE014 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "POD_CONFIGURATION_DEBUG=1", + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.2; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_INSTALLED_PRODUCT = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + SYMROOT = "${SRCROOT}/../build"; + }; + name = Debug; + }; + 99CC275715D1B780AF7C26EC04D2C859 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3022CC4D2BD5802E1A96A2789D9BE909 /* PromisesObjC.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/PromisesObjC/PromisesObjC-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/PromisesObjC/PromisesObjC.modulemap"; + PRODUCT_MODULE_NAME = FBLPromises; + PRODUCT_NAME = FBLPromises; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + A3EA98776ED8CC637541BCE2260F66BB /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 867B6C8244092F35079D098F2AD0327A /* FirebaseAnalytics.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + A5691693484CA1E5BCB4791ABC955B1B /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C2B57CC70F0024AC3039D5A76E0C6B33 /* FirebaseInstanceID.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/FirebaseInstanceID/FirebaseInstanceID-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/FirebaseInstanceID/FirebaseInstanceID.modulemap"; + PRODUCT_MODULE_NAME = FirebaseInstanceID; + PRODUCT_NAME = FirebaseInstanceID; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + AC18B2B50A22B31EC5EF71FF676C903A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3022CC4D2BD5802E1A96A2789D9BE909 /* PromisesObjC.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/PromisesObjC/PromisesObjC-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/PromisesObjC/PromisesObjC.modulemap"; + PRODUCT_MODULE_NAME = FBLPromises; + PRODUCT_NAME = FBLPromises; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + ADFF21D5AEA98DE95073249DD638F73A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C3460D05DC30E134CA7B463814A904EE /* leveldb-library.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/leveldb-library/leveldb-library-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/leveldb-library/leveldb-library-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/leveldb-library/leveldb-library.modulemap"; + PRODUCT_MODULE_NAME = leveldb; + PRODUCT_NAME = leveldb; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + BEF1D1350E52675C976D5AAFD6396CF7 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 67DA492C3336C7C3ACE453F59A59590B /* GoogleDataTransportCCTSupport.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/GoogleDataTransportCCTSupport/GoogleDataTransportCCTSupport.modulemap"; + PRODUCT_MODULE_NAME = GoogleDataTransportCCTSupport; + PRODUCT_NAME = GoogleDataTransportCCTSupport; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + C24F12595C4F87588299AD92596C12D6 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 39C548D8FA8FED7C63B92834D761E16A /* GoogleUtilities.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/GoogleUtilities/GoogleUtilities-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/GoogleUtilities/GoogleUtilities-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/GoogleUtilities/GoogleUtilities.modulemap"; + PRODUCT_MODULE_NAME = GoogleUtilities; + PRODUCT_NAME = GoogleUtilities; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + C2E8FDB0B39F8978A88095F55D0D14A8 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8D2EFA3A80904C2A2AB08FDA578F8844 /* GoogleDataTransport.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/GoogleDataTransport/GoogleDataTransport-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/GoogleDataTransport/GoogleDataTransport.modulemap"; + PRODUCT_MODULE_NAME = GoogleDataTransport; + PRODUCT_NAME = GoogleDataTransport; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + C3F0D74CD75425DDA01F14AF5A2A561E /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2D2540B39435F5F149667EA4419EA0E1 /* FirebaseDatabase.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/FirebaseDatabase/FirebaseDatabase-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/FirebaseDatabase/FirebaseDatabase.modulemap"; + PRODUCT_MODULE_NAME = FirebaseDatabase; + PRODUCT_NAME = FirebaseDatabase; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + C50BEF5B2343E054DD5E0232B98066EC /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 73CFA238FD606E2E699DB4E8281F155B /* FirebaseCoreDiagnostics.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics.modulemap"; + PRODUCT_MODULE_NAME = FirebaseCoreDiagnostics; + PRODUCT_NAME = FirebaseCoreDiagnostics; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + C5B2F3AC032263BC23702CEECE53CA96 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DB74B9A6B10A8AA948A01B99D9D9CDFF /* FirebaseInstallations.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/FirebaseInstallations/FirebaseInstallations-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/FirebaseInstallations/FirebaseInstallations.modulemap"; + PRODUCT_MODULE_NAME = FirebaseInstallations; + PRODUCT_NAME = FirebaseInstallations; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + C5B4E7037BA5FBB4048906FF2C4B4488 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 73CFA238FD606E2E699DB4E8281F155B /* FirebaseCoreDiagnostics.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics.modulemap"; + PRODUCT_MODULE_NAME = FirebaseCoreDiagnostics; + PRODUCT_NAME = FirebaseCoreDiagnostics; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + CBEBE8AE16828E58BA01D73C6AEEC28E /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9AA0E16A090681028EE0681EE3F25288 /* GoogleAppMeasurement.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + CD383A50371DB066CCE30956E516BC22 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8D2EFA3A80904C2A2AB08FDA578F8844 /* GoogleDataTransport.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/GoogleDataTransport/GoogleDataTransport-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/GoogleDataTransport/GoogleDataTransport.modulemap"; + PRODUCT_MODULE_NAME = GoogleDataTransport; + PRODUCT_NAME = GoogleDataTransport; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + D37B4CD95D500D01A1D681709E08E4D3 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9AA0E16A090681028EE0681EE3F25288 /* GoogleAppMeasurement.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + DA1A02B1DC7EFF460BA21233FDF0ADB3 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 50FA543897C7A9B21B74C83B4F812945 /* Pods-GeekbrainsUI.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/Pods-GeekbrainsUI/Pods-GeekbrainsUI-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-GeekbrainsUI/Pods-GeekbrainsUI.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + ECB437504DF805BD96CAABF97400B526 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "POD_CONFIGURATION_RELEASE=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.2; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_INSTALLED_PRODUCT = NO; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + SYMROOT = "${SRCROOT}/../build"; + }; + name = Release; + }; + F110C6EB7167E59121EB348F6D9B34DE /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = BF6C758A52AEA6F30D091E5D3D99C459 /* Kingfisher.xcconfig */; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/Kingfisher/Kingfisher-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/Kingfisher/Kingfisher-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/Kingfisher/Kingfisher.modulemap"; + PRODUCT_MODULE_NAME = Kingfisher; + PRODUCT_NAME = Kingfisher; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + F86F5AA5D94340F9BF0508955C1D75C5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C0B68F31335F12A48404BBD938C49152 /* RealmSwift.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/RealmSwift/RealmSwift-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/RealmSwift/RealmSwift-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/RealmSwift/RealmSwift.modulemap"; + PRODUCT_MODULE_NAME = RealmSwift; + PRODUCT_NAME = RealmSwift; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + F94FD07C591B17B7E61A1528AB9BCB3B /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E181357E02598A486F35973C9C68CC76 /* Alamofire.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/Alamofire/Alamofire-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/Alamofire/Alamofire-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/Alamofire/Alamofire.modulemap"; + PRODUCT_MODULE_NAME = Alamofire; + PRODUCT_NAME = Alamofire; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.1; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 048F3456D8B6D09E4223203A2B3483BA /* Build configuration list for PBXAggregateTarget "FirebaseAuthInterop" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 25B159D0A2423E81173FAB0A18AE8DB2 /* Debug */, + 66D9B471A39F438884F13C5FD7575767 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 0B9A4D705F70B9BA2316A19C5CFA153F /* Build configuration list for PBXNativeTarget "GoogleUtilities" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C24F12595C4F87588299AD92596C12D6 /* Debug */, + 5273D6577DD5F7A2FBDD5D7FD58FE256 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 22886CEE8A1196C43C2AD1112FC689C4 /* Build configuration list for PBXNativeTarget "RealmSwift" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3DE2C4A3A68C76D798374D77D7644459 /* Debug */, + F86F5AA5D94340F9BF0508955C1D75C5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 375D202C856DE3999522590304F667FD /* Build configuration list for PBXNativeTarget "Pods-GeekbrainsUI" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2561401C2769271EDF5B6AB4F1300230 /* Debug */, + DA1A02B1DC7EFF460BA21233FDF0ADB3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 41BB255B9390F3173EA80D74E50ADA81 /* Build configuration list for PBXAggregateTarget "FirebaseCoreDiagnosticsInterop" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6AA888D29791BA1B1FF2555702220AC9 /* Debug */, + 47BB16E00A4C070F92355A3D117D2027 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 422CDDC2F642E931AE5FEE0AB55DEE66 /* Build configuration list for PBXNativeTarget "FirebaseDatabase" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C3F0D74CD75425DDA01F14AF5A2A561E /* Debug */, + 7689F37542CE9FB09B099BD855644446 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 939F1A8F6BCEB7F9F64D9EEE50CAE014 /* Debug */, + ECB437504DF805BD96CAABF97400B526 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 51DE3347D9C3728AF991BCBE24CCF522 /* Build configuration list for PBXNativeTarget "PromisesObjC" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 99CC275715D1B780AF7C26EC04D2C859 /* Debug */, + AC18B2B50A22B31EC5EF71FF676C903A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 5BCD2A01F1CF558807A1EF939E4799E4 /* Build configuration list for PBXNativeTarget "FirebaseAuth" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 25BCB10DC5C37001BA46A93575944C91 /* Debug */, + 369B98281735BAC2219FD2C5CDBC1FE1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 61E400600BC3D57ED87E094438ED32B3 /* Build configuration list for PBXAggregateTarget "Firebase" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3C9172E4286B886980BF3AE29637CC20 /* Debug */, + 7C8AF119368E52F2C55D30BA6712D159 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 83DA204DD006948939BC645580D82964 /* Build configuration list for PBXNativeTarget "FirebaseCoreDiagnostics" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C50BEF5B2343E054DD5E0232B98066EC /* Debug */, + C5B4E7037BA5FBB4048906FF2C4B4488 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9242B65F6853164F7A63A53CB7012901 /* Build configuration list for PBXNativeTarget "FirebaseInstanceID" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A5691693484CA1E5BCB4791ABC955B1B /* Debug */, + 87CAE5582F2C084ACE4C4F8A4F3DD309 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + AA3ABA7985FD8E40F9E3432F446E4B5D /* Build configuration list for PBXNativeTarget "leveldb-library" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8FFB63B52C2DF6D4CCBE1C9EFB42888F /* Debug */, + ADFF21D5AEA98DE95073249DD638F73A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + AADDC00E893C2C74062874D0780A3D35 /* Build configuration list for PBXAggregateTarget "GoogleAppMeasurement" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CBEBE8AE16828E58BA01D73C6AEEC28E /* Debug */, + D37B4CD95D500D01A1D681709E08E4D3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + B2AC4D5B812BD64F70849561DAA7E63B /* Build configuration list for PBXNativeTarget "GoogleDataTransport" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C2E8FDB0B39F8978A88095F55D0D14A8 /* Debug */, + CD383A50371DB066CCE30956E516BC22 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + BECB003B49DC8868FC475E308A9E71DF /* Build configuration list for PBXNativeTarget "FirebaseCore" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 12BF405C79DF208F1E7BC486AFE40F35 /* Debug */, + 232469F9ED17F6ADDE8C8BB225676CDD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C78C1F585AF9F23F8E5BE58C65DFB1B0 /* Build configuration list for PBXNativeTarget "Realm" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 76B04784E199F9E057C51FBC85B40AF9 /* Debug */, + 467209F2B478496F5E53A5939039B29E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C84C02119D44D6E45D81B9190AFAA86F /* Build configuration list for PBXAggregateTarget "FirebaseAnalyticsInterop" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3657217B49B8CA5A78839AC01DEE89F0 /* Debug */, + 39F85C83E7A7DCC9F8A3E3C991970B96 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D005677E54324E7E9FD9E4881C4035B3 /* Build configuration list for PBXNativeTarget "nanopb" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 472D128FA54EB4B32161125C2F2EBCC2 /* Debug */, + 04C735CBA622053583A0CF8ADD40F5F1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D9C6481EA4A7A51B8D39B3FE857B5A82 /* Build configuration list for PBXNativeTarget "GTMSessionFetcher" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2D001A65DD41252A3739BAE288D885E8 /* Debug */, + 0E3AB9FE856931FDF3579D2831EBEB8F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + E4A5194ABAF7A4780609E0E581DA6B54 /* Build configuration list for PBXNativeTarget "Alamofire" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 75BBB760DFD877E344322B54D29DFE64 /* Debug */, + F94FD07C591B17B7E61A1528AB9BCB3B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + E764F986CA22AE287CF37FAF5822F40C /* Build configuration list for PBXNativeTarget "FirebaseInstallations" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 514C3CA9637E2073CEFB594C45083CE6 /* Debug */, + C5B2F3AC032263BC23702CEECE53CA96 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + EFFBBEC058839ADEF2EE6CF4A7B89A81 /* Build configuration list for PBXNativeTarget "GoogleDataTransportCCTSupport" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2A9C82AC677FBE865F082A976F32AEA0 /* Debug */, + BEF1D1350E52675C976D5AAFD6396CF7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F039405F0AD035E8228C259D7DAA585B /* Build configuration list for PBXNativeTarget "FirebaseCrashlytics" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4592A658BFE5AA153429BDDDEACE8A91 /* Debug */, + 88CEA30C537F9FDD71C672375B8FAAE1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F474713E7462A344D5557EB151CFA07E /* Build configuration list for PBXNativeTarget "Kingfisher" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 685C7399720396677319FCAE9F7931BD /* Debug */, + F110C6EB7167E59121EB348F6D9B34DE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F7B6A2C25C58E401CC0123EEDFC62D1D /* Build configuration list for PBXAggregateTarget "FirebaseAnalytics" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0D39FC230BE07CE3325C380B86D1E458 /* Debug */, + A3EA98776ED8CC637541BCE2260F66BB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = BFDFE7DC352907FC980B868725387E98 /* Project object */; +} diff --git a/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/Alamofire.xcscheme b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/Alamofire.xcscheme new file mode 100644 index 0000000..bc06c13 --- /dev/null +++ b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/Alamofire.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/Firebase.xcscheme b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/Firebase.xcscheme new file mode 100644 index 0000000..ab9e834 --- /dev/null +++ b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/Firebase.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseAnalytics.xcscheme b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseAnalytics.xcscheme new file mode 100644 index 0000000..3eba3b7 --- /dev/null +++ b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseAnalytics.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseAnalyticsInterop.xcscheme b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseAnalyticsInterop.xcscheme new file mode 100644 index 0000000..8f97d91 --- /dev/null +++ b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseAnalyticsInterop.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseAuth.xcscheme b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseAuth.xcscheme new file mode 100644 index 0000000..4e571de --- /dev/null +++ b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseAuth.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseAuthInterop.xcscheme b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseAuthInterop.xcscheme new file mode 100644 index 0000000..7202e6b --- /dev/null +++ b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseAuthInterop.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseCore.xcscheme b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseCore.xcscheme new file mode 100644 index 0000000..a4c140c --- /dev/null +++ b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseCore.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseCoreDiagnostics.xcscheme b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseCoreDiagnostics.xcscheme new file mode 100644 index 0000000..21f827d --- /dev/null +++ b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseCoreDiagnostics.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseCoreDiagnosticsInterop.xcscheme b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseCoreDiagnosticsInterop.xcscheme new file mode 100644 index 0000000..7a77896 --- /dev/null +++ b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseCoreDiagnosticsInterop.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseCrashlytics.xcscheme b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseCrashlytics.xcscheme new file mode 100644 index 0000000..4f33d87 --- /dev/null +++ b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseCrashlytics.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseDatabase.xcscheme b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseDatabase.xcscheme new file mode 100644 index 0000000..0756d3d --- /dev/null +++ b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseDatabase.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseInstallations.xcscheme b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseInstallations.xcscheme new file mode 100644 index 0000000..68d579e --- /dev/null +++ b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseInstallations.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseInstanceID.xcscheme b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseInstanceID.xcscheme new file mode 100644 index 0000000..35d5ec7 --- /dev/null +++ b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/FirebaseInstanceID.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/GTMSessionFetcher.xcscheme b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/GTMSessionFetcher.xcscheme new file mode 100644 index 0000000..4e23ec8 --- /dev/null +++ b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/GTMSessionFetcher.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/GoogleAppMeasurement.xcscheme b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/GoogleAppMeasurement.xcscheme new file mode 100644 index 0000000..3b0614e --- /dev/null +++ b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/GoogleAppMeasurement.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/GoogleDataTransport.xcscheme b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/GoogleDataTransport.xcscheme new file mode 100644 index 0000000..6208c32 --- /dev/null +++ b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/GoogleDataTransport.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/GoogleDataTransportCCTSupport.xcscheme b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/GoogleDataTransportCCTSupport.xcscheme new file mode 100644 index 0000000..968a5eb --- /dev/null +++ b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/GoogleDataTransportCCTSupport.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/GoogleUtilities.xcscheme b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/GoogleUtilities.xcscheme new file mode 100644 index 0000000..3915f21 --- /dev/null +++ b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/GoogleUtilities.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/Kingfisher.xcscheme b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/Kingfisher.xcscheme new file mode 100644 index 0000000..32d098a --- /dev/null +++ b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/Kingfisher.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/Pods-GeekbrainsUI.xcscheme b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/Pods-GeekbrainsUI.xcscheme new file mode 100644 index 0000000..fc36a51 --- /dev/null +++ b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/Pods-GeekbrainsUI.xcscheme @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/PromisesObjC.xcscheme b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/PromisesObjC.xcscheme new file mode 100644 index 0000000..d1487d6 --- /dev/null +++ b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/PromisesObjC.xcscheme @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/Realm.xcscheme b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/Realm.xcscheme new file mode 100644 index 0000000..a7415c6 --- /dev/null +++ b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/Realm.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/RealmSwift.xcscheme b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/RealmSwift.xcscheme new file mode 100644 index 0000000..d7291ca --- /dev/null +++ b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/RealmSwift.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/leveldb-library.xcscheme b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/leveldb-library.xcscheme new file mode 100644 index 0000000..b199804 --- /dev/null +++ b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/leveldb-library.xcscheme @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/nanopb.xcscheme b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/nanopb.xcscheme new file mode 100644 index 0000000..2183889 --- /dev/null +++ b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/nanopb.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/xcschememanagement.plist b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..5ecc494 --- /dev/null +++ b/!main project/Pods/Pods.xcodeproj/xcuserdata/raskin-sa.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,136 @@ + + + + + SchemeUserState + + Alamofire.xcscheme + + isShown + + + Firebase.xcscheme + + isShown + + + FirebaseAnalytics.xcscheme + + isShown + + + FirebaseAnalyticsInterop.xcscheme + + isShown + + + FirebaseAuth.xcscheme + + isShown + + + FirebaseAuthInterop.xcscheme + + isShown + + + FirebaseCore.xcscheme + + isShown + + + FirebaseCoreDiagnostics.xcscheme + + isShown + + + FirebaseCoreDiagnosticsInterop.xcscheme + + isShown + + + FirebaseCrashlytics.xcscheme + + isShown + + + FirebaseDatabase.xcscheme + + isShown + + + FirebaseInstallations.xcscheme + + isShown + + + FirebaseInstanceID.xcscheme + + isShown + + + GTMSessionFetcher.xcscheme + + isShown + + + GoogleAppMeasurement.xcscheme + + isShown + + + GoogleDataTransport.xcscheme + + isShown + + + GoogleDataTransportCCTSupport.xcscheme + + isShown + + + GoogleUtilities.xcscheme + + isShown + + + Kingfisher.xcscheme + + isShown + + + Pods-GeekbrainsUI.xcscheme + + isShown + + + PromisesObjC.xcscheme + + isShown + + + Realm.xcscheme + + isShown + + + RealmSwift.xcscheme + + isShown + + + leveldb-library.xcscheme + + isShown + + + nanopb.xcscheme + + isShown + + + + SuppressBuildableAutocreation + + + diff --git a/!main project/Pods/PromisesObjC/LICENSE b/!main project/Pods/PromisesObjC/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/!main project/Pods/PromisesObjC/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/!main project/Pods/PromisesObjC/README.md b/!main project/Pods/PromisesObjC/README.md new file mode 100644 index 0000000..0eecbb1 --- /dev/null +++ b/!main project/Pods/PromisesObjC/README.md @@ -0,0 +1,60 @@ +[![Apache +License](https://img.shields.io/github/license/google/promises.svg)](LICENSE) +[![Travis](https://api.travis-ci.org/google/promises.svg?branch=master)](https://travis-ci.org/google/promises) +[![Gitter Chat](https://badges.gitter.im/google/promises.svg)](https://gitter.im/google/promises) + +![Platforms](https://img.shields.io/badge/platforms-macOS%20%7C%20iOS%20%7C%20tvOS%20%7C%20watchOS-blue.svg?longCache=true&style=flat) +![Languages](https://img.shields.io/badge/languages-Swift%20%7C%20ObjC-orange.svg?longCache=true&style=flat) +![Package Managers](https://img.shields.io/badge/supports-Bazel%20%7C%20SwiftPM%20%7C%20CocoaPods%20%7C%20Carthage-yellow.svg?longCache=true&style=flat) + +# Promises + +Promises is a modern framework that provides a synchronization construct for +Objective-C and Swift to facilitate writing asynchronous code. + +* [Introduction](g3doc/index.md) + * [The problem with async + code](g3doc/index.md#the-problem-with-async-code) + * [Promises to the rescue](g3doc/index.md#promises-to-the-rescue) + * [What is a promise?](g3doc/index.md#what-is-a-promise) +* [Framework](g3doc/index.md#framework) + * [Features](g3doc/index.md#features) + * [Benchmark](g3doc/index.md#benchmark) +* [Getting started](g3doc/index.md#getting-started) + * [Add dependency](g3doc/index.md#add-dependency) + * [Import](g3doc/index.md#import) + * [Adopt](g3doc/index.md#adopt) +* [Basics](g3doc/index.md#basics) + * [Creating promises](g3doc/index.md#creating-promises) + * [Async](g3doc/index.md#async) + * [Do](g3doc/index.md#do) + * [Pending](g3doc/index.md#pending) + * [Resolved](g3doc/index.md#create-a-resolved-promise) + * [Observing fulfillment](g3doc/index.md#observing-fulfillment) + * [Then](g3doc/index.md#then) + * [Observing rejection](g3doc/index.md#observing-rejection) + * [Catch](g3doc/index.md#catch) +* [Extensions](g3doc/index.md#extensions) + * [All](g3doc/index.md#all) + * [Always](g3doc/index.md#always) + * [Any](g3doc/index.md#any) + * [Await](g3doc/index.md#await) + * [Delay](g3doc/index.md#delay) + * [Race](g3doc/index.md#race) + * [Recover](g3doc/index.md#recover) + * [Reduce](g3doc/index.md#reduce) + * [Retry](g3doc/index.md#retry) + * [Timeout](g3doc/index.md#timeout) + * [Validate](g3doc/index.md#validate) + * [Wrap](g3doc/index.md#wrap) +* [Advanced topics](g3doc/index.md#advanced-topics) + * [Default dispatch queue](g3doc/index.md#default-dispatch-queue) + * [Ownership and retain + cycles](g3doc/index.md#ownership-and-retain-cycles) + * [Testing](g3doc/index.md#testing) + * [Objective-C <-> Swift + interoperability](g3doc/index.md#objective-c---swift-interoperability) + * [Dot-syntax in Objective-C](g3doc/index.md#dot-syntax-in-objective-c) +* [Anti-patterns](g3doc/index.md#anti-patterns) + * [Broken chain](g3doc/index.md#broken-chain) + * [Nested promises](g3doc/index.md#nested-promises) diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+All.m b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+All.m new file mode 100644 index 0000000..c21f30e --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+All.m @@ -0,0 +1,86 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise+All.h" + +#import "FBLPromise+Async.h" +#import "FBLPromisePrivate.h" + +@implementation FBLPromise (AllAdditions) + ++ (FBLPromise *)all:(NSArray *)promises { + return [self onQueue:self.defaultDispatchQueue all:promises]; +} + ++ (FBLPromise *)onQueue:(dispatch_queue_t)queue all:(NSArray *)allPromises { + NSParameterAssert(queue); + NSParameterAssert(allPromises); + + if (allPromises.count == 0) { + return [[FBLPromise alloc] initWithResolution:@[]]; + } + NSMutableArray *promises = [allPromises mutableCopy]; + return [FBLPromise + onQueue:queue + async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { + for (NSUInteger i = 0; i < promises.count; ++i) { + id promise = promises[i]; + if ([promise isKindOfClass:self]) { + continue; + } else if ([promise isKindOfClass:[NSError class]]) { + reject(promise); + return; + } else { + [promises replaceObjectAtIndex:i + withObject:[[FBLPromise alloc] initWithResolution:promise]]; + } + } + for (FBLPromise *promise in promises) { + [promise observeOnQueue:queue + fulfill:^(id __unused _) { + // Wait until all are fulfilled. + for (FBLPromise *promise in promises) { + if (!promise.isFulfilled) { + return; + } + } + // If called multiple times, only the first one affects the result. + fulfill([promises valueForKey:NSStringFromSelector(@selector(value))]); + } + reject:^(NSError *error) { + reject(error); + }]; + } + }]; +} + +@end + +@implementation FBLPromise (DotSyntax_AllAdditions) + ++ (FBLPromise * (^)(NSArray *))all { + return ^(NSArray *promises) { + return [self all:promises]; + }; +} + ++ (FBLPromise * (^)(dispatch_queue_t, NSArray *))allOn { + return ^(dispatch_queue_t queue, NSArray *promises) { + return [self onQueue:queue all:promises]; + }; +} + +@end diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Always.m b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Always.m new file mode 100644 index 0000000..6927442 --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Always.m @@ -0,0 +1,58 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise+Always.h" + +#import "FBLPromisePrivate.h" + +@implementation FBLPromise (AlwaysAdditions) + +- (FBLPromise *)always:(FBLPromiseAlwaysWorkBlock)work { + return [self onQueue:FBLPromise.defaultDispatchQueue always:work]; +} + +- (FBLPromise *)onQueue:(dispatch_queue_t)queue always:(FBLPromiseAlwaysWorkBlock)work { + NSParameterAssert(queue); + NSParameterAssert(work); + + return [self chainOnQueue:queue + chainedFulfill:^id(id value) { + work(); + return value; + } + chainedReject:^id(NSError *error) { + work(); + return error; + }]; +} + +@end + +@implementation FBLPromise (DotSyntax_AlwaysAdditions) + +- (FBLPromise * (^)(FBLPromiseAlwaysWorkBlock))always { + return ^(FBLPromiseAlwaysWorkBlock work) { + return [self always:work]; + }; +} + +- (FBLPromise * (^)(dispatch_queue_t, FBLPromiseAlwaysWorkBlock))alwaysOn { + return ^(dispatch_queue_t queue, FBLPromiseAlwaysWorkBlock work) { + return [self onQueue:queue always:work]; + }; +} + +@end diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Any.m b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Any.m new file mode 100644 index 0000000..e101c98 --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Any.m @@ -0,0 +1,112 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise+Any.h" + +#import "FBLPromise+Async.h" +#import "FBLPromisePrivate.h" + +static NSArray *FBLPromiseCombineValuesAndErrors(NSArray *promises) { + NSMutableArray *combinedValuesAndErrors = [[NSMutableArray alloc] init]; + for (FBLPromise *promise in promises) { + if (promise.isFulfilled) { + [combinedValuesAndErrors addObject:promise.value ?: [NSNull null]]; + continue; + } + if (promise.isRejected) { + [combinedValuesAndErrors addObject:promise.error]; + continue; + } + assert(!promise.isPending); + }; + return combinedValuesAndErrors; +} + +@implementation FBLPromise (AnyAdditions) + ++ (FBLPromise *)any:(NSArray *)promises { + return [self onQueue:FBLPromise.defaultDispatchQueue any:promises]; +} + ++ (FBLPromise *)onQueue:(dispatch_queue_t)queue any:(NSArray *)anyPromises { + NSParameterAssert(queue); + NSParameterAssert(anyPromises); + + if (anyPromises.count == 0) { + return [[FBLPromise alloc] initWithResolution:@[]]; + } + NSMutableArray *promises = [anyPromises mutableCopy]; + return [FBLPromise + onQueue:queue + async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { + for (NSUInteger i = 0; i < promises.count; ++i) { + id promise = promises[i]; + if ([promise isKindOfClass:self]) { + continue; + } else { + [promises replaceObjectAtIndex:i + withObject:[[FBLPromise alloc] initWithResolution:promise]]; + } + } + for (FBLPromise *promise in promises) { + [promise observeOnQueue:queue + fulfill:^(id __unused _) { + // Wait until all are resolved. + for (FBLPromise *promise in promises) { + if (promise.isPending) { + return; + } + } + // If called multiple times, only the first one affects the result. + fulfill(FBLPromiseCombineValuesAndErrors(promises)); + } + reject:^(NSError *error) { + BOOL atLeastOneIsFulfilled = NO; + for (FBLPromise *promise in promises) { + if (promise.isPending) { + return; + } + if (promise.isFulfilled) { + atLeastOneIsFulfilled = YES; + } + } + if (atLeastOneIsFulfilled) { + fulfill(FBLPromiseCombineValuesAndErrors(promises)); + } else { + reject(error); + } + }]; + } + }]; +} + +@end + +@implementation FBLPromise (DotSyntax_AnyAdditions) + ++ (FBLPromise * (^)(NSArray *))any { + return ^(NSArray *promises) { + return [self any:promises]; + }; +} + ++ (FBLPromise * (^)(dispatch_queue_t, NSArray *))anyOn { + return ^(dispatch_queue_t queue, NSArray *promises) { + return [self onQueue:queue any:promises]; + }; +} + +@end diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Async.m b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Async.m new file mode 100644 index 0000000..249158c --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Async.m @@ -0,0 +1,70 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise+Async.h" + +#import "FBLPromisePrivate.h" + +@implementation FBLPromise (AsyncAdditions) + ++ (instancetype)async:(FBLPromiseAsyncWorkBlock)work { + return [self onQueue:self.defaultDispatchQueue async:work]; +} + ++ (instancetype)onQueue:(dispatch_queue_t)queue async:(FBLPromiseAsyncWorkBlock)work { + NSParameterAssert(queue); + NSParameterAssert(work); + + FBLPromise *promise = [[FBLPromise alloc] initPending]; + dispatch_group_async(FBLPromise.dispatchGroup, queue, ^{ + work( + ^(id __nullable value) { + if ([value isKindOfClass:[FBLPromise class]]) { + [(FBLPromise *)value observeOnQueue:queue + fulfill:^(id __nullable value) { + [promise fulfill:value]; + } + reject:^(NSError *error) { + [promise reject:error]; + }]; + } else { + [promise fulfill:value]; + } + }, + ^(NSError *error) { + [promise reject:error]; + }); + }); + return promise; +} + +@end + +@implementation FBLPromise (DotSyntax_AsyncAdditions) + ++ (FBLPromise* (^)(FBLPromiseAsyncWorkBlock))async { + return ^(FBLPromiseAsyncWorkBlock work) { + return [self async:work]; + }; +} + ++ (FBLPromise* (^)(dispatch_queue_t, FBLPromiseAsyncWorkBlock))asyncOn { + return ^(dispatch_queue_t queue, FBLPromiseAsyncWorkBlock work) { + return [self onQueue:queue async:work]; + }; +} + +@end diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Await.m b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Await.m new file mode 100644 index 0000000..ea3b87a --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Await.m @@ -0,0 +1,48 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise+Await.h" + +#import "FBLPromisePrivate.h" + +id __nullable FBLPromiseAwait(FBLPromise *promise, NSError **outError) { + assert(promise); + + static dispatch_once_t onceToken; + static dispatch_queue_t queue; + dispatch_once(&onceToken, ^{ + queue = dispatch_queue_create("com.google.FBLPromises.Await", DISPATCH_QUEUE_CONCURRENT); + }); + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + id __block resolution; + NSError __block *blockError; + [promise chainOnQueue:queue + chainedFulfill:^id(id value) { + resolution = value; + dispatch_semaphore_signal(semaphore); + return value; + } + chainedReject:^id(NSError *error) { + blockError = error; + dispatch_semaphore_signal(semaphore); + return error; + }]; + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); + if (outError) { + *outError = blockError; + } + return resolution; +} diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Catch.m b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Catch.m new file mode 100644 index 0000000..25e8ce6 --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Catch.m @@ -0,0 +1,55 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise+Catch.h" + +#import "FBLPromisePrivate.h" + +@implementation FBLPromise (CatchAdditions) + +- (FBLPromise *)catch:(FBLPromiseCatchWorkBlock)reject { + return [self onQueue:FBLPromise.defaultDispatchQueue catch:reject]; +} + +- (FBLPromise *)onQueue:(dispatch_queue_t)queue catch:(FBLPromiseCatchWorkBlock)reject { + NSParameterAssert(queue); + NSParameterAssert(reject); + + return [self chainOnQueue:queue + chainedFulfill:nil + chainedReject:^id(NSError *error) { + reject(error); + return error; + }]; +} + +@end + +@implementation FBLPromise (DotSyntax_CatchAdditions) + +- (FBLPromise* (^)(FBLPromiseCatchWorkBlock))catch { + return ^(FBLPromiseCatchWorkBlock catch) { + return [self catch:catch]; + }; +} + +- (FBLPromise* (^)(dispatch_queue_t, FBLPromiseCatchWorkBlock))catchOn { + return ^(dispatch_queue_t queue, FBLPromiseCatchWorkBlock catch) { + return [self onQueue:queue catch:catch]; + }; +} + +@end diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Delay.m b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Delay.m new file mode 100644 index 0000000..ce94c33 --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Delay.m @@ -0,0 +1,59 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise+Delay.h" + +#import "FBLPromisePrivate.h" + +@implementation FBLPromise (DelayAdditions) + +- (FBLPromise *)delay:(NSTimeInterval)interval { + return [self onQueue:FBLPromise.defaultDispatchQueue delay:interval]; +} + +- (FBLPromise *)onQueue:(dispatch_queue_t)queue delay:(NSTimeInterval)interval { + NSParameterAssert(queue); + + FBLPromise *promise = [[FBLPromise alloc] initPending]; + [self observeOnQueue:queue + fulfill:^(id __nullable value) { + dispatch_after(dispatch_time(0, (int64_t)(interval * NSEC_PER_SEC)), queue, ^{ + [promise fulfill:value]; + }); + } + reject:^(NSError *error) { + [promise reject:error]; + }]; + return promise; +} + +@end + +@implementation FBLPromise (DotSyntax_DelayAdditions) + +- (FBLPromise * (^)(NSTimeInterval))delay { + return ^(NSTimeInterval interval) { + return [self delay:interval]; + }; +} + +- (FBLPromise * (^)(dispatch_queue_t, NSTimeInterval))delayOn { + return ^(dispatch_queue_t queue, NSTimeInterval interval) { + return [self onQueue:queue delay:interval]; + }; +} + +@end diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Do.m b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Do.m new file mode 100644 index 0000000..eb7e10d --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Do.m @@ -0,0 +1,59 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise+Do.h" + +#import "FBLPromisePrivate.h" + +@implementation FBLPromise (DoAdditions) + ++ (instancetype)do:(FBLPromiseDoWorkBlock)work { + return [self onQueue:self.defaultDispatchQueue do:work]; +} + ++ (instancetype)onQueue:(dispatch_queue_t)queue do:(FBLPromiseDoWorkBlock)work { + NSParameterAssert(queue); + NSParameterAssert(work); + + FBLPromise *promise = [[FBLPromise alloc] initPending]; + dispatch_group_async(FBLPromise.dispatchGroup, queue, ^{ + id value = work(); + if ([value isKindOfClass:[FBLPromise class]]) { + [(FBLPromise *)value observeOnQueue:queue + fulfill:^(id __nullable value) { + [promise fulfill:value]; + } + reject:^(NSError *error) { + [promise reject:error]; + }]; + } else { + [promise fulfill:value]; + } + }); + return promise; +} + +@end + +@implementation FBLPromise (DotSyntax_DoAdditions) + ++ (FBLPromise* (^)(dispatch_queue_t, FBLPromiseDoWorkBlock))doOn { + return ^(dispatch_queue_t queue, FBLPromiseDoWorkBlock work) { + return [self onQueue:queue do:work]; + }; +} + +@end diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Race.m b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Race.m new file mode 100644 index 0000000..b5bd9f1 --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Race.m @@ -0,0 +1,65 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise+Race.h" + +#import "FBLPromise+Async.h" +#import "FBLPromisePrivate.h" + +@implementation FBLPromise (RaceAdditions) + ++ (instancetype)race:(NSArray *)promises { + return [self onQueue:self.defaultDispatchQueue race:promises]; +} + ++ (instancetype)onQueue:(dispatch_queue_t)queue race:(NSArray *)racePromises { + NSParameterAssert(queue); + NSAssert(racePromises.count > 0, @"No promises to observe"); + + NSArray *promises = [racePromises copy]; + return [FBLPromise onQueue:queue + async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { + for (id promise in promises) { + if (![promise isKindOfClass:self]) { + fulfill(promise); + return; + } + } + // Subscribe all, but only the first one to resolve will change + // the resulting promise's state. + for (FBLPromise *promise in promises) { + [promise observeOnQueue:queue fulfill:fulfill reject:reject]; + } + }]; +} + +@end + +@implementation FBLPromise (DotSyntax_RaceAdditions) + ++ (FBLPromise * (^)(NSArray *))race { + return ^(NSArray *promises) { + return [self race:promises]; + }; +} + ++ (FBLPromise * (^)(dispatch_queue_t, NSArray *))raceOn { + return ^(dispatch_queue_t queue, NSArray *promises) { + return [self onQueue:queue race:promises]; + }; +} + +@end diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Recover.m b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Recover.m new file mode 100644 index 0000000..0c9326a --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Recover.m @@ -0,0 +1,54 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise+Recover.h" + +#import "FBLPromisePrivate.h" + +@implementation FBLPromise (RecoverAdditions) + +- (FBLPromise *)recover:(FBLPromiseRecoverWorkBlock)recovery { + return [self onQueue:FBLPromise.defaultDispatchQueue recover:recovery]; +} + +- (FBLPromise *)onQueue:(dispatch_queue_t)queue recover:(FBLPromiseRecoverWorkBlock)recovery { + NSParameterAssert(queue); + NSParameterAssert(recovery); + + return [self chainOnQueue:queue + chainedFulfill:nil + chainedReject:^id(NSError *error) { + return recovery(error); + }]; +} + +@end + +@implementation FBLPromise (DotSyntax_RecoverAdditions) + +- (FBLPromise * (^)(FBLPromiseRecoverWorkBlock))recover { + return ^(FBLPromiseRecoverWorkBlock recovery) { + return [self recover:recovery]; + }; +} + +- (FBLPromise * (^)(dispatch_queue_t, FBLPromiseRecoverWorkBlock))recoverOn { + return ^(dispatch_queue_t queue, FBLPromiseRecoverWorkBlock recovery) { + return [self onQueue:queue recover:recovery]; + }; +} + +@end diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Reduce.m b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Reduce.m new file mode 100644 index 0000000..1f3fc50 --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Reduce.m @@ -0,0 +1,61 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise+Reduce.h" + +#import "FBLPromisePrivate.h" + +@implementation FBLPromise (ReduceAdditions) + +- (FBLPromise *)reduce:(NSArray *)items combine:(FBLPromiseReducerBlock)reducer { + return [self onQueue:FBLPromise.defaultDispatchQueue reduce:items combine:reducer]; +} + +- (FBLPromise *)onQueue:(dispatch_queue_t)queue + reduce:(NSArray *)items + combine:(FBLPromiseReducerBlock)reducer { + NSParameterAssert(queue); + NSParameterAssert(items); + NSParameterAssert(reducer); + + FBLPromise *promise = self; + for (id item in items) { + promise = [promise chainOnQueue:queue + chainedFulfill:^id(id value) { + return reducer(value, item); + } + chainedReject:nil]; + } + return promise; +} + +@end + +@implementation FBLPromise (DotSyntax_ReduceAdditions) + +- (FBLPromise * (^)(NSArray *, FBLPromiseReducerBlock))reduce { + return ^(NSArray *items, FBLPromiseReducerBlock reducer) { + return [self reduce:items combine:reducer]; + }; +} + +- (FBLPromise * (^)(dispatch_queue_t, NSArray *, FBLPromiseReducerBlock))reduceOn { + return ^(dispatch_queue_t queue, NSArray *items, FBLPromiseReducerBlock reducer) { + return [self onQueue:queue reduce:items combine:reducer]; + }; +} + +@end diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Retry.m b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Retry.m new file mode 100644 index 0000000..37c5576 --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Retry.m @@ -0,0 +1,128 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise+Retry.h" + +#import "FBLPromisePrivate.h" + +NSInteger const FBLPromiseRetryDefaultAttemptsCount = 1; +NSTimeInterval const FBLPromiseRetryDefaultDelayInterval = 1.0; + +static void FBLPromiseRetryAttempt(FBLPromise *promise, dispatch_queue_t queue, NSInteger count, + NSTimeInterval interval, FBLPromiseRetryPredicateBlock predicate, + FBLPromiseRetryWorkBlock work) { + __auto_type retrier = ^(id __nullable value) { + if ([value isKindOfClass:[NSError class]]) { + if (count <= 0 || (predicate && !predicate(count, value))) { + [promise reject:value]; + } else { + dispatch_after(dispatch_time(0, (int64_t)(interval * NSEC_PER_SEC)), queue, ^{ + FBLPromiseRetryAttempt(promise, queue, count - 1, interval, predicate, work); + }); + } + } else { + [promise fulfill:value]; + } + }; + id value = work(); + if ([value isKindOfClass:[FBLPromise class]]) { + [(FBLPromise *)value observeOnQueue:queue fulfill:retrier reject:retrier]; + } else { + retrier(value); + } +} + +@implementation FBLPromise (RetryAdditions) + ++ (FBLPromise *)retry:(FBLPromiseRetryWorkBlock)work { + return [self onQueue:FBLPromise.defaultDispatchQueue retry:work]; +} + ++ (FBLPromise *)onQueue:(dispatch_queue_t)queue retry:(FBLPromiseRetryWorkBlock)work { + return [self onQueue:queue attempts:FBLPromiseRetryDefaultAttemptsCount retry:work]; +} + ++ (FBLPromise *)attempts:(NSInteger)count retry:(FBLPromiseRetryWorkBlock)work { + return [self onQueue:FBLPromise.defaultDispatchQueue attempts:count retry:work]; +} + ++ (FBLPromise *)onQueue:(dispatch_queue_t)queue + attempts:(NSInteger)count + retry:(FBLPromiseRetryWorkBlock)work { + return [self onQueue:queue + attempts:count + delay:FBLPromiseRetryDefaultDelayInterval + condition:nil + retry:work]; +} + ++ (FBLPromise *)attempts:(NSInteger)count + delay:(NSTimeInterval)interval + condition:(nullable FBLPromiseRetryPredicateBlock)predicate + retry:(FBLPromiseRetryWorkBlock)work { + return [self onQueue:FBLPromise.defaultDispatchQueue + attempts:count + delay:interval + condition:predicate + retry:work]; +} + ++ (FBLPromise *)onQueue:(dispatch_queue_t)queue + attempts:(NSInteger)count + delay:(NSTimeInterval)interval + condition:(nullable FBLPromiseRetryPredicateBlock)predicate + retry:(FBLPromiseRetryWorkBlock)work { + NSParameterAssert(queue); + NSParameterAssert(work); + + FBLPromise *promise = [[FBLPromise alloc] initPending]; + FBLPromiseRetryAttempt(promise, queue, count, interval, predicate, work); + return promise; +} + +@end + +@implementation FBLPromise (DotSyntax_RetryAdditions) + ++ (FBLPromise * (^)(FBLPromiseRetryWorkBlock))retry { + return ^id(FBLPromiseRetryWorkBlock work) { + return [self retry:work]; + }; +} + ++ (FBLPromise * (^)(dispatch_queue_t, FBLPromiseRetryWorkBlock))retryOn { + return ^id(dispatch_queue_t queue, FBLPromiseRetryWorkBlock work) { + return [self onQueue:queue retry:work]; + }; +} + ++ (FBLPromise * (^)(NSInteger, NSTimeInterval, FBLPromiseRetryPredicateBlock, + FBLPromiseRetryWorkBlock))retryAgain { + return ^id(NSInteger count, NSTimeInterval interval, FBLPromiseRetryPredicateBlock predicate, + FBLPromiseRetryWorkBlock work) { + return [self attempts:count delay:interval condition:predicate retry:work]; + }; +} + ++ (FBLPromise * (^)(dispatch_queue_t, NSInteger, NSTimeInterval, FBLPromiseRetryPredicateBlock, + FBLPromiseRetryWorkBlock))retryAgainOn { + return ^id(dispatch_queue_t queue, NSInteger count, NSTimeInterval interval, + FBLPromiseRetryPredicateBlock predicate, FBLPromiseRetryWorkBlock work) { + return [self onQueue:queue attempts:count delay:interval condition:predicate retry:work]; + }; +} + +@end diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Testing.m b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Testing.m new file mode 100644 index 0000000..27e8e6c --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Testing.m @@ -0,0 +1,56 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise+Testing.h" + +BOOL FBLWaitForPromisesWithTimeout(NSTimeInterval timeout) { + BOOL isTimedOut = NO; + NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeout]; + static NSTimeInterval const minimalTimeout = 0.01; + static int64_t const minimalTimeToWait = (int64_t)(minimalTimeout * NSEC_PER_SEC); + dispatch_time_t waitTime = dispatch_time(DISPATCH_TIME_NOW, minimalTimeToWait); + dispatch_group_t dispatchGroup = FBLPromise.dispatchGroup; + NSRunLoop *runLoop = NSRunLoop.currentRunLoop; + while (dispatch_group_wait(dispatchGroup, waitTime)) { + isTimedOut = timeoutDate.timeIntervalSinceNow < 0.0; + if (isTimedOut) { + break; + } + [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:minimalTimeout]]; + } + return !isTimedOut; +} + +@implementation FBLPromise (TestingAdditions) + +// These properties are implemented in the FBLPromise class itself. +@dynamic isPending; +@dynamic isFulfilled; +@dynamic isRejected; +@dynamic pendingObjects; +@dynamic value; +@dynamic error; + ++ (dispatch_group_t)dispatchGroup { + static dispatch_group_t gDispatchGroup; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + gDispatchGroup = dispatch_group_create(); + }); + return gDispatchGroup; +} + +@end diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Then.m b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Then.m new file mode 100644 index 0000000..ab03bd1 --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Then.m @@ -0,0 +1,50 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise+Then.h" + +#import "FBLPromisePrivate.h" + +@implementation FBLPromise (ThenAdditions) + +- (FBLPromise *)then:(FBLPromiseThenWorkBlock)work { + return [self onQueue:FBLPromise.defaultDispatchQueue then:work]; +} + +- (FBLPromise *)onQueue:(dispatch_queue_t)queue then:(FBLPromiseThenWorkBlock)work { + NSParameterAssert(queue); + NSParameterAssert(work); + + return [self chainOnQueue:queue chainedFulfill:work chainedReject:nil]; +} + +@end + +@implementation FBLPromise (DotSyntax_ThenAdditions) + +- (FBLPromise* (^)(FBLPromiseThenWorkBlock))then { + return ^(FBLPromiseThenWorkBlock work) { + return [self then:work]; + }; +} + +- (FBLPromise* (^)(dispatch_queue_t, FBLPromiseThenWorkBlock))thenOn { + return ^(dispatch_queue_t queue, FBLPromiseThenWorkBlock work) { + return [self onQueue:queue then:work]; + }; +} + +@end diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Timeout.m b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Timeout.m new file mode 100644 index 0000000..a2252e6 --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Timeout.m @@ -0,0 +1,64 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise+Timeout.h" + +#import "FBLPromisePrivate.h" + +@implementation FBLPromise (TimeoutAdditions) + +- (FBLPromise *)timeout:(NSTimeInterval)interval { + return [self onQueue:FBLPromise.defaultDispatchQueue timeout:interval]; +} + +- (FBLPromise *)onQueue:(dispatch_queue_t)queue timeout:(NSTimeInterval)interval { + NSParameterAssert(queue); + + FBLPromise *promise = [[FBLPromise alloc] initPending]; + [self observeOnQueue:queue + fulfill:^(id __nullable value) { + [promise fulfill:value]; + } + reject:^(NSError *error) { + [promise reject:error]; + }]; + typeof(self) __weak weakPromise = promise; + dispatch_after(dispatch_time(0, (int64_t)(interval * NSEC_PER_SEC)), queue, ^{ + NSError *timedOutError = [[NSError alloc] initWithDomain:FBLPromiseErrorDomain + code:FBLPromiseErrorCodeTimedOut + userInfo:nil]; + [weakPromise reject:timedOutError]; + }); + return promise; +} + +@end + +@implementation FBLPromise (DotSyntax_TimeoutAdditions) + +- (FBLPromise* (^)(NSTimeInterval))timeout { + return ^(NSTimeInterval interval) { + return [self timeout:interval]; + }; +} + +- (FBLPromise* (^)(dispatch_queue_t, NSTimeInterval))timeoutOn { + return ^(dispatch_queue_t queue, NSTimeInterval interval) { + return [self onQueue:queue timeout:interval]; + }; +} + +@end diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Validate.m b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Validate.m new file mode 100644 index 0000000..1e21e81 --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Validate.m @@ -0,0 +1,56 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise+Validate.h" + +#import "FBLPromisePrivate.h" + +@implementation FBLPromise (ValidateAdditions) + +- (FBLPromise*)validate:(FBLPromiseValidateWorkBlock)predicate { + return [self onQueue:FBLPromise.defaultDispatchQueue validate:predicate]; +} + +- (FBLPromise*)onQueue:(dispatch_queue_t)queue validate:(FBLPromiseValidateWorkBlock)predicate { + NSParameterAssert(queue); + NSParameterAssert(predicate); + + FBLPromiseChainedFulfillBlock chainedFulfill = ^id(id value) { + return predicate(value) ? value : + [[NSError alloc] initWithDomain:FBLPromiseErrorDomain + code:FBLPromiseErrorCodeValidationFailure + userInfo:nil]; + }; + return [self chainOnQueue:queue chainedFulfill:chainedFulfill chainedReject:nil]; +} + +@end + +@implementation FBLPromise (DotSyntax_ValidateAdditions) + +- (FBLPromise* (^)(FBLPromiseValidateWorkBlock))validate { + return ^(FBLPromiseValidateWorkBlock predicate) { + return [self validate:predicate]; + }; +} + +- (FBLPromise* (^)(dispatch_queue_t, FBLPromiseValidateWorkBlock))validateOn { + return ^(dispatch_queue_t queue, FBLPromiseValidateWorkBlock predicate) { + return [self onQueue:queue validate:predicate]; + }; +} + +@end diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Wrap.m b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Wrap.m new file mode 100644 index 0000000..3d3341e --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Wrap.m @@ -0,0 +1,420 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise+Wrap.h" + +#import "FBLPromise+Async.h" + +@implementation FBLPromise (WrapAdditions) + ++ (instancetype)wrapCompletion:(void (^)(FBLPromiseCompletion))work { + return [self onQueue:self.defaultDispatchQueue wrapCompletion:work]; +} + ++ (instancetype)onQueue:(dispatch_queue_t)queue + wrapCompletion:(void (^)(FBLPromiseCompletion))work { + NSParameterAssert(queue); + NSParameterAssert(work); + + return [self onQueue:queue + async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock __unused _) { + work(^{ + fulfill(nil); + }); + }]; +} + ++ (instancetype)wrapObjectCompletion:(void (^)(FBLPromiseObjectCompletion))work { + return [self onQueue:self.defaultDispatchQueue wrapObjectCompletion:work]; +} + ++ (instancetype)onQueue:(dispatch_queue_t)queue + wrapObjectCompletion:(void (^)(FBLPromiseObjectCompletion))work { + NSParameterAssert(queue); + NSParameterAssert(work); + + return [self onQueue:queue + async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock __unused _) { + work(^(id __nullable value) { + fulfill(value); + }); + }]; +} + ++ (instancetype)wrapErrorCompletion:(void (^)(FBLPromiseErrorCompletion))work { + return [self onQueue:self.defaultDispatchQueue wrapErrorCompletion:work]; +} + ++ (instancetype)onQueue:(dispatch_queue_t)queue + wrapErrorCompletion:(void (^)(FBLPromiseErrorCompletion))work { + NSParameterAssert(queue); + NSParameterAssert(work); + + return [self onQueue:queue + async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { + work(^(NSError *__nullable error) { + if (error) { + reject(error); + } else { + fulfill(nil); + } + }); + }]; +} + ++ (instancetype)wrapObjectOrErrorCompletion:(void (^)(FBLPromiseObjectOrErrorCompletion))work { + return [self onQueue:self.defaultDispatchQueue wrapObjectOrErrorCompletion:work]; +} + ++ (instancetype)onQueue:(dispatch_queue_t)queue + wrapObjectOrErrorCompletion:(void (^)(FBLPromiseObjectOrErrorCompletion))work { + NSParameterAssert(queue); + NSParameterAssert(work); + + return [self onQueue:queue + async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { + work(^(id __nullable value, NSError *__nullable error) { + if (error) { + reject(error); + } else { + fulfill(value); + } + }); + }]; +} + ++ (instancetype)wrapErrorOrObjectCompletion:(void (^)(FBLPromiseErrorOrObjectCompletion))work { + return [self onQueue:self.defaultDispatchQueue wrapErrorOrObjectCompletion:work]; +} + ++ (instancetype)onQueue:(dispatch_queue_t)queue + wrapErrorOrObjectCompletion:(void (^)(FBLPromiseErrorOrObjectCompletion))work { + NSParameterAssert(queue); + NSParameterAssert(work); + + return [self onQueue:queue + async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { + work(^(NSError *__nullable error, id __nullable value) { + if (error) { + reject(error); + } else { + fulfill(value); + } + }); + }]; +} + ++ (FBLPromise *)wrap2ObjectsOrErrorCompletion: + (void (^)(FBLPromise2ObjectsOrErrorCompletion))work { + return [self onQueue:self.defaultDispatchQueue wrap2ObjectsOrErrorCompletion:work]; +} + ++ (FBLPromise *)onQueue:(dispatch_queue_t)queue + wrap2ObjectsOrErrorCompletion:(void (^)(FBLPromise2ObjectsOrErrorCompletion))work { + NSParameterAssert(queue); + NSParameterAssert(work); + + return [self onQueue:queue + async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { + work(^(id __nullable value1, id __nullable value2, NSError *__nullable error) { + if (error) { + reject(error); + } else { + fulfill(@[ value1, value2 ]); + } + }); + }]; +} + ++ (FBLPromise *)wrapBoolCompletion:(void (^)(FBLPromiseBoolCompletion))work { + return [self onQueue:self.defaultDispatchQueue wrapBoolCompletion:work]; +} + ++ (FBLPromise *)onQueue:(dispatch_queue_t)queue + wrapBoolCompletion:(void (^)(FBLPromiseBoolCompletion))work { + NSParameterAssert(queue); + NSParameterAssert(work); + + return [self onQueue:queue + async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock __unused _) { + work(^(BOOL value) { + fulfill(@(value)); + }); + }]; +} + ++ (FBLPromise *)wrapBoolOrErrorCompletion: + (void (^)(FBLPromiseBoolOrErrorCompletion))work { + return [self onQueue:self.defaultDispatchQueue wrapBoolOrErrorCompletion:work]; +} + ++ (FBLPromise *)onQueue:(dispatch_queue_t)queue + wrapBoolOrErrorCompletion:(void (^)(FBLPromiseBoolOrErrorCompletion))work { + NSParameterAssert(queue); + NSParameterAssert(work); + + return [self onQueue:queue + async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { + work(^(BOOL value, NSError *__nullable error) { + if (error) { + reject(error); + } else { + fulfill(@(value)); + } + }); + }]; +} + ++ (FBLPromise *)wrapIntegerCompletion:(void (^)(FBLPromiseIntegerCompletion))work { + return [self onQueue:self.defaultDispatchQueue wrapIntegerCompletion:work]; +} + ++ (FBLPromise *)onQueue:(dispatch_queue_t)queue + wrapIntegerCompletion:(void (^)(FBLPromiseIntegerCompletion))work { + NSParameterAssert(queue); + NSParameterAssert(work); + + return [self onQueue:queue + async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock __unused _) { + work(^(NSInteger value) { + fulfill(@(value)); + }); + }]; +} + ++ (FBLPromise *)wrapIntegerOrErrorCompletion: + (void (^)(FBLPromiseIntegerOrErrorCompletion))work { + return [self onQueue:self.defaultDispatchQueue wrapIntegerOrErrorCompletion:work]; +} + ++ (FBLPromise *)onQueue:(dispatch_queue_t)queue + wrapIntegerOrErrorCompletion:(void (^)(FBLPromiseIntegerOrErrorCompletion))work { + NSParameterAssert(queue); + NSParameterAssert(work); + + return [self onQueue:queue + async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { + work(^(NSInteger value, NSError *__nullable error) { + if (error) { + reject(error); + } else { + fulfill(@(value)); + } + }); + }]; +} + ++ (FBLPromise *)wrapDoubleCompletion:(void (^)(FBLPromiseDoubleCompletion))work { + return [self onQueue:self.defaultDispatchQueue wrapDoubleCompletion:work]; +} + ++ (FBLPromise *)onQueue:(dispatch_queue_t)queue + wrapDoubleCompletion:(void (^)(FBLPromiseDoubleCompletion))work { + NSParameterAssert(queue); + NSParameterAssert(work); + + return [self onQueue:(dispatch_queue_t)queue + async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock __unused _) { + work(^(double value) { + fulfill(@(value)); + }); + }]; +} + ++ (FBLPromise *)wrapDoubleOrErrorCompletion: + (void (^)(FBLPromiseDoubleOrErrorCompletion))work { + return [self onQueue:self.defaultDispatchQueue wrapDoubleOrErrorCompletion:work]; +} + ++ (FBLPromise *)onQueue:(dispatch_queue_t)queue + wrapDoubleOrErrorCompletion:(void (^)(FBLPromiseDoubleOrErrorCompletion))work { + NSParameterAssert(queue); + NSParameterAssert(work); + + return [self onQueue:queue + async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { + work(^(double value, NSError *__nullable error) { + if (error) { + reject(error); + } else { + fulfill(@(value)); + } + }); + }]; +} + +@end + +@implementation FBLPromise (DotSyntax_WrapAdditions) + ++ (FBLPromise * (^)(void (^)(FBLPromiseCompletion)))wrapCompletion { + return ^(void (^work)(FBLPromiseCompletion)) { + return [self wrapCompletion:work]; + }; +} + ++ (FBLPromise * (^)(dispatch_queue_t, void (^)(FBLPromiseCompletion)))wrapCompletionOn { + return ^(dispatch_queue_t queue, void (^work)(FBLPromiseCompletion)) { + return [self onQueue:queue wrapCompletion:work]; + }; +} + ++ (FBLPromise * (^)(void (^)(FBLPromiseObjectCompletion)))wrapObjectCompletion { + return ^(void (^work)(FBLPromiseObjectCompletion)) { + return [self wrapObjectCompletion:work]; + }; +} + ++ (FBLPromise * (^)(dispatch_queue_t, void (^)(FBLPromiseObjectCompletion)))wrapObjectCompletionOn { + return ^(dispatch_queue_t queue, void (^work)(FBLPromiseObjectCompletion)) { + return [self onQueue:queue wrapObjectCompletion:work]; + }; +} + ++ (FBLPromise * (^)(void (^)(FBLPromiseErrorCompletion)))wrapErrorCompletion { + return ^(void (^work)(FBLPromiseErrorCompletion)) { + return [self wrapErrorCompletion:work]; + }; +} + ++ (FBLPromise * (^)(dispatch_queue_t, void (^)(FBLPromiseErrorCompletion)))wrapErrorCompletionOn { + return ^(dispatch_queue_t queue, void (^work)(FBLPromiseErrorCompletion)) { + return [self onQueue:queue wrapErrorCompletion:work]; + }; +} + ++ (FBLPromise * (^)(void (^)(FBLPromiseObjectOrErrorCompletion)))wrapObjectOrErrorCompletion { + return ^(void (^work)(FBLPromiseObjectOrErrorCompletion)) { + return [self wrapObjectOrErrorCompletion:work]; + }; +} + ++ (FBLPromise * (^)(dispatch_queue_t, + void (^)(FBLPromiseObjectOrErrorCompletion)))wrapObjectOrErrorCompletionOn { + return ^(dispatch_queue_t queue, void (^work)(FBLPromiseObjectOrErrorCompletion)) { + return [self onQueue:queue wrapObjectOrErrorCompletion:work]; + }; +} + ++ (FBLPromise * (^)(void (^)(FBLPromiseErrorOrObjectCompletion)))wrapErrorOrObjectCompletion { + return ^(void (^work)(FBLPromiseErrorOrObjectCompletion)) { + return [self wrapErrorOrObjectCompletion:work]; + }; +} + ++ (FBLPromise * (^)(dispatch_queue_t, + void (^)(FBLPromiseErrorOrObjectCompletion)))wrapErrorOrObjectCompletionOn { + return ^(dispatch_queue_t queue, void (^work)(FBLPromiseErrorOrObjectCompletion)) { + return [self onQueue:queue wrapErrorOrObjectCompletion:work]; + }; +} + ++ (FBLPromise * (^)(void (^)(FBLPromise2ObjectsOrErrorCompletion))) + wrap2ObjectsOrErrorCompletion { + return ^(void (^work)(FBLPromise2ObjectsOrErrorCompletion)) { + return [self wrap2ObjectsOrErrorCompletion:work]; + }; +} + ++ (FBLPromise * (^)(dispatch_queue_t, void (^)(FBLPromise2ObjectsOrErrorCompletion))) + wrap2ObjectsOrErrorCompletionOn { + return ^(dispatch_queue_t queue, void (^work)(FBLPromise2ObjectsOrErrorCompletion)) { + return [self onQueue:queue wrap2ObjectsOrErrorCompletion:work]; + }; +} + ++ (FBLPromise * (^)(void (^)(FBLPromiseBoolCompletion)))wrapBoolCompletion { + return ^(void (^work)(FBLPromiseBoolCompletion)) { + return [self wrapBoolCompletion:work]; + }; +} + ++ (FBLPromise * (^)(dispatch_queue_t, + void (^)(FBLPromiseBoolCompletion)))wrapBoolCompletionOn { + return ^(dispatch_queue_t queue, void (^work)(FBLPromiseBoolCompletion)) { + return [self onQueue:queue wrapBoolCompletion:work]; + }; +} + ++ (FBLPromise * (^)(void (^)(FBLPromiseBoolOrErrorCompletion))) + wrapBoolOrErrorCompletion { + return ^(void (^work)(FBLPromiseBoolOrErrorCompletion)) { + return [self wrapBoolOrErrorCompletion:work]; + }; +} + ++ (FBLPromise * (^)(dispatch_queue_t, void (^)(FBLPromiseBoolOrErrorCompletion))) + wrapBoolOrErrorCompletionOn { + return ^(dispatch_queue_t queue, void (^work)(FBLPromiseBoolOrErrorCompletion)) { + return [self onQueue:queue wrapBoolOrErrorCompletion:work]; + }; +} + ++ (FBLPromise * (^)(void (^)(FBLPromiseIntegerCompletion)))wrapIntegerCompletion { + return ^(void (^work)(FBLPromiseIntegerCompletion)) { + return [self wrapIntegerCompletion:work]; + }; +} + ++ (FBLPromise * (^)(dispatch_queue_t, + void (^)(FBLPromiseIntegerCompletion)))wrapIntegerCompletionOn { + return ^(dispatch_queue_t queue, void (^work)(FBLPromiseIntegerCompletion)) { + return [self onQueue:queue wrapIntegerCompletion:work]; + }; +} + ++ (FBLPromise * (^)(void (^)(FBLPromiseIntegerOrErrorCompletion))) + wrapIntegerOrErrorCompletion { + return ^(void (^work)(FBLPromiseIntegerOrErrorCompletion)) { + return [self wrapIntegerOrErrorCompletion:work]; + }; +} + ++ (FBLPromise * (^)(dispatch_queue_t, void (^)(FBLPromiseIntegerOrErrorCompletion))) + wrapIntegerOrErrorCompletionOn { + return ^(dispatch_queue_t queue, void (^work)(FBLPromiseIntegerOrErrorCompletion)) { + return [self onQueue:queue wrapIntegerOrErrorCompletion:work]; + }; +} + ++ (FBLPromise * (^)(void (^)(FBLPromiseDoubleCompletion)))wrapDoubleCompletion { + return ^(void (^work)(FBLPromiseDoubleCompletion)) { + return [self wrapDoubleCompletion:work]; + }; +} + ++ (FBLPromise * (^)(dispatch_queue_t, + void (^)(FBLPromiseDoubleCompletion)))wrapDoubleCompletionOn { + return ^(dispatch_queue_t queue, void (^work)(FBLPromiseDoubleCompletion)) { + return [self onQueue:queue wrapDoubleCompletion:work]; + }; +} + ++ (FBLPromise * (^)(void (^)(FBLPromiseDoubleOrErrorCompletion))) + wrapDoubleOrErrorCompletion { + return ^(void (^work)(FBLPromiseDoubleOrErrorCompletion)) { + return [self wrapDoubleOrErrorCompletion:work]; + }; +} + ++ (FBLPromise * (^)(dispatch_queue_t, void (^)(FBLPromiseDoubleOrErrorCompletion))) + wrapDoubleOrErrorCompletionOn { + return ^(dispatch_queue_t queue, void (^work)(FBLPromiseDoubleOrErrorCompletion)) { + return [self onQueue:queue wrapDoubleOrErrorCompletion:work]; + }; +} + +@end diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise.m b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise.m new file mode 100644 index 0000000..0837b04 --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise.m @@ -0,0 +1,297 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromisePrivate.h" + +/** All states a promise can be in. */ +typedef NS_ENUM(NSInteger, FBLPromiseState) { + FBLPromiseStatePending = 0, + FBLPromiseStateFulfilled, + FBLPromiseStateRejected, +}; + +typedef void (^FBLPromiseObserver)(FBLPromiseState state, id __nullable resolution); + +static dispatch_queue_t gFBLPromiseDefaultDispatchQueue; + +@implementation FBLPromise { + /** Current state of the promise. */ + FBLPromiseState _state; + /** + Set of arbitrary objects to keep strongly while the promise is pending. + Becomes nil after the promise has been resolved. + */ + NSMutableSet *__nullable _pendingObjects; + /** + Value to fulfill the promise with. + Can be nil if the promise is still pending, was resolved with nil or after it has been rejected. + */ + id __nullable _value; + /** + Error to reject the promise with. + Can be nil if the promise is still pending or after it has been fulfilled. + */ + NSError *__nullable _error; + /** List of observers to notify when the promise gets resolved. */ + NSMutableArray *_observers; +} + ++ (void)initialize { + if (self == [FBLPromise class]) { + gFBLPromiseDefaultDispatchQueue = dispatch_get_main_queue(); + } +} + ++ (dispatch_queue_t)defaultDispatchQueue { + @synchronized(self) { + return gFBLPromiseDefaultDispatchQueue; + } +} + ++ (void)setDefaultDispatchQueue:(dispatch_queue_t)queue { + NSParameterAssert(queue); + + @synchronized(self) { + gFBLPromiseDefaultDispatchQueue = queue; + } +} + ++ (instancetype)pendingPromise { + return [[self alloc] initPending]; +} + ++ (instancetype)resolvedWith:(nullable id)resolution { + return [[self alloc] initWithResolution:resolution]; +} + +- (void)fulfill:(nullable id)value { + if ([value isKindOfClass:[NSError class]]) { + [self reject:(NSError *)value]; + } else { + @synchronized(self) { + if (_state == FBLPromiseStatePending) { + _state = FBLPromiseStateFulfilled; + _value = value; + _pendingObjects = nil; + for (FBLPromiseObserver observer in _observers) { + observer(_state, _value); + } + _observers = nil; + dispatch_group_leave(FBLPromise.dispatchGroup); + } + } + } +} + +- (void)reject:(NSError *)error { + NSAssert([error isKindOfClass:[NSError class]], @"Invalid error type."); + + if (![error isKindOfClass:[NSError class]]) { + // Give up on invalid error type in Release mode. + @throw error; // NOLINT + } + @synchronized(self) { + if (_state == FBLPromiseStatePending) { + _state = FBLPromiseStateRejected; + _error = error; + _pendingObjects = nil; + for (FBLPromiseObserver observer in _observers) { + observer(_state, _error); + } + _observers = nil; + dispatch_group_leave(FBLPromise.dispatchGroup); + } + } +} + +#pragma mark - NSObject + +- (NSString *)description { + if (self.isFulfilled) { + return [NSString stringWithFormat:@"<%@ %p> Fulfilled: %@", NSStringFromClass([self class]), + self, self.value]; + } + if (self.isRejected) { + return [NSString stringWithFormat:@"<%@ %p> Rejected: %@", NSStringFromClass([self class]), + self, self.error]; + } + return [NSString stringWithFormat:@"<%@ %p> Pending", NSStringFromClass([self class]), self]; +} + +#pragma mark - Private + +- (instancetype)initPending { + self = [super init]; + if (self) { + dispatch_group_enter(FBLPromise.dispatchGroup); + } + return self; +} + +- (instancetype)initWithResolution:(nullable id)resolution { + self = [super init]; + if (self) { + if ([resolution isKindOfClass:[NSError class]]) { + _state = FBLPromiseStateRejected; + _error = (NSError *)resolution; + } else { + _state = FBLPromiseStateFulfilled; + _value = resolution; + } + } + return self; +} + +- (void)dealloc { + if (_state == FBLPromiseStatePending) { + dispatch_group_leave(FBLPromise.dispatchGroup); + } +} + +- (BOOL)isPending { + @synchronized(self) { + return _state == FBLPromiseStatePending; + } +} + +- (BOOL)isFulfilled { + @synchronized(self) { + return _state == FBLPromiseStateFulfilled; + } +} + +- (BOOL)isRejected { + @synchronized(self) { + return _state == FBLPromiseStateRejected; + } +} + +- (nullable id)value { + @synchronized(self) { + return _value; + } +} + +- (NSError *__nullable)error { + @synchronized(self) { + return _error; + } +} + +- (NSMutableSet *__nullable)pendingObjects { + @synchronized(self) { + if (_state == FBLPromiseStatePending) { + if (!_pendingObjects) { + _pendingObjects = [[NSMutableSet alloc] init]; + } + } + return _pendingObjects; + } +} + +- (void)observeOnQueue:(dispatch_queue_t)queue + fulfill:(FBLPromiseOnFulfillBlock)onFulfill + reject:(FBLPromiseOnRejectBlock)onReject { + NSParameterAssert(queue); + NSParameterAssert(onFulfill); + NSParameterAssert(onReject); + + @synchronized(self) { + switch (_state) { + case FBLPromiseStatePending: { + if (!_observers) { + _observers = [[NSMutableArray alloc] init]; + } + [_observers addObject:^(FBLPromiseState state, id __nullable resolution) { + dispatch_group_async(FBLPromise.dispatchGroup, queue, ^{ + switch (state) { + case FBLPromiseStatePending: + break; + case FBLPromiseStateFulfilled: + onFulfill(resolution); + break; + case FBLPromiseStateRejected: + onReject(resolution); + break; + } + }); + }]; + break; + } + case FBLPromiseStateFulfilled: { + dispatch_group_async(FBLPromise.dispatchGroup, queue, ^{ + onFulfill(self->_value); + }); + break; + } + case FBLPromiseStateRejected: { + dispatch_group_async(FBLPromise.dispatchGroup, queue, ^{ + onReject(self->_error); + }); + break; + } + } + } +} + +- (FBLPromise *)chainOnQueue:(dispatch_queue_t)queue + chainedFulfill:(FBLPromiseChainedFulfillBlock)chainedFulfill + chainedReject:(FBLPromiseChainedRejectBlock)chainedReject { + NSParameterAssert(queue); + + FBLPromise *promise = [[FBLPromise alloc] initPending]; + __auto_type resolver = ^(id __nullable value) { + if ([value isKindOfClass:[FBLPromise class]]) { + [(FBLPromise *)value observeOnQueue:queue + fulfill:^(id __nullable value) { + [promise fulfill:value]; + } + reject:^(NSError *error) { + [promise reject:error]; + }]; + } else { + [promise fulfill:value]; + } + }; + [self observeOnQueue:queue + fulfill:^(id __nullable value) { + value = chainedFulfill ? chainedFulfill(value) : value; + resolver(value); + } + reject:^(NSError *error) { + id value = chainedReject ? chainedReject(error) : error; + resolver(value); + }]; + return promise; +} + +@end + +@implementation FBLPromise (DotSyntaxAdditions) + ++ (instancetype (^)(void))pending { + return ^(void) { + return [self pendingPromise]; + }; +} + ++ (instancetype (^)(id __nullable))resolved { + return ^(id resolution) { + return [self resolvedWith:resolution]; + }; +} + +@end diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromiseError.m b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromiseError.m new file mode 100644 index 0000000..1cc181a --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/FBLPromiseError.m @@ -0,0 +1,19 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromiseError.h" + +NSErrorDomain const FBLPromiseErrorDomain = @"com.google.FBLPromises.Error"; diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+All.h b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+All.h new file mode 100644 index 0000000..9c0090e --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+All.h @@ -0,0 +1,63 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FBLPromise(AllAdditions) + +/** + Wait until all of the given promises are fulfilled. + If one of the given promises is rejected, then the returned promise is rejected with same error. + If any other arbitrary value or `NSError` appears in the array instead of `FBLPromise`, + it's implicitly considered a pre-fulfilled or pre-rejected `FBLPromise` correspondingly. + Promises resolved with `nil` become `NSNull` instances in the resulting array. + + @param promises Promises to wait for. + @return Promise of an array containing the values of input promises in the same order. + */ ++ (FBLPromise *)all:(NSArray *)promises NS_SWIFT_UNAVAILABLE(""); + +/** + Wait until all of the given promises are fulfilled. + If one of the given promises is rejected, then the returned promise is rejected with same error. + If any other arbitrary value or `NSError` appears in the array instead of `FBLPromise`, + it's implicitly considered a pre-fulfilled or pre-rejected FBLPromise correspondingly. + Promises resolved with `nil` become `NSNull` instances in the resulting array. + + @param queue A queue to dispatch on. + @param promises Promises to wait for. + @return Promise of an array containing the values of input promises in the same order. + */ ++ (FBLPromise *)onQueue:(dispatch_queue_t)queue + all:(NSArray *)promises NS_REFINED_FOR_SWIFT; + +@end + +/** + Convenience dot-syntax wrappers for `FBLPromise` `all` operators. + Usage: FBLPromise.all(@[ ... ]) + */ +@interface FBLPromise(DotSyntax_AllAdditions) + ++ (FBLPromise * (^)(NSArray *))all FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise * (^)(dispatch_queue_t, NSArray *))allOn FBL_PROMISES_DOT_SYNTAX + NS_SWIFT_UNAVAILABLE(""); + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Always.h b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Always.h new file mode 100644 index 0000000..13000f5 --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Always.h @@ -0,0 +1,54 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FBLPromise(AlwaysAdditions) + +typedef void (^FBLPromiseAlwaysWorkBlock)(void) NS_SWIFT_UNAVAILABLE(""); + +/** + @param work A block that always executes, no matter if the receiver is rejected or fulfilled. + @return A new pending promise to be resolved with same resolution as the receiver. + */ +- (FBLPromise *)always:(FBLPromiseAlwaysWorkBlock)work NS_SWIFT_UNAVAILABLE(""); + +/** + @param queue A queue to dispatch on. + @param work A block that always executes, no matter if the receiver is rejected or fulfilled. + @return A new pending promise to be resolved with same resolution as the receiver. + */ +- (FBLPromise *)onQueue:(dispatch_queue_t)queue + always:(FBLPromiseAlwaysWorkBlock)work NS_REFINED_FOR_SWIFT; + +@end + +/** + Convenience dot-syntax wrappers for `FBLPromise` `always` operators. + Usage: promise.always(^{...}) + */ +@interface FBLPromise(DotSyntax_AlwaysAdditions) + +- (FBLPromise* (^)(FBLPromiseAlwaysWorkBlock))always FBL_PROMISES_DOT_SYNTAX + NS_SWIFT_UNAVAILABLE(""); +- (FBLPromise* (^)(dispatch_queue_t, FBLPromiseAlwaysWorkBlock))alwaysOn FBL_PROMISES_DOT_SYNTAX + NS_SWIFT_UNAVAILABLE(""); + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Any.h b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Any.h new file mode 100644 index 0000000..82875bf --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Any.h @@ -0,0 +1,69 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FBLPromise(AnyAdditions) + +/** + Waits until all of the given promises are either fulfilled or rejected. + If all promises are rejected, then the returned promise is rejected with same error + as the last one rejected. + If at least one of the promises is fulfilled, the resulting promise is fulfilled with an array of + values or `NSErrors`, matching the original order of fulfilled or rejected promises respectively. + If any other arbitrary value or `NSError` appears in the array instead of `FBLPromise`, + it's implicitly considered a pre-fulfilled or pre-rejected `FBLPromise` correspondingly. + Promises resolved with `nil` become `NSNull` instances in the resulting array. + + @param promises Promises to wait for. + @return Promise of array containing the values or `NSError`s of input promises in the same order. + */ ++ (FBLPromise *)any:(NSArray *)promises NS_SWIFT_UNAVAILABLE(""); + +/** + Waits until all of the given promises are either fulfilled or rejected. + If all promises are rejected, then the returned promise is rejected with same error + as the last one rejected. + If at least one of the promises is fulfilled, the resulting promise is fulfilled with an array of + values or `NSError`s, matching the original order of fulfilled or rejected promises respectively. + If any other arbitrary value or `NSError` appears in the array instead of `FBLPromise`, + it's implicitly considered a pre-fulfilled or pre-rejected `FBLPromise` correspondingly. + Promises resolved with `nil` become `NSNull` instances in the resulting array. + + @param queue A queue to dispatch on. + @param promises Promises to wait for. + @return Promise of array containing the values or `NSError`s of input promises in the same order. + */ ++ (FBLPromise *)onQueue:(dispatch_queue_t)queue + any:(NSArray *)promises NS_REFINED_FOR_SWIFT; + +@end + +/** + Convenience dot-syntax wrappers for `FBLPromise` `any` operators. + Usage: FBLPromise.any(@[ ... ]) + */ +@interface FBLPromise(DotSyntax_AnyAdditions) + ++ (FBLPromise * (^)(NSArray *))any FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise * (^)(dispatch_queue_t, NSArray *))anyOn FBL_PROMISES_DOT_SYNTAX + NS_SWIFT_UNAVAILABLE(""); + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Async.h b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Async.h new file mode 100644 index 0000000..0588a9e --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Async.h @@ -0,0 +1,60 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FBLPromise(AsyncAdditions) + +typedef void (^FBLPromiseFulfillBlock)(Value __nullable value) NS_SWIFT_UNAVAILABLE(""); +typedef void (^FBLPromiseRejectBlock)(NSError *error) NS_SWIFT_UNAVAILABLE(""); +typedef void (^FBLPromiseAsyncWorkBlock)(FBLPromiseFulfillBlock fulfill, + FBLPromiseRejectBlock reject) NS_SWIFT_UNAVAILABLE(""); + +/** + Creates a pending promise and executes `work` block asynchronously. + + @param work A block to perform any operations needed to resolve the promise. + @return A new pending promise. + */ ++ (instancetype)async:(FBLPromiseAsyncWorkBlock)work NS_SWIFT_UNAVAILABLE(""); + +/** + Creates a pending promise and executes `work` block asynchronously on the given queue. + + @param queue A queue to invoke the `work` block on. + @param work A block to perform any operations needed to resolve the promise. + @return A new pending promise. + */ ++ (instancetype)onQueue:(dispatch_queue_t)queue + async:(FBLPromiseAsyncWorkBlock)work NS_REFINED_FOR_SWIFT; + +@end + +/** + Convenience dot-syntax wrappers for `FBLPromise` `async` operators. + Usage: FBLPromise.async(^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { ... }) + */ +@interface FBLPromise(DotSyntax_AsyncAdditions) + ++ (FBLPromise* (^)(FBLPromiseAsyncWorkBlock))async FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise* (^)(dispatch_queue_t, FBLPromiseAsyncWorkBlock))asyncOn FBL_PROMISES_DOT_SYNTAX + NS_SWIFT_UNAVAILABLE(""); + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Await.h b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Await.h new file mode 100644 index 0000000..c97a1ba --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Await.h @@ -0,0 +1,32 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + Waits for promise resolution. The current thread blocks until the promise is resolved. + + @param promise Promise to wait for. + @param error Error the promise was rejected with, or `nil` if the promise was fulfilled. + @return Value the promise was fulfilled with. If the promise was rejected, the return value + is always `nil`, but the error out arg is not. + */ +FOUNDATION_EXTERN id __nullable FBLPromiseAwait(FBLPromise *promise, + NSError **error) NS_REFINED_FOR_SWIFT; + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Catch.h b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Catch.h new file mode 100644 index 0000000..a9ff170 --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Catch.h @@ -0,0 +1,59 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FBLPromise(CatchAdditions) + +typedef void (^FBLPromiseCatchWorkBlock)(NSError *error) NS_SWIFT_UNAVAILABLE(""); + +/** + Creates a pending promise which eventually gets resolved with same resolution as the receiver. + If receiver is rejected, then `reject` block is executed asynchronously. + + @param reject A block to handle the error that receiver was rejected with. + @return A new pending promise. + */ +- (FBLPromise *)catch:(FBLPromiseCatchWorkBlock)reject NS_SWIFT_UNAVAILABLE(""); + +/** + Creates a pending promise which eventually gets resolved with same resolution as the receiver. + If receiver is rejected, then `reject` block is executed asynchronously on the given queue. + + @param queue A queue to invoke the `reject` block on. + @param reject A block to handle the error that receiver was rejected with. + @return A new pending promise. + */ +- (FBLPromise *)onQueue:(dispatch_queue_t)queue + catch:(FBLPromiseCatchWorkBlock)reject NS_REFINED_FOR_SWIFT; + +@end + +/** + Convenience dot-syntax wrappers for `FBLPromise` `catch` operators. + Usage: promise.catch(^(NSError *error) { ... }) + */ +@interface FBLPromise(DotSyntax_CatchAdditions) + +- (FBLPromise* (^)(FBLPromiseCatchWorkBlock))catch FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); +- (FBLPromise* (^)(dispatch_queue_t, FBLPromiseCatchWorkBlock))catchOn FBL_PROMISES_DOT_SYNTAX + NS_SWIFT_UNAVAILABLE(""); + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Delay.h b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Delay.h new file mode 100644 index 0000000..557df48 --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Delay.h @@ -0,0 +1,59 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FBLPromise(DelayAdditions) + +/** + Creates a new pending promise that fulfills with the same value as `self` after the `delay`, or + rejects with the same error immediately. + + @param interval Time to wait in seconds. + @return A new pending promise that fulfills at least `delay` seconds later than `self`, or rejects + with the same error immediately. + */ +- (FBLPromise *)delay:(NSTimeInterval)interval NS_SWIFT_UNAVAILABLE(""); + +/** + Creates a new pending promise that fulfills with the same value as `self` after the `delay`, or + rejects with the same error immediately. + + @param queue A queue to dispatch on. + @param interval Time to wait in seconds. + @return A new pending promise that fulfills at least `delay` seconds later than `self`, or rejects + with the same error immediately. + */ +- (FBLPromise *)onQueue:(dispatch_queue_t)queue + delay:(NSTimeInterval)interval NS_REFINED_FOR_SWIFT; + +@end + +/** + Convenience dot-syntax wrappers for `FBLPromise` `delay` operators. + Usage: promise.delay(...) + */ +@interface FBLPromise(DotSyntax_DelayAdditions) + +- (FBLPromise * (^)(NSTimeInterval))delay FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); +- (FBLPromise * (^)(dispatch_queue_t, NSTimeInterval))delayOn FBL_PROMISES_DOT_SYNTAX + NS_SWIFT_UNAVAILABLE(""); + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Do.h b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Do.h new file mode 100644 index 0000000..6838e0a --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Do.h @@ -0,0 +1,55 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FBLPromise(DoAdditions) + +typedef id __nullable (^FBLPromiseDoWorkBlock)(void) NS_SWIFT_UNAVAILABLE(""); + +/** + Creates a pending promise and executes `work` block asynchronously. + + @param work A block that returns a value or an error used to resolve the promise. + @return A new pending promise. + */ ++ (instancetype)do:(FBLPromiseDoWorkBlock)work NS_SWIFT_UNAVAILABLE(""); + +/** + Creates a pending promise and executes `work` block asynchronously on the given queue. + + @param queue A queue to invoke the `work` block on. + @param work A block that returns a value or an error used to resolve the promise. + @return A new pending promise. + */ ++ (instancetype)onQueue:(dispatch_queue_t)queue do:(FBLPromiseDoWorkBlock)work NS_REFINED_FOR_SWIFT; + +@end + +/** + Convenience dot-syntax wrappers for `FBLPromise` `do` operators. + Usage: FBLPromise.doOn(queue, ^(NSError *error) { ... }) + */ +@interface FBLPromise(DotSyntax_DoAdditions) + ++ (FBLPromise * (^)(dispatch_queue_t, FBLPromiseDoWorkBlock))doOn FBL_PROMISES_DOT_SYNTAX + NS_SWIFT_UNAVAILABLE(""); + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Race.h b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Race.h new file mode 100644 index 0000000..2f67258 --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Race.h @@ -0,0 +1,62 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FBLPromise(RaceAdditions) + +/** + Wait until any of the given promises are fulfilled. + If one of the promises is rejected, then the returned promise is rejected with same error. + If any other arbitrary value or `NSError` appears in the array instead of `FBLPromise`, + it's implicitly considered a pre-fulfilled or pre-rejected `FBLPromise` correspondingly. + + @param promises Promises to wait for. + @return A new pending promise to be resolved with the same resolution as the first promise, among + the given ones, which was resolved. + */ ++ (instancetype)race:(NSArray *)promises NS_SWIFT_UNAVAILABLE(""); + +/** + Wait until any of the given promises are fulfilled. + If one of the promises is rejected, then the returned promise is rejected with same error. + If any other arbitrary value or `NSError` appears in the array instead of `FBLPromise`, + it's implicitly considered a pre-fulfilled or pre-rejected `FBLPromise` correspondingly. + + @param queue A queue to dispatch on. + @param promises Promises to wait for. + @return A new pending promise to be resolved with the same resolution as the first promise, among + the given ones, which was resolved. + */ ++ (instancetype)onQueue:(dispatch_queue_t)queue race:(NSArray *)promises NS_REFINED_FOR_SWIFT; + +@end + +/** + Convenience dot-syntax wrappers for `FBLPromise` `race` operators. + Usage: FBLPromise.race(@[ ... ]) + */ +@interface FBLPromise(DotSyntax_RaceAdditions) + ++ (FBLPromise * (^)(NSArray *))race FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise * (^)(dispatch_queue_t, NSArray *))raceOn FBL_PROMISES_DOT_SYNTAX + NS_SWIFT_UNAVAILABLE(""); + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Recover.h b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Recover.h new file mode 100644 index 0000000..bb7df7e --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Recover.h @@ -0,0 +1,60 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FBLPromise(RecoverAdditions) + +typedef id __nullable (^FBLPromiseRecoverWorkBlock)(NSError *error) NS_SWIFT_UNAVAILABLE(""); + +/** + Provides a new promise to recover in case the receiver gets rejected. + + @param recovery A block to handle the error that the receiver was rejected with. + @return A new pending promise to use instead of the rejected one that gets resolved with resolution + returned from `recovery` block. + */ +- (FBLPromise *)recover:(FBLPromiseRecoverWorkBlock)recovery NS_SWIFT_UNAVAILABLE(""); + +/** + Provides a new promise to recover in case the receiver gets rejected. + + @param queue A queue to dispatch on. + @param recovery A block to handle the error that the receiver was rejected with. + @return A new pending promise to use instead of the rejected one that gets resolved with resolution + returned from `recovery` block. + */ +- (FBLPromise *)onQueue:(dispatch_queue_t)queue + recover:(FBLPromiseRecoverWorkBlock)recovery NS_REFINED_FOR_SWIFT; + +@end + +/** + Convenience dot-syntax wrappers for `FBLPromise` `recover` operators. + Usage: promise.recover(^id(NSError *error) {...}) + */ +@interface FBLPromise(DotSyntax_RecoverAdditions) + +- (FBLPromise * (^)(FBLPromiseRecoverWorkBlock))recover FBL_PROMISES_DOT_SYNTAX + NS_SWIFT_UNAVAILABLE(""); +- (FBLPromise * (^)(dispatch_queue_t, FBLPromiseRecoverWorkBlock))recoverOn FBL_PROMISES_DOT_SYNTAX + NS_SWIFT_UNAVAILABLE(""); + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Reduce.h b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Reduce.h new file mode 100644 index 0000000..5bb1eee --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Reduce.h @@ -0,0 +1,71 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FBLPromise(ReduceAdditions) + +typedef id __nullable (^FBLPromiseReducerBlock)(Value __nullable partial, id next) + NS_SWIFT_UNAVAILABLE(""); + +/** + Sequentially reduces a collection of values to a single promise using a given combining block + and the value `self` resolves with as initial value. + + @param items An array of values to process in order. + @param reducer A block to combine an accumulating value and an element of the sequence into + the new accumulating value or a promise resolved with it, to be used in the next + call of the `reducer` or returned to the caller. + @return A new pending promise returned from the last `reducer` invocation. + Or `self` if `items` is empty. + */ +- (FBLPromise *)reduce:(NSArray *)items + combine:(FBLPromiseReducerBlock)reducer NS_SWIFT_UNAVAILABLE(""); + +/** + Sequentially reduces a collection of values to a single promise using a given combining block + and the value `self` resolves with as initial value. + + @param queue A queue to dispatch on. + @param items An array of values to process in order. + @param reducer A block to combine an accumulating value and an element of the sequence into + the new accumulating value or a promise resolved with it, to be used in the next + call of the `reducer` or returned to the caller. + @return A new pending promise returned from the last `reducer` invocation. + Or `self` if `items` is empty. + */ +- (FBLPromise *)onQueue:(dispatch_queue_t)queue + reduce:(NSArray *)items + combine:(FBLPromiseReducerBlock)reducer NS_SWIFT_UNAVAILABLE(""); + +@end + +/** + Convenience dot-syntax wrappers for `FBLPromise` `reduce` operators. + Usage: promise.reduce(values, ^id(id partial, id next) { ... }) + */ +@interface FBLPromise(DotSyntax_ReduceAdditions) + +- (FBLPromise * (^)(NSArray *, FBLPromiseReducerBlock))reduce FBL_PROMISES_DOT_SYNTAX + NS_SWIFT_UNAVAILABLE(""); +- (FBLPromise * (^)(dispatch_queue_t, NSArray *, FBLPromiseReducerBlock))reduceOn + FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Retry.h b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Retry.h new file mode 100644 index 0000000..98ef558 --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Retry.h @@ -0,0 +1,165 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise.h" + +NS_ASSUME_NONNULL_BEGIN + +/** The default number of retry attempts is 1. */ +FOUNDATION_EXTERN NSInteger const FBLPromiseRetryDefaultAttemptsCount NS_REFINED_FOR_SWIFT; + +/** The default delay interval before making a retry attempt is 1.0 second. */ +FOUNDATION_EXTERN NSTimeInterval const FBLPromiseRetryDefaultDelayInterval NS_REFINED_FOR_SWIFT; + +@interface FBLPromise(RetryAdditions) + +typedef id __nullable (^FBLPromiseRetryWorkBlock)(void) NS_SWIFT_UNAVAILABLE(""); +typedef BOOL (^FBLPromiseRetryPredicateBlock)(NSInteger, NSError *) NS_SWIFT_UNAVAILABLE(""); + +/** + Creates a pending promise that fulfills with the same value as the promise returned from `work` + block, which executes asynchronously, or rejects with the same error after all retry attempts have + been exhausted. Defaults to `FBLPromiseRetryDefaultAttemptsCount` attempt(s) on rejection where the + `work` block is retried after a delay of `FBLPromiseRetryDefaultDelayInterval` second(s). + + @param work A block that executes asynchronously on the default queue and returns a value or an + error used to resolve the promise. + @return A new pending promise that fulfills with the same value as the promise returned from `work` + block, or rejects with the same error after all retry attempts have been exhausted. + */ ++ (FBLPromise *)retry:(FBLPromiseRetryWorkBlock)work NS_SWIFT_UNAVAILABLE(""); + +/** + Creates a pending promise that fulfills with the same value as the promise returned from `work` + block, which executes asynchronously on the given `queue`, or rejects with the same error after all + retry attempts have been exhausted. Defaults to `FBLPromiseRetryDefaultAttemptsCount` attempt(s) on + rejection where the `work` block is retried on the given `queue` after a delay of + `FBLPromiseRetryDefaultDelayInterval` second(s). + + @param queue A queue to invoke the `work` block on. + @param work A block that executes asynchronously on the given `queue` and returns a value or an + error used to resolve the promise. + @return A new pending promise that fulfills with the same value as the promise returned from `work` + block, or rejects with the same error after all retry attempts have been exhausted. + */ ++ (FBLPromise *)onQueue:(dispatch_queue_t)queue + retry:(FBLPromiseRetryWorkBlock)work NS_SWIFT_UNAVAILABLE(""); + +/** + Creates a pending promise that fulfills with the same value as the promise returned from `work` + block, which executes asynchronously, or rejects with the same error after all retry attempts have + been exhausted. + + @param count Max number of retry attempts. The `work` block will be executed once if the specified + count is less than or equal to zero. + @param work A block that executes asynchronously on the default queue and returns a value or an + error used to resolve the promise. + @return A new pending promise that fulfills with the same value as the promise returned from `work` + block, or rejects with the same error after all retry attempts have been exhausted. + */ ++ (FBLPromise *)attempts:(NSInteger)count + retry:(FBLPromiseRetryWorkBlock)work NS_SWIFT_UNAVAILABLE(""); + +/** + Creates a pending promise that fulfills with the same value as the promise returned from `work` + block, which executes asynchronously on the given `queue`, or rejects with the same error after all + retry attempts have been exhausted. + + @param queue A queue to invoke the `work` block on. + @param count Max number of retry attempts. The `work` block will be executed once if the specified + count is less than or equal to zero. + @param work A block that executes asynchronously on the given `queue` and returns a value or an + error used to resolve the promise. + @return A new pending promise that fulfills with the same value as the promise returned from `work` + block, or rejects with the same error after all retry attempts have been exhausted. + */ ++ (FBLPromise *)onQueue:(dispatch_queue_t)queue + attempts:(NSInteger)count + retry:(FBLPromiseRetryWorkBlock)work NS_SWIFT_UNAVAILABLE(""); + +/** + Creates a pending promise that fulfills with the same value as the promise returned from `work` + block, which executes asynchronously, or rejects with the same error after all retry attempts have + been exhausted. On rejection, the `work` block is retried after the given delay `interval` and will + continue to retry until the number of specified attempts have been exhausted or will bail early if + the given condition is not met. + + @param count Max number of retry attempts. The `work` block will be executed once if the specified + count is less than or equal to zero. + @param interval Time to wait before the next retry attempt. + @param predicate Condition to check before the next retry attempt. The predicate block provides the + the number of remaining retry attempts and the error that the promise was rejected + with. + @param work A block that executes asynchronously on the default queue and returns a value or an + error used to resolve the promise. + @return A new pending promise that fulfills with the same value as the promise returned from `work` + block, or rejects with the same error after all retry attempts have been exhausted or if + the given condition is not met. + */ ++ (FBLPromise *)attempts:(NSInteger)count + delay:(NSTimeInterval)interval + condition:(nullable FBLPromiseRetryPredicateBlock)predicate + retry:(FBLPromiseRetryWorkBlock)work NS_SWIFT_UNAVAILABLE(""); + +/** + Creates a pending promise that fulfills with the same value as the promise returned from `work` + block, which executes asynchronously on the given `queue`, or rejects with the same error after all + retry attempts have been exhausted. On rejection, the `work` block is retried after the given + delay `interval` and will continue to retry until the number of specified attempts have been + exhausted or will bail early if the given condition is not met. + + @param queue A queue to invoke the `work` block on. + @param count Max number of retry attempts. The `work` block will be executed once if the specified + count is less than or equal to zero. + @param interval Time to wait before the next retry attempt. + @param predicate Condition to check before the next retry attempt. The predicate block provides the + the number of remaining retry attempts and the error that the promise was rejected + with. + @param work A block that executes asynchronously on the given `queue` and returns a value or an + error used to resolve the promise. + @return A new pending promise that fulfills with the same value as the promise returned from `work` + block, or rejects with the same error after all retry attempts have been exhausted or if + the given condition is not met. + */ ++ (FBLPromise *)onQueue:(dispatch_queue_t)queue + attempts:(NSInteger)count + delay:(NSTimeInterval)interval + condition:(nullable FBLPromiseRetryPredicateBlock)predicate + retry:(FBLPromiseRetryWorkBlock)work NS_REFINED_FOR_SWIFT; + +@end + +/** + Convenience dot-syntax wrappers for `FBLPromise+Retry` operators. + Usage: FBLPromise.retry(^id { ... }) + */ +@interface FBLPromise(DotSyntax_RetryAdditions) + ++ (FBLPromise * (^)(FBLPromiseRetryWorkBlock))retry FBL_PROMISES_DOT_SYNTAX + NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise * (^)(dispatch_queue_t, FBLPromiseRetryWorkBlock))retryOn FBL_PROMISES_DOT_SYNTAX + NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise * (^)(NSInteger, NSTimeInterval, FBLPromiseRetryPredicateBlock __nullable, + FBLPromiseRetryWorkBlock))retryAgain FBL_PROMISES_DOT_SYNTAX + NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise * (^)(dispatch_queue_t, NSInteger, NSTimeInterval, + FBLPromiseRetryPredicateBlock __nullable, + FBLPromiseRetryWorkBlock))retryAgainOn FBL_PROMISES_DOT_SYNTAX + NS_SWIFT_UNAVAILABLE(""); + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Testing.h b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Testing.h new file mode 100644 index 0000000..07a65ec --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Testing.h @@ -0,0 +1,63 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + Waits for all scheduled promises blocks. + + @param timeout Maximum time to wait. + @return YES if all promises blocks have completed before the timeout and NO otherwise. + */ +FOUNDATION_EXTERN BOOL FBLWaitForPromisesWithTimeout(NSTimeInterval timeout) NS_REFINED_FOR_SWIFT; + +@interface FBLPromise(TestingAdditions) + +/** + Dispatch group for promises that is typically used to wait for all scheduled blocks. + */ +@property(class, nonatomic, readonly) dispatch_group_t dispatchGroup NS_REFINED_FOR_SWIFT; + +/** + Properties to get the current state of the promise. + */ +@property(nonatomic, readonly) BOOL isPending NS_REFINED_FOR_SWIFT; +@property(nonatomic, readonly) BOOL isFulfilled NS_REFINED_FOR_SWIFT; +@property(nonatomic, readonly) BOOL isRejected NS_REFINED_FOR_SWIFT; + +/** + Set of arbitrary objects to keep strongly while the promise is pending. + Becomes nil after the promise has been resolved. + */ +@property(nonatomic, readonly, nullable) NSMutableSet *pendingObjects NS_REFINED_FOR_SWIFT; + +/** + Value the promise was fulfilled with. + Can be nil if the promise is still pending, was resolved with nil or after it has been rejected. + */ +@property(nonatomic, readonly, nullable) Value value NS_REFINED_FOR_SWIFT; + +/** + Error the promise was rejected with. + Can be nil if the promise is still pending or after it has been fulfilled. + */ +@property(nonatomic, readonly, nullable) NSError *error NS_REFINED_FOR_SWIFT; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Then.h b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Then.h new file mode 100644 index 0000000..32027e6 --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Then.h @@ -0,0 +1,63 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FBLPromise(ThenAdditions) + +typedef id __nullable (^FBLPromiseThenWorkBlock)(Value __nullable value) NS_SWIFT_UNAVAILABLE(""); + +/** + Creates a pending promise which eventually gets resolved with resolution returned from `work` + block: either value, error or another promise. The `work` block is executed asynchronously only + when the receiver is fulfilled. If receiver is rejected, the returned promise is also rejected with + the same error. + + @param work A block to handle the value that receiver was fulfilled with. + @return A new pending promise to be resolved with resolution returned from the `work` block. + */ +- (FBLPromise *)then:(FBLPromiseThenWorkBlock)work NS_SWIFT_UNAVAILABLE(""); + +/** + Creates a pending promise which eventually gets resolved with resolution returned from `work` + block: either value, error or another promise. The `work` block is executed asynchronously when the + receiver is fulfilled. If receiver is rejected, the returned promise is also rejected with the same + error. + + @param queue A queue to invoke the `work` block on. + @param work A block to handle the value that receiver was fulfilled with. + @return A new pending promise to be resolved with resolution returned from the `work` block. + */ +- (FBLPromise *)onQueue:(dispatch_queue_t)queue + then:(FBLPromiseThenWorkBlock)work NS_REFINED_FOR_SWIFT; + +@end + +/** + Convenience dot-syntax wrappers for `FBLPromise` `then` operators. + Usage: promise.then(^id(id value) { ... }) + */ +@interface FBLPromise(DotSyntax_ThenAdditions) + +- (FBLPromise* (^)(FBLPromiseThenWorkBlock))then FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); +- (FBLPromise* (^)(dispatch_queue_t, FBLPromiseThenWorkBlock))thenOn FBL_PROMISES_DOT_SYNTAX + NS_SWIFT_UNAVAILABLE(""); + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Timeout.h b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Timeout.h new file mode 100644 index 0000000..184ba16 --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Timeout.h @@ -0,0 +1,57 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FBLPromise(TimeoutAdditions) + +/** + Waits for a promise with the specified `timeout`. + + @param interval Time to wait in seconds. + @return A new pending promise that gets either resolved with same resolution as the receiver or + rejected with `FBLPromiseErrorCodeTimedOut` error code in `FBLPromiseErrorDomain`. + */ +- (FBLPromise *)timeout:(NSTimeInterval)interval NS_SWIFT_UNAVAILABLE(""); + +/** + Waits for a promise with the specified `timeout`. + + @param queue A queue to dispatch on. + @param interval Time to wait in seconds. + @return A new pending promise that gets either resolved with same resolution as the receiver or + rejected with `FBLPromiseErrorCodeTimedOut` error code in `FBLPromiseErrorDomain`. + */ +- (FBLPromise *)onQueue:(dispatch_queue_t)queue + timeout:(NSTimeInterval)interval NS_REFINED_FOR_SWIFT; + +@end + +/** + Convenience dot-syntax wrappers for `FBLPromise` `timeout` operators. + Usage: promise.timeout(...) + */ +@interface FBLPromise(DotSyntax_TimeoutAdditions) + +- (FBLPromise* (^)(NSTimeInterval))timeout FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); +- (FBLPromise* (^)(dispatch_queue_t, NSTimeInterval))timeoutOn FBL_PROMISES_DOT_SYNTAX + NS_SWIFT_UNAVAILABLE(""); + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Validate.h b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Validate.h new file mode 100644 index 0000000..9dfa2f1 --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Validate.h @@ -0,0 +1,60 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FBLPromise(ValidateAdditions) + +typedef BOOL (^FBLPromiseValidateWorkBlock)(Value __nullable value) NS_SWIFT_UNAVAILABLE(""); + +/** + Validates a fulfilled value or rejects the value if it can not be validated. + + @param predicate An expression to validate. + @return A new pending promise that gets either resolved with same resolution as the receiver or + rejected with `FBLPromiseErrorCodeValidationFailure` error code in `FBLPromiseErrorDomain`. + */ +- (FBLPromise *)validate:(FBLPromiseValidateWorkBlock)predicate NS_SWIFT_UNAVAILABLE(""); + +/** + Validates a fulfilled value or rejects the value if it can not be validated. + + @param queue A queue to dispatch on. + @param predicate An expression to validate. + @return A new pending promise that gets either resolved with same resolution as the receiver or + rejected with `FBLPromiseErrorCodeValidationFailure` error code in `FBLPromiseErrorDomain`. + */ +- (FBLPromise *)onQueue:(dispatch_queue_t)queue + validate:(FBLPromiseValidateWorkBlock)predicate NS_REFINED_FOR_SWIFT; + +@end + +/** + Convenience dot-syntax wrappers for `FBLPromise` `validate` operators. + Usage: promise.validate(^BOOL(id value) { ... }) + */ +@interface FBLPromise(DotSyntax_ValidateAdditions) + +- (FBLPromise * (^)(FBLPromiseValidateWorkBlock))validate FBL_PROMISES_DOT_SYNTAX + NS_SWIFT_UNAVAILABLE(""); +- (FBLPromise * (^)(dispatch_queue_t, FBLPromiseValidateWorkBlock))validateOn + FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Wrap.h b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Wrap.h new file mode 100644 index 0000000..664e1bb --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Wrap.h @@ -0,0 +1,316 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + Different types of completion handlers available to be wrapped with promise. + */ +typedef void (^FBLPromiseCompletion)(void) NS_SWIFT_UNAVAILABLE(""); +typedef void (^FBLPromiseObjectCompletion)(id __nullable) NS_SWIFT_UNAVAILABLE(""); +typedef void (^FBLPromiseErrorCompletion)(NSError* __nullable) NS_SWIFT_UNAVAILABLE(""); +typedef void (^FBLPromiseObjectOrErrorCompletion)(id __nullable, NSError* __nullable) + NS_SWIFT_UNAVAILABLE(""); +typedef void (^FBLPromiseErrorOrObjectCompletion)(NSError* __nullable, id __nullable) + NS_SWIFT_UNAVAILABLE(""); +typedef void (^FBLPromise2ObjectsOrErrorCompletion)(id __nullable, id __nullable, + NSError* __nullable) NS_SWIFT_UNAVAILABLE(""); +typedef void (^FBLPromiseBoolCompletion)(BOOL) NS_SWIFT_UNAVAILABLE(""); +typedef void (^FBLPromiseBoolOrErrorCompletion)(BOOL, NSError* __nullable) NS_SWIFT_UNAVAILABLE(""); +typedef void (^FBLPromiseIntegerCompletion)(NSInteger) NS_SWIFT_UNAVAILABLE(""); +typedef void (^FBLPromiseIntegerOrErrorCompletion)(NSInteger, NSError* __nullable) + NS_SWIFT_UNAVAILABLE(""); +typedef void (^FBLPromiseDoubleCompletion)(double) NS_SWIFT_UNAVAILABLE(""); +typedef void (^FBLPromiseDoubleOrErrorCompletion)(double, NSError* __nullable) + NS_SWIFT_UNAVAILABLE(""); + +/** + Provides an easy way to convert methods that use common callback patterns into promises. + */ +@interface FBLPromise(WrapAdditions) + +/** + @param work A block to perform any operations needed to resolve the promise. + @returns A promise that resolves with `nil` when completion handler is invoked. + */ ++ (instancetype)wrapCompletion:(void (^)(FBLPromiseCompletion handler))work + NS_SWIFT_UNAVAILABLE(""); + +/** + @param queue A queue to invoke the `work` block on. + @param work A block to perform any operations needed to resolve the promise. + @returns A promise that resolves with `nil` when completion handler is invoked. + */ ++ (instancetype)onQueue:(dispatch_queue_t)queue + wrapCompletion:(void (^)(FBLPromiseCompletion handler))work NS_SWIFT_UNAVAILABLE(""); + +/** + @param work A block to perform any operations needed to resolve the promise. + @returns A promise that resolves with an object provided by completion handler. + */ ++ (instancetype)wrapObjectCompletion:(void (^)(FBLPromiseObjectCompletion handler))work + NS_SWIFT_UNAVAILABLE(""); + +/** + @param queue A queue to invoke the `work` block on. + @param work A block to perform any operations needed to resolve the promise. + @returns A promise that resolves with an object provided by completion handler. + */ ++ (instancetype)onQueue:(dispatch_queue_t)queue + wrapObjectCompletion:(void (^)(FBLPromiseObjectCompletion handler))work + NS_SWIFT_UNAVAILABLE(""); + +/** + @param work A block to perform any operations needed to resolve the promise. + @returns A promise that resolves with an error provided by completion handler. + If error is `nil`, fulfills with `nil`, otherwise rejects with the error. + */ ++ (instancetype)wrapErrorCompletion:(void (^)(FBLPromiseErrorCompletion handler))work + NS_SWIFT_UNAVAILABLE(""); + +/** + @param queue A queue to invoke the `work` block on. + @param work A block to perform any operations needed to resolve the promise. + @returns A promise that resolves with an error provided by completion handler. + If error is `nil`, fulfills with `nil`, otherwise rejects with the error. + */ ++ (instancetype)onQueue:(dispatch_queue_t)queue + wrapErrorCompletion:(void (^)(FBLPromiseErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); + +/** + @param work A block to perform any operations needed to resolve the promise. + @returns A promise that resolves with an object provided by completion handler if error is `nil`. + Otherwise, rejects with the error. + */ ++ (instancetype)wrapObjectOrErrorCompletion: + (void (^)(FBLPromiseObjectOrErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); + +/** + @param queue A queue to invoke the `work` block on. + @param work A block to perform any operations needed to resolve the promise. + @returns A promise that resolves with an object provided by completion handler if error is `nil`. + Otherwise, rejects with the error. + */ ++ (instancetype)onQueue:(dispatch_queue_t)queue + wrapObjectOrErrorCompletion:(void (^)(FBLPromiseObjectOrErrorCompletion handler))work + NS_SWIFT_UNAVAILABLE(""); + +/** + @param work A block to perform any operations needed to resolve the promise. + @returns A promise that resolves with an error or object provided by completion handler. If error + is not `nil`, rejects with the error. + */ ++ (instancetype)wrapErrorOrObjectCompletion: + (void (^)(FBLPromiseErrorOrObjectCompletion handler))work NS_SWIFT_UNAVAILABLE(""); + +/** + @param queue A queue to invoke the `work` block on. + @param work A block to perform any operations needed to resolve the promise. + @returns A promise that resolves with an error or object provided by completion handler. If error + is not `nil`, rejects with the error. + */ ++ (instancetype)onQueue:(dispatch_queue_t)queue + wrapErrorOrObjectCompletion:(void (^)(FBLPromiseErrorOrObjectCompletion handler))work + NS_SWIFT_UNAVAILABLE(""); + +/** + @param work A block to perform any operations needed to resolve the promise. + @returns A promise that resolves with an array of objects provided by completion handler in order + if error is `nil`. Otherwise, rejects with the error. + */ ++ (FBLPromise*)wrap2ObjectsOrErrorCompletion: + (void (^)(FBLPromise2ObjectsOrErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); + +/** + @param queue A queue to invoke the `work` block on. + @param work A block to perform any operations needed to resolve the promise. + @returns A promise that resolves with an array of objects provided by completion handler in order + if error is `nil`. Otherwise, rejects with the error. + */ ++ (FBLPromise*)onQueue:(dispatch_queue_t)queue + wrap2ObjectsOrErrorCompletion:(void (^)(FBLPromise2ObjectsOrErrorCompletion handler))work + NS_SWIFT_UNAVAILABLE(""); + +/** + @param work A block to perform any operations needed to resolve the promise. + @returns A promise that resolves with an `NSNumber` wrapping YES/NO. + */ ++ (FBLPromise*)wrapBoolCompletion:(void (^)(FBLPromiseBoolCompletion handler))work + NS_SWIFT_UNAVAILABLE(""); + +/** + @param queue A queue to invoke the `work` block on. + @param work A block to perform any operations needed to resolve the promise. + @returns A promise that resolves with an `NSNumber` wrapping YES/NO. + */ ++ (FBLPromise*)onQueue:(dispatch_queue_t)queue + wrapBoolCompletion:(void (^)(FBLPromiseBoolCompletion handler))work + NS_SWIFT_UNAVAILABLE(""); + +/** + @param work A block to perform any operations needed to resolve the promise. + @returns A promise that resolves with an `NSNumber` wrapping YES/NO when error is `nil`. + Otherwise rejects with the error. + */ ++ (FBLPromise*)wrapBoolOrErrorCompletion: + (void (^)(FBLPromiseBoolOrErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); + +/** + @param queue A queue to invoke the `work` block on. + @param work A block to perform any operations needed to resolve the promise. + @returns A promise that resolves with an `NSNumber` wrapping YES/NO when error is `nil`. + Otherwise rejects with the error. + */ ++ (FBLPromise*)onQueue:(dispatch_queue_t)queue + wrapBoolOrErrorCompletion:(void (^)(FBLPromiseBoolOrErrorCompletion handler))work + NS_SWIFT_UNAVAILABLE(""); + +/** + @param work A block to perform any operations needed to resolve the promise. + @returns A promise that resolves with an `NSNumber` wrapping an integer. + */ ++ (FBLPromise*)wrapIntegerCompletion:(void (^)(FBLPromiseIntegerCompletion handler))work + NS_SWIFT_UNAVAILABLE(""); + +/** + @param queue A queue to invoke the `work` block on. + @param work A block to perform any operations needed to resolve the promise. + @returns A promise that resolves with an `NSNumber` wrapping an integer. + */ ++ (FBLPromise*)onQueue:(dispatch_queue_t)queue + wrapIntegerCompletion:(void (^)(FBLPromiseIntegerCompletion handler))work + NS_SWIFT_UNAVAILABLE(""); + +/** + @param work A block to perform any operations needed to resolve the promise. + @returns A promise that resolves with an `NSNumber` wrapping an integer when error is `nil`. + Otherwise rejects with the error. + */ ++ (FBLPromise*)wrapIntegerOrErrorCompletion: + (void (^)(FBLPromiseIntegerOrErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); + +/** + @param queue A queue to invoke the `work` block on. + @param work A block to perform any operations needed to resolve the promise. + @returns A promise that resolves with an `NSNumber` wrapping an integer when error is `nil`. + Otherwise rejects with the error. + */ ++ (FBLPromise*)onQueue:(dispatch_queue_t)queue + wrapIntegerOrErrorCompletion:(void (^)(FBLPromiseIntegerOrErrorCompletion handler))work + NS_SWIFT_UNAVAILABLE(""); + +/** + @param work A block to perform any operations needed to resolve the promise. + @returns A promise that resolves with an `NSNumber` wrapping a double. + */ ++ (FBLPromise*)wrapDoubleCompletion:(void (^)(FBLPromiseDoubleCompletion handler))work + NS_SWIFT_UNAVAILABLE(""); + +/** + @param queue A queue to invoke the `work` block on. + @param work A block to perform any operations needed to resolve the promise. + @returns A promise that resolves with an `NSNumber` wrapping a double. + */ ++ (FBLPromise*)onQueue:(dispatch_queue_t)queue + wrapDoubleCompletion:(void (^)(FBLPromiseDoubleCompletion handler))work + NS_SWIFT_UNAVAILABLE(""); + +/** + @param work A block to perform any operations needed to resolve the promise. + @returns A promise that resolves with an `NSNumber` wrapping a double when error is `nil`. + Otherwise rejects with the error. + */ ++ (FBLPromise*)wrapDoubleOrErrorCompletion: + (void (^)(FBLPromiseDoubleOrErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); + +/** + @param queue A queue to invoke the `work` block on. + @param work A block to perform any operations needed to resolve the promise. + @returns A promise that resolves with an `NSNumber` wrapping a double when error is `nil`. + Otherwise rejects with the error. + */ ++ (FBLPromise*)onQueue:(dispatch_queue_t)queue + wrapDoubleOrErrorCompletion:(void (^)(FBLPromiseDoubleOrErrorCompletion handler))work + NS_SWIFT_UNAVAILABLE(""); + +@end + +/** + Convenience dot-syntax wrappers for `FBLPromise` `wrap` operators. + Usage: FBLPromise.wrapCompletion(^(FBLPromiseCompletion handler) {...}) + */ +@interface FBLPromise(DotSyntax_WrapAdditions) + ++ (FBLPromise* (^)(void (^)(FBLPromiseCompletion)))wrapCompletion FBL_PROMISES_DOT_SYNTAX + NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromiseCompletion)))wrapCompletionOn + FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise* (^)(void (^)(FBLPromiseObjectCompletion)))wrapObjectCompletion + FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromiseObjectCompletion)))wrapObjectCompletionOn + FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise* (^)(void (^)(FBLPromiseErrorCompletion)))wrapErrorCompletion FBL_PROMISES_DOT_SYNTAX + NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromiseErrorCompletion)))wrapErrorCompletionOn + FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise* (^)(void (^)(FBLPromiseObjectOrErrorCompletion)))wrapObjectOrErrorCompletion + FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise* (^)(dispatch_queue_t, + void (^)(FBLPromiseObjectOrErrorCompletion)))wrapObjectOrErrorCompletionOn + FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise* (^)(void (^)(FBLPromiseErrorOrObjectCompletion)))wrapErrorOrObjectCompletion + FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise* (^)(dispatch_queue_t, + void (^)(FBLPromiseErrorOrObjectCompletion)))wrapErrorOrObjectCompletionOn + FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise* (^)(void (^)(FBLPromise2ObjectsOrErrorCompletion))) + wrap2ObjectsOrErrorCompletion FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromise2ObjectsOrErrorCompletion))) + wrap2ObjectsOrErrorCompletionOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise* (^)(void (^)(FBLPromiseBoolCompletion)))wrapBoolCompletion + FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise* (^)(dispatch_queue_t, + void (^)(FBLPromiseBoolCompletion)))wrapBoolCompletionOn + FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise* (^)(void (^)(FBLPromiseBoolOrErrorCompletion)))wrapBoolOrErrorCompletion + FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise* (^)(dispatch_queue_t, + void (^)(FBLPromiseBoolOrErrorCompletion)))wrapBoolOrErrorCompletionOn + FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise* (^)(void (^)(FBLPromiseIntegerCompletion)))wrapIntegerCompletion + FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise* (^)(dispatch_queue_t, + void (^)(FBLPromiseIntegerCompletion)))wrapIntegerCompletionOn + FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise* (^)(void (^)(FBLPromiseIntegerOrErrorCompletion))) + wrapIntegerOrErrorCompletion FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromiseIntegerOrErrorCompletion))) + wrapIntegerOrErrorCompletionOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise* (^)(void (^)(FBLPromiseDoubleCompletion)))wrapDoubleCompletion + FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise* (^)(dispatch_queue_t, + void (^)(FBLPromiseDoubleCompletion)))wrapDoubleCompletionOn + FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise* (^)(void (^)(FBLPromiseDoubleOrErrorCompletion))) + wrapDoubleOrErrorCompletion FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); ++ (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromiseDoubleOrErrorCompletion))) + wrapDoubleOrErrorCompletionOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise.h b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise.h new file mode 100644 index 0000000..cec6336 --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise.h @@ -0,0 +1,82 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromiseError.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + Promises synchronization construct in Objective-C. + */ +@interface FBLPromise<__covariant Value> : NSObject + +/** + Default dispatch queue used for `FBLPromise`, which is `main` if a queue is not specified. + */ +@property(class) dispatch_queue_t defaultDispatchQueue NS_REFINED_FOR_SWIFT; + +/** + Creates a pending promise. + */ ++ (instancetype)pendingPromise NS_REFINED_FOR_SWIFT; + +/** + Creates a resolved promise. + + @param resolution An object to resolve the promise with: either a value or an error. + @return A new resolved promise. + */ ++ (instancetype)resolvedWith:(nullable id)resolution NS_REFINED_FOR_SWIFT; + +/** + Synchronously fulfills the promise with a value. + + @param value An arbitrary value to fulfill the promise with, including `nil`. + */ +- (void)fulfill:(nullable Value)value NS_REFINED_FOR_SWIFT; + +/** + Synchronously rejects the promise with an error. + + @param error An error to reject the promise with. + */ +- (void)reject:(NSError *)error NS_REFINED_FOR_SWIFT; + ++ (instancetype)new NS_UNAVAILABLE; +- (instancetype)init NS_UNAVAILABLE; + +@end + +#ifdef FBL_PROMISES_DOT_SYNTAX_IS_DEPRECATED +#define FBL_PROMISES_DOT_SYNTAX __attribute__((deprecated)) +#else +#define FBL_PROMISES_DOT_SYNTAX +#endif + +@interface FBLPromise(DotSyntaxAdditions) + +/** + Convenience dot-syntax wrappers for FBLPromise. + Usage: FBLPromise.pending() + FBLPromise.resolved(value) + + */ ++ (instancetype (^)(void))pending FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); ++ (instancetype (^)(id __nullable))resolved FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromiseError.h b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromiseError.h new file mode 100644 index 0000000..d37af53 --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromiseError.h @@ -0,0 +1,43 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +FOUNDATION_EXTERN NSErrorDomain const FBLPromiseErrorDomain NS_REFINED_FOR_SWIFT; + +/** + Possible error codes in `FBLPromiseErrorDomain`. + */ +typedef NS_ENUM(NSInteger, FBLPromiseErrorCode) { + /** Promise failed to resolve in time. */ + FBLPromiseErrorCodeTimedOut = 1, + /** Validation predicate returned false. */ + FBLPromiseErrorCodeValidationFailure = 2, +} NS_REFINED_FOR_SWIFT; + +NS_INLINE BOOL FBLPromiseErrorIsTimedOut(NSError *error) NS_SWIFT_UNAVAILABLE("") { + return error.domain == FBLPromiseErrorDomain && + error.code == FBLPromiseErrorCodeTimedOut; +} + +NS_INLINE BOOL FBLPromiseErrorIsValidationFailure(NSError *error) NS_SWIFT_UNAVAILABLE("") { + return error.domain == FBLPromiseErrorDomain && + error.code == FBLPromiseErrorCodeValidationFailure; +} + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromisePrivate.h b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromisePrivate.h new file mode 100644 index 0000000..7a132f2 --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromisePrivate.h @@ -0,0 +1,66 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise+Testing.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + Miscellaneous low-level private interfaces available to extend standard FBLPromise functionality. + */ +@interface FBLPromise() + +typedef void (^FBLPromiseOnFulfillBlock)(Value __nullable value) NS_SWIFT_UNAVAILABLE(""); +typedef void (^FBLPromiseOnRejectBlock)(NSError *error) NS_SWIFT_UNAVAILABLE(""); +typedef id __nullable (^__nullable FBLPromiseChainedFulfillBlock)(Value __nullable value) + NS_SWIFT_UNAVAILABLE(""); +typedef id __nullable (^__nullable FBLPromiseChainedRejectBlock)(NSError *error) + NS_SWIFT_UNAVAILABLE(""); + +/** + Creates a pending promise. + */ +- (instancetype)initPending NS_SWIFT_UNAVAILABLE(""); + +/** + Creates a resolved promise. + + @param resolution An object to resolve the promise with: either a value or an error. + @return A new resolved promise. + */ +- (instancetype)initWithResolution:(nullable id)resolution NS_SWIFT_UNAVAILABLE(""); + +/** + Invokes `fulfill` and `reject` blocks on `queue` when the receiver gets either fulfilled or + rejected respectively. + */ +- (void)observeOnQueue:(dispatch_queue_t)queue + fulfill:(FBLPromiseOnFulfillBlock)onFulfill + reject:(FBLPromiseOnRejectBlock)onReject NS_SWIFT_UNAVAILABLE(""); + +/** + Returns a new promise which gets resolved with the return value of `chainedFulfill` or + `chainedReject` blocks respectively. The blocks are invoked when the receiver gets either + fulfilled or rejected. If `nil` is passed to either block arg, the returned promise is resolved + with the same resolution as the receiver. + */ +- (FBLPromise *)chainOnQueue:(dispatch_queue_t)queue + chainedFulfill:(FBLPromiseChainedFulfillBlock)chainedFulfill + chainedReject:(FBLPromiseChainedRejectBlock)chainedReject NS_SWIFT_UNAVAILABLE(""); + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromises.h b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromises.h new file mode 100644 index 0000000..2d90bad --- /dev/null +++ b/!main project/Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromises.h @@ -0,0 +1,32 @@ +/** + Copyright 2018 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "FBLPromise+All.h" +#import "FBLPromise+Always.h" +#import "FBLPromise+Any.h" +#import "FBLPromise+Async.h" +#import "FBLPromise+Await.h" +#import "FBLPromise+Catch.h" +#import "FBLPromise+Delay.h" +#import "FBLPromise+Do.h" +#import "FBLPromise+Race.h" +#import "FBLPromise+Recover.h" +#import "FBLPromise+Reduce.h" +#import "FBLPromise+Retry.h" +#import "FBLPromise+Then.h" +#import "FBLPromise+Timeout.h" +#import "FBLPromise+Validate.h" +#import "FBLPromise+Wrap.h" diff --git a/!main project/Pods/Realm/LICENSE b/!main project/Pods/Realm/LICENSE new file mode 100644 index 0000000..57a0e0b --- /dev/null +++ b/!main project/Pods/Realm/LICENSE @@ -0,0 +1,248 @@ +TABLE OF CONTENTS + +1. Apache License version 2.0 +2. Realm Components +3. Export Compliance + +1. ------------------------------------------------------------------------------- + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +2. ------------------------------------------------------------------------------- + +REALM COMPONENTS + +This software contains components with separate copyright and license terms. +Your use of these components is subject to the terms and conditions of the +following licenses. + +For the Realm Platform Extensions component + + Realm Platform Extensions License + + Copyright (c) 2011-2017 Realm Inc All rights reserved + + Redistribution and use in binary form, with or without modification, is + permitted provided that the following conditions are met: + + 1. You agree not to attempt to decompile, disassemble, reverse engineer or + otherwise discover the source code from which the binary code was derived. + You may, however, access and obtain a separate license for most of the + source code from which this Software was created, at + http://realm.io/pricing/. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +3. ------------------------------------------------------------------------------- + +EXPORT COMPLIANCE + +You understand that the Software may contain cryptographic functions that may be +subject to export restrictions, and you represent and warrant that you are not +(i) located in a jurisdiction that is subject to United States economic +sanctions (“Prohibited Jurisdiction”), including Cuba, Iran, North Korea, +Sudan, Syria or the Crimea region, (ii) a person listed on any U.S. government +blacklist (to include the List of Specially Designated Nationals and Blocked +Persons or the Consolidated Sanctions List administered by the U.S. Department +of the Treasury’s Office of Foreign Assets Control, or the Denied Persons List +or Entity List administered by the U.S. Department of Commerce) +(“Sanctioned Person”), or (iii) controlled or 50% or more owned by a Sanctioned +Person. + +You agree to comply with all export, re-export and import restrictions and +regulations of the U.S. Department of Commerce or other agency or authority of +the United States or other applicable countries. You also agree not to transfer, +or authorize the transfer of, directly or indirectly, of the Software to any +Prohibited Jurisdiction, or otherwise in violation of any such restrictions or +regulations. diff --git a/!main project/Pods/Realm/README.md b/!main project/Pods/Realm/README.md new file mode 100644 index 0000000..d111286 --- /dev/null +++ b/!main project/Pods/Realm/README.md @@ -0,0 +1,73 @@ +![Realm](https://github.com/realm/realm-cocoa/raw/master/logo.png) + +Realm is a mobile database that runs directly inside phones, tablets or wearables. +This repository holds the source code for the iOS, macOS, tvOS & watchOS versions of Realm Swift & Realm Objective-C. + +## Features + +* **Mobile-first:** Realm is the first database built from the ground up to run directly inside phones, tablets and wearables. +* **Simple:** Data is directly [exposed as objects](https://realm.io/docs/objc/latest/#models) and [queryable by code](https://realm.io/docs/objc/latest/#queries), removing the need for ORM's riddled with performance & maintenance issues. Most of our users pick it up intuitively, getting simple apps up & running in minutes. +* **Modern:** Realm supports relationships, generics, vectorization and Swift. +* **Fast:** Realm is faster than even raw SQLite on common operations, while maintaining an extremely rich feature set. + +## Getting Started + +Please see the detailed instructions in our docs to add [Realm Objective-C](https://realm.io/docs/objc/latest/#installation) _or_ [Realm Swift](https://realm.io/docs/swift/latest/#installation) to your Xcode project. + +## Documentation + +### Realm Objective-C + +The documentation can be found at [realm.io/docs/objc/latest](https://realm.io/docs/objc/latest). +The API reference is located at [realm.io/docs/objc/latest/api/](https://realm.io/docs/objc/latest/api/). + +### Realm Swift + +The documentation can be found at [realm.io/docs/swift/latest](https://realm.io/docs/swift/latest). +The API reference is located at [realm.io/docs/swift/latest/api/](https://realm.io/docs/swift/latest/api/). + +## Getting Help + +- **Need help with your code?**: Look for previous questions with the[`realm` tag](https://stackoverflow.com/questions/tagged/realm?sort=newest) on Stack Overflow or [ask a new question](https://stackoverflow.com/questions/ask?tags=realm). For general discussion that might be considered too broad for Stack Overflow, use the [Realm Forums](https://forums.realm.io). +- **Have a bug to report?** [Open a GitHub issue](https://github.com/realm/realm-cocoa/issues/new). If possible, include the version of Realm, a full log, the Realm file, and a project that shows the issue. +- **Have a feature request?** [Open a GitHub issue](https://github.com/realm/realm-cocoa/issues/new). Tell us what the feature should do and why you want the feature. + +## Building Realm + +In case you don't want to use the precompiled version, you can build Realm yourself from source. + +Prerequisites: + +* Building Realm requires Xcode 8.x. +* If cloning from git, submodules are required: `git submodule update --init --recursive`. +* Building Realm documentation requires [jazzy](https://github.com/realm/jazzy) + +Once you have all the necessary prerequisites, building Realm.framework just takes a single command: `sh build.sh build`. You'll need an internet connection the first time you build Realm to download the core binary. + +Run `sh build.sh help` to see all the actions you can perform (build ios/osx, generate docs, test, etc.). + +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) for more details! + +This project adheres to the [Contributor Covenant Code of Conduct](https://realm.io/conduct). +By participating, you are expected to uphold this code. Please report +unacceptable behavior to [info@realm.io](mailto:info@realm.io). + +## License + +Realm Objective-C & Realm Swift are published under the Apache 2.0 license. +Realm Core is also published under the Apache 2.0 license and is available +[here](https://github.com/realm/realm-core). + +**This product is not being made available to any person located in Cuba, Iran, +North Korea, Sudan, Syria or the Crimea region, or to any other person that is +not eligible to receive the product under U.S. law.** + +## Feedback + +**_If you use Realm and are happy with it, all we ask is that you please consider sending out a tweet mentioning [@realm](https://twitter.com/realm) to share your thoughts!_** + +**_And if you don't like it, please let us know what you would like improved, so we can fix it!_** + +![analytics](https://ga-beacon.appspot.com/UA-50247013-2/realm-cocoa/README?pixel) diff --git a/!main project/Pods/Realm/Realm/NSError+RLMSync.m b/!main project/Pods/Realm/Realm/NSError+RLMSync.m new file mode 100644 index 0000000..e8c2590 --- /dev/null +++ b/!main project/Pods/Realm/Realm/NSError+RLMSync.m @@ -0,0 +1,43 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2017 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "NSError+RLMSync.h" + +#import "RLMSyncUtil.h" + +@implementation NSError (RLMSync) + +- (RLMSyncErrorActionToken *)rlmSync_errorActionToken { + if (self.domain != RLMSyncErrorDomain) { + return nil; + } + if (self.code == RLMSyncErrorClientResetError + || self.code == RLMSyncErrorPermissionDeniedError) { + return (RLMSyncErrorActionToken *)self.userInfo[kRLMSyncErrorActionTokenKey]; + } + return nil; +} + +- (NSString *)rlmSync_clientResetBackedUpRealmPath { + if (self.domain == RLMSyncErrorDomain && self.code == RLMSyncErrorClientResetError) { + return self.userInfo[kRLMSyncPathOfRealmBackupCopyKey]; + } + return nil; +} + +@end diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/binding_callback_thread_observer.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/binding_callback_thread_observer.cpp new file mode 100644 index 0000000..0c388d0 --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/binding_callback_thread_observer.cpp @@ -0,0 +1,23 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2017 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "binding_callback_thread_observer.hpp" + +namespace realm { +BindingCallbackThreadObserver* g_binding_callback_thread_observer = nullptr; +} diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/collection_notifications.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/collection_notifications.cpp new file mode 100644 index 0000000..03fad73 --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/collection_notifications.cpp @@ -0,0 +1,61 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "collection_notifications.hpp" + +#include "impl/collection_notifier.hpp" + +using namespace realm; +using namespace realm::_impl; + +NotificationToken::NotificationToken(std::shared_ptr<_impl::CollectionNotifier> notifier, uint64_t token) +: m_notifier(std::move(notifier)), m_token(token) +{ +} + +NotificationToken::~NotificationToken() +{ + // m_notifier itself (and not just the pointed-to thing) needs to be accessed + // atomically to ensure that there are no data races when the token is + // destroyed after being modified on a different thread. + // This is needed despite the token not being thread-safe in general as + // users find it very surprising for obj-c objects to care about what + // thread they are deallocated on. + if (auto notifier = m_notifier.exchange({})) { + notifier->remove_callback(m_token); + } +} + +NotificationToken::NotificationToken(NotificationToken&&) = default; + +NotificationToken& NotificationToken::operator=(realm::NotificationToken&& rgt) +{ + if (this != &rgt) { + if (auto notifier = m_notifier.exchange({})) { + notifier->remove_callback(m_token); + } + m_notifier = std::move(rgt.m_notifier); + m_token = rgt.m_token; + } + return *this; +} + +void NotificationToken::suppress_next() +{ + m_notifier.load()->suppress_next_notification(m_token); +} diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/impl/apple/external_commit_helper.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/impl/apple/external_commit_helper.cpp new file mode 100644 index 0000000..fd56c9e --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/impl/apple/external_commit_helper.cpp @@ -0,0 +1,243 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "impl/external_commit_helper.hpp" +#include "impl/realm_coordinator.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace realm; +using namespace realm::_impl; + +namespace { +// Write a byte to a pipe to notify anyone waiting for data on the pipe +void notify_fd(int fd, int read_fd) +{ + while (true) { + char c = 0; + ssize_t ret = write(fd, &c, 1); + if (ret == 1) { + break; + } + + // If the pipe's buffer is full, we need to read some of the old data in + // it to make space. We don't just read in the code waiting for + // notifications so that we can notify multiple waiters with a single + // write. + assert(ret == -1 && errno == EAGAIN); + char buff[1024]; + read(read_fd, buff, sizeof buff); + } +} + +} // anonymous namespace + +void ExternalCommitHelper::FdHolder::close() +{ + if (m_fd != -1) { + ::close(m_fd); + } + m_fd = -1; +} + +// Inter-thread and inter-process notifications of changes are done using a +// named pipe in the filesystem next to the Realm file. Everyone who wants to be +// notified of commits waits for data to become available on the pipe, and anyone +// who commits a write transaction writes data to the pipe after releasing the +// write lock. Note that no one ever actually *reads* from the pipe: the data +// actually written is meaningless, and trying to read from a pipe from multiple +// processes at once is fraught with race conditions. + +// When a RLMRealm instance is created, we add a CFRunLoopSource to the current +// thread's runloop. On each cycle of the run loop, the run loop checks each of +// its sources for work to do, which in the case of CFRunLoopSource is just +// checking if CFRunLoopSourceSignal has been called since the last time it ran, +// and if so invokes the function pointer supplied when the source is created, +// which in our case just invokes `[realm handleExternalChange]`. + +// Listening for external changes is done using kqueue() on a background thread. +// kqueue() lets us efficiently wait until the amount of data which can be read +// from one or more file descriptors has changed, and tells us which of the file +// descriptors it was that changed. We use this to wait on both the shared named +// pipe, and a local anonymous pipe. When data is written to the named pipe, we +// signal the runloop source and wake up the target runloop, and when data is +// written to the anonymous pipe the background thread removes the runloop +// source from the runloop and and shuts down. +ExternalCommitHelper::ExternalCommitHelper(RealmCoordinator& parent) +: m_parent(parent) +{ + m_kq = kqueue(); + if (m_kq == -1) { + throw std::system_error(errno, std::system_category()); + } + +#if !TARGET_OS_TV + + + // Object Store needs to create a named pipe in order to coordinate notifications. + // This can be a problem on some file systems (e.g. FAT32) or due to security policies in SELinux. Most commonly + // it is a problem when saving Realms on external storage: https://stackoverflow.com/questions/2740321/how-to-create-named-pipe-mkfifo-in-android + // + // For this reason we attempt to create this file in a temporary location known to be safe to write these files. + // + // In order of priority we attempt to write the file in the following locations: + // 1) Next to the Realm file itself + // 2) A location defined by `Realm::Config::fifo_files_fallback_path` + // 3) A location defined by `SharedGroupOptions::set_sys_tmp_dir()` + // + // Core has a similar policy for its named pipes. + // + // Also see https://github.com/realm/realm-java/issues/3140 + // Note that hash collisions are okay here because they just result in doing extra work instead of resulting + // in correctness problems. + + std::string path; + std::string temp_dir = util::normalize_dir(parent.get_config().fifo_files_fallback_path); + std::string sys_temp_dir = util::normalize_dir(SharedGroupOptions::get_sys_tmp_dir()); + + path = parent.get_path() + ".note"; + bool fifo_created = realm::util::try_create_fifo(path); + if (!fifo_created && !temp_dir.empty()) { + path = util::format("%1realm_%2.note", temp_dir, std::hash()(parent.get_path())); + fifo_created = realm::util::try_create_fifo(path); + } + if (!fifo_created && !sys_temp_dir.empty()) { + path = util::format("%1realm_%2.note", sys_temp_dir, std::hash()(parent.get_path())); + realm::util::create_fifo(path); + } + + m_notify_fd = open(path.c_str(), O_RDWR); + if (m_notify_fd == -1) { + throw std::system_error(errno, std::system_category()); + } + + // Make writing to the pipe return -1 when the pipe's buffer is full + // rather than blocking until there's space available + int ret = fcntl(m_notify_fd, F_SETFL, O_NONBLOCK); + if (ret == -1) { + throw std::system_error(errno, std::system_category()); + } + +#else // !TARGET_OS_TV + + // tvOS does not support named pipes, so use an anonymous pipe instead + int notification_pipe[2]; + int ret = pipe(notification_pipe); + if (ret == -1) { + throw std::system_error(errno, std::system_category()); + } + + m_notify_fd = notification_pipe[0]; + m_notify_fd_write = notification_pipe[1]; + +#endif // TARGET_OS_TV + + // Create the anonymous pipe for shutdown notifications + int shutdown_pipe[2]; + ret = pipe(shutdown_pipe); + if (ret == -1) { + throw std::system_error(errno, std::system_category()); + } + + m_shutdown_read_fd = shutdown_pipe[0]; + m_shutdown_write_fd = shutdown_pipe[1]; + + m_thread = std::thread([=] { + try { + listen(); + } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + catch (std::exception const& e) { + fprintf(stderr, "uncaught exception in notifier thread: %s: %s\n", typeid(e).name(), e.what()); + asl_log(nullptr, nullptr, ASL_LEVEL_ERR, "uncaught exception in notifier thread: %s: %s", typeid(e).name(), e.what()); + throw; + } + catch (...) { + fprintf(stderr, "uncaught exception in notifier thread\n"); + asl_log(nullptr, nullptr, ASL_LEVEL_ERR, "uncaught exception in notifier thread"); + throw; + } +#pragma clang diagnostic pop + }); +} + +ExternalCommitHelper::~ExternalCommitHelper() +{ + notify_fd(m_shutdown_write_fd, m_shutdown_read_fd); + m_thread.join(); // Wait for the thread to exit +} + +void ExternalCommitHelper::listen() +{ + pthread_setname_np("Realm notification listener"); + + // Set up the kqueue + // EVFILT_READ indicates that we care about data being available to read + // on the given file descriptor. + // EV_CLEAR makes it wait for the amount of data available to be read to + // change rather than just returning when there is any data to read. + struct kevent ke[2]; + EV_SET(&ke[0], m_notify_fd, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, 0); + EV_SET(&ke[1], m_shutdown_read_fd, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, 0); + int ret = kevent(m_kq, ke, 2, nullptr, 0, nullptr); + assert(ret == 0); + + while (true) { + struct kevent event; + // Wait for data to become on either fd + // Return code is number of bytes available or -1 on error + ret = kevent(m_kq, nullptr, 0, &event, 1, nullptr); + if (ret == 0 || (ret < 0 && errno == EINTR)) { + // Spurious wakeup; just wait again + continue; + } + assert(ret > 0); + + // Check which file descriptor had activity: if it's the shutdown + // pipe, then someone called -stop; otherwise it's the named pipe + // and someone committed a write transaction + if (event.ident == (uint32_t)m_shutdown_read_fd) { + return; + } + assert(event.ident == (uint32_t)m_notify_fd); + + m_parent.on_change(); + } +} + +void ExternalCommitHelper::notify_others() +{ + if (m_notify_fd_write != -1) { + notify_fd(m_notify_fd_write, m_notify_fd); + } + else { + notify_fd(m_notify_fd, m_notify_fd); + } +} diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/impl/apple/keychain_helper.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/impl/apple/keychain_helper.cpp new file mode 100644 index 0000000..8c9000c --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/impl/apple/keychain_helper.cpp @@ -0,0 +1,143 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "impl/apple/keychain_helper.hpp" + + +#include +#include + +#include + +#include + +using realm::util::CFPtr; +using realm::util::adoptCF; +using realm::util::retainCF; + +namespace realm { +namespace keychain { + +KeychainAccessException::KeychainAccessException(int32_t error_code) +: std::runtime_error(util::format("Keychain returned unexpected status code: %1", error_code)) { } + +namespace { + +constexpr size_t key_size = 64; + +#if !TARGET_IPHONE_SIMULATOR +CFPtr convert_string(const std::string& string) +{ + auto result = adoptCF(CFStringCreateWithBytes(nullptr, reinterpret_cast(string.data()), + string.size(), kCFStringEncodingASCII, false)); + if (!result) { + throw std::bad_alloc(); + } + return result; +} +#endif + +CFPtr build_search_dictionary(CFStringRef account, CFStringRef service, + __unused util::Optional group) +{ + auto d = adoptCF(CFDictionaryCreateMutable(nullptr, 0, &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + if (!d) + throw std::bad_alloc(); + + CFDictionaryAddValue(d.get(), kSecClass, kSecClassGenericPassword); + CFDictionaryAddValue(d.get(), kSecReturnData, kCFBooleanTrue); + CFDictionaryAddValue(d.get(), kSecAttrAccessible, kSecAttrAccessibleAlways); + CFDictionaryAddValue(d.get(), kSecAttrAccount, account); + CFDictionaryAddValue(d.get(), kSecAttrService, service); +#if !TARGET_IPHONE_SIMULATOR + if (group) + CFDictionaryAddValue(d.get(), kSecAttrAccessGroup, convert_string(*group).get()); +#endif + return d; +} + +/// Get the encryption key for a given service, returning it only if it exists. +util::Optional> get_key(CFStringRef account, CFStringRef service) +{ + auto search_dictionary = build_search_dictionary(account, service, none); + CFDataRef retained_key_data; + if (OSStatus status = SecItemCopyMatching(search_dictionary.get(), (CFTypeRef *)&retained_key_data)) { + if (status != errSecItemNotFound) + throw KeychainAccessException(status); + + // Key was not found. + return none; + } + + // Key was previously stored. Extract it. + CFPtr key_data = adoptCF(retained_key_data); + if (key_size != CFDataGetLength(key_data.get())) + throw std::runtime_error("Password stored in keychain was not expected size."); + + auto key_bytes = reinterpret_cast(CFDataGetBytePtr(key_data.get())); + return std::vector(key_bytes, key_bytes + key_size); +} + +void set_key(const std::vector& key, CFStringRef account, CFStringRef service) +{ + auto search_dictionary = build_search_dictionary(account, service, none); + auto key_data = adoptCF(CFDataCreate(nullptr, reinterpret_cast(key.data()), key_size)); + if (!key_data) + throw std::bad_alloc(); + + CFDictionaryAddValue(search_dictionary.get(), kSecValueData, key_data.get()); + if (OSStatus status = SecItemAdd(search_dictionary.get(), nullptr)) + throw KeychainAccessException(status); +} + +} // anonymous namespace + +std::vector metadata_realm_encryption_key(bool check_legacy_service) +{ + CFStringRef account = CFSTR("metadata"); + CFStringRef legacy_service = CFSTR("io.realm.sync.keychain"); + + CFPtr service; + if (CFStringRef bundle_id = CFBundleGetIdentifier(CFBundleGetMainBundle())) + service = adoptCF(CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ - Realm Sync Metadata Key"), bundle_id)); + else { + service = retainCF(legacy_service); + check_legacy_service = false; + } + + // Try retrieving the key. + if (auto existing_key = get_key(account, service.get())) { + return *existing_key; + } else if (check_legacy_service) { + // See if there's a key stored using the legacy shared keychain item. + if (auto existing_legacy_key = get_key(account, legacy_service)) { + // If so, copy it to the per-app keychain item before returning it. + set_key(*existing_legacy_key, account, service.get()); + return *existing_legacy_key; + } + } + // Make a completely new key. + std::vector key(key_size); + arc4random_buf(key.data(), key_size); + set_key(key, account, service.get()); + return key; +} + +} // keychain +} // realm diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/impl/collection_change_builder.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/impl/collection_change_builder.cpp new file mode 100644 index 0000000..40b5754 --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/impl/collection_change_builder.cpp @@ -0,0 +1,887 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "impl/collection_change_builder.hpp" + +#include +#include + +#include + +using namespace realm; +using namespace realm::_impl; + +CollectionChangeBuilder::CollectionChangeBuilder(IndexSet deletions, + IndexSet insertions, + IndexSet modifications, + std::vector moves) +: CollectionChangeSet({std::move(deletions), std::move(insertions), std::move(modifications), {}, std::move(moves)}) +{ + for (auto&& move : this->moves) { + this->deletions.add(move.from); + this->insertions.add(move.to); + } +} + +void CollectionChangeBuilder::merge(CollectionChangeBuilder&& c) +{ + if (c.empty()) + return; + if (empty()) { + *this = std::move(c); + return; + } + + verify(); + c.verify(); + + auto for_each_col = [&](auto&& f) { + f(modifications, c.modifications); + if (m_track_columns) { + if (columns.size() < c.columns.size()) + columns.resize(c.columns.size()); + else if (columns.size() > c.columns.size()) + c.columns.resize(columns.size()); + for (size_t i = 0; i < columns.size(); ++i) + f(columns[i], c.columns[i]); + } + }; + + // First update any old moves + if (!c.moves.empty() || !c.deletions.empty() || !c.insertions.empty()) { + auto it = std::remove_if(begin(moves), end(moves), [&](auto& old) { + // Check if the moved row was moved again, and if so just update the destination + auto it = find_if(begin(c.moves), end(c.moves), [&](auto const& m) { + return old.to == m.from; + }); + if (it != c.moves.end()) { + for_each_col([&](auto& col, auto& other) { + if (col.contains(it->from)) + other.add(it->to); + }); + old.to = it->to; + *it = c.moves.back(); + c.moves.pop_back(); + return false; + } + + // Check if the destination was deleted + // Removing the insert for this move will happen later + if (c.deletions.contains(old.to)) + return true; + + // Update the destination to adjust for any new insertions and deletions + old.to = c.insertions.shift(c.deletions.unshift(old.to)); + return false; + }); + moves.erase(it, end(moves)); + } + + // Ignore new moves of rows which were previously inserted (the implicit + // delete from the move will remove the insert) + if (!insertions.empty() && !c.moves.empty()) { + c.moves.erase(std::remove_if(begin(c.moves), end(c.moves), + [&](auto const& m) { return insertions.contains(m.from); }), + end(c.moves)); + } + + // Ensure that any previously modified rows which were moved are still modified + if (!modifications.empty() && !c.moves.empty()) { + for (auto const& move : c.moves) { + for_each_col([&](auto& col, auto& other) { + if (col.contains(move.from)) + other.add(move.to); + }); + } + } + + // Update the source position of new moves to compensate for the changes made + // in the old changeset + if (!deletions.empty() || !insertions.empty()) { + for (auto& move : c.moves) + move.from = deletions.shift(insertions.unshift(move.from)); + } + + moves.insert(end(moves), begin(c.moves), end(c.moves)); + + // New deletion indices have been shifted by the insertions, so unshift them + // before adding + deletions.add_shifted_by(insertions, c.deletions); + + // Drop any inserted-then-deleted rows, then merge in new insertions + insertions.erase_at(c.deletions); + insertions.insert_at(c.insertions); + + clean_up_stale_moves(); + + for_each_col([&](auto& col, auto& other) { + col.erase_at(c.deletions); + col.shift_for_insert_at(c.insertions); + col.add(other); + }); + + c = {}; + verify(); +} + +void CollectionChangeBuilder::clean_up_stale_moves() +{ + // Look for moves which are now no-ops, and remove them plus the associated + // insert+delete. Note that this isn't just checking for from == to due to + // that rows can also be shifted by other inserts and deletes + moves.erase(std::remove_if(begin(moves), end(moves), [&](auto const& move) { + if (move.from - deletions.count(0, move.from) != move.to - insertions.count(0, move.to)) + return false; + deletions.remove(move.from); + insertions.remove(move.to); + return true; + }), end(moves)); +} + +void CollectionChangeBuilder::parse_complete() +{ + moves.reserve(m_move_mapping.size()); + for (auto move : m_move_mapping) { + REALM_ASSERT_DEBUG(deletions.contains(move.second)); + REALM_ASSERT_DEBUG(insertions.contains(move.first)); + if (move.first == move.second) { + deletions.remove(move.second); + insertions.remove(move.first); + } + else + moves.push_back({move.second, move.first}); + } + m_move_mapping.clear(); + std::sort(begin(moves), end(moves), + [](auto const& a, auto const& b) { return a.from < b.from; }); +} + +void CollectionChangeBuilder::modify(size_t ndx, size_t col) +{ + modifications.add(ndx); + if (!m_track_columns || col == IndexSet::npos) + return; + + if (col >= columns.size()) + columns.resize(col + 1); + columns[col].add(ndx); +} + +template +void CollectionChangeBuilder::for_each_col(Func&& f) +{ + f(modifications); + if (m_track_columns) { + for (auto& col : columns) + f(col); + } +} + +void CollectionChangeBuilder::insert(size_t index, size_t count, bool track_moves) +{ + REALM_ASSERT(count != 0); + + for_each_col([=](auto& col) { col.shift_for_insert_at(index, count); }); + if (!track_moves) + return; + + insertions.insert_at(index, count); + + for (auto& move : moves) { + if (move.to >= index) + move.to += count; + } + + if (m_move_mapping.empty()) + return; + + // m_move_mapping is new_ndx -> old_ndx, so updating the keys requires + // deleting and re-inserting at the new index + std::vector> shifted; + for (auto it = m_move_mapping.begin(); it != m_move_mapping.end(); ) { + if (it->first >= index) { + shifted.emplace_back(it->first + count, it->second); + it = m_move_mapping.erase(it); + } + else { + ++it; + } + } + for (auto& pair : shifted) + m_move_mapping.insert(pair); +} + +void CollectionChangeBuilder::erase(size_t index) +{ + for_each_col([=](auto& col) { col.erase_at(index); }); + size_t unshifted = insertions.erase_or_unshift(index); + if (unshifted != IndexSet::npos) + deletions.add_shifted(unshifted); + + for (size_t i = 0; i < moves.size(); ++i) { + auto& move = moves[i]; + if (move.to == index) { + moves.erase(moves.begin() + i); + --i; + } + else if (move.to > index) + --move.to; + } +} + +void CollectionChangeBuilder::clear(size_t old_size) +{ + if (old_size != std::numeric_limits::max()) { + for (auto range : deletions) + old_size += range.second - range.first; + for (auto range : insertions) + old_size -= range.second - range.first; + } + + modifications.clear(); + insertions.clear(); + moves.clear(); + m_move_mapping.clear(); + columns.clear(); + deletions.set(old_size); +} + +void CollectionChangeBuilder::move(size_t from, size_t to) +{ + REALM_ASSERT(from != to); + + bool updated_existing_move = false; + for (auto& move : moves) { + if (move.to != from) { + // Shift other moves if this row is moving from one side of them + // to the other + if (move.to >= to && move.to < from) + ++move.to; + else if (move.to <= to && move.to > from) + --move.to; + continue; + } + REALM_ASSERT(!updated_existing_move); + + // Collapse A -> B, B -> C into a single A -> C move + move.to = to; + updated_existing_move = true; + + insertions.erase_at(from); + insertions.insert_at(to); + } + + if (!updated_existing_move) { + auto shifted_from = insertions.erase_or_unshift(from); + insertions.insert_at(to); + + // Don't report deletions/moves for newly inserted rows + if (shifted_from != IndexSet::npos) { + shifted_from = deletions.add_shifted(shifted_from); + moves.push_back({shifted_from, to}); + } + } + + for_each_col([=](auto& col) { + bool modified = col.contains(from); + col.erase_at(from); + + if (modified) + col.insert_at(to); + else + col.shift_for_insert_at(to); + }); +} + +void CollectionChangeBuilder::move_over(size_t row_ndx, size_t last_row, bool track_moves) +{ + REALM_ASSERT(row_ndx <= last_row); + REALM_ASSERT(insertions.empty() || prev(insertions.end())->second - 1 <= last_row); + REALM_ASSERT(modifications.empty() || prev(modifications.end())->second - 1 <= last_row); + + if (row_ndx == last_row) { + if (track_moves) { + auto shifted_from = insertions.erase_or_unshift(row_ndx); + if (shifted_from != IndexSet::npos) + deletions.add_shifted(shifted_from); + m_move_mapping.erase(row_ndx); + } + for_each_col([=](auto& col) { col.remove(row_ndx); }); + return; + } + + for_each_col([=](auto& col) { + bool modified = col.contains(last_row); + if (modified) { + col.remove(last_row); + col.add(row_ndx); + } + else + col.remove(row_ndx); + }); + + if (!track_moves) + return; + + bool row_is_insertion = insertions.contains(row_ndx); + bool last_is_insertion = !insertions.empty() && prev(insertions.end())->second == last_row + 1; + REALM_ASSERT_DEBUG(insertions.empty() || prev(insertions.end())->second <= last_row + 1); + + // Collapse A -> B, B -> C into a single A -> C move + bool last_was_already_moved = false; + if (last_is_insertion) { + auto it = m_move_mapping.find(last_row); + if (it != m_move_mapping.end() && it->first == last_row) { + m_move_mapping[row_ndx] = it->second; + m_move_mapping.erase(it); + last_was_already_moved = true; + } + } + + // Remove moves to the row being deleted + if (row_is_insertion && !last_was_already_moved) { + auto it = m_move_mapping.find(row_ndx); + if (it != m_move_mapping.end() && it->first == row_ndx) + m_move_mapping.erase(it); + } + + // Don't report deletions/moves if last_row is newly inserted + if (last_is_insertion) { + insertions.remove(last_row); + } + // If it was previously moved, the unshifted source row has already been marked as deleted + else if (!last_was_already_moved) { + auto shifted_last_row = insertions.unshift(last_row); + shifted_last_row = deletions.add_shifted(shifted_last_row); + m_move_mapping[row_ndx] = shifted_last_row; + } + + // Don't mark the moved-over row as deleted if it was a new insertion + if (!row_is_insertion) { + deletions.add_shifted(insertions.unshift(row_ndx)); + insertions.add(row_ndx); + } + verify(); +} + +void CollectionChangeBuilder::swap(size_t ndx_1, size_t ndx_2, bool track_moves) +{ + REALM_ASSERT(ndx_1 != ndx_2); + // The order of the two indices doesn't matter semantically, but making them + // consistent simplifies the logic + if (ndx_1 > ndx_2) + std::swap(ndx_1, ndx_2); + + for_each_col([=](auto& col) { + bool row_1_modified = col.contains(ndx_1); + bool row_2_modified = col.contains(ndx_2); + if (row_1_modified != row_2_modified) { + if (row_1_modified) { + col.remove(ndx_1); + col.add(ndx_2); + } + else { + col.remove(ndx_2); + col.add(ndx_1); + } + } + }); + + if (!track_moves) + return; + + auto update_move = [&](auto existing_it, auto ndx_1, auto ndx_2) { + // update the existing move to ndx_2 to point at ndx_1 + auto original = existing_it->second; + m_move_mapping.erase(existing_it); + m_move_mapping[ndx_1] = original; + + // add a move from 1 -> 2 unless 1 was a new insertion + if (!insertions.contains(ndx_1)) { + m_move_mapping[ndx_2] = deletions.add_shifted(insertions.unshift(ndx_1)); + insertions.add(ndx_1); + } + REALM_ASSERT_DEBUG(insertions.contains(ndx_2)); + }; + + auto move_1 = m_move_mapping.find(ndx_1); + auto move_2 = m_move_mapping.find(ndx_2); + bool have_move_1 = move_1 != end(m_move_mapping) && move_1->first == ndx_1; + bool have_move_2 = move_2 != end(m_move_mapping) && move_2->first == ndx_2; + if (have_move_1 && have_move_2) { + // both are already moves, so just swap the destinations + std::swap(move_1->second, move_2->second); + } + else if (have_move_1) { + update_move(move_1, ndx_2, ndx_1); + } + else if (have_move_2) { + update_move(move_2, ndx_1, ndx_2); + } + else { + // ndx_2 needs to be done before 1 to avoid incorrect shifting + if (!insertions.contains(ndx_2)) { + m_move_mapping[ndx_1] = deletions.add_shifted(insertions.unshift(ndx_2)); + insertions.add(ndx_2); + } + if (!insertions.contains(ndx_1)) { + m_move_mapping[ndx_2] = deletions.add_shifted(insertions.unshift(ndx_1)); + insertions.add(ndx_1); + } + } +} + +void CollectionChangeBuilder::subsume(size_t old_ndx, size_t new_ndx, bool track_moves) +{ + REALM_ASSERT(old_ndx != new_ndx); + + for_each_col([=](auto& col) { + if (col.contains(old_ndx)) { + col.add(new_ndx); + } + }); + + if (!track_moves) + return; + + REALM_ASSERT_DEBUG(insertions.contains(new_ndx)); + REALM_ASSERT_DEBUG(!m_move_mapping.count(new_ndx)); + + // If the source row was already moved, update the existing move + auto it = m_move_mapping.find(old_ndx); + if (it != m_move_mapping.end() && it->first == old_ndx) { + m_move_mapping[new_ndx] = it->second; + m_move_mapping.erase(it); + } + // otherwise add a new move unless it was a new insertion + else if (!insertions.contains(old_ndx)) { + m_move_mapping[new_ndx] = deletions.shift(insertions.unshift(old_ndx)); + } + + verify(); +} + +void CollectionChangeBuilder::verify() +{ +#ifdef REALM_DEBUG + for (auto&& move : moves) { + REALM_ASSERT(deletions.contains(move.from)); + REALM_ASSERT(insertions.contains(move.to)); + } +#endif +} + +void CollectionChangeBuilder::insert_column(size_t ndx) +{ + if (ndx < columns.size()) + columns.insert(columns.begin() + ndx, IndexSet{}); +} + +void CollectionChangeBuilder::move_column(size_t from, size_t to) +{ + if (from >= columns.size() && to >= columns.size()) + return; + if (from >= columns.size() || to >= columns.size()) + columns.resize(std::max(from, to) + 1); + if (from < to) + std::rotate(begin(columns) + from, begin(columns) + from + 1, begin(columns) + to + 1); + else + std::rotate(begin(columns) + to, begin(columns) + from, begin(columns) + from + 1); +} + +namespace { +struct RowInfo { + size_t row_index; + size_t prev_tv_index; + size_t tv_index; + size_t shifted_tv_index; +}; + +// Calculates the insertions/deletions required for a query on a table without +// a sort, where `removed` includes the rows which were modified to no longer +// match the query (but not outright deleted rows, which are filtered out long +// before any of this logic), and `move_candidates` tracks the rows which may +// be the result of a move. +// +// This function is not strictly required, as calculate_moves_sorted() will +// produce correct results even for the scenarios where this function is used. +// However, this function has asymptotically better worst-case performance and +// extremely cheap best-case performance, and is guaranteed to produce a minimal +// diff when the only row moves are due to move_last_over(). +void calculate_moves_unsorted(std::vector& new_rows, IndexSet& removed, + IndexSet const& move_candidates, + CollectionChangeSet& changeset) +{ + // Here we track which row we expect to see, which in the absence of swap() + // is always the row immediately after the last row which was not moved. + size_t expected = 0; + for (auto& row : new_rows) { + if (row.shifted_tv_index == expected) { + ++expected; + continue; + } + + // We didn't find the row we were expecting to find, which means that + // either a row was moved forward to here, the row we were expecting was + // removed, or the row we were expecting moved back. + + // First check if this row even could have moved. If it can't, just + // treat it as a match and move on, and we'll handle the row we were + // expecting when we hit it later. + if (!move_candidates.contains(row.row_index)) { + expected = row.shifted_tv_index + 1; + continue; + } + + // Next calculate where we expect this row to be based on the insertions + // and removals (i.e. rows changed to not match the query), as it could + // be that the row actually ends up in this spot due to the rows before + // it being removed. + size_t calc_expected = row.tv_index - changeset.insertions.count(0, row.tv_index) + removed.count(0, row.prev_tv_index); + if (row.shifted_tv_index == calc_expected) { + expected = calc_expected + 1; + continue; + } + + // The row still isn't the expected one, so record it as a move + changeset.moves.push_back({row.prev_tv_index, row.tv_index}); + changeset.insertions.add(row.tv_index); + removed.add(row.prev_tv_index); + } +} + +class LongestCommonSubsequenceCalculator { +public: + // A pair of an index in the table and an index in the table view + struct Row { + size_t row_index; + size_t tv_index; + }; + + struct Match { + // The index in `a` at which this match begins + size_t i; + // The index in `b` at which this match begins + size_t j; + // The length of this match + size_t size; + // The number of rows in this block which were modified + size_t modified; + }; + std::vector m_longest_matches; + + LongestCommonSubsequenceCalculator(std::vector& a, std::vector& b, + size_t start_index, + IndexSet const& modifications) + : m_modified(modifications) + , a(a), b(b) + { + find_longest_matches(start_index, a.size(), + start_index, b.size()); + m_longest_matches.push_back({a.size(), b.size(), 0}); + } + +private: + IndexSet const& m_modified; + + // The two arrays of rows being diffed + // a is sorted by tv_index, b is sorted by row_index + std::vector &a, &b; + + // Find the longest matching range in (a + begin1, a + end1) and (b + begin2, b + end2) + // "Matching" is defined as "has the same row index"; the TV index is just + // there to let us turn an index in a/b into an index which can be reported + // in the output changeset. + // + // This is done with the O(N) space variant of the dynamic programming + // algorithm for longest common subsequence, where N is the maximum number + // of the most common row index (which for everything but linkview-derived + // TVs will be 1). + Match find_longest_match(size_t begin1, size_t end1, size_t begin2, size_t end2) + { + struct Length { + size_t j, len; + }; + // The length of the matching block for each `j` for the previously checked row + std::vector prev; + // The length of the matching block for each `j` for the row currently being checked + std::vector cur; + + // Calculate the length of the matching block *ending* at b[j], which + // is 1 if b[j - 1] did not match, and b[j - 1] + 1 otherwise. + auto length = [&](size_t j) -> size_t { + for (auto const& pair : prev) { + if (pair.j + 1 == j) + return pair.len + 1; + } + return 1; + }; + + // Iterate over each `j` which has the same row index as a[i] and falls + // within the range begin2 <= j < end2 + auto for_each_b_match = [&](size_t i, auto&& f) { + size_t ai = a[i].row_index; + // Find the TV indicies at which this row appears in the new results + // There should always be at least one (or it would have been + // filtered out earlier), but there can be multiple if there are dupes + auto it = lower_bound(begin(b), end(b), ai, + [](auto lft, auto rgt) { return lft.row_index < rgt; }); + REALM_ASSERT(it != end(b) && it->row_index == ai); + for (; it != end(b) && it->row_index == ai; ++it) { + size_t j = it->tv_index; + if (j < begin2) + continue; + if (j >= end2) + break; // b is sorted by tv_index so this can't transition from false to true + f(j); + } + }; + + Match best = {begin1, begin2, 0, 0}; + for (size_t i = begin1; i < end1; ++i) { + // prev = std::move(cur), but avoids discarding prev's heap allocation + cur.swap(prev); + cur.clear(); + + for_each_b_match(i, [&](size_t j) { + size_t size = length(j); + + cur.push_back({j, size}); + + // If the matching block ending at a[i] and b[j] is longer than + // the previous one, select it as the best + if (size > best.size) + best = {i - size + 1, j - size + 1, size, IndexSet::npos}; + // Given two equal-length matches, prefer the one with fewer modified rows + else if (size == best.size) { + if (best.modified == IndexSet::npos) + best.modified = m_modified.count(best.j - size + 1, best.j + 1); + auto count = m_modified.count(j - size + 1, j + 1); + if (count < best.modified) + best = {i - size + 1, j - size + 1, size, count}; + } + + // The best block should always fall within the range being searched + REALM_ASSERT(best.i >= begin1 && best.i + best.size <= end1); + REALM_ASSERT(best.j >= begin2 && best.j + best.size <= end2); + }); + } + return best; + } + + void find_longest_matches(size_t begin1, size_t end1, size_t begin2, size_t end2) + { + // FIXME: recursion could get too deep here + // recursion depth worst case is currently O(N) and each recursion uses 320 bytes of stack + // could reduce worst case to O(sqrt(N)) (and typical case to O(log N)) + // biasing equal selections towards the middle, but that's still + // insufficient for Android's 8 KB stacks + auto m = find_longest_match(begin1, end1, begin2, end2); + if (!m.size) + return; + if (m.i > begin1 && m.j > begin2) + find_longest_matches(begin1, m.i, begin2, m.j); + m_longest_matches.push_back(m); + if (m.i + m.size < end2 && m.j + m.size < end2) + find_longest_matches(m.i + m.size, end1, m.j + m.size, end2); + } +}; + +void calculate_moves_sorted(std::vector& rows, CollectionChangeSet& changeset) +{ + // The RowInfo array contains information about the old and new TV indices of + // each row, which we need to turn into two sequences of rows, which we'll + // then find matches in + std::vector a, b; + + a.reserve(rows.size()); + for (auto& row : rows) { + a.push_back({row.row_index, row.prev_tv_index}); + } + std::sort(begin(a), end(a), [](auto lft, auto rgt) { + return std::tie(lft.tv_index, lft.row_index) < std::tie(rgt.tv_index, rgt.row_index); + }); + + // Before constructing `b`, first find the first index in `a` which will + // actually differ in `b`, and skip everything else if there aren't any + size_t first_difference = IndexSet::npos; + for (size_t i = 0; i < a.size(); ++i) { + if (a[i].row_index != rows[i].row_index) { + first_difference = i; + break; + } + } + if (first_difference == IndexSet::npos) + return; + + // Note that `b` is sorted by row_index, while `a` is sorted by tv_index + b.reserve(rows.size()); + for (size_t i = 0; i < rows.size(); ++i) + b.push_back({rows[i].row_index, i}); + std::sort(begin(b), end(b), [](auto lft, auto rgt) { + return std::tie(lft.row_index, lft.tv_index) < std::tie(rgt.row_index, rgt.tv_index); + }); + + // Calculate the LCS of the two sequences + auto matches = LongestCommonSubsequenceCalculator(a, b, first_difference, + changeset.modifications).m_longest_matches; + + // And then insert and delete rows as needed to align them + size_t i = first_difference, j = first_difference; + for (auto match : matches) { + for (; i < match.i; ++i) + changeset.deletions.add(a[i].tv_index); + for (; j < match.j; ++j) + changeset.insertions.add(rows[j].tv_index); + i += match.size; + j += match.size; + } +} + +} // Anonymous namespace + +CollectionChangeBuilder CollectionChangeBuilder::calculate(std::vector const& prev_rows, + std::vector const& next_rows, + std::function row_did_change, + util::Optional const& move_candidates) +{ + REALM_ASSERT_DEBUG(!move_candidates || std::is_sorted(begin(next_rows), end(next_rows))); + + CollectionChangeBuilder ret; + + size_t deleted = 0; + std::vector old_rows; + old_rows.reserve(prev_rows.size()); + for (size_t i = 0; i < prev_rows.size(); ++i) { + if (prev_rows[i] == IndexSet::npos) { + ++deleted; + ret.deletions.add(i); + } + else + old_rows.push_back({prev_rows[i], IndexSet::npos, i, i - deleted}); + } + std::sort(begin(old_rows), end(old_rows), [](auto& lft, auto& rgt) { + return lft.row_index < rgt.row_index; + }); + + std::vector new_rows; + new_rows.reserve(next_rows.size()); + for (size_t i = 0; i < next_rows.size(); ++i) { + new_rows.push_back({next_rows[i], IndexSet::npos, i, 0}); + } + std::sort(begin(new_rows), end(new_rows), [](auto& lft, auto& rgt) { + return lft.row_index < rgt.row_index; + }); + + // Don't add rows which were modified to not match the query to `deletions` + // immediately because the unsorted move logic needs to be able to + // distinguish them from rows which were outright deleted + IndexSet removed; + + // Now that our old and new sets of rows are sorted by row index, we can + // iterate over them and either record old+new TV indices for rows present + // in both, or mark them as inserted/deleted if they appear only in one + size_t i = 0, j = 0; + while (i < old_rows.size() && j < new_rows.size()) { + auto old_index = old_rows[i]; + auto new_index = new_rows[j]; + if (old_index.row_index == new_index.row_index) { + new_rows[j].prev_tv_index = old_rows[i].tv_index; + new_rows[j].shifted_tv_index = old_rows[i].shifted_tv_index; + ++i; + ++j; + } + else if (old_index.row_index < new_index.row_index) { + removed.add(old_index.tv_index); + ++i; + } + else { + ret.insertions.add(new_index.tv_index); + ++j; + } + } + + for (; i < old_rows.size(); ++i) + removed.add(old_rows[i].tv_index); + for (; j < new_rows.size(); ++j) + ret.insertions.add(new_rows[j].tv_index); + + // Filter out the new insertions since we don't need them for any of the + // further calculations + new_rows.erase(std::remove_if(begin(new_rows), end(new_rows), + [](auto& row) { return row.prev_tv_index == IndexSet::npos; }), + end(new_rows)); + std::sort(begin(new_rows), end(new_rows), + [](auto& lft, auto& rgt) { return lft.tv_index < rgt.tv_index; }); + + for (auto& row : new_rows) { + if (row_did_change(row.row_index)) { + ret.modifications.add(row.tv_index); + } + } + + if (move_candidates) { + calculate_moves_unsorted(new_rows, removed, *move_candidates, ret); + } + else { + calculate_moves_sorted(new_rows, ret); + } + ret.deletions.add(removed); + ret.verify(); + +#ifdef REALM_DEBUG + { // Verify that applying the calculated change to prev_rows actually produces next_rows + auto rows = prev_rows; + auto it = util::make_reverse_iterator(ret.deletions.end()); + auto end = util::make_reverse_iterator(ret.deletions.begin()); + for (; it != end; ++it) { + rows.erase(rows.begin() + it->first, rows.begin() + it->second); + } + + for (auto i : ret.insertions.as_indexes()) { + rows.insert(rows.begin() + i, next_rows[i]); + } + + REALM_ASSERT(rows == next_rows); + } +#endif + + return ret; +} + +CollectionChangeSet CollectionChangeBuilder::finalize() && +{ + // Calculate which indices in the old collection were modified + auto modifications_in_old = modifications; + modifications_in_old.erase_at(insertions); + modifications_in_old.shift_for_insert_at(deletions); + + // During changeset calculation we allow marking a row as both inserted and + // modified in case changeset merging results in it no longer being an insert, + // but we don't want inserts in the final modification set + modifications.remove(insertions); + + return { + std::move(deletions), + std::move(insertions), + std::move(modifications_in_old), + std::move(modifications), + std::move(moves), + std::move(columns) + }; +} diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/impl/collection_notifier.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/impl/collection_notifier.cpp new file mode 100644 index 0000000..8fa94d3 --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/impl/collection_notifier.cpp @@ -0,0 +1,497 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "impl/collection_notifier.hpp" + +#include "impl/realm_coordinator.hpp" +#include "shared_realm.hpp" + +#include +#include + +using namespace realm; +using namespace realm::_impl; + +std::function +CollectionNotifier::get_modification_checker(TransactionChangeInfo const& info, + Table const& root_table) +{ + if (info.schema_changed) + set_table(root_table); + + // First check if any of the tables accessible from the root table were + // actually modified. This can be false if there were only insertions, or + // deletions which were not linked to by any row in the linking table + auto table_modified = [&](auto& tbl) { + return tbl.table_ndx < info.tables.size() + && !info.tables[tbl.table_ndx].modifications.empty(); + }; + if (!any_of(begin(m_related_tables), end(m_related_tables), table_modified)) { + return [](size_t) { return false; }; + } + if (m_related_tables.size() == 1) { + auto& modifications = info.tables[m_related_tables[0].table_ndx].modifications; + return [&](size_t row) { return modifications.contains(row); }; + } + + return DeepChangeChecker(info, root_table, m_related_tables); +} + +void DeepChangeChecker::find_related_tables(std::vector& out, Table const& table) +{ + auto table_ndx = table.get_index_in_group(); + if (table_ndx == npos) + return; + if (any_of(begin(out), end(out), [=](auto& tbl) { return tbl.table_ndx == table_ndx; })) + return; + + // We need to add this table to `out` before recurring so that the check + // above works, but we can't store a pointer to the thing being populated + // because the recursive calls may resize `out`, so instead look it up by + // index every time + size_t out_index = out.size(); + out.push_back({table_ndx, {}}); + + for (size_t i = 0, count = table.get_column_count(); i != count; ++i) { + auto type = table.get_column_type(i); + if (type == type_Link || type == type_LinkList) { + out[out_index].links.push_back({i, type == type_LinkList}); + find_related_tables(out, *table.get_link_target(i)); + } + } +} + +DeepChangeChecker::DeepChangeChecker(TransactionChangeInfo const& info, + Table const& root_table, + std::vector const& related_tables) +: m_info(info) +, m_root_table(root_table) +, m_root_table_ndx(root_table.get_index_in_group()) +, m_root_modifications(m_root_table_ndx < info.tables.size() ? &info.tables[m_root_table_ndx].modifications : nullptr) +, m_related_tables(related_tables) +{ +} + +bool DeepChangeChecker::check_outgoing_links(size_t table_ndx, + Table const& table, + size_t row_ndx, size_t depth) +{ + auto it = find_if(begin(m_related_tables), end(m_related_tables), + [&](auto&& tbl) { return tbl.table_ndx == table_ndx; }); + if (it == m_related_tables.end()) + return false; + + // Check if we're already checking if the destination of the link is + // modified, and if not add it to the stack + auto already_checking = [&](size_t col) { + auto end = m_current_path.begin() + depth; + auto match = std::find_if(m_current_path.begin(), end, [&](auto& p) { + return p.table == table_ndx && p.row == row_ndx && p.col == col; + }); + if (match != end) { + for (; match < end; ++match) match->depth_exceeded = true; + return true; + } + m_current_path[depth] = {table_ndx, row_ndx, col, false}; + return false; + }; + + auto linked_object_changed = [&](OutgoingLink const& link) { + if (already_checking(link.col_ndx)) + return false; + if (!link.is_list) { + if (table.is_null_link(link.col_ndx, row_ndx)) + return false; + auto dst = table.get_link(link.col_ndx, row_ndx); + return check_row(*table.get_link_target(link.col_ndx), dst, depth + 1); + } + + auto& target = *table.get_link_target(link.col_ndx); + auto lvr = table.get_linklist(link.col_ndx, row_ndx); + for (size_t j = 0, size = lvr->size(); j < size; ++j) { + size_t dst = lvr->get(j).get_index(); + if (check_row(target, dst, depth + 1)) + return true; + } + return false; + }; + + return std::any_of(begin(it->links), end(it->links), linked_object_changed); +} + +bool DeepChangeChecker::check_row(Table const& table, size_t idx, size_t depth) +{ + // Arbitrary upper limit on the maximum depth to search + if (depth >= m_current_path.size()) { + // Don't mark any of the intermediate rows checked along the path as + // not modified, as a search starting from them might hit a modification + for (size_t i = 0; i < m_current_path.size(); ++i) + m_current_path[i].depth_exceeded = true; + return false; + } + + size_t table_ndx = table.get_index_in_group(); + if (depth > 0 && table_ndx < m_info.tables.size() && m_info.tables[table_ndx].modifications.contains(idx)) + return true; + + if (m_not_modified.size() <= table_ndx) + m_not_modified.resize(table_ndx + 1); + if (m_not_modified[table_ndx].contains(idx)) + return false; + + bool ret = check_outgoing_links(table_ndx, table, idx, depth); + if (!ret && (depth == 0 || !m_current_path[depth - 1].depth_exceeded)) + m_not_modified[table_ndx].add(idx); + return ret; +} + +bool DeepChangeChecker::operator()(size_t ndx) +{ + if (m_root_modifications && m_root_modifications->contains(ndx)) + return true; + return check_row(m_root_table, ndx, 0); +} + +CollectionNotifier::CollectionNotifier(std::shared_ptr realm) +: m_realm(std::move(realm)) +, m_sg_version(Realm::Internal::get_shared_group(*m_realm)->get_version_of_current_transaction()) +{ +} + +CollectionNotifier::~CollectionNotifier() +{ + // Need to do this explicitly to ensure m_realm is destroyed with the mutex + // held to avoid potential double-deletion + unregister(); +} + +uint64_t CollectionNotifier::add_callback(CollectionChangeCallback callback) +{ + m_realm->verify_thread(); + + std::lock_guard lock(m_callback_mutex); + auto token = m_next_token++; + m_callbacks.push_back({std::move(callback), {}, {}, token, false, false}); + if (m_callback_index == npos) { // Don't need to wake up if we're already sending notifications + Realm::Internal::get_coordinator(*m_realm).wake_up_notifier_worker(); + m_have_callbacks = true; + } + return token; +} + +void CollectionNotifier::remove_callback(uint64_t token) +{ + // the callback needs to be destroyed after releasing the lock as destroying + // it could cause user code to be called + Callback old; + { + std::lock_guard lock(m_callback_mutex); + auto it = find_callback(token); + if (it == end(m_callbacks)) { + return; + } + + size_t idx = distance(begin(m_callbacks), it); + if (m_callback_index != npos) { + if (m_callback_index >= idx) + --m_callback_index; + } + --m_callback_count; + + old = std::move(*it); + m_callbacks.erase(it); + + m_have_callbacks = !m_callbacks.empty(); + } +} + +void CollectionNotifier::suppress_next_notification(uint64_t token) +{ + { + std::lock_guard lock(m_realm_mutex); + REALM_ASSERT(m_realm); + m_realm->verify_thread(); + m_realm->verify_in_write(); + } + + std::lock_guard lock(m_callback_mutex); + auto it = find_callback(token); + if (it != end(m_callbacks)) { + it->skip_next = true; + } +} + +std::vector::iterator CollectionNotifier::find_callback(uint64_t token) +{ + REALM_ASSERT(m_error || m_callbacks.size() > 0); + + auto it = find_if(begin(m_callbacks), end(m_callbacks), + [=](const auto& c) { return c.token == token; }); + // We should only fail to find the callback if it was removed due to an error + REALM_ASSERT(m_error || it != end(m_callbacks)); + return it; +} + +void CollectionNotifier::unregister() noexcept +{ + std::lock_guard lock(m_realm_mutex); + m_realm = nullptr; +} + +bool CollectionNotifier::is_alive() const noexcept +{ + std::lock_guard lock(m_realm_mutex); + return m_realm != nullptr; +} + +std::unique_lock CollectionNotifier::lock_target() +{ + return std::unique_lock{m_realm_mutex}; +} + +void CollectionNotifier::set_table(Table const& table) +{ + m_related_tables.clear(); + DeepChangeChecker::find_related_tables(m_related_tables, table); +} + +void CollectionNotifier::add_required_change_info(TransactionChangeInfo& info) +{ + if (!do_add_required_change_info(info) || m_related_tables.empty()) { + return; + } + + auto max = max_element(begin(m_related_tables), end(m_related_tables), + [](auto&& a, auto&& b) { return a.table_ndx < b.table_ndx; }); + + if (max->table_ndx >= info.table_modifications_needed.size()) + info.table_modifications_needed.resize(max->table_ndx + 1, false); + for (auto& tbl : m_related_tables) { + info.table_modifications_needed[tbl.table_ndx] = true; + } +} + +void CollectionNotifier::prepare_handover() +{ + REALM_ASSERT(m_sg); + m_sg_version = m_sg->get_version_of_current_transaction(); + do_prepare_handover(*m_sg); + m_has_run = true; + +#ifdef REALM_DEBUG + std::lock_guard lock(m_callback_mutex); + for (auto& callback : m_callbacks) + REALM_ASSERT(!callback.skip_next); +#endif +} + +void CollectionNotifier::before_advance() +{ + for_each_callback([&](auto& lock, auto& callback) { + if (callback.changes_to_deliver.empty()) { + return; + } + + auto changes = callback.changes_to_deliver; + // acquire a local reference to the callback so that removing the + // callback from within it can't result in a dangling pointer + auto cb = callback.fn; + lock.unlock(); + cb.before(changes); + }); +} + +void CollectionNotifier::after_advance() +{ + for_each_callback([&](auto& lock, auto& callback) { + if (callback.initial_delivered && callback.changes_to_deliver.empty()) { + return; + } + callback.initial_delivered = true; + + auto changes = std::move(callback.changes_to_deliver); + // acquire a local reference to the callback so that removing the + // callback from within it can't result in a dangling pointer + auto cb = callback.fn; + lock.unlock(); + cb.after(changes); + }); +} + +void CollectionNotifier::deliver_error(std::exception_ptr error) +{ + // Don't complain about double-unregistering callbacks + m_error = true; + + m_callback_count = m_callbacks.size(); + for_each_callback([this, &error](auto& lock, auto& callback) { + // acquire a local reference to the callback so that removing the + // callback from within it can't result in a dangling pointer + auto cb = std::move(callback.fn); + auto token = callback.token; + lock.unlock(); + cb.error(error); + + // We never want to call the callback again after this, so just remove it + this->remove_callback(token); + }); +} + +bool CollectionNotifier::is_for_realm(Realm& realm) const noexcept +{ + std::lock_guard lock(m_realm_mutex); + return m_realm.get() == &realm; +} + +bool CollectionNotifier::package_for_delivery() +{ + if (!prepare_to_deliver()) + return false; + std::lock_guard l(m_callback_mutex); + for (auto& callback : m_callbacks) + callback.changes_to_deliver = std::move(callback.accumulated_changes).finalize(); + m_callback_count = m_callbacks.size(); + return true; +} + +template +void CollectionNotifier::for_each_callback(Fn&& fn) +{ + std::unique_lock callback_lock(m_callback_mutex); + REALM_ASSERT_DEBUG(m_callback_count <= m_callbacks.size()); + for (++m_callback_index; m_callback_index < m_callback_count; ++m_callback_index) { + fn(callback_lock, m_callbacks[m_callback_index]); + if (!callback_lock.owns_lock()) + callback_lock.lock(); + } + + m_callback_index = npos; +} + +void CollectionNotifier::attach_to(SharedGroup& sg) +{ + REALM_ASSERT(!m_sg); + + m_sg = &sg; + do_attach_to(sg); +} + +void CollectionNotifier::detach() +{ + REALM_ASSERT(m_sg); + do_detach_from(*m_sg); + m_sg = nullptr; +} + +SharedGroup& CollectionNotifier::source_shared_group() +{ + return *Realm::Internal::get_shared_group(*m_realm); +} + +void CollectionNotifier::add_changes(CollectionChangeBuilder change) +{ + std::lock_guard lock(m_callback_mutex); + for (auto& callback : m_callbacks) { + if (callback.skip_next) { + REALM_ASSERT_DEBUG(callback.accumulated_changes.empty()); + callback.skip_next = false; + } + else { + if (&callback == &m_callbacks.back()) + callback.accumulated_changes.merge(std::move(change)); + else + callback.accumulated_changes.merge(CollectionChangeBuilder(change)); + } + } +} + +NotifierPackage::NotifierPackage(std::exception_ptr error, + std::vector> notifiers, + RealmCoordinator* coordinator) +: m_notifiers(std::move(notifiers)) +, m_coordinator(coordinator) +, m_error(std::move(error)) +{ +} + +void NotifierPackage::package_and_wait(util::Optional target_version) +{ + if (!m_coordinator || m_error || !*this) + return; + + auto lock = m_coordinator->wait_for_notifiers([&] { + if (!target_version) + return true; + return std::all_of(begin(m_notifiers), end(m_notifiers), [&](auto const& n) { + return !n->have_callbacks() || (n->has_run() && n->version().version >= *target_version); + }); + }); + + // Package the notifiers for delivery and remove any which don't have anything to deliver + auto package = [&](auto& notifier) { + if (notifier->has_run() && notifier->package_for_delivery()) { + m_version = notifier->version(); + return false; + } + return true; + }; + m_notifiers.erase(std::remove_if(begin(m_notifiers), end(m_notifiers), package), end(m_notifiers)); + if (m_version && target_version && m_version->version < *target_version) { + m_notifiers.clear(); + m_version = util::none; + } + REALM_ASSERT(m_version || m_notifiers.empty()); + + m_coordinator = nullptr; +} + +void NotifierPackage::before_advance() +{ + if (m_error) + return; + for (auto& notifier : m_notifiers) + notifier->before_advance(); +} + +void NotifierPackage::deliver(SharedGroup& sg) +{ + if (m_error) { + for (auto& notifier : m_notifiers) + notifier->deliver_error(m_error); + return; + } + // Can't deliver while in a write transaction + if (sg.get_transact_stage() != SharedGroup::transact_Reading) + return; + for (auto& notifier : m_notifiers) + notifier->deliver(sg); +} + +void NotifierPackage::after_advance() +{ + if (m_error) + return; + for (auto& notifier : m_notifiers) + notifier->after_advance(); +} + +void NotifierPackage::add_notifier(std::shared_ptr notifier) +{ + m_notifiers.push_back(notifier); + m_coordinator->register_notifier(notifier); +} diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/impl/list_notifier.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/impl/list_notifier.cpp new file mode 100644 index 0000000..a2b459c --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/impl/list_notifier.cpp @@ -0,0 +1,109 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "impl/list_notifier.hpp" + +#include "shared_realm.hpp" + +#include + +using namespace realm; +using namespace realm::_impl; + +ListNotifier::ListNotifier(LinkViewRef lv, std::shared_ptr realm) +: CollectionNotifier(std::move(realm)) +, m_prev_size(lv->size()) +{ + set_table(lv->get_target_table()); + m_lv_handover = source_shared_group().export_linkview_for_handover(lv); +} + +void ListNotifier::release_data() noexcept +{ + m_lv.reset(); +} + +void ListNotifier::do_attach_to(SharedGroup& sg) +{ + REALM_ASSERT(m_lv_handover); + REALM_ASSERT(!m_lv); + m_lv = sg.import_linkview_from_handover(std::move(m_lv_handover)); +} + +void ListNotifier::do_detach_from(SharedGroup& sg) +{ + REALM_ASSERT(!m_lv_handover); + if (m_lv) { + m_lv_handover = sg.export_linkview_for_handover(m_lv); + m_lv = {}; + } +} + +bool ListNotifier::do_add_required_change_info(TransactionChangeInfo& info) +{ + REALM_ASSERT(!m_lv_handover); + if (!m_lv || !m_lv->is_attached()) { + return false; // origin row was deleted after the notification was added + } + + auto& table = m_lv->get_origin_table(); + size_t row_ndx = m_lv->get_origin_row_index(); + size_t col_ndx = find_container_column(table, row_ndx, m_lv, type_LinkList, &Table::get_linklist); + info.lists.push_back({table.get_index_in_group(), row_ndx, col_ndx, &m_change}); + + m_info = &info; + return true; +} + +void ListNotifier::run() +{ + if (!m_lv || !m_lv->is_attached()) { + // LV was deleted, so report all of the rows being removed if this is + // the first run after that + if (m_prev_size) { + m_change.deletions.set(m_prev_size); + m_prev_size = 0; + } + else { + m_change = {}; + } + return; + } + + auto row_did_change = get_modification_checker(*m_info, m_lv->get_target_table()); + for (size_t i = 0; i < m_lv->size(); ++i) { + if (m_change.modifications.contains(i)) + continue; + if (row_did_change(m_lv->get(i).get_index())) + m_change.modifications.add(i); + } + + for (auto const& move : m_change.moves) { + if (m_change.modifications.contains(move.to)) + continue; + if (row_did_change(m_lv->get(move.to).get_index())) + m_change.modifications.add(move.to); + } + + m_prev_size = m_lv->size(); +} + +void ListNotifier::do_prepare_handover(SharedGroup&) +{ + add_changes(std::move(m_change)); +} diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/impl/object_notifier.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/impl/object_notifier.cpp new file mode 100644 index 0000000..cb31065 --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/impl/object_notifier.cpp @@ -0,0 +1,97 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2017 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "impl/object_notifier.hpp" + +#include "shared_realm.hpp" + +using namespace realm; +using namespace realm::_impl; + +ObjectNotifier::ObjectNotifier(Row const& row, std::shared_ptr realm) +: CollectionNotifier(std::move(realm)) +{ + REALM_ASSERT(row.get_table()); + set_table(*row.get_table()); + + m_handover = source_shared_group().export_for_handover(row); +} + +void ObjectNotifier::release_data() noexcept +{ + m_row = nullptr; +} + +void ObjectNotifier::do_attach_to(SharedGroup& sg) +{ + REALM_ASSERT(m_handover); + REALM_ASSERT(!m_row); + m_row = sg.import_from_handover(std::move(m_handover)); +} + +void ObjectNotifier::do_detach_from(SharedGroup& sg) +{ + REALM_ASSERT(!m_handover); + if (m_row) { + m_handover = sg.export_for_handover(*m_row); + m_row = nullptr; + } +} + +bool ObjectNotifier::do_add_required_change_info(TransactionChangeInfo& info) +{ + REALM_ASSERT(!m_handover); + m_info = &info; + if (m_row && m_row->is_attached()) { + size_t table_ndx = m_row->get_table()->get_index_in_group(); + if (table_ndx >= info.table_modifications_needed.size()) + info.table_modifications_needed.resize(table_ndx + 1); + info.table_modifications_needed[table_ndx] = true; + } + return false; +} + +void ObjectNotifier::run() +{ + if (!m_row) + return; + if (!m_row->is_attached()) { + m_change.deletions.add(0); + m_row = nullptr; + return; + } + + size_t table_ndx = m_row->get_table()->get_index_in_group(); + if (table_ndx >= m_info->tables.size()) + return; + auto& change = m_info->tables[table_ndx]; + if (!change.modifications.contains(m_row->get_index())) + return; + m_change.modifications.add(0); + m_change.columns.reserve(change.columns.size()); + for (auto& col : change.columns) { + m_change.columns.emplace_back(); + if (col.contains(m_row->get_index())) + m_change.columns.back().add(0); + } +} + +void ObjectNotifier::do_prepare_handover(SharedGroup&) +{ + add_changes(std::move(m_change)); +} diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/impl/primitive_list_notifier.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/impl/primitive_list_notifier.cpp new file mode 100644 index 0000000..111807f --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/impl/primitive_list_notifier.cpp @@ -0,0 +1,100 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2017 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "impl/primitive_list_notifier.hpp" + +#include "shared_realm.hpp" + +#include + +using namespace realm; +using namespace realm::_impl; + +PrimitiveListNotifier::PrimitiveListNotifier(TableRef table, std::shared_ptr realm) +: CollectionNotifier(std::move(realm)) +, m_prev_size(table->size()) +{ + set_table(*table->get_parent_table()); + m_table_handover = source_shared_group().export_table_for_handover(table); +} + +void PrimitiveListNotifier::release_data() noexcept +{ + m_table.reset(); +} + +void PrimitiveListNotifier::do_attach_to(SharedGroup& sg) +{ + REALM_ASSERT(!m_table); + if (m_table_handover) + m_table = sg.import_table_from_handover(std::move(m_table_handover)); +} + +void PrimitiveListNotifier::do_detach_from(SharedGroup& sg) +{ + REALM_ASSERT(!m_table_handover); + if (m_table) { + if (m_table->is_attached()) + m_table_handover = sg.export_table_for_handover(m_table); + m_table = {}; + } +} + +bool PrimitiveListNotifier::do_add_required_change_info(TransactionChangeInfo& info) +{ + REALM_ASSERT(!m_table_handover); + if (!m_table || !m_table->is_attached()) { + return false; // origin row was deleted after the notification was added + } + + auto& table = *m_table->get_parent_table(); + size_t row_ndx = m_table->get_parent_row_index(); + size_t col_ndx = find_container_column(table, row_ndx, m_table, type_Table, &Table::get_subtable); + info.lists.push_back({table.get_index_in_group(), row_ndx, col_ndx, &m_change}); + + m_info = &info; + return true; +} + +void PrimitiveListNotifier::run() +{ + if (!m_table || !m_table->is_attached()) { + // Table was deleted entirely, so report all of the rows being removed + // if this is the first run after that + if (m_prev_size) { + m_change.deletions.set(m_prev_size); + m_prev_size = 0; + } + else { + m_change = {}; + } + return; + } + + if (!m_change.deletions.empty() && m_change.deletions.begin()->second == std::numeric_limits::max()) { + // Table was cleared, so set the deletions to the actual previous size + m_change.deletions.set(m_prev_size); + } + + m_prev_size = m_table->size(); +} + +void PrimitiveListNotifier::do_prepare_handover(SharedGroup&) +{ + add_changes(std::move(m_change)); +} diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/impl/realm_coordinator.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/impl/realm_coordinator.cpp new file mode 100644 index 0000000..05f8baf --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/impl/realm_coordinator.cpp @@ -0,0 +1,1037 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "impl/realm_coordinator.hpp" + +#include "impl/collection_notifier.hpp" +#include "impl/external_commit_helper.hpp" +#include "impl/transact_log_handler.hpp" +#include "impl/weak_realm_notifier.hpp" +#include "binding_context.hpp" +#include "object_schema.hpp" +#include "object_store.hpp" +#include "property.hpp" +#include "schema.hpp" +#include "thread_safe_reference.hpp" + +#if REALM_ENABLE_SYNC +#include "sync/impl/work_queue.hpp" +#include "sync/async_open_task.hpp" +#include "sync/partial_sync.hpp" +#include "sync/sync_config.hpp" +#include "sync/sync_manager.hpp" +#include "sync/sync_session.hpp" +#endif + +#include +#include +#include + +#include +#include + +using namespace realm; +using namespace realm::_impl; + +static auto& s_coordinator_mutex = *new std::mutex; +static auto& s_coordinators_per_path = *new std::unordered_map>; + +std::shared_ptr RealmCoordinator::get_coordinator(StringData path) +{ + std::lock_guard lock(s_coordinator_mutex); + + auto& weak_coordinator = s_coordinators_per_path[path]; + if (auto coordinator = weak_coordinator.lock()) { + return coordinator; + } + + auto coordinator = std::make_shared(); + weak_coordinator = coordinator; + return coordinator; +} + +std::shared_ptr RealmCoordinator::get_coordinator(const Realm::Config& config) +{ + auto coordinator = get_coordinator(config.path); + std::lock_guard lock(coordinator->m_realm_mutex); + coordinator->set_config(config); + return coordinator; +} + +std::shared_ptr RealmCoordinator::get_existing_coordinator(StringData path) +{ + std::lock_guard lock(s_coordinator_mutex); + auto it = s_coordinators_per_path.find(path); + return it == s_coordinators_per_path.end() ? nullptr : it->second.lock(); +} + +void RealmCoordinator::create_sync_session(bool force_client_resync, bool validate_sync_history) +{ +#if REALM_ENABLE_SYNC + if (m_sync_session) + return; + + if (!m_config.encryption_key.empty() && !m_config.sync_config->realm_encryption_key) { + throw std::logic_error("A realm encryption key was specified in Realm::Config but not in SyncConfig"); + } else if (m_config.sync_config->realm_encryption_key && m_config.encryption_key.empty()) { + throw std::logic_error("A realm encryption key was specified in SyncConfig but not in Realm::Config"); + } else if (m_config.sync_config->realm_encryption_key && + !std::equal(m_config.sync_config->realm_encryption_key->begin(), m_config.sync_config->realm_encryption_key->end(), + m_config.encryption_key.begin(), m_config.encryption_key.end())) { + throw std::logic_error("The realm encryption key specified in SyncConfig does not match the one in Realm::Config"); + } + + auto sync_config = *m_config.sync_config; + sync_config.validate_sync_history = validate_sync_history; + m_sync_session = SyncManager::shared().get_session(m_config.path, sync_config, force_client_resync); + + std::weak_ptr weak_self = shared_from_this(); + SyncSession::Internal::set_sync_transact_callback(*m_sync_session, + [weak_self](VersionID old_version, VersionID new_version) { + if (auto self = weak_self.lock()) { + if (self->m_transaction_callback) + self->m_transaction_callback(old_version, new_version); + if (self->m_notifier) + self->m_notifier->notify_others(); + } + }); +#else + static_cast(force_client_resync); + static_cast(validate_sync_history); +#endif +} + +void RealmCoordinator::set_config(const Realm::Config& config) +{ + if (config.encryption_key.data() && config.encryption_key.size() != 64) + throw InvalidEncryptionKeyException(); + if (config.schema_mode == SchemaMode::Immutable && config.sync_config) + throw std::logic_error("Synchronized Realms cannot be opened in immutable mode"); + if (config.schema_mode == SchemaMode::Additive && config.migration_function) + throw std::logic_error("Realms opened in Additive-only schema mode do not use a migration function"); + if (config.schema_mode == SchemaMode::Immutable && config.migration_function) + throw std::logic_error("Realms opened in immutable mode do not use a migration function"); + if (config.schema_mode == SchemaMode::ReadOnlyAlternative && config.migration_function) + throw std::logic_error("Realms opened in read-only mode do not use a migration function"); + if (config.schema_mode == SchemaMode::Immutable && config.initialization_function) + throw std::logic_error("Realms opened in immutable mode do not use an initialization function"); + if (config.schema_mode == SchemaMode::ReadOnlyAlternative && config.initialization_function) + throw std::logic_error("Realms opened in read-only mode do not use an initialization function"); + if (config.schema && config.schema_version == ObjectStore::NotVersioned) + throw std::logic_error("A schema version must be specified when the schema is specified"); + if (!config.realm_data.is_null() && (!config.immutable() || !config.in_memory)) + throw std::logic_error("In-memory realms initialized from memory buffers can only be opened in read-only mode"); + if (!config.realm_data.is_null() && !config.path.empty()) + throw std::logic_error("Specifying both memory buffer and path is invalid"); + if (!config.realm_data.is_null() && !config.encryption_key.empty()) + throw std::logic_error("Memory buffers do not support encryption"); + // ResetFile also won't use the migration function, but specifying one is + // allowed to simplify temporarily switching modes during development + + bool no_existing_realm = std::all_of(begin(m_weak_realm_notifiers), end(m_weak_realm_notifiers), + [](auto& notifier) { return notifier.expired(); }); + if (no_existing_realm) { + m_config = config; + } + else { + if (m_config.immutable() != config.immutable()) { + throw MismatchedConfigException("Realm at path '%1' already opened with different read permissions.", config.path); + } + if (m_config.in_memory != config.in_memory) { + throw MismatchedConfigException("Realm at path '%1' already opened with different inMemory settings.", config.path); + } + if (m_config.encryption_key != config.encryption_key) { + throw MismatchedConfigException("Realm at path '%1' already opened with a different encryption key.", config.path); + } + if (m_config.schema_mode != config.schema_mode) { + throw MismatchedConfigException("Realm at path '%1' already opened with a different schema mode.", config.path); + } + if (config.schema && m_schema_version != ObjectStore::NotVersioned && m_schema_version != config.schema_version) { + throw MismatchedConfigException("Realm at path '%1' already opened with different schema version.", config.path); + } + +#if REALM_ENABLE_SYNC + if (bool(m_config.sync_config) != bool(config.sync_config)) { + throw MismatchedConfigException("Realm at path '%1' already opened with different sync configurations.", config.path); + } + + if (config.sync_config) { + if (m_config.sync_config->user != config.sync_config->user) { + throw MismatchedConfigException("Realm at path '%1' already opened with different sync user.", config.path); + } + if (m_config.sync_config->realm_url() != config.sync_config->realm_url()) { + throw MismatchedConfigException("Realm at path '%1' already opened with different sync server URL.", config.path); + } + if (m_config.sync_config->transformer != config.sync_config->transformer) { + throw MismatchedConfigException("Realm at path '%1' already opened with different transformer.", config.path); + } + if (m_config.sync_config->realm_encryption_key != config.sync_config->realm_encryption_key) { + throw MismatchedConfigException("Realm at path '%1' already opened with sync session encryption key.", config.path); + } + } +#endif + // Mixing cached and uncached Realms is allowed + m_config.cache = config.cache; + + // Realm::update_schema() handles complaining about schema mismatches + } +} + +std::shared_ptr RealmCoordinator::get_cached_realm(Realm::Config const& config, AnyExecutionContextID execution_context) +{ + if (!config.cache) + return nullptr; + for (auto& cached_realm : m_weak_realm_notifiers) { + if (!cached_realm.is_cached_for_execution_context(execution_context)) + continue; + // can be null if we jumped in between ref count hitting zero and + // unregister_realm() getting the lock + if (auto realm = cached_realm.realm()) { + // If the file is uninitialized and was opened without a schema, + // do the normal schema init + if (realm->schema_version() == ObjectStore::NotVersioned) + break; + + // Otherwise if we have a realm schema it needs to be an exact + // match (even having the same properties but in different + // orders isn't good enough) + if (config.schema && realm->schema() != *config.schema) + throw MismatchedConfigException("Realm at path '%1' already opened on current thread with different schema.", config.path); + + return realm; + } + } + return nullptr; +} + +std::shared_ptr RealmCoordinator::get_realm(Realm::Config config) +{ + // realm must be declared before lock so that the mutex is released before + // we release the strong reference to realm, as Realm's destructor may want + // to acquire the same lock + std::shared_ptr realm; + std::unique_lock lock(m_realm_mutex); + set_config(config); + if ((realm = get_cached_realm(config, config.execution_context))) + return realm; + do_get_realm(std::move(config), realm, lock); + return realm; +} + +std::shared_ptr RealmCoordinator::get_realm() +{ + std::shared_ptr realm; + std::unique_lock lock(m_realm_mutex); + if ((realm = get_cached_realm(m_config, m_config.execution_context))) + return realm; + do_get_realm(m_config, realm, lock); + return realm; +} + +ThreadSafeReference RealmCoordinator::get_unbound_realm() +{ + ThreadSafeReference ref; + std::unique_lock lock(m_realm_mutex); + do_get_realm(m_config, ref.m_realm, lock, false); + return ref; +} + +void RealmCoordinator::do_get_realm(Realm::Config config, std::shared_ptr& realm, + std::unique_lock& realm_lock, bool bind_to_context) +{ + auto schema = std::move(config.schema); + auto migration_function = std::move(config.migration_function); + auto initialization_function = std::move(config.initialization_function); + auto audit_factory = std::move(config.audit_factory); + config.schema = {}; + + bool should_initialize_notifier = !config.immutable() && config.automatic_change_notifications; + realm = Realm::make_shared_realm(std::move(config), shared_from_this()); + if (!m_notifier && should_initialize_notifier) { + try { + m_notifier = std::make_unique(*this); + } + catch (std::system_error const& ex) { + throw RealmFileException(RealmFileException::Kind::AccessError, get_path(), ex.code().message(), ""); + } + } + m_weak_realm_notifiers.emplace_back(realm, realm->config().cache, bind_to_context); + + if (realm->config().sync_config) + create_sync_session(false, false); + + if (!m_audit_context && audit_factory) + m_audit_context = audit_factory(); + + realm_lock.unlock(); + if (schema) { +#if REALM_ENABLE_SYNC && REALM_PLATFORM_JAVA + // Workaround for https://github.com/realm/realm-java/issues/6619 + // Between Realm Java 5.10.0 and 5.13.0 created_at/updated_at was optional + // when created from Java, even though the Object Store code specified them as + // required. Due to how the Realm was initialized, this wasn't a problem before + // 5.13.0, but after that the Object Store initializer code was changed causing + // problems when Java clients upgraded. In order to prevent older clients from + // breaking with a schema mismatch when upgrading we thus fix the schema in transit. + // This means that schema reported back from Realm will be different than the one + // specified in the Java model class, but this seemed like the approach with the + // least amount of disadvantages. + if (realm->is_partial()) { + auto& new_schema = schema.value(); + auto current_schema = realm->schema(); + auto current_resultsets_schema_obj = current_schema.find("__ResultSets"); + if (current_resultsets_schema_obj != current_schema.end()) { + Property* p = current_resultsets_schema_obj->property_for_public_name("created_at"); + if (is_nullable(p->type)) { + auto it = new_schema.find("__ResultSets"); + if (it != new_schema.end()) { + auto created_at_property = it->property_for_public_name("created_at"); + auto updated_at_property = it->property_for_public_name("updated_at"); + if (created_at_property && updated_at_property) { + created_at_property->type = created_at_property->type | PropertyType::Nullable; + updated_at_property->type = updated_at_property->type | PropertyType::Nullable; + } + } + } + } + } +#endif + realm->update_schema(std::move(*schema), config.schema_version, std::move(migration_function), + std::move(initialization_function)); + } +#if REALM_ENABLE_SYNC + else if (realm->is_partial()) + _impl::ensure_partial_sync_schema_initialized(*realm); +#endif +} + +void RealmCoordinator::bind_to_context(Realm& realm, AnyExecutionContextID execution_context) +{ + std::unique_lock lock(m_realm_mutex); + for (auto& cached_realm : m_weak_realm_notifiers) { + if (!cached_realm.is_for_realm(&realm)) + continue; + cached_realm.bind_to_execution_context(execution_context); + return; + } + REALM_TERMINATE("Invalid Realm passed to bind_to_context()"); +} + +#if REALM_ENABLE_SYNC +std::shared_ptr RealmCoordinator::get_synchronized_realm(Realm::Config config) +{ + if (!config.sync_config) + throw std::logic_error("This method is only available for fully synchronized Realms."); + + std::unique_lock lock(m_realm_mutex); + set_config(config); + bool exists = File::exists(m_config.path); + create_sync_session(!config.sync_config->is_partial && !exists, exists); + return std::make_shared(shared_from_this(), m_sync_session); +} + +void RealmCoordinator::create_session(const Realm::Config& config) +{ + REALM_ASSERT(config.sync_config); + std::unique_lock lock(m_realm_mutex); + set_config(config); + bool exists = File::exists(m_config.path); + create_sync_session(!config.sync_config->is_partial && !exists, exists); +} +#endif + +bool RealmCoordinator::get_cached_schema(Schema& schema, uint64_t& schema_version, + uint64_t& transaction) const noexcept +{ + std::lock_guard lock(m_schema_cache_mutex); + if (!m_cached_schema) + return false; + schema = *m_cached_schema; + schema_version = m_schema_version; + transaction = m_schema_transaction_version_max; + return true; +} + +void RealmCoordinator::cache_schema(Schema const& new_schema, uint64_t new_schema_version, + uint64_t transaction_version) +{ + std::lock_guard lock(m_schema_cache_mutex); + if (transaction_version < m_schema_transaction_version_max) + return; + if (new_schema.empty() || new_schema_version == ObjectStore::NotVersioned) + return; + + m_cached_schema = new_schema; + m_schema_version = new_schema_version; + m_schema_transaction_version_min = transaction_version; + m_schema_transaction_version_max = transaction_version; +} + +void RealmCoordinator::clear_schema_cache_and_set_schema_version(uint64_t new_schema_version) +{ + std::lock_guard lock(m_schema_cache_mutex); + m_cached_schema = util::none; + m_schema_version = new_schema_version; +} + +void RealmCoordinator::advance_schema_cache(uint64_t previous, uint64_t next) +{ + std::lock_guard lock(m_schema_cache_mutex); + if (!m_cached_schema) + return; + REALM_ASSERT(previous <= m_schema_transaction_version_max); + if (next < m_schema_transaction_version_min) + return; + m_schema_transaction_version_min = std::min(previous, m_schema_transaction_version_min); + m_schema_transaction_version_max = std::max(next, m_schema_transaction_version_max); +} + +RealmCoordinator::RealmCoordinator() +#if REALM_ENABLE_SYNC +: m_partial_sync_work_queue(std::make_unique<_impl::partial_sync::WorkQueue>()) +#endif +{ +} + +RealmCoordinator::~RealmCoordinator() +{ + std::lock_guard coordinator_lock(s_coordinator_mutex); + for (auto it = s_coordinators_per_path.begin(); it != s_coordinators_per_path.end(); ) { + if (it->second.expired()) { + it = s_coordinators_per_path.erase(it); + } + else { + ++it; + } + } +} + +void RealmCoordinator::unregister_realm(Realm* realm) +{ + // Normally results notifiers are cleaned up by the background worker thread + // but if that's disabled we need to ensure that any notifiers from this + // Realm get cleaned up + if (!m_config.automatic_change_notifications) { + std::unique_lock lock(m_notifier_mutex); + clean_up_dead_notifiers(); + } + { + std::lock_guard lock(m_realm_mutex); + auto new_end = remove_if(begin(m_weak_realm_notifiers), end(m_weak_realm_notifiers), + [=](auto& notifier) { return notifier.expired() || notifier.is_for_realm(realm); }); + m_weak_realm_notifiers.erase(new_end, end(m_weak_realm_notifiers)); + } +} + +void RealmCoordinator::clear_cache() +{ + std::vector realms_to_close; + { + std::lock_guard lock(s_coordinator_mutex); + + for (auto& weak_coordinator : s_coordinators_per_path) { + auto coordinator = weak_coordinator.second.lock(); + if (!coordinator) { + continue; + } + + coordinator->m_notifier = nullptr; + + // Gather a list of all of the realms which will be removed + for (auto& weak_realm_notifier : coordinator->m_weak_realm_notifiers) { + if (auto realm = weak_realm_notifier.realm()) { + realms_to_close.push_back(realm); + } + } + } + + s_coordinators_per_path.clear(); + } + + // Close all of the previously cached Realms. This can't be done while + // s_coordinator_mutex is held as it may try to re-lock it. + for (auto& weak_realm : realms_to_close) { + if (auto realm = weak_realm.lock()) { + realm->close(); + } + } +} + +void RealmCoordinator::clear_all_caches() +{ + std::vector> to_clear; + { + std::lock_guard lock(s_coordinator_mutex); + for (auto iter : s_coordinators_per_path) { + to_clear.push_back(iter.second); + } + } + for (auto weak_coordinator : to_clear) { + if (auto coordinator = weak_coordinator.lock()) { + coordinator->clear_cache(); + } + } +} + +void RealmCoordinator::assert_no_open_realms() noexcept +{ +#ifdef REALM_DEBUG + std::lock_guard lock(s_coordinator_mutex); + REALM_ASSERT(s_coordinators_per_path.empty()); +#endif +} + +void RealmCoordinator::wake_up_notifier_worker() +{ + if (m_notifier) { + // FIXME: this wakes up the notification workers for all processes and + // not just us. This might be worth optimizing in the future. + m_notifier->notify_others(); + } +} + +void RealmCoordinator::commit_write(Realm& realm) +{ + REALM_ASSERT(!m_config.immutable()); + REALM_ASSERT(realm.is_in_transaction()); + + { + // Need to acquire this lock before committing or another process could + // perform a write and notify us before we get the chance to set the + // skip version + std::lock_guard l(m_notifier_mutex); + + transaction::commit(*Realm::Internal::get_shared_group(realm)); + + // Don't need to check m_new_notifiers because those don't skip versions + bool have_notifiers = std::any_of(m_notifiers.begin(), m_notifiers.end(), + [&](auto&& notifier) { return notifier->is_for_realm(realm); }); + if (have_notifiers) { + m_notifier_skip_version = Realm::Internal::get_shared_group(realm)->get_version_of_current_transaction(); + } + } + +#if REALM_ENABLE_SYNC + // Realm could be closed in did_change. So send sync notification first before did_change. + if (m_sync_session) { + auto& sg = Realm::Internal::get_shared_group(realm); + auto version = LangBindHelper::get_version_of_latest_snapshot(*sg); + SyncSession::Internal::nonsync_transact_notify(*m_sync_session, version); + } +#endif + if (realm.m_binding_context) { + realm.m_binding_context->did_change({}, {}); + } + + if (m_notifier) { + m_notifier->notify_others(); + } +} + +void RealmCoordinator::pin_version(VersionID versionid) +{ + REALM_ASSERT_DEBUG(!m_notifier_mutex.try_lock()); + if (m_async_error) { + return; + } + + if (!m_advancer_sg) { + try { + std::unique_ptr read_only_group; + Realm::open_with_config(m_config, m_advancer_history, m_advancer_sg, read_only_group, nullptr); + REALM_ASSERT(!read_only_group); + m_advancer_sg->begin_read(versionid); + } + catch (...) { + m_async_error = std::current_exception(); + m_advancer_sg = nullptr; + m_advancer_history = nullptr; + } + } + else if (m_new_notifiers.empty()) { + // If this is the first notifier then we don't already have a read transaction + REALM_ASSERT_3(m_advancer_sg->get_transact_stage(), ==, SharedGroup::transact_Ready); + m_advancer_sg->begin_read(versionid); + } + else { + REALM_ASSERT_3(m_advancer_sg->get_transact_stage(), ==, SharedGroup::transact_Reading); + if (versionid < m_advancer_sg->get_version_of_current_transaction()) { + // Ensure we're holding a readlock on the oldest version we have a + // handover object for, as handover objects don't + m_advancer_sg->end_read(); + m_advancer_sg->begin_read(versionid); + } + } +} + +void RealmCoordinator::register_notifier(std::shared_ptr notifier) +{ + auto version = notifier->version(); + auto& self = Realm::Internal::get_coordinator(*notifier->get_realm()); + { + std::lock_guard lock(self.m_notifier_mutex); + self.pin_version(version); + self.m_new_notifiers.push_back(std::move(notifier)); + } +} + +void RealmCoordinator::clean_up_dead_notifiers() +{ + auto swap_remove = [&](auto& container) { + bool did_remove = false; + for (size_t i = 0; i < container.size(); ++i) { + if (container[i]->is_alive()) + continue; + + // Ensure the notifier is destroyed here even if there's lingering refs + // to the async notifier elsewhere + container[i]->release_data(); + + if (container.size() > i + 1) + container[i] = std::move(container.back()); + container.pop_back(); + --i; + did_remove = true; + } + return did_remove; + }; + + if (swap_remove(m_notifiers)) { + // Make sure we aren't holding on to read versions needlessly if there + // are no notifiers left, but don't close them entirely as opening shared + // groups is expensive + if (m_notifiers.empty() && m_notifier_sg) { + REALM_ASSERT_3(m_notifier_sg->get_transact_stage(), ==, SharedGroup::transact_Reading); + m_notifier_sg->end_read(); + m_notifier_skip_version = {0, 0}; + } + } + if (swap_remove(m_new_notifiers) && m_advancer_sg) { + REALM_ASSERT_3(m_advancer_sg->get_transact_stage(), ==, SharedGroup::transact_Reading); + if (m_new_notifiers.empty()) { + m_advancer_sg->end_read(); + } + } +} + +void RealmCoordinator::on_change() +{ + run_async_notifiers(); + + std::lock_guard lock(m_realm_mutex); + for (auto& realm : m_weak_realm_notifiers) { + realm.notify(); + } +} + +namespace { +class IncrementalChangeInfo { +public: + IncrementalChangeInfo(SharedGroup& sg, + std::vector>& notifiers) + : m_sg(sg) + { + if (notifiers.empty()) + return; + + auto cmp = [&](auto&& lft, auto&& rgt) { + return lft->version() < rgt->version(); + }; + + // Sort the notifiers by their source version so that we can pull them + // all forward to the latest version in a single pass over the transaction log + std::sort(notifiers.begin(), notifiers.end(), cmp); + + // Preallocate the required amount of space in the vector so that we can + // safely give out pointers to within the vector + size_t count = 1; + for (auto it = notifiers.begin(), next = it + 1; next != notifiers.end(); ++it, ++next) { + if (cmp(*it, *next)) + ++count; + } + m_info.reserve(count); + m_info.resize(1); + m_current = &m_info[0]; + } + + TransactionChangeInfo& current() const { return *m_current; } + + bool advance_incremental(VersionID version) + { + if (version != m_sg.get_version_of_current_transaction()) { + transaction::advance(m_sg, *m_current, version); + m_info.push_back({ + m_current->table_modifications_needed, + m_current->table_moves_needed, + std::move(m_current->lists)}); + m_current = &m_info.back(); + return true; + } + return false; + } + + void advance_to_final(VersionID version) + { + if (!m_current) { + transaction::advance(m_sg, nullptr, version); + return; + } + + transaction::advance(m_sg, *m_current, version); + + // We now need to combine the transaction change info objects so that all of + // the notifiers see the complete set of changes from their first version to + // the most recent one + for (size_t i = m_info.size() - 1; i > 0; --i) { + auto& cur = m_info[i]; + if (cur.tables.empty()) + continue; + auto& prev = m_info[i - 1]; + if (prev.tables.empty()) { + prev.tables = cur.tables; + continue; + } + + for (size_t j = 0; j < prev.tables.size() && j < cur.tables.size(); ++j) { + prev.tables[j].merge(CollectionChangeBuilder{cur.tables[j]}); + } + prev.tables.reserve(cur.tables.size()); + while (prev.tables.size() < cur.tables.size()) { + prev.tables.push_back(cur.tables[prev.tables.size()]); + } + } + + // Copy the list change info if there are multiple LinkViews for the same LinkList + auto id = [](auto const& list) { return std::tie(list.table_ndx, list.col_ndx, list.row_ndx); }; + for (size_t i = 1; i < m_current->lists.size(); ++i) { + for (size_t j = i; j > 0; --j) { + if (id(m_current->lists[i]) == id(m_current->lists[j - 1])) { + m_current->lists[j - 1].changes->merge(CollectionChangeBuilder{*m_current->lists[i].changes}); + } + } + } + } + +private: + std::vector m_info; + TransactionChangeInfo* m_current = nullptr; + SharedGroup& m_sg; +}; +} // anonymous namespace + +void RealmCoordinator::run_async_notifiers() +{ + std::unique_lock lock(m_notifier_mutex); + + clean_up_dead_notifiers(); + + if (m_notifiers.empty() && m_new_notifiers.empty()) { + m_notifier_cv.notify_all(); + return; + } + + if (!m_async_error) { + open_helper_shared_group(); + } + + if (m_async_error) { + std::move(m_new_notifiers.begin(), m_new_notifiers.end(), std::back_inserter(m_notifiers)); + m_new_notifiers.clear(); + m_notifier_cv.notify_all(); + return; + } + + VersionID version; + + // Advance all of the new notifiers to the most recent version, if any + auto new_notifiers = std::move(m_new_notifiers); + IncrementalChangeInfo new_notifier_change_info(*m_advancer_sg, new_notifiers); + + if (!new_notifiers.empty()) { + REALM_ASSERT_3(m_advancer_sg->get_transact_stage(), ==, SharedGroup::transact_Reading); + REALM_ASSERT_3(m_advancer_sg->get_version_of_current_transaction().version, + <=, new_notifiers.front()->version().version); + + // The advancer SG can be at an older version than the oldest new notifier + // if a notifier was added and then removed before it ever got the chance + // to run, as we don't move the pin forward when removing dead notifiers + transaction::advance(*m_advancer_sg, nullptr, new_notifiers.front()->version()); + + // Advance each of the new notifiers to the latest version, attaching them + // to the SG at their handover version. This requires a unique + // TransactionChangeInfo for each source version, so that things don't + // see changes from before the version they were handed over from. + // Each Info has all of the changes between that source version and the + // next source version, and they'll be merged together later after + // releasing the lock + for (auto& notifier : new_notifiers) { + new_notifier_change_info.advance_incremental(notifier->version()); + notifier->attach_to(*m_advancer_sg); + notifier->add_required_change_info(new_notifier_change_info.current()); + } + new_notifier_change_info.advance_to_final(VersionID{}); + + for (auto& notifier : new_notifiers) { + notifier->detach(); + } + + // We want to advance the non-new notifiers to the same version as the + // new notifiers to avoid having to merge changes from any new + // transaction that happen immediately after this into the new notifier + // changes + version = m_advancer_sg->get_version_of_current_transaction(); + m_advancer_sg->end_read(); + } + else { + // If we have no new notifiers we want to just advance to the latest + // version, but we have to pick a "latest" version while holding the + // notifier lock to avoid advancing over a transaction which should be + // skipped + m_advancer_sg->begin_read(); + version = m_advancer_sg->get_version_of_current_transaction(); + m_advancer_sg->end_read(); + } + REALM_ASSERT_3(m_advancer_sg->get_transact_stage(), ==, SharedGroup::transact_Ready); + + auto skip_version = m_notifier_skip_version; + m_notifier_skip_version = {0, 0}; + + // Make a copy of the notifiers vector and then release the lock to avoid + // blocking other threads trying to register or unregister notifiers while we run them + auto notifiers = m_notifiers; + m_notifiers.insert(m_notifiers.end(), new_notifiers.begin(), new_notifiers.end()); + lock.unlock(); + + if (skip_version.version) { + REALM_ASSERT(!notifiers.empty()); + REALM_ASSERT(version >= skip_version); + IncrementalChangeInfo change_info(*m_notifier_sg, notifiers); + for (auto& notifier : notifiers) + notifier->add_required_change_info(change_info.current()); + change_info.advance_to_final(skip_version); + + for (auto& notifier : notifiers) + notifier->run(); + + lock.lock(); + for (auto& notifier : notifiers) + notifier->prepare_handover(); + lock.unlock(); + } + + // Advance the non-new notifiers to the same version as we advanced the new + // ones to (or the latest if there were no new ones) + IncrementalChangeInfo change_info(*m_notifier_sg, notifiers); + for (auto& notifier : notifiers) { + notifier->add_required_change_info(change_info.current()); + } + change_info.advance_to_final(version); + + // Attach the new notifiers to the main SG and move them to the main list + for (auto& notifier : new_notifiers) { + notifier->attach_to(*m_notifier_sg); + notifier->run(); + } + + // Change info is now all ready, so the notifiers can now perform their + // background work + for (auto& notifier : notifiers) { + notifier->run(); + } + + // Reacquire the lock while updating the fields that are actually read on + // other threads + lock.lock(); + for (auto& notifier : new_notifiers) { + notifier->prepare_handover(); + } + for (auto& notifier : notifiers) { + notifier->prepare_handover(); + } + clean_up_dead_notifiers(); + m_notifier_cv.notify_all(); +} + +void RealmCoordinator::open_helper_shared_group() +{ + if (!m_notifier_sg) { + try { + std::unique_ptr read_only_group; + Realm::open_with_config(m_config, m_notifier_history, m_notifier_sg, read_only_group, nullptr); + REALM_ASSERT(!read_only_group); + m_notifier_sg->begin_read(); + } + catch (...) { + // Store the error to be passed to the async notifiers + m_async_error = std::current_exception(); + m_notifier_sg = nullptr; + m_notifier_history = nullptr; + } + } + else if (m_notifiers.empty()) { + m_notifier_sg->begin_read(); + } +} + +void RealmCoordinator::advance_to_ready(Realm& realm) +{ + std::unique_lock lock(m_notifier_mutex); + _impl::NotifierPackage notifiers(m_async_error, notifiers_for_realm(realm), this); + lock.unlock(); + notifiers.package_and_wait(util::none); + + auto& sg = Realm::Internal::get_shared_group(realm); + if (notifiers) { + auto version = notifiers.version(); + if (version) { + auto current_version = sg->get_version_of_current_transaction(); + // Notifications are out of date, so just discard + // This should only happen if begin_read() was used to change the + // read version outside of our control + if (*version < current_version) + return; + // While there is a newer version, notifications are for the current + // version so just deliver them without advancing + if (*version == current_version) { + if (realm.m_binding_context) + realm.m_binding_context->will_send_notifications(); + notifiers.deliver(*sg); + notifiers.after_advance(); + if (realm.m_binding_context) + realm.m_binding_context->did_send_notifications(); + return; + } + } + } + + transaction::advance(sg, realm.m_binding_context.get(), notifiers); +} + +std::vector> RealmCoordinator::notifiers_for_realm(Realm& realm) +{ + std::vector> ret; + for (auto& notifier : m_new_notifiers) { + if (notifier->is_for_realm(realm)) + ret.push_back(notifier); + } + for (auto& notifier : m_notifiers) { + if (notifier->is_for_realm(realm)) + ret.push_back(notifier); + } + return ret; +} + +bool RealmCoordinator::advance_to_latest(Realm& realm) +{ + using sgf = SharedGroupFriend; + + auto& sg = Realm::Internal::get_shared_group(realm); + std::unique_lock lock(m_notifier_mutex); + _impl::NotifierPackage notifiers(m_async_error, notifiers_for_realm(realm), this); + lock.unlock(); + notifiers.package_and_wait(sgf::get_version_of_latest_snapshot(*sg)); + + auto version = sg->get_version_of_current_transaction(); + transaction::advance(sg, realm.m_binding_context.get(), notifiers); + + // Realm could be closed in the callbacks. + if (realm.is_closed()) + return false; + + return version != sg->get_version_of_current_transaction(); +} + +void RealmCoordinator::promote_to_write(Realm& realm) +{ + REALM_ASSERT(!realm.is_in_transaction()); + + std::unique_lock lock(m_notifier_mutex); + _impl::NotifierPackage notifiers(m_async_error, notifiers_for_realm(realm), this); + lock.unlock(); + + auto& sg = Realm::Internal::get_shared_group(realm); + transaction::begin(sg, realm.m_binding_context.get(), notifiers); +} + +void RealmCoordinator::process_available_async(Realm& realm) +{ + REALM_ASSERT(!realm.is_in_transaction()); + + std::unique_lock lock(m_notifier_mutex); + auto notifiers = notifiers_for_realm(realm); + if (notifiers.empty()) + return; + + if (auto error = m_async_error) { + lock.unlock(); + if (realm.m_binding_context) + realm.m_binding_context->will_send_notifications(); + for (auto& notifier : notifiers) + notifier->deliver_error(m_async_error); + if (realm.m_binding_context) + realm.m_binding_context->did_send_notifications(); + return; + } + + bool in_read = realm.is_in_read_transaction(); + auto& sg = Realm::Internal::get_shared_group(realm); + auto version = sg->get_version_of_current_transaction(); + auto package = [&](auto& notifier) { + return !(notifier->has_run() && (!in_read || notifier->version() == version) && notifier->package_for_delivery()); + }; + notifiers.erase(std::remove_if(begin(notifiers), end(notifiers), package), end(notifiers)); + if (notifiers.empty()) + return; + lock.unlock(); + + // no before advance because the Realm is already at the given version, + // because we're either sending initial notifications or the write was + // done on this Realm instance + + if (realm.m_binding_context) { + realm.m_binding_context->will_send_notifications(); + if (!sg) // i.e. the Realm was closed in the callback above + return; + } + + // Skip delivering if the Realm isn't in a read transaction + if (in_read) { + for (auto& notifier : notifiers) + notifier->deliver(*sg); + } + + // but still call the change callbacks + for (auto& notifier : notifiers) + notifier->after_advance(); + + if (realm.m_binding_context) + realm.m_binding_context->did_send_notifications(); +} + +void RealmCoordinator::set_transaction_callback(std::function fn) +{ + create_sync_session(false, false); + m_transaction_callback = std::move(fn); +} + +#if REALM_ENABLE_SYNC +_impl::partial_sync::WorkQueue& RealmCoordinator::partial_sync_work_queue() +{ + return *m_partial_sync_work_queue; +} +#endif diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/impl/results_notifier.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/impl/results_notifier.cpp new file mode 100644 index 0000000..4a711d3 --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/impl/results_notifier.cpp @@ -0,0 +1,252 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "impl/results_notifier.hpp" + +#include "shared_realm.hpp" + +using namespace realm; +using namespace realm::_impl; + +ResultsNotifier::ResultsNotifier(Results& target) +: CollectionNotifier(target.get_realm()) +, m_target_results(&target) +, m_target_is_in_table_order(target.is_in_table_order()) +{ + Query q = target.get_query(); + set_table(*q.get_table()); + m_query_handover = source_shared_group().export_for_handover(q, MutableSourcePayload::Move); + DescriptorOrdering::generate_patch(target.get_descriptor_ordering(), m_ordering_handover); +} + +void ResultsNotifier::target_results_moved(Results& old_target, Results& new_target) +{ + auto lock = lock_target(); + + REALM_ASSERT(m_target_results == &old_target); + m_target_results = &new_target; +} + +void ResultsNotifier::release_data() noexcept +{ + m_query = nullptr; +} + +// Most of the inter-thread synchronization for run(), prepare_handover(), +// attach_to(), detach(), release_data() and deliver() is done by +// RealmCoordinator external to this code, which has some potentially +// non-obvious results on which members are and are not safe to use without +// holding a lock. +// +// add_required_change_info(), attach_to(), detach(), run(), +// prepare_handover(), and release_data() are all only ever called on a single +// background worker thread. call_callbacks() and deliver() are called on the +// target thread. Calls to prepare_handover() and deliver() are guarded by a +// lock. +// +// In total, this means that the safe data flow is as follows: +// - add_Required_change_info(), prepare_handover(), attach_to(), detach() and +// release_data() can read members written by each other +// - deliver() can read members written to in prepare_handover(), deliver(), +// and call_callbacks() +// - call_callbacks() and read members written to in deliver() +// +// Separately from the handover data flow, m_target_results is guarded by the target lock + +bool ResultsNotifier::do_add_required_change_info(TransactionChangeInfo& info) +{ + REALM_ASSERT(m_query); + m_info = &info; + + auto& table = *m_query->get_table(); + if (!table.is_attached()) + return false; + + auto table_ndx = table.get_index_in_group(); + if (table_ndx == npos) { // is a subtable + auto& parent = *table.get_parent_table(); + size_t row_ndx = table.get_parent_row_index(); + size_t col_ndx = find_container_column(parent, row_ndx, &table, type_Table, &Table::get_subtable); + info.lists.push_back({parent.get_index_in_group(), row_ndx, col_ndx, &m_changes}); + } + else { // is a top-level table + if (info.table_moves_needed.size() <= table_ndx) + info.table_moves_needed.resize(table_ndx + 1); + info.table_moves_needed[table_ndx] = true; + } + + return has_run() && have_callbacks(); +} + +bool ResultsNotifier::need_to_run() +{ + REALM_ASSERT(m_info); + REALM_ASSERT(!m_tv.is_attached()); + + { + auto lock = lock_target(); + // Don't run the query if the results aren't actually going to be used + if (!get_realm() || (!have_callbacks() && !m_target_results->wants_background_updates())) { + return false; + } + } + + // If we've run previously, check if we need to rerun + if (has_run() && m_query->sync_view_if_needed() == m_last_seen_version) { + return false; + } + + return true; +} + +void ResultsNotifier::calculate_changes() +{ + size_t table_ndx = m_query->get_table()->get_index_in_group(); + if (has_run() && have_callbacks()) { + CollectionChangeBuilder* changes = nullptr; + if (table_ndx == npos) + changes = &m_changes; + else if (table_ndx < m_info->tables.size()) + changes = &m_info->tables[table_ndx]; + + std::vector next_rows; + next_rows.reserve(m_tv.size()); + for (size_t i = 0; i < m_tv.size(); ++i) + next_rows.push_back(m_tv[i].get_index()); + + util::Optional move_candidates; + if (changes) { + auto const& moves = changes->moves; + for (auto& idx : m_previous_rows) { + if (changes->deletions.contains(idx)) { + // check if this deletion was actually a move + auto it = lower_bound(begin(moves), end(moves), idx, + [](auto const& a, auto b) { return a.from < b; }); + idx = it != moves.end() && it->from == idx ? it->to : npos; + } + else + idx = changes->insertions.shift(changes->deletions.unshift(idx)); + } + if (m_target_is_in_table_order && !m_descriptor_ordering.will_apply_sort()) + move_candidates = changes->insertions; + } + + m_changes = CollectionChangeBuilder::calculate(m_previous_rows, next_rows, + get_modification_checker(*m_info, *m_query->get_table()), + move_candidates); + + m_previous_rows = std::move(next_rows); + } + else { + m_previous_rows.resize(m_tv.size()); + for (size_t i = 0; i < m_tv.size(); ++i) + m_previous_rows[i] = m_tv[i].get_index(); + } +} + +void ResultsNotifier::run() +{ + // Table's been deleted, so report all rows as deleted + if (!m_query->get_table()->is_attached()) { + m_changes = {}; + m_changes.deletions.set(m_previous_rows.size()); + m_previous_rows.clear(); + return; + } + + if (!need_to_run()) + return; + + m_query->sync_view_if_needed(); + m_tv = m_query->find_all(); + m_tv.apply_descriptor_ordering(m_descriptor_ordering); + m_last_seen_version = m_tv.sync_if_needed(); + + calculate_changes(); +} + +void ResultsNotifier::do_prepare_handover(SharedGroup& sg) +{ + if (!m_tv.is_attached()) { + // if the table version didn't change we can just reuse the same handover + // object and bump its version to the current SG version + if (m_tv_handover) + m_tv_handover->version = sg.get_version_of_current_transaction(); + + // add_changes() needs to be called even if there are no changes to + // clear the skip flag on the callbacks + add_changes(std::move(m_changes)); + return; + } + + REALM_ASSERT(m_tv.is_in_sync()); + + m_tv_handover = sg.export_for_handover(m_tv, MutableSourcePayload::Move); + + add_changes(std::move(m_changes)); + REALM_ASSERT(m_changes.empty()); + + // detach the TableView as we won't need it again and keeping it around + // makes advance_read() much more expensive + m_tv = {}; +} + +void ResultsNotifier::deliver(SharedGroup& sg) +{ + auto lock = lock_target(); + + // Target realm being null here indicates that we were unregistered while we + // were in the process of advancing the Realm version and preparing for + // delivery, i.e. the results was destroyed from the "wrong" thread + if (!get_realm()) { + return; + } + + REALM_ASSERT(!m_query_handover); + if (m_tv_to_deliver) { + Results::Internal::set_table_view(*m_target_results, + std::move(*sg.import_from_handover(std::move(m_tv_to_deliver)))); + } + REALM_ASSERT(!m_tv_to_deliver); +} + +bool ResultsNotifier::prepare_to_deliver() +{ + auto lock = lock_target(); + if (!get_realm()) + return false; + m_tv_to_deliver = std::move(m_tv_handover); + return true; +} + +void ResultsNotifier::do_attach_to(SharedGroup& sg) +{ + REALM_ASSERT(m_query_handover); + m_query = sg.import_from_handover(std::move(m_query_handover)); + m_descriptor_ordering = DescriptorOrdering::create_from_and_consume_patch(m_ordering_handover, *m_query->get_table()); +} + +void ResultsNotifier::do_detach_from(SharedGroup& sg) +{ + REALM_ASSERT(m_query); + REALM_ASSERT(!m_tv.is_attached()); + + DescriptorOrdering::generate_patch(m_descriptor_ordering, m_ordering_handover); + m_query_handover = sg.export_for_handover(*m_query, MutableSourcePayload::Move); + m_query = nullptr; +} diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/impl/transact_log_handler.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/impl/transact_log_handler.cpp new file mode 100644 index 0000000..2b3ff27 --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/impl/transact_log_handler.cpp @@ -0,0 +1,875 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "impl/transact_log_handler.hpp" + +#include "binding_context.hpp" +#include "impl/collection_notifier.hpp" +#include "index_set.hpp" +#include "shared_realm.hpp" + +#include +#include + +#include +#include + +using namespace realm; + +namespace { + +class KVOAdapter : public _impl::TransactionChangeInfo { +public: + KVOAdapter(std::vector& observers, BindingContext* context); + + void before(SharedGroup& sg); + void after(SharedGroup& sg); + +private: + BindingContext* m_context; + std::vector& m_observers; + std::vector m_invalidated; + + struct ListInfo { + BindingContext::ObserverState* observer; + _impl::CollectionChangeBuilder builder; + size_t col; + size_t initial_size; + }; + std::vector m_lists; + VersionID m_version; + + size_t new_table_ndx(size_t ndx) const { return ndx < table_indices.size() ? table_indices[ndx] : ndx; } +}; + +KVOAdapter::KVOAdapter(std::vector& observers, BindingContext* context) +: _impl::TransactionChangeInfo{} +, m_context(context) +, m_observers(observers) +{ + if (m_observers.empty()) + return; + + std::vector tables_needed; + for (auto& observer : observers) { + tables_needed.push_back(observer.table_ndx); + } + std::sort(begin(tables_needed), end(tables_needed)); + tables_needed.erase(std::unique(begin(tables_needed), end(tables_needed)), end(tables_needed)); + + auto realm = context->realm.lock(); + auto& group = realm->read_group(); + for (auto& observer : observers) { + auto table = group.get_table(observer.table_ndx); + for (size_t i = 0, count = table->get_column_count(); i < count; ++i) { + auto type = table->get_column_type(i); + if (type == type_LinkList) + m_lists.push_back({&observer, {}, i, size_t(-1)}); + else if (type == type_Table) + m_lists.push_back({&observer, {}, i, table->get_subtable_size(i, observer.row_ndx)}); + } + } + + auto max = std::max_element(begin(tables_needed), end(tables_needed)); + if (*max >= table_modifications_needed.size()) + table_modifications_needed.resize(*max + 1, false); + if (*max >= table_moves_needed.size()) + table_moves_needed.resize(*max + 1, false); + for (auto& tbl : tables_needed) { + table_modifications_needed[tbl] = true; + table_moves_needed[tbl] = true; + } + for (auto& list : m_lists) + lists.push_back({list.observer->table_ndx, list.observer->row_ndx, list.col, &list.builder}); +} + +void KVOAdapter::before(SharedGroup& sg) +{ + if (!m_context) + return; + + m_version = sg.get_version_of_current_transaction(); + if (tables.empty()) + return; + + for (auto& observer : m_observers) { + size_t table_ndx = new_table_ndx(observer.table_ndx); + if (table_ndx >= tables.size()) + continue; + + auto const& table = tables[table_ndx]; + auto const& moves = table.moves; + auto idx = observer.row_ndx; + auto it = lower_bound(begin(moves), end(moves), idx, + [](auto const& a, auto b) { return a.from < b; }); + if (it != moves.end() && it->from == idx) + idx = it->to; + else if (table.deletions.contains(idx)) { + m_invalidated.push_back(observer.info); + continue; + } + else + idx = table.insertions.shift(table.deletions.unshift(idx)); + if (table.modifications.contains(idx)) { + observer.changes.resize(table.columns.size()); + size_t i = 0; + for (auto& c : table.columns) { + auto& change = observer.changes[i]; + if (table_ndx >= column_indices.size() || column_indices[table_ndx].empty()) + change.initial_column_index = i; + else if (i >= column_indices[table_ndx].size()) + change.initial_column_index = i - column_indices[table_ndx].size() + column_indices[table_ndx].back() + 1; + else + change.initial_column_index = column_indices[table_ndx][i]; + if (change.initial_column_index != npos && c.contains(idx)) + change.kind = BindingContext::ColumnInfo::Kind::Set; + ++i; + } + } + } + + for (auto& list : m_lists) { + if (list.builder.empty()) { + // We may have pre-emptively marked the column as modified if the + // LinkList was selected but the actual changes made ended up being + // a no-op + if (list.col < list.observer->changes.size()) + list.observer->changes[list.col].kind = BindingContext::ColumnInfo::Kind::None; + continue; + } + // If the containing row was deleted then changes will be empty + if (list.observer->changes.empty()) { + REALM_ASSERT_DEBUG(tables[new_table_ndx(list.observer->table_ndx)].deletions.contains(list.observer->row_ndx)); + continue; + } + // otherwise the column should have been marked as modified + REALM_ASSERT(list.col < list.observer->changes.size()); + auto& builder = list.builder; + auto& changes = list.observer->changes[list.col]; + + builder.modifications.remove(builder.insertions); + + // KVO can't express moves (becaue NSArray doesn't have them), so + // transform them into a series of sets on each affected index when possible + if (!builder.moves.empty() && builder.insertions.count() == builder.moves.size() && builder.deletions.count() == builder.moves.size()) { + changes.kind = BindingContext::ColumnInfo::Kind::Set; + changes.indices = builder.modifications; + changes.indices.add(builder.deletions); + + // Iterate over each of the rows which may have been shifted by + // the moves and check if it actually has been, or if it's ended + // up in the same place as it started (either because the moves were + // actually a swap that doesn't effect the rows in between, or the + // combination of moves happen to leave some intermediate rows in + // the same place) + auto in_range = [](auto& it, auto end, size_t i) { + if (it != end && i >= it->second) + ++it; + return it != end && i >= it->first && i < it->second; + }; + + auto del_it = builder.deletions.begin(), del_end = builder.deletions.end(); + auto ins_it = builder.insertions.begin(), ins_end = builder.insertions.end(); + size_t start = std::min(ins_it->first, del_it->first); + size_t end = std::max(std::prev(ins_end)->second, std::prev(del_end)->second); + ptrdiff_t shift = 0; + for (size_t i = start; i < end; ++i) { + if (in_range(del_it, del_end, i)) + --shift; + else if (in_range(ins_it, ins_end, i + shift)) + ++shift; + if (shift != 0) + changes.indices.add(i); + } + } + // KVO can't express multiple types of changes at once + else if (builder.insertions.empty() + builder.modifications.empty() + builder.deletions.empty() < 2) { + changes.kind = BindingContext::ColumnInfo::Kind::SetAll; + } + else if (!builder.insertions.empty()) { + changes.kind = BindingContext::ColumnInfo::Kind::Insert; + changes.indices = builder.insertions; + } + else if (!builder.modifications.empty()) { + changes.kind = BindingContext::ColumnInfo::Kind::Set; + changes.indices = builder.modifications; + } + else { + REALM_ASSERT(!builder.deletions.empty()); + changes.kind = BindingContext::ColumnInfo::Kind::Remove; + // Table clears don't come with the size, so we need to fix up the + // notification to make it just delete all rows that actually existed + if (std::prev(builder.deletions.end())->second > list.initial_size) + changes.indices.set(list.initial_size); + else + changes.indices = builder.deletions; + } + } + m_context->will_change(m_observers, m_invalidated); +} + +void KVOAdapter::after(SharedGroup& sg) +{ + if (!m_context) + return; + m_context->did_change(m_observers, m_invalidated, + m_version != VersionID{} && + m_version != sg.get_version_of_current_transaction()); +} + +template +struct MarkDirtyMixin { + bool mark_dirty(size_t row, size_t col, _impl::Instruction instr=_impl::instr_Set) + { + // Ignore SetDefault and SetUnique as those conceptually cannot be + // changes to existing rows + if (instr == _impl::instr_Set) + static_cast(this)->mark_dirty(row, col); + return true; + } + + bool set_int(size_t c, size_t r, int_fast64_t, _impl::Instruction i, size_t) { return mark_dirty(r, c, i); } + bool set_bool(size_t c, size_t r, bool, _impl::Instruction i) { return mark_dirty(r, c, i); } + bool set_float(size_t c, size_t r, float, _impl::Instruction i) { return mark_dirty(r, c, i); } + bool set_double(size_t c, size_t r, double, _impl::Instruction i) { return mark_dirty(r, c, i); } + bool set_string(size_t c, size_t r, StringData, _impl::Instruction i, size_t) { return mark_dirty(r, c, i); } + bool set_binary(size_t c, size_t r, BinaryData, _impl::Instruction i) { return mark_dirty(r, c, i); } + bool set_olddatetime(size_t c, size_t r, OldDateTime, _impl::Instruction i) { return mark_dirty(r, c, i); } + bool set_timestamp(size_t c, size_t r, Timestamp, _impl::Instruction i) { return mark_dirty(r, c, i); } + bool set_table(size_t c, size_t r, _impl::Instruction i) { return mark_dirty(r, c, i); } + bool set_mixed(size_t c, size_t r, const Mixed&, _impl::Instruction i) { return mark_dirty(r, c, i); } + bool set_link(size_t c, size_t r, size_t, size_t, _impl::Instruction i) { return mark_dirty(r, c, i); } + bool set_null(size_t c, size_t r, _impl::Instruction i, size_t) { return mark_dirty(r, c, i); } + + bool add_int(size_t col, size_t row, int_fast64_t) { return mark_dirty(row, col); } + bool nullify_link(size_t col, size_t row, size_t) { return mark_dirty(row, col); } + bool insert_substring(size_t col, size_t row, size_t, StringData) { return mark_dirty(row, col); } + bool erase_substring(size_t col, size_t row, size_t, size_t) { return mark_dirty(row, col); } + + bool set_int_unique(size_t, size_t, size_t, int_fast64_t) { return true; } + bool set_string_unique(size_t, size_t, size_t, StringData) { return true; } + + bool add_row_with_key(size_t, size_t, size_t, int64_t) { return true; } +}; + +class TransactLogValidationMixin { + // Index of currently selected table + size_t m_current_table = 0; + + REALM_NORETURN + REALM_NOINLINE + void schema_error() + { + throw _impl::UnsupportedSchemaChange(); + } + +protected: + size_t current_table() const noexcept { return m_current_table; } + +public: + + bool select_table(size_t group_level_ndx, int, const size_t*) noexcept + { + m_current_table = group_level_ndx; + return true; + } + + // Removing or renaming things while a Realm is open is never supported + bool erase_group_level_table(size_t, size_t) { schema_error(); } + bool rename_group_level_table(size_t, StringData) { schema_error(); } + bool erase_column(size_t) { schema_error(); } + bool erase_link_column(size_t, size_t, size_t) { schema_error(); } + bool rename_column(size_t, StringData) { schema_error(); } + + // Schema changes which don't involve a change in the schema version are + // allowed + bool add_search_index(size_t) { return true; } + bool remove_search_index(size_t) { return true; } + + // Additive changes and reorderings are supported + bool insert_group_level_table(size_t, size_t, StringData) { return true; } + bool insert_column(size_t, DataType, StringData, bool) { return true; } + bool insert_link_column(size_t, DataType, StringData, size_t, size_t) { return true; } + bool set_link_type(size_t, LinkType) { return true; } + bool move_column(size_t, size_t) { return true; } + bool move_group_level_table(size_t, size_t) { return true; } + + // Non-schema changes are all allowed + void parse_complete() { } + bool select_descriptor(int, const size_t*) { return true; } + bool select_link_list(size_t, size_t, size_t) { return true; } + bool insert_empty_rows(size_t, size_t, size_t, bool) { return true; } + bool erase_rows(size_t, size_t, size_t, bool) { return true; } + bool swap_rows(size_t, size_t) { return true; } + bool move_row(size_t, size_t) { return true; } + bool clear_table(size_t=0) noexcept { return true; } + bool link_list_set(size_t, size_t, size_t) { return true; } + bool link_list_insert(size_t, size_t, size_t) { return true; } + bool link_list_erase(size_t, size_t) { return true; } + bool link_list_nullify(size_t, size_t) { return true; } + bool link_list_clear(size_t) { return true; } + bool link_list_move(size_t, size_t) { return true; } + bool link_list_swap(size_t, size_t) { return true; } + bool merge_rows(size_t, size_t) { return true; } + bool optimize_table() { return true; } +}; + + +// A transaction log handler that just validates that all operations made are +// ones supported by the object store +struct TransactLogValidator : public TransactLogValidationMixin, public MarkDirtyMixin { + void mark_dirty(size_t, size_t) { } +}; + +// Move the value at container[from] to container[to], shifting everything in +// between, or do nothing if either are out of bounds +template +void rotate(Container& container, size_t from, size_t to) +{ + REALM_ASSERT(from != to); + if (from >= container.size() && to >= container.size()) + return; + if (from >= container.size() || to >= container.size()) + container.resize(std::max(from, to) + 1); + if (from < to) + std::rotate(begin(container) + from, begin(container) + from + 1, begin(container) + to + 1); + else + std::rotate(begin(container) + to, begin(container) + from, begin(container) + from + 1); +} + +// Insert a default-initialized value at pos if there is anything after pos in the container. +template +void insert_empty_at(Container& container, size_t pos) +{ + if (pos < container.size()) + container.emplace(container.begin() + pos); +} + +// Shift `value` to reflect a move from `from` to `to` +void adjust_for_move(size_t& value, size_t from, size_t to) +{ + if (value == from) + value = to; + else if (value > from && value <= to) + --value; + else if (value < from && value >= to) + ++value; +} + +void adjust_for_move(std::vector& values, size_t from, size_t to) +{ + for (auto& value : values) + adjust_for_move(value, from, to); +} + +void expand_to(std::vector& cols, size_t i) +{ + auto old_size = cols.size(); + if (old_size > i) + return; + + cols.resize(std::max(old_size * 2, i + 1)); + std::iota(begin(cols) + old_size, end(cols), old_size == 0 ? 0 : cols[old_size - 1] + 1); +} + +void adjust_ge(std::vector& values, size_t i) +{ + for (auto& value : values) { + if (value >= i) + ++value; + } +} + +// Extends TransactLogValidator to track changes made to LinkViews +class TransactLogObserver : public TransactLogValidationMixin, public MarkDirtyMixin { + _impl::TransactionChangeInfo& m_info; + _impl::CollectionChangeBuilder* m_active_list = nullptr; + _impl::CollectionChangeBuilder* m_active_table = nullptr; + _impl::CollectionChangeBuilder* m_active_descriptor = nullptr; + + bool m_need_move_info = false; + bool m_is_top_level_table = true; + + _impl::CollectionChangeBuilder* find_list(size_t tbl, size_t col, size_t row) + { + // When there are multiple source versions there could be multiple + // change objects for a single LinkView, in which case we need to use + // the last one + for (auto it = m_info.lists.rbegin(), end = m_info.lists.rend(); it != end; ++it) { + if (it->table_ndx == tbl && it->row_ndx == row && it->col_ndx == col) + return it->changes; + } + return nullptr; + } + +public: + TransactLogObserver(_impl::TransactionChangeInfo& info) + : m_info(info) { } + + void mark_dirty(size_t row, size_t col) + { + if (m_active_table) + m_active_table->modify(row, col); + } + + void parse_complete() + { + for (auto& table : m_info.tables) + table.parse_complete(); + for (auto& list : m_info.lists) + list.changes->clean_up_stale_moves(); + } + + bool select_descriptor(int levels, const size_t*) noexcept + { + if (levels == 0) // schema of selected table is being modified + m_active_descriptor = m_active_table; + else // schema of subtable is being modified; currently don't need to track this + m_active_descriptor = nullptr; + return true; + } + + bool select_table(size_t group_level_ndx, int len, size_t const* path) noexcept + { + TransactLogValidationMixin::select_table(group_level_ndx, len, path); + m_active_table = nullptr; + m_is_top_level_table = true; + + // Nested subtables currently not supported + if (len > 1) { + m_is_top_level_table = false; + return true; + } + + auto tbl_ndx = current_table(); + if (!m_info.track_all && (tbl_ndx >= m_info.table_modifications_needed.size() || !m_info.table_modifications_needed[tbl_ndx])) + return true; + + m_need_move_info = m_info.track_all || (tbl_ndx < m_info.table_moves_needed.size() && + m_info.table_moves_needed[tbl_ndx]); + if (m_info.tables.size() <= tbl_ndx) + m_info.tables.resize(std::max(m_info.tables.size() * 2, tbl_ndx + 1)); + m_active_table = &m_info.tables[tbl_ndx]; + + if (len == 1) { + // Mark the cell containing the subtable as modified since selecting + // a table is always followed by a modification of some sort + size_t col = path[0]; + size_t row = path[1]; + mark_dirty(row, col); + + m_active_table = nullptr; + m_is_top_level_table = false; + if (auto table = find_list(current_table(), col, row)) { + m_active_table = table; + m_need_move_info = true; + } + } + return true; + } + + bool select_link_list(size_t col, size_t row, size_t) + { + mark_dirty(row, col); + m_active_list = find_list(current_table(), col, row); + return true; + } + + bool link_list_set(size_t index, size_t, size_t) + { + if (m_active_list) + m_active_list->modify(index); + return true; + } + + bool link_list_insert(size_t index, size_t, size_t) + { + if (m_active_list) + m_active_list->insert(index); + return true; + } + + bool link_list_erase(size_t index, size_t) + { + if (m_active_list) + m_active_list->erase(index); + return true; + } + + bool link_list_nullify(size_t index, size_t prior_size) + { + return link_list_erase(index, prior_size); + } + + bool link_list_swap(size_t index1, size_t index2) + { + link_list_set(index1, 0, npos); + link_list_set(index2, 0, npos); + return true; + } + + bool link_list_clear(size_t old_size) + { + if (m_active_list) + m_active_list->clear(old_size); + return true; + } + + bool link_list_move(size_t from, size_t to) + { + if (m_active_list) + m_active_list->move(from, to); + return true; + } + + bool insert_empty_rows(size_t row_ndx, size_t num_rows_to_insert, size_t, bool) + { + if (m_active_table) + m_active_table->insert(row_ndx, num_rows_to_insert, m_need_move_info); + if (!m_is_top_level_table) + return true; + for (auto& list : m_info.lists) { + if (list.table_ndx == current_table() && list.row_ndx >= row_ndx) + list.row_ndx += num_rows_to_insert; + } + return true; + } + + bool add_row_with_key(size_t row_ndx, size_t prior_num_rows, size_t, int64_t) + { + insert_empty_rows(row_ndx, 1, prior_num_rows, false); + return true; + } + + bool erase_rows(size_t row_ndx, size_t, size_t prior_num_rows, bool unordered) + { + if (!unordered) { + if (m_active_table) + m_active_table->erase(row_ndx); + return true; + } + size_t last_row = prior_num_rows - 1; + if (m_active_table) + m_active_table->move_over(row_ndx, last_row, m_need_move_info); + + if (!m_is_top_level_table) + return true; + for (size_t i = 0; i < m_info.lists.size(); ++i) { + auto& list = m_info.lists[i]; + if (list.table_ndx != current_table()) + continue; + if (list.row_ndx == row_ndx) { + if (i + 1 < m_info.lists.size()) + m_info.lists[i] = std::move(m_info.lists.back()); + m_info.lists.pop_back(); + continue; + } + if (list.row_ndx == last_row) + list.row_ndx = row_ndx; + } + + return true; + } + + bool swap_rows(size_t row_ndx_1, size_t row_ndx_2) { + REALM_ASSERT(row_ndx_1 < row_ndx_2); + if (!m_is_top_level_table) { + if (m_active_table) { + m_active_table->move(row_ndx_1, row_ndx_2); + if (row_ndx_1 + 1 != row_ndx_2) + m_active_table->move(row_ndx_2 - 1, row_ndx_1); + } + return true; + } + + if (m_active_table) + m_active_table->swap(row_ndx_1, row_ndx_2, m_need_move_info); + for (auto& list : m_info.lists) { + if (list.table_ndx == current_table()) { + if (list.row_ndx == row_ndx_1) + list.row_ndx = row_ndx_2; + else if (list.row_ndx == row_ndx_2) + list.row_ndx = row_ndx_1; + } + } + return true; + } + + bool move_row(size_t from_ndx, size_t to_ndx) { + // Move row is not supported for top level tables: + REALM_ASSERT(!m_active_table || !m_is_top_level_table); + + if (m_active_table) + m_active_table->move(from_ndx, to_ndx); + return true; + } + + bool merge_rows(size_t from, size_t to) + { + if (m_active_table) + m_active_table->subsume(from, to, m_need_move_info); + if (!m_is_top_level_table) + return true; + for (auto& list : m_info.lists) { + if (list.table_ndx == current_table() && list.row_ndx == from) + list.row_ndx = to; + } + return true; + } + + bool clear_table(size_t=0) + { + auto tbl_ndx = current_table(); + if (m_active_table) + m_active_table->clear(std::numeric_limits::max()); + if (!m_is_top_level_table) + return true; + auto it = remove_if(begin(m_info.lists), end(m_info.lists), + [&](auto const& lv) { return lv.table_ndx == tbl_ndx; }); + m_info.lists.erase(it, end(m_info.lists)); + return true; + } + + bool insert_column(size_t ndx, DataType, StringData, bool) + { + m_info.schema_changed = true; + + if (m_active_descriptor) + m_active_descriptor->insert_column(ndx); + if (m_active_descriptor != m_active_table || !m_is_top_level_table) + return true; + for (auto& list : m_info.lists) { + if (list.table_ndx == current_table() && list.col_ndx >= ndx) + ++list.col_ndx; + } + if (m_info.column_indices.size() <= current_table()) + m_info.column_indices.resize(current_table() + 1); + auto& indices = m_info.column_indices[current_table()]; + expand_to(indices, ndx); + insert_empty_at(indices, ndx); + indices[ndx] = npos; + return true; + } + + void prepare_table_indices() + { + if (m_info.table_indices.empty() && !m_info.table_modifications_needed.empty()) { + m_info.table_indices.resize(m_info.table_modifications_needed.size()); + std::iota(begin(m_info.table_indices), end(m_info.table_indices), 0); + } + } + + bool insert_group_level_table(size_t ndx, size_t, StringData) + { + m_info.schema_changed = true; + + for (auto& list : m_info.lists) { + if (list.table_ndx >= ndx) + ++list.table_ndx; + } + prepare_table_indices(); + adjust_ge(m_info.table_indices, ndx); + insert_empty_at(m_info.tables, ndx); + insert_empty_at(m_info.table_moves_needed, ndx); + insert_empty_at(m_info.table_modifications_needed, ndx); + return true; + } + + bool move_column(size_t from, size_t to) + { + m_info.schema_changed = true; + + if (m_active_descriptor) + m_active_descriptor->move_column(from, to); + if (m_active_descriptor != m_active_table || !m_is_top_level_table) + return true; + for (auto& list : m_info.lists) { + if (list.table_ndx == current_table()) + adjust_for_move(list.col_ndx, from, to); + } + if (m_info.column_indices.size() <= current_table()) + m_info.column_indices.resize(current_table() + 1); + expand_to(m_info.column_indices[current_table()], std::max(from, to) + 1); + rotate(m_info.column_indices[current_table()], from, to); + return true; + } + + bool move_group_level_table(size_t from, size_t to) + { + m_info.schema_changed = true; + + for (auto& list : m_info.lists) + adjust_for_move(list.table_ndx, from, to); + + prepare_table_indices(); + adjust_for_move(m_info.table_indices, from, to); + rotate(m_info.tables, from, to); + rotate(m_info.table_modifications_needed, from, to); + rotate(m_info.table_moves_needed, from, to); + return true; + } + + bool insert_link_column(size_t ndx, DataType type, StringData name, size_t, size_t) { return insert_column(ndx, type, name, false); } +}; + +class KVOTransactLogObserver : public TransactLogObserver { + KVOAdapter m_adapter; + _impl::NotifierPackage& m_notifiers; + SharedGroup& m_sg; + +public: + KVOTransactLogObserver(std::vector& observers, + BindingContext* context, + _impl::NotifierPackage& notifiers, + SharedGroup& sg) + : TransactLogObserver(m_adapter) + , m_adapter(observers, context) + , m_notifiers(notifiers) + , m_sg(sg) + { + } + + ~KVOTransactLogObserver() + { + m_adapter.after(m_sg); + } + + void parse_complete() + { + TransactLogObserver::parse_complete(); + m_adapter.before(m_sg); + + using sgf = _impl::SharedGroupFriend; + m_notifiers.package_and_wait(sgf::get_version_of_latest_snapshot(m_sg)); + m_notifiers.before_advance(); + } +}; + +template +void advance_with_notifications(BindingContext* context, const std::unique_ptr& sg, + Func&& func, _impl::NotifierPackage& notifiers) +{ + auto old_version = sg->get_version_of_current_transaction(); + std::vector observers; + if (context) { + observers = context->get_observed_rows(); + } + + // Advancing to the latest version with notifiers requires using the full + // transaction log observer so that we have a point where we know what + // version we're going to before we actually advance to that version + if (observers.empty() && (!notifiers || notifiers.version())) { + notifiers.before_advance(); + func(TransactLogValidator()); + auto new_version = sg->get_version_of_current_transaction(); + if (context && old_version != new_version) + context->did_change({}, {}); + if (!sg) // did_change() could close the Realm. Just return if it does. + return; + if (context) + context->will_send_notifications(); + if (!sg) // will_send_notifications() could close the Realm. Just return if it does. + return; + // did_change() can change the read version, and if it does we can't + // deliver notifiers + if (new_version == sg->get_version_of_current_transaction()) + notifiers.deliver(*sg); + notifiers.after_advance(); + if (sg && context) + context->did_send_notifications(); + return; + } + + if (context) + context->will_send_notifications(); + func(KVOTransactLogObserver(observers, context, notifiers, *sg)); + notifiers.package_and_wait(sg->get_version_of_current_transaction().version); // is a no-op if parse_complete() was called + notifiers.deliver(*sg); + notifiers.after_advance(); + if (context) + context->did_send_notifications(); +} + +} // anonymous namespace + +namespace realm { +namespace _impl { + +UnsupportedSchemaChange::UnsupportedSchemaChange() +: std::logic_error("Schema mismatch detected: another process has modified the Realm file's schema in an incompatible way") +{ +} + +namespace transaction { +void advance(SharedGroup& sg, BindingContext*, VersionID version) +{ + LangBindHelper::advance_read(sg, TransactLogValidator(), version); +} + +void advance(const std::unique_ptr& sg, BindingContext* context, NotifierPackage& notifiers) +{ + advance_with_notifications(context, sg, [&](auto&&... args) { + LangBindHelper::advance_read(*sg, std::move(args)..., notifiers.version().value_or(VersionID{})); + }, notifiers); +} + +void begin_without_validation(SharedGroup& sg) +{ + LangBindHelper::promote_to_write(sg); +} + +void begin(const std::unique_ptr& sg, BindingContext* context, NotifierPackage& notifiers) +{ + advance_with_notifications(context, sg, [&](auto&&... args) { + LangBindHelper::promote_to_write(*sg, std::move(args)...); + }, notifiers); +} + +void commit(SharedGroup& sg) +{ + LangBindHelper::commit_and_continue_as_read(sg); +} + +void cancel(SharedGroup& sg, BindingContext* context) +{ + std::vector observers; + if (context) { + observers = context->get_observed_rows(); + } + if (observers.empty()) { + LangBindHelper::rollback_and_continue_as_read(sg); + return; + } + + _impl::NotifierPackage notifiers; + LangBindHelper::rollback_and_continue_as_read(sg, KVOTransactLogObserver(observers, context, notifiers, sg)); +} + +void advance(SharedGroup& sg, TransactionChangeInfo& info, VersionID version) +{ + if (!info.track_all && info.table_modifications_needed.empty() && info.lists.empty()) { + LangBindHelper::advance_read(sg, version); + } + else { + LangBindHelper::advance_read(sg, TransactLogObserver(info), version); + } +} + +} // namespace transaction +} // namespace _impl +} // namespace realm diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/impl/weak_realm_notifier.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/impl/weak_realm_notifier.cpp new file mode 100644 index 0000000..d52c2e6 --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/impl/weak_realm_notifier.cpp @@ -0,0 +1,56 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "impl/weak_realm_notifier.hpp" + +#include "shared_realm.hpp" +#include "util/event_loop_signal.hpp" + +using namespace realm; +using namespace realm::_impl; + +WeakRealmNotifier::WeakRealmNotifier(const std::shared_ptr& realm, bool cache, bool bind_to_context) +: m_realm(realm) +, m_execution_context(realm->config().execution_context) +, m_realm_key(realm.get()) +, m_cache(cache) +, m_signal(bind_to_context ? std::make_shared>(Callback{realm}) : nullptr) +{ +} + +WeakRealmNotifier::~WeakRealmNotifier() = default; + +void WeakRealmNotifier::Callback::operator()() const +{ + if (auto realm = weak_realm.lock()) { + realm->notify(); + } +} + +void WeakRealmNotifier::notify() +{ + if (m_signal) + m_signal->notify(); +} + +void WeakRealmNotifier::bind_to_execution_context(AnyExecutionContextID context) +{ + REALM_ASSERT(!m_signal); + m_signal = std::make_shared>(Callback{m_realm}); + m_execution_context = context; +} diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/index_set.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/index_set.cpp new file mode 100644 index 0000000..e1a3b10 --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/index_set.cpp @@ -0,0 +1,707 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "index_set.hpp" + +#include + +#include + +using namespace realm; +using namespace realm::_impl; + +const size_t IndexSet::npos; + +template +void MutableChunkedRangeVectorIterator::set(size_t front, size_t back) +{ + this->m_outer->count -= this->m_inner->second - this->m_inner->first; + if (this->offset() == 0) { + this->m_outer->begin = front; + } + if (this->m_inner == &this->m_outer->data.back()) { + this->m_outer->end = back; + } + this->m_outer->count += back - front; + this->m_inner->first = front; + this->m_inner->second = back; +} + +template +void MutableChunkedRangeVectorIterator::adjust(ptrdiff_t front, ptrdiff_t back) +{ + if (this->offset() == 0) { + this->m_outer->begin += front; + } + if (this->m_inner == &this->m_outer->data.back()) { + this->m_outer->end += back; + } + this->m_outer->count += -front + back; + this->m_inner->first += front; + this->m_inner->second += back; +} + +template +void MutableChunkedRangeVectorIterator::shift(ptrdiff_t distance) +{ + if (this->offset() == 0) { + this->m_outer->begin += distance; + } + if (this->m_inner == &this->m_outer->data.back()) { + this->m_outer->end += distance; + } + this->m_inner->first += distance; + this->m_inner->second += distance; +} + +void ChunkedRangeVector::push_back(value_type value) +{ + if (!empty() && m_data.back().data.size() < max_size) { + auto& range = m_data.back(); + REALM_ASSERT(range.end <= value.first); + + range.data.push_back(value); + range.count += value.second - value.first; + range.end = value.second; + } + else { + m_data.push_back({{value}, value.first, value.second, value.second - value.first}); + } + verify(); +} + +ChunkedRangeVector::iterator ChunkedRangeVector::insert(iterator pos, value_type value) +{ + if (pos.m_outer == m_data.end()) { + push_back(std::move(value)); + return std::prev(end()); + } + + pos = ensure_space(pos); + auto& chunk = *pos.m_outer; + pos.m_inner = &*chunk.data.insert(pos.m_outer->data.begin() + pos.offset(), value); + chunk.count += value.second - value.first; + chunk.begin = std::min(chunk.begin, value.first); + chunk.end = std::max(chunk.end, value.second); + + verify(); + return pos; +} + +ChunkedRangeVector::iterator ChunkedRangeVector::ensure_space(iterator pos) +{ + if (pos.m_outer->data.size() + 1 <= max_size) + return pos; + + auto offset = pos.offset(); + + // Split the chunk in half to make space for the new insertion + auto new_pos = m_data.insert(pos.m_outer + 1, Chunk{}); + auto prev = new_pos - 1; + auto to_move = max_size / 2; + new_pos->data.reserve(to_move); + new_pos->data.assign(prev->data.end() - to_move, prev->data.end()); + prev->data.resize(prev->data.size() - to_move); + + size_t moved_count = 0; + for (auto range : new_pos->data) + moved_count += range.second - range.first; + + prev->end = prev->data.back().second; + prev->count -= moved_count; + new_pos->begin = new_pos->data.front().first; + new_pos->end = new_pos->data.back().second; + new_pos->count = moved_count; + + if (offset >= to_move) { + pos.m_outer = new_pos; + offset -= to_move; + } + else { + pos.m_outer = prev; + } + pos.m_end = m_data.end(); + pos.m_inner = &pos.m_outer->data[offset]; + verify(); + return pos; +} + +ChunkedRangeVector::iterator ChunkedRangeVector::erase(iterator pos) noexcept +{ + auto offset = pos.offset(); + auto& chunk = *pos.m_outer; + chunk.count -= pos->second - pos->first; + chunk.data.erase(chunk.data.begin() + offset); + + if (chunk.data.size() == 0) { + pos.m_outer = m_data.erase(pos.m_outer); + pos.m_end = m_data.end(); + pos.m_inner = pos.m_outer == m_data.end() ? nullptr : &pos.m_outer->data.front(); + verify(); + return pos; + } + + chunk.begin = chunk.data.front().first; + chunk.end = chunk.data.back().second; + if (offset < chunk.data.size()) + pos.m_inner = &chunk.data[offset]; + else { + ++pos.m_outer; + pos.m_inner = pos.m_outer == pos.m_end ? nullptr : &pos.m_outer->data.front(); + } + + verify(); + return pos; +} + +void ChunkedRangeVector::verify() const noexcept +{ +#ifdef REALM_DEBUG + size_t prev_end = -1; + for (auto range : *this) { + REALM_ASSERT(range.first < range.second); + REALM_ASSERT(prev_end == size_t(-1) || range.first > prev_end); + prev_end = range.second; + } + + for (auto& chunk : m_data) { + REALM_ASSERT(!chunk.data.empty()); + REALM_ASSERT(chunk.data.front().first == chunk.begin); + REALM_ASSERT(chunk.data.back().second == chunk.end); + REALM_ASSERT(chunk.count <= chunk.end - chunk.begin); + size_t count = 0; + for (auto range : chunk.data) + count += range.second - range.first; + REALM_ASSERT(count == chunk.count); + } +#endif +} + +namespace { +class ChunkedRangeVectorBuilder { +public: + using value_type = std::pair; + + ChunkedRangeVectorBuilder(ChunkedRangeVector const& expected); + void push_back(size_t index); + void push_back(std::pair range); + std::vector finalize(); +private: + std::vector m_data; + size_t m_outer_pos = 0; +}; + +ChunkedRangeVectorBuilder::ChunkedRangeVectorBuilder(ChunkedRangeVector const& expected) +{ + size_t size = 0; + for (auto const& chunk : expected.m_data) + size += chunk.data.size(); + m_data.resize(size / ChunkedRangeVector::max_size + 1); + for (size_t i = 0; i < m_data.size() - 1; ++i) + m_data[i].data.reserve(ChunkedRangeVector::max_size); +} + +void ChunkedRangeVectorBuilder::push_back(size_t index) +{ + push_back({index, index + 1}); +} + +void ChunkedRangeVectorBuilder::push_back(std::pair range) +{ + auto& chunk = m_data[m_outer_pos]; + if (chunk.data.empty()) { + chunk.data.push_back(range); + chunk.count = range.second - range.first; + chunk.begin = range.first; + } + else if (range.first == chunk.data.back().second) { + chunk.data.back().second = range.second; + chunk.count += range.second - range.first; + } + else if (chunk.data.size() < ChunkedRangeVector::max_size) { + chunk.data.push_back(range); + chunk.count += range.second - range.first; + } + else { + chunk.end = chunk.data.back().second; + ++m_outer_pos; + if (m_outer_pos >= m_data.size()) + m_data.push_back({{range}, range.first, 0, 1}); + else { + auto& chunk = m_data[m_outer_pos]; + chunk.data.push_back(range); + chunk.begin = range.first; + chunk.count = range.second - range.first; + } + } +} + +std::vector ChunkedRangeVectorBuilder::finalize() +{ + if (!m_data.empty()) { + m_data.resize(m_outer_pos + 1); + if (m_data.back().data.empty()) + m_data.pop_back(); + else + m_data.back().end = m_data.back().data.back().second; + } + return std::move(m_data); +} +} + +IndexSet::IndexSet(std::initializer_list values) +{ + for (size_t v : values) + add(v); +} + +bool IndexSet::contains(size_t index) const noexcept +{ + auto it = const_cast(this)->find(index); + return it != end() && it->first <= index; +} + +size_t IndexSet::count(size_t start_index, size_t end_index) const noexcept +{ + auto it = const_cast(this)->find(start_index); + const auto end = this->end(); + if (it == end || it->first >= end_index) { + return 0; + } + if (it->second >= end_index) + return std::min(it->second, end_index) - std::max(it->first, start_index); + + size_t ret = 0; + + if (start_index > it->first || it.offset() != 0) { + // Start index is in the middle of a chunk, so start by counting the + // rest of that chunk + ret = it->second - std::max(it->first, start_index); + for (++it; it != end && it->second < end_index && it.offset() != 0; ++it) { + ret += it->second - it->first; + } + if (it != end && it->first < end_index && it.offset() != 0) + ret += end_index - it->first; + if (it == end || it->second >= end_index) + return ret; + } + + // Now count all complete chunks that fall within the range + while (it != end && it.outer()->end <= end_index) { + REALM_ASSERT_DEBUG(it.offset() == 0); + ret += it.outer()->count; + it.next_chunk(); + } + + // Cound all complete ranges within the last chunk + while (it != end && it->second <= end_index) { + ret += it->second - it->first; + ++it; + } + + // And finally add in the partial last range + if (it != end && it->first < end_index) + ret += end_index - it->first; + return ret; +} + +IndexSet::iterator IndexSet::find(size_t index) noexcept +{ + return find(index, begin()); +} + +IndexSet::iterator IndexSet::find(size_t index, iterator begin) noexcept +{ + auto it = std::find_if(begin.outer(), m_data.end(), + [&](auto const& lft) { return lft.end > index; }); + if (it == m_data.end()) + return end(); + if (index < it->begin) + return iterator(it, m_data.end(), &it->data[0]); + auto inner_begin = it->data.begin(); + if (it == begin.outer()) + inner_begin += begin.offset(); + auto inner = std::lower_bound(inner_begin, it->data.end(), index, + [&](auto const& lft, auto) { return lft.second <= index; }); + REALM_ASSERT_DEBUG(inner != it->data.end()); + + return iterator(it, m_data.end(), &*inner); +} + +void IndexSet::add(size_t index) +{ + do_add(find(index), index); +} + +void IndexSet::add(IndexSet const& other) +{ + auto it = begin(); + for (size_t index : other.as_indexes()) { + it = do_add(find(index, it), index); + } +} + +size_t IndexSet::add_shifted(size_t index) +{ + iterator it = begin(), end = this->end(); + + // Shift for any complete chunks before the target + for (; it != end && it.outer()->end <= index; it.next_chunk()) + index += it.outer()->count; + + // And any ranges within the last partial chunk + for (; it != end && it->first <= index; ++it) + index += it->second - it->first; + + do_add(it, index); + return index; +} + +void IndexSet::add_shifted_by(IndexSet const& shifted_by, IndexSet const& values) +{ + if (values.empty()) + return; + +#ifdef REALM_DEBUG + size_t expected = std::distance(as_indexes().begin(), as_indexes().end()); + for (auto index : values.as_indexes()) { + if (!shifted_by.contains(index)) + ++expected; + } +#endif + + ChunkedRangeVectorBuilder builder(*this); + + auto old_it = cbegin(), old_end = cend(); + auto shift_it = shifted_by.cbegin(), shift_end = shifted_by.cend(); + + size_t skip_until = 0; + size_t old_shift = 0; + size_t new_shift = 0; + for (size_t index : values.as_indexes()) { + for (; shift_it != shift_end && shift_it->first <= index; ++shift_it) { + new_shift += shift_it->second - shift_it->first; + skip_until = shift_it->second; + } + if (index < skip_until) + continue; + + for (; old_it != old_end && old_it->first <= index - new_shift + old_shift; ++old_it) { + for (size_t i = old_it->first; i < old_it->second; ++i) + builder.push_back(i); + old_shift += old_it->second - old_it->first; + } + + REALM_ASSERT(index >= new_shift); + builder.push_back(index - new_shift + old_shift); + } + + copy(old_it, old_end, std::back_inserter(builder)); + m_data = builder.finalize(); + +#ifdef REALM_DEBUG + REALM_ASSERT((size_t)std::distance(as_indexes().begin(), as_indexes().end()) == expected); +#endif +} + +void IndexSet::set(size_t len) +{ + clear(); + if (len) { + push_back({0, len}); + } +} + +void IndexSet::insert_at(size_t index, size_t count) +{ + REALM_ASSERT(count > 0); + + auto pos = find(index); + auto end = this->end(); + bool in_existing = false; + if (pos != end) { + if (pos->first <= index) { + in_existing = true; + pos.adjust(0, count); + } + else { + pos.shift(count); + } + for (auto it = std::next(pos); it != end; ++it) + it.shift(count); + } + if (!in_existing) { + for (size_t i = 0; i < count; ++i) + pos = std::next(do_add(pos, index + i)); + } + + verify(); +} + +void IndexSet::insert_at(IndexSet const& positions) +{ + if (positions.empty()) + return; + if (empty()) { + *this = positions; + return; + } + + IndexIterator begin1 = cbegin(), begin2 = positions.cbegin(); + IndexIterator end1 = cend(), end2 = positions.cend(); + + ChunkedRangeVectorBuilder builder(*this); + size_t shift = 0; + while (begin1 != end1 && begin2 != end2) { + if (*begin1 + shift < *begin2) { + builder.push_back(*begin1++ + shift); + } + else { + ++shift; + builder.push_back(*begin2++); + } + } + for (; begin1 != end1; ++begin1) + builder.push_back(*begin1 + shift); + for (; begin2 != end2; ++begin2) + builder.push_back(*begin2); + + m_data = builder.finalize(); +} + +void IndexSet::shift_for_insert_at(size_t index, size_t count) +{ + REALM_ASSERT(count > 0); + + auto it = find(index); + if (it == end()) + return; + + for (auto pos = it, end = this->end(); pos != end; ++pos) + pos.shift(count); + + // If the range contained the insertion point, split the range and move + // the part of it before the insertion point back + if (it->first < index + count) { + auto old_second = it->second; + it.set(it->first - count, index); + insert(std::next(it), {index + count, old_second}); + } + verify(); +} + +void IndexSet::shift_for_insert_at(realm::IndexSet const& values) +{ + if (empty() || values.empty()) + return; + if (values.m_data.front().begin >= m_data.back().end) + return; + + IndexIterator begin1 = cbegin(), begin2 = values.cbegin(); + IndexIterator end1 = cend(), end2 = values.cend(); + + ChunkedRangeVectorBuilder builder(*this); + size_t shift = 0; + while (begin1 != end1 && begin2 != end2) { + if (*begin1 + shift < *begin2) { + builder.push_back(*begin1++ + shift); + } + else { + ++shift; + begin2++; + } + } + for (; begin1 != end1; ++begin1) + builder.push_back(*begin1 + shift); + + m_data = builder.finalize(); +} + +void IndexSet::erase_at(size_t index) +{ + auto it = find(index); + if (it != end()) + do_erase(it, index); +} + +void IndexSet::erase_at(IndexSet const& positions) +{ + if (empty() || positions.empty()) + return; + + ChunkedRangeVectorBuilder builder(*this); + + IndexIterator begin1 = cbegin(), begin2 = positions.cbegin(); + IndexIterator end1 = cend(), end2 = positions.cend(); + + size_t shift = 0; + while (begin1 != end1 && begin2 != end2) { + if (*begin1 < *begin2) { + builder.push_back(*begin1++ - shift); + } + else if (*begin1 == *begin2) { + ++shift; + ++begin1; + ++begin2; + } + else { + ++shift; + ++begin2; + } + } + for (; begin1 != end1; ++begin1) + builder.push_back(*begin1 - shift); + + m_data = builder.finalize(); +} + +size_t IndexSet::erase_or_unshift(size_t index) +{ + auto shifted = index; + iterator it = begin(), end = this->end(); + + // Shift for any complete chunks before the target + for (; it != end && it.outer()->end <= index; it.next_chunk()) + shifted -= it.outer()->count; + + // And any ranges within the last partial chunk + for (; it != end && it->second <= index; ++it) + shifted -= it->second - it->first; + + if (it == end) + return shifted; + + if (it->first <= index) + shifted = npos; + + do_erase(it, index); + + return shifted; +} + +void IndexSet::do_erase(iterator it, size_t index) +{ + if (it->first <= index) { + if (it->first + 1 == it->second) { + it = erase(it); + } + else { + it.adjust(0, -1); + ++it; + } + } + else if (it != begin() && std::prev(it)->second + 1 == it->first) { + std::prev(it).adjust(0, it->second - it->first); + it = erase(it); + } + + for (; it != end(); ++it) + it.shift(-1); +} + +IndexSet::iterator IndexSet::do_remove(iterator it, size_t begin, size_t end) +{ + for (it = find(begin, it); it != this->end() && it->first < end; it = find(begin, it)) { + // Trim off any part of the range to remove that's before the matching range + begin = std::max(it->first, begin); + + // If the matching range extends to both sides of the range to remove, + // split it on the range to remove + if (it->first < begin && it->second > end) { + auto old_second = it->second; + it.set(it->first, begin); + it = std::prev(insert(std::next(it), {end, old_second})); + } + // Range to delete now coverages (at least) one end of the matching range + else if (begin == it->first && end >= it->second) + it = erase(it); + else if (begin == it->first) + it.set(end, it->second); + else + it.set(it->first, begin); + } + return it; +} + +void IndexSet::remove(size_t index, size_t count) +{ + do_remove(find(index), index, index + count); +} + +void IndexSet::remove(realm::IndexSet const& values) +{ + auto it = begin(); + for (auto range : values) { + it = do_remove(it, range.first, range.second); + if (it == end()) + return; + } +} + +size_t IndexSet::shift(size_t index) const noexcept +{ + // FIXME: optimize + for (auto range : *this) { + if (range.first > index) + break; + index += range.second - range.first; + } + return index; +} + +size_t IndexSet::unshift(size_t index) const noexcept +{ + REALM_ASSERT_DEBUG(!contains(index)); + return index - count(0, index); +} + +void IndexSet::clear() noexcept +{ + m_data.clear(); +} + +IndexSet::iterator IndexSet::do_add(iterator it, size_t index) +{ + verify(); + bool more_before = it != begin(), valid = it != end(); + REALM_ASSERT(!more_before || index >= std::prev(it)->second); + if (valid && it->first <= index && it->second > index) { + // index is already in set + return it; + } + if (more_before && std::prev(it)->second == index) { + auto prev = std::prev(it); + // index is immediately after an existing range + prev.adjust(0, 1); + + if (valid && prev->second == it->first) { + // index joins two existing ranges + prev.adjust(0, it->second - it->first); + return std::prev(erase(it)); + } + return prev; + } + if (valid && it->first == index + 1) { + // index is immediately before an existing range + it.adjust(-1, 0); + return it; + } + + // index is not next to an existing range + return insert(it, {index, index + 1}); +} diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/list.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/list.cpp new file mode 100644 index 0000000..12b4082 --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/list.cpp @@ -0,0 +1,468 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "list.hpp" + +#include "impl/list_notifier.hpp" +#include "impl/primitive_list_notifier.hpp" +#include "impl/realm_coordinator.hpp" +#include "object_schema.hpp" +#include "object_store.hpp" +#include "results.hpp" +#include "schema.hpp" +#include "shared_realm.hpp" + +#include + +namespace realm { +using namespace realm::_impl; + +List::List() noexcept = default; +List::~List() = default; + +List::List(const List&) = default; +List& List::operator=(const List&) = default; +List::List(List&&) = default; +List& List::operator=(List&&) = default; + +List::List(std::shared_ptr r, Table& parent_table, size_t col, size_t row) +: m_realm(std::move(r)) +{ + auto type = parent_table.get_column_type(col); + REALM_ASSERT(type == type_LinkList || type == type_Table); + if (type == type_LinkList) { + m_link_view = parent_table.get_linklist(col, row); + m_table.reset(&m_link_view->get_target_table()); + } + else { + m_table = parent_table.get_subtable(col, row); + } +} + +List::List(std::shared_ptr r, LinkViewRef l) noexcept +: m_realm(std::move(r)) +, m_link_view(std::move(l)) +{ + m_table.reset(&m_link_view->get_target_table()); +} + +List::List(std::shared_ptr r, TableRef t) noexcept +: m_realm(std::move(r)) +, m_table(std::move(t)) +{ +} + +static StringData object_name(Table const& table) +{ + return ObjectStore::object_type_for_table_name(table.get_name()); +} + +ObjectSchema const& List::get_object_schema() const +{ + verify_attached(); + REALM_ASSERT(m_link_view); + + if (!m_object_schema) { + REALM_ASSERT(get_type() == PropertyType::Object); + auto object_type = object_name(m_link_view->get_target_table()); + auto it = m_realm->schema().find(object_type); + REALM_ASSERT(it != m_realm->schema().end()); + m_object_schema = &*it; + } + return *m_object_schema; +} + +Query List::get_query() const +{ + verify_attached(); + return m_link_view ? m_table->where(m_link_view) : m_table->where(); +} + +size_t List::get_origin_row_index() const +{ + verify_attached(); + return m_link_view ? m_link_view->get_origin_row_index() : m_table->get_parent_row_index(); +} + +void List::verify_valid_row(size_t row_ndx, bool insertion) const +{ + size_t s = size(); + if (row_ndx > s || (!insertion && row_ndx == s)) { + throw OutOfBoundsIndexException{row_ndx, s + insertion}; + } +} + +void List::validate(RowExpr row) const +{ + if (!row.is_attached()) + throw std::invalid_argument("Object has been deleted or invalidated"); + if (row.get_table() != &m_link_view->get_target_table()) + throw std::invalid_argument(util::format("Object of type (%1) does not match List type (%2)", + object_name(*row.get_table()), + object_name(m_link_view->get_target_table()))); +} + +bool List::is_valid() const +{ + if (!m_realm) + return false; + m_realm->verify_thread(); + if (m_link_view) + return m_link_view->is_attached(); + return m_table && m_table->is_attached(); +} + +void List::verify_attached() const +{ + if (!is_valid()) { + throw InvalidatedException(); + } +} + +void List::verify_in_transaction() const +{ + verify_attached(); + m_realm->verify_in_write(); +} + +size_t List::size() const +{ + verify_attached(); + return m_link_view ? m_link_view->size() : m_table->size(); +} + +size_t List::to_table_ndx(size_t row) const noexcept +{ + return m_link_view ? m_link_view->get(row).get_index() : row; +} + +PropertyType List::get_type() const +{ + verify_attached(); + return m_link_view ? PropertyType::Object + : ObjectSchema::from_core_type(*m_table->get_descriptor(), 0); +} + +namespace { +template +auto get(Table& table, size_t row) +{ + return table.get(0, row); +} + +template<> +auto get(Table& table, size_t row) +{ + return table.get(row); +} +} + +template +T List::get(size_t row_ndx) const +{ + verify_valid_row(row_ndx); + return realm::get(*m_table, to_table_ndx(row_ndx)); +} + +template RowExpr List::get(size_t) const; + +template +size_t List::find(T const& value) const +{ + verify_attached(); + return m_table->find_first(0, value); +} + +template<> +size_t List::find(RowExpr const& row) const +{ + verify_attached(); + if (!row.is_attached()) + return not_found; + validate(row); + + return m_link_view ? m_link_view->find(row.get_index()) : row.get_index(); +} + +size_t List::find(Query&& q) const +{ + verify_attached(); + if (m_link_view) { + size_t index = get_query().and_query(std::move(q)).find(); + return index == not_found ? index : m_link_view->find(index); + } + return q.find(); +} + +template +void List::add(T value) +{ + verify_in_transaction(); + m_table->set(0, m_table->add_empty_row(), value); +} + +template<> +void List::add(size_t target_row_ndx) +{ + verify_in_transaction(); + m_link_view->add(target_row_ndx); +} + +template<> +void List::add(RowExpr row) +{ + validate(row); + add(row.get_index()); +} + +template<> +void List::add(int value) +{ + verify_in_transaction(); + if (m_link_view) + add(static_cast(value)); + else + add(static_cast(value)); +} + +template +void List::insert(size_t row_ndx, T value) +{ + verify_in_transaction(); + verify_valid_row(row_ndx, true); + m_table->insert_empty_row(row_ndx); + m_table->set(0, row_ndx, value); +} + +template<> +void List::insert(size_t row_ndx, size_t target_row_ndx) +{ + verify_in_transaction(); + verify_valid_row(row_ndx, true); + m_link_view->insert(row_ndx, target_row_ndx); +} + +template<> +void List::insert(size_t row_ndx, RowExpr row) +{ + validate(row); + insert(row_ndx, row.get_index()); +} + +void List::move(size_t source_ndx, size_t dest_ndx) +{ + verify_in_transaction(); + verify_valid_row(source_ndx); + verify_valid_row(dest_ndx); // Can't be one past end due to removing one earlier + if (source_ndx == dest_ndx) + return; + + if (m_link_view) + m_link_view->move(source_ndx, dest_ndx); + else + m_table->move_row(source_ndx, dest_ndx); +} + +void List::remove(size_t row_ndx) +{ + verify_in_transaction(); + verify_valid_row(row_ndx); + if (m_link_view) + m_link_view->remove(row_ndx); + else + m_table->remove(row_ndx); +} + +void List::remove_all() +{ + verify_in_transaction(); + if (m_link_view) + m_link_view->clear(); + else + m_table->clear(); +} + +template +void List::set(size_t row_ndx, T value) +{ + verify_in_transaction(); + verify_valid_row(row_ndx); + m_table->set(0, row_ndx, value); +} + +template<> +void List::set(size_t row_ndx, size_t target_row_ndx) +{ + verify_in_transaction(); + verify_valid_row(row_ndx); + m_link_view->set(row_ndx, target_row_ndx); +} + +template<> +void List::set(size_t row_ndx, RowExpr row) +{ + validate(row); + set(row_ndx, row.get_index()); +} + +void List::swap(size_t ndx1, size_t ndx2) +{ + verify_in_transaction(); + verify_valid_row(ndx1); + verify_valid_row(ndx2); + if (m_link_view) + m_link_view->swap(ndx1, ndx2); + else + m_table->swap_rows(ndx1, ndx2); +} + +void List::delete_at(size_t row_ndx) +{ + verify_in_transaction(); + verify_valid_row(row_ndx); + if (m_link_view) + m_link_view->remove_target_row(row_ndx); + else + m_table->remove(row_ndx); +} + +void List::delete_all() +{ + verify_in_transaction(); + if (m_link_view) + m_link_view->remove_all_target_rows(); + else + m_table->clear(); +} + +Results List::sort(SortDescriptor order) const +{ + verify_attached(); + if (m_link_view) + return Results(m_realm, m_link_view, util::none, std::move(order)); + + DescriptorOrdering new_order; + new_order.append_sort(std::move(order)); + return Results(m_realm, get_query(), std::move(new_order)); +} + +Results List::sort(std::vector> const& keypaths) const +{ + return as_results().sort(keypaths); +} + +Results List::filter(Query q) const +{ + verify_attached(); + if (m_link_view) + return Results(m_realm, m_link_view, get_query().and_query(std::move(q))); + return Results(m_realm, get_query().and_query(std::move(q))); +} + +Results List::as_results() const +{ + verify_attached(); + return m_link_view ? Results(m_realm, m_link_view) : Results(m_realm, *m_table); +} + +Results List::snapshot() const +{ + return as_results().snapshot(); +} + +util::Optional List::max(size_t column) +{ + return as_results().max(column); +} + +util::Optional List::min(size_t column) +{ + return as_results().min(column); +} + +Mixed List::sum(size_t column) +{ + // Results::sum() returns none only for Mode::Empty Results, so we can + // safely ignore that possibility here + return *as_results().sum(column); +} + +util::Optional List::average(size_t column) +{ + return as_results().average(column); +} + +// These definitions rely on that LinkViews and Tables are interned by core +bool List::operator==(List const& rgt) const noexcept +{ + return m_link_view == rgt.m_link_view && m_table.get() == rgt.m_table.get(); +} + +NotificationToken List::add_notification_callback(CollectionChangeCallback cb) & +{ + verify_attached(); + // Adding a new callback to a notifier which had all of its callbacks + // removed does not properly reinitialize the notifier. Work around this by + // recreating it instead. + // FIXME: The notifier lifecycle here is dumb (when all callbacks are removed + // from a notifier a zombie is left sitting around uselessly) and should be + // cleaned up. + if (m_notifier && !m_notifier->have_callbacks()) + m_notifier.reset(); + if (!m_notifier) { + if (get_type() == PropertyType::Object) + m_notifier = std::static_pointer_cast<_impl::CollectionNotifier>(std::make_shared(m_link_view, m_realm)); + else + m_notifier = std::static_pointer_cast<_impl::CollectionNotifier>(std::make_shared(m_table, m_realm)); + RealmCoordinator::register_notifier(m_notifier); + } + return {m_notifier, m_notifier->add_callback(std::move(cb))}; +} + +List::OutOfBoundsIndexException::OutOfBoundsIndexException(size_t r, size_t c) +: std::out_of_range(util::format("Requested index %1 greater than max %2", r, c - 1)) +, requested(r), valid_count(c) {} + +#define REALM_PRIMITIVE_LIST_TYPE(T) \ + template T List::get(size_t) const; \ + template size_t List::find(T const&) const; \ + template void List::add(T); \ + template void List::insert(size_t, T); \ + template void List::set(size_t, T); + +REALM_PRIMITIVE_LIST_TYPE(bool) +REALM_PRIMITIVE_LIST_TYPE(int64_t) +REALM_PRIMITIVE_LIST_TYPE(float) +REALM_PRIMITIVE_LIST_TYPE(double) +REALM_PRIMITIVE_LIST_TYPE(StringData) +REALM_PRIMITIVE_LIST_TYPE(BinaryData) +REALM_PRIMITIVE_LIST_TYPE(Timestamp) +REALM_PRIMITIVE_LIST_TYPE(util::Optional) +REALM_PRIMITIVE_LIST_TYPE(util::Optional) +REALM_PRIMITIVE_LIST_TYPE(util::Optional) +REALM_PRIMITIVE_LIST_TYPE(util::Optional) + +#undef REALM_PRIMITIVE_LIST_TYPE +} // namespace realm + +namespace std { +size_t hash::operator()(realm::List const& list) const +{ + return std::hash()(list.m_link_view ? list.m_link_view.get() : (void*)list.m_table.get()); +} +} diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/object.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/object.cpp new file mode 100644 index 0000000..09c165b --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/object.cpp @@ -0,0 +1,120 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2017 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "object.hpp" + +#include "impl/object_notifier.hpp" +#include "impl/realm_coordinator.hpp" +#include "object_schema.hpp" +#include "object_store.hpp" + +using namespace realm; + +InvalidatedObjectException::InvalidatedObjectException(const std::string& object_type) +: std::logic_error("Accessing object of type " + object_type + " which has been invalidated or deleted") +, object_type(object_type) +{} + +InvalidPropertyException::InvalidPropertyException(const std::string& object_type, const std::string& property_name) +: std::logic_error(util::format("Property '%1.%2' does not exist", object_type, property_name)) +, object_type(object_type), property_name(property_name) +{} + +MissingPropertyValueException::MissingPropertyValueException(const std::string& object_type, const std::string& property_name) +: std::logic_error(util::format("Missing value for property '%1.%2'", object_type, property_name)) +, object_type(object_type), property_name(property_name) +{} + +MissingPrimaryKeyException::MissingPrimaryKeyException(const std::string& object_type) +: std::logic_error(util::format("'%1' does not have a primary key defined", object_type)) +, object_type(object_type) +{} + +ReadOnlyPropertyException::ReadOnlyPropertyException(const std::string& object_type, const std::string& property_name) +: std::logic_error(util::format("Cannot modify read-only property '%1.%2'", object_type, property_name)) +, object_type(object_type), property_name(property_name) {} + +ModifyPrimaryKeyException::ModifyPrimaryKeyException(const std::string& object_type, const std::string& property_name) + : std::logic_error(util::format("Cannot modify primary key after creation: '%1.%2'", object_type, property_name)) + , object_type(object_type), property_name(property_name) {} + +Object::Object(SharedRealm r, ObjectSchema const& s, RowExpr const& o) +: m_realm(std::move(r)), m_object_schema(&s), m_row(o) { } + +Object::Object(SharedRealm r, StringData object_type, size_t ndx) +: m_realm(std::move(r)) +, m_object_schema(&*m_realm->schema().find(object_type)) +, m_row(ObjectStore::table_for_object_type(m_realm->read_group(), object_type)->get(ndx)) +{ } + +Object::Object() = default; +Object::~Object() = default; +Object::Object(Object const&) = default; +Object::Object(Object&&) = default; +Object& Object::operator=(Object const&) = default; +Object& Object::operator=(Object&&) = default; + +NotificationToken Object::add_notification_callback(CollectionChangeCallback callback) & +{ + verify_attached(); + if (!m_notifier) { + m_notifier = std::make_shared<_impl::ObjectNotifier>(m_row, m_realm); + _impl::RealmCoordinator::register_notifier(m_notifier); + } + return {m_notifier, m_notifier->add_callback(std::move(callback))}; +} + +void Object::verify_attached() const +{ + m_realm->verify_thread(); + if (!m_row.is_attached()) { + throw InvalidatedObjectException(m_object_schema->name); + } +} + +Property const& Object::property_for_name(StringData prop_name) const +{ + auto prop = m_object_schema->property_for_name(prop_name); + if (!prop) { + throw InvalidPropertyException(m_object_schema->name, prop_name); + } + return *prop; +} + +#if REALM_ENABLE_SYNC +void Object::ensure_user_in_everyone_role() +{ + auto role_table = m_realm->read_group().get_table("class___Role"); + if (!role_table) + return; + size_t ndx = role_table->find_first_string(role_table->get_column_index("name"), "everyone"); + if (ndx == npos) + return; + auto users = role_table->get_linklist(role_table->get_column_index("members"), ndx); + if (users->find(m_row.get_index()) != not_found) + return; + + users->add(m_row.get_index()); +} + +void Object::ensure_private_role_exists_for_user() +{ + auto user_id = m_row.get(m_row.get_table()->get_column_index("id")); + ObjectStore::ensure_private_role_exists_for_user(m_realm->read_group(), user_id); +} +#endif diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/object_schema.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/object_schema.cpp new file mode 100644 index 0000000..4e6f4d8 --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/object_schema.cpp @@ -0,0 +1,319 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "object_schema.hpp" + +#include "feature_checks.hpp" +#include "object_store.hpp" +#include "property.hpp" +#include "schema.hpp" + + +#include +#include +#include +#include + +using namespace realm; + +ObjectSchema::ObjectSchema() = default; +ObjectSchema::~ObjectSchema() = default; + +ObjectSchema::ObjectSchema(std::string name, std::initializer_list persisted_properties) +: ObjectSchema(std::move(name), persisted_properties, {}) +{ +} + +ObjectSchema::ObjectSchema(std::string name, std::initializer_list persisted_properties, + std::initializer_list computed_properties) +: name(std::move(name)) +, persisted_properties(persisted_properties) +, computed_properties(computed_properties) +{ + for (auto const& prop : persisted_properties) { + if (prop.is_primary) { + primary_key = prop.name; + break; + } + } +} + +PropertyType ObjectSchema::from_core_type(Descriptor const& table, size_t col) +{ + auto optional = table.is_nullable(col) ? PropertyType::Nullable : PropertyType::Required; + switch (table.get_column_type(col)) { + case type_Int: return PropertyType::Int | optional; + case type_Float: return PropertyType::Float | optional; + case type_Double: return PropertyType::Double | optional; + case type_Bool: return PropertyType::Bool | optional; + case type_String: return PropertyType::String | optional; + case type_Binary: return PropertyType::Data | optional; + case type_Timestamp: return PropertyType::Date | optional; + case type_Mixed: return PropertyType::Any | optional; + case type_Link: return PropertyType::Object | PropertyType::Nullable; + case type_LinkList: return PropertyType::Object | PropertyType::Array; + case type_Table: return from_core_type(*table.get_subdescriptor(col), 0) | PropertyType::Array; + default: REALM_UNREACHABLE(); + } +} + +ObjectSchema::ObjectSchema(Group const& group, StringData name, size_t index) : name(name) { + ConstTableRef table; + if (index < group.size()) { + table = group.get_table(index); + } + else { + table = ObjectStore::table_for_object_type(group, name); + } + + size_t count = table->get_column_count(); + persisted_properties.reserve(count); + for (size_t col = 0; col < count; col++) { + if (auto property = ObjectStore::property_for_column_index(table, col)) { + persisted_properties.push_back(std::move(property.value())); + } + } + + primary_key = realm::ObjectStore::get_primary_key_for_object(group, name); + set_primary_key_property(); +} + +Property *ObjectSchema::property_for_name(StringData name) +{ + for (auto& prop : persisted_properties) { + if (StringData(prop.name) == name) { + return ∝ + } + } + for (auto& prop : computed_properties) { + if (StringData(prop.name) == name) { + return ∝ + } + } + return nullptr; +} + +Property *ObjectSchema::property_for_public_name(StringData public_name) +{ + // If no `public_name` is defined, the internal `name` is also considered the public name. + for (auto& prop : persisted_properties) { + if (prop.public_name == public_name || (prop.public_name.empty() && prop.name == public_name)) + return ∝ + } + + // Computed properties are not persisted, so creating a public name for such properties + // are a bit pointless since the internal name is already the "public name", but since + // this distinction isn't visible in the Property struct we allow it anyway. + for (auto& prop : computed_properties) { + if ((prop.public_name.empty() ? StringData(prop.name) : StringData(prop.public_name)) == public_name) + return ∝ + } + return nullptr; +} + +const Property *ObjectSchema::property_for_public_name(StringData public_name) const +{ + return const_cast(this)->property_for_public_name(public_name); +} + +const Property *ObjectSchema::property_for_name(StringData name) const +{ + return const_cast(this)->property_for_name(name); +} + +bool ObjectSchema::property_is_computed(Property const& property) const +{ + auto end = computed_properties.end(); + return std::find(computed_properties.begin(), end, property) != end; +} + +void ObjectSchema::set_primary_key_property() +{ + if (primary_key.length()) { + if (auto primary_key_prop = primary_key_property()) { + primary_key_prop->is_primary = true; + } + } +} + +static void validate_property(Schema const& schema, + std::string const& object_name, + Property const& prop, + Property const** primary, + std::vector& exceptions) +{ + if (prop.type == PropertyType::LinkingObjects && !is_array(prop.type)) { + exceptions.emplace_back("Linking Objects property '%1.%2' must be an array.", + object_name, prop.name); + } + + // check nullablity + if (is_nullable(prop.type) && !prop.type_is_nullable()) { + exceptions.emplace_back("Property '%1.%2' of type '%3' cannot be nullable.", + object_name, prop.name, string_for_property_type(prop.type)); + } + else if (prop.type == PropertyType::Object && !is_nullable(prop.type) && !is_array(prop.type)) { + exceptions.emplace_back("Property '%1.%2' of type 'object' must be nullable.", object_name, prop.name); + } + + // check primary keys + if (prop.is_primary) { + if (prop.type != PropertyType::Int && prop.type != PropertyType::String) { + exceptions.emplace_back("Property '%1.%2' of type '%3' cannot be made the primary key.", + object_name, prop.name, string_for_property_type(prop.type)); + } + if (*primary) { + exceptions.emplace_back("Properties '%1' and '%2' are both marked as the primary key of '%3'.", + prop.name, (*primary)->name, object_name); + } + *primary = ∝ + } + + // check indexable + if (prop.is_indexed && !prop.type_is_indexable()) { + exceptions.emplace_back("Property '%1.%2' of type '%3' cannot be indexed.", + object_name, prop.name, string_for_property_type(prop.type)); + } + + // check that only link properties have object types + if (prop.type != PropertyType::LinkingObjects && !prop.link_origin_property_name.empty()) { + exceptions.emplace_back("Property '%1.%2' of type '%3' cannot have an origin property name.", + object_name, prop.name, string_for_property_type(prop.type)); + } + else if (prop.type == PropertyType::LinkingObjects && prop.link_origin_property_name.empty()) { + exceptions.emplace_back("Property '%1.%2' of type '%3' must have an origin property name.", + object_name, prop.name, string_for_property_type(prop.type)); + } + + if (prop.type != PropertyType::Object && prop.type != PropertyType::LinkingObjects) { + if (!prop.object_type.empty()) { + exceptions.emplace_back("Property '%1.%2' of type '%3' cannot have an object type.", + object_name, prop.name, prop.type_string()); + } + return; + } + + + // check that the object_type is valid for link properties + auto it = schema.find(prop.object_type); + if (it == schema.end()) { + exceptions.emplace_back("Property '%1.%2' of type '%3' has unknown object type '%4'", + object_name, prop.name, string_for_property_type(prop.type), prop.object_type); + return; + } + if (prop.type != PropertyType::LinkingObjects) { + return; + } + + const Property *origin_property = it->property_for_name(prop.link_origin_property_name); + if (!origin_property) { + exceptions.emplace_back("Property '%1.%2' declared as origin of linking objects property '%3.%4' does not exist", + prop.object_type, prop.link_origin_property_name, + object_name, prop.name); + } + else if (origin_property->type != PropertyType::Object) { + exceptions.emplace_back("Property '%1.%2' declared as origin of linking objects property '%3.%4' is not a link", + prop.object_type, prop.link_origin_property_name, + object_name, prop.name); + } + else if (origin_property->object_type != object_name) { + exceptions.emplace_back("Property '%1.%2' declared as origin of linking objects property '%3.%4' links to type '%5'", + prop.object_type, prop.link_origin_property_name, + object_name, prop.name, origin_property->object_type); + } +} + +void ObjectSchema::validate(Schema const& schema, std::vector& exceptions) const +{ + std::vector public_property_names; + std::vector internal_property_names; + internal_property_names.reserve(persisted_properties.size() + computed_properties.size()); + auto gather_names = [&](auto const &properties) { + for (auto const &prop : properties) { + internal_property_names.push_back(prop.name); + if (!prop.public_name.empty()) + public_property_names.push_back(prop.public_name); + } + }; + gather_names(persisted_properties); + gather_names(computed_properties); + std::sort(public_property_names.begin(), public_property_names.end()); + std::sort(internal_property_names.begin(), internal_property_names.end()); + + // Check that property names and aliases are unique + auto for_each_duplicate = [](auto &&container, auto &&fn) { + auto end = container.end(); + for (auto it = std::adjacent_find(container.begin(), end); it != end; it = std::adjacent_find(it + 2, end)) + fn(*it); + }; + for_each_duplicate(public_property_names, [&](auto public_property_name) { + exceptions.emplace_back("Alias '%1' appears more than once in the schema for type '%2'.", + public_property_name, name); + }); + for_each_duplicate(internal_property_names, [&](auto internal_name) { + exceptions.emplace_back("Property '%1' appears more than once in the schema for type '%2'.", + internal_name, name); + }); + + // Check that no aliases conflict with property names + struct ErrorWriter { + ObjectSchema const &os; + std::vector &exceptions; + + struct Proxy { + ObjectSchema const &os; + std::vector &exceptions; + + Proxy &operator=(StringData name) { + exceptions.emplace_back( + "Property '%1.%2' has an alias '%3' that conflicts with a property of the same name.", + os.name, os.property_for_public_name(name)->name, name); + return *this; + } + }; + + Proxy operator*() { return Proxy{os, exceptions}; } + ErrorWriter &operator=(const ErrorWriter &) { return *this; } + ErrorWriter &operator++() { return *this; } + ErrorWriter &operator++(int) { return *this; } + } writer{*this, exceptions}; + std::set_intersection(public_property_names.begin(), public_property_names.end(), + internal_property_names.begin(), internal_property_names.end(), writer); + + // Validate all properties + const Property *primary = nullptr; + for (auto const& prop : persisted_properties) { + validate_property(schema, name, prop, &primary, exceptions); + } + for (auto const& prop : computed_properties) { + validate_property(schema, name, prop, &primary, exceptions); + } + + if (!primary_key.empty() && !primary && !primary_key_property()) { + exceptions.emplace_back("Specified primary key '%1.%2' does not exist.", name, primary_key); + } +} + +namespace realm { +bool operator==(ObjectSchema const& a, ObjectSchema const& b) +{ + return std::tie(a.name, a.primary_key, a.persisted_properties, a.computed_properties) + == std::tie(b.name, b.primary_key, b.persisted_properties, b.computed_properties); + +} +} diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/object_store.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/object_store.cpp new file mode 100644 index 0000000..da255c4 --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/object_store.cpp @@ -0,0 +1,1044 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "object_store.hpp" + +#include "feature_checks.hpp" +#include "object_schema.hpp" +#include "schema.hpp" +#include "shared_realm.hpp" +#include "sync/partial_sync.hpp" + +#include +#include +#include +#include +#include + +#if REALM_ENABLE_SYNC +#include +#include +#include +#endif // REALM_ENABLE_SYNC + +#include + +using namespace realm; + +constexpr uint64_t ObjectStore::NotVersioned; + +namespace { +const char * const c_metadataTableName = "metadata"; +const char * const c_versionColumnName = "version"; +const size_t c_versionColumnIndex = 0; + +const char * const c_primaryKeyTableName = "pk"; +const char * const c_primaryKeyObjectClassColumnName = "pk_table"; +const size_t c_primaryKeyObjectClassColumnIndex = 0; +const char * const c_primaryKeyPropertyNameColumnName = "pk_property"; +const size_t c_primaryKeyPropertyNameColumnIndex = 1; + +const size_t c_zeroRowIndex = 0; + +const char c_object_table_prefix[] = "class_"; + +void create_metadata_tables(Group& group) { + // The tables 'pk' and 'metadata' are treated specially by Sync. The 'pk' table + // is populated by `sync::create_table` and friends, while the 'metadata' table + // is simply ignored. + TableRef pk_table = group.get_or_add_table(c_primaryKeyTableName); + TableRef metadata_table = group.get_or_add_table(c_metadataTableName); + + if (metadata_table->get_column_count() == 0) { + metadata_table->insert_column(c_versionColumnIndex, type_Int, c_versionColumnName); + metadata_table->add_empty_row(); + // set initial version + metadata_table->set_int(c_versionColumnIndex, c_zeroRowIndex, ObjectStore::NotVersioned); + } + + if (pk_table->get_column_count() == 0) { + pk_table->insert_column(c_primaryKeyObjectClassColumnIndex, type_String, c_primaryKeyObjectClassColumnName); + pk_table->insert_column(c_primaryKeyPropertyNameColumnIndex, type_String, c_primaryKeyPropertyNameColumnName); + } + pk_table->add_search_index(c_primaryKeyObjectClassColumnIndex); +} + +void set_schema_version(Group& group, uint64_t version) { + TableRef table = group.get_table(c_metadataTableName); + table->set_int(c_versionColumnIndex, c_zeroRowIndex, version); +} + +template +auto table_for_object_schema(Group& group, ObjectSchema const& object_schema) +{ + return ObjectStore::table_for_object_type(group, object_schema.name); +} + +DataType to_core_type(PropertyType type) +{ + REALM_ASSERT(type != PropertyType::Object); // Link columns have to be handled differently + REALM_ASSERT(type != PropertyType::Any); // Mixed columns can't be created + switch (type & ~PropertyType::Flags) { + case PropertyType::Int: return type_Int; + case PropertyType::Bool: return type_Bool; + case PropertyType::Float: return type_Float; + case PropertyType::Double: return type_Double; + case PropertyType::String: return type_String; + case PropertyType::Date: return type_Timestamp; + case PropertyType::Data: return type_Binary; + default: REALM_COMPILER_HINT_UNREACHABLE(); + } +} + +void insert_column(Group& group, Table& table, Property const& property, size_t col_ndx) +{ + // Cannot directly insert a LinkingObjects column (a computed property). + // LinkingObjects must be an artifact of an existing link column. + REALM_ASSERT(property.type != PropertyType::LinkingObjects); + + if (property.type == PropertyType::Object) { + auto target_name = ObjectStore::table_name_for_object_type(property.object_type); + TableRef link_table = group.get_table(target_name); + REALM_ASSERT(link_table); + table.insert_column_link(col_ndx, is_array(property.type) ? type_LinkList : type_Link, + property.name, *link_table); + } + else if (is_array(property.type)) { + DescriptorRef desc; + table.insert_column(col_ndx, type_Table, property.name, &desc); + desc->add_column(to_core_type(property.type & ~PropertyType::Flags), ObjectStore::ArrayColumnName, + nullptr, is_nullable(property.type)); + } + else { + table.insert_column(col_ndx, to_core_type(property.type), property.name, is_nullable(property.type)); + if (property.requires_index()) + table.add_search_index(col_ndx); + } +} + +void add_column(Group& group, Table& table, Property const& property) +{ + insert_column(group, table, property, table.get_column_count()); +} + +void replace_column(Group& group, Table& table, Property const& old_property, Property const& new_property) +{ + insert_column(group, table, new_property, old_property.table_column); + table.remove_column(old_property.table_column + 1); +} + +TableRef create_table(Group& group, ObjectSchema const& object_schema) +{ + auto name = ObjectStore::table_name_for_object_type(object_schema.name); + + TableRef table; +#if REALM_ENABLE_SYNC + if (auto* pk_property = object_schema.primary_key_property()) { + table = sync::create_table_with_primary_key(group, name, to_core_type(pk_property->type), + pk_property->name, is_nullable(pk_property->type)); + } + else { + table = sync::create_table(group, name); + } +#else + table = group.get_or_add_table(name); + ObjectStore::set_primary_key_for_object(group, object_schema.name, object_schema.primary_key); +#endif // REALM_ENABLE_SYNC + + return table; +} + +void add_initial_columns(Group& group, ObjectSchema const& object_schema) +{ + auto name = ObjectStore::table_name_for_object_type(object_schema.name); + TableRef table = group.get_table(name); + + for (auto const& prop : object_schema.persisted_properties) { +#if REALM_ENABLE_SYNC + // The sync::create_table* functions create the PK column for us. + if (prop.is_primary) + continue; +#endif // REALM_ENABLE_SYNC + add_column(group, *table, prop); + } +} + +void copy_property_values(Property const& prop, Table& table) +{ + auto copy_property_values = [&](auto getter, auto setter) { + for (size_t i = 0, count = table.size(); i < count; i++) { + bool is_default = false; + (table.*setter)(prop.table_column, i, (table.*getter)(prop.table_column + 1, i), + is_default); + } + }; + + switch (prop.type & ~PropertyType::Flags) { + case PropertyType::Int: + copy_property_values(&Table::get_int, &Table::set_int); + break; + case PropertyType::Bool: + copy_property_values(&Table::get_bool, &Table::set_bool); + break; + case PropertyType::Float: + copy_property_values(&Table::get_float, &Table::set_float); + break; + case PropertyType::Double: + copy_property_values(&Table::get_double, &Table::set_double); + break; + case PropertyType::String: + copy_property_values(&Table::get_string, &Table::set_string); + break; + case PropertyType::Data: + copy_property_values(&Table::get_binary, &Table::set_binary); + break; + case PropertyType::Date: + copy_property_values(&Table::get_timestamp, &Table::set_timestamp); + break; + default: + break; + } +} + +void make_property_optional(Group& group, Table& table, Property property) +{ + property.type |= PropertyType::Nullable; + insert_column(group, table, property, property.table_column); + copy_property_values(property, table); + table.remove_column(property.table_column + 1); +} + +void make_property_required(Group& group, Table& table, Property property) +{ + property.type &= ~PropertyType::Nullable; + insert_column(group, table, property, property.table_column); + table.remove_column(property.table_column + 1); +} + +void validate_primary_column_uniqueness(Group const& group, StringData object_type, StringData primary_property) +{ + auto table = ObjectStore::table_for_object_type(group, object_type); + if (table->get_distinct_view(table->get_column_index(primary_property)).size() != table->size()) { + throw DuplicatePrimaryKeyValueException(object_type, primary_property); + } +} + +void validate_primary_column_uniqueness(Group const& group) +{ + auto pk_table = group.get_table(c_primaryKeyTableName); + for (size_t i = 0, count = pk_table->size(); i < count; ++i) { + validate_primary_column_uniqueness(group, + pk_table->get_string(c_primaryKeyObjectClassColumnIndex, i), + pk_table->get_string(c_primaryKeyPropertyNameColumnIndex, i)); + } +} +} // anonymous namespace + +void ObjectStore::set_schema_version(Group& group, uint64_t version) { + ::create_metadata_tables(group); + ::set_schema_version(group, version); +} + +uint64_t ObjectStore::get_schema_version(Group const& group) { + ConstTableRef table = group.get_table(c_metadataTableName); + if (!table || table->get_column_count() == 0) { + return ObjectStore::NotVersioned; + } + return table->get_int(c_versionColumnIndex, c_zeroRowIndex); +} + +StringData ObjectStore::get_primary_key_for_object(Group const& group, StringData object_type) { + ConstTableRef table = group.get_table(c_primaryKeyTableName); + if (!table) { + return ""; + } + size_t row = table->find_first_string(c_primaryKeyObjectClassColumnIndex, object_type); + if (row == not_found) { + return ""; + } + return table->get_string(c_primaryKeyPropertyNameColumnIndex, row); +} + +void ObjectStore::set_primary_key_for_object(Group& group, StringData object_type, StringData primary_key) { + TableRef table = group.get_table(c_primaryKeyTableName); + + size_t row = table->find_first_string(c_primaryKeyObjectClassColumnIndex, object_type); + +#if REALM_ENABLE_SYNC + // sync::create_table* functions should have already updated the pk table. + if (sync::has_object_ids(group)) { + if (primary_key.size() == 0) + REALM_ASSERT(row == not_found); + else { + REALM_ASSERT(row != not_found); + REALM_ASSERT(table->get_string(c_primaryKeyPropertyNameColumnIndex, row) == primary_key); + } + return; + } +#endif // REALM_ENABLE_SYNC + + if (row == not_found && primary_key.size()) { + row = table->add_empty_row(); + table->set_string_unique(c_primaryKeyObjectClassColumnIndex, row, object_type); + table->set_string(c_primaryKeyPropertyNameColumnIndex, row, primary_key); + return; + } + // set if changing, or remove if setting to nil + if (primary_key.size() == 0) { + if (row != not_found) { + table->move_last_over(row); + } + } + else { + table->set_string(c_primaryKeyPropertyNameColumnIndex, row, primary_key); + } +} + +StringData ObjectStore::object_type_for_table_name(StringData table_name) { + if (table_name.begins_with(c_object_table_prefix)) { + return table_name.substr(sizeof(c_object_table_prefix) - 1); + } + return StringData(); +} + +std::string ObjectStore::table_name_for_object_type(StringData object_type) { + return std::string(c_object_table_prefix) + std::string(object_type); +} + +TableRef ObjectStore::table_for_object_type(Group& group, StringData object_type) { + auto name = table_name_for_object_type(object_type); + return group.get_table(name); +} + +ConstTableRef ObjectStore::table_for_object_type(Group const& group, StringData object_type) { + auto name = table_name_for_object_type(object_type); + return group.get_table(name); +} + +namespace { +struct SchemaDifferenceExplainer { + std::vector errors; + + void operator()(schema_change::AddTable op) + { + errors.emplace_back("Class '%1' has been added.", op.object->name); + } + + void operator()(schema_change::RemoveTable) + { + // We never do anything for RemoveTable + } + + void operator()(schema_change::AddInitialProperties) + { + // Nothing. Always preceded by AddTable. + } + + void operator()(schema_change::AddProperty op) + { + errors.emplace_back("Property '%1.%2' has been added.", op.object->name, op.property->name); + } + + void operator()(schema_change::RemoveProperty op) + { + errors.emplace_back("Property '%1.%2' has been removed.", op.object->name, op.property->name); + } + + void operator()(schema_change::ChangePropertyType op) + { + errors.emplace_back("Property '%1.%2' has been changed from '%3' to '%4'.", + op.object->name, op.new_property->name, + op.old_property->type_string(), + op.new_property->type_string()); + } + + void operator()(schema_change::MakePropertyNullable op) + { + errors.emplace_back("Property '%1.%2' has been made optional.", op.object->name, op.property->name); + } + + void operator()(schema_change::MakePropertyRequired op) + { + errors.emplace_back("Property '%1.%2' has been made required.", op.object->name, op.property->name); + } + + void operator()(schema_change::ChangePrimaryKey op) + { + if (op.property && !op.object->primary_key.empty()) { + errors.emplace_back("Primary Key for class '%1' has changed from '%2' to '%3'.", + op.object->name, op.object->primary_key, op.property->name); + } + else if (op.property) { + errors.emplace_back("Primary Key for class '%1' has been added.", op.object->name); + } + else { + errors.emplace_back("Primary Key for class '%1' has been removed.", op.object->name); + } + } + + void operator()(schema_change::AddIndex op) + { + errors.emplace_back("Property '%1.%2' has been made indexed.", op.object->name, op.property->name); + } + + void operator()(schema_change::RemoveIndex op) + { + errors.emplace_back("Property '%1.%2' has been made unindexed.", op.object->name, op.property->name); + } +}; + +class TableHelper { +public: + TableHelper(Group& g) : m_group(g) { } + + Table& operator()(const ObjectSchema* object_schema) + { + if (object_schema != m_current_object_schema) { + m_current_table = table_for_object_schema(m_group, *object_schema); + m_current_object_schema = object_schema; + } + REALM_ASSERT(m_current_table); + return *m_current_table; + } + +private: + Group& m_group; + const ObjectSchema* m_current_object_schema = nullptr; + TableRef m_current_table; +}; + +template +void verify_no_errors(Verifier&& verifier, std::vector const& changes) +{ + for (auto& change : changes) { + change.visit(verifier); + } + + if (!verifier.errors.empty()) { + throw ErrorType(verifier.errors); + } +} +} // anonymous namespace + +bool ObjectStore::needs_migration(std::vector const& changes) +{ + using namespace schema_change; + struct Visitor { + bool operator()(AddIndex) { return false; } + bool operator()(AddInitialProperties) { return false; } + bool operator()(AddProperty) { return true; } + bool operator()(AddTable) { return false; } + bool operator()(RemoveTable) { return false; } + bool operator()(ChangePrimaryKey) { return true; } + bool operator()(ChangePropertyType) { return true; } + bool operator()(MakePropertyNullable) { return true; } + bool operator()(MakePropertyRequired) { return true; } + bool operator()(RemoveIndex) { return false; } + bool operator()(RemoveProperty) { return true; } + }; + + return std::any_of(begin(changes), end(changes), + [](auto&& change) { return change.visit(Visitor()); }); +} + +void ObjectStore::verify_no_changes_required(std::vector const& changes) +{ + verify_no_errors(SchemaDifferenceExplainer(), changes); +} + +void ObjectStore::verify_no_migration_required(std::vector const& changes) +{ + using namespace schema_change; + struct Verifier : SchemaDifferenceExplainer { + using SchemaDifferenceExplainer::operator(); + + // Adding a table or adding/removing indexes can be done automatically. + // All other changes require migrations. + void operator()(AddTable) { } + void operator()(AddInitialProperties) { } + void operator()(AddIndex) { } + void operator()(RemoveIndex) { } + } verifier; + verify_no_errors(verifier, changes); +} + +bool ObjectStore::verify_valid_additive_changes(std::vector const& changes, bool update_indexes) +{ + using namespace schema_change; + struct Verifier : SchemaDifferenceExplainer { + using SchemaDifferenceExplainer::operator(); + + bool index_changes = false; + bool other_changes = false; + + // Additive mode allows adding things, extra columns, and adding/removing indexes + void operator()(AddTable) { other_changes = true; } + void operator()(AddInitialProperties) { other_changes = true; } + void operator()(AddProperty) { other_changes = true; } + void operator()(RemoveProperty) { } + void operator()(AddIndex) { index_changes = true; } + void operator()(RemoveIndex) { index_changes = true; } + } verifier; + verify_no_errors(verifier, changes); + return verifier.other_changes || (verifier.index_changes && update_indexes); +} + +void ObjectStore::verify_valid_external_changes(std::vector const& changes) +{ + using namespace schema_change; + struct Verifier : SchemaDifferenceExplainer { + using SchemaDifferenceExplainer::operator(); + + // Adding new things is fine + void operator()(AddTable) { } + void operator()(AddInitialProperties) { } + void operator()(AddProperty) { } + void operator()(AddIndex) { } + void operator()(RemoveIndex) { } + + // Deleting tables is not okay + void operator()(RemoveTable op) { + errors.emplace_back("Class '%1' has been removed.", op.object->name); + } + } verifier; + verify_no_errors(verifier, changes); +} + +void ObjectStore::verify_compatible_for_immutable_and_readonly(std::vector const& changes) +{ + using namespace schema_change; + struct Verifier : SchemaDifferenceExplainer { + using SchemaDifferenceExplainer::operator(); + + void operator()(AddTable) { } + void operator()(AddInitialProperties) { } + void operator()(RemoveProperty) { } + void operator()(AddIndex) { } + void operator()(RemoveIndex) { } + } verifier; + verify_no_errors(verifier, changes); +} + +static void apply_non_migration_changes(Group& group, std::vector const& changes) +{ + using namespace schema_change; + struct Applier : SchemaDifferenceExplainer { + Applier(Group& group) : group{group}, table{group} { } + Group& group; + TableHelper table; + + // Produce an exception listing the unsupported schema changes for + // everything but the explicitly supported ones + using SchemaDifferenceExplainer::operator(); + + void operator()(AddTable op) { create_table(group, *op.object); } + void operator()(AddInitialProperties op) { add_initial_columns(group, *op.object); } + void operator()(AddIndex op) { table(op.object).add_search_index(op.property->table_column); } + void operator()(RemoveIndex op) { table(op.object).remove_search_index(op.property->table_column); } + } applier{group}; + verify_no_errors(applier, changes); +} + +static void create_initial_tables(Group& group, std::vector const& changes) +{ + using namespace schema_change; + struct Applier { + Applier(Group& group) : group{group}, table{group} { } + Group& group; + TableHelper table; + + void operator()(AddTable op) { create_table(group, *op.object); } + void operator()(RemoveTable) { } + void operator()(AddInitialProperties op) { add_initial_columns(group, *op.object); } + + // Note that in normal operation none of these will be hit, as if we're + // creating the initial tables there shouldn't be anything to update. + // Implementing these makes us better able to handle weird + // not-quite-correct files produced by other things and has no obvious + // downside. + void operator()(AddProperty op) { add_column(group, table(op.object), *op.property); } + void operator()(RemoveProperty op) { table(op.object).remove_column(op.property->table_column); } + void operator()(MakePropertyNullable op) { make_property_optional(group, table(op.object), *op.property); } + void operator()(MakePropertyRequired op) { make_property_required(group, table(op.object), *op.property); } + void operator()(ChangePrimaryKey op) { ObjectStore::set_primary_key_for_object(group, op.object->name, op.property ? StringData{op.property->name} : ""); } + void operator()(AddIndex op) { table(op.object).add_search_index(op.property->table_column); } + void operator()(RemoveIndex op) { table(op.object).remove_search_index(op.property->table_column); } + + void operator()(ChangePropertyType op) + { + replace_column(group, table(op.object), *op.old_property, *op.new_property); + } + } applier{group}; + + for (auto& change : changes) { + change.visit(applier); + } +} + +void ObjectStore::apply_additive_changes(Group& group, std::vector const& changes, bool update_indexes) +{ + using namespace schema_change; + struct Applier { + Applier(Group& group, bool update_indexes) + : group{group}, table{group}, update_indexes{update_indexes} { } + Group& group; + TableHelper table; + bool update_indexes; + + void operator()(AddTable op) { create_table(group, *op.object); } + void operator()(RemoveTable) { } + void operator()(AddInitialProperties op) { add_initial_columns(group, *op.object); } + void operator()(AddProperty op) { add_column(group, table(op.object), *op.property); } + void operator()(AddIndex op) { if (update_indexes) table(op.object).add_search_index(op.property->table_column); } + void operator()(RemoveIndex op) { if (update_indexes) table(op.object).remove_search_index(op.property->table_column); } + void operator()(RemoveProperty) { } + + // No need for errors for these, as we've already verified that they aren't present + void operator()(ChangePrimaryKey) { } + void operator()(ChangePropertyType) { } + void operator()(MakePropertyNullable) { } + void operator()(MakePropertyRequired) { } + } applier{group, update_indexes}; + + for (auto& change : changes) { + change.visit(applier); + } +} + +static void apply_pre_migration_changes(Group& group, std::vector const& changes) +{ + using namespace schema_change; + struct Applier { + Applier(Group& group) : group{group}, table{group} { } + Group& group; + TableHelper table; + + void operator()(AddTable op) { create_table(group, *op.object); } + void operator()(RemoveTable) { } + void operator()(AddInitialProperties op) { add_initial_columns(group, *op.object); } + void operator()(AddProperty op) { add_column(group, table(op.object), *op.property); } + void operator()(RemoveProperty) { /* delayed until after the migration */ } + void operator()(ChangePropertyType op) { replace_column(group, table(op.object), *op.old_property, *op.new_property); } + void operator()(MakePropertyNullable op) { make_property_optional(group, table(op.object), *op.property); } + void operator()(MakePropertyRequired op) { make_property_required(group, table(op.object), *op.property); } + void operator()(ChangePrimaryKey op) { ObjectStore::set_primary_key_for_object(group, op.object->name.c_str(), op.property ? op.property->name.c_str() : ""); } + void operator()(AddIndex op) { table(op.object).add_search_index(op.property->table_column); } + void operator()(RemoveIndex op) { table(op.object).remove_search_index(op.property->table_column); } + } applier{group}; + + for (auto& change : changes) { + change.visit(applier); + } +} + +enum class DidRereadSchema { Yes, No }; + +static void apply_post_migration_changes(Group& group, std::vector const& changes, Schema const& initial_schema, + DidRereadSchema did_reread_schema) +{ + using namespace schema_change; + struct Applier { + Applier(Group& group, Schema const& initial_schema, DidRereadSchema did_reread_schema) + : group{group}, initial_schema(initial_schema), table(group) + , did_reread_schema(did_reread_schema == DidRereadSchema::Yes) + { } + Group& group; + Schema const& initial_schema; + TableHelper table; + bool did_reread_schema; + + void operator()(RemoveProperty op) + { + if (!initial_schema.empty() && !initial_schema.find(op.object->name)->property_for_name(op.property->name)) + throw std::logic_error(util::format("Renamed property '%1.%2' does not exist.", op.object->name, op.property->name)); + auto table = table_for_object_schema(group, *op.object); + table->remove_column(op.property->table_column); + } + + void operator()(ChangePrimaryKey op) + { + if (op.property) { + validate_primary_column_uniqueness(group, op.object->name, op.property->name); + } + } + + void operator()(AddTable op) { create_table(group, *op.object); } + + void operator()(AddInitialProperties op) { + if (did_reread_schema) + add_initial_columns(group, *op.object); + else { + // If we didn't re-read the schema then AddInitialProperties was already taken care of + // during apply_pre_migration_changes. + } + } + + void operator()(AddIndex op) { table(op.object).add_search_index(op.property->table_column); } + void operator()(RemoveIndex op) { table(op.object).remove_search_index(op.property->table_column); } + + void operator()(RemoveTable) { } + void operator()(ChangePropertyType) { } + void operator()(MakePropertyNullable) { } + void operator()(MakePropertyRequired) { } + void operator()(AddProperty) { } + } applier{group, initial_schema, did_reread_schema}; + + for (auto& change : changes) { + change.visit(applier); + } +} + +static void create_default_permissions(Group& group, std::vector const& changes, + std::string const& sync_user_id) +{ +#if !REALM_ENABLE_SYNC + static_cast(group); + static_cast(changes); + static_cast(sync_user_id); +#else + _impl::initialize_schema(group); + sync::set_up_basic_permissions(group, true); + + // Ensure that this user exists so that local privileges checks work immediately + sync::add_user_to_role(group, sync_user_id, "everyone"); + + // Ensure that the user's private role exists so that local privilege checks work immediately. + ObjectStore::ensure_private_role_exists_for_user(group, sync_user_id); + + // Mark all tables we just created as fully world-accessible + // This has to be done after the first pass of schema init is done so that we can be + // sure that the permissions tables actually exist. + using namespace schema_change; + struct Applier { + Group& group; + void operator()(AddTable op) + { + sync::set_class_permissions_for_role(group, op.object->name, "everyone", + static_cast(ComputedPrivileges::All)); + } + + void operator()(RemoveTable) { } + void operator()(AddInitialProperties) { } + void operator()(AddProperty) { } + void operator()(RemoveProperty) { } + void operator()(MakePropertyNullable) { } + void operator()(MakePropertyRequired) { } + void operator()(ChangePrimaryKey) { } + void operator()(AddIndex) { } + void operator()(RemoveIndex) { } + void operator()(ChangePropertyType) { } + } applier{group}; + + for (auto& change : changes) { + change.visit(applier); + } +#endif +} + +#if REALM_ENABLE_SYNC +void ObjectStore::ensure_private_role_exists_for_user(Group& group, StringData sync_user_id) +{ + std::string private_role_name = util::format("__User:%1", sync_user_id); + + TableRef roles = ObjectStore::table_for_object_type(group, "__Role"); + size_t private_role_ndx = roles->find_first_string(roles->get_column_index("name"), private_role_name); + if (private_role_ndx != npos) { + // The private role already exists, so there's nothing for us to do. + return; + } + + // Add the user to the private role, creating the private role in the process. + sync::add_user_to_role(group, sync_user_id, private_role_name); + + // Set the private role on the user. + private_role_ndx = roles->find_first_string(roles->get_column_index("name"), private_role_name); + TableRef users = ObjectStore::table_for_object_type(group, "__User"); + size_t user_ndx = users->find_first_string(users->get_column_index("id"), sync_user_id); + users->set_link(users->get_column_index("role"), user_ndx, private_role_ndx); +} +#endif + +void ObjectStore::apply_schema_changes(Group& group, uint64_t schema_version, + Schema& target_schema, uint64_t target_schema_version, + SchemaMode mode, std::vector const& changes, + util::Optional sync_user_id, + std::function migration_function) +{ + create_metadata_tables(group); + + if (mode == SchemaMode::Additive) { + bool target_schema_is_newer = (schema_version < target_schema_version + || schema_version == ObjectStore::NotVersioned); + + // With sync v2.x, indexes are no longer synced, so there's no reason to avoid creating them. + bool update_indexes = true; + apply_additive_changes(group, changes, update_indexes); + + if (target_schema_is_newer) + set_schema_version(group, target_schema_version); + + if (sync_user_id) + create_default_permissions(group, changes, *sync_user_id); + + set_schema_columns(group, target_schema); + return; + } + + if (schema_version == ObjectStore::NotVersioned) { + create_initial_tables(group, changes); + set_schema_version(group, target_schema_version); + set_schema_columns(group, target_schema); + return; + } + + if (mode == SchemaMode::Manual) { + set_schema_columns(group, target_schema); + if (migration_function) { + migration_function(); + } + + verify_no_changes_required(schema_from_group(group).compare(target_schema)); + validate_primary_column_uniqueness(group); + set_schema_columns(group, target_schema); + set_schema_version(group, target_schema_version); + return; + } + + if (schema_version == target_schema_version) { + apply_non_migration_changes(group, changes); + set_schema_columns(group, target_schema); + return; + } + + auto old_schema = schema_from_group(group); + apply_pre_migration_changes(group, changes); + if (migration_function) { + set_schema_columns(group, target_schema); + migration_function(); + + // Migration function may have changed the schema, so we need to re-read it + auto schema = schema_from_group(group); + apply_post_migration_changes(group, schema.compare(target_schema), old_schema, DidRereadSchema::Yes); + validate_primary_column_uniqueness(group); + } + else { + apply_post_migration_changes(group, changes, {}, DidRereadSchema::No); + } + + set_schema_version(group, target_schema_version); + set_schema_columns(group, target_schema); +} + +Schema ObjectStore::schema_from_group(Group const& group) { + std::vector schema; + schema.reserve(group.size()); + for (size_t i = 0; i < group.size(); i++) { + auto object_type = object_type_for_table_name(group.get_table_name(i)); + if (object_type.size()) { + schema.emplace_back(group, object_type, i); + } + } + return schema; +} + +util::Optional ObjectStore::property_for_column_index(ConstTableRef& table, size_t column_index) +{ + StringData column_name = table->get_column_name(column_index); + +#if REALM_ENABLE_SYNC + // The object ID column is an implementation detail, and is omitted from the schema. + // FIXME: Consider filtering out all column names starting with `!`. + if (column_name == sync::object_id_column_name) + return util::none; +#endif + + if (table->get_column_type(column_index) == type_Table) { + auto subdesc = table->get_subdescriptor(column_index); + if (subdesc->get_column_count() != 1 || subdesc->get_column_name(0) != ObjectStore::ArrayColumnName) + return util::none; + } + + Property property; + property.name = column_name; + property.type = ObjectSchema::from_core_type(*table->get_descriptor(), column_index); + property.is_indexed = table->has_search_index(column_index); + property.table_column = column_index; + + if (property.type == PropertyType::Object) { + // set link type for objects and arrays + ConstTableRef linkTable = table->get_link_target(column_index); + property.object_type = ObjectStore::object_type_for_table_name(linkTable->get_name().data()); + } + return property; +} + +void ObjectStore::set_schema_columns(Group const& group, Schema& schema) +{ + for (auto& object_schema : schema) { + auto table = table_for_object_schema(group, object_schema); + if (!table) { + continue; + } + for (auto& property : object_schema.persisted_properties) { + property.table_column = table->get_column_index(property.name); + } + } +} + +void ObjectStore::delete_data_for_object(Group& group, StringData object_type) { + if (TableRef table = table_for_object_type(group, object_type)) { + group.remove_table(table->get_index_in_group()); + ObjectStore::set_primary_key_for_object(group, object_type, ""); + } +} + +bool ObjectStore::is_empty(Group const& group) { + for (size_t i = 0; i < group.size(); i++) { + ConstTableRef table = group.get_table(i); + auto object_type = object_type_for_table_name(table->get_name()); + if (object_type.size() == 0 || object_type.begins_with("__")) { + continue; + } + if (!table->is_empty()) { + return false; + } + } + return true; +} + +void ObjectStore::rename_property(Group& group, Schema& target_schema, StringData object_type, StringData old_name, StringData new_name) +{ + TableRef table = table_for_object_type(group, object_type); + if (!table) { + throw std::logic_error(util::format("Cannot rename properties for type '%1' because it does not exist.", object_type)); + } + + auto target_object_schema = target_schema.find(object_type); + if (target_object_schema == target_schema.end()) { + throw std::logic_error(util::format("Cannot rename properties for type '%1' because it has been removed from the Realm.", object_type)); + } + + if (target_object_schema->property_for_name(old_name)) { + throw std::logic_error(util::format("Cannot rename property '%1.%2' to '%3' because the source property still exists.", + object_type, old_name, new_name)); + } + + ObjectSchema table_object_schema(group, object_type); + Property *old_property = table_object_schema.property_for_name(old_name); + if (!old_property) { + throw std::logic_error(util::format("Cannot rename property '%1.%2' because it does not exist.", object_type, old_name)); + } + + Property *new_property = table_object_schema.property_for_name(new_name); + if (!new_property) { + // New property doesn't exist in the table, which means we're probably + // renaming to an intermediate property in a multi-version migration. + // This is safe because the migration will fail schema validation unless + // this property is renamed again to a valid name before the end. + table->rename_column(old_property->table_column, new_name); + return; + } + + if (old_property->type != new_property->type || old_property->object_type != new_property->object_type) { + throw std::logic_error(util::format("Cannot rename property '%1.%2' to '%3' because it would change from type '%4' to '%5'.", + object_type, old_name, new_name, old_property->type_string(), new_property->type_string())); + } + + if (is_nullable(old_property->type) && !is_nullable(new_property->type)) { + throw std::logic_error(util::format("Cannot rename property '%1.%2' to '%3' because it would change from optional to required.", + object_type, old_name, new_name)); + } + + size_t column_to_remove = new_property->table_column; + table->rename_column(old_property->table_column, new_name); + table->remove_column(column_to_remove); + + // update table_column for each property since it may have shifted + for (auto& current_prop : target_object_schema->persisted_properties) { + if (current_prop.table_column == column_to_remove) + current_prop.table_column = old_property->table_column; + else if (current_prop.table_column > column_to_remove) + --current_prop.table_column; + } + + // update nullability for column + if (is_nullable(new_property->type) && !is_nullable(old_property->type)) { + auto prop = *new_property; + prop.table_column = old_property->table_column; + make_property_optional(group, *table, prop); + } +} + +InvalidSchemaVersionException::InvalidSchemaVersionException(uint64_t old_version, uint64_t new_version) +: logic_error(util::format("Provided schema version %1 is less than last set version %2.", new_version, old_version)) +, m_old_version(old_version), m_new_version(new_version) +{ +} + +DuplicatePrimaryKeyValueException::DuplicatePrimaryKeyValueException(std::string object_type, std::string property) +: logic_error(util::format("Primary key property '%1.%2' has duplicate values after migration.", object_type, property)) +, m_object_type(object_type), m_property(property) +{ +} + +SchemaValidationException::SchemaValidationException(std::vector const& errors) +: std::logic_error([&] { + std::string message = "Schema validation failed due to the following errors:"; + for (auto const& error : errors) { + message += std::string("\n- ") + error.what(); + } + return message; +}()) +{ +} + +SchemaMismatchException::SchemaMismatchException(std::vector const& errors) +: std::logic_error([&] { + std::string message = "Migration is required due to the following errors:"; + for (auto const& error : errors) { + message += std::string("\n- ") + error.what(); + } + return message; +}()) +{ +} + +InvalidSchemaChangeException::InvalidSchemaChangeException(std::vector const& errors) +: std::logic_error([&] { + std::string message = "The following changes cannot be made in additive-only schema mode:"; + for (auto const& error : errors) { + message += std::string("\n- ") + error.what(); + } + return message; +}()) +{ +} + +InvalidExternalSchemaChangeException::InvalidExternalSchemaChangeException(std::vector const& errors) +: std::logic_error([&] { + std::string message = + "Unsupported schema changes were made by another client or process. For a " + "synchronized Realm, this may be due to the server reverting schema changes which " + "the local user did not have permission to make."; + for (auto const& error : errors) { + message += std::string("\n- ") + error.what(); + } + return message; +}()) +{ +} diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/placeholder.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/placeholder.cpp new file mode 100644 index 0000000..8936534 --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/placeholder.cpp @@ -0,0 +1 @@ +// This file is intentionally left blank. diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/results.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/results.cpp new file mode 100644 index 0000000..15c21f2 --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/results.cpp @@ -0,0 +1,836 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "results.hpp" + +#include "impl/realm_coordinator.hpp" +#include "impl/results_notifier.hpp" +#include "audit.hpp" +#include "object_schema.hpp" +#include "object_store.hpp" +#include "schema.hpp" + +#include + +namespace realm { + +Results::Results() = default; +Results::~Results() = default; + +Results::Results(SharedRealm r, Query q, DescriptorOrdering o) +: m_realm(std::move(r)) +, m_query(std::move(q)) +, m_table(m_query.get_table()) +, m_descriptor_ordering(std::move(o)) +, m_mode(Mode::Query) +{ +} + +Results::Results(SharedRealm r, Table& table) +: m_realm(std::move(r)) +, m_mode(Mode::Table) +{ + m_table.reset(&table); +} + +Results::Results(SharedRealm r, LinkViewRef lv, util::Optional q, SortDescriptor s) +: m_realm(std::move(r)) +, m_link_view(lv) +, m_mode(Mode::LinkView) +{ + m_table.reset(&lv->get_target_table()); + if (q) { + m_query = std::move(*q); + m_mode = Mode::Query; + } + m_descriptor_ordering.append_sort(std::move(s)); +} + +Results::Results(SharedRealm r, TableView tv, DescriptorOrdering o) +: m_realm(std::move(r)) +, m_table_view(std::move(tv)) +, m_descriptor_ordering(std::move(o)) +, m_mode(Mode::TableView) +{ + m_table.reset(&m_table_view.get_parent()); +} + +Results::Results(const Results&) = default; +Results& Results::operator=(const Results&) = default; + +Results::Results(Results&& other) +: m_realm(std::move(other.m_realm)) +, m_object_schema(std::move(other.m_object_schema)) +, m_query(std::move(other.m_query)) +, m_table_view(std::move(other.m_table_view)) +, m_link_view(std::move(other.m_link_view)) +, m_table(std::move(other.m_table)) +, m_descriptor_ordering(std::move(other.m_descriptor_ordering)) +, m_notifier(std::move(other.m_notifier)) +, m_mode(other.m_mode) +, m_update_policy(other.m_update_policy) +, m_has_used_table_view(other.m_has_used_table_view) +, m_wants_background_updates(other.m_wants_background_updates) +{ + if (m_notifier) { + m_notifier->target_results_moved(other, *this); + } +} + +Results& Results::operator=(Results&& other) +{ + this->~Results(); + new (this) Results(std::move(other)); + return *this; +} + +bool Results::is_valid() const +{ + if (m_realm) + m_realm->verify_thread(); + + if (m_table && !m_table->is_attached()) + return false; + + return true; +} + +void Results::validate_read() const +{ + // is_valid ensures that we're on the correct thread. + if (!is_valid()) + throw InvalidatedException(); +} + +void Results::validate_write() const +{ + validate_read(); + if (!m_realm || !m_realm->is_in_transaction()) + throw InvalidTransactionException("Must be in a write transaction"); +} + +size_t Results::size() +{ + validate_read(); + switch (m_mode) { + case Mode::Empty: return 0; + case Mode::Table: return m_table->size(); + case Mode::LinkView: return m_link_view->size(); + case Mode::Query: + m_query.sync_view_if_needed(); + if (!m_descriptor_ordering.will_apply_distinct()) + return m_query.count(m_descriptor_ordering); + REALM_FALLTHROUGH; + case Mode::TableView: + evaluate_query_if_needed(); + return m_table_view.size(); + } + REALM_COMPILER_HINT_UNREACHABLE(); +} + +const ObjectSchema& Results::get_object_schema() const +{ + validate_read(); + + if (!m_object_schema) { + REALM_ASSERT(m_realm); + auto it = m_realm->schema().find(get_object_type()); + REALM_ASSERT(it != m_realm->schema().end()); + m_object_schema = &*it; + } + + return *m_object_schema; +} + + +StringData Results::get_object_type() const noexcept +{ + if (!m_table) { + return StringData(); + } + + return ObjectStore::object_type_for_table_name(m_table->get_name()); +} + +namespace { +template +auto get(Table& table, size_t row) +{ + return table.get(0, row); +} + +template<> +auto get(Table& table, size_t row) +{ + return table.get(row); +} +} + +template +util::Optional Results::try_get(size_t row_ndx) +{ + validate_read(); + switch (m_mode) { + case Mode::Empty: break; + case Mode::Table: + if (row_ndx < m_table->size()) + return realm::get(*m_table, row_ndx); + break; + case Mode::LinkView: + if (update_linkview()) { + if (row_ndx < m_link_view->size()) + return realm::get(*m_table, m_link_view->get(row_ndx).get_index()); + break; + } + REALM_FALLTHROUGH; + case Mode::Query: + case Mode::TableView: + evaluate_query_if_needed(); + if (row_ndx >= m_table_view.size()) + break; + if (m_update_policy == UpdatePolicy::Never && !m_table_view.is_row_attached(row_ndx)) + return T{}; + return realm::get(*m_table, m_table_view.get(row_ndx).get_index()); + } + return util::none; +} + +template +T Results::get(size_t row_ndx) +{ + if (auto row = try_get(row_ndx)) + return *row; + throw OutOfBoundsIndexException{row_ndx, size()}; +} + +template +util::Optional Results::first() +{ + return try_get(0); +} + +template +util::Optional Results::last() +{ + validate_read(); + if (m_mode == Mode::Query) + evaluate_query_if_needed(); // avoid running the query twice (for size() and for get()) + return try_get(size() - 1); +} + +bool Results::update_linkview() +{ + REALM_ASSERT(m_update_policy == UpdatePolicy::Auto); + + if (!m_descriptor_ordering.is_empty()) { + m_query = get_query(); + m_mode = Mode::Query; + evaluate_query_if_needed(); + return false; + } + return true; +} + +void Results::evaluate_query_if_needed(bool wants_notifications) +{ + if (m_update_policy == UpdatePolicy::Never) { + REALM_ASSERT(m_mode == Mode::TableView); + return; + } + + switch (m_mode) { + case Mode::Empty: + case Mode::Table: + case Mode::LinkView: + return; + case Mode::Query: + m_query.sync_view_if_needed(); + m_table_view = m_query.find_all(m_descriptor_ordering); + m_mode = Mode::TableView; + REALM_FALLTHROUGH; + case Mode::TableView: + if (wants_notifications) + prepare_async(ForCallback{false}); + m_has_used_table_view = true; + m_table_view.sync_if_needed(); + if (auto audit = m_realm->audit_context()) + audit->record_query(m_realm->read_transaction_version(), m_table_view); + break; + } +} + +template<> +size_t Results::index_of(RowExpr const& row) +{ + validate_read(); + if (!row) { + throw DetatchedAccessorException{}; + } + if (m_table && row.get_table() != m_table) { + throw IncorrectTableException( + ObjectStore::object_type_for_table_name(m_table->get_name()), + ObjectStore::object_type_for_table_name(row.get_table()->get_name()), + "Attempting to get the index of a Row of the wrong type" + ); + } + + switch (m_mode) { + case Mode::Empty: + return not_found; + case Mode::Table: + return row.get_index(); + case Mode::LinkView: + if (update_linkview()) + return m_link_view->find(row.get_index()); + REALM_FALLTHROUGH; + case Mode::Query: + case Mode::TableView: + evaluate_query_if_needed(); + return m_table_view.find_by_source_ndx(row.get_index()); + } + REALM_COMPILER_HINT_UNREACHABLE(); +} + +template +size_t Results::index_of(T const& value) +{ + validate_read(); + switch (m_mode) { + case Mode::Empty: + return not_found; + case Mode::Table: + return m_table->find_first(0, value); + case Mode::LinkView: + REALM_UNREACHABLE(); + case Mode::Query: + case Mode::TableView: + evaluate_query_if_needed(); + return m_table_view.find_first(0, value); + } + REALM_COMPILER_HINT_UNREACHABLE(); +} + +size_t Results::index_of(Query&& q) +{ + if (m_descriptor_ordering.will_apply_sort()) { + auto first = filter(std::move(q)).first(); + return first ? index_of(*first) : not_found; + } + + auto query = get_query().and_query(std::move(q)); + query.sync_view_if_needed(); + size_t row = query.find(); + return row != not_found ? index_of(m_table->get(row)) : row; +} + +void Results::prepare_for_aggregate(size_t column, const char* name) +{ + if (column > m_table->get_column_count()) + throw OutOfBoundsIndexException{column, m_table->get_column_count()}; + switch (m_mode) { + case Mode::Empty: break; + case Mode::Table: break; + case Mode::LinkView: + m_query = this->get_query(); + m_mode = Mode::Query; + REALM_FALLTHROUGH; + case Mode::Query: + case Mode::TableView: + evaluate_query_if_needed(); + break; + default: + REALM_COMPILER_HINT_UNREACHABLE(); + } + switch (m_table->get_column_type(column)) { + case type_Timestamp: case type_Double: case type_Float: case type_Int: break; + default: throw UnsupportedColumnTypeException{column, m_table.get(), name}; + } +} + +template +util::Optional Results::aggregate(size_t column, + const char* name, + Int agg_int, Float agg_float, + Double agg_double, Timestamp agg_timestamp) +{ + validate_read(); + if (!m_table) + return none; + prepare_for_aggregate(column, name); + + auto do_agg = [&](auto const& getter) { + return Mixed(m_mode == Mode::Table ? getter(*m_table) : getter(m_table_view)); + }; + switch (m_table->get_column_type(column)) { + case type_Timestamp: return do_agg(agg_timestamp); + case type_Double: return do_agg(agg_double); + case type_Float: return do_agg(agg_float); + case type_Int: return do_agg(agg_int); + default: REALM_COMPILER_HINT_UNREACHABLE(); + } +} + +util::Optional Results::max(size_t column) +{ + size_t return_ndx = npos; + auto results = aggregate(column, "max", + [&](auto const& table) { return table.maximum_int(column, &return_ndx); }, + [&](auto const& table) { return table.maximum_float(column, &return_ndx); }, + [&](auto const& table) { return table.maximum_double(column, &return_ndx); }, + [&](auto const& table) { return table.maximum_timestamp(column, &return_ndx); }); + return return_ndx == npos ? none : results; +} + +util::Optional Results::min(size_t column) +{ + size_t return_ndx = npos; + auto results = aggregate(column, "min", + [&](auto const& table) { return table.minimum_int(column, &return_ndx); }, + [&](auto const& table) { return table.minimum_float(column, &return_ndx); }, + [&](auto const& table) { return table.minimum_double(column, &return_ndx); }, + [&](auto const& table) { return table.minimum_timestamp(column, &return_ndx); }); + return return_ndx == npos ? none : results; +} + +util::Optional Results::sum(size_t column) +{ + return aggregate(column, "sum", + [=](auto const& table) { return table.sum_int(column); }, + [=](auto const& table) { return table.sum_float(column); }, + [=](auto const& table) { return table.sum_double(column); }, + [=](auto const&) -> Timestamp { throw UnsupportedColumnTypeException{column, m_table.get(), "sum"}; }); +} + +util::Optional Results::average(size_t column) +{ + size_t value_count = 0; + auto results = aggregate(column, "average", + [&](auto const& table) { return table.average_int(column, &value_count); }, + [&](auto const& table) { return table.average_float(column, &value_count); }, + [&](auto const& table) { return table.average_double(column, &value_count); }, + [&](auto const&) -> Timestamp { throw UnsupportedColumnTypeException{column, m_table.get(), "average"}; }); + return value_count == 0 ? none : util::make_optional(results->get_double()); +} + +void Results::clear() +{ + switch (m_mode) { + case Mode::Empty: + return; + case Mode::Table: + validate_write(); + if (m_realm->is_partial()) + Results(m_realm, m_table->where()).clear(); + else + m_table->clear(); + break; + case Mode::Query: + // Not using Query:remove() because building the tableview and + // clearing it is actually significantly faster + case Mode::TableView: + validate_write(); + evaluate_query_if_needed(); + + switch (m_update_policy) { + case UpdatePolicy::Auto: + m_table_view.clear(RemoveMode::unordered); + break; + case UpdatePolicy::Never: { + // Copy the TableView because a frozen Results shouldn't let its size() change. + TableView copy(m_table_view); + copy.clear(RemoveMode::unordered); + break; + } + } + break; + case Mode::LinkView: + validate_write(); + m_link_view->remove_all_target_rows(); + break; + } +} + +PropertyType Results::get_type() const +{ + validate_read(); + switch (m_mode) { + case Mode::Empty: + case Mode::LinkView: + return PropertyType::Object; + case Mode::Query: + case Mode::TableView: + case Mode::Table: + if (m_table->get_index_in_group() != npos) + return PropertyType::Object; + return ObjectSchema::from_core_type(*m_table->get_descriptor(), 0); + } + REALM_COMPILER_HINT_UNREACHABLE(); +} + +Query Results::get_query() const +{ + validate_read(); + switch (m_mode) { + case Mode::Empty: + case Mode::Query: + return m_query; + case Mode::TableView: { + // A TableView has an associated Query if it was produced by Query::find_all. This is indicated + // by TableView::get_query returning a Query with a non-null table. + Query query = m_table_view.get_query(); + if (query.get_table()) { + return query; + } + + // The TableView has no associated query so create one with no conditions that is restricted + // to the rows in the TableView. + if (m_update_policy == UpdatePolicy::Auto) { + m_table_view.sync_if_needed(); + } + return Query(*m_table, std::unique_ptr(new TableView(m_table_view))); + } + case Mode::LinkView: + return m_table->where(m_link_view); + case Mode::Table: + return m_table->where(); + } + REALM_COMPILER_HINT_UNREACHABLE(); +} + +TableView Results::get_tableview() +{ + validate_read(); + switch (m_mode) { + case Mode::Empty: + return {}; + case Mode::LinkView: + if (update_linkview()) + return m_table->where(m_link_view).find_all(); + REALM_FALLTHROUGH; + case Mode::Query: + case Mode::TableView: + evaluate_query_if_needed(); + return m_table_view; + case Mode::Table: + return m_table->where().find_all(); + } + REALM_COMPILER_HINT_UNREACHABLE(); +} + +static std::vector parse_keypath(StringData keypath, Schema const& schema, const ObjectSchema *object_schema) +{ + auto check = [&](bool condition, const char* fmt, auto... args) { + if (!condition) { + throw std::invalid_argument(util::format("Cannot sort on key path '%1': %2.", + keypath, util::format(fmt, args...))); + } + }; + auto is_sortable_type = [](PropertyType type) { + return !is_array(type) && type != PropertyType::LinkingObjects && type != PropertyType::Data; + }; + + const char* begin = keypath.data(); + const char* end = keypath.data() + keypath.size(); + check(begin != end, "missing property name"); + + std::vector indices; + while (begin != end) { + auto sep = std::find(begin, end, '.'); + check(sep != begin && sep + 1 != end, "missing property name"); + StringData key(begin, sep - begin); + begin = sep + (sep != end); + + auto prop = object_schema->property_for_name(key); + check(prop, "property '%1.%2' does not exist", object_schema->name, key); + check(is_sortable_type(prop->type), "property '%1.%2' is of unsupported type '%3'", + object_schema->name, key, string_for_property_type(prop->type)); + if (prop->type == PropertyType::Object) + check(begin != end, "property '%1.%2' of type 'object' cannot be the final property in the key path", + object_schema->name, key); + else + check(begin == end, "property '%1.%2' of type '%3' may only be the final property in the key path", + object_schema->name, key, prop->type_string()); + + indices.push_back(prop->table_column); + if (prop->type == PropertyType::Object) + object_schema = &*schema.find(prop->object_type); + } + return indices; +} + +Results Results::sort(std::vector> const& keypaths) const +{ + if (keypaths.empty()) + return *this; + if (get_type() != PropertyType::Object) { + if (keypaths.size() != 1) + throw std::invalid_argument(util::format("Cannot sort array of '%1' on more than one key path", + string_for_property_type(get_type()))); + if (keypaths[0].first != "self") + throw std::invalid_argument(util::format("Cannot sort on key path '%1': arrays of '%2' can only be sorted on 'self'", + keypaths[0].first, string_for_property_type(get_type()))); + return sort({*m_table, {{0}}, {keypaths[0].second}}); + } + + std::vector> column_indices; + std::vector ascending; + column_indices.reserve(keypaths.size()); + ascending.reserve(keypaths.size()); + + for (auto& keypath : keypaths) { + column_indices.push_back(parse_keypath(keypath.first, m_realm->schema(), &get_object_schema())); + ascending.push_back(keypath.second); + } + return sort({*m_table, std::move(column_indices), std::move(ascending)}); +} + +Results Results::sort(SortDescriptor&& sort) const +{ + if (m_mode == Mode::LinkView) + return Results(m_realm, m_link_view, util::none, std::move(sort)); + DescriptorOrdering new_order = m_descriptor_ordering; + new_order.append_sort(std::move(sort)); + return Results(m_realm, get_query(), std::move(new_order)); +} + +Results Results::filter(Query&& q) const +{ + if (m_descriptor_ordering.will_apply_limit()) + throw UnimplementedOperationException("Filtering a Results with a limit is not yet implemented"); + return Results(m_realm, get_query().and_query(std::move(q)), m_descriptor_ordering); +} + +Results Results::limit(size_t max_count) const +{ + auto new_order = m_descriptor_ordering; + new_order.append_limit(max_count); + return Results(m_realm, get_query(), std::move(new_order)); +} + +Results Results::apply_ordering(DescriptorOrdering&& ordering) +{ + DescriptorOrdering new_order = m_descriptor_ordering; + for (size_t i = 0; i < ordering.size(); ++i) { + auto desc = ordering[i]; + DescriptorType desc_type = ordering.get_type(i); + switch (desc_type) { + case DescriptorType::Sort: + new_order.append_sort(std::move(*dynamic_cast(desc))); + break; + case DescriptorType::Distinct: + new_order.append_distinct(std::move(*dynamic_cast(desc))); + break; + case DescriptorType::Limit: + new_order.append_limit(std::move(*dynamic_cast(desc))); + break; + case DescriptorType::Include: + new_order.append_include(std::move(*dynamic_cast(desc))); + break; + } + } + return Results(m_realm, get_query(), std::move(new_order)); +} + +Results Results::distinct(DistinctDescriptor&& uniqueness) const +{ + DescriptorOrdering new_order = m_descriptor_ordering; + new_order.append_distinct(std::move(uniqueness)); + return Results(m_realm, get_query(), std::move(new_order)); +} + +Results Results::distinct(std::vector const& keypaths) const +{ + if (keypaths.empty()) + return *this; + if (get_type() != PropertyType::Object) { + if (keypaths.size() != 1) + throw std::invalid_argument(util::format("Cannot sort array of '%1' on more than one key path", + string_for_property_type(get_type()))); + if (keypaths[0] != "self") + throw std::invalid_argument(util::format("Cannot sort on key path '%1': arrays of '%2' can only be sorted on 'self'", + keypaths[0], string_for_property_type(get_type()))); + return distinct({*m_table, {{0}}}); + } + + std::vector> column_indices; + column_indices.reserve(keypaths.size()); + for (auto& keypath : keypaths) + column_indices.push_back(parse_keypath(keypath, m_realm->schema(), &get_object_schema())); + return distinct({*m_table, std::move(column_indices)}); +} + +Results Results::snapshot() const & +{ + validate_read(); + return Results(*this).snapshot(); +} + +Results Results::snapshot() && +{ + validate_read(); + + switch (m_mode) { + case Mode::Empty: + return Results(); + + case Mode::Table: + case Mode::LinkView: + m_query = get_query(); + m_mode = Mode::Query; + + REALM_FALLTHROUGH; + case Mode::Query: + case Mode::TableView: + evaluate_query_if_needed(false); + m_notifier.reset(); + m_update_policy = UpdatePolicy::Never; + return std::move(*this); + } + REALM_COMPILER_HINT_UNREACHABLE(); +} + +void Results::prepare_async(ForCallback force) +{ + if (m_notifier) { + return; + } + if (m_realm->config().immutable()) { + if (force) + throw InvalidTransactionException("Cannot create asynchronous query for immutable Realms"); + return; + } + if (m_realm->is_in_transaction()) { + if (force) + throw InvalidTransactionException("Cannot create asynchronous query while in a write transaction"); + return; + } + if (m_update_policy == UpdatePolicy::Never) { + if (force) + throw std::logic_error("Cannot create asynchronous query for snapshotted Results."); + return; + } + if (!force) { + // Don't do implicit background updates if we can't actually deliver them + if (!m_realm->can_deliver_notifications()) + return; + // Don't do implicit background updates if there isn't actually anything + // that needs to be run. + if (!m_query.get_table() && m_descriptor_ordering.is_empty()) + return; + } + + m_wants_background_updates = true; + m_notifier = std::make_shared<_impl::ResultsNotifier>(*this); + _impl::RealmCoordinator::register_notifier(m_notifier); +} + +NotificationToken Results::add_notification_callback(CollectionChangeCallback cb) & +{ + prepare_async(ForCallback{true}); + return {m_notifier, m_notifier->add_callback(std::move(cb))}; +} + +bool Results::is_in_table_order() const +{ + switch (m_mode) { + case Mode::Empty: + case Mode::Table: + return true; + case Mode::LinkView: + return false; + case Mode::Query: + return m_query.produces_results_in_table_order() && !m_descriptor_ordering.will_apply_sort(); + case Mode::TableView: + return m_table_view.is_in_table_order(); + } + REALM_COMPILER_HINT_UNREACHABLE(); +} + +void Results::Internal::set_table_view(Results& results, TableView &&tv) +{ + REALM_ASSERT(results.m_update_policy != UpdatePolicy::Never); + // If the previous TableView was never actually used, then stop generating + // new ones until the user actually uses the Results object again + if (results.m_mode == Mode::TableView) { + results.m_wants_background_updates = results.m_has_used_table_view; + } + + results.m_table_view = std::move(tv); + results.m_mode = Mode::TableView; + results.m_has_used_table_view = false; + REALM_ASSERT(results.m_table_view.is_in_sync()); + REALM_ASSERT(results.m_table_view.is_attached()); +} +#define REALM_RESULTS_TYPE(T) \ + template T Results::get(size_t); \ + template util::Optional Results::first(); \ + template util::Optional Results::last(); \ + template size_t Results::index_of(T const&); + +template RowExpr Results::get(size_t); +template util::Optional Results::first(); +template util::Optional Results::last(); + +REALM_RESULTS_TYPE(bool) +REALM_RESULTS_TYPE(int64_t) +REALM_RESULTS_TYPE(float) +REALM_RESULTS_TYPE(double) +REALM_RESULTS_TYPE(StringData) +REALM_RESULTS_TYPE(BinaryData) +REALM_RESULTS_TYPE(Timestamp) +REALM_RESULTS_TYPE(util::Optional) +REALM_RESULTS_TYPE(util::Optional) +REALM_RESULTS_TYPE(util::Optional) +REALM_RESULTS_TYPE(util::Optional) + +#undef REALM_RESULTS_TYPE + +Results::OutOfBoundsIndexException::OutOfBoundsIndexException(size_t r, size_t c) +: std::out_of_range(util::format("Requested index %1 greater than max %2", r, c - 1)) +, requested(r), valid_count(c) {} + +static std::string unsupported_operation_msg(size_t column, const Table* table, const char* operation) +{ + const char* column_type = string_for_property_type(ObjectSchema::from_core_type(*table->get_descriptor(), column)); + if (table->is_group_level()) + return util::format("Cannot %1 property '%2': operation not supported for '%3' properties", + operation, table->get_column_name(column), column_type); + return util::format("Cannot %1 '%2' array: operation not supported", + operation, column_type); +} + +Results::UnsupportedColumnTypeException::UnsupportedColumnTypeException(size_t column, const Table* table, const char* operation) +: std::logic_error(unsupported_operation_msg(column, table, operation)) +, column_index(column) +, column_name(table->get_column_name(column)) +, property_type(ObjectSchema::from_core_type(*table->get_descriptor(), column)) +{ +} + +Results::InvalidPropertyException::InvalidPropertyException(const std::string& object_type, const std::string& property_name) +: std::logic_error(util::format("Property '%1.%2' does not exist", object_type, property_name)) +, object_type(object_type), property_name(property_name) +{ +} + +Results::UnimplementedOperationException::UnimplementedOperationException(const char* msg) +: std::logic_error(msg) +{ +} + +} // namespace realm diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/schema.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/schema.cpp new file mode 100644 index 0000000..4b572e7 --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/schema.cpp @@ -0,0 +1,275 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "schema.hpp" + +#include "object_schema.hpp" +#include "object_store.hpp" +#include "object_schema.hpp" +#include "property.hpp" + +#include + +using namespace realm; + +namespace realm { +bool operator==(Schema const& a, Schema const& b) +{ + return static_cast(a) == static_cast(b); +} +} + +Schema::Schema() = default; +Schema::~Schema() = default; +Schema::Schema(Schema const&) = default; +Schema::Schema(Schema &&) = default; +Schema& Schema::operator=(Schema const&) = default; +Schema& Schema::operator=(Schema&&) = default; + +Schema::Schema(std::initializer_list types) : Schema(base(types)) { } + +Schema::Schema(base types) : base(std::move(types)) +{ + std::sort(begin(), end(), [](ObjectSchema const& lft, ObjectSchema const& rgt) { + return lft.name < rgt.name; + }); +} + +Schema::iterator Schema::find(StringData name) +{ + auto it = std::lower_bound(begin(), end(), name, [](ObjectSchema const& lft, StringData rgt) { + return lft.name < rgt; + }); + if (it != end() && it->name != name) { + it = end(); + } + return it; +} + +Schema::const_iterator Schema::find(StringData name) const +{ + return const_cast(this)->find(name); +} + +Schema::iterator Schema::find(ObjectSchema const& object) noexcept +{ + return find(object.name); +} + +Schema::const_iterator Schema::find(ObjectSchema const& object) const noexcept +{ + return const_cast(this)->find(object); +} + +void Schema::validate() const +{ + std::vector exceptions; + + // As the types are added sorted by name, we can detect duplicates by just looking at the following element. + auto find_next_duplicate = [&](const_iterator start) { + return std::adjacent_find(start, cend(), [](ObjectSchema const& lft, ObjectSchema const& rgt) { + return lft.name == rgt.name; + }); + }; + + for (auto it = find_next_duplicate(cbegin()); it != cend(); it = find_next_duplicate(++it)) { + exceptions.push_back(ObjectSchemaValidationException("Type '%1' appears more than once in the schema.", + it->name)); + } + + for (auto const& object : *this) { + object.validate(*this, exceptions); + } + + if (exceptions.size()) { + throw SchemaValidationException(exceptions); + } +} + +namespace { +struct IsNotRemoveProperty { + bool operator()(SchemaChange sc) const { return sc.visit(*this); } + bool operator()(schema_change::RemoveProperty) const { return false; } + template bool operator()(T) const { return true; } +}; +struct GetRemovedColumn { + size_t operator()(SchemaChange sc) const { return sc.visit(*this); } + size_t operator()(schema_change::RemoveProperty p) const { return p.property->table_column; } + template size_t operator()(T) const { REALM_COMPILER_HINT_UNREACHABLE(); } +}; +} + +static void compare(ObjectSchema const& existing_schema, + ObjectSchema const& target_schema, + std::vector& changes) +{ + for (auto& current_prop : existing_schema.persisted_properties) { + auto target_prop = target_schema.property_for_name(current_prop.name); + + if (!target_prop) { + changes.emplace_back(schema_change::RemoveProperty{&existing_schema, ¤t_prop}); + continue; + } + if (target_schema.property_is_computed(*target_prop)) { + changes.emplace_back(schema_change::RemoveProperty{&existing_schema, ¤t_prop}); + continue; + } + if (current_prop.type != target_prop->type || + current_prop.object_type != target_prop->object_type || + is_array(current_prop.type) != is_array(target_prop->type)) { + + changes.emplace_back(schema_change::ChangePropertyType{&existing_schema, ¤t_prop, target_prop}); + continue; + } + if (is_nullable(current_prop.type) != is_nullable(target_prop->type)) { + if (is_nullable(current_prop.type)) + changes.emplace_back(schema_change::MakePropertyRequired{&existing_schema, ¤t_prop}); + else + changes.emplace_back(schema_change::MakePropertyNullable{&existing_schema, ¤t_prop}); + } + if (target_prop->requires_index()) { + if (!current_prop.is_indexed) + changes.emplace_back(schema_change::AddIndex{&existing_schema, ¤t_prop}); + } + else if (current_prop.requires_index()) { + changes.emplace_back(schema_change::RemoveIndex{&existing_schema, ¤t_prop}); + } + } + + if (existing_schema.primary_key != target_schema.primary_key) { + changes.emplace_back(schema_change::ChangePrimaryKey{&existing_schema, target_schema.primary_key_property()}); + } + + for (auto& target_prop : target_schema.persisted_properties) { + if (!existing_schema.property_for_name(target_prop.name)) { + changes.emplace_back(schema_change::AddProperty{&existing_schema, &target_prop}); + } + } + + // Move all RemovePropertys to the end and sort in descending order of + // column index, as removing a column will shift all columns after that one + auto it = std::partition(begin(changes), end(changes), IsNotRemoveProperty{}); + std::sort(it, end(changes), + [](auto a, auto b) { return GetRemovedColumn()(a) > GetRemovedColumn()(b); }); +} + +template +void Schema::zip_matching(T&& a, U&& b, Func&& func) +{ + size_t i = 0, j = 0; + while (i < a.size() && j < b.size()) { + auto& object_schema = a[i]; + auto& matching_schema = b[j]; + int cmp = object_schema.name.compare(matching_schema.name); + if (cmp == 0) { + func(&object_schema, &matching_schema); + ++i; + ++j; + } + else if (cmp < 0) { + func(&object_schema, nullptr); + ++i; + } + else { + func(nullptr, &matching_schema); + ++j; + } + } + for (; i < a.size(); ++i) + func(&a[i], nullptr); + for (; j < b.size(); ++j) + func(nullptr, &b[j]); + +} + +std::vector Schema::compare(Schema const& target_schema, bool include_table_removals) const +{ + std::vector changes; + + // Add missing tables + zip_matching(target_schema, *this, [&](const ObjectSchema* target, const ObjectSchema* existing) { + if (target && !existing) { + changes.emplace_back(schema_change::AddTable{target}); + } + else if (include_table_removals && existing && !target) { + changes.emplace_back(schema_change::RemoveTable{existing}); + } + }); + + // Modify columns + zip_matching(target_schema, *this, [&](const ObjectSchema* target, const ObjectSchema* existing) { + if (target && existing) + ::compare(*existing, *target, changes); + else if (target) { + // Target is a new table -- add all properties + changes.emplace_back(schema_change::AddInitialProperties{target}); + } + // nothing for tables in existing but not target + }); + return changes; +} + +void Schema::copy_table_columns_from(realm::Schema const& other) +{ + zip_matching(*this, other, [&](ObjectSchema* existing, const ObjectSchema* other) { + if (!existing || !other) + return; + + for (auto& current_prop : other->persisted_properties) { + auto target_prop = existing->property_for_name(current_prop.name); + if (target_prop) { + target_prop->table_column = current_prop.table_column; + } + } + }); +} + +namespace realm { +bool operator==(SchemaChange const& lft, SchemaChange const& rgt) +{ + if (lft.m_kind != rgt.m_kind) + return false; + + using namespace schema_change; + struct Visitor { + SchemaChange const& value; + + #define REALM_SC_COMPARE(type, ...) \ + bool operator()(type rgt) const \ + { \ + auto cmp = [](auto&& v) { return std::tie(__VA_ARGS__); }; \ + return cmp(value.type) == cmp(rgt); \ + } + + REALM_SC_COMPARE(AddIndex, v.object, v.property) + REALM_SC_COMPARE(AddProperty, v.object, v.property) + REALM_SC_COMPARE(AddInitialProperties, v.object) + REALM_SC_COMPARE(AddTable, v.object) + REALM_SC_COMPARE(RemoveTable, v.object) + REALM_SC_COMPARE(ChangePrimaryKey, v.object, v.property) + REALM_SC_COMPARE(ChangePropertyType, v.object, v.old_property, v.new_property) + REALM_SC_COMPARE(MakePropertyNullable, v.object, v.property) + REALM_SC_COMPARE(MakePropertyRequired, v.object, v.property) + REALM_SC_COMPARE(RemoveIndex, v.object, v.property) + REALM_SC_COMPARE(RemoveProperty, v.object, v.property) + + #undef REALM_SC_COMPARE + } visitor{lft}; + return rgt.visit(visitor); +} +} // namespace realm diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/shared_realm.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/shared_realm.cpp new file mode 100644 index 0000000..a3f2665 --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/shared_realm.cpp @@ -0,0 +1,1159 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "shared_realm.hpp" + +#include "impl/collection_notifier.hpp" +#include "impl/realm_coordinator.hpp" +#include "impl/transact_log_handler.hpp" + +#include "audit.hpp" +#include "binding_context.hpp" +#include "list.hpp" +#include "object.hpp" +#include "object_schema.hpp" +#include "object_store.hpp" +#include "results.hpp" +#include "schema.hpp" +#include "thread_safe_reference.hpp" + +#include +#include +#include + + +#if REALM_ENABLE_SYNC +#include "sync/impl/sync_file.hpp" +#include "sync/sync_config.hpp" +#include "sync/sync_manager.hpp" + +#include +#include +#include +#else +namespace realm { +namespace sync { + struct PermissionsCache {}; + struct TableInfoCache {}; +} +} +#endif + +using namespace realm; +using namespace realm::_impl; + +Realm::Realm(Config config, std::shared_ptr<_impl::RealmCoordinator> coordinator) +: m_config(std::move(config)) +, m_execution_context(m_config.execution_context) +{ + open_with_config(m_config, m_history, m_shared_group, m_read_only_group, this); + + if (m_read_only_group) { + m_group = m_read_only_group.get(); + m_schema_version = ObjectStore::get_schema_version(*m_group); + m_schema = ObjectStore::schema_from_group(*m_group); + } + else if (!coordinator || !coordinator->get_cached_schema(m_schema, m_schema_version, m_schema_transaction_version)) { + if (m_config.should_compact_on_launch_function) { + size_t free_space = -1; + size_t used_space = -1; + // getting stats requires committing a write transaction beforehand. + Group* group = nullptr; + if (m_shared_group->try_begin_write(group)) { + m_shared_group->commit(); + m_shared_group->get_stats(free_space, used_space); + if (m_config.should_compact_on_launch_function(free_space + used_space, used_space)) + compact(); + } + } + read_group(); + if (coordinator) + coordinator->cache_schema(m_schema, m_schema_version, m_schema_transaction_version); + m_shared_group->end_read(); + m_group = nullptr; + } + + m_coordinator = std::move(coordinator); +} + +REALM_NOINLINE static void translate_file_exception(StringData path, bool immutable=false) +{ + try { + throw; + } + catch (util::File::PermissionDenied const& ex) { + throw RealmFileException(RealmFileException::Kind::PermissionDenied, ex.get_path(), + util::format("Unable to open a realm at path '%1'. Please use a path where your app has %2 permissions.", + ex.get_path(), immutable ? "read" : "read-write"), + ex.what()); + } + catch (util::File::Exists const& ex) { + throw RealmFileException(RealmFileException::Kind::Exists, ex.get_path(), + util::format("File at path '%1' already exists.", ex.get_path()), + ex.what()); + } + catch (util::File::NotFound const& ex) { + throw RealmFileException(RealmFileException::Kind::NotFound, ex.get_path(), + util::format("Directory at path '%1' does not exist.", ex.get_path()), ex.what()); + } + catch (util::File::AccessError const& ex) { + // Errors for `open()` include the path, but other errors don't. We + // don't want two copies of the path in the error, so strip it out if it + // appears, and then include it in our prefix. + std::string underlying = ex.what(); + RealmFileException::Kind error_kind = RealmFileException::Kind::AccessError; + // FIXME: Replace this with a proper specific exception type once Core adds support for it. + if (underlying == "Bad or incompatible history type") + error_kind = RealmFileException::Kind::BadHistoryError; + auto pos = underlying.find(ex.get_path()); + if (pos != std::string::npos && pos > 0) { + // One extra char at each end for the quotes + underlying.replace(pos - 1, ex.get_path().size() + 2, ""); + } + throw RealmFileException(error_kind, ex.get_path(), + util::format("Unable to open a realm at path '%1': %2.", ex.get_path(), underlying), ex.what()); + } + catch (IncompatibleLockFile const& ex) { + throw RealmFileException(RealmFileException::Kind::IncompatibleLockFile, path, + "Realm file is currently open in another process " + "which cannot share access with this process. " + "All processes sharing a single file must be the same architecture.", + ex.what()); + } + catch (FileFormatUpgradeRequired const& ex) { + throw RealmFileException(RealmFileException::Kind::FormatUpgradeRequired, path, + "The Realm file format must be allowed to be upgraded " + "in order to proceed.", + ex.what()); + } +} + +#if REALM_ENABLE_SYNC +static bool is_nonupgradable_history(IncompatibleHistories const& ex) +{ + // FIXME: Replace this with a proper specific exception type once Core adds support for it. + return std::string(ex.what()).find(std::string("Incompatible histories. Nonupgradable history schema")) != npos; +} +#endif + +void Realm::open_with_config(const Config& config, + std::unique_ptr& history, + std::unique_ptr& shared_group, + std::unique_ptr& read_only_group, + Realm* realm) +{ + bool server_synchronization_mode = bool(config.sync_config) || config.force_sync_history; + try { + if (config.immutable()) { + if (config.realm_data.is_null()) { + read_only_group = std::make_unique(config.path, config.encryption_key.data(), Group::mode_ReadOnly); + } + else { + // Create in-memory read-only realm from existing buffer (without taking ownership of the buffer) + read_only_group = std::make_unique(config.realm_data, false); + } + } + else { + if (server_synchronization_mode) { +#if REALM_ENABLE_SYNC + history = realm::sync::make_client_history(config.path); +#else + REALM_TERMINATE("Realm was not built with sync enabled"); +#endif + } + else { + history = realm::make_in_realm_history(config.path); + } + + SharedGroupOptions options; + options.durability = config.in_memory ? SharedGroupOptions::Durability::MemOnly : + SharedGroupOptions::Durability::Full; + if (!config.fifo_files_fallback_path.empty()) { + options.temp_dir = util::normalize_dir(config.fifo_files_fallback_path); + } + options.encryption_key = config.encryption_key.data(); + options.allow_file_format_upgrade = !config.disable_format_upgrade && + config.schema_mode != SchemaMode::ResetFile; + options.upgrade_callback = [&](int from_version, int to_version) { + if (realm) { + realm->upgrade_initial_version = from_version; + realm->upgrade_final_version = to_version; + } + }; + shared_group = std::make_unique(*history, options); + } + } + catch (realm::FileFormatUpgradeRequired const&) { + if (config.schema_mode != SchemaMode::ResetFile) { + translate_file_exception(config.path, config.immutable()); + } + util::File::remove(config.path); + open_with_config(config, history, shared_group, read_only_group, realm); + } +#if REALM_ENABLE_SYNC + catch (IncompatibleHistories const& ex) { + if (!server_synchronization_mode || !is_nonupgradable_history(ex)) + translate_file_exception(config.path, config.immutable()); // Throws + + // Move the Realm file into the recovery directory. + std::string recovery_directory = SyncManager::shared().recovery_directory_path(config.sync_config ? config.sync_config->recovery_directory : none); + std::string new_realm_path = util::reserve_unique_file_name(recovery_directory, "synced-realm-XXXXXXX"); + util::File::move(config.path, new_realm_path); + + const char* message = "The local copy of this synced Realm was created with an incompatible version of " + "Realm. It has been moved aside, and the Realm will be re-downloaded the next time it " + "is opened. You should write a handler for this error that uses the provided " + "configuration to open the old Realm in read-only mode to recover any pending changes " + "and then remove the Realm file."; + throw RealmFileException(RealmFileException::Kind::IncompatibleSyncedRealm, std::move(new_realm_path), + message, ex.what()); + } +#endif // REALM_ENABLE_SYNC + catch (...) { + translate_file_exception(config.path, config.immutable()); + } +} + +Realm::~Realm() +{ + if (m_coordinator) { + m_coordinator->unregister_realm(this); + } +} + +bool Realm::is_partial() const noexcept +{ +#if REALM_ENABLE_SYNC + return m_config.sync_config && m_config.sync_config->is_partial; +#else + return false; +#endif +} + +Group& Realm::read_group() +{ + verify_open(); + + if (!m_group) + begin_read(VersionID{}); + return *m_group; +} + +void Realm::Internal::begin_read(Realm& realm, VersionID version_id) +{ + realm.begin_read(version_id); +} + +void Realm::begin_read(VersionID version_id) +{ + REALM_ASSERT(!m_group); + m_group = &const_cast(m_shared_group->begin_read(version_id)); + add_schema_change_handler(); + read_schema_from_group_if_needed(); +} + +SharedRealm Realm::get_shared_realm(Config config) +{ + auto coordinator = RealmCoordinator::get_coordinator(config.path); + return coordinator->get_realm(std::move(config)); +} + +SharedRealm Realm::get_shared_realm(ThreadSafeReference ref, util::Optional execution_context) +{ + REALM_ASSERT(ref.m_realm); + auto& config = ref.m_realm->config(); + auto coordinator = RealmCoordinator::get_coordinator(config.path); + if (auto realm = coordinator->get_cached_realm(config, execution_context)) + return realm; + coordinator->bind_to_context(*ref.m_realm, execution_context); + ref.m_realm->m_execution_context = execution_context; + return std::move(ref.m_realm); +} + +#if REALM_ENABLE_SYNC +std::shared_ptr Realm::get_synchronized_realm(Config config) +{ + auto coordinator = RealmCoordinator::get_coordinator(config.path); + return coordinator->get_synchronized_realm(std::move(config)); +} +#endif + +void Realm::set_schema(Schema const& reference, Schema schema) +{ + m_dynamic_schema = false; + schema.copy_table_columns_from(reference); + m_schema = std::move(schema); + notify_schema_changed(); +} + +void Realm::read_schema_from_group_if_needed() +{ + REALM_ASSERT(!m_read_only_group); + + Group& group = read_group(); + auto current_version = m_shared_group->get_version_of_current_transaction().version; + if (m_schema_transaction_version == current_version) + return; + + m_schema_transaction_version = current_version; + m_schema_version = ObjectStore::get_schema_version(group); + auto schema = ObjectStore::schema_from_group(group); + if (m_coordinator) + m_coordinator->cache_schema(schema, m_schema_version, + m_schema_transaction_version); + + if (m_dynamic_schema) { + if (m_schema == schema) { + // The structure of the schema hasn't changed. Bring the table column indices up to date. + m_schema.copy_table_columns_from(schema); + } + else { + // The structure of the schema has changed, so replace our copy of the schema. + // FIXME: This invalidates any pointers to the object schemas within the schema vector, + // which will cause problems for anyone that caches such a pointer. + m_schema = std::move(schema); + } + } + else { + ObjectStore::verify_valid_external_changes(m_schema.compare(schema)); + m_schema.copy_table_columns_from(schema); + } + notify_schema_changed(); +} + +bool Realm::reset_file(Schema& schema, std::vector& required_changes) +{ + // FIXME: this does not work if multiple processes try to open the file at + // the same time, or even multiple threads if there is not any external + // synchronization. The latter is probably fixable, but making it + // multi-process-safe requires some sort of multi-process exclusive lock + m_group = nullptr; + m_shared_group = nullptr; + m_history = nullptr; + util::File::remove(m_config.path); + + open_with_config(m_config, m_history, m_shared_group, m_read_only_group, this); + m_schema = ObjectStore::schema_from_group(read_group()); + m_schema_version = ObjectStore::get_schema_version(read_group()); + required_changes = m_schema.compare(schema); + m_coordinator->clear_schema_cache_and_set_schema_version(m_schema_version); + return false; +} + +bool Realm::schema_change_needs_write_transaction(Schema& schema, + std::vector& changes, + uint64_t version) +{ + if (version == m_schema_version && changes.empty()) + return false; + + switch (m_config.schema_mode) { + case SchemaMode::Automatic: + if (version < m_schema_version && m_schema_version != ObjectStore::NotVersioned) + throw InvalidSchemaVersionException(m_schema_version, version); + return true; + + case SchemaMode::Immutable: + if (version != m_schema_version) + throw InvalidSchemaVersionException(m_schema_version, version); + REALM_FALLTHROUGH; + case SchemaMode::ReadOnlyAlternative: + ObjectStore::verify_compatible_for_immutable_and_readonly(changes); + return false; + + case SchemaMode::ResetFile: + if (m_schema_version == ObjectStore::NotVersioned) + return true; + if (m_schema_version == version && !ObjectStore::needs_migration(changes)) + return true; + reset_file(schema, changes); + return true; + + case SchemaMode::Additive: { + bool will_apply_index_changes = version > m_schema_version; + if (ObjectStore::verify_valid_additive_changes(changes, will_apply_index_changes)) + return true; + return version != m_schema_version; + } + + case SchemaMode::Manual: + if (version < m_schema_version && m_schema_version != ObjectStore::NotVersioned) + throw InvalidSchemaVersionException(m_schema_version, version); + if (version == m_schema_version) { + ObjectStore::verify_no_changes_required(changes); + REALM_UNREACHABLE(); // changes is non-empty so above line always throws + } + return true; + } + REALM_COMPILER_HINT_UNREACHABLE(); +} + +Schema Realm::get_full_schema() +{ + if (!m_read_only_group) + refresh(); + + // If the user hasn't specified a schema previously then m_schema is always + // the full schema + if (m_dynamic_schema) + return m_schema; + + // Otherwise we may have a subset of the file's schema, so we need to get + // the complete thing to calculate what changes to make + if (m_read_only_group) + return ObjectStore::schema_from_group(read_group()); + + Schema actual_schema; + uint64_t actual_version; + uint64_t transaction = -1; + bool got_cached = m_coordinator->get_cached_schema(actual_schema, actual_version, transaction); + if (!got_cached || transaction != m_shared_group->get_version_of_current_transaction().version) + return ObjectStore::schema_from_group(read_group()); + return actual_schema; +} + +void Realm::set_schema_subset(Schema schema) +{ + REALM_ASSERT(m_dynamic_schema); + REALM_ASSERT(m_schema_version != ObjectStore::NotVersioned); + + std::vector changes = m_schema.compare(schema); + switch (m_config.schema_mode) { + case SchemaMode::Automatic: + case SchemaMode::ResetFile: + ObjectStore::verify_no_migration_required(changes); + break; + + case SchemaMode::Immutable: + case SchemaMode::ReadOnlyAlternative: + ObjectStore::verify_compatible_for_immutable_and_readonly(changes); + break; + + case SchemaMode::Additive: + ObjectStore::verify_valid_additive_changes(changes); + break; + + case SchemaMode::Manual: + ObjectStore::verify_no_changes_required(changes); + break; + } + + set_schema(m_schema, std::move(schema)); +} + +void Realm::update_schema(Schema schema, uint64_t version, MigrationFunction migration_function, + DataInitializationFunction initialization_function, bool in_transaction) +{ + schema.validate(); + + Schema actual_schema = get_full_schema(); + std::vector required_changes = actual_schema.compare(schema); + + if (!schema_change_needs_write_transaction(schema, required_changes, version)) { + set_schema(actual_schema, std::move(schema)); + return; + } + // Either the schema version has changed or we need to do non-migration changes + + if (!in_transaction) { + transaction::begin_without_validation(*m_shared_group); + + // Beginning the write transaction may have advanced the version and left + // us with nothing to do if someone else initialized the schema on disk + if (m_new_schema) { + actual_schema = *m_new_schema; + required_changes = actual_schema.compare(schema); + if (!schema_change_needs_write_transaction(schema, required_changes, version)) { + cancel_transaction(); + cache_new_schema(); + set_schema(actual_schema, std::move(schema)); + return; + } + } + cache_new_schema(); + } + + // Cancel the write transaction if we exit this function before committing it + auto cleanup = util::make_scope_exit([&]() noexcept { + // When in_transaction is true, caller is responsible to cancel the transaction. + if (!in_transaction && is_in_transaction()) + cancel_transaction(); + }); + + uint64_t old_schema_version = m_schema_version; + bool additive = m_config.schema_mode == SchemaMode::Additive; + if (migration_function && !additive) { + auto wrapper = [&] { + SharedRealm old_realm(new Realm(m_config, nullptr)); + // Need to open in read-write mode so that it uses a SharedGroup, but + // users shouldn't actually be able to write via the old realm + old_realm->m_config.schema_mode = SchemaMode::Immutable; + migration_function(old_realm, shared_from_this(), m_schema); + }; + + // migration function needs to see the target schema on the "new" Realm + std::swap(m_schema, schema); + std::swap(m_schema_version, version); + m_in_migration = true; + auto restore = util::make_scope_exit([&]() noexcept { + std::swap(m_schema, schema); + std::swap(m_schema_version, version); + m_in_migration = false; + }); + + ObjectStore::apply_schema_changes(read_group(), version, m_schema, m_schema_version, + m_config.schema_mode, required_changes, util::none, wrapper); + } + else { + util::Optional sync_user_id; +#if REALM_ENABLE_SYNC + if (m_config.sync_config && m_config.sync_config->is_partial) + sync_user_id = m_config.sync_config->user->identity(); +#endif + ObjectStore::apply_schema_changes(read_group(), m_schema_version, schema, version, + m_config.schema_mode, required_changes, std::move(sync_user_id)); + REALM_ASSERT_DEBUG(additive || (required_changes = ObjectStore::schema_from_group(read_group()).compare(schema)).empty()); + } + + if (initialization_function && old_schema_version == ObjectStore::NotVersioned) { + // Initialization function needs to see the latest schema + uint64_t temp_version = ObjectStore::get_schema_version(read_group()); + std::swap(m_schema, schema); + std::swap(m_schema_version, temp_version); + auto restore = util::make_scope_exit([&]() noexcept { + std::swap(m_schema, schema); + std::swap(m_schema_version, temp_version); + }); + initialization_function(shared_from_this()); + } + + if (!in_transaction) { + commit_transaction(); + } + + m_schema = std::move(schema); + m_schema_version = ObjectStore::get_schema_version(read_group()); + m_dynamic_schema = false; + m_coordinator->clear_schema_cache_and_set_schema_version(version); + notify_schema_changed(); +} + +void Realm::add_schema_change_handler() +{ + if (m_config.immutable()) + return; + m_group->set_schema_change_notification_handler([&] { + m_new_schema = ObjectStore::schema_from_group(read_group()); + m_schema_version = ObjectStore::get_schema_version(read_group()); + if (m_dynamic_schema) { + // FIXME: This invalidates any pointers to the object schemas within the schema vector, + // which will cause problems for anyone that caches such a pointer. + m_schema = *m_new_schema; + } + else + m_schema.copy_table_columns_from(*m_new_schema); + + notify_schema_changed(); + }); +} + +void Realm::cache_new_schema() +{ + if (!m_shared_group) + return; + + auto new_version = m_shared_group->get_version_of_current_transaction().version; + if (m_coordinator) { + if (m_new_schema) + m_coordinator->cache_schema(std::move(*m_new_schema), m_schema_version, new_version); + else + m_coordinator->advance_schema_cache(m_schema_transaction_version, new_version); + } + m_schema_transaction_version = new_version; + m_new_schema = util::none; +} + +void Realm::translate_schema_error() +{ + // Open another copy of the file to read the new (incompatible) schema without changing + // our read transaction + auto config = m_config; + config.schema = util::none; + auto realm = Realm::make_shared_realm(std::move(config), nullptr); + auto& new_schema = realm->schema(); + + // Should always throw + ObjectStore::verify_valid_external_changes(m_schema.compare(new_schema, true)); + + // Something strange happened so just rethrow the old exception + throw; +} + +void Realm::notify_schema_changed() +{ + if (m_binding_context) { + m_binding_context->schema_did_change(m_schema); + } +} + +static void check_read_write(const Realm* realm) +{ + if (realm->config().immutable()) { + throw InvalidTransactionException("Can't perform transactions on read-only Realms."); + } +} + +static void check_write(const Realm* realm) +{ + if (realm->config().immutable() || realm->config().read_only_alternative()) { + throw InvalidTransactionException("Can't perform transactions on read-only Realms."); + } +} + +void Realm::verify_thread() const +{ + if (!m_execution_context.contains()) + return; + + auto thread_id = m_execution_context.get(); + if (thread_id != std::this_thread::get_id()) + throw IncorrectThreadException(); +} + +void Realm::verify_in_write() const +{ + if (!is_in_transaction()) { + throw InvalidTransactionException("Cannot modify managed objects outside of a write transaction."); + } +} + +void Realm::verify_open() const +{ + if (is_closed()) { + throw ClosedRealmException(); + } +} + +VersionID Realm::read_transaction_version() const +{ + verify_thread(); + verify_open(); + check_read_write(this); + return m_shared_group->get_version_of_current_transaction(); +} + +bool Realm::is_in_transaction() const noexcept +{ + if (!m_shared_group) { + return false; + } + return m_shared_group->get_transact_stage() == SharedGroup::transact_Writing; +} + +void Realm::begin_transaction() +{ + check_write(this); + verify_thread(); + + if (is_in_transaction()) { + throw InvalidTransactionException("The Realm is already in a write transaction"); + } + + // Any of the callbacks to user code below could drop the last remaining + // strong reference to `this` + auto retain_self = shared_from_this(); + + // If we're already in the middle of sending notifications, just begin the + // write transaction without sending more notifications. If this actually + // advances the read version this could leave the user in an inconsistent + // state, but that's unavoidable. + if (m_is_sending_notifications) { + _impl::NotifierPackage notifiers; + transaction::begin(m_shared_group, m_binding_context.get(), notifiers); + return; + } + + // make sure we have a read transaction + read_group(); + + m_is_sending_notifications = true; + auto cleanup = util::make_scope_exit([this]() noexcept { m_is_sending_notifications = false; }); + + try { + m_coordinator->promote_to_write(*this); + } + catch (_impl::UnsupportedSchemaChange const&) { + translate_schema_error(); + } + cache_new_schema(); +} + +void Realm::commit_transaction() +{ + check_write(this); + verify_thread(); + + if (!is_in_transaction()) { + throw InvalidTransactionException("Can't commit a non-existing write transaction"); + } + + if (auto audit = audit_context()) { + auto prev_version = m_shared_group->pin_version(); + m_coordinator->commit_write(*this); + audit->record_write(prev_version, m_shared_group->get_version_of_current_transaction()); + m_shared_group->unpin_version(prev_version); + } + else { + m_coordinator->commit_write(*this); + } + cache_new_schema(); + invalidate_permission_cache(); +} + +void Realm::cancel_transaction() +{ + check_write(this); + verify_thread(); + + if (!is_in_transaction()) { + throw InvalidTransactionException("Can't cancel a non-existing write transaction"); + } + + transaction::cancel(*m_shared_group, m_binding_context.get()); + invalidate_permission_cache(); +} + +void Realm::invalidate() +{ + verify_open(); + verify_thread(); + check_read_write(this); + + if (m_is_sending_notifications) { + return; + } + + if (is_in_transaction()) { + cancel_transaction(); + } + if (!m_group) { + return; + } + + m_permissions_cache = nullptr; + m_table_info_cache = nullptr; + m_shared_group->end_read(); + m_group = nullptr; +} + +bool Realm::compact() +{ + verify_thread(); + + if (m_config.immutable() || m_config.read_only_alternative()) { + throw InvalidTransactionException("Can't compact a read-only Realm"); + } + if (is_in_transaction()) { + throw InvalidTransactionException("Can't compact a Realm within a write transaction"); + } + + verify_open(); + // FIXME: when enum columns are ready, optimise all tables in a write transaction + if (m_group) { + m_shared_group->end_read(); + } + m_group = nullptr; + + return m_shared_group->compact(); +} + +void Realm::write_copy(StringData path, BinaryData key) +{ + if (key.data() && key.size() != 64) { + throw InvalidEncryptionKeyException(); + } + verify_thread(); + try { + read_group().write(path, key.data()); + } + catch (...) { + translate_file_exception(path); + } +} + +OwnedBinaryData Realm::write_copy() +{ + verify_thread(); + BinaryData buffer = read_group().write_to_mem(); + + // Since OwnedBinaryData does not have a constructor directly taking + // ownership of BinaryData, we have to do this to avoid copying the buffer + return OwnedBinaryData(std::unique_ptr((char*)buffer.data()), buffer.size()); +} + +void Realm::notify() +{ + if (is_closed() || is_in_transaction()) { + return; + } + + verify_thread(); + invalidate_permission_cache(); + + // Any of the callbacks to user code below could drop the last remaining + // strong reference to `this` + auto retain_self = shared_from_this(); + + if (m_binding_context) { + m_binding_context->before_notify(); + if (is_closed() || is_in_transaction()) { + return; + } + } + + auto cleanup = util::make_scope_exit([this]() noexcept { m_is_sending_notifications = false; }); + if (!m_shared_group->has_changed()) { + m_is_sending_notifications = true; + m_coordinator->process_available_async(*this); + return; + } + + if (m_binding_context) { + m_binding_context->changes_available(); + + // changes_available() may have advanced the read version, and if + // so we don't need to do anything further + if (!m_shared_group->has_changed()) + return; + } + + m_is_sending_notifications = true; + if (m_auto_refresh) { + if (m_group) { + try { + m_coordinator->advance_to_ready(*this); + } + catch (_impl::UnsupportedSchemaChange const&) { + translate_schema_error(); + } + cache_new_schema(); + } + else { + if (m_binding_context) { + m_binding_context->did_change({}, {}); + } + if (!is_closed()) { + m_coordinator->process_available_async(*this); + } + } + } +} + +bool Realm::refresh() +{ + verify_thread(); + check_read_write(this); + + // can't be any new changes if we're in a write transaction + if (is_in_transaction()) { + return false; + } + // don't advance if we're already in the process of advancing as that just + // makes things needlessly complicated + if (m_is_sending_notifications) { + return false; + } + invalidate_permission_cache(); + + // Any of the callbacks to user code below could drop the last remaining + // strong reference to `this` + auto retain_self = shared_from_this(); + + m_is_sending_notifications = true; + auto cleanup = util::make_scope_exit([this]() noexcept { m_is_sending_notifications = false; }); + + if (m_binding_context) { + m_binding_context->before_notify(); + } + if (m_group) { + try { + bool version_changed = m_coordinator->advance_to_latest(*this); + cache_new_schema(); + return version_changed; + } + catch (_impl::UnsupportedSchemaChange const&) { + translate_schema_error(); + } + } + + // No current read transaction, so just create a new one + read_group(); + m_coordinator->process_available_async(*this); + return true; +} + +bool Realm::can_deliver_notifications() const noexcept +{ + if (m_config.immutable() || !m_config.automatic_change_notifications) { + return false; + } + + if (m_binding_context && !m_binding_context->can_deliver_notifications()) { + return false; + } + + return true; +} + +uint64_t Realm::get_schema_version(const Realm::Config &config) +{ + auto coordinator = RealmCoordinator::get_existing_coordinator(config.path); + if (coordinator) { + return coordinator->get_schema_version(); + } + + return ObjectStore::get_schema_version(Realm(config, nullptr).read_group()); +} + +void Realm::close() +{ + if (m_coordinator) { + m_coordinator->unregister_realm(this); + } + + m_permissions_cache = nullptr; + m_table_info_cache = nullptr; + m_group = nullptr; + m_shared_group = nullptr; + m_history = nullptr; + m_read_only_group = nullptr; + m_binding_context = nullptr; + m_coordinator = nullptr; +} + +util::Optional Realm::file_format_upgraded_from_version() const +{ + if (upgrade_initial_version != upgrade_final_version) { + return upgrade_initial_version; + } + return util::none; +} + +template +realm::ThreadSafeReference Realm::obtain_thread_safe_reference(T const& value) +{ + verify_thread(); + if (is_in_transaction()) { + throw InvalidTransactionException("Cannot obtain thread safe reference during a write transaction."); + } + return ThreadSafeReference(value); +} + +template ThreadSafeReference Realm::obtain_thread_safe_reference(Object const& value); +template ThreadSafeReference Realm::obtain_thread_safe_reference(List const& value); +template ThreadSafeReference Realm::obtain_thread_safe_reference(Results const& value); + +template +T Realm::resolve_thread_safe_reference(ThreadSafeReference reference) +{ + verify_thread(); + if (is_in_transaction()) { + throw InvalidTransactionException("Cannot resolve thread safe reference during a write transaction."); + } + if (reference.is_invalidated()) { + throw std::logic_error("Cannot resolve thread safe reference more than once."); + } + if (!reference.has_same_config(*this)) { + throw MismatchedRealmException("Cannot resolve thread safe reference in Realm with different configuration " + "than the source Realm."); + } + invalidate_permission_cache(); + + // Any of the callbacks to user code below could drop the last remaining + // strong reference to `this` + auto retain_self = shared_from_this(); + + // Ensure we're on the same version as the reference + if (!m_group) { + // A read transaction doesn't yet exist, so create at the reference's version + begin_read(reference.m_version_id); + } + else { + // A read transaction does exist, but let's make sure that its version matches the reference's + auto current_version = m_shared_group->get_version_of_current_transaction(); + VersionID reference_version(reference.m_version_id); + + if (reference_version == current_version) { + return std::move(reference).import_into_realm(shared_from_this()); + } + + refresh(); + + current_version = m_shared_group->get_version_of_current_transaction(); + + // If the reference's version is behind, advance it to our version + if (reference_version < current_version) { + // Duplicate config for uncached Realm so we don't advance the user's Realm + Realm::Config config = m_coordinator->get_config(); + config.automatic_change_notifications = false; + config.cache = false; + config.schema = util::none; + SharedRealm temporary_realm = m_coordinator->get_realm(config); + temporary_realm->begin_read(reference_version); + + // With reference imported, advance temporary Realm to our version + T imported_value = std::move(reference).import_into_realm(temporary_realm); + transaction::advance(*temporary_realm->m_shared_group, nullptr, current_version); + if (!imported_value.is_valid()) + return T{}; + reference = ThreadSafeReference(imported_value); + } + } + + return std::move(reference).import_into_realm(shared_from_this()); +} + +template Object Realm::resolve_thread_safe_reference(ThreadSafeReference reference); +template List Realm::resolve_thread_safe_reference(ThreadSafeReference reference); +template Results Realm::resolve_thread_safe_reference(ThreadSafeReference reference); + +AuditInterface* Realm::audit_context() const noexcept +{ + return m_coordinator ? m_coordinator->audit_context() : nullptr; +} + +#if REALM_ENABLE_SYNC +static_assert(static_cast(ComputedPrivileges::Read) == static_cast(sync::Privilege::Read), ""); +static_assert(static_cast(ComputedPrivileges::Update) == static_cast(sync::Privilege::Update), ""); +static_assert(static_cast(ComputedPrivileges::Delete) == static_cast(sync::Privilege::Delete), ""); +static_assert(static_cast(ComputedPrivileges::SetPermissions) == static_cast(sync::Privilege::SetPermissions), ""); +static_assert(static_cast(ComputedPrivileges::Query) == static_cast(sync::Privilege::Query), ""); +static_assert(static_cast(ComputedPrivileges::Create) == static_cast(sync::Privilege::Create), ""); +static_assert(static_cast(ComputedPrivileges::ModifySchema) == static_cast(sync::Privilege::ModifySchema), ""); + +static constexpr const uint8_t s_allRealmPrivileges = sync::Privilege::Read + | sync::Privilege::Update + | sync::Privilege::SetPermissions + | sync::Privilege::ModifySchema; +static constexpr const uint8_t s_allClassPrivileges = sync::Privilege::Read + | sync::Privilege::Update + | sync::Privilege::Create + | sync::Privilege::Query + | sync::Privilege::SetPermissions; +static constexpr const uint8_t s_allObjectPrivileges = sync::Privilege::Read + | sync::Privilege::Update + | sync::Privilege::Delete + | sync::Privilege::SetPermissions; + +bool Realm::init_permission_cache() +{ + verify_thread(); + + if (m_permissions_cache) { + // Rather than trying to track changes to permissions tables, just skip the caching + // entirely within write transactions for now + if (is_in_transaction()) + m_permissions_cache->clear(); + return true; + } + + // Admin users bypass permissions checks outside of the logic in PermissionsCache + if (m_config.sync_config && m_config.sync_config->is_partial && !m_config.sync_config->user->is_admin()) { +#if REALM_SYNC_VER_MAJOR == 3 && (REALM_SYNC_VER_MINOR < 13 || (REALM_SYNC_VER_MINOR == 13 && REALM_SYNC_VER_PATCH < 3)) + m_permissions_cache = std::make_unique(read_group(), m_config.sync_config->user->identity()); +#else + m_table_info_cache = std::make_unique(read_group()); + m_permissions_cache = std::make_unique(read_group(), *m_table_info_cache, + m_config.sync_config->user->identity()); +#endif + return true; + } + return false; +} + +void Realm::invalidate_permission_cache() +{ + if (m_permissions_cache) + m_permissions_cache->clear(); +} + +ComputedPrivileges Realm::get_privileges() +{ + if (!init_permission_cache()) + return static_cast(s_allRealmPrivileges); + return static_cast(m_permissions_cache->get_realm_privileges() & s_allRealmPrivileges); +} + +static uint8_t inherited_mask(uint32_t privileges) +{ + uint8_t mask = ~0; + if (!(privileges & sync::Privilege::Read)) + mask = 0; + else if (!(privileges & sync::Privilege::Update)) + mask = static_cast(sync::Privilege::Read | sync::Privilege::Query); + return mask; +} + +ComputedPrivileges Realm::get_privileges(StringData object_type) +{ + if (!init_permission_cache()) + return static_cast(s_allClassPrivileges); + auto privileges = inherited_mask(m_permissions_cache->get_realm_privileges()) + & m_permissions_cache->get_class_privileges(object_type); + return static_cast(privileges & s_allClassPrivileges); +} + +ComputedPrivileges Realm::get_privileges(RowExpr row) +{ + if (!init_permission_cache()) + return static_cast(s_allObjectPrivileges); + + auto& table = *row.get_table(); + auto object_type = ObjectStore::object_type_for_table_name(table.get_name()); + sync::GlobalID global_id{object_type, sync::object_id_for_row(read_group(), table, row.get_index())}; + auto privileges = inherited_mask(m_permissions_cache->get_realm_privileges()) + & inherited_mask(m_permissions_cache->get_class_privileges(object_type)) + & m_permissions_cache->get_object_privileges(global_id); + return static_cast(privileges & s_allObjectPrivileges); +} +#else +void Realm::invalidate_permission_cache() { } +#endif + +MismatchedConfigException::MismatchedConfigException(StringData message, StringData path) +: std::logic_error(util::format(message.data(), path)) { } + +MismatchedRealmException::MismatchedRealmException(StringData message) +: std::logic_error(message.data()) { } + +// FIXME Those are exposed for Java async queries, mainly because of handover related methods. +SharedGroup& RealmFriend::get_shared_group(Realm& realm) +{ + return *realm.m_shared_group; +} + +Group& RealmFriend::read_group_to(Realm& realm, VersionID version) +{ + if (realm.m_group && realm.m_shared_group->get_version_of_current_transaction() == version) + return *realm.m_group; + + if (realm.m_group) + realm.m_shared_group->end_read(); + realm.begin_read(version); + return *realm.m_group; +} diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/sync/async_open_task.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/sync/async_open_task.cpp new file mode 100644 index 0000000..3189cd5 --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/sync/async_open_task.cpp @@ -0,0 +1,89 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2019 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "sync/async_open_task.hpp" + +#include "impl/realm_coordinator.hpp" +#include "sync/sync_manager.hpp" +#include "sync/sync_session.hpp" +#include "thread_safe_reference.hpp" + +namespace realm { + +AsyncOpenTask::AsyncOpenTask(std::shared_ptr<_impl::RealmCoordinator> coordinator, std::shared_ptr session) +: m_coordinator(coordinator) +, m_session(session) +{ +} + +void AsyncOpenTask::start(std::function, std::exception_ptr)> callback) +{ + auto session = m_session.load(); + if (!session) + return; + + std::shared_ptr self(shared_from_this()); + session->wait_for_download_completion([callback, self, this](std::error_code ec) { + auto session = m_session.exchange(nullptr); + if (!session) + return; // Swallow all events if the task as been canceled. + + // Release our references to the coordinator after calling the callback + auto coordinator = std::move(m_coordinator); + m_coordinator = nullptr; + + if (ec) + return callback({}, std::make_exception_ptr(std::system_error(ec))); + + ThreadSafeReference realm; + try { + realm = coordinator->get_unbound_realm(); + } + catch (...) { + return callback({}, std::current_exception()); + } + callback(std::move(realm), nullptr); + }); +} + +void AsyncOpenTask::cancel() +{ + if (auto session = m_session.exchange(nullptr)) { + // Does a better way exists for canceling the download? + session->log_out(); + m_coordinator = nullptr; + } +} + +uint64_t AsyncOpenTask::register_download_progress_notifier(std::function callback) +{ + if (auto session = m_session.load()) { + return session->register_progress_notifier(callback, realm::SyncSession::NotifierType::download, false); + } + else { + return 0; + } +} + +void AsyncOpenTask::unregister_download_progress_notifier(uint64_t token) +{ + if (auto session = m_session.load()) + session->unregister_progress_notifier(token); +} + +} diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/sync/impl/apple/network_reachability_observer.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/sync/impl/apple/network_reachability_observer.cpp new file mode 100644 index 0000000..ad040a4 --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/sync/impl/apple/network_reachability_observer.cpp @@ -0,0 +1,127 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "sync/impl/apple/network_reachability_observer.hpp" + +#if NETWORK_REACHABILITY_AVAILABLE + +using namespace realm; +using namespace realm::_impl; + +namespace { + +NetworkReachabilityStatus reachability_status_for_flags(SCNetworkReachabilityFlags flags) +{ + if (!(flags & kSCNetworkReachabilityFlagsReachable)) + return NotReachable; + + if (flags & kSCNetworkReachabilityFlagsConnectionRequired) { + if (!(flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) || + (flags & kSCNetworkReachabilityFlagsInterventionRequired)) + return NotReachable; + } + + NetworkReachabilityStatus status = ReachableViaWiFi; + +#if TARGET_OS_IPHONE + if (flags & kSCNetworkReachabilityFlagsIsWWAN) + status = ReachableViaWWAN; +#endif + + return status; +} + +} // (anonymous namespace) + +NetworkReachabilityObserver::NetworkReachabilityObserver(util::Optional hostname, + std::function handler) +: m_callback_queue(dispatch_queue_create("io.realm.sync.reachability", DISPATCH_QUEUE_SERIAL)) +, m_change_handler(std::move(handler)) +{ + if (hostname) { + m_reachability_ref = util::adoptCF(SystemConfiguration::shared().network_reachability_create_with_name(nullptr, + hostname->c_str())); + } else { + struct sockaddr zeroAddress = {}; + zeroAddress.sa_len = sizeof(zeroAddress); + zeroAddress.sa_family = AF_INET; + + m_reachability_ref = util::adoptCF(SystemConfiguration::shared().network_reachability_create_with_address(nullptr, + &zeroAddress)); + } +} + +NetworkReachabilityObserver::~NetworkReachabilityObserver() +{ + stop_observing(); + dispatch_release(m_callback_queue); +} + +NetworkReachabilityStatus NetworkReachabilityObserver::reachability_status() const +{ + SCNetworkReachabilityFlags flags; + + if (SystemConfiguration::shared().network_reachability_get_flags(m_reachability_ref.get(), &flags)) + return reachability_status_for_flags(flags); + + return NotReachable; +} + +bool NetworkReachabilityObserver::start_observing() +{ + m_previous_status = reachability_status(); + + auto callback = [](SCNetworkReachabilityRef, SCNetworkReachabilityFlags, void* self) { + static_cast(self)->reachability_changed(); + }; + + SCNetworkReachabilityContext context = {0, this, nullptr, nullptr, nullptr}; + + if (!SystemConfiguration::shared().network_reachability_set_callback(m_reachability_ref.get(), callback, &context)) + return false; + + if (!SystemConfiguration::shared().network_reachability_set_dispatch_queue(m_reachability_ref.get(), m_callback_queue)) + return false; + + return true; +} + +void NetworkReachabilityObserver::stop_observing() +{ + SystemConfiguration::shared().network_reachability_set_dispatch_queue(m_reachability_ref.get(), nullptr); + SystemConfiguration::shared().network_reachability_set_callback(m_reachability_ref.get(), nullptr, nullptr); + + // Wait for all previously-enqueued blocks to execute to guarantee that + // no callback will be called after returning from this method + dispatch_sync(m_callback_queue, ^{}); +} + +void NetworkReachabilityObserver::reachability_changed() +{ + auto current_status = reachability_status(); + + // When observing reachability of the specific host the callback might be called + // several times (because of DNS queries) with the same reachability flags while + // the caller should be notified only when the reachability status is really changed. + if (current_status != m_previous_status) { + m_change_handler(current_status); + m_previous_status = current_status; + } +} + +#endif diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/sync/impl/apple/system_configuration.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/sync/impl/apple/system_configuration.cpp new file mode 100644 index 0000000..6c4c127 --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/sync/impl/apple/system_configuration.cpp @@ -0,0 +1,98 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2017 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "sync/impl/apple/system_configuration.hpp" + +#if NETWORK_REACHABILITY_AVAILABLE + +#include +#include "dlfcn.h" + +using namespace realm; +using namespace realm::_impl; + +SystemConfiguration::SystemConfiguration() +{ + m_framework_handle = dlopen("/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration", RTLD_LAZY); + + if (m_framework_handle) { + m_create_with_name = (create_with_name_t)dlsym(m_framework_handle, "SCNetworkReachabilityCreateWithName"); + m_create_with_address = (create_with_address_t)dlsym(m_framework_handle, "SCNetworkReachabilityCreateWithAddress"); + m_set_dispatch_queue = (set_dispatch_queue_t)dlsym(m_framework_handle, "SCNetworkReachabilitySetDispatchQueue"); + m_set_callback = (set_callback_t)dlsym(m_framework_handle, "SCNetworkReachabilitySetCallback"); + m_get_flags = (get_flags_t)dlsym(m_framework_handle, "SCNetworkReachabilityGetFlags"); + } else { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + asl_log(nullptr, nullptr, ASL_LEVEL_WARNING, "network reachability is not available"); +#pragma clang diagnostic pop + } +} + +SystemConfiguration& SystemConfiguration::shared() +{ + static SystemConfiguration system_configuration; + + return system_configuration; +} + +SCNetworkReachabilityRef SystemConfiguration::network_reachability_create_with_name(CFAllocatorRef allocator, + const char *hostname) +{ + if (m_create_with_name) + return m_create_with_name(allocator, hostname); + + return nullptr; +} + +SCNetworkReachabilityRef SystemConfiguration::network_reachability_create_with_address(CFAllocatorRef allocator, + const sockaddr *address) +{ + if (m_create_with_address) + return m_create_with_address(allocator, address); + + return nullptr; +} + +bool SystemConfiguration::network_reachability_set_dispatch_queue(SCNetworkReachabilityRef target, dispatch_queue_t queue) +{ + if (m_set_dispatch_queue) + return m_set_dispatch_queue(target, queue); + + return false; +} + +bool SystemConfiguration::network_reachability_set_callback(SCNetworkReachabilityRef target, + SCNetworkReachabilityCallBack callback, + SCNetworkReachabilityContext *context) +{ + if (m_set_callback) + return m_set_callback(target, callback, context); + + return false; +} + +bool SystemConfiguration::network_reachability_get_flags(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags *flags) +{ + if (m_get_flags) + return m_get_flags(target, flags); + + return false; +} + +#endif // NETWORK_REACHABILITY_AVAILABLE diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/sync/impl/sync_file.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/sync/impl/sync_file.cpp new file mode 100644 index 0000000..8022847 --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/sync/impl/sync_file.cpp @@ -0,0 +1,358 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "sync/impl/sync_file.hpp" + +#include +#include +#include + +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include + +inline static int mkstemp(char* _template) { return _open(_mktemp(_template), _O_CREAT | _O_TEMPORARY, _S_IREAD | _S_IWRITE); } +#else +#include +#endif + + +using File = realm::util::File; + +namespace realm { + +namespace { + +uint8_t value_of_hex_digit(char hex_digit) +{ + if (hex_digit >= '0' && hex_digit <= '9') { + return hex_digit - '0'; + } else if (hex_digit >= 'A' && hex_digit <= 'F') { + return 10 + hex_digit - 'A'; + } else if (hex_digit >= 'a' && hex_digit <= 'f') { + return 10 + hex_digit - 'a'; + } else { + throw std::invalid_argument("Cannot get the value of a character that isn't a hex digit."); + } +} + +bool filename_is_reserved(const std::string& filename) { + return (filename == "." || filename == ".."); +} + +bool character_is_unreserved(char character) +{ + bool is_capital_letter = (character >= 'A' && character <= 'Z'); + bool is_lowercase_letter = (character >= 'a' && character <= 'z'); + bool is_number = (character >= '0' && character <= '9'); + bool is_allowed_symbol = (character == '-' || character == '_' || character == '.'); + return is_capital_letter || is_lowercase_letter || is_number || is_allowed_symbol; +} + +char decoded_char_for(const std::string& percent_encoding, size_t index) +{ + if (index+2 >= percent_encoding.length()) { + throw std::invalid_argument("Malformed string: not enough characters after '%' before end of string."); + } + REALM_ASSERT(percent_encoding[index] == '%'); + return (16*value_of_hex_digit(percent_encoding[index + 1])) + value_of_hex_digit(percent_encoding[index + 2]); +} + +} // (anonymous namespace) + +namespace util { + +std::string make_percent_encoded_string(const std::string& raw_string) +{ + std::string buffer; + buffer.reserve(raw_string.size()); + for (size_t i=0; i 0); + std::string escaped = util::make_percent_encoded_string(local_identity); + if (filename_is_reserved(escaped)) + throw std::invalid_argument("A user can't have an identifier reserved by the filesystem."); + + auto user_path = file_path_by_appending_component(get_base_sync_directory(), + escaped, + util::FilePathType::Directory); + util::try_make_dir(user_path); + return user_path; +} + +void SyncFileManager::remove_user_directory(const std::string& local_identity) const +{ + REALM_ASSERT(local_identity.length() > 0); + const auto& escaped = util::make_percent_encoded_string(local_identity); + if (filename_is_reserved(escaped)) + throw std::invalid_argument("A user can't have an identifier reserved by the filesystem."); + + auto user_path = file_path_by_appending_component(get_base_sync_directory(), + escaped, + util::FilePathType::Directory); + util::try_remove_dir_recursive(user_path); +} + +bool SyncFileManager::try_rename_user_directory(const std::string& old_name, const std::string& new_name) const +{ + REALM_ASSERT_DEBUG(old_name.length() > 0 && new_name.length() > 0); + const auto& old_name_escaped = util::make_percent_encoded_string(old_name); + const auto& new_name_escaped = util::make_percent_encoded_string(new_name); + const std::string& base = get_base_sync_directory(); + if (filename_is_reserved(old_name_escaped) || filename_is_reserved(new_name_escaped)) + throw std::invalid_argument("A user directory can't be renamed using a reserved identifier."); + + const auto& old_path = file_path_by_appending_component(base, old_name_escaped, util::FilePathType::Directory); + const auto& new_path = file_path_by_appending_component(base, new_name_escaped, util::FilePathType::Directory); + + try { + File::move(old_path, new_path); + } catch (File::NotFound const&) { + return false; + } + return true; +} + +bool SyncFileManager::remove_realm(const std::string& absolute_path) const +{ + REALM_ASSERT(absolute_path.length() > 0); + bool success = true; + // Remove the Realm file (e.g. "example.realm"). + success = File::try_remove(absolute_path); + // Remove the lock file (e.g. "example.realm.lock"). + auto lock_path = util::file_path_by_appending_extension(absolute_path, "lock"); + success = File::try_remove(lock_path); + // Remove the management directory (e.g. "example.realm.management"). + auto management_path = util::file_path_by_appending_extension(absolute_path, "management"); + try { + util::try_remove_dir_recursive(management_path); + } + catch (File::AccessError const&) { + success = false; + } + return success; +} + +bool SyncFileManager::copy_realm_file(const std::string& old_path, const std::string& new_path) const +{ + REALM_ASSERT(old_path.length() > 0); + try { + if (File::exists(new_path)) { + return false; + } + File::copy(old_path, new_path); + } + catch (File::NotFound const&) { + return false; + } + catch (File::AccessError const&) { + return false; + } + return true; +} + +bool SyncFileManager::remove_realm(const std::string& local_identity, const std::string& raw_realm_path) const +{ + REALM_ASSERT(local_identity.length() > 0); + REALM_ASSERT(raw_realm_path.length() > 0); + if (filename_is_reserved(local_identity) || filename_is_reserved(raw_realm_path)) + throw std::invalid_argument("A user or Realm can't have an identifier reserved by the filesystem."); + + auto escaped = util::make_percent_encoded_string(raw_realm_path); + auto realm_path = util::file_path_by_appending_component(user_directory(local_identity), escaped); + return remove_realm(realm_path); +} + +std::string SyncFileManager::path(const std::string& local_identity, const std::string& raw_realm_path) const +{ + REALM_ASSERT(local_identity.length() > 0); + REALM_ASSERT(raw_realm_path.length() > 0); + if (filename_is_reserved(local_identity) || filename_is_reserved(raw_realm_path)) + throw std::invalid_argument("A user or Realm can't have an identifier reserved by the filesystem."); + + auto escaped = util::make_percent_encoded_string(raw_realm_path); + return util::file_path_by_appending_component(user_directory(local_identity), escaped); +} + +std::string SyncFileManager::metadata_path() const +{ + auto dir_path = file_path_by_appending_component(get_utility_directory(), + c_metadata_directory, + util::FilePathType::Directory); + util::try_make_dir(dir_path); + return util::file_path_by_appending_component(dir_path, c_metadata_realm); +} + +bool SyncFileManager::remove_metadata_realm() const +{ + auto dir_path = file_path_by_appending_component(get_utility_directory(), + c_metadata_directory, + util::FilePathType::Directory); + try { + util::try_remove_dir_recursive(dir_path); + return true; + } + catch (File::AccessError const&) { + return false; + } +} + +} // realm diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/sync/impl/sync_metadata.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/sync/impl/sync_metadata.cpp new file mode 100644 index 0000000..99dbee4 --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/sync/impl/sync_metadata.cpp @@ -0,0 +1,466 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "sync/impl/sync_metadata.hpp" + +#include "object_schema.hpp" +#include "object_store.hpp" +#include "property.hpp" +#include "results.hpp" +#include "schema.hpp" +#include "util/uuid.hpp" +#if REALM_PLATFORM_APPLE +#include "impl/apple/keychain_helper.hpp" +#endif + +#include +#include + +namespace { +static const char * const c_sync_userMetadata = "UserMetadata"; +static const char * const c_sync_marked_for_removal = "marked_for_removal"; +static const char * const c_sync_identity = "identity"; +static const char * const c_sync_local_uuid = "local_uuid"; +static const char * const c_sync_auth_server_url = "auth_server_url"; +static const char * const c_sync_user_token = "user_token"; +static const char * const c_sync_user_is_admin = "user_is_admin"; + +static const char * const c_sync_fileActionMetadata = "FileActionMetadata"; +static const char * const c_sync_original_name = "original_name"; +static const char * const c_sync_new_name = "new_name"; +static const char * const c_sync_action = "action"; +static const char * const c_sync_url = "url"; + +static const char * const c_sync_clientMetadata = "ClientMetadata"; +static const char * const c_sync_uuid = "uuid"; + +realm::Schema make_schema() +{ + using namespace realm; + return Schema{ + {c_sync_userMetadata, { + {c_sync_identity, PropertyType::String}, + {c_sync_local_uuid, PropertyType::String}, + {c_sync_marked_for_removal, PropertyType::Bool}, + {c_sync_user_token, PropertyType::String|PropertyType::Nullable}, + {c_sync_auth_server_url, PropertyType::String}, + {c_sync_user_is_admin, PropertyType::Bool}, + }}, + {c_sync_fileActionMetadata, { + {c_sync_original_name, PropertyType::String, Property::IsPrimary{true}}, + {c_sync_new_name, PropertyType::String|PropertyType::Nullable}, + {c_sync_action, PropertyType::Int}, + {c_sync_url, PropertyType::String}, + {c_sync_identity, PropertyType::String}, + }}, + {c_sync_clientMetadata, { + {c_sync_uuid, PropertyType::String}, + }} + }; +} + +} // anonymous namespace + +namespace realm { + +// MARK: - Sync metadata manager + +SyncMetadataManager::SyncMetadataManager(std::string path, + bool should_encrypt, + util::Optional> encryption_key) +{ + constexpr uint64_t SCHEMA_VERSION = 2; + + Realm::Config config; + config.automatic_change_notifications = false; + config.path = path; + config.schema = make_schema(); + config.schema_version = SCHEMA_VERSION; + config.schema_mode = SchemaMode::Automatic; +#if REALM_PLATFORM_APPLE + if (should_encrypt && !encryption_key) { + encryption_key = keychain::metadata_realm_encryption_key(File::exists(path)); + } +#endif + if (should_encrypt) { + if (!encryption_key) { + throw std::invalid_argument("Metadata Realm encryption was specified, but no encryption key was provided."); + } + config.encryption_key = std::move(*encryption_key); + } + + config.migration_function = [](SharedRealm old_realm, SharedRealm realm, Schema&) { + if (old_realm->schema_version() < 2) { + TableRef old_table = ObjectStore::table_for_object_type(old_realm->read_group(), c_sync_userMetadata); + TableRef table = ObjectStore::table_for_object_type(realm->read_group(), c_sync_userMetadata); + + // Get all the SyncUserMetadata objects. + Results results(old_realm, *old_table); + + // Column indices. + size_t old_idx_identity = old_table->get_column_index(c_sync_identity); + size_t old_idx_url = old_table->get_column_index(c_sync_auth_server_url); + size_t idx_local_uuid = table->get_column_index(c_sync_local_uuid); + size_t idx_url = table->get_column_index(c_sync_auth_server_url); + + for (size_t i = 0; i < results.size(); i++) { + RowExpr entry = results.get(i); + // Set the UUID equal to the user identity for existing users. + auto identity = entry.get_string(old_idx_identity); + table->set_string(idx_local_uuid, entry.get_index(), identity); + // Migrate the auth server URLs to a non-nullable property. + auto url = entry.get_string(old_idx_url); + table->set_string(idx_url, entry.get_index(), url.is_null() ? "" : url); + } + } + }; + + SharedRealm realm = Realm::get_shared_realm(config); + + // Get data about the (hardcoded) schemas + auto object_schema = realm->schema().find(c_sync_userMetadata); + m_user_schema = { + object_schema->persisted_properties[0].table_column, + object_schema->persisted_properties[1].table_column, + object_schema->persisted_properties[2].table_column, + object_schema->persisted_properties[3].table_column, + object_schema->persisted_properties[4].table_column, + object_schema->persisted_properties[5].table_column, + }; + + object_schema = realm->schema().find(c_sync_fileActionMetadata); + m_file_action_schema = { + object_schema->persisted_properties[0].table_column, + object_schema->persisted_properties[1].table_column, + object_schema->persisted_properties[2].table_column, + object_schema->persisted_properties[3].table_column, + object_schema->persisted_properties[4].table_column, + }; + + object_schema = realm->schema().find(c_sync_clientMetadata); + m_client_schema = { + object_schema->persisted_properties[0].table_column, + }; + + m_metadata_config = std::move(config); + + m_client_uuid = [&]() -> std::string { + TableRef table = ObjectStore::table_for_object_type(realm->read_group(), c_sync_clientMetadata); + if (table->is_empty()) { + realm->begin_transaction(); + if (table->is_empty()) { + size_t idx = table->add_empty_row(); + REALM_ASSERT_DEBUG(idx == 0); + auto uuid = uuid_string(); + table->set_string(m_client_schema.idx_uuid, idx, uuid); + realm->commit_transaction(); + return uuid; + } + realm->cancel_transaction(); + } + return table->get_string(m_client_schema.idx_uuid, 0); + }(); +} + +SyncUserMetadataResults SyncMetadataManager::all_unmarked_users() const +{ + return get_users(false); +} + +SyncUserMetadataResults SyncMetadataManager::all_users_marked_for_removal() const +{ + return get_users(true); +} + +SyncUserMetadataResults SyncMetadataManager::get_users(bool marked) const +{ + auto realm = get_realm(); + TableRef table = ObjectStore::table_for_object_type(realm->read_group(), c_sync_userMetadata); + Query query = table->where().equal(m_user_schema.idx_marked_for_removal, marked); + + Results results(realm, std::move(query)); + return SyncUserMetadataResults(std::move(results), std::move(realm), m_user_schema); +} + +SyncFileActionMetadataResults SyncMetadataManager::all_pending_actions() const +{ + auto realm = get_realm(); + TableRef table = ObjectStore::table_for_object_type(realm->read_group(), c_sync_fileActionMetadata); + Results results(realm, table->where()); + return SyncFileActionMetadataResults(std::move(results), std::move(realm), m_file_action_schema); +} + +util::Optional SyncMetadataManager::get_or_make_user_metadata(const std::string& identity, + const std::string& url, + bool make_if_absent) const +{ + auto realm = get_realm(); + auto& schema = m_user_schema; + + // Retrieve or create the row for this object. + TableRef table = ObjectStore::table_for_object_type(realm->read_group(), c_sync_userMetadata); + Query query = table->where().equal(schema.idx_identity, identity).equal(schema.idx_auth_server_url, url); + Results results(realm, std::move(query)); + REALM_ASSERT_DEBUG(results.size() < 2); + auto row = results.first(); + + if (!row) { + if (!make_if_absent) + return none; + + realm->begin_transaction(); + // Check the results again. + row = results.first(); + if (!row) { + auto row = table->get(table->add_empty_row()); + std::string uuid = util::uuid_string(); + row.set_string(schema.idx_identity, identity); + row.set_string(schema.idx_auth_server_url, url); + row.set_string(schema.idx_local_uuid, uuid); + row.set_bool(schema.idx_user_is_admin, false); + row.set_bool(schema.idx_marked_for_removal, false); + realm->commit_transaction(); + return SyncUserMetadata(schema, std::move(realm), std::move(row)); + } else { + // Someone beat us to adding this user. + if (row->get_bool(schema.idx_marked_for_removal)) { + // User is dead. Revive or return none. + if (make_if_absent) { + row->set_bool(schema.idx_marked_for_removal, false); + realm->commit_transaction(); + } else { + realm->cancel_transaction(); + return none; + } + } else { + // User is alive, nothing else to do. + realm->cancel_transaction(); + } + return SyncUserMetadata(schema, std::move(realm), std::move(*row)); + } + } + + // Got an existing user. + if (row->get_bool(schema.idx_marked_for_removal)) { + // User is dead. Revive or return none. + if (make_if_absent) { + realm->begin_transaction(); + row->set_bool(schema.idx_marked_for_removal, false); + realm->commit_transaction(); + } else { + return none; + } + } + return SyncUserMetadata(schema, std::move(realm), std::move(*row)); +} + +void SyncMetadataManager::make_file_action_metadata(StringData original_name, + StringData url, + StringData local_uuid, + SyncFileActionMetadata::Action action, + StringData new_name) const +{ + // This function can't use get_shared_realm() because it's called on a + // background thread and that's currently not supported by the libuv + // implementation of EventLoopSignal + std::unique_ptr history; + std::unique_ptr shared_group; + std::unique_ptr read_only_group; + Realm::open_with_config(m_metadata_config, history, shared_group, read_only_group, nullptr); + + // Retrieve or create the row for this object. + WriteTransaction wt(*shared_group); + TableRef table = ObjectStore::table_for_object_type(wt.get_group(), c_sync_fileActionMetadata); + + auto& schema = m_file_action_schema; + size_t row_idx = table->find_first_string(schema.idx_original_name, original_name); + if (row_idx == not_found) { + row_idx = table->add_empty_row(); + table->set_string(schema.idx_original_name, row_idx, original_name); + } + table->set_string(schema.idx_new_name, row_idx, new_name); + table->set_int(schema.idx_action, row_idx, static_cast(action)); + table->set_string(schema.idx_url, row_idx, url); + table->set_string(schema.idx_user_identity, row_idx, local_uuid); + wt.commit(); +} + +util::Optional SyncMetadataManager::get_file_action_metadata(StringData original_name) const +{ + auto realm = get_realm(); + auto& schema = m_file_action_schema; + TableRef table = ObjectStore::table_for_object_type(realm->read_group(), c_sync_fileActionMetadata); + size_t row_idx = table->find_first_string(schema.idx_original_name, original_name); + if (row_idx == not_found) + return none; + + return SyncFileActionMetadata(std::move(schema), std::move(realm), table->get(row_idx)); +} + +std::shared_ptr SyncMetadataManager::get_realm() const +{ + auto realm = Realm::get_shared_realm(m_metadata_config); + realm->refresh(); + return realm; +} + +// MARK: - Sync user metadata + +SyncUserMetadata::SyncUserMetadata(Schema schema, SharedRealm realm, RowExpr row) +: m_realm(std::move(realm)) +, m_schema(std::move(schema)) +, m_row(row) +{ } + +std::string SyncUserMetadata::identity() const +{ + REALM_ASSERT(m_realm); + m_realm->verify_thread(); + return m_row.get_string(m_schema.idx_identity); +} + +std::string SyncUserMetadata::local_uuid() const +{ + REALM_ASSERT(m_realm); + m_realm->verify_thread(); + return m_row.get_string(m_schema.idx_local_uuid); +} + +util::Optional SyncUserMetadata::user_token() const +{ + REALM_ASSERT(m_realm); + m_realm->verify_thread(); + StringData result = m_row.get_string(m_schema.idx_user_token); + return result.is_null() ? util::none : util::make_optional(std::string(result)); +} + +std::string SyncUserMetadata::auth_server_url() const +{ + REALM_ASSERT(m_realm); + m_realm->verify_thread(); + return m_row.get_string(m_schema.idx_auth_server_url); +} + +bool SyncUserMetadata::is_admin() const +{ + REALM_ASSERT(m_realm); + m_realm->verify_thread(); + return m_row.get_bool(m_schema.idx_user_is_admin); +} + +void SyncUserMetadata::set_user_token(util::Optional user_token) +{ + if (m_invalid) + return; + + REALM_ASSERT_DEBUG(m_realm); + m_realm->verify_thread(); + m_realm->begin_transaction(); + m_row.set_string(m_schema.idx_user_token, *user_token); + m_realm->commit_transaction(); +} + +void SyncUserMetadata::set_is_admin(bool is_admin) +{ + if (m_invalid) + return; + + REALM_ASSERT_DEBUG(m_realm); + m_realm->verify_thread(); + m_realm->begin_transaction(); + m_row.set_bool(m_schema.idx_user_is_admin, is_admin); + m_realm->commit_transaction(); +} + +void SyncUserMetadata::mark_for_removal() +{ + if (m_invalid) + return; + + m_realm->verify_thread(); + m_realm->begin_transaction(); + m_row.set_bool(m_schema.idx_marked_for_removal, true); + m_realm->commit_transaction(); +} + +void SyncUserMetadata::remove() +{ + m_invalid = true; + m_realm->begin_transaction(); + TableRef table = ObjectStore::table_for_object_type(m_realm->read_group(), c_sync_userMetadata); + table->move_last_over(m_row.get_index()); + m_realm->commit_transaction(); + m_realm = nullptr; +} + +// MARK: - File action metadata + +SyncFileActionMetadata::SyncFileActionMetadata(Schema schema, SharedRealm realm, RowExpr row) +: m_realm(std::move(realm)) +, m_schema(std::move(schema)) +, m_row(row) +{ } + +std::string SyncFileActionMetadata::original_name() const +{ + REALM_ASSERT(m_realm); + m_realm->verify_thread(); + return m_row.get_string(m_schema.idx_original_name); +} + +util::Optional SyncFileActionMetadata::new_name() const +{ + REALM_ASSERT(m_realm); + m_realm->verify_thread(); + StringData result = m_row.get_string(m_schema.idx_new_name); + return result.is_null() ? util::none : util::make_optional(std::string(result)); +} + +std::string SyncFileActionMetadata::user_local_uuid() const +{ + REALM_ASSERT(m_realm); + m_realm->verify_thread(); + return m_row.get_string(m_schema.idx_user_identity); +} + +SyncFileActionMetadata::Action SyncFileActionMetadata::action() const +{ + REALM_ASSERT(m_realm); + m_realm->verify_thread(); + return static_cast(m_row.get_int(m_schema.idx_action)); +} + +std::string SyncFileActionMetadata::url() const +{ + REALM_ASSERT(m_realm); + m_realm->verify_thread(); + return m_row.get_string(m_schema.idx_url); +} + +void SyncFileActionMetadata::remove() +{ + REALM_ASSERT(m_realm); + m_realm->verify_thread(); + m_realm->begin_transaction(); + TableRef table = ObjectStore::table_for_object_type(m_realm->read_group(), c_sync_fileActionMetadata); + table->move_last_over(m_row.get_index()); + m_realm->commit_transaction(); + m_realm = nullptr; +} + +} // namespace realm diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/sync/impl/work_queue.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/sync/impl/work_queue.cpp new file mode 100644 index 0000000..b11b397 --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/sync/impl/work_queue.cpp @@ -0,0 +1,80 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2018 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "sync/impl/work_queue.hpp" + +#include + +namespace realm { +namespace _impl { +namespace partial_sync { + +WorkQueue::~WorkQueue() +{ + { + std::unique_lock lock(m_mutex); + m_stopping = true; + } + m_cv.notify_one(); + + if (m_thread.joinable()) + m_thread.join(); +} + +void WorkQueue::enqueue(std::function function) +{ + { + std::unique_lock lock(m_mutex); + m_queue.push_back(std::move(function)); + + if (m_stopped) + create_thread(); + } + m_cv.notify_one(); +} + +void WorkQueue::create_thread() +{ + if (m_thread.joinable()) + m_thread.join(); + + m_thread = std::thread([this] { + std::vector> queue; + + std::unique_lock lock(m_mutex); + while (!m_stopping && + m_cv.wait_for(lock, std::chrono::milliseconds(500), + [&] { return !m_queue.empty() || m_stopping; })) { + + swap(queue, m_queue); + + lock.unlock(); + for (auto& f : queue) + f(); + queue.clear(); + lock.lock(); + } + + m_stopped = true; + }); + m_stopped = false; +} + +} // namespace partial_sync +} // namespace _impl +} // namespace realm diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/sync/partial_sync.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/sync/partial_sync.cpp new file mode 100644 index 0000000..1b104c6 --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/sync/partial_sync.cpp @@ -0,0 +1,877 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2017 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "sync/partial_sync.hpp" + +#include "impl/collection_notifier.hpp" +#include "impl/notification_wrapper.hpp" +#include "impl/object_accessor_impl.hpp" +#include "impl/realm_coordinator.hpp" +#include "object_schema.hpp" +#include "results.hpp" +#include "shared_realm.hpp" +#include "sync/impl/work_queue.hpp" +#include "sync/subscription_state.hpp" +#include "sync/sync_config.hpp" +#include "sync/sync_session.hpp" + +#include +#include + +#include +#include + +using namespace std::chrono; + +namespace { + + // Delete all subscriptions that are no longer relevant. + // This method must be called within a write transaction. + void cleanup_subscriptions(realm::Group& group, realm::Timestamp now) + { + // Remove all subscriptions no longer considered "active" + // "inactive" is currently defined as any subscription with an `expires_at` < now()` + // + // Note, that we do not check if someone is actively using the subscription right now (this + // is also hard to get right). This does leave some loop holes where a subscription might be + // removed while still in use. E.g. consider a Kiosk app showing a screen 24/7 with a background + // job that accidentially triggers the cleanup codepath. This case is considered rare, but should + // still be documented. + auto table = realm::ObjectStore::table_for_object_type(group, realm::partial_sync::result_sets_type_name); + + size_t expires_at_col_ndx = table->get_column_index(realm::partial_sync::property_expires_at); + realm::TableView results = table->where().less(expires_at_col_ndx, now).find_all(); + results.clear(realm::RemoveMode::unordered); + } + + // Calculates the expiry date, claming at the high end if a timestamp overflows + realm::Timestamp calculate_expiry_date(realm::Timestamp starting_time, int64_t user_ttl_ms) + { + auto tp = starting_time.get_time_point(); + using time_point = decltype(tp); + milliseconds ttl(user_ttl_ms); + if (time_point::max() - ttl < tp) + return time_point::max(); + return tp + ttl; + } + + using namespace ::realm; + using namespace ::realm::partial_sync; + struct ParitalSyncProperty { + const char *name; + DataType type; + bool nullable; + bool indexed; + }; + + static constexpr const ParitalSyncProperty s_partial_sync_schema[] = { + {property_query, type_String, false, false}, + {property_matches_property_name, type_String, false, false}, + {property_status, type_Int, false, false}, + {property_error_message, type_String, false, false}, + {property_query_parse_counter, type_Int, false, false}, + + // Add columns not required by Sync, but used by the bindings to offer better tracking of subscriptions. + // These columns are not automatically added by the server, so we need to add them manually if needed. + + // Name used to uniquely identify a subscription. If a name isn't provided for a subscription one will be + // autogenerated. + {property_name, type_String, false, true}, + + // Timestamp for when then the subscription is created. This should only be set the first time the subscription + // is created. + {property_created_at, type_Timestamp, false, false}, + + // Timestamp for when the subscription is either updated or someone resubscribes to it. + {property_updated_at, type_Timestamp, false, false}, + + // Relative time-to-live in milliseconds. This indicates the period from when a subscription was last updated + // to when it isn't considered valid anymore and can be safely deleted. Realm will attempt to perform this + // cleanup automatically either when the app is started or someone discards the subscription token for it. + {property_time_to_live, type_Int, true, false}, // null = infinite TTL + + // Timestamp representing the fixed point in time when this subscription isn't valid anymore and can + // be safely deleted. This value should be considered read-only from the perspective of any Bindings + // and should never be modified by itself, but only updated whenever the `updatedAt` or `timefield is. + {property_expires_at, type_Timestamp, true, false}, // null = Subscription never expires + }; +} + +namespace realm { + +namespace _impl { +using namespace ::realm::partial_sync; + +void initialize_schema(Group& group) +{ + std::string result_sets_table_name = ObjectStore::table_name_for_object_type(result_sets_type_name); + TableRef table = group.get_table(result_sets_table_name); + if (!table) { + // Create the schema required by Sync + table = sync::create_table(group, result_sets_table_name); + } + + // Create all required properties which don't already exist + for (auto& property : s_partial_sync_schema) { + if (table->get_column_index(property.name) != npos) + continue; + size_t idx = table->add_column(property.type, property.name, property.nullable); + if (property.indexed) + table->add_search_index(idx); + } + + // Remove any subscriptions no longer relevant + cleanup_subscriptions(group, system_clock::now()); +} + +void ensure_partial_sync_schema_initialized(Realm& realm) +{ + auto was_in_read = realm.is_in_read_transaction(); + auto cleanup = util::make_scope_exit([&]() noexcept { + if (realm.is_in_transaction()) + realm.cancel_transaction(); + if (!was_in_read) + realm.invalidate(); + }); + + auto has_all_required_columns = [](auto& table) -> bool { + return std::all_of(std::begin(s_partial_sync_schema), std::end(s_partial_sync_schema), + [&](auto& property) { return table.get_column_index(property.name) != npos; }); + }; + + auto& group = realm.read_group(); + // Check if the result sets table already has the expected number of columns + auto table = ObjectStore::table_for_object_type(group, result_sets_type_name); + if (table && has_all_required_columns(*table)) + return; + + realm.begin_transaction(); + // Recheck after starting the transaction as it refreshes + if (!table) + table = ObjectStore::table_for_object_type(group, result_sets_type_name); + if (table && has_all_required_columns(*table)) + return; + initialize_schema(group); + realm.commit_transaction(); +} + +// A stripped-down version of WriteTransaction that can promote an existing read transaction +// and that notifies the sync session after committing a change. +class WriteTransactionNotifyingSync { +public: + WriteTransactionNotifyingSync(Realm::Config const& config, SharedGroup& sg) + : m_config(config) + , m_shared_group(&sg) + { + if (m_shared_group->get_transact_stage() == SharedGroup::transact_Reading) + LangBindHelper::promote_to_write(*m_shared_group); + else + m_shared_group->begin_write(); + } + + ~WriteTransactionNotifyingSync() + { + if (m_shared_group) + m_shared_group->rollback(); + } + + SharedGroup::version_type commit() + { + REALM_ASSERT(m_shared_group); + auto version = m_shared_group->commit(); + m_shared_group = nullptr; + + auto session = SyncManager::shared().get_session(m_config.path, *m_config.sync_config, false); + SyncSession::Internal::nonsync_transact_notify(*session, version); + return version; + } + + void rollback() + { + REALM_ASSERT(m_shared_group); + m_shared_group->rollback(); + m_shared_group = nullptr; + } + + Group& get_group() const noexcept + { + REALM_ASSERT(m_shared_group); + return _impl::SharedGroupFriend::get_group(*m_shared_group); + } + +private: + Realm::Config const& m_config; + SharedGroup* m_shared_group; +}; + +// Provides a convenient way for code in this file to access private details of `Realm` +// without having to add friend declarations for each individual use. +class PartialSyncHelper { +public: + static decltype(auto) get_shared_group(Realm& realm) + { + return Realm::Internal::get_shared_group(realm); + } + + static decltype(auto) get_coordinator(Realm& realm) + { + return Realm::Internal::get_coordinator(realm); + } +}; + +template +static auto export_for_handover(Realm& realm, Args&&... args) +{ + auto& sg = *PartialSyncHelper::get_shared_group(realm); + sg.pin_version(); + auto handover = sg.export_for_handover(std::forward(args)...); + // We need to store the handover object in a shared_ptr because it's captured + // in a std::function<>, which requires copyable objects + return std::make_shared(std::move(handover)); +} + +template +static auto import_from_handover(SharedGroup& sg, std::unique_ptr>& handover) +{ + sg.begin_read(handover->version); + auto obj = sg.import_from_handover(std::move(handover)); + sg.unpin_version(sg.get_version_of_current_transaction()); + return *obj; +} + +} // namespace _impl + +namespace partial_sync { + +InvalidRealmStateException::InvalidRealmStateException(const std::string& msg) +: std::logic_error(msg) +{} + +ExistingSubscriptionException::ExistingSubscriptionException(const std::string& msg) +: std::runtime_error(msg) +{} + +QueryTypeMismatchException::QueryTypeMismatchException(const std::string& msg) +: std::logic_error(msg) +{} + +namespace { + +template +void with_open_shared_group(Realm::Config const& config, F&& function) +{ + std::unique_ptr history; + std::unique_ptr sg; + std::unique_ptr read_only_group; + Realm::open_with_config(config, history, sg, read_only_group, nullptr); + + function(*sg); +} + +struct ResultSetsColumns { + ResultSetsColumns(Table& table, std::string const& matches_property_name) + { + name = table.get_column_index(property_name); + REALM_ASSERT(name != npos); + + query = table.get_column_index(property_query); + REALM_ASSERT(query != npos); + + error_message = table.get_column_index(property_error_message); + REALM_ASSERT(error_message != npos); + + status = table.get_column_index(property_status); + REALM_ASSERT(status != npos); + + this->matches_property_name = table.get_column_index(property_matches_property_name); + REALM_ASSERT(this->matches_property_name != npos); + + created_at = table.get_column_index(property_created_at); + REALM_ASSERT(created_at != npos); + + updated_at = table.get_column_index(property_updated_at); + REALM_ASSERT(updated_at != npos); + + expires_at = table.get_column_index(property_expires_at); + REALM_ASSERT(expires_at != npos); + + time_to_live = table.get_column_index(property_time_to_live); + REALM_ASSERT(time_to_live != npos); + + // This may be `npos` if the column does not yet exist. + matches_property = table.get_column_index(matches_property_name); + } + + size_t name; + size_t query; + size_t error_message; + size_t status; + size_t matches_property_name; + size_t matches_property; + size_t created_at; + size_t updated_at; + size_t expires_at; + size_t time_to_live; +}; + +// Performs the logic of actually writing the subscription (if needed) to the Realm and making sure +// that the `matches_property` field is setup correctly. This method will throw if the query cannot +// be serialized or the name is already used by another subscription. +// +// The row of the resulting subscription is returned. If an old subscription exists that matches +// the one about to be created, a new subscription is not created, but the old one is returned +// instead. +// +// If `update = true` and if a subscription with `name` already exists, its query and time_to_live +// will be updated instead of an exception being thrown if the query parsed in was different than +// the persisted query. +Row write_subscription(std::string const& object_type, std::string const& name, std::string const& query, + util::Optional time_to_live_ms, bool update, Group& group) +{ + Timestamp now = system_clock::now(); + auto matches_property = std::string(object_type) + "_matches"; + + auto table = ObjectStore::table_for_object_type(group, result_sets_type_name); + ResultSetsColumns columns(*table, matches_property); + + // Update schema if needed. + if (columns.matches_property == npos) { + auto target_table = ObjectStore::table_for_object_type(group, object_type); + columns.matches_property = table->add_column_link(type_LinkList, matches_property, *target_table); + } + else { + // FIXME: Validate that the column type and link target are correct. + } + + // Find existing subscription (if any) + auto row_ndx = table->find_first_string(columns.name, name); + if (row_ndx != npos) { + + // Check that we don't attempt to replace an existing query with a query on a new type. + // There is nothing that prevents Sync from handling this, but allowing it will complicate + // Binding API's, so for now it is disallowed. + auto existing_matching_property = table->get_string(columns.matches_property_name, row_ndx); + if (existing_matching_property != matches_property) { + throw QueryTypeMismatchException(util::format("Replacing an existing query with a query on " + "a different type is not allowed: %1 vs. %2 for %3", + existing_matching_property, matches_property, name)); + } + + // If an subscription exist, we only update the query and TTL if allowed to. + // TODO: Consider how Binding API's are going to use this. It might make sense to disallow + // updating TTL using this API and instead require updates to TTL to go through a managed Subscription. + if (update) { + // If the query changed we must reset state to force the server to re-evaluate the subscription. + if (table->get_string(columns.query, row_ndx) != query) { + table->set_string(columns.error_message, row_ndx, ""); + table->set_int(columns.status, row_ndx, 0); + } + table->set_string(columns.query, row_ndx, query); + table->set(columns.time_to_live, row_ndx, time_to_live_ms); + } + else { + StringData existing_query = table->get_string(columns.query, row_ndx); + if (existing_query != query) + throw ExistingSubscriptionException(util::format("An existing subscription exists with the name '%1' " + "but with a different query: '%1' vs '%2'", + name, existing_query, query)); + } + + } + else { + // No existing subscription was found. Create a new one + row_ndx = sync::create_object(group, *table); + table->set_string(columns.name, row_ndx, name); + table->set_string(columns.query, row_ndx, query); + table->set_string(columns.matches_property_name, row_ndx, matches_property); + table->set_timestamp(columns.created_at, row_ndx, now); + table->set(columns.time_to_live, row_ndx, time_to_live_ms); + } + + // Always set updated_at/expires_at when a subscription is touched, no matter if it is new, updated or someone just + // resubscribes. + table->set_timestamp(columns.updated_at, row_ndx, now); + if (table->is_null(columns.time_to_live, row_ndx) || table->get_int(columns.time_to_live, row_ndx) == std::numeric_limits::max()) { + table->set_null(columns.expires_at, row_ndx); + } + else { + table->set_timestamp(columns.expires_at, row_ndx, calculate_expiry_date(now, table->get_int(columns.time_to_live, row_ndx))); + } + + // Fetch subscription first and return it. Cleanup needs to be performed after as it might delete subscription + // causing the row_ndx to change. + Row subscription = table->get(row_ndx); + cleanup_subscriptions(group, now); + return subscription; +} + +void enqueue_registration(Realm& realm, std::string object_type, std::string query, std::string name, + util::Optional time_to_live, bool update, + std::function callback) +{ + auto config = realm.config(); + + auto& work_queue = _impl::PartialSyncHelper::get_coordinator(realm).partial_sync_work_queue(); + work_queue.enqueue([object_type=std::move(object_type), query=std::move(query), name=std::move(name), + callback=std::move(callback), config=std::move(config), time_to_live=time_to_live, update=update] { + try { + with_open_shared_group(config, [&](SharedGroup& sg) { + _impl::WriteTransactionNotifyingSync write(config, sg); + write_subscription(object_type, name, query, time_to_live, update, write.get_group()); + write.commit(); + }); + } catch (...) { + callback(std::current_exception()); + return; + } + + callback(nullptr); + }); +} + +void enqueue_unregistration(Object result_set, std::function callback) +{ + auto realm = result_set.realm(); + auto config = realm->config(); + auto& work_queue = _impl::PartialSyncHelper::get_coordinator(*realm).partial_sync_work_queue(); + + // Export a reference to the __ResultSets row so we can hand it to the worker thread. + auto handover = _impl::export_for_handover(*realm, Row(result_set.row())); + + work_queue.enqueue([handover=std::move(handover), callback=std::move(callback), + config=std::move(config)] () { + with_open_shared_group(config, [&](SharedGroup& sg) { + Row row = _impl::import_from_handover(sg, *handover); + _impl::WriteTransactionNotifyingSync write(config, sg); + if (row.is_attached()) { + row.move_last_over(); + write.commit(); + } + else { + write.rollback(); + } + }); + callback(); + }); +} + +template +void enqueue_unregistration(Results const& result_set, std::shared_ptr notifier, + std::function callback) +{ + auto realm = result_set.get_realm(); + auto config = realm->config(); + auto& work_queue = _impl::PartialSyncHelper::get_coordinator(*realm).partial_sync_work_queue(); + + // Export a reference to the query which will match the __ResultSets row + // once it's created so we can hand it to the worker thread + Query q = result_set.get_query(); + auto handover = _impl::export_for_handover(*realm, q, MutableSourcePayload::Move); + + work_queue.enqueue([handover=std::move(handover), callback=std::move(callback), + config=std::move(config), notifier=std::move(notifier)] () { + with_open_shared_group(config, [&](SharedGroup& sg) { + Query query = _impl::import_from_handover(sg, *handover); + + // If creating the subscription failed there might be another + // pre-existing subscription which matches our query, so we need to + // not remove that + if (notifier->failed()) + return; + + _impl::WriteTransactionNotifyingSync write(config, sg); + size_t row = query.find(); + if (row != npos) { + query.get_table()->move_last_over(row); + write.commit(); + } + else { + // If unsubscribe() is called twice before the subscription is + // even created the row might already be gone + write.rollback(); + } + }); + callback(); + }); +} + +std::string default_name_for_query(const std::string& query, const std::string& object_type) +{ + return util::format("[%1] %2", object_type, query); +} + +} // unnamed namespace + + +struct Subscription::Notifier : public _impl::CollectionNotifier { + enum State { + Creating, + Complete, + Removed, + }; + + Notifier(std::shared_ptr realm) + : _impl::CollectionNotifier(std::move(realm)) + , m_coordinator(&_impl::PartialSyncHelper::get_coordinator(*get_realm())) + { + } + + void release_data() noexcept override { } + void run() override + { + std::unique_lock lock(m_mutex); + if (m_has_results_to_deliver) { + // Mark the object as being modified so that CollectionNotifier is aware + // that there are changes to deliver. + m_changes.modify(0); + } + } + + void deliver(SharedGroup&) override + { + std::unique_lock lock(m_mutex); + m_error = m_pending_error; + m_pending_error = nullptr; + + m_state = m_pending_state; + m_has_results_to_deliver = false; + } + + void finished_subscribing(std::exception_ptr error) + { + { + std::unique_lock lock(m_mutex); + m_pending_error = error; + m_pending_state = Complete; + m_has_results_to_deliver = true; + m_failed = error != nullptr; + } + + // Trigger processing of change notifications. + m_coordinator->wake_up_notifier_worker(); + } + + void finished_unsubscribing() + { + { + std::unique_lock lock(m_mutex); + + m_pending_state = Removed; + m_has_results_to_deliver = true; + } + + // Trigger processing of change notifications. + m_coordinator->wake_up_notifier_worker(); + } + + std::exception_ptr error() const + { + std::unique_lock lock(m_mutex); + return m_error; + } + + State state() const + { + std::unique_lock lock(m_mutex); + return m_state; + } + + bool failed() const + { + std::unique_lock lock(m_mutex); + return m_failed; + } + +private: + void do_attach_to(SharedGroup&) override { } + void do_detach_from(SharedGroup&) override { } + + void do_prepare_handover(SharedGroup&) override + { + add_changes(std::move(m_changes)); + } + + bool do_add_required_change_info(_impl::TransactionChangeInfo&) override { return false; } + bool prepare_to_deliver() override + { + std::lock_guard lock(m_mutex); + return m_has_results_to_deliver; + + } + + _impl::RealmCoordinator *m_coordinator; + + mutable std::mutex m_mutex; + _impl::CollectionChangeBuilder m_changes; + std::exception_ptr m_pending_error = nullptr; + std::exception_ptr m_error = nullptr; + bool m_has_results_to_deliver = false; + bool m_failed = false; + + State m_state = Creating; + State m_pending_state = Creating; +}; + +Subscription subscribe(Results const& results, SubscriptionOptions options) +{ + auto realm = results.get_realm(); + + auto sync_config = realm->config().sync_config; + if (!sync_config || !sync_config->is_partial) + throw InvalidRealmStateException("A Subscription can only be created in a Query-based Realm."); + + auto query = results.get_query().get_description(); // Throws if the query cannot be serialized. + if (!results.get_descriptor_ordering().is_empty()) { + query += " " + results.get_descriptor_ordering().get_description(results.get_query().get_table()); + } + + if (options.inclusions.is_valid()) { + query += " " + options.inclusions.get_description(results.get_query().get_table()); + } + + std::string name = options.user_provided_name ? std::move(*options.user_provided_name) + : default_name_for_query(query, results.get_object_type()); + + Subscription subscription(name, results.get_object_type(), realm); + std::weak_ptr weak_notifier = subscription.m_notifier; + enqueue_registration(*realm, results.get_object_type(), std::move(query), std::move(name), std::move(options.time_to_live_ms), options.update, + [weak_notifier=std::move(weak_notifier)](std::exception_ptr error) { + if (auto notifier = weak_notifier.lock()) + notifier->finished_subscribing(error); + }); + return subscription; +} + +Row subscribe_blocking(Results const& results, util::Optional user_provided_name, + util::Optional time_to_live_ms, bool update) +{ + + auto realm = results.get_realm(); + if (!realm->is_in_transaction()) { + throw InvalidRealmStateException("The subscription can only be created inside a write transaction."); + } + auto sync_config = realm->config().sync_config; + if (!sync_config || !sync_config->is_partial) { + throw InvalidRealmStateException("A Subscription can only be created in a Query-based Realm."); + } + + auto query = results.get_query().get_description(); // Throws if the query cannot be serialized. + if (!results.get_descriptor_ordering().is_empty()) { + query += " " + results.get_descriptor_ordering().get_description(results.get_query().get_table()); + } + std::string name = user_provided_name ? std::move(*user_provided_name) + : default_name_for_query(query, results.get_object_type()); + return write_subscription(results.get_object_type(), name, query, time_to_live_ms, update, realm->read_group()); +} + +void unsubscribe(Subscription& subscription) +{ + if (auto result_set_object = subscription.result_set_object()) { + // The subscription has its result set object, so we can queue up the unsubscription immediately. + std::weak_ptr weak_notifier = subscription.m_notifier; + enqueue_unregistration(*result_set_object, [weak_notifier=std::move(weak_notifier)]() { + if (auto notifier = weak_notifier.lock()) + notifier->finished_unsubscribing(); + }); + return; + } + + switch (subscription.state()) { + case SubscriptionState::Creating: { + // The result set object is in the process of being created. Try unsubscribing again once it exists. + std::weak_ptr weak_notifier = subscription.m_notifier; + enqueue_unregistration(subscription.m_result_sets, subscription.m_notifier, [weak_notifier=std::move(weak_notifier)]() { + if (auto notifier = weak_notifier.lock()) + notifier->finished_unsubscribing(); + }); + return; + } + + case SubscriptionState::Error: + // We encountered an error when creating the subscription. There's nothing to remove, so just + // mark the subscription as removed. + subscription.m_notifier->finished_unsubscribing(); + break; + + case SubscriptionState::Invalidated: + // Nothing to do. We have already removed the subscription. + break; + + case SubscriptionState::Pending: + case SubscriptionState::Complete: + // This should not be reachable as these states require the result set object to exist. + REALM_ASSERT(false); + break; + } +} + +void unsubscribe(Object&& subscription) +{ + REALM_ASSERT(subscription.get_object_schema().name == result_sets_type_name); + auto realm = subscription.realm(); + enqueue_unregistration(std::move(subscription), [=] { + // The partial sync worker thread bypasses the normal machinery which + // would trigger notifications since it does its own notification things + // in the other cases, so manually trigger it here. + _impl::PartialSyncHelper::get_coordinator(*realm).wake_up_notifier_worker(); + }); +} + +Subscription::Subscription(std::string name, std::string object_type, std::shared_ptr realm) +: m_object_schema(realm->read_group(), result_sets_type_name) +{ + // FIXME: Why can't I do this in the initializer list? + m_notifier = std::make_shared(realm); + _impl::RealmCoordinator::register_notifier(m_notifier); + + auto matches_property = std::string(object_type) + "_matches"; + + m_wrapper_created_at = system_clock::now(); + TableRef table = ObjectStore::table_for_object_type(realm->read_group(), result_sets_type_name); + Query query = table->where(); + query.equal(m_object_schema.property_for_name(property_name)->table_column, name); + query.equal(m_object_schema.property_for_name(property_matches_property_name)->table_column, matches_property); + m_result_sets = Results(std::move(realm), std::move(query)); +} + +Subscription::~Subscription() = default; +Subscription::Subscription(Subscription&&) = default; +Subscription& Subscription::operator=(Subscription&&) = default; + +SubscriptionNotificationToken Subscription::add_notification_callback(std::function callback) +{ + auto callback_wrapper = std::make_shared(SubscriptionCallbackWrapper{callback, none}); + auto result_sets_token = m_result_sets.add_notification_callback([this, callback_wrapper] (CollectionChangeSet, std::exception_ptr) { + run_callback(*callback_wrapper); + }); + NotificationToken registration_token(m_notifier, m_notifier->add_callback([this, callback_wrapper] (CollectionChangeSet, std::exception_ptr) { + run_callback(*callback_wrapper); + })); + + return SubscriptionNotificationToken{std::move(registration_token), std::move(result_sets_token)}; +} + +util::Optional Subscription::result_set_object() const +{ + if (m_notifier->state() == Notifier::Complete) { + if (auto row = m_result_sets.first()) + return Object(m_result_sets.get_realm(), m_object_schema, *row); + } + + return util::none; +} + +void Subscription::run_callback(SubscriptionCallbackWrapper& callback_wrapper) { + // Store reference to underlying subscription object the first time we encounter it. + // Used to track if anyone is later deleting it. + if (!m_result_sets_object && m_result_sets.size() > 0) { + auto row = m_result_sets.first().value(); + m_result_sets_object = util::Optional(row); + } + + // Verify this is a state change we actually want to report to the user + auto new_state = state(); + if (callback_wrapper.last_state && callback_wrapper.last_state.value() == new_state) + return; + + callback_wrapper.last_state = util::Optional(new_state); + + // Finally trigger callback + callback_wrapper.callback(); +} + +SubscriptionState Subscription::state() const +{ + // State transitions are complex due to multiple source being able to create and modify the subscriptions. + // This means that there are unavoidable race conditions with regard to the states and we just make + // a best effort to provide a sensible experience for the end user. + // + // In particular this means the following: + // + // - There is no guarantee that a user will see all the states from `Creating -> Pending -> Complete` + // They might only see `Pending -> Complete` or `Complete` + // + // What we do guarantee is: + // + // - States will never be reported twice in a row for the same callback. This could e.g. happen if some property + // like `updated_at` was updated while the status was still `Pending`, but these properties are not important + // until the subscription is actually created. So we intentionally swallow all duplicated state notifications. + // + // - When calling `subscribe()` with `update = true` we will never report `Complete` until the updated subscription + // reaches that state. + + // Errors take precedence over all other notifications + if (m_notifier->error()) + return SubscriptionState::Error; + + // In some cases the subscription already exists. In that case we just report the state of the __ResultSets object. + if (auto object = result_set_object()) { + CppContext context; + auto state = static_cast(any_cast(object->get_property_value(context, property_status))); + auto updated_at = any_cast(object->get_property_value(context, property_updated_at)); + + if (updated_at < m_wrapper_created_at) { + // If the `updated_at` property on an existing subscription wasn't updated after the wrapper was created, + // it meant the query callback triggered before the async write completed. In that case we don't want + // to return the state associated with the subscription before it was updated. So we override the state + // in the actual subscription and return the expected state after the update. + return partial_sync::SubscriptionState::Pending; + } else { + return state; + } + } + + // If no existing subscription exist, we can use the state of the Notifier as an indication of the underlying + // progress. + switch (m_notifier->state()) { + case Notifier::Creating: + return SubscriptionState::Creating; + case Notifier::Removed: + return SubscriptionState::Invalidated; + case Notifier::Complete: + break; + } + + // If we previously had a reference to the subscription and that is now gone, we interpret that as + // someone deleted the subscription (without using the explict unsubscribe API). + if (m_result_sets_object && !m_result_sets_object->is_attached()) { + return SubscriptionState::Invalidated; + } + + // We may not have an object even if the subscription has completed if the completion callback fired + // but the result sets callback is yet to fire. + return SubscriptionState::Creating; +} + +std::exception_ptr Subscription::error() const +{ + if (auto error = m_notifier->error()) + return error; + + if (auto object = result_set_object()) { + CppContext context; + auto message = any_cast(object->get_property_value(context, "error_message")); + if (message.size()) + return make_exception_ptr(std::runtime_error(message)); + } + + return nullptr; +} + +} // namespace partial_sync +} // namespace realm diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/sync/sync_config.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/sync/sync_config.cpp new file mode 100644 index 0000000..3adea0a --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/sync/sync_config.cpp @@ -0,0 +1,65 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2017 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "sync/sync_config.hpp" + +#include "sync/sync_manager.hpp" + +#include + +namespace realm { + +std::string SyncConfig::partial_sync_identifier(const SyncUser& user) +{ + std::string raw_identifier = SyncManager::shared().client_uuid() + "/" + user.local_identity(); + + // The type of the argument to sha1() changed in sync 3.11.1. Implicitly + // convert to either char or unsigned char so that both signatures work. + struct cast { + uint8_t* value; + operator uint8_t*() { return value; } + operator char*() { return reinterpret_cast(value); } + }; + uint8_t identifier[20]; + sync::crypto::sha1(raw_identifier.data(), raw_identifier.size(), cast{&identifier[0]}); + + std::stringstream ss; + ss << std::hex << std::setfill('0'); + for (uint8_t c : identifier) + ss << std::setw(2) << (unsigned)c; + return ss.str(); +} + +std::string SyncConfig::realm_url() const +{ + REALM_ASSERT(reference_realm_url.length() > 0); + REALM_ASSERT(user); + + if (!is_partial) + return reference_realm_url; + + std::string base_url = reference_realm_url; + if (base_url.back() == '/') + base_url.pop_back(); + + if (custom_partial_sync_identifier) + return util::format("%1/__partial/%2", base_url, *custom_partial_sync_identifier); + return util::format("%1/__partial/%2/%3", base_url, user->identity(), partial_sync_identifier(*user)); +} + +} // namespace realm diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/sync/sync_manager.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/sync/sync_manager.cpp new file mode 100644 index 0000000..757e3b8 --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/sync/sync_manager.cpp @@ -0,0 +1,534 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "sync/sync_manager.hpp" + +#include "sync/impl/sync_client.hpp" +#include "sync/impl/sync_file.hpp" +#include "sync/impl/sync_metadata.hpp" +#include "sync/sync_session.hpp" +#include "sync/sync_user.hpp" + +using namespace realm; +using namespace realm::_impl; + +constexpr const char SyncManager::c_admin_identity[]; + +SyncManager& SyncManager::shared() +{ + // The singleton is heap-allocated in order to fix an issue when running unit tests where tests would crash after + // they were done running because the manager was destroyed too early. + static SyncManager& manager = *new SyncManager; + return manager; +} + +void SyncManager::configure(SyncClientConfig config) +{ + { + std::lock_guard lock(m_mutex); + m_config = std::move(config); + if (m_sync_client) + return; + } + + struct UserCreationData { + std::string identity; + std::string user_token; + std::string server_url; + bool is_admin; + }; + + std::vector users_to_add; + { + std::lock_guard lock(m_file_system_mutex); + + // Set up the file manager. + if (m_file_manager) { + // Changing the base path for tests requires calling reset_for_testing() + // first, and otherwise isn't supported + REALM_ASSERT(m_file_manager->base_path() == m_config.base_file_path); + } else { + m_file_manager = std::make_unique(m_config.base_file_path); + } + + // Set up the metadata manager, and perform initial loading/purging work. + if (m_metadata_manager || m_config.metadata_mode == MetadataMode::NoMetadata) { + return; + } + + bool encrypt = m_config.metadata_mode == MetadataMode::Encryption; + try { + m_metadata_manager = std::make_unique(m_file_manager->metadata_path(), + encrypt, + m_config.custom_encryption_key); + } catch (RealmFileException const& ex) { + if (m_config.reset_metadata_on_error && m_file_manager->remove_metadata_realm()) { + m_metadata_manager = std::make_unique(m_file_manager->metadata_path(), + encrypt, + std::move(m_config.custom_encryption_key)); + } else { + throw; + } + } + + REALM_ASSERT(m_metadata_manager); + m_client_uuid = m_metadata_manager->client_uuid(); + + // Perform our "on next startup" actions such as deleting Realm files + // which we couldn't delete immediately due to them being in use + std::vector completed_actions; + SyncFileActionMetadataResults file_actions = m_metadata_manager->all_pending_actions(); + for (size_t i = 0; i < file_actions.size(); i++) { + auto file_action = file_actions.get(i); + if (run_file_action(file_action)) { + completed_actions.emplace_back(std::move(file_action)); + } + } + for (auto& action : completed_actions) { + action.remove(); + } + + // Load persisted users into the users map. + SyncUserMetadataResults users = m_metadata_manager->all_unmarked_users(); + for (size_t i = 0; i < users.size(); i++) { + // Note that 'admin' style users are not persisted. + auto user_data = users.get(i); + if (auto user_token = user_data.user_token()) { + users_to_add.push_back(UserCreationData{ + user_data.identity(), + std::move(*user_token), + user_data.auth_server_url(), + user_data.is_admin() + }); + } + } + + // Delete any users marked for death. + std::vector dead_users; + SyncUserMetadataResults users_to_remove = m_metadata_manager->all_users_marked_for_removal(); + dead_users.reserve(users_to_remove.size()); + for (size_t i = 0; i < users_to_remove.size(); i++) { + auto user = users_to_remove.get(i); + // FIXME: delete user data in a different way? (This deletes a logged-out user's data as soon as the app + // launches again, which might not be how some apps want to treat their data.) + try { + m_file_manager->remove_user_directory(user.local_uuid()); + dead_users.emplace_back(std::move(user)); + } catch (util::File::AccessError const&) { + continue; + } + } + for (auto& user : dead_users) { + user.remove(); + } + } + { + std::lock_guard lock(m_user_mutex); + for (auto& user_data : users_to_add) { + auto& identity = user_data.identity; + auto& server_url = user_data.server_url; + auto user = std::make_shared(user_data.user_token, identity, server_url); + user->set_is_admin(user_data.is_admin); + m_users.insert({ {identity, server_url}, std::move(user) }); + } + } +} + +bool SyncManager::immediately_run_file_actions(const std::string& realm_path) +{ + if (!m_metadata_manager) { + return false; + } + if (auto metadata = m_metadata_manager->get_file_action_metadata(realm_path)) { + if (run_file_action(*metadata)) { + metadata->remove(); + return true; + } + } + return false; +} + +// Perform a file action. Returns whether or not the file action can be removed. +bool SyncManager::run_file_action(const SyncFileActionMetadata& md) +{ + switch (md.action()) { + case SyncFileActionMetadata::Action::DeleteRealm: + // Delete all the files for the given Realm. + m_file_manager->remove_realm(md.original_name()); + return true; + case SyncFileActionMetadata::Action::BackUpThenDeleteRealm: + // Copy the primary Realm file to the recovery dir, and then delete the Realm. + auto new_name = md.new_name(); + auto original_name = md.original_name(); + if (!util::File::exists(original_name)) { + // The Realm file doesn't exist anymore. + return true; + } + if (new_name && !util::File::exists(*new_name) && m_file_manager->copy_realm_file(original_name, *new_name)) { + // We successfully copied the Realm file to the recovery directory. + m_file_manager->remove_realm(original_name); + return true; + } + return false; + } + return false; +} + +void SyncManager::reset_for_testing() +{ + std::lock_guard lock(m_file_system_mutex); + m_file_manager = nullptr; + m_metadata_manager = nullptr; + m_client_uuid = util::none; + + { + // Destroy all the users. + std::lock_guard lock(m_user_mutex); + m_users.clear(); + m_admin_token_users.clear(); + } + { + std::lock_guard lock(m_mutex); + + // Stop the client. This will abort any uploads that inactive sessions are waiting for. + if (m_sync_client) + m_sync_client->stop(); + + { + std::lock_guard lock(m_session_mutex); + // Callers of `SyncManager::reset_for_testing` should ensure there are no existing sessions + // prior to calling `reset_for_testing`. + bool no_sessions = !do_has_existing_sessions(); + REALM_ASSERT_RELEASE(no_sessions); + + // Destroy any inactive sessions. + // FIXME: We shouldn't have any inactive sessions at this point! Sessions are expected to + // remain inactive until their final upload completes, at which point they are unregistered + // and destroyed. Our call to `sync::Client::stop` above aborts all uploads, so all sessions + // should have already been destroyed. + m_sessions.clear(); + } + + // Destroy the client now that we have no remaining sessions. + m_sync_client = nullptr; + + // Reset even more state. + m_config = {}; + } +} + +void SyncManager::set_log_level(util::Logger::Level level) noexcept +{ + std::lock_guard lock(m_mutex); + m_config.log_level = level; +} + +void SyncManager::set_logger_factory(SyncLoggerFactory& factory) noexcept +{ + std::lock_guard lock(m_mutex); + m_config.logger_factory = &factory; +} + +std::unique_ptr SyncManager::make_logger() const +{ + if (m_config.logger_factory) { + return m_config.logger_factory->make_logger(m_config.log_level); // Throws + } + + auto stderr_logger = std::make_unique(); // Throws + stderr_logger->set_level_threshold(m_config.log_level); + return std::unique_ptr(stderr_logger.release()); +} + +void SyncManager::set_user_agent(std::string user_agent) +{ + std::lock_guard lock(m_mutex); + m_config.user_agent_application_info = std::move(user_agent); +} + +void SyncManager::set_timeouts(SyncClientTimeouts timeouts) +{ + std::lock_guard lock(m_mutex); + m_config.timeouts = timeouts; +} + +void SyncManager::reconnect() +{ + std::lock_guard lock(m_session_mutex); + for (auto& it : m_sessions) { + it.second->handle_reconnect(); + } +} + +util::Logger::Level SyncManager::log_level() const noexcept +{ + std::lock_guard lock(m_mutex); + return m_config.log_level; +} + +bool SyncManager::perform_metadata_update(std::function update_function) const +{ + std::lock_guard lock(m_file_system_mutex); + if (!m_metadata_manager) { + return false; + } + update_function(*m_metadata_manager); + return true; +} + +std::shared_ptr SyncManager::get_user(const SyncUserIdentifier& identifier, std::string refresh_token) +{ + std::lock_guard lock(m_user_mutex); + auto it = m_users.find(identifier); + if (it == m_users.end()) { + // No existing user. + auto new_user = std::make_shared(std::move(refresh_token), + identifier.user_id, + identifier.auth_server_url, + none, + SyncUser::TokenType::Normal); + m_users.insert({ identifier, new_user }); + return new_user; + } else { + auto user = it->second; + if (user->state() == SyncUser::State::Error) { + return nullptr; + } + user->update_refresh_token(std::move(refresh_token)); + return user; + } +} + +std::shared_ptr SyncManager::get_admin_token_user_from_identity(const std::string& identity, + util::Optional server_url, + const std::string& token) +{ + if (server_url) + return get_admin_token_user(*server_url, token, identity); + + std::lock_guard lock(m_user_mutex); + // Look up the user based off the identity. + // No server URL, so no migration possible. + auto it = m_admin_token_users.find(identity); + if (it == m_admin_token_users.end()) { + // No existing user. + auto new_user = std::make_shared(token, + c_admin_identity, + std::move(server_url), + identity, + SyncUser::TokenType::Admin); + m_admin_token_users.insert({ identity, new_user }); + return new_user; + } else { + return it->second; + } +} + +std::shared_ptr SyncManager::get_admin_token_user(const std::string& server_url, + const std::string& token, + util::Optional old_identity) +{ + std::shared_ptr user; + { + std::lock_guard lock(m_user_mutex); + // Look up the user based off the server URL. + auto it = m_admin_token_users.find(server_url); + if (it != m_admin_token_users.end()) + return it->second; + + // No existing user. + user = std::make_shared(token, + c_admin_identity, + server_url, + c_admin_identity + server_url, + SyncUser::TokenType::Admin); + m_admin_token_users.insert({ server_url, user }); + } + if (old_identity) { + // Try renaming the user's directory to use our new naming standard, if applicable. + std::lock_guard fm_lock(m_file_system_mutex); + if (m_file_manager) + m_file_manager->try_rename_user_directory(*old_identity, c_admin_identity + server_url); + } + return user; +} + +std::vector> SyncManager::all_logged_in_users() const +{ + std::lock_guard lock(m_user_mutex); + std::vector> users; + users.reserve(m_users.size() + m_admin_token_users.size()); + for (auto& it : m_users) { + auto user = it.second; + if (user->state() == SyncUser::State::Active) { + users.emplace_back(std::move(user)); + } + } + for (auto& it : m_admin_token_users) { + users.emplace_back(std::move(it.second)); + } + return users; +} + +std::shared_ptr SyncManager::get_current_user() const +{ + std::lock_guard lock(m_user_mutex); + + auto is_active_user = [](auto& el) { return el.second->state() == SyncUser::State::Active; }; + auto it = std::find_if(m_users.begin(), m_users.end(), is_active_user); + if (it == m_users.end()) + return nullptr; + + if (std::find_if(std::next(it), m_users.end(), is_active_user) != m_users.end()) + throw std::logic_error("Current user is not valid if more that one valid, logged-in user exists."); + + return it->second; +} + +std::shared_ptr SyncManager::get_existing_logged_in_user(const SyncUserIdentifier& identifier) const +{ + std::lock_guard lock(m_user_mutex); + auto it = m_users.find(identifier); + if (it == m_users.end()) + return nullptr; + + auto user = it->second; + return user->state() == SyncUser::State::Active ? user : nullptr; +} + +std::string SyncManager::path_for_realm(const SyncUser& user, const std::string& raw_realm_url) const +{ + std::lock_guard lock(m_file_system_mutex); + REALM_ASSERT(m_file_manager); + return m_file_manager->path(user.local_identity(), raw_realm_url); +} + +std::string SyncManager::recovery_directory_path(util::Optional const& custom_dir_name) const +{ + std::lock_guard lock(m_file_system_mutex); + REALM_ASSERT(m_file_manager); + return m_file_manager->recovery_directory_path(custom_dir_name); +} + +std::shared_ptr SyncManager::get_existing_active_session(const std::string& path) const +{ + std::lock_guard lock(m_session_mutex); + if (auto session = get_existing_session_locked(path)) { + if (auto external_reference = session->existing_external_reference()) + return external_reference; + } + return nullptr; +} + +std::shared_ptr SyncManager::get_existing_session_locked(const std::string& path) const +{ + REALM_ASSERT(!m_session_mutex.try_lock()); + auto it = m_sessions.find(path); + return it == m_sessions.end() ? nullptr : it->second; +} + +std::shared_ptr SyncManager::get_existing_session(const std::string& path) const +{ + std::lock_guard lock(m_session_mutex); + if (auto session = get_existing_session_locked(path)) + return session->external_reference(); + + return nullptr; +} + +std::shared_ptr SyncManager::get_session(const std::string& path, const SyncConfig& sync_config, bool force_client_resync) +{ + auto& client = get_sync_client(); // Throws + + std::lock_guard lock(m_session_mutex); + if (auto session = get_existing_session_locked(path)) { + sync_config.user->register_session(session); + return session->external_reference(); + } + + auto shared_session = SyncSession::create(client, path, sync_config, force_client_resync); + m_sessions[path] = shared_session; + + // Create the external reference immediately to ensure that the session will become + // inactive if an exception is thrown in the following code. + auto external_reference = shared_session->external_reference(); + + sync_config.user->register_session(std::move(shared_session)); + + return external_reference; +} + + +bool SyncManager::has_existing_sessions() +{ + std::lock_guard lock(m_session_mutex); + return do_has_existing_sessions(); +} + +bool SyncManager::do_has_existing_sessions(){ + return std::any_of(m_sessions.begin(), m_sessions.end(), [](auto& element){ + return element.second->existing_external_reference(); + }); +} + +void SyncManager::unregister_session(const std::string& path) +{ + std::lock_guard lock(m_session_mutex); + auto it = m_sessions.find(path); + REALM_ASSERT(it != m_sessions.end()); + + // If the session has an active external reference, leave it be. This will happen if the session + // moves to an inactive state while still externally reference, for instance, as a result of + // the session's user being logged out. + if (it->second->existing_external_reference()) + return; + + m_sessions.erase(path); +} + +void SyncManager::enable_session_multiplexing() +{ + std::lock_guard lock(m_mutex); + if (m_config.multiplex_sessions) + return; // Already enabled, we can ignore + + if (m_sync_client) + throw std::logic_error("Cannot enable session multiplexing after creating the sync client"); + + m_config.multiplex_sessions = true; +} + +SyncClient& SyncManager::get_sync_client() const +{ + std::lock_guard lock(m_mutex); + if (!m_sync_client) + m_sync_client = create_sync_client(); // Throws + return *m_sync_client; +} + +std::unique_ptr SyncManager::create_sync_client() const +{ + REALM_ASSERT(!m_mutex.try_lock()); + return std::make_unique(make_logger(), m_config); +} + +std::string SyncManager::client_uuid() const +{ + REALM_ASSERT(m_client_uuid); + return *m_client_uuid; +} diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/sync/sync_permission.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/sync/sync_permission.cpp new file mode 100644 index 0000000..4143bb9 --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/sync/sync_permission.cpp @@ -0,0 +1,370 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2017 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "sync/sync_permission.hpp" + +#include "impl/notification_wrapper.hpp" +#include "impl/object_accessor_impl.hpp" +#include "object_schema.hpp" +#include "property.hpp" + +#include "sync/sync_config.hpp" +#include "sync/sync_manager.hpp" +#include "sync/sync_session.hpp" +#include "sync/sync_user.hpp" +#include "util/event_loop_signal.hpp" +#include "util/uuid.hpp" + +#include + +using namespace realm; +using namespace std::chrono; + +// MARK: - Utility + +namespace { + +// Make a handler that extracts either an exception pointer, or the string value +// of the property with the specified name. +Permissions::AsyncOperationHandler make_handler_extracting_property(std::string property, + Permissions::PermissionOfferCallback callback) +{ + return [property=std::move(property), + callback=std::move(callback)](Object* object, std::exception_ptr exception) { + if (exception) { + callback(none, exception); + } else { + CppContext context; + auto token = any_cast(object->get_property_value(context, property)); + callback(util::make_optional(std::move(token)), nullptr); + } + }; +} + +AccessLevel extract_access_level(Object& permission, CppContext& context) +{ + auto may_manage = permission.get_property_value(context, "mayManage"); + if (may_manage.has_value() && any_cast(may_manage)) + return AccessLevel::Admin; + + auto may_write = permission.get_property_value(context, "mayWrite"); + if (may_write.has_value() && any_cast(may_write)) + return AccessLevel::Write; + + auto may_read = permission.get_property_value(context, "mayRead"); + if (may_read.has_value() && any_cast(may_read)) + return AccessLevel::Read; + + return AccessLevel::None; +} + +/// Turn a system time point value into the 64-bit integer representing ns since the Unix epoch. +int64_t ns_since_unix_epoch(const system_clock::time_point& point) +{ + tm unix_epoch{}; + unix_epoch.tm_year = 70; + time_t epoch_time = mktime(&unix_epoch); + auto epoch_point = system_clock::from_time_t(epoch_time); + return duration_cast(point - epoch_point).count(); +} + +} // anonymous namespace + +// MARK: - Permission + +Permission::Permission(Object& permission) +{ + CppContext context; + path = any_cast(permission.get_property_value(context, "path")); + access = extract_access_level(permission, context); + condition = Condition(any_cast(permission.get_property_value(context, "userId"))); + updated_at = any_cast(permission.get_property_value(context, "updatedAt")); +} + +Permission::Permission(std::string path, AccessLevel access, Condition condition, Timestamp updated_at) +: path(std::move(path)) +, access(access) +, condition(std::move(condition)) +, updated_at(std::move(updated_at)) +{ } + +std::string Permission::description_for_access_level(AccessLevel level) +{ + switch (level) { + case AccessLevel::None: return "none"; + case AccessLevel::Read: return "read"; + case AccessLevel::Write: return "write"; + case AccessLevel::Admin: return "admin"; + } + REALM_UNREACHABLE(); +} + +bool Permission::paths_are_equivalent(std::string path_1, std::string path_2, + const std::string& user_id_1, const std::string& user_id_2) +{ + REALM_ASSERT_DEBUG(path_1.length() > 0); + REALM_ASSERT_DEBUG(path_2.length() > 0); + if (path_1 == path_2) { + // If both paths are identical and contain `/~/`, the user IDs must match. + return (path_1.find("/~/") == std::string::npos) || (user_id_1 == user_id_2); + } + // Make substitutions for the first `/~/` in the string. + size_t index = path_1.find("/~/"); + if (index != std::string::npos) + path_1.replace(index + 1, 1, user_id_1); + + index = path_2.find("/~/"); + if (index != std::string::npos) + path_2.replace(index + 1, 1, user_id_2); + + return path_1 == path_2; +} + +// MARK: - Permissions + +void Permissions::get_permissions(std::shared_ptr user, + PermissionResultsCallback callback, + const ConfigMaker& make_config) +{ + auto realm = Permissions::permission_realm(user, make_config); + auto table = ObjectStore::table_for_object_type(realm->read_group(), "Permission"); + auto results = std::make_shared<_impl::NotificationWrapper>(std::move(realm), *table); + + // `get_permissions` works by temporarily adding an async notifier to the permission Realm. + // This notifier will run the `async` callback until the Realm contains permissions or + // an error happens. When either of these two things happen, the notifier will be + // unregistered by nulling out the `results_wrapper` container. + auto async = [results, callback=std::move(callback)](CollectionChangeSet, std::exception_ptr ex) mutable { + if (ex) { + callback(Results(), ex); + results.reset(); + return; + } + if (results->size() > 0) { + // We monitor the raw results. The presence of a `__management` Realm indicates + // that the permissions have been downloaded (hence, we wait until size > 0). + TableRef table = ObjectStore::table_for_object_type(results->get_realm()->read_group(), "Permission"); + size_t col_idx = table->get_descriptor()->get_column_index("path"); + auto query = !(table->column(col_idx).ends_with("/__permission") + || table->column(col_idx).ends_with("/__perm") + || table->column(col_idx).ends_with("/__management")); + // Call the callback with our new permissions object. This object will exclude the + // private Realms. + callback(results->filter(std::move(query)), nullptr); + results.reset(); + } + }; + results->add_notification_callback(std::move(async)); +} + +void Permissions::set_permission(std::shared_ptr user, + Permission permission, + PermissionChangeCallback callback, + const ConfigMaker& make_config) +{ + auto props = AnyDict{ + {"userId", permission.condition.user_id}, + {"realmUrl", user->server_url() + permission.path}, + {"mayRead", permission.access != AccessLevel::None}, + {"mayWrite", permission.access == AccessLevel::Write || permission.access == AccessLevel::Admin}, + {"mayManage", permission.access == AccessLevel::Admin}, + }; + if (permission.condition.type == Permission::Condition::Type::KeyValue) { + props.insert({"metadataKey", permission.condition.key_value.first}); + props.insert({"metadataValue", permission.condition.key_value.second}); + } + auto cb = [callback=std::move(callback)](Object*, std::exception_ptr exception) { + callback(exception); + }; + perform_async_operation("PermissionChange", std::move(user), std::move(cb), std::move(props), make_config); +} + +void Permissions::delete_permission(std::shared_ptr user, + Permission permission, + PermissionChangeCallback callback, + const ConfigMaker& make_config) +{ + permission.access = AccessLevel::None; + set_permission(std::move(user), std::move(permission), std::move(callback), make_config); +} + +void Permissions::make_offer(std::shared_ptr user, + PermissionOffer offer, + PermissionOfferCallback callback, + const ConfigMaker& make_config) +{ + auto props = AnyDict{ + {"expiresAt", std::move(offer.expiration)}, + {"userId", user->identity()}, + {"realmUrl", user->server_url() + offer.path}, + {"mayRead", offer.access != AccessLevel::None}, + {"mayWrite", offer.access == AccessLevel::Write || offer.access == AccessLevel::Admin}, + {"mayManage", offer.access == AccessLevel::Admin}, + }; + perform_async_operation("PermissionOffer", + std::move(user), + make_handler_extracting_property("token", std::move(callback)), + std::move(props), + make_config); +} + +void Permissions::accept_offer(std::shared_ptr user, + const std::string& token, + PermissionOfferCallback callback, + const ConfigMaker& make_config) +{ + perform_async_operation("PermissionOfferResponse", + std::move(user), + make_handler_extracting_property("realmUrl", std::move(callback)), + AnyDict{ {"token", token} }, + make_config); +} + +void Permissions::perform_async_operation(const std::string& object_type, + std::shared_ptr user, + AsyncOperationHandler handler, + AnyDict additional_props, + const ConfigMaker& make_config) +{; + auto realm = Permissions::management_realm(std::move(user), make_config); + CppContext context; + + // Get the current time. + int64_t ns_since_epoch = ns_since_unix_epoch(system_clock::now()); + int64_t s_arg = ns_since_epoch / (int64_t)Timestamp::nanoseconds_per_second; + int32_t ns_arg = ns_since_epoch % Timestamp::nanoseconds_per_second; + + auto props = AnyDict{ + {"id", util::uuid_string()}, + {"createdAt", Timestamp(s_arg, ns_arg)}, + {"updatedAt", Timestamp(s_arg, ns_arg)}, + }; + props.insert(additional_props.begin(), additional_props.end()); + + // Write the permission object. + realm->begin_transaction(); + auto raw = Object::create(context, realm, *realm->schema().find(object_type), std::move(props)); + auto object = std::make_shared<_impl::NotificationWrapper>(std::move(raw)); + realm->commit_transaction(); + + // Observe the permission object until the permission change has been processed or failed. + // The notifier is automatically unregistered upon the completion of the permission + // change, one way or another. + auto block = [object, handler=std::move(handler)](CollectionChangeSet, std::exception_ptr ex) mutable { + if (ex) { + handler(nullptr, ex); + object.reset(); + return; + } + + CppContext context; + auto status_code = object->get_property_value(context, "statusCode"); + if (!status_code.has_value()) { + // Continue waiting for the sync server to complete the operation. + return; + } + + // Determine whether an error happened or not. + if (auto code = any_cast(status_code)) { + // The permission change failed because an error was returned from the server. + auto status = object->get_property_value(context, "statusMessage"); + std::string error_str = (status.has_value() + ? any_cast(status) + : util::format("Error code: %1", code)); + handler(nullptr, std::make_exception_ptr(PermissionActionException(error_str, code))); + } + else { + handler(object.get(), nullptr); + } + object.reset(); + }; + object->add_notification_callback(std::move(block)); +} + +SharedRealm Permissions::management_realm(std::shared_ptr user, const ConfigMaker& make_config) +{ + // FIXME: maybe we should cache the management Realm on the user, so we don't need to open it every time. + const auto realm_url = util::format("realm%1/~/__management", user->server_url().substr(4)); + Realm::Config config = make_config(user, std::move(realm_url)); + config.sync_config->stop_policy = SyncSessionStopPolicy::Immediately; + config.schema = Schema{ + {"PermissionChange", { + Property{"id", PropertyType::String, Property::IsPrimary{true}}, + Property{"createdAt", PropertyType::Date}, + Property{"updatedAt", PropertyType::Date}, + Property{"statusCode", PropertyType::Int|PropertyType::Nullable}, + Property{"statusMessage", PropertyType::String|PropertyType::Nullable}, + Property{"userId", PropertyType::String}, + Property{"metadataKey", PropertyType::String|PropertyType::Nullable}, + Property{"metadataValue", PropertyType::String|PropertyType::Nullable}, + Property{"metadataNameSpace", PropertyType::String|PropertyType::Nullable}, + Property{"realmUrl", PropertyType::String}, + Property{"mayRead", PropertyType::Bool|PropertyType::Nullable}, + Property{"mayWrite", PropertyType::Bool|PropertyType::Nullable}, + Property{"mayManage", PropertyType::Bool|PropertyType::Nullable}, + }}, + {"PermissionOffer", { + Property{"id", PropertyType::String, Property::IsPrimary{true}}, + Property{"createdAt", PropertyType::Date}, + Property{"updatedAt", PropertyType::Date}, + Property{"expiresAt", PropertyType::Date|PropertyType::Nullable}, + Property{"statusCode", PropertyType::Int|PropertyType::Nullable}, + Property{"statusMessage", PropertyType::String|PropertyType::Nullable}, + Property{"token", PropertyType::String|PropertyType::Nullable}, + Property{"realmUrl", PropertyType::String}, + Property{"mayRead", PropertyType::Bool}, + Property{"mayWrite", PropertyType::Bool}, + Property{"mayManage", PropertyType::Bool}, + }}, + {"PermissionOfferResponse", { + Property{"id", PropertyType::String, Property::IsPrimary{true}}, + Property{"createdAt", PropertyType::Date}, + Property{"updatedAt", PropertyType::Date}, + Property{"statusCode", PropertyType::Int|PropertyType::Nullable}, + Property{"statusMessage", PropertyType::String|PropertyType::Nullable}, + Property{"token", PropertyType::String}, + Property{"realmUrl", PropertyType::String|PropertyType::Nullable}, + }}, + }; + config.schema_version = 0; + auto shared_realm = Realm::get_shared_realm(std::move(config)); + user->register_management_session(shared_realm->config().path); + return shared_realm; +} + +SharedRealm Permissions::permission_realm(std::shared_ptr user, const ConfigMaker& make_config) +{ + // FIXME: maybe we should cache the permission Realm on the user, so we don't need to open it every time. + const auto realm_url = util::format("realm%1/~/__permission", user->server_url().substr(4)); + Realm::Config config = make_config(user, std::move(realm_url)); + config.sync_config->stop_policy = SyncSessionStopPolicy::Immediately; + config.schema = Schema{ + {"Permission", { + {"updatedAt", PropertyType::Date}, + {"userId", PropertyType::String}, + {"path", PropertyType::String}, + {"mayRead", PropertyType::Bool}, + {"mayWrite", PropertyType::Bool}, + {"mayManage", PropertyType::Bool}, + }} + }; + config.schema_version = 0; + auto shared_realm = Realm::get_shared_realm(std::move(config)); + user->register_permission_session(shared_realm->config().path); + return shared_realm; +} diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/sync/sync_session.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/sync/sync_session.cpp new file mode 100644 index 0000000..f75891c --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/sync/sync_session.cpp @@ -0,0 +1,1150 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "sync/sync_session.hpp" + +#include "sync/impl/sync_client.hpp" +#include "sync/impl/sync_file.hpp" +#include "sync/impl/sync_metadata.hpp" +#include "sync/sync_manager.hpp" +#include "sync/sync_user.hpp" + +#include +#include + +using namespace realm; +using namespace realm::_impl; +using namespace realm::_impl::sync_session_states; + +using SessionWaiterPointer = void(sync::Session::*)(std::function); + +constexpr const char SyncError::c_original_file_path_key[]; +constexpr const char SyncError::c_recovery_file_path_key[]; + +/// A state which a `SyncSession` can currently be within. State classes handle various actions +/// and state transitions. +/// +/// STATES: +/// +/// WAITING_FOR_ACCESS_TOKEN: upon entering this state, the binding is informed +/// that the session wants an access token. The session is now waiting for the +/// binding to provide the token. +/// From: INACTIVE +/// To: +/// * ACTIVE: when the binding successfully refreshes the token +/// * INACTIVE: if asked to log out, or if asked to close and the stop policy +/// is Immediate. +/// +/// ACTIVE: the session is connected to the Realm Object Server and is actively +/// transferring data. +/// From: WAITING_FOR_ACCESS_TOKEN, DYING +/// To: +/// * WAITING_FOR_ACCESS_TOKEN: if the session is informed (through the error +/// handler) that the token expired +/// * INACTIVE: if asked to log out, or if asked to close and the stop policy +/// is Immediate. +/// * DYING: if asked to close and the stop policy is AfterChangesUploaded +/// +/// DYING: the session is performing clean-up work in preparation to be destroyed. +/// From: ACTIVE +/// To: +/// * INACTIVE: when the clean-up work completes, if the session wasn't +/// revived, or if explicitly asked to log out before the +/// clean-up work begins +/// * ACTIVE: if the session is revived +/// +/// INACTIVE: the user owning this session has logged out, the `sync::Session` +/// owned by this session is destroyed, and the session is quiescent. +/// Note that a session briefly enters this state before being destroyed, but +/// it can also enter this state and stay there if the user has been logged out. +/// From: initial, WAITING_FOR_ACCESS_TOKEN, ACTIVE, DYING +/// To: +/// * WAITING_FOR_ACCESS_TOKEN: if the session is revived +/// +struct SyncSession::State { + virtual ~State() { } + + // Move the given session into this state. All state transitions MUST be carried out through this method. + virtual void enter_state(std::unique_lock&, SyncSession&) const { } + + virtual void refresh_access_token(std::unique_lock&, + SyncSession&, std::string, + const util::Optional&) const { } + + // Returns true iff the lock is still locked when the method returns. + virtual bool access_token_expired(std::unique_lock&, SyncSession&) const { return true; } + + virtual void nonsync_transact_notify(std::unique_lock&, SyncSession&, sync::version_type) const { } + + // Perform any work needed to reactivate a session that is not already active. + // Returns true iff the session should ask the binding to get a token for `bind()`. + virtual bool revive_if_needed(std::unique_lock&, SyncSession&) const { return false; } + + // Perform any work needed to respond to the application regaining network connectivity. + virtual void handle_reconnect(std::unique_lock&, SyncSession&) const { }; + + // The user that owns this session has been logged out, and the session should take appropriate action. + virtual void log_out(std::unique_lock&, SyncSession&) const { } + + // The session should be closed and moved to `inactive`, in accordance with its stop policy and other state. + virtual void close(std::unique_lock&, SyncSession&) const { } + + // Returns true iff the error has been fully handled and the error handler should immediately return. + virtual bool handle_error(std::unique_lock&, SyncSession&, const SyncError&) const { return false; } + + // Register a handler to wait for sync session uploads, downloads, or synchronization. + // PRECONDITION: the session state lock must be held at the time this method is called, until after it returns. + virtual void wait_for_completion(SyncSession&, _impl::SyncProgressNotifier::NotifierType) const { } + + virtual void override_server(std::unique_lock&, SyncSession&, std::string, int) const { } + + static const State& waiting_for_access_token; + static const State& active; + static const State& dying; + static const State& inactive; +}; + +struct sync_session_states::WaitingForAccessToken : public SyncSession::State { + void enter_state(std::unique_lock&, SyncSession& session) const override + { + session.m_deferred_close = false; + } + + void refresh_access_token(std::unique_lock& lock, SyncSession& session, + std::string access_token, + const util::Optional& server_url) const override + { + session.create_sync_session(); + + // Since the sync session was previously unbound, it's safe to do this from the + // calling thread. + if (!session.m_server_url) { + session.m_server_url = server_url; + } + if (session.m_session_has_been_bound) { + session.m_session->refresh(std::move(access_token)); + session.m_session->cancel_reconnect_delay(); + } else { + session.m_session->bind(*session.m_server_url, std::move(access_token)); + session.m_session_has_been_bound = true; + } + + if (session.m_server_override) + session.m_session->override_server(session.m_server_override->address, session.m_server_override->port); + + // Handle any deferred commit notification. + if (session.m_deferred_commit_notification) { + session.m_session->nonsync_transact_notify(*session.m_deferred_commit_notification); + session.m_deferred_commit_notification = util::none; + } + + session.advance_state(lock, active); + if (session.m_deferred_close) { + session.m_state->close(lock, session); + } + } + + void log_out(std::unique_lock& lock, SyncSession& session) const override + { + session.advance_state(lock, inactive); + } + + bool revive_if_needed(std::unique_lock&, SyncSession& session) const override + { + session.m_deferred_close = false; + return false; + } + + void handle_reconnect(std::unique_lock& lock, SyncSession& session) const override + { + // Ask the binding to retry getting the token for this session. + std::shared_ptr session_ptr = session.shared_from_this(); + lock.unlock(); + session.m_config.bind_session_handler(session_ptr->m_realm_path, session_ptr->m_config, session_ptr); + } + + void nonsync_transact_notify(std::unique_lock&, + SyncSession& session, + sync::version_type version) const override + { + // Notify at first available opportunity. + session.m_deferred_commit_notification = version; + } + + void close(std::unique_lock& lock, SyncSession& session) const override + { + switch (session.m_config.stop_policy) { + case SyncSessionStopPolicy::Immediately: + // Immediately kill the session. + session.advance_state(lock, inactive); + break; + case SyncSessionStopPolicy::LiveIndefinitely: + case SyncSessionStopPolicy::AfterChangesUploaded: + // Defer handling closing the session until after the login response succeeds. + session.m_deferred_close = true; + break; + } + } + + void override_server(std::unique_lock&, SyncSession& session, + std::string address, int port) const override + { + session.m_server_override = SyncSession::ServerOverride{address, port}; + } +}; + +struct sync_session_states::Active : public SyncSession::State { + void enter_state(std::unique_lock&, SyncSession& session) const override + { + // Register all the pending wait-for-completion blocks. This can + // potentially add a redundant callback if we're coming from the Dying + // state, but that's okay (we won't call the user callbacks twice). + if (!session.m_upload_completion_callbacks.empty()) + session.add_completion_callback(_impl::SyncProgressNotifier::NotifierType::upload); + if (!session.m_download_completion_callbacks.empty()) + session.add_completion_callback(_impl::SyncProgressNotifier::NotifierType::download); + } + + void refresh_access_token(std::unique_lock&, SyncSession& session, + std::string access_token, + const util::Optional&) const override + { + session.m_session->refresh(std::move(access_token)); + // Cancel the session's reconnection delay. This is important if the + // token is being refreshed as a response to a 202 (token expired) + // error, or similar non-fatal sync errors. + session.m_session->cancel_reconnect_delay(); + } + + bool access_token_expired(std::unique_lock& lock, SyncSession& session) const override + { + session.advance_state(lock, waiting_for_access_token); + std::shared_ptr session_ptr = session.shared_from_this(); + lock.unlock(); + session.m_config.bind_session_handler(session_ptr->m_realm_path, session_ptr->m_config, session_ptr); + return false; + } + + void log_out(std::unique_lock& lock, SyncSession& session) const override + { + session.advance_state(lock, inactive); + } + + void nonsync_transact_notify(std::unique_lock&, SyncSession& session, + sync::version_type version) const override + { + // Fully ready sync session, notify immediately. + session.m_session->nonsync_transact_notify(version); + } + + void close(std::unique_lock& lock, SyncSession& session) const override + { + switch (session.m_config.stop_policy) { + case SyncSessionStopPolicy::Immediately: + session.advance_state(lock, inactive); + break; + case SyncSessionStopPolicy::LiveIndefinitely: + // Don't do anything; session lives forever. + break; + case SyncSessionStopPolicy::AfterChangesUploaded: + // Wait for all pending changes to upload. + session.advance_state(lock, dying); + break; + } + } + + void wait_for_completion(SyncSession& session, _impl::SyncProgressNotifier::NotifierType direction) const override + { + REALM_ASSERT(session.m_session); + session.add_completion_callback(direction); + } + + void handle_reconnect(std::unique_lock&, SyncSession& session) const override + { + session.m_session->cancel_reconnect_delay(); + } + + void override_server(std::unique_lock&, SyncSession& session, + std::string address, int port) const override + { + session.m_server_override = SyncSession::ServerOverride{address, port}; + session.m_session->override_server(address, port); + } +}; + +struct sync_session_states::Dying : public SyncSession::State { + void enter_state(std::unique_lock& lock, SyncSession& session) const override + { + // If we have no session, we cannot possibly upload anything. + if (!session.m_session) { + session.advance_state(lock, inactive); + return; + } + + size_t current_death_count = ++session.m_death_count; + std::weak_ptr weak_session = session.shared_from_this(); + session.m_session->async_wait_for_upload_completion([weak_session, current_death_count](std::error_code) { + if (auto session = weak_session.lock()) { + std::unique_lock lock(session->m_state_mutex); + if (session->m_state == &State::dying && session->m_death_count == current_death_count) { + session->advance_state(lock, inactive); + } + } + }); + } + + bool handle_error(std::unique_lock& lock, SyncSession& session, const SyncError& error) const override + { + if (error.is_fatal) { + session.advance_state(lock, inactive); + } + // If the error isn't fatal, don't change state, but don't + // allow it to be reported either. + // FIXME: What if the token expires while a session is dying? + // Should we allow the token to be refreshed so that changes + // can finish being uploaded? + return true; + } + + bool revive_if_needed(std::unique_lock& lock, SyncSession& session) const override + { + // Revive. + session.advance_state(lock, active); + return false; + } + + void log_out(std::unique_lock& lock, SyncSession& session) const override + { + session.advance_state(lock, inactive); + } + + void wait_for_completion(SyncSession& session, _impl::SyncProgressNotifier::NotifierType direction) const override + { + REALM_ASSERT(session.m_session); + session.add_completion_callback(direction); + } + + void override_server(std::unique_lock&, SyncSession& session, + std::string address, int port) const override + { + session.m_server_override = SyncSession::ServerOverride{address, port}; + session.m_session->override_server(address, port); + } +}; + +struct sync_session_states::Inactive : public SyncSession::State { + void enter_state(std::unique_lock& lock, SyncSession& session) const override + { + // Manually set the disconnected state. Sync would also do this, but + // since the underlying SyncSession object already have been destroyed, + // we are not able to get the callback. + auto old_state = session.m_connection_state; + auto new_state = session.m_connection_state = SyncSession::ConnectionState::Disconnected; + + auto download_waits = std::move(session.m_download_completion_callbacks); + auto upload_waits = std::move(session.m_upload_completion_callbacks); + session.m_download_completion_callbacks.clear(); + session.m_upload_completion_callbacks.clear(); + + session.m_session = nullptr; + session.unregister(lock); // releases lock + + // Send notifications after releasing the lock to prevent deadlocks in the callback. + if (old_state != new_state) { + session.m_connection_change_notifier.invoke_callbacks(old_state, session.connection_state()); + } + + // Inform any queued-up completion handlers that they were cancelled. + for (auto& callback : download_waits) + callback(make_error_code(util::error::operation_aborted)); + for (auto& callback : upload_waits) + callback(make_error_code(util::error::operation_aborted)); + } + + bool revive_if_needed(std::unique_lock& lock, SyncSession& session) const override + { + session.advance_state(lock, waiting_for_access_token); + return true; + } + + void override_server(std::unique_lock&, SyncSession& session, + std::string address, int port) const override + { + session.m_server_override = SyncSession::ServerOverride{address, port}; + } + + void close(std::unique_lock& lock, SyncSession& session) const override + { + session.unregister(lock); // releases lock + } +}; + + +const SyncSession::State& SyncSession::State::waiting_for_access_token = WaitingForAccessToken(); +const SyncSession::State& SyncSession::State::active = Active(); +const SyncSession::State& SyncSession::State::dying = Dying(); +const SyncSession::State& SyncSession::State::inactive = Inactive(); + +SyncSession::SyncSession(SyncClient& client, std::string realm_path, SyncConfig config, bool force_client_resync) +: m_state(&State::inactive) +, m_config(std::move(config)) +, m_force_client_resync(force_client_resync) +, m_realm_path(std::move(realm_path)) +, m_client(client) +{ + // Sync history validation ensures that the history within the Realm file is in a format that can be used + // by the version of realm-sync that we're using. Validation is enabled by default when the binding manually + // opens a sync session (via `SyncManager::get_session`), but is disabled when the sync session is opened + // as a side effect of opening a `Realm`. In that case, the sync history has already been validated by the + // act of opening the `Realm` so it's not necessary to repeat it here. + if (m_config.validate_sync_history) { + Realm::Config realm_config; + realm_config.path = m_realm_path; + realm_config.schema_mode = SchemaMode::Additive; + realm_config.force_sync_history = true; + realm_config.cache = false; + + if (m_config.realm_encryption_key) { + realm_config.encryption_key.resize(64); + std::copy(m_config.realm_encryption_key->begin(), m_config.realm_encryption_key->end(), + realm_config.encryption_key.begin()); + } + + std::unique_ptr history; + std::unique_ptr shared_group; + std::unique_ptr read_only_group; + Realm::open_with_config(realm_config, history, shared_group, read_only_group, nullptr); + } +} + +std::string SyncSession::get_recovery_file_path() +{ + return util::reserve_unique_file_name(SyncManager::shared().recovery_directory_path(m_config.recovery_directory), + util::create_timestamped_template("recovered_realm")); +} + +void SyncSession::update_error_and_mark_file_for_deletion(SyncError& error, ShouldBackup should_backup) +{ + // Add a SyncFileActionMetadata marking the Realm as needing to be deleted. + std::string recovery_path; + auto original_path = path(); + error.user_info[SyncError::c_original_file_path_key] = original_path; + if (should_backup == ShouldBackup::yes) { + recovery_path = get_recovery_file_path(); + error.user_info[SyncError::c_recovery_file_path_key] = recovery_path; + } + using Action = SyncFileActionMetadata::Action; + auto action = should_backup == ShouldBackup::yes ? Action::BackUpThenDeleteRealm : Action::DeleteRealm; + SyncManager::shared().perform_metadata_update([this, action, + original_path=std::move(original_path), + recovery_path=std::move(recovery_path)](const auto& manager) { + auto realm_url = m_config.realm_url(); + manager.make_file_action_metadata(original_path, realm_url, m_config.user->identity(), action, recovery_path); + }); +} + +// This method should only be called from within the error handler callback registered upon the underlying `m_session`. +void SyncSession::handle_error(SyncError error) +{ + enum class NextStateAfterError { none, inactive, error }; + auto next_state = error.is_fatal ? NextStateAfterError::error : NextStateAfterError::none; + auto error_code = error.error_code; + + { + // See if the current state wishes to take responsibility for handling the error. + std::unique_lock lock(m_state_mutex); + if (m_state->handle_error(lock, *this, error)) { + return; + } + } + + if (error.is_client_reset_requested()) { + switch (m_config.client_resync_mode) { + case ClientResyncMode::Manual: + break; + case ClientResyncMode::DiscardLocal: + case ClientResyncMode::Recover: { + // Performing a client resync requires tearing down our current + // sync session and creating a new one with a forced client + // reset. This will result in session completion handlers firing + // when the old session is torn down, which we don't want as this + // is supposed to be transparent to the user. + // + // To avoid this, we need to do two things: move the completion + // handlers aside temporarily so that moving to the inactive + // state doesn't clear them, and track which sync::Session each + // completion notification came from so that we can ignore + // notifications from the old session. + { + std::unique_lock lock(m_state_mutex); + m_force_client_resync = true; + + ++m_client_resync_counter; + auto download_handlers = std::move(m_download_completion_callbacks); + auto upload_handlers = std::move(m_upload_completion_callbacks); + + advance_state(lock, State::inactive); + + m_download_completion_callbacks = std::move(download_handlers); + m_upload_completion_callbacks = std::move(upload_handlers); + } + revive_if_needed(); + return; + } + } + } + + if (error_code.category() == realm::sync::protocol_error_category()) { + using ProtocolError = realm::sync::ProtocolError; + switch (static_cast(error_code.value())) { + // Connection level errors + case ProtocolError::connection_closed: + case ProtocolError::other_error: + // Not real errors, don't need to be reported to the binding. + return; + case ProtocolError::unknown_message: + case ProtocolError::bad_syntax: + case ProtocolError::limits_exceeded: + case ProtocolError::wrong_protocol_version: + case ProtocolError::bad_session_ident: + case ProtocolError::reuse_of_session_ident: + case ProtocolError::bound_in_other_session: + case ProtocolError::bad_message_order: + case ProtocolError::bad_client_version: + case ProtocolError::illegal_realm_path: + case ProtocolError::no_such_realm: + case ProtocolError::bad_changeset: + case ProtocolError::bad_changeset_header_syntax: + case ProtocolError::bad_changeset_size: + case ProtocolError::bad_changesets: + case ProtocolError::bad_decompression: + case ProtocolError::partial_sync_disabled: + case ProtocolError::unsupported_session_feature: + case ProtocolError::transact_before_upload: + break; + // Session errors + case ProtocolError::session_closed: + case ProtocolError::other_session_error: + case ProtocolError::disabled_session: + // The binding doesn't need to be aware of these because they are strictly informational, and do not + // represent actual errors. + return; + case ProtocolError::token_expired: { + std::unique_lock lock(m_state_mutex); + // This isn't an error from the binding's point of view. If we're connected we'll + // simply ask the binding to log in again. + m_state->access_token_expired(lock, *this); + return; + } + case ProtocolError::bad_authentication: { + std::shared_ptr user_to_invalidate; + next_state = NextStateAfterError::none; + { + std::unique_lock lock(m_state_mutex); + user_to_invalidate = user(); + cancel_pending_waits(lock, error.error_code); + } + if (user_to_invalidate) + user_to_invalidate->invalidate(); + break; + } + case ProtocolError::permission_denied: { + next_state = NextStateAfterError::inactive; + update_error_and_mark_file_for_deletion(error, ShouldBackup::no); + break; + } + case ProtocolError::bad_client_file: + case ProtocolError::bad_client_file_ident: + case ProtocolError::bad_origin_file_ident: + case ProtocolError::bad_server_file_ident: + case ProtocolError::bad_server_version: + case ProtocolError::client_file_blacklisted: + case ProtocolError::diverging_histories: + case ProtocolError::server_file_deleted: + case ProtocolError::user_blacklisted: + case ProtocolError::client_file_expired: + next_state = NextStateAfterError::inactive; + update_error_and_mark_file_for_deletion(error, ShouldBackup::yes); + break; + } + } else if (error_code.category() == realm::sync::client_error_category()) { + using ClientError = realm::sync::Client::Error; + switch (static_cast(error_code.value())) { + case ClientError::connection_closed: + case ClientError::pong_timeout: + // Not real errors, don't need to be reported to the binding. + return; + case ClientError::bad_changeset: + case ClientError::bad_changeset_header_syntax: + case ClientError::bad_changeset_size: + case ClientError::bad_client_file_ident: + case ClientError::bad_client_file_ident_salt: + case ClientError::bad_client_version: + case ClientError::bad_compression: + case ClientError::bad_error_code: + case ClientError::bad_file_ident: + case ClientError::bad_message_order: + case ClientError::bad_origin_file_ident: + case ClientError::bad_progress: + case ClientError::bad_protocol_from_server: + case ClientError::bad_request_ident: + case ClientError::bad_server_version: + case ClientError::bad_session_ident: + case ClientError::bad_state_message: + case ClientError::bad_syntax: + case ClientError::bad_timestamp: + case ClientError::client_too_new_for_server: + case ClientError::client_too_old_for_server: + case ClientError::connect_timeout: + case ClientError::limits_exceeded: + case ClientError::protocol_mismatch: + case ClientError::ssl_server_cert_rejected: + case ClientError::unknown_message: + case ClientError::missing_protocol_feature: + case ClientError::bad_serial_transact_status: + case ClientError::bad_object_id_substitutions: + case ClientError::http_tunnel_failed: + // Don't do anything special for these errors. + // Future functionality may require special-case handling for existing + // errors, or newly introduced error codes. + break; + } + } else { + // Unrecognized error code. + error.is_unrecognized_by_client = true; + } + switch (next_state) { + case NextStateAfterError::none: + if (m_config.cancel_waits_on_nonfatal_error) { + std::unique_lock lock(m_state_mutex); + cancel_pending_waits(lock, error.error_code); + } + break; + case NextStateAfterError::inactive: { + std::unique_lock lock(m_state_mutex); + advance_state(lock, State::inactive); + break; + } + case NextStateAfterError::error: { + std::unique_lock lock(m_state_mutex); + cancel_pending_waits(lock, error.error_code); + break; + } + } + if (m_config.error_handler) { + m_config.error_handler(shared_from_this(), std::move(error)); + } +} + +void SyncSession::cancel_pending_waits(std::unique_lock& lock, std::error_code error) +{ + auto download = std::move(m_download_completion_callbacks); + auto upload = std::move(m_upload_completion_callbacks); + lock.unlock(); + + // Inform any queued-up completion handlers that they were cancelled. + for (auto& callback : download) + callback(error); + for (auto& callback : upload) + callback(error); +} + +void SyncSession::handle_progress_update(uint64_t downloaded, uint64_t downloadable, + uint64_t uploaded, uint64_t uploadable, + uint64_t download_version, uint64_t snapshot_version) +{ + m_progress_notifier.update(downloaded, downloadable, uploaded, uploadable, download_version, snapshot_version); +} + +void SyncSession::create_sync_session() +{ + if (m_session) + return; + + sync::Session::Config session_config; + session_config.changeset_cooker = m_config.transformer; + session_config.encryption_key = m_config.realm_encryption_key; + session_config.verify_servers_ssl_certificate = m_config.client_validate_ssl; + session_config.ssl_trust_certificate_path = m_config.ssl_trust_certificate_path; + session_config.ssl_verify_callback = m_config.ssl_verify_callback; + session_config.proxy_config = m_config.proxy_config; + session_config.multiplex_ident = m_multiplex_identity; + + if (m_config.authorization_header_name) { + session_config.authorization_header_name = *m_config.authorization_header_name; + } + session_config.custom_http_headers = m_config.custom_http_headers; + + if (m_config.url_prefix) { + session_config.url_prefix = *m_config.url_prefix; + } + + if (m_force_client_resync) { + std::string metadata_dir = m_realm_path + ".resync"; + util::try_make_dir(metadata_dir); + + sync::Session::Config::ClientReset config; + config.metadata_dir = metadata_dir; + if (m_config.client_resync_mode != ClientResyncMode::Recover) + config.recover_local_changes = false; + session_config.client_reset_config = config; + } + + m_session = m_client.make_session(m_realm_path, std::move(session_config)); + + // The next time we get a token, call `bind()` instead of `refresh()`. + m_session_has_been_bound = false; + + std::weak_ptr weak_self = shared_from_this(); + + // Configure the sync transaction callback. + auto wrapped_callback = [this, weak_self](VersionID old_version, VersionID new_version) { + if (auto self = weak_self.lock()) { + if (m_sync_transact_callback) { + m_sync_transact_callback(old_version, new_version); + } + } + }; + m_session->set_sync_transact_callback(std::move(wrapped_callback)); + + // Set up the wrapped progress handler callback + m_session->set_progress_handler([weak_self](uint_fast64_t downloaded, uint_fast64_t downloadable, + uint_fast64_t uploaded, uint_fast64_t uploadable, + uint_fast64_t progress_version, uint_fast64_t snapshot_version) { + if (auto self = weak_self.lock()) { + self->handle_progress_update(downloaded, downloadable, uploaded, + uploadable, progress_version, snapshot_version); + } + }); + + // Sets up the connection state listener. This callback is used for both reporting errors as well as changes to the + // connection state. + m_session->set_connection_state_change_listener([weak_self](sync::Session::ConnectionState state, + const sync::Session::ErrorInfo* error) { + // If the OS SyncSession object is destroyed, we ignore any events from the underlying Session as there is + // nothing useful we can do with them. + if (auto self = weak_self.lock()) { + std::unique_lock lock(self->m_state_mutex); + auto old_state = self->m_connection_state; + using cs = sync::Session::ConnectionState; + switch (state) { + case cs::disconnected: self->m_connection_state = ConnectionState::Disconnected; break; + case cs::connecting: self->m_connection_state = ConnectionState::Connecting; break; + case cs::connected: self->m_connection_state = ConnectionState::Connected; break; + default: REALM_UNREACHABLE(); + } + auto new_state = self->m_connection_state; + lock.unlock(); + self->m_connection_change_notifier.invoke_callbacks(old_state, new_state); + if (error) { + self->handle_error(SyncError{error->error_code, std::move(error->detailed_message), error->is_fatal}); + } + } + }); +} + +void SyncSession::set_sync_transact_callback(std::function callback) +{ + m_sync_transact_callback = std::move(callback); +} + +void SyncSession::advance_state(std::unique_lock& lock, const State& state) +{ + REALM_ASSERT(lock.owns_lock()); + REALM_ASSERT(&state != m_state); + m_state = &state; + m_state->enter_state(lock, *this); +} + +void SyncSession::nonsync_transact_notify(sync::version_type version) +{ + m_progress_notifier.set_local_version(version); + + std::unique_lock lock(m_state_mutex); + m_state->nonsync_transact_notify(lock, *this, version); +} + +void SyncSession::revive_if_needed() +{ + util::Optional&> handler; + { + std::unique_lock lock(m_state_mutex); + if (m_state->revive_if_needed(lock, *this)) + handler = m_config.bind_session_handler; + } + if (handler) + handler.value()(m_realm_path, m_config, shared_from_this()); +} + +void SyncSession::handle_reconnect() +{ + std::unique_lock lock(m_state_mutex); + m_state->handle_reconnect(lock, *this); +} + +void SyncSession::log_out() +{ + std::unique_lock lock(m_state_mutex); + m_state->log_out(lock, *this); +} + +void SyncSession::close() +{ + std::unique_lock lock(m_state_mutex); + m_state->close(lock, *this); +} + +void SyncSession::unregister(std::unique_lock& lock) +{ + REALM_ASSERT(lock.owns_lock()); + REALM_ASSERT(m_state == &State::inactive); // Must stop an active session before unregistering. + + lock.unlock(); + SyncManager::shared().unregister_session(m_realm_path); +} + +void SyncSession::add_completion_callback(_impl::SyncProgressNotifier::NotifierType direction) +{ + bool is_download = direction == _impl::SyncProgressNotifier::NotifierType::download; + + int resync_counter = m_client_resync_counter; + std::weak_ptr weak_self = shared_from_this(); + auto waiter = is_download ? &sync::Session::async_wait_for_download_completion + : &sync::Session::async_wait_for_upload_completion; + (m_session.get()->*waiter)([resync_counter, weak_self, is_download](std::error_code ec) { + auto self = weak_self.lock(); + if (!self) + return; + std::unique_lock lock(self->m_state_mutex); + if (resync_counter != self->m_client_resync_counter) { + // This callback was registered on a previous sync session and not + // the current one, so we want to simply discard completion + // notifications from it + return; + } + auto callbacks = std::move(is_download ? self->m_download_completion_callbacks + : self->m_upload_completion_callbacks); + lock.unlock(); + for (auto& callback : callbacks) + callback(ec); + }); +} + +void SyncSession::wait_for_upload_completion(std::function callback) +{ + std::unique_lock lock(m_state_mutex); + if (m_upload_completion_callbacks.empty()) + m_state->wait_for_completion(*this, _impl::SyncProgressNotifier::NotifierType::upload); + m_upload_completion_callbacks.push_back(std::move(callback)); +} + +void SyncSession::wait_for_download_completion(std::function callback) +{ + std::unique_lock lock(m_state_mutex); + if (m_download_completion_callbacks.empty()) + m_state->wait_for_completion(*this, _impl::SyncProgressNotifier::NotifierType::download); + m_download_completion_callbacks.push_back(std::move(callback)); +} + +uint64_t SyncSession::register_progress_notifier(std::function notifier, + NotifierType direction, bool is_streaming) +{ + return m_progress_notifier.register_callback(std::move(notifier), direction, is_streaming); +} + +void SyncSession::unregister_progress_notifier(uint64_t token) +{ + m_progress_notifier.unregister_callback(token); +} + +uint64_t SyncSession::register_connection_change_callback(std::function callback) +{ + return m_connection_change_notifier.add_callback(callback); +} + +void SyncSession::unregister_connection_change_callback(uint64_t token) +{ + m_connection_change_notifier.remove_callback(token); +} + +void SyncSession::refresh_access_token(std::string access_token, util::Optional server_url) +{ + std::unique_lock lock(m_state_mutex); + if (!m_server_url && !server_url) { + // The first time this method is called, the server URL must be provided. + return; + } + m_state->refresh_access_token(lock, *this, std::move(access_token), server_url); +} + +void SyncSession::override_server(std::string address, int port) +{ + std::unique_lock lock(m_state_mutex); + m_state->override_server(lock, *this, std::move(address), port); +} + +void SyncSession::set_multiplex_identifier(std::string multiplex_identity) +{ + m_multiplex_identity = std::move(multiplex_identity); +} + +void SyncSession::set_url_prefix(std::string url_prefix) +{ + m_config.url_prefix = std::move(url_prefix); +} + +SyncSession::PublicState SyncSession::get_public_state() const +{ + if (m_state == nullptr) { + return PublicState::Inactive; + } else if (m_state == &State::waiting_for_access_token) { + return PublicState::WaitingForAccessToken; + } else if (m_state == &State::active) { + return PublicState::Active; + } else if (m_state == &State::dying) { + return PublicState::Dying; + } else if (m_state == &State::inactive) { + return PublicState::Inactive; + } + REALM_UNREACHABLE(); +} + +SyncSession::PublicState SyncSession::state() const +{ + std::unique_lock lock(m_state_mutex); + return get_public_state(); +} + +SyncSession::ConnectionState SyncSession::connection_state() const +{ + std::unique_lock lock(m_state_mutex); + return m_connection_state; +} + +void SyncSession::update_configuration(SyncConfig new_config) +{ + while (true) { + std::unique_lock lock(m_state_mutex); + if (m_state != &State::inactive) { + // Changing the state releases the lock, which means that by the + // time we reacquire the lock the state may have changed again + // (either due to one of the callbacks being invoked or another + // thread coincidentally doing something). We just attempt to keep + // switching it to inactive until it stays there. + advance_state(lock, State::inactive); + continue; + } + + REALM_ASSERT(m_state == &State::inactive); + REALM_ASSERT(!m_session); + REALM_ASSERT(m_config.user == new_config.user); + REALM_ASSERT(m_config.reference_realm_url == new_config.reference_realm_url); + REALM_ASSERT(m_config.is_partial == new_config.is_partial); + m_config = std::move(new_config); + break; + } + revive_if_needed(); +} + +// Represents a reference to the SyncSession from outside of the sync subsystem. +// We attempt to keep the SyncSession in an active state as long as it has an external reference. +class SyncSession::ExternalReference { +public: + ExternalReference(std::shared_ptr session) : m_session(std::move(session)) + {} + + ~ExternalReference() + { + m_session->did_drop_external_reference(); + } + +private: + std::shared_ptr m_session; +}; + +std::shared_ptr SyncSession::external_reference() +{ + std::unique_lock lock(m_state_mutex); + + if (auto external_reference = m_external_reference.lock()) + return std::shared_ptr(external_reference, this); + + auto external_reference = std::make_shared(shared_from_this()); + m_external_reference = external_reference; + return std::shared_ptr(external_reference, this); +} + +std::shared_ptr SyncSession::existing_external_reference() +{ + std::unique_lock lock(m_state_mutex); + + if (auto external_reference = m_external_reference.lock()) + return std::shared_ptr(external_reference, this); + + return nullptr; +} + +void SyncSession::did_drop_external_reference() +{ + std::unique_lock lock(m_state_mutex); + + // If the session is being resurrected we should not close the session. + if (!m_external_reference.expired()) + return; + + m_state->close(lock, *this); +} + +uint64_t SyncProgressNotifier::register_callback(std::function notifier, + NotifierType direction, bool is_streaming) +{ + std::function invocation; + uint64_t token_value = 0; + { + std::lock_guard lock(m_mutex); + token_value = m_progress_notifier_token++; + NotifierPackage package{std::move(notifier), util::none, m_local_transaction_version, + is_streaming, direction == NotifierType::download}; + if (!m_current_progress) { + // Simply register the package, since we have no data yet. + m_packages.emplace(token_value, std::move(package)); + return token_value; + } + bool skip_registration = false; + invocation = package.create_invocation(*m_current_progress, skip_registration); + if (skip_registration) { + token_value = 0; + } else { + m_packages.emplace(token_value, std::move(package)); + } + } + invocation(); + return token_value; +} + +void SyncProgressNotifier::unregister_callback(uint64_t token) +{ + std::lock_guard lock(m_mutex); + m_packages.erase(token); +} + +void SyncProgressNotifier::update(uint64_t downloaded, uint64_t downloadable, + uint64_t uploaded, uint64_t uploadable, + uint64_t download_version, uint64_t snapshot_version) +{ + // Ignore progress messages from before we first receive a DOWNLOAD message + if (download_version == 0) + return; + + std::vector> invocations; + { + std::lock_guard lock(m_mutex); + m_current_progress = Progress{uploadable, downloadable, uploaded, downloaded, snapshot_version}; + + for (auto it = m_packages.begin(); it != m_packages.end(); ) { + bool should_delete = false; + invocations.emplace_back(it->second.create_invocation(*m_current_progress, should_delete)); + it = should_delete ? m_packages.erase(it) : std::next(it); + } + } + // Run the notifiers only after we've released the lock. + for (auto& invocation : invocations) + invocation(); +} + +void SyncProgressNotifier::set_local_version(uint64_t snapshot_version) +{ + std::lock_guard lock(m_mutex); + m_local_transaction_version = snapshot_version; +} + +std::function SyncProgressNotifier::NotifierPackage::create_invocation(Progress const& current_progress, bool& is_expired) +{ + uint64_t transferrable; + if (is_streaming) { + transferrable = is_download ? current_progress.downloadable : current_progress.uploadable; + } + else if (captured_transferrable) { + transferrable = *captured_transferrable; + } + else { + if (is_download) + captured_transferrable = current_progress.downloadable; + else { + // If the sync client has not yet processed all of the local + // transactions then the uploadable data is incorrect and we should + // not invoke the callback + if (snapshot_version > current_progress.snapshot_version) + return []{}; + captured_transferrable = current_progress.uploadable; + } + transferrable = *captured_transferrable; + } + + uint64_t transferred = is_download ? current_progress.downloaded : current_progress.uploaded; + // A notifier is expired if at least as many bytes have been transferred + // as were originally considered transferrable. + is_expired = !is_streaming && transferred >= transferrable; + return [=, notifier=notifier] { notifier(transferred, transferrable); }; +} + +uint64_t SyncSession::ConnectionChangeNotifier::add_callback(std::function callback) +{ + std::lock_guard lock(m_callback_mutex); + auto token = m_next_token++; + m_callbacks.push_back({std::move(callback), token}); + return token; +} + +void SyncSession::ConnectionChangeNotifier::remove_callback(uint64_t token) +{ + Callback old; + { + std::lock_guard lock(m_callback_mutex); + auto it = find_if(begin(m_callbacks), end(m_callbacks), + [=](const auto& c) { return c.token == token; }); + if (it == end(m_callbacks)) { + return; + } + + size_t idx = distance(begin(m_callbacks), it); + if (m_callback_index != npos) { + if (m_callback_index >= idx) + --m_callback_index; + } + --m_callback_count; + + old = std::move(*it); + m_callbacks.erase(it); + } +} + +void SyncSession::ConnectionChangeNotifier::invoke_callbacks(ConnectionState old_state, ConnectionState new_state) +{ + std::unique_lock lock(m_callback_mutex); + m_callback_count = m_callbacks.size(); + for (++m_callback_index; m_callback_index < m_callback_count; ++m_callback_index) { + // acquire a local reference to the callback so that removing the + // callback from within it can't result in a dangling pointer + auto cb = m_callbacks[m_callback_index].fn; + lock.unlock(); + cb(old_state, new_state); + lock.lock(); + } + m_callback_index = npos; +} diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/sync/sync_user.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/sync/sync_user.cpp new file mode 100644 index 0000000..b06dcde --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/sync/sync_user.cpp @@ -0,0 +1,263 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "sync/sync_user.hpp" + +#include "sync/impl/sync_metadata.hpp" +#include "sync/sync_manager.hpp" +#include "sync/sync_session.hpp" + +namespace realm { + +SyncUserContextFactory SyncUser::s_binding_context_factory; +std::mutex SyncUser::s_binding_context_factory_mutex; + +SyncUser::SyncUser(std::string refresh_token, + std::string identity, + util::Optional server_url, + util::Optional local_identity, + TokenType token_type) +: m_state(State::Active) +, m_server_url(server_url.value_or("")) +, m_token_type(token_type) +, m_refresh_token(std::move(refresh_token)) +, m_identity(std::move(identity)) +{ + { + std::lock_guard lock(s_binding_context_factory_mutex); + if (s_binding_context_factory) { + m_binding_context = s_binding_context_factory(); + } + } + if (token_type == TokenType::Normal) { + REALM_ASSERT(m_server_url.length() > 0); + bool updated = SyncManager::shared().perform_metadata_update([=](const auto& manager) { + auto metadata = manager.get_or_make_user_metadata(m_identity, m_server_url); + metadata->set_user_token(m_refresh_token); + m_is_admin = metadata->is_admin(); + m_local_identity = metadata->local_uuid(); + }); + if (!updated) + m_local_identity = m_identity; + } else { + // Admin token users. The local identity serves as the directory path. + REALM_ASSERT(local_identity); + m_local_identity = std::move(*local_identity); + } +} + +std::vector> SyncUser::all_sessions() +{ + std::lock_guard lock(m_mutex); + std::vector> sessions; + if (m_state == State::Error) { + return sessions; + } + for (auto it = m_sessions.begin(); it != m_sessions.end();) { + if (auto ptr_to_session = it->second.lock()) { + sessions.emplace_back(std::move(ptr_to_session)); + it++; + continue; + } + // This session is bad, destroy it. + it = m_sessions.erase(it); + } + return sessions; +} + +std::shared_ptr SyncUser::session_for_on_disk_path(const std::string& path) +{ + std::lock_guard lock(m_mutex); + if (m_state == State::Error) { + return nullptr; + } + auto it = m_sessions.find(path); + if (it == m_sessions.end()) { + return nullptr; + } + auto locked = it->second.lock(); + if (!locked) { + // Remove the session from the map, because it has fatally errored out or the entry is invalid. + m_sessions.erase(it); + } + return locked; +} + +void SyncUser::update_refresh_token(std::string token) +{ + std::vector> sessions_to_revive; + { + std::unique_lock lock(m_mutex); + if (auto session = m_management_session.lock()) + sessions_to_revive.emplace_back(std::move(session)); + + if (auto session = m_permission_session.lock()) + sessions_to_revive.emplace_back(std::move(session)); + + switch (m_state) { + case State::Error: + return; + case State::Active: + m_refresh_token = token; + break; + case State::LoggedOut: { + sessions_to_revive.reserve(m_waiting_sessions.size()); + m_refresh_token = token; + m_state = State::Active; + for (auto& pair : m_waiting_sessions) { + if (auto ptr = pair.second.lock()) { + m_sessions[pair.first] = ptr; + sessions_to_revive.emplace_back(std::move(ptr)); + } + } + m_waiting_sessions.clear(); + break; + } + } + // Update persistent user metadata. + if (m_token_type != TokenType::Admin) { + SyncManager::shared().perform_metadata_update([=](const auto& manager) { + auto metadata = manager.get_or_make_user_metadata(m_identity, m_server_url); + metadata->set_user_token(token); + }); + } + } + // (Re)activate all pending sessions. + // Note that we do this after releasing the lock, since the session may + // need to access protected User state in the process of binding itself. + for (auto& session : sessions_to_revive) { + session->revive_if_needed(); + } +} + +void SyncUser::log_out() +{ + if (m_token_type == TokenType::Admin) { + // Admin-token users cannot be logged out. + return; + } + std::lock_guard lock(m_mutex); + if (m_state == State::LoggedOut) { + return; + } + m_state = State::LoggedOut; + // Move all active sessions into the waiting sessions pool. If the user is + // logged back in, they will automatically be reactivated. + for (auto& pair : m_sessions) { + if (auto ptr = pair.second.lock()) { + ptr->log_out(); + m_waiting_sessions[pair.first] = ptr; + } + } + m_sessions.clear(); + // Deactivate the sessions for the management and admin Realms. + if (auto session = m_management_session.lock()) + session->log_out(); + + if (auto session = m_permission_session.lock()) + session->log_out(); + + // Mark the user as 'dead' in the persisted metadata Realm. + SyncManager::shared().perform_metadata_update([=](const auto& manager) { + auto metadata = manager.get_or_make_user_metadata(m_identity, m_server_url, false); + if (metadata) + metadata->mark_for_removal(); + }); +} + +void SyncUser::set_is_admin(bool is_admin) +{ + if (m_token_type == TokenType::Admin) { + return; + } + m_is_admin = is_admin; + SyncManager::shared().perform_metadata_update([=](const auto& manager) { + auto metadata = manager.get_or_make_user_metadata(m_identity, m_server_url); + metadata->set_is_admin(is_admin); + }); +} + +void SyncUser::invalidate() +{ + std::lock_guard lock(m_mutex); + m_state = State::Error; +} + +std::string SyncUser::refresh_token() const +{ + std::lock_guard lock(m_mutex); + return m_refresh_token; +} + +SyncUser::State SyncUser::state() const +{ + std::lock_guard lock(m_mutex); + return m_state; +} + +void SyncUser::register_session(std::shared_ptr session) +{ + const std::string& path = session->path(); + std::unique_lock lock(m_mutex); + switch (m_state) { + case State::Active: + // Immediately ask the session to come online. + m_sessions[path] = session; + lock.unlock(); + session->revive_if_needed(); + break; + case State::LoggedOut: + m_waiting_sessions[path] = session; + break; + case State::Error: + break; + } +} + +void SyncUser::set_binding_context_factory(SyncUserContextFactory factory) +{ + std::lock_guard lock(s_binding_context_factory_mutex); + s_binding_context_factory = std::move(factory); +} + +void SyncUser::register_management_session(const std::string& path) +{ + std::lock_guard lock(m_mutex); + if (m_management_session.lock() || m_state == State::Error) + return; + + m_management_session = SyncManager::shared().get_existing_session(path); +} + +void SyncUser::register_permission_session(const std::string& path) +{ + std::lock_guard lock(m_mutex); + if (m_permission_session.lock() || m_state == State::Error) + return; + + m_permission_session = SyncManager::shared().get_existing_session(path); +} + +} + +namespace std { +size_t hash::operator()(const realm::SyncUserIdentifier& k) const +{ + return ((hash()(k.user_id) ^ (hash()(k.auth_server_url) << 1)) >> 1); +} +} diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/thread_safe_reference.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/thread_safe_reference.cpp new file mode 100644 index 0000000..9469d89 --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/thread_safe_reference.cpp @@ -0,0 +1,123 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "thread_safe_reference.hpp" + +#include "impl/realm_coordinator.hpp" +#include "list.hpp" +#include "object.hpp" +#include "object_schema.hpp" +#include "results.hpp" + +#include + +using namespace realm; + +ThreadSafeReferenceBase::ThreadSafeReferenceBase(SharedRealm source_realm) : m_source_realm(std::move(source_realm)) +{ + m_source_realm->verify_thread(); + if (m_source_realm->is_in_transaction()) { + throw InvalidTransactionException("Cannot obtain thread safe reference during a write transaction."); + } + + try { + m_version_id = get_source_shared_group().pin_version(); + } catch (...) { + invalidate(); + throw; + } +} + +ThreadSafeReferenceBase::~ThreadSafeReferenceBase() +{ + if (!is_invalidated()) + invalidate(); +} + +template +V ThreadSafeReferenceBase::invalidate_after_import(Realm& destination_realm, T construct_with_shared_group) { + destination_realm.verify_thread(); + REALM_ASSERT_DEBUG(!m_source_realm->is_in_transaction()); + REALM_ASSERT_DEBUG(!is_invalidated()); + + SharedGroup& destination_shared_group = *Realm::Internal::get_shared_group(destination_realm); + auto unpin_version = util::make_scope_exit([&]() noexcept { invalidate(); }); + + return construct_with_shared_group(destination_shared_group); +} + +SharedGroup& ThreadSafeReferenceBase::get_source_shared_group() const { + return *Realm::Internal::get_shared_group(*m_source_realm); +} + +bool ThreadSafeReferenceBase::has_same_config(Realm& realm) const { + return &Realm::Internal::get_coordinator(*m_source_realm) == &Realm::Internal::get_coordinator(realm); +} + +void ThreadSafeReferenceBase::invalidate() { + REALM_ASSERT_DEBUG(m_source_realm); + SharedRealm thread_local_realm = Realm::Internal::get_coordinator(*m_source_realm).get_realm(); + Realm::Internal::get_shared_group(*thread_local_realm)->unpin_version(m_version_id); + m_source_realm = nullptr; +} + +ThreadSafeReference::ThreadSafeReference(List const& list) +: ThreadSafeReferenceBase(list.get_realm()) +, m_link_view(get_source_shared_group().export_linkview_for_handover(list.m_link_view)) +, m_table(get_source_shared_group().export_table_for_handover(list.m_table)) +{ } + +List ThreadSafeReference::import_into_realm(SharedRealm realm) && { + return invalidate_after_import(*realm, [&](SharedGroup& shared_group) { + if (auto link_view = shared_group.import_linkview_from_handover(std::move(m_link_view))) + return List(std::move(realm), std::move(link_view)); + return List(std::move(realm), shared_group.import_table_from_handover(std::move(m_table))); + }); +} + +ThreadSafeReference::ThreadSafeReference(Object const& object) +: ThreadSafeReferenceBase(object.realm()) +, m_row(get_source_shared_group().export_for_handover(Row(object.row()))) +, m_object_schema_name(object.get_object_schema().name) { } + +Object ThreadSafeReference::import_into_realm(SharedRealm realm) && { + return invalidate_after_import(*realm, [&](SharedGroup& shared_group) { + Row row = *shared_group.import_from_handover(std::move(m_row)); + auto object_schema = realm->schema().find(m_object_schema_name); + REALM_ASSERT_DEBUG(object_schema != realm->schema().end()); + return Object(std::move(realm), *object_schema, row); + }); +} + +ThreadSafeReference::ThreadSafeReference(Results const& results) +: ThreadSafeReferenceBase(results.get_realm()) +, m_query(get_source_shared_group().export_for_handover(results.get_query(), ConstSourcePayload::Copy)) +, m_ordering_patch([&]() { + DescriptorOrdering::HandoverPatch ordering_patch; + DescriptorOrdering::generate_patch(results.get_descriptor_ordering(), ordering_patch); + return ordering_patch; +}()){ } + +Results ThreadSafeReference::import_into_realm(SharedRealm realm) && { + return invalidate_after_import(*realm, [&](SharedGroup& shared_group) { + Query query = *shared_group.import_from_handover(std::move(m_query)); + Table& table = *query.get_table(); + DescriptorOrdering descriptors = DescriptorOrdering::create_from_and_consume_patch(m_ordering_patch, table); + return Results(std::move(realm), std::move(query), std::move(descriptors)); + }); +} diff --git a/!main project/Pods/Realm/Realm/ObjectStore/src/util/uuid.cpp b/!main project/Pods/Realm/Realm/ObjectStore/src/util/uuid.cpp new file mode 100644 index 0000000..0110002 --- /dev/null +++ b/!main project/Pods/Realm/Realm/ObjectStore/src/util/uuid.cpp @@ -0,0 +1,80 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2017 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "util/uuid.hpp" + +#include +#include +#include +#include +#include + +namespace { + +// Seed `engine` with as much random state as it requires, based on the approach outlined in P0205R0. +// +template +T create_and_seed_engine() +{ + constexpr auto bytes_needed = T::state_size * sizeof(typename T::result_type); + + constexpr auto numbers_needed = sizeof(std::random_device::result_type) < sizeof(std::seed_seq::result_type) + ? (bytes_needed / sizeof(std::random_device::result_type)) + : (bytes_needed / sizeof(std::seed_seq::result_type)); + + std::array state; + std::random_device rd; + std::generate(begin(state), end(state), std::ref(rd)); + std::seed_seq seeds(begin(state), end(state)); + + T engine; + engine.seed(seeds); + return engine; +} + +} // unnamed namespace + +namespace realm { +namespace util { + +std::string uuid_string() +{ + static auto engine = create_and_seed_engine(); + + std::array uuid_bytes; + std::uniform_int_distribution distribution(0, std::numeric_limits::max()); + std::generate(begin(uuid_bytes), end(uuid_bytes), [&] { return distribution(engine); }); + + // Version 4 UUID. + uuid_bytes[6] = (uuid_bytes[6] & 0x0f) | 0x40; + // IETF variant. + uuid_bytes[8] = (uuid_bytes[8] & 0x3f) | 0x80; + + std::array uuid_formatted; + snprintf(uuid_formatted.data(), uuid_formatted.size(), + "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + uuid_bytes[0], uuid_bytes[1], uuid_bytes[2], uuid_bytes[3], + uuid_bytes[4], uuid_bytes[5], uuid_bytes[6], uuid_bytes[7], + uuid_bytes[8], uuid_bytes[9], uuid_bytes[10], uuid_bytes[11], + uuid_bytes[12], uuid_bytes[13], uuid_bytes[14], uuid_bytes[15]); + + return std::string(uuid_formatted.data(), uuid_formatted.size() - 1); +} + +} // namespace util +} // namespace realm diff --git a/!main project/Pods/Realm/Realm/RLMAccessor.mm b/!main project/Pods/Realm/Realm/RLMAccessor.mm new file mode 100644 index 0000000..48a288a --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMAccessor.mm @@ -0,0 +1,808 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMAccessor.hpp" + +#import "RLMArray_Private.hpp" +#import "RLMListBase.h" +#import "RLMObjectSchema_Private.hpp" +#import "RLMObjectStore.h" +#import "RLMObject_Private.hpp" +#import "RLMObservation.hpp" +#import "RLMProperty_Private.h" +#import "RLMRealm_Private.hpp" +#import "RLMResults_Private.hpp" +#import "RLMSchema_Private.h" +#import "RLMUtil.hpp" +#import "results.hpp" +#import "property.hpp" + +#import +#import +#import + +#pragma mark - Helper functions + +namespace { +template +T get(__unsafe_unretained RLMObjectBase *const obj, NSUInteger index) { + RLMVerifyAttached(obj); + return obj->_row.get(obj->_info->objectSchema->persisted_properties[index].table_column); +} + +template +id getBoxed(__unsafe_unretained RLMObjectBase *const obj, NSUInteger index) { + RLMVerifyAttached(obj); + auto& prop = obj->_info->objectSchema->persisted_properties[index]; + auto col = prop.table_column; + if (obj->_row.is_null(col)) { + return nil; + } + + RLMAccessorContext ctx(obj, &prop); + return ctx.box(obj->_row.get(col)); +} + +template +void setValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex, T val) { + RLMVerifyInWriteTransaction(obj); + obj->_row.set(colIndex, val); +} + +template +auto translateError(Fn&& fn) { + try { + return fn(); + } + catch (std::exception const& e) { + @throw RLMException(e); + } +} + +void setValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex, + __unsafe_unretained NSString *const val) { + RLMVerifyInWriteTransaction(obj); + translateError([&] { + obj->_row.set(colIndex, RLMStringDataWithNSString(val)); + }); +} + +[[gnu::noinline]] +void setNull(realm::Row& row, size_t col) { + translateError([&] { row.set_null(col); }); +} + +void setValue(__unsafe_unretained RLMObjectBase *const obj, + NSUInteger colIndex, __unsafe_unretained NSDate *const date) { + RLMVerifyInWriteTransaction(obj); + if (date) { + obj->_row.set(colIndex, RLMTimestampForNSDate(date)); + } + else { + setNull(obj->_row, colIndex); + } +} + +void setValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex, + __unsafe_unretained NSData *const data) { + RLMVerifyInWriteTransaction(obj); + translateError([&] { + obj->_row.set(colIndex, RLMBinaryDataForNSData(data)); + }); +} + +void setValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex, + __unsafe_unretained RLMObjectBase *const val) { + RLMVerifyInWriteTransaction(obj); + if (!val) { + obj->_row.nullify_link(colIndex); + return; + } + + RLMAddObjectToRealm(val, obj->_realm, RLMUpdatePolicyError); + + // make sure it is the correct type + if (val->_row.get_table() != obj->_row.get_table()->get_link_target(colIndex)) { + @throw RLMException(@"Can't set object of type '%@' to property of type '%@'", + val->_objectSchema.className, + obj->_info->propertyForTableColumn(colIndex).objectClassName); + } + obj->_row.set_link(colIndex, val->_row.get_index()); +} + +// array getter/setter +RLMArray *getArray(__unsafe_unretained RLMObjectBase *const obj, NSUInteger propIndex) { + RLMVerifyAttached(obj); + auto prop = obj->_info->rlmObjectSchema.properties[propIndex]; + return [[RLMManagedArray alloc] initWithParent:obj property:prop]; +} + +void setValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex, + __unsafe_unretained id const value) { + RLMVerifyInWriteTransaction(obj); + auto prop = obj->_info->propertyForTableColumn(colIndex); + RLMValidateValueForProperty(value, obj->_info->rlmObjectSchema, prop, true); + + realm::List list(obj->_realm->_realm, *obj->_row.get_table(), colIndex, obj->_row.get_index()); + RLMClassInfo *info = obj->_info; + if (list.get_type() == realm::PropertyType::Object) { + info = &obj->_info->linkTargetType(prop.index); + } + RLMAccessorContext ctx(*info); + translateError([&] { + list.assign(ctx, value, realm::CreatePolicy::ForceCreate); + }); +} + +void setValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex, + __unsafe_unretained NSNumber *const intObject) { + RLMVerifyInWriteTransaction(obj); + + if (intObject) { + obj->_row.set(colIndex, intObject.longLongValue); + } + else { + setNull(obj->_row, colIndex); + } +} + +void setValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex, + __unsafe_unretained NSNumber *const floatObject) { + RLMVerifyInWriteTransaction(obj); + + if (floatObject) { + obj->_row.set(colIndex, floatObject.floatValue); + } + else { + setNull(obj->_row, colIndex); + } +} + +void setValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex, + __unsafe_unretained NSNumber *const doubleObject) { + RLMVerifyInWriteTransaction(obj); + + if (doubleObject) { + obj->_row.set(colIndex, doubleObject.doubleValue); + } + else { + setNull(obj->_row, colIndex); + } +} + +void setValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex, + __unsafe_unretained NSNumber *const boolObject) { + RLMVerifyInWriteTransaction(obj); + + if (boolObject) { + obj->_row.set(colIndex, (bool)boolObject.boolValue); + } + else { + setNull(obj->_row, colIndex); + } +} + +RLMLinkingObjects *getLinkingObjects(__unsafe_unretained RLMObjectBase *const obj, + __unsafe_unretained RLMProperty *const property) { + RLMVerifyAttached(obj); + auto& objectInfo = obj->_realm->_info[property.objectClassName]; + auto& linkOrigin = obj->_info->objectSchema->computed_properties[property.index].link_origin_property_name; + auto linkingProperty = objectInfo.objectSchema->property_for_name(linkOrigin); + auto backlinkView = obj->_row.get_table()->get_backlink_view(obj->_row.get_index(), + objectInfo.table(), + linkingProperty->table_column); + realm::Results results(obj->_realm->_realm, std::move(backlinkView)); + return [RLMLinkingObjects resultsWithObjectInfo:objectInfo results:std::move(results)]; +} + +// any getter/setter +template +id makeGetter(NSUInteger index) { + return ^(__unsafe_unretained RLMObjectBase *const obj) { + return static_cast(get(obj, index)); + }; +} + +template +id makeBoxedGetter(NSUInteger index) { + return ^(__unsafe_unretained RLMObjectBase *const obj) { + return getBoxed(obj, index); + }; +} +template +id makeOptionalGetter(NSUInteger index) { + return ^(__unsafe_unretained RLMObjectBase *const obj) { + return getBoxed>(obj, index); + }; +} +template +id makeNumberGetter(NSUInteger index, bool boxed, bool optional) { + if (optional) { + return makeOptionalGetter(index); + } + if (boxed) { + return makeBoxedGetter(index); + } + return makeGetter(index); +} + +// dynamic getter with column closure +id managedGetter(RLMProperty *prop, const char *type) { + NSUInteger index = prop.index; + if (prop.array && prop.type != RLMPropertyTypeLinkingObjects) { + return ^id(__unsafe_unretained RLMObjectBase *const obj) { + return getArray(obj, index); + }; + } + + bool boxed = *type == '@'; + switch (prop.type) { + case RLMPropertyTypeInt: + if (prop.optional || boxed) { + return makeNumberGetter(index, boxed, prop.optional); + } + switch (*type) { + case 'c': return makeGetter(index); + case 's': return makeGetter(index); + case 'i': return makeGetter(index); + case 'l': return makeGetter(index); + case 'q': return makeGetter(index); + default: + @throw RLMException(@"Unexpected property type for Objective-C type code"); + } + case RLMPropertyTypeFloat: + return makeNumberGetter(index, boxed, prop.optional); + case RLMPropertyTypeDouble: + return makeNumberGetter(index, boxed, prop.optional); + case RLMPropertyTypeBool: + return makeNumberGetter(index, boxed, prop.optional); + case RLMPropertyTypeString: + return makeBoxedGetter(index); + case RLMPropertyTypeDate: + return makeBoxedGetter(index); + case RLMPropertyTypeData: + return makeBoxedGetter(index); + case RLMPropertyTypeObject: + return makeBoxedGetter(index); + case RLMPropertyTypeAny: + @throw RLMException(@"Cannot create accessor class for schema with Mixed properties"); + case RLMPropertyTypeLinkingObjects: + return ^(__unsafe_unretained RLMObjectBase *const obj) { + return getLinkingObjects(obj, prop); + }; + } +} + +template +id makeSetter(__unsafe_unretained RLMProperty *const prop) { + NSUInteger index = prop.index; + NSString *name = prop.name; + if (prop.isPrimary) { + return ^(__unused RLMObjectBase *obj, __unused ArgType val) { + @throw RLMException(@"Primary key can't be changed after an object is inserted."); + }; + } + + return ^(__unsafe_unretained RLMObjectBase *const obj, ArgType val) { + auto set = [&] { + setValue(obj, obj->_info->objectSchema->persisted_properties[index].table_column, + static_cast(val)); + }; + if (RLMObservationInfo *info = RLMGetObservationInfo(obj->_observationInfo, + obj->_row.get_index(), *obj->_info)) { + info->willChange(name); + set(); + info->didChange(name); + } + else { + set(); + } + }; +} + +// dynamic setter with column closure +id managedSetter(RLMProperty *prop, const char *type) { + if (prop.array && prop.type != RLMPropertyTypeLinkingObjects) { + return makeSetter>(prop); + } + + bool boxed = prop.optional || *type == '@'; + switch (prop.type) { + case RLMPropertyTypeInt: + if (boxed) { + return makeSetter *>(prop); + } + switch (*type) { + case 'c': return makeSetter(prop); + case 's': return makeSetter(prop); + case 'i': return makeSetter(prop); + case 'l': return makeSetter(prop); + case 'q': return makeSetter(prop); + default: + @throw RLMException(@"Unexpected property type for Objective-C type code"); + } + case RLMPropertyTypeFloat: + return boxed ? makeSetter *>(prop) : makeSetter(prop); + case RLMPropertyTypeDouble: + return boxed ? makeSetter *>(prop) : makeSetter(prop); + case RLMPropertyTypeBool: + return boxed ? makeSetter *>(prop) : makeSetter(prop); + case RLMPropertyTypeString: return makeSetter(prop); + case RLMPropertyTypeDate: return makeSetter(prop); + case RLMPropertyTypeData: return makeSetter(prop); + case RLMPropertyTypeAny: return nil; + case RLMPropertyTypeLinkingObjects: return nil; + case RLMPropertyTypeObject: return makeSetter(prop); + } +} + +// call getter for superclass for property at colIndex +id superGet(RLMObjectBase *obj, NSString *propName) { + typedef id (*getter_type)(RLMObjectBase *, SEL); + RLMProperty *prop = obj->_objectSchema[propName]; + Class superClass = class_getSuperclass(obj.class); + getter_type superGetter = (getter_type)[superClass instanceMethodForSelector:prop.getterSel]; + return superGetter(obj, prop.getterSel); +} + +// call setter for superclass for property at colIndex +void superSet(RLMObjectBase *obj, NSString *propName, id val) { + typedef void (*setter_type)(RLMObjectBase *, SEL, RLMArray *ar); + RLMProperty *prop = obj->_objectSchema[propName]; + Class superClass = class_getSuperclass(obj.class); + setter_type superSetter = (setter_type)[superClass instanceMethodForSelector:prop.setterSel]; + superSetter(obj, prop.setterSel, val); +} + +// getter/setter for unmanaged object +id unmanagedGetter(RLMProperty *prop, const char *) { + // only override getters for RLMArray and linking objects properties + if (prop.type == RLMPropertyTypeLinkingObjects) { + return ^(RLMObjectBase *) { return [RLMResults emptyDetachedResults]; }; + } + if (prop.array) { + NSString *propName = prop.name; + if (prop.type == RLMPropertyTypeObject) { + NSString *objectClassName = prop.objectClassName; + return ^(RLMObjectBase *obj) { + id val = superGet(obj, propName); + if (!val) { + val = [[RLMArray alloc] initWithObjectClassName:objectClassName]; + superSet(obj, propName, val); + } + return val; + }; + } + auto type = prop.type; + auto optional = prop.optional; + return ^(RLMObjectBase *obj) { + id val = superGet(obj, propName); + if (!val) { + val = [[RLMArray alloc] initWithObjectType:type optional:optional]; + superSet(obj, propName, val); + } + return val; + }; + } + return nil; +} + +id unmanagedSetter(RLMProperty *prop, const char *) { + // Only RLMArray needs special handling for the unmanaged setter + if (!prop.array) { + return nil; + } + + NSString *propName = prop.name; + return ^(RLMObjectBase *obj, id values) { + auto prop = obj->_objectSchema[propName]; + RLMValidateValueForProperty(values, obj->_objectSchema, prop, true); + + // make copy when setting (as is the case for all other variants) + RLMArray *ar; + if (prop.type == RLMPropertyTypeObject) + ar = [[RLMArray alloc] initWithObjectClassName:prop.objectClassName]; + else + ar = [[RLMArray alloc] initWithObjectType:prop.type optional:prop.optional]; + [ar addObjects:values]; + superSet(obj, propName, ar); + }; +} + +void addMethod(Class cls, __unsafe_unretained RLMProperty *const prop, + id (*getter)(RLMProperty *, const char *), + id (*setter)(RLMProperty *, const char *)) { + SEL sel = prop.getterSel; + auto getterMethod = class_getInstanceMethod(cls, sel); + if (!getterMethod) { + return; + } + + const char *getterType = method_getTypeEncoding(getterMethod); + if (id block = getter(prop, getterType)) { + class_addMethod(cls, sel, imp_implementationWithBlock(block), getterType); + } + + if (!(sel = prop.setterSel)) { + return; + } + auto setterMethod = class_getInstanceMethod(cls, sel); + if (!setterMethod) { + return; + } + if (id block = setter(prop, getterType)) { // note: deliberately getterType as it's easier to grab the relevant type from + class_addMethod(cls, sel, imp_implementationWithBlock(block), method_getTypeEncoding(setterMethod)); + } +} + +Class createAccessorClass(Class objectClass, + RLMObjectSchema *schema, + const char *accessorClassName, + id (*getterGetter)(RLMProperty *, const char *), + id (*setterGetter)(RLMProperty *, const char *)) { + REALM_ASSERT_DEBUG(RLMIsObjectOrSubclass(objectClass)); + + // create and register proxy class which derives from object class + Class accClass = objc_allocateClassPair(objectClass, accessorClassName, 0); + if (!accClass) { + // Class with that name already exists, so just return the pre-existing one + // This should only happen for our standalone "accessors" + return objc_lookUpClass(accessorClassName); + } + + // override getters/setters for each propery + for (RLMProperty *prop in schema.properties) { + addMethod(accClass, prop, getterGetter, setterGetter); + } + for (RLMProperty *prop in schema.computedProperties) { + addMethod(accClass, prop, getterGetter, setterGetter); + } + + objc_registerClassPair(accClass); + + return accClass; +} +} // anonymous namespace + +#pragma mark - Public Interface + +Class RLMManagedAccessorClassForObjectClass(Class objectClass, RLMObjectSchema *schema, const char *name) { + return createAccessorClass(objectClass, schema, name, managedGetter, managedSetter); +} + +Class RLMUnmanagedAccessorClassForObjectClass(Class objectClass, RLMObjectSchema *schema) { + return createAccessorClass(objectClass, schema, + [@"RLM:Unmanaged " stringByAppendingString:schema.className].UTF8String, + unmanagedGetter, unmanagedSetter); +} + +// implement the class method className on accessors to return the className of the +// base object +void RLMReplaceClassNameMethod(Class accessorClass, NSString *className) { + Class metaClass = object_getClass(accessorClass); + IMP imp = imp_implementationWithBlock(^(Class){ return className; }); + class_addMethod(metaClass, @selector(className), imp, "@@:"); +} + +// implement the shared schema method +void RLMReplaceSharedSchemaMethod(Class accessorClass, RLMObjectSchema *schema) { + Class metaClass = object_getClass(accessorClass); + IMP imp = imp_implementationWithBlock(^(Class cls) { + if (cls == accessorClass) { + return schema; + } + + // If we aren't being called directly on the class this was overriden + // for, the class is either a subclass which we haven't initialized yet, + // or it's a runtime-generated class which should use the parent's + // schema. We check for the latter by checking if the immediate + // descendent of the desired class is a class generated by us (there + // may be further subclasses not generated by us for things like KVO). + Class parent = class_getSuperclass(cls); + while (parent != accessorClass) { + cls = parent; + parent = class_getSuperclass(cls); + } + + static const char accessorClassPrefix[] = "RLM:"; + if (!strncmp(class_getName(cls), accessorClassPrefix, sizeof(accessorClassPrefix) - 1)) { + return schema; + } + + return [RLMSchema sharedSchemaForClass:cls]; + }); + class_addMethod(metaClass, @selector(sharedSchema), imp, "@@:"); +} + +void RLMDynamicValidatedSet(RLMObjectBase *obj, NSString *propName, id val) { + RLMVerifyAttached(obj); + RLMObjectSchema *schema = obj->_objectSchema; + RLMProperty *prop = schema[propName]; + if (!prop) { + @throw RLMException(@"Invalid property name '%@' for class '%@'.", + propName, obj->_objectSchema.className); + } + if (prop.isPrimary) { + @throw RLMException(@"Primary key can't be changed to '%@' after an object is inserted.", val); + } + RLMValidateValueForProperty(val, schema, prop, true); + RLMDynamicSet(obj, prop, RLMCoerceToNil(val)); +} + +// Precondition: the property is not a primary key +void RLMDynamicSet(__unsafe_unretained RLMObjectBase *const obj, + __unsafe_unretained RLMProperty *const prop, + __unsafe_unretained id const val) { + REALM_ASSERT_DEBUG(!prop.isPrimary); + realm::Object o(obj->_info->realm->_realm, *obj->_info->objectSchema, obj->_row); + RLMAccessorContext c(obj); + translateError([&] { + o.set_property_value(c, prop.columnName.UTF8String, val ?: NSNull.null); + }); +} + +id RLMDynamicGet(__unsafe_unretained RLMObjectBase *const obj, __unsafe_unretained RLMProperty *const prop) { + realm::Object o(obj->_realm->_realm, *obj->_info->objectSchema, obj->_row); + RLMAccessorContext c(obj); + c.currentProperty = prop; + return translateError([&] { + return RLMCoerceToNil(o.get_property_value(c, prop.columnName.UTF8String)); + }); +} + +id RLMDynamicGetByName(__unsafe_unretained RLMObjectBase *const obj, + __unsafe_unretained NSString *const propName) { + RLMProperty *prop = obj->_objectSchema[propName]; + if (!prop) { + @throw RLMException(@"Invalid property name '%@' for class '%@'.", + propName, obj->_objectSchema.className); + } + return RLMDynamicGet(obj, prop); +} + +RLMAccessorContext::RLMAccessorContext(RLMAccessorContext& parent, realm::Property const& property) +: _realm(parent._realm) +, _info(property.type == realm::PropertyType::Object ? parent._info.linkTargetType(property) : parent._info) +, _promote_existing(parent._promote_existing) +{ +} + +RLMAccessorContext::RLMAccessorContext(RLMClassInfo& info, bool promote) +: _realm(info.realm), _info(info), _promote_existing(promote) +{ +} + +RLMAccessorContext::RLMAccessorContext(__unsafe_unretained RLMObjectBase *const parent, + const realm::Property *prop) +: _realm(parent->_realm) +, _info(prop && prop->type == realm::PropertyType::Object ? parent->_info->linkTargetType(*prop) + : *parent->_info) +, _parentObject(parent) +{ +} + +id RLMAccessorContext::defaultValue(__unsafe_unretained NSString *const key) { + if (!_defaultValues) { + _defaultValues = RLMDefaultValuesForObjectSchema(_info.rlmObjectSchema); + } + return _defaultValues[key]; +} + +id RLMAccessorContext::propertyValue(__unsafe_unretained id const obj, size_t propIndex, + __unsafe_unretained RLMProperty *const prop) { + // Property value from an NSArray + if ([obj respondsToSelector:@selector(objectAtIndex:)]) { + return propIndex < [obj count] ? [obj objectAtIndex:propIndex] : nil; + } + + // Property value from an NSDictionary + if ([obj respondsToSelector:@selector(objectForKey:)]) { + return [obj objectForKey:prop.name]; + } + + // Property value from an instance of this object type + id value; + if ([obj isKindOfClass:_info.rlmObjectSchema.objectClass] && prop.swiftIvar) { + if (prop.array) { + return static_cast(object_getIvar(obj, prop.swiftIvar))._rlmArray; + } + else { // optional + value = RLMGetOptional(static_cast(object_getIvar(obj, prop.swiftIvar))); + } + } + else { + // Property value from some object that's KVC-compatible + value = RLMValidatedValueForProperty(obj, [obj respondsToSelector:prop.getterSel] ? prop.getterName : prop.name, + _info.rlmObjectSchema.className); + } + return value ?: NSNull.null; +} + +id RLMAccessorContext::box(realm::List&& l) { + REALM_ASSERT(_parentObject); + REALM_ASSERT(currentProperty); + return [[RLMManagedArray alloc] initWithList:std::move(l) + parentInfo:_parentObject->_info + property:currentProperty]; +} + +id RLMAccessorContext::box(realm::Object&& o) { + REALM_ASSERT(currentProperty); + return RLMCreateObjectAccessor(_info.linkTargetType(currentProperty.index), o.row()); +} + +id RLMAccessorContext::box(realm::RowExpr r) { + return RLMCreateObjectAccessor(_info, r); +} + +id RLMAccessorContext::box(realm::Results&& r) { + REALM_ASSERT(currentProperty); + return [RLMResults resultsWithObjectInfo:_realm->_info[currentProperty.objectClassName] + results:std::move(r)]; +} + +template<> +realm::Timestamp RLMAccessorContext::unbox(__unsafe_unretained id const value, realm::CreatePolicy, size_t) { + id v = RLMCoerceToNil(value); + return RLMTimestampForNSDate(v); +} + +template<> +bool RLMAccessorContext::unbox(__unsafe_unretained id const v, realm::CreatePolicy, size_t) { + return [v boolValue]; +} +template<> +double RLMAccessorContext::unbox(__unsafe_unretained id const v, realm::CreatePolicy, size_t) { + return [v doubleValue]; +} +template<> +float RLMAccessorContext::unbox(__unsafe_unretained id const v, realm::CreatePolicy, size_t) { + return [v floatValue]; +} +template<> +long long RLMAccessorContext::unbox(__unsafe_unretained id const v, realm::CreatePolicy, size_t) { + return [v longLongValue]; +} +template<> +realm::BinaryData RLMAccessorContext::unbox(id v, realm::CreatePolicy, size_t) { + v = RLMCoerceToNil(v); + return RLMBinaryDataForNSData(v); +} +template<> +realm::StringData RLMAccessorContext::unbox(id v, realm::CreatePolicy, size_t) { + v = RLMCoerceToNil(v); + return RLMStringDataWithNSString(v); +} + +template +static auto to_optional(__unsafe_unretained id const value, Fn&& fn) { + id v = RLMCoerceToNil(value); + return v && v != NSNull.null ? realm::util::make_optional(fn(v)) : realm::util::none; +} + +template<> +realm::util::Optional RLMAccessorContext::unbox(__unsafe_unretained id const v, realm::CreatePolicy, size_t) { + return to_optional(v, [&](__unsafe_unretained id v) { return (bool)[v boolValue]; }); +} +template<> +realm::util::Optional RLMAccessorContext::unbox(__unsafe_unretained id const v, realm::CreatePolicy, size_t) { + return to_optional(v, [&](__unsafe_unretained id v) { return [v doubleValue]; }); +} +template<> +realm::util::Optional RLMAccessorContext::unbox(__unsafe_unretained id const v, realm::CreatePolicy, size_t) { + return to_optional(v, [&](__unsafe_unretained id v) { return [v floatValue]; }); +} +template<> +realm::util::Optional RLMAccessorContext::unbox(__unsafe_unretained id const v, realm::CreatePolicy, size_t) { + return to_optional(v, [&](__unsafe_unretained id v) { return [v longLongValue]; }); +} + +template<> +realm::RowExpr RLMAccessorContext::unbox(__unsafe_unretained id const v, realm::CreatePolicy createPolicy, size_t) { + bool create = createPolicy != realm::CreatePolicy::Skip; + auto policy = static_cast(createPolicy); + RLMObjectBase *link = RLMDynamicCast(v); + if (!link) { + if (!create) + return realm::RowExpr(); + return RLMCreateObjectInRealmWithValue(_realm, _info.rlmObjectSchema.className, v, policy)->_row; + } + + if (link.isInvalidated) { + if (create) { + @throw RLMException(@"Adding a deleted or invalidated object to a Realm is not permitted"); + } + else { + @throw RLMException(@"Object has been invalidated"); + } + } + + if (![link->_objectSchema.className isEqualToString:_info.rlmObjectSchema.className]) { + if (create && !_promote_existing) + return RLMCreateObjectInRealmWithValue(_realm, _info.rlmObjectSchema.className, link, policy)->_row; + return link->_row; + } + + if (!link->_realm) { + if (!create) + return realm::RowExpr(); + if (!_promote_existing) + return RLMCreateObjectInRealmWithValue(_realm, _info.rlmObjectSchema.className, link, policy)->_row; + RLMAddObjectToRealm(link, _realm, policy); + } + else if (link->_realm != _realm) { + if (_promote_existing) + @throw RLMException(@"Object is already managed by another Realm. Use create instead to copy it into this Realm."); + return RLMCreateObjectInRealmWithValue(_realm, _info.rlmObjectSchema.className, v, policy)->_row; + } + return link->_row; +} + +void RLMAccessorContext::will_change(realm::Row const& row, realm::Property const& prop) { + _observationInfo = RLMGetObservationInfo(nullptr, row.get_index(), _info); + if (_observationInfo) { + _kvoPropertyName = _info.propertyForTableColumn(prop.table_column).name; + _observationInfo->willChange(_kvoPropertyName); + } +} + +void RLMAccessorContext::did_change() { + if (_observationInfo) { + _observationInfo->didChange(_kvoPropertyName); + _kvoPropertyName = nil; + _observationInfo = nullptr; + } +} + +RLMOptionalId RLMAccessorContext::value_for_property(__unsafe_unretained id const obj, + realm::Property const&, size_t propIndex) { + auto prop = _info.rlmObjectSchema.properties[propIndex]; + id value = propertyValue(obj, propIndex, prop); + if (value) { + RLMValidateValueForProperty(value, _info.rlmObjectSchema, prop); + } + + if (_promote_existing && [obj isKindOfClass:_info.rlmObjectSchema.objectClass] && !prop.swiftIvar) { + // set the ivars for object and array properties to nil as otherwise the + // accessors retain objects that are no longer accessible via the properties + // this is mainly an issue when the object graph being added has cycles, + // as it's not obvious that the user has to set the *ivars* to nil to + // avoid leaking memory + if (prop.type == RLMPropertyTypeObject) { + ((void(*)(id, SEL, id))objc_msgSend)(obj, prop.setterSel, nil); + } + } + + return RLMOptionalId{value}; +} + +RLMOptionalId RLMAccessorContext::default_value_for_property(realm::ObjectSchema const&, + realm::Property const& prop) +{ + return RLMOptionalId{defaultValue(@(prop.name.c_str()))}; +} + +bool RLMAccessorContext::is_same_list(realm::List const& list, __unsafe_unretained id const v) const noexcept { + return [v respondsToSelector:@selector(isBackedByList:)] && [v isBackedByList:list]; +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wincomplete-implementation" +@implementation RLMManagedPropertyAccessor +@end +#pragma clang diagnostic pop diff --git a/!main project/Pods/Realm/Realm/RLMAnalytics.mm b/!main project/Pods/Realm/Realm/RLMAnalytics.mm new file mode 100644 index 0000000..b1cd5ac --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMAnalytics.mm @@ -0,0 +1,247 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +// Asynchronously submits build information to Realm if running in an iOS +// simulator or on OS X if a debugger is attached. Does nothing if running on an +// iOS / watchOS device or if a debugger is *not* attached. +// +// To be clear: this does *not* run when your app is in production or on +// your end-user’s devices; it will only run in the simulator or when a debugger +// is attached. +// +// Why are we doing this? In short, because it helps us build a better product +// for you. None of the data personally identifies you, your employer or your +// app, but it *will* help us understand what language you use, what iOS +// versions you target, etc. Having this info will help prioritizing our time, +// adding new features and deprecating old features. Collecting an anonymized +// bundle & anonymized MAC is the only way for us to count actual usage of the +// other metrics accurately. If we don’t have a way to deduplicate the info +// reported, it will be useless, as a single developer building their Swift app +// 10 times would report 10 times more than a single Objective-C developer that +// only builds once, making the data all but useless. +// No one likes sharing data unless it’s necessary, we get it, and we’ve +// debated adding this for a long long time. Since Realm is a free product +// without an email signup, we feel this is a necessary step so we can collect +// relevant data to build a better product for you. If you truly, absolutely +// feel compelled to not send this data back to Realm, then you can set an env +// variable named REALM_DISABLE_ANALYTICS. Since Realm is free we believe +// letting these analytics run is a small price to pay for the product & support +// we give you. +// +// Currently the following information is reported: +// - What version of Realm is being used, and from which language (obj-c or Swift). +// - What version of OS X it's running on (in case Xcode aggressively drops +// support for older versions again, we need to know what we need to support). +// - The minimum iOS/OS X version that the application is targeting (again, to +// help us decide what versions we need to support). +// - An anonymous MAC address and bundle ID to aggregate the other information on. +// - What version of Swift is being used (if applicable). + +#import "RLMAnalytics.hpp" + +#import + +#if TARGET_IPHONE_SIMULATOR || TARGET_OS_MAC || (TARGET_OS_WATCH && TARGET_OS_SIMULATOR) || (TARGET_OS_TV && TARGET_OS_SIMULATOR) +#import "RLMRealm.h" +#import "RLMUtil.hpp" + +#import +#import +#import +#import +#import + +#import + +#ifndef REALM_COCOA_VERSION +#import "RLMVersion.h" +#endif + +#if REALM_ENABLE_SYNC +#import +#endif + +// Declared for RealmSwiftObjectUtil +@interface NSObject (SwiftVersion) ++ (NSString *)swiftVersion; +@end + +// Wrapper for sysctl() that handles the memory management stuff +static auto RLMSysCtl(int *mib, u_int mibSize, size_t *bufferSize) { + std::unique_ptr buffer(nullptr, &free); + + int ret = sysctl(mib, mibSize, nullptr, bufferSize, nullptr, 0); + if (ret != 0) { + return buffer; + } + + buffer.reset(malloc(*bufferSize)); + if (!buffer) { + return buffer; + } + + ret = sysctl(mib, mibSize, buffer.get(), bufferSize, nullptr, 0); + if (ret != 0) { + buffer.reset(); + } + + return buffer; +} + +// Get the version of OS X we're running on (even in the simulator this gives +// the OS X version and not the simulated iOS version) +static NSString *RLMOSVersion() { + std::array mib = {{CTL_KERN, KERN_OSRELEASE}}; + size_t bufferSize; + auto buffer = RLMSysCtl(&mib[0], mib.size(), &bufferSize); + if (!buffer) { + return nil; + } + + return [[NSString alloc] initWithBytesNoCopy:buffer.release() + length:bufferSize - 1 + encoding:NSUTF8StringEncoding + freeWhenDone:YES]; +} + +// Hash the data in the given buffer and convert it to a hex-format string +static NSString *RLMHashData(const void *bytes, size_t length) { + unsigned char buffer[CC_SHA256_DIGEST_LENGTH]; + CC_SHA256(bytes, static_cast(length), buffer); + + char formatted[CC_SHA256_DIGEST_LENGTH * 2 + 1]; + for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i) { + sprintf(formatted + i * 2, "%02x", buffer[i]); + } + + return [[NSString alloc] initWithBytes:formatted + length:CC_SHA256_DIGEST_LENGTH * 2 + encoding:NSUTF8StringEncoding]; +} + +// Returns the hash of the MAC address of the first network adaptor since the +// vendorIdentifier isn't constant between iOS simulators. +static NSString *RLMMACAddress() { + int en0 = static_cast(if_nametoindex("en0")); + if (!en0) { + return nil; + } + + std::array mib = {{CTL_NET, PF_ROUTE, 0, AF_LINK, NET_RT_IFLIST, en0}}; + size_t bufferSize; + auto buffer = RLMSysCtl(&mib[0], mib.size(), &bufferSize); + if (!buffer) { + return nil; + } + + // sockaddr_dl struct is immediately after the if_msghdr struct in the buffer + auto sockaddr = reinterpret_cast(static_cast(buffer.get()) + 1); + auto mac = reinterpret_cast(sockaddr->sdl_data + sockaddr->sdl_nlen); + + return RLMHashData(mac, 6); +} + +static NSDictionary *RLMAnalyticsPayload() { + NSBundle *appBundle = NSBundle.mainBundle; + NSString *hashedBundleID = appBundle.bundleIdentifier; + + // Main bundle isn't always the one of interest (e.g. when running tests + // it's xctest rather than the app's bundle), so look for one with a bundle ID + if (!hashedBundleID) { + for (NSBundle *bundle in NSBundle.allBundles) { + if ((hashedBundleID = bundle.bundleIdentifier)) { + appBundle = bundle; + break; + } + } + } + + // If we found a bundle ID anywhere, hash it as it could contain sensitive + // information (e.g. the name of an unnanounced product) + if (hashedBundleID) { + NSData *data = [hashedBundleID dataUsingEncoding:NSUTF8StringEncoding]; + hashedBundleID = RLMHashData(data.bytes, data.length); + } + + NSString *osVersionString = [[NSProcessInfo processInfo] operatingSystemVersionString]; + Class swiftObjectUtilClass = NSClassFromString(@"RealmSwiftObjectUtil"); + BOOL isSwift = swiftObjectUtilClass != nil; + NSString *swiftVersion = isSwift ? [swiftObjectUtilClass swiftVersion] : @"N/A"; + + static NSString *kUnknownString = @"unknown"; + NSString *hashedMACAddress = RLMMACAddress() ?: kUnknownString; + + return @{ + @"event": @"Run", + @"properties": @{ + // MixPanel properties + @"token": @"ce0fac19508f6c8f20066d345d360fd0", + + // Anonymous identifiers to deduplicate events + @"distinct_id": hashedMACAddress, + @"Anonymized MAC Address": hashedMACAddress, + @"Anonymized Bundle ID": hashedBundleID ?: kUnknownString, + + // Which version of Realm is being used + @"Binding": @"cocoa", + @"Language": isSwift ? @"swift" : @"objc", + @"Realm Version": REALM_COCOA_VERSION, +#if REALM_ENABLE_SYNC + @"Sync Version": @(REALM_SYNC_VER_STRING), +#endif +#if TARGET_OS_WATCH + @"Target OS Type": @"watchos", +#elif TARGET_OS_TV + @"Target OS Type": @"tvos", +#elif TARGET_OS_IPHONE + @"Target OS Type": @"ios", +#else + @"Target OS Type": @"osx", +#endif + @"Swift Version": swiftVersion, + // Current OS version the app is targetting + @"Target OS Version": osVersionString, + // Minimum OS version the app is targetting + @"Target OS Minimum Version": appBundle.infoDictionary[@"MinimumOSVersion"] ?: kUnknownString, + + // Host OS version being built on + @"Host OS Type": @"osx", + @"Host OS Version": RLMOSVersion() ?: kUnknownString, + } + }; +} + +void RLMSendAnalytics() { + if (getenv("REALM_DISABLE_ANALYTICS") || !RLMIsDebuggerAttached() || RLMIsRunningInPlayground()) { + return; + } + + + NSData *payload = [NSJSONSerialization dataWithJSONObject:RLMAnalyticsPayload() options:0 error:nil]; + NSString *url = [NSString stringWithFormat:@"https://api.mixpanel.com/track/?data=%@&ip=1", [payload base64EncodedStringWithOptions:0]]; + + // No error handling or anything because logging errors annoyed people for no + // real benefit, and it's not clear what else we could do + [[NSURLSession.sharedSession dataTaskWithURL:[NSURL URLWithString:url]] resume]; +} + +#else + +void RLMSendAnalytics() {} + +#endif diff --git a/!main project/Pods/Realm/Realm/RLMArray.mm b/!main project/Pods/Realm/Realm/RLMArray.mm new file mode 100644 index 0000000..2262e78 --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMArray.mm @@ -0,0 +1,587 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMArray_Private.hpp" + +#import "RLMObjectSchema.h" +#import "RLMObjectStore.h" +#import "RLMObject_Private.h" +#import "RLMProperty_Private.h" +#import "RLMQueryUtil.hpp" +#import "RLMSchema_Private.h" +#import "RLMSwiftSupport.h" +#import "RLMThreadSafeReference_Private.hpp" +#import "RLMUtil.hpp" + +// See -countByEnumeratingWithState:objects:count +@interface RLMArrayHolder : NSObject { +@public + std::unique_ptr items; +} +@end +@implementation RLMArrayHolder +@end + +@interface RLMArray () +@end + +@implementation RLMArray { +@public + // Backing array when this instance is unmanaged + NSMutableArray *_backingArray; +} + +#pragma mark - Initializers + +- (instancetype)initWithObjectClassName:(__unsafe_unretained NSString *const)objectClassName { + REALM_ASSERT([objectClassName length] > 0); + self = [super init]; + if (self) { + _objectClassName = objectClassName; + _type = RLMPropertyTypeObject; + } + return self; +} + +- (instancetype)initWithObjectType:(RLMPropertyType)type optional:(BOOL)optional { + self = [super init]; + if (self) { + _type = type; + _optional = optional; + } + return self; +} + +#pragma mark - Convenience wrappers used for all RLMArray types + +- (void)addObjects:(id)objects { + for (id obj in objects) { + [self addObject:obj]; + } +} + +- (void)addObject:(id)object { + [self insertObject:object atIndex:self.count]; +} + +- (void)removeLastObject { + NSUInteger count = self.count; + if (count) { + [self removeObjectAtIndex:count-1]; + } +} + +- (id)objectAtIndexedSubscript:(NSUInteger)index { + return [self objectAtIndex:index]; +} + +- (void)setObject:(id)newValue atIndexedSubscript:(NSUInteger)index { + [self replaceObjectAtIndex:index withObject:newValue]; +} + +- (RLMResults *)sortedResultsUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending { + return [self sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:keyPath ascending:ascending]]]; +} + +- (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat, ... { + va_list args; + va_start(args, predicateFormat); + NSUInteger index = [self indexOfObjectWhere:predicateFormat args:args]; + va_end(args); + return index; +} + +- (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat args:(va_list)args { + return [self indexOfObjectWithPredicate:[NSPredicate predicateWithFormat:predicateFormat + arguments:args]]; +} + +#pragma mark - Unmanaged RLMArray implementation + +- (RLMRealm *)realm { + return nil; +} + +- (id)firstObject { + if (self.count) { + return [self objectAtIndex:0]; + } + return nil; +} + +- (id)lastObject { + NSUInteger count = self.count; + if (count) { + return [self objectAtIndex:count-1]; + } + return nil; +} + +- (id)objectAtIndex:(NSUInteger)index { + validateArrayBounds(self, index); + return [_backingArray objectAtIndex:index]; +} + +- (NSUInteger)count { + return _backingArray.count; +} + +- (BOOL)isInvalidated { + return NO; +} + +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state + objects:(__unused __unsafe_unretained id [])buffer + count:(__unused NSUInteger)len { + if (state->state != 0) { + return 0; + } + + // We need to enumerate a copy of the backing array so that it doesn't + // reflect changes made during enumeration. This copy has to be autoreleased + // (since there's nowhere for us to store a strong reference), and uses + // RLMArrayHolder rather than an NSArray because NSArray doesn't guarantee + // that it'll use a single contiguous block of memory, and if it doesn't + // we'd need to forward multiple calls to this method to the same NSArray, + // which would require holding a reference to it somewhere. + __autoreleasing RLMArrayHolder *copy = [[RLMArrayHolder alloc] init]; + copy->items = std::make_unique(self.count); + + NSUInteger i = 0; + for (id object in _backingArray) { + copy->items[i++] = object; + } + + state->itemsPtr = (__unsafe_unretained id *)(void *)copy->items.get(); + // needs to point to something valid, but the whole point of this is so + // that it can't be changed + state->mutationsPtr = state->extra; + state->state = i; + + return i; +} + + +template +static void changeArray(__unsafe_unretained RLMArray *const ar, + NSKeyValueChange kind, dispatch_block_t f, IndexSetFactory&& is) { + if (!ar->_backingArray) { + ar->_backingArray = [NSMutableArray new]; + } + + if (RLMObjectBase *parent = ar->_parentObject) { + NSIndexSet *indexes = is(); + [parent willChange:kind valuesAtIndexes:indexes forKey:ar->_key]; + f(); + [parent didChange:kind valuesAtIndexes:indexes forKey:ar->_key]; + } + else { + f(); + } +} + +static void changeArray(__unsafe_unretained RLMArray *const ar, NSKeyValueChange kind, + NSUInteger index, dispatch_block_t f) { + changeArray(ar, kind, f, [=] { return [NSIndexSet indexSetWithIndex:index]; }); +} + +static void changeArray(__unsafe_unretained RLMArray *const ar, NSKeyValueChange kind, + NSRange range, dispatch_block_t f) { + changeArray(ar, kind, f, [=] { return [NSIndexSet indexSetWithIndexesInRange:range]; }); +} + +static void changeArray(__unsafe_unretained RLMArray *const ar, NSKeyValueChange kind, + NSIndexSet *is, dispatch_block_t f) { + changeArray(ar, kind, f, [=] { return is; }); +} + +void RLMArrayValidateMatchingObjectType(__unsafe_unretained RLMArray *const array, + __unsafe_unretained id const value) { + if (!value && !array->_optional) { + @throw RLMException(@"Invalid nil value for array of '%@'.", + array->_objectClassName ?: RLMTypeToString(array->_type)); + } + if (array->_type != RLMPropertyTypeObject) { + if (!RLMValidateValue(value, array->_type, array->_optional, false, nil)) { + @throw RLMException(@"Invalid value '%@' of type '%@' for expected type '%@%s'.", + value, [value class], RLMTypeToString(array->_type), + array->_optional ? "?" : ""); + } + return; + } + + auto object = RLMDynamicCast(value); + if (!object) { + return; + } + if (!object->_objectSchema) { + @throw RLMException(@"Object cannot be inserted unless the schema is initialized. " + "This can happen if you try to insert objects into a RLMArray / List from a default value or from an overriden unmanaged initializer (`init()`)."); + } + if (![array->_objectClassName isEqualToString:object->_objectSchema.className]) { + @throw RLMException(@"Object of type '%@' does not match RLMArray type '%@'.", + object->_objectSchema.className, array->_objectClassName); + } +} + +static void validateArrayBounds(__unsafe_unretained RLMArray *const ar, + NSUInteger index, bool allowOnePastEnd=false) { + NSUInteger max = ar->_backingArray.count + allowOnePastEnd; + if (index >= max) { + @throw RLMException(@"Index %llu is out of bounds (must be less than %llu).", + (unsigned long long)index, (unsigned long long)max); + } +} + +- (void)addObjectsFromArray:(NSArray *)array { + for (id obj in array) { + RLMArrayValidateMatchingObjectType(self, obj); + } + changeArray(self, NSKeyValueChangeInsertion, NSMakeRange(_backingArray.count, array.count), ^{ + [_backingArray addObjectsFromArray:array]; + }); +} + +- (void)insertObject:(id)anObject atIndex:(NSUInteger)index { + RLMArrayValidateMatchingObjectType(self, anObject); + validateArrayBounds(self, index, true); + changeArray(self, NSKeyValueChangeInsertion, index, ^{ + [_backingArray insertObject:anObject atIndex:index]; + }); +} + +- (void)insertObjects:(id)objects atIndexes:(NSIndexSet *)indexes { + changeArray(self, NSKeyValueChangeInsertion, indexes, ^{ + NSUInteger currentIndex = [indexes firstIndex]; + for (RLMObject *obj in objects) { + RLMArrayValidateMatchingObjectType(self, obj); + [_backingArray insertObject:obj atIndex:currentIndex]; + currentIndex = [indexes indexGreaterThanIndex:currentIndex]; + } + }); +} + +- (void)removeObjectAtIndex:(NSUInteger)index { + validateArrayBounds(self, index); + changeArray(self, NSKeyValueChangeRemoval, index, ^{ + [_backingArray removeObjectAtIndex:index]; + }); +} + +- (void)removeObjectsAtIndexes:(NSIndexSet *)indexes { + changeArray(self, NSKeyValueChangeRemoval, indexes, ^{ + [_backingArray removeObjectsAtIndexes:indexes]; + }); +} + +- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject { + RLMArrayValidateMatchingObjectType(self, anObject); + validateArrayBounds(self, index); + changeArray(self, NSKeyValueChangeReplacement, index, ^{ + [_backingArray replaceObjectAtIndex:index withObject:anObject]; + }); +} + +- (void)moveObjectAtIndex:(NSUInteger)sourceIndex toIndex:(NSUInteger)destinationIndex { + validateArrayBounds(self, sourceIndex); + validateArrayBounds(self, destinationIndex); + id original = _backingArray[sourceIndex]; + + auto start = std::min(sourceIndex, destinationIndex); + auto len = std::max(sourceIndex, destinationIndex) - start + 1; + changeArray(self, NSKeyValueChangeReplacement, {start, len}, ^{ + [_backingArray removeObjectAtIndex:sourceIndex]; + [_backingArray insertObject:original atIndex:destinationIndex]; + }); +} + +- (void)exchangeObjectAtIndex:(NSUInteger)index1 withObjectAtIndex:(NSUInteger)index2 { + validateArrayBounds(self, index1); + validateArrayBounds(self, index2); + + changeArray(self, NSKeyValueChangeReplacement, ^{ + [_backingArray exchangeObjectAtIndex:index1 withObjectAtIndex:index2]; + }, [=] { + NSMutableIndexSet *set = [[NSMutableIndexSet alloc] initWithIndex:index1]; + [set addIndex:index2]; + return set; + }); +} + +- (NSUInteger)indexOfObject:(id)object { + RLMArrayValidateMatchingObjectType(self, object); + if (!_backingArray) { + return NSNotFound; + } + if (_type != RLMPropertyTypeObject) { + return [_backingArray indexOfObject:object]; + } + + NSUInteger index = 0; + for (RLMObjectBase *cmp in _backingArray) { + if (RLMObjectBaseAreEqual(object, cmp)) { + return index; + } + index++; + } + return NSNotFound; +} + +- (void)removeAllObjects { + changeArray(self, NSKeyValueChangeRemoval, NSMakeRange(0, _backingArray.count), ^{ + [_backingArray removeAllObjects]; + }); +} + +- (RLMResults *)objectsWhere:(NSString *)predicateFormat, ... { + va_list args; + va_start(args, predicateFormat); + RLMResults *results = [self objectsWhere:predicateFormat args:args]; + va_end(args); + return results; +} + +- (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args { + return [self objectsWithPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]]; +} + +static bool canAggregate(RLMPropertyType type, bool allowDate) { + switch (type) { + case RLMPropertyTypeInt: + case RLMPropertyTypeFloat: + case RLMPropertyTypeDouble: + return true; + case RLMPropertyTypeDate: + return allowDate; + default: + return false; + } +} + +- (RLMPropertyType)typeForProperty:(NSString *)propertyName { + if ([propertyName isEqualToString:@"self"]) { + return _type; + } + + RLMObjectSchema *objectSchema; + if (_backingArray.count) { + objectSchema = [_backingArray[0] objectSchema]; + } + else { + objectSchema = [RLMSchema.partialPrivateSharedSchema schemaForClassName:_objectClassName]; + } + + return RLMValidatedProperty(objectSchema, propertyName).type; +} + +- (id)aggregateProperty:(NSString *)key operation:(NSString *)op method:(SEL)sel { + // Although delegating to valueForKeyPath: here would allow to support + // nested key paths as well, limiting functionality gives consistency + // between unmanaged and managed arrays. + if ([key rangeOfString:@"."].location != NSNotFound) { + @throw RLMException(@"Nested key paths are not supported yet for KVC collection operators."); + } + + bool allowDate = false; + bool sum = false; + if ([op isEqualToString:@"@min"] || [op isEqualToString:@"@max"]) { + allowDate = true; + } + else if ([op isEqualToString:@"@sum"]) { + sum = true; + } + else if (![op isEqualToString:@"@avg"]) { + // Just delegate to NSArray for all other operators + return [_backingArray valueForKeyPath:[op stringByAppendingPathExtension:key]]; + } + + RLMPropertyType type = [self typeForProperty:key]; + if (!canAggregate(type, allowDate)) { + NSString *method = sel ? NSStringFromSelector(sel) : op; + if (_type == RLMPropertyTypeObject) { + @throw RLMException(@"%@: is not supported for %@ property '%@.%@'", + method, RLMTypeToString(type), _objectClassName, key); + } + else { + @throw RLMException(@"%@ is not supported for %@%s array", + method, RLMTypeToString(_type), _optional ? "?" : ""); + } + } + + NSArray *values = [key isEqualToString:@"self"] ? _backingArray : [_backingArray valueForKey:key]; + if (_optional) { + // Filter out NSNull values to match our behavior on managed arrays + NSIndexSet *nonnull = [values indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger, BOOL *) { + return obj != NSNull.null; + }]; + if (nonnull.count < values.count) { + values = [values objectsAtIndexes:nonnull]; + } + } + id result = [values valueForKeyPath:[op stringByAppendingString:@".self"]]; + return sum && !result ? @0 : result; +} + +- (id)valueForKeyPath:(NSString *)keyPath { + if ([keyPath characterAtIndex:0] != '@') { + return _backingArray ? [_backingArray valueForKeyPath:keyPath] : [super valueForKeyPath:keyPath]; + } + + if (!_backingArray) { + _backingArray = [NSMutableArray new]; + } + + NSUInteger dot = [keyPath rangeOfString:@"."].location; + if (dot == NSNotFound) { + return [_backingArray valueForKeyPath:keyPath]; + } + + NSString *op = [keyPath substringToIndex:dot]; + NSString *key = [keyPath substringFromIndex:dot + 1]; + return [self aggregateProperty:key operation:op method:nil]; +} + +- (id)valueForKey:(NSString *)key { + if ([key isEqualToString:RLMInvalidatedKey]) { + return @NO; // Unmanaged arrays are never invalidated + } + if (!_backingArray) { + _backingArray = [NSMutableArray new]; + } + return [_backingArray valueForKey:key]; +} + +- (void)setValue:(id)value forKey:(NSString *)key { + if ([key isEqualToString:@"self"]) { + RLMArrayValidateMatchingObjectType(self, value); + for (NSUInteger i = 0, count = _backingArray.count; i < count; ++i) { + _backingArray[i] = value; + } + return; + } + else if (_type == RLMPropertyTypeObject) { + [_backingArray setValue:value forKey:key]; + } + else { + [self setValue:value forUndefinedKey:key]; + } +} + +- (id)minOfProperty:(NSString *)property { + return [self aggregateProperty:property operation:@"@min" method:_cmd]; +} + +- (id)maxOfProperty:(NSString *)property { + return [self aggregateProperty:property operation:@"@max" method:_cmd]; +} + +- (id)sumOfProperty:(NSString *)property { + return [self aggregateProperty:property operation:@"@sum" method:_cmd]; +} + +- (id)averageOfProperty:(NSString *)property { + return [self aggregateProperty:property operation:@"@avg" method:_cmd]; +} + +- (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate { + if (!_backingArray) { + return NSNotFound; + } + return [_backingArray indexOfObjectPassingTest:^BOOL(id obj, NSUInteger, BOOL *) { + return [predicate evaluateWithObject:obj]; + }]; +} + +- (NSArray *)objectsAtIndexes:(NSIndexSet *)indexes { + if (!_backingArray) { + _backingArray = [NSMutableArray new]; + } + return [_backingArray objectsAtIndexes:indexes]; +} + +- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath + options:(NSKeyValueObservingOptions)options context:(void *)context { + RLMValidateArrayObservationKey(keyPath, self); + [super addObserver:observer forKeyPath:keyPath options:options context:context]; +} + +#pragma mark - Methods unsupported on unmanaged RLMArray instances + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-parameter" + +- (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate { + @throw RLMException(@"This method may only be called on RLMArray instances retrieved from an RLMRealm"); +} + +- (RLMResults *)sortedResultsUsingDescriptors:(NSArray *)properties { + @throw RLMException(@"This method may only be called on RLMArray instances retrieved from an RLMRealm"); +} + +// The compiler complains about the method's argument type not matching due to +// it not having the generic type attached, but it doesn't seem to be possible +// to actually include the generic type +// http://www.openradar.me/radar?id=6135653276319744 +#pragma clang diagnostic ignored "-Wmismatched-parameter-types" +- (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, RLMCollectionChange *, NSError *))block { + @throw RLMException(@"This method may only be called on RLMArray instances retrieved from an RLMRealm"); +} + +#pragma mark - Thread Confined Protocol Conformance + +- (std::unique_ptr)makeThreadSafeReference { + REALM_TERMINATE("Unexpected handover of unmanaged `RLMArray`"); +} + +- (id)objectiveCMetadata { + REALM_TERMINATE("Unexpected handover of unmanaged `RLMArray`"); +} + ++ (instancetype)objectWithThreadSafeReference:(std::unique_ptr)reference + metadata:(id)metadata + realm:(RLMRealm *)realm { + REALM_TERMINATE("Unexpected handover of unmanaged `RLMArray`"); +} + +#pragma clang diagnostic pop // unused parameter warning + +#pragma mark - Superclass Overrides + +- (NSString *)description { + return [self descriptionWithMaxDepth:RLMDescriptionMaxDepth]; +} + +- (NSString *)descriptionWithMaxDepth:(NSUInteger)depth { + return RLMDescriptionWithMaxDepth(@"RLMArray", self, depth); +} +@end + +@implementation RLMSortDescriptor + ++ (instancetype)sortDescriptorWithKeyPath:(NSString *)keyPath ascending:(BOOL)ascending { + RLMSortDescriptor *desc = [[RLMSortDescriptor alloc] init]; + desc->_keyPath = keyPath; + desc->_ascending = ascending; + return desc; +} + +- (instancetype)reversedSortDescriptor { + return [self.class sortDescriptorWithKeyPath:_keyPath ascending:!_ascending]; +} + +@end diff --git a/!main project/Pods/Realm/Realm/RLMClassInfo.mm b/!main project/Pods/Realm/Realm/RLMClassInfo.mm new file mode 100644 index 0000000..52b44f8 --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMClassInfo.mm @@ -0,0 +1,131 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMClassInfo.hpp" + +#import "RLMRealm_Private.hpp" +#import "RLMObjectSchema_Private.h" +#import "RLMSchema.h" +#import "RLMProperty_Private.h" +#import "RLMQueryUtil.hpp" +#import "RLMUtil.hpp" + +#import "object_schema.hpp" +#import "object_store.hpp" +#import "schema.hpp" +#import "shared_realm.hpp" + +#import + +using namespace realm; + +RLMClassInfo::RLMClassInfo(RLMRealm *realm, RLMObjectSchema *rlmObjectSchema, + const realm::ObjectSchema *objectSchema) +: realm(realm), rlmObjectSchema(rlmObjectSchema), objectSchema(objectSchema) { } + +realm::Table *RLMClassInfo::table() const { + if (!m_table) { + m_table = ObjectStore::table_for_object_type(realm.group, objectSchema->name).get(); + } + return m_table; +} + +RLMProperty *RLMClassInfo::propertyForTableColumn(NSUInteger col) const noexcept { + auto const& props = objectSchema->persisted_properties; + for (size_t i = 0; i < props.size(); ++i) { + if (props[i].table_column == col) { + return rlmObjectSchema.properties[i]; + } + } + return nil; +} + +RLMProperty *RLMClassInfo::propertyForPrimaryKey() const noexcept { + return rlmObjectSchema.primaryKeyProperty; +} + +NSUInteger RLMClassInfo::tableColumn(NSString *propertyName) const { + return tableColumn(RLMValidatedProperty(rlmObjectSchema, propertyName)); +} + +NSUInteger RLMClassInfo::tableColumn(RLMProperty *property) const { + return objectSchema->persisted_properties[property.index].table_column; +} + +RLMClassInfo &RLMClassInfo::linkTargetType(size_t propertyIndex) { + if (propertyIndex < m_linkTargets.size() && m_linkTargets[propertyIndex]) { + return *m_linkTargets[propertyIndex]; + } + if (m_linkTargets.size() <= propertyIndex) { + m_linkTargets.resize(propertyIndex + 1); + } + m_linkTargets[propertyIndex] = &realm->_info[rlmObjectSchema.properties[propertyIndex].objectClassName]; + return *m_linkTargets[propertyIndex]; +} + +RLMClassInfo &RLMClassInfo::linkTargetType(realm::Property const& property) { + REALM_ASSERT(property.type == PropertyType::Object); + return linkTargetType(&property - &objectSchema->persisted_properties[0]); +} + +RLMSchemaInfo::impl::iterator RLMSchemaInfo::begin() noexcept { return m_objects.begin(); } +RLMSchemaInfo::impl::iterator RLMSchemaInfo::end() noexcept { return m_objects.end(); } +RLMSchemaInfo::impl::const_iterator RLMSchemaInfo::begin() const noexcept { return m_objects.begin(); } +RLMSchemaInfo::impl::const_iterator RLMSchemaInfo::end() const noexcept { return m_objects.end(); } + +RLMClassInfo& RLMSchemaInfo::operator[](NSString *name) { + auto it = m_objects.find(name); + if (it == m_objects.end()) { + @throw RLMException(@"Object type '%@' is not managed by the Realm. " + @"If using a custom `objectClasses` / `objectTypes` array in your configuration, " + @"add `%@` to the list of `objectClasses` / `objectTypes`.", + name, name); + } + return *&it->second; +} + +RLMSchemaInfo::RLMSchemaInfo(RLMRealm *realm) { + RLMSchema *rlmSchema = realm.schema; + realm::Schema const& schema = realm->_realm->schema(); + // rlmSchema can be larger due to multiple classes backed by one table + REALM_ASSERT(rlmSchema.objectSchema.count >= schema.size()); + + m_objects.reserve(schema.size()); + for (RLMObjectSchema *rlmObjectSchema in rlmSchema.objectSchema) { + m_objects.emplace(std::piecewise_construct, + std::forward_as_tuple(rlmObjectSchema.className), + std::forward_as_tuple(realm, rlmObjectSchema, + &*schema.find(rlmObjectSchema.objectName.UTF8String))); + } +} + +RLMSchemaInfo RLMSchemaInfo::clone(realm::Schema const& source_schema, + __unsafe_unretained RLMRealm *const target_realm) { + RLMSchemaInfo info; + info.m_objects.reserve(m_objects.size()); + + auto& schema = target_realm->_realm->schema(); + for (auto& pair : m_objects) { + size_t idx = pair.second.objectSchema - &*source_schema.begin(); + info.m_objects.emplace(std::piecewise_construct, + std::forward_as_tuple(pair.first), + std::forward_as_tuple(target_realm, pair.second.rlmObjectSchema, + &*schema.begin() + idx)); + } + return info; +} diff --git a/!main project/Pods/Realm/Realm/RLMCollection.mm b/!main project/Pods/Realm/Realm/RLMCollection.mm new file mode 100644 index 0000000..b7e68ae --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMCollection.mm @@ -0,0 +1,419 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMCollection_Private.hpp" + +#import "RLMAccessor.hpp" +#import "RLMArray_Private.hpp" +#import "RLMListBase.h" +#import "RLMObjectSchema_Private.hpp" +#import "RLMObjectStore.h" +#import "RLMObject_Private.hpp" +#import "RLMProperty_Private.h" + +#import "collection_notifications.hpp" +#import "list.hpp" +#import "results.hpp" + +static const int RLMEnumerationBufferSize = 16; + +@implementation RLMFastEnumerator { + // The buffer supplied by fast enumeration does not retain the objects given + // to it, but because we create objects on-demand and don't want them + // autoreleased (a table can have more rows than the device has memory for + // accessor objects) we need a thing to retain them. + id _strongBuffer[RLMEnumerationBufferSize]; + + RLMRealm *_realm; + RLMClassInfo *_info; + + // A pointer to either _snapshot or a Results from the source collection, + // to avoid having to copy the Results when not in a write transaction + realm::Results *_results; + realm::Results _snapshot; + + // A strong reference to the collection being enumerated to ensure it stays + // alive when we're holding a pointer to a member in it + id _collection; +} + +- (instancetype)initWithList:(realm::List&)list + collection:(id)collection + classInfo:(RLMClassInfo&)info +{ + self = [super init]; + if (self) { + _info = &info; + _realm = _info->realm; + if (_realm.inWriteTransaction) { + _snapshot = list.snapshot(); + } + else { + _snapshot = list.as_results(); + _collection = collection; + [_realm registerEnumerator:self]; + } + _results = &_snapshot; + } + return self; +} + +- (instancetype)initWithResults:(realm::Results&)results + collection:(id)collection + classInfo:(RLMClassInfo&)info +{ + self = [super init]; + if (self) { + _info = &info; + _realm = _info->realm; + if (_realm.inWriteTransaction) { + _snapshot = results.snapshot(); + _results = &_snapshot; + } + else { + _results = &results; + _collection = collection; + [_realm registerEnumerator:self]; + } + } + return self; +} + +- (void)dealloc { + if (_collection) { + [_realm unregisterEnumerator:self]; + } +} + +- (void)detach { + _snapshot = _results->snapshot(); + _results = &_snapshot; + _collection = nil; +} + +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state + count:(NSUInteger)len { + [_realm verifyThread]; + if (!_results->is_valid()) { + @throw RLMException(@"Collection is no longer valid"); + } + // The fast enumeration buffer size is currently a hardcoded number in the + // compiler so this can't actually happen, but just in case it changes in + // the future... + if (len > RLMEnumerationBufferSize) { + len = RLMEnumerationBufferSize; + } + + NSUInteger batchCount = 0, count = state->extra[1]; + + @autoreleasepool { + RLMAccessorContext ctx(*_info); + for (NSUInteger index = state->state; index < count && batchCount < len; ++index) { + _strongBuffer[batchCount] = _results->get(ctx, index); + batchCount++; + } + } + + for (NSUInteger i = batchCount; i < len; ++i) { + _strongBuffer[i] = nil; + } + + if (batchCount == 0) { + // Release our data if we're done, as we're autoreleased and so may + // stick around for a while + if (_collection) { + _collection = nil; + [_realm unregisterEnumerator:self]; + } + _snapshot = {}; + } + + state->itemsPtr = (__unsafe_unretained id *)(void *)_strongBuffer; + state->state += batchCount; + state->mutationsPtr = state->extra+1; + + return batchCount; +} +@end + +NSUInteger RLMFastEnumerate(NSFastEnumerationState *state, NSUInteger len, id collection) { + __autoreleasing RLMFastEnumerator *enumerator; + if (state->state == 0) { + enumerator = collection.fastEnumerator; + state->extra[0] = (long)enumerator; + state->extra[1] = collection.count; + } + else { + enumerator = (__bridge id)(void *)state->extra[0]; + } + + return [enumerator countByEnumeratingWithState:state count:len]; +} + +template +NSArray *RLMCollectionValueForKey(Collection& collection, NSString *key, RLMClassInfo& info) { + size_t count = collection.size(); + if (count == 0) { + return @[]; + } + + NSMutableArray *array = [NSMutableArray arrayWithCapacity:count]; + if ([key isEqualToString:@"self"]) { + RLMAccessorContext context(info); + for (size_t i = 0; i < count; ++i) { + [array addObject:collection.get(context, i) ?: NSNull.null]; + } + return array; + } + + if (collection.get_type() != realm::PropertyType::Object) { + RLMAccessorContext context(info); + for (size_t i = 0; i < count; ++i) { + [array addObject:[collection.get(context, i) valueForKey:key] ?: NSNull.null]; + } + return array; + } + + RLMObject *accessor = RLMCreateManagedAccessor(info.rlmObjectSchema.accessorClass, &info); + + // List properties need to be handled specially since we need to create a + // new List each time + if (info.rlmObjectSchema.isSwiftClass) { + auto prop = info.rlmObjectSchema[key]; + if (prop && prop.array && prop.swiftIvar) { + // Grab the actual class for the generic List from an instance of it + // so that we can make instances of the List without creating a new + // object accessor each time + Class cls = [object_getIvar(accessor, prop.swiftIvar) class]; + RLMAccessorContext context(info); + for (size_t i = 0; i < count; ++i) { + RLMListBase *list = [[cls alloc] init]; + list._rlmArray = [[RLMManagedArray alloc] initWithList:realm::List(info.realm->_realm, *info.table(), + info.tableColumn(prop), + collection.get(i).get_index()) + parentInfo:&info + property:prop]; + [array addObject:list]; + } + return array; + } + } + + for (size_t i = 0; i < count; i++) { + accessor->_row = collection.get(i); + RLMInitializeSwiftAccessorGenerics(accessor); + [array addObject:[accessor valueForKey:key] ?: NSNull.null]; + } + return array; +} + +template NSArray *RLMCollectionValueForKey(realm::Results&, NSString *, RLMClassInfo&); +template NSArray *RLMCollectionValueForKey(realm::List&, NSString *, RLMClassInfo&); + +void RLMCollectionSetValueForKey(id collection, NSString *key, id value) { + realm::TableView tv = [collection tableView]; + if (tv.size() == 0) { + return; + } + + RLMClassInfo *info = collection.objectInfo; + RLMObject *accessor = RLMCreateManagedAccessor(info->rlmObjectSchema.accessorClass, info); + for (size_t i = 0; i < tv.size(); i++) { + accessor->_row = tv[i]; + RLMInitializeSwiftAccessorGenerics(accessor); + [accessor setValue:value forKey:key]; + } +} + +NSString *RLMDescriptionWithMaxDepth(NSString *name, + id collection, + NSUInteger depth) { + if (depth == 0) { + return @""; + } + + const NSUInteger maxObjects = 100; + auto str = [NSMutableString stringWithFormat:@"%@<%@> <%p> (\n", name, + [collection objectClassName] ?: RLMTypeToString([collection type]), + (void *)collection]; + size_t index = 0, skipped = 0; + for (id obj in collection) { + NSString *sub; + if ([obj respondsToSelector:@selector(descriptionWithMaxDepth:)]) { + sub = [obj descriptionWithMaxDepth:depth - 1]; + } + else { + sub = [obj description]; + } + + // Indent child objects + NSString *objDescription = [sub stringByReplacingOccurrencesOfString:@"\n" + withString:@"\n\t"]; + [str appendFormat:@"\t[%zu] %@,\n", index++, objDescription]; + if (index >= maxObjects) { + skipped = collection.count - maxObjects; + break; + } + } + + // Remove last comma and newline characters + if (collection.count > 0) { + [str deleteCharactersInRange:NSMakeRange(str.length-2, 2)]; + } + if (skipped) { + [str appendFormat:@"\n\t... %zu objects skipped.", skipped]; + } + [str appendFormat:@"\n)"]; + return str; +} + +std::vector> RLMSortDescriptorsToKeypathArray(NSArray *properties) { + std::vector> keypaths; + keypaths.reserve(properties.count); + for (RLMSortDescriptor *desc in properties) { + if ([desc.keyPath rangeOfString:@"@"].location != NSNotFound) { + @throw RLMException(@"Cannot sort on key path '%@': KVC collection operators are not supported.", desc.keyPath); + } + keypaths.push_back({desc.keyPath.UTF8String, desc.ascending}); + } + return keypaths; +} + +@implementation RLMCancellationToken { + realm::NotificationToken _token; + __unsafe_unretained RLMRealm *_realm; +} +- (instancetype)initWithToken:(realm::NotificationToken)token realm:(RLMRealm *)realm { + self = [super init]; + if (self) { + _token = std::move(token); + _realm = realm; + } + return self; +} + +- (RLMRealm *)realm { + return _realm; +} + +- (void)suppressNextNotification { + _token.suppress_next(); +} + +- (void)invalidate { + _token = {}; +} + +@end + +@implementation RLMCollectionChange { + realm::CollectionChangeSet _indices; +} + +- (instancetype)initWithChanges:(realm::CollectionChangeSet)indices { + self = [super init]; + if (self) { + _indices = std::move(indices); + } + return self; +} + +static NSArray *toArray(realm::IndexSet const& set) { + NSMutableArray *ret = [NSMutableArray new]; + for (auto index : set.as_indexes()) { + [ret addObject:@(index)]; + } + return ret; +} + +- (NSArray *)insertions { + return toArray(_indices.insertions); +} + +- (NSArray *)deletions { + return toArray(_indices.deletions); +} + +- (NSArray *)modifications { + return toArray(_indices.modifications); +} + +static NSArray *toIndexPathArray(realm::IndexSet const& set, NSUInteger section) { + NSMutableArray *ret = [NSMutableArray new]; + NSUInteger path[2] = {section, 0}; + for (auto index : set.as_indexes()) { + path[1] = index; + [ret addObject:[NSIndexPath indexPathWithIndexes:path length:2]]; + } + return ret; +} + +- (NSArray *)deletionsInSection:(NSUInteger)section { + return toIndexPathArray(_indices.deletions, section); +} + +- (NSArray *)insertionsInSection:(NSUInteger)section { + return toIndexPathArray(_indices.insertions, section); + +} + +- (NSArray *)modificationsInSection:(NSUInteger)section { + return toIndexPathArray(_indices.modifications, section); + +} +@end + +template +RLMNotificationToken *RLMAddNotificationBlock(id objcCollection, + Collection& collection, + void (^block)(id, RLMCollectionChange *, NSError *), + bool suppressInitialChange) { + auto skip = suppressInitialChange ? std::make_shared(true) : nullptr; + auto cb = [=, &collection](realm::CollectionChangeSet const& changes, + std::exception_ptr err) { + if (err) { + try { + rethrow_exception(err); + } + catch (...) { + NSError *error = nil; + RLMRealmTranslateException(&error); + block(nil, nil, error); + return; + } + } + + if (skip && *skip) { + *skip = false; + block(objcCollection, nil, nil); + } + else if (changes.empty()) { + block(objcCollection, nil, nil); + } + else { + block(objcCollection, [[RLMCollectionChange alloc] initWithChanges:changes], nil); + } + }; + + return [[RLMCancellationToken alloc] initWithToken:collection.add_notification_callback(cb) + realm:(RLMRealm *)[objcCollection realm]]; +} + +// Explicitly instantiate the templated function for the two types we'll use it on +template RLMNotificationToken *RLMAddNotificationBlock(id, realm::List&, void (^)(id, RLMCollectionChange *, NSError *), bool); +template RLMNotificationToken *RLMAddNotificationBlock(id, realm::Results&, void (^)(id, RLMCollectionChange *, NSError *), bool); diff --git a/!main project/Pods/Realm/Realm/RLMConstants.m b/!main project/Pods/Realm/Realm/RLMConstants.m new file mode 100644 index 0000000..b45638f --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMConstants.m @@ -0,0 +1,36 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +RLMNotification const RLMRealmRefreshRequiredNotification = @"RLMRealmRefreshRequiredNotification"; +RLMNotification const RLMRealmDidChangeNotification = @"RLMRealmDidChangeNotification"; + +NSString * const RLMErrorDomain = @"io.realm"; + +NSString * const RLMUnknownSystemErrorDomain = @"io.realm.unknown"; + +NSString * const RLMExceptionName = @"RLMException"; + +NSString * const RLMRealmVersionKey = @"RLMRealmVersion"; + +NSString * const RLMRealmCoreVersionKey = @"RLMRealmCoreVersion"; + +NSString * const RLMInvalidatedKey = @"invalidated"; + +NSString * const RLMBackupRealmConfigurationErrorKey = @"RLMBackupRealmConfiguration"; diff --git a/!main project/Pods/Realm/Realm/RLMJSONModels.m b/!main project/Pods/Realm/Realm/RLMJSONModels.m new file mode 100644 index 0000000..8934d38 --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMJSONModels.m @@ -0,0 +1,233 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2017 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMJSONModels.h" +#import "RLMSyncUtil_Private.h" +#import "RLMSyncUser.h" + +#pragma mark - Constants + +static const NSString *const kRLMSyncAccessTokenKey = @"access_token"; +static const NSString *const kRLMSyncAccountsKey = @"accounts"; +static const NSString *const kRLMSyncErrorCodeKey = @"code"; +static const NSString *const kRLMSyncExpiresKey = @"expires"; +static const NSString *const kRLMSyncErrorHintKey = @"hint"; +static const NSString *const kRLMSyncIdKey = @"id"; +static const NSString *const kRLMSyncKeyKey = @"key"; +static const NSString *const kRLMSyncMetadataKey = @"metadata"; +static const NSString *const kRLMSyncRefreshTokenKey = @"refresh_token"; +static const NSString *const kRLMSyncErrorStatusKey = @"status"; +static const NSString *const kRLMSyncErrorTitleKey = @"title"; +static const NSString *const kRLMSyncTokenDataKey = @"token_data"; +static const NSString *const kRLMSyncUserKey = @"user"; +static const NSString *const kRLMSyncValueKey = @"value"; + +#pragma mark - RLMTokenDataModel + +@interface RLMTokenDataModel () + +@property (nonatomic, readwrite) NSString *identity; +@property (nonatomic, readwrite) NSString *appID; +@property (nonatomic, readwrite) NSString *path; +@property (nonatomic, readwrite) NSTimeInterval expires; +@property (nonatomic, readwrite) BOOL isAdmin; + +@end + +@implementation RLMTokenDataModel + +- (instancetype)initWithDictionary:(NSDictionary *)jsonDictionary { + if (self = [super init]) { + self.isAdmin = NO; + RLM_SYNC_PARSE_STRING_OR_ABORT(jsonDictionary, kRLMSyncIdentityKey, identity); + RLM_SYNC_PARSE_OPTIONAL_STRING(jsonDictionary, kRLMSyncAppIDKey, appID); + RLM_SYNC_PARSE_OPTIONAL_STRING(jsonDictionary, kRLMSyncPathKey, path); + RLM_SYNC_PARSE_OPTIONAL_BOOL(jsonDictionary, kRLMSyncIsAdminKey, isAdmin); + RLM_SYNC_PARSE_DOUBLE_OR_ABORT(jsonDictionary, kRLMSyncExpiresKey, expires); + return self; + } + return nil; +} + +@end + +#pragma mark - RLMTokenModel + +@interface RLMTokenModel () + +@property (nonatomic, readwrite) NSString *token; +@property (nonatomic, nullable, readwrite) NSString *path; +@property (nonatomic, readwrite) RLMTokenDataModel *tokenData; + +@end + +@implementation RLMTokenModel + +- (instancetype)initWithDictionary:(NSDictionary *)jsonDictionary { + if (self = [super init]) { + RLM_SYNC_PARSE_STRING_OR_ABORT(jsonDictionary, kRLMSyncTokenKey, token); + RLM_SYNC_PARSE_OPTIONAL_STRING(jsonDictionary, kRLMSyncPathKey, path); + RLM_SYNC_PARSE_MODEL_OR_ABORT(jsonDictionary, kRLMSyncTokenDataKey, RLMTokenDataModel, tokenData); + return self; + } + return nil; +} + +@end + +#pragma mark - RLMAuthResponseModel + +@interface RLMAuthResponseModel () + +@property (nonatomic, readwrite) RLMTokenModel *accessToken; +@property (nonatomic, readwrite) RLMTokenModel *refreshToken; +@property (nonatomic, readwrite) NSString *urlPrefix; + +@end + +@implementation RLMAuthResponseModel + +- (instancetype)initWithDictionary:(NSDictionary *)jsonDictionary + requireAccessToken:(BOOL)requireAccessToken + requireRefreshToken:(BOOL)requireRefreshToken { + if (self = [super init]) { + // Get the access token. + if (requireAccessToken) { + RLM_SYNC_PARSE_MODEL_OR_ABORT(jsonDictionary, kRLMSyncAccessTokenKey, RLMTokenModel, accessToken); + } else { + RLM_SYNC_PARSE_OPTIONAL_MODEL(jsonDictionary, kRLMSyncAccessTokenKey, RLMTokenModel, accessToken); + } + // Get the refresh token. + if (requireRefreshToken) { + RLM_SYNC_PARSE_MODEL_OR_ABORT(jsonDictionary, kRLMSyncRefreshTokenKey, RLMTokenModel, refreshToken); + } else { + RLM_SYNC_PARSE_OPTIONAL_MODEL(jsonDictionary, kRLMSyncRefreshTokenKey, RLMTokenModel, refreshToken); + } + self.urlPrefix = jsonDictionary[@"sync_worker"][@"path"]; + return self; + } + return nil; +} + +@end + +#pragma mark - RLMUserInfoResponseModel + +@interface RLMSyncUserAccountInfo () +@property (nonatomic, readwrite) NSString *provider; +@property (nonatomic, readwrite) NSString *providerUserIdentity; +@end + +@implementation RLMSyncUserAccountInfo + +- (instancetype)initWithDictionary:(NSDictionary *)jsonDictionary { + if (self = [super init]) { + RLM_SYNC_PARSE_STRING_OR_ABORT(jsonDictionary, kRLMSyncProviderKey, provider); + RLM_SYNC_PARSE_STRING_OR_ABORT(jsonDictionary, kRLMSyncProviderIDKey, providerUserIdentity); + return self; + } + return nil; +} + +@end + +@interface RLMUserResponseModel () + +@property (nonatomic, readwrite) NSString *identity; +@property (nonatomic, readwrite) NSArray *accounts; +@property (nonatomic, readwrite) NSDictionary *metadata; +@property (nonatomic, readwrite) BOOL isAdmin; + +@end + +@implementation RLMUserResponseModel + +- (void)parseMetadataFromJSON:(NSDictionary *)jsonDictionary { + NSMutableDictionary *buffer = [NSMutableDictionary dictionary]; + NSArray *metadataArray = jsonDictionary[kRLMSyncMetadataKey]; + if (![metadataArray isKindOfClass:[NSArray class]]) { + self.metadata = @{}; + return; + } + for (NSDictionary *object in metadataArray) { + if (![object isKindOfClass:[NSDictionary class]]) { + continue; + } + NSString *key = object[kRLMSyncKeyKey]; + NSString *value = object[kRLMSyncValueKey]; + if (!key || !value) { + continue; + } + buffer[key] = value; + } + self.metadata = [buffer copy]; +} + +- (instancetype)initWithDictionary:(NSDictionary *)jsonDictionary { + if (self = [super init]) { + self.isAdmin = NO; + RLM_SYNC_PARSE_STRING_OR_ABORT(jsonDictionary, kRLMSyncUserIDKey, identity); + RLM_SYNC_PARSE_OPTIONAL_BOOL(jsonDictionary, kRLMSyncIsAdminKey, isAdmin); + RLM_SYNC_PARSE_MODEL_ARRAY_OR_ABORT(jsonDictionary, kRLMSyncAccountsKey, RLMSyncUserAccountInfo, accounts); + [self parseMetadataFromJSON:jsonDictionary]; + return self; + } + return nil; +} + +@end + +#pragma mark - RLMSyncErrorResponseModel + +@interface RLMSyncErrorResponseModel () + +@property (nonatomic, readwrite) NSInteger status; +@property (nonatomic, readwrite) NSInteger code; +@property (nonatomic, readwrite) NSString *title; +@property (nonatomic, readwrite) NSString *hint; + +@end + +@implementation RLMSyncErrorResponseModel + +- (instancetype)initWithDictionary:(NSDictionary *)jsonDictionary { + if (self = [super init]) { + RLM_SYNC_PARSE_DOUBLE_OR_ABORT(jsonDictionary, kRLMSyncErrorStatusKey, status); + RLM_SYNC_PARSE_DOUBLE_OR_ABORT(jsonDictionary, kRLMSyncErrorCodeKey, code); + RLM_SYNC_PARSE_OPTIONAL_STRING(jsonDictionary, kRLMSyncErrorTitleKey, title); + RLM_SYNC_PARSE_OPTIONAL_STRING(jsonDictionary, kRLMSyncErrorHintKey, hint); + + NSString *detail = jsonDictionary[@"detail"]; + if ([detail isKindOfClass:[NSString class]]) { + _title = detail; + } + + for (NSDictionary *problem in jsonDictionary[@"invalid_params"]) { + NSString *name = problem[@"name"]; + NSString *reason = problem[@"reason"]; + if (name && reason) { + _title = [NSString stringWithFormat:@"%@ %@: %@;", _title, name, reason]; + } + } + + return self; + } + return nil; +} + +@end diff --git a/!main project/Pods/Realm/Realm/RLMListBase.mm b/!main project/Pods/Realm/Realm/RLMListBase.mm new file mode 100644 index 0000000..bcc8169 --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMListBase.mm @@ -0,0 +1,135 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMListBase.h" + +#import "RLMArray_Private.hpp" +#import "RLMObjectSchema_Private.h" +#import "RLMObject_Private.hpp" +#import "RLMObservation.hpp" +#import "RLMProperty_Private.h" +#import "RLMRealm_Private.hpp" + +@interface RLMArray (KVO) +- (NSArray *)objectsAtIndexes:(__unused NSIndexSet *)indexes; +@end + +@implementation RLMListBase { + std::unique_ptr _observationInfo; +} + ++ (RLMArray *)_unmanagedArray { + return nil; +} + +- (instancetype)init { + return self = [super init]; +} + +- (instancetype)initWithArray:(RLMArray *)array { + self = [super init]; + if (self) { + __rlmArray = array; + } + return self; +} + +- (RLMArray *)_rlmArray { + if (!__rlmArray) { + __rlmArray = self.class._unmanagedArray; + } + return __rlmArray; +} + +- (id)valueForKey:(NSString *)key { + return [self._rlmArray valueForKey:key]; +} + +- (id)valueForKeyPath:(NSString *)keyPath { + return [self._rlmArray valueForKeyPath:keyPath]; +} + +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state + objects:(id __unsafe_unretained [])buffer + count:(NSUInteger)len { + return [self._rlmArray countByEnumeratingWithState:state objects:buffer count:len]; +} + +- (NSArray *)objectsAtIndexes:(NSIndexSet *)indexes { + return [self._rlmArray objectsAtIndexes:indexes]; +} + +- (void)addObserver:(id)observer + forKeyPath:(NSString *)keyPath + options:(NSKeyValueObservingOptions)options + context:(void *)context { + RLMEnsureArrayObservationInfo(_observationInfo, keyPath, self._rlmArray, self); + [super addObserver:observer forKeyPath:keyPath options:options context:context]; +} + +@end + +@implementation RLMLinkingObjectsHandle { + realm::Row _row; + RLMClassInfo *_info; + RLMRealm *_realm; + RLMProperty *_property; + + RLMResults *_results; +} + +- (instancetype)initWithObject:(RLMObjectBase *)object property:(RLMProperty *)prop { + if (!(self = [super init])) { + return nil; + } + + _row = object->_row; + _info = object->_info; + _realm = object->_realm; + _property = prop; + + return self; +} + +- (RLMResults *)results { + if (_results) { + return _results; + } + if (!_row.is_attached()) { + @throw RLMException(@"Object has been deleted or invalidated."); + } + [_realm verifyThread]; + + auto& objectInfo = _realm->_info[_property.objectClassName]; + auto& linkOrigin = _info->objectSchema->computed_properties[_property.index].link_origin_property_name; + auto linkingProperty = objectInfo.objectSchema->property_for_name(linkOrigin); + auto backlinkView = _row.get_table()->get_backlink_view(_row.get_index(), + objectInfo.table(), + linkingProperty->table_column); + realm::Results results(_realm->_realm, std::move(backlinkView)); + _results = [RLMLinkingObjects resultsWithObjectInfo:objectInfo results:std::move(results)]; + _realm = nil; + return _results; +} + +- (RLMObjectBase *)parent { + RLMObjectBase *obj = RLMCreateManagedAccessor(_info->rlmObjectSchema.accessorClass, _info); + obj->_row = _row; + return obj; +} +@end diff --git a/!main project/Pods/Realm/Realm/RLMManagedArray.mm b/!main project/Pods/Realm/Realm/RLMManagedArray.mm new file mode 100644 index 0000000..0b9e393 --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMManagedArray.mm @@ -0,0 +1,542 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMArray_Private.hpp" + +#import "RLMAccessor.hpp" +#import "RLMObjectSchema_Private.hpp" +#import "RLMObjectStore.h" +#import "RLMObject_Private.hpp" +#import "RLMObservation.hpp" +#import "RLMProperty_Private.h" +#import "RLMQueryUtil.hpp" +#import "RLMRealm_Private.hpp" +#import "RLMSchema.h" +#import "RLMThreadSafeReference_Private.hpp" +#import "RLMUtil.hpp" + +#import "list.hpp" +#import "results.hpp" +#import "shared_realm.hpp" + +#import +#import + +@interface RLMManagedArrayHandoverMetadata : NSObject +@property (nonatomic) NSString *parentClassName; +@property (nonatomic) NSString *key; +@end + +@implementation RLMManagedArrayHandoverMetadata +@end + +@interface RLMManagedArray () +@end + +// +// RLMArray implementation +// +@implementation RLMManagedArray { +@public + realm::List _backingList; + RLMRealm *_realm; + RLMClassInfo *_objectInfo; + RLMClassInfo *_ownerInfo; + std::unique_ptr _observationInfo; +} + +- (RLMManagedArray *)initWithList:(realm::List)list + parentInfo:(RLMClassInfo *)parentInfo + property:(__unsafe_unretained RLMProperty *const)property { + if (property.type == RLMPropertyTypeObject) + self = [self initWithObjectClassName:property.objectClassName]; + else + self = [self initWithObjectType:property.type optional:property.optional]; + if (self) { + _realm = parentInfo->realm; + REALM_ASSERT(list.get_realm() == _realm->_realm); + _backingList = std::move(list); + _ownerInfo = parentInfo; + if (property.type == RLMPropertyTypeObject) + _objectInfo = &parentInfo->linkTargetType(property.index); + else + _objectInfo = _ownerInfo; + _key = property.name; + } + return self; +} + +- (RLMManagedArray *)initWithParent:(__unsafe_unretained RLMObjectBase *const)parentObject + property:(__unsafe_unretained RLMProperty *const)property { + __unsafe_unretained RLMRealm *const realm = parentObject->_realm; + auto col = parentObject->_info->tableColumn(property); + auto& row = parentObject->_row; + return [self initWithList:realm::List(realm->_realm, *row.get_table(), col, row.get_index()) + parentInfo:parentObject->_info + property:property]; +} + +void RLMValidateArrayObservationKey(__unsafe_unretained NSString *const keyPath, + __unsafe_unretained RLMArray *const array) { + if (![keyPath isEqualToString:RLMInvalidatedKey]) { + @throw RLMException(@"[<%@ %p> addObserver:forKeyPath:options:context:] is not supported. Key path: %@", + [array class], array, keyPath); + } +} + +void RLMEnsureArrayObservationInfo(std::unique_ptr& info, + __unsafe_unretained NSString *const keyPath, + __unsafe_unretained RLMArray *const array, + __unsafe_unretained id const observed) { + RLMValidateArrayObservationKey(keyPath, array); + if (!info && array.class == [RLMManagedArray class]) { + auto lv = static_cast(array); + info = std::make_unique(*lv->_ownerInfo, + lv->_backingList.get_origin_row_index(), + observed); + } +} + +// +// validation helpers +// +[[gnu::noinline]] +[[noreturn]] +static void throwError(__unsafe_unretained RLMManagedArray *const ar, NSString *aggregateMethod) { + try { + throw; + } + catch (realm::InvalidTransactionException const&) { + @throw RLMException(@"Cannot modify managed RLMArray outside of a write transaction."); + } + catch (realm::IncorrectThreadException const&) { + @throw RLMException(@"Realm accessed from incorrect thread."); + } + catch (realm::List::InvalidatedException const&) { + @throw RLMException(@"RLMArray has been invalidated or the containing object has been deleted."); + } + catch (realm::List::OutOfBoundsIndexException const& e) { + @throw RLMException(@"Index %zu is out of bounds (must be less than %zu).", + e.requested, e.valid_count); + } + catch (realm::Results::UnsupportedColumnTypeException const& e) { + if (ar->_backingList.get_type() == realm::PropertyType::Object) { + @throw RLMException(@"%@: is not supported for %s%s property '%s'.", + aggregateMethod, + string_for_property_type(e.property_type), + is_nullable(e.property_type) ? "?" : "", + e.column_name.data()); + } + @throw RLMException(@"%@: is not supported for %s%s array '%@.%@'.", + aggregateMethod, + string_for_property_type(e.property_type), + is_nullable(e.property_type) ? "?" : "", + ar->_ownerInfo->rlmObjectSchema.className, ar->_key); + } + catch (std::logic_error const& e) { + @throw RLMException(e); + } +} + +template +static auto translateErrors(__unsafe_unretained RLMManagedArray *const ar, + Function&& f, NSString *aggregateMethod=nil) { + try { + return f(); + } + catch (...) { + throwError(ar, aggregateMethod); + } +} + +template +static auto translateErrors(Function&& f) { + try { + return f(); + } + catch (...) { + throwError(nil, nil); + } +} + +template +static void changeArray(__unsafe_unretained RLMManagedArray *const ar, + NSKeyValueChange kind, dispatch_block_t f, IndexSetFactory&& is) { + translateErrors([&] { ar->_backingList.verify_in_transaction(); }); + RLMObservationInfo *info = RLMGetObservationInfo(ar->_observationInfo.get(), + ar->_backingList.get_origin_row_index(), + *ar->_ownerInfo); + if (info) { + NSIndexSet *indexes = is(); + info->willChange(ar->_key, kind, indexes); + try { + f(); + } + catch (...) { + info->didChange(ar->_key, kind, indexes); + throwError(ar, nil); + } + info->didChange(ar->_key, kind, indexes); + } + else { + translateErrors([&] { f(); }); + } +} + +static void changeArray(__unsafe_unretained RLMManagedArray *const ar, NSKeyValueChange kind, NSUInteger index, dispatch_block_t f) { + changeArray(ar, kind, f, [=] { return [NSIndexSet indexSetWithIndex:index]; }); +} + +static void changeArray(__unsafe_unretained RLMManagedArray *const ar, NSKeyValueChange kind, NSRange range, dispatch_block_t f) { + changeArray(ar, kind, f, [=] { return [NSIndexSet indexSetWithIndexesInRange:range]; }); +} + +static void changeArray(__unsafe_unretained RLMManagedArray *const ar, NSKeyValueChange kind, NSIndexSet *is, dispatch_block_t f) { + changeArray(ar, kind, f, [=] { return is; }); +} + +// +// public method implementations +// +- (RLMRealm *)realm { + return _realm; +} + +- (NSUInteger)count { + return translateErrors([&] { return _backingList.size(); }); +} + +- (BOOL)isInvalidated { + return translateErrors([&] { return !_backingList.is_valid(); }); +} + +- (RLMClassInfo *)objectInfo { + return _objectInfo; +} + + +- (bool)isBackedByList:(realm::List const&)list { + return _backingList == list; +} + +- (BOOL)isEqual:(id)object { + return [object respondsToSelector:@selector(isBackedByList:)] && [object isBackedByList:_backingList]; +} + +- (NSUInteger)hash { + return std::hash()(_backingList); +} + +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state + objects:(__unused __unsafe_unretained id [])buffer + count:(NSUInteger)len { + return RLMFastEnumerate(state, len, self); +} + +- (id)objectAtIndex:(NSUInteger)index { + return translateErrors([&] { + RLMAccessorContext context(*_objectInfo); + return _backingList.get(context, index); + }); +} + +static void RLMInsertObject(RLMManagedArray *ar, id object, NSUInteger index) { + RLMArrayValidateMatchingObjectType(ar, object); + if (index == NSUIntegerMax) { + index = translateErrors([&] { return ar->_backingList.size(); }); + } + + changeArray(ar, NSKeyValueChangeInsertion, index, ^{ + RLMAccessorContext context(*ar->_objectInfo); + ar->_backingList.insert(context, index, object); + }); +} + +- (void)addObject:(id)object { + RLMInsertObject(self, object, NSUIntegerMax); +} + +- (void)insertObject:(id)object atIndex:(NSUInteger)index { + RLMInsertObject(self, object, index); +} + +- (void)insertObjects:(id)objects atIndexes:(NSIndexSet *)indexes { + changeArray(self, NSKeyValueChangeInsertion, indexes, ^{ + NSUInteger index = [indexes firstIndex]; + RLMAccessorContext context(*_objectInfo); + for (id obj in objects) { + RLMArrayValidateMatchingObjectType(self, obj); + _backingList.insert(context, index, obj); + index = [indexes indexGreaterThanIndex:index]; + } + }); +} + + +- (void)removeObjectAtIndex:(NSUInteger)index { + changeArray(self, NSKeyValueChangeRemoval, index, ^{ + _backingList.remove(index); + }); +} + +- (void)removeObjectsAtIndexes:(NSIndexSet *)indexes { + changeArray(self, NSKeyValueChangeRemoval, indexes, ^{ + [indexes enumerateIndexesWithOptions:NSEnumerationReverse usingBlock:^(NSUInteger idx, BOOL *) { + _backingList.remove(idx); + }]; + }); +} + +- (void)addObjectsFromArray:(NSArray *)array { + changeArray(self, NSKeyValueChangeInsertion, NSMakeRange(self.count, array.count), ^{ + RLMAccessorContext context(*_objectInfo); + for (id obj in array) { + RLMArrayValidateMatchingObjectType(self, obj); + _backingList.add(context, obj); + } + }); +} + +- (void)removeAllObjects { + changeArray(self, NSKeyValueChangeRemoval, NSMakeRange(0, self.count), ^{ + _backingList.remove_all(); + }); +} + +- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)object { + RLMArrayValidateMatchingObjectType(self, object); + changeArray(self, NSKeyValueChangeReplacement, index, ^{ + RLMAccessorContext context(*_objectInfo); + _backingList.set(context, index, object); + }); +} + +- (void)moveObjectAtIndex:(NSUInteger)sourceIndex toIndex:(NSUInteger)destinationIndex { + auto start = std::min(sourceIndex, destinationIndex); + auto len = std::max(sourceIndex, destinationIndex) - start + 1; + changeArray(self, NSKeyValueChangeReplacement, {start, len}, ^{ + _backingList.move(sourceIndex, destinationIndex); + }); +} + +- (void)exchangeObjectAtIndex:(NSUInteger)index1 withObjectAtIndex:(NSUInteger)index2 { + changeArray(self, NSKeyValueChangeReplacement, ^{ + _backingList.swap(index1, index2); + }, [=] { + NSMutableIndexSet *set = [[NSMutableIndexSet alloc] initWithIndex:index1]; + [set addIndex:index2]; + return set; + }); +} + +- (NSUInteger)indexOfObject:(id)object { + RLMArrayValidateMatchingObjectType(self, object); + return translateErrors([&] { + RLMAccessorContext context(*_objectInfo); + return RLMConvertNotFound(_backingList.find(context, object)); + }); +} + +- (id)valueForKeyPath:(NSString *)keyPath { + if ([keyPath hasPrefix:@"@"]) { + // Delegate KVC collection operators to RLMResults + return translateErrors([&] { + auto results = [RLMResults resultsWithObjectInfo:*_objectInfo results:_backingList.as_results()]; + return [results valueForKeyPath:keyPath]; + }); + } + return [super valueForKeyPath:keyPath]; +} + +- (id)valueForKey:(NSString *)key { + // Ideally we'd use "@invalidated" for this so that "invalidated" would use + // normal array KVC semantics, but observing @things works very oddly (when + // it's part of a key path, it's triggered automatically when array index + // changes occur, and can't be sent explicitly, but works normally when it's + // the entire key path), and an RLMManagedArray *can't* have objects where + // invalidated is true, so we're not losing much. + return translateErrors([&]() -> id { + if ([key isEqualToString:RLMInvalidatedKey]) { + return @(!_backingList.is_valid()); + } + + _backingList.verify_attached(); + return RLMCollectionValueForKey(_backingList, key, *_objectInfo); + }); +} + +- (void)setValue:(id)value forKey:(NSString *)key { + if ([key isEqualToString:@"self"]) { + RLMArrayValidateMatchingObjectType(self, value); + RLMAccessorContext context(*_objectInfo); + translateErrors([&] { + for (size_t i = 0, count = _backingList.size(); i < count; ++i) { + _backingList.set(context, i, value); + } + }); + return; + } + else if (_type == RLMPropertyTypeObject) { + RLMArrayValidateMatchingObjectType(self, value); + translateErrors([&] { _backingList.verify_in_transaction(); }); + RLMCollectionSetValueForKey(self, key, value); + } + else { + [self setValue:value forUndefinedKey:key]; + } +} + +- (size_t)columnForProperty:(NSString *)propertyName { + if (_backingList.get_type() == realm::PropertyType::Object) { + return _objectInfo->tableColumn(propertyName); + } + if (![propertyName isEqualToString:@"self"]) { + @throw RLMException(@"Arrays of '%@' can only be aggregated on \"self\"", RLMTypeToString(_type)); + } + return 0; +} + +- (id)minOfProperty:(NSString *)property { + size_t column = [self columnForProperty:property]; + auto value = translateErrors(self, [&] { return _backingList.min(column); }, @"minOfProperty"); + return value ? RLMMixedToObjc(*value) : nil; +} + +- (id)maxOfProperty:(NSString *)property { + size_t column = [self columnForProperty:property]; + auto value = translateErrors(self, [&] { return _backingList.max(column); }, @"maxOfProperty"); + return value ? RLMMixedToObjc(*value) : nil; +} + +- (id)sumOfProperty:(NSString *)property { + size_t column = [self columnForProperty:property]; + return RLMMixedToObjc(translateErrors(self, [&] { return _backingList.sum(column); }, @"sumOfProperty")); +} + +- (id)averageOfProperty:(NSString *)property { + size_t column = [self columnForProperty:property]; + auto value = translateErrors(self, [&] { return _backingList.average(column); }, @"averageOfProperty"); + return value ? @(*value) : nil; +} + +- (void)deleteObjectsFromRealm { + if (_type != RLMPropertyTypeObject) { + @throw RLMException(@"Cannot delete objects from RLMArray<%@>: only RLMObjects can be deleted.", RLMTypeToString(_type)); + } + // delete all target rows from the realm + RLMTrackDeletions(_realm, ^{ + translateErrors([&] { _backingList.delete_all(); }); + }); +} + +- (RLMResults *)sortedResultsUsingDescriptors:(NSArray *)properties { + return translateErrors([&] { + return [RLMResults resultsWithObjectInfo:*_objectInfo + results:_backingList.sort(RLMSortDescriptorsToKeypathArray(properties))]; + }); +} + +- (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate { + if (_type != RLMPropertyTypeObject) { + @throw RLMException(@"Querying is currently only implemented for arrays of Realm Objects"); + } + auto query = RLMPredicateToQuery(predicate, _objectInfo->rlmObjectSchema, _realm.schema, _realm.group); + auto results = translateErrors([&] { return _backingList.filter(std::move(query)); }); + return [RLMResults resultsWithObjectInfo:*_objectInfo results:std::move(results)]; +} + +- (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate { + if (_type != RLMPropertyTypeObject) { + @throw RLMException(@"Querying is currently only implemented for arrays of Realm Objects"); + } + realm::Query query = RLMPredicateToQuery(predicate, _objectInfo->rlmObjectSchema, + _realm.schema, _realm.group); + + return translateErrors([&] { + return RLMConvertNotFound(_backingList.find(std::move(query))); + }); +} + +- (NSArray *)objectsAtIndexes:(__unused NSIndexSet *)indexes { + // FIXME: this is called by KVO when array changes are made. It's not clear + // why, and returning nil seems to work fine. + return nil; +} + +- (void)addObserver:(id)observer + forKeyPath:(NSString *)keyPath + options:(NSKeyValueObservingOptions)options + context:(void *)context { + RLMEnsureArrayObservationInfo(_observationInfo, keyPath, self, self); + [super addObserver:observer forKeyPath:keyPath options:options context:context]; +} + +- (realm::TableView)tableView { + return translateErrors([&] { return _backingList.get_query(); }).find_all(); +} + +- (RLMFastEnumerator *)fastEnumerator { + return translateErrors([&] { + return [[RLMFastEnumerator alloc] initWithList:_backingList collection:self + classInfo:*_objectInfo]; + }); +} + +// The compiler complains about the method's argument type not matching due to +// it not having the generic type attached, but it doesn't seem to be possible +// to actually include the generic type +// http://www.openradar.me/radar?id=6135653276319744 +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmismatched-parameter-types" +- (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, RLMCollectionChange *, NSError *))block { + [_realm verifyNotificationsAreSupported:true]; + return RLMAddNotificationBlock(self, _backingList, block); +} +#pragma clang diagnostic pop + +#pragma mark - Thread Confined Protocol Conformance + +- (std::unique_ptr)makeThreadSafeReference { + auto list_reference = _realm->_realm->obtain_thread_safe_reference(_backingList); + return std::make_unique>(std::move(list_reference)); +} + +- (RLMManagedArrayHandoverMetadata *)objectiveCMetadata { + RLMManagedArrayHandoverMetadata *metadata = [[RLMManagedArrayHandoverMetadata alloc] init]; + metadata.parentClassName = _ownerInfo->rlmObjectSchema.className; + metadata.key = _key; + return metadata; +} + ++ (instancetype)objectWithThreadSafeReference:(std::unique_ptr)reference + metadata:(RLMManagedArrayHandoverMetadata *)metadata + realm:(RLMRealm *)realm { + REALM_ASSERT_DEBUG(dynamic_cast *>(reference.get())); + auto list_reference = static_cast *>(reference.get()); + + auto list = realm->_realm->resolve_thread_safe_reference(std::move(*list_reference)); + if (!list.is_valid()) { + return nil; + } + RLMClassInfo *parentInfo = &realm->_info[metadata.parentClassName]; + return [[RLMManagedArray alloc] initWithList:std::move(list) + parentInfo:parentInfo + property:parentInfo->rlmObjectSchema[metadata.key]]; +} + +@end diff --git a/!main project/Pods/Realm/Realm/RLMMigration.mm b/!main project/Pods/Realm/Realm/RLMMigration.mm new file mode 100644 index 0000000..6c0c571 --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMMigration.mm @@ -0,0 +1,192 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMMigration_Private.h" + +#import "RLMAccessor.h" +#import "RLMObject_Private.h" +#import "RLMObject_Private.hpp" +#import "RLMObjectSchema_Private.hpp" +#import "RLMObjectStore.h" +#import "RLMProperty_Private.h" +#import "RLMRealm_Dynamic.h" +#import "RLMRealm_Private.hpp" +#import "RLMResults_Private.hpp" +#import "RLMSchema_Private.hpp" +#import "RLMUtil.hpp" + +#import "object_store.hpp" +#import "shared_realm.hpp" +#import "schema.hpp" + +#import + +using namespace realm; + +// The source realm for a migration has to use a SharedGroup to be able to share +// the file with the destination realm, but we don't want to let the user call +// beginWriteTransaction on it as that would make no sense. +@interface RLMMigrationRealm : RLMRealm +@end + +@implementation RLMMigrationRealm +- (BOOL)readonly { + return YES; +} + +- (void)beginWriteTransaction { + @throw RLMException(@"Cannot modify the source Realm in a migration"); +} +@end + +@implementation RLMMigration { + realm::Schema *_schema; + std::unordered_map _deletedObjectIndices; +} + +- (instancetype)initWithRealm:(RLMRealm *)realm oldRealm:(RLMRealm *)oldRealm schema:(realm::Schema &)schema { + self = [super init]; + if (self) { + _realm = realm; + _oldRealm = oldRealm; + _schema = &schema; + object_setClass(_oldRealm, RLMMigrationRealm.class); + } + return self; +} + +- (RLMSchema *)oldSchema { + return self.oldRealm.schema; +} + +- (RLMSchema *)newSchema { + return self.realm.schema; +} + +- (void)enumerateObjects:(NSString *)className block:(__attribute__((noescape)) RLMObjectMigrationBlock)block { + RLMResults *objects = [_realm.schema schemaForClassName:className] ? [_realm allObjects:className] : nil; + RLMResults *oldObjects = [_oldRealm.schema schemaForClassName:className] ? [_oldRealm allObjects:className] : nil; + + // For whatever reason if this is a newly added table we enumerate the + // objects in it, while in all other cases we enumerate only the existing + // objects. It's unclear how this could be useful, but changing it would + // also be a pointless breaking change and it's unlikely to be hurting anyone. + if (objects && !oldObjects) { + for (auto i = objects.count; i > 0; --i) { + @autoreleasepool { + block(nil, objects[i - 1]); + } + } + return; + } + + auto count = oldObjects.count; + if (count == 0) { + return; + } + auto deletedObjects = _deletedObjectIndices.find(className); + for (auto i = count; i > 0; --i) { + auto index = i - 1; + if (deletedObjects != _deletedObjectIndices.end() && deletedObjects->second.contains(index)) { + continue; + } + @autoreleasepool { + block(oldObjects[index], objects[index]); + } + } +} + +- (void)execute:(RLMMigrationBlock)block { + @autoreleasepool { + // disable all primary keys for migration and use DynamicObject for all types + for (RLMObjectSchema *objectSchema in _realm.schema.objectSchema) { + objectSchema.accessorClass = RLMDynamicObject.class; + objectSchema.primaryKeyProperty.isPrimary = NO; + } + for (RLMObjectSchema *objectSchema in _oldRealm.schema.objectSchema) { + objectSchema.accessorClass = RLMDynamicObject.class; + } + + block(self, _oldRealm->_realm->schema_version()); + + [self deleteObjectsMarkedForDeletion]; + + _oldRealm = nil; + _realm = nil; + } +} + +- (RLMObject *)createObject:(NSString *)className withValue:(id)value { + return [_realm createObject:className withValue:value]; +} + +- (RLMObject *)createObject:(NSString *)className withObject:(id)object { + return [self createObject:className withValue:object]; +} + +- (void)deleteObject:(RLMObject *)object { + _deletedObjectIndices[object.objectSchema.className].add(object->_row.get_index()); +} + +- (void)deleteObjectsMarkedForDeletion { + for (auto& objectType : _deletedObjectIndices) { + TableRef table = ObjectStore::table_for_object_type(_realm.group, objectType.first.UTF8String); + if (!table) { + continue; + } + + auto& indices = objectType.second; + // Just clear the table if we're removing all of the rows + if (table->size() == indices.count()) { + table->clear(); + } + // Otherwise delete in reverse order to avoid invaliding any of the + // not-yet-deleted indices + else { + for (auto it = std::make_reverse_iterator(indices.end()), end = std::make_reverse_iterator(indices.begin()); it != end; ++it) { + for (size_t i = it->second; i > it->first; --i) { + table->move_last_over(i - 1); + } + } + } + } +} + +- (BOOL)deleteDataForClassName:(NSString *)name { + if (!name) { + return false; + } + + TableRef table = ObjectStore::table_for_object_type(_realm.group, name.UTF8String); + if (!table) { + return false; + } + _deletedObjectIndices[name].set(table->size()); + if (![_realm.schema schemaForClassName:name]) { + realm::ObjectStore::delete_data_for_object(_realm.group, name.UTF8String); + } + + return true; +} + +- (void)renamePropertyForClass:(NSString *)className oldName:(NSString *)oldName newName:(NSString *)newName { + realm::ObjectStore::rename_property(_realm.group, *_schema, className.UTF8String, + oldName.UTF8String, newName.UTF8String); +} + +@end diff --git a/!main project/Pods/Realm/Realm/RLMNetworkClient.mm b/!main project/Pods/Realm/Realm/RLMNetworkClient.mm new file mode 100644 index 0000000..009795e --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMNetworkClient.mm @@ -0,0 +1,449 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMNetworkClient.h" + +#import "RLMRealmConfiguration.h" +#import "RLMJSONModels.h" +#import "RLMSyncUtil_Private.hpp" +#import "RLMSyncManager_Private.h" +#import "RLMUtil.hpp" + +#import + +typedef void(^RLMServerURLSessionCompletionBlock)(NSData *, NSURLResponse *, NSError *); + +static NSUInteger const kHTTPCodeRange = 100; + +typedef enum : NSUInteger { + Informational = 1, // 1XX + Success = 2, // 2XX + Redirection = 3, // 3XX + ClientError = 4, // 4XX + ServerError = 5, // 5XX +} RLMServerHTTPErrorCodeType; + +static NSRange rangeForErrorType(RLMServerHTTPErrorCodeType type) { + return NSMakeRange(type*100, kHTTPCodeRange); +} + +#pragma mark Network client + +@interface RLMSessionDelegate : NSObject ++ (instancetype)delegateWithCertificatePaths:(NSDictionary *)paths + completion:(void (^)(NSError *, NSDictionary *))completion; +@end + +@interface RLMSyncServerEndpoint () +/// The HTTP method the endpoint expects. Defaults to POST. ++ (NSString *)httpMethod; + +/// The URL to which the request should be made. Must be implemented. ++ (NSURL *)urlForAuthServer:(NSURL *)authServerURL payload:(NSDictionary *)json; + +/// The body for the request, if any. ++ (NSData *)httpBodyForPayload:(NSDictionary *)json error:(NSError **)error; + +/// The HTTP headers to be added to the request, if any. ++ (NSDictionary *)httpHeadersForPayload:(NSDictionary *)json + options:(nullable RLMNetworkRequestOptions *)options; +@end + +@implementation RLMSyncServerEndpoint + ++ (void)sendRequestToServer:(NSURL *)serverURL + JSON:(NSDictionary *)jsonDictionary + completion:(void (^)(NSError *))completionBlock { + [self sendRequestToServer:serverURL JSON:jsonDictionary timeout:0 + completion:^(NSError *error, NSDictionary *) { + completionBlock(error); + }]; +} ++ (void)sendRequestToServer:(NSURL *)serverURL + JSON:(NSDictionary *)jsonDictionary + timeout:(NSTimeInterval)timeout + completion:(void (^)(NSError *, NSDictionary *))completionBlock { + // If the timeout isn't set then use the timeout set on the sync manager, + // or 60 seconds if it isn't set there either. + RLMSyncManager *syncManager = RLMSyncManager.sharedManager; + if (timeout < 1) + timeout = syncManager.timeoutOptions.connectTimeout / 1000.0; + if (timeout < 1) + timeout = 60.0; + + // Create the request + NSError *localError = nil; + NSURL *requestURL = [self urlForAuthServer:serverURL payload:jsonDictionary]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:requestURL]; + request.HTTPMethod = [self httpMethod]; + if (![request.HTTPMethod isEqualToString:@"GET"]) { + request.HTTPBody = [self httpBodyForPayload:jsonDictionary error:&localError]; + if (localError) { + completionBlock(localError, nil); + return; + } + } + request.timeoutInterval = timeout; + RLMNetworkRequestOptions *options = syncManager.networkRequestOptions; + NSDictionary *headers = [self httpHeadersForPayload:jsonDictionary options:options]; + for (NSString *key in headers) { + [request addValue:headers[key] forHTTPHeaderField:key]; + } + id delegate = [RLMSessionDelegate delegateWithCertificatePaths:options.pinnedCertificatePaths + completion:completionBlock]; + auto session = [NSURLSession sessionWithConfiguration:NSURLSessionConfiguration.defaultSessionConfiguration + delegate:delegate delegateQueue:nil]; + + // Add the request to a task and start it + [[session dataTaskWithRequest:request] resume]; + // Tell the session to destroy itself once it's done with the request + [session finishTasksAndInvalidate]; +} + ++ (NSString *)httpMethod { + return @"POST"; +} + ++ (NSURL *)urlForAuthServer:(__unused NSURL *)authServerURL payload:(__unused NSDictionary *)json { + NSAssert(NO, @"This method must be overriden by concrete subclasses."); + return nil; +} + ++ (NSData *)httpBodyForPayload:(NSDictionary *)json error:(NSError **)error { + NSError *localError = nil; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:json + options:(NSJSONWritingOptions)0 + error:&localError]; + if (jsonData && !localError) { + return jsonData; + } + NSAssert(localError, @"If there isn't a converted data object there must be an error."); + if (error) { + *error = localError; + } + return nil; +} + ++ (NSDictionary *)httpHeadersForPayload:(NSDictionary *)json + options:(nullable RLMNetworkRequestOptions *)options { + NSMutableDictionary *headers = [[NSMutableDictionary alloc] init]; + headers[@"Content-Type"] = @"application/json;charset=utf-8"; + headers[@"Accept"] = @"application/json"; + + if (NSDictionary *customHeaders = options.customHeaders) { + [headers addEntriesFromDictionary:customHeaders]; + } + if (NSString *authToken = [json objectForKey:kRLMSyncTokenKey]) { + headers[options.authorizationHeaderName ?: @"Authorization"] = authToken; + } + + return headers; +} +@end + +@implementation RLMSessionDelegate { + NSDictionary *_certificatePaths; + NSData *_data; + void (^_completionBlock)(NSError *, NSDictionary *); +} + ++ (instancetype)delegateWithCertificatePaths:(NSDictionary *)paths + completion:(void (^)(NSError *, NSDictionary *))completion { + RLMSessionDelegate *delegate = [RLMSessionDelegate new]; + delegate->_certificatePaths = paths; + delegate->_completionBlock = completion; + return delegate; +} + +- (void)URLSession:(__unused NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge + completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { + auto protectionSpace = challenge.protectionSpace; + + // Just fall back to the default logic for HTTP basic auth + if (protectionSpace.authenticationMethod != NSURLAuthenticationMethodServerTrust || !protectionSpace.serverTrust) { + completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); + return; + } + + // If we have a pinned certificate for this hostname, we want to validate + // against that, and otherwise just do the default thing + auto certPath = _certificatePaths[protectionSpace.host]; + if (!certPath) { + completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); + return; + } + if ([certPath isKindOfClass:[NSString class]]) { + certPath = [NSURL fileURLWithPath:(id)certPath]; + } + + + // Reject the server auth and report an error if any errors occur along the way + CFArrayRef items = nil; + NSError *error; + auto reportStatus = realm::util::make_scope_exit([&]() noexcept { + if (items) { + CFRelease(items); + } + if (error) { + _completionBlock(error, nil); + // Don't also report errors about the connection itself failing later + _completionBlock = ^(NSError *, id) { }; + completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil); + } + }); + + NSData *data = [NSData dataWithContentsOfURL:certPath options:0 error:&error]; + if (!data) { + return; + } + + // Load our pinned certificate and add it to the anchor set +#if TARGET_OS_IPHONE + id certificate = (__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)data); + if (!certificate) { + error = [NSError errorWithDomain:NSOSStatusErrorDomain code:errSecUnknownFormat userInfo:nil]; + return; + } + items = (CFArrayRef)CFBridgingRetain(@[certificate]); +#else + SecItemImportExportKeyParameters params{ + .version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION + }; + if (OSStatus status = SecItemImport((__bridge CFDataRef)data, (__bridge CFStringRef)certPath.absoluteString, + nullptr, nullptr, 0, ¶ms, nullptr, &items)) { + error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil]; + return; + } +#endif + SecTrustRef serverTrust = protectionSpace.serverTrust; + if (OSStatus status = SecTrustSetAnchorCertificates(serverTrust, items)) { + error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil]; + return; + } + + // Only use our certificate and not the ones from the default CA roots + if (OSStatus status = SecTrustSetAnchorCertificatesOnly(serverTrust, true)) { + error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil]; + return; + } + + // Verify that our pinned certificate is valid for this connection + SecTrustResultType trustResult; + if (OSStatus status = SecTrustEvaluate(serverTrust, &trustResult)) { + error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil]; + return; + } + if (trustResult != kSecTrustResultProceed && trustResult != kSecTrustResultUnspecified) { + completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil); + return; + } + + completionHandler(NSURLSessionAuthChallengeUseCredential, + [NSURLCredential credentialForTrust:protectionSpace.serverTrust]); +} + +- (void)URLSession:(__unused NSURLSession *)session + dataTask:(__unused NSURLSessionDataTask *)dataTask + didReceiveData:(NSData *)data { + if (!_data) { + _data = data; + return; + } + if (![_data respondsToSelector:@selector(appendData:)]) { + _data = [_data mutableCopy]; + } + [(id)_data appendData:data]; +} + +- (void)URLSession:(__unused NSURLSession *)session + task:(NSURLSessionTask *)task +didCompleteWithError:(NSError *)error +{ + if (error) { + _completionBlock(error, nil); + return; + } + + if (NSError *error = [self validateResponse:task.response data:_data]) { + _completionBlock(error, nil); + return; + } + + id json = [NSJSONSerialization JSONObjectWithData:_data + options:(NSJSONReadingOptions)0 + error:&error]; + if (!json) { + _completionBlock(error, nil); + return; + } + if (![json isKindOfClass:[NSDictionary class]]) { + _completionBlock(make_auth_error_bad_response(json), nil); + return; + } + + _completionBlock(nil, (NSDictionary *)json); +} + +- (NSError *)validateResponse:(NSURLResponse *)response data:(NSData *)data { + if (![response isKindOfClass:[NSHTTPURLResponse class]]) { + // FIXME: Provide error message + return make_auth_error_bad_response(); + } + + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + BOOL badResponse = (NSLocationInRange(httpResponse.statusCode, rangeForErrorType(ClientError)) + || NSLocationInRange(httpResponse.statusCode, rangeForErrorType(ServerError))); + if (badResponse) { + if (RLMSyncErrorResponseModel *responseModel = [self responseModelFromData:data]) { + switch (responseModel.code) { + case RLMSyncAuthErrorInvalidParameters: + case RLMSyncAuthErrorMissingPath: + case RLMSyncAuthErrorInvalidCredential: + case RLMSyncAuthErrorUserDoesNotExist: + case RLMSyncAuthErrorUserAlreadyExists: + case RLMSyncAuthErrorAccessDeniedOrInvalidPath: + case RLMSyncAuthErrorInvalidAccessToken: + case RLMSyncAuthErrorExpiredPermissionOffer: + case RLMSyncAuthErrorAmbiguousPermissionOffer: + case RLMSyncAuthErrorFileCannotBeShared: + return make_auth_error(responseModel); + default: + // Right now we assume that any codes not described + // above are generic HTTP error codes. + return make_auth_error_http_status(responseModel.status); + } + } + return make_auth_error_http_status(httpResponse.statusCode); + } + + if (!data) { + // FIXME: provide error message + return make_auth_error_bad_response(); + } + + return nil; +} + +- (RLMSyncErrorResponseModel *)responseModelFromData:(NSData *)data { + if (data.length == 0) { + return nil; + } + id json = [NSJSONSerialization JSONObjectWithData:data + options:(NSJSONReadingOptions)0 + error:nil]; + if (!json || ![json isKindOfClass:[NSDictionary class]]) { + return nil; + } + return [[RLMSyncErrorResponseModel alloc] initWithDictionary:json]; +} + +@end + +@implementation RLMNetworkRequestOptions +@end + +#pragma mark - Endpoint Implementations + +@implementation RLMSyncAuthEndpoint ++ (NSURL *)urlForAuthServer:(NSURL *)authServerURL payload:(__unused NSDictionary *)json { + return [authServerURL URLByAppendingPathComponent:@"auth"]; +} +@end + +@implementation RLMSyncChangePasswordEndpoint ++ (NSString *)httpMethod { + return @"PUT"; +} + ++ (NSURL *)urlForAuthServer:(NSURL *)authServerURL payload:(__unused NSDictionary *)json { + return [authServerURL URLByAppendingPathComponent:@"auth/password"]; +} +@end + +@implementation RLMSyncUpdateAccountEndpoint ++ (NSURL *)urlForAuthServer:(NSURL *)authServerURL payload:(__unused NSDictionary *)json { + return [authServerURL URLByAppendingPathComponent:@"auth/password/updateAccount"]; +} +@end + +@implementation RLMSyncGetUserInfoEndpoint ++ (NSString *)httpMethod { + return @"GET"; +} + ++ (NSURL *)urlForAuthServer:(NSURL *)authServerURL payload:(NSDictionary *)json { + NSString *provider = json[kRLMSyncProviderKey]; + NSString *providerID = json[kRLMSyncProviderIDKey]; + NSAssert([provider isKindOfClass:[NSString class]] && [providerID isKindOfClass:[NSString class]], + @"malformed request; this indicates a logic error in the binding."); + NSCharacterSet *allowed = [NSCharacterSet URLQueryAllowedCharacterSet]; + NSString *pathComponent = [NSString stringWithFormat:@"auth/users/%@/%@", + [provider stringByAddingPercentEncodingWithAllowedCharacters:allowed], + [providerID stringByAddingPercentEncodingWithAllowedCharacters:allowed]]; + return [authServerURL URLByAppendingPathComponent:pathComponent]; +} + ++ (NSData *)httpBodyForPayload:(__unused NSDictionary *)json error:(__unused NSError **)error { + return nil; +} +@end + +@implementation RLMSyncGetPermissionsEndpoint ++ (NSString *)httpMethod { + return @"GET"; +} ++ (NSURL *)urlForAuthServer:(NSURL *)authServerURL payload:(__unused NSDictionary *)json { + return [authServerURL URLByAppendingPathComponent:@"permissions"]; +} +@end + +@implementation RLMSyncGetPermissionOffersEndpoint ++ (NSString *)httpMethod { + return @"GET"; +} ++ (NSURL *)urlForAuthServer:(NSURL *)authServerURL payload:(__unused NSDictionary *)json { + return [authServerURL URLByAppendingPathComponent:@"permissions/offers"]; +} +@end + +@implementation RLMSyncApplyPermissionsEndpoint ++ (NSURL *)urlForAuthServer:(NSURL *)authServerURL payload:(__unused NSDictionary *)json { + return [authServerURL URLByAppendingPathComponent:@"permissions/apply"]; +} +@end + +@implementation RLMSyncOfferPermissionsEndpoint ++ (NSURL *)urlForAuthServer:(NSURL *)authServerURL payload:(__unused NSDictionary *)json { + return [authServerURL URLByAppendingPathComponent:@"permissions/offers"]; +} +@end + +@implementation RLMSyncAcceptPermissionOfferEndpoint ++ (NSURL *)urlForAuthServer:(NSURL *)authServerURL payload:(NSDictionary *)json { + return [authServerURL URLByAppendingPathComponent:[NSString stringWithFormat:@"permissions/offers/%@/accept", json[@"offerToken"]]]; +} +@end + +@implementation RLMSyncInvalidatePermissionOfferEndpoint ++ (NSString *)httpMethod { + return @"DELETE"; +} ++ (NSURL *)urlForAuthServer:(NSURL *)authServerURL payload:(NSDictionary *)json { + return [authServerURL URLByAppendingPathComponent:[NSString stringWithFormat:@"permissions/offers/%@", json[@"offerToken"]]]; +} +@end diff --git a/!main project/Pods/Realm/Realm/RLMObject.mm b/!main project/Pods/Realm/Realm/RLMObject.mm new file mode 100644 index 0000000..663ec64 --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMObject.mm @@ -0,0 +1,364 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMObject_Private.hpp" + +#import "RLMAccessor.h" +#import "RLMArray.h" +#import "RLMCollection_Private.hpp" +#import "RLMObjectBase_Private.h" +#import "RLMObjectSchema_Private.hpp" +#import "RLMObjectStore.h" +#import "RLMProperty_Private.h" +#import "RLMQueryUtil.hpp" +#import "RLMRealm_Private.hpp" +#import "RLMSchema_Private.h" + +#import "collection_notifications.hpp" +#import "object.hpp" + +@interface RLMPropertyChange () +@property (nonatomic, readwrite, strong) NSString *name; +@property (nonatomic, readwrite, strong, nullable) id previousValue; +@property (nonatomic, readwrite, strong, nullable) id value; +@end + +// We declare things in RLMObject which are actually implemented in RLMObjectBase +// for documentation's sake, which leads to -Wunimplemented-method warnings. +// Other alternatives to this would be to disable -Wunimplemented-method for this +// file (but then we could miss legitimately missing things), or declaring the +// inherited things in a category (but they currently aren't nicely grouped for +// that). +@implementation RLMObject + +// synthesized in RLMObjectBase +@dynamic invalidated, realm, objectSchema; + +#pragma mark - Designated Initializers + +- (instancetype)init { + return [super init]; +} + +#pragma mark - Convenience Initializers + +- (instancetype)initWithValue:(id)value { + if (!(self = [self init])) { + return nil; + } + RLMInitializeWithValue(self, value, RLMSchema.partialPrivateSharedSchema); + return self; +} + +#pragma mark - Class-based Object Creation + ++ (instancetype)createInDefaultRealmWithValue:(id)value { + return (RLMObject *)RLMCreateObjectInRealmWithValue([RLMRealm defaultRealm], [self className], value, RLMUpdatePolicyError); +} + ++ (instancetype)createInRealm:(RLMRealm *)realm withValue:(id)value { + return (RLMObject *)RLMCreateObjectInRealmWithValue(realm, [self className], value, RLMUpdatePolicyError); +} + ++ (instancetype)createOrUpdateInDefaultRealmWithValue:(id)value { + return [self createOrUpdateInRealm:[RLMRealm defaultRealm] withValue:value]; +} + ++ (instancetype)createOrUpdateModifiedInDefaultRealmWithValue:(id)value { + return [self createOrUpdateModifiedInRealm:[RLMRealm defaultRealm] withValue:value]; +} + ++ (instancetype)createOrUpdateInRealm:(RLMRealm *)realm withValue:(id)value { + RLMVerifyHasPrimaryKey(self); + return (RLMObject *)RLMCreateObjectInRealmWithValue(realm, [self className], value, RLMUpdatePolicyUpdateAll); +} + ++ (instancetype)createOrUpdateModifiedInRealm:(RLMRealm *)realm withValue:(id)value { + RLMVerifyHasPrimaryKey(self); + return (RLMObject *)RLMCreateObjectInRealmWithValue(realm, [self className], value, RLMUpdatePolicyUpdateChanged); +} + +#pragma mark - Subscripting + +- (id)objectForKeyedSubscript:(NSString *)key { + return RLMObjectBaseObjectForKeyedSubscript(self, key); +} + +- (void)setObject:(id)obj forKeyedSubscript:(NSString *)key { + RLMObjectBaseSetObjectForKeyedSubscript(self, key, obj); +} + +#pragma mark - Getting & Querying + ++ (RLMResults *)allObjects { + return RLMGetObjects(RLMRealm.defaultRealm, self.className, nil); +} + ++ (RLMResults *)allObjectsInRealm:(__unsafe_unretained RLMRealm *const)realm { + return RLMGetObjects(realm, self.className, nil); +} + ++ (RLMResults *)objectsWhere:(NSString *)predicateFormat, ... { + va_list args; + va_start(args, predicateFormat); + RLMResults *results = [self objectsWhere:predicateFormat args:args]; + va_end(args); + return results; +} + ++ (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args { + return [self objectsWithPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]]; +} + ++ (RLMResults *)objectsInRealm:(RLMRealm *)realm where:(NSString *)predicateFormat, ... { + va_list args; + va_start(args, predicateFormat); + RLMResults *results = [self objectsInRealm:realm where:predicateFormat args:args]; + va_end(args); + return results; +} + ++ (RLMResults *)objectsInRealm:(RLMRealm *)realm where:(NSString *)predicateFormat args:(va_list)args { + return [self objectsInRealm:realm withPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]]; +} + ++ (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate { + return RLMGetObjects(RLMRealm.defaultRealm, self.className, predicate); +} + ++ (RLMResults *)objectsInRealm:(RLMRealm *)realm withPredicate:(NSPredicate *)predicate { + return RLMGetObjects(realm, self.className, predicate); +} + ++ (instancetype)objectForPrimaryKey:(id)primaryKey { + return RLMGetObject(RLMRealm.defaultRealm, self.className, primaryKey); +} + ++ (instancetype)objectInRealm:(RLMRealm *)realm forPrimaryKey:(id)primaryKey { + return RLMGetObject(realm, self.className, primaryKey); +} + +#pragma mark - Other Instance Methods + +- (BOOL)isEqualToObject:(RLMObject *)object { + return [object isKindOfClass:RLMObject.class] && RLMObjectBaseAreEqual(self, object); +} + +- (RLMNotificationToken *)addNotificationBlock:(RLMObjectChangeBlock)block { + return RLMObjectAddNotificationBlock(self, ^(NSArray *propertyNames, + NSArray *oldValues, NSArray *newValues, NSError *error) { + if (error) { + block(false, nil, error); + } + else if (!propertyNames) { + block(true, nil, nil); + } + else { + auto properties = [NSMutableArray arrayWithCapacity:propertyNames.count]; + for (NSUInteger i = 0, count = propertyNames.count; i < count; ++i) { + auto prop = [RLMPropertyChange new]; + prop.name = propertyNames[i]; + prop.previousValue = RLMCoerceToNil(oldValues[i]); + prop.value = RLMCoerceToNil(newValues[i]); + [properties addObject:prop]; + } + block(false, properties, nil); + } + }); +} + ++ (NSString *)className { + return [super className]; +} + +#pragma mark - Default values for schema definition + ++ (NSArray *)indexedProperties { + return @[]; +} + ++ (NSDictionary *)linkingObjectsProperties { + return @{}; +} + ++ (NSDictionary *)defaultPropertyValues { + return nil; +} + ++ (NSString *)primaryKey { + return nil; +} + ++ (NSArray *)ignoredProperties { + return nil; +} + ++ (NSArray *)requiredProperties { + return @[]; +} + ++ (bool)_realmIgnoreClass { + return false; +} + +@end + +@implementation RLMDynamicObject + ++ (bool)_realmIgnoreClass { + return true; +} + ++ (BOOL)shouldIncludeInDefaultSchema { + return NO; +} + +- (id)valueForUndefinedKey:(NSString *)key { + return RLMDynamicGetByName(self, key); +} + +- (void)setValue:(id)value forUndefinedKey:(NSString *)key { + RLMDynamicValidatedSet(self, key, value); +} + ++ (RLMObjectSchema *)sharedSchema { + return nil; +} + +@end + +BOOL RLMIsObjectOrSubclass(Class klass) { + return RLMIsKindOfClass(klass, RLMObjectBase.class); +} + +BOOL RLMIsObjectSubclass(Class klass) { + return RLMIsKindOfClass(class_getSuperclass(class_getSuperclass(klass)), RLMObjectBase.class); +} + +@interface RLMObjectNotificationToken : RLMCancellationToken +@end +@implementation RLMObjectNotificationToken { +@public + realm::Object _object; +} +@end + +RLMNotificationToken *RLMObjectAddNotificationBlock(RLMObjectBase *obj, RLMObjectNotificationCallback block) { + if (!obj->_realm) { + @throw RLMException(@"Only objects which are managed by a Realm support change notifications"); + } + [obj->_realm verifyNotificationsAreSupported:true]; + + struct { + void (^block)(NSArray *, NSArray *, NSArray *, NSError *); + RLMObjectBase *object; + + NSArray *propertyNames = nil; + NSArray *oldValues = nil; + bool deleted = false; + + void populateProperties(realm::CollectionChangeSet const& c) { + if (propertyNames) { + return; + } + if (!c.deletions.empty()) { + deleted = true; + return; + } + if (c.columns.empty()) { + return; + } + + auto properties = [NSMutableArray new]; + for (size_t i = 0; i < c.columns.size(); ++i) { + if (c.columns[i].empty()) { + continue; + } + if (auto prop = object->_info->propertyForTableColumn(i)) { + [properties addObject:prop.name]; + } + } + if (properties.count) { + propertyNames = properties; + } + } + + NSArray *readValues(realm::CollectionChangeSet const& c) { + if (c.empty()) { + return nil; + } + populateProperties(c); + if (!propertyNames) { + return nil; + } + + auto values = [NSMutableArray arrayWithCapacity:propertyNames.count]; + for (NSString *name in propertyNames) { + id value = [object valueForKey:name]; + if (!value || [value isKindOfClass:[RLMArray class]]) { + [values addObject:NSNull.null]; + } + else { + [values addObject:value]; + } + } + return values; + } + + void before(realm::CollectionChangeSet const& c) { + @autoreleasepool { + oldValues = readValues(c); + } + } + + void after(realm::CollectionChangeSet const& c) { + @autoreleasepool { + auto newValues = readValues(c); + if (deleted) { + block(nil, nil, nil, nil); + } + else if (newValues) { + block(propertyNames, oldValues, newValues, nil); + } + propertyNames = nil; + oldValues = nil; + } + } + + void error(std::exception_ptr err) { + @autoreleasepool { + try { + rethrow_exception(err); + } + catch (...) { + NSError *error = nil; + RLMRealmTranslateException(&error); + block(nil, nil, nil, error); + } + } + } + } callback{block, obj}; + + realm::Object object(obj->_realm->_realm, *obj->_info->objectSchema, obj->_row); + auto token = [[RLMObjectNotificationToken alloc] initWithToken:object.add_notification_callback(callback) realm:obj->_realm]; + token->_object = std::move(object); + return token; +} + +@implementation RLMPropertyChange +@end diff --git a/!main project/Pods/Realm/Realm/RLMObjectBase.mm b/!main project/Pods/Realm/Realm/RLMObjectBase.mm new file mode 100644 index 0000000..3b264c8 --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMObjectBase.mm @@ -0,0 +1,468 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMObject_Private.hpp" + +#import "RLMAccessor.h" +#import "RLMArray_Private.hpp" +#import "RLMListBase.h" +#import "RLMObjectSchema_Private.hpp" +#import "RLMObjectStore.h" +#import "RLMObservation.hpp" +#import "RLMOptionalBase.h" +#import "RLMProperty_Private.h" +#import "RLMRealm_Private.hpp" +#import "RLMSchema_Private.h" +#import "RLMSwiftSupport.h" +#import "RLMThreadSafeReference_Private.hpp" +#import "RLMUtil.hpp" + +#import "object.hpp" +#import "object_schema.hpp" +#import "shared_realm.hpp" + +using namespace realm; + +const NSUInteger RLMDescriptionMaxDepth = 5; + + +static bool isManagedAccessorClass(Class cls) { + const char *className = class_getName(cls); + const char accessorClassPrefix[] = "RLM:Managed"; + return strncmp(className, accessorClassPrefix, sizeof(accessorClassPrefix) - 1) == 0; +} + +static bool maybeInitObjectSchemaForUnmanaged(RLMObjectBase *obj) { + Class cls = obj.class; + if (isManagedAccessorClass(cls)) { + return false; + } + + obj->_objectSchema = [cls sharedSchema]; + if (!obj->_objectSchema) { + return false; + } + + // set default values + if (!obj->_objectSchema.isSwiftClass) { + NSDictionary *dict = RLMDefaultValuesForObjectSchema(obj->_objectSchema); + for (NSString *key in dict) { + [obj setValue:dict[key] forKey:key]; + } + } + + // set unmanaged accessor class + object_setClass(obj, obj->_objectSchema.unmanagedClass); + return true; +} + +@interface RLMObjectBase () +@end + +@implementation RLMObjectBase +// unmanaged init +- (instancetype)init { + if ((self = [super init])) { + maybeInitObjectSchemaForUnmanaged(self); + } + return self; +} + +- (void)dealloc { + // This can't be a unique_ptr because associated objects are removed + // *after* c++ members are destroyed and dealloc is called, and we need it + // to be in a validish state when that happens + delete _observationInfo; + _observationInfo = nullptr; +} + +static id coerceToObjectType(id obj, Class cls, RLMSchema *schema) { + if ([obj isKindOfClass:cls]) { + return obj; + } + id value = [[cls alloc] init]; + RLMInitializeWithValue(value, obj, schema); + return value; +} + +static id validatedObjectForProperty(__unsafe_unretained id const obj, + __unsafe_unretained RLMObjectSchema *const objectSchema, + __unsafe_unretained RLMProperty *const prop, + __unsafe_unretained RLMSchema *const schema) { + RLMValidateValueForProperty(obj, objectSchema, prop); + if (!obj || obj == NSNull.null) { + return nil; + } + if (prop.type == RLMPropertyTypeObject) { + Class objectClass = schema[prop.objectClassName].objectClass; + if (prop.array) { + NSMutableArray *ret = [[NSMutableArray alloc] init]; + for (id el in obj) { + [ret addObject:coerceToObjectType(el, objectClass, schema)]; + } + return ret; + } + return coerceToObjectType(obj, objectClass, schema); + } + return obj; +} + +void RLMInitializeWithValue(RLMObjectBase *self, id value, RLMSchema *schema) { + if (!value || value == NSNull.null) { + @throw RLMException(@"Must provide a non-nil value."); + } + + RLMObjectSchema *objectSchema = self->_objectSchema; + if (!objectSchema) { + // Will be nil if we're called during schema init, when we don't want + // to actually populate the object anyway + return; + } + + NSArray *properties = objectSchema.properties; + if (NSArray *array = RLMDynamicCast(value)) { + if (array.count > properties.count) { + @throw RLMException(@"Invalid array input: more values (%llu) than properties (%llu).", + (unsigned long long)array.count, (unsigned long long)properties.count); + } + NSUInteger i = 0; + for (id val in array) { + RLMProperty *prop = properties[i++]; + [self setValue:validatedObjectForProperty(RLMCoerceToNil(val), objectSchema, prop, schema) + forKey:prop.name]; + } + } + else { + // assume our object is an NSDictionary or an object with kvc properties + for (RLMProperty *prop in properties) { + id obj = RLMValidatedValueForProperty(value, prop.name, objectSchema.className); + + // don't set unspecified properties + if (!obj) { + continue; + } + + [self setValue:validatedObjectForProperty(RLMCoerceToNil(obj), objectSchema, prop, schema) + forKey:prop.name]; + } + } +} + +id RLMCreateManagedAccessor(Class cls, RLMClassInfo *info) { + RLMObjectBase *obj = [[cls alloc] init]; + obj->_info = info; + obj->_realm = info->realm; + obj->_objectSchema = info->rlmObjectSchema; + return obj; +} + +- (id)valueForKey:(NSString *)key { + if (_observationInfo) { + return _observationInfo->valueForKey(key); + } + return [super valueForKey:key]; +} + +// Generic Swift properties can't be dynamic, so KVO doesn't work for them by default +- (id)valueForUndefinedKey:(NSString *)key { + RLMProperty *prop = _objectSchema[key]; + if (Class accessor = prop.swiftAccessor) { + return [accessor get:(char *)(__bridge void *)self + ivar_getOffset(prop.swiftIvar)]; + } + if (Ivar ivar = prop.swiftIvar) { + return RLMCoerceToNil(object_getIvar(self, ivar)); + } + return [super valueForUndefinedKey:key]; +} + +- (void)setValue:(id)value forUndefinedKey:(NSString *)key { + value = RLMCoerceToNil(value); + RLMProperty *property = _objectSchema[key]; + if (Ivar ivar = property.swiftIvar) { + if (property.array) { + value = RLMAsFastEnumeration(value); + RLMArray *array = [object_getIvar(self, ivar) _rlmArray]; + [array removeAllObjects]; + + if (value) { + [array addObjects:validatedObjectForProperty(value, _objectSchema, property, + RLMSchema.partialPrivateSharedSchema)]; + } + } + else if (property.optional) { + RLMSetOptional(object_getIvar(self, ivar), value); + } + return; + } + [super setValue:value forUndefinedKey:key]; +} + +// overridden at runtime per-class for performance ++ (NSString *)className { + NSString *className = NSStringFromClass(self); + if ([RLMSwiftSupport isSwiftClassName:className]) { + className = [RLMSwiftSupport demangleClassName:className]; + } + return className; +} + +// overridden at runtime per-class for performance ++ (RLMObjectSchema *)sharedSchema { + return [RLMSchema sharedSchemaForClass:self.class]; +} + ++ (void)initializeLinkedObjectSchemas { + for (RLMProperty *prop in self.sharedSchema.properties) { + if (prop.type == RLMPropertyTypeObject && !RLMSchema.partialPrivateSharedSchema[prop.objectClassName]) { + [[RLMSchema classForString:prop.objectClassName] initializeLinkedObjectSchemas]; + } + } +} + ++ (nullable NSArray *)_getPropertiesWithInstance:(__unused id)obj { + return nil; +} + +- (NSString *)description { + if (self.isInvalidated) { + return @"[invalid object]"; + } + + return [self descriptionWithMaxDepth:RLMDescriptionMaxDepth]; +} + +- (NSString *)descriptionWithMaxDepth:(NSUInteger)depth { + if (depth == 0) { + return @""; + } + + NSString *baseClassName = _objectSchema.className; + NSMutableString *mString = [NSMutableString stringWithFormat:@"%@ {\n", baseClassName]; + + for (RLMProperty *property in _objectSchema.properties) { + id object = [(id)self objectForKeyedSubscript:property.name]; + NSString *sub; + if ([object respondsToSelector:@selector(descriptionWithMaxDepth:)]) { + sub = [object descriptionWithMaxDepth:depth - 1]; + } + else if (property.type == RLMPropertyTypeData) { + static NSUInteger maxPrintedDataLength = 24; + NSData *data = object; + NSUInteger length = data.length; + if (length > maxPrintedDataLength) { + data = [NSData dataWithBytes:data.bytes length:maxPrintedDataLength]; + } + NSString *dataDescription = [data description]; + sub = [NSString stringWithFormat:@"<%@ — %lu total bytes>", [dataDescription substringWithRange:NSMakeRange(1, dataDescription.length - 2)], (unsigned long)length]; + } + else { + sub = [object description]; + } + [mString appendFormat:@"\t%@ = %@;\n", property.name, [sub stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]]; + } + [mString appendString:@"}"]; + + return [NSString stringWithString:mString]; +} + +- (RLMRealm *)realm { + return _realm; +} + +- (RLMObjectSchema *)objectSchema { + return _objectSchema; +} + +- (BOOL)isInvalidated { + // if not unmanaged and our accessor has been detached, we have been deleted + return self.class == _objectSchema.accessorClass && !_row.is_attached(); +} + +- (BOOL)isEqual:(id)object { + if (RLMObjectBase *other = RLMDynamicCast(object)) { + if (_objectSchema.primaryKeyProperty) { + return RLMObjectBaseAreEqual(self, other); + } + } + return [super isEqual:object]; +} + +- (NSUInteger)hash { + if (_objectSchema.primaryKeyProperty) { + id primaryProperty = [self valueForKey:_objectSchema.primaryKeyProperty.name]; + + // modify the hash of our primary key value to avoid potential (although unlikely) collisions + return [primaryProperty hash] ^ 1; + } + else { + return [super hash]; + } +} + ++ (BOOL)shouldIncludeInDefaultSchema { + return RLMIsObjectSubclass(self); +} + ++ (NSString *)_realmObjectName { + return nil; +} + ++ (NSDictionary *)_realmColumnNames { + return nil; +} + ++ (bool)_realmIgnoreClass { + return false; +} + +- (id)mutableArrayValueForKey:(NSString *)key { + id obj = [self valueForKey:key]; + if ([obj isKindOfClass:[RLMArray class]]) { + return obj; + } + return [super mutableArrayValueForKey:key]; +} + +- (void)addObserver:(id)observer + forKeyPath:(NSString *)keyPath + options:(NSKeyValueObservingOptions)options + context:(void *)context { + if (!_observationInfo) { + _observationInfo = new RLMObservationInfo(self); + } + _observationInfo->recordObserver(_row, _info, _objectSchema, keyPath); + + [super addObserver:observer forKeyPath:keyPath options:options context:context]; +} + +- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath { + [super removeObserver:observer forKeyPath:keyPath]; + if (_observationInfo) + _observationInfo->removeObserver(); +} + ++ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { + if (isManagedAccessorClass(self) && [class_getSuperclass(self.class) sharedSchema][key]) { + return NO; + } + + return [super automaticallyNotifiesObserversForKey:key]; +} + +#pragma mark - Thread Confined Protocol Conformance + +- (std::unique_ptr)makeThreadSafeReference { + Object object(_realm->_realm, *_info->objectSchema, _row); + realm::ThreadSafeReference reference = _realm->_realm->obtain_thread_safe_reference(std::move(object)); + return std::make_unique>(std::move(reference)); +} + +- (id)objectiveCMetadata { + return nil; +} + ++ (instancetype)objectWithThreadSafeReference:(std::unique_ptr)reference + metadata:(__unused id)metadata + realm:(RLMRealm *)realm { + REALM_ASSERT_DEBUG(dynamic_cast *>(reference.get())); + auto object_reference = static_cast *>(reference.get()); + + Object object = realm->_realm->resolve_thread_safe_reference(std::move(*object_reference)); + if (!object.is_valid()) { + return nil; + } + NSString *objectClassName = @(object.get_object_schema().name.c_str()); + + return RLMCreateObjectAccessor(realm->_info[objectClassName], object.row().get_index()); +} + +@end + +RLMRealm *RLMObjectBaseRealm(__unsafe_unretained RLMObjectBase *object) { + return object ? object->_realm : nil; +} + +RLMObjectSchema *RLMObjectBaseObjectSchema(__unsafe_unretained RLMObjectBase *object) { + return object ? object->_objectSchema : nil; +} + +id RLMObjectBaseObjectForKeyedSubscript(RLMObjectBase *object, NSString *key) { + if (!object) { + return nil; + } + + if (object->_realm) { + return RLMDynamicGetByName(object, key); + } + else { + return [object valueForKey:key]; + } +} + +void RLMObjectBaseSetObjectForKeyedSubscript(RLMObjectBase *object, NSString *key, id obj) { + if (!object) { + return; + } + + if (object->_realm || object.class == object->_objectSchema.accessorClass) { + RLMDynamicValidatedSet(object, key, obj); + } + else { + [object setValue:obj forKey:key]; + } +} + + +BOOL RLMObjectBaseAreEqual(RLMObjectBase *o1, RLMObjectBase *o2) { + // if not the correct types throw + if ((o1 && ![o1 isKindOfClass:RLMObjectBase.class]) || (o2 && ![o2 isKindOfClass:RLMObjectBase.class])) { + @throw RLMException(@"Can only compare objects of class RLMObjectBase"); + } + // if identical object (or both are nil) + if (o1 == o2) { + return YES; + } + // if one is nil + if (o1 == nil || o2 == nil) { + return NO; + } + // if not in realm or differing realms + if (o1->_realm == nil || o1->_realm != o2->_realm) { + return NO; + } + // if either are detached + if (!o1->_row.is_attached() || !o2->_row.is_attached()) { + return NO; + } + // if table and index are the same + return o1->_row.get_table() == o2->_row.get_table() + && o1->_row.get_index() == o2->_row.get_index(); +} + +id RLMValidatedValueForProperty(id object, NSString *key, NSString *className) { + @try { + return [object valueForKey:key]; + } + @catch (NSException *e) { + if ([e.name isEqualToString:NSUndefinedKeyException]) { + @throw RLMException(@"Invalid value '%@' to initialize object of type '%@': missing key '%@'", + object, className, key); + } + @throw; + } +} diff --git a/!main project/Pods/Realm/Realm/RLMObjectSchema.mm b/!main project/Pods/Realm/Realm/RLMObjectSchema.mm new file mode 100644 index 0000000..4bbd1c1 --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMObjectSchema.mm @@ -0,0 +1,370 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMObjectSchema_Private.hpp" + +#import "RLMArray.h" +#import "RLMListBase.h" +#import "RLMObject_Private.h" +#import "RLMProperty_Private.hpp" +#import "RLMRealm_Dynamic.h" +#import "RLMRealm_Private.hpp" +#import "RLMSchema_Private.h" +#import "RLMSwiftSupport.h" +#import "RLMUtil.hpp" + +#import "object_schema.hpp" +#import "object_store.hpp" + +using namespace realm; + +// private properties +@interface RLMObjectSchema () +@property (nonatomic, readwrite) NSDictionary *allPropertiesByName; +@property (nonatomic, readwrite) NSString *className; +@end + +@implementation RLMObjectSchema { + NSArray *_swiftGenericProperties; +} + +- (instancetype)initWithClassName:(NSString *)objectClassName objectClass:(Class)objectClass properties:(NSArray *)properties { + self = [super init]; + self.className = objectClassName; + self.properties = properties; + self.objectClass = objectClass; + self.accessorClass = objectClass; + self.unmanagedClass = objectClass; + return self; +} + +// return properties by name +- (RLMProperty *)objectForKeyedSubscript:(__unsafe_unretained NSString *const)key { + return _allPropertiesByName[key]; +} + +// create property map when setting property array +- (void)setProperties:(NSArray *)properties { + _properties = properties; + [self _propertiesDidChange]; +} + +- (void)setComputedProperties:(NSArray *)computedProperties { + _computedProperties = computedProperties; + [self _propertiesDidChange]; +} + +- (void)_propertiesDidChange { + NSMutableDictionary *map = [NSMutableDictionary dictionaryWithCapacity:_properties.count + _computedProperties.count]; + NSUInteger index = 0; + for (RLMProperty *prop in _properties) { + prop.index = index++; + map[prop.name] = prop; + if (prop.isPrimary) { + self.primaryKeyProperty = prop; + } + } + index = 0; + for (RLMProperty *prop in _computedProperties) { + prop.index = index++; + map[prop.name] = prop; + } + _allPropertiesByName = map; +} + + +- (void)setPrimaryKeyProperty:(RLMProperty *)primaryKeyProperty { + _primaryKeyProperty.isPrimary = NO; + primaryKeyProperty.isPrimary = YES; + _primaryKeyProperty = primaryKeyProperty; +} + ++ (instancetype)schemaForObjectClass:(Class)objectClass { + RLMObjectSchema *schema = [RLMObjectSchema new]; + + // determine classname from objectclass as className method has not yet been updated + NSString *className = NSStringFromClass(objectClass); + bool hasSwiftName = [RLMSwiftSupport isSwiftClassName:className]; + if (hasSwiftName) { + className = [RLMSwiftSupport demangleClassName:className]; + } + + static Class s_swiftObjectClass = NSClassFromString(@"RealmSwiftObject"); + bool isSwift = hasSwiftName || [objectClass isSubclassOfClass:s_swiftObjectClass]; + + schema.className = className; + schema.objectClass = objectClass; + schema.accessorClass = objectClass; + schema.isSwiftClass = isSwift; + + // create array of RLMProperties, inserting properties of superclasses first + Class cls = objectClass; + Class superClass = class_getSuperclass(cls); + NSArray *allProperties = @[]; + while (superClass && superClass != RLMObjectBase.class) { + allProperties = [[RLMObjectSchema propertiesForClass:cls isSwift:isSwift] + arrayByAddingObjectsFromArray:allProperties]; + cls = superClass; + superClass = class_getSuperclass(superClass); + } + NSArray *persistedProperties = [allProperties filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(RLMProperty *property, NSDictionary *) { + return !RLMPropertyTypeIsComputed(property.type); + }]]; + schema.properties = persistedProperties; + + NSArray *computedProperties = [allProperties filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(RLMProperty *property, NSDictionary *) { + return RLMPropertyTypeIsComputed(property.type); + }]]; + schema.computedProperties = computedProperties; + + // verify that we didn't add any properties twice due to inheritance + if (allProperties.count != [NSSet setWithArray:[allProperties valueForKey:@"name"]].count) { + NSCountedSet *countedPropertyNames = [NSCountedSet setWithArray:[allProperties valueForKey:@"name"]]; + NSArray *duplicatePropertyNames = [countedPropertyNames filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *) { + return [countedPropertyNames countForObject:object] > 1; + }]].allObjects; + + if (duplicatePropertyNames.count == 1) { + @throw RLMException(@"Property '%@' is declared multiple times in the class hierarchy of '%@'", duplicatePropertyNames.firstObject, className); + } else { + @throw RLMException(@"Object '%@' has properties that are declared multiple times in its class hierarchy: '%@'", className, [duplicatePropertyNames componentsJoinedByString:@"', '"]); + } + } + + if (NSString *primaryKey = [objectClass primaryKey]) { + for (RLMProperty *prop in schema.properties) { + if ([primaryKey isEqualToString:prop.name]) { + prop.indexed = YES; + schema.primaryKeyProperty = prop; + break; + } + } + + if (!schema.primaryKeyProperty) { + @throw RLMException(@"Primary key property '%@' does not exist on object '%@'", primaryKey, className); + } + if (schema.primaryKeyProperty.type != RLMPropertyTypeInt && schema.primaryKeyProperty.type != RLMPropertyTypeString) { + @throw RLMException(@"Property '%@' cannot be made the primary key of '%@' because it is not a 'string' or 'int' property.", + primaryKey, className); + } + } + + for (RLMProperty *prop in schema.properties) { + if (prop.optional && prop.array && (prop.type == RLMPropertyTypeObject || prop.type == RLMPropertyTypeLinkingObjects)) { + // FIXME: message is awkward + @throw RLMException(@"Property '%@.%@' cannot be made optional because optional '%@' properties are not supported.", + className, prop.name, RLMTypeToString(prop.type)); + } + } + + return schema; +} + ++ (NSArray *)propertiesForClass:(Class)objectClass isSwift:(bool)isSwiftClass { + // For Swift classes we need an instance of the object when parsing properties + id swiftObjectInstance = isSwiftClass ? [[objectClass alloc] init] : nil; + + if (NSArray *props = [objectClass _getPropertiesWithInstance:swiftObjectInstance]) { + return props; + } + + NSArray *ignoredProperties = [objectClass ignoredProperties]; + NSDictionary *linkingObjectsProperties = [objectClass linkingObjectsProperties]; + NSDictionary *columnNameMap = [objectClass _realmColumnNames]; + + unsigned int count; + std::unique_ptr props(class_copyPropertyList(objectClass, &count), &free); + NSMutableArray *propArray = [NSMutableArray arrayWithCapacity:count]; + NSSet *indexed = [[NSSet alloc] initWithArray:[objectClass indexedProperties]]; + for (unsigned int i = 0; i < count; i++) { + NSString *propertyName = @(property_getName(props[i])); + if ([ignoredProperties containsObject:propertyName]) { + continue; + } + + RLMProperty *prop = nil; + if (isSwiftClass) { + prop = [[RLMProperty alloc] initSwiftPropertyWithName:propertyName + indexed:[indexed containsObject:propertyName] + linkPropertyDescriptor:linkingObjectsProperties[propertyName] + property:props[i] + instance:swiftObjectInstance]; + } + else { + prop = [[RLMProperty alloc] initWithName:propertyName + indexed:[indexed containsObject:propertyName] + linkPropertyDescriptor:linkingObjectsProperties[propertyName] + property:props[i]]; + } + + if (prop) { + if (columnNameMap) { + prop.columnName = columnNameMap[prop.name]; + } + [propArray addObject:prop]; + } + } + + if (auto requiredProperties = [objectClass requiredProperties]) { + for (RLMProperty *property in propArray) { + bool required = [requiredProperties containsObject:property.name]; + if (required && property.type == RLMPropertyTypeObject && !property.array) { + @throw RLMException(@"Object properties cannot be made required, " + "but '+[%@ requiredProperties]' included '%@'", objectClass, property.name); + } + property.optional &= !required; + } + } + + for (RLMProperty *property in propArray) { + if (!property.optional && property.type == RLMPropertyTypeObject && !property.array) { + @throw RLMException(@"The `%@.%@` property must be marked as being optional.", + [objectClass className], property.name); + } + } + + return propArray; +} + +- (id)copyWithZone:(NSZone *)zone { + RLMObjectSchema *schema = [[RLMObjectSchema allocWithZone:zone] init]; + schema->_objectClass = _objectClass; + schema->_className = _className; + schema->_objectClass = _objectClass; + schema->_accessorClass = _objectClass; + schema->_unmanagedClass = _unmanagedClass; + schema->_isSwiftClass = _isSwiftClass; + + // call property setter to reset map and primary key + schema.properties = [[NSArray allocWithZone:zone] initWithArray:_properties copyItems:YES]; + schema.computedProperties = [[NSArray allocWithZone:zone] initWithArray:_computedProperties copyItems:YES]; + + return schema; +} + +- (BOOL)isEqualToObjectSchema:(RLMObjectSchema *)objectSchema { + if (objectSchema.properties.count != _properties.count) { + return NO; + } + + if (![_properties isEqualToArray:objectSchema.properties]) { + return NO; + } + if (![_computedProperties isEqualToArray:objectSchema.computedProperties]) { + return NO; + } + + return YES; +} + +- (NSString *)description { + NSMutableString *propertiesString = [NSMutableString string]; + for (RLMProperty *property in self.properties) { + [propertiesString appendFormat:@"\t%@\n", [property.description stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]]; + } + for (RLMProperty *property in self.computedProperties) { + [propertiesString appendFormat:@"\t%@\n", [property.description stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]]; + } + return [NSString stringWithFormat:@"%@ {\n%@}", self.className, propertiesString]; +} + +- (NSString *)objectName { + return [self.objectClass _realmObjectName] ?: _className; +} + +- (realm::ObjectSchema)objectStoreCopy:(RLMSchema *)schema { + ObjectSchema objectSchema; + objectSchema.name = self.objectName.UTF8String; + objectSchema.primary_key = _primaryKeyProperty ? _primaryKeyProperty.columnName.UTF8String : ""; + for (RLMProperty *prop in _properties) { + Property p = [prop objectStoreCopy:schema]; + p.is_primary = (prop == _primaryKeyProperty); + objectSchema.persisted_properties.push_back(std::move(p)); + } + for (RLMProperty *prop in _computedProperties) { + objectSchema.computed_properties.push_back([prop objectStoreCopy:schema]); + } + return objectSchema; +} + ++ (instancetype)objectSchemaForObjectStoreSchema:(realm::ObjectSchema const&)objectSchema { + RLMObjectSchema *schema = [RLMObjectSchema new]; + schema.className = @(objectSchema.name.c_str()); + + // create array of RLMProperties + NSMutableArray *properties = [NSMutableArray arrayWithCapacity:objectSchema.persisted_properties.size()]; + for (const Property &prop : objectSchema.persisted_properties) { + RLMProperty *property = [RLMProperty propertyForObjectStoreProperty:prop]; + property.isPrimary = (prop.name == objectSchema.primary_key); + [properties addObject:property]; + } + schema.properties = properties; + + NSMutableArray *computedProperties = [NSMutableArray arrayWithCapacity:objectSchema.computed_properties.size()]; + for (const Property &prop : objectSchema.computed_properties) { + [computedProperties addObject:[RLMProperty propertyForObjectStoreProperty:prop]]; + } + schema.computedProperties = computedProperties; + + // get primary key from realm metadata + if (objectSchema.primary_key.length()) { + NSString *primaryKeyString = [NSString stringWithUTF8String:objectSchema.primary_key.c_str()]; + schema.primaryKeyProperty = schema[primaryKeyString]; + if (!schema.primaryKeyProperty) { + @throw RLMException(@"No property matching primary key '%@'", primaryKeyString); + } + } + + // for dynamic schema use vanilla RLMDynamicObject accessor classes + schema.objectClass = RLMObject.class; + schema.accessorClass = RLMDynamicObject.class; + schema.unmanagedClass = RLMObject.class; + + return schema; +} + +- (NSArray *)swiftGenericProperties { + if (_swiftGenericProperties) { + return _swiftGenericProperties; + } + + // This check isn't semantically required, but avoiding accessing the local + // static helps perf in the obj-c case + if (!_isSwiftClass) { + return _swiftGenericProperties = @[]; + } + + // Check if it's a swift class using the obj-c API + static Class s_swiftObjectClass = NSClassFromString(@"RealmSwiftObject"); + if (![_accessorClass isSubclassOfClass:s_swiftObjectClass]) { + return _swiftGenericProperties = @[]; + } + + NSMutableArray *genericProperties = [NSMutableArray new]; + for (RLMProperty *prop in _properties) { + if (prop->_swiftIvar) { + [genericProperties addObject:prop]; + } + } + // Currently all computed properties are Swift generics + [genericProperties addObjectsFromArray:_computedProperties]; + + return _swiftGenericProperties = genericProperties; +} + +@end diff --git a/!main project/Pods/Realm/Realm/RLMObjectStore.mm b/!main project/Pods/Realm/Realm/RLMObjectStore.mm new file mode 100644 index 0000000..3625004 --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMObjectStore.mm @@ -0,0 +1,273 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMObjectStore.h" + +#import "RLMAccessor.hpp" +#import "RLMArray_Private.hpp" +#import "RLMListBase.h" +#import "RLMObservation.hpp" +#import "RLMObject_Private.hpp" +#import "RLMObjectSchema_Private.hpp" +#import "RLMOptionalBase.h" +#import "RLMProperty_Private.h" +#import "RLMQueryUtil.hpp" +#import "RLMRealm_Private.hpp" +#import "RLMSchema_Private.h" +#import "RLMSwiftSupport.h" +#import "RLMUtil.hpp" + +#import "object_store.hpp" +#import "results.hpp" +#import "shared_realm.hpp" + +#import + +#import + +using namespace realm; + +static_assert(RLMUpdatePolicyError == static_cast(CreatePolicy::ForceCreate), ""); +static_assert(RLMUpdatePolicyUpdateAll == static_cast(CreatePolicy::UpdateAll), ""); +static_assert(RLMUpdatePolicyUpdateChanged == static_cast(CreatePolicy::UpdateModified), ""); + +void RLMRealmCreateAccessors(RLMSchema *schema) { + const size_t bufferSize = sizeof("RLM:Managed ") // includes null terminator + + std::numeric_limits::digits10 + + realm::Group::max_table_name_length; + + char className[bufferSize] = "RLM:Managed "; + char *const start = className + strlen(className); + + for (RLMObjectSchema *objectSchema in schema.objectSchema) { + if (objectSchema.accessorClass != objectSchema.objectClass) { + continue; + } + + static unsigned long long count = 0; + sprintf(start, "%llu %s", count++, objectSchema.className.UTF8String); + objectSchema.accessorClass = RLMManagedAccessorClassForObjectClass(objectSchema.objectClass, objectSchema, className); + } +} + +static inline void RLMVerifyRealmRead(__unsafe_unretained RLMRealm *const realm) { + if (!realm) { + @throw RLMException(@"Realm must not be nil"); + } + [realm verifyThread]; +} + +static inline void RLMVerifyInWriteTransaction(__unsafe_unretained RLMRealm *const realm) { + RLMVerifyRealmRead(realm); + // if realm is not writable throw + if (!realm.inWriteTransaction) { + @throw RLMException(@"Can only add, remove, or create objects in a Realm in a write transaction - call beginWriteTransaction on an RLMRealm instance first."); + } +} + +void RLMInitializeSwiftAccessorGenerics(__unsafe_unretained RLMObjectBase *const object) { + if (!object || !object->_row || !object->_objectSchema->_isSwiftClass) { + return; + } + if (![object isKindOfClass:object->_objectSchema.objectClass]) { + // It can be a different class if it's a dynamic object, and those don't + // require any init here (and would crash since they don't have the ivars) + return; + } + + for (RLMProperty *prop in object->_objectSchema.swiftGenericProperties) { + if (prop.type == RLMPropertyTypeLinkingObjects) { + [prop.swiftAccessor initializeObject:(char *)(__bridge void *)object + ivar_getOffset(prop.swiftIvar) + parent:object property:prop]; + } + else if (prop.array) { + id ivar = object_getIvar(object, prop.swiftIvar); + RLMArray *array = [[RLMManagedArray alloc] initWithParent:object property:prop]; + [ivar set_rlmArray:array]; + } + else { + id ivar = object_getIvar(object, prop.swiftIvar); + RLMInitializeManagedOptional(ivar, object, prop); + } + } +} + +void RLMVerifyHasPrimaryKey(Class cls) { + RLMObjectSchema *schema = [cls sharedSchema]; + if (!schema.primaryKeyProperty) { + NSString *reason = [NSString stringWithFormat:@"'%@' does not have a primary key and can not be updated", schema.className]; + @throw [NSException exceptionWithName:@"RLMException" reason:reason userInfo:nil]; + } +} + +void RLMAddObjectToRealm(__unsafe_unretained RLMObjectBase *const object, + __unsafe_unretained RLMRealm *const realm, + RLMUpdatePolicy updatePolicy) { + RLMVerifyInWriteTransaction(realm); + + // verify that object is unmanaged + if (object.invalidated) { + @throw RLMException(@"Adding a deleted or invalidated object to a Realm is not permitted"); + } + if (object->_realm) { + if (object->_realm == realm) { + // Adding an object to the Realm it's already manged by is a no-op + return; + } + // for differing realms users must explicitly create the object in the second realm + @throw RLMException(@"Object is already managed by another Realm. Use create instead to copy it into this Realm."); + } + if (object->_observationInfo && object->_observationInfo->hasObservers()) { + @throw RLMException(@"Cannot add an object with observers to a Realm"); + } + + auto& info = realm->_info[object->_objectSchema.className]; + RLMAccessorContext c{info, true}; + object->_info = &info; + object->_realm = realm; + object->_objectSchema = info.rlmObjectSchema; + try { + realm::Object::create(c, realm->_realm, *info.objectSchema, (id)object, + static_cast(updatePolicy), + -1, &object->_row); + } + catch (std::exception const& e) { + @throw RLMException(e); + } + object_setClass(object, info.rlmObjectSchema.accessorClass); + RLMInitializeSwiftAccessorGenerics(object); +} + +RLMObjectBase *RLMCreateObjectInRealmWithValue(RLMRealm *realm, NSString *className, + id value, RLMUpdatePolicy updatePolicy) { + RLMVerifyInWriteTransaction(realm); + + if (updatePolicy != RLMUpdatePolicyError && RLMIsObjectSubclass([value class])) { + RLMObjectBase *obj = value; + if (obj->_realm == realm && [obj->_objectSchema.className isEqualToString:className]) { + // This is a no-op if value is an RLMObject of the same type already backed by the target realm. + return value; + } + } + + if (!value || value == NSNull.null) { + @throw RLMException(@"Must provide a non-nil value."); + } + + auto& info = realm->_info[className]; + if ([value isKindOfClass:[NSArray class]] && [value count] > info.objectSchema->persisted_properties.size()) { + @throw RLMException(@"Invalid array input: more values (%llu) than properties (%llu).", + (unsigned long long)[value count], + (unsigned long long)info.objectSchema->persisted_properties.size()); + } + + RLMAccessorContext c{info, false}; + RLMObjectBase *object = RLMCreateManagedAccessor(info.rlmObjectSchema.accessorClass, &info); + try { + object->_row = realm::Object::create(c, realm->_realm, *info.objectSchema, (id)value, + static_cast(updatePolicy)).row(); + } + catch (std::exception const& e) { + @throw RLMException(e); + } + RLMInitializeSwiftAccessorGenerics(object); + return object; +} + +void RLMDeleteObjectFromRealm(__unsafe_unretained RLMObjectBase *const object, + __unsafe_unretained RLMRealm *const realm) { + if (realm != object->_realm) { + @throw RLMException(@"Can only delete an object from the Realm it belongs to."); + } + + RLMVerifyInWriteTransaction(object->_realm); + + // move last row to row we are deleting + if (object->_row.is_attached()) { + RLMTrackDeletions(realm, ^{ + object->_row.move_last_over(); + }); + } + + // set realm to nil + object->_realm = nil; +} + +void RLMDeleteAllObjectsFromRealm(RLMRealm *realm) { + RLMVerifyInWriteTransaction(realm); + + // clear table for each object schema + for (auto& info : realm->_info) { + RLMClearTable(info.second); + } +} + +RLMResults *RLMGetObjects(__unsafe_unretained RLMRealm *const realm, + NSString *objectClassName, + NSPredicate *predicate) { + RLMVerifyRealmRead(realm); + + // create view from table and predicate + RLMClassInfo& info = realm->_info[objectClassName]; + if (!info.table()) { + // read-only realms may be missing tables since we can't add any + // missing ones on init + return [RLMResults resultsWithObjectInfo:info results:{}]; + } + + if (predicate) { + realm::Query query = RLMPredicateToQuery(predicate, info.rlmObjectSchema, realm.schema, realm.group); + return [RLMResults resultsWithObjectInfo:info + results:realm::Results(realm->_realm, std::move(query))]; + } + + return [RLMResults resultsWithObjectInfo:info + results:realm::Results(realm->_realm, *info.table())]; +} + +id RLMGetObject(RLMRealm *realm, NSString *objectClassName, id key) { + RLMVerifyRealmRead(realm); + + auto& info = realm->_info[objectClassName]; + if (RLMProperty *prop = info.propertyForPrimaryKey()) { + RLMValidateValueForProperty(key, info.rlmObjectSchema, prop); + } + try { + RLMAccessorContext c{info}; + auto obj = realm::Object::get_for_primary_key(c, realm->_realm, *info.objectSchema, + key ?: NSNull.null); + if (!obj.is_valid()) + return nil; + return RLMCreateObjectAccessor(info, obj.row()); + } + catch (std::exception const& e) { + @throw RLMException(e); + } +} + +RLMObjectBase *RLMCreateObjectAccessor(RLMClassInfo& info, NSUInteger index) { + return RLMCreateObjectAccessor(info, (*info.table())[index]); +} + +// Create accessor and register with realm +RLMObjectBase *RLMCreateObjectAccessor(RLMClassInfo& info, realm::RowExpr row) { + RLMObjectBase *accessor = RLMCreateManagedAccessor(info.rlmObjectSchema.accessorClass, &info); + accessor->_row = row; + RLMInitializeSwiftAccessorGenerics(accessor); + return accessor; +} diff --git a/!main project/Pods/Realm/Realm/RLMObservation.mm b/!main project/Pods/Realm/Realm/RLMObservation.mm new file mode 100644 index 0000000..f8102ed --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMObservation.mm @@ -0,0 +1,499 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMObservation.hpp" + +#import "RLMAccessor.h" +#import "RLMArray_Private.hpp" +#import "RLMListBase.h" +#import "RLMObjectSchema_Private.hpp" +#import "RLMObject_Private.hpp" +#import "RLMProperty_Private.h" +#import "RLMRealm_Private.hpp" + +#import + +using namespace realm; + +namespace { + template + struct IteratorPair { + Iterator first; + Iterator second; + }; + template + Iterator begin(IteratorPair const& p) { + return p.first; + } + template + Iterator end(IteratorPair const& p) { + return p.second; + } + + template + auto reverse(Container const& c) { + return IteratorPair{c.rbegin(), c.rend()}; + } +} + +RLMObservationInfo::RLMObservationInfo(RLMClassInfo &objectSchema, std::size_t row, id object) +: object(object) +, objectSchema(&objectSchema) +{ + setRow(*objectSchema.table(), row); +} + +RLMObservationInfo::RLMObservationInfo(id object) +: object(object) +{ +} + +RLMObservationInfo::~RLMObservationInfo() { + if (prev) { + // Not the head of the linked list, so just detach from the list + REALM_ASSERT_DEBUG(prev->next == this); + prev->next = next; + if (next) { + REALM_ASSERT_DEBUG(next->prev == this); + next->prev = prev; + } + } + else if (objectSchema) { + // The head of the list, so remove self from the object schema's array + // of observation info, either replacing self with the next info or + // removing entirely if there is no next + auto end = objectSchema->observedObjects.end(); + auto it = find(objectSchema->observedObjects.begin(), end, this); + if (it != end) { + if (next) { + *it = next; + next->prev = nullptr; + } + else { + iter_swap(it, std::prev(end)); + objectSchema->observedObjects.pop_back(); + } + } + } + // Otherwise the observed object was unmanaged, so nothing to do + +#ifdef DEBUG + // ensure that incorrect cleanup fails noisily + object = (__bridge id)(void *)-1; + prev = (RLMObservationInfo *)-1; + next = (RLMObservationInfo *)-1; +#endif +} + +NSString *RLMObservationInfo::columnName(size_t col) const noexcept { + return objectSchema->propertyForTableColumn(col).name; +} + +void RLMObservationInfo::willChange(NSString *key, NSKeyValueChange kind, NSIndexSet *indexes) const { + if (indexes) { + forEach([=](__unsafe_unretained auto o) { + [o willChange:kind valuesAtIndexes:indexes forKey:key]; + }); + } + else { + forEach([=](__unsafe_unretained auto o) { + [o willChangeValueForKey:key]; + }); + } +} + +void RLMObservationInfo::didChange(NSString *key, NSKeyValueChange kind, NSIndexSet *indexes) const { + if (indexes) { + forEach([=](__unsafe_unretained auto o) { + [o didChange:kind valuesAtIndexes:indexes forKey:key]; + }); + } + else { + forEach([=](__unsafe_unretained auto o) { + [o didChangeValueForKey:key]; + }); + } +} + +void RLMObservationInfo::prepareForInvalidation() { + REALM_ASSERT_DEBUG(objectSchema); + REALM_ASSERT_DEBUG(!prev); + for (auto info = this; info; info = info->next) + info->invalidated = true; +} + +void RLMObservationInfo::setRow(realm::Table &table, size_t newRow) { + REALM_ASSERT_DEBUG(!row); + REALM_ASSERT_DEBUG(objectSchema); + row = table[newRow]; + for (auto info : objectSchema->observedObjects) { + if (info->row && info->row.get_index() == row.get_index()) { + prev = info; + next = info->next; + if (next) + next->prev = this; + info->next = this; + return; + } + } + objectSchema->observedObjects.push_back(this); +} + +void RLMObservationInfo::recordObserver(realm::Row& objectRow, RLMClassInfo *objectInfo, + __unsafe_unretained RLMObjectSchema *const objectSchema, + __unsafe_unretained NSString *const keyPath) { + ++observerCount; + if (row) { + return; + } + + // add ourselves to the list of observed objects if this is the first time + // an observer is being added to a managed object + if (objectRow) { + this->objectSchema = objectInfo; + setRow(*objectRow.get_table(), objectRow.get_index()); + return; + } + + // Arrays need a reference to their containing object to avoid having to + // go through the awful proxy object from mutableArrayValueForKey. + // For managed objects we do this when the object is added or created + // (and have to to support notifications from modifying an object which + // was never observed), but for Swift classes (both RealmSwift and + // RLMObject) we can't do it then because we don't know what the parent + // object is. + + NSUInteger sep = [keyPath rangeOfString:@"."].location; + NSString *key = sep == NSNotFound ? keyPath : [keyPath substringToIndex:sep]; + RLMProperty *prop = objectSchema[key]; + if (prop && prop.array) { + id value = valueForKey(key); + RLMArray *array = [value isKindOfClass:[RLMListBase class]] ? [value _rlmArray] : value; + array->_key = key; + array->_parentObject = object; + } + else if (auto swiftIvar = prop.swiftIvar) { + if (auto optional = RLMDynamicCast(object_getIvar(object, swiftIvar))) { + RLMInitializeUnmanagedOptional(optional, object, prop); + } + } +} + +void RLMObservationInfo::removeObserver() { + --observerCount; +} + +id RLMObservationInfo::valueForKey(NSString *key) { + if (invalidated) { + if ([key isEqualToString:RLMInvalidatedKey]) { + return @YES; + } + return cachedObjects[key]; + } + + if (key != lastKey) { + lastKey = key; + lastProp = objectSchema ? objectSchema->rlmObjectSchema[key] : nil; + } + + static auto superValueForKey = reinterpret_cast([NSObject methodForSelector:@selector(valueForKey:)]); + if (!lastProp) { + // Not a managed property, so use NSObject's implementation of valueForKey: + return RLMCoerceToNil(superValueForKey(object, @selector(valueForKey:), key)); + } + + auto getSuper = [&] { + return row ? RLMDynamicGet(object, lastProp) : RLMCoerceToNil(superValueForKey(object, @selector(valueForKey:), key)); + }; + + // We need to return the same object each time for observing over keypaths + // to work, so we store a cache of them here. We can't just cache them on + // the object as that leads to retain cycles. + if (lastProp.array) { + RLMArray *value = cachedObjects[key]; + if (!value) { + value = getSuper(); + if (!cachedObjects) { + cachedObjects = [NSMutableDictionary new]; + } + cachedObjects[key] = value; + } + return value; + } + + if (lastProp.type == RLMPropertyTypeObject) { + size_t col = row.get_column_index(lastProp.name.UTF8String); + if (row.is_null_link(col)) { + [cachedObjects removeObjectForKey:key]; + return nil; + } + + RLMObjectBase *value = cachedObjects[key]; + if (value && value->_row.get_index() == row.get_link(col)) { + return value; + } + value = getSuper(); + if (!cachedObjects) { + cachedObjects = [NSMutableDictionary new]; + } + cachedObjects[key] = value; + return value; + } + + return getSuper(); +} + +RLMObservationInfo *RLMGetObservationInfo(RLMObservationInfo *info, size_t row, + RLMClassInfo& objectSchema) { + if (info) { + return info; + } + + for (RLMObservationInfo *info : objectSchema.observedObjects) { + if (info->isForRow(row)) { + return info; + } + } + + return nullptr; +} + +void RLMClearTable(RLMClassInfo &objectSchema) { + for (auto info : objectSchema.observedObjects) { + info->willChange(RLMInvalidatedKey); + } + + RLMTrackDeletions(objectSchema.realm, ^{ + Results(objectSchema.realm->_realm, *objectSchema.table()).clear(); + + for (auto info : objectSchema.observedObjects) { + info->prepareForInvalidation(); + } + }); + + for (auto info : reverse(objectSchema.observedObjects)) { + info->didChange(RLMInvalidatedKey); + } + + objectSchema.observedObjects.clear(); +} + +void RLMTrackDeletions(__unsafe_unretained RLMRealm *const realm, dispatch_block_t block) { + std::vector *> observers; + + // Build up an array of observation info arrays which is indexed by table + // index (the object schemata may be in an entirely different order) + for (auto& info : realm->_info) { + if (info.second.observedObjects.empty()) { + continue; + } + size_t ndx = info.second.table()->get_index_in_group(); + if (ndx >= observers.size()) { + observers.resize(std::max(observers.size() * 2, ndx + 1)); + } + observers[ndx] = &info.second.observedObjects; + } + + // No need for change tracking if no objects are observed + if (observers.empty()) { + block(); + return; + } + + struct change { + RLMObservationInfo *info; + __unsafe_unretained NSString *property; + NSMutableIndexSet *indexes; + }; + + std::vector changes; + std::vector invalidated; + + // This callback is called by core with a list of row deletions and + // resulting link nullifications immediately before things are deleted and nullified + realm.group.set_cascade_notification_handler([&](realm::Group::CascadeNotification const& cs) { + for (auto const& link : cs.links) { + size_t table_ndx = link.origin_table->get_index_in_group(); + if (table_ndx >= observers.size() || !observers[table_ndx]) { + // The modified table has no observers + continue; + } + + for (auto observer : *observers[table_ndx]) { + if (!observer->isForRow(link.origin_row_ndx)) { + continue; + } + + NSString *name = observer->columnName(link.origin_col_ndx); + if (observer->getRow().get_table()->get_column_type(link.origin_col_ndx) != type_LinkList) { + changes.push_back({observer, name}); + continue; + } + + auto c = find_if(begin(changes), end(changes), [&](auto const& c) { + return c.info == observer && c.property == name; + }); + if (c == end(changes)) { + changes.push_back({observer, name, [NSMutableIndexSet new]}); + c = prev(end(changes)); + } + + // We know what row index is being removed from the LinkView, + // but what we actually want is the indexes in the LinkView that + // are going away + auto linkview = observer->getRow().get_linklist(link.origin_col_ndx); + size_t start = 0, index; + while ((index = linkview->find(link.old_target_row_ndx, start)) != realm::not_found) { + [c->indexes addIndex:index]; + start = index + 1; + } + } + } + + for (auto const& row : cs.rows) { + if (row.table_ndx >= observers.size() || !observers[row.table_ndx]) { + // The modified table has no observers + continue; + } + + for (auto observer : *observers[row.table_ndx]) { + if (observer->isForRow(row.row_ndx)) { + invalidated.push_back(observer); + break; + } + } + } + + // The relative order of these loops is very important + for (auto info : invalidated) { + info->willChange(RLMInvalidatedKey); + } + for (auto const& change : changes) { + change.info->willChange(change.property, NSKeyValueChangeRemoval, change.indexes); + } + for (auto info : invalidated) { + info->prepareForInvalidation(); + } + }); + + try { + block(); + } + catch (...) { + realm.group.set_cascade_notification_handler(nullptr); + throw; + } + + for (auto const& change : reverse(changes)) { + change.info->didChange(change.property, NSKeyValueChangeRemoval, change.indexes); + } + for (auto info : reverse(invalidated)) { + info->didChange(RLMInvalidatedKey); + } + + realm.group.set_cascade_notification_handler(nullptr); +} + +namespace { +template +void forEach(realm::BindingContext::ObserverState const& state, Func&& func) { + for (size_t i = 0, size = state.changes.size(); i < size; ++i) { + if (state.changes[i].kind != realm::BindingContext::ColumnInfo::Kind::None) { + func(i, state.changes[i], static_cast(state.info)); + } + } +} +} + +std::vector RLMGetObservedRows(RLMSchemaInfo const& schema) { + std::vector observers; + for (auto& table : schema) { + for (auto info : table.second.observedObjects) { + auto const& row = info->getRow(); + if (!row.is_attached()) + continue; + observers.push_back({ + row.get_table()->get_index_in_group(), + row.get_index(), + info}); + } + } + sort(begin(observers), end(observers)); + return observers; +} + +static NSKeyValueChange convert(realm::BindingContext::ColumnInfo::Kind kind) { + switch (kind) { + case realm::BindingContext::ColumnInfo::Kind::None: + case realm::BindingContext::ColumnInfo::Kind::SetAll: + return NSKeyValueChangeSetting; + case realm::BindingContext::ColumnInfo::Kind::Set: + return NSKeyValueChangeReplacement; + case realm::BindingContext::ColumnInfo::Kind::Insert: + return NSKeyValueChangeInsertion; + case realm::BindingContext::ColumnInfo::Kind::Remove: + return NSKeyValueChangeRemoval; + } +} + +static NSIndexSet *convert(realm::IndexSet const& in, NSMutableIndexSet *out) { + if (in.empty()) { + return nil; + } + + [out removeAllIndexes]; + for (auto range : in) { + [out addIndexesInRange:{range.first, range.second - range.first}]; + } + return out; +} + +void RLMWillChange(std::vector const& observed, + std::vector const& invalidated) { + for (auto info : invalidated) { + static_cast(info)->willChange(RLMInvalidatedKey); + } + if (!observed.empty()) { + NSMutableIndexSet *indexes = [NSMutableIndexSet new]; + for (auto const& o : observed) { + forEach(o, [&](size_t, auto const& change, RLMObservationInfo *info) { + info->willChange(info->columnName(change.initial_column_index), + convert(change.kind), convert(change.indices, indexes)); + }); + } + } + for (auto info : invalidated) { + static_cast(info)->prepareForInvalidation(); + } +} + +void RLMDidChange(std::vector const& observed, + std::vector const& invalidated) { + if (!observed.empty()) { + // Loop in reverse order to avoid O(N^2) behavior in Foundation + NSMutableIndexSet *indexes = [NSMutableIndexSet new]; + for (auto const& o : reverse(observed)) { + forEach(o, [&](size_t i, auto const& change, RLMObservationInfo *info) { + info->didChange(info->columnName(i), convert(change.kind), convert(change.indices, indexes)); + }); + } + } + for (auto const& info : reverse(invalidated)) { + static_cast(info)->didChange(RLMInvalidatedKey); + } +} diff --git a/!main project/Pods/Realm/Realm/RLMOptionalBase.mm b/!main project/Pods/Realm/Realm/RLMOptionalBase.mm new file mode 100644 index 0000000..2617f0b --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMOptionalBase.mm @@ -0,0 +1,163 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMOptionalBase.h" + +#import "RLMAccessor.hpp" +#import "RLMObject_Private.hpp" +#import "RLMProperty.h" +#import "RLMUtil.hpp" +#import "object.hpp" + +namespace { +struct OptionalBase { + virtual id get() = 0; + virtual void set(id) = 0; + virtual ~OptionalBase() = default; +}; + +class UnmanagedOptional : public OptionalBase { +public: + id get() override { + return _value; + } + + void set(__unsafe_unretained const id newValue) override { + @autoreleasepool { + RLMObjectBase *object = _parent; + [object willChangeValueForKey:_property]; + _value = newValue; + [object didChangeValueForKey:_property]; + } + } + + void attach(__unsafe_unretained RLMObjectBase *const obj, NSString *property) { + if (!_property) { + _property = property; + _parent = obj; + } + } + +private: + id _value; + NSString *_property; + __weak RLMObjectBase *_parent; + +}; + +class ManagedOptional : public OptionalBase { +public: + ManagedOptional(RLMObjectBase *obj, RLMProperty *prop) + : _realm(obj->_realm) + , _object(obj->_realm->_realm, *obj->_info->objectSchema, obj->_row) + , _propertyName(prop.name.UTF8String) + , _ctx(*obj->_info) + { + } + + id get() override { + return _object.get_property_value(_ctx, _propertyName); + } + + void set(__unsafe_unretained id const value) override { + _object.set_property_value(_ctx, _propertyName, value ?: NSNull.null); + } + +private: + // We have to hold onto a strong reference to the Realm as + // RLMAccessorContext holds a non-retaining one. + __unused RLMRealm *_realm; + realm::Object _object; + std::string _propertyName; + RLMAccessorContext _ctx; +}; +} // anonymous namespace + +@interface RLMOptionalBase () { + std::unique_ptr _impl; +} +@end + +@implementation RLMOptionalBase +- (instancetype)init { + return self; +} + +- (BOOL)isKindOfClass:(Class)aClass { + return [RLMGetOptional(self) isKindOfClass:aClass] || RLMIsKindOfClass(object_getClass(self), aClass); +} + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { + return [RLMGetOptional(self) methodSignatureForSelector:sel]; +} + +- (void)forwardInvocation:(NSInvocation *)invocation { + [invocation invokeWithTarget:RLMGetOptional(self)]; +} + +- (id)forwardingTargetForSelector:(__unused SEL)sel { + return RLMGetOptional(self); +} + +- (BOOL)respondsToSelector:(SEL)aSelector { + return [RLMGetOptional(self) respondsToSelector:aSelector]; +} + +- (void)doesNotRecognizeSelector:(SEL)aSelector { + [RLMGetOptional(self) doesNotRecognizeSelector:aSelector]; +} + +id RLMGetOptional(__unsafe_unretained RLMOptionalBase *const self) { + try { + return self->_impl ? RLMCoerceToNil(self->_impl->get()) : nil; + } + catch (std::exception const& err) { + @throw RLMException(err); + } +} + +void RLMSetOptional(__unsafe_unretained RLMOptionalBase *const self, __unsafe_unretained const id value) { + try { + if (!self->_impl && value) { + self->_impl.reset(new UnmanagedOptional); + } + if (self->_impl) { + self->_impl->set(value); + } + } + catch (std::exception const& err) { + @throw RLMException(err); + } +} + +void RLMInitializeManagedOptional(__unsafe_unretained RLMOptionalBase *const self, + __unsafe_unretained RLMObjectBase *const parent, + __unsafe_unretained RLMProperty *const prop) { + REALM_ASSERT(parent->_realm); + self->_impl.reset(new ManagedOptional(parent, prop)); +} + +void RLMInitializeUnmanagedOptional(__unsafe_unretained RLMOptionalBase *const self, + __unsafe_unretained RLMObjectBase *const parent, + __unsafe_unretained RLMProperty *const prop) { + if (!self->_impl) { + self->_impl.reset(new UnmanagedOptional); + } + static_cast(*self->_impl).attach(parent, prop.name); +} +@end diff --git a/!main project/Pods/Realm/Realm/RLMPredicateUtil.mm b/!main project/Pods/Realm/Realm/RLMPredicateUtil.mm new file mode 100644 index 0000000..830a9b2 --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMPredicateUtil.mm @@ -0,0 +1,127 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "RLMPredicateUtil.hpp" + +#include + +// NSConditionalExpressionType is new in OS X 10.11 and iOS 9.0 +#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) +#define CONDITIONAL_EXPRESSION_DECLARED (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100) +#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#define CONDITIONAL_EXPRESSION_DECLARED (__IPHONE_OS_VERSION_MIN_REQUIRED >= 90000) +#else +#define CONDITIONAL_EXPRESSION_DECLARED 0 +#endif + +#if !CONDITIONAL_EXPRESSION_DECLARED + +#define NSConditionalExpressionType 20 + +@interface NSExpression (NewIn1011And90) ++ (NSExpression *)expressionForConditional:(NSPredicate *)predicate trueExpression:(NSExpression *)trueExpression falseExpression:(NSExpression *)falseExpression; +- (NSExpression *)trueExpression; +- (NSExpression *)falseExpression; +@end + +#endif + +namespace { + +struct PredicateExpressionTransformer { + PredicateExpressionTransformer(ExpressionVisitor visitor) : m_visitor(visitor) { } + + NSExpression *visit(NSExpression *expression) const; + NSPredicate *visit(NSPredicate *predicate) const; + + ExpressionVisitor m_visitor; +}; + +NSExpression *PredicateExpressionTransformer::visit(NSExpression *expression) const { + expression = m_visitor(expression); + + switch (expression.expressionType) { + case NSFunctionExpressionType: { + NSMutableArray *arguments = [NSMutableArray array]; + for (NSExpression *argument in expression.arguments) { + [arguments addObject:visit(argument)]; + } + if (expression.operand) { + return [NSExpression expressionForFunction:visit(expression.operand) selectorName:expression.function arguments:arguments]; + } else { + return [NSExpression expressionForFunction:expression.function arguments:arguments]; + } + } + + case NSUnionSetExpressionType: + return [NSExpression expressionForUnionSet:visit(expression.leftExpression) with:visit(expression.rightExpression)]; + case NSIntersectSetExpressionType: + return [NSExpression expressionForIntersectSet:visit(expression.leftExpression) with:visit(expression.rightExpression)]; + case NSMinusSetExpressionType: + return [NSExpression expressionForMinusSet:visit(expression.leftExpression) with:visit(expression.rightExpression)]; + + case NSSubqueryExpressionType: { + NSExpression *collection = expression.collection; + // NSExpression.collection is declared as id, but appears to always hold an NSExpression for subqueries. + REALM_ASSERT([collection isKindOfClass:[NSExpression class]]); + return [NSExpression expressionForSubquery:visit(collection) usingIteratorVariable:expression.variable predicate:visit(expression.predicate)]; + } + + case NSAggregateExpressionType: { + NSMutableArray *subexpressions = [NSMutableArray array]; + for (NSExpression *subexpression in expression.collection) { + [subexpressions addObject:visit(subexpression)]; + } + return [NSExpression expressionForAggregate:subexpressions]; + } + + case NSConditionalExpressionType: +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpartial-availability" + return [NSExpression expressionForConditional:visit(expression.predicate) trueExpression:visit(expression.trueExpression) falseExpression:visit(expression.falseExpression)]; +#pragma clang diagnostic pop + + default: + // The remaining expression types do not contain nested expressions or predicates. + return expression; + } +} + +NSPredicate *PredicateExpressionTransformer::visit(NSPredicate *predicate) const { + if ([predicate isKindOfClass:[NSCompoundPredicate class]]) { + NSCompoundPredicate *compoundPredicate = (NSCompoundPredicate *)predicate; + NSMutableArray *subpredicates = [NSMutableArray array]; + for (NSPredicate *subpredicate in compoundPredicate.subpredicates) { + [subpredicates addObject:visit(subpredicate)]; + } + return [[NSCompoundPredicate alloc] initWithType:compoundPredicate.compoundPredicateType subpredicates:subpredicates]; + } + if ([predicate isKindOfClass:[NSComparisonPredicate class]]) { + NSComparisonPredicate *comparisonPredicate = (NSComparisonPredicate *)predicate; + NSExpression *leftExpression = visit(comparisonPredicate.leftExpression); + NSExpression *rightExpression = visit(comparisonPredicate.rightExpression); + return [NSComparisonPredicate predicateWithLeftExpression:leftExpression rightExpression:rightExpression modifier:comparisonPredicate.comparisonPredicateModifier type:comparisonPredicate.predicateOperatorType options:comparisonPredicate.options]; + } + return predicate; +} + +} // anonymous namespace + +NSPredicate *transformPredicate(NSPredicate *predicate, ExpressionVisitor visitor) { + PredicateExpressionTransformer transformer(visitor); + return transformer.visit(predicate); +} diff --git a/!main project/Pods/Realm/Realm/RLMProperty.mm b/!main project/Pods/Realm/Realm/RLMProperty.mm new file mode 100644 index 0000000..7b4e5ec --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMProperty.mm @@ -0,0 +1,622 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMProperty_Private.hpp" + +#import "RLMArray_Private.hpp" +#import "RLMListBase.h" +#import "RLMObject.h" +#import "RLMObjectSchema_Private.hpp" +#import "RLMObject_Private.h" +#import "RLMSchema_Private.h" +#import "RLMSwiftSupport.h" +#import "RLMUtil.hpp" + +#import "property.hpp" + +static_assert((int)RLMPropertyTypeInt == (int)realm::PropertyType::Int, ""); +static_assert((int)RLMPropertyTypeBool == (int)realm::PropertyType::Bool, ""); +static_assert((int)RLMPropertyTypeFloat == (int)realm::PropertyType::Float, ""); +static_assert((int)RLMPropertyTypeDouble == (int)realm::PropertyType::Double, ""); +static_assert((int)RLMPropertyTypeString == (int)realm::PropertyType::String, ""); +static_assert((int)RLMPropertyTypeData == (int)realm::PropertyType::Data, ""); +static_assert((int)RLMPropertyTypeDate == (int)realm::PropertyType::Date, ""); +static_assert((int)RLMPropertyTypeObject == (int)realm::PropertyType::Object, ""); + +BOOL RLMPropertyTypeIsComputed(RLMPropertyType propertyType) { + return propertyType == RLMPropertyTypeLinkingObjects; +} + +// Swift obeys the ARC naming conventions for method families (except for init) +// but the end result doesn't really work (using KVC on a method returning a +// retained value results in a leak, but not returning a retained value results +// in crashes). Objective-C makes properties with naming fitting the method +// families a compile error, so we just disallow them in Swift as well. +// http://clang.llvm.org/docs/AutomaticReferenceCounting.html#arc-method-families +void RLMValidateSwiftPropertyName(NSString *name) { + // To belong to a method family, the property name must begin with the family + // name followed by a non-lowercase letter (or nothing), with an optional + // leading underscore + const char *str = name.UTF8String; + if (str[0] == '_') + ++str; + auto nameSize = strlen(str); + + // Note that "init" is deliberately not in this list because Swift does not + // infer family membership for it. + for (auto family : {"alloc", "new", "copy", "mutableCopy"}) { + auto familySize = strlen(family); + if (nameSize < familySize || !std::equal(str, str + familySize, family)) { + continue; + } + if (familySize == nameSize || !islower(str[familySize])) { + @throw RLMException(@"Property names beginning with '%s' are not " + "supported. Swift follows ARC's ownership " + "rules for methods based on their name, which " + "results in memory leaks when accessing " + "properties which return retained values via KVC.", + family); + } + return; + } +} + +static bool rawTypeShouldBeTreatedAsComputedProperty(NSString *rawType) { + return [rawType isEqualToString:@"@\"RLMLinkingObjects\""] || [rawType hasPrefix:@"@\"RLMLinkingObjects<"]; +} + +@implementation RLMProperty + ++ (instancetype)propertyForObjectStoreProperty:(const realm::Property &)prop { + auto ret = [[RLMProperty alloc] initWithName:@(prop.name.c_str()) + type:static_cast(prop.type & ~realm::PropertyType::Flags) + objectClassName:prop.object_type.length() ? @(prop.object_type.c_str()) : nil + linkOriginPropertyName:prop.link_origin_property_name.length() ? @(prop.link_origin_property_name.c_str()) : nil + indexed:prop.is_indexed + optional:is_nullable(prop.type)]; + if (is_array(prop.type)) { + ret->_array = true; + } + if (!prop.public_name.empty()) { + ret->_columnName = ret->_name; + ret->_name = @(prop.public_name.c_str()); + } + return ret; +} + +- (instancetype)initWithName:(NSString *)name + type:(RLMPropertyType)type + objectClassName:(NSString *)objectClassName + linkOriginPropertyName:(NSString *)linkOriginPropertyName + indexed:(BOOL)indexed + optional:(BOOL)optional { + self = [super init]; + if (self) { + _name = name; + _type = type; + _objectClassName = objectClassName; + _linkOriginPropertyName = linkOriginPropertyName; + _indexed = indexed; + _optional = optional; + [self updateAccessors]; + } + + return self; +} + +- (void)setName:(NSString *)name { + _name = name; + [self updateAccessors]; +} + +- (void)updateAccessors { + // populate getter/setter names if generic + if (!_getterName) { + _getterName = _name; + } + if (!_setterName) { + // Objective-C setters only capitalize the first letter of the property name if it falls between 'a' and 'z' + int asciiCode = [_name characterAtIndex:0]; + BOOL shouldUppercase = asciiCode >= 'a' && asciiCode <= 'z'; + NSString *firstChar = [_name substringToIndex:1]; + firstChar = shouldUppercase ? firstChar.uppercaseString : firstChar; + _setterName = [NSString stringWithFormat:@"set%@%@:", firstChar, [_name substringFromIndex:1]]; + } + + _getterSel = NSSelectorFromString(_getterName); + _setterSel = NSSelectorFromString(_setterName); +} + +static realm::util::Optional typeFromProtocolString(const char *type) { + if (strncmp(type, "RLM", 3)) { + return realm::none; + } + type += 3; + if (strcmp(type, "Int>\"") == 0) { + return RLMPropertyTypeInt; + } + if (strcmp(type, "Float>\"") == 0) { + return RLMPropertyTypeFloat; + } + if (strcmp(type, "Double>\"") == 0) { + return RLMPropertyTypeDouble; + } + if (strcmp(type, "Bool>\"") == 0) { + return RLMPropertyTypeBool; + } + if (strcmp(type, "String>\"") == 0) { + return RLMPropertyTypeString; + } + if (strcmp(type, "Data>\"") == 0) { + return RLMPropertyTypeData; + } + if (strcmp(type, "Date>\"") == 0) { + return RLMPropertyTypeDate; + } + return realm::none; +} + +// determine RLMPropertyType from objc code - returns true if valid type was found/set +- (BOOL)setTypeFromRawType:(NSString *)rawType { + const char *code = rawType.UTF8String; + switch (*code) { + case 's': // short + case 'i': // int + case 'l': // long + case 'q': // long long + _type = RLMPropertyTypeInt; + return YES; + case 'f': + _type = RLMPropertyTypeFloat; + return YES; + case 'd': + _type = RLMPropertyTypeDouble; + return YES; + case 'c': // BOOL is stored as char - since rlm has no char type this is ok + case 'B': + _type = RLMPropertyTypeBool; + return YES; + case '@': + break; + default: + return NO; + } + + _optional = true; + static const char arrayPrefix[] = "@\"RLMArray<"; + static const int arrayPrefixLen = sizeof(arrayPrefix) - 1; + + static const char numberPrefix[] = "@\"NSNumber<"; + static const int numberPrefixLen = sizeof(numberPrefix) - 1; + + static const char linkingObjectsPrefix[] = "@\"RLMLinkingObjects"; + static const int linkingObjectsPrefixLen = sizeof(linkingObjectsPrefix) - 1; + + if (strcmp(code, "@\"NSString\"") == 0) { + _type = RLMPropertyTypeString; + } + else if (strcmp(code, "@\"NSDate\"") == 0) { + _type = RLMPropertyTypeDate; + } + else if (strcmp(code, "@\"NSData\"") == 0) { + _type = RLMPropertyTypeData; + } + else if (strncmp(code, arrayPrefix, arrayPrefixLen) == 0) { + _array = true; + if (auto type = typeFromProtocolString(code + arrayPrefixLen)) { + _type = *type; + return YES; + } + + // get object class from type string - @"RLMArray" + _objectClassName = [[NSString alloc] initWithBytes:code + arrayPrefixLen + length:strlen(code + arrayPrefixLen) - 2 // drop trailing >" + encoding:NSUTF8StringEncoding]; + + if ([RLMSchema classForString:_objectClassName]) { + _optional = false; + _type = RLMPropertyTypeObject; + return YES; + } + @throw RLMException(@"Property '%@' is of type 'RLMArray<%@>' which is not a supported RLMArray object type. " + @"RLMArrays can only contain instances of RLMObject subclasses. " + @"See https://realm.io/docs/objc/latest/#to-many for more information.", _name, _objectClassName); + } + else if (strncmp(code, numberPrefix, numberPrefixLen) == 0) { + auto type = typeFromProtocolString(code + numberPrefixLen); + if (type && (*type == RLMPropertyTypeInt || *type == RLMPropertyTypeFloat || *type == RLMPropertyTypeDouble || *type == RLMPropertyTypeBool)) { + _type = *type; + return YES; + } + @throw RLMException(@"Property '%@' is of type %s which is not a supported NSNumber object type. " + @"NSNumbers can only be RLMInt, RLMFloat, RLMDouble, and RLMBool at the moment. " + @"See https://realm.io/docs/objc/latest for more information.", _name, code + 1); + } + else if (strncmp(code, linkingObjectsPrefix, linkingObjectsPrefixLen) == 0 && + (code[linkingObjectsPrefixLen] == '"' || code[linkingObjectsPrefixLen] == '<')) { + _type = RLMPropertyTypeLinkingObjects; + _optional = false; + _array = true; + + if (!_objectClassName || !_linkOriginPropertyName) { + @throw RLMException(@"Property '%@' is of type RLMLinkingObjects but +linkingObjectsProperties did not specify the class " + "or property that is the origin of the link.", _name); + } + + // If the property was declared with a protocol indicating the contained type, validate that it matches + // the class from the dictionary returned by +linkingObjectsProperties. + if (code[linkingObjectsPrefixLen] == '<') { + NSString *classNameFromProtocol = [[NSString alloc] initWithBytes:code + linkingObjectsPrefixLen + 1 + length:strlen(code + linkingObjectsPrefixLen) - 3 // drop trailing >" + encoding:NSUTF8StringEncoding]; + if (![_objectClassName isEqualToString:classNameFromProtocol]) { + @throw RLMException(@"Property '%@' was declared with type RLMLinkingObjects<%@>, but a conflicting " + "class name of '%@' was returned by +linkingObjectsProperties.", _name, + classNameFromProtocol, _objectClassName); + } + } + } + else if (strcmp(code, "@\"NSNumber\"") == 0) { + @throw RLMException(@"Property '%@' requires a protocol defining the contained type - example: NSNumber.", _name); + } + else if (strcmp(code, "@\"RLMArray\"") == 0) { + @throw RLMException(@"Property '%@' requires a protocol defining the contained type - example: RLMArray.", _name); + } + else { + NSString *className; + Class cls = nil; + if (code[1] == '\0') { + className = @"id"; + } + else { + // for objects strip the quotes and @ + className = [rawType substringWithRange:NSMakeRange(2, rawType.length-3)]; + cls = [RLMSchema classForString:className]; + } + + if (!cls) { + @throw RLMException(@"Property '%@' is declared as '%@', which is not a supported RLMObject property type. " + @"All properties must be primitives, NSString, NSDate, NSData, NSNumber, RLMArray, RLMLinkingObjects, or subclasses of RLMObject. " + @"See https://realm.io/docs/objc/latest/api/Classes/RLMObject.html for more information.", _name, className); + } + + _type = RLMPropertyTypeObject; + _optional = true; + _objectClassName = [cls className] ?: className; + } + return YES; +} + +- (void)parseObjcProperty:(objc_property_t)property + readOnly:(bool *)readOnly + computed:(bool *)computed + rawType:(NSString **)rawType { + unsigned int count; + objc_property_attribute_t *attrs = property_copyAttributeList(property, &count); + + *computed = true; + for (size_t i = 0; i < count; ++i) { + switch (*attrs[i].name) { + case 'T': + *rawType = @(attrs[i].value); + break; + case 'R': + *readOnly = true; + break; + case 'G': + _getterName = @(attrs[i].value); + break; + case 'S': + _setterName = @(attrs[i].value); + break; + case 'V': // backing ivar name + *computed = false; + break; + + case '&': + // retain/assign + break; + case 'C': + // copy + break; + case 'D': + // dynamic + break; + case 'N': + // nonatomic + break; + case 'P': + // GC'able + break; + case 'W': + // weak + break; + default: + break; + } + } + free(attrs); +} + +- (instancetype)initSwiftPropertyWithName:(NSString *)name + indexed:(BOOL)indexed + linkPropertyDescriptor:(RLMPropertyDescriptor *)linkPropertyDescriptor + property:(objc_property_t)property + instance:(RLMObject *)obj { + self = [super init]; + if (!self) { + return nil; + } + + RLMValidateSwiftPropertyName(name); + + _name = name; + _indexed = indexed; + + if (linkPropertyDescriptor) { + _objectClassName = [linkPropertyDescriptor.objectClass className]; + _linkOriginPropertyName = linkPropertyDescriptor.propertyName; + } + + NSString *rawType; + bool readOnly = false; + bool isComputed = false; + [self parseObjcProperty:property readOnly:&readOnly computed:&isComputed rawType:&rawType]; + + // Swift sometimes doesn't explicitly set the ivar name in the metadata, so check if + // there's an ivar with the same name as the property. + if (!readOnly && isComputed && class_getInstanceVariable([obj class], name.UTF8String)) { + isComputed = false; + } + + // Check if there's a storage ivar for a lazy property in this name. We don't honor + // @lazy in managed objects, but allow it for unmanaged objects which are + // subclasses of RLMObject (but not RealmSwift.Object). It's unclear if there's a + // good reason for this difference. + if (!readOnly && isComputed) { + // Xcode 10 and earlier + NSString *backingPropertyName = [NSString stringWithFormat:@"%@.storage", name]; + isComputed = !class_getInstanceVariable([obj class], backingPropertyName.UTF8String); + } + if (!readOnly && isComputed) { + // Xcode 11 + NSString *backingPropertyName = [NSString stringWithFormat:@"$__lazy_storage_$_%@", name]; + isComputed = !class_getInstanceVariable([obj class], backingPropertyName.UTF8String); + } + + if (readOnly || isComputed) { + return nil; + } + + id propertyValue = [obj valueForKey:_name]; + + // FIXME: temporarily workaround added since Objective-C generics used in Swift show up as `@` + // * broken starting in Swift 3.0 Xcode 8 b1 + // * tested to still be broken in Swift 3.0 Xcode 8 b6 + // * if the Realm Objective-C Swift tests pass with this removed, it's been fixed + // * once it has been fixed, remove this entire conditional block (contents included) entirely + // * Bug Report: SR-2031 https://bugs.swift.org/browse/SR-2031 + if ([rawType isEqualToString:@"@"]) { + if (propertyValue) { + rawType = [NSString stringWithFormat:@"@\"%@\"", [propertyValue class]]; + } else if (linkPropertyDescriptor) { + // we're going to naively assume that the user used the correct type since we can't check it + rawType = @"@\"RLMLinkingObjects\""; + } + } + + // convert array types to objc variant + if ([rawType isEqualToString:@"@\"RLMArray\""]) { + RLMArray *value = propertyValue; + _type = value.type; + _optional = value.optional; + _array = true; + _objectClassName = value.objectClassName; + if (_type == RLMPropertyTypeObject && ![RLMSchema classForString:_objectClassName]) { + @throw RLMException(@"Property '%@' is of type 'RLMArray<%@>' which is not a supported RLMArray object type. " + @"RLMArrays can only contain instances of RLMObject subclasses. " + @"See https://realm.io/docs/objc/latest/#to-many for more information.", _name, _objectClassName); + } + } + else if ([rawType isEqualToString:@"@\"NSNumber\""]) { + const char *numberType = [propertyValue objCType]; + if (!numberType) { + @throw RLMException(@"Can't persist NSNumber without default value: use a Swift-native number type or provide a default value."); + } + _optional = true; + switch (*numberType) { + case 'i': case 'l': case 'q': + _type = RLMPropertyTypeInt; + break; + case 'f': + _type = RLMPropertyTypeFloat; + break; + case 'd': + _type = RLMPropertyTypeDouble; + break; + case 'B': case 'c': + _type = RLMPropertyTypeBool; + break; + default: + @throw RLMException(@"Can't persist NSNumber of type '%s': only integers, floats, doubles, and bools are currently supported.", numberType); + } + } + else if (![self setTypeFromRawType:rawType]) { + @throw RLMException(@"Can't persist property '%@' with incompatible type. " + "Add to Object.ignoredProperties() class method to ignore.", + self.name); + } + + if ([rawType isEqualToString:@"c"]) { + // Check if it's a BOOL or Int8 by trying to set it to 2 and seeing if + // it actually sets it to 1. + [obj setValue:@2 forKey:name]; + NSNumber *value = [obj valueForKey:name]; + _type = value.intValue == 2 ? RLMPropertyTypeInt : RLMPropertyTypeBool; + } + + // update getter/setter names + [self updateAccessors]; + + return self; +} + +- (instancetype)initWithName:(NSString *)name + indexed:(BOOL)indexed + linkPropertyDescriptor:(RLMPropertyDescriptor *)linkPropertyDescriptor + property:(objc_property_t)property +{ + self = [super init]; + if (!self) { + return nil; + } + + _name = name; + _indexed = indexed; + + if (linkPropertyDescriptor) { + _objectClassName = [linkPropertyDescriptor.objectClass className]; + _linkOriginPropertyName = linkPropertyDescriptor.propertyName; + } + + NSString *rawType; + bool isReadOnly = false; + bool isComputed = false; + [self parseObjcProperty:property readOnly:&isReadOnly computed:&isComputed rawType:&rawType]; + bool shouldBeTreatedAsComputedProperty = rawTypeShouldBeTreatedAsComputedProperty(rawType); + if ((isReadOnly || isComputed) && !shouldBeTreatedAsComputedProperty) { + return nil; + } + + if (![self setTypeFromRawType:rawType]) { + @throw RLMException(@"Can't persist property '%@' with incompatible type. " + "Add to ignoredPropertyNames: method to ignore.", self.name); + } + + if (!isReadOnly && shouldBeTreatedAsComputedProperty) { + @throw RLMException(@"Property '%@' must be declared as readonly as %@ properties cannot be written to.", + self.name, RLMTypeToString(_type)); + } + + // update getter/setter names + [self updateAccessors]; + + return self; +} + +- (id)copyWithZone:(NSZone *)zone { + RLMProperty *prop = [[RLMProperty allocWithZone:zone] init]; + prop->_name = _name; + prop->_columnName = _columnName; + prop->_type = _type; + prop->_objectClassName = _objectClassName; + prop->_array = _array; + prop->_indexed = _indexed; + prop->_getterName = _getterName; + prop->_setterName = _setterName; + prop->_getterSel = _getterSel; + prop->_setterSel = _setterSel; + prop->_isPrimary = _isPrimary; + prop->_swiftIvar = _swiftIvar; + prop->_optional = _optional; + prop->_linkOriginPropertyName = _linkOriginPropertyName; + return prop; +} + +- (RLMProperty *)copyWithNewName:(NSString *)name { + RLMProperty *prop = [self copy]; + prop.name = name; + return prop; +} + +- (BOOL)isEqual:(id)object { + if (![object isKindOfClass:[RLMProperty class]]) { + return NO; + } + + return [self isEqualToProperty:object]; +} + +- (BOOL)isEqualToProperty:(RLMProperty *)property { + return _type == property->_type + && _indexed == property->_indexed + && _isPrimary == property->_isPrimary + && _optional == property->_optional + && [_name isEqualToString:property->_name] + && (_objectClassName == property->_objectClassName || [_objectClassName isEqualToString:property->_objectClassName]) + && (_linkOriginPropertyName == property->_linkOriginPropertyName || + [_linkOriginPropertyName isEqualToString:property->_linkOriginPropertyName]); +} + +- (NSString *)description { + return [NSString stringWithFormat: + @"%@ {\n" + "\ttype = %@;\n" + "\tobjectClassName = %@;\n" + "\tlinkOriginPropertyName = %@;\n" + "\tindexed = %@;\n" + "\tisPrimary = %@;\n" + "\tarray = %@;\n" + "\toptional = %@;\n" + "}", + self.name, RLMTypeToString(self.type), self.objectClassName, + self.linkOriginPropertyName, + self.indexed ? @"YES" : @"NO", + self.isPrimary ? @"YES" : @"NO", + self.array ? @"YES" : @"NO", + self.optional ? @"YES" : @"NO"]; +} + +- (NSString *)columnName { + return _columnName ?: _name; +} + +- (realm::Property)objectStoreCopy:(RLMSchema *)schema { + realm::Property p; + p.name = self.columnName.UTF8String; + if (_objectClassName) { + RLMObjectSchema *targetSchema = schema[_objectClassName]; + p.object_type = (targetSchema.objectName ?: _objectClassName).UTF8String; + if (_linkOriginPropertyName) { + p.link_origin_property_name = (targetSchema[_linkOriginPropertyName].columnName ?: _linkOriginPropertyName).UTF8String; + } + } + p.is_indexed = static_cast(_indexed); + p.type = static_cast(_type); + if (_array) { + p.type |= realm::PropertyType::Array; + } + if (_optional) { + p.type |= realm::PropertyType::Nullable; + } + return p; +} + +@end + +@implementation RLMPropertyDescriptor + ++ (instancetype)descriptorWithClass:(Class)objectClass propertyName:(NSString *)propertyName +{ + RLMPropertyDescriptor *descriptor = [[RLMPropertyDescriptor alloc] init]; + descriptor->_objectClass = objectClass; + descriptor->_propertyName = propertyName; + return descriptor; +} + +@end diff --git a/!main project/Pods/Realm/Realm/RLMQueryUtil.mm b/!main project/Pods/Realm/Realm/RLMQueryUtil.mm new file mode 100644 index 0000000..93fe03b --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMQueryUtil.mm @@ -0,0 +1,1490 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMQueryUtil.hpp" + +#import "RLMArray.h" +#import "RLMObjectSchema_Private.h" +#import "RLMObject_Private.hpp" +#import "RLMPredicateUtil.hpp" +#import "RLMProperty_Private.h" +#import "RLMSchema.h" +#import "RLMUtil.hpp" + +#import "object_store.hpp" +#import "results.hpp" + +#include +#include +#include +#include + +using namespace realm; + +NSString * const RLMPropertiesComparisonTypeMismatchException = @"RLMPropertiesComparisonTypeMismatchException"; +NSString * const RLMUnsupportedTypesFoundInPropertyComparisonException = @"RLMUnsupportedTypesFoundInPropertyComparisonException"; + +NSString * const RLMPropertiesComparisonTypeMismatchReason = @"Property type mismatch between %@ and %@"; +NSString * const RLMUnsupportedTypesFoundInPropertyComparisonReason = @"Comparison between %@ and %@"; + +// small helper to create the many exceptions thrown when parsing predicates +static NSException *RLMPredicateException(NSString *name, NSString *format, ...) { + va_list args; + va_start(args, format); + NSString *reason = [[NSString alloc] initWithFormat:format arguments:args]; + va_end(args); + + return [NSException exceptionWithName:name reason:reason userInfo:nil]; +} + +// check a precondition and throw an exception if it is not met +// this should be used iff the condition being false indicates a bug in the caller +// of the function checking its preconditions +static void RLMPrecondition(bool condition, NSString *name, NSString *format, ...) { + if (__builtin_expect(condition, 1)) { + return; + } + + va_list args; + va_start(args, format); + NSString *reason = [[NSString alloc] initWithFormat:format arguments:args]; + va_end(args); + + @throw [NSException exceptionWithName:name reason:reason userInfo:nil]; +} + +// return the property for a validated column name +RLMProperty *RLMValidatedProperty(RLMObjectSchema *desc, NSString *columnName) { + RLMProperty *prop = desc[columnName]; + RLMPrecondition(prop, @"Invalid property name", + @"Property '%@' not found in object of type '%@'", columnName, desc.className); + return prop; +} + +namespace { +BOOL RLMPropertyTypeIsNumeric(RLMPropertyType propertyType) { + switch (propertyType) { + case RLMPropertyTypeInt: + case RLMPropertyTypeFloat: + case RLMPropertyTypeDouble: + return YES; + default: + return NO; + } +} + +// Equal and ContainsSubstring are used by QueryBuilder::add_string_constraint as the comparator +// for performing diacritic-insensitive comparisons. + +bool equal(CFStringCompareFlags options, StringData v1, StringData v2) +{ + if (v1.is_null() || v2.is_null()) { + return v1.is_null() == v2.is_null(); + } + + auto s1 = util::adoptCF(CFStringCreateWithBytesNoCopy(kCFAllocatorSystemDefault, (const UInt8*)v1.data(), v1.size(), + kCFStringEncodingUTF8, false, kCFAllocatorNull)); + auto s2 = util::adoptCF(CFStringCreateWithBytesNoCopy(kCFAllocatorSystemDefault, (const UInt8*)v2.data(), v2.size(), + kCFStringEncodingUTF8, false, kCFAllocatorNull)); + + return CFStringCompare(s1.get(), s2.get(), options) == kCFCompareEqualTo; +} + +template +struct Equal { + using CaseSensitive = Equal; + using CaseInsensitive = Equal; + + bool operator()(StringData v1, StringData v2, bool v1_null, bool v2_null) const + { + REALM_ASSERT_DEBUG(v1_null == v1.is_null()); + REALM_ASSERT_DEBUG(v2_null == v2.is_null()); + + return equal(options, v1, v2); + } + + // FIXME: Consider the options. + static const char* description() { return "equal"; } +}; + +bool contains_substring(CFStringCompareFlags options, StringData v1, StringData v2) +{ + if (v2.is_null()) { + // Everything contains NULL + return true; + } + + if (v1.is_null()) { + // NULL contains nothing (except NULL, handled above) + return false; + } + + if (v2.size() == 0) { + // Everything (except NULL, handled above) contains the empty string + return true; + } + + auto s1 = util::adoptCF(CFStringCreateWithBytesNoCopy(kCFAllocatorSystemDefault, (const UInt8*)v1.data(), v1.size(), + kCFStringEncodingUTF8, false, kCFAllocatorNull)); + auto s2 = util::adoptCF(CFStringCreateWithBytesNoCopy(kCFAllocatorSystemDefault, (const UInt8*)v2.data(), v2.size(), + kCFStringEncodingUTF8, false, kCFAllocatorNull)); + + return CFStringFind(s1.get(), s2.get(), options).location != kCFNotFound; +} + +template +struct ContainsSubstring { + using CaseSensitive = ContainsSubstring; + using CaseInsensitive = ContainsSubstring; + + bool operator()(StringData v1, StringData v2, bool v1_null, bool v2_null) const + { + REALM_ASSERT_DEBUG(v1_null == v1.is_null()); + REALM_ASSERT_DEBUG(v2_null == v2.is_null()); + + return contains_substring(options, v1, v2); + } + + // FIXME: Consider the options. + static const char* description() { return "contains"; } +}; + + +NSString *operatorName(NSPredicateOperatorType operatorType) +{ + switch (operatorType) { + case NSLessThanPredicateOperatorType: + return @"<"; + case NSLessThanOrEqualToPredicateOperatorType: + return @"<="; + case NSGreaterThanPredicateOperatorType: + return @">"; + case NSGreaterThanOrEqualToPredicateOperatorType: + return @">="; + case NSEqualToPredicateOperatorType: + return @"=="; + case NSNotEqualToPredicateOperatorType: + return @"!="; + case NSMatchesPredicateOperatorType: + return @"MATCHES"; + case NSLikePredicateOperatorType: + return @"LIKE"; + case NSBeginsWithPredicateOperatorType: + return @"BEGINSWITH"; + case NSEndsWithPredicateOperatorType: + return @"ENDSWITH"; + case NSInPredicateOperatorType: + return @"IN"; + case NSContainsPredicateOperatorType: + return @"CONTAINS"; + case NSBetweenPredicateOperatorType: + return @"BETWEEN"; + case NSCustomSelectorPredicateOperatorType: + return @"custom selector"; + } + + return [NSString stringWithFormat:@"unknown operator %lu", (unsigned long)operatorType]; +} + +Table& get_table(Group& group, RLMObjectSchema *objectSchema) +{ + return *ObjectStore::table_for_object_type(group, objectSchema.objectName.UTF8String); +} + +// A reference to a column within a query. Can be resolved to a Columns for use in query expressions. +class ColumnReference { +public: + ColumnReference(Query& query, Group& group, RLMSchema *schema, RLMProperty* property, const std::vector& links = {}) + : m_links(links), m_property(property), m_schema(schema), m_group(&group), m_query(&query), m_table(query.get_table().get()) + { + auto& table = walk_link_chain([](Table&, size_t, RLMPropertyType) { }); + m_index = table.get_column_index(m_property.columnName.UTF8String); + } + + template + auto resolve(SubQuery&&... subquery) const + { + static_assert(sizeof...(SubQuery) < 2, "resolve() takes at most one subquery"); + set_link_chain_on_table(); + if (type() != RLMPropertyTypeLinkingObjects) { + return m_table->template column(index(), std::forward(subquery)...); + } + else { + return resolve_backlink(std::forward(subquery)...); + } + } + + RLMProperty *property() const { return m_property; } + size_t index() const { return m_index; } + RLMPropertyType type() const { return property().type; } + Group& group() const { return *m_group; } + + RLMObjectSchema *link_target_object_schema() const + { + switch (type()) { + case RLMPropertyTypeObject: + case RLMPropertyTypeLinkingObjects: + return m_schema[property().objectClassName]; + default: + REALM_UNREACHABLE(); + } + } + + bool has_links() const { return m_links.size(); } + + bool has_any_to_many_links() const { + return std::any_of(begin(m_links), end(m_links), + [](RLMProperty *property) { return property.array; }); + } + + ColumnReference last_link_column() const { + REALM_ASSERT(!m_links.empty()); + return {*m_query, *m_group, m_schema, m_links.back(), {m_links.begin(), m_links.end() - 1}}; + } + + ColumnReference column_ignoring_links(Query& query) const { + return {query, *m_group, m_schema, m_property}; + } + +private: + template + auto resolve_backlink(SubQuery&&... subquery) const + { + // We actually just want `if constexpr (std::is_same::value) { ... }`, + // so fake it by tag-dispatching on the conditional + return do_resolve_backlink(std::is_same(), std::forward(subquery)...); + } + + template + auto do_resolve_backlink(std::true_type, SubQuery&&... subquery) const + { + return with_link_origin(m_property, [&](Table& table, size_t col) { + return m_table->template column(table, col, std::forward(subquery)...); + }); + } + + template + Columns do_resolve_backlink(std::false_type, SubQuery&&...) const + { + // This can't actually happen as we only call resolve_backlink() if + // it's RLMPropertyTypeLinkingObjects + __builtin_unreachable(); + } + + template + Table& walk_link_chain(Func&& func) const + { + auto table = m_query->get_table().get(); + for (const auto& link : m_links) { + if (link.type != RLMPropertyTypeLinkingObjects) { + auto index = table->get_column_index(link.columnName.UTF8String); + func(*table, index, link.type); + table = table->get_link_target(index).get(); + } + else { + with_link_origin(link, [&](Table& link_origin_table, size_t link_origin_column) { + func(link_origin_table, link_origin_column, link.type); + table = &link_origin_table; + }); + } + } + return *table; + } + + template + auto with_link_origin(RLMProperty *prop, Func&& func) const + { + RLMObjectSchema *link_origin_schema = m_schema[prop.objectClassName]; + Table& link_origin_table = get_table(*m_group, link_origin_schema); + NSString *column_name = link_origin_schema[prop.linkOriginPropertyName].columnName; + size_t link_origin_column = link_origin_table.get_column_index(column_name.UTF8String); + return func(link_origin_table, link_origin_column); + } + + void set_link_chain_on_table() const + { + walk_link_chain([&](Table& current_table, size_t column, RLMPropertyType type) { + if (type == RLMPropertyTypeLinkingObjects) { + m_table->backlink(current_table, column); + } + else { + m_table->link(column); + } + }); + } + + std::vector m_links; + RLMProperty *m_property; + RLMSchema *m_schema; + Group *m_group; + Query *m_query; + Table *m_table; + size_t m_index; +}; + +class CollectionOperation { +public: + enum Type { + Count, + Minimum, + Maximum, + Sum, + Average, + }; + + CollectionOperation(Type type, ColumnReference link_column, util::Optional column) + : m_type(type) + , m_link_column(std::move(link_column)) + , m_column(std::move(column)) + { + RLMPrecondition(m_link_column.property().array, + @"Invalid predicate", @"Collection operation can only be applied to a property of type RLMArray."); + + switch (m_type) { + case Count: + RLMPrecondition(!m_column, @"Invalid predicate", @"Result of @count does not have any properties."); + break; + case Minimum: + case Maximum: + case Sum: + case Average: + RLMPrecondition(m_column && RLMPropertyTypeIsNumeric(m_column->type()), @"Invalid predicate", + @"%@ can only be applied to a numeric property.", name_for_type(m_type)); + break; + } + } + + CollectionOperation(NSString *operationName, ColumnReference link_column, util::Optional column = util::none) + : CollectionOperation(type_for_name(operationName), std::move(link_column), std::move(column)) + { + } + + Type type() const { return m_type; } + const ColumnReference& link_column() const { return m_link_column; } + const ColumnReference& column() const { return *m_column; } + + void validate_comparison(id value) const { + switch (m_type) { + case Count: + case Average: + RLMPrecondition([value isKindOfClass:[NSNumber class]], @"Invalid operand", + @"%@ can only be compared with a numeric value.", name_for_type(m_type)); + break; + case Minimum: + case Maximum: + case Sum: + RLMPrecondition(RLMIsObjectValidForProperty(value, m_column->property()), @"Invalid operand", + @"%@ on a property of type %@ cannot be compared with '%@'", + name_for_type(m_type), RLMTypeToString(m_column->type()), value); + break; + } + } + + void validate_comparison(const ColumnReference& column) const { + switch (m_type) { + case Count: + RLMPrecondition(RLMPropertyTypeIsNumeric(column.type()), @"Invalid operand", + @"%@ can only be compared with a numeric value.", name_for_type(m_type)); + break; + case Average: + case Minimum: + case Maximum: + case Sum: + RLMPrecondition(RLMPropertyTypeIsNumeric(column.type()), @"Invalid operand", + @"%@ on a property of type %@ cannot be compared with property of type '%@'", + name_for_type(m_type), RLMTypeToString(m_column->type()), RLMTypeToString(column.type())); + break; + } + } + +private: + static Type type_for_name(NSString *name) { + if ([name isEqualToString:@"@count"]) { + return Count; + } + if ([name isEqualToString:@"@min"]) { + return Minimum; + } + if ([name isEqualToString:@"@max"]) { + return Maximum; + } + if ([name isEqualToString:@"@sum"]) { + return Sum; + } + if ([name isEqualToString:@"@avg"]) { + return Average; + } + @throw RLMPredicateException(@"Invalid predicate", @"Unsupported collection operation '%@'", name); + } + + static NSString *name_for_type(Type type) { + switch (type) { + case Count: return @"@count"; + case Minimum: return @"@min"; + case Maximum: return @"@max"; + case Sum: return @"@sum"; + case Average: return @"@avg"; + } + } + + Type m_type; + ColumnReference m_link_column; + util::Optional m_column; +}; + +class QueryBuilder { +public: + QueryBuilder(Query& query, Group& group, RLMSchema *schema) + : m_query(query), m_group(group), m_schema(schema) { } + + void apply_predicate(NSPredicate *predicate, RLMObjectSchema *objectSchema); + + + void apply_collection_operator_expression(RLMObjectSchema *desc, NSString *keyPath, id value, NSComparisonPredicate *pred); + void apply_value_expression(RLMObjectSchema *desc, NSString *keyPath, id value, NSComparisonPredicate *pred); + void apply_column_expression(RLMObjectSchema *desc, NSString *leftKeyPath, NSString *rightKeyPath, NSComparisonPredicate *predicate); + void apply_subquery_count_expression(RLMObjectSchema *objectSchema, NSExpression *subqueryExpression, + NSPredicateOperatorType operatorType, NSExpression *right); + void apply_function_subquery_expression(RLMObjectSchema *objectSchema, NSExpression *functionExpression, + NSPredicateOperatorType operatorType, NSExpression *right); + void apply_function_expression(RLMObjectSchema *objectSchema, NSExpression *functionExpression, + NSPredicateOperatorType operatorType, NSExpression *right); + + + template + void add_numeric_constraint(RLMPropertyType datatype, + NSPredicateOperatorType operatorType, + A&& lhs, B&& rhs); + + template + void add_bool_constraint(NSPredicateOperatorType operatorType, A lhs, B rhs); + + void add_substring_constraint(null, Query condition); + template + void add_substring_constraint(const T& value, Query condition); + template + void add_substring_constraint(const Columns& value, Query condition); + + template + void add_string_constraint(NSPredicateOperatorType operatorType, + NSComparisonPredicateOptions predicateOptions, + Columns &&column, + T value); + + void add_string_constraint(NSPredicateOperatorType operatorType, + NSComparisonPredicateOptions predicateOptions, + StringData value, + Columns&& column); + + template + void add_constraint(RLMPropertyType type, + NSPredicateOperatorType operatorType, + NSComparisonPredicateOptions predicateOptions, + L lhs, R rhs); + template + void do_add_constraint(RLMPropertyType type, NSPredicateOperatorType operatorType, + NSComparisonPredicateOptions predicateOptions, T... values); + void do_add_constraint(RLMPropertyType, NSPredicateOperatorType, NSComparisonPredicateOptions, id, realm::null); + + void add_between_constraint(const ColumnReference& column, id value); + + void add_binary_constraint(NSPredicateOperatorType operatorType, const ColumnReference& column, BinaryData value); + void add_binary_constraint(NSPredicateOperatorType operatorType, const ColumnReference& column, id value); + void add_binary_constraint(NSPredicateOperatorType operatorType, const ColumnReference& column, null); + void add_binary_constraint(NSPredicateOperatorType operatorType, id value, const ColumnReference& column); + void add_binary_constraint(NSPredicateOperatorType, const ColumnReference&, const ColumnReference&); + + void add_link_constraint(NSPredicateOperatorType operatorType, const ColumnReference& column, RLMObject *obj); + void add_link_constraint(NSPredicateOperatorType operatorType, const ColumnReference& column, realm::null); + template + void add_link_constraint(NSPredicateOperatorType operatorType, T obj, const ColumnReference& column); + void add_link_constraint(NSPredicateOperatorType, const ColumnReference&, const ColumnReference&); + + template + void add_collection_operation_constraint(RLMPropertyType propertyType, NSPredicateOperatorType operatorType, T... values); + template + void add_collection_operation_constraint(NSPredicateOperatorType operatorType, + CollectionOperation collectionOperation, T... values); + + + CollectionOperation collection_operation_from_key_path(RLMObjectSchema *desc, NSString *keyPath); + ColumnReference column_reference_from_key_path(RLMObjectSchema *objectSchema, NSString *keyPath, bool isAggregate); + +private: + Query& m_query; + Group& m_group; + RLMSchema *m_schema; +}; + +// add a clause for numeric constraints based on operator type +template +void QueryBuilder::add_numeric_constraint(RLMPropertyType datatype, + NSPredicateOperatorType operatorType, + A&& lhs, B&& rhs) +{ + switch (operatorType) { + case NSLessThanPredicateOperatorType: + m_query.and_query(lhs < rhs); + break; + case NSLessThanOrEqualToPredicateOperatorType: + m_query.and_query(lhs <= rhs); + break; + case NSGreaterThanPredicateOperatorType: + m_query.and_query(lhs > rhs); + break; + case NSGreaterThanOrEqualToPredicateOperatorType: + m_query.and_query(lhs >= rhs); + break; + case NSEqualToPredicateOperatorType: + m_query.and_query(lhs == rhs); + break; + case NSNotEqualToPredicateOperatorType: + m_query.and_query(lhs != rhs); + break; + default: + @throw RLMPredicateException(@"Invalid operator type", + @"Operator '%@' not supported for type %@", + operatorName(operatorType), RLMTypeToString(datatype)); + } +} + +template +void QueryBuilder::add_bool_constraint(NSPredicateOperatorType operatorType, A lhs, B rhs) { + switch (operatorType) { + case NSEqualToPredicateOperatorType: + m_query.and_query(lhs == rhs); + break; + case NSNotEqualToPredicateOperatorType: + m_query.and_query(lhs != rhs); + break; + default: + @throw RLMPredicateException(@"Invalid operator type", + @"Operator '%@' not supported for bool type", operatorName(operatorType)); + } +} + +void QueryBuilder::add_substring_constraint(null, Query) { + // Foundation always returns false for substring operations with a RHS of null or "". + m_query.and_query(std::unique_ptr(new FalseExpression)); +} + +template +void QueryBuilder::add_substring_constraint(const T& value, Query condition) { + // Foundation always returns false for substring operations with a RHS of null or "". + m_query.and_query(value.size() + ? std::move(condition) + : std::unique_ptr(new FalseExpression)); +} + +template +void QueryBuilder::add_substring_constraint(const Columns& value, Query condition) { + // Foundation always returns false for substring operations with a RHS of null or "". + // We don't need to concern ourselves with the possibility of value traversing a link list + // and producing multiple values per row as such expressions will have been rejected. + m_query.and_query(const_cast&>(value).size() != 0 && std::move(condition)); +} + +template +void QueryBuilder::add_string_constraint(NSPredicateOperatorType operatorType, + NSComparisonPredicateOptions predicateOptions, + Columns &&column, + T value) { + bool caseSensitive = !(predicateOptions & NSCaseInsensitivePredicateOption); + bool diacriticSensitive = !(predicateOptions & NSDiacriticInsensitivePredicateOption); + + if (diacriticSensitive) { + switch (operatorType) { + case NSBeginsWithPredicateOperatorType: + add_substring_constraint(value, column.begins_with(value, caseSensitive)); + break; + case NSEndsWithPredicateOperatorType: + add_substring_constraint(value, column.ends_with(value, caseSensitive)); + break; + case NSContainsPredicateOperatorType: + add_substring_constraint(value, column.contains(value, caseSensitive)); + break; + case NSEqualToPredicateOperatorType: + m_query.and_query(column.equal(value, caseSensitive)); + break; + case NSNotEqualToPredicateOperatorType: + m_query.and_query(column.not_equal(value, caseSensitive)); + break; + case NSLikePredicateOperatorType: + m_query.and_query(column.like(value, caseSensitive)); + break; + default: + @throw RLMPredicateException(@"Invalid operator type", + @"Operator '%@' not supported for string type", + operatorName(operatorType)); + } + return; + } + + auto as_subexpr = util::overload([](StringData value) { return make_subexpr(value); }, + [](const Columns& c) { return c.clone(); }); + auto left = as_subexpr(column); + auto right = as_subexpr(value); + + auto make_constraint = [&](auto comparator) { + using Comparator = decltype(comparator); + using CompareCS = Compare; + using CompareCI = Compare; + if (caseSensitive) { + return make_expression(std::move(left), std::move(right)); + } + else { + return make_expression(std::move(left), std::move(right)); + } + }; + + switch (operatorType) { + case NSBeginsWithPredicateOperatorType: { + using C = ContainsSubstring; + add_substring_constraint(value, make_constraint(C{})); + break; + } + case NSEndsWithPredicateOperatorType: { + using C = ContainsSubstring; + add_substring_constraint(value, make_constraint(C{})); + break; + } + case NSContainsPredicateOperatorType: { + using C = ContainsSubstring; + add_substring_constraint(value, make_constraint(C{})); + break; + } + case NSNotEqualToPredicateOperatorType: + m_query.Not(); + REALM_FALLTHROUGH; + case NSEqualToPredicateOperatorType: + m_query.and_query(make_constraint(Equal{})); + break; + case NSLikePredicateOperatorType: + @throw RLMPredicateException(@"Invalid operator type", + @"Operator 'LIKE' not supported with diacritic-insensitive modifier."); + default: + @throw RLMPredicateException(@"Invalid operator type", + @"Operator '%@' not supported for string type", operatorName(operatorType)); + } +} + +void QueryBuilder::add_string_constraint(NSPredicateOperatorType operatorType, + NSComparisonPredicateOptions predicateOptions, + StringData value, + Columns&& column) { + switch (operatorType) { + case NSEqualToPredicateOperatorType: + case NSNotEqualToPredicateOperatorType: + add_string_constraint(operatorType, predicateOptions, std::move(column), value); + break; + default: + @throw RLMPredicateException(@"Invalid operator type", + @"Operator '%@' is not supported for string type with key path on right side of operator", + operatorName(operatorType)); + } +} + +id value_from_constant_expression_or_value(id value) { + if (NSExpression *exp = RLMDynamicCast(value)) { + RLMPrecondition(exp.expressionType == NSConstantValueExpressionType, + @"Invalid value", + @"Expressions within predicate aggregates must be constant values"); + return exp.constantValue; + } + return value; +} + +void validate_and_extract_between_range(id value, RLMProperty *prop, id *from, id *to) { + NSArray *array = RLMDynamicCast(value); + RLMPrecondition(array, @"Invalid value", @"object must be of type NSArray for BETWEEN operations"); + RLMPrecondition(array.count == 2, @"Invalid value", @"NSArray object must contain exactly two objects for BETWEEN operations"); + + *from = value_from_constant_expression_or_value(array.firstObject); + *to = value_from_constant_expression_or_value(array.lastObject); + RLMPrecondition(RLMIsObjectValidForProperty(*from, prop) && RLMIsObjectValidForProperty(*to, prop), + @"Invalid value", + @"NSArray objects must be of type %@ for BETWEEN operations", RLMTypeToString(prop.type)); +} + +void QueryBuilder::add_between_constraint(const ColumnReference& column, id value) { + if (column.has_any_to_many_links()) { + auto link_column = column.last_link_column(); + Query subquery = get_table(m_group, link_column.link_target_object_schema()).where(); + QueryBuilder(subquery, m_group, m_schema).add_between_constraint(column.column_ignoring_links(subquery), value); + + m_query.and_query(link_column.resolve(std::move(subquery)).count() > 0); + return; + } + + id from, to; + validate_and_extract_between_range(value, column.property(), &from, &to); + + RLMPropertyType type = column.type(); + + m_query.group(); + add_constraint(type, NSGreaterThanOrEqualToPredicateOperatorType, 0, column, from); + add_constraint(type, NSLessThanOrEqualToPredicateOperatorType, 0, column, to); + m_query.end_group(); +} + +void QueryBuilder::add_binary_constraint(NSPredicateOperatorType operatorType, + const ColumnReference& column, + BinaryData value) { + RLMPrecondition(!column.has_links(), @"Unsupported operator", @"NSData properties cannot be queried over an object link."); + + size_t index = column.index(); + Query query = m_query.get_table()->where(); + + switch (operatorType) { + case NSBeginsWithPredicateOperatorType: + add_substring_constraint(value, query.begins_with(index, value)); + break; + case NSEndsWithPredicateOperatorType: + add_substring_constraint(value, query.ends_with(index, value)); + break; + case NSContainsPredicateOperatorType: + add_substring_constraint(value, query.contains(index, value)); + break; + case NSEqualToPredicateOperatorType: + m_query.equal(index, value); + break; + case NSNotEqualToPredicateOperatorType: + m_query.not_equal(index, value); + break; + default: + @throw RLMPredicateException(@"Invalid operator type", + @"Operator '%@' not supported for binary type", operatorName(operatorType)); + } +} + +void QueryBuilder::add_binary_constraint(NSPredicateOperatorType operatorType, const ColumnReference& column, id value) { + add_binary_constraint(operatorType, column, RLMBinaryDataForNSData(value)); +} + +void QueryBuilder::add_binary_constraint(NSPredicateOperatorType operatorType, const ColumnReference& column, null) { + add_binary_constraint(operatorType, column, BinaryData()); +} + +void QueryBuilder::add_binary_constraint(NSPredicateOperatorType operatorType, id value, const ColumnReference& column) { + switch (operatorType) { + case NSEqualToPredicateOperatorType: + case NSNotEqualToPredicateOperatorType: + add_binary_constraint(operatorType, column, value); + break; + default: + @throw RLMPredicateException(@"Invalid operator type", + @"Operator '%@' is not supported for binary type with key path on right side of operator", + operatorName(operatorType)); + } +} + +void QueryBuilder::add_binary_constraint(NSPredicateOperatorType, const ColumnReference&, const ColumnReference&) { + @throw RLMPredicateException(@"Invalid predicate", @"Comparisons between two NSData properties are not supported"); +} + +void QueryBuilder::add_link_constraint(NSPredicateOperatorType operatorType, + const ColumnReference& column, RLMObject *obj) { + RLMPrecondition(operatorType == NSEqualToPredicateOperatorType || operatorType == NSNotEqualToPredicateOperatorType, + @"Invalid operator type", @"Only 'Equal' and 'Not Equal' operators supported for object comparison"); + + if (operatorType == NSEqualToPredicateOperatorType) { + m_query.and_query(column.resolve() == obj->_row); + } + else { + m_query.and_query(column.resolve() != obj->_row); + } +} + +void QueryBuilder::add_link_constraint(NSPredicateOperatorType operatorType, + const ColumnReference& column, + realm::null) { + RLMPrecondition(operatorType == NSEqualToPredicateOperatorType || operatorType == NSNotEqualToPredicateOperatorType, + @"Invalid operator type", @"Only 'Equal' and 'Not Equal' operators supported for object comparison"); + + if (operatorType == NSEqualToPredicateOperatorType) { + m_query.and_query(column.resolve() == null()); + } + else { + m_query.and_query(column.resolve() != null()); + } +} + +template +void QueryBuilder::add_link_constraint(NSPredicateOperatorType operatorType, T obj, const ColumnReference& column) { + // Link constraints only support the equal-to and not-equal-to operators. The order of operands + // is not important for those comparisons so we can delegate to the other implementation. + add_link_constraint(operatorType, column, obj); +} + +void QueryBuilder::add_link_constraint(NSPredicateOperatorType, const ColumnReference&, const ColumnReference&) { + // This is not actually reachable as this case is caught earlier, but this + // overload is needed for the code to compile + @throw RLMPredicateException(@"Invalid predicate", @"Comparisons between two RLMArray properties are not supported"); +} + + +// iterate over an array of subpredicates, using @func to build a query from each +// one and ORing them together +template +void process_or_group(Query &query, id array, Func&& func) { + array = RLMAsFastEnumeration(array); + RLMPrecondition(array, @"Invalid value", @"IN clause requires an array of items"); + + query.group(); + + bool first = true; + for (id item in array) { + if (!first) { + query.Or(); + } + first = false; + + func(item); + } + + if (first) { + // Queries can't be empty, so if there's zero things in the OR group + // validation will fail. Work around this by adding an expression which + // will never find any rows in a table. + query.and_query(std::unique_ptr(new FalseExpression)); + } + + query.end_group(); +} + +template +RequestedType convert(id value); + +template <> +Timestamp convert(id value) { + return RLMTimestampForNSDate(value); +} + +template <> +bool convert(id value) { + return [value boolValue]; +} + +template <> +Double convert(id value) { + return [value doubleValue]; +} + +template <> +Float convert(id value) { + return [value floatValue]; +} + +template <> +Int convert(id value) { + return [value longLongValue]; +} + +template <> +String convert(id value) { + return RLMStringDataWithNSString(value); +} + +template +realm::null value_of_type(realm::null) { + return realm::null(); +} + +template +auto value_of_type(id value) { + return ::convert(value); +} + +template +auto value_of_type(const ColumnReference& column) { + return column.resolve(); +} + + +template +void QueryBuilder::do_add_constraint(RLMPropertyType type, NSPredicateOperatorType operatorType, + NSComparisonPredicateOptions predicateOptions, T... values) +{ + static_assert(sizeof...(T) == 2, "do_add_constraint accepts only two values as arguments"); + + switch (type) { + case RLMPropertyTypeBool: + add_bool_constraint(operatorType, value_of_type(values)...); + break; + case RLMPropertyTypeDate: + add_numeric_constraint(type, operatorType, value_of_type(values)...); + break; + case RLMPropertyTypeDouble: + add_numeric_constraint(type, operatorType, value_of_type(values)...); + break; + case RLMPropertyTypeFloat: + add_numeric_constraint(type, operatorType, value_of_type(values)...); + break; + case RLMPropertyTypeInt: + add_numeric_constraint(type, operatorType, value_of_type(values)...); + break; + case RLMPropertyTypeString: + add_string_constraint(operatorType, predicateOptions, value_of_type(values)...); + break; + case RLMPropertyTypeData: + add_binary_constraint(operatorType, values...); + break; + case RLMPropertyTypeObject: + case RLMPropertyTypeLinkingObjects: + add_link_constraint(operatorType, values...); + break; + default: + @throw RLMPredicateException(@"Unsupported predicate value type", + @"Object type %@ not supported", RLMTypeToString(type)); + } +} + +void QueryBuilder::do_add_constraint(RLMPropertyType, NSPredicateOperatorType, NSComparisonPredicateOptions, id, realm::null) +{ + // This is not actually reachable as this case is caught earlier, but this + // overload is needed for the code to compile + @throw RLMPredicateException(@"Invalid predicate expressions", + @"Predicate expressions must compare a keypath and another keypath or a constant value"); +} + +bool is_nsnull(id value) { + return !value || value == NSNull.null; +} + +template +bool is_nsnull(T) { + return false; +} + +template +void QueryBuilder::add_constraint(RLMPropertyType type, NSPredicateOperatorType operatorType, + NSComparisonPredicateOptions predicateOptions, L lhs, R rhs) +{ + // The expression operators are only overloaded for realm::null on the rhs + RLMPrecondition(!is_nsnull(lhs), @"Unsupported operator", + @"Nil is only supported on the right side of operators"); + + if (is_nsnull(rhs)) { + do_add_constraint(type, operatorType, predicateOptions, lhs, realm::null()); + } + else { + do_add_constraint(type, operatorType, predicateOptions, lhs, rhs); + } +} + +struct KeyPath { + std::vector links; + RLMProperty *property; + bool containsToManyRelationship; +}; + +KeyPath key_path_from_string(RLMSchema *schema, RLMObjectSchema *objectSchema, NSString *keyPath) +{ + RLMProperty *property; + std::vector links; + + bool keyPathContainsToManyRelationship = false; + + NSUInteger start = 0, length = keyPath.length, end = NSNotFound; + do { + end = [keyPath rangeOfString:@"." options:0 range:{start, length - start}].location; + NSString *propertyName = [keyPath substringWithRange:{start, end == NSNotFound ? length - start : end - start}]; + property = objectSchema[propertyName]; + RLMPrecondition(property, @"Invalid property name", + @"Property '%@' not found in object of type '%@'", + propertyName, objectSchema.className); + + if (property.array) + keyPathContainsToManyRelationship = true; + + if (end != NSNotFound) { + RLMPrecondition(property.type == RLMPropertyTypeObject || property.type == RLMPropertyTypeLinkingObjects, + @"Invalid value", @"Property '%@' is not a link in object of type '%@'", + propertyName, objectSchema.className); + + links.push_back(property); + REALM_ASSERT(property.objectClassName); + objectSchema = schema[property.objectClassName]; + } + + start = end + 1; + } while (end != NSNotFound); + + return {std::move(links), property, keyPathContainsToManyRelationship}; +} + +ColumnReference QueryBuilder::column_reference_from_key_path(RLMObjectSchema *objectSchema, + NSString *keyPathString, bool isAggregate) +{ + auto keyPath = key_path_from_string(m_schema, objectSchema, keyPathString); + + if (isAggregate && !keyPath.containsToManyRelationship) { + @throw RLMPredicateException(@"Invalid predicate", + @"Aggregate operations can only be used on key paths that include an array property"); + } else if (!isAggregate && keyPath.containsToManyRelationship) { + @throw RLMPredicateException(@"Invalid predicate", + @"Key paths that include an array property must use aggregate operations"); + } + + return ColumnReference(m_query, m_group, m_schema, keyPath.property, std::move(keyPath.links)); +} + +void validate_property_value(const ColumnReference& column, + __unsafe_unretained id const value, + __unsafe_unretained NSString *const err, + __unsafe_unretained RLMObjectSchema *const objectSchema, + __unsafe_unretained NSString *const keyPath) { + RLMProperty *prop = column.property(); + if (prop.array) { + RLMPrecondition([RLMObjectBaseObjectSchema(RLMDynamicCast(value)).className isEqualToString:prop.objectClassName], + @"Invalid value", err, prop.objectClassName, keyPath, objectSchema.className, value); + } + else { + RLMPrecondition(RLMIsObjectValidForProperty(value, prop), + @"Invalid value", err, RLMTypeToString(prop.type), keyPath, objectSchema.className, value); + } + if (RLMObjectBase *obj = RLMDynamicCast(value)) { + RLMPrecondition(!obj->_row.is_attached() || &column.group() == &obj->_realm.group, + @"Invalid value origin", @"Object must be from the Realm being queried"); + } +} + +template +struct ValueOfTypeWithCollectionOperationHelper; + +template <> +struct ValueOfTypeWithCollectionOperationHelper { + static auto convert(const CollectionOperation& operation) + { + assert(operation.type() == CollectionOperation::Count); + return operation.link_column().resolve().count(); + } +}; + +#define VALUE_OF_TYPE_WITH_COLLECTION_OPERATOR_HELPER(OperationType, function) \ +template \ +struct ValueOfTypeWithCollectionOperationHelper { \ + static auto convert(const CollectionOperation& operation) \ + { \ + REALM_ASSERT(operation.type() == OperationType); \ + auto targetColumn = operation.link_column().resolve().template column(operation.column().index()); \ + return targetColumn.function(); \ + } \ +} \ + +VALUE_OF_TYPE_WITH_COLLECTION_OPERATOR_HELPER(CollectionOperation::Minimum, min); +VALUE_OF_TYPE_WITH_COLLECTION_OPERATOR_HELPER(CollectionOperation::Maximum, max); +VALUE_OF_TYPE_WITH_COLLECTION_OPERATOR_HELPER(CollectionOperation::Sum, sum); +VALUE_OF_TYPE_WITH_COLLECTION_OPERATOR_HELPER(CollectionOperation::Average, average); +#undef VALUE_OF_TYPE_WITH_COLLECTION_OPERATOR_HELPER + +template +auto value_of_type_with_collection_operation(T&& value) { + return value_of_type(std::forward(value)); +} + +template +auto value_of_type_with_collection_operation(CollectionOperation operation) { + using helper = ValueOfTypeWithCollectionOperationHelper; + return helper::convert(operation); +} + +template +void QueryBuilder::add_collection_operation_constraint(RLMPropertyType propertyType, NSPredicateOperatorType operatorType, T... values) +{ + switch (propertyType) { + case RLMPropertyTypeInt: + add_numeric_constraint(propertyType, operatorType, value_of_type_with_collection_operation(values)...); + break; + case RLMPropertyTypeFloat: + add_numeric_constraint(propertyType, operatorType, value_of_type_with_collection_operation(values)...); + break; + case RLMPropertyTypeDouble: + add_numeric_constraint(propertyType, operatorType, value_of_type_with_collection_operation(values)...); + break; + default: + REALM_ASSERT(false && "Only numeric property types should hit this path."); + } +} + +template +void QueryBuilder::add_collection_operation_constraint(NSPredicateOperatorType operatorType, + CollectionOperation collectionOperation, T... values) +{ + static_assert(sizeof...(T) == 2, "add_collection_operation_constraint accepts only two values as arguments"); + + switch (collectionOperation.type()) { + case CollectionOperation::Count: + add_numeric_constraint(RLMPropertyTypeInt, operatorType, + value_of_type_with_collection_operation(values)...); + break; + case CollectionOperation::Minimum: + add_collection_operation_constraint(collectionOperation.column().type(), operatorType, values...); + break; + case CollectionOperation::Maximum: + add_collection_operation_constraint(collectionOperation.column().type(), operatorType, values...); + break; + case CollectionOperation::Sum: + add_collection_operation_constraint(collectionOperation.column().type(), operatorType, values...); + break; + case CollectionOperation::Average: + add_collection_operation_constraint(collectionOperation.column().type(), operatorType, values...); + break; + } +} + +bool key_path_contains_collection_operator(NSString *keyPath) { + return [keyPath rangeOfString:@"@"].location != NSNotFound; +} + +NSString *get_collection_operation_name_from_key_path(NSString *keyPath, NSString **leadingKeyPath, + NSString **trailingKey) { + NSRange at = [keyPath rangeOfString:@"@"]; + if (at.location == NSNotFound || at.location >= keyPath.length - 1) { + @throw RLMPredicateException(@"Invalid key path", @"'%@' is not a valid key path'", keyPath); + } + + if (at.location == 0 || [keyPath characterAtIndex:at.location - 1] != '.') { + @throw RLMPredicateException(@"Invalid key path", @"'%@' is not a valid key path'", keyPath); + } + + NSRange trailingKeyRange = [keyPath rangeOfString:@"." options:0 range:{at.location, keyPath.length - at.location} locale:nil]; + + *leadingKeyPath = [keyPath substringToIndex:at.location - 1]; + if (trailingKeyRange.location == NSNotFound) { + *trailingKey = nil; + return [keyPath substringFromIndex:at.location]; + } else { + *trailingKey = [keyPath substringFromIndex:trailingKeyRange.location + 1]; + return [keyPath substringWithRange:{at.location, trailingKeyRange.location - at.location}]; + } +} + +CollectionOperation QueryBuilder::collection_operation_from_key_path(RLMObjectSchema *desc, NSString *keyPath) { + NSString *leadingKeyPath; + NSString *trailingKey; + NSString *collectionOperationName = get_collection_operation_name_from_key_path(keyPath, &leadingKeyPath, &trailingKey); + + ColumnReference linkColumn = column_reference_from_key_path(desc, leadingKeyPath, true); + util::Optional column; + if (trailingKey) { + RLMPrecondition([trailingKey rangeOfString:@"."].location == NSNotFound, @"Invalid key path", + @"Right side of collection operator may only have a single level key"); + NSString *fullKeyPath = [leadingKeyPath stringByAppendingFormat:@".%@", trailingKey]; + column = column_reference_from_key_path(desc, fullKeyPath, true); + } + + return {collectionOperationName, std::move(linkColumn), std::move(column)}; +} + +void QueryBuilder::apply_collection_operator_expression(RLMObjectSchema *desc, + NSString *keyPath, id value, + NSComparisonPredicate *pred) { + CollectionOperation operation = collection_operation_from_key_path(desc, keyPath); + operation.validate_comparison(value); + + if (pred.leftExpression.expressionType == NSKeyPathExpressionType) { + add_collection_operation_constraint(pred.predicateOperatorType, operation, operation, value); + } else { + add_collection_operation_constraint(pred.predicateOperatorType, operation, value, operation); + } +} + +void QueryBuilder::apply_value_expression(RLMObjectSchema *desc, + NSString *keyPath, id value, + NSComparisonPredicate *pred) +{ + if (key_path_contains_collection_operator(keyPath)) { + apply_collection_operator_expression(desc, keyPath, value, pred); + return; + } + + bool isAny = pred.comparisonPredicateModifier == NSAnyPredicateModifier; + ColumnReference column = column_reference_from_key_path(desc, keyPath, isAny); + + // check to see if this is a between query + if (pred.predicateOperatorType == NSBetweenPredicateOperatorType) { + add_between_constraint(std::move(column), value); + return; + } + + // turn "key.path IN collection" into ored together ==. "collection IN key.path" is handled elsewhere. + if (pred.predicateOperatorType == NSInPredicateOperatorType) { + process_or_group(m_query, value, [&](id item) { + id normalized = value_from_constant_expression_or_value(item); + validate_property_value(column, normalized, + @"Expected object of type %@ in IN clause for property '%@' on object of type '%@', but received: %@", desc, keyPath); + add_constraint(column.type(), NSEqualToPredicateOperatorType, pred.options, column, normalized); + }); + return; + } + + validate_property_value(column, value, @"Expected object of type %@ for property '%@' on object of type '%@', but received: %@", desc, keyPath); + if (pred.leftExpression.expressionType == NSKeyPathExpressionType) { + add_constraint(column.type(), pred.predicateOperatorType, pred.options, std::move(column), value); + } else { + add_constraint(column.type(), pred.predicateOperatorType, pred.options, value, std::move(column)); + } +} + +void QueryBuilder::apply_column_expression(RLMObjectSchema *desc, + NSString *leftKeyPath, NSString *rightKeyPath, + NSComparisonPredicate *predicate) +{ + bool left_key_path_contains_collection_operator = key_path_contains_collection_operator(leftKeyPath); + bool right_key_path_contains_collection_operator = key_path_contains_collection_operator(rightKeyPath); + if (left_key_path_contains_collection_operator && right_key_path_contains_collection_operator) { + @throw RLMPredicateException(@"Unsupported predicate", @"Key paths including aggregate operations cannot be compared with other aggregate operations."); + } + + if (left_key_path_contains_collection_operator) { + CollectionOperation left = collection_operation_from_key_path(desc, leftKeyPath); + ColumnReference right = column_reference_from_key_path(desc, rightKeyPath, false); + left.validate_comparison(right); + add_collection_operation_constraint(predicate.predicateOperatorType, left, left, std::move(right)); + return; + } + if (right_key_path_contains_collection_operator) { + ColumnReference left = column_reference_from_key_path(desc, leftKeyPath, false); + CollectionOperation right = collection_operation_from_key_path(desc, rightKeyPath); + right.validate_comparison(left); + add_collection_operation_constraint(predicate.predicateOperatorType, right, std::move(left), right); + return; + } + + bool isAny = false; + ColumnReference left = column_reference_from_key_path(desc, leftKeyPath, isAny); + ColumnReference right = column_reference_from_key_path(desc, rightKeyPath, isAny); + + // NOTE: It's assumed that column type must match and no automatic type conversion is supported. + RLMPrecondition(left.type() == right.type(), + RLMPropertiesComparisonTypeMismatchException, + RLMPropertiesComparisonTypeMismatchReason, + RLMTypeToString(left.type()), + RLMTypeToString(right.type())); + + // TODO: Should we handle special case where left row is the same as right row (tautology) + add_constraint(left.type(), predicate.predicateOperatorType, predicate.options, + std::move(left), std::move(right)); +} + +// Identify expressions of the form [SELF valueForKeyPath:] +bool is_self_value_for_key_path_function_expression(NSExpression *expression) +{ + if (expression.expressionType != NSFunctionExpressionType) + return false; + + if (expression.operand.expressionType != NSEvaluatedObjectExpressionType) + return false; + + return [expression.function isEqualToString:@"valueForKeyPath:"]; +} + +// -[NSPredicate predicateWithSubtitutionVariables:] results in function expressions of the form [SELF valueForKeyPath:] +// that apply_predicate cannot handle. Replace such expressions with equivalent NSKeyPathExpressionType expressions. +NSExpression *simplify_self_value_for_key_path_function_expression(NSExpression *expression) { + if (is_self_value_for_key_path_function_expression(expression)) { + if (NSString *keyPath = [expression.arguments.firstObject keyPath]) { + return [NSExpression expressionForKeyPath:keyPath]; + } + } + return expression; +} + +void QueryBuilder::apply_subquery_count_expression(RLMObjectSchema *objectSchema, + NSExpression *subqueryExpression, NSPredicateOperatorType operatorType, NSExpression *right) { + if (right.expressionType != NSConstantValueExpressionType || ![right.constantValue isKindOfClass:[NSNumber class]]) { + @throw RLMPredicateException(@"Invalid predicate expression", @"SUBQUERY(…).@count is only supported when compared with a constant number."); + } + int64_t value = [right.constantValue integerValue]; + + ColumnReference collectionColumn = column_reference_from_key_path(objectSchema, [subqueryExpression.collection keyPath], true); + RLMObjectSchema *collectionMemberObjectSchema = m_schema[collectionColumn.property().objectClassName]; + + // Eliminate references to the iteration variable in the subquery. + NSPredicate *subqueryPredicate = [subqueryExpression.predicate predicateWithSubstitutionVariables:@{ subqueryExpression.variable : [NSExpression expressionForEvaluatedObject] }]; + subqueryPredicate = transformPredicate(subqueryPredicate, simplify_self_value_for_key_path_function_expression); + + Query subquery = RLMPredicateToQuery(subqueryPredicate, collectionMemberObjectSchema, m_schema, m_group); + add_numeric_constraint(RLMPropertyTypeInt, operatorType, + collectionColumn.resolve(std::move(subquery)).count(), value); +} + +void QueryBuilder::apply_function_subquery_expression(RLMObjectSchema *objectSchema, NSExpression *functionExpression, + NSPredicateOperatorType operatorType, NSExpression *right) { + if (![functionExpression.function isEqualToString:@"valueForKeyPath:"] || functionExpression.arguments.count != 1) { + @throw RLMPredicateException(@"Invalid predicate", @"The '%@' function is not supported on the result of a SUBQUERY.", functionExpression.function); + } + + NSExpression *keyPathExpression = functionExpression.arguments.firstObject; + if ([keyPathExpression.keyPath isEqualToString:@"@count"]) { + apply_subquery_count_expression(objectSchema, functionExpression.operand, operatorType, right); + } else { + @throw RLMPredicateException(@"Invalid predicate", @"SUBQUERY is only supported when immediately followed by .@count that is compared with a constant number."); + } +} + +void QueryBuilder::apply_function_expression(RLMObjectSchema *objectSchema, NSExpression *functionExpression, + NSPredicateOperatorType operatorType, NSExpression *right) { + if (functionExpression.operand.expressionType == NSSubqueryExpressionType) { + apply_function_subquery_expression(objectSchema, functionExpression, operatorType, right); + } else { + @throw RLMPredicateException(@"Invalid predicate", @"The '%@' function is not supported.", functionExpression.function); + } +} + + +void QueryBuilder::apply_predicate(NSPredicate *predicate, RLMObjectSchema *objectSchema) +{ + // Compound predicates. + if ([predicate isMemberOfClass:[NSCompoundPredicate class]]) { + NSCompoundPredicate *comp = (NSCompoundPredicate *)predicate; + + switch ([comp compoundPredicateType]) { + case NSAndPredicateType: + if (comp.subpredicates.count) { + // Add all of the subpredicates. + m_query.group(); + for (NSPredicate *subp in comp.subpredicates) { + apply_predicate(subp, objectSchema); + } + m_query.end_group(); + } else { + // NSCompoundPredicate's documentation states that an AND predicate with no subpredicates evaluates to TRUE. + m_query.and_query(std::unique_ptr(new TrueExpression)); + } + break; + + case NSOrPredicateType: { + // Add all of the subpredicates with ors inbetween. + process_or_group(m_query, comp.subpredicates, [&](__unsafe_unretained NSPredicate *const subp) { + apply_predicate(subp, objectSchema); + }); + break; + } + + case NSNotPredicateType: + // Add the negated subpredicate + m_query.Not(); + apply_predicate(comp.subpredicates.firstObject, objectSchema); + break; + + default: + @throw RLMPredicateException(@"Invalid compound predicate type", + @"Only support AND, OR and NOT predicate types"); + } + } + else if ([predicate isMemberOfClass:[NSComparisonPredicate class]]) { + NSComparisonPredicate *compp = (NSComparisonPredicate *)predicate; + + // check modifier + RLMPrecondition(compp.comparisonPredicateModifier != NSAllPredicateModifier, + @"Invalid predicate", @"ALL modifier not supported"); + + NSExpressionType exp1Type = compp.leftExpression.expressionType; + NSExpressionType exp2Type = compp.rightExpression.expressionType; + + if (compp.comparisonPredicateModifier == NSAnyPredicateModifier) { + // for ANY queries + RLMPrecondition(exp1Type == NSKeyPathExpressionType && exp2Type == NSConstantValueExpressionType, + @"Invalid predicate", + @"Predicate with ANY modifier must compare a KeyPath with RLMArray with a value"); + } + + if (compp.predicateOperatorType == NSBetweenPredicateOperatorType || compp.predicateOperatorType == NSInPredicateOperatorType) { + // Inserting an array via %@ gives NSConstantValueExpressionType, but including it directly gives NSAggregateExpressionType + if (exp1Type == NSKeyPathExpressionType && (exp2Type == NSAggregateExpressionType || exp2Type == NSConstantValueExpressionType)) { + // "key.path IN %@", "key.path IN {…}", "key.path BETWEEN %@", or "key.path BETWEEN {…}". + exp2Type = NSConstantValueExpressionType; + } + else if (compp.predicateOperatorType == NSInPredicateOperatorType && exp1Type == NSConstantValueExpressionType && exp2Type == NSKeyPathExpressionType) { + // "%@ IN key.path" is equivalent to "ANY key.path IN %@". Rewrite the former into the latter. + compp = [NSComparisonPredicate predicateWithLeftExpression:compp.rightExpression rightExpression:compp.leftExpression + modifier:NSAnyPredicateModifier type:NSEqualToPredicateOperatorType options:0]; + exp1Type = NSKeyPathExpressionType; + exp2Type = NSConstantValueExpressionType; + } + else { + if (compp.predicateOperatorType == NSBetweenPredicateOperatorType) { + @throw RLMPredicateException(@"Invalid predicate", + @"Predicate with BETWEEN operator must compare a KeyPath with an aggregate with two values"); + } + else if (compp.predicateOperatorType == NSInPredicateOperatorType) { + @throw RLMPredicateException(@"Invalid predicate", + @"Predicate with IN operator must compare a KeyPath with an aggregate"); + } + } + } + + if (exp1Type == NSKeyPathExpressionType && exp2Type == NSKeyPathExpressionType) { + // both expression are KeyPaths + apply_column_expression(objectSchema, compp.leftExpression.keyPath, compp.rightExpression.keyPath, compp); + } + else if (exp1Type == NSKeyPathExpressionType && exp2Type == NSConstantValueExpressionType) { + // comparing keypath to value + apply_value_expression(objectSchema, compp.leftExpression.keyPath, compp.rightExpression.constantValue, compp); + } + else if (exp1Type == NSConstantValueExpressionType && exp2Type == NSKeyPathExpressionType) { + // comparing value to keypath + apply_value_expression(objectSchema, compp.rightExpression.keyPath, compp.leftExpression.constantValue, compp); + } + else if (exp1Type == NSFunctionExpressionType) { + apply_function_expression(objectSchema, compp.leftExpression, compp.predicateOperatorType, compp.rightExpression); + } + else if (exp1Type == NSSubqueryExpressionType) { + // The subquery expressions that we support are handled by the NSFunctionExpressionType case above. + @throw RLMPredicateException(@"Invalid predicate expression", @"SUBQUERY is only supported when immediately followed by .@count."); + } + else { + @throw RLMPredicateException(@"Invalid predicate expressions", + @"Predicate expressions must compare a keypath and another keypath or a constant value"); + } + } + else if ([predicate isEqual:[NSPredicate predicateWithValue:YES]]) { + m_query.and_query(std::unique_ptr(new TrueExpression)); + } else if ([predicate isEqual:[NSPredicate predicateWithValue:NO]]) { + m_query.and_query(std::unique_ptr(new FalseExpression)); + } + else { + // invalid predicate type + @throw RLMPredicateException(@"Invalid predicate", + @"Only support compound, comparison, and constant predicates"); + } +} +} // namespace + +realm::Query RLMPredicateToQuery(NSPredicate *predicate, RLMObjectSchema *objectSchema, + RLMSchema *schema, Group &group) +{ + auto query = get_table(group, objectSchema).where(); + + // passing a nil predicate is a no-op + if (!predicate) { + return query; + } + + @autoreleasepool { + QueryBuilder(query, group, schema).apply_predicate(predicate, objectSchema); + } + + // Test the constructed query in core + std::string validateMessage = query.validate(); + RLMPrecondition(validateMessage.empty(), @"Invalid query", @"%.*s", + (int)validateMessage.size(), validateMessage.c_str()); + return query; +} diff --git a/!main project/Pods/Realm/Realm/RLMRealm+Sync.mm b/!main project/Pods/Realm/Realm/RLMRealm+Sync.mm new file mode 100644 index 0000000..02f10d7 --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMRealm+Sync.mm @@ -0,0 +1,86 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2017 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMRealm+Sync.h" + +#import "RLMObjectBase.h" +#import "RLMQueryUtil.hpp" +#import "RLMObjectSchema.h" +#import "RLMRealm_Private.hpp" +#import "RLMResults_Private.hpp" +#import "RLMSchema.h" +#import "RLMSyncSession.h" + +#import "results.hpp" +#import "shared_realm.hpp" +#import "sync/partial_sync.hpp" +#import "sync/subscription_state.hpp" + +using namespace realm; + +@implementation RLMRealm (Sync) + +- (void)subscribeToObjects:(Class)type where:(NSString *)query callback:(RLMPartialSyncFetchCallback)callback { + [self verifyThread]; + + RLMClassInfo& info = _info[[type className]]; + Query q = RLMPredicateToQuery([NSPredicate predicateWithFormat:query], + info.rlmObjectSchema, self.schema, self.group); + struct Holder { + partial_sync::Subscription subscription; + partial_sync::SubscriptionNotificationToken token; + + Holder(partial_sync::Subscription&& s) : subscription(std::move(s)) { } + }; + auto state = std::make_shared(partial_sync::subscribe(Results(_realm, std::move(q)), {})); + state->token = state->subscription.add_notification_callback([=]() mutable { + if (!callback) { + return; + } + switch (state->subscription.state()) { + case partial_sync::SubscriptionState::Invalidated: + case partial_sync::SubscriptionState::Pending: + case partial_sync::SubscriptionState::Creating: + return; + + case partial_sync::SubscriptionState::Error: + try { + rethrow_exception(state->subscription.error()); + } + catch (...) { + NSError *error = nil; + RLMRealmTranslateException(&error); + callback(nil, error); + } + break; + + case partial_sync::SubscriptionState::Complete: + callback([RLMResults emptyDetachedResults], nil); + break; + } + + callback = nil; + state->token = {}; + }); +} + +- (RLMSyncSession *)syncSession { + return [RLMSyncSession sessionForRealm:self]; +} + +@end diff --git a/!main project/Pods/Realm/Realm/RLMRealm.mm b/!main project/Pods/Realm/Realm/RLMRealm.mm new file mode 100644 index 0000000..2ec9b2b --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMRealm.mm @@ -0,0 +1,1033 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMRealm_Private.hpp" + +#import "RLMAnalytics.hpp" +#import "RLMArray_Private.hpp" +#import "RLMMigration_Private.h" +#import "RLMObject_Private.h" +#import "RLMObject_Private.hpp" +#import "RLMObjectSchema_Private.hpp" +#import "RLMObjectStore.h" +#import "RLMObservation.hpp" +#import "RLMProperty.h" +#import "RLMProperty_Private.h" +#import "RLMQueryUtil.hpp" +#import "RLMRealmConfiguration_Private.hpp" +#import "RLMRealmUtil.hpp" +#import "RLMSchema_Private.hpp" +#import "RLMThreadSafeReference_Private.hpp" +#import "RLMUpdateChecker.hpp" +#import "RLMUtil.hpp" + +#include "impl/realm_coordinator.hpp" +#include "object_store.hpp" +#include "schema.hpp" +#include "shared_realm.hpp" +#include "thread_safe_reference.hpp" + +#include +#include +#include + +#if REALM_ENABLE_SYNC +#import "RLMSyncManager_Private.h" +#import "RLMSyncSession_Private.hpp" +#import "RLMSyncUtil_Private.hpp" + +#import "sync/async_open_task.hpp" +#import "sync/sync_session.hpp" +#endif + +using namespace realm; +using util::File; + +@interface RLMRealmNotificationToken : RLMNotificationToken +@property (nonatomic, strong) RLMRealm *realm; +@property (nonatomic, copy) RLMNotificationBlock block; +@end + +@interface RLMRealm () +@property (nonatomic, strong) NSHashTable *notificationHandlers; +- (void)sendNotifications:(RLMNotification)notification; +@end + +void RLMDisableSyncToDisk() { + realm::disable_sync_to_disk(); +} + +static void RLMAddSkipBackupAttributeToItemAtPath(std::string const& path) { + [[NSURL fileURLWithPath:@(path.c_str())] setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil]; +} + +@implementation RLMRealmNotificationToken +- (void)invalidate { + [_realm verifyThread]; + [_realm.notificationHandlers removeObject:self]; + _realm = nil; + _block = nil; +} + +- (void)suppressNextNotification { + // Temporarily replace the block with one which restores the old block + // rather than producing a notification. + + // This briefly creates a retain cycle but it's fine because the block will + // be synchronously called shortly after this method is called. Unlike with + // collection notifications, this does not have to go through the object + // store or do fancy things to handle transaction coalescing because it's + // called synchronously by the obj-c code and not by the object store. + auto notificationBlock = _block; + _block = ^(RLMNotification, RLMRealm *) { + _block = notificationBlock; + }; +} + +- (void)dealloc { + if (_realm || _block) { + NSLog(@"RLMNotificationToken released without unregistering a notification. You must hold " + @"on to the RLMNotificationToken returned from addNotificationBlock and call " + @"-[RLMNotificationToken invalidate] when you no longer wish to receive RLMRealm notifications."); + } +} +@end + +#if !REALM_ENABLE_SYNC +@interface RLMAsyncOpenTask : NSObject +@end +@implementation RLMAsyncOpenTask +@end +#endif + +static bool shouldForciblyDisableEncryption() { + static bool disableEncryption = getenv("REALM_DISABLE_ENCRYPTION"); + return disableEncryption; +} + +NSData *RLMRealmValidatedEncryptionKey(NSData *key) { + if (shouldForciblyDisableEncryption()) { + return nil; + } + + if (key && key.length != 64) { + @throw RLMException(@"Encryption key must be exactly 64 bytes long"); + } + + return key; +} + +@implementation RLMRealm { + NSHashTable *_collectionEnumerators; + bool _sendingNotifications; +} + ++ (BOOL)isCoreDebug { + return realm::Version::has_feature(realm::feature_Debug); +} + ++ (void)initialize { + static bool initialized; + if (initialized) { + return; + } + initialized = true; + + RLMCheckForUpdates(); + RLMSendAnalytics(); +} + +- (instancetype)initPrivate { + self = [super init]; + return self; +} + +- (BOOL)isEmpty { + return realm::ObjectStore::is_empty(self.group); +} + +- (void)verifyThread { + try { + _realm->verify_thread(); + } + catch (std::exception const& e) { + @throw RLMException(e); + } +} + +- (BOOL)inWriteTransaction { + return _realm->is_in_transaction(); +} + +- (realm::Group &)group { + return _realm->read_group(); +} + +- (BOOL)autorefresh { + return _realm->auto_refresh(); +} + +- (void)setAutorefresh:(BOOL)autorefresh { + _realm->set_auto_refresh(autorefresh); +} + ++ (instancetype)defaultRealm { + return [RLMRealm realmWithConfiguration:[RLMRealmConfiguration rawDefaultConfiguration] error:nil]; +} + ++ (instancetype)realmWithURL:(NSURL *)fileURL { + RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; + configuration.fileURL = fileURL; + return [RLMRealm realmWithConfiguration:configuration error:nil]; +} + +// The server doesn't send us the subscriptions for permission types until the +// first subscription is created. This is fine for synchronous opens (if we're +// creating a new Realm we create the permission objects ourselves), but it +// causes issues for asyncOpen because it means that when our download completes +// we don't actually have the full Realm state yet. +static void waitForPartialSyncSubscriptions(Realm::Config const& config) { +#if REALM_ENABLE_SYNC + auto realm = Realm::get_shared_realm(config); + auto table = ObjectStore::table_for_object_type(realm->read_group(), "__ResultSets"); + + realm->begin_transaction(); + size_t row = realm::sync::create_object(realm->read_group(), *table); + + // Set expires_at to time 0 so that this object will be cleaned up the first + // time the user creates a subscription + size_t expires_at_col = table->get_column_index("expires_at"); + if (expires_at_col == npos) { + expires_at_col = table->add_column(type_Timestamp, "expires_at", true); + } + table->set_timestamp(expires_at_col, row, Timestamp(0, 0)); + realm->commit_transaction(); + + NotificationToken token; + Results results(realm, *table); + CFRunLoopRef runLoop = CFRunLoopGetCurrent(); + CFRunLoopPerformBlock(runLoop, kCFRunLoopDefaultMode, [&]() mutable { + token = results.add_notification_callback([&](CollectionChangeSet const&, std::exception_ptr) mutable { + if (table->size() > 1) { + token = {}; + CFRunLoopStop(runLoop); + } + }); + }); + CFRunLoopRun(); +#else + static_cast(config); +#endif +} + +static dispatch_queue_t s_async_open_queue = dispatch_queue_create("io.realm.asyncOpenDispatchQueue", + DISPATCH_QUEUE_CONCURRENT); +void RLMSetAsyncOpenQueue(dispatch_queue_t queue) { + s_async_open_queue = queue; +} + ++ (RLMAsyncOpenTask *)asyncOpenWithConfiguration:(RLMRealmConfiguration *)configuration + callbackQueue:(dispatch_queue_t)callbackQueue + callback:(RLMAsyncOpenRealmCallback)callback { + auto openCompletion = [=](ThreadSafeReference ref, std::exception_ptr err) { + @autoreleasepool { + if (err) { + try { + std::rethrow_exception(err); + } + catch (...) { + NSError *error; + RLMRealmTranslateException(&error); + dispatch_async(callbackQueue, ^{ + callback(nil, error); + }); + } + return; + } + + auto complete = ^{ + dispatch_async(callbackQueue, ^{ + @autoreleasepool { + NSError *error; + RLMRealm *localRealm = [RLMRealm realmWithConfiguration:configuration error:&error]; + callback(localRealm, error); + } + }); + }; + + auto realm = Realm::get_shared_realm(std::move(ref)); + bool needsSubscriptions = realm->is_partial() && ObjectStore::table_for_object_type(realm->read_group(), "__ResultSets")->size() == 0; + if (needsSubscriptions) { + // We need to dispatch back to the work queue to wait for the + // subscriptions as we're currently running on the sync worker + // thread and blocking it to wait for subscriptions means no syncing + dispatch_async(s_async_open_queue, ^{ + @autoreleasepool { + waitForPartialSyncSubscriptions(realm->config()); + complete(); + } + }); + } + else { + complete(); + } + } + }; + + RLMAsyncOpenTask *ret = [RLMAsyncOpenTask new]; + dispatch_async(s_async_open_queue, ^{ + @autoreleasepool { + Realm::Config& config = configuration.config; + if (config.sync_config) { +#if REALM_ENABLE_SYNC + auto task = realm::Realm::get_synchronized_realm(config); + ret.task = task; + task->start(openCompletion); +#else + @throw RLMException(@"Realm was not built with sync enabled"); +#endif + } + else { + try { + openCompletion(realm::_impl::RealmCoordinator::get_coordinator(config)->get_unbound_realm(), nullptr); + } + catch (...) { + openCompletion({}, std::current_exception()); + } + } + } + }); + return ret; +} + +// ARC tries to eliminate calls to autorelease when the value is then immediately +// returned, but this results in significantly different semantics between debug +// and release builds for RLMRealm, so force it to always autorelease. +static id RLMAutorelease(__unsafe_unretained id value) { + // +1 __bridge_retained, -1 CFAutorelease + return value ? (__bridge id)CFAutorelease((__bridge_retained CFTypeRef)value) : nil; +} + ++ (instancetype)realmWithSharedRealm:(SharedRealm)sharedRealm schema:(RLMSchema *)schema { + RLMRealm *realm = [[RLMRealm alloc] initPrivate]; + realm->_realm = sharedRealm; + realm->_dynamic = YES; + realm->_schema = schema; + realm->_info = RLMSchemaInfo(realm); + return RLMAutorelease(realm); +} + +REALM_NOINLINE void RLMRealmTranslateException(NSError **error) { + try { + throw; + } + catch (RealmFileException const& ex) { + switch (ex.kind()) { + case RealmFileException::Kind::PermissionDenied: + RLMSetErrorOrThrow(RLMMakeError(RLMErrorFilePermissionDenied, ex), error); + break; + case RealmFileException::Kind::IncompatibleLockFile: { + NSString *err = @"Realm file is currently open in another process " + "which cannot share access with this process. All " + "processes sharing a single file must be the same " + "architecture. For sharing files between the Realm " + "Browser and an iOS simulator, this means that you " + "must use a 64-bit simulator."; + RLMSetErrorOrThrow(RLMMakeError(RLMErrorIncompatibleLockFile, + File::PermissionDenied(err.UTF8String, ex.path())), error); + break; + } + case RealmFileException::Kind::NotFound: + RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileNotFound, ex), error); + break; + case RealmFileException::Kind::Exists: + RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileExists, ex), error); + break; + case RealmFileException::Kind::BadHistoryError: { + NSString *err = @"Realm file's history format is incompatible with the " + "settings in the configuration object being used to open " + "the Realm. Note that Realms configured for sync cannot be " + "opened as non-synced Realms, and vice versa. Otherwise, the " + "file may be corrupt."; + RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileAccess, + File::AccessError(err.UTF8String, ex.path())), error); + break; + } + case RealmFileException::Kind::AccessError: + RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileAccess, ex), error); + break; + case RealmFileException::Kind::FormatUpgradeRequired: + RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileFormatUpgradeRequired, ex), error); + break; + default: + RLMSetErrorOrThrow(RLMMakeError(RLMErrorFail, ex), error); + break; + } + } + catch (AddressSpaceExhausted const &ex) { + RLMSetErrorOrThrow(RLMMakeError(RLMErrorAddressSpaceExhausted, ex), error); + } + catch (SchemaMismatchException const& ex) { + RLMSetErrorOrThrow(RLMMakeError(RLMErrorSchemaMismatch, ex), error); + } + catch (std::system_error const& ex) { + RLMSetErrorOrThrow(RLMMakeError(ex), error); + } + catch (const std::exception &exp) { + RLMSetErrorOrThrow(RLMMakeError(RLMErrorFail, exp), error); + } +} + +REALM_NOINLINE static void translateSharedGroupOpenException(RLMRealmConfiguration *originalConfiguration, NSError **error) { + try { + throw; + } + catch (RealmFileException const& ex) { + switch (ex.kind()) { + case RealmFileException::Kind::IncompatibleSyncedRealm: { + RLMRealmConfiguration *configuration = [originalConfiguration copy]; + configuration.fileURL = [NSURL fileURLWithPath:@(ex.path().data())]; + configuration.readOnly = YES; + + NSError *intermediateError = RLMMakeError(RLMErrorIncompatibleSyncedFile, ex); + NSMutableDictionary *userInfo = [intermediateError.userInfo mutableCopy]; + userInfo[RLMBackupRealmConfigurationErrorKey] = configuration; + NSError *finalError = [NSError errorWithDomain:intermediateError.domain code:intermediateError.code + userInfo:userInfo]; + RLMSetErrorOrThrow(finalError, error); + break; + } + default: + RLMRealmTranslateException(error); + break; + } + } + catch (...) { + RLMRealmTranslateException(error); + } +} + + ++ (instancetype)realmWithConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error { + bool dynamic = configuration.dynamic; + bool cache = configuration.cache; + bool readOnly = configuration.readOnly; + + { + Realm::Config& config = configuration.config; + + // try to reuse existing realm first + if (cache || dynamic) { + if (RLMRealm *realm = RLMGetThreadLocalCachedRealmForPath(config.path)) { + auto const& old_config = realm->_realm->config(); + if (old_config.immutable() != config.immutable() + || old_config.read_only_alternative() != config.read_only_alternative()) { + @throw RLMException(@"Realm at path '%s' already opened with different read permissions", config.path.c_str()); + } + if (old_config.in_memory != config.in_memory) { + @throw RLMException(@"Realm at path '%s' already opened with different inMemory settings", config.path.c_str()); + } + if (realm->_dynamic != dynamic) { + @throw RLMException(@"Realm at path '%s' already opened with different dynamic settings", config.path.c_str()); + } + if (old_config.encryption_key != config.encryption_key) { + @throw RLMException(@"Realm at path '%s' already opened with different encryption key", config.path.c_str()); + } + return RLMAutorelease(realm); + } + } + } + + configuration = [configuration copy]; + Realm::Config& config = configuration.config; + + RLMRealm *realm = [[self alloc] initPrivate]; + realm->_dynamic = dynamic; + + // protects the realm cache and accessors cache + static std::mutex& initLock = *new std::mutex(); + std::lock_guard lock(initLock); + + try { + realm->_realm = Realm::get_shared_realm(config); + } + catch (...) { + translateSharedGroupOpenException(configuration, error); + return nil; + } + + // if we have a cached realm on another thread we can skip a few steps and + // just grab its schema + @autoreleasepool { + // ensure that cachedRealm doesn't end up in this thread's autorelease pool + if (auto cachedRealm = RLMGetAnyCachedRealmForPath(config.path)) { + realm->_realm->set_schema_subset(cachedRealm->_realm->schema()); + realm->_schema = cachedRealm.schema; + realm->_info = cachedRealm->_info.clone(cachedRealm->_realm->schema(), realm); + } + } + + if (realm->_schema) { } + else if (dynamic) { + realm->_schema = [RLMSchema dynamicSchemaFromObjectStoreSchema:realm->_realm->schema()]; + realm->_info = RLMSchemaInfo(realm); + } + else { + // set/align schema or perform migration if needed + RLMSchema *schema = configuration.customSchema ?: RLMSchema.sharedSchema; + + Realm::MigrationFunction migrationFunction; + auto migrationBlock = configuration.migrationBlock; + if (migrationBlock && configuration.schemaVersion > 0) { + migrationFunction = [=](SharedRealm old_realm, SharedRealm realm, Schema& mutableSchema) { + RLMSchema *oldSchema = [RLMSchema dynamicSchemaFromObjectStoreSchema:old_realm->schema()]; + RLMRealm *oldRealm = [RLMRealm realmWithSharedRealm:old_realm schema:oldSchema]; + + // The destination RLMRealm can't just use the schema from the + // SharedRealm because it doesn't have information about whether or + // not a class was defined in Swift, which effects how new objects + // are created + RLMRealm *newRealm = [RLMRealm realmWithSharedRealm:realm schema:schema.copy]; + + [[[RLMMigration alloc] initWithRealm:newRealm oldRealm:oldRealm schema:mutableSchema] execute:migrationBlock]; + + oldRealm->_realm = nullptr; + newRealm->_realm = nullptr; + }; + } + + try { + realm->_realm->update_schema(schema.objectStoreCopy, config.schema_version, + std::move(migrationFunction)); + } + catch (...) { + RLMRealmTranslateException(error); + return nil; + } + + realm->_schema = schema; + realm->_info = RLMSchemaInfo(realm); + RLMRealmCreateAccessors(realm.schema); + + if (!readOnly) { + // initializing the schema started a read transaction, so end it + [realm invalidate]; + + RLMAddSkipBackupAttributeToItemAtPath(config.path + ".management"); + RLMAddSkipBackupAttributeToItemAtPath(config.path + ".lock"); + RLMAddSkipBackupAttributeToItemAtPath(config.path + ".note"); + } + } + + if (cache) { + RLMCacheRealm(config.path, realm); + } + + if (!readOnly) { + realm->_realm->m_binding_context = RLMCreateBindingContext(realm); + realm->_realm->m_binding_context->realm = realm->_realm; + } + + return RLMAutorelease(realm); +} + ++ (instancetype)uncachedSchemalessRealmWithConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error { + RLMRealm *realm = [[RLMRealm alloc] initPrivate]; + try { + realm->_realm = Realm::get_shared_realm(configuration.config); + } + catch (...) { + translateSharedGroupOpenException(configuration, error); + return nil; + } + return realm; +} + ++ (void)resetRealmState { + RLMClearRealmCache(); + realm::_impl::RealmCoordinator::clear_cache(); + [RLMRealmConfiguration resetRealmConfigurationState]; +} + +- (void)verifyNotificationsAreSupported:(bool)isCollection { + [self verifyThread]; + if (_realm->config().immutable()) { + @throw RLMException(@"Read-only Realms do not change and do not have change notifications"); + } + if (!_realm->can_deliver_notifications()) { + @throw RLMException(@"Can only add notification blocks from within runloops."); + } + if (isCollection && _realm->is_in_transaction()) { + @throw RLMException(@"Cannot register notification blocks from within write transactions."); + } +} + +- (RLMNotificationToken *)addNotificationBlock:(RLMNotificationBlock)block { + if (!block) { + @throw RLMException(@"The notification block should not be nil"); + } + [self verifyNotificationsAreSupported:false]; + + _realm->read_group(); + + if (!_notificationHandlers) { + _notificationHandlers = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory]; + } + + RLMRealmNotificationToken *token = [[RLMRealmNotificationToken alloc] init]; + token.realm = self; + token.block = block; + [_notificationHandlers addObject:token]; + return token; +} + +- (void)sendNotifications:(RLMNotification)notification { + NSAssert(!_realm->config().immutable(), @"Read-only realms do not have notifications"); + if (_sendingNotifications) { + return; + } + NSUInteger count = _notificationHandlers.count; + if (count == 0) { + return; + } + + _sendingNotifications = true; + auto cleanup = realm::util::make_scope_exit([&]() noexcept { + _sendingNotifications = false; + }); + + // call this realm's notification blocks + if (count == 1) { + if (auto block = [_notificationHandlers.anyObject block]) { + block(notification, self); + } + } + else { + for (RLMRealmNotificationToken *token in _notificationHandlers.allObjects) { + if (auto block = token.block) { + block(notification, self); + } + } + } +} + +- (RLMRealmConfiguration *)configuration { + RLMRealmConfiguration *configuration = [[RLMRealmConfiguration alloc] init]; + configuration.config = _realm->config(); + configuration.dynamic = _dynamic; + configuration.customSchema = _schema; + return configuration; +} + +- (void)beginWriteTransaction { + try { + _realm->begin_transaction(); + } + catch (std::exception &ex) { + @throw RLMException(ex); + } +} + +- (void)commitWriteTransaction { + [self commitWriteTransaction:nil]; +} + +- (BOOL)commitWriteTransaction:(NSError **)error { + return [self commitWriteTransactionWithoutNotifying:@[] error:error]; +} + +- (BOOL)commitWriteTransactionWithoutNotifying:(NSArray *)tokens error:(NSError **)error { + for (RLMNotificationToken *token in tokens) { + if (token.realm != self) { + @throw RLMException(@"Incorrect Realm: only notifications for the Realm being modified can be skipped."); + } + [token suppressNextNotification]; + } + + try { + _realm->commit_transaction(); + return YES; + } + catch (...) { + RLMRealmTranslateException(error); + return NO; + } +} + +- (void)transactionWithBlock:(__attribute__((noescape)) void(^)(void))block { + [self transactionWithBlock:block error:nil]; +} + +- (BOOL)transactionWithBlock:(__attribute__((noescape)) void(^)(void))block error:(NSError **)outError { + return [self transactionWithoutNotifying:@[] block:block error:outError]; +} + +- (void)transactionWithoutNotifying:(NSArray *)tokens block:(__attribute__((noescape)) void(^)(void))block { + [self transactionWithoutNotifying:tokens block:block error:nil]; +} + +- (BOOL)transactionWithoutNotifying:(NSArray *)tokens block:(__attribute__((noescape)) void(^)(void))block error:(NSError **)error { + [self beginWriteTransaction]; + block(); + if (_realm->is_in_transaction()) { + return [self commitWriteTransactionWithoutNotifying:tokens error:error]; + } + return YES; +} + +- (void)cancelWriteTransaction { + try { + _realm->cancel_transaction(); + } + catch (std::exception &ex) { + @throw RLMException(ex); + } +} + +- (void)invalidate { + if (_realm->is_in_transaction()) { + NSLog(@"WARNING: An RLMRealm instance was invalidated during a write " + "transaction and all pending changes have been rolled back."); + } + + [self detachAllEnumerators]; + + for (auto& objectInfo : _info) { + for (RLMObservationInfo *info : objectInfo.second.observedObjects) { + info->willChange(RLMInvalidatedKey); + } + } + + _realm->invalidate(); + + for (auto& objectInfo : _info) { + for (RLMObservationInfo *info : objectInfo.second.observedObjects) { + info->didChange(RLMInvalidatedKey); + } + objectInfo.second.releaseTable(); + } +} + +- (nullable id)resolveThreadSafeReference:(RLMThreadSafeReference *)reference { + return [reference resolveReferenceInRealm:self]; +} + +/** + Replaces all string columns in this Realm with a string enumeration column and compacts the + database file. + + Cannot be called from a write transaction. + + Compaction will not occur if other `RLMRealm` instances exist. + + While compaction is in progress, attempts by other threads or processes to open the database will + wait. + + Be warned that resource requirements for compaction is proportional to the amount of live data in + the database. + + Compaction works by writing the database contents to a temporary database file and then replacing + the database with the temporary one. The name of the temporary file is formed by appending + `.tmp_compaction_space` to the name of the database. + + @return YES if the compaction succeeded. + */ +- (BOOL)compact { + // compact() automatically ends the read transaction, but we need to clean + // up cached state and send invalidated notifications when that happens, so + // explicitly end it first unless we're in a write transaction (in which + // case compact() will throw an exception) + if (!_realm->is_in_transaction()) { + [self invalidate]; + } + + try { + return _realm->compact(); + } + catch (std::exception const& ex) { + @throw RLMException(ex); + } +} + +- (void)dealloc { + if (_realm) { + if (_realm->is_in_transaction()) { + [self cancelWriteTransaction]; + NSLog(@"WARNING: An RLMRealm instance was deallocated during a write transaction and all " + "pending changes have been rolled back. Make sure to retain a reference to the " + "RLMRealm for the duration of the write transaction."); + } + } +} + +- (BOOL)refresh { + return _realm->refresh(); +} + +- (void)addObject:(__unsafe_unretained RLMObject *const)object { + RLMAddObjectToRealm(object, self, RLMUpdatePolicyError); +} + +- (void)addObjects:(id)objects { + for (RLMObject *obj in objects) { + if (![obj isKindOfClass:RLMObjectBase.class]) { + @throw RLMException(@"Cannot insert objects of type %@ with addObjects:. Only RLMObjects are supported.", + NSStringFromClass(obj.class)); + } + [self addObject:obj]; + } +} + +- (void)addOrUpdateObject:(RLMObject *)object { + // verify primary key + if (!object.objectSchema.primaryKeyProperty) { + @throw RLMException(@"'%@' does not have a primary key and can not be updated", object.objectSchema.className); + } + + RLMAddObjectToRealm(object, self, RLMUpdatePolicyUpdateAll); +} + +- (void)addOrUpdateObjects:(id)objects { + for (RLMObject *obj in objects) { + if (![obj isKindOfClass:RLMObjectBase.class]) { + @throw RLMException(@"Cannot add or update objects of type %@ with addOrUpdateObjects:. Only RLMObjects are" + " supported.", + NSStringFromClass(obj.class)); + } + [self addOrUpdateObject:obj]; + } +} + +- (void)deleteObject:(RLMObject *)object { + RLMDeleteObjectFromRealm(object, self); +} + +- (void)deleteObjects:(id)objects { + id idObjects = objects; + if ([idObjects respondsToSelector:@selector(realm)] + && [idObjects respondsToSelector:@selector(deleteObjectsFromRealm)]) { + if (self != (RLMRealm *)[idObjects realm]) { + @throw RLMException(@"Can only delete objects from the Realm they belong to."); + } + [idObjects deleteObjectsFromRealm]; + return; + } + if (auto array = RLMDynamicCast(objects)) { + if (array.type != RLMPropertyTypeObject) { + @throw RLMException(@"Cannot delete objects from RLMArray<%@>: only RLMObjects can be deleted.", + RLMTypeToString(array.type)); + } + } + for (RLMObject *obj in objects) { + if (![obj isKindOfClass:RLMObjectBase.class]) { + @throw RLMException(@"Cannot delete objects of type %@ with deleteObjects:. Only RLMObjects can be deleted.", + NSStringFromClass(obj.class)); + } + RLMDeleteObjectFromRealm(obj, self); + } +} + +- (void)deleteAllObjects { + RLMDeleteAllObjectsFromRealm(self); +} + +- (RLMResults *)allObjects:(NSString *)objectClassName { + return RLMGetObjects(self, objectClassName, nil); +} + +- (RLMResults *)objects:(NSString *)objectClassName where:(NSString *)predicateFormat, ... { + va_list args; + va_start(args, predicateFormat); + RLMResults *results = [self objects:objectClassName where:predicateFormat args:args]; + va_end(args); + return results; +} + +- (RLMResults *)objects:(NSString *)objectClassName where:(NSString *)predicateFormat args:(va_list)args { + return [self objects:objectClassName withPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]]; +} + +- (RLMResults *)objects:(NSString *)objectClassName withPredicate:(NSPredicate *)predicate { + return RLMGetObjects(self, objectClassName, predicate); +} + +- (RLMObject *)objectWithClassName:(NSString *)className forPrimaryKey:(id)primaryKey { + return RLMGetObject(self, className, primaryKey); +} + ++ (uint64_t)schemaVersionAtURL:(NSURL *)fileURL encryptionKey:(NSData *)key error:(NSError **)error { + RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init]; + try { + config.fileURL = fileURL; + config.encryptionKey = RLMRealmValidatedEncryptionKey(key); + + uint64_t version = Realm::get_schema_version(config.config); + if (version == realm::ObjectStore::NotVersioned) { + RLMSetErrorOrThrow([NSError errorWithDomain:RLMErrorDomain code:RLMErrorFail userInfo:@{NSLocalizedDescriptionKey:@"Cannot open an uninitialized realm in read-only mode"}], error); + } + return version; + } + catch (...) { + translateSharedGroupOpenException(config, error); + return RLMNotVersioned; + } +} + ++ (BOOL)performMigrationForConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error { + if (RLMGetAnyCachedRealmForPath(configuration.config.path)) { + @throw RLMException(@"Cannot migrate Realms that are already open."); + } + + NSError *localError; // Prevents autorelease + BOOL success; + @autoreleasepool { + success = [RLMRealm realmWithConfiguration:configuration error:&localError] != nil; + } + if (!success && error) { + *error = localError; // Must set outside pool otherwise will free anyway + } + return success; +} + +- (RLMObject *)createObject:(NSString *)className withValue:(id)value { + return (RLMObject *)RLMCreateObjectInRealmWithValue(self, className, value, RLMUpdatePolicyError); +} + +- (BOOL)writeCopyToURL:(NSURL *)fileURL encryptionKey:(NSData *)key error:(NSError **)error { + key = RLMRealmValidatedEncryptionKey(key); + NSString *path = fileURL.path; + + try { + _realm->write_copy(path.UTF8String, {static_cast(key.bytes), key.length}); + return YES; + } + catch (...) { + __autoreleasing NSError *dummyError; + if (!error) { + error = &dummyError; + } + RLMRealmTranslateException(error); + return NO; + } + + return NO; +} + ++ (BOOL)fileExistsForConfiguration:(RLMRealmConfiguration *)config { + return [NSFileManager.defaultManager fileExistsAtPath:config.pathOnDisk]; +} + ++ (BOOL)deleteFilesForConfiguration:(RLMRealmConfiguration *)config error:(NSError **)error { + auto& path = config.config.path; + bool anyDeleted = false; + NSError *localError; + bool didCall = SharedGroup::call_with_lock(path, [&](auto const& path) { + NSURL *url = [NSURL fileURLWithPath:@(path.c_str())]; + NSFileManager *fm = NSFileManager.defaultManager; + + anyDeleted = [fm removeItemAtURL:url error:&localError]; + if (localError && localError.code != NSFileNoSuchFileError) { + return; + } + + [fm removeItemAtURL:[url URLByAppendingPathExtension:@"management"] error:&localError]; + if (localError && localError.code != NSFileNoSuchFileError) { + return; + } + + [fm removeItemAtURL:[url URLByAppendingPathExtension:@"note"] error:&localError]; + }); + if (error && localError && localError.code != NSFileNoSuchFileError) { + *error = localError; + } + else if (!didCall) { + if (error) { + NSString *msg = [NSString stringWithFormat:@"Realm file at path %s cannot be deleted because it is currently opened.", path.c_str()]; + *error = [NSError errorWithDomain:RLMErrorDomain + code:RLMErrorAlreadyOpen + userInfo:@{NSLocalizedDescriptionKey: msg}]; + } + } + return anyDeleted; +} + +#if REALM_ENABLE_SYNC +using Privilege = realm::ComputedPrivileges; +static bool hasPrivilege(realm::ComputedPrivileges actual, realm::ComputedPrivileges expected) { + return (static_cast(actual) & static_cast(expected)) == static_cast(expected); +} + +- (RLMRealmPrivileges)privilegesForRealm { + auto p = _realm->get_privileges(); + return { + .read = hasPrivilege(p, Privilege::Read), + .update = hasPrivilege(p, Privilege::Update), + .setPermissions = hasPrivilege(p, Privilege::SetPermissions), + .modifySchema = hasPrivilege(p, Privilege::ModifySchema), + }; +} + +- (RLMObjectPrivileges)privilegesForObject:(RLMObject *)object { + RLMVerifyAttached(object); + auto p = _realm->get_privileges(object->_row); + return { + .read = hasPrivilege(p, Privilege::Read), + .update = hasPrivilege(p, Privilege::Update), + .del = hasPrivilege(p, Privilege::Delete), + .setPermissions = hasPrivilege(p, Privilege::Delete), + }; +} + +- (RLMClassPrivileges)privilegesForClass:(Class)cls { + if (![cls respondsToSelector:@selector(_realmObjectName)]) { + @throw RLMException(@"Cannot get privileges for non-RLMObject class %@", cls); + } + return [self privilegesForClassNamed:[cls _realmObjectName] ?: [cls className]]; +} + +- (RLMClassPrivileges)privilegesForClassNamed:(NSString *)className { + auto p = _realm->get_privileges(className.UTF8String); + return { + .read = hasPrivilege(p, Privilege::Read), + .update = hasPrivilege(p, Privilege::Update), + .setPermissions = hasPrivilege(p, Privilege::SetPermissions), + .subscribe = hasPrivilege(p, Privilege::Query), + .create = hasPrivilege(p, Privilege::Create), + }; +} +#endif + +- (void)registerEnumerator:(RLMFastEnumerator *)enumerator { + if (!_collectionEnumerators) { + _collectionEnumerators = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory]; + } + [_collectionEnumerators addObject:enumerator]; +} + +- (void)unregisterEnumerator:(RLMFastEnumerator *)enumerator { + [_collectionEnumerators removeObject:enumerator]; +} + +- (void)detachAllEnumerators { + for (RLMFastEnumerator *enumerator in _collectionEnumerators) { + [enumerator detach]; + } + _collectionEnumerators = nil; +} + +@end diff --git a/!main project/Pods/Realm/Realm/RLMRealmConfiguration+Sync.mm b/!main project/Pods/Realm/Realm/RLMRealmConfiguration+Sync.mm new file mode 100644 index 0000000..36721d8 --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMRealmConfiguration+Sync.mm @@ -0,0 +1,73 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMRealmConfiguration+Sync.h" + +#import "RLMRealmConfiguration_Private.hpp" +#import "RLMSyncConfiguration_Private.hpp" +#import "RLMSyncUser_Private.hpp" +#import "RLMSyncManager_Private.h" +#import "RLMSyncUtil_Private.hpp" +#import "RLMUtil.hpp" + +#import "sync/sync_config.hpp" +#import "sync/sync_manager.hpp" + +@implementation RLMRealmConfiguration (Sync) + +#pragma mark - API + +- (void)setSyncConfiguration:(RLMSyncConfiguration *)syncConfiguration { + if (self.config.should_compact_on_launch_function) { + @throw RLMException(@"Cannot set `syncConfiguration` when `shouldCompactOnLaunch` is set."); + } + RLMSyncUser *user = syncConfiguration.user; + if (user.state == RLMSyncUserStateError) { + @throw RLMException(@"Cannot set a sync configuration which has an errored-out user."); + } + + // Ensure sync manager is initialized, if it hasn't already been. + [RLMSyncManager sharedManager]; + NSAssert(user.identity, @"Cannot call this method on a user that doesn't have an identity."); + self.config.in_memory = false; + self.config.sync_config = std::make_shared([syncConfiguration rawConfiguration]); + self.config.schema_mode = realm::SchemaMode::Additive; + + if (syncConfiguration.customFileURL) { + self.config.path = syncConfiguration.customFileURL.path.UTF8String; + } else { + self.config.path = SyncManager::shared().path_for_realm(*[user _syncUser], + self.config.sync_config->realm_url()); + } + + if (!self.config.encryption_key.empty()) { + auto& sync_encryption_key = self.config.sync_config->realm_encryption_key; + sync_encryption_key = std::array(); + std::copy_n(self.config.encryption_key.begin(), 64, sync_encryption_key->begin()); + } +} + +- (RLMSyncConfiguration *)syncConfiguration { + if (!self.config.sync_config) { + return nil; + } + realm::SyncConfig& sync_config = *self.config.sync_config; + return [[RLMSyncConfiguration alloc] initWithRawConfig:sync_config]; +} + +@end diff --git a/!main project/Pods/Realm/Realm/RLMRealmConfiguration.mm b/!main project/Pods/Realm/Realm/RLMRealmConfiguration.mm new file mode 100644 index 0000000..114b8d6 --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMRealmConfiguration.mm @@ -0,0 +1,318 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMRealmConfiguration_Private.h" + +#import "RLMObjectSchema_Private.hpp" +#import "RLMRealm_Private.h" +#import "RLMSchema_Private.hpp" +#import "RLMUtil.hpp" + +#import "schema.hpp" +#import "shared_realm.hpp" + +#if REALM_ENABLE_SYNC +#import "sync/sync_config.hpp" +#else +@class RLMSyncConfiguration; +#endif + +static NSString *const c_RLMRealmConfigurationProperties[] = { + @"fileURL", + @"inMemoryIdentifier", + @"encryptionKey", + @"readOnly", + @"schemaVersion", + @"migrationBlock", + @"deleteRealmIfMigrationNeeded", + @"shouldCompactOnLaunch", + @"dynamic", + @"customSchema", +}; + +static NSString *const c_defaultRealmFileName = @"default.realm"; +RLMRealmConfiguration *s_defaultConfiguration; + +NSString *RLMRealmPathForFileAndBundleIdentifier(NSString *fileName, NSString *bundleIdentifier) { + return [RLMDefaultDirectoryForBundleIdentifier(bundleIdentifier) + stringByAppendingPathComponent:fileName]; +} + +NSString *RLMRealmPathForFile(NSString *fileName) { + static NSString *directory = RLMDefaultDirectoryForBundleIdentifier(nil); + return [directory stringByAppendingPathComponent:fileName]; +} + +@implementation RLMRealmConfiguration { + realm::Realm::Config _config; +} + +- (realm::Realm::Config&)config { + return _config; +} + ++ (instancetype)defaultConfiguration { + return [[self rawDefaultConfiguration] copy]; +} + ++ (void)setDefaultConfiguration:(RLMRealmConfiguration *)configuration { + if (!configuration) { + @throw RLMException(@"Cannot set the default configuration to nil."); + } + @synchronized(c_defaultRealmFileName) { + s_defaultConfiguration = [configuration copy]; + } +} + ++ (RLMRealmConfiguration *)rawDefaultConfiguration { + RLMRealmConfiguration *configuration; + @synchronized(c_defaultRealmFileName) { + if (!s_defaultConfiguration) { + s_defaultConfiguration = [[RLMRealmConfiguration alloc] init]; + } + configuration = s_defaultConfiguration; + } + return configuration; +} + ++ (void)resetRealmConfigurationState { + @synchronized(c_defaultRealmFileName) { + s_defaultConfiguration = nil; + } +} + +- (instancetype)init { + self = [super init]; + if (self) { + static NSURL *defaultRealmURL = [NSURL fileURLWithPath:RLMRealmPathForFile(c_defaultRealmFileName)]; + self.fileURL = defaultRealmURL; + self.schemaVersion = 0; + self.cache = YES; + + // We have our own caching of RLMRealm instances, so the ObjectStore + // cache is at best pointless, and may result in broken behavior when + // a realm::Realm instance outlives the RLMRealm (due to collection + // notifiers being in the middle of running when the RLMRealm is + // dealloced) and then reused for a new RLMRealm + _config.cache = false; + } + + return self; +} + +- (instancetype)copyWithZone:(NSZone *)zone { + RLMRealmConfiguration *configuration = [[[self class] allocWithZone:zone] init]; + configuration->_config = _config; + configuration->_cache = _cache; + configuration->_dynamic = _dynamic; + configuration->_migrationBlock = _migrationBlock; + configuration->_shouldCompactOnLaunch = _shouldCompactOnLaunch; + configuration->_customSchema = _customSchema; + return configuration; +} + +- (NSString *)description { + NSMutableString *string = [NSMutableString stringWithFormat:@"%@ {\n", self.class]; + for (NSString *key : c_RLMRealmConfigurationProperties) { + NSString *description = [[self valueForKey:key] description]; + description = [description stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]; + + [string appendFormat:@"\t%@ = %@;\n", key, description]; + } + return [string stringByAppendingString:@"}"]; +} + +static void RLMNSStringToStdString(std::string &out, NSString *in) { + out.resize([in maximumLengthOfBytesUsingEncoding:NSUTF8StringEncoding]); + if (out.empty()) { + return; + } + + NSUInteger size = out.size(); + [in getBytes:&out[0] + maxLength:size + usedLength:&size + encoding:NSUTF8StringEncoding + options:0 range:{0, in.length} remainingRange:nullptr]; + out.resize(size); +} + +- (NSURL *)fileURL { + if (_config.in_memory || _config.sync_config) { + return nil; + } + return [NSURL fileURLWithPath:@(_config.path.c_str())]; +} + +- (void)setFileURL:(NSURL *)fileURL { + NSString *path = fileURL.path; + if (path.length == 0) { + @throw RLMException(@"Realm path must not be empty"); + } + _config.sync_config = nullptr; + + RLMNSStringToStdString(_config.path, path); + _config.in_memory = false; +} + +- (NSString *)inMemoryIdentifier { + if (!_config.in_memory) { + return nil; + } + return [@(_config.path.c_str()) lastPathComponent]; +} + +- (void)setInMemoryIdentifier:(NSString *)inMemoryIdentifier { + if (inMemoryIdentifier.length == 0) { + @throw RLMException(@"In-memory identifier must not be empty"); + } + _config.sync_config = nullptr; + + RLMNSStringToStdString(_config.path, [NSTemporaryDirectory() stringByAppendingPathComponent:inMemoryIdentifier]); + _config.in_memory = true; +} + +- (NSData *)encryptionKey { + return _config.encryption_key.empty() ? nil : [NSData dataWithBytes:_config.encryption_key.data() length:_config.encryption_key.size()]; +} + +- (void)setEncryptionKey:(NSData * __nullable)encryptionKey { + if (NSData *key = RLMRealmValidatedEncryptionKey(encryptionKey)) { + auto bytes = static_cast(key.bytes); + _config.encryption_key.assign(bytes, bytes + key.length); +#if REALM_ENABLE_SYNC + if (_config.sync_config) { + auto& sync_encryption_key = self.config.sync_config->realm_encryption_key; + sync_encryption_key = std::array(); + std::copy_n(_config.encryption_key.begin(), 64, sync_encryption_key->begin()); + } +#endif + } + else { + _config.encryption_key.clear(); +#if REALM_ENABLE_SYNC + if (_config.sync_config) + _config.sync_config->realm_encryption_key = realm::util::none; +#endif + } +} + +- (BOOL)readOnly { + return _config.immutable(); +} + +- (void)setReadOnly:(BOOL)readOnly { + if (readOnly) { + if (self.deleteRealmIfMigrationNeeded) { + @throw RLMException(@"Cannot set `readOnly` when `deleteRealmIfMigrationNeeded` is set."); + } else if (self.shouldCompactOnLaunch) { + @throw RLMException(@"Cannot set `readOnly` when `shouldCompactOnLaunch` is set."); + } + _config.schema_mode = realm::SchemaMode::Immutable; + } + else if (self.readOnly) { + _config.schema_mode = realm::SchemaMode::Automatic; + } +} + +- (uint64_t)schemaVersion { + return _config.schema_version; +} + +- (void)setSchemaVersion:(uint64_t)schemaVersion { + if (schemaVersion == RLMNotVersioned) { + @throw RLMException(@"Cannot set schema version to %llu (RLMNotVersioned)", RLMNotVersioned); + } + _config.schema_version = schemaVersion; +} + +- (BOOL)deleteRealmIfMigrationNeeded { + return _config.schema_mode == realm::SchemaMode::ResetFile; +} + +- (void)setDeleteRealmIfMigrationNeeded:(BOOL)deleteRealmIfMigrationNeeded { + if (deleteRealmIfMigrationNeeded) { + if (self.readOnly) { + @throw RLMException(@"Cannot set `deleteRealmIfMigrationNeeded` when `readOnly` is set."); + } + _config.schema_mode = realm::SchemaMode::ResetFile; + } + else if (self.deleteRealmIfMigrationNeeded) { + _config.schema_mode = realm::SchemaMode::Automatic; + } +} + +- (NSArray *)objectClasses { + return [_customSchema.objectSchema valueForKeyPath:@"objectClass"]; +} + +- (void)setObjectClasses:(NSArray *)objectClasses { + self.customSchema = [RLMSchema schemaWithObjectClasses:objectClasses]; +} + +- (void)setDynamic:(bool)dynamic { + _dynamic = dynamic; + self.cache = !dynamic; +} + +- (bool)disableFormatUpgrade { + return _config.disable_format_upgrade; +} + +- (void)setDisableFormatUpgrade:(bool)disableFormatUpgrade { + _config.disable_format_upgrade = disableFormatUpgrade; +} + +- (realm::SchemaMode)schemaMode { + return _config.schema_mode; +} + +- (void)setSchemaMode:(realm::SchemaMode)mode { + _config.schema_mode = mode; +} + +- (NSString *)pathOnDisk { + return @(_config.path.c_str()); +} + +- (void)setShouldCompactOnLaunch:(RLMShouldCompactOnLaunchBlock)shouldCompactOnLaunch { + if (shouldCompactOnLaunch) { + if (self.readOnly) { + @throw RLMException(@"Cannot set `shouldCompactOnLaunch` when `readOnly` is set."); + } + _config.should_compact_on_launch_function = [=](size_t totalBytes, size_t usedBytes) { + return shouldCompactOnLaunch(totalBytes, usedBytes); + }; + } + else { + _config.should_compact_on_launch_function = nullptr; + } + _shouldCompactOnLaunch = shouldCompactOnLaunch; +} + +- (void)setCustomSchemaWithoutCopying:(RLMSchema *)schema { + _customSchema = schema; +} + +#if !REALM_ENABLE_SYNC +- (RLMSyncConfiguration *)syncConfiguration { + return nil; +} +#endif + +@end diff --git a/!main project/Pods/Realm/Realm/RLMRealmUtil.mm b/!main project/Pods/Realm/Realm/RLMRealmUtil.mm new file mode 100644 index 0000000..10d6ff3 --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMRealmUtil.mm @@ -0,0 +1,147 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMRealmUtil.hpp" + +#import "RLMObjectSchema_Private.hpp" +#import "RLMObservation.hpp" +#import "RLMRealm_Private.hpp" +#import "RLMUtil.hpp" + +#import +#import + +#import "binding_context.hpp" + +#import +#import +#import +#import +#import +#import + +// Global realm state +static std::mutex& s_realmCacheMutex = *new std::mutex(); +static std::map& s_realmsPerPath = *new std::map(); + +void RLMCacheRealm(std::string const& path, __unsafe_unretained RLMRealm *const realm) { + std::lock_guard lock(s_realmCacheMutex); + NSMapTable *realms = s_realmsPerPath[path]; + if (!realms) { + s_realmsPerPath[path] = realms = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsOpaquePersonality|NSPointerFunctionsOpaqueMemory + valueOptions:NSPointerFunctionsWeakMemory]; + } + [realms setObject:realm forKey:(__bridge id)pthread_self()]; +} + +RLMRealm *RLMGetAnyCachedRealmForPath(std::string const& path) { + std::lock_guard lock(s_realmCacheMutex); + return [s_realmsPerPath[path] objectEnumerator].nextObject; +} + +RLMRealm *RLMGetThreadLocalCachedRealmForPath(std::string const& path) { + std::lock_guard lock(s_realmCacheMutex); + return [s_realmsPerPath[path] objectForKey:(__bridge id)pthread_self()]; +} + +void RLMClearRealmCache() { + std::lock_guard lock(s_realmCacheMutex); + s_realmsPerPath.clear(); +} + +bool RLMIsInRunLoop() { + // The main thread may not be in a run loop yet if we're called from + // something like `applicationDidFinishLaunching:`, but it presumably will + // be in the future + if ([NSThread isMainThread]) { + return true; + } + // Current mode indicates why the current callout from the runloop was made, + // and is null if a runloop callout isn't currently being processed + if (auto mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent())) { + CFRelease(mode); + return true; + } + return false; +} + +namespace { +class RLMNotificationHelper : public realm::BindingContext { +public: + RLMNotificationHelper(RLMRealm *realm) : _realm(realm) { } + + bool can_deliver_notifications() const noexcept override { + return RLMIsInRunLoop(); + } + + void changes_available() override { + @autoreleasepool { + auto realm = _realm; + if (realm && !realm.autorefresh) { + [realm sendNotifications:RLMRealmRefreshRequiredNotification]; + } + } + } + + std::vector get_observed_rows() override { + @autoreleasepool { + if (auto realm = _realm) { + [realm detachAllEnumerators]; + return RLMGetObservedRows(realm->_info); + } + return {}; + } + } + + void will_change(std::vector const& observed, std::vector const& invalidated) override { + @autoreleasepool { + RLMWillChange(observed, invalidated); + } + } + + void did_change(std::vector const& observed, std::vector const& invalidated, bool version_changed) override { + try { + @autoreleasepool { + RLMDidChange(observed, invalidated); + if (version_changed) { + [_realm sendNotifications:RLMRealmDidChangeNotification]; + } + } + } + catch (...) { + // This can only be called during a write transaction if it was + // called due to the transaction beginning, so cancel it to ensure + // exceptions thrown here behave the same as exceptions thrown when + // actually beginning the write + if (_realm.inWriteTransaction) { + [_realm cancelWriteTransaction]; + } + throw; + } + } + +private: + // This is owned by the realm, so it needs to not retain the realm + __weak RLMRealm *const _realm; +}; +} // anonymous namespace + + +std::unique_ptr RLMCreateBindingContext(__unsafe_unretained RLMRealm *const realm) { + return std::unique_ptr(new RLMNotificationHelper(realm)); +} diff --git a/!main project/Pods/Realm/Realm/RLMResults.mm b/!main project/Pods/Realm/Realm/RLMResults.mm new file mode 100644 index 0000000..77b1471 --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMResults.mm @@ -0,0 +1,528 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMResults_Private.hpp" + +#import "RLMAccessor.hpp" +#import "RLMArray_Private.hpp" +#import "RLMCollection_Private.hpp" +#import "RLMObjectSchema_Private.hpp" +#import "RLMObjectStore.h" +#import "RLMObject_Private.hpp" +#import "RLMObservation.hpp" +#import "RLMProperty_Private.h" +#import "RLMQueryUtil.hpp" +#import "RLMRealm_Private.hpp" +#import "RLMSchema_Private.h" +#import "RLMThreadSafeReference_Private.hpp" +#import "RLMUtil.hpp" + +#import "results.hpp" +#import "shared_realm.hpp" + +#import +#import + +using namespace realm; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wincomplete-implementation" +@implementation RLMNotificationToken +@end +#pragma clang diagnostic pop + +@interface RLMResults () +@end + +// +// RLMResults implementation +// +@implementation RLMResults { + RLMRealm *_realm; + RLMClassInfo *_info; +} + +- (instancetype)initPrivate { + self = [super init]; + return self; +} + +- (instancetype)initWithResults:(Results)results { + if (self = [super init]) { + _results = std::move(results); + } + return self; +} + +static void assertKeyPathIsNotNested(NSString *keyPath) { + if ([keyPath rangeOfString:@"."].location != NSNotFound) { + @throw RLMException(@"Nested key paths are not supported yet for KVC collection operators."); + } +} + +void RLMThrowResultsError(NSString *aggregateMethod) { + try { + throw; + } + catch (realm::InvalidTransactionException const&) { + @throw RLMException(@"Cannot modify Results outside of a write transaction."); + } + catch (realm::IncorrectThreadException const&) { + @throw RLMException(@"Realm accessed from incorrect thread."); + } + catch (realm::Results::InvalidatedException const&) { + @throw RLMException(@"RLMResults has been invalidated."); + } + catch (realm::Results::DetatchedAccessorException const&) { + @throw RLMException(@"Object has been invalidated."); + } + catch (realm::Results::IncorrectTableException const& e) { + @throw RLMException(@"Object of type '%s' does not match RLMResults type '%s'.", + e.actual.data(), e.expected.data()); + } + catch (realm::Results::OutOfBoundsIndexException const& e) { + @throw RLMException(@"Index %zu is out of bounds (must be less than %zu).", + e.requested, e.valid_count); + } + catch (realm::Results::UnsupportedColumnTypeException const& e) { + @throw RLMException(@"%@ is not supported for %s%s property '%s'.", + aggregateMethod, + string_for_property_type(e.property_type), + is_nullable(e.property_type) ? "?" : "", + e.column_name.data()); + } + catch (std::exception const& e) { + @throw RLMException(e); + } +} + +- (instancetype)initWithObjectInfo:(RLMClassInfo&)info + results:(realm::Results&&)results { + if (self = [super init]) { + _results = std::move(results); + _realm = info.realm; + _info = &info; + } + return self; +} + ++ (instancetype)resultsWithObjectInfo:(RLMClassInfo&)info + results:(realm::Results&&)results { + return [[self alloc] initWithObjectInfo:info results:std::move(results)]; +} + ++ (instancetype)emptyDetachedResults { + return [[self alloc] initPrivate]; +} + +- (instancetype)subresultsWithResults:(realm::Results)results { + return [self.class resultsWithObjectInfo:*_info results:std::move(results)]; +} + +static inline void RLMResultsValidateInWriteTransaction(__unsafe_unretained RLMResults *const ar) { + ar->_realm->_realm->verify_thread(); + ar->_realm->_realm->verify_in_write(); +} + +- (BOOL)isInvalidated { + return translateRLMResultsErrors([&] { return !_results.is_valid(); }); +} + +- (NSUInteger)count { + return translateRLMResultsErrors([&] { return _results.size(); }); +} + +- (RLMPropertyType)type { + return translateRLMResultsErrors([&] { + return static_cast(_results.get_type() & ~realm::PropertyType::Nullable); + }); +} + +- (BOOL)isOptional { + return translateRLMResultsErrors([&] { + return is_nullable(_results.get_type()); + }); +} + +- (NSString *)objectClassName { + return translateRLMResultsErrors([&] { + if (_info && _results.get_type() == realm::PropertyType::Object) { + return _info->rlmObjectSchema.className; + } + return (NSString *)nil; + }); +} + +- (RLMClassInfo *)objectInfo { + return _info; +} + +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state + objects:(__unused __unsafe_unretained id [])buffer + count:(NSUInteger)len { + if (!_info) { + return 0; + } + return RLMFastEnumerate(state, len, self); +} + +- (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat, ... { + va_list args; + va_start(args, predicateFormat); + NSUInteger index = [self indexOfObjectWhere:predicateFormat args:args]; + va_end(args); + return index; +} + +- (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat args:(va_list)args { + return [self indexOfObjectWithPredicate:[NSPredicate predicateWithFormat:predicateFormat + arguments:args]]; +} + +- (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate { + if (_results.get_mode() == Results::Mode::Empty) { + return NSNotFound; + } + + return translateRLMResultsErrors([&] { + if (_results.get_type() != realm::PropertyType::Object) { + @throw RLMException(@"Querying is currently only implemented for arrays of Realm Objects"); + } + return RLMConvertNotFound(_results.index_of(RLMPredicateToQuery(predicate, _info->rlmObjectSchema, _realm.schema, _realm.group))); + }); +} + +- (id)objectAtIndex:(NSUInteger)index { + RLMAccessorContext ctx(*_info); + return translateRLMResultsErrors([&] { + return _results.get(ctx, index); + }); +} + +- (id)firstObject { + if (!_info) { + return nil; + } + RLMAccessorContext ctx(*_info); + return translateRLMResultsErrors([&] { + return _results.first(ctx); + }); +} + +- (id)lastObject { + if (!_info) { + return nil; + } + RLMAccessorContext ctx(*_info); + return translateRLMResultsErrors([&] { + return _results.last(ctx); + }); +} + +- (NSUInteger)indexOfObject:(RLMObject *)object { + if (!_info || !object || (!object->_realm && !object.invalidated)) { + return NSNotFound; + } + RLMAccessorContext ctx(*_info); + return translateRLMResultsErrors([&] { + return RLMConvertNotFound(_results.index_of(ctx, object)); + }); +} + +- (id)valueForKeyPath:(NSString *)keyPath { + if ([keyPath characterAtIndex:0] != '@') { + return [super valueForKeyPath:keyPath]; + } + if ([keyPath isEqualToString:@"@count"]) { + return @(self.count); + } + + NSRange operatorRange = [keyPath rangeOfString:@"." options:NSLiteralSearch]; + NSUInteger keyPathLength = keyPath.length; + NSUInteger separatorIndex = operatorRange.location != NSNotFound ? operatorRange.location : keyPathLength; + NSString *operatorName = [keyPath substringWithRange:NSMakeRange(1, separatorIndex - 1)]; + SEL opSelector = NSSelectorFromString([NSString stringWithFormat:@"_%@ForKeyPath:", operatorName]); + if (![self respondsToSelector:opSelector]) { + @throw RLMException(@"Unsupported KVC collection operator found in key path '%@'", keyPath); + } + if (separatorIndex >= keyPathLength - 1) { + @throw RLMException(@"Missing key path for KVC collection operator %@ in key path '%@'", + operatorName, keyPath); + } + NSString *operatorKeyPath = [keyPath substringFromIndex:separatorIndex + 1]; + return ((id(*)(id, SEL, id))objc_msgSend)(self, opSelector, operatorKeyPath); +} + +- (id)valueForKey:(NSString *)key { + if (!_info) { + return @[]; + } + return translateRLMResultsErrors([&] { + return RLMCollectionValueForKey(_results, key, *_info); + }); +} + +- (void)setValue:(id)value forKey:(NSString *)key { + translateRLMResultsErrors([&] { RLMResultsValidateInWriteTransaction(self); }); + RLMCollectionSetValueForKey(self, key, value); +} + +- (NSNumber *)_aggregateForKeyPath:(NSString *)keyPath + method:(util::Optional (Results::*)(size_t))method + methodName:(NSString *)methodName returnNilForEmpty:(BOOL)returnNilForEmpty { + assertKeyPathIsNotNested(keyPath); + return [self aggregate:keyPath method:method methodName:methodName returnNilForEmpty:returnNilForEmpty]; +} + +- (NSNumber *)_minForKeyPath:(NSString *)keyPath { + return [self _aggregateForKeyPath:keyPath method:&Results::min methodName:@"@min" returnNilForEmpty:YES]; +} + +- (NSNumber *)_maxForKeyPath:(NSString *)keyPath { + return [self _aggregateForKeyPath:keyPath method:&Results::max methodName:@"@max" returnNilForEmpty:YES]; +} + +- (NSNumber *)_sumForKeyPath:(NSString *)keyPath { + return [self _aggregateForKeyPath:keyPath method:&Results::sum methodName:@"@sum" returnNilForEmpty:NO]; +} + +- (NSNumber *)_avgForKeyPath:(NSString *)keyPath { + assertKeyPathIsNotNested(keyPath); + return [self averageOfProperty:keyPath]; +} + +- (NSArray *)_unionOfObjectsForKeyPath:(NSString *)keyPath { + assertKeyPathIsNotNested(keyPath); + return translateRLMResultsErrors([&] { + return RLMCollectionValueForKey(_results, keyPath, *_info); + }); +} + +- (NSArray *)_distinctUnionOfObjectsForKeyPath:(NSString *)keyPath { + return [NSSet setWithArray:[self _unionOfObjectsForKeyPath:keyPath]].allObjects; +} + +- (NSArray *)_unionOfArraysForKeyPath:(NSString *)keyPath { + assertKeyPathIsNotNested(keyPath); + if ([keyPath isEqualToString:@"self"]) { + @throw RLMException(@"self is not a valid key-path for a KVC array collection operator as 'unionOfArrays'."); + } + + return translateRLMResultsErrors([&] { + NSMutableArray *flatArray = [NSMutableArray new]; + for (id array in RLMCollectionValueForKey(_results, keyPath, *_info)) { + for (id value in array) { + [flatArray addObject:value]; + } + } + return flatArray; + }); +} + +- (NSArray *)_distinctUnionOfArraysForKeyPath:(__unused NSString *)keyPath { + return [NSSet setWithArray:[self _unionOfArraysForKeyPath:keyPath]].allObjects; +} + +- (RLMResults *)objectsWhere:(NSString *)predicateFormat, ... { + va_list args; + va_start(args, predicateFormat); + RLMResults *results = [self objectsWhere:predicateFormat args:args]; + va_end(args); + return results; +} + +- (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args { + return [self objectsWithPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]]; +} + +- (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate { + return translateRLMResultsErrors([&] { + if (_results.get_mode() == Results::Mode::Empty) { + return self; + } + if (_results.get_type() != realm::PropertyType::Object) { + @throw RLMException(@"Querying is currently only implemented for arrays of Realm Objects"); + } + auto query = RLMPredicateToQuery(predicate, _info->rlmObjectSchema, _realm.schema, _realm.group); + return [self subresultsWithResults:_results.filter(std::move(query))]; + }); +} + +- (RLMResults *)sortedResultsUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending { + return [self sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:keyPath ascending:ascending]]]; +} + +- (RLMResults *)sortedResultsUsingDescriptors:(NSArray *)properties { + if (properties.count == 0) { + return self; + } + return translateRLMResultsErrors([&] { + if (_results.get_mode() == Results::Mode::Empty) { + return self; + } + return [self subresultsWithResults:_results.sort(RLMSortDescriptorsToKeypathArray(properties))]; + }); +} + +- (RLMResults *)distinctResultsUsingKeyPaths:(NSArray *)keyPaths { + for (NSString *keyPath in keyPaths) { + if ([keyPath rangeOfString:@"@"].location != NSNotFound) { + @throw RLMException(@"Cannot distinct on keypath '%@': KVC collection operators are not supported.", keyPath); + } + } + + return translateRLMResultsErrors([&] { + if (_results.get_mode() == Results::Mode::Empty) { + return self; + } + + std::vector keyPathsVector; + for (NSString *keyPath in keyPaths) { + keyPathsVector.push_back(keyPath.UTF8String); + } + + return [self subresultsWithResults:_results.distinct(keyPathsVector)]; + }); +} + +- (id)objectAtIndexedSubscript:(NSUInteger)index { + return [self objectAtIndex:index]; +} + +- (id)aggregate:(NSString *)property method:(util::Optional (Results::*)(size_t))method + methodName:(NSString *)methodName returnNilForEmpty:(BOOL)returnNilForEmpty { + if (_results.get_mode() == Results::Mode::Empty) { + return returnNilForEmpty ? nil : @0; + } + size_t column = 0; + if (self.type == RLMPropertyTypeObject || ![property isEqualToString:@"self"]) { + column = _info->tableColumn(property); + } + + auto value = translateRLMResultsErrors([&] { return (_results.*method)(column); }, methodName); + return value ? RLMMixedToObjc(*value) : nil; +} + +- (id)minOfProperty:(NSString *)property { + return [self aggregate:property method:&Results::min + methodName:@"minOfProperty" returnNilForEmpty:YES]; +} + +- (id)maxOfProperty:(NSString *)property { + return [self aggregate:property method:&Results::max + methodName:@"maxOfProperty" returnNilForEmpty:YES]; +} + +- (id)sumOfProperty:(NSString *)property { + return [self aggregate:property method:&Results::sum + methodName:@"sumOfProperty" returnNilForEmpty:NO]; +} + +- (id)averageOfProperty:(NSString *)property { + if (_results.get_mode() == Results::Mode::Empty) { + return nil; + } + size_t column = 0; + if (self.type == RLMPropertyTypeObject || ![property isEqualToString:@"self"]) { + column = _info->tableColumn(property); + } + auto value = translateRLMResultsErrors([&] { return _results.average(column); }, @"averageOfProperty"); + return value ? @(*value) : nil; +} + +- (void)deleteObjectsFromRealm { + if (self.type != RLMPropertyTypeObject) { + @throw RLMException(@"Cannot delete objects from RLMResults<%@>: only RLMObjects can be deleted.", + RLMTypeToString(self.type)); + } + return translateRLMResultsErrors([&] { + if (_results.get_mode() == Results::Mode::Table) { + RLMResultsValidateInWriteTransaction(self); + RLMClearTable(*_info); + } + else { + RLMTrackDeletions(_realm, [&] { _results.clear(); }); + } + }); +} + +- (NSString *)description { + return RLMDescriptionWithMaxDepth(@"RLMResults", self, RLMDescriptionMaxDepth); +} + +- (realm::TableView)tableView { + return translateRLMResultsErrors([&] { return _results.get_tableview(); }); +} + +- (RLMFastEnumerator *)fastEnumerator { + return translateRLMResultsErrors([&] { + return [[RLMFastEnumerator alloc] initWithResults:_results collection:self + classInfo:*_info]; + }); +} + +// The compiler complains about the method's argument type not matching due to +// it not having the generic type attached, but it doesn't seem to be possible +// to actually include the generic type +// http://www.openradar.me/radar?id=6135653276319744 +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmismatched-parameter-types" +- (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMResults *, RLMCollectionChange *, NSError *))block { + if (!_realm) { + @throw RLMException(@"Linking objects notifications are only supported on managed objects."); + } + [_realm verifyNotificationsAreSupported:true]; + return RLMAddNotificationBlock(self, _results, block, true); +} +#pragma clang diagnostic pop + +- (BOOL)isAttached +{ + return !!_realm; +} + +#pragma mark - Thread Confined Protocol Conformance + +- (std::unique_ptr)makeThreadSafeReference { + return std::make_unique>(_realm->_realm->obtain_thread_safe_reference(_results)); +} + +- (id)objectiveCMetadata { + return nil; +} + ++ (instancetype)objectWithThreadSafeReference:(std::unique_ptr)reference + metadata:(__unused id)metadata + realm:(RLMRealm *)realm { + REALM_ASSERT_DEBUG(dynamic_cast *>(reference.get())); + auto results_reference = static_cast *>(reference.get()); + + Results results = realm->_realm->resolve_thread_safe_reference(std::move(*results_reference)); + + return [RLMResults resultsWithObjectInfo:realm->_info[RLMStringDataToNSString(results.get_object_type())] + results:std::move(results)]; +} + +@end + +@implementation RLMLinkingObjects +- (NSString *)description { + return RLMDescriptionWithMaxDepth(@"RLMLinkingObjects", self, RLMDescriptionMaxDepth); +} +@end + diff --git a/!main project/Pods/Realm/Realm/RLMSchema.mm b/!main project/Pods/Realm/Realm/RLMSchema.mm new file mode 100644 index 0000000..0787125 --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMSchema.mm @@ -0,0 +1,378 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMSchema_Private.h" + +#import "RLMAccessor.h" +#import "RLMObjectBase_Private.h" +#import "RLMObject_Private.hpp" +#import "RLMObjectSchema_Private.hpp" +#import "RLMProperty_Private.h" +#import "RLMRealm_Private.hpp" +#import "RLMSwiftSupport.h" +#import "RLMUtil.hpp" + +#import "object_schema.hpp" +#import "object_store.hpp" +#import "schema.hpp" + +#import + +#import +#include + +using namespace realm; + +const uint64_t RLMNotVersioned = realm::ObjectStore::NotVersioned; + +// RLMSchema private properties +@interface RLMSchema () +@property (nonatomic, readwrite) NSMutableDictionary *objectSchemaByName; +@end + +// Private RLMSchema subclass that skips class registration on lookup +@interface RLMPrivateSchema : RLMSchema +@end +@implementation RLMPrivateSchema +- (RLMObjectSchema *)schemaForClassName:(NSString *)className { + return self.objectSchemaByName[className]; +} + +- (RLMObjectSchema *)objectForKeyedSubscript:(__unsafe_unretained NSString *const)className { + return [self schemaForClassName:className]; +} +@end + +static RLMSchema *s_sharedSchema = [[RLMSchema alloc] init]; +static NSMutableDictionary *s_localNameToClass = [[NSMutableDictionary alloc] init]; +static RLMSchema *s_privateSharedSchema = [[RLMPrivateSchema alloc] init]; + +static enum class SharedSchemaState { + Uninitialized, + Initializing, + Initialized +} s_sharedSchemaState = SharedSchemaState::Uninitialized; + +@implementation RLMSchema { + NSArray *_objectSchema; + realm::Schema _objectStoreSchema; +} + +// Caller must @synchronize on s_localNameToClass +static RLMObjectSchema *RLMRegisterClass(Class cls) { + if (RLMObjectSchema *schema = s_privateSharedSchema[[cls className]]) { + return schema; + } + + auto prevState = s_sharedSchemaState; + s_sharedSchemaState = SharedSchemaState::Initializing; + RLMObjectSchema *schema = [RLMObjectSchema schemaForObjectClass:cls]; + s_sharedSchemaState = prevState; + + // set unmanaged class on shared shema for unmanaged object creation + schema.unmanagedClass = RLMUnmanagedAccessorClassForObjectClass(schema.objectClass, schema); + + // override sharedSchema class methods for performance + RLMReplaceSharedSchemaMethod(cls, schema); + + s_privateSharedSchema.objectSchemaByName[schema.className] = schema; + if ([cls shouldIncludeInDefaultSchema] && prevState != SharedSchemaState::Initialized) { + s_sharedSchema.objectSchemaByName[schema.className] = schema; + } + + return schema; +} + +// Caller must @synchronize on s_localNameToClass +static void RLMRegisterClassLocalNames(Class *classes, NSUInteger count) { + for (NSUInteger i = 0; i < count; i++) { + Class cls = classes[i]; + if (!RLMIsObjectSubclass(cls)) { + continue; + } + if ([cls _realmIgnoreClass]) { + continue; + } + + NSString *className = NSStringFromClass(cls); + if ([className hasPrefix:@"RLM:"] || [className hasPrefix:@"NSKVONotifying"]) { + continue; + } + + if ([RLMSwiftSupport isSwiftClassName:className]) { + className = [RLMSwiftSupport demangleClassName:className]; + } + // NSStringFromClass demangles the names for top-level Swift classes + // but not for nested classes. _T indicates it's a Swift symbol, t + // indicates it's a type, and C indicates it's a class. + else if ([className hasPrefix:@"_TtC"]) { + @throw RLMException(@"RLMObject subclasses cannot be nested within other declarations. Please move %@ to global scope.", className); + } + + if (Class existingClass = s_localNameToClass[className]) { + if (existingClass != cls) { + @throw RLMException(@"RLMObject subclasses with the same name cannot be included twice in the same target. " + @"Please make sure '%@' is only linked once to your current target.", className); + } + continue; + } + + s_localNameToClass[className] = cls; + RLMReplaceClassNameMethod(cls, className); + } +} + +- (instancetype)init { + self = [super init]; + if (self) { + _objectSchemaByName = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (NSArray *)objectSchema { + if (!_objectSchema) { + _objectSchema = [_objectSchemaByName allValues]; + } + return _objectSchema; +} + +- (void)setObjectSchema:(NSArray *)objectSchema { + _objectSchema = objectSchema; + _objectSchemaByName = [NSMutableDictionary dictionaryWithCapacity:objectSchema.count]; + for (RLMObjectSchema *object in objectSchema) { + [_objectSchemaByName setObject:object forKey:object.className]; + } +} + +- (RLMObjectSchema *)schemaForClassName:(NSString *)className { + if (RLMObjectSchema *schema = _objectSchemaByName[className]) { + return schema; // fast path for already-initialized schemas + } else if (Class cls = [RLMSchema classForString:className]) { + [cls sharedSchema]; // initialize the schema + return _objectSchemaByName[className]; // try again + } else { + return nil; + } +} + +- (RLMObjectSchema *)objectForKeyedSubscript:(__unsafe_unretained NSString *const)className { + RLMObjectSchema *schema = [self schemaForClassName:className]; + if (!schema) { + @throw RLMException(@"Object type '%@' not managed by the Realm", className); + } + return schema; +} + ++ (instancetype)schemaWithObjectClasses:(NSArray *)classes { + NSUInteger count = classes.count; + auto classArray = std::make_unique<__unsafe_unretained Class[]>(count); + [classes getObjects:classArray.get() range:NSMakeRange(0, count)]; + + RLMSchema *schema = [[self alloc] init]; + @synchronized(s_localNameToClass) { + RLMRegisterClassLocalNames(classArray.get(), count); + + schema->_objectSchemaByName = [NSMutableDictionary dictionaryWithCapacity:count]; + for (Class cls in classes) { + if (!RLMIsObjectSubclass(cls)) { + @throw RLMException(@"Can't add non-Object type '%@' to a schema.", cls); + } + schema->_objectSchemaByName[[cls className]] = RLMRegisterClass(cls); + } + } + + NSMutableArray *errors = [NSMutableArray new]; + // Verify that all of the targets of links are included in the class list + [schema->_objectSchemaByName enumerateKeysAndObjectsUsingBlock:^(id, RLMObjectSchema *objectSchema, BOOL *) { + for (RLMProperty *prop in objectSchema.properties) { + if (prop.type != RLMPropertyTypeObject) { + continue; + } + if (!schema->_objectSchemaByName[prop.objectClassName]) { + [errors addObject:[NSString stringWithFormat:@"- '%@.%@' links to class '%@', which is missing from the list of classes managed by the Realm", objectSchema.className, prop.name, prop.objectClassName]]; + } + } + }]; + if (errors.count) { + @throw RLMException(@"Invalid class subset list:\n%@", [errors componentsJoinedByString:@"\n"]); + } + + return schema; +} + ++ (RLMObjectSchema *)sharedSchemaForClass:(Class)cls { + @synchronized(s_localNameToClass) { + // We create instances of Swift objects during schema init, and they + // obviously need to not also try to initialize the schema + if (s_sharedSchemaState == SharedSchemaState::Initializing) { + return nil; + } + + RLMRegisterClassLocalNames(&cls, 1); + RLMObjectSchema *objectSchema = RLMRegisterClass(cls); + [cls initializeLinkedObjectSchemas]; + return objectSchema; + } +} + ++ (instancetype)partialSharedSchema { + return s_sharedSchema; +} + ++ (instancetype)partialPrivateSharedSchema { + return s_privateSharedSchema; +} + +// schema based on runtime objects ++ (instancetype)sharedSchema { + @synchronized(s_localNameToClass) { + // We replace this method with one which just returns s_sharedSchema + // once initialization is complete, but we still need to check if it's + // already complete because it may have been done by another thread + // while we were waiting for the lock + if (s_sharedSchemaState == SharedSchemaState::Initialized) { + return s_sharedSchema; + } + + if (s_sharedSchemaState == SharedSchemaState::Initializing) { + @throw RLMException(@"Illegal recursive call of +[%@ %@]. Note: Properties of Swift `Object` classes must not be prepopulated with queried results from a Realm.", self, NSStringFromSelector(_cmd)); + } + + s_sharedSchemaState = SharedSchemaState::Initializing; + try { + // Make sure we've discovered all classes + { + unsigned int numClasses; + using malloc_ptr = std::unique_ptr<__unsafe_unretained Class[], decltype(&free)>; + malloc_ptr classes(objc_copyClassList(&numClasses), &free); + RLMRegisterClassLocalNames(classes.get(), numClasses); + } + + [s_localNameToClass enumerateKeysAndObjectsUsingBlock:^(NSString *, Class cls, BOOL *) { + RLMRegisterClass(cls); + }]; + } + catch (...) { + s_sharedSchemaState = SharedSchemaState::Uninitialized; + throw; + } + + // Replace this method with one that doesn't need to acquire a lock + Class metaClass = objc_getMetaClass(class_getName(self)); + IMP imp = imp_implementationWithBlock(^{ return s_sharedSchema; }); + class_replaceMethod(metaClass, @selector(sharedSchema), imp, "@@:"); + + s_sharedSchemaState = SharedSchemaState::Initialized; + } + + return s_sharedSchema; +} + +// schema based on tables in a realm ++ (instancetype)dynamicSchemaFromObjectStoreSchema:(Schema const&)objectStoreSchema { + // cache descriptors for all subclasses of RLMObject + NSMutableArray *schemaArray = [NSMutableArray arrayWithCapacity:objectStoreSchema.size()]; + for (auto &objectSchema : objectStoreSchema) { + RLMObjectSchema *schema = [RLMObjectSchema objectSchemaForObjectStoreSchema:objectSchema]; + [schemaArray addObject:schema]; + } + + // set class array and mapping + RLMSchema *schema = [RLMSchema new]; + schema.objectSchema = schemaArray; + return schema; +} + ++ (Class)classForString:(NSString *)className { + if (Class cls = s_localNameToClass[className]) { + return cls; + } + + if (Class cls = NSClassFromString(className)) { + return RLMIsObjectSubclass(cls) ? cls : nil; + } + + // className might be the local name of a Swift class we haven't registered + // yet, so scan them all then recheck + { + unsigned int numClasses; + std::unique_ptr<__unsafe_unretained Class[], decltype(&free)> classes(objc_copyClassList(&numClasses), &free); + RLMRegisterClassLocalNames(classes.get(), numClasses); + } + + return s_localNameToClass[className]; +} + +- (id)copyWithZone:(NSZone *)zone { + RLMSchema *schema = [[RLMSchema allocWithZone:zone] init]; + schema->_objectSchemaByName = [[NSMutableDictionary allocWithZone:zone] + initWithDictionary:_objectSchemaByName copyItems:YES]; + return schema; +} + +- (BOOL)isEqualToSchema:(RLMSchema *)schema { + if (_objectSchemaByName.count != schema->_objectSchemaByName.count) { + return NO; + } + __block BOOL matches = YES; + [_objectSchemaByName enumerateKeysAndObjectsUsingBlock:^(NSString *name, RLMObjectSchema *objectSchema, BOOL *stop) { + if (![schema->_objectSchemaByName[name] isEqualToObjectSchema:objectSchema]) { + *stop = YES; + matches = NO; + } + }]; + return matches; +} + +- (NSString *)description { + NSMutableString *objectSchemaString = [NSMutableString string]; + NSArray *sort = @[[NSSortDescriptor sortDescriptorWithKey:@"className" ascending:YES]]; + for (RLMObjectSchema *objectSchema in [self.objectSchema sortedArrayUsingDescriptors:sort]) { + [objectSchemaString appendFormat:@"\t%@\n", + [objectSchema.description stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]]; + } + return [NSString stringWithFormat:@"Schema {\n%@}", objectSchemaString]; +} + +- (Schema)objectStoreCopy { + if (_objectStoreSchema.size() == 0) { + std::vector schema; + schema.reserve(_objectSchemaByName.count); + [_objectSchemaByName enumerateKeysAndObjectsUsingBlock:[&](NSString *, RLMObjectSchema *objectSchema, BOOL *) { + schema.push_back([objectSchema objectStoreCopy:self]); + }]; + + // Having both obj-c and Swift classes for the same tables results in + // duplicate ObjectSchemas that we need to filter out + std::sort(begin(schema), end(schema), [](auto&& a, auto&& b) { return a.name < b.name; }); + schema.erase(std::unique(begin(schema), end(schema), [](auto&& a, auto&& b) { + if (a.name == b.name) { + // If we make _realmObjectName public this needs to be turned into an exception + REALM_ASSERT_DEBUG(a.persisted_properties == b.persisted_properties); + return true; + } + return false; + }), end(schema)); + + _objectStoreSchema = std::move(schema); + } + return _objectStoreSchema; +} + +@end diff --git a/!main project/Pods/Realm/Realm/RLMSwiftSupport.m b/!main project/Pods/Realm/Realm/RLMSwiftSupport.m new file mode 100644 index 0000000..e16c79e --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMSwiftSupport.m @@ -0,0 +1,31 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMSwiftSupport.h" + +@implementation RLMSwiftSupport + ++ (BOOL)isSwiftClassName:(NSString *)className { + return [className rangeOfString:@"."].location != NSNotFound; +} + ++ (NSString *)demangleClassName:(NSString *)className { + return [className substringFromIndex:[className rangeOfString:@"."].location + 1]; +} + +@end diff --git a/!main project/Pods/Realm/Realm/RLMSyncConfiguration.mm b/!main project/Pods/Realm/Realm/RLMSyncConfiguration.mm new file mode 100644 index 0000000..b082477 --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMSyncConfiguration.mm @@ -0,0 +1,323 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMSyncConfiguration_Private.hpp" + +#import "RLMRealmConfiguration+Sync.h" +#import "RLMSyncManager_Private.h" +#import "RLMSyncSession_Private.hpp" +#import "RLMSyncSessionRefreshHandle.hpp" +#import "RLMSyncUser_Private.hpp" +#import "RLMSyncUtil_Private.hpp" +#import "RLMUtil.hpp" + +#import "sync/sync_manager.hpp" +#import "sync/sync_config.hpp" + +#import + +using namespace realm; + +namespace { +using ProtocolError = realm::sync::ProtocolError; + +RLMSyncSystemErrorKind errorKindForSyncError(SyncError error) { + if (error.is_client_reset_requested()) { + return RLMSyncSystemErrorKindClientReset; + } else if (error.error_code == ProtocolError::permission_denied) { + return RLMSyncSystemErrorKindPermissionDenied; + } else if (error.error_code == ProtocolError::bad_authentication) { + return RLMSyncSystemErrorKindUser; + } else if (error.is_session_level_protocol_error()) { + return RLMSyncSystemErrorKindSession; + } else if (error.is_connection_level_protocol_error()) { + return RLMSyncSystemErrorKindConnection; + } else if (error.is_client_error()) { + return RLMSyncSystemErrorKindClient; + } else { + return RLMSyncSystemErrorKindUnknown; + } +} + +BOOL isValidRealmURL(NSURL *url) { + NSString *scheme = [url scheme]; + return [scheme isEqualToString:@"realm"] || [scheme isEqualToString:@"realms"]; +} +} + +@interface RLMSyncConfiguration () { + std::unique_ptr _config; +} + +- (instancetype)initWithUser:(RLMSyncUser *)user + realmURL:(NSURL *)url + customFileURL:(nullable NSURL *)customFileURL + isPartial:(BOOL)isPartial + stopPolicy:(RLMSyncStopPolicy)stopPolicy; +@end + +@implementation RLMSyncConfiguration + +@dynamic stopPolicy; + +- (instancetype)initWithRawConfig:(realm::SyncConfig)config { + if (self = [super init]) { + _config = std::make_unique(std::move(config)); + } + return self; +} + +- (BOOL)isEqual:(id)object { + if (![object isKindOfClass:[RLMSyncConfiguration class]]) { + return NO; + } + RLMSyncConfiguration *that = (RLMSyncConfiguration *)object; + return [self.realmURL isEqual:that.realmURL] + && [self.user isEqual:that.user] + && self.stopPolicy == that.stopPolicy + && self.fullSynchronization == that.fullSynchronization; +} + +- (void)setEnableSSLValidation:(BOOL)enableSSLValidation { + _config->client_validate_ssl = (bool)enableSSLValidation; +} + +- (BOOL)enableSSLValidation { + return (BOOL)_config->client_validate_ssl; +} + +- (void)setIsPartial:(BOOL)isPartial { + _config->is_partial = (bool)isPartial; +} + +- (BOOL)isPartial { + return (BOOL)_config->is_partial; +} + +- (NSURL *)pinnedCertificateURL { + if (auto& path = _config->ssl_trust_certificate_path) { + return [NSURL fileURLWithPath:RLMStringDataToNSString(*path)]; + } + return nil; +} + +- (void)setPinnedCertificateURL:(NSURL *)pinnedCertificateURL { + if (pinnedCertificateURL) { + if ([pinnedCertificateURL respondsToSelector:@selector(UTF8String)]) { + _config->ssl_trust_certificate_path = std::string([(id)pinnedCertificateURL UTF8String]); + } + else { + _config->ssl_trust_certificate_path = std::string(pinnedCertificateURL.path.UTF8String); + } + } + else { + _config->ssl_trust_certificate_path = util::none; + } +} + +- (void)setFullSynchronization:(BOOL)fullSynchronization { + _config->is_partial = !(bool)fullSynchronization; +} + +- (BOOL)fullSynchronization { + return !(BOOL)_config->is_partial; +} + +- (realm::SyncConfig&)rawConfiguration { + return *_config; +} + +- (RLMSyncUser *)user { + return [[RLMSyncUser alloc] initWithSyncUser:_config->user]; +} + +- (RLMSyncStopPolicy)stopPolicy { + return translateStopPolicy(_config->stop_policy); +} + +- (void)setStopPolicy:(RLMSyncStopPolicy)stopPolicy { + _config->stop_policy = translateStopPolicy(stopPolicy); +} + +- (NSString *)urlPrefix { + if (_config->url_prefix) { + return @(_config->url_prefix->c_str()); + } + return nil; +} + +- (void)setUrlPrefix:(NSString *)urlPrefix { + if (urlPrefix) { + _config->url_prefix.emplace(urlPrefix.UTF8String); + } else { + _config->url_prefix = none; + } +} + +- (bool)cancelAsyncOpenOnNonFatalErrors { + return _config->cancel_waits_on_nonfatal_error; +} + +- (void)setCancelAsyncOpenOnNonFatalErrors:(bool)cancelAsyncOpenOnNonFatalErrors { + _config->cancel_waits_on_nonfatal_error = cancelAsyncOpenOnNonFatalErrors; +} + +- (NSURL *)realmURL { + NSString *rawStringURL = @(_config->reference_realm_url.c_str()); + return [NSURL URLWithString:rawStringURL]; +} + +- (instancetype)initWithUser:(RLMSyncUser *)user realmURL:(NSURL *)url { + return [self initWithUser:user + realmURL:url + customFileURL:nil + isPartial:NO + stopPolicy:RLMSyncStopPolicyAfterChangesUploaded]; +} + +- (instancetype)initWithUser:(RLMSyncUser *)user + realmURL:(NSURL *)url + isPartial:(BOOL)isPartial + urlPrefix:(NSString *)urlPrefix + stopPolicy:(RLMSyncStopPolicy)stopPolicy + enableSSLValidation:(BOOL)enableSSLValidation + certificatePath:(nullable NSURL *)certificatePath { + auto config = [self initWithUser:user + realmURL:url + customFileURL:nil + isPartial:isPartial + stopPolicy:stopPolicy]; + config.urlPrefix = urlPrefix; + config.enableSSLValidation = enableSSLValidation; + config.pinnedCertificateURL = certificatePath; + return config; +} + +static void bindHandler(std::string const&, SyncConfig const& config, std::shared_ptr session) { + const std::shared_ptr& user = config.user; + NSURL *realmURL = [NSURL URLWithString:@(config.realm_url().c_str())]; + NSString *path = [realmURL path]; + REALM_ASSERT(realmURL && path); + auto handle = [[RLMSyncSessionRefreshHandle alloc] initWithRealmURL:realmURL + user:user + session:std::move(session) + completionBlock:RLMSyncManager.sharedManager.sessionCompletionNotifier]; + context_for(user).register_refresh_handle([path UTF8String], handle); +} + +static void errorHandler(std::shared_ptr errored_session, SyncError error) { + NSString *recoveryPath; + RLMSyncErrorActionToken *token; + for (auto& pair : error.user_info) { + if (pair.first == realm::SyncError::c_original_file_path_key) { + token = [[RLMSyncErrorActionToken alloc] initWithOriginalPath:pair.second]; + } + else if (pair.first == realm::SyncError::c_recovery_file_path_key) { + recoveryPath = @(pair.second.c_str()); + } + } + + BOOL shouldMakeError = YES; + NSDictionary *custom = nil; + // Note that certain types of errors are 'interactive'; users have several options + // as to how to proceed after the error is reported. + auto errorClass = errorKindForSyncError(error); + switch (errorClass) { + case RLMSyncSystemErrorKindClientReset: { + custom = @{kRLMSyncPathOfRealmBackupCopyKey: recoveryPath, kRLMSyncErrorActionTokenKey: token}; + break; + } + case RLMSyncSystemErrorKindPermissionDenied: { + custom = @{kRLMSyncErrorActionTokenKey: token}; + break; + } + case RLMSyncSystemErrorKindUser: + case RLMSyncSystemErrorKindSession: + break; + case RLMSyncSystemErrorKindConnection: + case RLMSyncSystemErrorKindClient: + case RLMSyncSystemErrorKindUnknown: + // Report the error. There's nothing the user can do about it, though. + shouldMakeError = error.is_fatal; + break; + } + auto errorHandler = RLMSyncManager.sharedManager.errorHandler; + if (!shouldMakeError || !errorHandler) { + return; + } + NSError *nsError = make_sync_error(errorClass, @(error.message.c_str()), error.error_code.value(), custom); + RLMSyncSession *session = [[RLMSyncSession alloc] initWithSyncSession:errored_session]; + dispatch_async(dispatch_get_main_queue(), ^{ + errorHandler(nsError, session); + }); +}; + +- (instancetype)initWithUser:(RLMSyncUser *)user + realmURL:(NSURL *)url + customFileURL:(nullable NSURL *)customFileURL + isPartial:(BOOL)isPartial + stopPolicy:(RLMSyncStopPolicy)stopPolicy { + if (self = [super init]) { + if (!isValidRealmURL(url)) { + @throw RLMException(@"The provided URL (%@) was not a valid Realm URL.", [url absoluteString]); + } + + _config = std::make_unique(SyncConfig{ + [user _syncUser], + [[url absoluteString] UTF8String] + }); + _config->stop_policy = translateStopPolicy(stopPolicy); + _config->bind_session_handler = bindHandler; + _config->error_handler = errorHandler; + _config->is_partial = isPartial; + _config->client_resync_mode = realm::ClientResyncMode::Manual; + + if (NSString *authorizationHeaderName = [RLMSyncManager sharedManager].authorizationHeaderName) { + _config->authorization_header_name.emplace(authorizationHeaderName.UTF8String); + } + if (NSDictionary *customRequestHeaders = [RLMSyncManager sharedManager].customRequestHeaders) { + for (NSString *key in customRequestHeaders) { + _config->custom_http_headers.emplace(key.UTF8String, customRequestHeaders[key].UTF8String); + } + } + + self.customFileURL = customFileURL; + return self; + } + return nil; +} + ++ (RLMRealmConfiguration *)automaticConfiguration { + if (RLMSyncUser.allUsers.count != 1) + @throw RLMException(@"The automatic configuration requires there be exactly one logged-in sync user."); + + return [RLMSyncConfiguration automaticConfigurationForUser:RLMSyncUser.currentUser]; +} + ++ (RLMRealmConfiguration *)automaticConfigurationForUser:(RLMSyncUser *)user { + RLMSyncConfiguration *syncConfig = [[RLMSyncConfiguration alloc] initWithUser:user + realmURL:user.defaultRealmURL + customFileURL:nil + isPartial:YES + stopPolicy:RLMSyncStopPolicyAfterChangesUploaded]; + RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init]; + config.syncConfiguration = syncConfig; + return config; +} + +@end diff --git a/!main project/Pods/Realm/Realm/RLMSyncCredentials.m b/!main project/Pods/Realm/Realm/RLMSyncCredentials.m new file mode 100644 index 0000000..480c539 --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMSyncCredentials.m @@ -0,0 +1,132 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMSyncCredentials.h" +#import "RLMSyncUtil_Private.h" + +/// A Twitter account as an identity provider. +//extern RLMIdentityProvider const RLMIdentityProviderTwitter; + +RLMIdentityProvider const RLMIdentityProviderDebug = @"debug"; +RLMIdentityProvider const RLMIdentityProviderRealm = @"realm"; +RLMIdentityProvider const RLMIdentityProviderUsernamePassword = @"password"; +RLMIdentityProvider const RLMIdentityProviderFacebook = @"facebook"; +RLMIdentityProvider const RLMIdentityProviderTwitter = @"twitter"; +RLMIdentityProvider const RLMIdentityProviderGoogle = @"google"; +RLMIdentityProvider const RLMIdentityProviderCloudKit = @"cloudkit"; +RLMIdentityProvider const RLMIdentityProviderJWT = @"jwt"; +RLMIdentityProvider const RLMIdentityProviderAnonymous = @"anonymous"; +RLMIdentityProvider const RLMIdentityProviderNickname = @"nickname"; +RLMIdentityProvider const RLMIdentityProviderCustomRefreshToken = @"customrefreshtoken"; + +@interface RLMSyncCredentials () + +- (instancetype)initWithCustomToken:(RLMSyncCredentialsToken)token + provider:(RLMIdentityProvider)provider + userInfo:(NSDictionary *)userInfo NS_DESIGNATED_INITIALIZER; + +@property (nonatomic, readwrite) RLMSyncCredentialsToken token; +@property (nonatomic, readwrite) RLMIdentityProvider provider; +@property (nonatomic, readwrite) NSDictionary *userInfo; + +@end + +@implementation RLMSyncCredentials + ++ (instancetype)credentialsWithFacebookToken:(RLMSyncCredentialsToken)token { + return [[self alloc] initWithCustomToken:token provider:RLMIdentityProviderFacebook userInfo:nil]; +} + ++ (instancetype)credentialsWithGoogleToken:(RLMSyncCredentialsToken)token { + return [[self alloc] initWithCustomToken:token provider:RLMIdentityProviderGoogle userInfo:nil]; +} + ++ (instancetype)credentialsWithCloudKitToken:(RLMSyncCredentialsToken)token { + return [[self alloc] initWithCustomToken:token provider:RLMIdentityProviderCloudKit userInfo:nil]; +} + ++ (instancetype)credentialsWithUsername:(NSString *)username + password:(NSString *)password + register:(BOOL)shouldRegister { + return [[self alloc] initWithCustomToken:username + provider:RLMIdentityProviderUsernamePassword + userInfo:@{kRLMSyncPasswordKey: password, + kRLMSyncRegisterKey: @(shouldRegister)}]; +} + ++ (instancetype)credentialsWithJWT:(NSString *)token { + return [[self alloc] initWithCustomToken:token provider:RLMIdentityProviderJWT userInfo:nil]; +} + ++ (instancetype)anonymousCredentials { + return [[self alloc] initWithCustomToken:@"" provider:RLMIdentityProviderAnonymous userInfo:nil]; +} + ++ (instancetype)credentialsWithNickname:(NSString *)nickname isAdmin:(BOOL)isAdmin { + return [[self alloc] initWithCustomToken:nickname + provider:RLMIdentityProviderNickname + userInfo:@{kRLMSyncIsAdminKey: @(isAdmin), kRLMSyncDataKey: nickname}]; +} + +/// Intended only for testing use. Will only work if the ROS is started with the `debug` provider enabled. ++ (instancetype)credentialsWithDebugUserID:(NSString *)userID isAdmin:(BOOL)isAdmin { + return [[self alloc] initWithCustomToken:userID + provider:RLMIdentityProviderDebug + userInfo:@{kRLMSyncIsAdminKey: @(isAdmin)}]; +} + ++ (instancetype)credentialsWithAccessToken:(RLMServerToken)accessToken identity:(NSString *)identity { + return [[self alloc] initWithCustomToken:accessToken + provider:RLMIdentityProviderAccessToken + userInfo:@{kRLMSyncIdentityKey: identity}]; +} + ++ (instancetype)credentialsWithCustomRefreshToken:(NSString *)token identity:(NSString *)identity isAdmin:(BOOL)isAdmin { + NSDictionary *userInfo = @{ + kRLMSyncIdentityKey: identity, + kRLMSyncIsAdminKey: @(isAdmin) + }; + return [[self alloc] initWithCustomToken:token + provider:RLMIdentityProviderCustomRefreshToken + userInfo:userInfo]; + +} + +- (BOOL)isEqual:(id)object { + if (![object isKindOfClass:[RLMSyncCredentials class]]) { + return NO; + } + RLMSyncCredentials *that = (RLMSyncCredentials *)object; + return ([self.token isEqualToString:that.token] + && [self.provider isEqualToString:that.provider] + && [self.userInfo isEqual:that.userInfo]); +} + +- (instancetype)initWithCustomToken:(RLMSyncCredentialsToken)token + provider:(RLMIdentityProvider)provider + userInfo:(NSDictionary *)userInfo { + if (self = [super init]) { + self.token = token; + self.provider = provider; + self.userInfo = userInfo; + return self; + } + return nil; +} + +@end diff --git a/!main project/Pods/Realm/Realm/RLMSyncManager.mm b/!main project/Pods/Realm/Realm/RLMSyncManager.mm new file mode 100644 index 0000000..cb2e1d3 --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMSyncManager.mm @@ -0,0 +1,295 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMSyncManager_Private.h" + +#import "RLMRealmConfiguration+Sync.h" +#import "RLMSyncConfiguration_Private.hpp" +#import "RLMSyncSession_Private.hpp" +#import "RLMSyncUser_Private.hpp" +#import "RLMSyncUtil_Private.hpp" +#import "RLMUtil.hpp" + +#import "sync/sync_config.hpp" +#import "sync/sync_manager.hpp" +#import "sync/sync_session.hpp" + +#if !defined(REALM_COCOA_VERSION) +#import "RLMVersion.h" +#endif + +using namespace realm; +using Level = realm::util::Logger::Level; + +namespace { + +Level levelForSyncLogLevel(RLMSyncLogLevel logLevel) { + switch (logLevel) { + case RLMSyncLogLevelOff: return Level::off; + case RLMSyncLogLevelFatal: return Level::fatal; + case RLMSyncLogLevelError: return Level::error; + case RLMSyncLogLevelWarn: return Level::warn; + case RLMSyncLogLevelInfo: return Level::info; + case RLMSyncLogLevelDetail: return Level::detail; + case RLMSyncLogLevelDebug: return Level::debug; + case RLMSyncLogLevelTrace: return Level::trace; + case RLMSyncLogLevelAll: return Level::all; + } + REALM_UNREACHABLE(); // Unrecognized log level. +} + +RLMSyncLogLevel logLevelForLevel(Level logLevel) { + switch (logLevel) { + case Level::off: return RLMSyncLogLevelOff; + case Level::fatal: return RLMSyncLogLevelFatal; + case Level::error: return RLMSyncLogLevelError; + case Level::warn: return RLMSyncLogLevelWarn; + case Level::info: return RLMSyncLogLevelInfo; + case Level::detail: return RLMSyncLogLevelDetail; + case Level::debug: return RLMSyncLogLevelDebug; + case Level::trace: return RLMSyncLogLevelTrace; + case Level::all: return RLMSyncLogLevelAll; + } + REALM_UNREACHABLE(); // Unrecognized log level. +} + +#pragma mark - Loggers + +struct CocoaSyncLogger : public realm::util::RootLogger { + void do_log(Level, std::string message) override { + NSLog(@"Sync: %@", RLMStringDataToNSString(message)); + } +}; + +struct CocoaSyncLoggerFactory : public realm::SyncLoggerFactory { + std::unique_ptr make_logger(realm::util::Logger::Level level) override { + auto logger = std::make_unique(); + logger->set_level_threshold(level); + return std::move(logger); + } +} s_syncLoggerFactory; + +struct CallbackLogger : public realm::util::RootLogger { + RLMSyncLogFunction logFn; + void do_log(Level level, std::string message) override { + @autoreleasepool { + logFn(logLevelForLevel(level), RLMStringDataToNSString(message)); + } + } +}; +struct CallbackLoggerFactory : public realm::SyncLoggerFactory { + RLMSyncLogFunction logFn; + std::unique_ptr make_logger(realm::util::Logger::Level level) override { + auto logger = std::make_unique(); + logger->logFn = logFn; + logger->set_level_threshold(level); + return std::move(logger); // not a redundant move because it's a different type + } + + CallbackLoggerFactory(RLMSyncLogFunction logFn) : logFn(logFn) { } +}; + +} // anonymous namespace + +#pragma mark - RLMSyncManager + +@interface RLMSyncTimeoutOptions () { + @public + realm::SyncClientTimeouts _options; +} +@end + +@implementation RLMSyncManager { + std::unique_ptr _loggerFactory; +} + +static RLMSyncManager *s_sharedManager = nil; + +- (instancetype)initPrivate { + return self = [super init]; +} + ++ (instancetype)sharedManager { + static std::once_flag flag; + std::call_once(flag, [] { + try { + [RLMSyncUser _setUpBindingContextFactory]; + s_sharedManager = [[RLMSyncManager alloc] initPrivate]; + [s_sharedManager configureWithRootDirectory:nil]; + } + catch (std::exception const& e) { + @throw RLMException(e); + } + }); + return s_sharedManager; +} + +- (void)configureWithRootDirectory:(NSURL *)rootDirectory { + SyncClientConfig config; + bool should_encrypt = !getenv("REALM_DISABLE_METADATA_ENCRYPTION") && !RLMIsRunningInPlayground(); + config.logger_factory = &s_syncLoggerFactory; + config.metadata_mode = should_encrypt ? SyncManager::MetadataMode::Encryption + : SyncManager::MetadataMode::NoEncryption; + @autoreleasepool { + rootDirectory = rootDirectory ?: [NSURL fileURLWithPath:RLMDefaultDirectoryForBundleIdentifier(nil)]; + config.base_file_path = rootDirectory.path.UTF8String; + + bool isSwift = !!NSClassFromString(@"RealmSwiftObjectUtil"); + config.user_agent_binding_info = + util::format("Realm%1/%2", isSwift ? "Swift" : "ObjectiveC", + RLMStringDataWithNSString(REALM_COCOA_VERSION)); + config.user_agent_application_info = RLMStringDataWithNSString(self.appID); + } + SyncManager::shared().configure(config); +} + +- (NSString *)appID { + if (!_appID) { + _appID = [[NSBundle mainBundle] bundleIdentifier] ?: @"(none)"; + } + return _appID; +} + +- (void)setUserAgent:(NSString *)userAgent { + SyncManager::shared().set_user_agent(RLMStringDataWithNSString(userAgent)); + _userAgent = userAgent; +} + +- (void)setCustomRequestHeaders:(NSDictionary *)customRequestHeaders { + _customRequestHeaders = customRequestHeaders.copy; + + for (auto&& user : SyncManager::shared().all_logged_in_users()) { + for (auto&& session : user->all_sessions()) { + auto config = session->config(); + config.custom_http_headers.clear();; + for (NSString *key in customRequestHeaders) { + config.custom_http_headers.emplace(key.UTF8String, customRequestHeaders[key].UTF8String); + } + session->update_configuration(std::move(config)); + } + } +} + +- (void)setLogger:(RLMSyncLogFunction)logFn { + _logger = logFn; + if (_logger) { + _loggerFactory = std::make_unique(logFn); + SyncManager::shared().set_logger_factory(*_loggerFactory); + } + else { + _loggerFactory = nullptr; + SyncManager::shared().set_logger_factory(s_syncLoggerFactory); + } +} + +- (void)setTimeoutOptions:(RLMSyncTimeoutOptions *)timeoutOptions { + _timeoutOptions = timeoutOptions; + SyncManager::shared().set_timeouts(timeoutOptions->_options); +} + +#pragma mark - Passthrough properties + +- (RLMSyncLogLevel)logLevel { + return logLevelForLevel(realm::SyncManager::shared().log_level()); +} + +- (void)setLogLevel:(RLMSyncLogLevel)logLevel { + realm::SyncManager::shared().set_log_level(levelForSyncLogLevel(logLevel)); +} + +#pragma mark - Private API + +- (void)_fireError:(NSError *)error { + dispatch_async(dispatch_get_main_queue(), ^{ + if (self.errorHandler) { + self.errorHandler(error, nil); + } + }); +} + +- (NSArray *)_allUsers { + NSMutableArray *buffer = [NSMutableArray array]; + for (auto user : SyncManager::shared().all_logged_in_users()) { + [buffer addObject:[[RLMSyncUser alloc] initWithSyncUser:std::move(user)]]; + } + return buffer; +} + ++ (void)resetForTesting { + RLMSyncManager *manager = self.sharedManager; + manager->_errorHandler = nil; + manager->_appID = nil; + manager->_userAgent = nil; + manager->_logger = nil; + manager->_authorizationHeaderName = nil; + manager->_customRequestHeaders = nil; + manager->_pinnedCertificatePaths = nil; + manager->_timeoutOptions = nil; + + SyncManager::shared().reset_for_testing(); +} + +- (RLMNetworkRequestOptions *)networkRequestOptions { + RLMNetworkRequestOptions *options = [[RLMNetworkRequestOptions alloc] init]; + options.authorizationHeaderName = self.authorizationHeaderName; + options.customHeaders = self.customRequestHeaders; + options.pinnedCertificatePaths = self.pinnedCertificatePaths; + return options; +} + +@end + +#pragma mark - RLMSyncTimeoutOptions + +@implementation RLMSyncTimeoutOptions +- (NSUInteger)connectTimeout { + return _options.connect_timeout; +} +- (void)setConnectTimeout:(NSUInteger)connectTimeout { + _options.connect_timeout = connectTimeout; +} + +- (NSUInteger)connectLingerTime { + return _options.connection_linger_time; +} +- (void)setConnectionLingerTime:(NSUInteger)connectionLingerTime { + _options.connection_linger_time = connectionLingerTime; +} + +- (NSUInteger)pingKeepalivePeriod { + return _options.ping_keepalive_period; +} +- (void)setPingKeepalivePeriod:(NSUInteger)pingKeepalivePeriod { + _options.ping_keepalive_period = pingKeepalivePeriod; +} + +- (NSUInteger)pongKeepaliveTimeout { + return _options.pong_keepalive_timeout; +} +- (void)setPongKeepaliveTimeout:(NSUInteger)pongKeepaliveTimeout { + _options.pong_keepalive_timeout = pongKeepaliveTimeout; +} + +- (NSUInteger)fastReconnectLimit { + return _options.fast_reconnect_limit; +} +- (void)setFastReconnectLimit:(NSUInteger)fastReconnectLimit { + _options.fast_reconnect_limit = fastReconnectLimit; +} + +@end diff --git a/!main project/Pods/Realm/Realm/RLMSyncPermission.mm b/!main project/Pods/Realm/Realm/RLMSyncPermission.mm new file mode 100644 index 0000000..6cbd0eb --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMSyncPermission.mm @@ -0,0 +1,375 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2017 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMSyncPermission.h" + +#import "RLMArray.h" +#import "RLMObjectSchema.h" +#import "RLMRealm_Dynamic.h" +#import "RLMResults.h" +#import "RLMSyncUser.h" +#import "RLMSyncUtil_Private.hpp" +#import "RLMUtil.hpp" + +using namespace realm; + +static void verifyInWriteTransaction(__unsafe_unretained RLMRealm *const realm, SEL sel) { + if (!realm) { + @throw RLMException(@"Cannot call %@ on an unmanaged object.", NSStringFromSelector(sel)); + } + if (!realm.inWriteTransaction) { + @throw RLMException(@"Cannot call %@ outside of a write transaction.", NSStringFromSelector(sel)); + } +} + +id RLMPermissionForRole(RLMArray *array, id role) { + RLMResults *filtered = [array objectsWhere:@"role.name = %@", [role name]]; + RLMPermission *permission; + for (RLMPermission *p in filtered) { + if (permission == nil) { + permission = p; + } + // If there's more than one permission for the role, merge it into the first + // one and then delete it as otherwise revoking permissions won't actually work + else { + if (p.canRead && !permission.canRead) { + permission.canRead = true; + } + if (p.canUpdate && !permission.canUpdate) { + permission.canUpdate = true; + } + if (p.canDelete && !permission.canDelete) { + permission.canDelete = true; + } + if (p.canSetPermissions && !permission.canSetPermissions) { + permission.canSetPermissions = true; + } + if (p.canQuery && !permission.canQuery) { + permission.canQuery = true; + } + if (p.canCreate && !permission.canCreate) { + permission.canCreate = true; + } + if (p.canModifySchema && !permission.canModifySchema) { + permission.canModifySchema = true; + } + [array.realm deleteObject:p]; + } + } + if (!permission) { + // Use the dynamic API to create the appropriate Permission class for the array + permission = (id)[array.realm createObject:array.objectClassName withValue:@[role]]; + [array addObject:permission]; + } + return permission; +} + +@implementation RLMPermissionRole ++ (NSString *)_realmObjectName { + return @"__Role"; +} ++ (NSString *)primaryKey { + return @"name"; +} ++ (NSArray *)requiredProperties { + return @[@"name"]; +} ++ (NSDictionary *)_realmColumnNames { + return @{@"users": @"members"}; +} +@end + +@implementation RLMPermissionUser ++ (NSString *)_realmObjectName { + return @"__User"; +} ++ (NSString *)primaryKey { + return @"identity"; +} ++ (NSArray *)requiredProperties { + return @[@"identity"]; +} ++ (NSDictionary *)_realmColumnNames { + return @{@"identity": @"id", @"role": @"role"}; +} ++ (NSDictionary *)linkingObjectsProperties { + return @{@"roles": [RLMPropertyDescriptor descriptorWithClass:RLMPermissionRole.class propertyName:@"users"]}; +} + ++ (RLMPermissionUser *)userInRealm:(RLMRealm *)realm withIdentity:(NSString *)identity { + return [self createOrUpdateInRealm:realm withValue:@[identity]]; +} +@end + +@implementation RLMPermission ++ (NSString *)_realmObjectName { + return @"__Permission"; +} ++ (NSDictionary *)defaultPropertyValues { + return @{@"canRead": @NO, + @"canUpdate": @NO, + @"canDelete": @NO, + @"canSetPermissions": @NO, + @"canQuery": @NO, + @"canCreate": @NO, + @"canModifySchema": @NO}; +} + ++ (RLMPermission *)permissionForRole:(RLMPermissionRole *)role inArray:(RLMArray *)array { + verifyInWriteTransaction(array.realm, _cmd); + auto index = [array indexOfObjectWhere:@"role = %@", role]; + if (index != NSNotFound) { + return array[index]; + } + RLMPermission *permission = [RLMPermission createInRealm:role.realm withValue:@[role]]; + [array addObject:permission]; + return permission; +} + ++ (RLMPermission *)permissionForRoleNamed:(NSString *)roleName inArray:(RLMArray *)array { + verifyInWriteTransaction(array.realm, _cmd); + return RLMPermissionForRole(array, [RLMPermissionRole createOrUpdateInRealm:array.realm withValue:@{@"name": roleName}]); +} + ++ (RLMPermission *)permissionForRoleNamed:(NSString *)roleName onRealm:(RLMRealm *)realm { + verifyInWriteTransaction(realm, _cmd); + return [self permissionForRoleNamed:roleName + inArray:[RLMRealmPermission objectInRealm:realm].permissions]; + +} + ++ (RLMPermission *)permissionForRoleNamed:(NSString *)roleName onClass:(Class)cls realm:(RLMRealm *)realm { + verifyInWriteTransaction(realm, _cmd); + return [self permissionForRoleNamed:roleName + inArray:[RLMClassPermission objectInRealm:realm forClass:cls].permissions]; +} + ++ (RLMPermission *)permissionForRoleNamed:(NSString *)roleName onClassNamed:(NSString *)className realm:(RLMRealm *)realm { + verifyInWriteTransaction(realm, _cmd); + return [self permissionForRoleNamed:roleName + inArray:[RLMClassPermission objectInRealm:realm forClassNamed:className].permissions]; +} + ++ (RLMPermission *)permissionForRoleNamed:(NSString *)roleName onObject:(RLMObject *)object { + verifyInWriteTransaction(object.realm, _cmd); + for (RLMProperty *prop in object.objectSchema.properties) { + if (prop.array && [prop.objectClassName isEqualToString:@"RLMPermission"]) { + return [self permissionForRoleNamed:roleName + inArray:[object valueForKey:prop.name]]; + } + } + @throw RLMException(@"Object %@ does not have a RLMArray property.", object); +} +@end + +@implementation RLMClassPermission ++ (NSString *)_realmObjectName { + return @"__Class"; +} ++ (NSString *)primaryKey { + return @"name"; +} ++ (NSArray *)requiredProperties { + return @[@"name"]; +} + ++ (instancetype)objectInRealm:(RLMRealm *)realm forClassNamed:(NSString *)name { + return [RLMClassPermission objectInRealm:realm forPrimaryKey:name]; +} ++ (instancetype)objectInRealm:(RLMRealm *)realm forClass:(Class)cls { + return [RLMClassPermission objectInRealm:realm forPrimaryKey:[cls _realmObjectName] ?: [cls className]]; +} +@end + +@interface RLMRealmPermission () +@property (nonatomic) int pk; +@end + +@implementation RLMRealmPermission ++ (NSString *)_realmObjectName { + return @"__Realm"; +} ++ (NSDictionary *)_realmColumnNames { + return @{@"pk": @"id"}; +} ++ (NSString *)primaryKey { + return @"pk"; +} + ++ (instancetype)objectInRealm:(RLMRealm *)realm { + return [RLMRealmPermission objectInRealm:realm forPrimaryKey:@0]; +} +@end + +#pragma mark - Permission + +@implementation RLMSyncPermission + +- (instancetype)initWithRealmPath:(NSString *)path + identity:(NSString *)identity + accessLevel:(RLMSyncAccessLevel)accessLevel { + if (self = [super init]) { + _accessLevel = accessLevel; + _path = path; + _identity = identity; + if (!identity) { + @throw RLMException(@"A permission value cannot be created without a valid user ID"); + } + _updatedAt = [NSDate date]; + } + return self; +} + +- (instancetype)initWithRealmPath:(NSString *)path + username:(NSString *)username + accessLevel:(RLMSyncAccessLevel)accessLevel { + if (self = [super init]) { + _accessLevel = accessLevel; + _path = path; + _identity = nil; + _key = @"email"; + _value = username; + _updatedAt = [NSDate date]; + } + return self; +} + ++ (BOOL)accessInstanceVariablesDirectly { + return NO; +} + +- (BOOL)mayRead { + return self.accessLevel > RLMSyncAccessLevelNone; +} + +- (BOOL)mayWrite { + return self.accessLevel > RLMSyncAccessLevelRead; +} + +- (BOOL)mayManage { + return self.accessLevel == RLMSyncAccessLevelAdmin; +} + +- (NSUInteger)hash { + return self.identity.hash ^ self.accessLevel ^ self.path.hash ^ self.key.hash ^ self.value.hash; +} + +- (BOOL)isEqual:(id)object { + if (self == object) { + return YES; + } + if (![object isKindOfClass:[RLMSyncPermission class]]) { + return NO; + } + + RLMSyncPermission *that = (RLMSyncPermission *)object; + if (self.accessLevel != that.accessLevel) { + return NO; + } + if (![self.path isEqualToString:that.path]) { + return NO; + } + if (self.identity) { + if (!that.identity || ![self.identity isEqualToString:that.identity]) { + return NO; + } + } + else { + if (that.identity || ![self.key isEqualToString:that.key] || ![self.value isEqualToString:that.value]) { + return NO; + } + } + return YES; +} + +- (RLMSyncPermission *)tildeExpandedSyncPermissionForUser:(RLMSyncUser *)user { + if (![self.path hasPrefix:@"/~/"]) { + return self; + } + NSString *path = [self.path stringByReplacingCharactersInRange:{1, 1} withString:user.identity]; + if (_identity) { + return [[RLMSyncPermission alloc] initWithRealmPath:path identity:_identity accessLevel:_accessLevel]; + } + return [[RLMSyncPermission alloc] initWithRealmPath:path username:_value accessLevel:_accessLevel]; +} + +- (NSString *)description { + NSString *typeDescription = nil; + if (self.identity) { + typeDescription = [NSString stringWithFormat:@"identity: %@", self.identity]; + } else { + typeDescription = [NSString stringWithFormat:@"key: %@, value: %@", self.key, self.value]; + } + return [NSString stringWithFormat:@" %@, path: %@, access level: %@", + typeDescription, self.path, RLMSyncAccessLevelToString(self.accessLevel)]; +} + +NSString *RLMSyncAccessLevelToString(RLMSyncAccessLevel level) { + switch (level) { + case RLMSyncAccessLevelNone: return @"none"; + case RLMSyncAccessLevelRead: return @"read"; + case RLMSyncAccessLevelWrite: return @"write"; + case RLMSyncAccessLevelAdmin: return @"admin"; + } + return @"unknown"; +} + +RLMSyncAccessLevel RLMSyncAccessLevelFromString(NSString *level) { + if ([level isEqualToString:@"none"]) { + return RLMSyncAccessLevelNone; + } + if ([level isEqualToString:@"read"]) { + return RLMSyncAccessLevelRead; + } + if ([level isEqualToString:@"write"]) { + return RLMSyncAccessLevelWrite; + } + if ([level isEqualToString:@"admin"]) { + return RLMSyncAccessLevelAdmin; + } + @throw RLMException(@"Invalid access level %@", level); +} + +@end + +@implementation RLMSyncPermissionOffer +- (instancetype)initWithRealmPath:(NSString *)path + token:(NSString *)token + expiresAt:(NSDate *)expiresAt + createdAt:(NSDate *)createdAt + accessLevel:(RLMSyncAccessLevel)accessLevel { + if (self = [super init]) { + _realmPath = path; + _token = token; + _expiresAt = expiresAt; + _createdAt = createdAt; + _accessLevel = accessLevel; + } + return self; +} + ++ (BOOL)accessInstanceVariablesDirectly { + return NO; +} + +- (NSString *)description { + return [NSString stringWithFormat:@" path: %@, access level: %@, token: %@, createdAt: %@, expiresAt: %@", + self, _realmPath, RLMSyncAccessLevelToString(_accessLevel), + _token, _createdAt, _expiresAt]; +} +@end diff --git a/!main project/Pods/Realm/Realm/RLMSyncSession.mm b/!main project/Pods/Realm/Realm/RLMSyncSession.mm new file mode 100644 index 0000000..cddaade --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMSyncSession.mm @@ -0,0 +1,306 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMSyncSession_Private.hpp" + +#import "RLMRealm_Private.hpp" +#import "RLMSyncConfiguration_Private.hpp" +#import "RLMSyncUser_Private.hpp" +#import "RLMSyncUtil_Private.hpp" + +#import "sync/async_open_task.hpp" +#import "sync/sync_session.hpp" + +using namespace realm; + +@interface RLMSyncErrorActionToken () { +@public + std::string _originalPath; + BOOL _isValid; +} +@end + +@interface RLMProgressNotificationToken() { + uint64_t _token; + std::weak_ptr _session; +} +@end + +@implementation RLMProgressNotificationToken + +- (void)suppressNextNotification { + // No-op, but implemented in case this token is passed to + // `-[RLMRealm commitWriteTransactionWithoutNotifying:]`. +} + +- (void)invalidate { + if (auto session = _session.lock()) { + session->unregister_progress_notifier(_token); + _session.reset(); + _token = 0; + } +} + +- (void)dealloc { + if (_token != 0) { + NSLog(@"RLMProgressNotificationToken released without unregistering a notification. " + @"You must hold on to the RLMProgressNotificationToken and call " + @"-[RLMProgressNotificationToken invalidate] when you no longer wish to receive " + @"progress update notifications."); + } +} + +- (nullable instancetype)initWithTokenValue:(uint64_t)token + session:(std::shared_ptr)session { + if (token == 0) { + return nil; + } + if (self = [super init]) { + _token = token; + _session = session; + return self; + } + return nil; +} + +@end + +@interface RLMSyncSession () +@property (class, nonatomic, readonly) dispatch_queue_t notificationsQueue; +@property (atomic, readwrite) RLMSyncConnectionState connectionState; +@end + +@implementation RLMSyncSession + ++ (dispatch_queue_t)notificationsQueue { + static dispatch_queue_t queue; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + queue = dispatch_queue_create("io.realm.sync.sessionsNotificationQueue", DISPATCH_QUEUE_SERIAL); + }); + return queue; +} + +static RLMSyncConnectionState convertConnectionState(SyncSession::ConnectionState state) { + switch (state) { + case SyncSession::ConnectionState::Disconnected: return RLMSyncConnectionStateDisconnected; + case SyncSession::ConnectionState::Connecting: return RLMSyncConnectionStateConnecting; + case SyncSession::ConnectionState::Connected: return RLMSyncConnectionStateConnected; + } +} + +- (instancetype)initWithSyncSession:(std::shared_ptr const&)session { + if (self = [super init]) { + _session = session; + _connectionState = convertConnectionState(session->connection_state()); + // No need to save the token as RLMSyncSession always outlives the + // underlying SyncSession + session->register_connection_change_callback([=](auto, auto newState) { + dispatch_async(dispatch_get_main_queue(), ^{ + self.connectionState = convertConnectionState(newState); + }); + }); + return self; + } + return nil; +} + +- (RLMSyncConfiguration *)configuration { + if (auto session = _session.lock()) { + return [[RLMSyncConfiguration alloc] initWithRawConfig:session->config()]; + } + return nil; +} + +- (NSURL *)realmURL { + if (auto session = _session.lock()) { + if (auto url = session->full_realm_url()) { + return [NSURL URLWithString:@(url->c_str())]; + } + } + return nil; +} + +- (RLMSyncUser *)parentUser { + if (auto session = _session.lock()) { + return [[RLMSyncUser alloc] initWithSyncUser:session->user()]; + } + return nil; +} + +- (RLMSyncSessionState)state { + if (auto session = _session.lock()) { + if (session->state() == SyncSession::PublicState::Inactive) { + return RLMSyncSessionStateInactive; + } + return RLMSyncSessionStateActive; + } + return RLMSyncSessionStateInvalid; +} + +- (void)suspend { + if (auto session = _session.lock()) { + session->log_out(); + } +} + +- (void)resume { + if (auto session = _session.lock()) { + session->revive_if_needed(); + } +} + +- (BOOL)waitForUploadCompletionOnQueue:(dispatch_queue_t)queue callback:(void(^)(NSError *))callback { + if (auto session = _session.lock()) { + queue = queue ?: dispatch_get_main_queue(); + session->wait_for_upload_completion([=](std::error_code err) { + NSError *error = (err == std::error_code{}) ? nil : make_sync_error(err); + dispatch_async(queue, ^{ + callback(error); + }); + }); + return YES; + } + return NO; +} + +- (BOOL)waitForDownloadCompletionOnQueue:(dispatch_queue_t)queue callback:(void(^)(NSError *))callback { + if (auto session = _session.lock()) { + queue = queue ?: dispatch_get_main_queue(); + session->wait_for_download_completion([=](std::error_code err) { + NSError *error = (err == std::error_code{}) ? nil : make_sync_error(err); + dispatch_async(queue, ^{ + callback(error); + }); + }); + return YES; + } + return NO; +} + +- (RLMProgressNotificationToken *)addProgressNotificationForDirection:(RLMSyncProgressDirection)direction + mode:(RLMSyncProgressMode)mode + block:(RLMProgressNotificationBlock)block { + if (auto session = _session.lock()) { + dispatch_queue_t queue = RLMSyncSession.notificationsQueue; + auto notifier_direction = (direction == RLMSyncProgressDirectionUpload + ? SyncSession::NotifierType::upload + : SyncSession::NotifierType::download); + bool is_streaming = (mode == RLMSyncProgressModeReportIndefinitely); + uint64_t token = session->register_progress_notifier([=](uint64_t transferred, uint64_t transferrable) { + dispatch_async(queue, ^{ + block((NSUInteger)transferred, (NSUInteger)transferrable); + }); + }, notifier_direction, is_streaming); + return [[RLMProgressNotificationToken alloc] initWithTokenValue:token session:std::move(session)]; + } + return nil; +} + ++ (void)immediatelyHandleError:(RLMSyncErrorActionToken *)token { + if (!token->_isValid) { + return; + } + token->_isValid = NO; + SyncManager::shared().immediately_run_file_actions(std::move(token->_originalPath)); +} + ++ (nullable RLMSyncSession *)sessionForRealm:(RLMRealm *)realm { + auto& config = realm->_realm->config().sync_config; + if (!config) { + return nil; + } + if (auto session = config->user->session_for_on_disk_path(realm->_realm->config().path)) { + return [[RLMSyncSession alloc] initWithSyncSession:session]; + } + return nil; +} + +@end + +// MARK: - Error action token + +@implementation RLMSyncErrorActionToken + +- (instancetype)initWithOriginalPath:(std::string)originalPath { + if (self = [super init]) { + _isValid = YES; + _originalPath = std::move(originalPath); + return self; + } + return nil; +} + +@end + +@implementation RLMAsyncOpenTask { + bool _cancel; + NSMutableArray *_blocks; +} + +- (void)addProgressNotificationOnQueue:(dispatch_queue_t)queue block:(RLMProgressNotificationBlock)block { + auto wrappedBlock = ^(NSUInteger transferred_bytes, NSUInteger transferrable_bytes) { + dispatch_async(queue, ^{ + @autoreleasepool { + block(transferred_bytes, transferrable_bytes); + } + }); + }; + + @synchronized (self) { + if (_task) { + _task->register_download_progress_notifier(wrappedBlock); + } + else if (!_cancel) { + if (!_blocks) { + _blocks = [NSMutableArray new]; + } + [_blocks addObject:wrappedBlock]; + } + } +} + +- (void)addProgressNotificationBlock:(RLMProgressNotificationBlock)block { + [self addProgressNotificationOnQueue:dispatch_get_main_queue() block:block]; +} + +- (void)cancel { + @synchronized (self) { + if (_task) { + _task->cancel(); + } + else { + _cancel = true; + _blocks = nil; + } + } +} + +- (void)setTask:(std::shared_ptr)task { + @synchronized (self) { + _task = task; + if (_cancel) { + _task->cancel(); + } + for (RLMProgressNotificationBlock block in _blocks) { + _task->register_download_progress_notifier(block); + } + _blocks = nil; + } +} +@end diff --git a/!main project/Pods/Realm/Realm/RLMSyncSessionRefreshHandle.mm b/!main project/Pods/Realm/Realm/RLMSyncSessionRefreshHandle.mm new file mode 100644 index 0000000..b2de98d --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMSyncSessionRefreshHandle.mm @@ -0,0 +1,278 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMSyncSessionRefreshHandle.hpp" + +#import "RLMJSONModels.h" +#import "RLMNetworkClient.h" +#import "RLMSyncManager_Private.h" +#import "RLMSyncUser_Private.hpp" +#import "RLMSyncUtil_Private.hpp" +#import "RLMUtil.hpp" + +#import "sync/sync_session.hpp" + +using namespace realm; + +namespace { + +void unregisterRefreshHandle(const std::weak_ptr& user, const std::string& path) { + if (auto strong_user = user.lock()) { + context_for(strong_user).unregister_refresh_handle(path); + } +} + +void reportInvalidAccessToken(const std::weak_ptr& user, NSError *error) { + if (auto strong_user = user.lock()) { + if (RLMUserErrorReportingBlock block = context_for(strong_user).error_handler()) { + RLMSyncUser *theUser = [[RLMSyncUser alloc] initWithSyncUser:std::move(strong_user)]; + [theUser logOut]; + block(theUser, error); + } + } +} + +} + +static const NSTimeInterval RLMRefreshBuffer = 10; + +@interface RLMSyncSessionRefreshHandle () { + std::weak_ptr _user; + std::string _path; + std::weak_ptr _session; + std::shared_ptr _strongSession; +} + +@property (nonatomic) dispatch_source_t timer; + +@property (nonatomic) NSURL *realmURL; +@property (nonatomic) NSURL *authServerURL; +@property (nonatomic, copy) RLMSyncBasicErrorReportingBlock completionBlock; + +@end + +@implementation RLMSyncSessionRefreshHandle + +- (instancetype)initWithRealmURL:(NSURL *)realmURL + user:(std::shared_ptr)user + session:(std::shared_ptr)session + completionBlock:(RLMSyncBasicErrorReportingBlock)completionBlock { + if (self = [super init]) { + NSString *path = [realmURL path]; + _path = [path UTF8String]; + self.authServerURL = [NSURL URLWithString:@(user->server_url().c_str())]; + if (!self.authServerURL) { + @throw RLMException(@"User object isn't configured with an auth server URL."); + } + self.completionBlock = completionBlock; + self.realmURL = realmURL; + // For the initial bind, we want to prolong the session's lifetime. + _strongSession = std::move(session); + _session = _strongSession; + _user = user; + // Immediately fire off the network request. + [self _timerFired]; + return self; + } + return nil; +} + +- (void)dealloc { + [self cancelTimer]; +} + +- (void)invalidate { + _strongSession = nullptr; + [self cancelTimer]; +} + +- (void)cancelTimer { + if (self.timer) { + dispatch_source_cancel(self.timer); + self.timer = nil; + } +} + ++ (NSDate *)fireDateForTokenExpirationDate:(NSDate *)date nowDate:(NSDate *)nowDate { + NSDate *fireDate = [date dateByAddingTimeInterval:-RLMRefreshBuffer]; + // Only fire times in the future are valid. + return ([fireDate compare:nowDate] == NSOrderedDescending ? fireDate : nil); +} + +- (void)scheduleRefreshTimer:(NSDate *)dateWhenTokenExpires { + [self cancelTimer]; + + NSDate *fireDate = [RLMSyncSessionRefreshHandle fireDateForTokenExpirationDate:dateWhenTokenExpires + nowDate:[NSDate date]]; + if (!fireDate) { + unregisterRefreshHandle(_user, _path); + return; + } + + NSTimeInterval timeToExpiration = [fireDate timeIntervalSinceNow]; + self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); + dispatch_source_set_timer(self.timer, dispatch_time(DISPATCH_TIME_NOW, timeToExpiration * NSEC_PER_SEC), + /* interval */ DISPATCH_TIME_FOREVER, + /* leeway */ NSEC_PER_SEC * (timeToExpiration / 10)); + dispatch_source_set_event_handler(self.timer, ^{ [self _timerFired]; }); + dispatch_resume(self.timer); +} + +/// Handler for network requests whose responses successfully parse into an auth response model. +- (BOOL)_handleSuccessfulRequest:(RLMAuthResponseModel *)model session:(SyncSession&)session { + // Realm Cloud will give us a url prefix in the auth response that we need + // to pass onto objectstore to have it connect to the proper sync worker + if (model.urlPrefix) { + session.set_url_prefix(model.urlPrefix.UTF8String); + } + + // Calculate the resolved path. + NSString *resolvedURLString = nil; + RLMServerPath resolvedPath = model.accessToken.tokenData.path; + // Munge the path back onto the original URL, because the `sync` API expects an entire URL. + NSURLComponents *urlBuffer = [NSURLComponents componentsWithURL:self.realmURL + resolvingAgainstBaseURL:YES]; + urlBuffer.path = resolvedPath; + resolvedURLString = [[urlBuffer URL] absoluteString]; + if (!resolvedURLString) { + @throw RLMException(@"Resolved path returned from the server was invalid (%@).", resolvedPath); + } + // Pass the token and resolved path to the underlying sync subsystem. + session.refresh_access_token([model.accessToken.token UTF8String], {resolvedURLString.UTF8String}); + + // Schedule a refresh. If we're successful we must already have `bind()`ed the session + // initially, so we can null out the strong pointer. + _strongSession = nullptr; + NSDate *expires = [NSDate dateWithTimeIntervalSince1970:model.accessToken.tokenData.expires]; + [self scheduleRefreshTimer:expires]; + + if (self.completionBlock) { + self.completionBlock(nil); + } + return true; +} + +/// Handler for network requests that failed before the JSON parsing stage. +- (void)_handleFailedRequest:(NSError *)error { + NSError *authError; + if ([error.domain isEqualToString:RLMSyncAuthErrorDomain]) { + // Network client may return sync related error + authError = error; + // Try to report this error to the expiration callback. + reportInvalidAccessToken(_user, authError); + } else { + // Something else went wrong + authError = make_auth_error_bad_response(); + } + if (self.completionBlock) { + self.completionBlock(authError); + } + [[RLMSyncManager sharedManager] _fireError:make_sync_error(authError)]; + // Certain errors related to network connectivity should trigger a retry. + NSDate *nextTryDate = nil; + if ([error.domain isEqualToString:NSURLErrorDomain]) { + switch (error.code) { + case NSURLErrorCannotConnectToHost: + case NSURLErrorNotConnectedToInternet: + case NSURLErrorNetworkConnectionLost: + case NSURLErrorTimedOut: + case NSURLErrorDNSLookupFailed: + case NSURLErrorCannotFindHost: + // FIXME: 10 seconds is an arbitrarily chosen value, consider rationalizing it. + nextTryDate = [NSDate dateWithTimeIntervalSinceNow:RLMRefreshBuffer + 10]; + break; + default: + break; + } + } + if (!nextTryDate) { + // This error isn't a network failure error. Just invalidate the refresh handle and stop. + if (_strongSession) { + _strongSession->log_out(); + } + unregisterRefreshHandle(_user, _path); + [self invalidate]; + return; + } + // If we tried to initially bind the session and failed, we'll try again. However, each + // subsequent attempt will use a weak pointer to avoid prolonging the session's lifetime + // unnecessarily. + _strongSession = nullptr; + [self scheduleRefreshTimer:nextTryDate]; + return; +} + +/// Callback handler for network requests. +- (BOOL)_onRefreshCompletionWithError:(NSError *)error json:(NSDictionary *)json { + std::shared_ptr session = _session.lock(); + if (!session) { + // The session is dead or in a fatal error state. + unregisterRefreshHandle(_user, _path); + [self invalidate]; + return NO; + } + + if (json && !error) { + RLMAuthResponseModel *model = [[RLMAuthResponseModel alloc] initWithDictionary:json + requireAccessToken:YES + requireRefreshToken:NO]; + if (model) { + return [self _handleSuccessfulRequest:model session:*session]; + } + // Otherwise, malformed JSON + unregisterRefreshHandle(_user, _path); + NSError *error = make_sync_error(make_auth_error_bad_response(json)); + if (self.completionBlock) { + self.completionBlock(error); + } + [[RLMSyncManager sharedManager] _fireError:error]; + } else { + REALM_ASSERT(error); + [self _handleFailedRequest:error]; + } + return NO; +} + +- (void)_timerFired { + RLMServerToken refreshToken = nil; + if (auto user = _user.lock()) { + refreshToken = @(user->refresh_token().c_str()); + } + if (!refreshToken) { + unregisterRefreshHandle(_user, _path); + return; + } + + NSDictionary *json = @{ + kRLMSyncProviderKey: @"realm", + kRLMSyncPathKey: @(_path.c_str()), + kRLMSyncDataKey: refreshToken, + kRLMSyncAppIDKey: [RLMSyncManager sharedManager].appID, + }; + + __weak RLMSyncSessionRefreshHandle *weakSelf = self; + RLMSyncCompletionBlock handler = ^(NSError *error, NSDictionary *json) { + [weakSelf _onRefreshCompletionWithError:error json:json]; + }; + [RLMSyncAuthEndpoint sendRequestToServer:self.authServerURL + JSON:json + timeout:60.0 + completion:handler]; +} + +@end diff --git a/!main project/Pods/Realm/Realm/RLMSyncSubscription.mm b/!main project/Pods/Realm/Realm/RLMSyncSubscription.mm new file mode 100644 index 0000000..b17c033 --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMSyncSubscription.mm @@ -0,0 +1,443 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2018 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMSyncSubscription.h" + +#import "RLMObjectSchema_Private.hpp" +#import "RLMObject_Private.hpp" +#import "RLMProperty_Private.hpp" +#import "RLMRealm_Private.hpp" +#import "RLMResults_Private.hpp" +#import "RLMUtil.hpp" + +#import "object_store.hpp" +#import "sync/partial_sync.hpp" + +using namespace realm; + +@implementation RLMSyncSubscriptionOptions +@end + +@interface RLMSyncSubscription () +@property (nonatomic, readwrite) RLMSyncSubscriptionState state; +@property (nonatomic, readwrite, nullable) NSError *error; +@property (nonatomic, readwrite) NSString *query; +@property (nonatomic, readwrite, nullable) NSDate *createdAt; +@property (nonatomic, readwrite, nullable) NSDate *updatedAt; +@property (nonatomic, readwrite, nullable) NSDate *expiresAt; +@property (nonatomic, readwrite) NSTimeInterval timeToLive; +@end + +@implementation RLMSyncSubscription { + partial_sync::SubscriptionNotificationToken _token; + util::Optional _subscription; + Object _obj; + RLMRealm *_realm; +} + +static std::vector parseKeypath(StringData keypath, Group const& group, + Schema const& schema, const ObjectSchema *objectSchema) { + auto check = [&](bool condition, const char* fmt, auto... args) { + if (!condition) { + throw std::invalid_argument(util::format("Invalid LinkingObjects inclusion from key path '%1': %2.", + keypath, util::format(fmt, args...))); + } + }; + + const char* begin = keypath.data(); + const char* end = keypath.data() + keypath.size(); + check(begin != end, "missing property name"); + + std::vector ret; + while (begin != end) { + auto sep = std::find(begin, end, '.'); + check(sep != begin && sep + 1 != end, "missing property name"); + StringData key(begin, sep - begin); + begin = sep + (sep != end); + + auto prop = objectSchema->property_for_name(key); + check(prop, "property '%1.%2' does not exist", objectSchema->name, key); + check(prop->type == PropertyType::Object || prop->type == PropertyType::LinkingObjects, + "property '%1.%2' is of unsupported type '%3'", + objectSchema->name, key, string_for_property_type(prop->type)); + + objectSchema = &*schema.find(prop->object_type); + + if (prop->type == PropertyType::Object) { + check(begin != end, "key path must end in a LinkingObjects property and '%1.%2' is of type '%3'", + objectSchema->name, key, string_for_property_type(prop->type)); + ret.emplace_back(prop->table_column); + } + else { + ret.emplace_back(objectSchema->property_for_name(prop->link_origin_property_name)->table_column, + ObjectStore::table_for_object_type(group, objectSchema->name)); + } + } + return ret; +} + +- (instancetype)initWithOptions:(RLMSyncSubscriptionOptions *)options results:(Results const&)results realm:(RLMRealm *)realm { + if (!(self = [super init])) + return nil; + + _name = [options.name copy]; + _timeToLive = NAN; + _realm = realm; + _createdAt = _updatedAt = NSDate.date; + try { + partial_sync::SubscriptionOptions opt; + if (options.name) { + opt.user_provided_name = std::string(RLMStringDataWithNSString(options.name)); + } + if (options.timeToLive > 0) { + opt.time_to_live_ms = options.timeToLive * 1000; + } + opt.update = options.overwriteExisting; + if (options.includeLinkingObjectProperties) { + std::vector> keypaths; + for (NSString *keyPath in options.includeLinkingObjectProperties) { + keypaths.push_back(parseKeypath(keyPath.UTF8String, realm.group, + realm->_realm->schema(), + &results.get_object_schema())); + } + opt.inclusions = IncludeDescriptor{*ObjectStore::table_for_object_type(realm.group, results.get_object_type()), keypaths}; + } + _subscription = partial_sync::subscribe(options.limit ? results.limit(options.limit) : results, std::move(opt)); + } + catch (std::exception const& e) { + @throw RLMException(e); + } + self.state = (RLMSyncSubscriptionState)_subscription->state(); + __weak auto weakSelf = self; + _token = _subscription->add_notification_callback([weakSelf] { + RLMSyncSubscription *self; + @autoreleasepool { + self = weakSelf; + if (!self) + return; + } + + // Retrieve the current error and status. Update our properties only if the values have changed, + // since clients use KVO to observe these properties. + + if (auto error = self->_subscription->error()) { + try { + std::rethrow_exception(error); + } catch (...) { + NSError *nsError; + RLMRealmTranslateException(&nsError); + if (!self.error || ![self.error isEqual:nsError]) + self.error = nsError; + } + } + else if (self.error) { + self.error = nil; + } + + auto status = (RLMSyncSubscriptionState)self->_subscription->state(); + if (status != self.state) { + if (status == RLMSyncSubscriptionStateCreating) { + // If a subscription is deleted without going through this + // object's unsubscribe() method the subscription will transition + // back to Creating rather than Invalidated since it doesn't + // have a good way to track that it previously existed + if (self.state != RLMSyncSubscriptionStateInvalidated) + self.state = RLMSyncSubscriptionStateInvalidated; + } + else { + self.state = status; + } + } + + if (status != RLMSyncSubscriptionStateComplete) { + return; + } + + auto obj = self->_subscription->result_set_object(); + if (obj && obj->is_valid()) { + _obj = std::move(*obj); + _token = {}; + _token.result_sets_token = _obj.add_notification_callback([weakSelf](CollectionChangeSet const&, std::exception_ptr) { + @autoreleasepool { + [weakSelf updateFromRow]; + } + }); + [self updateFromRow]; + } + }); + + return self; +} + +- (void)unsubscribe { + partial_sync::unsubscribe(*_subscription); +} + +- (void)updateFromRow { + // We only want to call the setter if the value actually changed because of KVO +#define REALM_SET_IF_CHANGED(prop, value) do { \ + auto newValue = value; \ + if (prop != newValue) { \ + prop = newValue; \ + } \ +} while (0) + + if (!_obj.is_valid()) { + REALM_SET_IF_CHANGED(self.state, RLMSyncSubscriptionStateInvalidated); + return; + } + + auto row = _obj.row(); + REALM_SET_IF_CHANGED(self.query, RLMStringDataToNSString(row.get_string(row.get_column_index("query")))); + REALM_SET_IF_CHANGED(self.createdAt, RLMTimestampToNSDate(row.get_timestamp(row.get_column_index("created_at")))); + REALM_SET_IF_CHANGED(self.updatedAt, RLMTimestampToNSDate(row.get_timestamp(row.get_column_index("updated_at")))); + REALM_SET_IF_CHANGED(self.expiresAt, RLMTimestampToNSDate(row.get_timestamp(row.get_column_index("expires_at")))); +#undef REALM_SET_IF_CHANGED + + auto ttl = row.get>(row.get_column_index("time_to_live")); + if (ttl && _timeToLive != *ttl / 1000.0) { + self.timeToLive = *ttl / 1000.0; + } + else if (!ttl && !isnan(_timeToLive)) { + self.timeToLive = NAN; + } +} +@end + +@interface RLMSyncSubscriptionObject : RLMObjectBase +@end +@implementation RLMSyncSubscriptionObject { + util::Optional _token; + Object _obj; +} + ++ (NSString *)primaryKey { + return nil; +} + ++ (NSDictionary *)defaultPropertyValues { + return nil; +} + +- (NSString *)name { + return _row.is_attached() ? RLMStringDataToNSString(_row.get_string(_row.get_column_index("name"))) : nil; +} + +- (NSString *)query { + return _row.is_attached() ? RLMStringDataToNSString(_row.get_string(_row.get_column_index("query"))) : nil; +} + +- (RLMSyncSubscriptionState)state { + if (!_row.is_attached()) { + return RLMSyncSubscriptionStateInvalidated; + } + return (RLMSyncSubscriptionState)_row.get_int(_row.get_column_index("status")); +} + +- (NSError *)error { + if (!_row.is_attached()) { + return nil; + } + StringData err = _row.get_string(_row.get_column_index("error_message")); + if (!err.size()) { + return nil; + } + return [NSError errorWithDomain:RLMErrorDomain + code:RLMErrorFail + userInfo:@{NSLocalizedDescriptionKey: RLMStringDataToNSString(err)}]; +} + +- (NSDate *)createdAt { + return _row.is_attached() ? RLMTimestampToNSDate(_row.get_timestamp(_row.get_column_index("created_at"))) : nil; +} + +- (NSDate *)updatedAt { + return _row.is_attached() ? RLMTimestampToNSDate(_row.get_timestamp(_row.get_column_index("updated_at"))) : nil; +} + +- (NSDate *)expiresAt { + return _row.is_attached() ? RLMTimestampToNSDate(_row.get_timestamp(_row.get_column_index("expires_at"))) : nil; +} + +- (NSTimeInterval)timeToLive { + if (!_row.is_attached()) { + return NAN; + } + auto columnIndex = _row.get_column_index("time_to_live"); + if (_row.is_null(columnIndex)) { + return NAN; + } + return _row.get_int(columnIndex) / 1000.0; +} + +- (NSString *)descriptionWithMaxDepth:(NSUInteger)depth { + if (depth == 0) { + return @""; + } + + auto objectType = _row.get_string(_row.get_column_index("matches_property")); + objectType = objectType.substr(0, objectType.size() - strlen("_matches")); + return [NSString stringWithFormat:@"RLMSyncSubscription {\n\tname = %@\n\tobjectType = %@\n\tquery = %@\n\tstatus = %@\n\terror = %@\n\tcreatedAt = %@\n\tupdatedAt = %@\n\texpiresAt = %@\n\ttimeToLive = %@\n}", + self.name, RLMStringDataToNSString(objectType), + RLMStringDataToNSString(_row.get_string(_row.get_column_index("query"))), + @(self.state), self.error, self.createdAt, self.updatedAt, self.expiresAt, @(self.timeToLive)]; +} + +- (void)unsubscribe { + if (_row) { + partial_sync::unsubscribe(Object(_realm->_realm, *_info->objectSchema, _row)); + } +} + +- (void)addObserver:(id)observer + forKeyPath:(NSString *)keyPath + options:(NSKeyValueObservingOptions)options + context:(void *)context { + // Make the `state` property observable by using an object notifier to + // trigger changes. The normal KVO mechanisms don't work for this class due + // to it not being a normal part of the schema. + if (!_token) { + struct { + __weak RLMSyncSubscriptionObject *weakSelf; + + void before(realm::CollectionChangeSet const&) { + @autoreleasepool { + [weakSelf willChangeValueForKey:@"state"]; + } + } + + void after(realm::CollectionChangeSet const&) { + @autoreleasepool { + [weakSelf didChangeValueForKey:@"state"]; + } + } + + void error(std::exception_ptr) {} + } callback{self}; + _obj = Object(_realm->_realm, *_info->objectSchema, _row); + _token = _obj.add_notification_callback(callback); + } + [super addObserver:observer forKeyPath:keyPath options:options context:context]; +} +@end + +static ObjectSchema& addPublicNames(ObjectSchema& os) { + using namespace partial_sync; + os.property_for_name(property_created_at)->public_name = "createdAt"; + os.property_for_name(property_updated_at)->public_name = "updatedAt"; + os.property_for_name(property_expires_at)->public_name = "expiresAt"; + os.property_for_name(property_time_to_live)->public_name = "timeToLive"; + os.property_for_name(property_error_message)->public_name = "error"; + return os; +} + +// RLMClassInfo stores pointers into the schema rather than owning the objects +// it points to, so for a ClassInfo that's not part of the schema we need a +// wrapper object that owns them +RLMResultsSetInfo::RLMResultsSetInfo(__unsafe_unretained RLMRealm *const realm) +: osObjectSchema(realm->_realm->read_group(), partial_sync::result_sets_type_name) +, rlmObjectSchema([RLMObjectSchema objectSchemaForObjectStoreSchema:addPublicNames(osObjectSchema)]) +, info(realm, rlmObjectSchema, &osObjectSchema) +{ + rlmObjectSchema.accessorClass = [RLMSyncSubscriptionObject class]; +} + +RLMClassInfo& RLMResultsSetInfo::get(__unsafe_unretained RLMRealm *const realm) { + if (!realm->_resultsSetInfo) { + realm->_resultsSetInfo = std::make_unique(realm); + } + return realm->_resultsSetInfo->info; +} + +@interface RLMSubscriptionResults : RLMResults +@end + +@implementation RLMSubscriptionResults ++ (instancetype)resultsWithRealm:(RLMRealm *)realm { + auto table = ObjectStore::table_for_object_type(realm->_realm->read_group(), partial_sync::result_sets_type_name); + if (!table) { + @throw RLMException(@"-[RLMRealm subscriptions] can only be called on a Realm using query-based sync"); + } + // The server automatically adds a few subscriptions for the permissions + // types which we want to hide. They're just an implementation detail and + // deleting them won't work out well for the user. + auto query = table->where().ends_with(table->get_column_index("matches_property"), "_matches"); + return [self resultsWithObjectInfo:RLMResultsSetInfo::get(realm) + results:Results(realm->_realm, std::move(query))]; +} + +// These operations require a valid schema for the type. It's unclear how they +// would be useful so it's probably not worth fixing this. +- (RLMResults *)sortedResultsUsingDescriptors:(__unused NSArray *)properties { + @throw RLMException(@"Sorting subscription results is currently not implemented"); +} + +- (RLMResults *)distinctResultsUsingKeyPaths:(__unused NSArray *)keyPaths { + @throw RLMException(@"Distincting subscription results is currently not implemented"); +} +@end + +@implementation RLMResults (SyncSubscription) +- (RLMSyncSubscription *)subscribe { + return [[RLMSyncSubscription alloc] initWithOptions:nil results:_results realm:self.realm]; +} + +- (RLMSyncSubscription *)subscribeWithName:(NSString *)subscriptionName { + auto options = [[RLMSyncSubscriptionOptions alloc] init]; + options.name = subscriptionName; + return [[RLMSyncSubscription alloc] initWithOptions:options results:_results realm:self.realm]; +} + +- (RLMSyncSubscription *)subscribeWithName:(NSString *)subscriptionName limit:(NSUInteger)limit { + auto options = [[RLMSyncSubscriptionOptions alloc] init]; + options.name = subscriptionName; + options.limit = limit; + return [[RLMSyncSubscription alloc] initWithOptions:options results:_results realm:self.realm]; +} + +- (RLMSyncSubscription *)subscribeWithOptions:(RLMSyncSubscriptionOptions *)options { + return [[RLMSyncSubscription alloc] initWithOptions:options results:_results realm:self.realm]; +} +@end + +@implementation RLMRealm (SyncSubscription) +- (RLMResults *)subscriptions { + [self verifyThread]; + return [RLMSubscriptionResults resultsWithRealm:self]; +} + +- (nullable RLMSyncSubscription *)subscriptionWithName:(NSString *)name { + [self verifyThread]; + auto& info = RLMResultsSetInfo::get(self); + if (!info.table()) { + @throw RLMException(@"-[RLMRealm subcriptionWithName:] can only be called on a Realm using query-based sync"); + } + auto row = info.table()->find_first(info.table()->get_column_index("name"), + RLMStringDataWithNSString(name)); + if (row == npos) { + return nil; + } + RLMObjectBase *acc = RLMCreateManagedAccessor(info.rlmObjectSchema.accessorClass, &info); + acc->_row = info.table()->get(row); + return (RLMSyncSubscription *)acc; +} +@end + +RLMSyncSubscription *RLMCastToSyncSubscription(id obj) { + return obj; +} diff --git a/!main project/Pods/Realm/Realm/RLMSyncUser.mm b/!main project/Pods/Realm/Realm/RLMSyncUser.mm new file mode 100644 index 0000000..1c60cd2 --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMSyncUser.mm @@ -0,0 +1,686 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMSyncUser_Private.hpp" + +#import "RLMJSONModels.h" +#import "RLMNetworkClient.h" +#import "RLMRealmConfiguration+Sync.h" +#import "RLMRealmConfiguration_Private.hpp" +#import "RLMRealmUtil.hpp" +#import "RLMResults_Private.hpp" +#import "RLMSyncConfiguration.h" +#import "RLMSyncConfiguration_Private.hpp" +#import "RLMSyncManager_Private.h" +#import "RLMSyncPermission.h" +#import "RLMSyncSessionRefreshHandle.hpp" +#import "RLMSyncSession_Private.hpp" +#import "RLMSyncUtil_Private.hpp" +#import "RLMUtil.hpp" + +#import "sync/sync_manager.hpp" +#import "sync/sync_session.hpp" +#import "sync/sync_user.hpp" + +using namespace realm; + +void CocoaSyncUserContext::register_refresh_handle(const std::string& path, RLMSyncSessionRefreshHandle *handle) +{ + REALM_ASSERT(handle); + std::lock_guard lock(m_mutex); + auto& refresh_handle = m_refresh_handles[path]; + [refresh_handle invalidate]; + refresh_handle = handle; +} + +void CocoaSyncUserContext::unregister_refresh_handle(const std::string& path) +{ + std::lock_guard lock(m_mutex); + m_refresh_handles.erase(path); +} + +void CocoaSyncUserContext::invalidate_all_handles() +{ + std::lock_guard lock(m_mutex); + for (auto& it : m_refresh_handles) { + [it.second invalidate]; + } + m_refresh_handles.clear(); +} + +RLMUserErrorReportingBlock CocoaSyncUserContext::error_handler() const +{ + std::lock_guard lock(m_error_handler_mutex); + return m_error_handler; +} + +void CocoaSyncUserContext::set_error_handler(RLMUserErrorReportingBlock block) +{ + std::lock_guard lock(m_error_handler_mutex); + m_error_handler = block; +} + +@interface RLMSyncUserInfo () + +@property (nonatomic, readwrite) NSArray *accounts; +@property (nonatomic, readwrite) NSDictionary *metadata; +@property (nonatomic, readwrite) NSString *identity; +@property (nonatomic, readwrite) BOOL isAdmin; + ++ (instancetype)syncUserInfoWithModel:(RLMUserResponseModel *)model; + +@end + +@interface RLMSyncUser () { + std::shared_ptr _user; +} +@end + +@implementation RLMSyncUser + +#pragma mark - static API + ++ (NSDictionary *)allUsers { + NSArray *allUsers = [[RLMSyncManager sharedManager] _allUsers]; + return [NSDictionary dictionaryWithObjects:allUsers + forKeys:[allUsers valueForKey:@"identity"]]; +} + ++ (RLMSyncUser *)currentUser { + NSArray *allUsers = [[RLMSyncManager sharedManager] _allUsers]; + if (allUsers.count > 1 && [NSSet setWithArray:[allUsers valueForKey:@"identity"]].count > 1) { + @throw RLMException(@"+currentUser cannot be called if more that one valid, logged-in user exists."); + } + return allUsers.firstObject; +} + +#pragma mark - API + +- (instancetype)initWithSyncUser:(std::shared_ptr)user { + if (self = [super init]) { + _user = user; + return self; + } + return nil; +} + +- (BOOL)isEqual:(id)object { + if (![object isKindOfClass:[RLMSyncUser class]]) { + return NO; + } + return _user == ((RLMSyncUser *)object)->_user; +} + ++ (void)logInWithCredentials:(RLMSyncCredentials *)credential + authServerURL:(NSURL *)authServerURL + onCompletion:(RLMUserCompletionBlock)completion { + [self logInWithCredentials:credential + authServerURL:authServerURL + timeout:0 // use timeout from RLMSyncManager + callbackQueue:dispatch_get_main_queue() + onCompletion:completion]; +} + ++ (void)logInWithCredentials:(RLMSyncCredentials *)credentials + authServerURL:(NSURL *)authServerURL + timeout:(NSTimeInterval)timeout + callbackQueue:(dispatch_queue_t)callbackQueue + onCompletion:(RLMUserCompletionBlock)completion { + // Special credential login should be treated differently. + if (credentials.provider == RLMIdentityProviderAccessToken) { + [self _performLoginForDirectAccessTokenCredentials:credentials + authServerURL:authServerURL + completionBlock:completion]; + return; + } + if (credentials.provider == RLMIdentityProviderCustomRefreshToken) { + [self _performLoginForCustomRefreshTokenCredentials:credentials + authServerURL:authServerURL + completionBlock:completion]; + return; + } + if (!authServerURL) { + @throw RLMException(@"A user cannot be logged in without specifying an authentication server URL."); + } + + // Prepare login network request + NSMutableDictionary *json = [@{ + kRLMSyncProviderKey: credentials.provider, + kRLMSyncDataKey: credentials.token, + kRLMSyncAppIDKey: RLMSyncManager.sharedManager.appID, + } mutableCopy]; + if (credentials.userInfo.count) { + // Munge user info into the JSON request. + json[@"user_info"] = credentials.userInfo; + } + + RLMSyncCompletionBlock handler = ^(NSError *error, NSDictionary *json) { + if (error) { + return completion(nil, error); + } + + RLMAuthResponseModel *model = [[RLMAuthResponseModel alloc] initWithDictionary:json + requireAccessToken:NO + requireRefreshToken:YES]; + if (!model) { + // Malformed JSON + return completion(nil, make_auth_error_bad_response(json)); + } + + SyncUserIdentifier identity{model.refreshToken.tokenData.identity.UTF8String, + authServerURL.absoluteString.UTF8String}; + auto sync_user = SyncManager::shared().get_user(identity , [model.refreshToken.token UTF8String]); + if (!sync_user) { + return completion(nil, make_auth_error_client_issue()); + } + sync_user->set_is_admin(model.refreshToken.tokenData.isAdmin); + return completion([[RLMSyncUser alloc] initWithSyncUser:std::move(sync_user)], nil); + }; + + [RLMSyncAuthEndpoint sendRequestToServer:authServerURL + JSON:json + timeout:timeout + completion:^(NSError *error, NSDictionary *dictionary) { + dispatch_async(callbackQueue, ^{ + handler(error, dictionary); + }); + }]; +} + +- (RLMRealmConfiguration *)configuration { + return [self configurationWithURL:nil + fullSynchronization:NO + enableSSLValidation:YES + urlPrefix:nil]; +} + +- (RLMRealmConfiguration *)configurationWithURL:(NSURL *)url { + return [self configurationWithURL:url + fullSynchronization:NO + enableSSLValidation:YES + urlPrefix:nil]; +} + +- (RLMRealmConfiguration *)configurationWithURL:(NSURL *)url fullSynchronization:(bool)fullSynchronization { + return [self configurationWithURL:url + fullSynchronization:fullSynchronization + enableSSLValidation:YES + urlPrefix:nil]; +} + +- (RLMRealmConfiguration *)configurationWithURL:(NSURL *)url + fullSynchronization:(bool)fullSynchronization + enableSSLValidation:(bool)enableSSLValidation + urlPrefix:(NSString * _Nullable)urlPrefix { + auto syncConfig = [[RLMSyncConfiguration alloc] initWithUser:self + realmURL:url ?: self.defaultRealmURL + customFileURL:nil + isPartial:!fullSynchronization + stopPolicy:RLMSyncStopPolicyAfterChangesUploaded]; + syncConfig.urlPrefix = urlPrefix; + syncConfig.enableSSLValidation = enableSSLValidation; + syncConfig.pinnedCertificateURL = RLMSyncManager.sharedManager.pinnedCertificatePaths[syncConfig.realmURL.host]; + RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init]; + config.syncConfiguration = syncConfig; + return config; +} + +- (void)logOut { + if (!_user) { + return; + } + _user->log_out(); + context_for(_user).invalidate_all_handles(); +} + +- (void)invalidate { + if (!_user) { + return; + } + context_for(_user).invalidate_all_handles(); + _user = nullptr; +} + +- (RLMUserErrorReportingBlock)errorHandler { + if (!_user) { + return nil; + } + return context_for(_user).error_handler(); +} + +- (void)setErrorHandler:(RLMUserErrorReportingBlock)errorHandler { + if (!_user) { + return; + } + context_for(_user).set_error_handler([errorHandler copy]); +} + +- (nullable RLMSyncSession *)sessionForURL:(NSURL *)url { + if (!_user) { + return nil; + } + auto path = SyncManager::shared().path_for_realm(*_user, [url.absoluteString UTF8String]); + if (auto session = _user->session_for_on_disk_path(path)) { + return [[RLMSyncSession alloc] initWithSyncSession:session]; + } + return nil; +} + +- (NSArray *)allSessions { + if (!_user) { + return @[]; + } + NSMutableArray *buffer = [NSMutableArray array]; + auto sessions = _user->all_sessions(); + for (auto session : sessions) { + [buffer addObject:[[RLMSyncSession alloc] initWithSyncSession:std::move(session)]]; + } + return [buffer copy]; +} + +- (NSString *)identity { + if (!_user) { + return nil; + } + return @(_user->identity().c_str()); +} + +- (RLMSyncUserState)state { + if (!_user) { + return RLMSyncUserStateError; + } + switch (_user->state()) { + case SyncUser::State::Active: + return RLMSyncUserStateActive; + case SyncUser::State::LoggedOut: + return RLMSyncUserStateLoggedOut; + case SyncUser::State::Error: + return RLMSyncUserStateError; + } +} + +- (NSURL *)authenticationServer { + if (!_user || _user->token_type() == SyncUser::TokenType::Admin) { + return nil; + } + return [NSURL URLWithString:@(_user->server_url().c_str())]; +} + +- (BOOL)isAdmin { + if (!_user) { + return NO; + } + return _user->is_admin(); +} + +#pragma mark - Passwords + +- (void)changePassword:(NSString *)newPassword completion:(RLMPasswordChangeStatusBlock)completion { + [self changePassword:newPassword forUserID:self.identity completion:completion]; +} + +- (void)changePassword:(NSString *)newPassword forUserID:(NSString *)userID completion:(RLMPasswordChangeStatusBlock)completion { + if (self.state != RLMSyncUserStateActive) { + completion([NSError errorWithDomain:RLMSyncErrorDomain + code:RLMSyncErrorClientSessionError + userInfo:nil]); + return; + } + [RLMSyncChangePasswordEndpoint sendRequestToServer:self.authenticationServer + JSON:@{kRLMSyncTokenKey: self.refreshToken, + kRLMSyncUserIDKey: userID, + kRLMSyncDataKey: @{kRLMSyncNewPasswordKey: newPassword}} + completion:completion]; +} + ++ (void)requestPasswordResetForAuthServer:(NSURL *)serverURL + userEmail:(NSString *)email + completion:(RLMPasswordChangeStatusBlock)completion { + [RLMSyncUpdateAccountEndpoint sendRequestToServer:serverURL + JSON:@{@"provider_id": email, @"data": @{@"action": @"reset_password"}} + completion:completion]; +} + ++ (void)completePasswordResetForAuthServer:(NSURL *)serverURL + token:(NSString *)token + password:(NSString *)newPassword + completion:(RLMPasswordChangeStatusBlock)completion { + [RLMSyncUpdateAccountEndpoint sendRequestToServer:serverURL + JSON:@{@"data": @{@"action": @"complete_reset", + @"token": token, + @"new_password": newPassword}} + completion:completion]; +} + ++ (void)requestEmailConfirmationForAuthServer:(NSURL *)serverURL + userEmail:(NSString *)email + completion:(RLMPasswordChangeStatusBlock)completion { + [RLMSyncUpdateAccountEndpoint sendRequestToServer:serverURL + JSON:@{@"provider_id": email, + @"data": @{@"action": @"request_email_confirmation"}} + completion:completion]; +} + ++ (void)confirmEmailForAuthServer:(NSURL *)serverURL + token:(NSString *)token + completion:(RLMPasswordChangeStatusBlock)completion { + [RLMSyncUpdateAccountEndpoint sendRequestToServer:serverURL + JSON:@{@"data": @{@"action": @"confirm_email", + @"token": token}} + completion:completion]; +} + +#pragma mark - Administrator API + +- (void)retrieveInfoForUser:(NSString *)providerUserIdentity + identityProvider:(RLMIdentityProvider)provider + completion:(RLMRetrieveUserBlock)completion { + [RLMSyncGetUserInfoEndpoint sendRequestToServer:self.authenticationServer + JSON:@{kRLMSyncProviderKey: provider, + kRLMSyncProviderIDKey: providerUserIdentity, + kRLMSyncTokenKey: self.refreshToken} + timeout:60 + completion:^(NSError *error, NSDictionary *json) { + if (error) { + return completion(nil, error); + } + RLMUserResponseModel *model = [[RLMUserResponseModel alloc] initWithDictionary:json]; + if (!model) { + return completion(nil, make_auth_error_bad_response(json)); + } + completion([RLMSyncUserInfo syncUserInfoWithModel:model], nil); + }]; +} + +#pragma mark - Permissions API + +namespace { +NSError *checkUser(std::shared_ptr const& user, NSString *msg) { + if (user && user->state() != SyncUser::State::Error) { + return nil; + } + msg = [NSString stringWithFormat:@"Permissions cannot be %@ using an invalid user.", msg]; + return [NSError errorWithDomain:RLMSyncPermissionErrorDomain code:RLMSyncAuthErrorInvalidParameters + userInfo:@{NSLocalizedFailureReasonErrorKey: msg}]; +} +} + +- (void)retrievePermissionsWithCallback:(RLMPermissionResultsBlock)callback { + if (NSError *error = checkUser(_user, @"retrieved")) { + callback(nullptr, error); + return; + } + + [RLMSyncGetPermissionsEndpoint + sendRequestToServer:self.authenticationServer + JSON:@{kRLMSyncTokenKey: self.refreshToken} + timeout:60.0 + completion:^(NSError *error, NSDictionary *json) { + if (error) { + return callback(nil, error); + } + // FIXME: ROS currently gives duplicated results for 'all' due to an incorrect query + NSMutableSet *permissions = [NSMutableSet new]; + for (NSDictionary *permission in json[@"permissions"]) { + // ROS reports the permission for __wildcardpermissions, which we + // don't want to include + if ([permission[@"path"] hasPrefix:@"/__"]) { + continue; + } + + // Wildcard permissions are reported as a null userId + id userId = permission[@"userId"]; + if (userId == NSNull.null) { + userId = @"*"; + } + + [permissions addObject:[[RLMSyncPermission alloc] + initWithRealmPath:permission[@"path"] + identity:userId + accessLevel:RLMSyncAccessLevelFromString(permission[@"accessLevel"])]]; + } + callback(permissions.allObjects, nil); + }]; +} + +- (void)applyPermission:(RLMSyncPermission *)permission callback:(RLMPermissionStatusBlock)callback { + if (NSError *error = checkUser(_user, @"applied")) { + callback(error); + return; + } + id condition; + if (permission.identity) { + condition = @{@"userId": permission.identity}; + } + else { + condition = @{@"metadataKey": permission.key, @"metadataValue": permission.value}; + } + [RLMSyncApplyPermissionsEndpoint + sendRequestToServer:self.authenticationServer + JSON:@{kRLMSyncTokenKey: self.refreshToken, + @"condition": condition, + @"realmPath": permission.path, + @"accessLevel": RLMSyncAccessLevelToString(permission.accessLevel)} + completion:callback]; +} + +- (void)createOfferForRealmAtURL:(NSURL *)url + accessLevel:(RLMSyncAccessLevel)accessLevel + expiration:(NSDate *)expirationDate + callback:(RLMPermissionOfferStatusBlock)callback { + if (NSError *error = checkUser(_user, @"offered")) { + callback(nil, error); + return; + } + + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + dateFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; + dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZZZZZ"; + dateFormatter.calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian]; + + [RLMSyncOfferPermissionsEndpoint + sendRequestToServer:self.authenticationServer + JSON:@{kRLMSyncTokenKey: self.refreshToken, + @"expiresAt": expirationDate ? [RLMISO8601Formatter() stringFromDate:expirationDate] : NSNull.null, + @"realmPath": url.path, + @"accessLevel": RLMSyncAccessLevelToString(accessLevel)} + timeout:60.0 + completion:^(NSError *error, NSDictionary *json) { + callback(json[@"token"], error); + }]; +} + +- (void)acceptOfferForToken:(NSString *)token + callback:(RLMPermissionOfferResponseStatusBlock)callback { + if (NSError *error = checkUser(_user, @"accepted")) { + callback(nil, error); + return; + } + [RLMSyncAcceptPermissionOfferEndpoint + sendRequestToServer:self.authenticationServer + JSON:@{kRLMSyncTokenKey: self.refreshToken, @"offerToken": token} + timeout:60.0 + completion:^(NSError *error, NSDictionary *json) { + callback([self urlForPath:json[@"path"]], error); + }]; +} + +- (void)invalidateOfferForToken:(NSString *)token + callback:(RLMPermissionStatusBlock)callback { + if (NSError *error = checkUser(_user, @"invalidated")) { + callback(error); + return; + } + [RLMSyncInvalidatePermissionOfferEndpoint + sendRequestToServer:self.authenticationServer + JSON:@{kRLMSyncTokenKey: self.refreshToken, @"offerToken": token} + timeout:60.0 + completion:^(NSError *error, NSDictionary *) { + callback(error); + }]; +} + +- (void)retrievePermissionOffersWithCallback:(RLMPermissionOfferResultsBlock)callback { + if (NSError *error = checkUser(_user, @"retrieved")) { + callback(nullptr, error); + return; + } + + [RLMSyncGetPermissionOffersEndpoint + sendRequestToServer:self.authenticationServer + JSON:@{kRLMSyncTokenKey: self.refreshToken} + timeout:60.0 + completion:^(NSError *error, NSDictionary *json) { + if (error) { + return callback(nil, error); + } + NSMutableArray *offers = [NSMutableArray new]; + NSDateFormatter *formatter = RLMISO8601Formatter(); + for (NSDictionary *offer in json[@"offers"]) { + NSString *expiresAt = RLMCoerceToNil(offer[@"expiresAt"]); + NSString *createdAt = RLMCoerceToNil(offer[@"createdAt"]); + [offers addObject:[[RLMSyncPermissionOffer alloc] + initWithRealmPath:offer[@"realmPath"] + token:offer[@"token"] + expiresAt:expiresAt ? [formatter dateFromString:expiresAt] : nil + createdAt:createdAt ? [formatter dateFromString:createdAt] : nil + accessLevel:RLMSyncAccessLevelFromString(offer[@"accessLevel"])]]; + } + callback(offers, nil); + }]; +} + +#pragma mark - Private API + +- (NSURL *)urlForPath:(nullable NSString *)path { + if (!path) { + return nil; + } + + NSURLComponents *components = [NSURLComponents componentsWithURL:self.authenticationServer resolvingAgainstBaseURL:YES]; + if ([components.scheme caseInsensitiveCompare:@"http"] == NSOrderedSame) + components.scheme = @"realm"; + else if ([components.scheme caseInsensitiveCompare:@"https"] == NSOrderedSame) + components.scheme = @"realms"; + else + @throw RLMException(@"The provided user's authentication server URL (%@) was not valid.", self.authenticationServer); + + components.path = path; + return components.URL; + +} + +- (NSURL *)defaultRealmURL { + return [self urlForPath:@"/default"]; +} + ++ (void)_setUpBindingContextFactory { + SyncUser::set_binding_context_factory([] { + return std::make_shared(); + }); +} + +- (NSString *)refreshToken { + if (!_user) { + return nil; + } + return @(_user->refresh_token().c_str()); +} + +- (std::shared_ptr)_syncUser { + return _user; +} + ++ (void)_performLoginForDirectAccessTokenCredentials:(RLMSyncCredentials *)credentials + authServerURL:(NSURL *)serverURL + completionBlock:(nonnull RLMUserCompletionBlock)completion { + NSString *identity = credentials.userInfo[kRLMSyncIdentityKey]; + std::shared_ptr sync_user; + if (serverURL) { + NSString *scheme = serverURL.scheme; + if (![scheme isEqualToString:@"http"] && ![scheme isEqualToString:@"https"]) { + @throw RLMException(@"The Realm Object Server authentication URL provided for this user, \"%@\", " + @" is invalid. It must begin with http:// or https://.", serverURL); + } + // Retrieve the user based on the auth server URL. + util::Optional identity_string; + if (identity) { + identity_string = std::string(identity.UTF8String); + } + sync_user = SyncManager::shared().get_admin_token_user([serverURL absoluteString].UTF8String, + credentials.token.UTF8String, + std::move(identity_string)); + } else { + // Retrieve the user based on the identity. + if (!identity) { + @throw RLMException(@"A direct access credential must specify either an identity, a server URL, or both."); + } + sync_user = SyncManager::shared().get_admin_token_user_from_identity(identity.UTF8String, + none, + credentials.token.UTF8String); + } + if (!sync_user) { + completion(nil, make_auth_error_client_issue()); + return; + } + completion([[RLMSyncUser alloc] initWithSyncUser:std::move(sync_user)], nil); +} + ++ (void)_performLoginForCustomRefreshTokenCredentials:(RLMSyncCredentials *)credentials + authServerURL:(NSURL *)serverURL + completionBlock:(nonnull RLMUserCompletionBlock)completion { + NSString *scheme = serverURL.scheme; + if (![scheme isEqualToString:@"http"] && ![scheme isEqualToString:@"https"]) { + @throw RLMException(@"The Realm Object Server authentication URL provided for this user, \"%@\", " + @" is invalid. It must begin with http:// or https://.", serverURL); + } + + NSString *identity = credentials.userInfo[kRLMSyncIdentityKey]; + SyncUserIdentifier identifier{identity.UTF8String, serverURL.absoluteString.UTF8String}; + + std::shared_ptr sync_user = SyncManager::shared().get_user(std::move(identifier), credentials.token.UTF8String); + if (!sync_user) { + completion(nil, make_auth_error_client_issue()); + return; + } + + NSNumber *isAdmin = credentials.userInfo[kRLMSyncIsAdminKey]; + sync_user->set_is_admin(isAdmin.boolValue); + completion([[RLMSyncUser alloc] initWithSyncUser:std::move(sync_user)], nil); +} + +@end + +#pragma mark - RLMSyncUserInfo + +@implementation RLMSyncUserInfo + +- (instancetype)initPrivate { + return [super init]; +} + ++ (instancetype)syncUserInfoWithModel:(RLMUserResponseModel *)model { + RLMSyncUserInfo *info = [[RLMSyncUserInfo alloc] initPrivate]; + info.accounts = model.accounts; + info.metadata = model.metadata; + info.isAdmin = model.isAdmin; + info.identity = model.identity; + return info; +} + +@end diff --git a/!main project/Pods/Realm/Realm/RLMSyncUtil.mm b/!main project/Pods/Realm/Realm/RLMSyncUtil.mm new file mode 100644 index 0000000..980ba46 --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMSyncUtil.mm @@ -0,0 +1,183 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMSyncUtil_Private.hpp" + +#import "RLMJSONModels.h" +#import "RLMObject_Private.hpp" +#import "RLMRealmConfiguration+Sync.h" +#import "RLMRealmConfiguration_Private.hpp" +#import "RLMRealm_Private.hpp" +#import "RLMSyncConfiguration_Private.hpp" +#import "RLMSyncUser_Private.hpp" +#import "RLMUtil.hpp" + +#import "shared_realm.hpp" + +#import "sync/sync_user.hpp" + +RLMIdentityProvider const RLMIdentityProviderAccessToken = @"_access_token"; + +NSString *const RLMSyncErrorDomain = @"io.realm.sync"; +NSString *const RLMSyncAuthErrorDomain = @"io.realm.sync.auth"; +NSString *const RLMSyncPermissionErrorDomain = @"io.realm.sync.permission"; + +NSString *const kRLMSyncPathOfRealmBackupCopyKey = @"recovered_realm_location_path"; +NSString *const kRLMSyncErrorActionTokenKey = @"error_action_token"; + +NSString *const kRLMSyncAppIDKey = @"app_id"; +NSString *const kRLMSyncDataKey = @"data"; +NSString *const kRLMSyncErrorJSONKey = @"json"; +NSString *const kRLMSyncErrorStatusCodeKey = @"statusCode"; +NSString *const kRLMSyncIdentityKey = @"identity"; +NSString *const kRLMSyncIsAdminKey = @"is_admin"; +NSString *const kRLMSyncNewPasswordKey = @"new_password"; +NSString *const kRLMSyncPasswordKey = @"password"; +NSString *const kRLMSyncPathKey = @"path"; +NSString *const kRLMSyncProviderKey = @"provider"; +NSString *const kRLMSyncProviderIDKey = @"provider_id"; +NSString *const kRLMSyncRegisterKey = @"register"; +NSString *const kRLMSyncTokenKey = @"token"; +NSString *const kRLMSyncUnderlyingErrorKey = @"underlying_error"; +NSString *const kRLMSyncUserIDKey = @"user_id"; + +uint8_t RLMGetComputedPermissions(RLMRealm *realm, id _Nullable object) { + if (!object) { + return static_cast(realm->_realm->get_privileges()); + } + if ([object isKindOfClass:[NSString class]]) { + return static_cast(realm->_realm->get_privileges([object UTF8String])); + } + if (auto obj = RLMDynamicCast(object)) { + RLMVerifyAttached(obj); + return static_cast(realm->_realm->get_privileges(obj->_row)); + } + return 0; +} + +#pragma mark - C++ APIs + +SyncSessionStopPolicy translateStopPolicy(RLMSyncStopPolicy stopPolicy) { + switch (stopPolicy) { + case RLMSyncStopPolicyImmediately: return SyncSessionStopPolicy::Immediately; + case RLMSyncStopPolicyLiveIndefinitely: return SyncSessionStopPolicy::LiveIndefinitely; + case RLMSyncStopPolicyAfterChangesUploaded: return SyncSessionStopPolicy::AfterChangesUploaded; + } + REALM_UNREACHABLE(); // Unrecognized stop policy. +} + +RLMSyncStopPolicy translateStopPolicy(SyncSessionStopPolicy stop_policy) { + switch (stop_policy) { + case SyncSessionStopPolicy::Immediately: return RLMSyncStopPolicyImmediately; + case SyncSessionStopPolicy::LiveIndefinitely: return RLMSyncStopPolicyLiveIndefinitely; + case SyncSessionStopPolicy::AfterChangesUploaded: return RLMSyncStopPolicyAfterChangesUploaded; + } + REALM_UNREACHABLE(); +} + +std::shared_ptr sync_session_for_realm(RLMRealm *realm) { + Realm::Config realmConfig = realm.configuration.config; + if (auto config = realmConfig.sync_config) { + std::shared_ptr user = config->user; + if (user && user->state() != SyncUser::State::Error) { + return user->session_for_on_disk_path(realmConfig.path); + } + } + return nullptr; +} + +CocoaSyncUserContext& context_for(const std::shared_ptr& user) +{ + return *std::static_pointer_cast(user->binding_context()); +} + +NSError *make_auth_error_bad_response(NSDictionary *json) { + return [NSError errorWithDomain:RLMSyncAuthErrorDomain + code:RLMSyncAuthErrorBadResponse + userInfo:json ? @{kRLMSyncErrorJSONKey: json} : nil]; +} + +NSError *make_auth_error_http_status(NSInteger status) { + return [NSError errorWithDomain:RLMSyncAuthErrorDomain + code:RLMSyncAuthErrorHTTPStatusCodeError + userInfo:@{kRLMSyncErrorStatusCodeKey: @(status)}]; +} + +NSError *make_auth_error_client_issue() { + return [NSError errorWithDomain:RLMSyncAuthErrorDomain + code:RLMSyncAuthErrorClientSessionError + userInfo:nil]; +} + +NSError *make_auth_error(RLMSyncErrorResponseModel *model) { + NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithCapacity:2]; + if (NSString *description = model.title) { + [userInfo setObject:description forKey:NSLocalizedDescriptionKey]; + } + if (NSString *hint = model.hint) { + [userInfo setObject:hint forKey:NSLocalizedRecoverySuggestionErrorKey]; + } + return [NSError errorWithDomain:RLMSyncAuthErrorDomain code:model.code userInfo:userInfo]; +} + +NSError *make_sync_error(RLMSyncSystemErrorKind kind, NSString *description, NSInteger code, NSDictionary *custom) { + NSMutableDictionary *buffer = [custom ?: @{} mutableCopy]; + buffer[NSLocalizedDescriptionKey] = description; + if (code != NSNotFound) { + buffer[kRLMSyncErrorStatusCodeKey] = @(code); + } + + RLMSyncError errorCode; + switch (kind) { + case RLMSyncSystemErrorKindClientReset: + errorCode = RLMSyncErrorClientResetError; + break; + case RLMSyncSystemErrorKindPermissionDenied: + errorCode = RLMSyncErrorPermissionDeniedError; + break; + case RLMSyncSystemErrorKindUser: + errorCode = RLMSyncErrorClientUserError; + break; + case RLMSyncSystemErrorKindSession: + errorCode = RLMSyncErrorClientSessionError; + break; + case RLMSyncSystemErrorKindConnection: + case RLMSyncSystemErrorKindClient: + case RLMSyncSystemErrorKindUnknown: + errorCode = RLMSyncErrorClientInternalError; + break; + } + return [NSError errorWithDomain:RLMSyncErrorDomain + code:errorCode + userInfo:[buffer copy]]; +} + +NSError *make_sync_error(NSError *wrapped_auth_error) { + return [NSError errorWithDomain:RLMSyncErrorDomain + code:RLMSyncErrorUnderlyingAuthError + userInfo:@{kRLMSyncUnderlyingErrorKey: wrapped_auth_error}]; +} + +NSError *make_sync_error(std::error_code sync_error, RLMSyncSystemErrorKind kind) { + return [NSError errorWithDomain:RLMSyncErrorDomain + code:kind + userInfo:@{ + NSLocalizedDescriptionKey: @(sync_error.message().c_str()), + kRLMSyncErrorStatusCodeKey: @(sync_error.value()) + }]; +} diff --git a/!main project/Pods/Realm/Realm/RLMThreadSafeReference.mm b/!main project/Pods/Realm/Realm/RLMThreadSafeReference.mm new file mode 100644 index 0000000..7149fa4 --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMThreadSafeReference.mm @@ -0,0 +1,79 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMThreadSafeReference_Private.hpp" +#import "RLMUtil.hpp" + +template +static auto translateErrors(Function&& f) { + try { + return f(); + } + catch (std::exception const& e) { + @throw RLMException(e); + } +} + +@implementation RLMThreadSafeReference { + std::unique_ptr _reference; + id _metadata; + Class _type; +} + +- (instancetype)initWithThreadConfined:(id)threadConfined { + if (!(self = [super init])) { + return nil; + } + + REALM_ASSERT_DEBUG([threadConfined conformsToProtocol:@protocol(RLMThreadConfined)]); + if (![threadConfined conformsToProtocol:@protocol(RLMThreadConfined_Private)]) { + @throw RLMException(@"Illegal custom conformance to `RLMThreadConfined` by `%@`", threadConfined.class); + } else if (threadConfined.invalidated) { + @throw RLMException(@"Cannot construct reference to invalidated object"); + } else if (!threadConfined.realm) { + @throw RLMException(@"Cannot construct reference to unmanaged object, " + "which can be passed across threads directly"); + } + + translateErrors([&] { + _reference = [(id)threadConfined makeThreadSafeReference]; + _metadata = ((id)threadConfined).objectiveCMetadata; + }); + _type = threadConfined.class; + + return self; +} + ++ (instancetype)referenceWithThreadConfined:(id)threadConfined { + return [[self alloc] initWithThreadConfined:threadConfined]; +} + +- (id)resolveReferenceInRealm:(RLMRealm *)realm { + if (!_reference) { + @throw RLMException(@"Can only resolve a thread safe reference once."); + } + return translateErrors([&] { + return [_type objectWithThreadSafeReference:std::move(_reference) metadata:_metadata realm:realm]; + }); +} + +- (BOOL)isInvalidated { + return !_reference; +} + +@end diff --git a/!main project/Pods/Realm/Realm/RLMUpdateChecker.mm b/!main project/Pods/Realm/Realm/RLMUpdateChecker.mm new file mode 100644 index 0000000..4ff0dd6 --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMUpdateChecker.mm @@ -0,0 +1,60 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMUpdateChecker.hpp" + +#import "RLMRealm.h" +#import "RLMUtil.hpp" + +#if TARGET_IPHONE_SIMULATOR && !defined(REALM_COCOA_VERSION) +#import "RLMVersion.h" +#endif + +void RLMCheckForUpdates() { +#if TARGET_IPHONE_SIMULATOR + if (getenv("REALM_DISABLE_UPDATE_CHECKER") || RLMIsRunningInPlayground()) { + return; + } + + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"alpha|beta|rc" + options:(NSRegularExpressionOptions)0 + error:nil]; + NSUInteger numberOfMatches = [regex numberOfMatchesInString:REALM_COCOA_VERSION + options:(NSMatchingOptions)0 + range:NSMakeRange(0, REALM_COCOA_VERSION.length)]; + + if (numberOfMatches > 0) { + // pre-release version, skip update checking + return; + } + + auto handler = ^(NSData *data, NSURLResponse *response, NSError *error) { + if (error || ((NSHTTPURLResponse *)response).statusCode != 200) { + return; + } + + NSString *latestVersion = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + if (![REALM_COCOA_VERSION isEqualToString:latestVersion]) { + NSLog(@"Version %@ of Realm is now available: https://github.com/realm/realm-cocoa/blob/v%@/CHANGELOG.md", latestVersion, latestVersion); + } + }; + + NSString *url = [NSString stringWithFormat:@"https://static.realm.io/update/cocoa?%@", REALM_COCOA_VERSION]; + [[NSURLSession.sharedSession dataTaskWithURL:[NSURL URLWithString:url] completionHandler:handler] resume]; +#endif +} diff --git a/!main project/Pods/Realm/Realm/RLMUtil.mm b/!main project/Pods/Realm/Realm/RLMUtil.mm new file mode 100644 index 0000000..59b9829 --- /dev/null +++ b/!main project/Pods/Realm/Realm/RLMUtil.mm @@ -0,0 +1,457 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMUtil.hpp" + +#import "RLMArray_Private.hpp" +#import "RLMListBase.h" +#import "RLMObjectSchema_Private.hpp" +#import "RLMObjectStore.h" +#import "RLMObject_Private.hpp" +#import "RLMProperty_Private.h" +#import "RLMSchema_Private.h" +#import "RLMSwiftSupport.h" + +#import "shared_realm.hpp" + +#if REALM_ENABLE_SYNC +#import "RLMSyncUtil.h" +#endif + +#import +#import + +#include +#include + +#if !defined(REALM_COCOA_VERSION) +#import "RLMVersion.h" +#endif + +static inline bool numberIsInteger(__unsafe_unretained NSNumber *const obj) { + char data_type = [obj objCType][0]; + return data_type == *@encode(bool) || + data_type == *@encode(char) || + data_type == *@encode(short) || + data_type == *@encode(int) || + data_type == *@encode(long) || + data_type == *@encode(long long) || + data_type == *@encode(unsigned short) || + data_type == *@encode(unsigned int) || + data_type == *@encode(unsigned long) || + data_type == *@encode(unsigned long long); +} + +static inline bool numberIsBool(__unsafe_unretained NSNumber *const obj) { + // @encode(BOOL) is 'B' on iOS 64 and 'c' + // objcType is always 'c'. Therefore compare to "c". + if ([obj objCType][0] == 'c') { + return true; + } + + if (numberIsInteger(obj)) { + int value = [obj intValue]; + return value == 0 || value == 1; + } + + return false; +} + +static inline bool numberIsFloat(__unsafe_unretained NSNumber *const obj) { + char data_type = [obj objCType][0]; + return data_type == *@encode(float) || + data_type == *@encode(short) || + data_type == *@encode(int) || + data_type == *@encode(long) || + data_type == *@encode(long long) || + data_type == *@encode(unsigned short) || + data_type == *@encode(unsigned int) || + data_type == *@encode(unsigned long) || + data_type == *@encode(unsigned long long) || + // A double is like float if it fits within float bounds or is NaN. + (data_type == *@encode(double) && (ABS([obj doubleValue]) <= FLT_MAX || isnan([obj doubleValue]))); +} + +static inline bool numberIsDouble(__unsafe_unretained NSNumber *const obj) { + char data_type = [obj objCType][0]; + return data_type == *@encode(double) || + data_type == *@encode(float) || + data_type == *@encode(short) || + data_type == *@encode(int) || + data_type == *@encode(long) || + data_type == *@encode(long long) || + data_type == *@encode(unsigned short) || + data_type == *@encode(unsigned int) || + data_type == *@encode(unsigned long) || + data_type == *@encode(unsigned long long); +} + +static inline RLMArray *asRLMArray(__unsafe_unretained id const value) { + return RLMDynamicCast(value) ?: RLMDynamicCast(value)._rlmArray; +} + +static inline bool checkArrayType(__unsafe_unretained RLMArray *const array, + RLMPropertyType type, bool optional, + __unsafe_unretained NSString *const objectClassName) { + return array.type == type && array.optional == optional + && (type != RLMPropertyTypeObject || [array.objectClassName isEqualToString:objectClassName]); +} + +id (*RLMSwiftAsFastEnumeration)(id); +id RLMAsFastEnumeration(__unsafe_unretained id obj) { + if (!obj) { + return nil; + } + if ([obj conformsToProtocol:@protocol(NSFastEnumeration)]) { + return obj; + } + if (RLMSwiftAsFastEnumeration) { + return RLMSwiftAsFastEnumeration(obj); + } + return nil; +} + +BOOL RLMValidateValue(__unsafe_unretained id const value, + RLMPropertyType type, bool optional, bool array, + __unsafe_unretained NSString *const objectClassName) { + if (optional && !RLMCoerceToNil(value)) { + return YES; + } + if (array) { + if (auto rlmArray = asRLMArray(value)) { + return checkArrayType(rlmArray, type, optional, objectClassName); + } + if (id enumeration = RLMAsFastEnumeration(value)) { + // check each element for compliance + for (id el in enumeration) { + if (!RLMValidateValue(el, type, optional, false, objectClassName)) { + return NO; + } + } + return YES; + } + if (!value || value == NSNull.null) { + return YES; + } + return NO; + } + + switch (type) { + case RLMPropertyTypeString: + return [value isKindOfClass:[NSString class]]; + case RLMPropertyTypeBool: + if ([value isKindOfClass:[NSNumber class]]) { + return numberIsBool(value); + } + return NO; + case RLMPropertyTypeDate: + return [value isKindOfClass:[NSDate class]]; + case RLMPropertyTypeInt: + if (NSNumber *number = RLMDynamicCast(value)) { + return numberIsInteger(number); + } + return NO; + case RLMPropertyTypeFloat: + if (NSNumber *number = RLMDynamicCast(value)) { + return numberIsFloat(number); + } + return NO; + case RLMPropertyTypeDouble: + if (NSNumber *number = RLMDynamicCast(value)) { + return numberIsDouble(number); + } + return NO; + case RLMPropertyTypeData: + return [value isKindOfClass:[NSData class]]; + case RLMPropertyTypeAny: + return NO; + case RLMPropertyTypeLinkingObjects: + return YES; + case RLMPropertyTypeObject: { + // only NSNull, nil, or objects which derive from RLMObject and match the given + // object class are valid + RLMObjectBase *objBase = RLMDynamicCast(value); + return objBase && [objBase->_objectSchema.className isEqualToString:objectClassName]; + } + } + @throw RLMException(@"Invalid RLMPropertyType specified"); +} + +void RLMThrowTypeError(__unsafe_unretained id const obj, + __unsafe_unretained RLMObjectSchema *const objectSchema, + __unsafe_unretained RLMProperty *const prop) { + @throw RLMException(@"Invalid value '%@' of type '%@' for '%@%s'%s property '%@.%@'.", + obj, [obj class], + prop.objectClassName ?: RLMTypeToString(prop.type), prop.optional ? "?" : "", + prop.array ? " array" : "", objectSchema.className, prop.name); +} + +void RLMValidateValueForProperty(__unsafe_unretained id const obj, + __unsafe_unretained RLMObjectSchema *const objectSchema, + __unsafe_unretained RLMProperty *const prop, + bool validateObjects) { + // This duplicates a lot of the checks in RLMIsObjectValidForProperty() + // for the sake of more specific error messages + if (prop.array) { + // nil is considered equivalent to an empty array for historical reasons + // since we don't support null arrays (only arrays containing null), + // it's not worth the BC break to change this + if (!obj || obj == NSNull.null) { + return; + } + id enumeration = RLMAsFastEnumeration(obj); + if (!enumeration) { + @throw RLMException(@"Invalid value (%@) for '%@%s' array property '%@.%@': value is not enumerable.", + obj, prop.objectClassName ?: RLMTypeToString(prop.type), prop.optional ? "?" : "", + objectSchema.className, prop.name); + } + if (!validateObjects && prop.type == RLMPropertyTypeObject) { + return; + } + + if (RLMArray *array = asRLMArray(obj)) { + if (!checkArrayType(array, prop.type, prop.optional, prop.objectClassName)) { + @throw RLMException(@"RLMArray<%@%s> does not match expected type '%@%s' for property '%@.%@'.", + array.objectClassName ?: RLMTypeToString(array.type), array.optional ? "?" : "", + prop.objectClassName ?: RLMTypeToString(prop.type), prop.optional ? "?" : "", + objectSchema.className, prop.name); + } + return; + } + + for (id value in enumeration) { + if (!RLMValidateValue(value, prop.type, prop.optional, false, prop.objectClassName)) { + RLMThrowTypeError(value, objectSchema, prop); + } + } + return; + } + + // For create() we want to skip the validation logic for objects because + // we allow much fuzzier matching (any KVC-compatible object with at least + // all the non-defaulted fields), and all the logic for that lives in the + // object store rather than here + if (prop.type == RLMPropertyTypeObject && !validateObjects) { + return; + } + if (RLMIsObjectValidForProperty(obj, prop)) { + return; + } + + RLMThrowTypeError(obj, objectSchema, prop); +} + +BOOL RLMIsObjectValidForProperty(__unsafe_unretained id const obj, + __unsafe_unretained RLMProperty *const property) { + return RLMValidateValue(obj, property.type, property.optional, property.array, property.objectClassName); +} + +NSDictionary *RLMDefaultValuesForObjectSchema(__unsafe_unretained RLMObjectSchema *const objectSchema) { + if (!objectSchema.isSwiftClass) { + return [objectSchema.objectClass defaultPropertyValues]; + } + + NSMutableDictionary *defaults = nil; + if ([objectSchema.objectClass isSubclassOfClass:RLMObject.class]) { + defaults = [NSMutableDictionary dictionaryWithDictionary:[objectSchema.objectClass defaultPropertyValues]]; + } + else { + defaults = [NSMutableDictionary dictionary]; + } + RLMObject *defaultObject = [[objectSchema.objectClass alloc] init]; + for (RLMProperty *prop in objectSchema.properties) { + if (!defaults[prop.name] && defaultObject[prop.name]) { + defaults[prop.name] = defaultObject[prop.name]; + } + } + return defaults; +} + +static NSException *RLMException(NSString *reason, NSDictionary *additionalUserInfo) { + NSMutableDictionary *userInfo = @{RLMRealmVersionKey: REALM_COCOA_VERSION, + RLMRealmCoreVersionKey: @REALM_VERSION}.mutableCopy; + if (additionalUserInfo != nil) { + [userInfo addEntriesFromDictionary:additionalUserInfo]; + } + NSException *e = [NSException exceptionWithName:RLMExceptionName + reason:reason + userInfo:userInfo]; + return e; +} + +NSException *RLMException(NSString *fmt, ...) { + va_list args; + va_start(args, fmt); + NSException *e = RLMException([[NSString alloc] initWithFormat:fmt arguments:args], @{}); + va_end(args); + return e; +} + +NSException *RLMException(std::exception const& exception) { + return RLMException(@"%s", exception.what()); +} + +NSError *RLMMakeError(RLMError code, std::exception const& exception) { + return [NSError errorWithDomain:RLMErrorDomain + code:code + userInfo:@{NSLocalizedDescriptionKey: @(exception.what()), + @"Error Code": @(code)}]; +} + +NSError *RLMMakeError(RLMError code, const realm::util::File::AccessError& exception) { + return [NSError errorWithDomain:RLMErrorDomain + code:code + userInfo:@{NSLocalizedDescriptionKey: @(exception.what()), + NSFilePathErrorKey: @(exception.get_path().c_str()), + @"Error Code": @(code)}]; +} + +NSError *RLMMakeError(RLMError code, const realm::RealmFileException& exception) { + NSString *underlying = @(exception.underlying().c_str()); + return [NSError errorWithDomain:RLMErrorDomain + code:code + userInfo:@{NSLocalizedDescriptionKey: @(exception.what()), + NSFilePathErrorKey: @(exception.path().c_str()), + @"Error Code": @(code), + @"Underlying": underlying.length == 0 ? @"n/a" : underlying}]; +} + +NSError *RLMMakeError(std::system_error const& exception) { + int code = exception.code().value(); + BOOL isGenericCategoryError = (exception.code().category() == std::generic_category()); + NSString *category = @(exception.code().category().name()); + NSString *errorDomain = isGenericCategoryError ? NSPOSIXErrorDomain : RLMUnknownSystemErrorDomain; +#if REALM_ENABLE_SYNC + if (exception.code().category() == realm::sync::client_error_category()) { + if (exception.code().value() == static_cast(realm::sync::Client::Error::connect_timeout)) { + errorDomain = NSPOSIXErrorDomain; + code = ETIMEDOUT; + } + else { + errorDomain = RLMSyncErrorDomain; + } + } +#endif + + return [NSError errorWithDomain:errorDomain code:code + userInfo:@{NSLocalizedDescriptionKey: @(exception.what()), + @"Error Code": @(exception.code().value()), + @"Category": category}]; +} + +void RLMSetErrorOrThrow(NSError *error, NSError **outError) { + if (outError) { + *outError = error; + } + else { + NSString *msg = error.localizedDescription; + if (error.userInfo[NSFilePathErrorKey]) { + msg = [NSString stringWithFormat:@"%@: %@", error.userInfo[NSFilePathErrorKey], error.localizedDescription]; + } + @throw RLMException(msg, @{NSUnderlyingErrorKey: error}); + } +} + +BOOL RLMIsDebuggerAttached() +{ + int name[] = { + CTL_KERN, + KERN_PROC, + KERN_PROC_PID, + getpid() + }; + + struct kinfo_proc info; + size_t info_size = sizeof(info); + if (sysctl(name, sizeof(name)/sizeof(name[0]), &info, &info_size, NULL, 0) == -1) { + NSLog(@"sysctl() failed: %s", strerror(errno)); + return false; + } + + return (info.kp_proc.p_flag & P_TRACED) != 0; +} + +BOOL RLMIsRunningInPlayground() { + return [[NSBundle mainBundle].bundleIdentifier hasPrefix:@"com.apple.dt.playground."]; +} + +id RLMMixedToObjc(realm::Mixed const& mixed) { + switch (mixed.get_type()) { + case realm::type_String: + return RLMStringDataToNSString(mixed.get_string()); + case realm::type_Int: + return @(mixed.get_int()); + case realm::type_Float: + return @(mixed.get_float()); + case realm::type_Double: + return @(mixed.get_double()); + case realm::type_Bool: + return @(mixed.get_bool()); + case realm::type_Timestamp: + return RLMTimestampToNSDate(mixed.get_timestamp()); + case realm::type_Binary: + return RLMBinaryDataToNSData(mixed.get_binary()); + case realm::type_Link: + case realm::type_LinkList: + default: + @throw RLMException(@"Invalid data type for RLMPropertyTypeAny property."); + } +} + +NSString *RLMDefaultDirectoryForBundleIdentifier(NSString *bundleIdentifier) { +#if TARGET_OS_TV + (void)bundleIdentifier; + // tvOS prohibits writing to the Documents directory, so we use the Library/Caches directory instead. + return NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0]; +#elif TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST + (void)bundleIdentifier; + // On iOS the Documents directory isn't user-visible, so put files there + return NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; +#else + // On OS X it is, so put files in Application Support. If we aren't running + // in a sandbox, put it in a subdirectory based on the bundle identifier + // to avoid accidentally sharing files between applications + NSString *path = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES)[0]; + if (![[NSProcessInfo processInfo] environment][@"APP_SANDBOX_CONTAINER_ID"]) { + if (!bundleIdentifier) { + bundleIdentifier = [NSBundle mainBundle].bundleIdentifier; + } + if (!bundleIdentifier) { + bundleIdentifier = [NSBundle mainBundle].executablePath.lastPathComponent; + } + + path = [path stringByAppendingPathComponent:bundleIdentifier]; + + // create directory + [[NSFileManager defaultManager] createDirectoryAtPath:path + withIntermediateDirectories:YES + attributes:nil + error:nil]; + } + return path; +#endif +} + +NSDateFormatter *RLMISO8601Formatter() { + // note: NSISO8601DateFormatter can't be used as it doesn't support milliseconds + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + dateFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; + dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSZ"; + dateFormatter.calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian]; + return dateFormatter; +} diff --git a/!main project/Pods/Realm/Realm/Realm.modulemap b/!main project/Pods/Realm/Realm/Realm.modulemap new file mode 100644 index 0000000..42845f6 --- /dev/null +++ b/!main project/Pods/Realm/Realm/Realm.modulemap @@ -0,0 +1,31 @@ +framework module Realm { + umbrella header "Realm.h" + + export * + module * { export * } + + explicit module Private { + header "RLMAccessor.h" + header "RLMArray_Private.h" + header "RLMCollection_Private.h" + header "RLMListBase.h" + header "RLMObject_Private.h" + header "RLMObjectBase_Dynamic.h" + header "RLMObjectBase_Private.h" + header "RLMObjectSchema_Private.h" + header "RLMObjectStore.h" + header "RLMOptionalBase.h" + header "RLMProperty_Private.h" + header "RLMRealm_Private.h" + header "RLMRealmConfiguration_Private.h" + header "RLMResults_Private.h" + header "RLMSchema_Private.h" + header "RLMSyncConfiguration_Private.h" + header "RLMSyncUtil_Private.h" + } + + explicit module Dynamic { + header "RLMRealm_Dynamic.h" + header "RLMObjectBase_Dynamic.h" + } +} diff --git a/!main project/Pods/Realm/build.sh b/!main project/Pods/Realm/build.sh new file mode 100755 index 0000000..c3bb27a --- /dev/null +++ b/!main project/Pods/Realm/build.sh @@ -0,0 +1,1571 @@ +#!/bin/bash + +################################################################################## +# Custom build tool for Realm Objective-C binding. +# +# (C) Copyright 2011-2015 by realm.io. +################################################################################## + +# Warning: pipefail is not a POSIX compatible option, but on macOS it works just fine. +# macOS uses a POSIX complain version of bash as /bin/sh, but apparently it does +# not strip away this feature. Also, this will fail if somebody forces the script +# to be run with zsh. +set -o pipefail +set -e + +source_root="$(dirname "$0")" + +# You can override the version of the core library +: ${REALM_BASE_URL:="https://static.realm.io/downloads"} # set it if you need to use a remote repo + +: ${REALM_CORE_VERSION:=$(sed -n 's/^REALM_CORE_VERSION=\(.*\)$/\1/p' ${source_root}/dependencies.list)} # set to "current" to always use the current build + +: ${REALM_SYNC_VERSION:=$(sed -n 's/^REALM_SYNC_VERSION=\(.*\)$/\1/p' ${source_root}/dependencies.list)} + +: ${REALM_OBJECT_SERVER_VERSION:=$(sed -n 's/^REALM_OBJECT_SERVER_VERSION=\(.*\)$/\1/p' ${source_root}/dependencies.list)} + +# You can override the xcmode used +: ${XCMODE:=xcodebuild} # must be one of: xcodebuild (default), xcpretty, xctool + +# Provide a fallback value for TMPDIR, relevant for Xcode Bots +: ${TMPDIR:=$(getconf DARWIN_USER_TEMP_DIR)} + +PATH=/usr/libexec:$PATH + +if ! [ -z "${JENKINS_HOME}" ]; then + XCPRETTY_PARAMS="--no-utf --report junit --output build/reports/junit.xml" + CODESIGN_PARAMS="CODE_SIGN_IDENTITY= CODE_SIGNING_REQUIRED=NO" +fi + +usage() { +cat < /dev/null +} + +###################################### +# Device Test Helper +###################################### + +test_devices() { + local serial_numbers=() + local awk_script=" + /^ +Vendor ID: / { is_apple = 0; } + /^ +Vendor ID: 0x05[aA][cC] / { is_apple = 1; } + /^ +Serial Number: / { + if (is_apple) { + match(\$0, /^ +Serial Number: /); + print substr(\$0, RLENGTH + 1); + } + } + " + local serial_numbers_text=$(/usr/sbin/system_profiler SPUSBDataType | /usr/bin/awk "$awk_script") + while read -r number; do + if [[ "$number" != "" ]]; then + serial_numbers+=("$number") + fi + done <<< "$serial_numbers_text" + if [[ ${#serial_numbers[@]} == 0 ]]; then + echo "At least one iOS/tvOS device must be connected to this computer to run device tests" + if [ -z "${JENKINS_HOME}" ]; then + # Don't fail if running locally and there's no device + exit 0 + fi + exit 1 + fi + local sdk="$1" + local scheme="$2" + local configuration="$3" + local failed=0 + for device in "${serial_numbers[@]}"; do + xc "-scheme '$scheme' -configuration $configuration -destination 'id=$device' -sdk $sdk test" || failed=1 + done + return $failed +} + +###################################### +# Docs +###################################### + +build_docs() { + local language="$1" + local version=$(sh build.sh get-version) + + local xcodebuild_arguments="--objc,Realm/Realm.h,--,-x,objective-c,-isysroot,$(xcrun --show-sdk-path),-I,$(pwd)" + local module="Realm" + local objc="--objc" + + if [[ "$language" == "swift" ]]; then + sh build.sh set-swift-version + xcodebuild_arguments="-scheme,RealmSwift" + module="RealmSwift" + objc="" + fi + + touch Realm/RLMPlatform.h # jazzy will fail if it can't find all public header files + jazzy \ + ${objc} \ + --clean \ + --author Realm \ + --author_url https://realm.io \ + --github_url https://github.com/realm/realm-cocoa \ + --github-file-prefix https://github.com/realm/realm-cocoa/tree/v${version} \ + --module-version ${version} \ + --xcodebuild-arguments ${xcodebuild_arguments} \ + --module ${module} \ + --root-url https://realm.io/docs/${language}/${version}/api/ \ + --output docs/${language}_output \ + --head "$(cat docs/custom_head.html)" + + rm Realm/RLMPlatform.h +} + +###################################### +# Input Validation +###################################### + +if [ "$#" -eq 0 -o "$#" -gt 3 ]; then + usage + exit 1 +fi + +###################################### +# Downloading +###################################### + +download_common() { + local download_type=$1 tries_left=3 version url error temp_dir temp_path tar_path + + if [ "$download_type" == "core" ]; then + version=$REALM_CORE_VERSION + url="${REALM_BASE_URL}/core/realm-core-${version}.tar.xz" + elif [ "$download_type" == "sync" ]; then + version=$REALM_SYNC_VERSION + url="${REALM_BASE_URL}/sync/realm-sync-cocoa-${version}.tar.xz" + else + echo "Unknown dowload_type: $download_type" + exit 1 + fi + + echo "Downloading dependency: ${download_type} ${version} from ${url}" + + if [ -z "$TMPDIR" ]; then + TMPDIR='/tmp' + fi + temp_dir=$(dirname "$TMPDIR/waste")/${download_type}_bin + mkdir -p "$temp_dir" + tar_path="${temp_dir}/${download_type}-${version}.tar.xz" + temp_path="${tar_path}.tmp" + + while [ 0 -lt $tries_left ] && [ ! -f "$tar_path" ]; do + if ! error=$(/usr/bin/curl --fail --silent --show-error --location "$url" --output "$temp_path" 2>&1); then + tries_left=$[$tries_left-1] + else + mv "$temp_path" "$tar_path" + fi + done + + if [ ! -f "$tar_path" ]; then + printf "Downloading ${download_type} failed:\n\t$url\n\t$error\n" + exit 1 + fi + + ( + cd "$temp_dir" + rm -rf "$download_type" + tar xf "$tar_path" --xz + mv core "${download_type}-${version}" + ) + + rm -rf "${download_type}-${version}" core + mv "${temp_dir}/${download_type}-${version}" . + ln -s "${download_type}-${version}" core +} + +download_core() { + download_common "core" +} + +download_sync() { + download_common "sync" +} + +###################################### +# Variables +###################################### + +COMMAND="$1" + +# Use Debug config if command ends with -debug, otherwise default to Release +case "$COMMAND" in + *-debug) + COMMAND="${COMMAND%-debug}" + CONFIGURATION="Debug" + ;; +esac +export CONFIGURATION=${CONFIGURATION:-Release} + +# Pre-choose Xcode and Swift versions for those operations that do not set them +REALM_XCODE_VERSION=${xcode_version:-$REALM_XCODE_VERSION} +REALM_SWIFT_VERSION=${swift_version:-$REALM_SWIFT_VERSION} +source "${source_root}/scripts/swift-version.sh" +set_xcode_and_swift_versions + +###################################### +# Commands +###################################### + +case "$COMMAND" in + + ###################################### + # Clean + ###################################### + "clean") + find . -type d -name build -exec rm -r "{}" + + exit 0 + ;; + + ###################################### + # Core + ###################################### + "download-core") + if [ "$REALM_CORE_VERSION" = "current" ]; then + echo "Using version of core already in core/ directory" + exit 0 + fi + if [ -d core -a -d ../realm-core -a ! -L core ]; then + # Allow newer versions than expected for local builds as testing + # with unreleased versions is one of the reasons to use a local build + if ! $(grep -i "${REALM_CORE_VERSION} Release notes" core/release_notes.txt >/dev/null); then + echo "Local build of core is out of date." + exit 1 + else + echo "The core library seems to be up to date." + fi + elif ! [ -L core ]; then + echo "core is not a symlink. Deleting..." + rm -rf core + download_core + # With a prebuilt version we only want to check the first non-empty + # line so that checking out an older commit will download the + # appropriate version of core if the already-present version is too new + elif ! $(grep -m 1 . core/release_notes.txt | grep -i "${REALM_CORE_VERSION} RELEASE NOTES" >/dev/null); then + download_core + else + echo "The core library seems to be up to date." + fi + exit 0 + ;; + + ###################################### + # Sync + ###################################### + "download-sync") + if [ "$REALM_SYNC_VERSION" = "current" ]; then + echo "Using version of core already in core/ directory" + exit 0 + fi + if [ -d core -a -d ../realm-core -a -d ../realm-sync -a ! -L core ]; then + echo "Using version of core already in core/ directory" + elif ! [ -L core ]; then + echo "core is not a symlink. Deleting..." + rm -rf core + download_sync + elif [[ "$(cat core/version.txt)" != "$REALM_SYNC_VERSION" ]]; then + download_sync + else + echo "The core library seems to be up to date." + fi + exit 0 + ;; + + ###################################### + # Swift versioning + ###################################### + "set-swift-version") + version=${2:-$REALM_SWIFT_VERSION} + + SWIFT_VERSION_FILE="RealmSwift/SwiftVersion.swift" + CONTENTS="let swiftLanguageVersion = \"$version\"" + if [ ! -f "$SWIFT_VERSION_FILE" ] || ! grep -q "$CONTENTS" "$SWIFT_VERSION_FILE"; then + echo "$CONTENTS" > "$SWIFT_VERSION_FILE" + fi + + exit 0 + ;; + + "prelaunch-simulator") + sh ${source_root}/scripts/reset-simulators.sh + ;; + + ###################################### + # Building + ###################################### + "build") + sh build.sh ios-static + sh build.sh ios-dynamic + sh build.sh ios-swift + sh build.sh watchos + sh build.sh watchos-swift + sh build.sh tvos + sh build.sh tvos-swift + sh build.sh osx + sh build.sh osx-swift + exit 0 + ;; + + "ios-static") + build_combined 'Realm iOS static' Realm iphoneos iphonesimulator "-static" + exit 0 + ;; + + "ios-dynamic") + build_combined Realm Realm iphoneos iphonesimulator + exit 0 + ;; + + "ios-swift") + sh build.sh ios-dynamic + build_combined RealmSwift RealmSwift iphoneos iphonesimulator '' "/swift-$REALM_XCODE_VERSION" + copy_realm_framework ios + exit 0 + ;; + + "watchos") + build_combined Realm Realm watchos watchsimulator + exit 0 + ;; + + "watchos-swift") + sh build.sh watchos + build_combined RealmSwift RealmSwift watchos watchsimulator '' "/swift-$REALM_XCODE_VERSION" + copy_realm_framework watchos + exit 0 + ;; + + "tvos") + build_combined Realm Realm appletvos appletvsimulator + exit 0 + ;; + + "tvos-swift") + sh build.sh tvos + build_combined RealmSwift RealmSwift appletvos appletvsimulator '' "/swift-$REALM_XCODE_VERSION" + copy_realm_framework tvos + exit 0 + ;; + + "osx") + xc "-scheme Realm -configuration $CONFIGURATION" + clean_retrieve "build/DerivedData/Realm/Build/Products/$CONFIGURATION/Realm.framework" "build/osx" "Realm.framework" + exit 0 + ;; + + "osx-swift") + sh build.sh osx + xc "-scheme 'RealmSwift' -configuration $CONFIGURATION build" + destination="build/osx/swift-$REALM_XCODE_VERSION" + clean_retrieve "build/DerivedData/Realm/Build/Products/$CONFIGURATION/RealmSwift.framework" "$destination" "RealmSwift.framework" + rm -rf "$destination/Realm.framework" + cp -R build/osx/Realm.framework "$destination" + exit 0 + ;; + + "catalyst") + if (( $(xcode_version_major) < 11 )); then + echo 'Building for Catalyst requires Xcode 11' + exit 1 + fi + + xc "-scheme Realm -configuration $CONFIGURATION \ + REALM_CATALYST_FLAGS='-target x86_64-apple-ios13.0-macabi' \ + REALM_PLATFORM_SUFFIX='maccatalyst' \ + IS_MACCATALYST=YES" + clean_retrieve "build/DerivedData/Realm/Build/Products/$CONFIGURATION/Realm.framework" "build/catalyst" "Realm.framework" + ;; + + "catalyst-swift") + if (( $(xcode_version_major) < 11 )); then + echo 'Building for Catalyst requires Xcode 11' + exit 1 + fi + + sh build.sh catalyst + # FIXME: change this to just "-destination variant='Mac Catalyst'" once the CI machines are running 10.15 + xc "-scheme 'RealmSwift' -configuration $CONFIGURATION build \ + REALM_CATALYST_FLAGS='-target x86_64-apple-ios13.0-macabi' \ + REALM_PLATFORM_SUFFIX='maccatalyst' \ + SWIFT_DEPLOYMENT_TARGET='13.0-macabi' \ + SWIFT_PLATFORM_TARGET_PREFIX='ios' \ + IS_MACCATALYST=YES" + destination="build/catalyst/swift-$REALM_XCODE_VERSION" + clean_retrieve "build/DerivedData/Realm/Build/Products/$CONFIGURATION/RealmSwift.framework" "$destination" "RealmSwift.framework" + rm -rf "$destination/Realm.framework" + cp -R build/catalyst/Realm.framework "$destination" + ;; + + "xcframework") + if (( $(xcode_version_major) < 11 )); then + echo 'Building a xcframework requires Xcode 11' + exit 1 + fi + + export REALM_EXTRA_BUILD_ARGUMENTS="$REALM_EXTRA_BUILD_ARGUMENTS BUILD_LIBRARY_FOR_DISTRIBUTION=YES REALM_OBJC_MACH_O_TYPE=staticlib" + + # Build all of the requested frameworks + shift + PLATFORMS="${*:-osx ios watchos tvos catalyst}" + for platform in $PLATFORMS; do + sh build.sh $platform-swift + done + + # Assemble them into xcframeworks + rm -rf build/*.xcframework + find build/DerivedData/Realm/Build/Products -name 'Realm.framework' \ + | grep -v '\-static' \ + | sed 's/.*/-framework &/' \ + | xargs xcodebuild -create-xcframework -output build/Realm.xcframework + find build/DerivedData/Realm/Build/Products -name 'RealmSwift.framework' \ + | sed 's/.*/-framework &/' \ + | xargs xcodebuild -create-xcframework -output build/RealmSwift.xcframework + + # strip-frameworks.sh isn't needed with xcframeworks since we don't + # lipo together device/simulator libs + find build/Realm.xcframework -name 'strip-frameworks.sh' -delete + find build/RealmSwift.xcframework -name 'strip-frameworks.sh' -delete + + # swiftinterface files currently have incorrect name resolution which + # results in the RealmSwift.Realm class name clashing with the Realm + # module name. Work around this by renaming the Realm module to + # RealmObjc. This is safe to do with a pre-built library because the + # module name is unrelated to what symbols are exported by an obj-c + # library, and we're statically linking the obj-c library into the + # swift library so it doesn't need to be loaded at runtime. + cd build + cp -R Realm.xcframework RealmObjc.xcframework + find RealmObjc.xcframework -name 'Realm.framework' \ + -execdir mv {} RealmObjc.framework \; || true 2> /dev/null + find RealmObjc.xcframework -name '*.h' \ + -exec sed -i '' 's/Realm\//RealmObjc\//' {} \; + find RealmObjc.xcframework -name 'module.modulemap' \ + -exec sed -i '' 's/module Realm/module RealmObjc/' {} \; + sed -i '' 's/Realm.framework/RealmObjc.framework/' RealmObjc.xcframework/Info.plist + + find RealmSwift.xcframework -name '*.swiftinterface' \ + -exec sed -i '' 's/import Realm/import RealmObjc/' {} \; + find RealmSwift.xcframework -name '*.swiftinterface' \ + -exec sed -i '' 's/Realm.RLM/RealmObjc.RLM/g' {} \; + + # Realm is statically linked into RealmSwift so we no longer actually + # need the obj-c static library, and just need the framework shell. + # Remove everything but placeholder.o so that there's still a library + # to link against that just doesn't define any symbols. + find RealmObjc.xcframework -name 'Realm' | while read file; do + ( + cd $(dirname $file) + if readlink Realm > /dev/null; then + ln -sf Versions/Current/RealmObjc Realm + elif lipo -info Realm | grep -q 'Non-fat'; then + ar -t Realm | grep -v placeholder | tr '\n' '\0' | xargs -0 ar -d Realm >/dev/null 2>&1 + ranlib Realm >/dev/null 2>&1 + else + for arch in $(lipo -info Realm | cut -f3 -d':'); do + lipo Realm -thin $arch -output tmp.a + ar -t tmp.a | grep -v placeholder | tr '\n' '\0' | xargs -0 ar -d tmp.a >/dev/null 2>&1 + ranlib tmp.a >/dev/null 2>&1 + lipo Realm -replace $arch tmp.a -output Realm + rm tmp.a + done + fi + mv Realm RealmObjc + ) + done + + # We built Realm.framework as a static framework so that we could link + # it into RealmSwift.framework and not have to deal with the runtime + # implications of renaming the shared library, but we want the end + # result to be that Realm.xcframework is a dynamic framework. Our build + # system isn't really set up to build both static and dynamic versions + # of it, so instead just turn each of the static libraries in + # Realm.xcframework into a shared library. + cd Realm.xcframework + i=0 + while plist_get Info.plist "AvailableLibraries:$i" > /dev/null; do + arch_dir_name="$(plist_get Info.plist "AvailableLibraries:$i:LibraryIdentifier")" + platform="$(plist_get Info.plist "AvailableLibraries:$i:SupportedPlatform")" + variant="$(plist_get Info.plist "AvailableLibraries:$i:SupportedPlatformVariant" 2> /dev/null || echo 'os')" + deployment_target_name="$platform" + install_name='@rpath/Realm.framework/Realm' + bitcode_flag='-fembed-bitcode' + if [ "$variant" = 'simulator' ]; then + bitcode_flag='' + fi + case "$platform" in + "macos") sdk='macosx'; install_name='@rpath/Realm.framework/Versions/A/Realm'; bitcode_flag='';; + "ios") sdk="iphone$variant"; deployment_target_name='iphoneos';; + "watchos") sdk="watch$variant";; + "tvos") sdk="appletv$variant";; + esac + deployment_target=$(grep -i "$deployment_target_name.*_DEPLOYMENT_TARGET" ../../Configuration/Base.xcconfig \ + | sed 's/.*= \(.*\);/\1/') + architectures="" + j=0 + while plist_get Info.plist "AvailableLibraries:$i:SupportedArchitectures:$j" > /dev/null; do + architectures="${architectures} -arch $(plist_get Info.plist "AvailableLibraries:$i:SupportedArchitectures:$j")" + j=$(($j + 1)) + done + + ( + cd $arch_dir_name/Realm.framework + realm_lib=$(readlink Realm || echo 'Realm') + # feature_token.cpp.o depends on PKey, which isn't actually + # present in the macOS build of the sync library. This normally + # works fine because we never reference any symbols from + # feature_token.cpp.o so it doesn't get pulled in at all, but + # -all_load makes every object file in the input get linked + # into the shared library. + ar -d $realm_lib feature_token.cpp.o 2> /dev/null || true + clang++ -shared $architectures \ + -target ${platform}${deployment_target} \ + -isysroot $(xcrun --sdk ${sdk} --show-sdk-path) \ + -install_name "$install_name" \ + -compatibility_version 1 -current_version 1 \ + -fapplication-extension \ + $bitcode_flag \ + -Wl,-all_load \ + -Wl,-unexported_symbol,'__Z*' \ + -o realm.dylib \ + Realm -lz + mv realm.dylib $realm_lib + ) + + i=$(($i + 1)) + done + + exit 0 + ;; + + ###################################### + # Analysis + ###################################### + + "analyze-osx") + xc "-scheme Realm -configuration $CONFIGURATION analyze" + exit 0 + ;; + + ###################################### + # Testing + ###################################### + "test") + set +e # Run both sets of tests even if the first fails + failed=0 + sh build.sh test-ios-static || failed=1 + sh build.sh test-ios-dynamic || failed=1 + sh build.sh test-ios-swift || failed=1 + sh build.sh test-ios-devices || failed=1 + sh build.sh test-tvos-devices || failed=1 + sh build.sh test-osx || failed=1 + sh build.sh test-osx-swift || failed=1 + if (( $(xcode_version_major) >= 11 )); then + sh build.sh test-catalyst || failed=1 + sh build.sh test-catalyst-swift || failed=1 + fi + exit $failed + ;; + + "test-all") + set +e + failed=0 + sh build.sh test || failed=1 + sh build.sh test-debug || failed=1 + exit $failed + ;; + + "test-ios-static") + test_ios_static "name=iPhone 8" + exit 0 + ;; + + "test-ios-dynamic") + xc "-scheme Realm -configuration $CONFIGURATION -sdk iphonesimulator -destination 'name=iPhone 8' build-for-testing" + xc "-scheme Realm -configuration $CONFIGURATION -sdk iphonesimulator -destination 'name=iPhone 8' test" + exit 0 + ;; + + "test-ios-swift") + xc "-scheme RealmSwift -configuration $CONFIGURATION -sdk iphonesimulator -destination 'name=iPhone 8' build-for-testing" + xc "-scheme RealmSwift -configuration $CONFIGURATION -sdk iphonesimulator -destination 'name=iPhone 8' test" + exit 0 + ;; + + "test-ios-devices") + failed=0 + trap "failed=1" ERR + sh build.sh test-ios-devices-objc + sh build.sh test-ios-devices-swift + exit $failed + ;; + + "test-ios-devices-objc") + test_devices iphoneos "Realm" "$CONFIGURATION" + exit $? + ;; + + "test-ios-devices-swift") + test_devices iphoneos "RealmSwift" "$CONFIGURATION" + exit $? + ;; + + "test-tvos") + destination="Apple TV" + xctest "-scheme Realm -configuration $CONFIGURATION -sdk appletvsimulator -destination 'name=$destination'" + exit $? + ;; + + "test-tvos-swift") + destination="Apple TV" + xctest "-scheme RealmSwift -configuration $CONFIGURATION -sdk appletvsimulator -destination 'name=$destination'" + exit $? + ;; + + "test-tvos-devices") + test_devices appletvos TestHost "$CONFIGURATION" + ;; + + "test-osx") + COVERAGE_PARAMS="" + if [[ "$CONFIGURATION" == "Debug" ]]; then + COVERAGE_PARAMS="GCC_GENERATE_TEST_COVERAGE_FILES=YES GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES" + fi + xctest "-scheme Realm -configuration $CONFIGURATION $COVERAGE_PARAMS" + exit 0 + ;; + + "test-osx-swift") + xctest "-scheme RealmSwift -configuration $CONFIGURATION" + exit 0 + ;; + + "test-osx-object-server") + xctest "-scheme 'Object Server Tests' -configuration $CONFIGURATION -sdk macosx" + exit 0 + ;; + + test-swiftpm*) + SANITIZER=$(echo $COMMAND | cut -d - -f 3) + if [ -n "$SANITIZER" ]; then + SANITIZER="--sanitize $SANITIZER" + export ASAN_OPTIONS='check_initialization_order=true:detect_stack_use_after_return=true' + fi + xcrun swift test --configuration $(echo $CONFIGURATION | tr "[:upper:]" "[:lower:]") $SANITIZER + exit 0 + ;; + + "test-catalyst") + export REALM_SDKROOT=iphoneos + xc "-scheme Realm -configuration $CONFIGURATION -destination 'platform=macOS,variant=Mac Catalyst' CODE_SIGN_IDENTITY='' build-for-testing" + xc "-scheme Realm -configuration $CONFIGURATION -destination 'platform=macOS,variant=Mac Catalyst' CODE_SIGN_IDENTITY='' test" + exit 0 + ;; + + "test-catalyst-swift") + export REALM_SDKROOT=iphoneos + xc "-scheme RealmSwift -configuration $CONFIGURATION -destination 'platform=macOS,variant=Mac Catalyst' CODE_SIGN_IDENTITY='' build-for-testing" + xc "-scheme RealmSwift -configuration $CONFIGURATION -destination 'platform=macOS,variant=Mac Catalyst' CODE_SIGN_IDENTITY='' test" + exit 0 + ;; + + ###################################### + # Full verification + ###################################### + "verify") + sh build.sh verify-cocoapods + sh build.sh verify-docs + sh build.sh verify-osx + sh build.sh verify-osx-debug + sh build.sh verify-osx-swift + sh build.sh verify-osx-swift-debug + sh build.sh verify-ios-static + sh build.sh verify-ios-static-debug + sh build.sh verify-ios-dynamic + sh build.sh verify-ios-dynamic-debug + sh build.sh verify-ios-swift + sh build.sh verify-ios-swift-debug + sh build.sh verify-ios-device-objc + sh build.sh verify-ios-device-swift + sh build.sh verify-watchos + sh build.sh verify-tvos + sh build.sh verify-tvos-debug + sh build.sh verify-tvos-device + sh build.sh verify-swiftlint + sh build.sh verify-swiftpm + sh build.sh verify-osx-object-server + if (( $(xcode_version_major) >= 11 )); then + sh build.sh verify-catalyst + sh build.sh verify-catalyst-swift + fi + ;; + + "verify-cocoapods") + if [[ -d .git ]]; then + # Verify the current branch, unless one was already specified in the sha environment variable. + if [[ -z $sha ]]; then + export sha=$(git rev-parse --abbrev-ref HEAD) + fi + + if [[ $(git log -1 @{push}..) != "" ]] || ! git diff-index --quiet HEAD; then + echo "WARNING: verify-cocoapods will test the latest revision of $sha found on GitHub." + echo " Any unpushed local changes will not be tested." + echo "" + sleep 1 + fi + fi + + sh build.sh verify-cocoapods-ios + sh build.sh verify-cocoapods-ios-dynamic + sh build.sh verify-cocoapods-osx + sh build.sh verify-cocoapods-watchos + + # https://github.com/CocoaPods/CocoaPods/issues/7708 + export EXPANDED_CODE_SIGN_IDENTITY='' + cd examples/installation + sh build.sh test-ios-objc-cocoapods + sh build.sh test-ios-objc-cocoapods-dynamic + sh build.sh test-ios-swift-cocoapods + sh build.sh test-osx-objc-cocoapods + sh build.sh test-osx-swift-cocoapods + sh build.sh test-watchos-objc-cocoapods + sh build.sh test-watchos-swift-cocoapods + ;; + + verify-cocoapods-ios-dynamic) + PLATFORM=$(echo $COMMAND | cut -d - -f 3) + # https://github.com/CocoaPods/CocoaPods/issues/7708 + export EXPANDED_CODE_SIGN_IDENTITY='' + cd examples/installation + sh build.sh test-ios-objc-cocoapods-dynamic + ;; + + verify-cocoapods-*) + PLATFORM=$(echo $COMMAND | cut -d - -f 3) + # https://github.com/CocoaPods/CocoaPods/issues/7708 + export EXPANDED_CODE_SIGN_IDENTITY='' + cd examples/installation + sh build.sh test-$PLATFORM-objc-cocoapods + sh build.sh test-$PLATFORM-swift-cocoapods + ;; + + "verify-osx-encryption") + REALM_ENCRYPT_ALL=YES sh build.sh test-osx + exit 0 + ;; + + "verify-osx") + sh build.sh test-osx + sh build.sh examples-osx + + ( + cd examples/osx/objc/build/DerivedData/RealmExamples/Build/Products/$CONFIGURATION + DYLD_FRAMEWORK_PATH=. ./JSONImport >/dev/null + ) + exit 0 + ;; + + "verify-osx-swift") + sh build.sh test-osx-swift + exit 0 + ;; + + "verify-ios-static") + REALM_EXTRA_BUILD_ARGUMENTS="$REALM_EXTRA_BUILD_ARGUMENTS -workspace examples/ios/objc/RealmExamples.xcworkspace" sh build.sh test-ios-static + sh build.sh examples-ios + ;; + + "verify-ios-dynamic") + sh build.sh test-ios-dynamic + ;; + + "verify-ios-swift") + REALM_EXTRA_BUILD_ARGUMENTS="$REALM_EXTRA_BUILD_ARGUMENTS -workspace examples/ios/swift/RealmExamples.xcworkspace" sh build.sh test-ios-swift + sh build.sh examples-ios-swift + ;; + + "verify-ios-device-objc") + sh build.sh test-ios-devices-objc + exit 0 + ;; + + "verify-ios-device-swift") + sh build.sh test-ios-devices-swift + exit 0 + ;; + + "verify-docs") + sh build.sh docs + for lang in swift objc; do + undocumented="docs/${lang}_output/undocumented.json" + if ruby -rjson -e "j = JSON.parse(File.read('docs/${lang}_output/undocumented.json')); exit j['warnings'].length != 0"; then + echo "Undocumented Realm $lang declarations:" + cat "$undocumented" + exit 1 + fi + done + exit 0 + ;; + + "verify-watchos") + sh build.sh watchos-swift + exit 0 + ;; + + "verify-tvos") + sh build.sh test-tvos + sh build.sh examples-tvos + exit 0 + ;; + + "verify-tvos-swift") + sh build.sh test-tvos-swift + sh build.sh examples-tvos-swift + exit 0 + ;; + + "verify-tvos-device") + sh build.sh test-tvos-devices + exit 0 + ;; + + "verify-swiftlint") + swiftlint lint --strict + exit 0 + ;; + + verify-swiftpm*) + sh build.sh test-$(echo $COMMAND | cut -d - -f 2-) + exit 0 + ;; + + "verify-osx-object-server") + sh build.sh test-osx-object-server + exit 0 + ;; + + "verify-catalyst") + sh build.sh test-catalyst + exit 0 + ;; + + "verify-catalyst-swift") + sh build.sh test-catalyst-swift + exit 0 + ;; + + ###################################### + # Docs + ###################################### + "docs") + build_docs objc + build_docs swift + exit 0 + ;; + + ###################################### + # Examples + ###################################### + "examples") + sh build.sh clean + sh build.sh examples-ios + sh build.sh examples-ios-swift + sh build.sh examples-osx + sh build.sh examples-tvos + sh build.sh examples-tvos-swift + exit 0 + ;; + + "examples-ios") + sh build.sh prelaunch-simulator + workspace="examples/ios/objc/RealmExamples.xcworkspace" + pod install --project-directory="$workspace/.." --no-repo-update + xc "-workspace $workspace -scheme Simple -configuration $CONFIGURATION -destination 'name=iPhone 8' build ${CODESIGN_PARAMS}" + xc "-workspace $workspace -scheme TableView -configuration $CONFIGURATION -destination 'name=iPhone 8' build ${CODESIGN_PARAMS}" + xc "-workspace $workspace -scheme Migration -configuration $CONFIGURATION -destination 'name=iPhone 8' build ${CODESIGN_PARAMS}" + xc "-workspace $workspace -scheme Backlink -configuration $CONFIGURATION -destination 'name=iPhone 8' build ${CODESIGN_PARAMS}" + xc "-workspace $workspace -scheme GroupedTableView -configuration $CONFIGURATION -destination 'name=iPhone 8' build ${CODESIGN_PARAMS}" + xc "-workspace $workspace -scheme RACTableView -configuration $CONFIGURATION -destination 'name=iPhone 8' build ${CODESIGN_PARAMS}" + xc "-workspace $workspace -scheme Encryption -configuration $CONFIGURATION -destination 'name=iPhone 8' build ${CODESIGN_PARAMS}" + xc "-workspace $workspace -scheme Draw -configuration $CONFIGURATION -destination 'name=iPhone 8' build ${CODESIGN_PARAMS}" + + if [ ! -z "${JENKINS_HOME}" ]; then + xc "-workspace $workspace -scheme Extension -configuration $CONFIGURATION -destination 'name=iPhone 8' build ${CODESIGN_PARAMS}" + fi + + exit 0 + ;; + + "examples-ios-swift") + sh build.sh prelaunch-simulator + workspace="examples/ios/swift/RealmExamples.xcworkspace" + if [[ ! -d "$workspace" ]]; then + workspace="${workspace/swift/swift-$REALM_XCODE_VERSION}" + fi + + xc "-workspace $workspace -scheme Simple -configuration $CONFIGURATION -destination 'name=iPhone 8' build ${CODESIGN_PARAMS}" + xc "-workspace $workspace -scheme TableView -configuration $CONFIGURATION -destination 'name=iPhone 8' build ${CODESIGN_PARAMS}" + xc "-workspace $workspace -scheme Migration -configuration $CONFIGURATION -destination 'name=iPhone 8' build ${CODESIGN_PARAMS}" + xc "-workspace $workspace -scheme Encryption -configuration $CONFIGURATION -destination 'name=iPhone 8' build ${CODESIGN_PARAMS}" + xc "-workspace $workspace -scheme Backlink -configuration $CONFIGURATION -destination 'name=iPhone 8' build ${CODESIGN_PARAMS}" + xc "-workspace $workspace -scheme GroupedTableView -configuration $CONFIGURATION -destination 'name=iPhone 8' build ${CODESIGN_PARAMS}" + exit 0 + ;; + + "examples-osx") + xc "-workspace examples/osx/objc/RealmExamples.xcworkspace -scheme JSONImport -configuration ${CONFIGURATION} build ${CODESIGN_PARAMS}" + ;; + + "examples-tvos") + workspace="examples/tvos/objc/RealmExamples.xcworkspace" + destination="Apple TV" + + xc "-workspace $workspace -scheme DownloadCache -configuration $CONFIGURATION -destination 'name=$destination' build ${CODESIGN_PARAMS}" + xc "-workspace $workspace -scheme PreloadedData -configuration $CONFIGURATION -destination 'name=$destination' build ${CODESIGN_PARAMS}" + exit 0 + ;; + + "examples-tvos-swift") + workspace="examples/tvos/swift/RealmExamples.xcworkspace" + if [[ ! -d "$workspace" ]]; then + workspace="${workspace/swift/swift-$REALM_XCODE_VERSION}" + fi + + destination="Apple TV" + xc "-workspace $workspace -scheme DownloadCache -configuration $CONFIGURATION -destination 'name=$destination' build ${CODESIGN_PARAMS}" + xc "-workspace $workspace -scheme PreloadedData -configuration $CONFIGURATION -destination 'name=$destination' build ${CODESIGN_PARAMS}" + exit 0 + ;; + + ###################################### + # Versioning + ###################################### + "get-version") + echo "$(plist_get 'Realm/Realm-Info.plist' 'CFBundleShortVersionString')" + exit 0 + ;; + + "set-version") + realm_version="$2" + version_files="Realm/Realm-Info.plist" + + if [ -z "$realm_version" ]; then + echo "You must specify a version." + exit 1 + fi + # The bundle version can contain only three groups of digits separated by periods, + # so strip off any -beta.x tag from the end of the version string. + bundle_version=$(echo "$realm_version" | cut -d - -f 1) + for version_file in $version_files; do + PlistBuddy -c "Set :CFBundleVersion $bundle_version" "$version_file" + PlistBuddy -c "Set :CFBundleShortVersionString $realm_version" "$version_file" + done + sed -i '' "s/^VERSION=.*/VERSION=$realm_version/" dependencies.list + sed -i '' "s/^let coreVersionStr =.*/let coreVersionStr = \"$REALM_CORE_VERSION\"/" Package.swift + sed -i '' "s/^let cocoaVersionStr =.*/let cocoaVersionStr = \"$realm_version\"/" Package.swift + exit 0 + ;; + + ###################################### + # Bitcode Detection + ###################################### + + "binary-has-bitcode") + # Disable pipefail as grep -q will make otool fail due to exiting + # before reading all the output + set +o pipefail + + BINARY="$2" + if otool -l "$BINARY" | grep -q "segname __LLVM"; then + exit 0 + fi + # Work around rdar://21826157 by checking for bitcode in thin binaries + + # Get architectures for binary + archs="$(lipo -info "$BINARY" | rev | cut -d ':' -f1 | rev)" + + archs_array=( $archs ) + if [[ ${#archs_array[@]} -lt 2 ]]; then + echo 'Error: Built library is not a fat binary' + exit 1 # Early exit if not a fat binary + fi + + TEMPDIR=$(mktemp -d $TMPDIR/realm-bitcode-check.XXXX) + + for arch in $archs; do + lipo -thin "$arch" "$BINARY" -output "$TEMPDIR/$arch" + if otool -l "$TEMPDIR/$arch" | grep -q "segname __LLVM"; then + exit 0 + fi + done + echo 'Error: Built library does not contain bitcode' + exit 1 + ;; + + ###################################### + # CocoaPods + ###################################### + "cocoapods-setup") + if [ ! -d core ]; then + sh build.sh download-sync + rm core + mv sync-* core + mv core/librealm-ios.a core/librealmcore-ios.a + mv core/librealm-macosx.a core/librealmcore-macosx.a + mv core/librealm-tvos.a core/librealmcore-tvos.a + mv core/librealm-watchos.a core/librealmcore-watchos.a + fi + + if [[ "$2" != "swift" ]]; then + if [ ! -d Realm/ObjectStore/src ]; then + cat >&2 < Realm/RLMPlatform.h + if [ -n "$COCOAPODS_VERSION" ]; then + # This variable is set for the prepare_command available + # from the 1.0 prereleases, which requires a different + # header layout within the header_mappings_dir. + cp Realm/*.h include + else + # For CocoaPods < 1.0, we need to scope the headers within + # the header_mappings_dir by another subdirectory to avoid + # Clang from complaining about non-modular headers. + mkdir -p include/Realm + cp Realm/*.h include/Realm + fi + else + sh build.sh set-swift-version + fi + ;; + + ###################################### + # Continuous Integration + ###################################### + + "ci-pr") + mkdir -p build/reports + export REALM_DISABLE_ANALYTICS=1 + export REALM_DISABLE_UPDATE_CHECKER=1 + # FIXME: Re-enable once CI can properly unlock the keychain + export REALM_DISABLE_METADATA_ENCRYPTION=1 + + # strip off the ios|tvos version specifier, e.g. the last part of: `ios-device-objc-ios8` + if [[ "$target" =~ ^((ios|tvos)-device(-(objc|swift))?)(-(ios|tvos)[[:digit:]]+)?$ ]]; then + export target=${BASH_REMATCH[1]} + fi + + if [ "$target" = "docs" ]; then + sh build.sh set-swift-version + sh build.sh verify-docs + elif [ "$target" = "swiftlint" ]; then + sh build.sh verify-swiftlint + else + export sha=$GITHUB_PR_SOURCE_BRANCH + export CONFIGURATION=$configuration + export REALM_EXTRA_BUILD_ARGUMENTS='GCC_GENERATE_DEBUGGING_SYMBOLS=NO -allowProvisioningUpdates' + if [[ ${target} != *"osx"* ]];then + sh build.sh prelaunch-simulator + fi + + source $(brew --prefix nvm)/nvm.sh --no-use + export REALM_NODE_PATH="$(nvm which 8)" + + # Reset CoreSimulator.log + mkdir -p ~/Library/Logs/CoreSimulator + echo > ~/Library/Logs/CoreSimulator/CoreSimulator.log + + if [ -d ~/Library/Developer/CoreSimulator/Devices/ ]; then + # Verify that no Realm files still exist + ! find ~/Library/Developer/CoreSimulator/Devices/ -name '*.realm' | grep -q . + fi + + failed=0 + sh build.sh verify-$target 2>&1 | tee build/build.log | xcpretty -r junit -o build/reports/junit.xml || failed=1 + if [ "$failed" = "1" ] && cat build/build.log | grep -E 'DTXProxyChannel|DTXChannel|out of date and needs to be rebuilt|operation never finished bootstrapping'; then + echo "Known Xcode error detected. Running job again." + if cat build/build.log | grep -E 'out of date and needs to be rebuilt'; then + rm -rf build/DerivedData + fi + failed=0 + sh build.sh verify-$target | tee build/build.log | xcpretty -r junit -o build/reports/junit.xml || failed=1 + elif [ "$failed" = "1" ] && tail ~/Library/Logs/CoreSimulator/CoreSimulator.log | grep -E "Operation not supported|Failed to lookup com.apple.coreservices.lsuseractivity.simulatorsupport"; then + echo "Known Xcode error detected. Running job again." + failed=0 + sh build.sh verify-$target | tee build/build.log | xcpretty -r junit -o build/reports/junit.xml || failed=1 + fi + if [ "$failed" = "1" ]; then + echo "\n\n***\nbuild/build.log\n***\n\n" && cat build/build.log || true + echo "\n\n***\nCoreSimulator.log\n***\n\n" && cat ~/Library/Logs/CoreSimulator/CoreSimulator.log + exit 1 + fi + fi + + if [ "$target" = "osx" ] && [ "$configuration" = "Debug" ]; then + gcovr -r . -f ".*Realm.*" -e ".*Tests.*" -e ".*core.*" --xml > build/reports/coverage-report.xml + WS=$(pwd | sed "s/\//\\\\\//g") + sed -i ".bak" "s/\./${WS}/" build/reports/coverage-report.xml + fi + ;; + + ###################################### + # Release packaging + ###################################### + + "package-examples") + ./scripts/package_examples.rb + zip --symlinks -r realm-examples.zip examples -x "examples/installation/*" + ;; + + "package-test-examples-objc") + if ! VERSION=$(echo realm-objc-*.zip | egrep -o '\d*\.\d*\.\d*-[a-z]*(\.\d*)?'); then + VERSION=$(echo realm-objc-*.zip | egrep -o '\d*\.\d*\.\d*') + fi + OBJC="realm-objc-${VERSION}" + unzip ${OBJC}.zip + + cp $0 ${OBJC} + cp -r ${source_root}/scripts ${OBJC} + cd ${OBJC} + sh build.sh examples-ios + sh build.sh examples-tvos + sh build.sh examples-osx + cd .. + rm -rf ${OBJC} + ;; + + "package-test-examples-swift") + if ! VERSION=$(echo realm-swift-*.zip | egrep -o '\d*\.\d*\.\d*-[a-z]*(\.\d*)?'); then + VERSION=$(echo realm-swift-*.zip | egrep -o '\d*\.\d*\.\d*') + fi + SWIFT="realm-swift-${VERSION}" + unzip ${SWIFT}.zip + + cp $0 ${SWIFT} + cp -r ${source_root}/scripts ${SWIFT} + cd ${SWIFT} + sh build.sh examples-ios-swift + sh build.sh examples-tvos-swift + cd .. + rm -rf ${SWIFT} + ;; + + "package-ios-static") + sh build.sh prelaunch-simulator + sh build.sh ios-static + + cd build/ios-static + zip --symlinks -r realm-framework-ios-static.zip Realm.framework + ;; + + "package") + PLATFORM="$2" + REALM_SWIFT_VERSION= + + set_xcode_and_swift_versions + + if [[ "$PLATFORM" = "catalyst" ]] && (( $(xcode_version_major) < 11 )); then + mkdir -p build/catalyst/swift-$REALM_XCODE_VERSION + else + sh build.sh prelaunch-simulator + sh build.sh $PLATFORM-swift + fi + + cd build/$PLATFORM + zip --symlinks -r realm-framework-$PLATFORM-$REALM_XCODE_VERSION.zip swift-$REALM_XCODE_VERSION + ;; + + "package-release") + LANG="$2" + TEMPDIR=$(mktemp -d $TMPDIR/realm-release-package-${LANG}.XXXX) + + VERSION=$(sh build.sh get-version) + + FOLDER=${TEMPDIR}/realm-${LANG}-${VERSION} + + mkdir -p ${FOLDER}/osx ${FOLDER}/ios ${FOLDER}/watchos ${FOLDER}/tvos + + if [[ "${LANG}" == "objc" ]]; then + mkdir -p ${FOLDER}/ios/static + mkdir -p ${FOLDER}/ios/dynamic + mkdir -p ${FOLDER}/Swift + + unzip ${WORKSPACE}/realm-framework-ios-static.zip -d ${FOLDER}/ios/static + for platform in osx ios watchos tvos catalyst; do + unzip ${WORKSPACE}/realm-framework-${platform}-${REALM_XCODE_VERSION}.zip -d ${FOLDER}/${platform} + mv ${FOLDER}/${platform}/swift-*/Realm.framework ${FOLDER}/${platform} + rm -r ${FOLDER}/${platform}/swift-* + done + + mv ${FOLDER}/ios/Realm.framework ${FOLDER}/ios/dynamic + else + for platform in osx ios watchos tvos catalyst; do + find ${WORKSPACE} -name "realm-framework-$platform-*.zip" \ + -maxdepth 1 \ + -exec unzip {} -d ${FOLDER}/${platform} \; + done + fi + + ( + cd ${WORKSPACE} + cp -R plugin ${FOLDER} + cp LICENSE ${FOLDER}/LICENSE.txt + if [[ "${LANG}" == "objc" ]]; then + cp Realm/Swift/RLMSupport.swift ${FOLDER}/Swift/ + fi + ) + + ( + cd ${FOLDER} + unzip ${WORKSPACE}/realm-examples.zip + cd examples + if [[ "${LANG}" == "objc" ]]; then + rm -rf ios/swift-* tvos/swift-* + else + rm -rf ios/objc ios/rubymotion osx tvos/objc + fi + ) + + cat > ${FOLDER}/docs.webloc < + + + + URL + https://realm.io/docs/${LANG}/${VERSION} + + +EOF + + ( + cd ${TEMPDIR} + zip --symlinks -r realm-${LANG}-${VERSION}.zip realm-${LANG}-${VERSION} + mv realm-${LANG}-${VERSION}.zip ${WORKSPACE} + ) + ;; + + "test-package-release") + # Generate a release package locally for testing purposes + # Real releases should always be done via Jenkins + if [ -z "${WORKSPACE}" ]; then + echo 'WORKSPACE must be set to a directory to assemble the release in' + exit 1 + fi + if [ -d "${WORKSPACE}" ]; then + echo 'WORKSPACE directory should not already exist' + exit 1 + fi + + REALM_SOURCE="$(pwd)" + mkdir -p "$WORKSPACE" + WORKSPACE="$(cd "$WORKSPACE" && pwd)" + export WORKSPACE + cd $WORKSPACE + git clone --recursive $REALM_SOURCE realm-cocoa + cd realm-cocoa + + echo 'Packaging iOS' + sh build.sh package-ios-static + cp build/ios-static/realm-framework-ios-static.zip . + sh build.sh package ios + cp build/ios/realm-framework-ios-$REALM_XCODE_VERSION.zip . + + echo 'Packaging macOS' + sh build.sh package osx + cp build/osx/realm-framework-osx-$REALM_XCODE_VERSION.zip . + + echo 'Packaging watchOS' + sh build.sh package watchos + cp build/watchos/realm-framework-watchos-$REALM_XCODE_VERSION.zip . + + echo 'Packaging tvOS' + sh build.sh package tvos + cp build/tvos/realm-framework-tvos-$REALM_XCODE_VERSION.zip . + + echo 'Packaging Catalyst' + sh build.sh package catalyst + cp build/catalyst/realm-framework-catalyst-$REALM_XCODE_VERSION.zip . + + echo 'Packaging examples' + sh build.sh package-examples + + echo 'Building final release packages' + export WORKSPACE="${WORKSPACE}/realm-cocoa" + sh build.sh package-release objc + sh build.sh package-release swift + + echo 'Testing packaged examples' + sh build.sh package-test-examples-objc + sh build.sh package-test-examples-swift + ;; + + "github-release") + if [ -z "${GITHUB_ACCESS_TOKEN}" ]; then + echo 'GITHUB_ACCESS_TOKEN must be set to create GitHub releases' + exit 1 + fi + ./scripts/github_release.rb + ;; + + "add-empty-changelog") + empty_section=$(cat < ([#????](https://github.com/realm/realm-js/issues/????), since v?.?.?) +* None. + + + +### Compatibility +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) +* Realm Object Server: 3.21.0 or later. +* APIs are backwards compatible with all previous releases in the 4.x.y series. +* Carthage release for Swift is built with Xcode 11.3. + +### Internal +Upgraded realm-core from ? to ? +Upgraded realm-sync from ? to ? + +EOS) + changelog=$(cat CHANGELOG.md) + echo "$empty_section" > CHANGELOG.md + echo >> CHANGELOG.md + echo "$changelog" >> CHANGELOG.md + ;; + + *) + echo "Unknown command '$COMMAND'" + usage + exit 1 + ;; +esac diff --git a/!main project/Pods/Realm/core/librealmcore-ios.a b/!main project/Pods/Realm/core/librealmcore-ios.a new file mode 100644 index 0000000..0f80c66 Binary files /dev/null and b/!main project/Pods/Realm/core/librealmcore-ios.a differ diff --git a/!main project/Pods/Realm/include/NSError+RLMSync.h b/!main project/Pods/Realm/include/NSError+RLMSync.h new file mode 100644 index 0000000..797db58 --- /dev/null +++ b/!main project/Pods/Realm/include/NSError+RLMSync.h @@ -0,0 +1,46 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2017 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RLMSyncErrorActionToken; + +/// NSError category extension providing methods to get data out of Realm's +/// "client reset" error. +@interface NSError (RLMSync) + +/** + Given an appropriate Realm Object Server error, return the token that + can be passed into `+[RLMSyncSession immediatelyHandleError:]` to + immediately perform error clean-up work, or nil if the error isn't of + a type that provides a token. + */ +- (nullable RLMSyncErrorActionToken *)rlmSync_errorActionToken NS_REFINED_FOR_SWIFT; + +/** + Given a Realm Object Server client reset error, return the path where the + backup copy of the Realm will be placed once the client reset process is + complete. + */ +- (nullable NSString *)rlmSync_clientResetBackedUpRealmPath NS_SWIFT_UNAVAILABLE(""); + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMAccessor.h b/!main project/Pods/Realm/include/RLMAccessor.h new file mode 100644 index 0000000..bcf5dfb --- /dev/null +++ b/!main project/Pods/Realm/include/RLMAccessor.h @@ -0,0 +1,53 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +@class RLMObjectSchema, RLMProperty, RLMObjectBase; + +NS_ASSUME_NONNULL_BEGIN + +// +// Accessors Class Creation/Caching +// + +// get accessor classes for an object class - generates classes if not cached +Class RLMManagedAccessorClassForObjectClass(Class objectClass, RLMObjectSchema *schema, const char *name); +Class RLMUnmanagedAccessorClassForObjectClass(Class objectClass, RLMObjectSchema *schema); + +// +// Dynamic getters/setters +// +FOUNDATION_EXTERN void RLMDynamicValidatedSet(RLMObjectBase *obj, NSString *propName, id __nullable val); +FOUNDATION_EXTERN id __nullable RLMDynamicGet(RLMObjectBase *obj, RLMProperty *prop); +FOUNDATION_EXTERN id __nullable RLMDynamicGetByName(RLMObjectBase *obj, NSString *propName); + +// by property/column +void RLMDynamicSet(RLMObjectBase *obj, RLMProperty *prop, id val); + +// +// Class modification +// + +// Replace className method for the given class +void RLMReplaceClassNameMethod(Class accessorClass, NSString *className); + +// Replace sharedSchema method for the given class +void RLMReplaceSharedSchemaMethod(Class accessorClass, RLMObjectSchema * __nullable schema); + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMAccessor.hpp b/!main project/Pods/Realm/include/RLMAccessor.hpp new file mode 100644 index 0000000..88884e2 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMAccessor.hpp @@ -0,0 +1,117 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2017 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMAccessor.h" + +#import "object_accessor.hpp" + +#import "RLMUtil.hpp" + +@class RLMRealm; +class RLMClassInfo; +class RLMObservationInfo; + +// realm::util::Optional doesn't work because Objective-C types can't +// be members of unions with ARC, so this covers the subset of Optional that we +// actually need. +struct RLMOptionalId { + id value; + RLMOptionalId(id value) : value(value) { } + explicit operator bool() const noexcept { return value; } + id operator*() const noexcept { return value; } +}; + +class RLMAccessorContext { +public: + // Accessor context interface + RLMAccessorContext(RLMAccessorContext& parent, realm::Property const& property); + + id box(realm::List&&); + id box(realm::Results&&); + id box(realm::Object&&); + id box(realm::RowExpr); + + id box(bool v) { return @(v); } + id box(double v) { return @(v); } + id box(float v) { return @(v); } + id box(long long v) { return @(v); } + id box(realm::StringData v) { return RLMStringDataToNSString(v) ?: NSNull.null; } + id box(realm::BinaryData v) { return RLMBinaryDataToNSData(v) ?: NSNull.null; } + id box(realm::Timestamp v) { return RLMTimestampToNSDate(v) ?: NSNull.null; } + id box(realm::Mixed v) { return RLMMixedToObjc(v); } + + id box(realm::util::Optional v) { return v ? @(*v) : NSNull.null; } + id box(realm::util::Optional v) { return v ? @(*v) : NSNull.null; } + id box(realm::util::Optional v) { return v ? @(*v) : NSNull.null; } + id box(realm::util::Optional v) { return v ? @(*v) : NSNull.null; } + + void will_change(realm::Row const&, realm::Property const&); + void will_change(realm::Object& obj, realm::Property const& prop) { will_change(obj.row(), prop); } + void did_change(); + + RLMOptionalId value_for_property(id dict, realm::Property const&, size_t prop_index); + RLMOptionalId default_value_for_property(realm::ObjectSchema const&, + realm::Property const& prop); + + bool is_same_list(realm::List const& list, id v) const noexcept; + + template + void enumerate_list(__unsafe_unretained const id v, Func&& func) { + id enumerable = RLMAsFastEnumeration(v) ?: v; + for (id value in enumerable) { + func(value); + } + } + + template + T unbox(id v, realm::CreatePolicy = realm::CreatePolicy::Skip, size_t = 0); + + bool is_null(id v) { return v == NSNull.null; } + id null_value() { return NSNull.null; } + id no_value() { return nil; } + bool allow_missing(id v) { return [v isKindOfClass:[NSArray class]]; } + + std::string print(id obj) { return [obj description].UTF8String; } + + // Internal API + RLMAccessorContext(RLMObjectBase *parentObject, const realm::Property *property = nullptr); + RLMAccessorContext(RLMClassInfo& info, bool promote=true); + + // The property currently being accessed; needed for KVO things for boxing + // List and Results + RLMProperty *currentProperty; + +private: + __unsafe_unretained RLMRealm *const _realm; + RLMClassInfo& _info; + // If true, promote unmanaged RLMObjects passed to box() with create=true + // rather than copying them + bool _promote_existing = true; + // Parent object of the thing currently being processed, for KVO purposes + __unsafe_unretained RLMObjectBase *const _parentObject = nil; + + // Cached default values dictionary to avoid having to call the class method + // for every property + NSDictionary *_defaultValues; + + RLMObservationInfo *_observationInfo = nullptr; + NSString *_kvoPropertyName = nil; + + id defaultValue(NSString *key); + id propertyValue(id obj, size_t propIndex, __unsafe_unretained RLMProperty *const prop); +}; diff --git a/!main project/Pods/Realm/include/RLMAnalytics.hpp b/!main project/Pods/Realm/include/RLMAnalytics.hpp new file mode 100644 index 0000000..ccb4ea2 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMAnalytics.hpp @@ -0,0 +1,55 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +// Asynchronously submits build information to Realm if running in an iOS +// simulator or on OS X if a debugger is attached. Does nothing if running on an +// iOS / watchOS device or if a debugger is *not* attached. +// +// To be clear: this does *not* run when your app is in production or on +// your end-user’s devices; it will only run in the simulator or when a debugger +// is attached. +// +// Why are we doing this? In short, because it helps us build a better product +// for you. None of the data personally identifies you, your employer or your +// app, but it *will* help us understand what language you use, what iOS +// versions you target, etc. Having this info will help prioritizing our time, +// adding new features and deprecating old features. Collecting an anonymized +// bundle & anonymized MAC is the only way for us to count actual usage of the +// other metrics accurately. If we don’t have a way to deduplicate the info +// reported, it will be useless, as a single developer building their Swift app +// 10 times would report 10 times more than a single Objective-C developer that +// only builds once, making the data all but useless. +// No one likes sharing data unless it’s necessary, we get it, and we’ve +// debated adding this for a long long time. Since Realm is a free product +// without an email signup, we feel this is a necessary step so we can collect +// relevant data to build a better product for you. If you truly, absolutely +// feel compelled to not send this data back to Realm, then you can set an env +// variable named REALM_DISABLE_ANALYTICS. Since Realm is free we believe +// letting these analytics run is a small price to pay for the product & support +// we give you. +// +// Currently the following information is reported: +// - What version of Realm is being used, and from which language (obj-c or Swift). +// - What version of OS X it's running on (in case Xcode aggressively drops +// support for older versions again, we need to know what we need to support). +// - The minimum iOS/OS X version that the application is targeting (again, to +// help us decide what versions we need to support). +// - An anonymous MAC address and bundle ID to aggregate the other information on. +// - What version of Swift is being used (if applicable). + +void RLMSendAnalytics(); diff --git a/!main project/Pods/Realm/include/RLMArray.h b/!main project/Pods/Realm/include/RLMArray.h new file mode 100644 index 0000000..3ef38eb --- /dev/null +++ b/!main project/Pods/Realm/include/RLMArray.h @@ -0,0 +1,440 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RLMObject, RLMResults; + +/** + `RLMArray` is the container type in Realm used to define to-many relationships. + + Unlike an `NSArray`, `RLMArray`s hold a single type, specified by the `objectClassName` property. + This is referred to in these docs as the “type” of the array. + + When declaring an `RLMArray` property, the type must be marked as conforming to a + protocol by the same name as the objects it should contain (see the + `RLM_ARRAY_TYPE` macro). In addition, the property can be declared using Objective-C + generics for better compile-time type safety. + + RLM_ARRAY_TYPE(ObjectType) + ... + @property RLMArray *arrayOfObjectTypes; + + `RLMArray`s can be queried with the same predicates as `RLMObject` and `RLMResult`s. + + `RLMArray`s cannot be created directly. `RLMArray` properties on `RLMObject`s are + lazily created when accessed, or can be obtained by querying a Realm. + + ### Key-Value Observing + + `RLMArray` supports array key-value observing on `RLMArray` properties on `RLMObject` + subclasses, and the `invalidated` property on `RLMArray` instances themselves is + key-value observing compliant when the `RLMArray` is attached to a managed + `RLMObject` (`RLMArray`s on unmanaged `RLMObject`s will never become invalidated). + + Because `RLMArray`s are attached to the object which they are a property of, they + do not require using the mutable collection proxy objects from + `-mutableArrayValueForKey:` or KVC-compatible mutation methods on the containing + object. Instead, you can call the mutation methods on the `RLMArray` directly. + */ + +@interface RLMArray : NSObject + +#pragma mark - Properties + +/** + The number of objects in the array. + */ +@property (nonatomic, readonly, assign) NSUInteger count; + +/** + The type of the objects in the array. + */ +@property (nonatomic, readonly, assign) RLMPropertyType type; + +/** + Indicates whether the objects in the collection can be `nil`. + */ +@property (nonatomic, readonly, getter = isOptional) BOOL optional; + +/** + The class name of the objects contained in the array. + + Will be `nil` if `type` is not RLMPropertyTypeObject. + */ +@property (nonatomic, readonly, copy, nullable) NSString *objectClassName; + +/** + The Realm which manages the array. Returns `nil` for unmanaged arrays. + */ +@property (nonatomic, readonly, nullable) RLMRealm *realm; + +/** + Indicates if the array can no longer be accessed. + */ +@property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated; + +#pragma mark - Accessing Objects from an Array + +/** + Returns the object at the index specified. + + @param index The index to look up. + + @return An object of the type contained in the array. + */ +- (RLMObjectType)objectAtIndex:(NSUInteger)index; + +/** + Returns the first object in the array. + + Returns `nil` if called on an empty array. + + @return An object of the type contained in the array. + */ +- (nullable RLMObjectType)firstObject; + +/** + Returns the last object in the array. + + Returns `nil` if called on an empty array. + + @return An object of the type contained in the array. + */ +- (nullable RLMObjectType)lastObject; + + + +#pragma mark - Adding, Removing, and Replacing Objects in an Array + +/** + Adds an object to the end of the array. + + @warning This method may only be called during a write transaction. + + @param object An object of the type contained in the array. + */ +- (void)addObject:(RLMObjectType)object; + +/** + Adds an array of objects to the end of the array. + + @warning This method may only be called during a write transaction. + + @param objects An enumerable object such as `NSArray` or `RLMResults` which contains objects of the + same class as the array. + */ +- (void)addObjects:(id)objects; + +/** + Inserts an object at the given index. + + Throws an exception if the index exceeds the bounds of the array. + + @warning This method may only be called during a write transaction. + + @param anObject An object of the type contained in the array. + @param index The index at which to insert the object. + */ +- (void)insertObject:(RLMObjectType)anObject atIndex:(NSUInteger)index; + +/** + Removes an object at the given index. + + Throws an exception if the index exceeds the bounds of the array. + + @warning This method may only be called during a write transaction. + + @param index The array index identifying the object to be removed. + */ +- (void)removeObjectAtIndex:(NSUInteger)index; + +/** + Removes the last object in the array. + + This is a no-op if the array is already empty. + + @warning This method may only be called during a write transaction. +*/ +- (void)removeLastObject; + +/** + Removes all objects from the array. + + @warning This method may only be called during a write transaction. + */ +- (void)removeAllObjects; + +/** + Replaces an object at the given index with a new object. + + Throws an exception if the index exceeds the bounds of the array. + + @warning This method may only be called during a write transaction. + + @param index The index of the object to be replaced. + @param anObject An object (of the same type as returned from the `objectClassName` selector). + */ +- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(RLMObjectType)anObject; + +/** + Moves the object at the given source index to the given destination index. + + Throws an exception if the index exceeds the bounds of the array. + + @warning This method may only be called during a write transaction. + + @param sourceIndex The index of the object to be moved. + @param destinationIndex The index to which the object at `sourceIndex` should be moved. + */ +- (void)moveObjectAtIndex:(NSUInteger)sourceIndex toIndex:(NSUInteger)destinationIndex; + +/** + Exchanges the objects in the array at given indices. + + Throws an exception if either index exceeds the bounds of the array. + + @warning This method may only be called during a write transaction. + + @param index1 The index of the object which should replace the object at index `index2`. + @param index2 The index of the object which should replace the object at index `index1`. + */ +- (void)exchangeObjectAtIndex:(NSUInteger)index1 withObjectAtIndex:(NSUInteger)index2; + +#pragma mark - Querying an Array + +/** + Returns the index of an object in the array. + + Returns `NSNotFound` if the object is not found in the array. + + @param object An object (of the same type as returned from the `objectClassName` selector). + */ +- (NSUInteger)indexOfObject:(RLMObjectType)object; + +/** + Returns the index of the first object in the array matching the predicate. + + @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. + + @return The index of the object, or `NSNotFound` if the object is not found in the array. + */ +- (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat, ...; + +/// :nodoc: +- (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat args:(va_list)args; + +/** + Returns the index of the first object in the array matching the predicate. + + @param predicate The predicate with which to filter the objects. + + @return The index of the object, or `NSNotFound` if the object is not found in the array. + */ +- (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate; + +/** + Returns all the objects matching the given predicate in the array. + + @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. + + @return An `RLMResults` of objects that match the given predicate. + */ +- (RLMResults *)objectsWhere:(NSString *)predicateFormat, ...; + +/// :nodoc: +- (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args; + +/** + Returns all the objects matching the given predicate in the array. + + @param predicate The predicate with which to filter the objects. + + @return An `RLMResults` of objects that match the given predicate + */ +- (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate; + +/** + Returns a sorted `RLMResults` from the array. + + @param keyPath The key path to sort by. + @param ascending The direction to sort in. + + @return An `RLMResults` sorted by the specified key path. + */ +- (RLMResults *)sortedResultsUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending; + +/** + Returns a sorted `RLMResults` from the array. + + @param properties An array of `RLMSortDescriptor`s to sort by. + + @return An `RLMResults` sorted by the specified properties. + */ +- (RLMResults *)sortedResultsUsingDescriptors:(NSArray *)properties; + +/// :nodoc: +- (RLMObjectType)objectAtIndexedSubscript:(NSUInteger)index; + +/// :nodoc: +- (void)setObject:(RLMObjectType)newValue atIndexedSubscript:(NSUInteger)index; + +#pragma mark - Notifications + +/** + Registers a block to be called each time the array changes. + + The block will be asynchronously called with the initial array, and then + called again after each write transaction which changes any of the objects in + the array, which objects are in the results, or the order of the objects in the + array. + + The `changes` parameter will be `nil` the first time the block is called. + For each call after that, it will contain information about + which rows in the array were added, removed or modified. If a write transaction + did not modify any objects in the array, the block is not called at all. + See the `RLMCollectionChange` documentation for information on how the changes + are reported and an example of updating a `UITableView`. + + If an error occurs the block will be called with `nil` for the results + parameter and a non-`nil` error. Currently the only errors that can occur are + when opening the Realm on the background worker thread. + + Notifications are delivered via the standard run loop, and so can't be + delivered while the run loop is blocked by other activity. When + notifications can't be delivered instantly, multiple notifications may be + coalesced into a single notification. This can include the notification + with the initial results. For example, the following code performs a write + transaction immediately after adding the notification block, so there is no + opportunity for the initial notification to be delivered first. As a + result, the initial notification will reflect the state of the Realm after + the write transaction. + + Person *person = [[Person allObjectsInRealm:realm] firstObject]; + NSLog(@"person.dogs.count: %zu", person.dogs.count); // => 0 + self.token = [person.dogs addNotificationBlock(RLMArray *dogs, + RLMCollectionChange *changes, + NSError *error) { + // Only fired once for the example + NSLog(@"dogs.count: %zu", dogs.count) // => 1 + }]; + [realm transactionWithBlock:^{ + Dog *dog = [[Dog alloc] init]; + dog.name = @"Rex"; + [person.dogs addObject:dog]; + }]; + // end of run loop execution context + + You must retain the returned token for as long as you want updates to continue + to be sent to the block. To stop receiving updates, call `-invalidate` on the token. + + @warning This method cannot be called during a write transaction, or when the + containing Realm is read-only. + @warning This method may only be called on a managed array. + + @param block The block to be called each time the array changes. + @return A token which must be held for as long as you want updates to be delivered. + */ +- (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *__nullable array, + RLMCollectionChange *__nullable changes, + NSError *__nullable error))block __attribute__((warn_unused_result)); + +#pragma mark - Aggregating Property Values + +/** + Returns the minimum (lowest) value of the given property among all the objects in the array. + + NSNumber *min = [object.arrayProperty minOfProperty:@"age"]; + + @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. + + @param property The property whose minimum value is desired. Only properties of + types `int`, `float`, `double`, and `NSDate` are supported. + + @return The minimum value of the property, or `nil` if the array is empty. + */ +- (nullable id)minOfProperty:(NSString *)property; + +/** + Returns the maximum (highest) value of the given property among all the objects in the array. + + NSNumber *max = [object.arrayProperty maxOfProperty:@"age"]; + + @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. + + @param property The property whose maximum value is desired. Only properties of + types `int`, `float`, `double`, and `NSDate` are supported. + + @return The maximum value of the property, or `nil` if the array is empty. + */ +- (nullable id)maxOfProperty:(NSString *)property; + +/** + Returns the sum of the values of a given property over all the objects in the array. + + NSNumber *sum = [object.arrayProperty sumOfProperty:@"age"]; + + @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. + + @param property The property whose values should be summed. Only properties of + types `int`, `float`, and `double` are supported. + + @return The sum of the given property. + */ +- (NSNumber *)sumOfProperty:(NSString *)property; + +/** + Returns the average value of a given property over the objects in the array. + + NSNumber *average = [object.arrayProperty averageOfProperty:@"age"]; + + @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. + + @param property The property whose average value should be calculated. Only + properties of types `int`, `float`, and `double` are supported. + + @return The average value of the given property, or `nil` if the array is empty. + */ +- (nullable NSNumber *)averageOfProperty:(NSString *)property; + + +#pragma mark - Unavailable Methods + +/** + `-[RLMArray init]` is not available because `RLMArray`s cannot be created directly. + `RLMArray` properties on `RLMObject`s are lazily created when accessed. + */ +- (instancetype)init __attribute__((unavailable("RLMArrays cannot be created directly"))); + +/** + `+[RLMArray new]` is not available because `RLMArray`s cannot be created directly. + `RLMArray` properties on `RLMObject`s are lazily created when accessed. + */ ++ (instancetype)new __attribute__((unavailable("RLMArrays cannot be created directly"))); + +@end + +/// :nodoc: +@interface RLMArray (Swift) +// for use only in Swift class definitions +- (instancetype)initWithObjectClassName:(NSString *)objectClassName; +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMArray_Private.h b/!main project/Pods/Realm/include/RLMArray_Private.h new file mode 100644 index 0000000..02b908a --- /dev/null +++ b/!main project/Pods/Realm/include/RLMArray_Private.h @@ -0,0 +1,32 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RLMArray () +- (instancetype)initWithObjectClassName:(NSString *)objectClassName; +- (instancetype)initWithObjectType:(RLMPropertyType)type optional:(BOOL)optional; +- (NSString *)descriptionWithMaxDepth:(NSUInteger)depth; +@end + +void RLMArrayValidateMatchingObjectType(RLMArray *array, id value); + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMArray_Private.hpp b/!main project/Pods/Realm/include/RLMArray_Private.hpp new file mode 100644 index 0000000..9626a16 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMArray_Private.hpp @@ -0,0 +1,72 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMArray_Private.h" + +#import "RLMCollection_Private.hpp" + +#import "RLMResults_Private.hpp" + +#import +#import + +namespace realm { + class Results; +} + +@class RLMObjectBase, RLMObjectSchema, RLMProperty; +class RLMClassInfo; +class RLMObservationInfo; + +@interface RLMArray () { +@protected + NSString *_objectClassName; + RLMPropertyType _type; + BOOL _optional; +@public + // The name of the property which this RLMArray represents + NSString *_key; + __weak RLMObjectBase *_parentObject; +} +@end + +@interface RLMManagedArray : RLMArray +- (instancetype)initWithParent:(RLMObjectBase *)parentObject property:(RLMProperty *)property; +- (RLMManagedArray *)initWithList:(realm::List)list + parentInfo:(RLMClassInfo *)parentInfo + property:(__unsafe_unretained RLMProperty *const)property; + +- (bool)isBackedByList:(realm::List const&)list; + +// deletes all objects in the RLMArray from their containing realms +- (void)deleteObjectsFromRealm; +@end + +void RLMValidateArrayObservationKey(NSString *keyPath, RLMArray *array); + +// Initialize the observation info for an array if needed +void RLMEnsureArrayObservationInfo(std::unique_ptr& info, + NSString *keyPath, RLMArray *array, id observed); + + +// +// RLMResults private methods +// +@interface RLMResults () +- (void)deleteObjectsFromRealm; +@end diff --git a/!main project/Pods/Realm/include/RLMClassInfo.hpp b/!main project/Pods/Realm/include/RLMClassInfo.hpp new file mode 100644 index 0000000..7d1d23e --- /dev/null +++ b/!main project/Pods/Realm/include/RLMClassInfo.hpp @@ -0,0 +1,113 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import +#import +#import + +namespace realm { + class ObjectSchema; + class Schema; + class Table; + struct Property; +} + +class RLMObservationInfo; +@class RLMRealm, RLMSchema, RLMObjectSchema, RLMProperty; + +NS_ASSUME_NONNULL_BEGIN + +namespace std { +// Add specializations so that NSString can be used as the key for hash containers +template<> struct hash { + size_t operator()(__unsafe_unretained NSString *const str) const { + return [str hash]; + } +}; +template<> struct equal_to { + bool operator()(__unsafe_unretained NSString * lhs, __unsafe_unretained NSString *rhs) const { + return [lhs isEqualToString:rhs]; + } +}; +} + +// The per-RLMRealm object schema information which stores the cached table +// reference, handles table column lookups, and tracks observed objects +class RLMClassInfo { +public: + RLMClassInfo(RLMRealm *, RLMObjectSchema *, const realm::ObjectSchema *); + + __unsafe_unretained RLMRealm *const realm; + __unsafe_unretained RLMObjectSchema *const rlmObjectSchema; + const realm::ObjectSchema *const objectSchema; + + // Storage for the functionality in RLMObservation for handling indirect + // changes to KVO-observed things + std::vector observedObjects; + + // Get the table for this object type. Will return nullptr only if it's a + // read-only Realm that is missing the table entirely. + realm::Table *_Nullable table() const; + + // Get the RLMProperty for a given table column, or `nil` if it is a column + // not used by the current schema + RLMProperty *_Nullable propertyForTableColumn(NSUInteger) const noexcept; + + // Get the RLMProperty that's used as the primary key, or `nil` if there is + // no primary key for the current schema + RLMProperty *_Nullable propertyForPrimaryKey() const noexcept; + + // Get the table column for the given property. The property must be a valid + // persisted property. + NSUInteger tableColumn(NSString *propertyName) const; + NSUInteger tableColumn(RLMProperty *property) const; + + // Get the info for the target of the link at the given property index. + RLMClassInfo &linkTargetType(size_t propertyIndex); + + // Get the info for the target of the given property + RLMClassInfo &linkTargetType(realm::Property const& property); + + void releaseTable() { m_table = nullptr; } + +private: + mutable realm::Table *_Nullable m_table = nullptr; + std::vector m_linkTargets; +}; + +// A per-RLMRealm object schema map which stores RLMClassInfo keyed on the name +class RLMSchemaInfo { + using impl = std::unordered_map; +public: + RLMSchemaInfo() = default; + RLMSchemaInfo(RLMRealm *realm); + + RLMSchemaInfo clone(realm::Schema const& source_schema, RLMRealm *target_realm); + + // Look up by name, throwing if it's not present + RLMClassInfo& operator[](NSString *name); + + impl::iterator begin() noexcept; + impl::iterator end() noexcept; + impl::const_iterator begin() const noexcept; + impl::const_iterator end() const noexcept; +private: + std::unordered_map m_objects; +}; + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMCollection.h b/!main project/Pods/Realm/include/RLMCollection.h new file mode 100644 index 0000000..8d8ca49 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMCollection.h @@ -0,0 +1,401 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RLMRealm, RLMResults, RLMSortDescriptor, RLMNotificationToken, RLMCollectionChange; +typedef RLM_CLOSED_ENUM(int32_t, RLMPropertyType); + +/** + A homogenous collection of Realm-managed objects. Examples of conforming types + include `RLMArray`, `RLMResults`, and `RLMLinkingObjects`. + */ +@protocol RLMCollection + +@required + +#pragma mark - Properties + +/** + The number of objects in the collection. + */ +@property (nonatomic, readonly, assign) NSUInteger count; + +/** + The type of the objects in the collection. + */ +@property (nonatomic, readonly, assign) RLMPropertyType type; + +/** + Indicates whether the objects in the collection can be `nil`. + */ +@property (nonatomic, readonly, getter = isOptional) BOOL optional; + +/** + The class name of the objects contained in the collection. + + Will be `nil` if `type` is not RLMPropertyTypeObject. + */ +@property (nonatomic, readonly, copy, nullable) NSString *objectClassName; + +/** + The Realm which manages the collection, or `nil` for unmanaged collections. + */ +@property (nonatomic, readonly) RLMRealm *realm; + +#pragma mark - Accessing Objects from a Collection + +/** + Returns the object at the index specified. + + @param index The index to look up. + + @return An object of the type contained in the collection. + */ +- (id)objectAtIndex:(NSUInteger)index; + +/** + Returns the first object in the collection. + + Returns `nil` if called on an empty collection. + + @return An object of the type contained in the collection. + */ +- (nullable id)firstObject; + +/** + Returns the last object in the collection. + + Returns `nil` if called on an empty collection. + + @return An object of the type contained in the collection. + */ +- (nullable id)lastObject; + +#pragma mark - Querying a Collection + +/** + Returns the index of an object in the collection. + + Returns `NSNotFound` if the object is not found in the collection. + + @param object An object (of the same type as returned from the `objectClassName` selector). + */ +- (NSUInteger)indexOfObject:(id)object; + +/** + Returns the index of the first object in the collection matching the predicate. + + @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. + + @return The index of the object, or `NSNotFound` if the object is not found in the collection. + */ +- (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat, ...; + +/// :nodoc: +- (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat args:(va_list)args; + +/** + Returns the index of the first object in the collection matching the predicate. + + @param predicate The predicate with which to filter the objects. + + @return The index of the object, or `NSNotFound` if the object is not found in the collection. + */ +- (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate; + +/** + Returns all objects matching the given predicate in the collection. + + @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. + + @return An `RLMResults` containing objects that match the given predicate. + */ +- (RLMResults *)objectsWhere:(NSString *)predicateFormat, ...; + +/// :nodoc: +- (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args; + +/** + Returns all objects matching the given predicate in the collection. + + @param predicate The predicate with which to filter the objects. + + @return An `RLMResults` containing objects that match the given predicate. + */ +- (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate; + +/** + Returns a sorted `RLMResults` from the collection. + + @param keyPath The keyPath to sort by. + @param ascending The direction to sort in. + + @return An `RLMResults` sorted by the specified key path. + */ +- (RLMResults *)sortedResultsUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending; + +/** + Returns a sorted `RLMResults` from the collection. + + @param properties An array of `RLMSortDescriptor`s to sort by. + + @return An `RLMResults` sorted by the specified properties. + */ +- (RLMResults *)sortedResultsUsingDescriptors:(NSArray *)properties; + +/// :nodoc: +- (id)objectAtIndexedSubscript:(NSUInteger)index; + +/** + Returns an `NSArray` containing the results of invoking `valueForKey:` using `key` on each of the collection's objects. + + @param key The name of the property. + + @return An `NSArray` containing results. + */ +- (nullable id)valueForKey:(NSString *)key; + +/** + Invokes `setValue:forKey:` on each of the collection's objects using the specified `value` and `key`. + + @warning This method may only be called during a write transaction. + + @param value The object value. + @param key The name of the property. + */ +- (void)setValue:(nullable id)value forKey:(NSString *)key; + +#pragma mark - Notifications + +/** + Registers a block to be called each time the collection changes. + + The block will be asynchronously called with the initial collection, and then + called again after each write transaction which changes either any of the + objects in the collection, or which objects are in the collection. + + The `change` parameter will be `nil` the first time the block is called. + For each call after that, it will contain information about + which rows in the collection were added, removed or modified. If a write transaction + did not modify any objects in this collection, the block is not called at all. + See the `RLMCollectionChange` documentation for information on how the changes + are reported and an example of updating a `UITableView`. + + If an error occurs the block will be called with `nil` for the collection + parameter and a non-`nil` error. Currently the only errors that can occur are + when opening the Realm on the background worker thread. + + At the time when the block is called, the collection object will be fully + evaluated and up-to-date, and as long as you do not perform a write transaction + on the same thread or explicitly call `-[RLMRealm refresh]`, accessing it will + never perform blocking work. + + Notifications are delivered via the standard run loop, and so can't be + delivered while the run loop is blocked by other activity. When + notifications can't be delivered instantly, multiple notifications may be + coalesced into a single notification. This can include the notification + with the initial collection. For example, the following code performs a write + transaction immediately after adding the notification block, so there is no + opportunity for the initial notification to be delivered first. As a + result, the initial notification will reflect the state of the Realm after + the write transaction. + + id collection = [Dog allObjects]; + NSLog(@"dogs.count: %zu", dogs.count); // => 0 + self.token = [collection addNotificationBlock:^(id dogs, + RLMCollectionChange *changes, + NSError *error) { + // Only fired once for the example + NSLog(@"dogs.count: %zu", dogs.count); // => 1 + }]; + [realm transactionWithBlock:^{ + Dog *dog = [[Dog alloc] init]; + dog.name = @"Rex"; + [realm addObject:dog]; + }]; + // end of run loop execution context + + You must retain the returned token for as long as you want updates to continue + to be sent to the block. To stop receiving updates, call `-invalidate` on the token. + + @warning This method cannot be called during a write transaction, or when the + containing Realm is read-only. + + @param block The block to be called each time the collection changes. + @return A token which must be held for as long as you want collection notifications to be delivered. + */ +- (RLMNotificationToken *)addNotificationBlock:(void (^)(id __nullable collection, + RLMCollectionChange *__nullable change, + NSError *__nullable error))block __attribute__((warn_unused_result)); + +#pragma mark - Aggregating Property Values + +/** + Returns the minimum (lowest) value of the given property among all the objects + in the collection. + + NSNumber *min = [results minOfProperty:@"age"]; + + @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. + + @param property The property whose minimum value is desired. Only properties of + types `int`, `float`, `double`, and `NSDate` are supported. + + @return The minimum value of the property, or `nil` if the Results are empty. + */ +- (nullable id)minOfProperty:(NSString *)property; + +/** + Returns the maximum (highest) value of the given property among all the objects + in the collection. + + NSNumber *max = [results maxOfProperty:@"age"]; + + @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. + + @param property The property whose maximum value is desired. Only properties of + types `int`, `float`, `double`, and `NSDate` are supported. + + @return The maximum value of the property, or `nil` if the Results are empty. + */ +- (nullable id)maxOfProperty:(NSString *)property; + +/** + Returns the sum of the values of a given property over all the objects in the collection. + + NSNumber *sum = [results sumOfProperty:@"age"]; + + @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. + + @param property The property whose values should be summed. Only properties of + types `int`, `float`, and `double` are supported. + + @return The sum of the given property. + */ +- (NSNumber *)sumOfProperty:(NSString *)property; + +/** + Returns the average value of a given property over the objects in the collection. + + NSNumber *average = [results averageOfProperty:@"age"]; + + @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. + + @param property The property whose average value should be calculated. Only + properties of types `int`, `float`, and `double` are supported. + + @return The average value of the given property, or `nil` if the Results are empty. + */ +- (nullable NSNumber *)averageOfProperty:(NSString *)property; + +@end + +/** + An `RLMSortDescriptor` stores a property name and a sort order for use with + `sortedResultsUsingDescriptors:`. It is similar to `NSSortDescriptor`, but supports + only the subset of functionality which can be efficiently run by Realm's query + engine. + + `RLMSortDescriptor` instances are immutable. + */ +@interface RLMSortDescriptor : NSObject + +#pragma mark - Properties + +/** + The key path which the sort descriptor orders results by. + */ +@property (nonatomic, readonly) NSString *keyPath; + +/** + Whether the descriptor sorts in ascending or descending order. + */ +@property (nonatomic, readonly) BOOL ascending; + +#pragma mark - Methods + +/** + Returns a new sort descriptor for the given key path and sort direction. + */ ++ (instancetype)sortDescriptorWithKeyPath:(NSString *)keyPath ascending:(BOOL)ascending; + +/** + Returns a copy of the receiver with the sort direction reversed. + */ +- (instancetype)reversedSortDescriptor; + +@end + +/** + A `RLMCollectionChange` object encapsulates information about changes to collections + that are reported by Realm notifications. + + `RLMCollectionChange` is passed to the notification blocks registered with + `-addNotificationBlock` on `RLMArray` and `RLMResults`, and reports what rows in the + collection changed since the last time the notification block was called. + + The change information is available in two formats: a simple array of row + indices in the collection for each type of change, and an array of index paths + in a requested section suitable for passing directly to `UITableView`'s batch + update methods. A complete example of updating a `UITableView` named `tv`: + + [tv beginUpdates]; + [tv deleteRowsAtIndexPaths:[changes deletionsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; + [tv insertRowsAtIndexPaths:[changes insertionsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; + [tv reloadRowsAtIndexPaths:[changes modificationsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; + [tv endUpdates]; + + All of the arrays in an `RLMCollectionChange` are always sorted in ascending order. + */ +@interface RLMCollectionChange : NSObject +/// The indices of objects in the previous version of the collection which have +/// been removed from this one. +@property (nonatomic, readonly) NSArray *deletions; + +/// The indices in the new version of the collection which were newly inserted. +@property (nonatomic, readonly) NSArray *insertions; + +/** + The indices in the new version of the collection which were modified. + + For `RLMResults`, this means that one or more of the properties of the object at + that index were modified (or an object linked to by that object was + modified). + + For `RLMArray`, the array itself being modified to contain a + different object at that index will also be reported as a modification. + */ +@property (nonatomic, readonly) NSArray *modifications; + +/// Returns the index paths of the deletion indices in the given section. +- (NSArray *)deletionsInSection:(NSUInteger)section; + +/// Returns the index paths of the insertion indices in the given section. +- (NSArray *)insertionsInSection:(NSUInteger)section; + +/// Returns the index paths of the modification indices in the given section. +- (NSArray *)modificationsInSection:(NSUInteger)section; +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMCollection_Private.h b/!main project/Pods/Realm/include/RLMCollection_Private.h new file mode 100644 index 0000000..eec5aea --- /dev/null +++ b/!main project/Pods/Realm/include/RLMCollection_Private.h @@ -0,0 +1,31 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +#import + +@protocol RLMFastEnumerable; + +NS_ASSUME_NONNULL_BEGIN + +void RLMCollectionSetValueForKey(id collection, NSString *key, id _Nullable value); +FOUNDATION_EXTERN NSString *RLMDescriptionWithMaxDepth(NSString *name, id collection, NSUInteger depth); +FOUNDATION_EXTERN id _Nullable (*_Nullable RLMSwiftAsFastEnumeration)(id); + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMCollection_Private.hpp b/!main project/Pods/Realm/include/RLMCollection_Private.hpp new file mode 100644 index 0000000..2d13016 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMCollection_Private.hpp @@ -0,0 +1,84 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +#import + +namespace realm { + class List; + class Results; + class TableView; + struct CollectionChangeSet; + struct NotificationToken; +} +class RLMClassInfo; +@class RLMFastEnumerator; + +@protocol RLMFastEnumerable +@property (nonatomic, readonly) RLMRealm *realm; +@property (nonatomic, readonly) RLMClassInfo *objectInfo; +@property (nonatomic, readonly) NSUInteger count; + +- (realm::TableView)tableView; +- (RLMFastEnumerator *)fastEnumerator; +@end + +// An object which encapulates the shared logic for fast-enumerating RLMArray +// and RLMResults, and has a buffer to store strong references to the current +// set of enumerated items +@interface RLMFastEnumerator : NSObject +- (instancetype)initWithList:(realm::List&)list + collection:(id)collection + classInfo:(RLMClassInfo&)info; +- (instancetype)initWithResults:(realm::Results&)results + collection:(id)collection + classInfo:(RLMClassInfo&)info; + +// Detach this enumerator from the source collection. Must be called before the +// source collection is changed. +- (void)detach; + +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state + count:(NSUInteger)len; +@end +NSUInteger RLMFastEnumerate(NSFastEnumerationState *state, NSUInteger len, id collection); + +@interface RLMNotificationToken () +- (void)suppressNextNotification; +- (RLMRealm *)realm; +@end + +@interface RLMCancellationToken : RLMNotificationToken +- (instancetype)initWithToken:(realm::NotificationToken)token realm:(RLMRealm *)realm; +@end + +@interface RLMCollectionChange () +- (instancetype)initWithChanges:(realm::CollectionChangeSet)indices; +@end + +template +RLMNotificationToken *RLMAddNotificationBlock(id objcCollection, + Collection& collection, + void (^block)(id, RLMCollectionChange *, NSError *), + bool suppressInitialChange=false); + +template +NSArray *RLMCollectionValueForKey(Collection& collection, NSString *key, RLMClassInfo& info); + +std::vector> RLMSortDescriptorsToKeypathArray(NSArray *properties); diff --git a/!main project/Pods/Realm/include/RLMConstants.h b/!main project/Pods/Realm/include/RLMConstants.h new file mode 100644 index 0000000..c4c1093 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMConstants.h @@ -0,0 +1,231 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +NS_ASSUME_NONNULL_BEGIN + +// For compatibility with Xcode 7, before extensible string enums were introduced, +#ifdef NS_EXTENSIBLE_STRING_ENUM +#define RLM_EXTENSIBLE_STRING_ENUM NS_EXTENSIBLE_STRING_ENUM +#define RLM_EXTENSIBLE_STRING_ENUM_CASE_SWIFT_NAME(_, extensible_string_enum) NS_SWIFT_NAME(extensible_string_enum) +#else +#define RLM_EXTENSIBLE_STRING_ENUM +#define RLM_EXTENSIBLE_STRING_ENUM_CASE_SWIFT_NAME(fully_qualified, _) NS_SWIFT_NAME(fully_qualified) +#endif + +// Swift 5 considers NS_ENUM to be "open", meaning there could be values present +// other than the defined cases (which allows adding more cases later without +// it being a breaking change), while older versions consider it "closed". +#ifdef NS_CLOSED_ENUM +#define RLM_CLOSED_ENUM NS_CLOSED_ENUM +#else +#define RLM_CLOSED_ENUM NS_ENUM +#endif + +#if __has_attribute(ns_error_domain) && (!defined(__cplusplus) || !__cplusplus || __cplusplus >= 201103L) +#define RLM_ERROR_ENUM(type, name, domain) \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wignored-attributes\"") \ + NS_ENUM(type, __attribute__((ns_error_domain(domain))) name) \ + _Pragma("clang diagnostic pop") +#else +#define RLM_ERROR_ENUM(type, name, domain) NS_ENUM(type, name) +#endif + + +#pragma mark - Enums + +/** + `RLMPropertyType` is an enumeration describing all property types supported in Realm models. + + For more information, see [Realm Models](https://realm.io/docs/objc/latest/#models). + */ +typedef RLM_CLOSED_ENUM(int32_t, RLMPropertyType) { + +#pragma mark - Primitive types + + /** Integers: `NSInteger`, `int`, `long`, `Int` (Swift) */ + RLMPropertyTypeInt = 0, + /** Booleans: `BOOL`, `bool`, `Bool` (Swift) */ + RLMPropertyTypeBool = 1, + /** Floating-point numbers: `float`, `Float` (Swift) */ + RLMPropertyTypeFloat = 5, + /** Double-precision floating-point numbers: `double`, `Double` (Swift) */ + RLMPropertyTypeDouble = 6, + +#pragma mark - Object types + + /** Strings: `NSString`, `String` (Swift) */ + RLMPropertyTypeString = 2, + /** Binary data: `NSData` */ + RLMPropertyTypeData = 3, + /** + Any object: `id`. + + This property type is no longer supported for new models. However, old files + with any-typed properties are still supported for migration purposes. + */ + RLMPropertyTypeAny = 9, + /** Dates: `NSDate` */ + RLMPropertyTypeDate = 4, + +#pragma mark - Linked object types + + /** Realm model objects. See [Realm Models](https://realm.io/docs/objc/latest/#models) for more information. */ + RLMPropertyTypeObject = 7, + /** Realm linking objects. See [Realm Models](https://realm.io/docs/objc/latest/#models) for more information. */ + RLMPropertyTypeLinkingObjects = 8, +}; + +/** An error domain identifying Realm-specific errors. */ +extern NSString * const RLMErrorDomain; + +/** An error domain identifying non-specific system errors. */ +extern NSString * const RLMUnknownSystemErrorDomain; + +/** + `RLMError` is an enumeration representing all recoverable errors. It is associated with the + Realm error domain specified in `RLMErrorDomain`. + */ +typedef RLM_ERROR_ENUM(NSInteger, RLMError, RLMErrorDomain) { + /** Denotes a general error that occurred when trying to open a Realm. */ + RLMErrorFail = 1, + + /** Denotes a file I/O error that occurred when trying to open a Realm. */ + RLMErrorFileAccess = 2, + + /** + Denotes a file permission error that ocurred when trying to open a Realm. + + This error can occur if the user does not have permission to open or create + the specified file in the specified access mode when opening a Realm. + */ + RLMErrorFilePermissionDenied = 3, + + /** Denotes an error where a file was to be written to disk, but another file with the same name already exists. */ + RLMErrorFileExists = 4, + + /** + Denotes an error that occurs if a file could not be found. + + This error may occur if a Realm file could not be found on disk when trying to open a + Realm as read-only, or if the directory part of the specified path was not found when + trying to write a copy. + */ + RLMErrorFileNotFound = 5, + + /** + Denotes an error that occurs if a file format upgrade is required to open the file, + but upgrades were explicitly disabled. + */ + RLMErrorFileFormatUpgradeRequired = 6, + + /** + Denotes an error that occurs if the database file is currently open in another + process which cannot share with the current process due to an + architecture mismatch. + + This error may occur if trying to share a Realm file between an i386 (32-bit) iOS + Simulator and the Realm Browser application. In this case, please use the 64-bit + version of the iOS Simulator. + */ + RLMErrorIncompatibleLockFile = 8, + + /** Denotes an error that occurs when there is insufficient available address space. */ + RLMErrorAddressSpaceExhausted = 9, + + /** Denotes an error that occurs if there is a schema version mismatch, so that a migration is required. */ + RLMErrorSchemaMismatch = 10, + + /** Denotes an error that occurs when attempting to open an incompatible synchronized Realm file. + + This error occurs when the Realm file was created with an older version of Realm and an automatic migration + to the current version is not possible. When such an error occurs, the original file is moved to a backup + location, and future attempts to open the synchronized Realm will result in a new file being created. + If you wish to migrate any data from the backup Realm, you can open it using the provided Realm configuration. + */ + RLMErrorIncompatibleSyncedFile = 11, + /** + Denotates an error where an operation was requested which cannot be performed on an open file. + */ + RLMErrorAlreadyOpen = 12, +}; + +#pragma mark - Constants + +#pragma mark - Notification Constants + +/** + A notification indicating that changes were made to a Realm. +*/ +typedef NSString * RLMNotification RLM_EXTENSIBLE_STRING_ENUM; + +/** + This notification is posted when a write transaction has been committed to a Realm on a different thread for + the same file. + + It is not posted if `autorefresh` is enabled, or if the Realm is refreshed before the notification has a chance + to run. + + Realms with autorefresh disabled should normally install a handler for this notification which calls + `-[RLMRealm refresh]` after doing some work. Refreshing the Realm is optional, but not refreshing the Realm may lead to + large Realm files. This is because an extra copy of the data must be kept for the stale Realm. + */ +extern RLMNotification const RLMRealmRefreshRequiredNotification +RLM_EXTENSIBLE_STRING_ENUM_CASE_SWIFT_NAME(RLMRealmRefreshRequiredNotification, RefreshRequired); + +/** + This notification is posted by a Realm when a write transaction has been + committed to a Realm on a different thread for the same file. + + It is not posted if `-[RLMRealm autorefresh]` is enabled, or if the Realm is + refreshed before the notification has a chance to run. + + Realms with autorefresh disabled should normally install a handler for this + notification which calls `-[RLMRealm refresh]` after doing some work. Refreshing + the Realm is optional, but not refreshing the Realm may lead to large Realm + files. This is because Realm must keep an extra copy of the data for the stale + Realm. + */ +extern RLMNotification const RLMRealmDidChangeNotification +RLM_EXTENSIBLE_STRING_ENUM_CASE_SWIFT_NAME(RLMRealmDidChangeNotification, DidChange); + +#pragma mark - Error keys + +/** Key to identify the associated backup Realm configuration in an error's `userInfo` dictionary */ +extern NSString * const RLMBackupRealmConfigurationErrorKey; + +#pragma mark - Other Constants + +/** The schema version used for uninitialized Realms */ +extern const uint64_t RLMNotVersioned; + +/** The corresponding value is the name of an exception thrown by Realm. */ +extern NSString * const RLMExceptionName; + +/** The corresponding value is a Realm file version. */ +extern NSString * const RLMRealmVersionKey; + +/** The corresponding key is the version of the underlying database engine. */ +extern NSString * const RLMRealmCoreVersionKey; + +/** The corresponding key is the Realm invalidated property name. */ +extern NSString * const RLMInvalidatedKey; + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMJSONModels.h b/!main project/Pods/Realm/include/RLMJSONModels.h new file mode 100644 index 0000000..82b8555 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMJSONModels.h @@ -0,0 +1,103 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2017 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +#import "RLMSyncUtil_Private.h" + +NS_ASSUME_NONNULL_BEGIN + +@class RLMTokenDataModel, RLMSyncUserAccountInfo; + +#pragma mark - RLMTokenModel + +@interface RLMTokenModel : NSObject RLM_SYNC_UNINITIALIZABLE + +@property (nonatomic, readonly) NSString *token; +@property (nonatomic, nullable, readonly) NSString *path; +@property (nonatomic, readonly) RLMTokenDataModel *tokenData; + +- (instancetype)initWithDictionary:(NSDictionary *)jsonDictionary; + +@end + +#pragma mark - RLMTokenDataModel + +@interface RLMTokenDataModel : NSObject RLM_SYNC_UNINITIALIZABLE + +@property (nonatomic, readonly) NSString *identity; +@property (nonatomic, nullable, readonly) NSString *appID; +@property (nonatomic, nullable, readonly) NSString *path; +@property (nonatomic, readonly) NSTimeInterval expires; +@property (nonatomic, readonly) BOOL isAdmin; + +- (instancetype)initWithDictionary:(NSDictionary *)jsonDictionary; + +@end + +#pragma mark - RLMAuthResponseModel + +/** + An internal class representing a valid JSON response to an auth request. + + ``` + { + "access_token": { ... } // (optional), + "refresh_token": { ... } // (optional) + } + ``` + */ +@interface RLMAuthResponseModel : NSObject RLM_SYNC_UNINITIALIZABLE + +@property (nonatomic, readonly, nullable) RLMTokenModel *accessToken; +@property (nonatomic, readonly, nullable) RLMTokenModel *refreshToken; +@property (nonatomic, readonly, nullable) NSString *urlPrefix; + +- (instancetype)initWithDictionary:(NSDictionary *)jsonDictionary + requireAccessToken:(BOOL)requireAccessToken + requireRefreshToken:(BOOL)requireRefreshToken; + +@end + +#pragma mark - RLMUserInfoResponseModel + +@interface RLMUserResponseModel : NSObject RLM_SYNC_UNINITIALIZABLE + +@property (nonatomic, readonly) NSString *identity; +@property (nonatomic, readonly) NSArray *accounts; +@property (nonatomic, readonly) NSDictionary *metadata; +@property (nonatomic, readonly) BOOL isAdmin; + +- (instancetype)initWithDictionary:(NSDictionary *)jsonDictionary; + +@end + +#pragma mark - RLMSyncErrorResponseModel + +@interface RLMSyncErrorResponseModel : NSObject RLM_SYNC_UNINITIALIZABLE + +@property (nonatomic, readonly) NSInteger status; +@property (nonatomic, readonly) NSInteger code; +@property (nullable, nonatomic, readonly) NSString *title; +@property (nullable, nonatomic, readonly) NSString *hint; + +- (instancetype)initWithDictionary:(NSDictionary *)jsonDictionary; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMListBase.h b/!main project/Pods/Realm/include/RLMListBase.h new file mode 100644 index 0000000..7ed1988 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMListBase.h @@ -0,0 +1,42 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +@class RLMArray, RLMObjectBase, RLMResults, RLMProperty; + +NS_ASSUME_NONNULL_BEGIN + +// A base class for Swift generic Lists to make it possible to interact with +// them from obj-c +@interface RLMListBase : NSObject +@property (nonatomic, strong) RLMArray *_rlmArray; + +- (instancetype)init; +- (instancetype)initWithArray:(RLMArray *)array; +@end + +@interface RLMLinkingObjectsHandle : NSObject +- (instancetype)initWithObject:(RLMObjectBase *)object property:(RLMProperty *)property; + +@property (nonatomic, readonly) RLMResults *results; +@property (nonatomic, readonly) RLMObjectBase *parent; +@property (nonatomic, readonly) RLMProperty *property; +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMMigration.h b/!main project/Pods/Realm/include/RLMMigration.h new file mode 100644 index 0000000..e910c42 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMMigration.h @@ -0,0 +1,127 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RLMSchema; +@class RLMArray; +@class RLMObject; + +/** + A block type which provides both the old and new versions of an object in the Realm. Object + properties can only be accessed using keyed subscripting. + + @see `-[RLMMigration enumerateObjects:block:]` + + @param oldObject The object from the original Realm (read-only). + @param newObject The object from the migrated Realm (read-write). +*/ +typedef void (^RLMObjectMigrationBlock)(RLMObject * __nullable oldObject, RLMObject * __nullable newObject); + +/** + `RLMMigration` instances encapsulate information intended to facilitate a schema migration. + + A `RLMMigration` instance is passed into a user-defined `RLMMigrationBlock` block when updating + the version of a Realm. This instance provides access to the old and new database schemas, the + objects in the Realm, and provides functionality for modifying the Realm during the migration. + */ +@interface RLMMigration : NSObject + +#pragma mark - Properties + +/** + Returns the old `RLMSchema`. This is the schema which describes the Realm before the + migration is applied. + */ +@property (nonatomic, readonly) RLMSchema *oldSchema; + +/** + Returns the new `RLMSchema`. This is the schema which describes the Realm after the + migration is applied. + */ +@property (nonatomic, readonly) RLMSchema *newSchema; + + +#pragma mark - Altering Objects during a Migration + +/** + Enumerates all the objects of a given type in the Realm, providing both the old and new versions + of each object. Within the block, object properties can only be accessed using keyed subscripting. + + @param className The name of the `RLMObject` class to enumerate. + + @warning All objects returned are of a type specific to the current migration and should not be cast + to `className`. Instead, treat them as `RLMObject`s and use keyed subscripting to access + properties. + */ +- (void)enumerateObjects:(NSString *)className block:(__attribute__((noescape)) RLMObjectMigrationBlock)block; + +/** + Creates and returns an `RLMObject` instance of type `className` in the Realm being migrated. + + The `value` argument is used to populate the object. It can be a key-value coding compliant object, an array or + dictionary returned from the methods in `NSJSONSerialization`, or an array containing one element for each managed + property. An exception will be thrown if any required properties are not present and those properties were not defined + with default values. + + When passing in an `NSArray` as the `value` argument, all properties must be present, valid and in the same order as + the properties defined in the model. + + @param className The name of the `RLMObject` class to create. + @param value The value used to populate the object. + */ +- (RLMObject *)createObject:(NSString *)className withValue:(id)value; + +/** + Deletes an object from a Realm during a migration. + + It is permitted to call this method from within the block passed to `-[enumerateObjects:block:]`. + + @param object Object to be deleted from the Realm being migrated. + */ +- (void)deleteObject:(RLMObject *)object; + +/** + Deletes the data for the class with the given name. + + All objects of the given class will be deleted. If the `RLMObject` subclass no longer exists in your program, + any remaining metadata for the class will be removed from the Realm file. + + @param name The name of the `RLMObject` class to delete. + + @return A Boolean value indicating whether there was any data to delete. + */ +- (BOOL)deleteDataForClassName:(NSString *)name; + +/** + Renames a property of the given class from `oldName` to `newName`. + + @param className The name of the class whose property should be renamed. This class must be present + in both the old and new Realm schemas. + @param oldName The old name for the property to be renamed. There must not be a property with this name in the + class as defined by the new Realm schema. + @param newName The new name for the property to be renamed. There must not be a property with this name in the + class as defined by the old Realm schema. + */ +- (void)renamePropertyForClass:(NSString *)className oldName:(NSString *)oldName newName:(NSString *)newName; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMMigration_Private.h b/!main project/Pods/Realm/include/RLMMigration_Private.h new file mode 100644 index 0000000..99699e5 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMMigration_Private.h @@ -0,0 +1,40 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import +#import +#import + +namespace realm { + class Schema; +} + +NS_ASSUME_NONNULL_BEGIN + +@interface RLMMigration () + +@property (nonatomic, strong) RLMRealm *oldRealm; +@property (nonatomic, strong) RLMRealm *realm; + +- (instancetype)initWithRealm:(RLMRealm *)realm oldRealm:(RLMRealm *)oldRealm schema:(realm::Schema &)schema; + +- (void)execute:(RLMMigrationBlock)block; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMNetworkClient.h b/!main project/Pods/Realm/include/RLMNetworkClient.h new file mode 100644 index 0000000..7f86ff2 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMNetworkClient.h @@ -0,0 +1,65 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +#import "RLMSyncUtil_Private.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface RLMNetworkRequestOptions : NSObject +@property (nonatomic, copy, nullable) NSString *authorizationHeaderName; +@property (nonatomic, copy, nullable) NSDictionary *customHeaders; +@property (nullable, nonatomic, copy) NSDictionary *pinnedCertificatePaths; +@end + +/// An abstract class representing a server endpoint. +@interface RLMSyncServerEndpoint : NSObject RLM_SYNC_UNINITIALIZABLE ++ (void)sendRequestToServer:(NSURL *)serverURL + JSON:(NSDictionary *)jsonDictionary + completion:(void (^)(NSError *))completionBlock; + ++ (void)sendRequestToServer:(NSURL *)serverURL + JSON:(NSDictionary *)jsonDictionary + timeout:(NSTimeInterval)timeout + completion:(void (^)(NSError *, NSDictionary *))completionBlock; +@end + +@interface RLMSyncAuthEndpoint : RLMSyncServerEndpoint RLM_SYNC_UNINITIALIZABLE +@end +@interface RLMSyncChangePasswordEndpoint : RLMSyncServerEndpoint RLM_SYNC_UNINITIALIZABLE +@end +@interface RLMSyncUpdateAccountEndpoint : RLMSyncServerEndpoint RLM_SYNC_UNINITIALIZABLE +@end +@interface RLMSyncGetUserInfoEndpoint : RLMSyncServerEndpoint RLM_SYNC_UNINITIALIZABLE +@end + +@interface RLMSyncGetPermissionsEndpoint : RLMSyncServerEndpoint RLM_SYNC_UNINITIALIZABLE +@end +@interface RLMSyncGetPermissionOffersEndpoint : RLMSyncServerEndpoint RLM_SYNC_UNINITIALIZABLE +@end +@interface RLMSyncApplyPermissionsEndpoint : RLMSyncServerEndpoint RLM_SYNC_UNINITIALIZABLE +@end +@interface RLMSyncOfferPermissionsEndpoint : RLMSyncServerEndpoint RLM_SYNC_UNINITIALIZABLE +@end +@interface RLMSyncAcceptPermissionOfferEndpoint : RLMSyncServerEndpoint RLM_SYNC_UNINITIALIZABLE +@end +@interface RLMSyncInvalidatePermissionOfferEndpoint : RLMSyncServerEndpoint RLM_SYNC_UNINITIALIZABLE +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMObject.h b/!main project/Pods/Realm/include/RLMObject.h new file mode 100644 index 0000000..23d99e5 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMObject.h @@ -0,0 +1,650 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RLMNotificationToken; +@class RLMObjectSchema; +@class RLMPropertyChange; +@class RLMPropertyDescriptor; +@class RLMRealm; +@class RLMResults; + +/** + `RLMObject` is a base class for model objects representing data stored in Realms. + + Define your model classes by subclassing `RLMObject` and adding properties to be managed. + Then instantiate and use your custom subclasses instead of using the `RLMObject` class directly. + + // Dog.h + @interface Dog : RLMObject + @property NSString *name; + @property BOOL adopted; + @end + + // Dog.m + @implementation Dog + @end //none needed + + ### Supported property types + + - `NSString` + - `NSInteger`, `int`, `long`, `float`, and `double` + - `BOOL` or `bool` + - `NSDate` + - `NSData` + - `NSNumber`, where `X` is one of `RLMInt`, `RLMFloat`, `RLMDouble` or `RLMBool`, for optional number properties + - `RLMObject` subclasses, to model many-to-one relationships. + - `RLMArray`, where `X` is an `RLMObject` subclass, to model many-to-many relationships. + + ### Querying + + You can initiate queries directly via the class methods: `allObjects`, `objectsWhere:`, and `objectsWithPredicate:`. + These methods allow you to easily query a custom subclass for instances of that class in the default Realm. + + To search in a Realm other than the default Realm, use the `allObjectsInRealm:`, `objectsInRealm:where:`, + and `objectsInRealm:withPredicate:` class methods. + + @see `RLMRealm` + + ### Relationships + + See our [Cocoa guide](https://realm.io/docs/objc/latest#relationships) for more details. + + ### Key-Value Observing + + All `RLMObject` properties (including properties you create in subclasses) are + [Key-Value Observing compliant](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html), + except for `realm` and `objectSchema`. + + Keep the following tips in mind when observing Realm objects: + + 1. Unlike `NSMutableArray` properties, `RLMArray` properties do not require + using the proxy object returned from `-mutableArrayValueForKey:`, or defining + KVC mutation methods on the containing class. You can simply call methods on + the `RLMArray` directly; any changes will be automatically observed by the containing + object. + 2. Unmanaged `RLMObject` instances cannot be added to a Realm while they have any + observed properties. + 3. Modifying managed `RLMObject`s within `-observeValueForKeyPath:ofObject:change:context:` + is not recommended. Properties may change even when the Realm is not in a write + transaction (for example, when `-[RLMRealm refresh]` is called after changes + are made on a different thread), and notifications sent prior to the change + being applied (when `NSKeyValueObservingOptionPrior` is used) may be sent at + times when you *cannot* begin a write transaction. + */ + +@interface RLMObject : RLMObjectBase + +#pragma mark - Creating & Initializing Objects + +/** + Creates an unmanaged instance of a Realm object. + + Call `addObject:` on an `RLMRealm` instance to add an unmanaged object into that Realm. + + @see `[RLMRealm addObject:]` + */ +- (instancetype)init NS_DESIGNATED_INITIALIZER; + + +/** + Creates an unmanaged instance of a Realm object. + + Pass in an `NSArray` or `NSDictionary` instance to set the values of the object's properties. + + Call `addObject:` on an `RLMRealm` instance to add an unmanaged object into that Realm. + + @see `[RLMRealm addObject:]` + */ +- (instancetype)initWithValue:(id)value; + + +/** + Returns the class name for a Realm object subclass. + + @warning Do not override. Realm relies on this method returning the exact class + name. + + @return The class name for the model class. + */ ++ (NSString *)className; + +/** + Creates an instance of a Realm object with a given value, and adds it to the default Realm. + + If nested objects are included in the argument, `createInDefaultRealmWithValue:` will be recursively called + on them. + + The `value` argument can be a key-value coding compliant object, an array or dictionary returned from the methods in + `NSJSONSerialization`, or an array containing one element for each managed property. + + An exception will be thrown if any required properties are not present and those properties + were not defined with default values. + + If the `value` argument is an array, all properties must be present, valid and in the same + order as the properties defined in the model. + + @param value The value used to populate the object. + + @see `defaultPropertyValues` + */ ++ (instancetype)createInDefaultRealmWithValue:(id)value; + +/** + Creates an instance of a Realm object with a given value, and adds it to the specified Realm. + + If nested objects are included in the argument, `createInRealm:withValue:` will be recursively called + on them. + + The `value` argument can be a key-value coding compliant object, an array or dictionary returned from the methods in + `NSJSONSerialization`, or an array containing one element for each managed property. + + An exception will be thrown if any required properties are not present and those properties + were not defined with default values. + + If the `value` argument is an array, all properties must be present, valid and in the same + order as the properties defined in the model. + + @param realm The Realm which should manage the newly-created object. + @param value The value used to populate the object. + + @see `defaultPropertyValues` + */ ++ (instancetype)createInRealm:(RLMRealm *)realm withValue:(id)value; + +/** + Creates or updates a Realm object within the default Realm. + + This method may only be called on Realm object types with a primary key defined. If there is already + an object with the same primary key value in the default Realm, its values are updated and the object + is returned. Otherwise, this method creates and populates a new instance of the object in the default Realm. + + If nested objects are included in the argument, `createOrUpdateInDefaultRealmWithValue:` will be + recursively called on them if they have primary keys, `createInDefaultRealmWithValue:` if they do not. + + The `value` argument is used to populate the object. It can be a Realm object, a key-value coding + compliant object, an array or dictionary returned from the methods in `NSJSONSerialization`, or an + array containing one element for each managed property. + + If the object is being created, an exception will be thrown if any required properties + are not present and those properties were not defined with default values. + + If the `value` argument is a Realm object already managed by the default Realm, the + argument's type is the same as the receiver, and the objects have identical values for + their managed properties, this method does nothing. + + If the object is being updated, each property defined in its schema will be set by copying from + `value` using key-value coding. If the `value` argument does not respond to `valueForKey:` for a + given property name (or getter name, if defined), that value will remain untouched. + Nullable properties on the object can be set to nil by using `NSNull` as the updated value. + Each property is set even if the existing value is the same as the new value being set, and + notifications will report them all being changed. See `createOrUpdateModifiedInDefaultRealmWithValue:` + for a version of this function which only sets the values which have changed. + + If the `value` argument is an array, all properties must be present, valid and in the same + order as the properties defined in the model. + + @param value The value used to populate the object. + + @see `defaultPropertyValues`, `primaryKey` + */ ++ (instancetype)createOrUpdateInDefaultRealmWithValue:(id)value; + +/** + Creates or updates a Realm object within the default Realm. + + This method may only be called on Realm object types with a primary key defined. If there is already + an object with the same primary key value in the default Realm, its values are updated and the object + is returned. Otherwise, this method creates and populates a new instance of the object in the default Realm. + + If nested objects are included in the argument, `createOrUpdateModifiedInDefaultRealmWithValue:` will be + recursively called on them if they have primary keys, `createInDefaultRealmWithValue:` if they do not. + + The `value` argument is used to populate the object. It can be a Realm object, a key-value coding + compliant object, an array or dictionary returned from the methods in `NSJSONSerialization`, or an + array containing one element for each managed property. + + If the object is being created, an exception will be thrown if any required properties + are not present and those properties were not defined with default values. + + If the `value` argument is a Realm object already managed by the default Realm, the + argument's type is the same as the receiver, and the objects have identical values for + their managed properties, this method does nothing. + + If the object is being updated, each property defined in its schema will be set by copying from + `value` using key-value coding. If the `value` argument does not respond to `valueForKey:` for a + given property name (or getter name, if defined), that value will remain untouched. + Nullable properties on the object can be set to nil by using `NSNull` as the updated value. + Unlike `createOrUpdateInDefaultRealmWithValue:`, only properties which have changed in value are + set, and any change notifications produced by this call will report only which properies have + actually changed. + + Checking which properties have changed imposes a small amount of overhead, and so this method + may be slower when all or nearly all of the properties being set have changed. If most or all + of the properties being set have not changed, this method will be much faster than unconditionally + setting all of them, and will also reduce how much data has to be written to the Realm, saving + both i/o time and disk space. + + If the `value` argument is an array, all properties must be present, valid and in the same + order as the properties defined in the model. + + @param value The value used to populate the object. + + @see `defaultPropertyValues`, `primaryKey` + */ ++ (instancetype)createOrUpdateModifiedInDefaultRealmWithValue:(id)value; + +/** + Creates or updates an Realm object within a specified Realm. + + This method may only be called on Realm object types with a primary key defined. If there is already + an object with the same primary key value in the given Realm, its values are updated and the object + is returned. Otherwise this method creates and populates a new instance of this object in the given Realm. + + If nested objects are included in the argument, `createOrUpdateInRealm:withValue:` will be + recursively called on them if they have primary keys, `createInRealm:withValue:` if they do not. + + The `value` argument is used to populate the object. It can be a Realm object, a key-value coding + compliant object, an array or dictionary returned from the methods in `NSJSONSerialization`, or an + array containing one element for each managed property. + + If the object is being created, an exception will be thrown if any required properties + are not present and those properties were not defined with default values. + + If the `value` argument is a Realm object already managed by the given Realm, the + argument's type is the same as the receiver, and the objects have identical values for + their managed properties, this method does nothing. + + If the object is being updated, each property defined in its schema will be set by copying from + `value` using key-value coding. If the `value` argument does not respond to `valueForKey:` for a + given property name (or getter name, if defined), that value will remain untouched. + Nullable properties on the object can be set to nil by using `NSNull` as the updated value. + Each property is set even if the existing value is the same as the new value being set, and + notifications will report them all being changed. See `createOrUpdateModifiedInRealm:withValue:` + for a version of this function which only sets the values which have changed. + + If the `value` argument is an array, all properties must be present, valid and in the same + order as the properties defined in the model. + + @param realm The Realm which should own the object. + @param value The value used to populate the object. + + @see `defaultPropertyValues`, `primaryKey` + */ ++ (instancetype)createOrUpdateInRealm:(RLMRealm *)realm withValue:(id)value; + +/** + Creates or updates an Realm object within a specified Realm. + + This method may only be called on Realm object types with a primary key defined. If there is already + an object with the same primary key value in the given Realm, its values are updated and the object + is returned. Otherwise this method creates and populates a new instance of this object in the given Realm. + + If nested objects are included in the argument, `createOrUpdateInRealm:withValue:` will be + recursively called on them if they have primary keys, `createInRealm:withValue:` if they do not. + + The `value` argument is used to populate the object. It can be a Realm object, a key-value coding + compliant object, an array or dictionary returned from the methods in `NSJSONSerialization`, or an + array containing one element for each managed property. + + If the object is being created, an exception will be thrown if any required properties + are not present and those properties were not defined with default values. + + If the `value` argument is a Realm object already managed by the given Realm, the + argument's type is the same as the receiver, and the objects have identical values for + their managed properties, this method does nothing. + + If the object is being updated, each property defined in its schema will be set by copying from + `value` using key-value coding. If the `value` argument does not respond to `valueForKey:` for a + given property name (or getter name, if defined), that value will remain untouched. + Nullable properties on the object can be set to nil by using `NSNull` as the updated value. + Unlike `createOrUpdateInRealm:withValue:`, only properties which have changed in value are + set, and any change notifications produced by this call will report only which properies have + actually changed. + + Checking which properties have changed imposes a small amount of overhead, and so this method + may be slower when all or nearly all of the properties being set have changed. If most or all + of the properties being set have not changed, this method will be much faster than unconditionally + setting all of them, and will also reduce how much data has to be written to the Realm, saving + both i/o time and disk space. + + If the `value` argument is an array, all properties must be present, valid and in the same + order as the properties defined in the model. + + @param realm The Realm which should own the object. + @param value The value used to populate the object. + + @see `defaultPropertyValues`, `primaryKey` + */ ++ (instancetype)createOrUpdateModifiedInRealm:(RLMRealm *)realm withValue:(id)value; + +#pragma mark - Properties + +/** + The Realm which manages the object, or `nil` if the object is unmanaged. + */ +@property (nonatomic, readonly, nullable) RLMRealm *realm; + +/** + The object schema which lists the managed properties for the object. + */ +@property (nonatomic, readonly) RLMObjectSchema *objectSchema; + +/** + Indicates if the object can no longer be accessed because it is now invalid. + + An object can no longer be accessed if the object has been deleted from the Realm that manages it, or + if `invalidate` is called on that Realm. + */ +@property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated; + + +#pragma mark - Customizing your Objects + +/** + Returns an array of property names for properties which should be indexed. + + Only string, integer, boolean, and `NSDate` properties are supported. + + @return An array of property names. + */ ++ (NSArray *)indexedProperties; + +/** + Override this method to specify the default values to be used for each property. + + @return A dictionary mapping property names to their default values. + */ ++ (nullable NSDictionary *)defaultPropertyValues; + +/** + Override this method to specify the name of a property to be used as the primary key. + + Only properties of types `RLMPropertyTypeString` and `RLMPropertyTypeInt` can be designated as the primary key. + Primary key properties enforce uniqueness for each value whenever the property is set, which incurs minor overhead. + Indexes are created automatically for primary key properties. + + @return The name of the property designated as the primary key. + */ ++ (nullable NSString *)primaryKey; + +/** + Override this method to specify the names of properties to ignore. These properties will not be managed by the Realm + that manages the object. + + @return An array of property names to ignore. + */ ++ (nullable NSArray *)ignoredProperties; + +/** + Override this method to specify the names of properties that are non-optional (i.e. cannot be assigned a `nil` value). + + By default, all properties of a type whose values can be set to `nil` are considered optional properties. + To require that an object in a Realm always store a non-`nil` value for a property, + add the name of the property to the array returned from this method. + + Properties of `RLMObject` type cannot be non-optional. Array and `NSNumber` properties + can be non-optional, but there is no reason to do so: arrays do not support storing nil, and + if you want a non-optional number you should instead use the primitive type. + + @return An array of property names that are required. + */ ++ (NSArray *)requiredProperties; + +/** + Override this method to provide information related to properties containing linking objects. + + Each property of type `RLMLinkingObjects` must have a key in the dictionary returned by this method consisting + of the property name. The corresponding value must be an instance of `RLMPropertyDescriptor` that describes the class + and property that the property is linked to. + + return @{ @"owners": [RLMPropertyDescriptor descriptorWithClass:Owner.class propertyName:@"dogs"] }; + + @return A dictionary mapping property names to `RLMPropertyDescriptor` instances. + */ ++ (NSDictionary *)linkingObjectsProperties; + + +#pragma mark - Getting & Querying Objects from the Default Realm + +/** + Returns all objects of this object type from the default Realm. + + @return An `RLMResults` containing all objects of this type in the default Realm. + */ ++ (RLMResults *)allObjects; + +/** + Returns all objects of this object type matching the given predicate from the default Realm. + + @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. + + @return An `RLMResults` containing all objects of this type in the default Realm that match the given predicate. + */ ++ (RLMResults *)objectsWhere:(NSString *)predicateFormat, ...; + +/// :nodoc: ++ (RLMResults<__kindof RLMObject *> *)objectsWhere:(NSString *)predicateFormat args:(va_list)args; + + +/** + Returns all objects of this object type matching the given predicate from the default Realm. + + @param predicate The predicate with which to filter the objects. + + @return An `RLMResults` containing all objects of this type in the default Realm that match the given predicate. + */ ++ (RLMResults *)objectsWithPredicate:(nullable NSPredicate *)predicate; + +/** + Retrieves the single instance of this object type with the given primary key from the default Realm. + + Returns the object from the default Realm which has the given primary key, or + `nil` if the object does not exist. This is slightly faster than the otherwise + equivalent `[[SubclassName objectsWhere:@"primaryKeyPropertyName = %@", key] firstObject]`. + + This method requires that `primaryKey` be overridden on the receiving subclass. + + @return An object of this object type, or `nil` if an object with the given primary key does not exist. + @see `-primaryKey` + */ ++ (nullable instancetype)objectForPrimaryKey:(nullable id)primaryKey NS_SWIFT_NAME(object(forPrimaryKey:)); + + +#pragma mark - Querying Specific Realms + +/** + Returns all objects of this object type from the specified Realm. + + @param realm The Realm to query. + + @return An `RLMResults` containing all objects of this type in the specified Realm. + */ ++ (RLMResults *)allObjectsInRealm:(RLMRealm *)realm; + +/** + Returns all objects of this object type matching the given predicate from the specified Realm. + + @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. + @param realm The Realm to query. + + @return An `RLMResults` containing all objects of this type in the specified Realm that match the given predicate. + */ ++ (RLMResults *)objectsInRealm:(RLMRealm *)realm where:(NSString *)predicateFormat, ...; + +/// :nodoc: ++ (RLMResults<__kindof RLMObject *> *)objectsInRealm:(RLMRealm *)realm where:(NSString *)predicateFormat args:(va_list)args; + +/** + Returns all objects of this object type matching the given predicate from the specified Realm. + + @param predicate A predicate to use to filter the elements. + @param realm The Realm to query. + + @return An `RLMResults` containing all objects of this type in the specified Realm that match the given predicate. + */ ++ (RLMResults *)objectsInRealm:(RLMRealm *)realm withPredicate:(nullable NSPredicate *)predicate; + +/** + Retrieves the single instance of this object type with the given primary key from the specified Realm. + + Returns the object from the specified Realm which has the given primary key, or + `nil` if the object does not exist. This is slightly faster than the otherwise + equivalent `[[SubclassName objectsInRealm:realm where:@"primaryKeyPropertyName = %@", key] firstObject]`. + + This method requires that `primaryKey` be overridden on the receiving subclass. + + @return An object of this object type, or `nil` if an object with the given primary key does not exist. + @see `-primaryKey` + */ ++ (nullable instancetype)objectInRealm:(RLMRealm *)realm forPrimaryKey:(nullable id)primaryKey NS_SWIFT_NAME(object(in:forPrimaryKey:)); + +#pragma mark - Notifications + +/** + A callback block for `RLMObject` notifications. + + If the object is deleted from the managing Realm, the block is called with + `deleted` set to `YES` and the other two arguments are `nil`. The block will + never be called again after this. + + If the object is modified, the block will be called with `deleted` set to + `NO`, a `nil` error, and an array of `RLMPropertyChange` objects which + indicate which properties of the objects were modified. + + If an error occurs, `deleted` will be `NO`, `changes` will be `nil`, and + `error` will include information about the error. The block will never be + called again after an error occurs. + */ +typedef void (^RLMObjectChangeBlock)(BOOL deleted, + NSArray *_Nullable changes, + NSError *_Nullable error); + +/** + Registers a block to be called each time the object changes. + + The block will be asynchronously called after each write transaction which + deletes the object or modifies any of the managed properties of the object, + including self-assignments that set a property to its existing value. + + For write transactions performed on different threads or in differen + processes, the block will be called when the managing Realm is + (auto)refreshed to a version including the changes, while for local write + transactions it will be called at some point in the future after the write + transaction is committed. + + Notifications are delivered via the standard run loop, and so can't be + delivered while the run loop is blocked by other activity. When notifications + can't be delivered instantly, multiple notifications may be coalesced into a + single notification. + + Unlike with `RLMArray` and `RLMResults`, there is no "initial" callback made + after you add a new notification block. + + Only objects which are managed by a Realm can be observed in this way. You + must retain the returned token for as long as you want updates to be sent to + the block. To stop receiving updates, call `-invalidate` on the token. + + It is safe to capture a strong reference to the observed object within the + callback block. There is no retain cycle due to that the callback is retained + by the returned token and not by the object itself. + + @warning This method cannot be called during a write transaction, when the + containing Realm is read-only, or on an unmanaged object. + + @param block The block to be called whenever a change occurs. + @return A token which must be held for as long as you want updates to be delivered. + */ +- (RLMNotificationToken *)addNotificationBlock:(RLMObjectChangeBlock)block; + +#pragma mark - Other Instance Methods + +/** + Returns YES if another Realm object instance points to the same object as the receiver in the Realm managing + the receiver. + + For object types with a primary, key, `isEqual:` is overridden to use the same logic as this + method (along with a corresponding implementation for `hash`). + + @param object The object to compare the receiver to. + + @return Whether the object represents the same object as the receiver. + */ +- (BOOL)isEqualToObject:(RLMObject *)object; + +#pragma mark - Dynamic Accessors + +/// :nodoc: +- (nullable id)objectForKeyedSubscript:(NSString *)key; + +/// :nodoc: +- (void)setObject:(nullable id)obj forKeyedSubscript:(NSString *)key; + +@end + +/** + Information about a specific property which changed in an `RLMObject` change notification. + */ +@interface RLMPropertyChange : NSObject + +/** + The name of the property which changed. + */ +@property (nonatomic, readonly, strong) NSString *name; + +/** + The value of the property before the change occurred. This will always be `nil` + if the change happened on the same thread as the notification and for `RLMArray` + properties. + + For object properties this will give the object which was previously linked to, + but that object will have its new values and not the values it had before the + changes. This means that `previousValue` may be a deleted object, and you will + need to check `invalidated` before accessing any of its properties. + */ +@property (nonatomic, readonly, strong, nullable) id previousValue; + +/** + The value of the property after the change occurred. This will always be `nil` + for `RLMArray` properties. + */ +@property (nonatomic, readonly, strong, nullable) id value; +@end + +#pragma mark - RLMArray Property Declaration + +/** + Properties on `RLMObject`s of type `RLMArray` must have an associated type. A type is associated + with an `RLMArray` property by defining a protocol for the object type that the array should contain. + To define the protocol for an object, you can use the macro RLM_ARRAY_TYPE: + + RLM_ARRAY_TYPE(ObjectType) + ... + @property RLMArray *arrayOfObjectTypes; + */ +#define RLM_ARRAY_TYPE(RLM_OBJECT_SUBCLASS)\ +@protocol RLM_OBJECT_SUBCLASS \ +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMObjectBase.h b/!main project/Pods/Realm/include/RLMObjectBase.h new file mode 100644 index 0000000..de40598 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMObjectBase.h @@ -0,0 +1,44 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RLMRealm; +@class RLMSchema; +@class RLMObjectSchema; + +/// :nodoc: +@interface RLMObjectBase : NSObject + +@property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated; + +- (instancetype)init NS_DESIGNATED_INITIALIZER; + ++ (NSString *)className; + +// Returns whether the class is included in the default set of classes managed by a Realm. ++ (BOOL)shouldIncludeInDefaultSchema; + ++ (nullable NSString *)_realmObjectName; ++ (nullable NSDictionary *)_realmColumnNames; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMObjectBase_Dynamic.h b/!main project/Pods/Realm/include/RLMObjectBase_Dynamic.h new file mode 100644 index 0000000..55f64ef --- /dev/null +++ b/!main project/Pods/Realm/include/RLMObjectBase_Dynamic.h @@ -0,0 +1,82 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +@class RLMObjectSchema, RLMRealm; + +NS_ASSUME_NONNULL_BEGIN + +/** + Returns the Realm that manages the object, if one exists. + + @warning This function is useful only in specialized circumstances, for example, when building components + that integrate with Realm. If you are simply building an app on Realm, it is + recommended to retrieve the Realm that manages the object via `RLMObject`. + + @param object An `RLMObjectBase` obtained via a Swift `Object` or `RLMObject`. + + @return The Realm which manages this object. Returns `nil `for unmanaged objects. + */ +FOUNDATION_EXTERN RLMRealm * _Nullable RLMObjectBaseRealm(RLMObjectBase * _Nullable object); + +/** + Returns an `RLMObjectSchema` which describes the managed properties of the object. + + @warning This function is useful only in specialized circumstances, for example, when building components + that integrate with Realm. If you are simply building an app on Realm, it is + recommended to retrieve `objectSchema` via `RLMObject`. + + @param object An `RLMObjectBase` obtained via a Swift `Object` or `RLMObject`. + + @return The object schema which lists the managed properties for the object. + */ +FOUNDATION_EXTERN RLMObjectSchema * _Nullable RLMObjectBaseObjectSchema(RLMObjectBase * _Nullable object); + +/** + Returns the object corresponding to a key value. + + @warning This function is useful only in specialized circumstances, for example, when building components + that integrate with Realm. If you are simply building an app on Realm, it is + recommended to retrieve key values via `RLMObject`. + + @warning Will throw an `NSUndefinedKeyException` if `key` is not present on the object. + + @param object An `RLMObjectBase` obtained via a Swift `Object` or `RLMObject`. + @param key The name of the property. + + @return The object for the property requested. + */ +FOUNDATION_EXTERN id _Nullable RLMObjectBaseObjectForKeyedSubscript(RLMObjectBase * _Nullable object, NSString *key); + +/** + Sets a value for a key on the object. + + @warning This function is useful only in specialized circumstances, for example, when building components + that integrate with Realm. If you are simply building an app on Realm, it is + recommended to set key values via `RLMObject`. + + @warning Will throw an `NSUndefinedKeyException` if `key` is not present on the object. + + @param object An `RLMObjectBase` obtained via a Swift `Object` or `RLMObject`. + @param key The name of the property. + @param obj The object to set as the value of the key. + */ +FOUNDATION_EXTERN void RLMObjectBaseSetObjectForKeyedSubscript(RLMObjectBase * _Nullable object, NSString *key, id _Nullable obj); + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMObjectBase_Private.h b/!main project/Pods/Realm/include/RLMObjectBase_Private.h new file mode 100644 index 0000000..74a54f1 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMObjectBase_Private.h @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2017 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +@class RLMArray; + +NS_ASSUME_NONNULL_BEGIN + +// RLMObjectBase private +@interface RLMObjectBase () ++ (void)initializeLinkedObjectSchemas; +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMObjectSchema.h b/!main project/Pods/Realm/include/RLMObjectSchema.h new file mode 100644 index 0000000..83a7d84 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMObjectSchema.h @@ -0,0 +1,72 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RLMProperty; + +/** + This class represents Realm model object schemas. + + When using Realm, `RLMObjectSchema` instances allow performing migrations and + introspecting the database's schema. + + Object schemas map to tables in the core database. + */ +@interface RLMObjectSchema : NSObject + +#pragma mark - Properties + +/** + An array of `RLMProperty` instances representing the managed properties of a class described by the schema. + + @see `RLMProperty` + */ +@property (nonatomic, readonly, copy) NSArray *properties; + +/** + The name of the class the schema describes. + */ +@property (nonatomic, readonly) NSString *className; + +/** + The property which serves as the primary key for the class the schema describes, if any. + */ +@property (nonatomic, readonly, nullable) RLMProperty *primaryKeyProperty; + +#pragma mark - Methods + +/** + Retrieves an `RLMProperty` object by the property name. + + @param propertyName The property's name. + + @return An `RLMProperty` object, or `nil` if there is no property with the given name. + */ +- (nullable RLMProperty *)objectForKeyedSubscript:(NSString *)propertyName; + +/** + Returns whether two `RLMObjectSchema` instances are equal. + */ +- (BOOL)isEqualToObjectSchema:(RLMObjectSchema *)objectSchema; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMObjectSchema_Private.h b/!main project/Pods/Realm/include/RLMObjectSchema_Private.h new file mode 100644 index 0000000..deca77d --- /dev/null +++ b/!main project/Pods/Realm/include/RLMObjectSchema_Private.h @@ -0,0 +1,71 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +// RLMObjectSchema private +@interface RLMObjectSchema () { +@public + bool _isSwiftClass; +} + +/// The object type name reported to the object store and core. +@property (nonatomic, readonly) NSString *objectName; + +// writable redeclaration +@property (nonatomic, readwrite, copy) NSArray *properties; +@property (nonatomic, readwrite, assign) bool isSwiftClass; + +// class used for this object schema +@property (nonatomic, readwrite, assign) Class objectClass; +@property (nonatomic, readwrite, assign) Class accessorClass; +@property (nonatomic, readwrite, assign) Class unmanagedClass; + +@property (nonatomic, readwrite, nullable) RLMProperty *primaryKeyProperty; + +@property (nonatomic, copy) NSArray *computedProperties; +@property (nonatomic, readonly) NSArray *swiftGenericProperties; + +// returns a cached or new schema for a given object class ++ (instancetype)schemaForObjectClass:(Class)objectClass; +@end + +@interface RLMObjectSchema (Dynamic) +/** + This method is useful only in specialized circumstances, for example, when accessing objects + in a Realm produced externally. If you are simply building an app on Realm, it is not recommended + to use this method as an [RLMObjectSchema](RLMObjectSchema) is generated automatically for every [RLMObject](RLMObject) subclass. + + Initialize an RLMObjectSchema with classname, objectClass, and an array of properties + + @warning This method is useful only in specialized circumstances. + + @param objectClassName The name of the class used to refer to objects of this type. + @param objectClass The Objective-C class used when creating instances of this type. + @param properties An array of RLMProperty instances describing the managed properties for this type. + + @return An initialized instance of RLMObjectSchema. + */ +- (instancetype)initWithClassName:(NSString *)objectClassName objectClass:(Class)objectClass properties:(NSArray *)properties; +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMObjectSchema_Private.hpp b/!main project/Pods/Realm/include/RLMObjectSchema_Private.hpp new file mode 100644 index 0000000..cb00dc8 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMObjectSchema_Private.hpp @@ -0,0 +1,32 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMObjectSchema_Private.h" + +namespace realm { + class ObjectSchema; +} +@class RLMSchema; + +@interface RLMObjectSchema () +// create realm::ObjectSchema copy +- (realm::ObjectSchema)objectStoreCopy:(RLMSchema *)schema; + +// initialize with realm::ObjectSchema ++ (instancetype)objectSchemaForObjectStoreSchema:(realm::ObjectSchema const&)objectSchema; +@end diff --git a/!main project/Pods/Realm/include/RLMObjectStore.h b/!main project/Pods/Realm/include/RLMObjectStore.h new file mode 100644 index 0000000..fb431d7 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMObjectStore.h @@ -0,0 +1,93 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +#ifdef __cplusplus +extern "C" { +#endif + +@class RLMRealm, RLMSchema, RLMObjectBase, RLMResults, RLMProperty; + +typedef NS_ENUM(NSUInteger, RLMUpdatePolicy) { + RLMUpdatePolicyError = 1, + RLMUpdatePolicyUpdateChanged = 3, + RLMUpdatePolicyUpdateAll = 2, +}; + +NS_ASSUME_NONNULL_BEGIN + +void RLMVerifyHasPrimaryKey(Class cls); + +// +// Accessor Creation +// + +// create or get cached accessors for the given schema +void RLMRealmCreateAccessors(RLMSchema *schema); + + +// +// Adding, Removing, Getting Objects +// + +// add an object to the given realm +void RLMAddObjectToRealm(RLMObjectBase *object, RLMRealm *realm, RLMUpdatePolicy); + +// delete an object from its realm +void RLMDeleteObjectFromRealm(RLMObjectBase *object, RLMRealm *realm); + +// deletes all objects from a realm +void RLMDeleteAllObjectsFromRealm(RLMRealm *realm); + +// get objects of a given class +RLMResults *RLMGetObjects(RLMRealm *realm, NSString *objectClassName, NSPredicate * _Nullable predicate) +NS_RETURNS_RETAINED; + +// get an object with the given primary key +id _Nullable RLMGetObject(RLMRealm *realm, NSString *objectClassName, id _Nullable key) NS_RETURNS_RETAINED; + +// create object from array or dictionary +RLMObjectBase *RLMCreateObjectInRealmWithValue(RLMRealm *realm, NSString *className, + id _Nullable value, RLMUpdatePolicy updatePolicy) +NS_RETURNS_RETAINED; + +// +// Accessor Creation +// + + +// switch List<> properties from being backed by unmanaged RLMArrays to RLMManagedArray +void RLMInitializeSwiftAccessorGenerics(RLMObjectBase *object); + +#ifdef __cplusplus +} + +namespace realm { + class Table; + template class BasicRowExpr; + using RowExpr = BasicRowExpr; +} +class RLMClassInfo; + +// Create accessors +RLMObjectBase *RLMCreateObjectAccessor(RLMClassInfo& info, NSUInteger index) NS_RETURNS_RETAINED; +RLMObjectBase *RLMCreateObjectAccessor(RLMClassInfo& info, realm::RowExpr row) NS_RETURNS_RETAINED; +#endif + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMObject_Private.h b/!main project/Pods/Realm/include/RLMObject_Private.h new file mode 100644 index 0000000..430051a --- /dev/null +++ b/!main project/Pods/Realm/include/RLMObject_Private.h @@ -0,0 +1,72 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RLMProperty, RLMArray; +typedef NS_ENUM(int32_t, RLMPropertyType); + +FOUNDATION_EXTERN void RLMInitializeWithValue(RLMObjectBase *, id, RLMSchema *); + +// RLMObject accessor and read/write realm +@interface RLMObjectBase () { +@public + RLMRealm *_realm; + __unsafe_unretained RLMObjectSchema *_objectSchema; +} + +// shared schema for this class ++ (nullable RLMObjectSchema *)sharedSchema; + ++ (nullable NSArray *)_getPropertiesWithInstance:(id)obj; ++ (bool)_realmIgnoreClass; + +@end + +@interface RLMDynamicObject : RLMObject + +@end + +// Calls valueForKey: and re-raises NSUndefinedKeyExceptions +FOUNDATION_EXTERN id _Nullable RLMValidatedValueForProperty(id object, NSString *key, NSString *className); + +// Compare two RLObjectBases +FOUNDATION_EXTERN BOOL RLMObjectBaseAreEqual(RLMObjectBase * _Nullable o1, RLMObjectBase * _Nullable o2); + +typedef void (^RLMObjectNotificationCallback)(NSArray *_Nullable propertyNames, + NSArray *_Nullable oldValues, + NSArray *_Nullable newValues, + NSError *_Nullable error); +FOUNDATION_EXTERN RLMNotificationToken *RLMObjectAddNotificationBlock(RLMObjectBase *obj, RLMObjectNotificationCallback block); + +// Returns whether the class is a descendent of RLMObjectBase +FOUNDATION_EXTERN BOOL RLMIsObjectOrSubclass(Class klass); + +// Returns whether the class is an indirect descendant of RLMObjectBase +FOUNDATION_EXTERN BOOL RLMIsObjectSubclass(Class klass); + +FOUNDATION_EXTERN const NSUInteger RLMDescriptionMaxDepth; + +@interface RLMManagedPropertyAccessor : NSObject ++ (void)initializeObject:(void *)object parent:(RLMObjectBase *)parent property:(RLMProperty *)property; ++ (id)get:(void *)pointer; +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMObject_Private.hpp b/!main project/Pods/Realm/include/RLMObject_Private.hpp new file mode 100644 index 0000000..3113961 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMObject_Private.hpp @@ -0,0 +1,56 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMObject_Private.h" + +#import "RLMRealm_Private.hpp" +#import "RLMUtil.hpp" + +#import // required by row.hpp +#import + +class RLMObservationInfo; + +// RLMObject accessor and read/write realm +@interface RLMObjectBase () { + @public + realm::Row _row; + RLMObservationInfo *_observationInfo; + RLMClassInfo *_info; +} +@end + +id RLMCreateManagedAccessor(Class cls, RLMClassInfo *info) NS_RETURNS_RETAINED; + +// throw an exception if the object is invalidated or on the wrong thread +static inline void RLMVerifyAttached(__unsafe_unretained RLMObjectBase *const obj) { + if (!obj->_row.is_attached()) { + @throw RLMException(@"Object has been deleted or invalidated."); + } + [obj->_realm verifyThread]; +} + +// throw an exception if the object can't be modified for any reason +static inline void RLMVerifyInWriteTransaction(__unsafe_unretained RLMObjectBase *const obj) { + // first verify is attached + RLMVerifyAttached(obj); + + if (!obj->_realm.inWriteTransaction) { + @throw RLMException(@"Attempting to modify object outside of a write transaction - call beginWriteTransaction on an RLMRealm instance first."); + } +} diff --git a/!main project/Pods/Realm/include/RLMObservation.hpp b/!main project/Pods/Realm/include/RLMObservation.hpp new file mode 100644 index 0000000..0f5b215 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMObservation.hpp @@ -0,0 +1,153 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +#import "binding_context.hpp" + +#import +#import + +#import + +@class RLMObjectBase, RLMRealm, RLMSchema, RLMProperty, RLMObjectSchema; +class RLMClassInfo; +class RLMSchemaInfo; + +namespace realm { + class History; + class SharedGroup; +} + +// RLMObservationInfo stores all of the KVO-related data for RLMObjectBase and +// RLMArray. There is a one-to-one relationship between observed objects and +// RLMObservationInfo instances, so it could be folded into RLMObjectBase, and +// is a separate class mostly to avoid making all accessor objects far larger. +// +// RLMClassInfo stores a vector of pointers to the first observation info +// created for each row. If there are multiple observation infos for a single +// row (such as if there are multiple observed objects backed by a single row, +// or if both an object and an array property of that object are observed), +// they're stored in an intrusive doubly-linked-list in the `next` and `prev` +// members. This is done primarily to make it simpler and faster to loop over +// all of the observed objects for a single row, as that needs to be done for +// every change. +class RLMObservationInfo { +public: + RLMObservationInfo(id object); + RLMObservationInfo(RLMClassInfo &objectSchema, std::size_t row, id object); + ~RLMObservationInfo(); + + realm::Row const& getRow() const { + return row; + } + + NSString *columnName(size_t col) const noexcept; + + // Send willChange/didChange notifications to all observers for this object/row + // Sends the array versions if indexes is non-nil, normal versions otherwise + void willChange(NSString *key, NSKeyValueChange kind=NSKeyValueChangeSetting, NSIndexSet *indexes=nil) const; + void didChange(NSString *key, NSKeyValueChange kind=NSKeyValueChangeSetting, NSIndexSet *indexes=nil) const; + + bool isForRow(size_t ndx) const { + return row && row.get_index() == ndx; + } + + void recordObserver(realm::Row& row, RLMClassInfo *objectInfo, RLMObjectSchema *objectSchema, NSString *keyPath); + void removeObserver(); + bool hasObservers() const { return observerCount > 0; } + + // valueForKey: on observed object and array properties needs to return the + // same object each time for KVO to work at all. Doing this all the time + // requires some odd semantics to avoid reference cycles, so instead we do + // it only to the extent specifically required by KVO. In addition, we + // need to continue to return the same object even if this row is deleted, + // or deleting an object with active observers will explode horribly. + // Once prepareForInvalidation() is called, valueForKey() will always return + // the cached value for object and array properties without checking the + // backing row to verify it's up-to-date. + // + // prepareForInvalidation() must be called on the head of the linked list + // (i.e. on the object pointed to directly by the object schema) + id valueForKey(NSString *key); + + void prepareForInvalidation(); + +private: + // Doubly-linked-list of observed objects for the same row as this + RLMObservationInfo *next = nullptr; + RLMObservationInfo *prev = nullptr; + + // Row being observed + realm::Row row; + RLMClassInfo *objectSchema = nullptr; + + // Object doing the observing + __unsafe_unretained id object = nil; + + // valueForKey: hack + bool invalidated = false; + size_t observerCount = 0; + NSString *lastKey = nil; + __unsafe_unretained RLMProperty *lastProp = nil; + + // objects returned from valueForKey() to keep them alive in case observers + // are added and so that they can still be accessed after row is detached + NSMutableDictionary *cachedObjects; + + void setRow(realm::Table &table, size_t newRow); + + template + void forEach(F&& f) const { + // The user's observation handler may release their last reference to + // the object being observed, which will result in the RLMObservationInfo + // being destroyed. As a result, we need to retain the object which owns + // both `this` and the current info we're looking at. + __attribute__((objc_precise_lifetime)) id self = object, current; + for (auto info = prev; info; info = info->prev) { + current = info->object; + f(info->object); + } + for (auto info = this; info; info = info->next) { + current = info->object; + f(info->object); + } + } + + // Default move/copy constructors don't work due to the intrusive linked + // list and we don't need them + RLMObservationInfo(RLMObservationInfo const&) = delete; + RLMObservationInfo(RLMObservationInfo&&) = delete; + RLMObservationInfo& operator=(RLMObservationInfo const&) = delete; + RLMObservationInfo& operator=(RLMObservationInfo&&) = delete; +}; + +// Get the the observation info chain for the given row +// Will simply return info if it's non-null, and will search ojectSchema's array +// for a matching one otherwise, and return null if there are none +RLMObservationInfo *RLMGetObservationInfo(RLMObservationInfo *info, size_t row, RLMClassInfo& objectSchema); + +// delete all objects from a single table with change notifications +void RLMClearTable(RLMClassInfo &realm); + +// invoke the block, sending notifications for cascading deletes/link nullifications +void RLMTrackDeletions(RLMRealm *realm, dispatch_block_t block); + +std::vector RLMGetObservedRows(RLMSchemaInfo const& schema); +void RLMWillChange(std::vector const& observed, std::vector const& invalidated); +void RLMDidChange(std::vector const& observed, std::vector const& invalidated); diff --git a/!main project/Pods/Realm/include/RLMOptionalBase.h b/!main project/Pods/Realm/include/RLMOptionalBase.h new file mode 100644 index 0000000..d5b2430 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMOptionalBase.h @@ -0,0 +1,36 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RLMObjectBase, RLMProperty; + +@interface RLMOptionalBase : NSProxy +- (instancetype)init; +@end + +FOUNDATION_EXTERN id _Nullable RLMGetOptional(RLMOptionalBase *); +FOUNDATION_EXTERN void RLMSetOptional(RLMOptionalBase *, id _Nullable); + +void RLMInitializeManagedOptional(RLMOptionalBase *, RLMObjectBase *parent, RLMProperty *prop); +void RLMInitializeUnmanagedOptional(RLMOptionalBase *, RLMObjectBase *parent, RLMProperty *prop); + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMPlatform.h b/!main project/Pods/Realm/include/RLMPlatform.h new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMPlatform.h @@ -0,0 +1 @@ + diff --git a/!main project/Pods/Realm/include/RLMPredicateUtil.hpp b/!main project/Pods/Realm/include/RLMPredicateUtil.hpp new file mode 100644 index 0000000..71426de --- /dev/null +++ b/!main project/Pods/Realm/include/RLMPredicateUtil.hpp @@ -0,0 +1,22 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +using ExpressionVisitor = std::function; +NSPredicate *transformPredicate(NSPredicate *, ExpressionVisitor); diff --git a/!main project/Pods/Realm/include/RLMPrefix.h b/!main project/Pods/Realm/include/RLMPrefix.h new file mode 100644 index 0000000..df08ce9 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMPrefix.h @@ -0,0 +1,35 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifdef __OBJC__ +#import +#endif + +#ifdef __cplusplus +#import +#import +#import +#import +#import + +#import +#import +#import +#import +#import +#endif diff --git a/!main project/Pods/Realm/include/RLMProperty.h b/!main project/Pods/Realm/include/RLMProperty.h new file mode 100644 index 0000000..a2bc894 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMProperty.h @@ -0,0 +1,126 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// :nodoc: +@protocol RLMInt @end +/// :nodoc: +@protocol RLMBool @end +/// :nodoc: +@protocol RLMDouble @end +/// :nodoc: +@protocol RLMFloat @end +/// :nodoc: +@protocol RLMString @end +/// :nodoc: +@protocol RLMDate @end +/// :nodoc: +@protocol RLMData @end + +/// :nodoc: +@interface NSNumber () +@end + +/** + `RLMProperty` instances represent properties managed by a Realm in the context + of an object schema. Such properties may be persisted to a Realm file or + computed from other data from the Realm. + + When using Realm, `RLMProperty` instances allow performing migrations and + introspecting the database's schema. + + These property instances map to columns in the core database. + */ +@interface RLMProperty : NSObject + +#pragma mark - Properties + +/** + The name of the property. + */ +@property (nonatomic, readonly) NSString *name; + +/** + The type of the property. + + @see `RLMPropertyType` + */ +@property (nonatomic, readonly) RLMPropertyType type; + +/** + Indicates whether this property is indexed. + + @see `RLMObject` + */ +@property (nonatomic, readonly) BOOL indexed; + +/** + For `RLMObject` and `RLMArray` properties, the name of the class of object stored in the property. + */ +@property (nonatomic, readonly, copy, nullable) NSString *objectClassName; + +/** + For linking objects properties, the property name of the property the linking objects property is linked to. + */ +@property (nonatomic, readonly, copy, nullable) NSString *linkOriginPropertyName; + +/** + Indicates whether this property is optional. + */ +@property (nonatomic, readonly) BOOL optional; + +/** + Indicates whether this property is an array. + */ +@property (nonatomic, readonly) BOOL array; + +#pragma mark - Methods + +/** + Returns whether a given property object is equal to the receiver. + */ +- (BOOL)isEqualToProperty:(RLMProperty *)property; + +@end + + +/** + An `RLMPropertyDescriptor` instance represents a specific property on a given class. + */ +@interface RLMPropertyDescriptor : NSObject + +/** + Creates and returns a property descriptor. + + @param objectClass The class of this property descriptor. + @param propertyName The name of this property descriptor. + */ ++ (instancetype)descriptorWithClass:(Class)objectClass propertyName:(NSString *)propertyName; + +/// The class of the property. +@property (nonatomic, readonly) Class objectClass; + +/// The name of the property. +@property (nonatomic, readonly) NSString *propertyName; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMProperty_Private.h b/!main project/Pods/Realm/include/RLMProperty_Private.h new file mode 100644 index 0000000..5829f7e --- /dev/null +++ b/!main project/Pods/Realm/include/RLMProperty_Private.h @@ -0,0 +1,128 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +#import + +@class RLMObjectBase; + +NS_ASSUME_NONNULL_BEGIN + +BOOL RLMPropertyTypeIsComputed(RLMPropertyType propertyType); +FOUNDATION_EXTERN void RLMValidateSwiftPropertyName(NSString *name); + +// Translate an rlmtype to a string representation +static inline NSString *RLMTypeToString(RLMPropertyType type) { + switch (type) { + case RLMPropertyTypeString: + return @"string"; + case RLMPropertyTypeInt: + return @"int"; + case RLMPropertyTypeBool: + return @"bool"; + case RLMPropertyTypeDate: + return @"date"; + case RLMPropertyTypeData: + return @"data"; + case RLMPropertyTypeDouble: + return @"double"; + case RLMPropertyTypeFloat: + return @"float"; + case RLMPropertyTypeAny: + return @"any"; + case RLMPropertyTypeObject: + return @"object"; + case RLMPropertyTypeLinkingObjects: + return @"linking objects"; + } + return @"Unknown"; +} + +// private property interface +@interface RLMProperty () { +@public + RLMPropertyType _type; + Ivar _swiftIvar; +} + +- (instancetype)initWithName:(NSString *)name + indexed:(BOOL)indexed + linkPropertyDescriptor:(nullable RLMPropertyDescriptor *)linkPropertyDescriptor + property:(objc_property_t)property; + +- (instancetype)initSwiftPropertyWithName:(NSString *)name + indexed:(BOOL)indexed + linkPropertyDescriptor:(nullable RLMPropertyDescriptor *)linkPropertyDescriptor + property:(objc_property_t)property + instance:(RLMObjectBase *)objectInstance; + +- (void)updateAccessors; + +// private setters +@property (nonatomic, readwrite) NSString *name; +@property (nonatomic, readwrite, assign) RLMPropertyType type; +@property (nonatomic, readwrite) BOOL indexed; +@property (nonatomic, readwrite) BOOL optional; +@property (nonatomic, readwrite) BOOL array; +@property (nonatomic, copy, nullable) NSString *objectClassName; +@property (nonatomic, copy, nullable) NSString *linkOriginPropertyName; + +// private properties +@property (nonatomic, readwrite, nullable) NSString *columnName; +@property (nonatomic, assign) NSUInteger index; +@property (nonatomic, assign) BOOL isPrimary; +@property (nonatomic, assign, nullable) Ivar swiftIvar; +@property (nonatomic, assign, nullable) Class swiftAccessor; + +// getter and setter names +@property (nonatomic, copy) NSString *getterName; +@property (nonatomic, copy) NSString *setterName; +@property (nonatomic) SEL getterSel; +@property (nonatomic) SEL setterSel; + +- (RLMProperty *)copyWithNewName:(NSString *)name; + +@end + +@interface RLMProperty (Dynamic) +/** + This method is useful only in specialized circumstances, for example, in conjunction with + +[RLMObjectSchema initWithClassName:objectClass:properties:]. If you are simply building an + app on Realm, it is not recommened to use this method. + + Initialize an RLMProperty + + @warning This method is useful only in specialized circumstances. + + @param name The property name. + @param type The property type. + @param objectClassName The object type used for Object and Array types. + @param linkOriginPropertyName The property name of the origin of a link. Used for linking objects properties. + + @return An initialized instance of RLMProperty. + */ +- (instancetype)initWithName:(NSString *)name + type:(RLMPropertyType)type + objectClassName:(nullable NSString *)objectClassName + linkOriginPropertyName:(nullable NSString *)linkOriginPropertyName + indexed:(BOOL)indexed + optional:(BOOL)optional; +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMProperty_Private.hpp b/!main project/Pods/Realm/include/RLMProperty_Private.hpp new file mode 100644 index 0000000..cf8b17a --- /dev/null +++ b/!main project/Pods/Realm/include/RLMProperty_Private.hpp @@ -0,0 +1,33 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +namespace realm { + struct Property; +} + +@class RLMSchema; + +@interface RLMProperty () + ++ (instancetype)propertyForObjectStoreProperty:(const realm::Property&)property; + +- (realm::Property)objectStoreCopy:(RLMSchema *)schema; + +@end diff --git a/!main project/Pods/Realm/include/RLMQueryUtil.hpp b/!main project/Pods/Realm/include/RLMQueryUtil.hpp new file mode 100644 index 0000000..12df76e --- /dev/null +++ b/!main project/Pods/Realm/include/RLMQueryUtil.hpp @@ -0,0 +1,39 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +#import + +namespace realm { + class Group; + class Query; + class SortDescriptor; +} + +@class RLMObjectSchema, RLMProperty, RLMSchema, RLMSortDescriptor; +class RLMClassInfo; + +extern NSString * const RLMPropertiesComparisonTypeMismatchException; +extern NSString * const RLMUnsupportedTypesFoundInPropertyComparisonException; + +realm::Query RLMPredicateToQuery(NSPredicate *predicate, RLMObjectSchema *objectSchema, + RLMSchema *schema, realm::Group &group); + +// return property - throw for invalid column name +RLMProperty *RLMValidatedProperty(RLMObjectSchema *objectSchema, NSString *columnName); diff --git a/!main project/Pods/Realm/include/RLMRealm+Sync.h b/!main project/Pods/Realm/include/RLMRealm+Sync.h new file mode 100644 index 0000000..42f1616 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMRealm+Sync.h @@ -0,0 +1,47 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2017 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +#import "RLMRealm.h" + +@class RLMResults, RLMSyncSession; + +/** + A callback used to vend the results of a partial sync fetch. + */ +typedef void(^RLMPartialSyncFetchCallback)(RLMResults * _Nullable results, NSError * _Nullable error); + +NS_ASSUME_NONNULL_BEGIN + +/// +@interface RLMRealm (Sync) + +/// :nodoc: +- (void)subscribeToObjects:(Class)type where:(NSString *)query callback:(RLMPartialSyncFetchCallback)callback +__attribute__((unavailable("Use -[RLMResults subscribe]"))); + +/** + Get the RLMSyncSession used by this Realm. Will be nil if this is not a + synchronized Realm. +*/ +@property (nonatomic, nullable, readonly) RLMSyncSession *syncSession; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMRealm.h b/!main project/Pods/Realm/include/RLMRealm.h new file mode 100644 index 0000000..cbc99ce --- /dev/null +++ b/!main project/Pods/Realm/include/RLMRealm.h @@ -0,0 +1,796 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import +#import "RLMConstants.h" + +@class RLMRealmConfiguration, RLMRealm, RLMObject, RLMSchema, RLMMigration, RLMNotificationToken, RLMThreadSafeReference, RLMAsyncOpenTask; +struct RLMRealmPrivileges; +struct RLMClassPrivileges; +struct RLMObjectPrivileges; + +/** + A callback block for opening Realms asynchronously. + + Returns the Realm if the open was successful, or an error otherwise. + */ +typedef void(^RLMAsyncOpenRealmCallback)(RLMRealm * _Nullable realm, NSError * _Nullable error); + +NS_ASSUME_NONNULL_BEGIN + +/** + An `RLMRealm` instance (also referred to as "a Realm") represents a Realm + database. + + Realms can either be stored on disk (see `+[RLMRealm realmWithURL:]`) or in + memory (see `RLMRealmConfiguration`). + + `RLMRealm` instances are cached internally, and constructing equivalent `RLMRealm` + objects (for example, by using the same path or identifier) multiple times on a single thread + within a single iteration of the run loop will normally return the same + `RLMRealm` object. + + If you specifically want to ensure an `RLMRealm` instance is + destroyed (for example, if you wish to open a Realm, check some property, and + then possibly delete the Realm file and re-open it), place the code which uses + the Realm within an `@autoreleasepool {}` and ensure you have no other + strong references to it. + + @warning `RLMRealm` instances are not thread safe and cannot be shared across + threads or dispatch queues. Trying to do so will cause an exception to be thrown. + You must call this method on each thread you want + to interact with the Realm on. For dispatch queues, this means that you must + call it in each block which is dispatched, as a queue is not guaranteed to run + all of its blocks on the same thread. + */ + +@interface RLMRealm : NSObject + +#pragma mark - Creating & Initializing a Realm + +/** + Obtains an instance of the default Realm. + + The default Realm is used by the `RLMObject` class methods + which do not take an `RLMRealm` parameter, but is otherwise not special. The + default Realm is persisted as *default.realm* under the *Documents* directory of + your Application on iOS, and in your application's *Application Support* + directory on OS X. + + The default Realm is created using the default `RLMRealmConfiguration`, which + can be changed via `+[RLMRealmConfiguration setDefaultConfiguration:]`. + + @return The default `RLMRealm` instance for the current thread. + */ ++ (instancetype)defaultRealm; + +/** + Obtains an `RLMRealm` instance with the given configuration. + + @param configuration A configuration object to use when creating the Realm. + @param error If an error occurs, upon return contains an `NSError` object + that describes the problem. If you are not interested in + possible errors, pass in `NULL`. + + @return An `RLMRealm` instance. + */ ++ (nullable instancetype)realmWithConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error; + +/** + Obtains an `RLMRealm` instance persisted at a specified file URL. + + @param fileURL The local URL of the file the Realm should be saved at. + + @return An `RLMRealm` instance. + */ ++ (instancetype)realmWithURL:(NSURL *)fileURL; + +/** + Asynchronously open a Realm and deliver it to a block on the given queue. + + Opening a Realm asynchronously will perform all work needed to get the Realm to + a usable state (such as running potentially time-consuming migrations) on a + background thread before dispatching to the given queue. In addition, + synchronized Realms wait for all remote content available at the time the + operation began to be downloaded and available locally. + + @param configuration A configuration object to use when opening the Realm. + @param callbackQueue The dispatch queue on which the callback should be run. + @param callback A callback block. If the Realm was successfully opened, + it will be passed in as an argument. + Otherwise, an `NSError` describing what went wrong will be + passed to the block instead. + + @note The returned Realm is confined to the thread on which it was created. + Because GCD does not guarantee that queues will always use the same + thread, accessing the returned Realm outside the callback block (even if + accessed from `callbackQueue`) is unsafe. + */ ++ (RLMAsyncOpenTask *)asyncOpenWithConfiguration:(RLMRealmConfiguration *)configuration + callbackQueue:(dispatch_queue_t)callbackQueue + callback:(RLMAsyncOpenRealmCallback)callback; + +/** + The `RLMSchema` used by the Realm. + */ +@property (nonatomic, readonly) RLMSchema *schema; + +/** + Indicates if the Realm is currently engaged in a write transaction. + + @warning Do not simply check this property and then start a write transaction whenever an object needs to be + created, updated, or removed. Doing so might cause a large number of write transactions to be created, + degrading performance. Instead, always prefer performing multiple updates during a single transaction. + */ +@property (nonatomic, readonly) BOOL inWriteTransaction; + +/** + The `RLMRealmConfiguration` object that was used to create this `RLMRealm` instance. + */ +@property (nonatomic, readonly) RLMRealmConfiguration *configuration; + +/** + Indicates if this Realm contains any objects. + */ +@property (nonatomic, readonly) BOOL isEmpty; + +#pragma mark - File Management + +/** + Writes a compacted and optionally encrypted copy of the Realm to the given local URL. + + The destination file cannot already exist. + + Note that if this method is called from within a write transaction, the + *current* data is written, not the data from the point when the previous write + transaction was committed. + + @param fileURL Local URL to save the Realm to. + @param key Optional 64-byte encryption key to encrypt the new file with. + @param error If an error occurs, upon return contains an `NSError` object + that describes the problem. If you are not interested in + possible errors, pass in `NULL`. + + @return `YES` if the Realm was successfully written to disk, `NO` if an error occurred. + */ +- (BOOL)writeCopyToURL:(NSURL *)fileURL encryptionKey:(nullable NSData *)key error:(NSError **)error; + +/** + Checks if the Realm file for the given configuration exists locally on disk. + + For non-synchronized, non-in-memory Realms, this is equivalent to + `-[NSFileManager.defaultManager fileExistsAtPath:config.path]`. For + synchronized Realms, it takes care of computing the actual path on disk based + on the server, virtual path, and user as is done when opening the Realm. + + @param config A Realm configuration to check the existence of. + @return YES if the Realm file for the given configuration exists on disk, NO otherwise. + */ ++ (BOOL)fileExistsForConfiguration:(RLMRealmConfiguration *)config; + +/** + Deletes the local Realm file and associated temporary files for the given configuration. + + This deletes the ".realm", ".note" and ".management" files which would be + created by opening the Realm with the given configuration. It does not delete + the ".lock" file (which contains no persisted data and is recreated from + scratch every time the Realm file is opened). + + The Realm must not be currently open on any thread or in another process. If + it is, this will return NO and report the error RLMErrorAlreadyOpen. Attempting to open + the Realm on another thread while the deletion is happening will block (and + then create a new Realm and open that afterwards). + + If the Realm already does not exist this will return `NO` and report the error NSFileNoSuchFileError; + + @param config A Realm configuration identifying the Realm to be deleted. + @return YES if any files were deleted, NO otherwise. + */ ++ (BOOL)deleteFilesForConfiguration:(RLMRealmConfiguration *)config error:(NSError **)error + __attribute__((swift_error(nonnull_error))); + +#pragma mark - Notifications + +/** + The type of a block to run whenever the data within the Realm is modified. + + @see `-[RLMRealm addNotificationBlock:]` + */ +typedef void (^RLMNotificationBlock)(RLMNotification notification, RLMRealm *realm); + +#pragma mark - Receiving Notification when a Realm Changes + +/** + Adds a notification handler for changes in this Realm, and returns a notification token. + + Notification handlers are called after each write transaction is committed, + either on the current thread or other threads. + + Handler blocks are called on the same thread that they were added on, and may + only be added on threads which are currently within a run loop. Unless you are + specifically creating and running a run loop on a background thread, this will + normally only be the main thread. + + The block has the following definition: + + typedef void(^RLMNotificationBlock)(RLMNotification notification, RLMRealm *realm); + + It receives the following parameters: + + - `NSString` \***notification**: The name of the incoming notification. See + `RLMRealmNotification` for information on what + notifications are sent. + - `RLMRealm` \***realm**: The Realm for which this notification occurred. + + @param block A block which is called to process Realm notifications. + + @return A token object which must be retained as long as you wish to continue + receiving change notifications. + */ +- (RLMNotificationToken *)addNotificationBlock:(RLMNotificationBlock)block __attribute__((warn_unused_result)); + +#pragma mark - Writing to a Realm + +/** + Begins a write transaction on the Realm. + + Only one write transaction can be open at a time for each Realm file. Write + transactions cannot be nested, and trying to begin a write transaction on a + Realm which is already in a write transaction will throw an exception. Calls to + `beginWriteTransaction` from `RLMRealm` instances for the same Realm file in + other threads or other processes will block until the current write transaction + completes or is cancelled. + + Before beginning the write transaction, `beginWriteTransaction` updates the + `RLMRealm` instance to the latest Realm version, as if `refresh` had been + called, and generates notifications if applicable. This has no effect if the + Realm was already up to date. + + It is rarely a good idea to have write transactions span multiple cycles of + the run loop, but if you do wish to do so you will need to ensure that the + Realm participating in the write transaction is kept alive until the write + transaction is committed. + */ +- (void)beginWriteTransaction; + +/** + Commits all write operations in the current write transaction, and ends the + transaction. + + After saving the changes, all notification blocks registered on this specific + `RLMRealm` instance are invoked synchronously. Notification blocks registered + on other threads or on collections are invoked asynchronously. If you do not + want to receive a specific notification for this write tranaction, see + `commitWriteTransactionWithoutNotifying:error:`. + + This method can fail if there is insufficient disk space available to save the + writes made, or due to unexpected i/o errors. This version of the method throws + an exception when errors occur. Use the version with a `NSError` out parameter + instead if you wish to handle errors. + + @warning This method may only be called during a write transaction. + */ +- (void)commitWriteTransaction NS_SWIFT_UNAVAILABLE(""); + +/** + Commits all write operations in the current write transaction, and ends the + transaction. + + After saving the changes, all notification blocks registered on this specific + `RLMRealm` instance are invoked synchronously. Notification blocks registered + on other threads or on collections are invoked asynchronously. If you do not + want to receive a specific notification for this write tranaction, see + `commitWriteTransactionWithoutNotifying:error:`. + + This method can fail if there is insufficient disk space available to save the + writes made, or due to unexpected i/o errors. + + @warning This method may only be called during a write transaction. + + @param error If an error occurs, upon return contains an `NSError` object + that describes the problem. If you are not interested in + possible errors, pass in `NULL`. + + @return Whether the transaction succeeded. + */ +- (BOOL)commitWriteTransaction:(NSError **)error; + +/** + Commits all write operations in the current write transaction, without + notifying specific notification blocks of the changes. + + After saving the changes, all notification blocks registered on this specific + `RLMRealm` instance are invoked synchronously. Notification blocks registered + on other threads or on collections are scheduled to be invoked asynchronously. + + You can skip notifiying specific notification blocks about the changes made + in this write transaction by passing in their associated notification tokens. + This is primarily useful when the write transaction is saving changes already + made in the UI and you do not want to have the notification block attempt to + re-apply the same changes. + + The tokens passed to this method must be for notifications for this specific + `RLMRealm` instance. Notifications for different threads cannot be skipped + using this method. + + This method can fail if there is insufficient disk space available to save the + writes made, or due to unexpected i/o errors. + + @warning This method may only be called during a write transaction. + + @param tokens An array of notification tokens which were returned from adding + callbacks which you do not want to be notified for the changes + made in this write transaction. + @param error If an error occurs, upon return contains an `NSError` object + that describes the problem. If you are not interested in + possible errors, pass in `NULL`. + + @return Whether the transaction succeeded. + */ +- (BOOL)commitWriteTransactionWithoutNotifying:(NSArray *)tokens error:(NSError **)error; + +/** + Reverts all writes made during the current write transaction and ends the transaction. + + This rolls back all objects in the Realm to the state they were in at the + beginning of the write transaction, and then ends the transaction. + + This restores the data for deleted objects, but does not revive invalidated + object instances. Any `RLMObject`s which were added to the Realm will be + invalidated rather than becoming unmanaged. + Given the following code: + + ObjectType *oldObject = [[ObjectType objectsWhere:@"..."] firstObject]; + ObjectType *newObject = [[ObjectType alloc] init]; + + [realm beginWriteTransaction]; + [realm addObject:newObject]; + [realm deleteObject:oldObject]; + [realm cancelWriteTransaction]; + + Both `oldObject` and `newObject` will return `YES` for `isInvalidated`, + but re-running the query which provided `oldObject` will once again return + the valid object. + + KVO observers on any objects which were modified during the transaction will + be notified about the change back to their initial values, but no other + notifcations are produced by a cancelled write transaction. + + @warning This method may only be called during a write transaction. + */ +- (void)cancelWriteTransaction; + +/** + Performs actions contained within the given block inside a write transaction. + + @see `[RLMRealm transactionWithoutNotifying:block:error:]` + */ +- (void)transactionWithBlock:(__attribute__((noescape)) void(^)(void))block NS_SWIFT_UNAVAILABLE(""); + +/** + Performs actions contained within the given block inside a write transaction. + + @see `[RLMRealm transactionWithoutNotifying:block:error:]` + */ +- (BOOL)transactionWithBlock:(__attribute__((noescape)) void(^)(void))block error:(NSError **)error; + +/** + Performs actions contained within the given block inside a write transaction. + + @see `[RLMRealm transactionWithoutNotifying:block:error:]` + */ +- (void)transactionWithoutNotifying:(NSArray *)tokens block:(__attribute__((noescape)) void(^)(void))block; + +/** + Performs actions contained within the given block inside a write transaction. + + Write transactions cannot be nested, and trying to execute a write transaction + on a Realm which is already participating in a write transaction will throw an + exception. Calls to `transactionWithBlock:` from `RLMRealm` instances in other + threads will block until the current write transaction completes. + + Before beginning the write transaction, `transactionWithBlock:` updates the + `RLMRealm` instance to the latest Realm version, as if `refresh` had been called, and + generates notifications if applicable. This has no effect if the Realm + was already up to date. + + You can skip notifiying specific notification blocks about the changes made + in this write transaction by passing in their associated notification tokens. + This is primarily useful when the write transaction is saving changes already + made in the UI and you do not want to have the notification block attempt to + re-apply the same changes. + + The tokens passed to this method must be for notifications for this specific + `RLMRealm` instance. Notifications for different threads cannot be skipped + using this method. + + @param tokens An array of notification tokens which were returned from adding + callbacks which you do not want to be notified for the changes + made in this write transaction. + @param block The block containing actions to perform. + @param error If an error occurs, upon return contains an `NSError` object + that describes the problem. If you are not interested in + possible errors, pass in `NULL`. + + @return Whether the transaction succeeded. + */ +- (BOOL)transactionWithoutNotifying:(NSArray *)tokens block:(__attribute__((noescape)) void(^)(void))block error:(NSError **)error; + +/** + Updates the Realm and outstanding objects managed by the Realm to point to the + most recent data. + + If the version of the Realm is actually changed, Realm and collection + notifications will be sent to reflect the changes. This may take some time, as + collection notifications are prepared on a background thread. As a result, + calling this method on the main thread is not advisable. + + @return Whether there were any updates for the Realm. Note that `YES` may be + returned even if no data actually changed. + */ +- (BOOL)refresh; + +/** + Set this property to `YES` to automatically update this Realm when changes + happen in other threads. + + If set to `YES` (the default), changes made on other threads will be reflected + in this Realm on the next cycle of the run loop after the changes are + committed. If set to `NO`, you must manually call `-refresh` on the Realm to + update it to get the latest data. + + Note that by default, background threads do not have an active run loop and you + will need to manually call `-refresh` in order to update to the latest version, + even if `autorefresh` is set to `YES`. + + Even with this property enabled, you can still call `-refresh` at any time to + update the Realm before the automatic refresh would occur. + + Write transactions will still always advance a Realm to the latest version and + produce local notifications on commit even if autorefresh is disabled. + + Disabling `autorefresh` on a Realm without any strong references to it will not + have any effect, and `autorefresh` will revert back to `YES` the next time the + Realm is created. This is normally irrelevant as it means that there is nothing + to refresh (as managed `RLMObject`s, `RLMArray`s, and `RLMResults` have strong + references to the Realm that manages them), but it means that setting + `RLMRealm.defaultRealm.autorefresh = NO` in + `application:didFinishLaunchingWithOptions:` and only later storing Realm + objects will not work. + + Defaults to `YES`. + */ +@property (nonatomic) BOOL autorefresh; + +/** + Invalidates all `RLMObject`s, `RLMResults`, `RLMLinkingObjects`, and `RLMArray`s managed by the Realm. + + A Realm holds a read lock on the version of the data accessed by it, so + that changes made to the Realm on different threads do not modify or delete the + data seen by this Realm. Calling this method releases the read lock, + allowing the space used on disk to be reused by later write transactions rather + than growing the file. This method should be called before performing long + blocking operations on a background thread on which you previously read data + from the Realm which you no longer need. + + All `RLMObject`, `RLMResults` and `RLMArray` instances obtained from this + `RLMRealm` instance on the current thread are invalidated. `RLMObject`s and `RLMArray`s + cannot be used. `RLMResults` will become empty. The Realm itself remains valid, + and a new read transaction is implicitly begun the next time data is read from the Realm. + + Calling this method multiple times in a row without reading any data from the + Realm, or before ever reading any data from the Realm, is a no-op. This method + may not be called on a read-only Realm. + */ +- (void)invalidate; + +#pragma mark - Accessing Objects + +/** + Returns the same object as the one referenced when the `RLMThreadSafeReference` was first created, + but resolved for the current Realm for this thread. Returns `nil` if this object was deleted after + the reference was created. + + @param reference The thread-safe reference to the thread-confined object to resolve in this Realm. + + @warning A `RLMThreadSafeReference` object must be resolved at most once. + Failing to resolve a `RLMThreadSafeReference` will result in the source version of the + Realm being pinned until the reference is deallocated. + An exception will be thrown if a reference is resolved more than once. + + @warning Cannot call within a write transaction. + + @note Will refresh this Realm if the source Realm was at a later version than this one. + + @see `+[RLMThreadSafeReference referenceWithThreadConfined:]` + */ +- (nullable id)resolveThreadSafeReference:(RLMThreadSafeReference *)reference +NS_REFINED_FOR_SWIFT; + +#pragma mark - Adding and Removing Objects from a Realm + +/** + Adds an object to the Realm. + + Once added, this object is considered to be managed by the Realm. It can be retrieved + using the `objectsWhere:` selectors on `RLMRealm` and on subclasses of `RLMObject`. + + When added, all child relationships referenced by this object will also be added to + the Realm if they are not already in it. + + If the object or any related objects are already being managed by a different Realm + an exception will be thrown. Use `-[RLMObject createInRealm:withObject:]` to insert a copy of a managed object + into a different Realm. + + The object to be added must be valid and cannot have been previously deleted + from a Realm (i.e. `isInvalidated` must be `NO`). + + @warning This method may only be called during a write transaction. + + @param object The object to be added to this Realm. + */ +- (void)addObject:(RLMObject *)object; + +/** + Adds all the objects in a collection to the Realm. + + This is the equivalent of calling `addObject:` for every object in a collection. + + @warning This method may only be called during a write transaction. + + @param objects An enumerable collection such as `NSArray`, `RLMArray`, or `RLMResults`, + containing Realm objects to be added to the Realm. + + @see `addObject:` + */ +- (void)addObjects:(id)objects; + +/** + Adds or updates an existing object into the Realm. + + The object provided must have a designated primary key. If no objects exist in the Realm + with the same primary key value, the object is inserted. Otherwise, the existing object is + updated with any changed values. + + As with `addObject:`, the object cannot already be managed by a different + Realm. Use `-[RLMObject createOrUpdateInRealm:withValue:]` to copy values to + a different Realm. + + If there is a property or KVC value on `object` whose value is nil, and it corresponds + to a nullable property on an existing object being updated, that nullable property will + be set to nil. + + @warning This method may only be called during a write transaction. + + @param object The object to be added or updated. + */ +- (void)addOrUpdateObject:(RLMObject *)object; + +/** + Adds or updates all the objects in a collection into the Realm. + + This is the equivalent of calling `addOrUpdateObject:` for every object in a collection. + + @warning This method may only be called during a write transaction. + + @param objects An enumerable collection such as `NSArray`, `RLMArray`, or `RLMResults`, + containing Realm objects to be added to or updated within the Realm. + + @see `addOrUpdateObject:` + */ +- (void)addOrUpdateObjects:(id)objects; + +/** + Deletes an object from the Realm. Once the object is deleted it is considered invalidated. + + @warning This method may only be called during a write transaction. + + @param object The object to be deleted. + */ +- (void)deleteObject:(RLMObject *)object; + +/** + Deletes one or more objects from the Realm. + + This is the equivalent of calling `deleteObject:` for every object in a collection. + + @warning This method may only be called during a write transaction. + + @param objects An enumerable collection such as `NSArray`, `RLMArray`, or `RLMResults`, + containing objects to be deleted from the Realm. + + @see `deleteObject:` + */ +- (void)deleteObjects:(id)objects; + +/** + Deletes all objects from the Realm. + + @warning This method may only be called during a write transaction. + + @see `deleteObject:` + */ +- (void)deleteAllObjects; + + +#pragma mark - Migrations + +/** + The type of a migration block used to migrate a Realm. + + @param migration A `RLMMigration` object used to perform the migration. The + migration object allows you to enumerate and alter any + existing objects which require migration. + + @param oldSchemaVersion The schema version of the Realm being migrated. + */ +typedef void (^RLMMigrationBlock)(RLMMigration *migration, uint64_t oldSchemaVersion); + +/** + Returns the schema version for a Realm at a given local URL. + + @param fileURL Local URL to a Realm file. + @param key 64-byte key used to encrypt the file, or `nil` if it is unencrypted. + @param error If an error occurs, upon return contains an `NSError` object + that describes the problem. If you are not interested in + possible errors, pass in `NULL`. + + @return The version of the Realm at `fileURL`, or `RLMNotVersioned` if the version cannot be read. + */ ++ (uint64_t)schemaVersionAtURL:(NSURL *)fileURL encryptionKey:(nullable NSData *)key error:(NSError **)error +NS_REFINED_FOR_SWIFT; + +/** + Performs the given Realm configuration's migration block on a Realm at the given path. + + This method is called automatically when opening a Realm for the first time and does + not need to be called explicitly. You can choose to call this method to control + exactly when and how migrations are performed. + + @param configuration The Realm configuration used to open and migrate the Realm. + @return The error that occurred while applying the migration, if any. + + @see RLMMigration + */ ++ (BOOL)performMigrationForConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error; + +#pragma mark - Privileges + +/** + Returns the computed privileges which the current user has for this Realm. + + This combines all privileges granted on the Realm by all Roles which the + current User is a member of into the final privileges which will be enforced by + the server. + + The privilege calculation is done locally using cached data, and inherently may + be stale. It is possible that this method may indicate that an operation is + permitted but the server will still reject it if permission is revoked before + the changes have been integrated on the server. + + Non-synchronized Realms always have permission to perform all operations. + + @warning This currently returns incorrect results for non-partially-synchronized read-only Realms. + @return The privileges which the current user has for the current Realm. + */ +- (struct RLMRealmPrivileges)privilegesForRealm; + +/** + Returns the computed privileges which the current user has for the given object. + + This combines all privileges granted on the object by all Roles which the + current User is a member of into the final privileges which will be enforced by + the server. + + The privilege calculation is done locally using cached data, and inherently may + be stale. It is possible that this method may indicate that an operation is + permitted but the server will still reject it if permission is revoked before + the changes have been integrated on the server. + + Non-synchronized Realms always have permission to perform all operations. + + The object must be a valid object managed by this Realm. Passing in an + invalidated object, an unmanaged object, or an object managed by a different + Realm will throw an exception. + + @warning This currently returns incorrect results for non-partially-synchronized read-only Realms. + @return The privileges which the current user has for the given object. + */ +- (struct RLMObjectPrivileges)privilegesForObject:(RLMObject *)object; + +/** + Returns the computed privileges which the current user has for the given class. + + This combines all privileges granted on the class by all Roles which the + current User is a member of into the final privileges which will be enforced by + the server. + + The privilege calculation is done locally using cached data, and inherently may + be stale. It is possible that this method may indicate that an operation is + permitted but the server will still reject it if permission is revoked before + the changes have been integrated on the server. + + Non-synchronized Realms always have permission to perform all operations. + + @warning This currently returns incorrect results for non-partially-synchronized read-only Realms. + @return The privileges which the current user has for the given object. + */ +- (struct RLMClassPrivileges)privilegesForClass:(Class)cls; + +/** + Returns the computed privileges which the current user has for the named class. + + This combines all privileges granted on the class by all Roles which the + current User is a member of into the final privileges which will be enforced by + the server. + + The privilege calculation is done locally using cached data, and inherently may + be stale. It is possible that this method may indicate that an operation is + permitted but the server will still reject it if permission is revoked before + the changes have been integrated on the server. + + Non-synchronized Realms always have permission to perform all operations. + + @warning This currently returns incorrect results for non-partially-synchronized read-only Realms. + @return The privileges which the current user has for the given object. + */ +- (struct RLMClassPrivileges)privilegesForClassNamed:(NSString *)className; + +#pragma mark - Unavailable Methods + +/** + RLMRealm instances are cached internally by Realm and cannot be created directly. + + Use `+[RLMRealm defaultRealm]`, `+[RLMRealm realmWithConfiguration:error:]` or + `+[RLMRealm realmWithURL]` to obtain a reference to an RLMRealm. + */ +- (instancetype)init __attribute__((unavailable("Use +defaultRealm, +realmWithConfiguration: or +realmWithURL:."))); + +/** + RLMRealm instances are cached internally by Realm and cannot be created directly. + + Use `+[RLMRealm defaultRealm]`, `+[RLMRealm realmWithConfiguration:error:]` or + `+[RLMRealm realmWithURL]` to obtain a reference to an RLMRealm. + */ ++ (instancetype)new __attribute__((unavailable("Use +defaultRealm, +realmWithConfiguration: or +realmWithURL:."))); + +/// :nodoc: +- (void)addOrUpdateObjectsFromArray:(id)array __attribute__((unavailable("Renamed to -addOrUpdateObjects:."))); + +@end + +// MARK: - RLMNotificationToken + +/** + A token which is returned from methods which subscribe to changes to a Realm. + + Change subscriptions in Realm return an `RLMNotificationToken` instance, + which can be used to unsubscribe from the changes. You must store a strong + reference to the token for as long as you want to continue to receive notifications. + When you wish to stop, call the `-invalidate` method. Notifications are also stopped if + the token is deallocated. + */ +@interface RLMNotificationToken : NSObject +/// Stops notifications for the change subscription that returned this token. +- (void)invalidate; + +/// Stops notifications for the change subscription that returned this token. +- (void)stop __attribute__((unavailable("Renamed to -invalidate."))) NS_REFINED_FOR_SWIFT; +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMRealmConfiguration+Sync.h b/!main project/Pods/Realm/include/RLMRealmConfiguration+Sync.h new file mode 100644 index 0000000..abd51bf --- /dev/null +++ b/!main project/Pods/Realm/include/RLMRealmConfiguration+Sync.h @@ -0,0 +1,43 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +#import "RLMSyncUtil.h" + +@class RLMSyncConfiguration; + +/// Realm configuration options related to Sync. +@interface RLMRealmConfiguration (Sync) + +NS_ASSUME_NONNULL_BEGIN + +/** + A configuration object representing configuration state for Realms intended + to sync with a Realm Object Server. + + This property is mutually exclusive with both `inMemoryIdentifier` and `fileURL`; + setting any one of the three properties will automatically nil out the other two. + + @see `RLMSyncConfiguration` + */ +@property (nullable, nonatomic) RLMSyncConfiguration *syncConfiguration; + +NS_ASSUME_NONNULL_END + +@end diff --git a/!main project/Pods/Realm/include/RLMRealmConfiguration.h b/!main project/Pods/Realm/include/RLMRealmConfiguration.h new file mode 100644 index 0000000..3ec8fd5 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMRealmConfiguration.h @@ -0,0 +1,123 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + A block called when opening a Realm for the first time during the life + of a process to determine if it should be compacted before being returned + to the user. It is passed the total file size (data + free space) and the total + bytes used by data in the file. + + Return `YES` to indicate that an attempt to compact the file should be made. + The compaction will be skipped if another process is accessing it. + */ +typedef BOOL (^RLMShouldCompactOnLaunchBlock)(NSUInteger totalBytes, NSUInteger bytesUsed); + +/** + An `RLMRealmConfiguration` instance describes the different options used to + create an instance of a Realm. + + `RLMRealmConfiguration` instances are just plain `NSObject`s. Unlike `RLMRealm`s + and `RLMObject`s, they can be freely shared between threads as long as you do not + mutate them. + + Creating configuration objects for class subsets (by setting the + `objectClasses` property) can be expensive. Because of this, you will normally want to + cache and reuse a single configuration object for each distinct configuration rather than + creating a new object each time you open a Realm. + */ +@interface RLMRealmConfiguration : NSObject + +#pragma mark - Default Configuration + +/** + Returns the default configuration used to create Realms when no other + configuration is explicitly specified (i.e. `+[RLMRealm defaultRealm]`). + + @return The default Realm configuration. + */ ++ (instancetype)defaultConfiguration; + +/** + Sets the default configuration to the given `RLMRealmConfiguration`. + + @param configuration The new default Realm configuration. + */ ++ (void)setDefaultConfiguration:(RLMRealmConfiguration *)configuration; + +#pragma mark - Properties + +/// The local URL of the Realm file. Mutually exclusive with `inMemoryIdentifier` and `syncConfiguration`; +/// setting any one of the three properties will automatically nil out the other two. +@property (nonatomic, copy, nullable) NSURL *fileURL; + +/// A string used to identify a particular in-memory Realm. Mutually exclusive with `fileURL` and `syncConfiguration`; +/// setting any one of the three properties will automatically nil out the other two. +@property (nonatomic, copy, nullable) NSString *inMemoryIdentifier; + +/// A 64-byte key to use to encrypt the data, or `nil` if encryption is not enabled. +@property (nonatomic, copy, nullable) NSData *encryptionKey; + +/// Whether to open the Realm in read-only mode. +/// +/// This is required to be able to open Realm files which are not writeable or +/// are in a directory which is not writeable. This should only be used on files +/// which will not be modified by anyone while they are open, and not just to +/// get a read-only view of a file which may be written to by another thread or +/// process. Opening in read-only mode requires disabling Realm's reader/writer +/// coordination, so committing a write transaction from another process will +/// result in crashes. +@property (nonatomic) BOOL readOnly; + +/// The current schema version. +@property (nonatomic) uint64_t schemaVersion; + +/// The block which migrates the Realm to the current version. +@property (nonatomic, copy, nullable) RLMMigrationBlock migrationBlock; + +/** + Whether to recreate the Realm file with the provided schema if a migration is required. + This is the case when the stored schema differs from the provided schema or + the stored schema version differs from the version on this configuration. + Setting this property to `YES` deletes the file if a migration would otherwise be required or executed. + + @note Setting this property to `YES` doesn't disable file format migrations. + */ +@property (nonatomic) BOOL deleteRealmIfMigrationNeeded; + +/** + A block called when opening a Realm for the first time during the life + of a process to determine if it should be compacted before being returned + to the user. It is passed the total file size (data + free space) and the total + bytes used by data in the file. + + Return `YES` to indicate that an attempt to compact the file should be made. + The compaction will be skipped if another process is accessing it. + */ +@property (nonatomic, copy, nullable) RLMShouldCompactOnLaunchBlock shouldCompactOnLaunch; + +/// The classes managed by the Realm. +@property (nonatomic, copy, nullable) NSArray *objectClasses; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMRealmConfiguration_Private.h b/!main project/Pods/Realm/include/RLMRealmConfiguration_Private.h new file mode 100644 index 0000000..b3e4784 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMRealmConfiguration_Private.h @@ -0,0 +1,45 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +@class RLMSchema; + +NS_ASSUME_NONNULL_BEGIN + +@interface RLMRealmConfiguration () + +@property (nonatomic, readwrite) bool cache; +@property (nonatomic, readwrite) bool dynamic; +@property (nonatomic, readwrite) bool disableFormatUpgrade; +@property (nonatomic, copy, nullable) RLMSchema *customSchema; +@property (nonatomic, copy) NSString *pathOnDisk; + +// Get the default confiugration without copying it ++ (RLMRealmConfiguration *)rawDefaultConfiguration; + ++ (void)resetRealmConfigurationState; + +- (void)setCustomSchemaWithoutCopying:(nullable RLMSchema *)schema; +@end + +// Get a path in the platform-appropriate documents directory with the given filename +FOUNDATION_EXTERN NSString *RLMRealmPathForFile(NSString *fileName); +FOUNDATION_EXTERN NSString *RLMRealmPathForFileAndBundleIdentifier(NSString *fileName, NSString *mainBundleIdentifier); + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMRealmConfiguration_Private.hpp b/!main project/Pods/Realm/include/RLMRealmConfiguration_Private.hpp new file mode 100644 index 0000000..a89fb0f --- /dev/null +++ b/!main project/Pods/Realm/include/RLMRealmConfiguration_Private.hpp @@ -0,0 +1,26 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMRealmConfiguration_Private.h" +#import "shared_realm.hpp" + +@interface RLMRealmConfiguration () +- (realm::Realm::Config&)config; + +@property (nonatomic) realm::SchemaMode schemaMode; +@end diff --git a/!main project/Pods/Realm/include/RLMRealmUtil.hpp b/!main project/Pods/Realm/include/RLMRealmUtil.hpp new file mode 100644 index 0000000..4960b9c --- /dev/null +++ b/!main project/Pods/Realm/include/RLMRealmUtil.hpp @@ -0,0 +1,39 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import +#import + +@class RLMRealm; + +namespace realm { + class BindingContext; +} + +// Add a Realm to the weak cache +void RLMCacheRealm(std::string const& path, RLMRealm *realm); +// Get a Realm for the given path which can be used on the current thread +RLMRealm *RLMGetThreadLocalCachedRealmForPath(std::string const& path); +// Get a Realm for the given path +RLMRealm *RLMGetAnyCachedRealmForPath(std::string const& path); +// Clear the weak cache of Realms +void RLMClearRealmCache(); +// Check if the current thread is currently within a running CFRunLoop +bool RLMIsInRunLoop(); + +std::unique_ptr RLMCreateBindingContext(RLMRealm *realm); diff --git a/!main project/Pods/Realm/include/RLMRealm_Dynamic.h b/!main project/Pods/Realm/include/RLMRealm_Dynamic.h new file mode 100644 index 0000000..f796ed3 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMRealm_Dynamic.h @@ -0,0 +1,118 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +#import +#import + +@class RLMResults; + +NS_ASSUME_NONNULL_BEGIN + +@interface RLMRealm (Dynamic) + +#pragma mark - Getting Objects from a Realm + +/** + Returns all objects of a given type from the Realm. + + @warning This method is useful only in specialized circumstances, for example, when building components + that integrate with Realm. The preferred way to get objects of a single class is to use the class + methods on `RLMObject`. + + @param className The name of the `RLMObject` subclass to retrieve on (e.g. `MyClass.className`). + + @return An `RLMResults` containing all objects in the Realm of the given type. + + @see `+[RLMObject allObjects]` + */ +- (RLMResults *)allObjects:(NSString *)className; + +/** + Returns all objects matching the given predicate from the Realm. + + @warning This method is useful only in specialized circumstances, for example, when building components + that integrate with Realm. The preferred way to get objects of a single class is to use the class + methods on `RLMObject`. + + @param className The type of objects you are looking for (name of the class). + @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. + + @return An `RLMResults` containing results matching the given predicate. + + @see `+[RLMObject objectsWhere:]` + */ +- (RLMResults *)objects:(NSString *)className where:(NSString *)predicateFormat, ...; + +/** + Returns all objects matching the given predicate from the Realm. + + @warning This method is useful only in specialized circumstances, for example, when building components + that integrate with Realm. The preferred way to get objects of a single class is to use the class + methods on `RLMObject`. + + @param className The type of objects you are looking for (name of the class). + @param predicate The predicate with which to filter the objects. + + @return An `RLMResults` containing results matching the given predicate. + + @see `+[RLMObject objectsWhere:]` + */ +- (RLMResults *)objects:(NSString *)className withPredicate:(NSPredicate *)predicate; + +/** + Returns the object of the given type with the given primary key from the Realm. + + @warning This method is useful only in specialized circumstances, for example, when building components + that integrate with Realm. The preferred way to get an object of a single class is to use the class + methods on `RLMObject`. + + @param className The class name for the object you are looking for. + @param primaryKey The primary key value for the object you are looking for. + + @return An object, or `nil` if an object with the given primary key does not exist. + + @see `+[RLMObject objectForPrimaryKey:]` + */ +- (nullable RLMObject *)objectWithClassName:(NSString *)className forPrimaryKey:(id)primaryKey; + +/** + Creates an `RLMObject` instance of type `className` in the Realm, and populates it using a given object. + + The `value` argument is used to populate the object. It can be a key-value coding compliant object, an array or + dictionary returned from the methods in `NSJSONSerialization`, or an array containing one element for each managed + property. An exception will be thrown if any required properties are not present and those properties were not defined + with default values. + + When passing in an array as the `value` argument, all properties must be present, valid and in the same order as the + properties defined in the model. + + @warning This method is useful only in specialized circumstances, for example, when building components + that integrate with Realm. If you are simply building an app on Realm, it is recommended to + use `[RLMObject createInDefaultRealmWithValue:]`. + + @param value The value used to populate the object. + + @return An `RLMObject` instance of type `className`. + */ +-(RLMObject *)createObject:(NSString *)className withValue:(id)value; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMRealm_Private.h b/!main project/Pods/Realm/include/RLMRealm_Private.h new file mode 100644 index 0000000..ae13f1e --- /dev/null +++ b/!main project/Pods/Realm/include/RLMRealm_Private.h @@ -0,0 +1,57 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +@class RLMFastEnumerator, RLMSyncSubscription; + +NS_ASSUME_NONNULL_BEGIN + +// Disable syncing files to disk. Cannot be re-enabled. Use only for tests. +FOUNDATION_EXTERN void RLMDisableSyncToDisk(void); + +FOUNDATION_EXTERN NSData * _Nullable RLMRealmValidatedEncryptionKey(NSData *key); + +FOUNDATION_EXTERN RLMSyncSubscription *RLMCastToSyncSubscription(id obj); + +// Set the queue used for async open. For testing purposes only. +FOUNDATION_EXTERN void RLMSetAsyncOpenQueue(dispatch_queue_t queue); + +// Translate an in-flight exception resulting from an operation on a SharedGroup to +// an NSError or NSException (if error is nil) +void RLMRealmTranslateException(NSError **error); + +// RLMRealm private members +@interface RLMRealm () + +@property (nonatomic, readonly) BOOL dynamic; +@property (nonatomic, readwrite) RLMSchema *schema; + ++ (void)resetRealmState; + +- (void)registerEnumerator:(RLMFastEnumerator *)enumerator; +- (void)unregisterEnumerator:(RLMFastEnumerator *)enumerator; +- (void)detachAllEnumerators; + +- (void)sendNotifications:(RLMNotification)notification; +- (void)verifyThread; +- (void)verifyNotificationsAreSupported:(bool)isCollection; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMRealm_Private.hpp b/!main project/Pods/Realm/include/RLMRealm_Private.hpp new file mode 100644 index 0000000..9b596cd --- /dev/null +++ b/!main project/Pods/Realm/include/RLMRealm_Private.hpp @@ -0,0 +1,46 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMRealm_Private.h" + +#import "RLMClassInfo.hpp" +#import "object_schema.hpp" + +namespace realm { + class Group; + class Realm; +} +struct RLMResultsSetInfo { + realm::ObjectSchema osObjectSchema; + RLMObjectSchema *rlmObjectSchema; + RLMClassInfo info; + + RLMResultsSetInfo(__unsafe_unretained RLMRealm *const realm); + static RLMClassInfo& get(__unsafe_unretained RLMRealm *const realm); +}; + +@interface RLMRealm () { + @public + std::shared_ptr _realm; + RLMSchemaInfo _info; + std::unique_ptr _resultsSetInfo; +} + +// FIXME - group should not be exposed +@property (nonatomic, readonly) realm::Group &group; +@end diff --git a/!main project/Pods/Realm/include/RLMResults.h b/!main project/Pods/Realm/include/RLMResults.h new file mode 100644 index 0000000..d8d1cf6 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMResults.h @@ -0,0 +1,351 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RLMObject; + +/** + `RLMResults` is an auto-updating container type in Realm returned from object + queries. It represents the results of the query in the form of a collection of objects. + + `RLMResults` can be queried using the same predicates as `RLMObject` and `RLMArray`, + and you can chain queries to further filter results. + + `RLMResults` always reflect the current state of the Realm on the current thread, + including during write transactions on the current thread. The one exception to + this is when using `for...in` fast enumeration, which will always enumerate + over the objects which matched the query when the enumeration is begun, even if + some of them are deleted or modified to be excluded by the filter during the + enumeration. + + `RLMResults` are lazily evaluated the first time they are accessed; they only + run queries when the result of the query is requested. This means that + chaining several temporary `RLMResults` to sort and filter your data does not + perform any extra work processing the intermediate state. + + Once the results have been evaluated or a notification block has been added, + the results are eagerly kept up-to-date, with the work done to keep them + up-to-date done on a background thread whenever possible. + + `RLMResults` cannot be directly instantiated. + */ +@interface RLMResults : NSObject + +#pragma mark - Properties + +/** + The number of objects in the results collection. + */ +@property (nonatomic, readonly, assign) NSUInteger count; + +/** + The type of the objects in the results collection. + */ +@property (nonatomic, readonly, assign) RLMPropertyType type; + +/** + Indicates whether the objects in the collection can be `nil`. + */ +@property (nonatomic, readwrite, getter = isOptional) BOOL optional; + +/** + The class name of the objects contained in the results collection. + + Will be `nil` if `type` is not RLMPropertyTypeObject. + */ +@property (nonatomic, readonly, copy, nullable) NSString *objectClassName; + +/** + The Realm which manages this results collection. + */ +@property (nonatomic, readonly) RLMRealm *realm; + +/** + Indicates if the results collection is no longer valid. + + The results collection becomes invalid if `invalidate` is called on the containing `realm`. + An invalidated results collection can be accessed, but will always be empty. + */ +@property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated; + +#pragma mark - Accessing Objects from an RLMResults + +/** + Returns the object at the index specified. + + @param index The index to look up. + + @return An object of the type contained in the results collection. + */ +- (RLMObjectType)objectAtIndex:(NSUInteger)index; + +/** + Returns the first object in the results collection. + + Returns `nil` if called on an empty results collection. + + @return An object of the type contained in the results collection. + */ +- (nullable RLMObjectType)firstObject; + +/** + Returns the last object in the results collection. + + Returns `nil` if called on an empty results collection. + + @return An object of the type contained in the results collection. + */ +- (nullable RLMObjectType)lastObject; + +#pragma mark - Querying Results + +/** + Returns the index of an object in the results collection. + + Returns `NSNotFound` if the object is not found in the results collection. + + @param object An object (of the same type as returned from the `objectClassName` selector). + */ +- (NSUInteger)indexOfObject:(RLMObjectType)object; + +/** + Returns the index of the first object in the results collection matching the predicate. + + @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. + + @return The index of the object, or `NSNotFound` if the object is not found in the results collection. + */ +- (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat, ...; + +/// :nodoc: +- (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat args:(va_list)args; + +/** + Returns the index of the first object in the results collection matching the predicate. + + @param predicate The predicate with which to filter the objects. + + @return The index of the object, or `NSNotFound` if the object is not found in the results collection. + */ +- (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate; + +/** + Returns all the objects matching the given predicate in the results collection. + + @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. + + @return An `RLMResults` of objects that match the given predicate. + */ +- (RLMResults *)objectsWhere:(NSString *)predicateFormat, ...; + +/// :nodoc: +- (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args; + +/** + Returns all the objects matching the given predicate in the results collection. + + @param predicate The predicate with which to filter the objects. + + @return An `RLMResults` of objects that match the given predicate. + */ +- (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate; + +/** + Returns a sorted `RLMResults` from an existing results collection. + + @param keyPath The key path to sort by. + @param ascending The direction to sort in. + + @return An `RLMResults` sorted by the specified key path. + */ +- (RLMResults *)sortedResultsUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending; + +/** + Returns a sorted `RLMResults` from an existing results collection. + + @param properties An array of `RLMSortDescriptor`s to sort by. + + @return An `RLMResults` sorted by the specified properties. + */ +- (RLMResults *)sortedResultsUsingDescriptors:(NSArray *)properties; + +/** + Returns a distinct `RLMResults` from an existing results collection. + + @param keyPaths The key paths used produce distinct results + + @return An `RLMResults` made distinct based on the specified key paths + */ +- (RLMResults *)distinctResultsUsingKeyPaths:(NSArray *)keyPaths; + +#pragma mark - Notifications + +/** + Registers a block to be called each time the results collection changes. + + The block will be asynchronously called with the initial results collection, + and then called again after each write transaction which changes either any + of the objects in the results, or which objects are in the results. + + The `change` parameter will be `nil` the first time the block is called. + For each call after that, it will contain information about + which rows in the results collection were added, removed or modified. If a + write transaction did not modify any objects in the results collection, + the block is not called at all. See the `RLMCollectionChange` documentation for + information on how the changes are reported and an example of updating a + `UITableView`. + + If an error occurs the block will be called with `nil` for the results + parameter and a non-`nil` error. Currently the only errors that can occur are + when opening the Realm on the background worker thread. + + At the time when the block is called, the `RLMResults` object will be fully + evaluated and up-to-date, and as long as you do not perform a write transaction + on the same thread or explicitly call `-[RLMRealm refresh]`, accessing it will + never perform blocking work. + + Notifications are delivered via the standard run loop, and so can't be + delivered while the run loop is blocked by other activity. When + notifications can't be delivered instantly, multiple notifications may be + coalesced into a single notification. This can include the notification + with the initial results. For example, the following code performs a write + transaction immediately after adding the notification block, so there is no + opportunity for the initial notification to be delivered first. As a + result, the initial notification will reflect the state of the Realm after + the write transaction. + + RLMResults *results = [Dog allObjects]; + NSLog(@"dogs.count: %zu", dogs.count); // => 0 + self.token = [results addNotificationBlock:^(RLMResults *dogs, + RLMCollectionChange *changes, + NSError *error) { + // Only fired once for the example + NSLog(@"dogs.count: %zu", dogs.count); // => 1 + }]; + [realm transactionWithBlock:^{ + Dog *dog = [[Dog alloc] init]; + dog.name = @"Rex"; + [realm addObject:dog]; + }]; + // end of run loop execution context + + You must retain the returned token for as long as you want updates to continue + to be sent to the block. To stop receiving updates, call `-invalidate` on the token. + + @warning This method cannot be called during a write transaction, or when the + containing Realm is read-only. + + @param block The block to be called whenever a change occurs. + @return A token which must be held for as long as you want updates to be delivered. + */ +- (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMResults *__nullable results, + RLMCollectionChange *__nullable change, + NSError *__nullable error))block __attribute__((warn_unused_result)); + +#pragma mark - Aggregating Property Values + +/** + Returns the minimum (lowest) value of the given property among all the objects + represented by the results collection. + + NSNumber *min = [results minOfProperty:@"age"]; + + @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. + + @param property The property whose minimum value is desired. Only properties of types `int`, `float`, `double`, and + `NSDate` are supported. + + @return The minimum value of the property, or `nil` if the Results are empty. + */ +- (nullable id)minOfProperty:(NSString *)property; + +/** + Returns the maximum (highest) value of the given property among all the objects represented by the results collection. + + NSNumber *max = [results maxOfProperty:@"age"]; + + @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. + + @param property The property whose maximum value is desired. Only properties of + types `int`, `float`, `double`, and `NSDate` are supported. + + @return The maximum value of the property, or `nil` if the Results are empty. + */ +- (nullable id)maxOfProperty:(NSString *)property; + +/** + Returns the sum of the values of a given property over all the objects represented by the results collection. + + NSNumber *sum = [results sumOfProperty:@"age"]; + + @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. + + @param property The property whose values should be summed. Only properties of + types `int`, `float`, and `double` are supported. + + @return The sum of the given property. + */ +- (NSNumber *)sumOfProperty:(NSString *)property; + +/** + Returns the average value of a given property over the objects represented by the results collection. + + NSNumber *average = [results averageOfProperty:@"age"]; + + @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. + + @param property The property whose average value should be calculated. Only + properties of types `int`, `float`, and `double` are supported. + + @return The average value of the given property, or `nil` if the Results are empty. + */ +- (nullable NSNumber *)averageOfProperty:(NSString *)property; + +/// :nodoc: +- (RLMObjectType)objectAtIndexedSubscript:(NSUInteger)index; + +#pragma mark - Unavailable Methods + +/** + `-[RLMResults init]` is not available because `RLMResults` cannot be created directly. + `RLMResults` can be obtained by querying a Realm. + */ +- (instancetype)init __attribute__((unavailable("RLMResults cannot be created directly"))); + +/** + `+[RLMResults new]` is not available because `RLMResults` cannot be created directly. + `RLMResults` can be obtained by querying a Realm. + */ ++ (instancetype)new __attribute__((unavailable("RLMResults cannot be created directly"))); + +@end + +/** + `RLMLinkingObjects` is an auto-updating container type. It represents a collection of objects that link to its + parent object. + + For more information, please see the "Inverse Relationships" section in the + [documentation](https://realm.io/docs/objc/latest/#relationships). + */ +@interface RLMLinkingObjects : RLMResults +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMResults_Private.h b/!main project/Pods/Realm/include/RLMResults_Private.h new file mode 100644 index 0000000..f74b4fd --- /dev/null +++ b/!main project/Pods/Realm/include/RLMResults_Private.h @@ -0,0 +1,32 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +@class RLMObjectSchema; + +NS_ASSUME_NONNULL_BEGIN + +@interface RLMResults () +@property (nonatomic, readonly, getter=isAttached) BOOL attached; + ++ (instancetype)emptyDetachedResults; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMResults_Private.hpp b/!main project/Pods/Realm/include/RLMResults_Private.hpp new file mode 100644 index 0000000..57edc23 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMResults_Private.hpp @@ -0,0 +1,66 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2017 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMResults_Private.h" + +#import "results.hpp" + +class RLMClassInfo; + +NS_ASSUME_NONNULL_BEGIN + +@interface RLMResults () { +@protected + realm::Results _results; +} + +/** + Initialize a 'raw' `RLMResults` using only an object store level Results. + This is only meant for applications where a results collection is being backed + by an object store object class that has no binding-level equivalent. The + consumer is responsible for bridging between the underlying objects and whatever + binding-level class is being vended out. + */ +- (instancetype)initWithResults:(realm::Results)results; + +- (instancetype)initWithObjectInfo:(RLMClassInfo&)info results:(realm::Results&&)results; ++ (instancetype)resultsWithObjectInfo:(RLMClassInfo&)info results:(realm::Results&&)results; + +- (instancetype)subresultsWithResults:(realm::Results)results; +@end + +NS_ASSUME_NONNULL_END + +// Utility functions + +[[gnu::noinline]] +[[noreturn]] +void RLMThrowResultsError(NSString * _Nullable aggregateMethod); + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnullability-completeness" +template +static auto translateRLMResultsErrors(Function&& f, NSString *aggregateMethod=nil) { + try { + return f(); + } + catch (...) { + RLMThrowResultsError(aggregateMethod); + } +} +#pragma clang diagnostic pop diff --git a/!main project/Pods/Realm/include/RLMSchema.h b/!main project/Pods/Realm/include/RLMSchema.h new file mode 100644 index 0000000..30325e4 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMSchema.h @@ -0,0 +1,77 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RLMObjectSchema; + +/** + `RLMSchema` instances represent collections of model object schemas managed by a Realm. + + When using Realm, `RLMSchema` instances allow performing migrations and + introspecting the database's schema. + + Schemas map to collections of tables in the core database. + */ +@interface RLMSchema : NSObject + +#pragma mark - Properties + +/** + An `NSArray` containing `RLMObjectSchema`s for all object types in the Realm. + + This property is intended to be used during migrations for dynamic introspection. + + @see `RLMObjectSchema` + */ +@property (nonatomic, readonly, copy) NSArray *objectSchema; + +#pragma mark - Methods + +/** + Returns an `RLMObjectSchema` for the given class name in the schema. + + @param className The object class name. + @return An `RLMObjectSchema` for the given class in the schema. + + @see `RLMObjectSchema` + */ +- (nullable RLMObjectSchema *)schemaForClassName:(NSString *)className; + +/** + Looks up and returns an `RLMObjectSchema` for the given class name in the Realm. + + If there is no object of type `className` in the schema, an exception will be thrown. + + @param className The object class name. + @return An `RLMObjectSchema` for the given class in this Realm. + + @see `RLMObjectSchema` + */ +- (RLMObjectSchema *)objectForKeyedSubscript:(NSString *)className; + +/** + Returns whether two `RLMSchema` instances are equivalent. + */ +- (BOOL)isEqualToSchema:(RLMSchema *)schema; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMSchema_Private.h b/!main project/Pods/Realm/include/RLMSchema_Private.h new file mode 100644 index 0000000..7ef4917 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMSchema_Private.h @@ -0,0 +1,58 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RLMRealm; + +// +// RLMSchema private interface +// +@interface RLMSchema () + +/** + Returns an `RLMSchema` containing only the given `RLMObject` subclasses. + + @param classes The classes to be included in the schema. + + @return An `RLMSchema` containing only the given classes. + */ ++ (instancetype)schemaWithObjectClasses:(NSArray *)classes; + +@property (nonatomic, readwrite, copy) NSArray *objectSchema; + +// schema based on runtime objects ++ (instancetype)sharedSchema; + +// schema based upon all currently registered object classes ++ (instancetype)partialSharedSchema; + +// private schema based upon all currently registered object classes. +// includes classes that are excluded from the default schema. ++ (instancetype)partialPrivateSharedSchema; + +// class for string ++ (nullable Class)classForString:(NSString *)className; + ++ (nullable RLMObjectSchema *)sharedSchemaForClass:(Class)cls; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMSchema_Private.hpp b/!main project/Pods/Realm/include/RLMSchema_Private.hpp new file mode 100644 index 0000000..197ddee --- /dev/null +++ b/!main project/Pods/Realm/include/RLMSchema_Private.hpp @@ -0,0 +1,31 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMSchema_Private.h" + +#import + +namespace realm { + class Schema; + class ObjectSchema; +} + +@interface RLMSchema () ++ (instancetype)dynamicSchemaFromObjectStoreSchema:(realm::Schema const&)objectStoreSchema; +- (realm::Schema)objectStoreCopy; +@end diff --git a/!main project/Pods/Realm/include/RLMSwiftBridgingHeader.h b/!main project/Pods/Realm/include/RLMSwiftBridgingHeader.h new file mode 100644 index 0000000..4758043 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMSwiftBridgingHeader.h @@ -0,0 +1,49 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import +#import + +@interface RLMRealm (Swift) ++ (void)resetRealmState; +@end + +@interface RLMArray (Swift) + +- (instancetype)initWithObjectClassName:(NSString *)objectClassName; + +- (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat args:(va_list)args; +- (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args; + +@end + +@interface RLMResults (Swift) + +- (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat args:(va_list)args; +- (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args; + +@end + +@interface RLMObjectBase (Swift) + +- (instancetype)initWithRealm:(RLMRealm *)realm schema:(RLMObjectSchema *)schema defaultValues:(BOOL)useDefaults; + ++ (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args; ++ (RLMResults *)objectsInRealm:(RLMRealm *)realm where:(NSString *)predicateFormat args:(va_list)args; + +@end diff --git a/!main project/Pods/Realm/include/RLMSwiftSupport.h b/!main project/Pods/Realm/include/RLMSwiftSupport.h new file mode 100644 index 0000000..6e45b65 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMSwiftSupport.h @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RLMSwiftSupport : NSObject + ++ (BOOL)isSwiftClassName:(NSString *)className; ++ (NSString *)demangleClassName:(NSString *)className; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMSyncConfiguration.h b/!main project/Pods/Realm/include/RLMSyncConfiguration.h new file mode 100644 index 0000000..5a74db2 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMSyncConfiguration.h @@ -0,0 +1,114 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +@class RLMRealmConfiguration; +@class RLMSyncUser; + +NS_ASSUME_NONNULL_BEGIN + +/** + A configuration object representing configuration state for a Realm which is intended to sync with a Realm Object + Server. + */ +@interface RLMSyncConfiguration : NSObject + +/// The user to which the remote Realm belongs. +@property (nonatomic, readonly) RLMSyncUser *user; + +/** + The URL of the remote Realm upon the Realm Object Server. + + @warning The URL cannot end with `.realm`, `.realm.lock` or `.realm.management`. + */ +@property (nonatomic, readonly) NSURL *realmURL; + +/** + A local path to a file containing the trust anchors for SSL connections. + + Only the certificates stored in the PEM file (or any certificates signed by it, + if the file contains a CA cert) will be accepted when initiating a connection + to a server. This prevents certain certain kinds of man-in-the-middle (MITM) + attacks, and can also be used to trust a self-signed certificate which would + otherwise be untrusted. + + On macOS, the file may be in any of the formats supported by SecItemImport(), + including PEM and .cer (see SecExternalFormat for a complete list of possible + formats). On iOS and other platforms, only DER .cer files are supported. + */ +@property (nonatomic, nullable) NSURL *pinnedCertificateURL; + +/** + Whether SSL certificate validation is enabled for the connection associated + with this configuration value. SSL certificate validation is ON by default. + + @warning NEVER disable certificate validation for clients and servers in production. + */ +@property (nonatomic) BOOL enableSSLValidation; + +/// :nodoc: +@property (nonatomic) BOOL isPartial __attribute__((unavailable("Use 'fullSynchronization' instead."))); + +/** + Whether this Realm should be a fully synchronized Realm. + + Synchronized Realms comes in two flavors: Query-based and Fully synchronized. + A fully synchronized Realm will automatically synchronize the entire Realm in + the background while a query-based Realm will only synchronize the data being + subscribed to. Synchronized realms are by default query-based unless this + boolean is set. + */ +@property (nonatomic) BOOL fullSynchronization; + +/** + The prefix that is prepended to the path in the HTTP request that initiates a + sync connection. The value specified must match with the server's expectation. + Changing the value of `urlPrefix` should be matched with a corresponding + change of the server's configuration. + If no value is specified here then the default `/realm-sync` path is used. +*/ +@property (nonatomic, nullable, copy) NSString *urlPrefix; + +/** + Whether nonfatal connection errors should cancel async opens. + + By default, if a nonfatal connection error such as a connection timing out occurs, any currently pending asyncOpen operations will ignore the error and continue to retry until it succeeds. If this is set to true, the open will instead fail and report the error. + + FIXME: This should probably be true by default in the next major version. + */ +@property (nonatomic) bool cancelAsyncOpenOnNonFatalErrors; + +/// :nodoc: +- (instancetype)initWithUser:(RLMSyncUser *)user realmURL:(NSURL *)url __attribute__((unavailable("Use [RLMSyncUser configurationWithURL:] instead"))); + +/// :nodoc: ++ (RLMRealmConfiguration *)automaticConfiguration __attribute__((unavailable("Use [RLMSyncUser configuration] instead"))); + +/// :nodoc: ++ (RLMRealmConfiguration *)automaticConfigurationForUser:(RLMSyncUser *)user __attribute__((unavailable("Use [RLMSyncUser configuration] instead"))); + +/// :nodoc: +- (instancetype)init __attribute__((unavailable("This type cannot be created directly"))); + +/// :nodoc: ++ (instancetype)new __attribute__((unavailable("This type cannot be created directly"))); + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMSyncConfiguration_Private.h b/!main project/Pods/Realm/include/RLMSyncConfiguration_Private.h new file mode 100644 index 0000000..07bf0a2 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMSyncConfiguration_Private.h @@ -0,0 +1,48 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef RLM_CLOSED_ENUM(NSUInteger, RLMSyncStopPolicy) { + RLMSyncStopPolicyImmediately, + RLMSyncStopPolicyLiveIndefinitely, + RLMSyncStopPolicyAfterChangesUploaded, +}; + +@interface RLMSyncConfiguration () + +- (instancetype)initWithUser:(RLMSyncUser *)user + realmURL:(NSURL *)url + isPartial:(BOOL)isPartial + urlPrefix:(nullable NSString *)urlPrefix + stopPolicy:(RLMSyncStopPolicy)stopPolicy + enableSSLValidation:(BOOL)enableSSLValidation + certificatePath:(nullable NSURL *)certificatePath; + +@property (nonatomic, readwrite) RLMSyncStopPolicy stopPolicy; + +// Internal-only APIs +@property (nullable, nonatomic) NSURL *customFileURL; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMSyncConfiguration_Private.hpp b/!main project/Pods/Realm/include/RLMSyncConfiguration_Private.hpp new file mode 100644 index 0000000..6314e4c --- /dev/null +++ b/!main project/Pods/Realm/include/RLMSyncConfiguration_Private.hpp @@ -0,0 +1,47 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMSyncConfiguration_Private.h" + +#import +#import + +namespace realm { +class SyncSession; +struct SyncConfig; +struct SyncError; +using SyncSessionErrorHandler = void(std::shared_ptr, SyncError); +} + +NS_ASSUME_NONNULL_BEGIN + +@interface RLMSyncConfiguration () + +- (instancetype)initWithUser:(RLMSyncUser *)user + realmURL:(NSURL *)url + customFileURL:(nullable NSURL *)customFileURL + isPartial:(BOOL)isPartial + stopPolicy:(RLMSyncStopPolicy)stopPolicy; + +- (instancetype)initWithRawConfig:(realm::SyncConfig)config; + +- (realm::SyncConfig&)rawConfiguration; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMSyncCredentials.h b/!main project/Pods/Realm/include/RLMSyncCredentials.h new file mode 100644 index 0000000..6c99ce5 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMSyncCredentials.h @@ -0,0 +1,158 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +#import "RLMSyncUtil.h" + +NS_ASSUME_NONNULL_BEGIN + +/// A token representing an identity provider's credentials. +typedef NSString *RLMSyncCredentialsToken; + +/// A type representing the unique identifier of a Realm Object Server identity provider. +typedef NSString *RLMIdentityProvider RLM_EXTENSIBLE_STRING_ENUM; + +/// The debug identity provider, which accepts any token string and creates a user associated with that token if one +/// does not yet exist. Not enabled for Realm Object Server configured for production. +extern RLMIdentityProvider const RLMIdentityProviderDebug; + +/// The username/password identity provider. User accounts are handled by the Realm Object Server directly without the +/// involvement of a third-party identity provider. +extern RLMIdentityProvider const RLMIdentityProviderUsernamePassword; + +/// A Facebook account as an identity provider. +extern RLMIdentityProvider const RLMIdentityProviderFacebook; + +/// A Google account as an identity provider. +extern RLMIdentityProvider const RLMIdentityProviderGoogle; + +/// A CloudKit account as an identity provider. +extern RLMIdentityProvider const RLMIdentityProviderCloudKit; + +/// A JSON Web Token as an identity provider. +extern RLMIdentityProvider const RLMIdentityProviderJWT; + +/// An Anonymous account as an identity provider. +extern RLMIdentityProvider const RLMIdentityProviderAnonymous; + +/// A Nickname account as an identity provider. +extern RLMIdentityProvider const RLMIdentityProviderNickname __deprecated_msg("Use RLMIdentityProviderUsernamePassword instead"); + +/** + Opaque credentials representing a specific Realm Object Server user. + */ +@interface RLMSyncCredentials : NSObject + +/// An opaque credentials token containing information that uniquely identifies a Realm Object Server user. +@property (nonatomic, readonly) RLMSyncCredentialsToken token; + +/// The name of the identity provider which generated the credentials token. +@property (nonatomic, readonly) RLMIdentityProvider provider; + +/// A dictionary containing additional pertinent information. In most cases this is automatically configured. +@property (nonatomic, readonly) NSDictionary *userInfo; + +/** + Construct and return credentials from a Facebook account token. + */ ++ (instancetype)credentialsWithFacebookToken:(RLMSyncCredentialsToken)token; + +/** + Construct and return credentials from a Google account token. + */ ++ (instancetype)credentialsWithGoogleToken:(RLMSyncCredentialsToken)token; + +/** + Construct and return credentials from an CloudKit account token. + */ ++ (instancetype)credentialsWithCloudKitToken:(RLMSyncCredentialsToken)token; + +/** + Construct and return credentials from a Realm Object Server username and password. + */ ++ (instancetype)credentialsWithUsername:(NSString *)username + password:(NSString *)password + register:(BOOL)shouldRegister; + +/** + Construct and return credentials from a JSON Web Token. + */ ++ (instancetype)credentialsWithJWT:(NSString *)token; + +/** + Construct and return anonymous credentials + */ ++ (instancetype)anonymousCredentials; + +/** + Construct and return credentials from a nickname + */ ++ (instancetype)credentialsWithNickname:(NSString *)nickname isAdmin:(BOOL)isAdmin __deprecated_msg("Use +credentialsWithUsername instead"); + +/** + Construct and return special credentials representing a token that can + be directly used to open a Realm. The identity is used to uniquely identify + the user across application launches. + + @warning The custom user identity will be deprecated in a future release. + + @warning Do not specify a user identity that is the URL of an authentication + server. + + @warning When passing an access token credential into any of `RLMSyncUser`'s + login methods, you must always specify the same authentication server + URL, or none at all, every time you call the login method. + */ ++ (instancetype)credentialsWithAccessToken:(RLMServerToken)accessToken identity:(NSString *)identity; + +/** + Construct and return special credentials representing a token issued by an external entity + that will be used instead of a ROS refresh token. + + @discussion Unlike other types of credentials, CustomRefreshToken credentials do not + participate in the regular login flow, because they take place of the refresh tokens login produces. + Instead, the token will be used to authorize each server operation such as opening a Realm or + editing permissions. When the token expires the SyncUser object will still be valid, but server operations will + fail until the token is updated. + + @remark ROS must be configured to accept the external issuing identity as a refresh token validator. + + @warning The values of @c identity and @c isAdmin are used for client-side validation only. + The server will compute their values based on the token string and the token validator configuration, + but it's important for correct functioning that the values here match the server. + */ ++ (instancetype)credentialsWithCustomRefreshToken:(NSString *)token identity:(NSString *)identity isAdmin:(BOOL)isAdmin; + +/** + Construct and return credentials with a custom token string, identity provider string, and optional user info. In most + cases, the convenience initializers should be used instead. + */ +- (instancetype)initWithCustomToken:(RLMSyncCredentialsToken)token + provider:(RLMIdentityProvider)provider + userInfo:(nullable NSDictionary *)userInfo NS_DESIGNATED_INITIALIZER; + +/// :nodoc: +- (instancetype)init __attribute__((unavailable("RLMSyncCredentials cannot be created directly"))); + +/// :nodoc: ++ (instancetype)new __attribute__((unavailable("RLMSyncCredentials cannot be created directly"))); + +NS_ASSUME_NONNULL_END + +@end diff --git a/!main project/Pods/Realm/include/RLMSyncManager.h b/!main project/Pods/Realm/include/RLMSyncManager.h new file mode 100644 index 0000000..debb5d4 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMSyncManager.h @@ -0,0 +1,245 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +#import "RLMSyncUtil.h" + +@class RLMSyncSession, RLMSyncTimeoutOptions; + +NS_ASSUME_NONNULL_BEGIN + +/// An enum representing different levels of sync-related logging that can be configured. +typedef RLM_CLOSED_ENUM(NSUInteger, RLMSyncLogLevel) { + /// Nothing will ever be logged. + RLMSyncLogLevelOff, + /// Only fatal errors will be logged. + RLMSyncLogLevelFatal, + /// Only errors will be logged. + RLMSyncLogLevelError, + /// Warnings and errors will be logged. + RLMSyncLogLevelWarn, + /// Information about sync events will be logged. Fewer events will be logged in order to avoid overhead. + RLMSyncLogLevelInfo, + /// Information about sync events will be logged. More events will be logged than with `RLMSyncLogLevelInfo`. + RLMSyncLogLevelDetail, + /// Log information that can aid in debugging. + /// + /// - warning: Will incur a measurable performance impact. + RLMSyncLogLevelDebug, + /// Log information that can aid in debugging. More events will be logged than with `RLMSyncLogLevelDebug`. + /// + /// - warning: Will incur a measurable performance impact. + RLMSyncLogLevelTrace, + /// Log information that can aid in debugging. More events will be logged than with `RLMSyncLogLevelTrace`. + /// + /// - warning: Will incur a measurable performance impact. + RLMSyncLogLevelAll +}; + +/// A log callback function which can be set on RLMSyncManager. +/// +/// The log function may be called from multiple threads simultaneously, and is +/// responsible for performing its own synchronization if any is required. +typedef void (*RLMSyncLogFunction)(RLMSyncLogLevel level, NSString *message); + +/// A block type representing a block which can be used to report a sync-related error to the application. If the error +/// pertains to a specific session, that session will also be passed into the block. +typedef void(^RLMSyncErrorReportingBlock)(NSError *, RLMSyncSession * _Nullable); + +/** + A singleton manager which serves as a central point for sync-related configuration. + */ +@interface RLMSyncManager : NSObject + +/** + A block which can optionally be set to report sync-related errors to your application. + + Any error reported through this block will be of the `RLMSyncError` type, and marked + with the `RLMSyncErrorDomain` domain. + + Errors reported through this mechanism are fatal, with several exceptions. Please consult + `RLMSyncError` for information about the types of errors that can be reported through + the block, and for for suggestions on handling recoverable error codes. + + @see `RLMSyncError` + */ +@property (nullable, nonatomic, copy) RLMSyncErrorReportingBlock errorHandler; + +/** + A reverse-DNS string uniquely identifying this application. In most cases this + is automatically set by the SDK, and does not have to be explicitly configured. + */ +@property (nonatomic, copy) NSString *appID; + +/** + A string identifying this application which is included in the User-Agent + header of sync connections. By default, this will be the application's bundle + identifier. + + This property must be set prior to opening a synchronized Realm for the first + time. Any modifications made after opening a Realm will be ignored. + */ +@property (nonatomic, copy) NSString *userAgent; + +/** + The logging threshold which newly opened synced Realms will use. Defaults to + `RLMSyncLogLevelInfo`. + + By default logging strings are output to Apple System Logger. Set `logger` to + perform custom logging logic instead. + + @warning This property must be set before any synced Realms are opened. Setting it after + opening any synced Realm will do nothing. + */ +@property (nonatomic) RLMSyncLogLevel logLevel; + +/** + The function which will be invoked whenever the sync client has a log message. + + If nil, log strings are output to Apple System Logger instead. + + @warning This property must be set before any synced Realms are opened. Setting + it after opening any synced Realm will do nothing. + */ +@property (nonatomic, nullable) RLMSyncLogFunction logger; + +/** + The name of the HTTP header to send authorization data in when making requests to a Realm Object Server which has + been configured to expect a custom authorization header. + */ +@property (nullable, nonatomic, copy) NSString *authorizationHeaderName; + +/** + Extra HTTP headers to append to every request to a Realm Object Server. + + Modifying this property while sync sessions are active will result in all + sessions disconnecting and reconnecting using the new headers. + */ +@property (nullable, nonatomic, copy) NSDictionary *customRequestHeaders; + +/** + A map of hostname to file URL for pinned certificates to use for HTTPS requests. + + When initiating a HTTPS connection to a server, if this dictionary contains an + entry for the server's hostname, only the certificates stored in the file (or + any certificates signed by it, if the file contains a CA cert) will be accepted + when initiating a connection to a server. This prevents certain certain kinds + of man-in-the-middle (MITM) attacks, and can also be used to trust a self-signed + certificate which would otherwise be untrusted. + + On macOS, the certificate files may be in any of the formats supported by + SecItemImport(), including PEM and .cer (see SecExternalFormat for a complete + list of possible formats). On iOS and other platforms, only DER .cer files are + supported. + + For example, to pin example.com to a .cer file included in your bundle: + +
+ RLMSyncManager.sharedManager.pinnedCertificatePaths = @{
+    @"example.com": [NSBundle.mainBundle pathForResource:@"example.com" ofType:@"cer"]
+ };
+ 
+ */ +@property (nullable, nonatomic, copy) NSDictionary *pinnedCertificatePaths; + +/** + Options for the assorted types of connection timeouts for sync connections. + + If nil default values for all timeouts are used instead. + + @warning This property must be set before any synced Realms are opened. Setting + it after opening any synced Realm will do nothing. + */ +@property (nullable, nonatomic, copy) RLMSyncTimeoutOptions *timeoutOptions; + +/// The sole instance of the singleton. ++ (instancetype)sharedManager NS_REFINED_FOR_SWIFT; + +/// :nodoc: +- (instancetype)init __attribute__((unavailable("RLMSyncManager cannot be created directly"))); + +/// :nodoc: ++ (instancetype)new __attribute__((unavailable("RLMSyncManager cannot be created directly"))); + +@end + +/** + Options for configuring timeouts and intervals in the sync client. + */ +@interface RLMSyncTimeoutOptions : NSObject +/// The maximum number of milliseconds to allow for a connection to +/// become fully established. This includes the time to resolve the +/// network address, the TCP connect operation, the SSL handshake, and +/// the WebSocket handshake. +/// +/// Defaults to 2 minutes. +@property (nonatomic) NSUInteger connectTimeout; + +/// The number of milliseconds to keep a connection open after all +/// sessions have been abandoned. +/// +/// After all synchronized Realms have been closed for a given server, the +/// connection is kept open until the linger time has expire to avoid the +/// overhead of reestablishing the connection when Realms are being closed and +/// reopened. +/// +/// Defaults to 30 seconds. +@property (nonatomic) NSUInteger connectionLingerTime; + +/// The number of milliseconds between each heartbeat ping message. +/// +/// The client periodically sends ping messages to the server to check if the +/// connection is still alive. Shorter periods make connection state change +/// notifications more responsive at the cost of battery life (as the antenna +/// will have to wake up more often). +/// +/// Defaults to 1 minute. +@property (nonatomic) NSUInteger pingKeepalivePeriod; + +/// How long in milliseconds to wait for a reponse to a heartbeat ping before +/// concluding that the connection has dropped. +/// +/// Shorter values will make connection state change notifications more +/// responsive as it will only change to `disconnected` after this much time has +/// elapsed, but overly short values may result in spurious disconnection +/// notifications when the server is simply taking a long time to respond. +/// +/// Defaults to 2 minutes. +@property (nonatomic) NSUInteger pongKeepaliveTimeout; + +/// The maximum amount of time, in milliseconds, since the loss of a +/// prior connection, for a new connection to be considered a "fast +/// reconnect". +/// +/// When a client first connects to the server, it defers uploading any local +/// changes until it has downloaded all changesets from the server. This +/// typically reduces the total amount of merging that has to be done, and is +/// particularly beneficial the first time that a specific client ever connects +/// to the server. +/// +/// When an existing client disconnects and then reconnects within the "fact +/// reconnect" time this is skipped and any local changes are uploaded +/// immediately without waiting for downloads, just as if the client was online +/// the whole time. +/// +/// Defaults to 1 minute. +@property (nonatomic) NSUInteger fastReconnectLimit; +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMSyncManager_Private.h b/!main project/Pods/Realm/include/RLMSyncManager_Private.h new file mode 100644 index 0000000..0284aee --- /dev/null +++ b/!main project/Pods/Realm/include/RLMSyncManager_Private.h @@ -0,0 +1,45 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +#import "RLMSyncUtil_Private.h" +#import "RLMNetworkClient.h" + +@class RLMSyncUser, RLMSyncConfiguration; + +// All private API methods are threadsafe and synchronized, unless denoted otherwise. Since they are expected to be +// called very infrequently, this should pose no issues. + +NS_ASSUME_NONNULL_BEGIN + +@interface RLMSyncManager () + +@property (nullable, nonatomic, copy) RLMSyncBasicErrorReportingBlock sessionCompletionNotifier; + +- (void)_fireError:(NSError *)error; + +- (NSArray *)_allUsers; + ++ (void)resetForTesting; + +- (RLMNetworkRequestOptions *)networkRequestOptions; + +NS_ASSUME_NONNULL_END + +@end diff --git a/!main project/Pods/Realm/include/RLMSyncPermission.h b/!main project/Pods/Realm/include/RLMSyncPermission.h new file mode 100644 index 0000000..96835cc --- /dev/null +++ b/!main project/Pods/Realm/include/RLMSyncPermission.h @@ -0,0 +1,527 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2017 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import +#import + +@protocol RLMPermission, RLMPermissionUser; +@class RLMPermission, RLMPermissionUser, RLMPermissionRole, + RLMArray, RLMLinkingObjects; + +NS_ASSUME_NONNULL_BEGIN + +/** + A permission which can be applied to a Realm, Class, or specific Object. + + Permissions are applied by adding the permission to the RLMRealmPermission singleton + object, the RLMClassPermission object for the desired class, or to a user-defined + RLMArray property on a specific Object instance. The meaning of each of + the properties of RLMPermission depend on what the permission is applied to, and so are + left undocumented here. See `RLMRealmPrivileges`, `RLMClassPrivileges`, and + `RLMObjectPrivileges` for details about what each of the properties mean when applied to + that type. + */ +@interface RLMPermission : RLMObject +/// The Role which this Permission applies to. All users within the Role are +/// granted the permissions specified by the fields below any +/// objects/classes/realms which use this Permission. +/// +/// This property cannot be modified once set. +@property (nonatomic) RLMPermissionRole *role; + +/// Whether the user can read the object to which this Permission is attached. +@property (nonatomic) bool canRead; +/// Whether the user can modify the object to which this Permission is attached. +@property (nonatomic) bool canUpdate; +/// Whether the user can delete the object to which this Permission is attached. +/// +/// This field is only applicable to Permissions attached to Objects, and not +/// to Realms or Classes. +@property (nonatomic) bool canDelete; +/// Whether the user can add or modify Permissions for the object which this +/// Permission is attached to. +@property (nonatomic) bool canSetPermissions; +/// Whether the user can subscribe to queries for this object type. +/// +/// This field is only applicable to Permissions attached to Classes, and not +/// to Realms or Objects. +@property (nonatomic) bool canQuery; +/// Whether the user can create new objects of the type this Permission is attached to. +/// +/// This field is only applicable to Permissions attached to Classes, and not +/// to Realms or Objects. +@property (nonatomic) bool canCreate; +/// Whether the user can modify the schema of the Realm which this +/// Permission is attached to. +/// +/// This field is only applicable to Permissions attached to Realms, and not +/// to Realms or Objects. +@property (nonatomic) bool canModifySchema; + +/** + Returns the Permission object for the named Role in the array, creating it if needed. + + This function should be used in preference to manually querying the array for + the applicable Permission as it ensures that there is exactly one Permission + for the given Role in the array, merging duplicates or creating and adding new + ones as needed. +*/ ++ (RLMPermission *)permissionForRoleNamed:(NSString *)roleName inArray:(RLMArray *)array; + +/** + Returns the Permission object for the named Role on the Realm, creating it if needed. + + This function should be used in preference to manually querying for the + applicable Permission as it ensures that there is exactly one Permission for + the given Role on the Realm, merging duplicates or creating and adding new ones + as needed. +*/ ++ (RLMPermission *)permissionForRoleNamed:(NSString *)roleName onRealm:(RLMRealm *)realm; + +/** + Returns the Permission object for the named Role on the Class, creating it if needed. + + This function should be used in preference to manually querying for the + applicable Permission as it ensures that there is exactly one Permission for + the given Role on the Class, merging duplicates or creating and adding new ones + as needed. +*/ ++ (RLMPermission *)permissionForRoleNamed:(NSString *)roleName onClass:(Class)cls realm:(RLMRealm *)realm; + +/** + Returns the Permission object for the named Role on the named class, creating it if needed. + + This function should be used in preference to manually querying for the + applicable Permission as it ensures that there is exactly one Permission for + the given Role on the Class, merging duplicates or creating and adding new ones + as needed. +*/ ++ (RLMPermission *)permissionForRoleNamed:(NSString *)roleName onClassNamed:(NSString *)className realm:(RLMRealm *)realm; + +/** + Returns the Permission object for the named Role on the object, creating it if needed. + + This function should be used in preference to manually querying for the + applicable Permission as it ensures that there is exactly one Permission for + the given Role on the Realm, merging duplicates or creating and adding new ones + as needed. + + The given object must have a RLMArray property defined on it. If + more than one such property is present, the first will be used. +*/ ++ (RLMPermission *)permissionForRoleNamed:(NSString *)roleName onObject:(RLMObject *)object; +@end + +/** + A Role within the permissions system. + + A Role consists of a name for the role and a list of users which are members of the role. + Roles are granted privileges on Realms, Classes and Objects, and in turn grant those + privileges to all users which are members of the role. + + A role named "everyone" is automatically created in new Realms, and all new users which + connect to the Realm are automatically added to it. Any other roles you wish to use are + managed as normal Realm objects. + */ +@interface RLMPermissionRole : RLMObject +/// The name of the Role +@property (nonatomic) NSString *name; +/// The users which belong to the role +@property (nonatomic) RLMArray *users; +@end + +/** + A representation of a sync user within the permissions system. + + RLMPermissionUser objects are created automatically for each sync user which connects to + a Realm, and can also be created manually if you wish to grant permissions to a user + which has not yet connected to this Realm. + */ +@interface RLMPermissionUser : RLMObject +/// The unique Realm Object Server user ID string identifying this user. This will have +/// the same value as `-[RLMSyncUser identity]`. +@property (nonatomic) NSString *identity; + +/// The user's private role. This will be initialized to a role named for the user's +/// identity that contains this user as its only member. +@property (nonatomic) RLMPermissionRole *role; + +/// Roles which this user belongs to. +@property (nonatomic, readonly) RLMLinkingObjects *roles; + +/// Get the user object in the given Realm, creating it if needed. ++ (RLMPermissionUser *)userInRealm:(RLMRealm *)realm withIdentity:(NSString *)identity; +@end + +/** + A singleton object which describes Realm-wide permissions. + + An object of this type is automatically created in the Realm for you, and more objects + cannot be created manually. Call `+[RLMRealmPermission objectInRealm:]` to obtain the + instance for a specific Realm. + + See `RLMRealmPrivileges` for the meaning of permissions applied to a Realm. + */ +@interface RLMRealmPermission : RLMObject +/// The permissions for the Realm. +@property (nonatomic) RLMArray *permissions; + +/// Retrieve the singleton object for the given Realm. This will return `nil` +/// for non-partial-sync Realms. ++ (nullable instancetype)objectInRealm:(RLMRealm *)realm; +@end + +/** + An object which describes class-wide permissions. + + An instance of this object is automatically created in the Realm for class in your schema, + and should not be created manually. Call `+[RLMClassPermission objectInRealm:forClassNamed:]` + or `+[RLMClassPermission objectInRealm:forClass:]` to obtain the existing instance, or + query `RLMClassPermission` as normal. + */ +@interface RLMClassPermission : RLMObject +/// The name of the class which these permissions apply to. +@property (nonatomic) NSString *name; +/// The permissions for this class. +@property (nonatomic) RLMArray *permissions; + +/// Retrieve the object for the named RLMObject subclass. This will return `nil` +/// for non-partial-sync Realms. ++ (nullable instancetype)objectInRealm:(RLMRealm *)realm forClassNamed:(NSString *)className; +/// Retrieve the object for the given RLMObject subclass. This will return `nil` +/// for non-partial-sync Realms. ++ (nullable instancetype)objectInRealm:(RLMRealm *)realm forClass:(Class)cls; +@end + +/** + A description of the actual privileges which apply to a Realm. + + This is a combination of all of the privileges granted to all of the Roles which the + current User is a member of, obtained by calling `-[RLMRealm privilegesForRealm]` on + the Realm. + + By default, all operations are permitted, and each privilege field indicates an operation + which may be forbidden. + */ +struct RLMRealmPrivileges { + /// If `false`, the current User is not permitted to see the Realm at all. This can + /// happen only if the Realm was created locally and has not yet been synchronized. + bool read : 1; + + /// If `false`, no modifications to the Realm are permitted. Write transactions can + /// be performed locally, but any changes made will be reverted by the server. + /// `setPermissions` and `modifySchema` will always be `false` when this is `false`. + bool update : 1; + + /// If `false`, no modifications to the permissions property of the RLMRealmPermissions + /// object for are permitted. Write transactions can be performed locally, but any + /// changes made will be reverted by the server. + /// + /// Note that if invalide privilege changes are made, `-[RLMRealm privilegesFor*:]` + /// will return results reflecting those invalid changes until synchronization occurs. + /// + /// Even if this field is `true`, note that the user will be unable to grant + /// privileges to a Role which they do not themselves have. + /// + /// Adding or removing Users from a Role is controlled by Update privileges on that + /// Role, and not by this value. + bool setPermissions : 1; + + /// If `false`, the user is not permitted to add new object types to the Realm or add + /// new properties to existing objec types. Defining new RLMObject subclasses (and not + /// excluding them from the schema with `-[RLMRealmConfiguration setObjectClasses:]`) + /// will result in the application crashing if the object types are not first added on + /// the server by a more privileged user. + bool modifySchema : 1; +}; + +/** + A description of the actual privileges which apply to a Class within a Realm. + + This is a combination of all of the privileges granted to all of the Roles which the + current User is a member of, obtained by calling `-[RLMRealm privilegesForClass:]` or + `-[RLMRealm privilegesForClassNamed:]` on the Realm. + + By default, all operations are permitted, and each privilege field indicates an operation + which may be forbidden. + */ +struct RLMClassPrivileges { + /// If `false`, the current User is not permitted to see objects of this type, and + /// attempting to query this class will always return empty results. + /// + /// Note that Read permissions are transitive, and so it may be possible to read an + /// object which the user does not directly have Read permissions for by following a + /// link to it from an object they do have Read permissions for. This does not apply + /// to any of the other permission types. + bool read : 1; + + /// If `false`, creating new objects of this type is not permitted. Write transactions + /// creating objects can be performed locally, but the objects will be deleted by the + /// server when synchronization occurs. + /// + /// For objects with Primary Keys, it may not be locally determinable if Create or + /// Update privileges are applicable. It may appear that you are creating a new object, + /// but an object with that Primary Key may already exist and simply not be visible to + /// you, in which case it is actually an Update operation. + bool create : 1; + + /// If `false`, no modifications to objects of this type are permitted. Write + /// transactions modifying the objects can be performed locally, but any changes made + /// will be reverted by the server. + /// + /// Deleting an object is considered a modification, and is governed by this privilege. + bool update : 1; + + /// If `false`, the User is not permitted to create new subscriptions for this class. + /// Local queries against the objects within the Realm will work, but new + /// subscriptions will never add objects to the Realm. + bool subscribe : 1; + + /// If `false`, no modifications to the permissions property of the RLMClassPermissions + /// object for this type are permitted. Write transactions can be performed locally, + /// but any changes made will be reverted by the server. + /// + /// Note that if invalid privilege changes are made, `-[RLMRealm privilegesFor*:]` + /// will return results reflecting those invalid changes until synchronization occurs. + /// + /// Even if this field is `true`, note that the user will be unable to grant + /// privileges to a Role which they do not themselves have. + bool setPermissions : 1; +}; + +/** + A description of the actual privileges which apply to a specific RLMObject. + + This is a combination of all of the privileges granted to all of the Roles which the + current User is a member of, obtained by calling `-[RLMRealm privilegesForObject:]` on + the Realm. + + By default, all operations are permitted, and each privilege field indicates an operation + which may be forbidden. + */ +struct RLMObjectPrivileges { + /// If `false`, the current User is not permitted to read this object directly. + /// + /// Objects which cannot be read by a user will appear in a Realm due to that read + /// permissions are transitive. All objects which a readable object links to are + /// themselves implicitly readable. If the link to an object with `read=false` is + /// removed, the object will be deleted from the local Realm. + bool read : 1; + + /// If `false`, modifying the fields of this type is not permitted. Write + /// transactions modifying the objects can be performed locally, but any changes made + /// will be reverted by the server. + /// + /// Note that even if this is `true`, the user may not be able to modify the + /// `RLMArray *` property of the object (if it exists), as that is + /// governed by `setPermissions`. + bool update : 1; + + /// If `false`, deleting this object is not permitted. Write transactions which delete + /// the object can be performed locally, but the server will restore it. + /// + /// It is possible to have `update` but not `delete` privileges, or vice versa. For + /// objects with primary keys, `delete` but not `update` is ill-advised, as an object + /// can be updated by deleting and recreating it. + bool del : 1; + + /// If `false`, modifying the privileges of this specific object is not permitted. + /// + /// Object-specific permissions are set by declaring a `RLMArray *` + /// property on the `RLMObject` subclass. Modifications to this property are + /// controlled by `setPermissions` rather than `update`. + /// + /// Even if this field is `true`, note that the user will be unable to grant + /// privileges to a Role which they do not themselves have. + bool setPermissions : 1; +}; + +/// :nodoc: +FOUNDATION_EXTERN id RLMPermissionForRole(RLMArray *array, id role); + +/** + Access levels which can be granted to Realm Mobile Platform users + for specific synchronized Realms, using the permissions APIs. + + Note that each access level guarantees all allowed actions provided + by less permissive access levels. Specifically, users with write + access to a Realm can always read from that Realm, and users with + administrative access can always read or write from the Realm. + */ +typedef RLM_CLOSED_ENUM(NSUInteger, RLMSyncAccessLevel) { + /// No access whatsoever. + RLMSyncAccessLevelNone = 0, + /** + User can only read the contents of the Realm. + + @warning Users who have read-only access to a Realm should open the + Realm using `+[RLMRealm asyncOpenWithConfiguration:callbackQueue:callback:]`. + Attempting to directly open the Realm is an error; in this + case the Realm must be deleted and re-opened. + */ + RLMSyncAccessLevelRead = 1, + /// User can read and write the contents of the Realm. + RLMSyncAccessLevelWrite = 2, + /// User can read, write, and administer the Realm, including + /// granting permissions to other users. + RLMSyncAccessLevelAdmin = 3, +}; + +/// Get a string representing a sync acces level +FOUNDATION_EXTERN NSString *RLMSyncAccessLevelToString(RLMSyncAccessLevel); + +/// Get a sync access level for the given string. +/// +/// Throws an exception if the string is not a valid access level. +FOUNDATION_EXTERN RLMSyncAccessLevel RLMSyncAccessLevelFromString(NSString *); + +/** + A value representing a permission granted to the specified user(s) to access the specified Realm(s). + + `RLMSyncPermission` is immutable and can be accessed from any thread. + + See https://realm.io/docs/realm-object-server/#permissions for general documentation. + */ +@interface RLMSyncPermission : NSObject + +/** + The Realm Object Server path to the Realm to which this permission applies (e.g. "/path/to/realm"). + + Specify "*" if this permission applies to all Realms managed by the server. + */ +@property (nonatomic, readonly) NSString *path; + +/** + The access level described by this permission. + */ +@property (nonatomic, readonly) RLMSyncAccessLevel accessLevel; + +/// Whether the access level allows the user to read from the Realm. +@property (nonatomic, readonly) BOOL mayRead; + +/// Whether the access level allows the user to write to the Realm. +@property (nonatomic, readonly) BOOL mayWrite; + +/// Whether the access level allows the user to administer the Realm. +@property (nonatomic, readonly) BOOL mayManage; + +/** + Create a new sync permission value, for use with permission APIs. + + @param path The Realm Object Server path to the Realm whose permission should be modified + (e.g. "/path/to/realm"). Pass "*" to apply to all Realms managed by the user. + @param identity The Realm Object Server identity of the user who should be granted access to + the Realm at `path`. + Pass "*" to apply to all users managed by the server. + @param accessLevel The access level to grant. + */ +- (instancetype)initWithRealmPath:(NSString *)path + identity:(NSString *)identity + accessLevel:(RLMSyncAccessLevel)accessLevel; + +/** + Create a new sync permission value, for use with permission APIs. + + @param path The Realm Object Server path to the Realm whose permission should be modified + (e.g. "/path/to/realm"). Pass "*" to apply to all Realms managed by the user. + @param username The username (often an email address) of the user who should be granted access + to the Realm at `path`. + @param accessLevel The access level to grant. + */ +- (instancetype)initWithRealmPath:(NSString *)path + username:(NSString *)username + accessLevel:(RLMSyncAccessLevel)accessLevel; + +/** + The identity of the user to whom this permission is granted, or "*" + if all users are granted this permission. Nil if the permission is + defined in terms of a key-value pair. + */ +@property (nullable, nonatomic, readonly) NSString *identity; + +/** + If the permission is defined in terms of a key-value pair, the key + describing the type of criterion used to determine what users the + permission applies to. Otherwise, nil. + */ +@property (nullable, nonatomic, readonly) NSString *key; + +/** + If the permission is defined in terms of a key-value pair, a string + describing the criterion value used to determine what users the + permission applies to. Otherwise, nil. + */ +@property (nullable, nonatomic, readonly) NSString *value; + +/** + When this permission was last updated. + */ +@property (nonatomic, readonly) NSDate *updatedAt; + +/// :nodoc: +- (instancetype)init __attribute__((unavailable("Use the designated initializer"))); + +/// :nodoc: ++ (instancetype)new __attribute__((unavailable("Use the designated initializer"))); + +// MARK: - Migration assistance + +/// :nodoc: +@property (nullable, nonatomic, readonly) NSString *userId __attribute__((unavailable("Renamed to `identity`"))); + +/// :nodoc: +- (instancetype)initWithRealmPath:(NSString *)path + userID:(NSString *)identity + accessLevel:(RLMSyncAccessLevel)accessLevel +__attribute__((unavailable("Renamed to `-initWithRealmPath:identity:accessLevel:`"))); + +@end + +/** + A pending offer to grant permissions on a Realm path. + + This type is immutable and can be safely read from any thread. + + @see -[RLMSyncUser retrievePermissionOffersWithCallback:] + */ +@interface RLMSyncPermissionOffer : NSObject +/// The Realm Object Server path to the Realm to which this permission applies (e.g. "/path/to/realm"). +@property (nonatomic, readonly) NSString *realmPath; +/// The token which can be used to aceept or revoke this offer. +@property (nonatomic, readonly) NSString *token; +/// The date when this offer will expire, or nil for a non-expiring offer. +@property (nonatomic, nullable, readonly) NSDate *expiresAt; +/// The date when this offer was created. +@property (nonatomic, nullable, readonly) NSDate *createdAt; +/// The access level which this offer grants. +@property (nonatomic, readonly) RLMSyncAccessLevel accessLevel; + +/// :nodoc: +- (instancetype)init __attribute__((unavailable("Use the designated initializer"))); +/// :nodoc: ++ (instancetype)new __attribute__((unavailable("Use the designated initializer"))); + +/// :nodoc: +- (instancetype)initWithRealmPath:(NSString *)path + token:(NSString *)token + expiresAt:(NSDate *)expiresAt + createdAt:(NSDate *)createdAt + accessLevel:(RLMSyncAccessLevel)accessLevel; +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMSyncSession.h b/!main project/Pods/Realm/include/RLMSyncSession.h new file mode 100644 index 0000000..3b26184 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMSyncSession.h @@ -0,0 +1,278 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +#import "RLMRealm.h" + +/** + The current state of the session represented by a session object. + */ +typedef NS_ENUM(NSUInteger, RLMSyncSessionState) { + /// The sync session is actively communicating or attempting to communicate + /// with the Realm Object Server. A session is considered Active even if + /// it is not currently connected. Check the connection state instead if you + /// wish to know if the connection is currently online. + RLMSyncSessionStateActive, + /// The sync session is not attempting to communicate with the Realm Object + /// Server, due to the user logging out or synchronization being paused. + RLMSyncSessionStateInactive, + /// The sync session encountered a fatal error and is permanently invalid; it should be discarded. + RLMSyncSessionStateInvalid +}; + +/** + The current state of a sync session's connection. Sessions which are not in + the Active state will always be Disconnected. + */ +typedef NS_ENUM(NSUInteger, RLMSyncConnectionState) { + /// The sync session is not connected to the server, and is not attempting + /// to connect, either because the session is inactive or because it is + /// waiting to retry after a failed connection. + RLMSyncConnectionStateDisconnected, + /// The sync session is attempting to connect to the Realm Object Server. + RLMSyncConnectionStateConnecting, + /// The sync session is currently connected to the Realm Object Server. + RLMSyncConnectionStateConnected, +}; + +/** + The transfer direction (upload or download) tracked by a given progress notification block. + + Progress notification blocks can be registered on sessions if your app wishes to be informed + how many bytes have been uploaded or downloaded, for example to show progress indicator UIs. + */ +typedef RLM_CLOSED_ENUM(NSUInteger, RLMSyncProgressDirection) { + /// For monitoring upload progress. + RLMSyncProgressDirectionUpload, + /// For monitoring download progress. + RLMSyncProgressDirectionDownload, +}; + +/** + The desired behavior of a progress notification block. + + Progress notification blocks can be registered on sessions if your app wishes to be informed + how many bytes have been uploaded or downloaded, for example to show progress indicator UIs. + */ +typedef NS_ENUM(NSUInteger, RLMSyncProgressMode) { + /** + The block will be called indefinitely, or until it is unregistered by calling + `-[RLMProgressNotificationToken invalidate]`. + + Notifications will always report the latest number of transferred bytes, and the + most up-to-date number of total transferrable bytes. + */ + RLMSyncProgressModeReportIndefinitely, + /** + The block will, upon registration, store the total number of bytes + to be transferred. When invoked, it will always report the most up-to-date number + of transferrable bytes out of that original number of transferrable bytes. + + When the number of transferred bytes reaches or exceeds the + number of transferrable bytes, the block will be unregistered. + */ + RLMSyncProgressModeForCurrentlyOutstandingWork, +}; + +@class RLMSyncUser, RLMSyncConfiguration, RLMSyncErrorActionToken; + +/** + The type of a progress notification block intended for reporting a session's network + activity to the user. + + `transferredBytes` refers to the number of bytes that have been uploaded or downloaded. + `transferrableBytes` refers to the total number of bytes transferred, and pending transfer. + */ +typedef void(^RLMProgressNotificationBlock)(NSUInteger transferredBytes, NSUInteger transferrableBytes); + +NS_ASSUME_NONNULL_BEGIN + +/** + A token object corresponding to a progress notification block on a session object. + + To stop notifications manually, call `-invalidate` on it. Notifications should be stopped before + the token goes out of scope or is destroyed. + */ +@interface RLMProgressNotificationToken : RLMNotificationToken +@end + +/** + An object encapsulating a Realm Object Server "session". Sessions represent the + communication between the client (and a local Realm file on disk), and the server + (and a remote Realm at a given URL stored on a Realm Object Server). + + Sessions are always created by the SDK and vended out through various APIs. The + lifespans of sessions associated with Realms are managed automatically. Session + objects can be accessed from any thread. + */ +@interface RLMSyncSession : NSObject + +/// The session's current state. +/// +/// This property is not KVO-compliant. +@property (nonatomic, readonly) RLMSyncSessionState state; + +/// The session's current connection state. +/// +/// This property is KVO-compliant and can be observed to be notified of changes. +/// Be warned that KVO observers for this property may be called on a background +/// thread. +@property (atomic, readonly) RLMSyncConnectionState connectionState; + +/// The Realm Object Server URL of the remote Realm this session corresponds to. +@property (nullable, nonatomic, readonly) NSURL *realmURL; + +/// The user that owns this session. +- (nullable RLMSyncUser *)parentUser; + +/** + If the session is valid, return a sync configuration that can be used to open the Realm + associated with this session. + */ +- (nullable RLMSyncConfiguration *)configuration; + +/** + Temporarily suspend syncronization and disconnect from the server. + + The session will not attempt to connect to Realm Object Server until `resume` + is called or the Realm file is closed and re-opened. + */ +- (void)suspend; + +/** + Resume syncronization and reconnect to Realm Object Server after suspending. + + This is a no-op if the session was already active or if the session is invalid. + Newly created sessions begin in the Active state and do not need to be resumed. + */ +- (void)resume; + +/** + Register a progress notification block. + + Multiple blocks can be registered with the same session at once. Each block + will be invoked on a side queue devoted to progress notifications. + + If the session has already received progress information from the + synchronization subsystem, the block will be called immediately. Otherwise, it + will be called as soon as progress information becomes available. + + The token returned by this method must be retained as long as progress + notifications are desired, and the `-invalidate` method should be called on it + when notifications are no longer needed and before the token is destroyed. + + If no token is returned, the notification block will never be called again. + There are a number of reasons this might be true. If the session has previously + experienced a fatal error it will not accept progress notification blocks. If + the block was configured in the `RLMSyncProgressForCurrentlyOutstandingWork` + mode but there is no additional progress to report (for example, the number + of transferrable bytes and transferred bytes are equal), the block will not be + called again. + + @param direction The transfer direction (upload or download) to track in this progress notification block. + @param mode The desired behavior of this progress notification block. + @param block The block to invoke when notifications are available. + + @return A token which must be held for as long as you want notifications to be delivered. + + @see `RLMSyncProgressDirection`, `RLMSyncProgress`, `RLMProgressNotificationBlock`, `RLMProgressNotificationToken` + */ +- (nullable RLMProgressNotificationToken *)addProgressNotificationForDirection:(RLMSyncProgressDirection)direction + mode:(RLMSyncProgressMode)mode + block:(RLMProgressNotificationBlock)block +NS_REFINED_FOR_SWIFT; + +/** + Given an error action token, immediately handle the corresponding action. + + @see `RLMSyncErrorClientResetError`, `RLMSyncErrorPermissionDeniedError` + */ ++ (void)immediatelyHandleError:(RLMSyncErrorActionToken *)token; + +/** + Get the sync session for the given Realm if it is a synchronized Realm, or `nil` + if it is not. + */ ++ (nullable RLMSyncSession *)sessionForRealm:(RLMRealm *)realm; + +@end + +// MARK: - Error action token + +#pragma mark - Error action token + +/** + An opaque token returned as part of certain errors. It can be + passed into certain APIs to perform certain actions. + + @see `RLMSyncErrorClientResetError`, `RLMSyncErrorPermissionDeniedError` + */ +@interface RLMSyncErrorActionToken : NSObject + +/// :nodoc: +- (instancetype)init __attribute__((unavailable("This type cannot be created directly"))); + +/// :nodoc: ++ (instancetype)new __attribute__((unavailable("This type cannot be created directly"))); + +@end + +/** + A task object which can be used to observe or cancel an async open. + + When a synchronized Realm is opened asynchronously, the latest state of the + Realm is downloaded from the server before the completion callback is invoked. + This task object can be used to observe the state of the download or to cancel + it. This should be used instead of trying to observe the download via the sync + session as the sync session itself is created asynchronously, and may not exist + yet when -[RLMRealm asyncOpenWithConfiguration:completion:] returns. + */ +@interface RLMAsyncOpenTask : NSObject +/** + Register a progress notification block. + + Each registered progress notification block is called whenever the sync + subsystem has new progress data to report until the task is either cancelled + or the completion callback is called. Progress notifications are delivered on + the main queue. + */ +- (void)addProgressNotificationBlock:(RLMProgressNotificationBlock)block; + +/** + Register a progress notification block which is called on the given queue. + + Each registered progress notification block is called whenever the sync + subsystem has new progress data to report until the task is either cancelled + or the completion callback is called. Progress notifications are delivered on + the supplied queue. + */ +- (void)addProgressNotificationOnQueue:(dispatch_queue_t)queue + block:(RLMProgressNotificationBlock)block; + +/** + Cancel the asynchronous open. + + Any download in progress will be cancelled, and the completion block for this + async open will never be called. If multiple async opens on the same Realm are + happening concurrently, all other opens will fail with the error "operation cancelled". + */ +- (void)cancel; +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMSyncSessionRefreshHandle.h b/!main project/Pods/Realm/include/RLMSyncSessionRefreshHandle.h new file mode 100644 index 0000000..78a278b --- /dev/null +++ b/!main project/Pods/Realm/include/RLMSyncSessionRefreshHandle.h @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2017 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +@class RLMSyncUser; + +/// An object that handles refreshing a session's token periodically, as long +/// as the session remains live and valid. +@interface RLMSyncSessionRefreshHandle : NSObject + +- (void)scheduleRefreshTimer:(NSDate *)dateWhenTokenExpires; +- (void)invalidate; + +@end diff --git a/!main project/Pods/Realm/include/RLMSyncSessionRefreshHandle.hpp b/!main project/Pods/Realm/include/RLMSyncSessionRefreshHandle.hpp new file mode 100644 index 0000000..26b5bea --- /dev/null +++ b/!main project/Pods/Realm/include/RLMSyncSessionRefreshHandle.hpp @@ -0,0 +1,43 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMSyncSessionRefreshHandle.h" + +#import "RLMSyncUtil_Private.h" + +#import + +namespace realm { +class SyncSession; +class SyncUser; +} + +@class RLMSyncUser; + +@interface RLMSyncSessionRefreshHandle () + +NS_ASSUME_NONNULL_BEGIN + +- (instancetype)initWithRealmURL:(NSURL *)realmURL + user:(std::shared_ptr)user + session:(std::shared_ptr)session + completionBlock:(nullable RLMSyncBasicErrorReportingBlock)completionBlock; + +NS_ASSUME_NONNULL_END + +@end diff --git a/!main project/Pods/Realm/include/RLMSyncSession_Private.hpp b/!main project/Pods/Realm/include/RLMSyncSession_Private.hpp new file mode 100644 index 0000000..b14d2f6 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMSyncSession_Private.hpp @@ -0,0 +1,56 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMSyncSession.h" + +#import "RLMSyncUtil_Private.h" +#import + +namespace realm { +class AsyncOpenTask; +class SyncSession; +} + +NS_ASSUME_NONNULL_BEGIN + +@interface RLMSyncSession () { +@public // So it's visible to tests + std::weak_ptr _session; +} RLM_SYNC_UNINITIALIZABLE + +- (instancetype)initWithSyncSession:(std::shared_ptr const&)session; + +/// Wait for pending uploads to complete or the session to expire, and dispatch the callback onto the specified queue. +- (BOOL)waitForUploadCompletionOnQueue:(nullable dispatch_queue_t)queue callback:(void(^)(NSError * _Nullable))callback; + +/// Wait for pending downloads to complete or the session to expire, and dispatch the callback onto the specified queue. +- (BOOL)waitForDownloadCompletionOnQueue:(nullable dispatch_queue_t)queue callback:(void(^)(NSError * _Nullable))callback; + +@end + +@interface RLMSyncErrorActionToken () + +- (instancetype)initWithOriginalPath:(std::string)originalPath; + +@end + +@interface RLMAsyncOpenTask () +@property (nonatomic) std::shared_ptr task; +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMSyncSubscription.h b/!main project/Pods/Realm/include/RLMSyncSubscription.h new file mode 100644 index 0000000..307daef --- /dev/null +++ b/!main project/Pods/Realm/include/RLMSyncSubscription.h @@ -0,0 +1,416 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2018 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + `RLMSyncSubscriptionState` is an enumeration representing the possible state of a sync subscription. + */ +typedef RLM_CLOSED_ENUM(NSInteger, RLMSyncSubscriptionState) { + /** + An error occurred while creating the subscription or while the server was processing it. + */ + RLMSyncSubscriptionStateError = -1, + + /** + The subscription is being created, but has not yet been written to the synced Realm. + */ + RLMSyncSubscriptionStateCreating = 2, + + /** + The subscription has been created, and is waiting to be processed by the server. + */ + RLMSyncSubscriptionStatePending = 0, + + /** + The subscription has been processed by the server, and objects matching the subscription + are now being synchronized to this client. + */ + RLMSyncSubscriptionStateComplete = 1, + + /** + This subscription has been removed. + */ + RLMSyncSubscriptionStateInvalidated = 3, +}; + +/** + `RLMSyncSubscription` represents a subscription to a set of objects in a synced Realm. + + When query-based sync is enabled for a synchronized Realm, the server only + synchronizes objects to the client when they match a sync subscription + registered by that client. A subscription consists of of a query (represented + by an `RLMResults`) and an optional name. + + The state of the subscription can be observed using + [Key-Value Observing](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html) + on the `state` property. + + Subscriptions are created using `-[RLMResults subscribe]` or + `-[RLMResults subscribeWithName:]`. Existing subscriptions for a Realm can be + looked up with `-[RLMRealm subscriptions]` or `-[RLMRealm subscriptionWithName:]`. + */ +@interface RLMSyncSubscription : NSObject + +/** + The unique name for this subscription. + + This will be `nil` if this object was created with `-[RLMResults subscribe]`. + Subscription objects read from a Realm with `-[RLMRealm subscriptions]` will + always have a non-`nil` name and subscriptions which were not explicitly named + will have an automatically generated one. + */ +@property (nonatomic, readonly, nullable) NSString *name; + +/** + The current state of the subscription. See `RLMSyncSubscriptionState`. + */ +@property (nonatomic, readonly) RLMSyncSubscriptionState state; + +/** + The error which occurred when registering this subscription, if any. + + Will be non-nil only when `state` is `RLMSyncSubscriptionStateError`. + */ +@property (nonatomic, readonly, nullable) NSError *error; + +/** + The raw query which this subscription is running on the server. + + This string is a serialized representation of the RLMResults which the + subscription was created from. This representation does *not* use NSPredicate + syntax, and is not guaranteed to remain consistent between versions of Realm. + Any use of this other than manual inspection when debugging is likely to be + incorrect. + + This is `nil` while the subscription is in the Creating state. + */ +@property (nonatomic, readonly, nullable) NSString *query; + +/** + When this subscription was first created. + + This value will be `nil` for subscriptions created with older versions of Realm + which did not store the creation date. Newly created subscriptions should + always have a non-nil creation date. + */ +@property (nonatomic, readonly, nullable) NSDate *createdAt; + +/** + When this subscription was last updated. + + This value will be `nil` for subscriptions created with older versions of Realm + which did not store the update date. Newly created subscriptions should + always have a non-nil update date. + + The update date is the time when the subscription was last updated by a call + to `-[RLMResults subscribeWithOptions:]`, and not when the set of objects which + match the subscription last changed. + */ +@property (nonatomic, readonly, nullable) NSDate *updatedAt; + +/** + When this subscription will be automatically removed. + + If the `timeToLive` parameter is set when creating a sync subscription, the + subscription will be automatically removed the first time that any subscription + is created, modified, or deleted after that time has elapsed. + + This property will be `nil` if the `timeToLive` option was not enabled. + */ +@property (nonatomic, readonly, nullable) NSDate *expiresAt; + +/** + How long this subscription will persist after last being updated. + + If the `timeToLive` parameter is set when creating a sync subscription, the + subscription will be automatically removed the first time that any subscription + is created, modified, or deleted after that time has elapsed. + + This property will be NaN if the `timeToLive` option was not enabled. + */ +@property (nonatomic, readonly) NSTimeInterval timeToLive; + +/** + Remove this subscription. + + Removing a subscription will delete all objects from the local Realm that were + matched only by that subscription and not any remaining subscriptions. The + deletion is performed by the server, and so has no immediate impact on the + contents of the local Realm. If the device is currently offline, the removal + will not be processed until the device returns online. + + Unsubscribing is an asynchronous operation and will not immediately remove the + subscription from the Realm's list of subscriptions. Observe the state property + to be notified of when the subscription has actually been removed. + */ +- (void)unsubscribe; + +#pragma mark - Unavailable Methods + +/** + `-[RLMSyncSubscription init]` is not available because `RLMSyncSubscription` cannot be created directly. + */ +- (instancetype)init __attribute__((unavailable("RLMSyncSubscription cannot be created directly"))); + +/** + `+[RLMSyncSubscription new]` is not available because `RLMSyncSubscription` cannot be created directly. + */ ++ (instancetype)new __attribute__((unavailable("RLMSyncSubscription cannot be created directly"))); + +@end + +/** + Configuration options for query-based sync subscriptions. + */ +@interface RLMSyncSubscriptionOptions : NSObject +/** + The name of the subscription. + + Naming a subscription makes it possible to look up a subscription by name + (using `-[RLMRealm subscriptionWithName:]`) or update an existing + subscription rather than creating a new one. + */ +@property (nonatomic, copy, nullable) NSString *name; + +/** + Whether this should update an existing subscription with the same name. + + By default trying to create a subscription with a name that's already in use + will fail unless the new subscription is an exact match for the existing one. + If this is set to YES, instead the existing subscription will be updated using + the query and options from the new subscription. This only works if the new + subscription is for the same type of objects as the existing subscription. + Trying to overwrite a subscription with a subscription of a different type of + objects will fail. + + The `updatedAt` and (if `timeToLive` is used) `expiresAt` properties are + updated whenever a subscription is overwritten even if nothing else has changed. + */ +@property (nonatomic) BOOL overwriteExisting; + +/** + How long (in seconds) a subscription should persist after being created. + + By default subscriptions are persistent, and last until they are explicitly + removed by calling `unsubscribe()`. Subscriptions can instead be made temporary + by setting the time to live to how long the subscription should remain. After + that time has elapsed the subscription will be automatically removed. + + A time to live of 0 or less disables subscription expiration. + */ +@property (nonatomic) NSTimeInterval timeToLive; + +/** + The maximum number of top-level matches to include in this subscription. + + If more top-level objects than the limit match the query, only the first + `limit` objects will be included. This respects the sort and distinct order of + the query being subscribed to for the determination of what the "first" objects + are. + + The limit does not count or apply to objects which are added indirectly due to + being linked to by the objects in the subscription or due to being listed in + `includeLinkingObjectProperties`. If the limit is larger than the number of + objects which match the query, all objects will be included. A limit of zero is + treated as unlimited. + */ +@property (nonatomic) NSUInteger limit; + +/** + Which RLMLinkingObjects properties should be included in the subscription. + + Outgoing links (i.e. `RLMArray` and `RLMObject` properties) are automatically + included in sync subscriptions. That is, if you subscribe to a query which + matches one object, every object which is reachable via links from that object + are also included in the subscription. + + By default, RLMLinkingObjects properties do not work this way. Instead, they + only report objects which happen to be included in a subscription. By naming + a RLMLinkingObjects property in this array, it can instead be treated as if + it was a RLMArray and include all objects which link to this object. + + Any keypath which ends in a RLMLinkingObject property can be included in this + array, including ones involving intermediate links. + */ +@property (nonatomic, copy, nullable) NSArray *includeLinkingObjectProperties; +@end + +/** + Support for subscribing to the results of object queries in a synced Realm. + */ +@interface RLMResults (SyncSubscription) + +/** + Subscribe to the query represented by this `RLMResults`. + + Subscribing to a query asks the server to synchronize all objects to the + client which match the query, along with all objects which are reachable + from those objects via links. This happens asynchronously, and the local + client Realm may not immediately have all objects which match the query. + Observe the `state` property of the returned subscription object to be + notified of when the subscription has been processed by the server and + all objects matching the query are available. + + The subscription will not be explicitly named. A name will be automatically + generated for internal use. The exact format of this name may change without + warning and should not be depended on. + + @return An object representing the newly-created subscription. + + @see RLMSyncSubscription +*/ +- (RLMSyncSubscription *)subscribe; + +/** + Subscribe to the query represented by this `RLMResults`. + + Subscribing to a query asks the server to synchronize all objects to the + client which match the query, along with all objects which are reachable + from those objects via links. This happens asynchronously, and the local + client Realm may not immediately have all objects which match the query. + Observe the `state` property of the returned subscription object to be + notified of when the subscription has been processed by the server and + all objects matching the query are available. + + Creating a new subscription with the same name and query as an existing + subscription will not create a new subscription, but instead will return + an object referring to the existing sync subscription. This means that + performing the same subscription twice followed by removing it once will + result in no subscription existing. + + The newly created subscription will not be reported by + `-[RLMRealm subscriptions]` or `-[RLMRealm subscriptionWithName:]` until + `state` has transitioned from `RLMSyncSubscriptionStateCreating` to any of the + other states. + + @param subscriptionName The name of the subscription. + + @return An object representing the newly-created subscription. + + @see RLMSyncSubscription +*/ +- (RLMSyncSubscription *)subscribeWithName:(nullable NSString *)subscriptionName; + +/** + Subscribe to a subset of the query represented by this `RLMResults`. + + Subscribing to a query asks the server to synchronize all objects to the + client which match the query, along with all objects which are reachable + from those objects via links. This happens asynchronously, and the local + client Realm may not immediately have all objects which match the query. + Observe the `state` property of the returned subscription object to be + notified of when the subscription has been processed by the server and + all objects matching the query are available. + + Creating a new subscription with the same name and query as an existing + subscription will not create a new subscription, but instead will return + an object referring to the existing sync subscription. This means that + performing the same subscription twice followed by removing it once will + result in no subscription existing. + + The newly created subscription will not be reported by + `-[RLMRealm subscriptions]` or `-[RLMRealm subscriptionWithName:]` until + `state` has transitioned from `RLMSyncSubscriptionStateCreating` to any of the + other states. + + The number of top-level matches may optionally be limited. This limit + respects the sort and distinct order of the query being subscribed to, + if any. Please note that the limit does not count or apply to objects + which are added indirectly due to being linked to by the objects in the + subscription. If the limit is larger than the number of objects which + match the query, all objects will be included. + + @param subscriptionName The name of the subscription + @param limit The maximum number of objects to include in the subscription. + + @return The subscription + + @see RLMSyncSubscription + */ +- (RLMSyncSubscription *)subscribeWithName:(nullable NSString *)subscriptionName limit:(NSUInteger)limit; + +/** + Subscribe to a subset of the query represented by this `RLMResults`. + + Subscribing to a query asks the server to synchronize all objects to the + client which match the query, along with all objects which are reachable + from those objects via links. This happens asynchronously, and the local + client Realm may not immediately have all objects which match the query. + Observe the `state` property of the returned subscription object to be + notified of when the subscription has been processed by the server and + all objects matching the query are available. + + Creating a new subscription with the same name and query as an existing + subscription will not create a new subscription, but instead will return + an object referring to the existing sync subscription. This means that + performing the same subscription twice followed by removing it once will + result in no subscription existing. + + The newly created subscription will not be reported by + `-[RLMRealm subscriptions]` or `-[RLMRealm subscriptionWithName:]` until + `state` has transitioned from `RLMSyncSubscriptionStateCreating` to any of the + other states. + + @param options The additional configuration options for the subscription. + @return The subscription. + + @see RLMSyncSubscription + */ +- (RLMSyncSubscription *)subscribeWithOptions:(RLMSyncSubscriptionOptions *)options; +@end + +/** + Support for managing existing subscriptions to object queries in a Realm. + */ +@interface RLMRealm (SyncSubscription) +/** + Get a list of the query-based sync subscriptions made for this Realm. + + This list includes all subscriptions which are currently in the states `Pending`, + `Created`, and `Error`. Newly created subscriptions which are still in the + `Creating` state are not included, and calling this immediately after calling + `-[RLMResults subscribe]` will typically not include that subscription. Similarly, + because unsubscription happens asynchronously, this may continue to include + subscriptions after `-[RLMSyncSubscription unsubscribe]` is called on them. + + This method can only be called on a Realm which is using query-based sync and + will throw an exception if called on a non-synchronized or full-sync Realm. + */ +- (RLMResults *)subscriptions; + +/** + Look up a specific query-based sync subscription by name. + + Subscriptions are created asynchronously, so calling this immediately after + calling `subscribeWithName:` on a `RLMResults` will typically return `nil`. + Only subscriptions which are currently in the states `Pending`, `Created`, + and `Error` can be retrieved with this method. + + This method can only be called on a Realm which is using query-based sync and + will throw an exception if called on a non-synchronized or full-sync Realm. + + @return The named subscription, or `nil` if no subscription exists with that name. + */ +- (nullable RLMSyncSubscription *)subscriptionWithName:(NSString *)name; +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMSyncUser.h b/!main project/Pods/Realm/include/RLMSyncUser.h new file mode 100644 index 0000000..d871490 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMSyncUser.h @@ -0,0 +1,536 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +#import "RLMRealmConfiguration.h" +#import "RLMSyncCredentials.h" +#import "RLMSyncPermission.h" + +@class RLMSyncUser, RLMSyncUserInfo, RLMSyncCredentials, RLMSyncPermission, RLMSyncSession, RLMRealm, RLMSyncPermissionOffer; + +/** + The state of the user object. + */ +typedef NS_ENUM(NSUInteger, RLMSyncUserState) { + /// The user is logged out. Call `logInWithCredentials:...` with valid credentials to log the user back in. + RLMSyncUserStateLoggedOut, + /// The user is logged in, and any Realms associated with it are syncing with the Realm Object Server. + RLMSyncUserStateActive, + /// The user has encountered a fatal error state, and cannot be used. + RLMSyncUserStateError, +}; + +/// A block type used for APIs which asynchronously vend an `RLMSyncUser`. +typedef void(^RLMUserCompletionBlock)(RLMSyncUser * _Nullable, NSError * _Nullable); + +/// A block type used to report the status of a password change operation. +/// If the `NSError` argument is nil, the operation succeeded. +typedef void(^RLMPasswordChangeStatusBlock)(NSError * _Nullable); + +/// A block type used to report the status of a permission apply or revoke operation. +/// If the `NSError` argument is nil, the operation succeeded. +typedef void(^RLMPermissionStatusBlock)(NSError * _Nullable); + +/// A block type used to report the status of a permission offer operation. +typedef void(^RLMPermissionOfferStatusBlock)(NSString * _Nullable, NSError * _Nullable); + +/// A block type used to report the status of a permission offer response operation. +typedef void(^RLMPermissionOfferResponseStatusBlock)(NSURL * _Nullable, NSError * _Nullable); + +/// A block type used to asynchronously report results of a permissions get operation. +/// Exactly one of the two arguments will be populated. +typedef void(^RLMPermissionResultsBlock)(NSArray * _Nullable, NSError * _Nullable); + +/// A block type used to asynchronously report results of a permission offerss get operation. +/// Exactly one of the two arguments will be populated. +typedef void(^RLMPermissionOfferResultsBlock)(NSArray * _Nullable, NSError * _Nullable); + +/// A block type used to asynchronously report results of a user info retrieval. +/// Exactly one of the two arguments will be populated. +typedef void(^RLMRetrieveUserBlock)(RLMSyncUserInfo * _Nullable, NSError * _Nullable); + +/// A block type used to report an error related to a specific user. +typedef void(^RLMUserErrorReportingBlock)(RLMSyncUser * _Nonnull, NSError * _Nonnull); + +NS_ASSUME_NONNULL_BEGIN + +/** + A `RLMSyncUser` instance represents a single Realm Object Server user account. + + A user may have one or more credentials associated with it. These credentials + uniquely identify the user to the authentication provider, and are used to sign + into a Realm Object Server user account. + + Note that user objects are only vended out via SDK APIs, and cannot be directly + initialized. User objects can be accessed from any thread. + */ +@interface RLMSyncUser : NSObject + +/** + A dictionary of all valid, logged-in user identities corresponding to their user objects. + */ ++ (NSDictionary *)allUsers NS_REFINED_FOR_SWIFT; + +/** + The logged-in user. `nil` if none exists. + + @warning Throws an exception if more than one logged-in user exists. + */ ++ (nullable RLMSyncUser *)currentUser NS_REFINED_FOR_SWIFT; + +/** + The unique Realm Object Server user ID string identifying this user. + */ +@property (nullable, nonatomic, readonly) NSString *identity; + +/** + The user's refresh token used to access the Realm Object Server. + + This is required to make HTTP requests to Realm Object Server's REST API + for functionality not exposed natively. It should be treated as sensitive data. + */ +@property (nullable, nonatomic, readonly) NSString *refreshToken; + +/** + The URL of the authentication server this user will communicate with. + */ +@property (nullable, nonatomic, readonly) NSURL *authenticationServer; + +/** + Whether the user is a Realm Object Server administrator. Value reflects the + state at the time of the last successful login of this user. + */ +@property (nonatomic, readonly) BOOL isAdmin; + +/** + The current state of the user. + */ +@property (nonatomic, readonly) RLMSyncUserState state; + +#pragma mark - Lifecycle + +/** + Create, log in, and asynchronously return a new user object, specifying a custom + timeout for the network request and a custom queue to run the callback upon. + Credentials identifying the user must be passed in. The user becomes available in + the completion block, at which point it is ready for use. + */ ++ (void)logInWithCredentials:(RLMSyncCredentials *)credentials + authServerURL:(NSURL *)authServerURL + timeout:(NSTimeInterval)timeout + callbackQueue:(dispatch_queue_t)callbackQueue + onCompletion:(RLMUserCompletionBlock)completion NS_REFINED_FOR_SWIFT; + +/** + Create, log in, and asynchronously return a new user object. + + If the login completes successfully, the completion block will invoked with + a `RLMSyncUser` object representing the logged-in user. This object can be + used to open synchronized Realms. If the login fails, the completion block + will be invoked with an error. + + The completion block always runs on the main queue. + + @param credentials A credentials value identifying the user to be logged in. + @param authServerURL The URL of the authentication server (e.g. "http://realm.example.org:9080"). + @param completion A callback block that returns a user object or an error, + indicating the completion of the login operation. + */ ++ (void)logInWithCredentials:(RLMSyncCredentials *)credentials + authServerURL:(NSURL *)authServerURL + onCompletion:(RLMUserCompletionBlock)completion +NS_SWIFT_UNAVAILABLE("Use the full version of this API."); + + +/** + Returns the default configuration for the user. The default configuration + points to the default query-based Realm on the server the user authenticated against. + */ +- (RLMRealmConfiguration *)configuration NS_REFINED_FOR_SWIFT; + +/** + Create a query-based configuration instance for the given url. + + @param url The unresolved absolute URL to the Realm on the Realm Object Server, + e.g. "realm://example.org/~/path/to/realm". "Unresolved" means the + path should contain the wildcard marker `~`, which will automatically + be filled in with the user identity by the Realm Object Server. + @return A default configuration object with the sync configuration set to use the given URL. + */ +- (RLMRealmConfiguration *)configurationWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT; + +/** + Create a configuration instance for the given url. + + @param url The unresolved absolute URL to the Realm on the Realm Object Server, + e.g. "realm://example.org/~/path/to/realm". "Unresolved" means the + path should contain the wildcard marker `~`, which will automatically + be filled in with the user identity by the Realm Object Server. + @param fullSynchronization If YES, all objects in the server Realm are + automatically synchronized, and the query subscription + methods cannot be used. + @return A default configuration object with the sync configuration set to use + the given URL and options. + */ +- (RLMRealmConfiguration *)configurationWithURL:(nullable NSURL *)url + fullSynchronization:(bool)fullSynchronization NS_REFINED_FOR_SWIFT; + +/** + Create a configuration instance for the given url. + + @param url The unresolved absolute URL to the Realm on the Realm Object Server, + e.g. "realm://example.org/~/path/to/realm". "Unresolved" means the + path should contain the wildcard marker `~`, which will automatically + be filled in with the user identity by the Realm Object Server. + @param fullSynchronization If YES, all objects in the server Realm are + automatically synchronized, and the query subscription + methods cannot be used. + @param enableSSLValidation If NO, invalid SSL certificates for the server will + not be rejected. THIS SHOULD NEVER BE USED IN + PRODUCTION AND EXISTS ONLY FOR TESTING PURPOSES. + @param urlPrefix A prefix which is prepending to URLs constructed for + the server. This should normally be `nil`, and customized only + to match corresponding settings on the server. + @return A default configuration object with the sync configuration set to use + the given URL and options. + */ +- (RLMRealmConfiguration *)configurationWithURL:(nullable NSURL *)url + fullSynchronization:(bool)fullSynchronization + enableSSLValidation:(bool)enableSSLValidation + urlPrefix:(nullable NSString *)urlPrefix NS_REFINED_FOR_SWIFT; + +/** + Log a user out, destroying their server state, unregistering them from the SDK, + and removing any synced Realms associated with them from on-disk storage on + next app launch. If the user is already logged out or in an error state, this + method does nothing. + + This method should be called whenever the application is committed to not using + a user again unless they are recreated. + Failing to call this method may result in unused files and metadata needlessly + taking up space. + */ +- (void)logOut; + +/** + An optional error handler which can be set to notify the host application when + the user encounters an error. Errors reported by this error handler are always + `RLMSyncAuthError`s. + + @note Check for `RLMSyncAuthErrorInvalidAccessToken` to see if the user has + been remotely logged out because its refresh token expired, or because the + third party authentication service providing the user's identity has + logged the user out. + + @warning Regardless of whether an error handler is installed, certain user errors + will automatically cause the user to enter the logged out state. + */ +@property (nullable, nonatomic) RLMUserErrorReportingBlock errorHandler NS_REFINED_FOR_SWIFT; + +#pragma mark - Sessions + +/** + Retrieve a valid session object belonging to this user for a given URL, or `nil` + if no such object exists. + */ +- (nullable RLMSyncSession *)sessionForURL:(NSURL *)url; + +/** + Retrieve all the valid sessions belonging to this user. + */ +- (NSArray *)allSessions; + +#pragma mark - Passwords + +/** + Change this user's password asynchronously. + + @warning Changing a user's password using an authentication server that doesn't + use HTTPS is a major security flaw, and should only be done while + testing. + + @param newPassword The user's new password. + @param completion Completion block invoked when login has completed or failed. + The callback will be invoked on a background queue provided + by `NSURLSession`. + */ +- (void)changePassword:(NSString *)newPassword completion:(RLMPasswordChangeStatusBlock)completion; + +/** + Change an arbitrary user's password asynchronously. + + @note The current user must be an admin user for this operation to succeed. + + @warning Changing a user's password using an authentication server that doesn't + use HTTPS is a major security flaw, and should only be done while + testing. + + @param newPassword The user's new password. + @param userID The identity of the user whose password should be changed. + @param completion Completion block invoked when login has completed or failed. + The callback will be invoked on a background queue provided + by `NSURLSession`. + */ +- (void)changePassword:(NSString *)newPassword forUserID:(NSString *)userID completion:(RLMPasswordChangeStatusBlock)completion; + +/** + Ask the server to send a password reset email to the given email address. + + If `email` is an email address which is associated with a user account that was + registered using the "password" authentication service, the server will send an + email to that address with a password reset token. No error is reported if the + email address is invalid or not associated with an account. + + @param serverURL The authentication server URL for the user. + @param email The email address to send the email to. + @param completion A block which will be called when the request completes or + fails. The callback will be invoked on a background queue + provided by `NSURLSession`, and not on the calling queue. + */ ++ (void)requestPasswordResetForAuthServer:(NSURL *)serverURL + userEmail:(NSString *)email + completion:(RLMPasswordChangeStatusBlock)completion; + +/** + Change a user's password using a one-time password reset token. + + By default, the password reset email sent by ROS will link to a web site where + the user can select a new password, and the app will not need to call this + method. If you wish to instead handle this within your native app, you must + change the `baseURL` in the server configuration for `PasswordAuthProvider` to + a scheme registered for your app, extract the token from the URL, and call this + method after prompting the user for a new password. + + @warning Changing a user's password using an authentication server that doesn't + use HTTPS is a major security flaw, and should only be done while + testing. + + @param serverURL The authentication server URL for the user. + @param token The one-time use token from the URL. + @param newPassword The user's new password. + @param completion A block which will be called when the request completes or + fails. The callback will be invoked on a background queue + provided by `NSURLSession`, and not on the calling queue. + */ ++ (void)completePasswordResetForAuthServer:(NSURL *)serverURL + token:(NSString *)token + password:(NSString *)newPassword + completion:(RLMPasswordChangeStatusBlock)completion; + +/** + Ask the server to send a confirmation email to the given email address. + + If `email` is an email address which is associated with a user account that was + registered using the "password" authentication service, the server will send an + email to that address with a confirmation token. No error is reported if the + email address is invalid or not associated with an account. + + @param serverURL The authentication server URL for the user. + @param email The email address to send the email to. + @param completion A block which will be called when the request completes or + fails. The callback will be invoked on a background queue + provided by `NSURLSession`, and not on the calling queue. + */ ++ (void)requestEmailConfirmationForAuthServer:(NSURL *)serverURL + userEmail:(NSString *)email + completion:(RLMPasswordChangeStatusBlock)completion; + +/** + Confirm a user's email using a one-time confirmation token. + + By default, the confirmation email sent by ROS will link to a web site with + a generic "thank you for confirming your email" message, and the app will not + need to call this method. If you wish to instead handle this within your native + app, you must change the `baseURL` in the server configuration for + `PasswordAuthProvider` to a scheme registered for your app, extract the token + from the URL, and call this method. + + @param serverURL The authentication server URL for the user. + @param token The one-time use token from the URL. + @param completion A block which will be called when the request completes or + fails. The callback will be invoked on a background queue + provided by `NSURLSession`, and not on the calling queue. + */ ++ (void)confirmEmailForAuthServer:(NSURL *)serverURL + token:(NSString *)token + completion:(RLMPasswordChangeStatusBlock)completion; + +#pragma mark - Administrator + +/** + Given a Realm Object Server authentication provider and a provider identifier for a user + (for example, a username), look up and return user information for that user. + + @param providerUserIdentity The username or identity of the user as issued by the authentication provider. + In most cases this is different from the Realm Object Server-issued identity. + @param provider The authentication provider that manages the user whose information is desired. + @param completion Completion block invoked when request has completed or failed. + The callback will be invoked on a background queue provided + by `NSURLSession`. + */ +- (void)retrieveInfoForUser:(NSString *)providerUserIdentity + identityProvider:(RLMIdentityProvider)provider + completion:(RLMRetrieveUserBlock)completion; + +#pragma mark - Permissions + +/** + Asynchronously retrieve all permissions associated with the user calling this method. + + The results will be returned through the callback block, or an error if the operation failed. + The callback block will be run on a background thread and not the calling thread. + */ +- (void)retrievePermissionsWithCallback:(RLMPermissionResultsBlock)callback; + +/** + Apply a given permission. + + The operation will take place asynchronously, and the callback will be used to report whether + the permission change succeeded or failed. The user calling this method must have the right + to grant the given permission, or else the operation will fail. + + @see `RLMSyncPermission` + */ +- (void)applyPermission:(RLMSyncPermission *)permission callback:(RLMPermissionStatusBlock)callback; + +/** + Create a permission offer for a Realm. + + A permission offer is used to grant access to a Realm this user manages to another + user. Creating a permission offer produces a string token which can be passed to the + recepient in any suitable way (for example, via e-mail). + + The operation will take place asynchronously. The token can be accepted by the recepient + using the `-[RLMSyncUser acceptOfferForToken:callback:]` method. + + @param url The URL of the Realm for which the permission offer should pertain. This + may be the URL of any Realm which this user is allowed to manage. If the URL + has a `~` wildcard it will be replaced with this user's user identity. + @param accessLevel What access level to grant to whoever accepts the token. + @param expirationDate Optionally, a date which indicates when the offer expires. If the + recepient attempts to accept the offer after the date it will be rejected. + @param callback A callback indicating whether the operation succeeded or failed. If it + succeeded the token will be passed in as a string. + + @see `acceptOfferForToken:callback:` + */ +- (void)createOfferForRealmAtURL:(NSURL *)url + accessLevel:(RLMSyncAccessLevel)accessLevel + expiration:(nullable NSDate *)expirationDate + callback:(RLMPermissionOfferStatusBlock)callback NS_REFINED_FOR_SWIFT; + +/** + Accept a permission offer. + + Pass in a token representing a permission offer. The operation will take place asynchronously. + If the operation succeeds, the callback will be passed the URL of the Realm for which the + offer applied, so the Realm can be opened. + + The token this method accepts can be created by the offering user through the + `-[RLMSyncUser createOfferForRealmAtURL:accessLevel:expiration:callback:]` method. + + @see `createOfferForRealmAtURL:accessLevel:expiration:callback:` + */ +- (void)acceptOfferForToken:(NSString *)token + callback:(RLMPermissionOfferResponseStatusBlock)callback; + +/** + Revoke a permission offer. + + Pass in a token representing a permission offer which was created by this + user. The operation will take place asynchronously. If the operation succeeds, + the callback will be passed the URL of the Realm for which the offer was + revoked. After this operation completes, the token can no longer be accepted + by the recipient. + + @see `createOfferForRealmAtURL:accessLevel:expiration:callback:` + */ +- (void)invalidateOfferForToken:(NSString *)token + callback:(RLMPermissionStatusBlock)callback; + +/** + Asynchronously retrieve all pending permission offers created by the calling user. + + The results will be returned through the callback block, or an error if the operation failed. + The callback block will be run on a background thread and not the calling thread. + */ +- (void)retrievePermissionOffersWithCallback:(RLMPermissionOfferResultsBlock)callback; + +/// :nodoc: +- (instancetype)init __attribute__((unavailable("RLMSyncUser cannot be created directly"))); +/// :nodoc: ++ (instancetype)new __attribute__((unavailable("RLMSyncUser cannot be created directly"))); + +@end + +#pragma mark - User info classes + +/** + A data object representing a user account associated with a user. + + @see `RLMSyncUserInfo` + */ +@interface RLMSyncUserAccountInfo : NSObject + +/// The authentication provider which manages this user account. +@property (nonatomic, readonly) RLMIdentityProvider provider; + +/// The username or identity of this user account. +@property (nonatomic, readonly) NSString *providerUserIdentity; + +/// :nodoc: +- (instancetype)init __attribute__((unavailable("RLMSyncUserAccountInfo cannot be created directly"))); +/// :nodoc: ++ (instancetype)new __attribute__((unavailable("RLMSyncUserAccountInfo cannot be created directly"))); + +@end + +/** + A data object representing information about a user that was retrieved from a user lookup call. + */ +@interface RLMSyncUserInfo : NSObject + +/** + An array of all the user accounts associated with this user. + */ +@property (nonatomic, readonly) NSArray *accounts; + +/** + The identity issued to this user by the Realm Object Server. + */ +@property (nonatomic, readonly) NSString *identity; + +/** + Metadata about this user stored on the Realm Object Server. + */ +@property (nonatomic, readonly) NSDictionary *metadata; + +/** + Whether the user is flagged on the Realm Object Server as an administrator. + */ +@property (nonatomic, readonly) BOOL isAdmin; + +/// :nodoc: +- (instancetype)init __attribute__((unavailable("RLMSyncUserInfo cannot be created directly"))); +/// :nodoc: ++ (instancetype)new __attribute__((unavailable("RLMSyncUserInfo cannot be created directly"))); + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMSyncUser_Private.hpp b/!main project/Pods/Realm/include/RLMSyncUser_Private.hpp new file mode 100644 index 0000000..4c414f6 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMSyncUser_Private.hpp @@ -0,0 +1,71 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMSyncUser.h" + +#import "RLMSyncConfiguration.h" +#import "RLMSyncUtil_Private.h" + +#import "sync/sync_config.hpp" +#import "sync/sync_user.hpp" +#import "sync/impl/sync_metadata.hpp" + +@class RLMSyncConfiguration, RLMSyncSessionRefreshHandle; + +using namespace realm; + +typedef void(^RLMFetchedRealmCompletionBlock)(NSError * _Nullable, RLMRealm * _Nullable, BOOL * _Nonnull); + +NS_ASSUME_NONNULL_BEGIN + +class CocoaSyncUserContext : public SyncUserContext { +public: + void register_refresh_handle(const std::string& path, RLMSyncSessionRefreshHandle *handle); + void unregister_refresh_handle(const std::string& path); + void invalidate_all_handles(); + + RLMUserErrorReportingBlock error_handler() const; + void set_error_handler(RLMUserErrorReportingBlock); + +private: + /** + A map of paths to 'refresh handles'. + + A refresh handle is an object that encapsulates the concept of periodically + refreshing the Realm's access token before it expires. Tokens are indexed by their + paths (e.g. `/~/path/to/realm`). + */ + std::unordered_map m_refresh_handles; + std::mutex m_mutex; + + /** + An optional callback invoked when the authentication server reports the user as + being in an expired state. + */ + RLMUserErrorReportingBlock m_error_handler; + mutable std::mutex m_error_handler_mutex; +}; + +@interface RLMSyncUser () +- (instancetype)initWithSyncUser:(std::shared_ptr)user; +- (NSURL *)defaultRealmURL; +- (std::shared_ptr)_syncUser; ++ (void)_setUpBindingContextFactory; +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMSyncUtil.h b/!main project/Pods/Realm/include/RLMSyncUtil.h new file mode 100644 index 0000000..222dcb7 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMSyncUtil.h @@ -0,0 +1,191 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +/// A token originating from the Realm Object Server. +typedef NSString* RLMServerToken; + +NS_ASSUME_NONNULL_BEGIN + +/// A user info key for use with `RLMSyncErrorClientResetError`. +extern NSString *const kRLMSyncPathOfRealmBackupCopyKey; + +/// A user info key for use with certain error types. +extern NSString *const kRLMSyncErrorActionTokenKey; + +/** + The error domain string for all SDK errors related to errors reported + by the synchronization manager error handler, as well as general sync + errors that don't fall into any of the other categories. + */ +extern NSString *const RLMSyncErrorDomain; + +/** + The error domain string for all SDK errors related to the authentication + endpoint. + */ +extern NSString *const RLMSyncAuthErrorDomain; + +/** + The error domain string for all SDK errors related to the permissions + system and APIs. + */ +extern NSString *const RLMSyncPermissionErrorDomain; + +/** + An error related to a problem that might be reported by the synchronization manager + error handler, or a callback on a sync-related API that performs asynchronous work. + */ +typedef RLM_ERROR_ENUM(NSInteger, RLMSyncError, RLMSyncErrorDomain) { + + /// An error that indicates a problem with the session (a specific Realm opened for sync). + RLMSyncErrorClientSessionError = 4, + + /// An error that indicates a problem with a specific user. + RLMSyncErrorClientUserError = 5, + + /** + An error that indicates an internal, unrecoverable problem + with the underlying synchronization engine. + */ + RLMSyncErrorClientInternalError = 6, + + /** + An error that indicates the Realm needs to be reset. + + A synced Realm may need to be reset because the Realm Object Server encountered an + error and had to be restored from a backup. If the backup copy of the remote Realm + is of an earlier version than the local copy of the Realm, the server will ask the + client to reset the Realm. + + The reset process is as follows: the local copy of the Realm is copied into a recovery + directory for safekeeping, and then deleted from the original location. The next time + the Realm for that URL is opened, the Realm will automatically be re-downloaded from the + Realm Object Server, and can be used as normal. + + Data written to the Realm after the local copy of the Realm diverged from the backup + remote copy will be present in the local recovery copy of the Realm file. The + re-downloaded Realm will initially contain only the data present at the time the Realm + was backed up on the server. + + The client reset process can be initiated in one of two ways. + + The `userInfo` dictionary contains an opaque token object under the key + `kRLMSyncErrorActionTokenKey`. This token can be passed into + `+[RLMSyncSession immediatelyHandleError:]` in order to immediately perform the client + reset process. This should only be done after your app closes and invalidates every + instance of the offending Realm on all threads (note that autorelease pools may make this + difficult to guarantee). + + If `+[RLMSyncSession immediatelyHandleError:]` is not called, the client reset process + will be automatically carried out the next time the app is launched and the + `RLMSyncManager` singleton is accessed. + + The value for the `kRLMSyncPathOfRealmBackupCopyKey` key in the `userInfo` dictionary + describes the path of the recovered copy of the Realm. This copy will not actually be + created until the client reset process is initiated. + + @see `-[NSError rlmSync_errorActionToken]`, `-[NSError rlmSync_clientResetBackedUpRealmPath]` + */ + RLMSyncErrorClientResetError = 7, + + /** + An error that indicates an authentication error occurred. + + The `kRLMSyncUnderlyingErrorKey` key in the user info dictionary will contain the + underlying error, which is guaranteed to be under the `RLMSyncAuthErrorDomain` + error domain. + */ + RLMSyncErrorUnderlyingAuthError = 8, + + /** + An error that indicates the user does not have permission to perform an operation + upon a synced Realm. For example, a user may receive this error if they attempt to + open a Realm they do not have at least read access to, or write to a Realm they only + have read access to. + + This error may also occur if a user incorrectly opens a Realm they have read-only + permissions to without using the `asyncOpen()` APIs. + + A Realm that suffers a permission denied error is, by default, flagged so that its + local copy will be deleted the next time the application starts. + + The `userInfo` dictionary contains an opaque token object under the key + `kRLMSyncErrorActionTokenKey`. This token can be passed into + `+[RLMSyncSession immediatelyHandleError:]` in order to immediately delete the local + copy. This should only be done after your app closes and invalidates every instance + of the offending Realm on all threads (note that autorelease pools may make this + difficult to guarantee). + + @warning It is strongly recommended that, if a Realm has encountered a permission denied + error, its files be deleted before attempting to re-open it. + + @see `-[NSError rlmSync_errorActionToken]` + */ + RLMSyncErrorPermissionDeniedError = 9, +}; + +/// An error which is related to authentication to a Realm Object Server. +typedef RLM_ERROR_ENUM(NSInteger, RLMSyncAuthError, RLMSyncAuthErrorDomain) { + /// An error that indicates that the response received from the authentication server was malformed. + RLMSyncAuthErrorBadResponse = 1, + + /// An error that indicates that the supplied Realm path was invalid, or could not be resolved by the authentication + /// server. + RLMSyncAuthErrorBadRemoteRealmPath = 2, + + /// An error that indicates that the response received from the authentication server was an HTTP error code. The + /// `userInfo` dictionary contains the actual error code value. + RLMSyncAuthErrorHTTPStatusCodeError = 3, + + /// An error that indicates a problem with the session (a specific Realm opened for sync). + RLMSyncAuthErrorClientSessionError = 4, + + /// An error that indicates that the provided credentials are ill-formed. + RLMSyncAuthErrorInvalidParameters = 601, + + /// An error that indicates that no Realm path was included in the URL. + RLMSyncAuthErrorMissingPath = 602, + + /// An error that indicates that the provided credentials are invalid. + RLMSyncAuthErrorInvalidCredential = 611, + + /// An error that indicates that the user with provided credentials does not exist. + RLMSyncAuthErrorUserDoesNotExist = 612, + + /// An error that indicates that the user cannot be registered as it exists already. + RLMSyncAuthErrorUserAlreadyExists = 613, + + /// An error that indicates the path is invalid or the user doesn't have access to that Realm. + RLMSyncAuthErrorAccessDeniedOrInvalidPath = 614, + + /// An error that indicates the refresh token was invalid. + RLMSyncAuthErrorInvalidAccessToken = 615, + + /// An error that indicates the permission offer is expired. + RLMSyncAuthErrorExpiredPermissionOffer = 701, + + /// An error that indicates the permission offer is ambiguous. + RLMSyncAuthErrorAmbiguousPermissionOffer = 702, + + /// An error that indicates the file at the given path can't be shared. + RLMSyncAuthErrorFileCannotBeShared = 703, +}; + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMSyncUtil_Private.h b/!main project/Pods/Realm/include/RLMSyncUtil_Private.h new file mode 100644 index 0000000..6b2c9c4 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMSyncUtil_Private.h @@ -0,0 +1,136 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +#import +#import +#import + +typedef NS_ENUM(NSUInteger, RLMSyncSystemErrorKind) { + // Specific + RLMSyncSystemErrorKindClientReset, + RLMSyncSystemErrorKindPermissionDenied, + // General + RLMSyncSystemErrorKindClient, + RLMSyncSystemErrorKindConnection, + RLMSyncSystemErrorKindSession, + RLMSyncSystemErrorKindUser, + RLMSyncSystemErrorKindUnknown, +}; + +@class RLMSyncUser; + +typedef void(^RLMSyncCompletionBlock)(NSError * _Nullable, NSDictionary * _Nullable); +typedef void(^RLMSyncBasicErrorReportingBlock)(NSError * _Nullable); + +typedef NSString* RLMServerPath; + +NS_ASSUME_NONNULL_BEGIN + +extern RLMIdentityProvider const RLMIdentityProviderAccessToken; +extern RLMIdentityProvider const RLMIdentityProviderRealm; +extern RLMIdentityProvider const RLMIdentityProviderCustomRefreshToken; + +extern NSString *const kRLMSyncAppIDKey; +extern NSString *const kRLMSyncDataKey; +extern NSString *const kRLMSyncErrorJSONKey; +extern NSString *const kRLMSyncErrorStatusCodeKey; +extern NSString *const kRLMSyncIdentityKey; +extern NSString *const kRLMSyncIsAdminKey; +extern NSString *const kRLMSyncNewPasswordKey; +extern NSString *const kRLMSyncPasswordKey; +extern NSString *const kRLMSyncPathKey; +extern NSString *const kRLMSyncTokenKey; +extern NSString *const kRLMSyncProviderKey; +extern NSString *const kRLMSyncProviderIDKey; +extern NSString *const kRLMSyncRegisterKey; +extern NSString *const kRLMSyncUnderlyingErrorKey; +extern NSString *const kRLMSyncUserIDKey; + +FOUNDATION_EXTERN uint8_t RLMGetComputedPermissions(RLMRealm *realm, id _Nullable object); + +#define RLM_SYNC_UNINITIALIZABLE \ +- (instancetype)init __attribute__((unavailable("This type cannot be created directly"))); \ ++ (instancetype)new __attribute__((unavailable("This type cannot be created directly"))); + +NS_ASSUME_NONNULL_END + +/// A macro to parse a string out of a JSON dictionary, or return nil. +#define RLM_SYNC_PARSE_STRING_OR_ABORT(json_macro_val, key_macro_val, prop_macro_val) \ +{ \ +id data = json_macro_val[key_macro_val]; \ +if (![data isKindOfClass:[NSString class]]) { return nil; } \ +self.prop_macro_val = data; \ +} \ + +#define RLM_SYNC_PARSE_OPTIONAL_STRING(json_macro_val, key_macro_val, prop_macro_val) \ +{ \ +id data = json_macro_val[key_macro_val]; \ +if (![data isKindOfClass:[NSString class]]) { data = nil; } \ +self.prop_macro_val = data; \ +} \ + +#define RLM_SYNC_PARSE_OPTIONAL_BOOL(json_macro_val, key_macro_val, prop_macro_val) \ +{ \ +id data = json_macro_val[key_macro_val]; \ +if (![data isKindOfClass:[NSNumber class]]) { data = @NO; } \ +self.prop_macro_val = [data boolValue]; \ +} \ + +/// A macro to parse a double out of a JSON dictionary, or return nil. +#define RLM_SYNC_PARSE_DOUBLE_OR_ABORT(json_macro_val, key_macro_val, prop_macro_val) \ +{ \ +id data = json_macro_val[key_macro_val]; \ +if (![data isKindOfClass:[NSNumber class]]) { return nil; } \ +self.prop_macro_val = [data doubleValue]; \ +} \ + +/// A macro to build a sub-model out of a JSON dictionary, or return nil. +#define RLM_SYNC_PARSE_MODEL_OR_ABORT(json_macro_val, key_macro_val, class_macro_val, prop_macro_val) \ +{ \ +id raw = json_macro_val[key_macro_val]; \ +if (![raw isKindOfClass:[NSDictionary class]]) { return nil; } \ +id model = [[class_macro_val alloc] initWithDictionary:raw]; \ +if (!model) { return nil; } \ +self.prop_macro_val = model; \ +} \ + +/// A macro to build an array of sub-models out of a JSON dictionary, or return nil. +#define RLM_SYNC_PARSE_MODEL_ARRAY_OR_ABORT(json_macro_val, key_macro_val, class_macro_val, prop_macro_val) \ +{ \ +NSArray *jsonArray = json_macro_val[key_macro_val]; \ +if (![jsonArray isKindOfClass:[NSArray class]]) { return nil; } \ +NSMutableArray *buffer = [NSMutableArray array]; \ +for (id value in jsonArray) { \ +id next = nil; \ +if ([value isKindOfClass:[NSDictionary class]]) { next = [[class_macro_val alloc] initWithDictionary:value]; } \ +if (!next) { return nil; } \ +[buffer addObject:next]; \ +} \ +self.prop_macro_val = [buffer copy]; \ +} \ + +#define RLM_SYNC_PARSE_OPTIONAL_MODEL(json_macro_val, key_macro_val, class_macro_val, prop_macro_val) \ +{ \ +id model; \ +id raw = json_macro_val[key_macro_val]; \ +if (![raw isKindOfClass:[NSDictionary class]]) { model = nil; } \ +else { model = [[class_macro_val alloc] initWithDictionary:raw]; } \ +self.prop_macro_val = model; \ +} \ diff --git a/!main project/Pods/Realm/include/RLMSyncUtil_Private.hpp b/!main project/Pods/Realm/include/RLMSyncUtil_Private.hpp new file mode 100644 index 0000000..8e622d1 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMSyncUtil_Private.hpp @@ -0,0 +1,53 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMSyncUtil_Private.h" + +#import "RLMSyncConfiguration_Private.h" +#import "RLMSyncPermission.h" + +#import "sync/sync_manager.hpp" +#import "realm/util/optional.hpp" + +@class RLMSyncErrorResponseModel; +class CocoaSyncUserContext; + +namespace realm { +enum class AccessLevel; +} + +realm::SyncSessionStopPolicy translateStopPolicy(RLMSyncStopPolicy stopPolicy); +RLMSyncStopPolicy translateStopPolicy(realm::SyncSessionStopPolicy stop_policy); + +std::shared_ptr sync_session_for_realm(RLMRealm *realm); + +#pragma mark - Get user context + +CocoaSyncUserContext& context_for(const std::shared_ptr& user); + +#pragma mark - Error construction + +NSError *make_auth_error_bad_response(NSDictionary *json=nil); +NSError *make_auth_error_http_status(NSInteger status); +NSError *make_auth_error_client_issue(); +NSError *make_auth_error(RLMSyncErrorResponseModel *responseModel); + +// Set 'code' to NSNotFound to not actually have an error code. +NSError *make_sync_error(RLMSyncSystemErrorKind kind, NSString *description, NSInteger code, NSDictionary *custom); +NSError *make_sync_error(NSError *wrapped_auth_error); +NSError *make_sync_error(std::error_code, RLMSyncSystemErrorKind kind=RLMSyncSystemErrorKindSession); diff --git a/!main project/Pods/Realm/include/RLMThreadSafeReference.h b/!main project/Pods/Realm/include/RLMThreadSafeReference.h new file mode 100644 index 0000000..a935ebe --- /dev/null +++ b/!main project/Pods/Realm/include/RLMThreadSafeReference.h @@ -0,0 +1,106 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +@class RLMRealm; + +NS_ASSUME_NONNULL_BEGIN + +/** + Objects of types which conform to `RLMThreadConfined` can be managed by a Realm, which will make + them bound to a thread-specific `RLMRealm` instance. Managed objects must be explicitly exported + and imported to be passed between threads. + + Managed instances of objects conforming to this protocol can be converted to a thread-safe + reference for transport between threads by passing to the + `+[RLMThreadSafeReference referenceWithThreadConfined:]` constructor. + + Note that only types defined by Realm can meaningfully conform to this protocol, and defining new + classes which attempt to conform to it will not make them work with `RLMThreadSafeReference`. + */ +@protocol RLMThreadConfined +// Conformance to the `RLMThreadConfined_Private` protocol will be enforced at runtime. + +/** + The Realm which manages the object, or `nil` if the object is unmanaged. + + Unmanaged objects are not confined to a thread and cannot be passed to methods expecting a + `RLMThreadConfined` object. + */ +@property (nonatomic, readonly, nullable) RLMRealm *realm; + +/// Indicates if the object can no longer be accessed because it is now invalid. +@property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated; + +@end + +/** + An object intended to be passed between threads containing a thread-safe reference to its + thread-confined object. + + To resolve a thread-safe reference on a target Realm on a different thread, pass to + `-[RLMRealm resolveThreadSafeReference:]`. + + @warning A `RLMThreadSafeReference` object must be resolved at most once. + Failing to resolve a `RLMThreadSafeReference` will result in the source version of the + Realm being pinned until the reference is deallocated. + + @note Prefer short-lived `RLMThreadSafeReference`s as the data for the version of the source Realm + will be retained until all references have been resolved or deallocated. + + @see `RLMThreadConfined` + @see `-[RLMRealm resolveThreadSafeReference:]` + */ +@interface RLMThreadSafeReference<__covariant Confined : id> : NSObject + +/** + Create a thread-safe reference to the thread-confined object. + + @param threadConfined The thread-confined object to create a thread-safe reference to. + + @note You may continue to use and access the thread-confined object after passing it to this + constructor. + */ ++ (instancetype)referenceWithThreadConfined:(Confined)threadConfined; + +/** + Indicates if the reference can no longer be resolved because an attempt to resolve it has already + occurred. References can only be resolved once. + */ +@property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated; + +#pragma mark - Unavailable Methods + +/** + `-[RLMThreadSafeReference init]` is not available because `RLMThreadSafeReference` cannot be + created directly. `RLMThreadSafeReference` instances must be obtained by calling + `-[RLMRealm resolveThreadSafeReference:]`. + */ +- (instancetype)init __attribute__((unavailable("RLMThreadSafeReference cannot be created directly"))); + +/** + `-[RLMThreadSafeReference new]` is not available because `RLMThreadSafeReference` cannot be + created directly. `RLMThreadSafeReference` instances must be obtained by calling + `-[RLMRealm resolveThreadSafeReference:]`. + */ ++ (instancetype)new __attribute__((unavailable("RLMThreadSafeReference cannot be created directly"))); + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMThreadSafeReference_Private.hpp b/!main project/Pods/Realm/include/RLMThreadSafeReference_Private.hpp new file mode 100644 index 0000000..5cf78ed --- /dev/null +++ b/!main project/Pods/Realm/include/RLMThreadSafeReference_Private.hpp @@ -0,0 +1,44 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import "RLMThreadSafeReference.h" +#import "thread_safe_reference.hpp" + +NS_ASSUME_NONNULL_BEGIN + +@protocol RLMThreadConfined_Private + +// Constructs a new `ThreadSafeReference` +- (std::unique_ptr)makeThreadSafeReference; + +// The extra information needed to construct an instance of this type from the Object Store type +@property (nonatomic, readonly, nullable) id objectiveCMetadata; + +// Constructs an new instance of this type ++ (nullable instancetype)objectWithThreadSafeReference:(std::unique_ptr)reference + metadata:(nullable id)metadata + realm:(RLMRealm *)realm; +@end + +@interface RLMThreadSafeReference () + +- (nullable id)resolveReferenceInRealm:(RLMRealm *)realm; + +@end + +NS_ASSUME_NONNULL_END diff --git a/!main project/Pods/Realm/include/RLMUpdateChecker.hpp b/!main project/Pods/Realm/include/RLMUpdateChecker.hpp new file mode 100644 index 0000000..7f01ac7 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMUpdateChecker.hpp @@ -0,0 +1,20 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +// Asynchronously check for updates to Realm if running on a simulator +void RLMCheckForUpdates(); diff --git a/!main project/Pods/Realm/include/RLMUtil.hpp b/!main project/Pods/Realm/include/RLMUtil.hpp new file mode 100644 index 0000000..6e72dd5 --- /dev/null +++ b/!main project/Pods/Realm/include/RLMUtil.hpp @@ -0,0 +1,182 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import +#import +#import + +#import +#import +#import +#import +#import + +namespace realm { + class Mixed; +} + +@class RLMObjectSchema; +@class RLMProperty; + +namespace realm { + class RealmFileException; +} + +__attribute__((format(NSString, 1, 2))) +NSException *RLMException(NSString *fmt, ...); +NSException *RLMException(std::exception const& exception); + +NSError *RLMMakeError(RLMError code, std::exception const& exception); +NSError *RLMMakeError(RLMError code, const realm::util::File::AccessError&); +NSError *RLMMakeError(RLMError code, const realm::RealmFileException&); +NSError *RLMMakeError(std::system_error const& exception); + +void RLMSetErrorOrThrow(NSError *error, NSError **outError); + +// returns if the object can be inserted as the given type +BOOL RLMIsObjectValidForProperty(id obj, RLMProperty *prop); +// throw an exception if the object is not a valid value for the property +void RLMValidateValueForProperty(id obj, RLMObjectSchema *objectSchema, + RLMProperty *prop, bool validateObjects=false); +BOOL RLMValidateValue(id value, RLMPropertyType type, bool optional, bool array, + NSString *objectClassName); + +void RLMThrowTypeError(id obj, RLMObjectSchema *objectSchema, RLMProperty *prop); + +// gets default values for the given schema (+defaultPropertyValues) +// merges with native property defaults if Swift class +NSDictionary *RLMDefaultValuesForObjectSchema(RLMObjectSchema *objectSchema); + +BOOL RLMIsDebuggerAttached(); +BOOL RLMIsRunningInPlayground(); + +// C version of isKindOfClass +static inline BOOL RLMIsKindOfClass(Class class1, Class class2) { + while (class1) { + if (class1 == class2) return YES; + class1 = class_getSuperclass(class1); + } + return NO; +} + +template +static inline T *RLMDynamicCast(__unsafe_unretained id obj) { + if ([obj isKindOfClass:[T class]]) { + return obj; + } + return nil; +} + +static inline id RLMCoerceToNil(__unsafe_unretained id obj) { + if (static_cast(obj) == NSNull.null) { + return nil; + } + else if (__unsafe_unretained auto optional = RLMDynamicCast(obj)) { + return RLMCoerceToNil(RLMGetOptional(optional)); + } + return obj; +} + +template +static inline T RLMCoerceToNil(__unsafe_unretained T obj) { + return RLMCoerceToNil(static_cast(obj)); +} + +id RLMAsFastEnumeration(id obj); + +// String conversion utilities +static inline NSString * RLMStringDataToNSString(realm::StringData stringData) { + static_assert(sizeof(NSUInteger) >= sizeof(size_t), + "Need runtime overflow check for size_t to NSUInteger conversion"); + if (stringData.is_null()) { + return nil; + } + else { + return [[NSString alloc] initWithBytes:stringData.data() + length:stringData.size() + encoding:NSUTF8StringEncoding]; + } +} + +static inline realm::StringData RLMStringDataWithNSString(__unsafe_unretained NSString *const string) { + static_assert(sizeof(size_t) >= sizeof(NSUInteger), + "Need runtime overflow check for NSUInteger to size_t conversion"); + return realm::StringData(string.UTF8String, + [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]); +} + +// Binary conversion utilities +static inline NSData *RLMBinaryDataToNSData(realm::BinaryData binaryData) { + return binaryData ? [NSData dataWithBytes:binaryData.data() length:binaryData.size()] : nil; +} + +static inline realm::BinaryData RLMBinaryDataForNSData(__unsafe_unretained NSData *const data) { + // this is necessary to ensure that the empty NSData isn't treated by core as the null realm::BinaryData + // because data.bytes == 0 when data.length == 0 + // the casting bit ensures that we create a data with a non-null pointer + auto bytes = static_cast(data.bytes) ?: static_cast((__bridge void *)data); + return realm::BinaryData(bytes, data.length); +} + +// Date conversion utilities +// These use the reference date and shift the seconds rather than just getting +// the time interval since the epoch directly to avoid losing sub-second precision +static inline NSDate *RLMTimestampToNSDate(realm::Timestamp ts) NS_RETURNS_RETAINED { + if (ts.is_null()) + return nil; + auto timeInterval = ts.get_seconds() - NSTimeIntervalSince1970 + ts.get_nanoseconds() / 1'000'000'000.0; + return [[NSDate alloc] initWithTimeIntervalSinceReferenceDate:timeInterval]; +} + +static inline realm::Timestamp RLMTimestampForNSDate(__unsafe_unretained NSDate *const date) { + if (!date) + return {}; + auto timeInterval = date.timeIntervalSinceReferenceDate; + if (isnan(timeInterval)) + return {0, 0}; // Arbitrary choice + + // Clamp dates that we can't represent as a Timestamp to the maximum value + if (timeInterval >= std::numeric_limits::max() - NSTimeIntervalSince1970) + return {std::numeric_limits::max(), 1'000'000'000 - 1}; + if (timeInterval - NSTimeIntervalSince1970 < std::numeric_limits::min()) + return {std::numeric_limits::min(), -1'000'000'000 + 1}; + + auto seconds = static_cast(timeInterval); + auto nanoseconds = static_cast((timeInterval - seconds) * 1'000'000'000.0); + seconds += static_cast(NSTimeIntervalSince1970); + + // Seconds and nanoseconds have to have the same sign + if (nanoseconds < 0 && seconds > 0) { + nanoseconds += 1'000'000'000; + --seconds; + } + return {seconds, nanoseconds}; +} + +static inline NSUInteger RLMConvertNotFound(size_t index) { + return index == realm::not_found ? NSNotFound : index; +} + +id RLMMixedToObjc(realm::Mixed const& value); + +// Given a bundle identifier, return the base directory on the disk within which Realm database and support files should +// be stored. +NSString *RLMDefaultDirectoryForBundleIdentifier(NSString *bundleIdentifier); + +// Get a NSDateFormatter for ISO8601-formatted strings +NSDateFormatter *RLMISO8601Formatter(); diff --git a/!main project/Pods/Realm/include/Realm.h b/!main project/Pods/Realm/include/Realm.h new file mode 100644 index 0000000..f460336 --- /dev/null +++ b/!main project/Pods/Realm/include/Realm.h @@ -0,0 +1,41 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#import + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import diff --git a/!main project/Pods/Realm/include/audit.hpp b/!main project/Pods/Realm/include/audit.hpp new file mode 100644 index 0000000..81ea76b --- /dev/null +++ b/!main project/Pods/Realm/include/audit.hpp @@ -0,0 +1,34 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2018 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +namespace realm { +class Table; +class TableView; +template class BasicRowExpr; +using RowExpr = BasicRowExpr
; +struct VersionID; + +class AuditInterface { +public: + virtual ~AuditInterface() {} + + virtual void record_query(realm::VersionID, realm::TableView const&) = 0; + virtual void record_read(realm::VersionID, realm::RowExpr) = 0; + virtual void record_write(realm::VersionID, realm::VersionID) = 0; +}; +} diff --git a/!main project/Pods/Realm/include/binding_callback_thread_observer.hpp b/!main project/Pods/Realm/include/binding_callback_thread_observer.hpp new file mode 100644 index 0000000..e956349 --- /dev/null +++ b/!main project/Pods/Realm/include/binding_callback_thread_observer.hpp @@ -0,0 +1,42 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2017 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_OS_BINDING_CALLBACK_THREAD_OBSERVER_HPP +#define REALM_OS_BINDING_CALLBACK_THREAD_OBSERVER_HPP + +#include + +namespace realm { +// Interface for bindings interested in registering callbacks before/after the ObjectStore thread runs. +// This is for example helpful to attach/detach the pthread to the JavaVM in order to be able to perform JNI calls. +class BindingCallbackThreadObserver { +public: + // This method is called just before the thread is started + virtual void did_create_thread() = 0; + + // This method is called just before the thread is being destroyed + virtual void will_destroy_thread() = 0; + + // This method is called with any exception throws by client.run(). + virtual void handle_error(std::exception const& e) = 0; +}; + +extern BindingCallbackThreadObserver* g_binding_callback_thread_observer; +} + +#endif // REALM_OS_BINDING_CALLBACK_THREAD_OBSERVER_HPP diff --git a/!main project/Pods/Realm/include/binding_context.hpp b/!main project/Pods/Realm/include/binding_context.hpp new file mode 100644 index 0000000..8d7532c --- /dev/null +++ b/!main project/Pods/Realm/include/binding_context.hpp @@ -0,0 +1,185 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef BINDING_CONTEXT_HPP +#define BINDING_CONTEXT_HPP + +#include "index_set.hpp" + +#include +#include +#include + +namespace realm { +// BindingContext is the extension point for adding binding-specific behavior to +// a SharedRealm. It can be used to store additional data associated with the +// Realm which is needed by the binding, and there are several methods which +// can be overridden to receive notifications of state changes within the Realm. +// +// A simple implementation which lets the user register functions to be +// called on refresh could look like the following: +// +// class BindingContextImplementation : public BindingContext { +// public: +// // A token returned from add_notification that can be used to remove the +// // notification later +// struct token : private std::list>::iterator { +// token(std::list>::iterator it) : std::list>::iterator(it) { } +// friend class DelegateImplementation; +// }; +// +// token add_notification(std::function func) +// { +// m_registered_notifications.push_back(std::move(func)); +// return token(std::prev(m_registered_notifications.end())); +// } +// +// void remove_notification(token entry) +// { +// m_registered_notifications.erase(entry); +// } +// +// // Override the did_change method to call each registered notification +// void did_change(std::vector const&, std::vector const&, bool) override +// { +// // Loop oddly so that unregistering a notification from within the +// // registered function works +// for (auto it = m_registered_notifications.begin(); it != m_registered_notifications.end(); ) { +// (*it++)(); +// } +// } +// +// private: +// std::list> m_registered_notifications; +// }; +class Realm; +class Schema; +class BindingContext { +public: + virtual ~BindingContext() = default; + + std::weak_ptr realm; + + // If the user adds a notification handler to the Realm, will it ever + // actually be called? + virtual bool can_deliver_notifications() const noexcept { return true; } + + // Called when the Realm is about to send notifications about Realm, + // Collection or Object changes. This method will be called even if + // no notification callbacks have been registered. + virtual void will_send_notifications() { } + + // Called when the Realm is done sending all change notifications. This method + // will be called even if no notification callbacks have been registered. + virtual void did_send_notifications() { } + + // Called by the Realm when refresh called or a notification arrives which + // is triggered through write transaction committed by itself or a different + // Realm instance. + virtual void before_notify() { } + + // Called by the Realm when a write transaction is committed to the file by + // a different Realm instance (possibly in a different process) + virtual void changes_available() { } + + struct ObserverState; + + // Override this function if you want to receive detailed information about + // external changes to a specific set of objects. + // This is called before each operation which may advance the read + // transaction to include + // ObserverStates for each row for which detailed change information is + // desired. + virtual std::vector get_observed_rows() { return {}; } + + // Called immediately before the read transaction is advanced if detailed + // change information was requested (by returning a non-empty array from + // get_observed_rows()). + // The observers vector is the vector returned by get_observed_row(), + // updated with change information. The invalidated vector is a list of the + // `info` fields of observed rows which will be deleted. + virtual void will_change(std::vector const& observers, + std::vector const& invalidated); + + // Called immediately after the read transaction version is advanced. Unlike + // will_change(), this is called even if detailed change information was not + // requested or if the Realm is not actually in a read transaction, although + // both vectors will be empty in that case. + virtual void did_change(std::vector const& observers, + std::vector const& invalidated, + bool version_changed=true); + + // Called immediately after the corresponding Realm's schema is changed through + // update_schema()/set_schema_subset() or the schema is changed by another Realm + // instance. The parameter is a schema reference which is the same as the return + // value of Realm::schema(). + virtual void schema_did_change(Schema const&) {} + + // Change information for a single field of a row + struct ColumnInfo { + // The index of this column prior to the changes in the tracked + // transaction, or -1 for newly inserted columns. + size_t initial_column_index = -1; + // What kind of change occurred? + // Always Set or None for everything but LinkList columns. + enum class Kind { + None, // No change + Set, // The value or entries at `indices` were assigned to + Insert, // New values were inserted at each of the indices given + Remove, // Values were removed at each of the indices given + SetAll // The entire LinkList has been replaced with a new set of values + } kind = Kind::None; + // The indices where things happened for Set, Insert and Remove on + // LinkList columns. Not used for other types or for None or SetAll. + IndexSet indices; + }; + + // Information about an observed row in a table + // + // Each object which needs detailed change information should have an + // ObserverState entry in the vector returned from get_observed_rows(), with + // the initial table and row indexes set (and optionally the info field). + // The Realm parses the transaction log, and populates the `changes` vector + // in each ObserverState with information about what changes were made. + struct ObserverState { + // Initial table and row which is observed + // May be updated by row insertions and removals + size_t table_ndx; + size_t row_ndx; + + // Opaque userdata for the delegate's use + void* info; + + // Populated with information about which columns were changed + // May be shorter than the actual number of columns if the later columns + // are not modified + std::vector changes; + + // Simple lexographic ordering + friend bool operator<(ObserverState const& lft, ObserverState const& rgt) + { + return std::tie(lft.table_ndx, lft.row_ndx) < std::tie(rgt.table_ndx, rgt.row_ndx); + } + }; +}; + +inline void BindingContext::will_change(std::vector const&, std::vector const&) { } +inline void BindingContext::did_change(std::vector const&, std::vector const&, bool) { } +} // namespace realm + +#endif /* BINDING_CONTEXT_HPP */ diff --git a/!main project/Pods/Realm/include/collection_notifications.hpp b/!main project/Pods/Realm/include/collection_notifications.hpp new file mode 100644 index 0000000..bbd41e5 --- /dev/null +++ b/!main project/Pods/Realm/include/collection_notifications.hpp @@ -0,0 +1,182 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_COLLECTION_NOTIFICATIONS_HPP +#define REALM_COLLECTION_NOTIFICATIONS_HPP + +#include "index_set.hpp" +#include "util/atomic_shared_ptr.hpp" + +#include +#include +#include +#include + +namespace realm { +namespace _impl { + class CollectionNotifier; +} + +// A token which keeps an asynchronous query alive +struct NotificationToken { + NotificationToken() = default; + NotificationToken(std::shared_ptr<_impl::CollectionNotifier> notifier, uint64_t token); + ~NotificationToken(); + + NotificationToken(NotificationToken&&); + NotificationToken& operator=(NotificationToken&&); + + NotificationToken(NotificationToken const&) = delete; + NotificationToken& operator=(NotificationToken const&) = delete; + + void suppress_next(); + +private: + util::AtomicSharedPtr<_impl::CollectionNotifier> m_notifier; + uint64_t m_token; +}; + +struct CollectionChangeSet { + struct Move { + size_t from; + size_t to; + + bool operator==(Move m) const noexcept { return from == m.from && to == m.to; } + }; + + // Indices which were removed from the _old_ collection + IndexSet deletions; + + // Indices in the _new_ collection which are new insertions + IndexSet insertions; + + // Indices of objects in the _old_ collection which were modified + IndexSet modifications; + + // Indices in the _new_ collection which were modified. This will always + // have the same number of indices as `modifications` and conceptually + // represents the same entries, just in different versions of the collection. + // It exists for the sake of code which finds it easier to process + // modifications after processing deletions and insertions rather than before. + IndexSet modifications_new; + + // Rows in the collection which moved. + // + // Every `from` index will also be present in `deletions` and every `to` + // index will be present in `insertions`. + // + // This is currently not reliably calculated for all types of collections. A + // reported move will always actually be a move, but there may also be + // unreported moves which show up only as a delete/insert pair. + std::vector moves; + + // Per-column version of `modifications` + std::vector columns; + + bool empty() const noexcept + { + return deletions.empty() && insertions.empty() && modifications.empty() + && modifications_new.empty() && moves.empty(); + } +}; + +// A type-erasing wrapper for the callback for collection notifications. Can be +// constructed with either any callable compatible with the signature +// `void (CollectionChangeSet, std::exception_ptr)`, an object with member +// functions `void before(CollectionChangeSet)`, `void after(CollectionChangeSet)`, +// `void error(std::exception_ptr)`, or a pointer to such an object. If a pointer +// is given, the caller is responsible for ensuring that the pointed-to object +// outlives the collection. +class CollectionChangeCallback { +public: + CollectionChangeCallback(std::nullptr_t={}) { } + + template + CollectionChangeCallback(Callback cb) : m_impl(make_impl(std::move(cb))) { } + template + CollectionChangeCallback& operator=(Callback cb) { m_impl = make_impl(std::move(cb)); return *this; } + + // Explicitly default the copy/move constructors as otherwise they'll use + // the above ones and add an extra layer of wrapping + CollectionChangeCallback(CollectionChangeCallback&&) = default; + CollectionChangeCallback(CollectionChangeCallback const&) = default; + CollectionChangeCallback& operator=(CollectionChangeCallback&&) = default; + CollectionChangeCallback& operator=(CollectionChangeCallback const&) = default; + + void before(CollectionChangeSet const& c) { m_impl->before(c); } + void after(CollectionChangeSet const& c) { m_impl->after(c); } + void error(std::exception_ptr e) { m_impl->error(e); } + + explicit operator bool() const { return !!m_impl; } + +private: + struct Base { + virtual ~Base() {} + virtual void before(CollectionChangeSet const&)=0; + virtual void after(CollectionChangeSet const&)=0; + virtual void error(std::exception_ptr)=0; + }; + + template()(CollectionChangeSet(), std::exception_ptr()))> + std::shared_ptr make_impl(Callback cb) + { + return std::make_shared>(std::move(cb)); + } + + template().after(CollectionChangeSet())), typename = void> + std::shared_ptr make_impl(Callback cb) + { + return std::make_shared>(std::move(cb)); + } + + template().after(CollectionChangeSet())), typename = void> + std::shared_ptr make_impl(Callback* cb) + { + return std::make_shared>(cb); + } + + template + struct Impl : public Base { + T impl; + Impl(T impl) : impl(std::move(impl)) { } + void before(CollectionChangeSet const&) override { } + void after(CollectionChangeSet const& change) override { impl(change, {}); } + void error(std::exception_ptr error) override { impl({}, error); } + }; + template + struct Impl2 : public Base { + T impl; + Impl2(T impl) : impl(std::move(impl)) { } + void before(CollectionChangeSet const& c) override { impl.before(c); } + void after(CollectionChangeSet const& c) override { impl.after(c); } + void error(std::exception_ptr error) override { impl.error(error); } + }; + template + struct Impl3 : public Base { + T* impl; + Impl3(T* impl) : impl(impl) { } + void before(CollectionChangeSet const& c) override { impl->before(c); } + void after(CollectionChangeSet const& c) override { impl->after(c); } + void error(std::exception_ptr error) override { impl->error(error); } + }; + + std::shared_ptr m_impl; +}; +} // namespace realm + +#endif // REALM_COLLECTION_NOTIFICATIONS_HPP diff --git a/!main project/Pods/Realm/include/core/realm.hpp b/!main project/Pods/Realm/include/core/realm.hpp new file mode 100644 index 0000000..7ede53f --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm.hpp @@ -0,0 +1,30 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_HPP +#define REALM_HPP + +#include +#include +#include +#include +#include +#include +#include + +#endif // REALM_HPP diff --git a/!main project/Pods/Realm/include/core/realm/alloc.hpp b/!main project/Pods/Realm/include/core/realm/alloc.hpp new file mode 100644 index 0000000..157d244 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/alloc.hpp @@ -0,0 +1,387 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_ALLOC_HPP +#define REALM_ALLOC_HPP + +#include +#include +#include + +#include +#include +#include + +namespace realm { + +class Allocator; + +class Replication; + +using ref_type = size_t; + +int_fast64_t from_ref(ref_type) noexcept; +ref_type to_ref(int_fast64_t) noexcept; +int64_t to_int64(size_t value) noexcept; + +class MemRef { +public: + MemRef() noexcept; + ~MemRef() noexcept; + + MemRef(char* addr, ref_type ref, Allocator& alloc) noexcept; + MemRef(ref_type ref, Allocator& alloc) noexcept; + + char* get_addr(); + ref_type get_ref(); + void set_ref(ref_type ref); + void set_addr(char* addr); + +private: + char* m_addr; + ref_type m_ref; +#if REALM_ENABLE_MEMDEBUG + // Allocator that created m_ref. Used to verify that the ref is valid whenever you call + // get_ref()/get_addr and that it e.g. has not been free'ed + const Allocator* m_alloc = nullptr; +#endif +}; + + +/// The common interface for Realm allocators. +/// +/// A Realm allocator must associate a 'ref' to each allocated +/// object and be able to efficiently map any 'ref' to the +/// corresponding memory address. The 'ref' is an integer and it must +/// always be divisible by 8. Also, a value of zero is used to +/// indicate a null-reference, and must therefore never be returned by +/// Allocator::alloc(). +/// +/// The purpose of the 'refs' is to decouple the memory reference from +/// the actual address and thereby allowing objects to be relocated in +/// memory without having to modify stored references. +/// +/// \sa SlabAlloc +class Allocator { +public: + /// The specified size must be divisible by 8, and must not be + /// zero. + /// + /// \throw std::bad_alloc If insufficient memory was available. + MemRef alloc(size_t size); + + /// Calls do_realloc(). + /// + /// Note: The underscore has been added because the name `realloc` + /// would conflict with a macro on the Windows platform. + MemRef realloc_(ref_type, const char* addr, size_t old_size, size_t new_size); + + /// Calls do_free(). + /// + /// Note: The underscore has been added because the name `free + /// would conflict with a macro on the Windows platform. + void free_(ref_type, const char* addr) noexcept; + + /// Shorthand for free_(mem.get_ref(), mem.get_addr()). + void free_(MemRef mem) noexcept; + + /// Calls do_translate(). + char* translate(ref_type ref) const noexcept; + + /// Returns true if, and only if the object at the specified 'ref' + /// is in the immutable part of the memory managed by this + /// allocator. The method by which some objects become part of the + /// immuatble part is entirely up to the class that implements + /// this interface. + bool is_read_only(ref_type) const noexcept; + + /// Returns a simple allocator that can be used with free-standing + /// Realm objects (such as a free-standing table). A + /// free-standing object is one that is not part of a Group, and + /// therefore, is not part of an actual database. + static Allocator& get_default() noexcept; + + virtual ~Allocator() noexcept; + + // Disable copying. Copying an allocator can produce double frees. + Allocator(const Allocator&) = delete; + Allocator& operator=(const Allocator&) = delete; + + virtual void verify() const = 0; + +#ifdef REALM_DEBUG + /// Terminate the program precisely when the specified 'ref' is + /// freed (or reallocated). You can use this to detect whether the + /// ref is freed (or reallocated), and even to get a stacktrace at + /// the point where it happens. Call watch(0) to stop watching + /// that ref. + void watch(ref_type ref) + { + m_debug_watch = ref; + } +#endif + + Replication* get_replication() noexcept; + +protected: + size_t m_baseline = 0; // Separation line between immutable and mutable refs. + + Replication* m_replication = nullptr; + + ref_type m_debug_watch = 0; + + /// The specified size must be divisible by 8, and must not be + /// zero. + /// + /// \throw std::bad_alloc If insufficient memory was available. + virtual MemRef do_alloc(const size_t size) = 0; + + /// The specified size must be divisible by 8, and must not be + /// zero. + /// + /// The default version of this function simply allocates a new + /// chunk of memory, copies over the old contents, and then frees + /// the old chunk. + /// + /// \throw std::bad_alloc If insufficient memory was available. + virtual MemRef do_realloc(ref_type, char* addr, size_t old_size, size_t new_size) = 0; + + /// Release the specified chunk of memory. + virtual void do_free(ref_type, char* addr) noexcept = 0; + + /// Map the specified \a ref to the corresponding memory + /// address. Note that if is_read_only(ref) returns true, then the + /// referenced object is to be considered immutable, and it is + /// then entirely the responsibility of the caller that the memory + /// is not modified by way of the returned memory pointer. + virtual char* do_translate(ref_type ref) const noexcept = 0; + + Allocator() noexcept; + + // FIXME: This really doesn't belong in an allocator, but it is the best + // place for now, because every table has a pointer leading here. It would + // be more obvious to place it in Group, but that would add a runtime overhead, + // and access is time critical. + // + // This means that multiple threads that allocate Realm objects through the + // default allocator will share this variable, which is a logical design flaw + // that can make sync_if_needed() re-run queries even though it is not required. + // It must be atomic because it's shared. + std::atomic m_table_versioning_counter; + std::atomic m_latest_observed_counter; + + /// Bump the global version counter. This method should be called when + /// version bumping is initiated. Then following calls to should_propagate_version() + /// can be used to prune the version bumping. + void bump_global_version() noexcept; + + /// Determine if the "local_version" is out of sync, so that it should + /// be updated. In that case: also update it. Called from Table::bump_version + /// to control propagation of version updates on tables within the group. + bool should_propagate_version(uint_fast64_t& local_version) noexcept; + + /// Note the current global version has been observed. + void observe_version() noexcept; + + friend class Table; + friend class Group; +}; + +inline void Allocator::bump_global_version() noexcept +{ + if (m_latest_observed_counter == m_table_versioning_counter) + m_table_versioning_counter += 1; +} + + +inline void Allocator::observe_version() noexcept +{ + if (m_latest_observed_counter != m_table_versioning_counter) + m_latest_observed_counter.store(m_table_versioning_counter, std::memory_order_relaxed); +} + + +inline bool Allocator::should_propagate_version(uint_fast64_t& local_version) noexcept +{ + if (local_version != m_table_versioning_counter) { + local_version = m_table_versioning_counter; + return true; + } + else { + return false; + } +} + + +// Implementation: + +inline int_fast64_t from_ref(ref_type v) noexcept +{ + // Check that v is divisible by 8 (64-bit aligned). + REALM_ASSERT_DEBUG(v % 8 == 0); + + static_assert(std::is_same::value, + "If ref_type changes, from_ref and to_ref should probably be updated"); + + // Make sure that we preserve the bit pattern of the ref_type (without sign extension). + return util::from_twos_compl(uint_fast64_t(v)); +} + +inline ref_type to_ref(int_fast64_t v) noexcept +{ + // Check that v is divisible by 8 (64-bit aligned). + REALM_ASSERT_DEBUG(v % 8 == 0); + + // C++11 standard, paragraph 4.7.2 [conv.integral]: + // If the destination type is unsigned, the resulting value is the least unsigned integer congruent to the source + // integer (modulo 2n where n is the number of bits used to represent the unsigned type). [ Note: In a two's + // complement representation, this conversion is conceptual and there is no change in the bit pattern (if there is + // no truncation). - end note ] + static_assert(std::is_unsigned::value, + "If ref_type changes, from_ref and to_ref should probably be updated"); + return ref_type(v); +} + +inline int64_t to_int64(size_t value) noexcept +{ + // FIXME: Enable once we get clang warning flags correct + // REALM_ASSERT_DEBUG(value <= std::numeric_limits::max()); + return static_cast(value); +} + + +inline MemRef::MemRef() noexcept + : m_addr(nullptr) + , m_ref(0) +{ +} + +inline MemRef::~MemRef() noexcept +{ +} + +inline MemRef::MemRef(char* addr, ref_type ref, Allocator& alloc) noexcept + : m_addr(addr) + , m_ref(ref) +{ + static_cast(alloc); +#if REALM_ENABLE_MEMDEBUG + m_alloc = &alloc; +#endif +} + +inline MemRef::MemRef(ref_type ref, Allocator& alloc) noexcept + : m_addr(alloc.translate(ref)) + , m_ref(ref) +{ + static_cast(alloc); +#if REALM_ENABLE_MEMDEBUG + m_alloc = &alloc; +#endif +} + +inline char* MemRef::get_addr() +{ +#if REALM_ENABLE_MEMDEBUG + // Asserts if the ref has been freed + m_alloc->translate(m_ref); +#endif + return m_addr; +} + +inline ref_type MemRef::get_ref() +{ +#if REALM_ENABLE_MEMDEBUG + // Asserts if the ref has been freed + m_alloc->translate(m_ref); +#endif + return m_ref; +} + +inline void MemRef::set_ref(ref_type ref) +{ +#if REALM_ENABLE_MEMDEBUG + // Asserts if the ref has been freed + m_alloc->translate(ref); +#endif + m_ref = ref; +} + +inline void MemRef::set_addr(char* addr) +{ + m_addr = addr; +} + +inline MemRef Allocator::alloc(size_t size) +{ + return do_alloc(size); +} + +inline MemRef Allocator::realloc_(ref_type ref, const char* addr, size_t old_size, size_t new_size) +{ +#ifdef REALM_DEBUG + if (ref == m_debug_watch) + REALM_TERMINATE("Allocator watch: Ref was reallocated"); +#endif + return do_realloc(ref, const_cast(addr), old_size, new_size); +} + +inline void Allocator::free_(ref_type ref, const char* addr) noexcept +{ +#ifdef REALM_DEBUG + if (ref == m_debug_watch) + REALM_TERMINATE("Allocator watch: Ref was freed"); +#endif + return do_free(ref, const_cast(addr)); +} + +inline void Allocator::free_(MemRef mem) noexcept +{ + free_(mem.get_ref(), mem.get_addr()); +} + +inline char* Allocator::translate(ref_type ref) const noexcept +{ + return do_translate(ref); +} + +inline bool Allocator::is_read_only(ref_type ref) const noexcept +{ + REALM_ASSERT_DEBUG(ref != 0); + REALM_ASSERT_DEBUG(m_baseline != 0); // Attached SlabAlloc + return ref < m_baseline; +} + +inline Allocator::Allocator() noexcept +{ + m_table_versioning_counter = 0; + m_latest_observed_counter = 0; +} + +inline Allocator::~Allocator() noexcept +{ +} + +inline Replication* Allocator::get_replication() noexcept +{ + return m_replication; +} + +} // namespace realm + +#endif // REALM_ALLOC_HPP diff --git a/!main project/Pods/Realm/include/core/realm/alloc_slab.hpp b/!main project/Pods/Realm/include/core/realm/alloc_slab.hpp new file mode 100644 index 0000000..e07edea --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/alloc_slab.hpp @@ -0,0 +1,745 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_ALLOC_SLAB_HPP +#define REALM_ALLOC_SLAB_HPP + +#include // unint8_t etc +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace realm { + +// Pre-declarations +class Group; +class GroupWriter; + + +/// Thrown by Group and SharedGroup constructors if the specified file +/// (or memory buffer) does not appear to contain a valid Realm +/// database. +struct InvalidDatabase; + + +/// The allocator that is used to manage the memory of a Realm +/// group, i.e., a Realm database. +/// +/// Optionally, it can be attached to an pre-existing database (file +/// or memory buffer) which then becomes an immuatble part of the +/// managed memory. +/// +/// To attach a slab allocator to a pre-existing database, call +/// attach_file() or attach_buffer(). To create a new database +/// in-memory, call attach_empty(). +/// +/// For efficiency, this allocator manages its mutable memory as a set +/// of slabs. +class SlabAlloc : public Allocator { +public: + ~SlabAlloc() noexcept override; + SlabAlloc(); + + // Disable copying. Copying an allocator can produce double frees. + SlabAlloc(const SlabAlloc&) = delete; + SlabAlloc& operator=(const SlabAlloc&) = delete; + + /// \struct Config + /// \brief Storage for combining setup flags for initialization to + /// the SlabAlloc. + /// + /// \var Config::is_shared + /// Must be true if, and only if we are called on behalf of SharedGroup. + /// + /// \var Config::read_only + /// Open the file in read-only mode. This implies \a Config::no_create. + /// + /// \var Config::no_create + /// Fail if the file does not already exist. + /// + /// \var Config::skip_validate + /// Skip validation of file header. In a + /// set of overlapping SharedGroups, only the first one (the one + /// that creates/initlializes the coordination file) may validate + /// the header, otherwise it will result in a race condition. + /// + /// \var Config::encryption_key + /// 32-byte key to use to encrypt and decrypt the backing storage, + /// or nullptr to disable encryption. + /// + /// \var Config::session_initiator + /// If set, the caller is the session initiator and + /// guarantees exclusive access to the file. If attaching in + /// read/write mode, the file is modified: files on streaming form + /// is changed to non-streaming form, and if needed the file size + /// is adjusted to match mmap boundaries. + /// Must be set to false if is_shared is false. + /// + /// \var Config::clear_file + /// Always initialize the file as if it was a newly + /// created file and ignore any pre-existing contents. Requires that + /// Config::session_initiator be true as well. + struct Config { + bool is_shared = false; + bool read_only = false; + bool no_create = false; + bool skip_validate = false; + bool session_initiator = false; + bool clear_file = false; + bool disable_sync = false; + const char* encryption_key = nullptr; + }; + + struct Retry { + }; + + /// \brief Attach this allocator to the specified file. + /// + /// It is an error if this function is called at a time where the specified + /// Realm file (file system inode) is modified asynchronously. + /// + /// In non-shared mode (when this function is called on behalf of a + /// free-standing Group instance), it is the responsibility of the + /// application to ensure that the Realm file is not modified concurrently + /// from any other thread or process. + /// + /// In shared mode (when this function is called on behalf of a SharedGroup + /// instance), the caller (SharedGroup::do_open()) must take steps to ensure + /// cross-process mutual exclusion. + /// + /// Except for \a file_path, the parameters are passed in through a + /// configuration object. + /// + /// \return The `ref` of the root node, or zero if there is none. + /// + /// Please note that attach_file can fail to attach to a file due to a + /// collision with a writer extending the file. This can only happen if the + /// caller is *not* the session initiator. When this happens, attach_file() + /// throws SlabAlloc::Retry, and the caller must retry the call. The caller + /// should check if it has become the session initiator before retrying. + /// This can happen if the conflicting thread (or process) terminates or + /// crashes before the next retry. + /// + /// \throw util::File::AccessError + /// \throw SlabAlloc::Retry + ref_type attach_file(const std::string& file_path, Config& cfg); + + /// Get the attached file. Only valid when called on an allocator with + /// an attached file. + util::File& get_file(); + + /// Attach this allocator to the specified memory buffer. + /// + /// It is an error to call this function on an attached + /// allocator. Doing so will result in undefined behavor. + /// + /// \return The `ref` of the root node, or zero if there is none. + /// + /// \sa own_buffer() + /// + /// \throw InvalidDatabase + ref_type attach_buffer(const char* data, size_t size); + + /// Reads file format from file header. Must be called from within a write + /// transaction. + int get_committed_file_format_version() const noexcept; + + bool is_file_on_streaming_form() const + { + const Header& header = *reinterpret_cast(m_data); + return is_file_on_streaming_form(header); + } + + /// Attach this allocator to an empty buffer. + /// + /// It is an error to call this function on an attached + /// allocator. Doing so will result in undefined behavor. + void attach_empty(); + + /// Detach from a previously attached file or buffer. + /// + /// This function does not reset free space tracking. To + /// completely reset the allocator, you must also call + /// reset_free_space_tracking(). + /// + /// This function has no effect if the allocator is already in the + /// detached state (idempotency). + void detach() noexcept; + + class DetachGuard; + + /// If a memory buffer has been attached using attach_buffer(), + /// mark it as owned by this slab allocator. Behaviour is + /// undefined if this function is called on a detached allocator, + /// one that is not attached using attach_buffer(), or one for + /// which this function has already been called during the latest + /// attachment. + void own_buffer() noexcept; + + /// Returns true if, and only if this allocator is currently + /// in the attached state. + bool is_attached() const noexcept; + + /// Returns true if, and only if this allocator is currently in + /// the attached state and attachment was not established using + /// attach_empty(). + bool nonempty_attachment() const noexcept; + + /// Reserve disk space now to avoid allocation errors at a later + /// point in time, and to minimize on-disk fragmentation. In some + /// cases, less fragmentation translates into improved + /// performance. On flash or SSD-drives this is likely a waste. + /// + /// Note: File::prealloc() may misbehave under race conditions (see + /// documentation of File::prealloc()). For that reason, to avoid race + /// conditions, when this allocator is used in a transactional mode, this + /// function may be called only when the caller has exclusive write + /// access. In non-transactional mode it is the responsibility of the user + /// to ensure non-concurrent file mutation. + /// + /// This function will call File::sync(). + /// + /// It is an error to call this function on an allocator that is not + /// attached to a file. Doing so will result in undefined behavior. + void resize_file(size_t new_file_size); + +#ifdef REALM_DEBUG + /// Deprecated method, only called from a unit test + /// + /// WARNING: This method is NOT thread safe on multiple platforms; see + /// File::prealloc(). + /// + /// Reserve disk space now to avoid allocation errors at a later point in + /// time, and to minimize on-disk fragmentation. In some cases, less + /// fragmentation translates into improved performance. On SSD-drives + /// preallocation is likely a waste. + /// + /// When supported by the system, a call to this function will make the + /// database file at least as big as the specified size, and cause space on + /// the target device to be allocated (note that on many systems on-disk + /// allocation is done lazily by default). If the file is already bigger + /// than the specified size, the size will be unchanged, and on-disk + /// allocation will occur only for the initial section that corresponds to + /// the specified size. + /// + /// This function will call File::sync() if it changes the size of the file. + /// + /// It is an error to call this function on an allocator that is not + /// attached to a file. Doing so will result in undefined behavior. + void reserve_disk_space(size_t size_in_bytes); +#endif + + /// Get the size of the attached database file or buffer in number + /// of bytes. This size is not affected by new allocations. After + /// attachment, it can only be modified by a call to update_reader_view(). + /// + /// It is an error to call this function on a detached allocator, + /// or one that was attached using attach_empty(). Doing so will + /// result in undefined behavior. + size_t get_baseline() const noexcept; + + /// Get the total amount of managed memory. This is the baseline plus the + /// sum of the sizes of the allocated slabs. It includes any free space. + /// + /// It is an error to call this function on a detached + /// allocator. Doing so will result in undefined behavior. + size_t get_total_size() const noexcept; + + /// Mark all mutable memory (ref-space outside the attached file) as free + /// space. + void reset_free_space_tracking(); + + /// Update the readers view of the file: + /// + /// Remap the attached file such that a prefix of the specified + /// size becomes available in memory. If sucessfull, + /// get_baseline() will return the specified new file size. + /// + /// It is an error to call this function on a detached allocator, + /// or one that was not attached using attach_file(). Doing so + /// will result in undefined behavior. + /// + /// The file_size argument must be aligned to a *section* boundary: + /// The database file is logically split into sections, each section + /// guaranteed to be mapped as a contiguous address range. The allocation + /// of memory in the file must ensure that no allocation crosses the + /// boundary between two sections. + /// + /// Clears any allocator specicific caching of address translations + /// and force any later address translations to trigger decryption if required. + void update_reader_view(size_t file_size); + + /// Returns true initially, and after a call to reset_free_space_tracking() + /// up until the point of the first call to SlabAlloc::alloc(). Note that a + /// call to SlabAlloc::alloc() corresponds to a mutation event. + bool is_free_space_clean() const noexcept; + + /// Returns the amount of memory requested by calls to SlabAlloc::alloc(). + size_t get_commit_size() const + { + return m_commit_size; + } + + /// Returns the total amount of memory currently allocated in slab area + size_t get_allocated_size() const noexcept; + + /// Returns total amount of slab for all slab allocators + static size_t get_total_slab_size() noexcept; + + /// Hooks used to keep the encryption layer informed of the start and stop + /// of transactions. + void note_reader_start(const void* reader_id); + void note_reader_end(const void* reader_id) noexcept; + + void verify() const override; +#ifdef REALM_DEBUG + void enable_debug(bool enable) + { + m_debug_out = enable; + } + bool is_all_free() const; + void print() const; +#endif + struct MappedFile; + +protected: + MemRef do_alloc(const size_t size) override; + MemRef do_realloc(ref_type, char*, size_t old_size, size_t new_size) override; + // FIXME: It would be very nice if we could detect an invalid free operation in debug mode + void do_free(ref_type, char*) noexcept override; + char* do_translate(ref_type) const noexcept override; + + /// Returns the first section boundary *above* the given position. + size_t get_upper_section_boundary(size_t start_pos) const noexcept; + + /// Returns the first section boundary *at or below* the given position. + size_t get_lower_section_boundary(size_t start_pos) const noexcept; + + /// Returns true if the given position is at a section boundary + bool matches_section_boundary(size_t pos) const noexcept; + + /// Returns the index of the section holding a given address. + /// The section index is determined solely by the minimal section size, + /// and does not necessarily reflect the mapping. A mapping may + /// cover multiple sections - the initial mapping often does. + size_t get_section_index(size_t pos) const noexcept; + + /// Reverse: get the base offset of a section at a given index. Since the + /// computation is very time critical, this method just looks it up in + /// a table. The actual computation and setup of that table is done + /// during initialization with the help of compute_section_base() below. + inline size_t get_section_base(size_t index) const noexcept; + + /// Actually compute the starting offset of a section. Only used to initialize + /// a table of predefined results, which are then used by get_section_base(). + size_t compute_section_base(size_t index) const noexcept; + + /// Find a possible allocation of 'request_size' that will fit into a section + /// which is inside the range from 'start_pos' to 'start_pos'+'free_chunk_size' + /// If found return the position, if not return 0. + size_t find_section_in_range(size_t start_pos, size_t free_chunk_size, size_t request_size) const noexcept; + +private: + void internal_invalidate_cache() noexcept; + enum AttachMode { + attach_None, // Nothing is attached + attach_OwnedBuffer, // We own the buffer (m_data = nullptr for empty buffer) + attach_UsersBuffer, // We do not own the buffer + attach_SharedFile, // On behalf of SharedGroup + attach_UnsharedFile // Not on behalf of SharedGroup + }; + + // A slab is a dynamically allocated contiguous chunk of memory used to + // extend the amount of space available for database node + // storage. Inter-node references are represented as file offsets + // (a.k.a. "refs"), and each slab creates an apparently seamless extension + // of this file offset addressable space. Slabs are stored as rows in the + // Slabs table in order of ascending file offsets. + struct Slab { + ref_type ref_end; + std::unique_ptr addr; + size_t size; + + Slab(ref_type r, size_t s); + Slab(Slab&& slab) + : ref_end(slab.ref_end) + , addr(std::move(slab.addr)) + , size(slab.size) + { + slab.size = 0; + } + ~Slab(); + }; + + struct Chunk { // describes a freed in-file block + ref_type ref; + size_t size; + }; + + // free blocks that are in the slab area are managed using the following structures: + // - FreeBlock: Placed at the start of any free space. Holds the 'ref' corresponding to + // the start of the space, and prev/next links used to place it in a size-specific + // freelist. + // - BetweenBlocks: Structure sitting between any two free OR allocated spaces. + // describes the size of the space before and after. + // Each slab (area obtained from the underlying system) has a terminating BetweenBlocks + // at the beginning and at the end of the Slab. + struct FreeBlock { + ref_type ref; // ref for this entry. Saves a reverse translate / representing links as refs + FreeBlock* prev; // circular doubly linked list + FreeBlock* next; + void clear_links() + { + prev = next = nullptr; + } + void unlink(); + }; + struct BetweenBlocks { // stores sizes and used/free status of blocks before and after. + int32_t block_before_size; // negated if block is in use, + int32_t block_after_size; // positive if block is free - and zero at end + }; + + Config m_cfg; + using FreeListMap = std::map; // log(N) addressing for larger blocks + FreeListMap m_block_map; + + // abstract notion of a freelist - used to hide whether a freelist + // is residing in the small blocks or the large blocks structures. + struct FreeList { + int size = 0; // size of every element in the list, 0 if not found + FreeListMap::iterator it; + bool found_something() + { + return size != 0; + } + bool found_exact(int sz) + { + return size == sz; + } + }; + + // simple helper functions for accessing/navigating blocks and betweenblocks (TM) + BetweenBlocks* bb_before(FreeBlock* entry) const { + return reinterpret_cast(entry) - 1; + } + BetweenBlocks* bb_after(FreeBlock* entry) const { + auto bb = bb_before(entry); + size_t sz = bb->block_after_size; + char* addr = reinterpret_cast(entry) + sz; + return reinterpret_cast(addr); + } + FreeBlock* block_before(BetweenBlocks* bb) const { + size_t sz = bb->block_before_size; + if (sz <= 0) + return nullptr; // only blocks that are not in use + char* addr = reinterpret_cast(bb) - sz; + return reinterpret_cast(addr); + } + FreeBlock* block_after(BetweenBlocks* bb) const { + if (bb->block_after_size <= 0) + return nullptr; + return reinterpret_cast(bb + 1); + } + int size_from_block(FreeBlock* entry) const { + return bb_before(entry)->block_after_size; + } + void mark_allocated(FreeBlock* entry); + // mark the entry freed in bordering BetweenBlocks. Also validate size. + void mark_freed(FreeBlock* entry, int size); + + // hook for the memory verifier in Group. + template + void for_all_free_entries(Func f) const; + + // Main entry points for alloc/free: + FreeBlock* allocate_block(int size); + void free_block(ref_type ref, FreeBlock* addr); + + // Searching/manipulating freelists + FreeList find(int size); + FreeList find_larger(FreeList hint, int size); + FreeBlock* pop_freelist_entry(FreeList list); + void push_freelist_entry(FreeBlock* entry); + void remove_freelist_entry(FreeBlock* element); + void rebuild_freelists_from_slab(); + void clear_freelists(); + + // grow the slab area to accommodate the requested size. + // returns a free block large enough to handle the request. + FreeBlock* grow_slab_for(int request_size); + // create a single free chunk with "BetweenBlocks" at both ends and a + // single free chunk between them. This free chunk will be of size: + // slab_size - 2 * sizeof(BetweenBlocks) + FreeBlock* slab_to_entry(const Slab& slab, ref_type ref_start); + + // breaking/merging of blocks + FreeBlock* get_prev_block_if_mergeable(FreeBlock* block); + FreeBlock* get_next_block_if_mergeable(FreeBlock* block); + // break 'block' to give it 'new_size'. Return remaining block. + // If the block is too small to split, return nullptr. + FreeBlock* break_block(FreeBlock* block, int new_size); + FreeBlock* merge_blocks(FreeBlock* first, FreeBlock* second); + + // Values of each used bit in m_flags + enum { + flags_SelectBit = 1, + }; + + // 24 bytes + struct Header { + uint64_t m_top_ref[2]; // 2 * 8 bytes + // Info-block 8-bytes + uint8_t m_mnemonic[4]; // "T-DB" + uint8_t m_file_format[2]; // See `library_file_format` + uint8_t m_reserved; + // bit 0 of m_flags is used to select between the two top refs. + uint8_t m_flags; + }; + + // 16 bytes + struct StreamingFooter { + uint64_t m_top_ref; + uint64_t m_magic_cookie; + }; + + static_assert(sizeof(Header) == 24, "Bad header size"); + static_assert(sizeof(StreamingFooter) == 16, "Bad footer size"); + + static const Header empty_file_header; + static void init_streaming_header(Header*, int file_format_version); + + static const uint_fast64_t footer_magic_cookie = 0x3034125237E526C8ULL; + + // The mappings are shared, if they are from a file + std::shared_ptr m_file_mappings; + + // We are caching local copies of all the additional mappings to allow + // for lock-free lookup during ref->address translation (we do not need + // to cache the first mapping, because it is immutable) (well, all the + // mappings are immutable, but the array holding them is not - it may + // have to be relocated) + std::unique_ptr>[]> m_local_mappings; + size_t m_num_local_mappings = 0; + + const char* m_data = nullptr; + size_t m_initial_chunk_size = 0; + size_t m_initial_section_size = 0; + int m_section_shifts = 0; + std::unique_ptr m_section_bases; + size_t m_num_section_bases = 0; + AttachMode m_attach_mode = attach_None; + enum FeeeSpaceState { + free_space_Clean, + free_space_Dirty, + free_space_Invalid, + }; + + /// When set to free_space_Invalid, the free lists are no longer + /// up-to-date. This happens if do_free() or + /// reset_free_space_tracking() fails, presumably due to + /// std::bad_alloc being thrown during updating of the free space + /// list. In this this case, alloc(), realloc_(), and + /// get_free_read_only() must throw. This member is deliberately + /// placed here (after m_attach_mode) in the hope that it leads to + /// less padding between members due to alignment requirements. + FeeeSpaceState m_free_space_state = free_space_Clean; + + typedef std::vector Slabs; + using Chunks = std::map; + Slabs m_slabs; + Chunks m_free_read_only; + size_t m_commit_size = 0; + + bool m_debug_out = false; + struct hash_entry { + ref_type ref = 0; + const char* addr = nullptr; + size_t version = 0; + }; + mutable hash_entry cache[256]; + mutable size_t version = 1; + + /// Throws if free-lists are no longer valid. + size_t consolidate_free_read_only(); + /// Throws if free-lists are no longer valid. + const Chunks& get_free_read_only() const; + + /// Throws InvalidDatabase if the file is not a Realm file, if the file is + /// corrupted, or if the specified encryption key is incorrect. This + /// function will not detect all forms of corruption, though. + void validate_buffer(const char* data, size_t len, const std::string& path); + void throw_header_exception(std::string msg, const Header& header, const std::string& path); + + static bool is_file_on_streaming_form(const Header& header); + /// Read the top_ref from the given buffer and set m_file_on_streaming_form + /// if the buffer contains a file in streaming form + static ref_type get_top_ref(const char* data, size_t len); + + // Gets the path of the attached file, or other relevant debugging info. + std::string get_file_path_for_assertions() const; + + class ChunkRefEq; + class ChunkRefEndEq; + class SlabRefEndEq; + static bool ref_less_than_slab_ref_end(ref_type, const Slab&) noexcept; + + Replication* get_replication() const noexcept + { + return m_replication; + } + void set_replication(Replication* r) noexcept + { + m_replication = r; + } + + friend class Group; + friend class SharedGroup; + friend class GroupWriter; +}; + +inline void SlabAlloc::internal_invalidate_cache() noexcept +{ + ++version; +} + +class SlabAlloc::DetachGuard { +public: + DetachGuard(SlabAlloc& alloc) noexcept + : m_alloc(&alloc) + { + } + ~DetachGuard() noexcept; + SlabAlloc* release() noexcept; + +private: + SlabAlloc* m_alloc; +}; + + +// Implementation: + +struct InvalidDatabase : util::File::AccessError { + InvalidDatabase(const std::string& msg, const std::string& path) + : util::File::AccessError(msg, path) + { + } +}; + +inline void SlabAlloc::own_buffer() noexcept +{ + REALM_ASSERT_3(m_attach_mode, ==, attach_UsersBuffer); + REALM_ASSERT(m_data); + REALM_ASSERT(m_file_mappings == nullptr); + m_attach_mode = attach_OwnedBuffer; +} + +inline bool SlabAlloc::is_attached() const noexcept +{ + return m_attach_mode != attach_None; +} + +inline bool SlabAlloc::nonempty_attachment() const noexcept +{ + return is_attached() && m_data; +} + +inline size_t SlabAlloc::get_baseline() const noexcept +{ + REALM_ASSERT_DEBUG(is_attached()); + return m_baseline; +} + +inline bool SlabAlloc::is_free_space_clean() const noexcept +{ + return m_free_space_state == free_space_Clean; +} + +inline SlabAlloc::DetachGuard::~DetachGuard() noexcept +{ + if (m_alloc) + m_alloc->detach(); +} + +inline SlabAlloc* SlabAlloc::DetachGuard::release() noexcept +{ + SlabAlloc* alloc = m_alloc; + m_alloc = nullptr; + return alloc; +} + +inline bool SlabAlloc::ref_less_than_slab_ref_end(ref_type ref, const Slab& slab) noexcept +{ + return ref < slab.ref_end; +} + +inline size_t SlabAlloc::get_upper_section_boundary(size_t start_pos) const noexcept +{ + return get_section_base(1 + get_section_index(start_pos)); +} + +inline size_t SlabAlloc::get_lower_section_boundary(size_t start_pos) const noexcept +{ + return get_section_base(get_section_index(start_pos)); +} + +inline bool SlabAlloc::matches_section_boundary(size_t pos) const noexcept +{ + return pos == get_lower_section_boundary(pos); +} + +inline size_t SlabAlloc::get_section_base(size_t index) const noexcept +{ + return m_section_bases[index]; +} + +template +void SlabAlloc::for_all_free_entries(Func f) const +{ + ref_type ref = m_baseline; + for (auto& e : m_slabs) { + BetweenBlocks* bb = reinterpret_cast(e.addr.get()); + REALM_ASSERT(bb->block_before_size == 0); + while (1) { + int size = bb->block_after_size; + f(ref, sizeof(BetweenBlocks)); + ref += sizeof(BetweenBlocks); + if (size == 0) { + break; + } + if (size > 0) { // freeblock. + f(ref, size); + bb = reinterpret_cast(reinterpret_cast(bb) + sizeof(BetweenBlocks) + size); + ref += size; + } + else { + bb = reinterpret_cast(reinterpret_cast(bb) + sizeof(BetweenBlocks) - size); + ref -= size; + } + } + } +} + +} // namespace realm + +#endif // REALM_ALLOC_SLAB_HPP diff --git a/!main project/Pods/Realm/include/core/realm/array.hpp b/!main project/Pods/Realm/include/core/realm/array.hpp new file mode 100644 index 0000000..a948943 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/array.hpp @@ -0,0 +1,3128 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +/* +Searching: The main finding function is: + template + void find(int64_t value, size_t start, size_t end, size_t baseindex, QueryState *state, Callback callback) const + + cond: One of Equal, NotEqual, Greater, etc. classes + Action: One of act_ReturnFirst, act_FindAll, act_Max, act_CallbackIdx, etc, constants + Callback: Optional function to call for each search result. Will be called if action == act_CallbackIdx + + find() will call find_action_pattern() or find_action() that again calls match() for each search result which + optionally calls callback(): + + find() -> find_action() -------> bool match() -> bool callback() + | ^ + +-> find_action_pattern()----+ + + If callback() returns false, find() will exit, otherwise it will keep searching remaining items in array. +*/ + +#ifndef REALM_ARRAY_HPP +#define REALM_ARRAY_HPP + +#include +#include // size_t +#include +#include +#include +#include + +#include // unint8_t etc + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + MMX: mmintrin.h + SSE: xmmintrin.h + SSE2: emmintrin.h + SSE3: pmmintrin.h + SSSE3: tmmintrin.h + SSE4A: ammintrin.h + SSE4.1: smmintrin.h + SSE4.2: nmmintrin.h +*/ +#ifdef REALM_COMPILER_SSE +#include // SSE2 +#include // SSE42 +#endif + +namespace realm { + +enum Action { + act_ReturnFirst, + act_Sum, + act_Max, + act_Min, + act_Count, + act_FindAll, + act_CallIdx, + act_CallbackIdx, + act_CallbackVal, + act_CallbackNone, + act_CallbackBoth, + act_Average +}; + +template +inline T no0(T v) +{ + return v == 0 ? 1 : v; +} + +/// Special index value. It has various meanings depending on +/// context. It is returned by some search functions to indicate 'not +/// found'. It is similar in function to std::string::npos. +const size_t npos = size_t(-1); + +const size_t max_array_size = 0x00ffffffL; // Maximum number of elements in an array +const size_t max_array_payload_aligned = 0x07ffffc0L; // Maximum number of bytes that the payload of an array can be + +/// Alias for realm::npos. +const size_t not_found = npos; + +// Pre-definitions +class Array; +class StringColumn; +class GroupWriter; +template +class QueryState; +namespace _impl { +class ArrayWriterBase; +} + + +struct MemStats { + size_t allocated = 0; + size_t used = 0; + size_t array_count = 0; +}; + +#ifdef REALM_DEBUG +template +std::basic_ostream& operator<<(std::basic_ostream& out, MemStats stats); +#endif + + +// Stores a value obtained from Array::get(). It is a ref if the least +// significant bit is clear, otherwise it is a tagged integer. A tagged interger +// is obtained from a logical integer value by left shifting by one bit position +// (multiplying by two), and then setting the least significant bit to +// one. Clearly, this means that the maximum value that can be stored as a +// tagged integer is 2**63 - 1. +class RefOrTagged { +public: + bool is_ref() const noexcept; + bool is_tagged() const noexcept; + ref_type get_as_ref() const noexcept; + uint_fast64_t get_as_int() const noexcept; + + static RefOrTagged make_ref(ref_type) noexcept; + static RefOrTagged make_tagged(uint_fast64_t) noexcept; + +private: + int_fast64_t m_value; + RefOrTagged(int_fast64_t) noexcept; + friend class Array; +}; + + +class ArrayParent { +public: + virtual ~ArrayParent() noexcept + { + } + +protected: + virtual void update_child_ref(size_t child_ndx, ref_type new_ref) = 0; + + virtual ref_type get_child_ref(size_t child_ndx) const noexcept = 0; + + // Used only by Array::to_dot(). + virtual std::pair get_to_dot_parent(size_t ndx_in_parent) const = 0; + + friend class Array; +}; + +struct TreeInsertBase { + size_t m_split_offset; + size_t m_split_size; +}; + +/// Provides access to individual array nodes of the database. +/// +/// This class serves purely as an accessor, it assumes no ownership of the +/// referenced memory. +/// +/// An array accessor can be in one of two states: attached or unattached. It is +/// in the attached state if, and only if is_attached() returns true. Most +/// non-static member functions of this class have undefined behaviour if the +/// accessor is in the unattached state. The exceptions are: is_attached(), +/// detach(), create(), init_from_ref(), init_from_mem(), init_from_parent(), +/// has_parent(), get_parent(), set_parent(), get_ndx_in_parent(), +/// set_ndx_in_parent(), adjust_ndx_in_parent(), and get_ref_from_parent(). +/// +/// An array accessor contains information about the parent of the referenced +/// array node. This 'reverse' reference is not explicitely present in the +/// underlying node hierarchy, but it is needed when modifying an array. A +/// modification may lead to relocation of the underlying array node, and the +/// parent must be updated accordingly. Since this applies recursivly all the +/// way to the root node, it is essential that the entire chain of parent +/// accessors is constructed and propperly maintained when a particular array is +/// modified. +/// +/// The parent reference (`pointer to parent`, `index in parent`) is updated +/// independently from the state of attachment to an underlying node. In +/// particular, the parent reference remains valid and is unannfected by changes +/// in attachment. These two aspects of the state of the accessor is updated +/// independently, and it is entirely the responsibility of the caller to update +/// them such that they are consistent with the underlying node hierarchy before +/// calling any method that modifies the underlying array node. +/// +/// FIXME: This class currently has fragments of ownership, in particular the +/// constructors that allocate underlying memory. On the other hand, the +/// destructor never frees the memory. This is a problematic situation, because +/// it so easily becomes an obscure source of leaks. There are three options for +/// a fix of which the third is most attractive but hardest to implement: (1) +/// Remove all traces of ownership semantics, that is, remove the constructors +/// that allocate memory, but keep the trivial copy constructor. For this to +/// work, it is important that the constness of the accessor has nothing to do +/// with the constness of the underlying memory, otherwise constness can be +/// violated simply by copying the accessor. (2) Disallov copying but associate +/// the constness of the accessor with the constness of the underlying +/// memory. (3) Provide full ownership semantics like is done for Table +/// accessors, and provide a proper copy constructor that really produces a copy +/// of the array. For this to work, the class should assume ownership if, and +/// only if there is no parent. A copy produced by a copy constructor will not +/// have a parent. Even if the original was part of a database, the copy will be +/// free-standing, that is, not be part of any database. For intra, or inter +/// database copying, one would have to also specify the target allocator. +class Array : public ArrayParent { +public: + // void state_init(int action, QueryState *state); + // bool match(int action, size_t index, int64_t value, QueryState *state); + + /// Create an array accessor in the unattached state. + explicit Array(Allocator&) noexcept; + + ~Array() noexcept override + { + } + + enum Type { + type_Normal, + + /// This array is the main array of an innner node of a B+-tree as used + /// in table columns. + type_InnerBptreeNode, + + /// This array may contain refs to subarrays. An element whose least + /// significant bit is zero, is a ref pointing to a subarray. An element + /// whose least significant bit is one, is just a value. It is the + /// responsibility of the application to ensure that non-ref values have + /// their least significant bit set. This will generally be done by + /// shifting the desired vlue to the left by one bit position, and then + /// setting the vacated bit to one. + type_HasRefs + }; + + /// Create a new integer array of the specified type and size, and filled + /// with the specified value, and attach this accessor to it. This does not + /// modify the parent reference information of this accessor. + /// + /// Note that the caller assumes ownership of the allocated underlying + /// node. It is not owned by the accessor. + void create(Type, bool context_flag = false, size_t size = 0, int_fast64_t value = 0); + + /// Reinitialize this array accessor to point to the specified new + /// underlying memory. This does not modify the parent reference information + /// of this accessor. + void init_from_ref(ref_type) noexcept; + + /// Same as init_from_ref(ref_type) but avoid the mapping of 'ref' to memory + /// pointer. + void init_from_mem(MemRef) noexcept; + + /// Same as `init_from_ref(get_ref_from_parent())`. + void init_from_parent() noexcept; + + /// Update the parents reference to this child. This requires, of course, + /// that the parent information stored in this child is up to date. If the + /// parent pointer is set to null, this function has no effect. + void update_parent(); + + /// Called in the context of Group::commit() to ensure that attached + /// accessors stay valid across a commit. Please note that this works only + /// for non-transactional commits. Accessors obtained during a transaction + /// are always detached when the transaction ends. + /// + /// Returns true if, and only if the array has changed. If the array has not + /// changed, then its children are guaranteed to also not have changed. + bool update_from_parent(size_t old_baseline) noexcept; + + /// Change the type of an already attached array node. + /// + /// The effect of calling this function on an unattached accessor is + /// undefined. + void set_type(Type); + + /// Construct a complete copy of this array (including its subarrays) using + /// the specified target allocator and return just the reference to the + /// underlying memory. + MemRef clone_deep(Allocator& target_alloc) const; + + /// Construct an empty integer array of the specified type, and return just + /// the reference to the underlying memory. + static MemRef create_empty_array(Type, bool context_flag, Allocator&); + + /// Construct an integer array of the specified type and size, and return + /// just the reference to the underlying memory. All elements will be + /// initialized to the specified value. + static MemRef create_array(Type, bool context_flag, size_t size, int_fast64_t value, Allocator&); + + /// Construct a shallow copy of the specified slice of this array using the + /// specified target allocator. Subarrays will **not** be cloned. See + /// slice_and_clone_children() for an alternative. + MemRef slice(size_t offset, size_t slice_size, Allocator& target_alloc) const; + + /// Construct a deep copy of the specified slice of this array using the + /// specified target allocator. Subarrays will be cloned. + MemRef slice_and_clone_children(size_t offset, size_t slice_size, Allocator& target_alloc) const; + + // Parent tracking + bool has_parent() const noexcept; + ArrayParent* get_parent() const noexcept; + + /// Setting a new parent affects ownership of the attached array node, if + /// any. If a non-null parent is specified, and there was no parent + /// originally, then the caller passes ownership to the parent, and vice + /// versa. This assumes, of course, that the change in parentship reflects a + /// corresponding change in the list of children in the affected parents. + void set_parent(ArrayParent* parent, size_t ndx_in_parent) noexcept; + + size_t get_ndx_in_parent() const noexcept; + void set_ndx_in_parent(size_t) noexcept; + void adjust_ndx_in_parent(int diff) noexcept; + + /// Get the ref of this array as known to the parent. The caller must ensure + /// that the parent information ('pointer to parent' and 'index in parent') + /// is correct before calling this function. + ref_type get_ref_from_parent() const noexcept; + + bool is_attached() const noexcept; + + /// Detach from the underlying array node. This method has no effect if the + /// accessor is currently unattached (idempotency). + void detach() noexcept; + + size_t size() const noexcept; + bool is_empty() const noexcept; + Type get_type() const noexcept; + + + static void add_to_column(IntegerColumn* column, int64_t value); + + void insert(size_t ndx, int_fast64_t value); + void add(int_fast64_t value); + + // Used from ArrayBlob + size_t blob_size() const noexcept; + ref_type blob_replace(size_t begin, size_t end, const char* data, size_t data_size, bool add_zero_term); + + /// This function is guaranteed to not throw if the current width is + /// sufficient for the specified value (e.g. if you have called + /// ensure_minimum_width(value)) and get_alloc().is_read_only(get_ref()) + /// returns false (noexcept:array-set). Note that for a value of zero, the + /// first criterion is trivially satisfied. + void set(size_t ndx, int64_t value); + + void set_as_ref(size_t ndx, ref_type ref); + + template + void set(size_t ndx, int64_t value); + + int64_t get(size_t ndx) const noexcept; + + template + int64_t get(size_t ndx) const noexcept; + + void get_chunk(size_t ndx, int64_t res[8]) const noexcept; + + template + void get_chunk(size_t ndx, int64_t res[8]) const noexcept; + + ref_type get_as_ref(size_t ndx) const noexcept; + + RefOrTagged get_as_ref_or_tagged(size_t ndx) const noexcept; + void set(size_t ndx, RefOrTagged); + void add(RefOrTagged); + void ensure_minimum_width(RefOrTagged); + + int64_t front() const noexcept; + int64_t back() const noexcept; + + /// Remove the element at the specified index, and move elements at higher + /// indexes to the next lower index. + /// + /// This function does **not** destroy removed subarrays. That is, if the + /// erased element is a 'ref' pointing to a subarray, then that subarray + /// will not be destroyed automatically. + /// + /// This function guarantees that no exceptions will be thrown if + /// get_alloc().is_read_only(get_ref()) would return false before the + /// call. This is automatically guaranteed if the array is used in a + /// non-transactional context, or if the array has already been successfully + /// modified within the current write transaction. + void erase(size_t ndx); + + /// Same as erase(size_t), but remove all elements in the specified + /// range. + /// + /// Please note that this function does **not** destroy removed subarrays. + /// + /// This function guarantees that no exceptions will be thrown if + /// get_alloc().is_read_only(get_ref()) would return false before the call. + void erase(size_t begin, size_t end); + + /// Reduce the size of this array to the specified number of elements. It is + /// an error to specify a size that is greater than the current size of this + /// array. The effect of doing so is undefined. This is just a shorthand for + /// calling the ranged erase() function with appropriate arguments. + /// + /// Please note that this function does **not** destroy removed + /// subarrays. See clear_and_destroy_children() for an alternative. + /// + /// This function guarantees that no exceptions will be thrown if + /// get_alloc().is_read_only(get_ref()) would return false before the call. + void truncate(size_t new_size); + + /// Reduce the size of this array to the specified number of elements. It is + /// an error to specify a size that is greater than the current size of this + /// array. The effect of doing so is undefined. Subarrays will be destroyed + /// recursively, as if by a call to `destroy_deep(subarray_ref, alloc)`. + /// + /// This function is guaranteed not to throw if + /// get_alloc().is_read_only(get_ref()) returns false. + void truncate_and_destroy_children(size_t new_size); + + /// Remove every element from this array. This is just a shorthand for + /// calling truncate(0). + /// + /// Please note that this function does **not** destroy removed + /// subarrays. See clear_and_destroy_children() for an alternative. + /// + /// This function guarantees that no exceptions will be thrown if + /// get_alloc().is_read_only(get_ref()) would return false before the call. + void clear(); + + /// Remove every element in this array. Subarrays will be destroyed + /// recursively, as if by a call to `destroy_deep(subarray_ref, + /// alloc)`. This is just a shorthand for calling + /// truncate_and_destroy_children(0). + /// + /// This function guarantees that no exceptions will be thrown if + /// get_alloc().is_read_only(get_ref()) would return false before the call. + void clear_and_destroy_children(); + + /// If neccessary, expand the representation so that it can store the + /// specified value. + void ensure_minimum_width(int_fast64_t value); + + /// This one may change the represenation of the array, so be carefull if + /// you call it after ensure_minimum_width(). + void set_all_to_zero(); + + /// Add \a diff to the element at the specified index. + void adjust(size_t ndx, int_fast64_t diff); + + /// Add \a diff to all the elements in the specified index range. + void adjust(size_t begin, size_t end, int_fast64_t diff); + + /// Add signed \a diff to all elements that are greater than, or equal to \a + /// limit. + void adjust_ge(int_fast64_t limit, int_fast64_t diff); + + //@{ + /// These are similar in spirit to std::move() and std::move_backward from + /// ``. \a dest_begin must not be in the range [`begin`,`end`), and + /// \a dest_end must not be in the range (`begin`,`end`]. + /// + /// These functions are guaranteed to not throw if + /// `get_alloc().is_read_only(get_ref())` returns false. + void move(size_t begin, size_t end, size_t dest_begin); + void move_backward(size_t begin, size_t end, size_t dest_end); + //@} + + /// move_rotate moves one element from \a from to be located at index \a to, + /// shifting all elements inbetween by one. + /// + /// If \a from is larger than \a to, the elements inbetween are shifted down. + /// If \a to is larger than \a from, the elements inbetween are shifted up. + /// + /// This function is guaranteed to not throw if + /// `get_alloc().is_read_only(get_ref())` returns false. + void move_rotate(size_t from, size_t to, size_t num_elems = 1); + + //@{ + /// Find the lower/upper bound of the specified value in a sequence of + /// integers which must already be sorted ascendingly. + /// + /// For an integer value '`v`', lower_bound_int(v) returns the index '`l`' + /// of the first element such that `get(l) ≥ v`, and upper_bound_int(v) + /// returns the index '`u`' of the first element such that `get(u) > + /// v`. In both cases, if no such element is found, the returned value is + /// the number of elements in the array. + /// + /// 3 3 3 4 4 4 5 6 7 9 9 9 + /// ^ ^ ^ ^ ^ + /// | | | | | + /// | | | | -- Lower and upper bound of 15 + /// | | | | + /// | | | -- Lower and upper bound of 8 + /// | | | + /// | | -- Upper bound of 4 + /// | | + /// | -- Lower bound of 4 + /// | + /// -- Lower and upper bound of 1 + /// + /// These functions are similar to std::lower_bound() and + /// std::upper_bound(). + /// + /// We currently use binary search. See for example + /// http://www.tbray.org/ongoing/When/200x/2003/03/22/Binary. + /// + /// FIXME: It may be worth considering if overall efficiency can be improved + /// by doing a linear search for short sequences. + size_t lower_bound_int(int64_t value) const noexcept; + size_t upper_bound_int(int64_t value) const noexcept; + //@} + + /// \brief Search the \c Array for a value greater or equal than \a target, + /// starting the search at the \a start index. If \a indirection is + /// provided, use it as a look-up table to iterate over the \c Array. + /// + /// If \a indirection is not provided, then the \c Array must be sorted in + /// ascending order. If \a indirection is provided, then its values should + /// point to indices in this \c Array in such a way that iteration happens + /// in ascending order. + /// + /// Behaviour is undefined if: + /// - a value in \a indirection is out of bounds for this \c Array; + /// - \a indirection does not contain at least as many elements as this \c + /// Array; + /// - sorting conditions are not respected; + /// - \a start is greater than the number of elements in this \c Array or + /// \a indirection (if provided). + /// + /// \param target the smallest value to search for + /// \param start the offset at which to start searching in the array + /// \param indirection an \c Array containing valid indices of values in + /// this \c Array, sorted in ascending order + /// \return the index of the value if found, or realm::not_found otherwise + size_t find_gte(const int64_t target, size_t start, size_t end = size_t(-1)) const; + + void preset(int64_t min, int64_t max, size_t num_items); + void preset(size_t bitwidth, size_t num_items); + + int64_t sum(size_t start = 0, size_t end = size_t(-1)) const; + size_t count(int64_t value) const noexcept; + + bool maximum(int64_t& result, size_t start = 0, size_t end = size_t(-1), size_t* return_ndx = nullptr) const; + + bool minimum(int64_t& result, size_t start = 0, size_t end = size_t(-1), size_t* return_ndx = nullptr) const; + + /// This information is guaranteed to be cached in the array accessor. + bool is_inner_bptree_node() const noexcept; + + /// Returns true if type is either type_HasRefs or type_InnerColumnNode. + /// + /// This information is guaranteed to be cached in the array accessor. + bool has_refs() const noexcept; + void set_has_refs(bool) noexcept; + + /// This information is guaranteed to be cached in the array accessor. + /// + /// Columns and indexes can use the context bit to differentiate leaf types. + bool get_context_flag() const noexcept; + void set_context_flag(bool) noexcept; + + ref_type get_ref() const noexcept; + MemRef get_mem() const noexcept; + + /// Destroy only the array that this accessor is attached to, not the + /// children of that array. See non-static destroy_deep() for an + /// alternative. If this accessor is already in the detached state, this + /// function has no effect (idempotency). + void destroy() noexcept; + + /// Recursively destroy children (as if calling + /// clear_and_destroy_children()), then put this accessor into the detached + /// state (as if calling detach()), then free the allocated memory. If this + /// accessor is already in the detached state, this function has no effect + /// (idempotency). + void destroy_deep() noexcept; + + /// Shorthand for `destroy(MemRef(ref, alloc), alloc)`. + static void destroy(ref_type ref, Allocator& alloc) noexcept; + + /// Destroy only the specified array node, not its children. See also + /// destroy_deep(MemRef, Allocator&). + static void destroy(MemRef, Allocator&) noexcept; + + /// Shorthand for `destroy_deep(MemRef(ref, alloc), alloc)`. + static void destroy_deep(ref_type ref, Allocator& alloc) noexcept; + + /// Destroy the specified array node and all of its children, recursively. + /// + /// This is done by freeing the specified array node after calling + /// destroy_deep() for every contained 'ref' element. + static void destroy_deep(MemRef, Allocator&) noexcept; + + Allocator& get_alloc() const noexcept + { + return m_alloc; + } + + // Serialization + + /// Returns the ref (position in the target stream) of the written copy of + /// this array, or the ref of the original array if \a only_if_modified is + /// true, and this array is unmodified (Alloc::is_read_only()). + /// + /// The number of bytes that will be written by a non-recursive invocation + /// of this function is exactly the number returned by get_byte_size(). + /// + /// \param out The destination stream (writer). + /// + /// \param deep If true, recursively write out subarrays, but still subject + /// to \a only_if_modified. + /// + /// \param only_if_modified Set to `false` to always write, or to `true` to + /// only write the array if it has been modified. + ref_type write(_impl::ArrayWriterBase& out, bool deep, bool only_if_modified) const; + + /// Same as non-static write() with `deep` set to true. This is for the + /// cases where you do not already have an array accessor available. + static ref_type write(ref_type, Allocator&, _impl::ArrayWriterBase&, bool only_if_modified); + + // Main finding function - used for find_first, find_all, sum, max, min, etc. + bool find(int cond, Action action, int64_t value, size_t start, size_t end, size_t baseindex, + QueryState* state, bool nullable_array = false, bool find_null = false) const; + + // Templated find function to avoid conversion to and from integer represenation of condition + template + bool find(Action action, int64_t value, size_t start, size_t end, size_t baseindex, QueryState* state, + bool nullable_array = false, bool find_null = false) const + { + if (action == act_ReturnFirst) { + REALM_TEMPEX3(return find, cond, act_ReturnFirst, m_width, + (value, start, end, baseindex, state, CallbackDummy(), nullable_array, find_null)) + } + else if (action == act_Sum) { + REALM_TEMPEX3(return find, cond, act_Sum, m_width, + (value, start, end, baseindex, state, CallbackDummy(), nullable_array, find_null)) + } + else if (action == act_Min) { + REALM_TEMPEX3(return find, cond, act_Min, m_width, + (value, start, end, baseindex, state, CallbackDummy(), nullable_array, find_null)) + } + else if (action == act_Max) { + REALM_TEMPEX3(return find, cond, act_Max, m_width, + (value, start, end, baseindex, state, CallbackDummy(), nullable_array, find_null)) + } + else if (action == act_Count) { + REALM_TEMPEX3(return find, cond, act_Count, m_width, + (value, start, end, baseindex, state, CallbackDummy(), nullable_array, find_null)) + } + else if (action == act_FindAll) { + REALM_TEMPEX3(return find, cond, act_FindAll, m_width, + (value, start, end, baseindex, state, CallbackDummy(), nullable_array, find_null)) + } + else if (action == act_CallbackIdx) { + REALM_TEMPEX3(return find, cond, act_CallbackIdx, m_width, + (value, start, end, baseindex, state, CallbackDummy(), nullable_array, find_null)) + } + REALM_ASSERT_DEBUG(false); + return false; + } + + + /* + bool find(int cond, Action action, null, size_t start, size_t end, size_t baseindex, + QueryState* state) const; + */ + + template + bool find(int64_t value, size_t start, size_t end, size_t baseindex, QueryState* state, + Callback callback, bool nullable_array = false, bool find_null = false) const; + + // This is the one installed into the m_vtable->finder slots. + template + bool find(int64_t value, size_t start, size_t end, size_t baseindex, QueryState* state) const; + + template + bool find(int64_t value, size_t start, size_t end, size_t baseindex, QueryState* state, + Callback callback, bool nullable_array = false, bool find_null = false) const; + + /* + template + bool find(null, size_t start, size_t end, size_t baseindex, + QueryState* state, Callback callback) const; + */ + + // Optimized implementation for release mode + template + bool find_optimized(int64_t value, size_t start, size_t end, size_t baseindex, QueryState* state, + Callback callback, bool nullable_array = false, bool find_null = false) const; + + // Called for each search result + template + bool find_action(size_t index, util::Optional value, QueryState* state, + Callback callback) const; + + template + bool find_action_pattern(size_t index, uint64_t pattern, QueryState* state, Callback callback) const; + + // Wrappers for backwards compatibility and for simple use without + // setting up state initialization etc + template + size_t find_first(int64_t value, size_t start = 0, size_t end = size_t(-1)) const; + + void find_all(IntegerColumn* result, int64_t value, size_t col_offset = 0, size_t begin = 0, + size_t end = size_t(-1)) const; + + size_t find_first(int64_t value, size_t begin = 0, size_t end = size_t(-1)) const; + + // Non-SSE find for the four functions Equal/NotEqual/Less/Greater + template + bool compare(int64_t value, size_t start, size_t end, size_t baseindex, QueryState* state, + Callback callback) const; + + // Non-SSE find for Equal/NotEqual + template + inline bool compare_equality(int64_t value, size_t start, size_t end, size_t baseindex, + QueryState* state, Callback callback) const; + + // Non-SSE find for Less/Greater + template + bool compare_relation(int64_t value, size_t start, size_t end, size_t baseindex, QueryState* state, + Callback callback) const; + + template + bool compare_leafs_4(const Array* foreign, size_t start, size_t end, size_t baseindex, QueryState* state, + Callback callback) const; + + template + bool compare_leafs(const Array* foreign, size_t start, size_t end, size_t baseindex, QueryState* state, + Callback callback) const; + + template + bool compare_leafs(const Array* foreign, size_t start, size_t end, size_t baseindex, QueryState* state, + Callback callback) const; + + template + bool compare_leafs(const Array* foreign, size_t start, size_t end, size_t baseindex, QueryState* state, + Callback callback) const; + +// SSE find for the four functions Equal/NotEqual/Less/Greater +#ifdef REALM_COMPILER_SSE + template + bool find_sse(int64_t value, __m128i* data, size_t items, QueryState* state, size_t baseindex, + Callback callback) const; + + template + REALM_FORCEINLINE bool find_sse_intern(__m128i* action_data, __m128i* data, size_t items, + QueryState* state, size_t baseindex, Callback callback) const; + +#endif + + template + inline bool test_zero(uint64_t value) const; // Tests value for 0-elements + + template + size_t find_zero(uint64_t v) const; // Finds position of 0/non-zero element + + template + uint64_t cascade(uint64_t a) const; // Sets lowermost bits of zero or non-zero elements + + template + int64_t + find_gtlt_magic(int64_t v) const; // Compute magic constant needed for searching for value 'v' using bit hacks + + template + inline int64_t lower_bits() const; // Return chunk with lower bit set in each element + + size_t first_set_bit(unsigned int v) const; + size_t first_set_bit64(int64_t v) const; + + template + int64_t get_universal(const char* const data, const size_t ndx) const; + + // Find value greater/less in 64-bit chunk - only works for positive values + template + bool find_gtlt_fast(uint64_t chunk, uint64_t magic, QueryState* state, size_t baseindex, + Callback callback) const; + + // Find value greater/less in 64-bit chunk - no constraints + template + bool find_gtlt(int64_t v, uint64_t chunk, QueryState* state, size_t baseindex, Callback callback) const; + + ref_type bptree_leaf_insert(size_t ndx, int64_t, TreeInsertBase& state); + + /// Get the specified element without the cost of constructing an + /// array instance. If an array instance is already available, or + /// you need to get multiple values, then this method will be + /// slower. + static int_fast64_t get(const char* header, size_t ndx) noexcept; + + /// Like get(const char*, size_t) but gets two consecutive + /// elements. + static std::pair get_two(const char* header, size_t ndx) noexcept; + + static void get_three(const char* data, size_t ndx, ref_type& v0, ref_type& v1, ref_type& v2) noexcept; + + /// The meaning of 'width' depends on the context in which this + /// array is used. + size_t get_width() const noexcept + { + return m_width; + } + + static char* get_data_from_header(char*) noexcept; + static char* get_header_from_data(char*) noexcept; + static const char* get_data_from_header(const char*) noexcept; + + enum WidthType { + wtype_Bits = 0, + wtype_Multiply = 1, + wtype_Ignore = 2, + }; + + static bool get_is_inner_bptree_node_from_header(const char*) noexcept; + static bool get_hasrefs_from_header(const char*) noexcept; + static bool get_context_flag_from_header(const char*) noexcept; + static WidthType get_wtype_from_header(const char*) noexcept; + static uint_least8_t get_width_from_header(const char*) noexcept; + static size_t get_size_from_header(const char*) noexcept; + + static Type get_type_from_header(const char*) noexcept; + + /// Get the number of bytes currently in use by this array. This + /// includes the array header, but it does not include allocated + /// bytes corresponding to excess capacity. The result is + /// guaranteed to be a multiple of 8 (i.e., 64-bit aligned). + /// + /// This number is exactly the number of bytes that will be + /// written by a non-recursive invocation of write(). + size_t get_byte_size() const noexcept; + + /// Get the maximum number of bytes that can be written by a + /// non-recursive invocation of write() on an array with the + /// specified number of elements, that is, the maximum value that + /// can be returned by get_byte_size(). + static size_t get_max_byte_size(size_t num_elems) noexcept; + + /// FIXME: Belongs in IntegerArray + static size_t calc_aligned_byte_size(size_t size, int width); + + class MemUsageHandler { + public: + virtual void handle(ref_type ref, size_t allocated, size_t used) = 0; + }; + + void report_memory_usage(MemUsageHandler&) const; + + void stats(MemStats& stats_dest) const noexcept; + +#ifdef REALM_DEBUG + void print() const; + void verify() const; + typedef size_t (*LeafVerifier)(MemRef, Allocator&); + void verify_bptree(LeafVerifier) const; + typedef void (*LeafDumper)(MemRef, Allocator&, std::ostream&, int level); + void dump_bptree_structure(std::ostream&, int level, LeafDumper) const; + void to_dot(std::ostream&, StringData title = StringData()) const; + class ToDotHandler { + public: + virtual void to_dot(MemRef leaf_mem, ArrayParent*, size_t ndx_in_parent, std::ostream&) = 0; + ~ToDotHandler() + { + } + }; + void bptree_to_dot(std::ostream&, ToDotHandler&) const; + void to_dot_parent_edge(std::ostream&) const; +#endif + + static const int header_size = 8; // Number of bytes used by header + + // The encryption layer relies on headers always fitting within a single page. + static_assert(header_size == 8, "Header must always fit in entirely on a page"); + + Array& operator=(const Array&) = delete; // not allowed + Array(const Array&) = delete; // not allowed +protected: + typedef bool (*CallbackDummy)(int64_t); + +protected: + // Includes array header. Not necessarily 8-byte aligned. + virtual size_t calc_byte_len(size_t num_items, size_t width) const; + + virtual size_t calc_item_count(size_t bytes, size_t width) const noexcept; + + bool get_is_inner_bptree_node_from_header() const noexcept; + bool get_hasrefs_from_header() const noexcept; + bool get_context_flag_from_header() const noexcept; + WidthType get_wtype_from_header() const noexcept; + uint_least8_t get_width_from_header() const noexcept; + size_t get_size_from_header() const noexcept; + + // Undefined behavior if m_alloc.is_read_only(m_ref) returns true + size_t get_capacity_from_header() const noexcept; + + void set_header_is_inner_bptree_node(bool value) noexcept; + void set_header_hasrefs(bool value) noexcept; + void set_header_context_flag(bool value) noexcept; + void set_header_wtype(WidthType value) noexcept; + void set_header_width(int value) noexcept; + void set_header_size(size_t value) noexcept; + void set_header_capacity(size_t value) noexcept; + + static void set_header_is_inner_bptree_node(bool value, char* header) noexcept; + static void set_header_hasrefs(bool value, char* header) noexcept; + static void set_header_context_flag(bool value, char* header) noexcept; + static void set_header_wtype(WidthType value, char* header) noexcept; + static void set_header_width(int value, char* header) noexcept; + static void set_header_size(size_t value, char* header) noexcept; + static void set_header_capacity(size_t value, char* header) noexcept; + + static void init_header(char* header, bool is_inner_bptree_node, bool has_refs, bool context_flag, + WidthType width_type, int width, size_t size, size_t capacity) noexcept; + + + // This returns the minimum value ("lower bound") of the representable values + // for the given bit width. Valid widths are 0, 1, 2, 4, 8, 16, 32, and 64. + template + static int_fast64_t lbound_for_width() noexcept; + + static int_fast64_t lbound_for_width(size_t width) noexcept; + + // This returns the maximum value ("inclusive upper bound") of the representable values + // for the given bit width. Valid widths are 0, 1, 2, 4, 8, 16, 32, and 64. + template + static int_fast64_t ubound_for_width() noexcept; + + static int_fast64_t ubound_for_width(size_t width) noexcept; + + template + void set_width() noexcept; + void set_width(size_t) noexcept; + void alloc(size_t init_size, size_t width); + void copy_on_write(); + +private: + void do_copy_on_write(size_t minimum_size = 0); + void do_ensure_minimum_width(int_fast64_t); + + template + int64_t sum(size_t start, size_t end) const; + + template + bool minmax(int64_t& result, size_t start, size_t end, size_t* return_ndx) const; + + template + size_t find_gte(const int64_t target, size_t start, size_t end) const; + + template + size_t adjust_ge(size_t start, size_t end, int_fast64_t limit, int_fast64_t diff); + +protected: + /// The total size in bytes (including the header) of a new empty + /// array. Must be a multiple of 8 (i.e., 64-bit aligned). + static const size_t initial_capacity = 128; + + /// It is an error to specify a non-zero value unless the width + /// type is wtype_Bits. It is also an error to specify a non-zero + /// size if the width type is wtype_Ignore. + static MemRef create(Type, bool context_flag, WidthType, size_t size, int_fast64_t value, Allocator&); + + static MemRef clone(MemRef header, Allocator& alloc, Allocator& target_alloc); + + /// Get the address of the header of this array. + char* get_header() noexcept; + + /// Same as get_byte_size(). + static size_t get_byte_size_from_header(const char*) noexcept; + + // Undefined behavior if array is in immutable memory + static size_t get_capacity_from_header(const char*) noexcept; + + // Overriding method in ArrayParent + void update_child_ref(size_t, ref_type) override; + + // Overriding method in ArrayParent + ref_type get_child_ref(size_t) const noexcept override; + + void destroy_children(size_t offset = 0) noexcept; + + std::pair get_to_dot_parent(size_t ndx_in_parent) const override; + + bool is_read_only() const noexcept; + +protected: + // Getters and Setters for adaptive-packed arrays + typedef int64_t (Array::*Getter)(size_t) const; // Note: getters must not throw + typedef void (Array::*Setter)(size_t, int64_t); + typedef bool (Array::*Finder)(int64_t, size_t, size_t, size_t, QueryState*) const; + typedef void (Array::*ChunkGetter)(size_t, int64_t res[8]) const; // Note: getters must not throw + + struct VTable { + Getter getter; + ChunkGetter chunk_getter; + Setter setter; + Finder finder[cond_VTABLE_FINDER_COUNT]; // one for each active function pointer + }; + template + struct VTableForWidth; + +protected: + /// Takes a 64-bit value and returns the minimum number of bits needed + /// to fit the value. For alignment this is rounded up to nearest + /// log2. Posssible results {0, 1, 2, 4, 8, 16, 32, 64} + static size_t bit_width(int64_t value); + + void report_memory_usage_2(MemUsageHandler&) const; + +private: + Getter m_getter = nullptr; // cached to avoid indirection + const VTable* m_vtable = nullptr; + +public: + // FIXME: Should not be public + char* m_data = nullptr; // Points to first byte after header + +#if REALM_ENABLE_MEMDEBUG + // If m_no_relocation is false, then copy_on_write() will always relocate this array, regardless if it's + // required or not. If it's true, then it will never relocate, which is currently only expeted inside + // GroupWriter::write_group() due to a unique chicken/egg problem (see description there). + bool m_no_relocation = false; +#endif + +protected: + int64_t m_lbound; // min number that can be stored with current m_width + int64_t m_ubound; // max number that can be stored with current m_width + + size_t m_size = 0; // Number of elements currently stored. + size_t m_capacity = 0; // Number of elements that fit inside the allocated memory. + + Allocator& m_alloc; + +private: + size_t m_ref; + ArrayParent* m_parent = nullptr; + size_t m_ndx_in_parent = 0; // Ignored if m_parent is null. + +protected: + uint_least8_t m_width = 0; // Size of an element (meaning depend on type of array). + bool m_is_inner_bptree_node; // This array is an inner node of B+-tree. + bool m_has_refs; // Elements whose first bit is zero are refs to subarrays. + bool m_context_flag; // Meaning depends on context. + +private: + ref_type do_write_shallow(_impl::ArrayWriterBase&) const; + ref_type do_write_deep(_impl::ArrayWriterBase&, bool only_if_modified) const; + static size_t calc_byte_size(WidthType wtype, size_t size, uint_least8_t width) noexcept; + + friend class SlabAlloc; + friend class GroupWriter; + friend class StringColumn; +}; + + +// Implementation: + +class QueryStateBase { + virtual void dyncast() + { + } +}; + +template <> +class QueryState : public QueryStateBase { +public: + int64_t m_state; + size_t m_match_count; + size_t m_limit; + size_t m_minmax_index; // used only for min/max, to save index of current min/max value + + template + bool uses_val() + { + if (action == act_Max || action == act_Min || action == act_Sum) + return true; + else + return false; + } + + void init(Action action, IntegerColumn* akku, size_t limit) + { + m_match_count = 0; + m_limit = limit; + m_minmax_index = not_found; + + if (action == act_Max) + m_state = -0x7fffffffffffffffLL - 1LL; + else if (action == act_Min) + m_state = 0x7fffffffffffffffLL; + else if (action == act_ReturnFirst) + m_state = not_found; + else if (action == act_Sum) + m_state = 0; + else if (action == act_Count) + m_state = 0; + else if (action == act_FindAll) + m_state = reinterpret_cast(akku); + else if (action == act_CallbackIdx) { + } + else { + REALM_ASSERT_DEBUG(false); + } + } + + template + inline bool match(size_t index, uint64_t indexpattern, int64_t value) + { + if (pattern) { + if (action == act_Count) { + // If we are close to 'limit' argument in query, we cannot count-up a complete chunk. Count up single + // elements instead + if (m_match_count + 64 >= m_limit) + return false; + + m_state += fast_popcount64(indexpattern); + m_match_count = size_t(m_state); + return true; + } + // Other aggregates cannot (yet) use bit pattern for anything. Make Array-finder call with pattern = false + // instead + return false; + } + + ++m_match_count; + + if (action == act_Max) { + if (value > m_state) { + m_state = value; + m_minmax_index = index; + } + } + else if (action == act_Min) { + if (value < m_state) { + m_state = value; + m_minmax_index = index; + } + } + else if (action == act_Sum) + m_state += value; + else if (action == act_Count) { + m_state++; + m_match_count = size_t(m_state); + } + else if (action == act_FindAll) { + Array::add_to_column(reinterpret_cast(m_state), index); + } + else if (action == act_ReturnFirst) { + m_state = index; + return false; + } + else { + REALM_ASSERT_DEBUG(false); + } + return (m_limit > m_match_count); + } + + template + inline bool match(size_t index, uint64_t indexpattern, util::Optional value) + { + // FIXME: This is a temporary hack for nullable integers. + if (value) { + return match(index, indexpattern, *value); + } + + // If value is null, the only sensible actions are count, find_all, and return first. + // Max, min, and sum should all have no effect. + if (action == act_Count) { + m_state++; + m_match_count = size_t(m_state); + } + else if (action == act_FindAll) { + Array::add_to_column(reinterpret_cast(m_state), index); + } + else if (action == act_ReturnFirst) { + m_match_count++; + m_state = index; + return false; + } + return m_limit > m_match_count; + } +}; + +// Used only for Basic-types: currently float and double +template +class QueryState : public QueryStateBase { +public: + R m_state; + size_t m_match_count; + size_t m_limit; + size_t m_minmax_index; // used only for min/max, to save index of current min/max value + + template + bool uses_val() + { + return (action == act_Max || action == act_Min || action == act_Sum || action == act_Count); + } + + void init(Action action, Array*, size_t limit) + { + REALM_ASSERT((std::is_same::value || std::is_same::value)); + m_match_count = 0; + m_limit = limit; + m_minmax_index = not_found; + + if (action == act_Max) + m_state = -std::numeric_limits::infinity(); + else if (action == act_Min) + m_state = std::numeric_limits::infinity(); + else if (action == act_Sum) + m_state = 0.0; + else { + REALM_ASSERT_DEBUG(false); + } + } + + template + inline bool match(size_t index, uint64_t /*indexpattern*/, resulttype value) + { + if (pattern) + return false; + + static_assert(action == act_Sum || action == act_Max || action == act_Min || action == act_Count, + "Search action not supported"); + + if (action == act_Count) { + ++m_match_count; + } + else if (!null::is_null_float(value)) { + ++m_match_count; + if (action == act_Max) { + if (value > m_state) { + m_state = value; + m_minmax_index = index; + } + } + else if (action == act_Min) { + if (value < m_state) { + m_state = value; + m_minmax_index = index; + } + } + else if (action == act_Sum) + m_state += value; + else { + REALM_ASSERT_DEBUG(false); + } + } + + return (m_limit > m_match_count); + } +}; + +inline bool RefOrTagged::is_ref() const noexcept +{ + return (m_value & 1) == 0; +} + +inline bool RefOrTagged::is_tagged() const noexcept +{ + return !is_ref(); +} + +inline ref_type RefOrTagged::get_as_ref() const noexcept +{ + // to_ref() is defined in + return to_ref(m_value); +} + +inline uint_fast64_t RefOrTagged::get_as_int() const noexcept +{ + // The bitwise AND is there in case uint_fast64_t is wider than 64 bits. + return (uint_fast64_t(m_value) & 0xFFFFFFFFFFFFFFFFULL) >> 1; +} + +inline RefOrTagged RefOrTagged::make_ref(ref_type ref) noexcept +{ + // from_ref() is defined in + int_fast64_t value = from_ref(ref); + return RefOrTagged(value); +} + +inline RefOrTagged RefOrTagged::make_tagged(uint_fast64_t i) noexcept +{ + REALM_ASSERT(i < (1ULL << 63)); + int_fast64_t value = util::from_twos_compl((i << 1) | 1); + return RefOrTagged(value); +} + +inline RefOrTagged::RefOrTagged(int_fast64_t value) noexcept + : m_value(value) +{ +} + +inline Array::Array(Allocator& allocator) noexcept + : m_alloc(allocator) +{ +} + +inline void Array::create(Type type, bool context_flag, size_t length, int_fast64_t value) +{ + MemRef mem = create_array(type, context_flag, length, value, m_alloc); // Throws + init_from_mem(mem); +} + + +inline void Array::init_from_ref(ref_type ref) noexcept +{ + REALM_ASSERT_DEBUG(ref); + char* header = m_alloc.translate(ref); + init_from_mem(MemRef(header, ref, m_alloc)); +} + + +inline void Array::init_from_parent() noexcept +{ + ref_type ref = get_ref_from_parent(); + init_from_ref(ref); +} + + +inline Array::Type Array::get_type() const noexcept +{ + if (m_is_inner_bptree_node) { + REALM_ASSERT_DEBUG(m_has_refs); + return type_InnerBptreeNode; + } + if (m_has_refs) + return type_HasRefs; + return type_Normal; +} + + +inline void Array::get_chunk(size_t ndx, int64_t res[8]) const noexcept +{ + REALM_ASSERT_DEBUG(ndx < m_size); + (this->*(m_vtable->chunk_getter))(ndx, res); +} + + +inline int64_t Array::get(size_t ndx) const noexcept +{ + REALM_ASSERT_DEBUG(is_attached()); + REALM_ASSERT_DEBUG(ndx < m_size); + return (this->*m_getter)(ndx); + + // Two ideas that are not efficient but may be worth looking into again: + /* + // Assume correct width is found early in REALM_TEMPEX, which is the case for B tree offsets that + // are probably either 2^16 long. Turns out to be 25% faster if found immediately, but 50-300% slower + // if found later + REALM_TEMPEX(return get, (ndx)); + */ + /* + // Slightly slower in both of the if-cases. Also needs an matchcount m_size check too, to avoid + // reading beyond array. + if (m_width >= 8 && m_size > ndx + 7) + return get<64>(ndx >> m_shift) & m_widthmask; + else + return (this->*(m_vtable->getter))(ndx); + */ +} + +inline int64_t Array::front() const noexcept +{ + return get(0); +} + +inline int64_t Array::back() const noexcept +{ + return get(m_size - 1); +} + +inline ref_type Array::get_as_ref(size_t ndx) const noexcept +{ + REALM_ASSERT_DEBUG(is_attached()); + REALM_ASSERT_DEBUG(m_has_refs); + int64_t v = get(ndx); + return to_ref(v); +} + +inline RefOrTagged Array::get_as_ref_or_tagged(size_t ndx) const noexcept +{ + REALM_ASSERT(has_refs()); + return RefOrTagged(get(ndx)); +} + +inline void Array::set(size_t ndx, RefOrTagged ref_or_tagged) +{ + REALM_ASSERT(has_refs()); + set(ndx, ref_or_tagged.m_value); // Throws +} + +inline void Array::add(RefOrTagged ref_or_tagged) +{ + REALM_ASSERT(has_refs()); + add(ref_or_tagged.m_value); // Throws +} + +inline void Array::ensure_minimum_width(RefOrTagged ref_or_tagged) +{ + REALM_ASSERT(has_refs()); + ensure_minimum_width(ref_or_tagged.m_value); // Throws +} + +inline bool Array::is_inner_bptree_node() const noexcept +{ + return m_is_inner_bptree_node; +} + +inline bool Array::has_refs() const noexcept +{ + return m_has_refs; +} + +inline void Array::set_has_refs(bool value) noexcept +{ + if (m_has_refs != value) { + REALM_ASSERT(!is_read_only()); + m_has_refs = value; + set_header_hasrefs(value); + } +} + +inline bool Array::get_context_flag() const noexcept +{ + return m_context_flag; +} + +inline void Array::set_context_flag(bool value) noexcept +{ + if (m_context_flag != value) { + REALM_ASSERT(!is_read_only()); + m_context_flag = value; + set_header_context_flag(value); + } +} + +inline ref_type Array::get_ref() const noexcept +{ + return m_ref; +} + +inline MemRef Array::get_mem() const noexcept +{ + return MemRef(get_header_from_data(m_data), m_ref, m_alloc); +} + +inline void Array::destroy() noexcept +{ + if (!is_attached()) + return; + char* header = get_header_from_data(m_data); + m_alloc.free_(m_ref, header); + m_data = nullptr; +} + +inline void Array::destroy_deep() noexcept +{ + if (!is_attached()) + return; + + if (m_has_refs) + destroy_children(); + + char* header = get_header_from_data(m_data); + m_alloc.free_(m_ref, header); + m_data = nullptr; +} + +inline ref_type Array::write(_impl::ArrayWriterBase& out, bool deep, bool only_if_modified) const +{ + REALM_ASSERT(is_attached()); + + if (only_if_modified && m_alloc.is_read_only(m_ref)) + return m_ref; + + if (!deep || !m_has_refs) + return do_write_shallow(out); // Throws + + return do_write_deep(out, only_if_modified); // Throws +} + +inline ref_type Array::write(ref_type ref, Allocator& alloc, _impl::ArrayWriterBase& out, bool only_if_modified) +{ + if (only_if_modified && alloc.is_read_only(ref)) + return ref; + + Array array(alloc); + array.init_from_ref(ref); + + if (!array.m_has_refs) + return array.do_write_shallow(out); // Throws + + return array.do_write_deep(out, only_if_modified); // Throws +} + +inline void Array::add(int_fast64_t value) +{ + insert(m_size, value); +} + +inline void Array::erase(size_t ndx) +{ + // This can throw, but only if array is currently in read-only + // memory. + move(ndx + 1, size(), ndx); + + // Update size (also in header) + --m_size; + set_header_size(m_size); +} + + +inline void Array::erase(size_t begin, size_t end) +{ + if (begin != end) { + // This can throw, but only if array is currently in read-only memory. + move(end, size(), begin); // Throws + + // Update size (also in header) + m_size -= end - begin; + set_header_size(m_size); + } +} + +inline void Array::clear() +{ + truncate(0); // Throws +} + +inline void Array::clear_and_destroy_children() +{ + truncate_and_destroy_children(0); +} + +inline void Array::destroy(ref_type ref, Allocator& alloc) noexcept +{ + destroy(MemRef(ref, alloc), alloc); +} + +inline void Array::destroy(MemRef mem, Allocator& alloc) noexcept +{ + alloc.free_(mem); +} + +inline void Array::destroy_deep(ref_type ref, Allocator& alloc) noexcept +{ + destroy_deep(MemRef(ref, alloc), alloc); +} + +inline void Array::destroy_deep(MemRef mem, Allocator& alloc) noexcept +{ + if (!get_hasrefs_from_header(mem.get_addr())) { + alloc.free_(mem); + return; + } + Array array(alloc); + array.init_from_mem(mem); + array.destroy_deep(); +} + + +inline void Array::adjust(size_t ndx, int_fast64_t diff) +{ + REALM_ASSERT_3(ndx, <=, m_size); + if (diff != 0) { + // FIXME: Should be optimized + int_fast64_t v = get(ndx); + set(ndx, int64_t(v + diff)); // Throws + } +} + +inline void Array::adjust(size_t begin, size_t end, int_fast64_t diff) +{ + if (diff != 0) { + // FIXME: Should be optimized + for (size_t i = begin; i != end; ++i) + adjust(i, diff); // Throws + } +} + + +//------------------------------------------------- + +inline bool Array::get_is_inner_bptree_node_from_header(const char* header) noexcept +{ + typedef unsigned char uchar; + const uchar* h = reinterpret_cast(header); + return (int(h[4]) & 0x80) != 0; +} +inline bool Array::get_hasrefs_from_header(const char* header) noexcept +{ + typedef unsigned char uchar; + const uchar* h = reinterpret_cast(header); + return (int(h[4]) & 0x40) != 0; +} +inline bool Array::get_context_flag_from_header(const char* header) noexcept +{ + typedef unsigned char uchar; + const uchar* h = reinterpret_cast(header); + return (int(h[4]) & 0x20) != 0; +} +inline Array::WidthType Array::get_wtype_from_header(const char* header) noexcept +{ + typedef unsigned char uchar; + const uchar* h = reinterpret_cast(header); + return WidthType((int(h[4]) & 0x18) >> 3); +} +inline uint_least8_t Array::get_width_from_header(const char* header) noexcept +{ + typedef unsigned char uchar; + const uchar* h = reinterpret_cast(header); + return uint_least8_t((1 << (int(h[4]) & 0x07)) >> 1); +} +inline size_t Array::get_size_from_header(const char* header) noexcept +{ + typedef unsigned char uchar; + const uchar* h = reinterpret_cast(header); + return (size_t(h[5]) << 16) + (size_t(h[6]) << 8) + h[7]; +} +inline size_t Array::get_capacity_from_header(const char* header) noexcept +{ + typedef unsigned char uchar; + const uchar* h = reinterpret_cast(header); + return (size_t(h[0]) << 19) + (size_t(h[1]) << 11) + (h[2] << 3); +} + + +inline char* Array::get_data_from_header(char* header) noexcept +{ + return header + header_size; +} +inline char* Array::get_header_from_data(char* data) noexcept +{ + return data - header_size; +} +inline const char* Array::get_data_from_header(const char* header) noexcept +{ + return get_data_from_header(const_cast(header)); +} + + +inline bool Array::get_is_inner_bptree_node_from_header() const noexcept +{ + return get_is_inner_bptree_node_from_header(get_header_from_data(m_data)); +} +inline bool Array::get_hasrefs_from_header() const noexcept +{ + return get_hasrefs_from_header(get_header_from_data(m_data)); +} +inline bool Array::get_context_flag_from_header() const noexcept +{ + return get_context_flag_from_header(get_header_from_data(m_data)); +} +inline Array::WidthType Array::get_wtype_from_header() const noexcept +{ + return get_wtype_from_header(get_header_from_data(m_data)); +} +inline uint_least8_t Array::get_width_from_header() const noexcept +{ + return get_width_from_header(get_header_from_data(m_data)); +} +inline size_t Array::get_size_from_header() const noexcept +{ + return get_size_from_header(get_header_from_data(m_data)); +} +inline size_t Array::get_capacity_from_header() const noexcept +{ + return get_capacity_from_header(get_header_from_data(m_data)); +} + + +inline void Array::set_header_is_inner_bptree_node(bool value, char* header) noexcept +{ + typedef unsigned char uchar; + uchar* h = reinterpret_cast(header); + h[4] = uchar((int(h[4]) & ~0x80) | int(value) << 7); +} + +inline void Array::set_header_hasrefs(bool value, char* header) noexcept +{ + typedef unsigned char uchar; + uchar* h = reinterpret_cast(header); + h[4] = uchar((int(h[4]) & ~0x40) | int(value) << 6); +} + +inline void Array::set_header_context_flag(bool value, char* header) noexcept +{ + typedef unsigned char uchar; + uchar* h = reinterpret_cast(header); + h[4] = uchar((int(h[4]) & ~0x20) | int(value) << 5); +} + +inline void Array::set_header_wtype(WidthType value, char* header) noexcept +{ + // Indicates how to calculate size in bytes based on width + // 0: bits (width/8) * size + // 1: multiply width * size + // 2: ignore 1 * size + typedef unsigned char uchar; + uchar* h = reinterpret_cast(header); + h[4] = uchar((int(h[4]) & ~0x18) | int(value) << 3); +} + +inline void Array::set_header_width(int value, char* header) noexcept +{ + // Pack width in 3 bits (log2) + int w = 0; + while (value) { + ++w; + value >>= 1; + } + REALM_ASSERT_3(w, <, 8); + + typedef unsigned char uchar; + uchar* h = reinterpret_cast(header); + h[4] = uchar((int(h[4]) & ~0x7) | w); +} + +inline void Array::set_header_size(size_t value, char* header) noexcept +{ + REALM_ASSERT_3(value, <=, max_array_size); + typedef unsigned char uchar; + uchar* h = reinterpret_cast(header); + h[5] = uchar((value >> 16) & 0x000000FF); + h[6] = uchar((value >> 8) & 0x000000FF); + h[7] = uchar(value & 0x000000FF); +} + +// Note: There is a copy of this function is test_alloc.cpp +inline void Array::set_header_capacity(size_t value, char* header) noexcept +{ + REALM_ASSERT_3(value, <=, (0xffffff << 3)); + typedef unsigned char uchar; + uchar* h = reinterpret_cast(header); + h[0] = uchar((value >> 19) & 0x000000FF); + h[1] = uchar((value >> 11) & 0x000000FF); + h[2] = uchar(value >> 3 & 0x000000FF); +} + + +inline void Array::set_header_is_inner_bptree_node(bool value) noexcept +{ + set_header_is_inner_bptree_node(value, get_header_from_data(m_data)); +} +inline void Array::set_header_hasrefs(bool value) noexcept +{ + set_header_hasrefs(value, get_header_from_data(m_data)); +} +inline void Array::set_header_context_flag(bool value) noexcept +{ + set_header_context_flag(value, get_header_from_data(m_data)); +} +inline void Array::set_header_wtype(WidthType value) noexcept +{ + set_header_wtype(value, get_header_from_data(m_data)); +} +inline void Array::set_header_width(int value) noexcept +{ + set_header_width(value, get_header_from_data(m_data)); +} +inline void Array::set_header_size(size_t value) noexcept +{ + set_header_size(value, get_header_from_data(m_data)); +} +inline void Array::set_header_capacity(size_t value) noexcept +{ + set_header_capacity(value, get_header_from_data(m_data)); +} + + +inline Array::Type Array::get_type_from_header(const char* header) noexcept +{ + if (get_is_inner_bptree_node_from_header(header)) + return type_InnerBptreeNode; + if (get_hasrefs_from_header(header)) + return type_HasRefs; + return type_Normal; +} + + +inline char* Array::get_header() noexcept +{ + return get_header_from_data(m_data); +} + +inline size_t Array::calc_byte_size(WidthType wtype, size_t size, uint_least8_t width) noexcept +{ + size_t num_bytes = 0; + switch (wtype) { + case wtype_Bits: { + // Current assumption is that size is at most 2^24 and that width is at most 64. + // In that case the following will never overflow. (Assuming that size_t is at least 32 bits) + REALM_ASSERT_3(size, <, 0x1000000); + size_t num_bits = size * width; + num_bytes = (num_bits + 7) >> 3; + break; + } + case wtype_Multiply: { + num_bytes = size * width; + break; + } + case wtype_Ignore: + num_bytes = size; + break; + } + + // Ensure 8-byte alignment + num_bytes = (num_bytes + 7) & ~size_t(7); + + num_bytes += header_size; + + return num_bytes; +} + +inline size_t Array::get_byte_size() const noexcept +{ + const char* header = get_header_from_data(m_data); + WidthType wtype = get_wtype_from_header(header); + size_t num_bytes = calc_byte_size(wtype, m_size, m_width); + + REALM_ASSERT_7(m_alloc.is_read_only(m_ref), ==, true, ||, num_bytes, <=, get_capacity_from_header(header)); + + return num_bytes; +} + + +inline size_t Array::get_byte_size_from_header(const char* header) noexcept +{ + size_t size = get_size_from_header(header); + uint_least8_t width = get_width_from_header(header); + WidthType wtype = get_wtype_from_header(header); + size_t num_bytes = calc_byte_size(wtype, size, width); + + return num_bytes; +} + + +inline void Array::init_header(char* header, bool is_inner_bptree_node, bool has_refs, bool context_flag, + WidthType width_type, int width, size_t size, size_t capacity) noexcept +{ + // Note: Since the header layout contains unallocated bit and/or + // bytes, it is important that we put the entire header into a + // well defined state initially. + std::fill(header, header + header_size, 0); + set_header_is_inner_bptree_node(is_inner_bptree_node, header); + set_header_hasrefs(has_refs, header); + set_header_context_flag(context_flag, header); + set_header_wtype(width_type, header); + set_header_width(width, header); + set_header_size(size, header); + set_header_capacity(capacity, header); +} + + +//------------------------------------------------- + +inline MemRef Array::clone_deep(Allocator& target_alloc) const +{ + char* header = get_header_from_data(m_data); + return clone(MemRef(header, m_ref, m_alloc), m_alloc, target_alloc); // Throws +} + +inline MemRef Array::create_empty_array(Type type, bool context_flag, Allocator& alloc) +{ + size_t size = 0; + int_fast64_t value = 0; + return create_array(type, context_flag, size, value, alloc); // Throws +} + +inline MemRef Array::create_array(Type type, bool context_flag, size_t size, int_fast64_t value, Allocator& alloc) +{ + return create(type, context_flag, wtype_Bits, size, value, alloc); // Throws +} + +inline bool Array::has_parent() const noexcept +{ + return m_parent != nullptr; +} + +inline ArrayParent* Array::get_parent() const noexcept +{ + return m_parent; +} + +inline void Array::set_parent(ArrayParent* parent, size_t ndx_in_parent) noexcept +{ + m_parent = parent; + m_ndx_in_parent = ndx_in_parent; +} + +inline size_t Array::get_ndx_in_parent() const noexcept +{ + return m_ndx_in_parent; +} + +inline void Array::set_ndx_in_parent(size_t ndx) noexcept +{ + m_ndx_in_parent = ndx; +} + +inline void Array::adjust_ndx_in_parent(int diff) noexcept +{ + // Note that `diff` is promoted to an unsigned type, and that + // C++03 still guarantees the expected result regardless of the + // sizes of `int` and `decltype(m_ndx_in_parent)`. + m_ndx_in_parent += diff; +} + +inline ref_type Array::get_ref_from_parent() const noexcept +{ + ref_type ref = m_parent->get_child_ref(m_ndx_in_parent); + return ref; +} + +inline bool Array::is_attached() const noexcept +{ + return m_data != nullptr; +} + +inline void Array::detach() noexcept +{ + m_data = nullptr; +} + +inline size_t Array::size() const noexcept +{ + REALM_ASSERT_DEBUG(is_attached()); + return m_size; +} + +inline bool Array::is_empty() const noexcept +{ + return size() == 0; +} + +inline size_t Array::get_max_byte_size(size_t num_elems) noexcept +{ + int max_bytes_per_elem = 8; + return header_size + num_elems * max_bytes_per_elem; +} + +inline void Array::update_parent() +{ + if (m_parent) + m_parent->update_child_ref(m_ndx_in_parent, m_ref); +} + + +inline void Array::update_child_ref(size_t child_ndx, ref_type new_ref) +{ + set(child_ndx, new_ref); +} + +inline ref_type Array::get_child_ref(size_t child_ndx) const noexcept +{ + return get_as_ref(child_ndx); +} + +inline bool Array::is_read_only() const noexcept +{ + REALM_ASSERT_DEBUG(is_attached()); + return m_alloc.is_read_only(m_ref); +} + +inline void Array::copy_on_write() +{ +#if REALM_ENABLE_MEMDEBUG + // We want to relocate this array regardless if there is a need or not, in order to catch use-after-free bugs. + // Only exception is inside GroupWriter::write_group() (see explanation at the definition of the m_no_relocation + // member) + if (!m_no_relocation) { +#else + if (is_read_only()) { +#endif + do_copy_on_write(); + } +} + +inline void Array::ensure_minimum_width(int_fast64_t value) +{ + if (value >= m_lbound && value <= m_ubound) + return; + do_ensure_minimum_width(value); +} + + +//************************************************************************************* +// Finding code * +//************************************************************************************* + +template +int64_t Array::get(size_t ndx) const noexcept +{ + return get_universal(m_data, ndx); +} + +template +int64_t Array::get_universal(const char* data, size_t ndx) const +{ + if (w == 0) { + return 0; + } + else if (w == 1) { + size_t offset = ndx >> 3; + return (data[offset] >> (ndx & 7)) & 0x01; + } + else if (w == 2) { + size_t offset = ndx >> 2; + return (data[offset] >> ((ndx & 3) << 1)) & 0x03; + } + else if (w == 4) { + size_t offset = ndx >> 1; + return (data[offset] >> ((ndx & 1) << 2)) & 0x0F; + } + else if (w == 8) { + return *reinterpret_cast(data + ndx); + } + else if (w == 16) { + size_t offset = ndx * 2; + return *reinterpret_cast(data + offset); + } + else if (w == 32) { + size_t offset = ndx * 4; + return *reinterpret_cast(data + offset); + } + else if (w == 64) { + size_t offset = ndx * 8; + return *reinterpret_cast(data + offset); + } + else { + REALM_ASSERT_DEBUG(false); + return int64_t(-1); + } +} + +/* +find() (calls find_optimized()) will call match() for each search result. + +If pattern == true: + 'indexpattern' contains a 64-bit chunk of elements, each of 'width' bits in size where each element indicates a + match if its lower bit is set, otherwise it indicates a non-match. 'index' tells the database row index of the + first element. You must return true if you chose to 'consume' the chunk or false if not. If not, then Array-finder + will afterwards call match() successive times with pattern == false. + +If pattern == false: + 'index' tells the row index of a single match and 'value' tells its value. Return false to make Array-finder break + its search or return true to let it continue until 'end' or 'limit'. + +Array-finder decides itself if - and when - it wants to pass you an indexpattern. It depends on array bit width, match +frequency, and whether the arithemetic and computations for the given search criteria makes it feasible to construct +such a pattern. +*/ + +// These wrapper functions only exist to enable a possibility to make the compiler see that 'value' and/or 'index' are +// unused, such that caller's computation of these values will not be made. Only works if find_action() and +// find_action_pattern() rewritten as macros. Note: This problem has been fixed in next upcoming array.hpp version +template +bool Array::find_action(size_t index, util::Optional value, QueryState* state, + Callback callback) const +{ + if (action == act_CallbackIdx) + return callback(index); + else + return state->match(index, 0, value); +} +template +bool Array::find_action_pattern(size_t index, uint64_t pattern, QueryState* state, Callback callback) const +{ + static_cast(callback); + if (action == act_CallbackIdx) { + // Possible future optimization: call callback(index) like in above find_action(), in a loop for each bit set + // in 'pattern' + return false; + } + return state->match(index, pattern, 0); +} + + +template +uint64_t Array::cascade(uint64_t a) const +{ + // Takes a chunk of values as argument and sets the least significant bit for each + // element which is zero or non-zero, depending on the template parameter. + // Example for zero=true: + // width == 4 and a = 0x5fd07a107610f610 + // will return: 0x0001000100010001 + + // static values needed for fast population count + const uint64_t m1 = 0x5555555555555555ULL; + + if (width == 1) { + return zero ? ~a : a; + } + else if (width == 2) { + // Masks to avoid spillover between segments in cascades + const uint64_t c1 = ~0ULL / 0x3 * 0x1; + + a |= (a >> 1) & c1; // cascade ones in non-zeroed segments + a &= m1; // isolate single bit in each segment + if (zero) + a ^= m1; // reverse isolated bits if checking for zeroed segments + + return a; + } + else if (width == 4) { + const uint64_t m = ~0ULL / 0xF * 0x1; + + // Masks to avoid spillover between segments in cascades + const uint64_t c1 = ~0ULL / 0xF * 0x7; + const uint64_t c2 = ~0ULL / 0xF * 0x3; + + a |= (a >> 1) & c1; // cascade ones in non-zeroed segments + a |= (a >> 2) & c2; + a &= m; // isolate single bit in each segment + if (zero) + a ^= m; // reverse isolated bits if checking for zeroed segments + + return a; + } + else if (width == 8) { + const uint64_t m = ~0ULL / 0xFF * 0x1; + + // Masks to avoid spillover between segments in cascades + const uint64_t c1 = ~0ULL / 0xFF * 0x7F; + const uint64_t c2 = ~0ULL / 0xFF * 0x3F; + const uint64_t c3 = ~0ULL / 0xFF * 0x0F; + + a |= (a >> 1) & c1; // cascade ones in non-zeroed segments + a |= (a >> 2) & c2; + a |= (a >> 4) & c3; + a &= m; // isolate single bit in each segment + if (zero) + a ^= m; // reverse isolated bits if checking for zeroed segments + + return a; + } + else if (width == 16) { + const uint64_t m = ~0ULL / 0xFFFF * 0x1; + + // Masks to avoid spillover between segments in cascades + const uint64_t c1 = ~0ULL / 0xFFFF * 0x7FFF; + const uint64_t c2 = ~0ULL / 0xFFFF * 0x3FFF; + const uint64_t c3 = ~0ULL / 0xFFFF * 0x0FFF; + const uint64_t c4 = ~0ULL / 0xFFFF * 0x00FF; + + a |= (a >> 1) & c1; // cascade ones in non-zeroed segments + a |= (a >> 2) & c2; + a |= (a >> 4) & c3; + a |= (a >> 8) & c4; + a &= m; // isolate single bit in each segment + if (zero) + a ^= m; // reverse isolated bits if checking for zeroed segments + + return a; + } + + else if (width == 32) { + const uint64_t m = ~0ULL / 0xFFFFFFFF * 0x1; + + // Masks to avoid spillover between segments in cascades + const uint64_t c1 = ~0ULL / 0xFFFFFFFF * 0x7FFFFFFF; + const uint64_t c2 = ~0ULL / 0xFFFFFFFF * 0x3FFFFFFF; + const uint64_t c3 = ~0ULL / 0xFFFFFFFF * 0x0FFFFFFF; + const uint64_t c4 = ~0ULL / 0xFFFFFFFF * 0x00FFFFFF; + const uint64_t c5 = ~0ULL / 0xFFFFFFFF * 0x0000FFFF; + + a |= (a >> 1) & c1; // cascade ones in non-zeroed segments + a |= (a >> 2) & c2; + a |= (a >> 4) & c3; + a |= (a >> 8) & c4; + a |= (a >> 16) & c5; + a &= m; // isolate single bit in each segment + if (zero) + a ^= m; // reverse isolated bits if checking for zeroed segments + + return a; + } + else if (width == 64) { + return (a == 0) == zero; + } + else { + REALM_ASSERT_DEBUG(false); + return uint64_t(-1); + } +} + +// This is the main finding function for Array. Other finding functions are just wrappers around this one. +// Search for 'value' using condition cond (Equal, NotEqual, Less, etc) and call find_action() or +// find_action_pattern() for each match. Break and return if find_action() returns false or 'end' is reached. + +// If nullable_array is set, then find_optimized() will treat the array is being nullable, i.e. it will skip the +// first entry and compare correctly against null, etc. +// +// If find_null is set, it means that we search for a null. In that case, `value` is ignored. If find_null is set, +// then nullable_array must be set too. +template +bool Array::find_optimized(int64_t value, size_t start, size_t end, size_t baseindex, QueryState* state, + Callback callback, bool nullable_array, bool find_null) const +{ + REALM_ASSERT(!(find_null && !nullable_array)); + REALM_ASSERT_DEBUG(start <= m_size && (end <= m_size || end == size_t(-1)) && start <= end); + + size_t start2 = start; + cond c; + + if (end == npos) + end = nullable_array ? size() - 1 : size(); + + if (nullable_array) { + if (std::is_same::value) { + // In case of Equal it is safe to use the optimized logic. We just have to fetch the null value + // if this is what we are looking for. And we have to adjust the indexes to compensate for the + // null value at position 0. + if (find_null) { + value = get(0); + } + else { + // If the value to search for is equal to the null value, the value cannot be in the array + if (value == get(0)) { + return true; + } + } + start2++; + end++; + baseindex--; + } + else { + // We were called by find() of a nullable array. So skip first entry, take nulls in count, etc, etc. Fixme: + // Huge speed optimizations are possible here! This is a very simple generic method. + auto null_value = get(0); + for (; start2 < end; start2++) { + int64_t v = get(start2 + 1); + bool value_is_null = (v == null_value); + if (c(v, value, value_is_null, find_null)) { + util::Optional v2(value_is_null ? util::none : util::make_optional(v)); + if (!find_action(start2 + baseindex, v2, state, callback)) + return false; // tell caller to stop aggregating/search + } + } + return true; // tell caller to continue aggregating/search (on next array leafs) + } + } + + + // Test first few items with no initial time overhead + if (start2 > 0) { + if (m_size > start2 && c(get(start2), value) && start2 < end) { + if (!find_action(start2 + baseindex, get(start2), state, callback)) + return false; + } + + ++start2; + + if (m_size > start2 && c(get(start2), value) && start2 < end) { + if (!find_action(start2 + baseindex, get(start2), state, callback)) + return false; + } + + ++start2; + + if (m_size > start2 && c(get(start2), value) && start2 < end) { + if (!find_action(start2 + baseindex, get(start2), state, callback)) + return false; + } + + ++start2; + + if (m_size > start2 && c(get(start2), value) && start2 < end) { + if (!find_action(start2 + baseindex, get(start2), state, callback)) + return false; + } + + ++start2; + } + + if (!(m_size > start2 && start2 < end)) + return true; + + if (end == size_t(-1)) + end = m_size; + + // Return immediately if no items in array can match (such as if cond == Greater && value == 100 && + // m_ubound == 15) + if (!c.can_match(value, m_lbound, m_ubound)) + return true; + + // optimization if all items are guaranteed to match (such as cond == NotEqual && value == 100 && m_ubound == 15) + if (c.will_match(value, m_lbound, m_ubound)) { + size_t end2; + + if (action == act_CallbackIdx) + end2 = end; + else { + REALM_ASSERT_DEBUG(state->m_match_count < state->m_limit); + size_t process = state->m_limit - state->m_match_count; + end2 = end - start2 > process ? start2 + process : end; + } + if (action == act_Sum || action == act_Max || action == act_Min) { + int64_t res; + size_t res_ndx = 0; + if (action == act_Sum) + res = Array::sum(start2, end2); + if (action == act_Max) + Array::maximum(res, start2, end2, &res_ndx); + if (action == act_Min) + Array::minimum(res, start2, end2, &res_ndx); + + find_action(res_ndx + baseindex, res, state, callback); + // find_action will increment match count by 1, so we need to `-1` from the number of elements that + // we performed the fast Array methods on. + state->m_match_count += end2 - start2 - 1; + } + else if (action == act_Count) { + state->m_state += end2 - start2; + } + else { + for (; start2 < end2; start2++) + if (!find_action(start2 + baseindex, get(start2), state, callback)) + return false; + } + return true; + } + + // finder cannot handle this bitwidth + REALM_ASSERT_3(m_width, !=, 0); + +#if defined(REALM_COMPILER_SSE) + // Only use SSE if payload is at least one SSE chunk (128 bits) in size. Also note taht SSE doesn't support + // Less-than comparison for 64-bit values. + if ((!(std::is_same::value && m_width == 64)) && end - start2 >= sizeof(__m128i) && m_width >= 8 && + (sseavx<42>() || (sseavx<30>() && std::is_same::value && m_width < 64))) { + + // find_sse() must start2 at 16-byte boundary, so search area before that using compare_equality() + __m128i* const a = reinterpret_cast<__m128i*>(round_up(m_data + start2 * bitwidth / 8, sizeof(__m128i))); + __m128i* const b = reinterpret_cast<__m128i*>(round_down(m_data + end * bitwidth / 8, sizeof(__m128i))); + + if (!compare( + value, start2, (reinterpret_cast(a) - m_data) * 8 / no0(bitwidth), baseindex, state, callback)) + return false; + + // Search aligned area with SSE + if (b > a) { + if (sseavx<42>()) { + if (!find_sse( + value, a, b - a, state, + baseindex + ((reinterpret_cast(a) - m_data) * 8 / no0(bitwidth)), callback)) + return false; + } + else if (sseavx<30>()) { + + if (!find_sse( + value, a, b - a, state, + baseindex + ((reinterpret_cast(a) - m_data) * 8 / no0(bitwidth)), callback)) + return false; + } + } + + // Search remainder with compare_equality() + if (!compare( + value, (reinterpret_cast(b) - m_data) * 8 / no0(bitwidth), end, baseindex, state, callback)) + return false; + + return true; + } + else { + return compare(value, start2, end, baseindex, state, callback); + } +#else + return compare(value, start2, end, baseindex, state, callback); +#endif +} + +template +inline int64_t Array::lower_bits() const +{ + if (width == 1) + return 0xFFFFFFFFFFFFFFFFULL; + else if (width == 2) + return 0x5555555555555555ULL; + else if (width == 4) + return 0x1111111111111111ULL; + else if (width == 8) + return 0x0101010101010101ULL; + else if (width == 16) + return 0x0001000100010001ULL; + else if (width == 32) + return 0x0000000100000001ULL; + else if (width == 64) + return 0x0000000000000001ULL; + else { + REALM_ASSERT_DEBUG(false); + return int64_t(-1); + } +} + +// Tests if any chunk in 'value' is 0 +template +inline bool Array::test_zero(uint64_t value) const +{ + uint64_t hasZeroByte; + uint64_t lower = lower_bits(); + uint64_t upper = lower_bits() * 1ULL << (width == 0 ? 0 : (width - 1ULL)); + hasZeroByte = (value - lower) & ~value & upper; + return hasZeroByte != 0; +} + +// Finds first zero (if eq == true) or non-zero (if eq == false) element in v and returns its position. +// IMPORTANT: This function assumes that at least 1 item matches (test this with test_zero() or other means first)! +template +size_t Array::find_zero(uint64_t v) const +{ + size_t start = 0; + uint64_t hasZeroByte; + // Warning free way of computing (1ULL << width) - 1 + uint64_t mask = (width == 64 ? ~0ULL : ((1ULL << (width == 64 ? 0 : width)) - 1ULL)); + + if (eq == (((v >> (width * start)) & mask) == 0)) { + return 0; + } + + // Bisection optimization, speeds up small bitwidths with high match frequency. More partions than 2 do NOT pay + // off because the work done by test_zero() is wasted for the cases where the value exists in first half, but + // useful if it exists in last half. Sweet spot turns out to be the widths and partitions below. + if (width <= 8) { + hasZeroByte = test_zero(v | 0xffffffff00000000ULL); + if (eq ? !hasZeroByte : (v & 0x00000000ffffffffULL) == 0) { + // 00?? -> increasing + start += 64 / no0(width) / 2; + if (width <= 4) { + hasZeroByte = test_zero(v | 0xffff000000000000ULL); + if (eq ? !hasZeroByte : (v & 0x0000ffffffffffffULL) == 0) { + // 000? + start += 64 / no0(width) / 4; + } + } + } + else { + if (width <= 4) { + // ??00 + hasZeroByte = test_zero(v | 0xffffffffffff0000ULL); + if (eq ? !hasZeroByte : (v & 0x000000000000ffffULL) == 0) { + // 0?00 + start += 64 / no0(width) / 4; + } + } + } + } + + while (eq == (((v >> (width * start)) & mask) != 0)) { + // You must only call find_zero() if you are sure that at least 1 item matches + REALM_ASSERT_3(start, <=, 8 * sizeof(v)); + start++; + } + + return start; +} + +// Generate a magic constant used for later bithacks +template +int64_t Array::find_gtlt_magic(int64_t v) const +{ + uint64_t mask1 = (width == 64 ? ~0ULL : ((1ULL << (width == 64 ? 0 : width)) - + 1ULL)); // Warning free way of computing (1ULL << width) - 1 + uint64_t mask2 = mask1 >> 1; + uint64_t magic = gt ? (~0ULL / no0(mask1) * (mask2 - v)) : (~0ULL / no0(mask1) * v); + return magic; +} + +template +bool Array::find_gtlt_fast(uint64_t chunk, uint64_t magic, QueryState* state, size_t baseindex, + Callback callback) const +{ + // Tests if a a chunk of values contains values that are greater (if gt == true) or less (if gt == false) than v. + // Fast, but limited to work when all values in the chunk are positive. + + uint64_t mask1 = (width == 64 ? ~0ULL : ((1ULL << (width == 64 ? 0 : width)) - + 1ULL)); // Warning free way of computing (1ULL << width) - 1 + uint64_t mask2 = mask1 >> 1; + uint64_t m = gt ? (((chunk + magic) | chunk) & ~0ULL / no0(mask1) * (mask2 + 1)) + : ((chunk - magic) & ~chunk & ~0ULL / no0(mask1) * (mask2 + 1)); + size_t p = 0; + while (m) { + if (find_action_pattern(baseindex, m >> (no0(width) - 1), state, callback)) + break; // consumed, so do not call find_action() + + size_t t = first_set_bit64(m) / no0(width); + p += t; + if (!find_action(p + baseindex, (chunk >> (p * width)) & mask1, state, callback)) + return false; + + if ((t + 1) * width == 64) + m = 0; + else + m >>= (t + 1) * width; + p++; + } + + return true; +} + +// clang-format off +template +bool Array::find_gtlt(int64_t v, uint64_t chunk, QueryState* state, size_t baseindex, Callback callback) const +{ + // Find items in 'chunk' that are greater (if gt == true) or smaller (if gt == false) than 'v'. Fixme, __forceinline can make it crash in vS2010 - find out why + if (width == 1) { + for (size_t t = 0; t < 64; t++) { + if (gt ? static_cast(chunk & 0x1) > v : static_cast(chunk & 0x1) < v) {if (!find_action( t + baseindex, static_cast(chunk & 0x1), state, callback)) return false;} + chunk >>= 1; + } + } + else if (width == 2) { + // Alot (50% +) faster than loop/compiler-unrolled loop + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 0 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 1 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 2 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 3 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 4 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 5 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 6 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 7 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 8 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 9 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 10 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 11 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 12 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 13 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 14 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 15 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 16 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 17 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 18 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 19 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 20 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 21 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 22 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 23 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 24 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 25 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 26 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 27 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 28 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 29 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 30 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + if (gt ? static_cast(chunk & 0x3) > v : static_cast(chunk & 0x3) < v) {if (!find_action( 31 + baseindex, static_cast(chunk & 0x3), state, callback)) return false;} + chunk >>= 2; + } + else if (width == 4) { + if (gt ? static_cast(chunk & 0xf) > v : static_cast(chunk & 0xf) < v) {if (!find_action( 0 + baseindex, static_cast(chunk & 0xf), state, callback)) return false;} + chunk >>= 4; + if (gt ? static_cast(chunk & 0xf) > v : static_cast(chunk & 0xf) < v) {if (!find_action( 1 + baseindex, static_cast(chunk & 0xf), state, callback)) return false;} + chunk >>= 4; + if (gt ? static_cast(chunk & 0xf) > v : static_cast(chunk & 0xf) < v) {if (!find_action( 2 + baseindex, static_cast(chunk & 0xf), state, callback)) return false;} + chunk >>= 4; + if (gt ? static_cast(chunk & 0xf) > v : static_cast(chunk & 0xf) < v) {if (!find_action( 3 + baseindex, static_cast(chunk & 0xf), state, callback)) return false;} + chunk >>= 4; + if (gt ? static_cast(chunk & 0xf) > v : static_cast(chunk & 0xf) < v) {if (!find_action( 4 + baseindex, static_cast(chunk & 0xf), state, callback)) return false;} + chunk >>= 4; + if (gt ? static_cast(chunk & 0xf) > v : static_cast(chunk & 0xf) < v) {if (!find_action( 5 + baseindex, static_cast(chunk & 0xf), state, callback)) return false;} + chunk >>= 4; + if (gt ? static_cast(chunk & 0xf) > v : static_cast(chunk & 0xf) < v) {if (!find_action( 6 + baseindex, static_cast(chunk & 0xf), state, callback)) return false;} + chunk >>= 4; + if (gt ? static_cast(chunk & 0xf) > v : static_cast(chunk & 0xf) < v) {if (!find_action( 7 + baseindex, static_cast(chunk & 0xf), state, callback)) return false;} + chunk >>= 4; + + if (gt ? static_cast(chunk & 0xf) > v : static_cast(chunk & 0xf) < v) {if (!find_action( 8 + baseindex, static_cast(chunk & 0xf), state, callback)) return false;} + chunk >>= 4; + if (gt ? static_cast(chunk & 0xf) > v : static_cast(chunk & 0xf) < v) {if (!find_action( 9 + baseindex, static_cast(chunk & 0xf), state, callback)) return false;} + chunk >>= 4; + if (gt ? static_cast(chunk & 0xf) > v : static_cast(chunk & 0xf) < v) {if (!find_action( 10 + baseindex, static_cast(chunk & 0xf), state, callback)) return false;} + chunk >>= 4; + if (gt ? static_cast(chunk & 0xf) > v : static_cast(chunk & 0xf) < v) {if (!find_action( 11 + baseindex, static_cast(chunk & 0xf), state, callback)) return false;} + chunk >>= 4; + if (gt ? static_cast(chunk & 0xf) > v : static_cast(chunk & 0xf) < v) {if (!find_action( 12 + baseindex, static_cast(chunk & 0xf), state, callback)) return false;} + chunk >>= 4; + if (gt ? static_cast(chunk & 0xf) > v : static_cast(chunk & 0xf) < v) {if (!find_action( 13 + baseindex, static_cast(chunk & 0xf), state, callback)) return false;} + chunk >>= 4; + if (gt ? static_cast(chunk & 0xf) > v : static_cast(chunk & 0xf) < v) {if (!find_action( 14 + baseindex, static_cast(chunk & 0xf), state, callback)) return false;} + chunk >>= 4; + if (gt ? static_cast(chunk & 0xf) > v : static_cast(chunk & 0xf) < v) {if (!find_action( 15 + baseindex, static_cast(chunk & 0xf), state, callback)) return false;} + chunk >>= 4; + } + else if (width == 8) { + if (gt ? static_cast(chunk) > v : static_cast(chunk) < v) {if (!find_action( 0 + baseindex, static_cast(chunk), state, callback)) return false;} + chunk >>= 8; + if (gt ? static_cast(chunk) > v : static_cast(chunk) < v) {if (!find_action( 1 + baseindex, static_cast(chunk), state, callback)) return false;} + chunk >>= 8; + if (gt ? static_cast(chunk) > v : static_cast(chunk) < v) {if (!find_action( 2 + baseindex, static_cast(chunk), state, callback)) return false;} + chunk >>= 8; + if (gt ? static_cast(chunk) > v : static_cast(chunk) < v) {if (!find_action( 3 + baseindex, static_cast(chunk), state, callback)) return false;} + chunk >>= 8; + if (gt ? static_cast(chunk) > v : static_cast(chunk) < v) {if (!find_action( 4 + baseindex, static_cast(chunk), state, callback)) return false;} + chunk >>= 8; + if (gt ? static_cast(chunk) > v : static_cast(chunk) < v) {if (!find_action( 5 + baseindex, static_cast(chunk), state, callback)) return false;} + chunk >>= 8; + if (gt ? static_cast(chunk) > v : static_cast(chunk) < v) {if (!find_action( 6 + baseindex, static_cast(chunk), state, callback)) return false;} + chunk >>= 8; + if (gt ? static_cast(chunk) > v : static_cast(chunk) < v) {if (!find_action( 7 + baseindex, static_cast(chunk), state, callback)) return false;} + chunk >>= 8; + } + else if (width == 16) { + + if (gt ? static_cast(chunk >> 0 * 16) > v : static_cast(chunk >> 0 * 16) < v) {if (!find_action( 0 + baseindex, static_cast(chunk >> 0 * 16), state, callback)) return false;}; + if (gt ? static_cast(chunk >> 1 * 16) > v : static_cast(chunk >> 1 * 16) < v) {if (!find_action( 1 + baseindex, static_cast(chunk >> 1 * 16), state, callback)) return false;}; + if (gt ? static_cast(chunk >> 2 * 16) > v : static_cast(chunk >> 2 * 16) < v) {if (!find_action( 2 + baseindex, static_cast(chunk >> 2 * 16), state, callback)) return false;}; + if (gt ? static_cast(chunk >> 3 * 16) > v : static_cast(chunk >> 3 * 16) < v) {if (!find_action( 3 + baseindex, static_cast(chunk >> 3 * 16), state, callback)) return false;}; + } + else if (width == 32) { + if (gt ? static_cast(chunk) > v : static_cast(chunk) < v) {if (!find_action( 0 + baseindex, static_cast(chunk), state, callback)) return false;} + chunk >>= 32; + if (gt ? static_cast(chunk) > v : static_cast(chunk) < v) {if (!find_action( 1 + baseindex, static_cast(chunk), state, callback)) return false;} + chunk >>= 32; + } + else if (width == 64) { + if (gt ? static_cast(v) > v : static_cast(v) < v) {if (!find_action( 0 + baseindex, static_cast(v), state, callback)) return false;}; + } + + return true; +} +// clang-format on + +/// Find items in this Array that are equal (eq == true) or different (eq = false) from 'value' +template +inline bool Array::compare_equality(int64_t value, size_t start, size_t end, size_t baseindex, + QueryState* state, Callback callback) const +{ + REALM_ASSERT_DEBUG(start <= m_size && (end <= m_size || end == size_t(-1)) && start <= end); + + size_t ee = round_up(start, 64 / no0(width)); + ee = ee > end ? end : ee; + for (; start < ee; ++start) + if (eq ? (get(start) == value) : (get(start) != value)) { + if (!find_action(start + baseindex, get(start), state, callback)) + return false; + } + + if (start >= end) + return true; + + if (width != 32 && width != 64) { + const int64_t* p = reinterpret_cast(m_data + (start * width / 8)); + const int64_t* const e = reinterpret_cast(m_data + (end * width / 8)) - 1; + const uint64_t mask = (width == 64 ? ~0ULL : ((1ULL << (width == 64 ? 0 : width)) - + 1ULL)); // Warning free way of computing (1ULL << width) - 1 + const uint64_t valuemask = + ~0ULL / no0(mask) * (value & mask); // the "== ? :" is to avoid division by 0 compiler error + + while (p < e) { + uint64_t chunk = *p; + uint64_t v2 = chunk ^ valuemask; + start = (p - reinterpret_cast(m_data)) * 8 * 8 / no0(width); + size_t a = 0; + + while (eq ? test_zero(v2) : v2) { + + if (find_action_pattern(start + baseindex, cascade(v2), state, callback)) + break; // consumed + + size_t t = find_zero(v2); + a += t; + + if (a >= 64 / no0(width)) + break; + + if (!find_action(a + start + baseindex, get(start + a), state, callback)) + return false; + v2 >>= (t + 1) * width; + a += 1; + } + + ++p; + } + + // Loop ended because we are near end or end of array. No need to optimize search in remainder in this case + // because end of array means that + // lots of search work has taken place prior to ending here. So time spent searching remainder is relatively + // tiny + start = (p - reinterpret_cast(m_data)) * 8 * 8 / no0(width); + } + + while (start < end) { + if (eq ? get(start) == value : get(start) != value) { + if (!find_action(start + baseindex, get(start), state, callback)) + return false; + } + ++start; + } + + return true; +} + +// There exists a couple of find() functions that take more or less template arguments. Always call the one that +// takes as most as possible to get best performance. + +// This is the one installed into the m_vtable->finder slots. +template +bool Array::find(int64_t value, size_t start, size_t end, size_t baseindex, QueryState* state) const +{ + return find(value, start, end, baseindex, state, CallbackDummy()); +} + +template +bool Array::find(int64_t value, size_t start, size_t end, size_t baseindex, QueryState* state, + Callback callback, bool nullable_array, bool find_null) const +{ + REALM_TEMPEX4(return find, cond, action, m_width, Callback, + (value, start, end, baseindex, state, callback, nullable_array, find_null)); +} + +template +bool Array::find(int64_t value, size_t start, size_t end, size_t baseindex, QueryState* state, + Callback callback, bool nullable_array, bool find_null) const +{ + return find_optimized(value, start, end, baseindex, state, callback, + nullable_array, find_null); +} + +#ifdef REALM_COMPILER_SSE +// 'items' is the number of 16-byte SSE chunks. Returns index of packed element relative to first integer of first +// chunk +template +bool Array::find_sse(int64_t value, __m128i* data, size_t items, QueryState* state, size_t baseindex, + Callback callback) const +{ + __m128i search = {0}; + + if (width == 8) + search = _mm_set1_epi8(static_cast(value)); + else if (width == 16) + search = _mm_set1_epi16(static_cast(value)); + else if (width == 32) + search = _mm_set1_epi32(static_cast(value)); + else if (width == 64) { + if (std::is_same::value) + REALM_ASSERT(false); + else + search = _mm_set_epi64x(value, value); + } + + return find_sse_intern(data, &search, items, state, baseindex, callback); +} + +// Compares packed action_data with packed data (equal, less, etc) and performs aggregate action (max, min, sum, +// find_all, etc) on value inside action_data for first match, if any +template +REALM_FORCEINLINE bool Array::find_sse_intern(__m128i* action_data, __m128i* data, size_t items, + QueryState* state, size_t baseindex, Callback callback) const +{ + size_t i = 0; + __m128i compare_result = {0}; + unsigned int resmask; + + // Search loop. Unrolling it has been tested to NOT increase performance (apparently mem bound) + for (i = 0; i < items; ++i) { + // equal / not-equal + if (std::is_same::value || std::is_same::value) { + if (width == 8) + compare_result = _mm_cmpeq_epi8(action_data[i], *data); + if (width == 16) + compare_result = _mm_cmpeq_epi16(action_data[i], *data); + if (width == 32) + compare_result = _mm_cmpeq_epi32(action_data[i], *data); + if (width == 64) { + compare_result = _mm_cmpeq_epi64(action_data[i], *data); // SSE 4.2 only + } + } + + // greater + else if (std::is_same::value) { + if (width == 8) + compare_result = _mm_cmpgt_epi8(action_data[i], *data); + if (width == 16) + compare_result = _mm_cmpgt_epi16(action_data[i], *data); + if (width == 32) + compare_result = _mm_cmpgt_epi32(action_data[i], *data); + if (width == 64) + compare_result = _mm_cmpgt_epi64(action_data[i], *data); + } + // less + else if (std::is_same::value) { + if (width == 8) + compare_result = _mm_cmplt_epi8(action_data[i], *data); + else if (width == 16) + compare_result = _mm_cmplt_epi16(action_data[i], *data); + else if (width == 32) + compare_result = _mm_cmplt_epi32(action_data[i], *data); + else + REALM_ASSERT(false); + } + + resmask = _mm_movemask_epi8(compare_result); + + if (std::is_same::value) + resmask = ~resmask & 0x0000ffff; + + size_t s = i * sizeof(__m128i) * 8 / no0(width); + + while (resmask != 0) { + + uint64_t upper = lower_bits() << (no0(width / 8) - 1); + uint64_t pattern = + resmask & + upper; // fixme, bits at wrong offsets. Only OK because we only use them in 'count' aggregate + if (find_action_pattern(s + baseindex, pattern, state, callback)) + break; + + size_t idx = first_set_bit(resmask) * 8 / no0(width); + s += idx; + if (!find_action( + s + baseindex, get_universal(reinterpret_cast(action_data), s), state, callback)) + return false; + resmask >>= (idx + 1) * no0(width) / 8; + ++s; + } + } + + return true; +} +#endif // REALM_COMPILER_SSE + +template +bool Array::compare_leafs(const Array* foreign, size_t start, size_t end, size_t baseindex, + QueryState* state, Callback callback) const +{ + cond c; + REALM_ASSERT_3(start, <=, end); + if (start == end) + return true; + + + int64_t v; + + // We can compare first element without checking for out-of-range + v = get(start); + if (c(v, foreign->get(start))) { + if (!find_action(start + baseindex, v, state, callback)) + return false; + } + + start++; + + if (start + 3 < end) { + v = get(start); + if (c(v, foreign->get(start))) + if (!find_action(start + baseindex, v, state, callback)) + return false; + + v = get(start + 1); + if (c(v, foreign->get(start + 1))) + if (!find_action(start + 1 + baseindex, v, state, callback)) + return false; + + v = get(start + 2); + if (c(v, foreign->get(start + 2))) + if (!find_action(start + 2 + baseindex, v, state, callback)) + return false; + + start += 3; + } + else if (start == end) { + return true; + } + + bool r; + REALM_TEMPEX4(r = compare_leafs, cond, action, m_width, Callback, + (foreign, start, end, baseindex, state, callback)) + return r; +} + + +template +bool Array::compare_leafs(const Array* foreign, size_t start, size_t end, size_t baseindex, + QueryState* state, Callback callback) const +{ + size_t fw = foreign->m_width; + bool r; + REALM_TEMPEX5(r = compare_leafs_4, cond, action, width, Callback, fw, + (foreign, start, end, baseindex, state, callback)) + return r; +} + + +template +bool Array::compare_leafs_4(const Array* foreign, size_t start, size_t end, size_t baseindex, + QueryState* state, Callback callback) const +{ + cond c; + char* foreign_m_data = foreign->m_data; + + if (width == 0 && foreign_width == 0) { + if (c(0, 0)) { + while (start < end) { + if (!find_action(start + baseindex, 0, state, callback)) + return false; + start++; + } + } + else { + return true; + } + } + + +#if defined(REALM_COMPILER_SSE) + if (sseavx<42>() && width == foreign_width && (width == 8 || width == 16 || width == 32)) { + // We can only use SSE if both bitwidths are equal and above 8 bits and all values are signed + // and the two arrays are aligned the same way + if ((reinterpret_cast(m_data) & 0xf) == (reinterpret_cast(foreign_m_data) & 0xf)) { + while (start < end && (((reinterpret_cast(m_data) & 0xf) * 8 + start * width) % (128) != 0)) { + int64_t v = get_universal(m_data, start); + int64_t fv = get_universal(foreign_m_data, start); + if (c(v, fv)) { + if (!find_action(start + baseindex, v, state, callback)) + return false; + } + start++; + } + if (start == end) + return true; + + + size_t sse_items = (end - start) * width / 128; + size_t sse_end = start + sse_items * 128 / no0(width); + + while (start < sse_end) { + __m128i* a = reinterpret_cast<__m128i*>(m_data + start * width / 8); + __m128i* b = reinterpret_cast<__m128i*>(foreign_m_data + start * width / 8); + + bool continue_search = + find_sse_intern(a, b, 1, state, baseindex + start, callback); + + if (!continue_search) + return false; + + start += 128 / no0(width); + } + } + } +#endif + + while (start < end) { + int64_t v = get_universal(m_data, start); + int64_t fv = get_universal(foreign_m_data, start); + + if (c(v, fv)) { + if (!find_action(start + baseindex, v, state, callback)) + return false; + } + + start++; + } + + return true; +} + + +template +bool Array::compare(int64_t value, size_t start, size_t end, size_t baseindex, QueryState* state, + Callback callback) const +{ + bool ret = false; + + if (std::is_same::value) + ret = compare_equality(value, start, end, baseindex, state, callback); + else if (std::is_same::value) + ret = compare_equality(value, start, end, baseindex, state, callback); + else if (std::is_same::value) + ret = compare_relation(value, start, end, baseindex, state, callback); + else if (std::is_same::value) + ret = compare_relation(value, start, end, baseindex, state, callback); + else + REALM_ASSERT_DEBUG(false); + + return ret; +} + +template +bool Array::compare_relation(int64_t value, size_t start, size_t end, size_t baseindex, QueryState* state, + Callback callback) const +{ + REALM_ASSERT(start <= m_size && (end <= m_size || end == size_t(-1)) && start <= end); + uint64_t mask = (bitwidth == 64 ? ~0ULL : ((1ULL << (bitwidth == 64 ? 0 : bitwidth)) - + 1ULL)); // Warning free way of computing (1ULL << width) - 1 + + size_t ee = round_up(start, 64 / no0(bitwidth)); + ee = ee > end ? end : ee; + for (; start < ee; start++) { + if (gt ? (get(start) > value) : (get(start) < value)) { + if (!find_action(start + baseindex, get(start), state, callback)) + return false; + } + } + + if (start >= end) + return true; // none found, continue (return true) regardless what find_action() would have returned on match + + const int64_t* p = reinterpret_cast(m_data + (start * bitwidth / 8)); + const int64_t* const e = reinterpret_cast(m_data + (end * bitwidth / 8)) - 1; + + // Matches are rare enough to setup fast linear search for remaining items. We use + // bit hacks from http://graphics.stanford.edu/~seander/bithacks.html#HasLessInWord + + if (bitwidth == 1 || bitwidth == 2 || bitwidth == 4 || bitwidth == 8 || bitwidth == 16) { + uint64_t magic = find_gtlt_magic(value); + + // Bit hacks only work if searched item has its most significant bit clear for 'greater than' or + // 'item <= 1 << bitwidth' for 'less than' + if (value != int64_t((magic & mask)) && value >= 0 && bitwidth >= 2 && + value <= static_cast((mask >> 1) - (gt ? 1 : 0))) { + // 15 ms + while (p < e) { + uint64_t upper = lower_bits() << (no0(bitwidth) - 1); + + const int64_t v = *p; + size_t idx; + + // Bit hacks only works if all items in chunk have their most significant bit clear. Test this: + upper = upper & v; + + if (!upper) { + idx = find_gtlt_fast( + v, magic, state, (p - reinterpret_cast(m_data)) * 8 * 8 / no0(bitwidth) + baseindex, + callback); + } + else + idx = find_gtlt( + value, v, state, (p - reinterpret_cast(m_data)) * 8 * 8 / no0(bitwidth) + baseindex, + callback); + + if (!idx) + return false; + ++p; + } + } + else { + // 24 ms + while (p < e) { + int64_t v = *p; + if (!find_gtlt( + value, v, state, (p - reinterpret_cast(m_data)) * 8 * 8 / no0(bitwidth) + baseindex, + callback)) + return false; + ++p; + } + } + start = (p - reinterpret_cast(m_data)) * 8 * 8 / no0(bitwidth); + } + + // matchcount logic in SIMD no longer pays off for 32/64 bit ints because we have just 4/2 elements + + // Test unaligned end and/or values of width > 16 manually + while (start < end) { + if (gt ? get(start) > value : get(start) < value) { + if (!find_action(start + baseindex, get(start), state, callback)) + return false; + } + ++start; + } + return true; +} + +template +size_t Array::find_first(int64_t value, size_t start, size_t end) const +{ + REALM_ASSERT(start <= m_size && (end <= m_size || end == size_t(-1)) && start <= end); + QueryState state; + state.init(act_ReturnFirst, nullptr, + 1); // todo, would be nice to avoid this in order to speed up find_first loops + Finder finder = m_vtable->finder[cond::condition]; + (this->*finder)(value, start, end, 0, &state); + + return static_cast(state.m_state); +} + +//************************************************************************************* +// Finding code ends * +//************************************************************************************* + + +} // namespace realm + +#endif // REALM_ARRAY_HPP diff --git a/!main project/Pods/Realm/include/core/realm/array_basic.hpp b/!main project/Pods/Realm/include/core/realm/array_basic.hpp new file mode 100644 index 0000000..6b9c212 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/array_basic.hpp @@ -0,0 +1,120 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_ARRAY_BASIC_HPP +#define REALM_ARRAY_BASIC_HPP + +#include + +namespace realm { + +/// A BasicArray can currently only be used for simple unstructured +/// types like float, double. +template +class BasicArray : public Array { +public: + explicit BasicArray(Allocator&) noexcept; + ~BasicArray() noexcept override + { + } + + // Disable copying, this is not allowed. + BasicArray& operator=(const BasicArray&) = delete; + BasicArray(const BasicArray&) = delete; + + T get(size_t ndx) const noexcept; + bool is_null(size_t ndx) const noexcept; + void add(T value); + void set(size_t ndx, T value); + void set_null(size_t ndx); + void insert(size_t ndx, T value); + void erase(size_t ndx); + void truncate(size_t size); + void clear(); + + size_t find_first(T value, size_t begin = 0, size_t end = npos) const; + void find_all(IntegerColumn* result, T value, size_t add_offset = 0, size_t begin = 0, size_t end = npos) const; + + size_t count(T value, size_t begin = 0, size_t end = npos) const; + bool maximum(T& result, size_t begin = 0, size_t end = npos) const; + bool minimum(T& result, size_t begin = 0, size_t end = npos) const; + + /// Compare two arrays for equality. + bool compare(const BasicArray&) const; + + /// Get the specified element without the cost of constructing an + /// array instance. If an array instance is already available, or + /// you need to get multiple values, then this method will be + /// slower. + static T get(const char* header, size_t ndx) noexcept; + + ref_type bptree_leaf_insert(size_t ndx, T, TreeInsertBase& state); + + size_t lower_bound(T value) const noexcept; + size_t upper_bound(T value) const noexcept; + + /// Construct a basic array of the specified size and return just + /// the reference to the underlying memory. All elements will be + /// initialized to `T()`. + static MemRef create_array(size_t size, Allocator&); + + static MemRef create_array(Array::Type leaf_type, bool context_flag, size_t size, T value, Allocator&); + + /// Create a new empty array and attach this accessor to it. This + /// does not modify the parent reference information of this + /// accessor. + /// + /// Note that the caller assumes ownership of the allocated + /// underlying node. It is not owned by the accessor. + void create(Array::Type = type_Normal, bool context_flag = false); + + /// Construct a copy of the specified slice of this basic array + /// using the specified target allocator. + MemRef slice(size_t offset, size_t size, Allocator& target_alloc) const; + MemRef slice_and_clone_children(size_t offset, size_t size, Allocator& target_alloc) const; + +#ifdef REALM_DEBUG + void to_dot(std::ostream&, StringData title = StringData()) const; +#endif + +private: + size_t find(T target, size_t begin, size_t end) const; + + size_t calc_byte_len(size_t count, size_t width) const override; + virtual size_t calc_item_count(size_t bytes, size_t width) const noexcept override; + + template + bool minmax(T& result, size_t begin, size_t end) const; + + /// Calculate the total number of bytes needed for a basic array + /// with the specified number of elements. This includes the size + /// of the header. The result will be upwards aligned to the + /// closest 8-byte boundary. + static size_t calc_aligned_byte_size(size_t size); +}; + + +// Class typedefs for BasicArray's: ArrayFloat and ArrayDouble +typedef BasicArray ArrayFloat; +typedef BasicArray ArrayDouble; + +} // namespace realm + +#include + +#endif // REALM_ARRAY_BASIC_HPP diff --git a/!main project/Pods/Realm/include/core/realm/array_basic_tpl.hpp b/!main project/Pods/Realm/include/core/realm/array_basic_tpl.hpp new file mode 100644 index 0000000..5ce37fd --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/array_basic_tpl.hpp @@ -0,0 +1,467 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_ARRAY_BASIC_TPL_HPP +#define REALM_ARRAY_BASIC_TPL_HPP + +#include +#include +#include +#include + +#include + +namespace realm { + +template +inline BasicArray::BasicArray(Allocator& allocator) noexcept + : Array(allocator) +{ +} + +template +inline MemRef BasicArray::create_array(size_t init_size, Allocator& allocator) +{ + size_t byte_size_0 = calc_aligned_byte_size(init_size); // Throws + // Adding zero to Array::initial_capacity to avoid taking the + // address of that member + size_t byte_size = std::max(byte_size_0, Array::initial_capacity + 0); // Throws + + MemRef mem = allocator.alloc(byte_size); // Throws + + bool is_inner_bptree_node = false; + bool has_refs = false; + bool context_flag = false; + int width = sizeof(T); + init_header(mem.get_addr(), is_inner_bptree_node, has_refs, context_flag, wtype_Multiply, width, init_size, + byte_size); + + return mem; +} + + +template +inline MemRef BasicArray::create_array(Array::Type type, bool context_flag, size_t init_size, T value, + Allocator& allocator) +{ + REALM_ASSERT(type == Array::type_Normal); + REALM_ASSERT(!context_flag); + MemRef mem = create_array(init_size, allocator); + if (init_size) { + // GCC 7.x emits a false-positive strict aliasing warning for this code. Suppress it, since it + // clutters up the build output. See for details. + REALM_DIAG_PUSH(); + REALM_DIAG(ignored "-Wstrict-aliasing"); + + BasicArray tmp(allocator); + tmp.init_from_mem(mem); + T* p = reinterpret_cast(tmp.m_data); + T* end = p + init_size; + while (p < end) { + *p++ = value; + } + + REALM_DIAG_POP(); + } + return mem; +} + + +template +inline void BasicArray::create(Array::Type type, bool context_flag) +{ + REALM_ASSERT(type == Array::type_Normal); + REALM_ASSERT(!context_flag); + size_t length = 0; + MemRef mem = create_array(length, get_alloc()); // Throws + init_from_mem(mem); +} + + +template +MemRef BasicArray::slice(size_t offset, size_t slice_size, Allocator& target_alloc) const +{ + REALM_ASSERT(is_attached()); + + // FIXME: This can be optimized as a single contiguous copy + // operation. + BasicArray array_slice(target_alloc); + _impl::ShallowArrayDestroyGuard dg(&array_slice); + array_slice.create(); // Throws + size_t begin = offset; + size_t end = offset + slice_size; + for (size_t i = begin; i != end; ++i) { + T value = get(i); + array_slice.add(value); // Throws + } + dg.release(); + return array_slice.get_mem(); +} + +template +MemRef BasicArray::slice_and_clone_children(size_t offset, size_t slice_size, Allocator& target_alloc) const +{ + // BasicArray never contains refs, so never has children. + return slice(offset, slice_size, target_alloc); +} + + +template +inline void BasicArray::add(T value) +{ + insert(m_size, value); +} + + +template +inline T BasicArray::get(size_t ndx) const noexcept +{ + return *(reinterpret_cast(m_data) + ndx); +} + + +template +inline bool BasicArray::is_null(size_t ndx) const noexcept +{ + // FIXME: This assumes BasicArray will only ever be instantiated for float-like T. + static_assert(realm::is_any::value, "T can only be float or double"); + auto x = get(ndx); + return null::is_null_float(x); +} + + +template +inline T BasicArray::get(const char* header, size_t ndx) noexcept +{ + const char* data = get_data_from_header(header); + // This casting assumes that T can be aliged on an 8-bype + // boundary (since data is aligned on an 8-byte boundary.) + return *(reinterpret_cast(data) + ndx); +} + + +template +inline void BasicArray::set(size_t ndx, T value) +{ + REALM_ASSERT_3(ndx, <, m_size); + if (get(ndx) == value) + return; + + // Check if we need to copy before modifying + copy_on_write(); // Throws + + // Set the value + T* data = reinterpret_cast(m_data) + ndx; + *data = value; +} + +template +inline void BasicArray::set_null(size_t ndx) +{ + // FIXME: This assumes BasicArray will only ever be instantiated for float-like T. + set(ndx, null::get_null_float()); +} + +template +void BasicArray::insert(size_t ndx, T value) +{ + REALM_ASSERT_3(ndx, <=, m_size); + + // Check if we need to copy before modifying + copy_on_write(); // Throws + + // Make room for the new value + alloc(m_size + 1, m_width); // Throws + + // Move values below insertion + if (ndx != m_size) { + char* src_begin = m_data + ndx * m_width; + char* src_end = m_data + m_size * m_width; + char* dst_end = src_end + m_width; + std::copy_backward(src_begin, src_end, dst_end); + } + + // Set the value + T* data = reinterpret_cast(m_data) + ndx; + *data = value; + + ++m_size; +} + +template +void BasicArray::erase(size_t ndx) +{ + REALM_ASSERT_3(ndx, <, m_size); + + // Check if we need to copy before modifying + copy_on_write(); // Throws + + // move data under deletion up + if (ndx < m_size - 1) { + char* dst_begin = m_data + ndx * m_width; + const char* src_begin = dst_begin + m_width; + const char* src_end = m_data + m_size * m_width; + realm::safe_copy_n(src_begin, src_end - src_begin, dst_begin); + } + + // Update size (also in header) + --m_size; + set_header_size(m_size); +} + +template +void BasicArray::truncate(size_t to_size) +{ + REALM_ASSERT(is_attached()); + REALM_ASSERT_3(to_size, <=, m_size); + + copy_on_write(); // Throws + + // Update size in accessor and in header. This leaves the capacity + // unchanged. + m_size = to_size; + set_header_size(to_size); +} + +template +inline void BasicArray::clear() +{ + truncate(0); // Throws +} + +template +bool BasicArray::compare(const BasicArray& a) const +{ + size_t n = size(); + if (a.size() != n) + return false; + const T* data_1 = reinterpret_cast(m_data); + const T* data_2 = reinterpret_cast(a.m_data); + return realm::safe_equal(data_1, data_1 + n, data_2); +} + + +template +size_t BasicArray::calc_byte_len(size_t for_size, size_t) const +{ + // FIXME: Consider calling `calc_aligned_byte_size(size)` + // instead. Note however, that calc_byte_len() is supposed to return + // the unaligned byte size. It is probably the case that no harm + // is done by returning the aligned version, and most callers of + // calc_byte_len() will actually benefit if calc_byte_len() was + // changed to always return the aligned byte size. + return header_size + for_size * sizeof(T); +} + +template +size_t BasicArray::calc_item_count(size_t bytes, size_t) const noexcept +{ + size_t bytes_without_header = bytes - header_size; + return bytes_without_header / sizeof(T); +} + +template +size_t BasicArray::find(T value, size_t begin, size_t end) const +{ + if (end == npos) + end = m_size; + REALM_ASSERT(begin <= m_size && end <= m_size && begin <= end); + const T* data = reinterpret_cast(m_data); + const T* i = std::find(data + begin, data + end, value); + return i == data + end ? not_found : size_t(i - data); +} + +template +inline size_t BasicArray::find_first(T value, size_t begin, size_t end) const +{ + return this->find(value, begin, end); +} + +template +void BasicArray::find_all(IntegerColumn* result, T value, size_t add_offset, size_t begin, size_t end) const +{ + size_t first = begin - 1; + for (;;) { + first = this->find(value, first + 1, end); + if (first == not_found) + break; + + Array::add_to_column(result, first + add_offset); + } +} + +template +size_t BasicArray::count(T value, size_t begin, size_t end) const +{ + if (end == npos) + end = m_size; + REALM_ASSERT(begin <= m_size && end <= m_size && begin <= end); + const T* data = reinterpret_cast(m_data); + return std::count(data + begin, data + end, value); +} + +#if 0 +// currently unused +template +double BasicArray::sum(size_t begin, size_t end) const +{ + if (end == npos) + end = m_size; + REALM_ASSERT(begin <= m_size && end <= m_size && begin <= end); + const T* data = reinterpret_cast(m_data); + return std::accumulate(data + begin, data + end, double(0)); +} +#endif + +template +template +bool BasicArray::minmax(T& result, size_t begin, size_t end) const +{ + if (end == npos) + end = m_size; + if (m_size == 0) + return false; + REALM_ASSERT(begin < m_size && end <= m_size && begin < end); + + T m = get(begin); + ++begin; + for (; begin < end; ++begin) { + T val = get(begin); + if (find_max ? val > m : val < m) + m = val; + } + result = m; + return true; +} + +template +bool BasicArray::maximum(T& result, size_t begin, size_t end) const +{ + return minmax(result, begin, end); +} + +template +bool BasicArray::minimum(T& result, size_t begin, size_t end) const +{ + return minmax(result, begin, end); +} + + +template +ref_type BasicArray::bptree_leaf_insert(size_t ndx, T value, TreeInsertBase& state) +{ + size_t leaf_size = size(); + REALM_ASSERT_3(leaf_size, <=, REALM_MAX_BPNODE_SIZE); + if (leaf_size < ndx) + ndx = leaf_size; + if (REALM_LIKELY(leaf_size < REALM_MAX_BPNODE_SIZE)) { + insert(ndx, value); + return 0; // Leaf was not split + } + + // Split leaf node + BasicArray new_leaf(get_alloc()); + new_leaf.create(); // Throws + if (ndx == leaf_size) { + new_leaf.add(value); + state.m_split_offset = ndx; + } + else { + // FIXME: Could be optimized by first resizing the target + // array, then copy elements with std::copy(). + for (size_t i = ndx; i != leaf_size; ++i) + new_leaf.add(get(i)); + truncate(ndx); + add(value); + state.m_split_offset = ndx + 1; + } + state.m_split_size = leaf_size + 1; + return new_leaf.get_ref(); +} + +template +inline size_t BasicArray::lower_bound(T value) const noexcept +{ + const T* begin = reinterpret_cast(m_data); + const T* end = begin + size(); + return std::lower_bound(begin, end, value) - begin; +} + +template +inline size_t BasicArray::upper_bound(T value) const noexcept +{ + const T* begin = reinterpret_cast(m_data); + const T* end = begin + size(); + return std::upper_bound(begin, end, value) - begin; +} + +template +inline size_t BasicArray::calc_aligned_byte_size(size_t size) +{ + size_t max = std::numeric_limits::max(); + size_t max_2 = max & ~size_t(7); // Allow for upwards 8-byte alignment + if (size > (max_2 - header_size) / sizeof(T)) + throw util::overflow_error("Byte size overflow"); + size_t byte_size = header_size + size * sizeof(T); + REALM_ASSERT_3(byte_size, >, 0); + size_t aligned_byte_size = ((byte_size - 1) | 7) + 1; // 8-byte alignment + return aligned_byte_size; +} + + +#ifdef REALM_DEBUG + +// LCOV_EXCL_START +template +void BasicArray::to_dot(std::ostream& out, StringData title) const +{ + ref_type ref = get_ref(); + if (title.size() != 0) { + out << "subgraph cluster_" << ref << " {\n"; + out << " label = \"" << title << "\";\n"; + out << " color = white;\n"; + } + + out << "n" << std::hex << ref << std::dec << "[shape=none,label=<"; + out << "
\n"; + + // Header + out << "\n"; + + // Values + size_t n = m_size; + for (size_t i = 0; i != n; ++i) + out << "\n"; + + out << "
"; + out << "0x" << std::hex << ref << std::dec << "
"; + out << "
" << get(i) << "
>];\n"; + + if (title.size() != 0) + out << "}\n"; + + to_dot_parent_edge(out); +} +// LCOV_EXCL_STOP + +#endif // REALM_DEBUG + + +} // namespace realm + +#endif // REALM_ARRAY_BASIC_TPL_HPP diff --git a/!main project/Pods/Realm/include/core/realm/array_binary.hpp b/!main project/Pods/Realm/include/core/realm/array_binary.hpp new file mode 100644 index 0000000..347c7be --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/array_binary.hpp @@ -0,0 +1,259 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_ARRAY_BINARY_HPP +#define REALM_ARRAY_BINARY_HPP + +#include +#include +#include +#include + +namespace realm { + +/* +STORAGE FORMAT +--------------------------------------------------------------------------------------- +ArrayBinary stores binary elements using two ArrayInteger and one ArrayBlob. The ArrayBlob can only store one +single concecutive array of bytes (contrary to its 'Array' name that misleadingly indicates it could store multiple +elements). + +Assume we have the strings "a", "", "abc", null, "ab". Then the three arrays will contain: + +ArrayInteger m_offsets 1, 1, 5, 5, 6 +ArrayBlob m_blob aabcab +ArrayInteger m_nulls 0, 0, 0, 1, 0 // 1 indicates null, 0 indicates non-null + +So for each element the ArrayInteger, the ArrayInteger points into the ArrayBlob at the position of the first +byte of the next element. + +m_nulls is always present (except for old database files; see below), so any ArrayBinary is always nullable! +The nullable property (such as throwing exception upon set(null) on non-nullable column, etc) is handled on +column level only. + +DATABASE FILE VERSION CHANGES +--------------------------------------------------------------------------------------- +Old database files do not have any m_nulls array. To be backwardscompatible, many methods will have tests like +`if(Array::size() == 3)` and have a backwards compatible code paths for these (e.g. avoid writing to m_nulls +in set(), etc). This way no file format upgrade is needed to support nulls for BinaryData. +*/ + +class ArrayBinary : public Array { +public: + explicit ArrayBinary(Allocator&) noexcept; + ~ArrayBinary() noexcept override + { + } + + // Disable copying, this is not allowed. + ArrayBinary& operator=(const ArrayBinary&) = delete; + ArrayBinary(const ArrayBinary&) = delete; + + /// Create a new empty binary array and attach this accessor to + /// it. This does not modify the parent reference information of + /// this accessor. + /// + /// Note that the caller assumes ownership of the allocated + /// underlying node. It is not owned by the accessor. + void create(); + + // Old database files will not have the m_nulls array, so we need code paths for + // backwards compatibility for these cases. + bool legacy_array_type() const noexcept; + + //@{ + /// Overriding functions of Array + void init_from_ref(ref_type) noexcept; + void init_from_mem(MemRef) noexcept; + void init_from_parent() noexcept; + //@} + + bool is_empty() const noexcept; + size_t size() const noexcept; + + BinaryData get(size_t ndx) const noexcept; + size_t read(size_t ndx, size_t pos, char* buffer, size_t max_size) const noexcept; + + void add(BinaryData value, bool add_zero_term = false); + void set(size_t ndx, BinaryData value, bool add_zero_term = false); + void insert(size_t ndx, BinaryData value, bool add_zero_term = false); + void erase(size_t ndx); + void truncate(size_t new_size); + void clear(); + void destroy(); + + /// Get the specified element without the cost of constructing an + /// array instance. If an array instance is already available, or + /// you need to get multiple values, then this method will be + /// slower. + static BinaryData get(const char* header, size_t ndx, Allocator&) noexcept; + + ref_type bptree_leaf_insert(size_t ndx, BinaryData, bool add_zero_term, TreeInsertBase& state); + + static size_t get_size_from_header(const char*, Allocator&) noexcept; + + /// Construct a binary array of the specified size and return just + /// the reference to the underlying memory. All elements will be + /// initialized to the binary value `defaults`, which can be either + /// null or zero-length non-null (value with size > 0 is not allowed as + /// initialization value). + static MemRef create_array(size_t size, Allocator&, BinaryData defaults); + + /// Construct a copy of the specified slice of this binary array + /// using the specified target allocator. + MemRef slice(size_t offset, size_t slice_size, Allocator& target_alloc) const; + +#ifdef REALM_DEBUG + void to_dot(std::ostream&, bool is_strings, StringData title = StringData()) const; +#endif + bool update_from_parent(size_t old_baseline) noexcept; + +private: + ArrayInteger m_offsets; + ArrayBlob m_blob; + ArrayInteger m_nulls; +}; + + +// Implementation: + +inline ArrayBinary::ArrayBinary(Allocator& allocator) noexcept + : Array(allocator) + , m_offsets(allocator) + , m_blob(allocator) + , m_nulls(allocator) +{ + m_offsets.set_parent(this, 0); + m_blob.set_parent(this, 1); + m_nulls.set_parent(this, 2); +} + +inline void ArrayBinary::create() +{ + size_t init_size = 0; + BinaryData defaults = BinaryData{}; // This init value is ignored because size = 0 + MemRef mem = create_array(init_size, get_alloc(), defaults); // Throws + init_from_mem(mem); +} + +inline void ArrayBinary::init_from_ref(ref_type ref) noexcept +{ + REALM_ASSERT(ref); + char* header = get_alloc().translate(ref); + init_from_mem(MemRef(header, ref, m_alloc)); +} + +inline void ArrayBinary::init_from_parent() noexcept +{ + ref_type ref = get_ref_from_parent(); + init_from_ref(ref); +} + +inline bool ArrayBinary::is_empty() const noexcept +{ + return m_offsets.is_empty(); +} + +// Old database files will not have the m_nulls array, so we need code paths for +// backwards compatibility for these cases. We can test if m_nulls exists by looking +// at number of references in this ArrayBinary. +inline bool ArrayBinary::legacy_array_type() const noexcept +{ + if (Array::size() == 3) + return false; // New database file + else if (Array::size() == 2) + return true; // Old database file + else + REALM_ASSERT(false); // Should never happen + return false; +} + +inline size_t ArrayBinary::size() const noexcept +{ + return m_offsets.size(); +} + +inline BinaryData ArrayBinary::get(size_t ndx) const noexcept +{ + REALM_ASSERT_3(ndx, <, m_offsets.size()); + + if (!legacy_array_type() && m_nulls.get(ndx)) { + return BinaryData(); + } + else { + size_t begin = ndx ? to_size_t(m_offsets.get(ndx - 1)) : 0; + size_t end = to_size_t(m_offsets.get(ndx)); + + BinaryData bd = BinaryData(m_blob.get(begin), end - begin); + // Old database file (non-nullable column should never return null) + REALM_ASSERT(!bd.is_null()); + return bd; + } +} + +inline void ArrayBinary::truncate(size_t new_size) +{ + REALM_ASSERT_3(new_size, <, m_offsets.size()); + + size_t sz = new_size ? to_size_t(m_offsets.get(new_size - 1)) : 0; + + m_offsets.truncate(new_size); + m_blob.truncate(sz); + if (!legacy_array_type()) + m_nulls.truncate(new_size); +} + +inline void ArrayBinary::clear() +{ + m_blob.clear(); + m_offsets.clear(); + if (!legacy_array_type()) + m_nulls.clear(); +} + +inline void ArrayBinary::destroy() +{ + m_blob.destroy(); + m_offsets.destroy(); + if (!legacy_array_type()) + m_nulls.destroy(); + Array::destroy(); +} + +inline size_t ArrayBinary::get_size_from_header(const char* header, Allocator& alloc) noexcept +{ + ref_type offsets_ref = to_ref(Array::get(header, 0)); + const char* offsets_header = alloc.translate(offsets_ref); + return Array::get_size_from_header(offsets_header); +} + +inline bool ArrayBinary::update_from_parent(size_t old_baseline) noexcept +{ + bool res = Array::update_from_parent(old_baseline); + if (res) { + m_blob.update_from_parent(old_baseline); + m_offsets.update_from_parent(old_baseline); + if (!legacy_array_type()) + m_nulls.update_from_parent(old_baseline); + } + return res; +} + +} // namespace realm + +#endif // REALM_ARRAY_BINARY_HPP diff --git a/!main project/Pods/Realm/include/core/realm/array_blob.hpp b/!main project/Pods/Realm/include/core/realm/array_blob.hpp new file mode 100644 index 0000000..556d40a --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/array_blob.hpp @@ -0,0 +1,146 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_ARRAY_BLOB_HPP +#define REALM_ARRAY_BLOB_HPP + +#include + +namespace realm { + + +class ArrayBlob : public Array { +public: + static constexpr size_t max_binary_size = 0xFFFFF8 - Array::header_size; + + explicit ArrayBlob(Allocator&) noexcept; + ~ArrayBlob() noexcept override + { + } + + // Disable copying, this is not allowed. + ArrayBlob& operator=(const ArrayBlob&) = delete; + ArrayBlob(const ArrayBlob&) = delete; + + const char* get(size_t index) const noexcept; + BinaryData get_at(size_t& pos) const noexcept; + bool is_null(size_t index) const noexcept; + ref_type add(const char* data, size_t data_size, bool add_zero_term = false); + void insert(size_t pos, const char* data, size_t data_size, bool add_zero_term = false); + ref_type replace(size_t begin, size_t end, const char* data, size_t data_size, bool add_zero_term = false); + void erase(size_t begin, size_t end); + + /// Get the specified element without the cost of constructing an + /// array instance. If an array instance is already available, or + /// you need to get multiple values, then this method will be + /// slower. + static const char* get(const char* header, size_t index) noexcept; + + /// Create a new empty blob (binary) array and attach this + /// accessor to it. This does not modify the parent reference + /// information of this accessor. + /// + /// Note that the caller assumes ownership of the allocated + /// underlying node. It is not owned by the accessor. + void create(); + + /// Construct a blob of the specified size and return just the + /// reference to the underlying memory. All bytes will be + /// initialized to zero. + static MemRef create_array(size_t init_size, Allocator&); + +#ifdef REALM_DEBUG + void verify() const; + void to_dot(std::ostream&, StringData title = StringData()) const; +#endif + +private: + size_t calc_byte_len(size_t for_size, size_t width) const override; + size_t calc_item_count(size_t bytes, size_t width) const noexcept override; +}; + + +// Implementation: + +// Creates new array (but invalid, call init_from_ref() to init) +inline ArrayBlob::ArrayBlob(Allocator& allocator) noexcept + : Array(allocator) +{ +} + +inline bool ArrayBlob::is_null(size_t index) const noexcept +{ + return (get(index) == nullptr); +} + +inline const char* ArrayBlob::get(size_t index) const noexcept +{ + return m_data + index; +} + +inline ref_type ArrayBlob::add(const char* data, size_t data_size, bool add_zero_term) +{ + return replace(m_size, m_size, data, data_size, add_zero_term); +} + +inline void ArrayBlob::insert(size_t pos, const char* data, size_t data_size, bool add_zero_term) +{ + replace(pos, pos, data, data_size, add_zero_term); +} + +inline void ArrayBlob::erase(size_t begin, size_t end) +{ + const char* data = nullptr; + size_t data_size = 0; + replace(begin, end, data, data_size); +} + +inline const char* ArrayBlob::get(const char* header, size_t pos) noexcept +{ + const char* data = get_data_from_header(header); + return data + pos; +} + +inline void ArrayBlob::create() +{ + size_t init_size = 0; + MemRef mem = create_array(init_size, get_alloc()); // Throws + init_from_mem(mem); +} + +inline MemRef ArrayBlob::create_array(size_t init_size, Allocator& allocator) +{ + bool context_flag = false; + int_fast64_t value = 0; + return Array::create(type_Normal, context_flag, wtype_Ignore, init_size, value, allocator); // Throws +} + +inline size_t ArrayBlob::calc_byte_len(size_t for_size, size_t) const +{ + return header_size + for_size; +} + +inline size_t ArrayBlob::calc_item_count(size_t bytes, size_t) const noexcept +{ + return bytes - header_size; +} + + +} // namespace realm + +#endif // REALM_ARRAY_BLOB_HPP diff --git a/!main project/Pods/Realm/include/core/realm/array_blobs_big.hpp b/!main project/Pods/Realm/include/core/realm/array_blobs_big.hpp new file mode 100644 index 0000000..cc793fb --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/array_blobs_big.hpp @@ -0,0 +1,222 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_ARRAY_BIG_BLOBS_HPP +#define REALM_ARRAY_BIG_BLOBS_HPP + +#include + +namespace realm { + + +class ArrayBigBlobs : public Array { +public: + typedef BinaryData value_type; + + explicit ArrayBigBlobs(Allocator&, bool nullable) noexcept; + + // Disable copying, this is not allowed. + ArrayBigBlobs& operator=(const ArrayBigBlobs&) = delete; + ArrayBigBlobs(const ArrayBigBlobs&) = delete; + + BinaryData get(size_t ndx) const noexcept; + BinaryData get_at(size_t ndx, size_t& pos) const noexcept; + void set(size_t ndx, BinaryData value, bool add_zero_term = false); + void add(BinaryData value, bool add_zero_term = false); + void insert(size_t ndx, BinaryData value, bool add_zero_term = false); + void erase(size_t ndx); + void truncate(size_t new_size); + void clear(); + void destroy(); + + size_t count(BinaryData value, bool is_string = false, size_t begin = 0, size_t end = npos) const noexcept; + size_t find_first(BinaryData value, bool is_string = false, size_t begin = 0, size_t end = npos) const noexcept; + void find_all(IntegerColumn& result, BinaryData value, bool is_string = false, size_t add_offset = 0, + size_t begin = 0, size_t end = npos); + + /// Get the specified element without the cost of constructing an + /// array instance. If an array instance is already available, or + /// you need to get multiple values, then this method will be + /// slower. + static BinaryData get(const char* header, size_t ndx, Allocator&) noexcept; + + ref_type bptree_leaf_insert(size_t ndx, BinaryData, bool add_zero_term, TreeInsertBase& state); + + //@{ + /// Those that return a string, discard the terminating zero from + /// the stored value. Those that accept a string argument, add a + /// terminating zero before storing the value. + StringData get_string(size_t ndx) const noexcept; + void add_string(StringData value); + void set_string(size_t ndx, StringData value); + void insert_string(size_t ndx, StringData value); + static StringData get_string(const char* header, size_t ndx, Allocator&, bool nullable) noexcept; + ref_type bptree_leaf_insert_string(size_t ndx, StringData, TreeInsertBase& state); + //@} + + /// Create a new empty big blobs array and attach this accessor to + /// it. This does not modify the parent reference information of + /// this accessor. + /// + /// Note that the caller assumes ownership of the allocated + /// underlying node. It is not owned by the accessor. + void create(); + + /// Construct a copy of the specified slice of this big blobs + /// array using the specified target allocator. + MemRef slice(size_t offset, size_t slice_size, Allocator& target_alloc) const; + +#ifdef REALM_DEBUG + void verify() const; + void to_dot(std::ostream&, bool is_strings, StringData title = StringData()) const; +#endif + +private: + bool m_nullable; +}; + + +// Implementation: + +inline ArrayBigBlobs::ArrayBigBlobs(Allocator& allocator, bool nullable) noexcept + : Array(allocator) + , m_nullable(nullable) +{ +} + +inline BinaryData ArrayBigBlobs::get(size_t ndx) const noexcept +{ + ref_type ref = get_as_ref(ndx); + if (ref == 0) + return {}; // realm::null(); + + const char* blob_header = get_alloc().translate(ref); + if (!get_context_flag_from_header(blob_header)) { + const char* value = ArrayBlob::get(blob_header, 0); + size_t sz = get_size_from_header(blob_header); + return BinaryData(value, sz); + } + return {}; +} + +inline BinaryData ArrayBigBlobs::get(const char* header, size_t ndx, Allocator& alloc) noexcept +{ + ref_type blob_ref = to_ref(Array::get(header, ndx)); + if (blob_ref == 0) + return {}; + + const char* blob_header = alloc.translate(blob_ref); + if (!get_context_flag_from_header(blob_header)) { + const char* blob_data = Array::get_data_from_header(blob_header); + size_t sz = Array::get_size_from_header(blob_header); + return BinaryData(blob_data, sz); + } + return {}; +} + +inline void ArrayBigBlobs::erase(size_t ndx) +{ + ref_type blob_ref = Array::get_as_ref(ndx); + if (blob_ref != 0) { // nothing to destroy if null + Array::destroy_deep(blob_ref, get_alloc()); // Deep + } + Array::erase(ndx); +} + +inline void ArrayBigBlobs::truncate(size_t new_size) +{ + Array::truncate_and_destroy_children(new_size); +} + +inline void ArrayBigBlobs::clear() +{ + Array::clear_and_destroy_children(); +} + +inline void ArrayBigBlobs::destroy() +{ + Array::destroy_deep(); +} + +inline StringData ArrayBigBlobs::get_string(size_t ndx) const noexcept +{ + BinaryData bin = get(ndx); + if (bin.is_null()) + return realm::null(); + else + return StringData(bin.data(), bin.size() - 1); // Do not include terminating zero +} + +inline void ArrayBigBlobs::set_string(size_t ndx, StringData value) +{ + REALM_ASSERT_DEBUG(!(!m_nullable && value.is_null())); + BinaryData bin(value.data(), value.size()); + bool add_zero_term = true; + set(ndx, bin, add_zero_term); +} + +inline void ArrayBigBlobs::add_string(StringData value) +{ + REALM_ASSERT_DEBUG(!(!m_nullable && value.is_null())); + BinaryData bin(value.data(), value.size()); + bool add_zero_term = true; + add(bin, add_zero_term); +} + +inline void ArrayBigBlobs::insert_string(size_t ndx, StringData value) +{ + REALM_ASSERT_DEBUG(!(!m_nullable && value.is_null())); + BinaryData bin(value.data(), value.size()); + bool add_zero_term = true; + insert(ndx, bin, add_zero_term); +} + +inline StringData ArrayBigBlobs::get_string(const char* header, size_t ndx, Allocator& alloc, bool nullable) noexcept +{ + static_cast(nullable); + BinaryData bin = get(header, ndx, alloc); + REALM_ASSERT_DEBUG(!(!nullable && bin.is_null())); + if (bin.is_null()) + return realm::null(); + else + return StringData(bin.data(), bin.size() - 1); // Do not include terminating zero +} + +inline ref_type ArrayBigBlobs::bptree_leaf_insert_string(size_t ndx, StringData value, TreeInsertBase& state) +{ + REALM_ASSERT_DEBUG(!(!m_nullable && value.is_null())); + BinaryData bin(value.data(), value.size()); + bool add_zero_term = true; + return bptree_leaf_insert(ndx, bin, add_zero_term, state); +} + +inline void ArrayBigBlobs::create() +{ + bool context_flag = true; + Array::create(type_HasRefs, context_flag); // Throws +} + +inline MemRef ArrayBigBlobs::slice(size_t offset, size_t slice_size, Allocator& target_alloc) const +{ + return slice_and_clone_children(offset, slice_size, target_alloc); +} + + +} // namespace realm + +#endif // REALM_ARRAY_BIG_BLOBS_HPP diff --git a/!main project/Pods/Realm/include/core/realm/array_direct.hpp b/!main project/Pods/Realm/include/core/realm/array_direct.hpp new file mode 100644 index 0000000..e337392 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/array_direct.hpp @@ -0,0 +1,372 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_ARRAY_DIRECT_HPP +#define REALM_ARRAY_DIRECT_HPP + +#include +#include + +using namespace realm::util; + +// clang-format off +/* wid == 16/32 likely when accessing offsets in B tree */ +#define REALM_TEMPEX(fun, wid, arg) \ + if (wid == 16) {fun<16> arg;} \ + else if (wid == 32) {fun<32> arg;} \ + else if (wid == 0) {fun<0> arg;} \ + else if (wid == 1) {fun<1> arg;} \ + else if (wid == 2) {fun<2> arg;} \ + else if (wid == 4) {fun<4> arg;} \ + else if (wid == 8) {fun<8> arg;} \ + else if (wid == 64) {fun<64> arg;} \ + else {REALM_ASSERT_DEBUG(false); fun<0> arg;} + +#define REALM_TEMPEX2(fun, targ, wid, arg) \ + if (wid == 16) {fun arg;} \ + else if (wid == 32) {fun arg;} \ + else if (wid == 0) {fun arg;} \ + else if (wid == 1) {fun arg;} \ + else if (wid == 2) {fun arg;} \ + else if (wid == 4) {fun arg;} \ + else if (wid == 8) {fun arg;} \ + else if (wid == 64) {fun arg;} \ + else {REALM_ASSERT_DEBUG(false); fun arg;} + +#define REALM_TEMPEX3(fun, targ1, targ2, wid, arg) \ + if (wid == 16) {fun arg;} \ + else if (wid == 32) {fun arg;} \ + else if (wid == 0) {fun arg;} \ + else if (wid == 1) {fun arg;} \ + else if (wid == 2) {fun arg;} \ + else if (wid == 4) {fun arg;} \ + else if (wid == 8) {fun arg;} \ + else if (wid == 64) {fun arg;} \ + else {REALM_ASSERT_DEBUG(false); fun arg;} + +#define REALM_TEMPEX4(fun, targ1, targ2, wid, targ3, arg) \ + if (wid == 16) {fun arg;} \ + else if (wid == 32) {fun arg;} \ + else if (wid == 0) {fun arg;} \ + else if (wid == 1) {fun arg;} \ + else if (wid == 2) {fun arg;} \ + else if (wid == 4) {fun arg;} \ + else if (wid == 8) {fun arg;} \ + else if (wid == 64) {fun arg;} \ + else {REALM_ASSERT_DEBUG(false); fun arg;} + +#define REALM_TEMPEX5(fun, targ1, targ2, targ3, targ4, wid, arg) \ + if (wid == 16) {fun arg;} \ + else if (wid == 32) {fun arg;} \ + else if (wid == 0) {fun arg;} \ + else if (wid == 1) {fun arg;} \ + else if (wid == 2) {fun arg;} \ + else if (wid == 4) {fun arg;} \ + else if (wid == 8) {fun arg;} \ + else if (wid == 64) {fun arg;} \ + else {REALM_ASSERT_DEBUG(false); fun arg;} +// clang-format on + +namespace realm { + +// Direct access methods + +template +void set_direct(char* data, size_t ndx, int_fast64_t value) noexcept +{ + if (width == 0) { + REALM_ASSERT_DEBUG(value == 0); + return; + } + else if (width == 1) { + REALM_ASSERT_DEBUG(0 <= value && value <= 0x01); + size_t byte_ndx = ndx / 8; + size_t bit_ndx = ndx % 8; + typedef unsigned char uchar; + uchar* p = reinterpret_cast(data) + byte_ndx; + *p = uchar((*p & ~(0x01 << bit_ndx)) | (int(value) & 0x01) << bit_ndx); + } + else if (width == 2) { + REALM_ASSERT_DEBUG(0 <= value && value <= 0x03); + size_t byte_ndx = ndx / 4; + size_t bit_ndx = ndx % 4 * 2; + typedef unsigned char uchar; + uchar* p = reinterpret_cast(data) + byte_ndx; + *p = uchar((*p & ~(0x03 << bit_ndx)) | (int(value) & 0x03) << bit_ndx); + } + else if (width == 4) { + REALM_ASSERT_DEBUG(0 <= value && value <= 0x0F); + size_t byte_ndx = ndx / 2; + size_t bit_ndx = ndx % 2 * 4; + typedef unsigned char uchar; + uchar* p = reinterpret_cast(data) + byte_ndx; + *p = uchar((*p & ~(0x0F << bit_ndx)) | (int(value) & 0x0F) << bit_ndx); + } + else if (width == 8) { + REALM_ASSERT_DEBUG(std::numeric_limits::min() <= value && + value <= std::numeric_limits::max()); + *(reinterpret_cast(data) + ndx) = int8_t(value); + } + else if (width == 16) { + REALM_ASSERT_DEBUG(std::numeric_limits::min() <= value && + value <= std::numeric_limits::max()); + *(reinterpret_cast(data) + ndx) = int16_t(value); + } + else if (width == 32) { + REALM_ASSERT_DEBUG(std::numeric_limits::min() <= value && + value <= std::numeric_limits::max()); + *(reinterpret_cast(data) + ndx) = int32_t(value); + } + else if (width == 64) { + REALM_ASSERT_DEBUG(std::numeric_limits::min() <= value && + value <= std::numeric_limits::max()); + *(reinterpret_cast(data) + ndx) = int64_t(value); + } + else { + REALM_ASSERT_DEBUG(false); + } +} + +template +void fill_direct(char* data, size_t begin, size_t end, int_fast64_t value) noexcept +{ + for (size_t i = begin; i != end; ++i) + set_direct(data, i, value); +} + +template +int64_t get_direct(const char* data, size_t ndx) noexcept +{ + if (w == 0) { + return 0; + } + if (w == 1) { + size_t offset = ndx >> 3; + return (data[offset] >> (ndx & 7)) & 0x01; + } + if (w == 2) { + size_t offset = ndx >> 2; + return (data[offset] >> ((ndx & 3) << 1)) & 0x03; + } + if (w == 4) { + size_t offset = ndx >> 1; + return (data[offset] >> ((ndx & 1) << 2)) & 0x0F; + } + if (w == 8) { + return *reinterpret_cast(data + ndx); + } + if (w == 16) { + size_t offset = ndx * 2; + return *reinterpret_cast(data + offset); + } + if (w == 32) { + size_t offset = ndx * 4; + return *reinterpret_cast(data + offset); + } + if (w == 64) { + size_t offset = ndx * 8; + return *reinterpret_cast(data + offset); + } + REALM_ASSERT_DEBUG(false); + return int64_t(-1); +} + +inline int64_t get_direct(const char* data, size_t width, size_t ndx) noexcept +{ + REALM_TEMPEX(return get_direct, width, (data, ndx)); +} + + +template +inline std::pair get_two(const char* data, size_t ndx) noexcept +{ + return std::make_pair(to_size_t(get_direct(data, ndx + 0)), to_size_t(get_direct(data, ndx + 1))); +} + +inline std::pair get_two(const char* data, size_t width, size_t ndx) noexcept +{ + REALM_TEMPEX(return get_two, width, (data, ndx)); +} + + +template +inline void get_three(const char* data, size_t ndx, ref_type& v0, ref_type& v1, ref_type& v2) noexcept +{ + v0 = to_ref(get_direct(data, ndx + 0)); + v1 = to_ref(get_direct(data, ndx + 1)); + v2 = to_ref(get_direct(data, ndx + 2)); +} + +inline void get_three(const char* data, size_t width, size_t ndx, ref_type& v0, ref_type& v1, ref_type& v2) noexcept +{ + REALM_TEMPEX(get_three, width, (data, ndx, v0, v1, v2)); +} + + +// Lower/upper bound in sorted sequence +// ------------------------------------ +// +// 3 3 3 4 4 4 5 6 7 9 9 9 +// ^ ^ ^ ^ ^ +// | | | | | +// | | | | -- Lower and upper bound of 15 +// | | | | +// | | | -- Lower and upper bound of 8 +// | | | +// | | -- Upper bound of 4 +// | | +// | -- Lower bound of 4 +// | +// -- Lower and upper bound of 1 +// +// These functions are semantically identical to std::lower_bound() and +// std::upper_bound(). +// +// We currently use binary search. See for example +// http://www.tbray.org/ongoing/When/200x/2003/03/22/Binary. +template +inline size_t lower_bound(const char* data, size_t size, int64_t value) noexcept +{ + // The binary search used here is carefully optimized. Key trick is to use a single + // loop controlling variable (size) instead of high/low pair, and to keep updates + // to size done inside the loop independent of comparisons. Further key to speed + // is to avoid branching inside the loop, using conditional moves instead. This + // provides robust performance for random searches, though predictable searches + // might be slightly faster if we used branches instead. The loop unrolling yields + // a final 5-20% speedup depending on circumstances. + + size_t low = 0; + + while (size >= 8) { + // The following code (at X, Y and Z) is 3 times manually unrolled instances of (A) below. + // These code blocks must be kept in sync. Meassurements indicate 3 times unrolling to give + // the best performance. See (A) for comments on the loop body. + // (X) + size_t half = size / 2; + size_t other_half = size - half; + size_t probe = low + half; + size_t other_low = low + other_half; + int64_t v = get_direct(data, probe); + size = half; + low = (v < value) ? other_low : low; + + // (Y) + half = size / 2; + other_half = size - half; + probe = low + half; + other_low = low + other_half; + v = get_direct(data, probe); + size = half; + low = (v < value) ? other_low : low; + + // (Z) + half = size / 2; + other_half = size - half; + probe = low + half; + other_low = low + other_half; + v = get_direct(data, probe); + size = half; + low = (v < value) ? other_low : low; + } + while (size > 0) { + // (A) + // To understand the idea in this code, please note that + // for performance, computation of size for the next iteration + // MUST be INDEPENDENT of the conditional. This allows the + // processor to unroll the loop as fast as possible, and it + // minimizes the length of dependence chains leading up to branches. + // Making the unfolding of the loop independent of the data being + // searched, also minimizes the delays incurred by branch + // mispredictions, because they can be determined earlier + // and the speculation corrected earlier. + + // Counterintuitive: + // To make size independent of data, we cannot always split the + // range at the theoretical optimal point. When we determine that + // the key is larger than the probe at some index K, and prepare + // to search the upper part of the range, you would normally start + // the search at the next index, K+1, to get the shortest range. + // We can only do this when splitting a range with odd number of entries. + // If there is an even number of entries we search from K instead of K+1. + // This potentially leads to redundant comparisons, but in practice we + // gain more performance by making the changes to size predictable. + + // if size is even, half and other_half are the same. + // if size is odd, half is one less than other_half. + size_t half = size / 2; + size_t other_half = size - half; + size_t probe = low + half; + size_t other_low = low + other_half; + int64_t v = get_direct(data, probe); + size = half; + // for max performance, the line below should compile into a conditional + // move instruction. Not all compilers do this. To maximize chance + // of succes, no computation should be done in the branches of the + // conditional. + low = (v < value) ? other_low : low; + }; + + return low; +} + +// See lower_bound() +template +inline size_t upper_bound(const char* data, size_t size, int64_t value) noexcept +{ + size_t low = 0; + while (size >= 8) { + size_t half = size / 2; + size_t other_half = size - half; + size_t probe = low + half; + size_t other_low = low + other_half; + int64_t v = get_direct(data, probe); + size = half; + low = (value >= v) ? other_low : low; + + half = size / 2; + other_half = size - half; + probe = low + half; + other_low = low + other_half; + v = get_direct(data, probe); + size = half; + low = (value >= v) ? other_low : low; + + half = size / 2; + other_half = size - half; + probe = low + half; + other_low = low + other_half; + v = get_direct(data, probe); + size = half; + low = (value >= v) ? other_low : low; + } + + while (size > 0) { + size_t half = size / 2; + size_t other_half = size - half; + size_t probe = low + half; + size_t other_low = low + other_half; + int64_t v = get_direct(data, probe); + size = half; + low = (value >= v) ? other_low : low; + }; + + return low; +} +} + +#endif /* ARRAY_TPL_HPP_ */ diff --git a/!main project/Pods/Realm/include/core/realm/array_integer.hpp b/!main project/Pods/Realm/include/core/realm/array_integer.hpp new file mode 100644 index 0000000..e460c53 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/array_integer.hpp @@ -0,0 +1,629 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_ARRAY_INTEGER_HPP +#define REALM_ARRAY_INTEGER_HPP + +#include +#include +#include + +namespace realm { + +class ArrayInteger : public Array { +public: + typedef int64_t value_type; + + explicit ArrayInteger(Allocator&) noexcept; + ~ArrayInteger() noexcept override + { + } + + // Disable copying, this is not allowed. + ArrayInteger& operator=(const ArrayInteger&) = delete; + ArrayInteger(const ArrayInteger&) = delete; + + void create(Type type = type_Normal, bool context_flag = false); + + void add(int64_t value); + void set(size_t ndx, int64_t value); + void set_uint(size_t ndx, uint_fast64_t value) noexcept; + int64_t get(size_t ndx) const noexcept; + uint64_t get_uint(size_t ndx) const noexcept; + static int64_t get(const char* header, size_t ndx) noexcept; + bool compare(const ArrayInteger& a) const noexcept; + + /// Add \a diff to the element at the specified index. + void adjust(size_t ndx, int_fast64_t diff); + + /// Add \a diff to all the elements in the specified index range. + void adjust(size_t begin, size_t end, int_fast64_t diff); + + /// Add signed \a diff to all elements that are greater than, or equal to \a + /// limit. + void adjust_ge(int_fast64_t limit, int_fast64_t diff); + + int64_t operator[](size_t ndx) const noexcept + { + return get(ndx); + } + int64_t front() const noexcept; + int64_t back() const noexcept; + + size_t lower_bound(int64_t value) const noexcept; + size_t upper_bound(int64_t value) const noexcept; + + std::vector to_vector() const; + +private: + template + bool minmax(size_t from, size_t to, uint64_t maxdiff, int64_t* min, int64_t* max) const; +}; + +class ArrayIntNull : public Array { +public: + using value_type = util::Optional; + + explicit ArrayIntNull(Allocator&) noexcept; + ~ArrayIntNull() noexcept override; + + /// Construct an array of the specified type and size, and return just the + /// reference to the underlying memory. All elements will be initialized to + /// the specified value. + static MemRef create_array(Type, bool context_flag, size_t size, value_type value, Allocator&); + void create(Type = type_Normal, bool context_flag = false); + + void init_from_ref(ref_type) noexcept; + void init_from_mem(MemRef) noexcept; + void init_from_parent() noexcept; + + size_t size() const noexcept; + bool is_empty() const noexcept; + + void insert(size_t ndx, value_type value); + void add(value_type value); + void set(size_t ndx, value_type value) noexcept; + value_type get(size_t ndx) const noexcept; + static value_type get(const char* header, size_t ndx) noexcept; + void get_chunk(size_t ndx, value_type res[8]) const noexcept; + void set_null(size_t ndx) noexcept; + bool is_null(size_t ndx) const noexcept; + int64_t null_value() const noexcept; + + value_type operator[](size_t ndx) const noexcept; + value_type front() const noexcept; + value_type back() const noexcept; + void erase(size_t ndx); + void erase(size_t begin, size_t end); + void truncate(size_t size); + void clear(); + void set_all_to_zero(); + + void move(size_t begin, size_t end, size_t dest_begin); + void move_backward(size_t begin, size_t end, size_t dest_end); + + size_t lower_bound(int64_t value) const noexcept; + size_t upper_bound(int64_t value) const noexcept; + + int64_t sum(size_t start = 0, size_t end = npos) const; + size_t count(int64_t value) const noexcept; + bool maximum(int64_t& result, size_t start = 0, size_t end = npos, size_t* return_ndx = nullptr) const; + bool minimum(int64_t& result, size_t start = 0, size_t end = npos, size_t* return_ndx = nullptr) const; + + bool find(int cond, Action action, value_type value, size_t start, size_t end, size_t baseindex, + QueryState* state) const; + + template + bool find(value_type value, size_t start, size_t end, size_t baseindex, QueryState* state, + Callback callback) const; + + // This is the one installed into the m_finder slots. + template + bool find(int64_t value, size_t start, size_t end, size_t baseindex, QueryState* state) const; + + template + bool find(value_type value, size_t start, size_t end, size_t baseindex, QueryState* state, + Callback callback) const; + + // Optimized implementation for release mode + template + bool find_optimized(value_type value, size_t start, size_t end, size_t baseindex, QueryState* state, + Callback callback) const; + + // Called for each search result + template + bool find_action(size_t index, value_type value, QueryState* state, Callback callback) const; + + template + bool find_action_pattern(size_t index, uint64_t pattern, QueryState* state, Callback callback) const; + + // Wrappers for backwards compatibility and for simple use without + // setting up state initialization etc + template + size_t find_first(value_type value, size_t start = 0, size_t end = npos) const; + + void find_all(IntegerColumn* result, value_type value, size_t col_offset = 0, size_t begin = 0, + size_t end = npos) const; + + + size_t find_first(value_type value, size_t begin = 0, size_t end = npos) const; + + + // Overwrite Array::bptree_leaf_insert to correctly split nodes. + ref_type bptree_leaf_insert(size_t ndx, value_type value, TreeInsertBase& state); + + MemRef slice(size_t offset, size_t slice_size, Allocator& target_alloc) const; + + /// Construct a deep copy of the specified slice of this array using the + /// specified target allocator. Subarrays will be cloned. + MemRef slice_and_clone_children(size_t offset, size_t slice_size, Allocator& target_alloc) const; + +protected: + void avoid_null_collision(int64_t value); + +private: + template + bool minmax_helper(int64_t& result, size_t start = 0, size_t end = npos, size_t* return_ndx = nullptr) const; + + int_fast64_t choose_random_null(int64_t incoming) const; + void replace_nulls_with(int64_t new_null); + bool can_use_as_null(int64_t value) const; +}; + + +// Implementation: + +inline ArrayInteger::ArrayInteger(Allocator& allocator) noexcept + : Array(allocator) +{ + m_is_inner_bptree_node = false; +} + +inline void ArrayInteger::add(int64_t value) +{ + Array::add(value); +} + +inline int64_t ArrayInteger::get(size_t ndx) const noexcept +{ + return Array::get(ndx); +} + +inline int64_t ArrayInteger::get(const char* header, size_t ndx) noexcept +{ + return Array::get(header, ndx); +} + +inline void ArrayInteger::set(size_t ndx, int64_t value) +{ + Array::set(ndx, value); +} + +inline void ArrayInteger::set_uint(size_t ndx, uint_fast64_t value) noexcept +{ + // When a value of a signed type is converted to an unsigned type, the C++ + // standard guarantees that negative values are converted from the native + // representation to 2's complement, but the effect of conversions in the + // opposite direction is left unspecified by the + // standard. `realm::util::from_twos_compl()` is used here to perform the + // correct opposite unsigned-to-signed conversion, which reduces to a no-op + // when 2's complement is the native representation of negative values. + set(ndx, util::from_twos_compl(value)); +} + +inline bool ArrayInteger::compare(const ArrayInteger& a) const noexcept +{ + if (a.size() != size()) + return false; + + for (size_t i = 0; i < size(); ++i) { + if (get(i) != a.get(i)) + return false; + } + + return true; +} + +inline int64_t ArrayInteger::front() const noexcept +{ + return Array::front(); +} + +inline int64_t ArrayInteger::back() const noexcept +{ + return Array::back(); +} + +inline void ArrayInteger::adjust(size_t ndx, int_fast64_t diff) +{ + Array::adjust(ndx, diff); +} + +inline void ArrayInteger::adjust(size_t begin, size_t end, int_fast64_t diff) +{ + Array::adjust(begin, end, diff); +} + +inline void ArrayInteger::adjust_ge(int_fast64_t limit, int_fast64_t diff) +{ + Array::adjust_ge(limit, diff); +} + +inline size_t ArrayInteger::lower_bound(int64_t value) const noexcept +{ + return lower_bound_int(value); +} + +inline size_t ArrayInteger::upper_bound(int64_t value) const noexcept +{ + return upper_bound_int(value); +} + + +inline ArrayIntNull::ArrayIntNull(Allocator& allocator) noexcept + : Array(allocator) +{ +} + +inline ArrayIntNull::~ArrayIntNull() noexcept +{ +} + +inline void ArrayIntNull::create(Type type, bool context_flag) +{ + MemRef r = create_array(type, context_flag, 0, util::none, m_alloc); + init_from_mem(r); +} + + +inline size_t ArrayIntNull::size() const noexcept +{ + return Array::size() - 1; +} + +inline bool ArrayIntNull::is_empty() const noexcept +{ + return size() == 0; +} + +inline void ArrayIntNull::insert(size_t ndx, value_type value) +{ + if (value) { + avoid_null_collision(*value); + Array::insert(ndx + 1, *value); + } + else { + Array::insert(ndx + 1, null_value()); + } +} + +inline void ArrayIntNull::add(value_type value) +{ + if (value) { + avoid_null_collision(*value); + Array::add(*value); + } + else { + Array::add(null_value()); + } +} + +inline void ArrayIntNull::set(size_t ndx, value_type value) noexcept +{ + if (value) { + avoid_null_collision(*value); + Array::set(ndx + 1, *value); + } + else { + Array::set(ndx + 1, null_value()); + } +} + +inline void ArrayIntNull::set_null(size_t ndx) noexcept +{ + Array::set(ndx + 1, null_value()); +} + +inline ArrayIntNull::value_type ArrayIntNull::get(size_t ndx) const noexcept +{ + int64_t value = Array::get(ndx + 1); + if (value == null_value()) { + return util::none; + } + return util::some(value); +} + +inline ArrayIntNull::value_type ArrayIntNull::get(const char* header, size_t ndx) noexcept +{ + int64_t null_value = Array::get(header, 0); + int64_t value = Array::get(header, ndx + 1); + if (value == null_value) { + return util::none; + } + else { + return util::some(value); + } +} + +inline bool ArrayIntNull::is_null(size_t ndx) const noexcept +{ + return !get(ndx); +} + +inline int64_t ArrayIntNull::null_value() const noexcept +{ + return Array::get(0); +} + +inline ArrayIntNull::value_type ArrayIntNull::operator[](size_t ndx) const noexcept +{ + return get(ndx); +} + +inline ArrayIntNull::value_type ArrayIntNull::front() const noexcept +{ + return get(0); +} + +inline ArrayIntNull::value_type ArrayIntNull::back() const noexcept +{ + return Array::back(); +} + +inline void ArrayIntNull::erase(size_t ndx) +{ + Array::erase(ndx + 1); +} + +inline void ArrayIntNull::erase(size_t begin, size_t end) +{ + Array::erase(begin + 1, end + 1); +} + +inline void ArrayIntNull::truncate(size_t to_size) +{ + Array::truncate(to_size + 1); +} + +inline void ArrayIntNull::clear() +{ + truncate(0); +} + +inline void ArrayIntNull::set_all_to_zero() +{ + // FIXME: Array::set_all_to_zero does something else + for (size_t i = 0; i < size(); ++i) { + set(i, 0); + } +} + +inline void ArrayIntNull::move(size_t begin, size_t end, size_t dest_begin) +{ + Array::move(begin + 1, end + 1, dest_begin + 1); +} + +inline void ArrayIntNull::move_backward(size_t begin, size_t end, size_t dest_end) +{ + Array::move_backward(begin + 1, end + 1, dest_end + 1); +} + +inline size_t ArrayIntNull::lower_bound(int64_t value) const noexcept +{ + // FIXME: Consider this behaviour with NULLs. + // Array::lower_bound_int assumes an already sorted array, but + // this array could be sorted with nulls first or last. + return Array::lower_bound_int(value); +} + +inline size_t ArrayIntNull::upper_bound(int64_t value) const noexcept +{ + // FIXME: see lower_bound + return Array::upper_bound_int(value); +} + +inline int64_t ArrayIntNull::sum(size_t start, size_t end) const +{ + // FIXME: Optimize + int64_t sum_of_range = 0; + if (end == npos) + end = size(); + for (size_t i = start; i < end; ++i) { + value_type x = get(i); + if (x) { + sum_of_range += *x; + } + } + return sum_of_range; +} + +inline size_t ArrayIntNull::count(int64_t value) const noexcept +{ + size_t count_of_value = Array::count(value); + if (value == null_value()) { + --count_of_value; + } + return count_of_value; +} + +// FIXME: Optimize +template +inline bool ArrayIntNull::minmax_helper(int64_t& result, size_t start, size_t end, size_t* return_ndx) const +{ + size_t best_index = 1; + + if (end == npos) { + end = m_size; + } + + ++start; + + REALM_ASSERT(start < m_size && end <= m_size && start < end); + + if (m_size == 1) { + // empty array + return false; + } + + if (m_width == 0) { + if (return_ndx) + *return_ndx = best_index - 1; + result = 0; + return true; + } + + int64_t m = Array::get(start); + + const int64_t null_val = null_value(); + for (; start < end; ++start) { + const int64_t v = Array::get(start); + if (find_max ? v > m : v < m) { + if (v == null_val) { + continue; + } + m = v; + best_index = start; + } + } + + result = m; + if (return_ndx) { + *return_ndx = best_index - 1; + } + return true; +} + +inline bool ArrayIntNull::maximum(int64_t& result, size_t start, size_t end, size_t* return_ndx) const +{ + return minmax_helper(result, start, end, return_ndx); +} + +inline bool ArrayIntNull::minimum(int64_t& result, size_t start, size_t end, size_t* return_ndx) const +{ + return minmax_helper(result, start, end, return_ndx); +} + +inline bool ArrayIntNull::find(int cond, Action action, value_type value, size_t start, size_t end, size_t baseindex, + QueryState* state) const +{ + if (value) { + return Array::find(cond, action, *value, start, end, baseindex, state, true /*treat as nullable array*/, + false /*search parameter given in 'value' argument*/); + } + else { + return Array::find(cond, action, 0 /* unused dummy*/, start, end, baseindex, state, + true /*treat as nullable array*/, true /*search for null, ignore value argument*/); + } +} + +template +bool ArrayIntNull::find(value_type value, size_t start, size_t end, size_t baseindex, QueryState* state, + Callback callback) const +{ + if (value) { + return Array::find(*value, start, end, baseindex, state, std::forward(callback), + true /*treat as nullable array*/, + false /*search parameter given in 'value' argument*/); + } + else { + return Array::find(0 /*ignored*/, start, end, baseindex, state, + std::forward(callback), true /*treat as nullable array*/, + true /*search for null, ignore value argument*/); + } +} + + +template +bool ArrayIntNull::find(int64_t value, size_t start, size_t end, size_t baseindex, QueryState* state) const +{ + return Array::find(value, start, end, baseindex, state, true /*treat as nullable array*/, + false /*search parameter given in 'value' argument*/); +} + + +template +bool ArrayIntNull::find(value_type value, size_t start, size_t end, size_t baseindex, QueryState* state, + Callback callback) const +{ + if (value) { + return Array::find(*value, start, end, baseindex, state, std::forward(callback), + true /*treat as nullable array*/, + false /*search parameter given in 'value' argument*/); + } + else { + return Array::find(0 /*ignored*/, start, end, baseindex, state, + std::forward(callback), true /*treat as nullable array*/, + true /*search for null, ignore value argument*/); + } +} + + +template +bool ArrayIntNull::find_action(size_t index, value_type value, QueryState* state, Callback callback) const +{ + if (value) { + return Array::find_action(index, *value, state, callback, true /*treat as nullable array*/, + false /*search parameter given in 'value' argument*/); + } + else { + return Array::find_action(index, 0 /* ignored */, state, callback, + true /*treat as nullable array*/, + true /*search for null, ignore value argument*/); + } +} + + +template +bool ArrayIntNull::find_action_pattern(size_t index, uint64_t pattern, QueryState* state, + Callback callback) const +{ + return Array::find_action_pattern(index, pattern, state, callback, + true /*treat as nullable array*/, + false /*search parameter given in 'value' argument*/); +} + + +template +size_t ArrayIntNull::find_first(value_type value, size_t start, size_t end) const +{ + QueryState state; + state.init(act_ReturnFirst, nullptr, 1); + if (value) { + Array::find(*value, start, end, 0, &state, Array::CallbackDummy(), + true /*treat as nullable array*/, + false /*search parameter given in 'value' argument*/); + } + else { + Array::find(0 /*ignored*/, start, end, 0, &state, Array::CallbackDummy(), + true /*treat as nullable array*/, + true /*search for null, ignore value argument*/); + } + + if (state.m_match_count > 0) + return to_size_t(state.m_state); + else + return not_found; +} + +inline size_t ArrayIntNull::find_first(value_type value, size_t begin, size_t end) const +{ + return find_first(value, begin, end); +} +} + +#endif // REALM_ARRAY_INTEGER_HPP diff --git a/!main project/Pods/Realm/include/core/realm/array_string.hpp b/!main project/Pods/Realm/include/core/realm/array_string.hpp new file mode 100644 index 0000000..a533f32 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/array_string.hpp @@ -0,0 +1,181 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_ARRAY_STRING_HPP +#define REALM_ARRAY_STRING_HPP + +#include + +namespace realm { + +/* +ArrayString stores strings as a concecutive list of fixed-length blocks of m_width bytes. The +longest string it can store is (m_width - 1) bytes before it needs to expand. + +An example of the format for m_width = 4 is following sequence of bytes, where x is payload: + +xxx0 xx01 x002 0003 0004 (strings "xxx",. "xx", "x", "", realm::null()) + +So each string is 0 terminated, and the last byte in a block tells how many 0s are present, except +for a realm::null() which has the byte set to m_width (4). The byte is used to compute the length of a string +in various functions. + +New: If m_witdh = 0, then all elements are realm::null(). So to add an empty string we must expand m_width +New: StringData is null() if-and-only-if StringData::data() == 0. +*/ + +class ArrayString : public Array { +public: + static const size_t max_width = 64; + + typedef StringData value_type; + // Constructor defaults to non-nullable because we use non-nullable ArrayString so many places internally in core + // (data which isn't user payload) where null isn't needed. + explicit ArrayString(Allocator&, bool nullable = false) noexcept; + ~ArrayString() noexcept override + { + } + + bool is_null(size_t ndx) const; + void set_null(size_t ndx); + StringData get(size_t ndx) const noexcept; + StringData get_string(size_t ndx) const noexcept { return get(ndx); } + void add(); + void add(StringData value); + void set(size_t ndx, StringData value); + void insert(size_t ndx, StringData value); + void erase(size_t ndx); + + size_t count(StringData value, size_t begin = 0, size_t end = npos) const noexcept; + size_t find_first(StringData value, size_t begin = 0, size_t end = npos) const noexcept; + void find_all(IntegerColumn& result, StringData value, size_t add_offset = 0, size_t begin = 0, + size_t end = npos); + + /// Compare two string arrays for equality. + bool compare_string(const ArrayString&) const noexcept; + + /// Get the specified element without the cost of constructing an + /// array instance. If an array instance is already available, or + /// you need to get multiple values, then this method will be + /// slower. + static StringData get(const char* header, size_t ndx, bool nullable) noexcept; + + ref_type bptree_leaf_insert(size_t ndx, StringData, TreeInsertBase& state); + + /// Construct a string array of the specified size and return just + /// the reference to the underlying memory. All elements will be + /// initialized to the empty string. + static MemRef create_array(size_t size, Allocator&); + + /// Create a new empty string array and attach this accessor to + /// it. This does not modify the parent reference information of + /// this accessor. + /// + /// Note that the caller assumes ownership of the allocated + /// underlying node. It is not owned by the accessor. + void create(); + + /// Construct a copy of the specified slice of this string array + /// using the specified target allocator. + MemRef slice(size_t offset, size_t slice_size, Allocator& target_alloc) const; + +#ifdef REALM_DEBUG + void string_stats() const; + void to_dot(std::ostream&, StringData title = StringData()) const; +#endif + +private: + size_t calc_byte_len(size_t num_items, size_t width) const override; + size_t calc_item_count(size_t bytes, size_t width) const noexcept override; + + bool m_nullable; +}; + + +// Implementation: + +// Creates new array (but invalid, call init_from_ref() to init) +inline ArrayString::ArrayString(Allocator& allocator, bool nullable) noexcept + : Array(allocator) + , m_nullable(nullable) +{ +} + +inline void ArrayString::create() +{ + size_t init_size = 0; + MemRef mem = create_array(init_size, get_alloc()); // Throws + init_from_mem(mem); +} + +inline MemRef ArrayString::create_array(size_t init_size, Allocator& allocator) +{ + bool context_flag = false; + int_fast64_t value = 0; + return Array::create(type_Normal, context_flag, wtype_Multiply, init_size, value, allocator); // Throws +} + +inline StringData ArrayString::get(size_t ndx) const noexcept +{ + REALM_ASSERT_3(ndx, <, m_size); + if (m_width == 0) + return m_nullable ? realm::null() : StringData(""); + + const char* data = m_data + (ndx * m_width); + size_t array_size = (m_width - 1) - data[m_width - 1]; + + if (array_size == static_cast(-1)) + return m_nullable ? realm::null() : StringData(""); + + REALM_ASSERT_EX(data[array_size] == 0, data[array_size], + array_size); // Realm guarantees 0 terminated return strings + return StringData(data, array_size); +} + +inline void ArrayString::add(StringData value) +{ + REALM_ASSERT(!(!m_nullable && value.is_null())); + insert(m_size, value); // Throws +} + +inline void ArrayString::add() +{ + add(m_nullable ? realm::null() : StringData("")); // Throws +} + +inline StringData ArrayString::get(const char* header, size_t ndx, bool nullable) noexcept +{ + REALM_ASSERT(ndx < get_size_from_header(header)); + uint_least8_t width = get_width_from_header(header); + const char* data = get_data_from_header(header) + (ndx * width); + + if (width == 0) + return nullable ? realm::null() : StringData(""); + + size_t size = (width - 1) - data[width - 1]; + + if (size == static_cast(-1)) + return nullable ? realm::null() : StringData(""); + + return StringData(data, size); +} + + +} // namespace realm + +#endif // REALM_ARRAY_STRING_HPP diff --git a/!main project/Pods/Realm/include/core/realm/array_string_long.hpp b/!main project/Pods/Realm/include/core/realm/array_string_long.hpp new file mode 100644 index 0000000..b03d8b4 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/array_string_long.hpp @@ -0,0 +1,229 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_ARRAY_STRING_LONG_HPP +#define REALM_ARRAY_STRING_LONG_HPP + +#include +#include + +namespace realm { + + +class ArrayStringLong : public Array { +public: + typedef StringData value_type; + + explicit ArrayStringLong(Allocator&, bool nullable) noexcept; + ~ArrayStringLong() noexcept override + { + } + + // Disable copying, this is not allowed. + ArrayStringLong& operator=(const ArrayStringLong&) = delete; + ArrayStringLong(const ArrayStringLong&) = delete; + + /// Create a new empty long string array and attach this accessor to + /// it. This does not modify the parent reference information of + /// this accessor. + /// + /// Note that the caller assumes ownership of the allocated + /// underlying node. It is not owned by the accessor. + void create(); + + //@{ + /// Overriding functions of Array + void init_from_ref(ref_type) noexcept; + void init_from_mem(MemRef) noexcept; + void init_from_parent() noexcept; + //@} + + bool is_empty() const noexcept; + size_t size() const noexcept; + + StringData get(size_t ndx) const noexcept; + StringData get_string(size_t ndx) const noexcept { return get(ndx); } + + + void add(StringData value); + void set(size_t ndx, StringData value); + void insert(size_t ndx, StringData value); + void erase(size_t ndx); + void truncate(size_t size); + void clear(); + void destroy(); + + bool is_null(size_t ndx) const; + void set_null(size_t ndx); + + size_t count(StringData value, size_t begin = 0, size_t end = npos) const noexcept; + size_t find_first(StringData value, size_t begin = 0, size_t end = npos) const noexcept; + void find_all(IntegerColumn& result, StringData value, size_t add_offset = 0, size_t begin = 0, + size_t end = npos) const; + + /// Get the specified element without the cost of constructing an + /// array instance. If an array instance is already available, or + /// you need to get multiple values, then this method will be + /// slower. + static StringData get(const char* header, size_t ndx, Allocator&, bool nullable) noexcept; + + ref_type bptree_leaf_insert(size_t ndx, StringData, TreeInsertBase&); + + static size_t get_size_from_header(const char*, Allocator&) noexcept; + + /// Construct a long string array of the specified size and return + /// just the reference to the underlying memory. All elements will + /// be initialized to zero size blobs. + static MemRef create_array(size_t size, Allocator&, bool nullable); + + /// Construct a copy of the specified slice of this long string + /// array using the specified target allocator. + MemRef slice(size_t offset, size_t slice_size, Allocator& target_alloc) const; + +#ifdef REALM_DEBUG + void to_dot(std::ostream&, StringData title = StringData()) const; +#endif + + bool update_from_parent(size_t old_baseline) noexcept; + +private: + ArrayInteger m_offsets; + ArrayBlob m_blob; + Array m_nulls; + bool m_nullable; +}; + + +// Implementation: +inline ArrayStringLong::ArrayStringLong(Allocator& allocator, bool nullable) noexcept + : Array(allocator) + , m_offsets(allocator) + , m_blob(allocator) + , m_nulls(nullable ? allocator : Allocator::get_default()) + , m_nullable(nullable) +{ + m_offsets.set_parent(this, 0); + m_blob.set_parent(this, 1); + if (nullable) + m_nulls.set_parent(this, 2); +} + +inline void ArrayStringLong::create() +{ + size_t init_size = 0; + MemRef mem = create_array(init_size, get_alloc(), m_nullable); // Throws + init_from_mem(mem); +} + +inline void ArrayStringLong::init_from_ref(ref_type ref) noexcept +{ + REALM_ASSERT(ref); + char* header = get_alloc().translate(ref); + init_from_mem(MemRef(header, ref, m_alloc)); + m_nullable = (Array::size() == 3); +} + +inline void ArrayStringLong::init_from_parent() noexcept +{ + ref_type ref = get_ref_from_parent(); + init_from_ref(ref); +} + +inline bool ArrayStringLong::is_empty() const noexcept +{ + return m_offsets.is_empty(); +} + +inline size_t ArrayStringLong::size() const noexcept +{ + return m_offsets.size(); +} + +inline StringData ArrayStringLong::get(size_t ndx) const noexcept +{ + REALM_ASSERT_3(ndx, <, m_offsets.size()); + + if (m_nullable && m_nulls.get(ndx) == 0) + return realm::null(); + + size_t begin, end; + if (0 < ndx) { + begin = to_size_t(m_offsets.get(ndx - 1)); + end = to_size_t(m_offsets.get(ndx)); + } + else { + begin = 0; + end = to_size_t(m_offsets.get(0)); + } + --end; // Discount the terminating zero + + return StringData(m_blob.get(begin), end - begin); +} + +inline void ArrayStringLong::truncate(size_t new_size) +{ + REALM_ASSERT_3(new_size, <, m_offsets.size()); + + size_t sz = new_size ? to_size_t(m_offsets.get(new_size - 1)) : 0; + + m_offsets.truncate(new_size); + m_blob.truncate(sz); + if (m_nullable) + m_nulls.truncate(new_size); +} + +inline void ArrayStringLong::clear() +{ + m_blob.clear(); + m_offsets.clear(); + if (m_nullable) + m_nulls.clear(); +} + +inline void ArrayStringLong::destroy() +{ + m_blob.destroy(); + m_offsets.destroy(); + if (m_nullable) + m_nulls.destroy(); + Array::destroy(); +} + +inline bool ArrayStringLong::update_from_parent(size_t old_baseline) noexcept +{ + bool res = Array::update_from_parent(old_baseline); + if (res) { + m_blob.update_from_parent(old_baseline); + m_offsets.update_from_parent(old_baseline); + if (m_nullable) + m_nulls.update_from_parent(old_baseline); + } + return res; +} + +inline size_t ArrayStringLong::get_size_from_header(const char* header, Allocator& alloc) noexcept +{ + ref_type offsets_ref = to_ref(Array::get(header, 0)); + const char* offsets_header = alloc.translate(offsets_ref); + return Array::get_size_from_header(offsets_header); +} + + +} // namespace realm + +#endif // REALM_ARRAY_STRING_LONG_HPP diff --git a/!main project/Pods/Realm/include/core/realm/binary_data.hpp b/!main project/Pods/Realm/include/core/realm/binary_data.hpp new file mode 100644 index 0000000..a184f0c --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/binary_data.hpp @@ -0,0 +1,239 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_BINARY_DATA_HPP +#define REALM_BINARY_DATA_HPP + +#include +#include +#include + +#include +#include +#include +#include + +namespace realm { + +/// A reference to a chunk of binary data. +/// +/// This class does not own the referenced memory, nor does it in any other way +/// attempt to manage the lifetime of it. +/// +/// \sa StringData +class BinaryData { +public: + BinaryData() noexcept + : m_data(nullptr) + , m_size(0) + { + } + BinaryData(const char* external_data, size_t data_size) noexcept + : m_data(external_data) + , m_size(data_size) + { + } + template + explicit BinaryData(const char (&external_data)[N]) + : m_data(external_data) + , m_size(N) + { + } + template + explicit BinaryData(const std::basic_string&); + + // BinaryData does not store data, callers must manage their own strings. + template + BinaryData(const std::basic_string&&) = delete; + + template + explicit operator std::basic_string() const; + + char operator[](size_t i) const noexcept + { + return m_data[i]; + } + + const char* data() const noexcept + { + return m_data; + } + size_t size() const noexcept + { + return m_size; + } + + /// Is this a null reference? + /// + /// An instance of BinaryData is a null reference when, and only when the + /// stored size is zero (size()) and the stored pointer is the null pointer + /// (data()). + /// + /// In the case of the empty byte sequence, the stored size is still zero, + /// but the stored pointer is **not** the null pointer. Note that the actual + /// value of the pointer is immaterial in this case (as long as it is not + /// zero), because when the size is zero, it is an error to dereference the + /// pointer. + /// + /// Conversion of a BinaryData object to `bool` yields the logical negation + /// of the result of calling this function. In other words, a BinaryData + /// object is converted to true if it is not the null reference, otherwise + /// it is converted to false. + /// + /// It is important to understand that all of the functions and operators in + /// this class, and most of the functions in the Realm API in general + /// makes no distinction between a null reference and a reference to the + /// empty byte sequence. These functions and operators never look at the + /// stored pointer if the stored size is zero. + bool is_null() const noexcept; + + friend bool operator==(const BinaryData&, const BinaryData&) noexcept; + friend bool operator!=(const BinaryData&, const BinaryData&) noexcept; + + //@{ + /// Trivial bytewise lexicographical comparison. + friend bool operator<(const BinaryData&, const BinaryData&) noexcept; + friend bool operator>(const BinaryData&, const BinaryData&) noexcept; + friend bool operator<=(const BinaryData&, const BinaryData&) noexcept; + friend bool operator>=(const BinaryData&, const BinaryData&) noexcept; + //@} + + bool begins_with(BinaryData) const noexcept; + bool ends_with(BinaryData) const noexcept; + bool contains(BinaryData) const noexcept; + + template + friend std::basic_ostream& operator<<(std::basic_ostream&, const BinaryData&); + + explicit operator bool() const noexcept; + +private: + const char* m_data; + size_t m_size; +}; + +/// A read-only chunk of binary data. +class OwnedBinaryData : public OwnedData { +public: + using OwnedData::OwnedData; + + OwnedBinaryData() = default; + OwnedBinaryData(const BinaryData& binary_data) + : OwnedData(binary_data.data(), binary_data.size()) + { + } + + BinaryData get() const + { + return {data(), size()}; + } +}; + + +// Implementation: + +template +inline BinaryData::BinaryData(const std::basic_string& s) + : m_data(s.data()) + , m_size(s.size()) +{ +} + +template +inline BinaryData::operator std::basic_string() const +{ + return std::basic_string(m_data, m_size); +} + +inline bool BinaryData::is_null() const noexcept +{ + return !m_data; +} + +inline bool operator==(const BinaryData& a, const BinaryData& b) noexcept +{ + return a.m_size == b.m_size && a.is_null() == b.is_null() && safe_equal(a.m_data, a.m_data + a.m_size, b.m_data); +} + +inline bool operator!=(const BinaryData& a, const BinaryData& b) noexcept +{ + return !(a == b); +} + +inline bool operator<(const BinaryData& a, const BinaryData& b) noexcept +{ + if (a.is_null() || b.is_null()) + return !a.is_null() < !b.is_null(); + + return std::lexicographical_compare(a.m_data, a.m_data + a.m_size, b.m_data, b.m_data + b.m_size); +} + +inline bool operator>(const BinaryData& a, const BinaryData& b) noexcept +{ + return b < a; +} + +inline bool operator<=(const BinaryData& a, const BinaryData& b) noexcept +{ + return !(b < a); +} + +inline bool operator>=(const BinaryData& a, const BinaryData& b) noexcept +{ + return !(a < b); +} + +inline bool BinaryData::begins_with(BinaryData d) const noexcept +{ + if (is_null() && !d.is_null()) + return false; + + return d.m_size <= m_size && safe_equal(m_data, m_data + d.m_size, d.m_data); +} + +inline bool BinaryData::ends_with(BinaryData d) const noexcept +{ + if (is_null() && !d.is_null()) + return false; + + return d.m_size <= m_size && safe_equal(m_data + m_size - d.m_size, m_data + m_size, d.m_data); +} + +inline bool BinaryData::contains(BinaryData d) const noexcept +{ + if (is_null() && !d.is_null()) + return false; + + return d.m_size == 0 || std::search(m_data, m_data + m_size, d.m_data, d.m_data + d.m_size) != m_data + m_size; +} + +template +inline std::basic_ostream& operator<<(std::basic_ostream& out, const BinaryData& d) +{ + out << "BinaryData(" << static_cast(d.m_data) << ", " << d.m_size << ")"; + return out; +} + +inline BinaryData::operator bool() const noexcept +{ + return !is_null(); +} + +} // namespace realm + +#endif // REALM_BINARY_DATA_HPP diff --git a/!main project/Pods/Realm/include/core/realm/bptree.hpp b/!main project/Pods/Realm/include/core/realm/bptree.hpp new file mode 100644 index 0000000..aae64de --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/bptree.hpp @@ -0,0 +1,1271 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_BPTREE_HPP +#define REALM_BPTREE_HPP + +#include // std::unique_ptr +#include +#include +#include +#include +#include + +namespace realm { + +/// Specialize BpTree to implement column types. +template +class BpTree; + +class ArrayInteger; +class ArrayIntNull; + +class BpTreeNode : public Array { +public: + using Array::Array; + /// Get the number of elements in the B+-tree rooted at this array + /// node. The root must not be a leaf. + /// + /// Please avoid using this function (consider it deprecated). It + /// will have to be removed if we choose to get rid of the last + /// element of the main array of an inner B+-tree node that stores + /// the total number of elements in the subtree. The motivation + /// for removing it, is that it will significantly improve the + /// efficiency when inserting after, and erasing the last element. + size_t get_bptree_size() const noexcept; + + /// The root must not be a leaf. + static size_t get_bptree_size_from_header(const char* root_header) noexcept; + + + /// Find the leaf node corresponding to the specified element + /// index index. The specified element index must refer to an + /// element that exists in the tree. This function must be called + /// on an inner B+-tree node, never a leaf. Note that according to + /// invar:bptree-nonempty-inner and invar:bptree-nonempty-leaf, an + /// inner B+-tree node can never be empty. + /// + /// This function is not obliged to instantiate intermediate array + /// accessors. For this reason, this function cannot be used for + /// operations that modify the tree, as that requires an unbroken + /// chain of parent array accessors between the root and the + /// leaf. Thus, despite the fact that the returned MemRef object + /// appears to allow modification of the referenced memory, the + /// caller must handle the memory reference as if it was + /// const-qualified. + /// + /// \return (`leaf_header`, `ndx_in_leaf`) where `leaf_header` + /// points to the the header of the located leaf, and + /// `ndx_in_leaf` is the local index within that leaf + /// corresponding to the specified element index. + std::pair get_bptree_leaf(size_t elem_ndx) const noexcept; + + + class NodeInfo; + class VisitHandler; + + /// Visit leaves of the B+-tree rooted at this inner node, + /// starting with the leaf that contains the element at the + /// specified element index start offset, and ending when the + /// handler returns false. The specified element index offset must + /// refer to an element that exists in the tree. This function + /// must be called on an inner B+-tree node, never a leaf. Note + /// that according to invar:bptree-nonempty-inner and + /// invar:bptree-nonempty-leaf, an inner B+-tree node can never be + /// empty. + /// + /// \param elem_ndx_offset The start position (must be valid). + /// + /// \param elems_in_tree The total number of elements in the tree. + /// + /// \param handler The callback which will get called for each leaf. + /// + /// \return True if, and only if the handler has returned true for + /// all visited leafs. + bool visit_bptree_leaves(size_t elem_ndx_offset, size_t elems_in_tree, VisitHandler& handler); + + + class UpdateHandler; + + /// Call the handler for every leaf. This function must be called + /// on an inner B+-tree node, never a leaf. + void update_bptree_leaves(UpdateHandler&); + + /// Call the handler for the leaf that contains the element at the + /// specified index. This function must be called on an inner + /// B+-tree node, never a leaf. + void update_bptree_elem(size_t elem_ndx, UpdateHandler&); + + + class EraseHandler; + + /// Erase the element at the specified index in the B+-tree with + /// the specified root. When erasing the last element, you must + /// pass npos in place of the index. This function must be called + /// with a root that is an inner B+-tree node, never a leaf. + /// + /// This function is guaranteed to succeed (not throw) if the + /// specified element was inserted during the current transaction, + /// and no other modifying operation has been carried out since + /// then (noexcept:bptree-erase-alt). + /// + /// FIXME: ExceptionSafety: The exception guarantee explained + /// above is not as powerfull as we would like it to be. Here is + /// what we would like: This function is guaranteed to succeed + /// (not throw) if the specified element was inserted during the + /// current transaction (noexcept:bptree-erase). This must be true + /// even if the element is modified after insertion, and/or if + /// other elements are inserted or erased around it. There are two + /// aspects of the current design that stand in the way of this + /// guarantee: (A) The fact that the node accessor, that is cached + /// in the column accessor, has to be reallocated/reinstantiated + /// when the root switches between being a leaf and an inner + /// node. This problem would go away if we always cached the last + /// used leaf accessor in the column accessor instead. (B) The + /// fact that replacing one child ref with another can fail, + /// because it may require reallocation of memory to expand the + /// bit-width. This can be fixed in two ways: Either have the + /// inner B+-tree nodes always have a bit-width of 64, or allow + /// the root node to be discarded and the column ref to be set to + /// zero in Table::m_columns. + static void erase_bptree_elem(BpTreeNode* root, size_t elem_ndx, EraseHandler&); + + template + struct TreeInsert : TreeInsertBase { + typename TreeTraits::value_type m_value; + bool m_nullable; + }; + + /// Same as bptree_insert() but insert after the last element. + template + ref_type bptree_append(TreeInsert& state); + + /// Insert an element into the B+-subtree rooted at this array + /// node. The element is inserted before the specified element + /// index. This function must be called on an inner B+-tree node, + /// never a leaf. If this inner node had to be split, this + /// function returns the `ref` of the new sibling. + template + ref_type bptree_insert(size_t elem_ndx, TreeInsert& state); + +protected: + /// Insert a new child after original. If the parent has to be + /// split, this function returns the `ref` of the new parent node. + ref_type insert_bptree_child(Array& offsets, size_t orig_child_ndx, ref_type new_sibling_ref, + TreeInsertBase& state); + + void ensure_bptree_offsets(Array& offsets); + void create_bptree_offsets(Array& offsets, int_fast64_t first_value); + + bool do_erase_bptree_elem(size_t elem_ndx, EraseHandler&); +}; + +class BpTreeBase { +public: + struct unattached_tag { + }; + + // Disable copying, this is not allowed. + BpTreeBase& operator=(const BpTreeBase&) = delete; + BpTreeBase(const BpTreeBase&) = delete; + + // Accessor concept: + Allocator& get_alloc() const noexcept; + void destroy() noexcept; + void detach(); + bool is_attached() const noexcept; + void set_parent(ArrayParent* parent, size_t ndx_in_parent) noexcept; + size_t get_ndx_in_parent() const noexcept; + void set_ndx_in_parent(size_t ndx) noexcept; + void update_from_parent(size_t old_baseline) noexcept; + MemRef clone_deep(Allocator& alloc) const; + + // BpTree interface: + const Array& root() const noexcept; + Array& root() noexcept; + bool root_is_leaf() const noexcept; + BpTreeNode& root_as_node(); + const BpTreeNode& root_as_node() const; + void introduce_new_root(ref_type new_sibling_ref, TreeInsertBase& state, bool is_append); + void replace_root(std::unique_ptr leaf); + +protected: + explicit BpTreeBase(std::unique_ptr root); + explicit BpTreeBase(BpTreeBase&&) = default; + BpTreeBase& operator=(BpTreeBase&&) = default; + std::unique_ptr m_root; + + struct SliceHandler { + virtual MemRef slice_leaf(MemRef leaf_mem, size_t offset, size_t size, Allocator& target_alloc) = 0; + ~SliceHandler() noexcept + { + } + }; + static ref_type write_subtree(const BpTreeNode& root, size_t slice_offset, size_t slice_size, size_t table_size, + SliceHandler&, _impl::OutputStream&); + friend class ColumnBase; + friend class ColumnBaseSimple; + +private: + struct WriteSliceHandler; + + // FIXME: Move B+Tree functionality from Array to this class. +}; + + +// Default implementation of BpTree. This should work for all types that have monomorphic +// leaves (i.e. all leaves are of the same type). +template +class BpTree : public BpTreeBase { +public: + using value_type = T; + using LeafType = typename ColumnTypeTraits::leaf_type; + + /// LeafInfo is used by get_leaf() to provide access to a leaf + /// without instantiating unnecessary nodes along the way. + /// Upon return, out_leaf with hold a pointer to the leaf containing + /// the index given to get_leaf(). If the index happens to be + /// in the root node (i.e., the root is a leaf), it will point + /// to the root node. + /// If the index isn't in the root node, fallback will be initialized + /// to represent the leaf holding the node, and out_leaf will be set + /// to point to fallback. + struct LeafInfo { + const LeafType** out_leaf; + LeafType* fallback; + }; + + BpTree(); + explicit BpTree(BpTreeBase::unattached_tag); + explicit BpTree(Allocator& alloc); + REALM_DEPRECATED("Initialize with MemRef instead") + explicit BpTree(std::unique_ptr init_root) + : BpTreeBase(std::move(init_root)) + { + } + explicit BpTree(Allocator& alloc, MemRef mem) + : BpTreeBase(std::unique_ptr(new LeafType(alloc))) + { + init_from_mem(alloc, mem); + } + BpTree(BpTree&&) = default; + BpTree& operator=(BpTree&&) = default; + + // Disable copying, this is not allowed. + BpTree& operator=(const BpTree&) = delete; + BpTree(const BpTree&) = delete; + + void init_from_ref(Allocator& alloc, ref_type ref); + void init_from_mem(Allocator& alloc, MemRef mem); + void init_from_parent(); + + size_t size() const noexcept; + bool is_empty() const noexcept + { + return size() == 0; + } + + T get(size_t ndx) const noexcept; + bool is_null(size_t ndx) const noexcept; + void set(size_t, T value); + void set_null(size_t); + void insert(size_t ndx, T value, size_t num_rows = 1); + void erase(size_t ndx, bool is_last = false); + void move_last_over(size_t ndx, size_t last_row_ndx); + void clear(); + T front() const noexcept; + T back() const noexcept; + + size_t find_first(T value, size_t begin = 0, size_t end = npos) const; + void find_all(IntegerColumn& out_indices, T value, size_t begin = 0, size_t end = npos) const; + + static MemRef create_leaf(Array::Type leaf_type, size_t size, T value, Allocator&); + + /// See LeafInfo for information about what to put in the inout_leaf + /// parameter. + /// + /// This function cannot be used for modifying operations as it + /// does not ensure the presence of an unbroken chain of parent + /// accessors. For this reason, the identified leaf should always + /// be accessed through the returned const-qualified reference, + /// and never directly through the specfied fallback accessor. + void get_leaf(size_t ndx, size_t& out_ndx_in_leaf, LeafInfo& inout_leaf) const noexcept; + + void update_each(BpTreeNode::UpdateHandler&); + void update_elem(size_t, BpTreeNode::UpdateHandler&); + + void adjust(size_t ndx, T diff); + void adjust(T diff); + void adjust_ge(T limit, T diff); + + ref_type write(size_t slice_offset, size_t slice_size, size_t table_size, _impl::OutputStream& out) const; + +#if defined(REALM_DEBUG) + void verify() const; + static size_t verify_leaf(MemRef mem, Allocator& alloc); +#endif + static void leaf_to_dot(MemRef mem, ArrayParent* parent, size_t ndx_in_parent, std::ostream& out, + Allocator& alloc); + +private: + LeafType& root_as_leaf(); + const LeafType& root_as_leaf() const; + + std::unique_ptr create_root_from_ref(Allocator& alloc, ref_type ref); + std::unique_ptr create_root_from_mem(Allocator& alloc, MemRef mem); + + struct EraseHandler; + struct UpdateHandler; + struct SetNullHandler; + struct SliceHandler; + struct AdjustHandler; + struct AdjustGEHandler; + + struct LeafValueInserter; + struct LeafNullInserter; + + template + void bptree_insert(size_t row_ndx, BpTreeNode::TreeInsert& state, size_t num_rows); +}; + + +class BpTreeNode::NodeInfo { +public: + MemRef m_mem; + Array* m_parent; + size_t m_ndx_in_parent; + size_t m_offset, m_size; +}; + +class BpTreeNode::VisitHandler { +public: + virtual bool visit(const NodeInfo& leaf_info) = 0; + virtual ~VisitHandler() noexcept + { + } +}; + + +class BpTreeNode::UpdateHandler { +public: + virtual void update(MemRef, ArrayParent*, size_t leaf_ndx_in_parent, size_t elem_ndx_in_leaf) = 0; + virtual ~UpdateHandler() noexcept + { + } +}; + + +class BpTreeNode::EraseHandler { +public: + /// If the specified leaf has more than one element, this function + /// must erase the specified element from the leaf and return + /// false. Otherwise, when the leaf has a single element, this + /// function must return true without modifying the leaf. If \a + /// elem_ndx_in_leaf is `npos`, it refers to the last element in + /// the leaf. The implementation of this function must be + /// exception safe. This function is guaranteed to be called at + /// most once during each execution of Array::erase_bptree_elem(), + /// and *exactly* once during each *successful* execution of + /// Array::erase_bptree_elem(). + virtual bool erase_leaf_elem(MemRef, ArrayParent*, size_t leaf_ndx_in_parent, size_t elem_ndx_in_leaf) = 0; + + virtual void destroy_leaf(MemRef leaf_mem) noexcept = 0; + + /// Must replace the current root with the specified leaf. The + /// implementation of this function must not destroy the + /// underlying root node, or any of its children, as that will be + /// done by Array::erase_bptree_elem(). The implementation of this + /// function must be exception safe. + virtual void replace_root_by_leaf(MemRef leaf_mem) = 0; + + /// Same as replace_root_by_leaf(), but must replace the root with + /// an empty leaf. Also, if this function is called during an + /// execution of Array::erase_bptree_elem(), it is guaranteed that + /// it will be preceeded by a call to erase_leaf_elem(). + virtual void replace_root_by_empty_leaf() = 0; + + virtual ~EraseHandler() noexcept + { + } +}; + + +/// Implementation: + +inline BpTreeBase::BpTreeBase(std::unique_ptr init_root) + : m_root(std::move(init_root)) +{ +} + +inline Allocator& BpTreeBase::get_alloc() const noexcept +{ + return m_root->get_alloc(); +} + +inline void BpTreeBase::destroy() noexcept +{ + if (m_root) + m_root->destroy_deep(); +} + +inline void BpTreeBase::detach() +{ + m_root->detach(); +} + +inline bool BpTreeBase::is_attached() const noexcept +{ + return m_root->is_attached(); +} + +inline bool BpTreeBase::root_is_leaf() const noexcept +{ + return !m_root->is_inner_bptree_node(); +} + +inline BpTreeNode& BpTreeBase::root_as_node() +{ + REALM_ASSERT_DEBUG(!root_is_leaf()); + REALM_ASSERT_DEBUG(dynamic_cast(m_root.get()) != nullptr); + return static_cast(root()); +} + +inline const BpTreeNode& BpTreeBase::root_as_node() const +{ + Array* arr = m_root.get(); + REALM_ASSERT_DEBUG(!root_is_leaf()); + REALM_ASSERT_DEBUG(dynamic_cast(arr) != nullptr); + return static_cast(*arr); +} + +inline void BpTreeBase::set_parent(ArrayParent* parent, size_t ndx_in_parent) noexcept +{ + m_root->set_parent(parent, ndx_in_parent); +} + +inline size_t BpTreeBase::get_ndx_in_parent() const noexcept +{ + return m_root->get_ndx_in_parent(); +} + +inline void BpTreeBase::set_ndx_in_parent(size_t ndx) noexcept +{ + m_root->set_ndx_in_parent(ndx); +} + +inline void BpTreeBase::update_from_parent(size_t old_baseline) noexcept +{ + m_root->update_from_parent(old_baseline); +} + +inline MemRef BpTreeBase::clone_deep(Allocator& alloc) const +{ + return m_root->clone_deep(alloc); +} + +inline const Array& BpTreeBase::root() const noexcept +{ + return *m_root; +} + +inline Array& BpTreeBase::root() noexcept +{ + return *m_root; +} + +inline size_t BpTreeNode::get_bptree_size() const noexcept +{ + REALM_ASSERT_DEBUG(is_inner_bptree_node()); + int_fast64_t v = back(); + return size_t(v / 2); // v = 1 + 2*total_elems_in_tree +} + +inline size_t BpTreeNode::get_bptree_size_from_header(const char* root_header) noexcept +{ + REALM_ASSERT_DEBUG(get_is_inner_bptree_node_from_header(root_header)); + size_t root_size = get_size_from_header(root_header); + int_fast64_t v = get(root_header, root_size - 1); + return size_t(v / 2); // v = 1 + 2*total_elems_in_tree +} + +inline void BpTreeNode::ensure_bptree_offsets(Array& offsets) +{ + int_fast64_t first_value = get(0); + if (first_value % 2 == 0) { + offsets.init_from_ref(to_ref(first_value)); + } + else { + create_bptree_offsets(offsets, first_value); // Throws + } + offsets.set_parent(this, 0); +} + + +template +ref_type BpTreeNode::bptree_append(TreeInsert& state) +{ + // FIXME: Consider exception safety. Especially, how can the split + // be carried out in an exception safe manner? + // + // Can split be done as a separate preparation step, such that if + // the actual insert fails, the split will still have occured. + // + // Unfortunately, it requires a rather significant rearrangement + // of the insertion flow. Instead of returning the sibling ref + // from insert functions, the leaf-insert functions must instead + // call the special bptree_insert() function on the parent, which + // will then cascade the split towards the root as required. + // + // At each level where a split is required (starting at the leaf): + // + // 1. Create the new sibling. + // + // 2. Copy relevant entries over such that new sibling is in + // its final state. + // + // 3. Call Array::bptree_insert() on parent with sibling ref. + // + // 4. Rearrange entries in original sibling and truncate as + // required (must not throw). + // + // What about the 'offsets' array? It will always be + // present. Consider this carefully. + + REALM_ASSERT_DEBUG(size() >= 1 + 1 + 1); // At least one child + + ArrayParent& childs_parent = *this; + size_t child_ref_ndx = size() - 2; + ref_type child_ref = get_as_ref(child_ref_ndx), new_sibling_ref; + char* child_header = static_cast(m_alloc.translate(child_ref)); + + bool child_is_leaf = !get_is_inner_bptree_node_from_header(child_header); + if (child_is_leaf) { + size_t elem_ndx_in_child = npos; // Append + new_sibling_ref = TreeTraits::leaf_insert(MemRef(child_header, child_ref, m_alloc), childs_parent, + child_ref_ndx, m_alloc, elem_ndx_in_child, state); // Throws + } + else { + BpTreeNode child(m_alloc); + child.init_from_mem(MemRef(child_header, child_ref, m_alloc)); + child.set_parent(&childs_parent, child_ref_ndx); + new_sibling_ref = child.bptree_append(state); // Throws + } + + if (REALM_LIKELY(!new_sibling_ref)) { + // +2 because stored value is 1 + 2*total_elems_in_subtree + adjust(size() - 1, +2); // Throws + return 0; // Child was not split, so parent was not split either + } + + Array offsets(m_alloc); + int_fast64_t first_value = get(0); + if (first_value % 2 == 0) { + // Offsets array is present (general form) + offsets.init_from_ref(to_ref(first_value)); + offsets.set_parent(this, 0); + } + size_t child_ndx = child_ref_ndx - 1; + return insert_bptree_child(offsets, child_ndx, new_sibling_ref, state); // Throws +} + + +template +ref_type BpTreeNode::bptree_insert(size_t elem_ndx, TreeInsert& state) +{ + REALM_ASSERT_3(size(), >=, 1 + 1 + 1); // At least one child + + // Conversion to general form if in compact form. Since this + // conversion will occur from root to leaf, it will maintain + // invar:bptree-node-form. + Array offsets(m_alloc); + ensure_bptree_offsets(offsets); // Throws + + size_t child_ndx, elem_ndx_in_child; + if (elem_ndx == 0) { + // Optimization for prepend + child_ndx = 0; + elem_ndx_in_child = 0; + } + else { + // There is a choice to be made when the element is to be + // inserted between two subtrees. It can either be appended to + // the first subtree, or it can be prepended to the second + // one. We currently always append to the first subtree. It is + // essentially a matter of using the lower vs. the upper bound + // when searching through the offsets array. + child_ndx = offsets.lower_bound_int(elem_ndx); + REALM_ASSERT_3(child_ndx, <, size() - 2); + size_t elem_ndx_offset = child_ndx == 0 ? 0 : to_size_t(offsets.get(child_ndx - 1)); + elem_ndx_in_child = elem_ndx - elem_ndx_offset; + } + + ArrayParent& childs_parent = *this; + size_t child_ref_ndx = child_ndx + 1; + ref_type child_ref = get_as_ref(child_ref_ndx), new_sibling_ref; + char* child_header = static_cast(m_alloc.translate(child_ref)); + bool child_is_leaf = !get_is_inner_bptree_node_from_header(child_header); + if (child_is_leaf) { + REALM_ASSERT_3(elem_ndx_in_child, <=, REALM_MAX_BPNODE_SIZE); + new_sibling_ref = TreeTraits::leaf_insert(MemRef(child_header, child_ref, m_alloc), childs_parent, + child_ref_ndx, m_alloc, elem_ndx_in_child, state); // Throws + } + else { + BpTreeNode child(m_alloc); + child.init_from_mem(MemRef(child_header, child_ref, m_alloc)); + child.set_parent(&childs_parent, child_ref_ndx); + new_sibling_ref = child.bptree_insert(elem_ndx_in_child, state); // Throws + } + + if (REALM_LIKELY(!new_sibling_ref)) { + // +2 because stored value is 1 + 2*total_elems_in_subtree + adjust(size() - 1, +2); // Throws + offsets.adjust(child_ndx, offsets.size(), +1); + return 0; // Child was not split, so parent was not split either + } + + return insert_bptree_child(offsets, child_ndx, new_sibling_ref, state); // Throws +} + +template +BpTree::BpTree() + : BpTree(Allocator::get_default()) +{ +} + +template +BpTree::BpTree(Allocator& alloc) + : BpTreeBase(std::unique_ptr(new LeafType(alloc))) +{ +} + +template +BpTree::BpTree(BpTreeBase::unattached_tag) + : BpTreeBase(nullptr) +{ +} + +template +std::unique_ptr BpTree::create_root_from_mem(Allocator& alloc, MemRef mem) +{ + const char* header = mem.get_addr(); + std::unique_ptr new_root; + bool is_inner_bptree_node = Array::get_is_inner_bptree_node_from_header(header); + + bool can_reuse_root_accessor = + m_root && &m_root->get_alloc() == &alloc && m_root->is_inner_bptree_node() == is_inner_bptree_node; + if (can_reuse_root_accessor) { + if (is_inner_bptree_node) { + m_root->init_from_mem(mem); + } + else { + static_cast(*m_root).init_from_mem(mem); + } + return std::move(m_root); // Same root will be reinstalled. + } + + // Not reusing root note, allocating a new one. + if (is_inner_bptree_node) { + new_root.reset(new BpTreeNode{alloc}); + new_root->init_from_mem(mem); + } + else { + std::unique_ptr leaf{new LeafType{alloc}}; + leaf->init_from_mem(mem); + new_root = std::move(leaf); + } + return new_root; +} + +template +std::unique_ptr BpTree::create_root_from_ref(Allocator& alloc, ref_type ref) +{ + MemRef mem = MemRef{alloc.translate(ref), ref, alloc}; + return create_root_from_mem(alloc, mem); +} + +template +void BpTree::init_from_ref(Allocator& alloc, ref_type ref) +{ + auto new_root = create_root_from_ref(alloc, ref); + replace_root(std::move(new_root)); +} + +template +void BpTree::init_from_mem(Allocator& alloc, MemRef mem) +{ + auto new_root = create_root_from_mem(alloc, mem); + replace_root(std::move(new_root)); +} + +template +void BpTree::init_from_parent() +{ + ref_type ref = root().get_ref_from_parent(); + if (ref) { + ArrayParent* parent = m_root->get_parent(); + size_t ndx_in_parent = m_root->get_ndx_in_parent(); + auto new_root = create_root_from_ref(get_alloc(), ref); + new_root->set_parent(parent, ndx_in_parent); + m_root = std::move(new_root); + } + else { + m_root->detach(); + } +} + +template +typename BpTree::LeafType& BpTree::root_as_leaf() +{ + REALM_ASSERT_DEBUG(root_is_leaf()); + REALM_ASSERT_DEBUG(dynamic_cast(m_root.get()) != nullptr); + return static_cast(root()); +} + +template +const typename BpTree::LeafType& BpTree::root_as_leaf() const +{ + REALM_ASSERT_DEBUG(root_is_leaf()); + REALM_ASSERT_DEBUG(dynamic_cast(m_root.get()) != nullptr); + return static_cast(root()); +} + +template +size_t BpTree::size() const noexcept +{ + if (root_is_leaf()) { + return root_as_leaf().size(); + } + return root_as_node().get_bptree_size(); +} + +template +T BpTree::back() const noexcept +{ + // FIXME: slow + return get(size() - 1); +} + +namespace _impl { + +// NullableOrNothing encapsulates the behavior of nullable and +// non-nullable leaf types, so that non-nullable leaf types +// don't have to implement is_null/set_null but BpTree can still +// support the interface (and return false / assert when null +// is not supported). +template +struct NullableOrNothing { + static bool is_null(const Leaf& leaf, size_t ndx) + { + return leaf.is_null(ndx); + } + static void set_null(Leaf& leaf, size_t ndx) + { + leaf.set_null(ndx); + } +}; +template <> +struct NullableOrNothing { + static bool is_null(const ArrayInteger&, size_t) + { + return false; + } + static void set_null(ArrayInteger&, size_t) + { + REALM_ASSERT_RELEASE(false); + } +}; +} + +template +bool BpTree::is_null(size_t ndx) const noexcept +{ + if (root_is_leaf()) { + return _impl::NullableOrNothing::is_null(root_as_leaf(), ndx); + } + LeafType fallback(get_alloc()); + const LeafType* leaf; + LeafInfo leaf_info{&leaf, &fallback}; + size_t ndx_in_leaf; + get_leaf(ndx, ndx_in_leaf, leaf_info); + return _impl::NullableOrNothing::is_null(*leaf, ndx_in_leaf); +} + +template +T BpTree::get(size_t ndx) const noexcept +{ + REALM_ASSERT_DEBUG_EX(ndx < size(), ndx, size()); + if (root_is_leaf()) { + return root_as_leaf().get(ndx); + } + + // Use direct getter to avoid initializing leaf array: + std::pair p = root_as_node().get_bptree_leaf(ndx); + const char* leaf_header = p.first.get_addr(); + size_t ndx_in_leaf = p.second; + return LeafType::get(leaf_header, ndx_in_leaf); +} + +template +template +void BpTree::bptree_insert(size_t row_ndx, BpTreeNode::TreeInsert& state, size_t num_rows) +{ + ref_type new_sibling_ref; + for (size_t i = 0; i < num_rows; ++i) { + size_t row_ndx_2 = row_ndx == realm::npos ? realm::npos : row_ndx + i; + if (root_is_leaf()) { + REALM_ASSERT_DEBUG(row_ndx_2 == realm::npos || row_ndx_2 < REALM_MAX_BPNODE_SIZE); + new_sibling_ref = root_as_leaf().bptree_leaf_insert(row_ndx_2, state.m_value, state); + } + else { + if (row_ndx_2 == realm::npos) { + new_sibling_ref = root_as_node().bptree_append(state); // Throws + } + else { + new_sibling_ref = root_as_node().bptree_insert(row_ndx_2, state); // Throws + } + } + + if (REALM_UNLIKELY(new_sibling_ref)) { + bool is_append = row_ndx_2 == realm::npos; + introduce_new_root(new_sibling_ref, state, is_append); + } + } +} + +template +struct BpTree::LeafValueInserter { + using value_type = T; + T m_value; + LeafValueInserter(T value) + : m_value(std::move(value)) + { + } + + // TreeTraits concept: + static ref_type leaf_insert(MemRef leaf_mem, ArrayParent& parent, size_t ndx_in_parent, Allocator& alloc, + size_t ndx_in_leaf, BpTreeNode::TreeInsert& state) + { + LeafType leaf{alloc}; + leaf.init_from_mem(leaf_mem); + leaf.set_parent(&parent, ndx_in_parent); + // Should not move out of m_value, because the same inserter may be used to perform + // multiple insertions (for example, if num_rows > 1). + return leaf.bptree_leaf_insert(ndx_in_leaf, state.m_value, state); + } +}; + +template +struct BpTree::LeafNullInserter { + using value_type = null; + // TreeTraits concept: + static ref_type leaf_insert(MemRef leaf_mem, ArrayParent& parent, size_t ndx_in_parent, Allocator& alloc, + size_t ndx_in_leaf, BpTreeNode::TreeInsert& state) + { + LeafType leaf{alloc}; + leaf.init_from_mem(leaf_mem); + leaf.set_parent(&parent, ndx_in_parent); + return leaf.bptree_leaf_insert(ndx_in_leaf, null{}, state); + } +}; + +template +void BpTree::insert(size_t row_ndx, T value, size_t num_rows) +{ + REALM_ASSERT_DEBUG(row_ndx == npos || row_ndx < size()); + BpTreeNode::TreeInsert inserter; + inserter.m_value = std::move(value); + inserter.m_nullable = std::is_same>::value; // FIXME + bptree_insert(row_ndx, inserter, num_rows); // Throws +} + +template +struct BpTree::UpdateHandler : BpTreeNode::UpdateHandler { + LeafType m_leaf; + const T m_value; + UpdateHandler(BpTreeBase& tree, T value) noexcept + : m_leaf(tree.get_alloc()) + , m_value(std::move(value)) + { + } + void update(MemRef mem, ArrayParent* parent, size_t ndx_in_parent, size_t elem_ndx_in_leaf) override + { + m_leaf.init_from_mem(mem); + m_leaf.set_parent(parent, ndx_in_parent); + m_leaf.set(elem_ndx_in_leaf, m_value); // Throws + } +}; + +template +struct BpTree::SetNullHandler : BpTreeNode::UpdateHandler { + LeafType m_leaf; + SetNullHandler(BpTreeBase& tree) noexcept + : m_leaf(tree.get_alloc()) + { + } + void update(MemRef mem, ArrayParent* parent, size_t ndx_in_parent, size_t elem_ndx_in_leaf) override + { + m_leaf.init_from_mem(mem); + m_leaf.set_parent(parent, ndx_in_parent); + _impl::NullableOrNothing::set_null(m_leaf, elem_ndx_in_leaf); // Throws + } +}; + +template +void BpTree::set(size_t ndx, T value) +{ + if (root_is_leaf()) { + root_as_leaf().set(ndx, std::move(value)); + } + else { + UpdateHandler set_leaf_elem(*this, std::move(value)); + static_cast(m_root.get())->update_bptree_elem(ndx, set_leaf_elem); // Throws + } +} + +template +void BpTree::set_null(size_t ndx) +{ + if (root_is_leaf()) { + _impl::NullableOrNothing::set_null(root_as_leaf(), ndx); + } + else { + SetNullHandler set_leaf_elem(*this); + static_cast(m_root.get())->update_bptree_elem(ndx, set_leaf_elem); // Throws; + } +} + +template +struct BpTree::EraseHandler : BpTreeNode::EraseHandler { + BpTreeBase& m_tree; + LeafType m_leaf; + bool m_leaves_have_refs; // FIXME: Should be able to eliminate this. + EraseHandler(BpTreeBase& tree) noexcept + : m_tree(tree) + , m_leaf(tree.get_alloc()) + , m_leaves_have_refs(false) + { + } + bool erase_leaf_elem(MemRef leaf_mem, ArrayParent* parent, size_t leaf_ndx_in_parent, + size_t elem_ndx_in_leaf) override + { + m_leaf.init_from_mem(leaf_mem); + REALM_ASSERT_3(m_leaf.size(), >=, 1); + size_t last_ndx = m_leaf.size() - 1; + if (last_ndx == 0) { + m_leaves_have_refs = m_leaf.has_refs(); + return true; + } + m_leaf.set_parent(parent, leaf_ndx_in_parent); + size_t ndx = elem_ndx_in_leaf; + if (ndx == npos) + ndx = last_ndx; + m_leaf.erase(ndx); // Throws + return false; + } + void destroy_leaf(MemRef leaf_mem) noexcept override + { + // FIXME: Seems like this would cause file space leaks if + // m_leaves_have_refs is true, but consider carefully how + // m_leaves_have_refs get its value. + m_tree.get_alloc().free_(leaf_mem); + } + void replace_root_by_leaf(MemRef leaf_mem) override + { + std::unique_ptr leaf{new LeafType(m_tree.get_alloc())}; // Throws + leaf->init_from_mem(leaf_mem); + m_tree.replace_root(std::move(leaf)); // Throws + } + void replace_root_by_empty_leaf() override + { + std::unique_ptr leaf{new LeafType(m_tree.get_alloc())}; // Throws + leaf->create(m_leaves_have_refs ? Array::type_HasRefs : Array::type_Normal); // Throws + m_tree.replace_root(std::move(leaf)); // Throws + } +}; + +template +void BpTree::erase(size_t ndx, bool is_last) +{ + REALM_ASSERT_DEBUG_EX(ndx < size(), ndx, size()); + REALM_ASSERT_DEBUG(is_last == (ndx == size() - 1)); + if (root_is_leaf()) { + root_as_leaf().erase(ndx); + } + else { + size_t ndx_2 = is_last ? npos : ndx; + EraseHandler handler(*this); + BpTreeNode::erase_bptree_elem(&root_as_node(), ndx_2, handler); + } +} + +template +void BpTree::move_last_over(size_t row_ndx, size_t last_row_ndx) +{ + // Copy value from last row over + T value = get(last_row_ndx); + set(row_ndx, value); + erase(last_row_ndx, true); +} + +template +void BpTree::clear() +{ + if (root_is_leaf()) { + if (std::is_same::value && root().get_type() == Array::type_HasRefs) { + // FIXME: This is because some column types rely on integer columns + // to contain refs. + root().clear_and_destroy_children(); + } + else { + root_as_leaf().clear(); + } + } + else { + Allocator& alloc = get_alloc(); + root().destroy_deep(); + + std::unique_ptr new_root(new LeafType(alloc)); + new_root->create(); + replace_root(std::move(new_root)); + } +} + + +template +struct BpTree::AdjustHandler : BpTreeNode::UpdateHandler { + LeafType m_leaf; + const T m_diff; + AdjustHandler(BpTreeBase& tree, T diff) + : m_leaf(tree.get_alloc()) + , m_diff(diff) + { + } + + void update(MemRef mem, ArrayParent* parent, size_t ndx_in_parent, size_t) final + { + m_leaf.init_from_mem(mem); + m_leaf.set_parent(parent, ndx_in_parent); + m_leaf.adjust(0, m_leaf.size(), m_diff); + } +}; + +template +void BpTree::adjust(T diff) +{ + if (root_is_leaf()) { + root_as_leaf().adjust(0, m_root->size(), std::move(diff)); // Throws + } + else { + AdjustHandler adjust_leaf_elem(*this, std::move(diff)); + root_as_node().update_bptree_leaves(adjust_leaf_elem); // Throws + } +} + +template +void BpTree::adjust(size_t ndx, T diff) +{ + static_assert(std::is_arithmetic::value, "adjust is undefined for non-arithmetic trees"); + set(ndx, get(ndx) + diff); +} + +template +struct BpTree::AdjustGEHandler : BpTreeNode::UpdateHandler { + LeafType m_leaf; + const T m_limit, m_diff; + + AdjustGEHandler(BpTreeBase& tree, T limit, T diff) + : m_leaf(tree.get_alloc()) + , m_limit(limit) + , m_diff(diff) + { + } + + void update(MemRef mem, ArrayParent* parent, size_t ndx_in_parent, size_t) final + { + m_leaf.init_from_mem(mem); + m_leaf.set_parent(parent, ndx_in_parent); + m_leaf.adjust_ge(m_limit, m_diff); + } +}; + +template +void BpTree::adjust_ge(T limit, T diff) +{ + if (root_is_leaf()) { + root_as_leaf().adjust_ge(std::move(limit), std::move(diff)); // Throws + } + else { + AdjustGEHandler adjust_leaf_elem(*this, std::move(limit), std::move(diff)); + root_as_node().update_bptree_leaves(adjust_leaf_elem); // Throws + } +} + +template +struct BpTree::SliceHandler : public BpTreeBase::SliceHandler { +public: + SliceHandler(Allocator& alloc) + : m_leaf(alloc) + { + } + MemRef slice_leaf(MemRef leaf_mem, size_t offset, size_t size, Allocator& target_alloc) override + { + m_leaf.init_from_mem(leaf_mem); + return m_leaf.slice_and_clone_children(offset, size, target_alloc); // Throws + } + +private: + LeafType m_leaf; +}; + +template +ref_type BpTree::write(size_t slice_offset, size_t slice_size, size_t table_size, _impl::OutputStream& out) const +{ + ref_type ref; + if (root_is_leaf()) { + Allocator& alloc = Allocator::get_default(); + MemRef mem = root_as_leaf().slice_and_clone_children(slice_offset, slice_size, alloc); // Throws + Array slice(alloc); + _impl::DeepArrayDestroyGuard dg(&slice); + slice.init_from_mem(mem); + bool deep = true; + bool only_when_modified = false; + ref = slice.write(out, deep, only_when_modified); // Throws + } + else { + SliceHandler handler(get_alloc()); + ref = write_subtree(root_as_node(), slice_offset, slice_size, table_size, handler, out); // Throws + } + return ref; +} + +template +MemRef BpTree::create_leaf(Array::Type leaf_type, size_t size, T value, Allocator& alloc) +{ + bool context_flag = false; + MemRef mem = LeafType::create_array(leaf_type, context_flag, size, std::move(value), alloc); + return mem; +} + +template +void BpTree::get_leaf(size_t ndx, size_t& ndx_in_leaf, LeafInfo& inout_leaf_info) const noexcept +{ + if (root_is_leaf()) { + ndx_in_leaf = ndx; + *inout_leaf_info.out_leaf = &root_as_leaf(); + return; + } + std::pair p = root_as_node().get_bptree_leaf(ndx); + inout_leaf_info.fallback->init_from_mem(p.first); + ndx_in_leaf = p.second; + *inout_leaf_info.out_leaf = inout_leaf_info.fallback; +} + +template +size_t BpTree::find_first(T value, size_t begin, size_t end) const +{ + if (root_is_leaf()) { + return root_as_leaf().find_first(value, begin, end); + } + + // FIXME: It would be better to always require that 'end' is + // specified explicitly, since Table has the size readily + // available, and Array::get_bptree_size() is deprecated. + if (end == npos) + end = size(); + + LeafType leaf_cache(get_alloc()); + size_t ndx_in_tree = begin; + while (ndx_in_tree < end) { + const LeafType* leaf; + LeafInfo leaf_info{&leaf, &leaf_cache}; + size_t ndx_in_leaf; + get_leaf(ndx_in_tree, ndx_in_leaf, leaf_info); + size_t leaf_offset = ndx_in_tree - ndx_in_leaf; + size_t end_in_leaf = std::min(leaf->size(), end - leaf_offset); + size_t ndx = leaf->find_first(value, ndx_in_leaf, end_in_leaf); // Throws (maybe) + if (ndx != not_found) + return leaf_offset + ndx; + ndx_in_tree = leaf_offset + end_in_leaf; + } + + return not_found; +} + +template +void BpTree::find_all(IntegerColumn& result, T value, size_t begin, size_t end) const +{ + if (root_is_leaf()) { + root_as_leaf().find_all(&result, value, 0, begin, end); // Throws + return; + } + + // FIXME: It would be better to always require that 'end' is + // specified explicitely, since Table has the size readily + // available, and Array::get_bptree_size() is deprecated. + if (end == npos) + end = size(); + + LeafType leaf_cache(get_alloc()); + size_t ndx_in_tree = begin; + while (ndx_in_tree < end) { + const LeafType* leaf; + LeafInfo leaf_info{&leaf, &leaf_cache}; + size_t ndx_in_leaf; + get_leaf(ndx_in_tree, ndx_in_leaf, leaf_info); + size_t leaf_offset = ndx_in_tree - ndx_in_leaf; + size_t end_in_leaf = std::min(leaf->size(), end - leaf_offset); + leaf->find_all(&result, value, leaf_offset, ndx_in_leaf, end_in_leaf); // Throws + ndx_in_tree = leaf_offset + end_in_leaf; + } +} + +#if defined(REALM_DEBUG) +template +size_t BpTree::verify_leaf(MemRef mem, Allocator& alloc) +{ + LeafType leaf(alloc); + leaf.init_from_mem(mem); + leaf.verify(); + return leaf.size(); +} + +template +void BpTree::verify() const +{ + if (root_is_leaf()) { + root_as_leaf().verify(); + } + else { + root().verify_bptree(&verify_leaf); + } +} +#endif // REALM_DEBUG + +template +void BpTree::leaf_to_dot(MemRef leaf_mem, ArrayParent* parent, size_t ndx_in_parent, std::ostream& out, + Allocator& alloc) +{ + LeafType leaf(alloc); + leaf.init_from_mem(leaf_mem); + leaf.set_parent(parent, ndx_in_parent); + leaf.to_dot(out); +} + +} // namespace realm + +#endif // REALM_BPTREE_HPP diff --git a/!main project/Pods/Realm/include/core/realm/chunked_binary.hpp b/!main project/Pods/Realm/include/core/realm/chunked_binary.hpp new file mode 100644 index 0000000..1e76fa8 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/chunked_binary.hpp @@ -0,0 +1,125 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2015] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ + +#ifndef REALM_NOINST_CHUNKED_BINARY_HPP +#define REALM_NOINST_CHUNKED_BINARY_HPP + +#include +#include +#include + +#include +#include + + +namespace realm { + +/// ChunkedBinaryData manages a vector of BinaryData. It is used to facilitate +/// extracting large binaries from binary columns and tables. +class ChunkedBinaryData { +public: + + ChunkedBinaryData(); + ChunkedBinaryData(const BinaryData& bd); + ChunkedBinaryData(const BinaryIterator& bd); + ChunkedBinaryData(const BinaryColumn& col, size_t index); + + /// size() returns the number of bytes in the chunked binary. + /// FIXME: This operation is O(n). + size_t size() const noexcept; + + /// is_null returns true if the chunked binary has zero chunks or if + /// the first chunk points to the nullptr. + bool is_null() const; + + /// FIXME: O(n) + char operator[](size_t index) const; + + std::string hex_dump(const char* separator = " ", int min_digits = -1) const; + + void write_to(util::ResettableExpandableBufferOutputStream& out) const; + + /// copy_to() copies the chunked binary data to \a buffer of size + /// \a buffer_size starting at \a offset in the ChunkedBinary. + /// copy_to() copies until the end of \a buffer or the end of + /// the ChunkedBinary whichever comes first. + /// copy_to() returns the number of copied bytes. + size_t copy_to(char* buffer, size_t buffer_size, size_t offset) const; + + /// copy_to() allocates a buffer of size() in \a dest and + /// copies the chunked binary data to \a dest. + size_t copy_to(std::unique_ptr& dest) const; + + /// get_first_chunk() is used in situations + /// where it is known that there is exactly one + /// chunk. This is the case if the ChunkedBinary + /// has been constructed from BinaryData. + BinaryData get_first_chunk() const; + +private: + BinaryIterator m_begin; + friend class ChunkedBinaryInputStream; +}; + +// FIXME: When ChunkedBinaryData is moved into Core, this should be moved as well. +class ChunkedBinaryInputStream : public _impl::NoCopyInputStream { +public: + explicit ChunkedBinaryInputStream(const ChunkedBinaryData& chunks) + : m_it(chunks.m_begin) + { + } + + bool next_block(const char*& begin, const char*& end) override + { + BinaryData block = m_it.get_next(); + begin = block.data(); + end = begin + block.size(); + return begin != end; + } + +private: + BinaryIterator m_it; +}; + + +/// Implementation: + + +inline ChunkedBinaryData::ChunkedBinaryData() +{ +} + +inline ChunkedBinaryData::ChunkedBinaryData(const BinaryData& bd) : m_begin{bd} +{ +} + +inline ChunkedBinaryData::ChunkedBinaryData(const BinaryIterator& bd) : m_begin{bd} +{ +} + +inline ChunkedBinaryData::ChunkedBinaryData(const BinaryColumn& col, size_t index) + : m_begin{&col, index} +{ +} + + +} // namespace realm + +#endif // REALM_NOINST_CHUNKED_BINARY_HPP diff --git a/!main project/Pods/Realm/include/core/realm/column.hpp b/!main project/Pods/Realm/include/core/realm/column.hpp new file mode 100644 index 0000000..eda8ca2 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/column.hpp @@ -0,0 +1,1897 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_COLUMN_HPP +#define REALM_COLUMN_HPP + +#include // unint8_t etc +#include // size_t +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace realm { + + +// Pre-definitions +struct CascadeState; +class StringIndex; + +template +struct ImplicitNull; + +template +struct ImplicitNull> { + static constexpr bool value = true; +}; + +template <> +struct ImplicitNull { + static constexpr bool value = false; +}; + +template <> +struct ImplicitNull { + static constexpr bool value = true; +}; + +template <> +struct ImplicitNull { + static constexpr bool value = true; +}; + +// FIXME: Add specialization for ImplicitNull for float, double, StringData, BinaryData. + +template +R aggregate(const ColType& column, T target, size_t start, size_t end, size_t limit, size_t* return_ndx); + + +// Iterator with random access for Columns +template +class ColumnRandIterator : public std::iterator { +public: + ColumnRandIterator(const Column* src_col, size_t ndx = 0); + bool operator==(const ColumnRandIterator& rhs) const; + bool operator!=(const ColumnRandIterator& rhs) const; + bool operator<(const ColumnRandIterator& rhs) const; + bool operator>(const ColumnRandIterator& rhs) const; + bool operator<=(const ColumnRandIterator& rhs) const; + bool operator>=(const ColumnRandIterator& rhs) const; + ColumnRandIterator& operator+=(ptrdiff_t movement); + ColumnRandIterator& operator-=(ptrdiff_t movement); + ColumnRandIterator& operator++(); + ColumnRandIterator& operator--(); + ColumnRandIterator operator++(int); + ColumnRandIterator operator--(int); + ColumnRandIterator operator+(ptrdiff_t movement); + ColumnRandIterator operator-(ptrdiff_t movement); + ptrdiff_t operator-(const ColumnRandIterator& right) const; + const ColumnDataType operator*() const; + const ColumnDataType operator->() const; + const ColumnDataType operator[](ptrdiff_t offset) const; + size_t get_col_ndx() const; + +protected: + size_t m_col_ndx; + const Column* m_col; +}; + +/// Base class for all column types. +class ColumnBase { +public: + /// Get the number of entries in this column. This operation is relatively + /// slow. + virtual size_t size() const noexcept = 0; + + /// \throw LogicError Thrown if this column is not string valued. + virtual void set_string(size_t row_ndx, StringData value); + + /// Whether or not this column is nullable. + virtual bool is_nullable() const noexcept; + + /// Whether or not the value at \a row_ndx is NULL. If the column is not + /// nullable, always returns false. + virtual bool is_null(size_t row_ndx) const noexcept; + + /// Sets the value at \a row_ndx to be NULL. + /// \throw LogicError Thrown if this column is not nullable. + virtual void set_null(size_t row_ndx); + + /// Inserts the specified number of elements into this column + /// starting at the specified row index. The new elements will have the + /// default value for the column type. + /// + /// \param row_ndx The row to start insertion at. If the row_ndx is less + /// than prior_num_rows then previous rows from row_ndx onwards will be + /// moved ahead by num_rows_to_insert. + /// + /// \param num_rows_to_insert The number of rows to insert. There is no + /// restriction on this value. + /// + /// \param prior_num_rows The number of elements in this column prior to the + /// modification. + /// + /// \param nullable Specifies whether or not this column is nullable. This + /// function may assert if nullable does not agree with \a is_nullable() + virtual void insert_rows(size_t row_ndx, size_t num_rows_to_insert, size_t prior_num_rows, bool nullable) = 0; + + /// Removes the specified number of consecutive elements from + /// this column, starting at the specified row index. + /// + /// \param row_ndx The row to start removal at (inclusive). This must be + /// less than prior_num_rows. + /// + /// \param num_rows_to_erase The number of rows to erase. + /// The row_ndx + num_rows_to_erase must be less than prior_num_rows. + /// + /// \param prior_num_rows The number of elements in this column prior to the + /// modification. + /// + /// \param broken_reciprocal_backlinks If true, link columns must assume + /// that reciprocal backlinks have already been removed. Non-link columns + /// should ignore this argument. + virtual void erase_rows(size_t row_ndx, size_t num_rows_to_erase, size_t prior_num_rows, + bool broken_reciprocal_backlinks) = 0; + + /// Removes the element at the specified row index by + /// moving the element at the last row index over it. This reduces the + /// number of elements by one. + /// + /// \param row_ndx The row to erase. Must be less than prior_num_rows. + /// + /// \param prior_num_rows The number of elements in this column prior to the + /// modification. + /// + /// \param broken_reciprocal_backlinks If true, link columns must assume + /// that reciprocal backlinks have already been removed. Non-link columns + /// should ignore this argument. + virtual void move_last_row_over(size_t row_ndx, size_t prior_num_rows, bool broken_reciprocal_backlinks) = 0; + + /// Remove all elements from this column. + /// + /// \param num_rows The total number of rows in this column. + /// + /// \param broken_reciprocal_backlinks If true, link columns must assume + /// that reciprocal backlinks have already been removed. Non-link columns + /// should ignore this argument. + virtual void clear(size_t num_rows, bool broken_reciprocal_backlinks) = 0; + + /// \brief Swap the elements at the specified indices. + /// + /// Behaviour is undefined if: + /// - \a row_ndx_1 or \a row_ndx_2 point to an invalid element (out-of + /// bounds) + /// - \a row_ndx_1 and \a row_ndx_2 point to the same value + virtual void swap_rows(size_t row_ndx_1, size_t row_ndx_2) = 0; + + virtual void destroy() noexcept = 0; + void move_assign(ColumnBase& col) noexcept; + + virtual ~ColumnBase() noexcept + { + } + + // Disable copying, this is not supported. + ColumnBase& operator=(const ColumnBase&) = delete; + ColumnBase(const ColumnBase&) = delete; + + // Getter function for index. For integer index, the caller must supply a + // buffer that we can store the extracted value in (it may be bitpacked, so + // we cannot return a pointer in to the Array as we do with String index). + virtual StringData get_index_data(size_t, StringIndex::StringConversionBuffer& buffer) const noexcept = 0; + + // Search index + virtual bool supports_search_index() const noexcept; + virtual bool has_search_index() const noexcept; + virtual StringIndex* create_search_index(); + virtual void destroy_search_index() noexcept; + virtual const StringIndex* get_search_index() const noexcept; + virtual StringIndex* get_search_index() noexcept; + virtual void set_search_index_ref(ref_type, ArrayParent*, size_t ndx_in_parent); + + virtual Allocator& get_alloc() const noexcept = 0; + + /// Returns the 'ref' of the root array. + virtual ref_type get_ref() const noexcept = 0; + virtual MemRef get_mem() const noexcept = 0; + + virtual void replace_root_array(std::unique_ptr leaf) = 0; + virtual MemRef clone_deep(Allocator& alloc) const = 0; + virtual void detach(void) = 0; + virtual bool is_attached(void) const noexcept = 0; + + static size_t get_size_from_type_and_ref(ColumnType, ref_type, Allocator&, bool) noexcept; + + // These assume that the right column compile-time type has been + // figured out. + static size_t get_size_from_ref(ref_type root_ref, Allocator&); + static size_t get_size_from_ref(ref_type spec_ref, ref_type columns_ref, Allocator&); + + /// Write a slice of this column to the specified output stream. + virtual ref_type write(size_t slice_offset, size_t slice_size, size_t table_size, _impl::OutputStream&) const = 0; + + /// Get this column's logical index within the containing table, or npos + /// for free-standing or non-top-level columns. + size_t get_column_index() const noexcept + { + return m_column_ndx; + } + + virtual void set_parent(ArrayParent*, size_t ndx_in_parent) noexcept = 0; + virtual size_t get_ndx_in_parent() const noexcept = 0; + virtual void set_ndx_in_parent(size_t ndx_in_parent) noexcept = 0; + + /// Called to update refs and memory pointers of this column accessor and + /// all its nested accessors, but only in cases where the logical contents + /// in strictly unchanged. Group::commit(), and + /// SharedGroup::commit_and_continue_as_read()() are examples of such + /// cases. In both those cases, the purpose is to keep user visible + /// accessors in a valid state across a commit. + virtual void update_from_parent(size_t old_baseline) noexcept = 0; + + //@{ + + /// cascade_break_backlinks_to() is called iteratively for each column by + /// Table::cascade_break_backlinks_to() with the same arguments as are + /// passed to Table::cascade_break_backlinks_to(). Link columns must + /// override it. The same is true for cascade_break_backlinks_to_all_rows(), + /// except that it is called from + /// Table::cascade_break_backlinks_to_all_rows(), and that it expects + /// Table::cascade_break_backlinks_to_all_rows() to pass the number of rows + /// in the table as \a num_rows. + + virtual void cascade_break_backlinks_to(size_t row_ndx, CascadeState&); + virtual void cascade_break_backlinks_to_all_rows(size_t num_rows, CascadeState&); + + //@} + + void discard_child_accessors() noexcept; + + /// For columns that are able to contain subtables, this function returns + /// the pointer to the subtable accessor at the specified row index if it + /// exists, otherwise it returns null. For other column types, this function + /// returns null. + virtual TableRef get_subtable_accessor(size_t row_ndx) const noexcept; + + /// Detach and remove the subtable accessor at the specified row if it + /// exists. For column types that are unable to contain subtable, this + /// function does nothing. + virtual void discard_subtable_accessor(size_t row_ndx) noexcept; + + virtual void adj_acc_insert_rows(size_t row_ndx, size_t num_rows) noexcept; + virtual void adj_acc_erase_row(size_t row_ndx) noexcept; + /// See Table::adj_acc_move_over() + virtual void adj_acc_move_over(size_t from_row_ndx, size_t to_row_ndx) noexcept; + virtual void adj_acc_swap_rows(size_t row_ndx_1, size_t row_ndx_2) noexcept; + virtual void adj_acc_move_row(size_t from_ndx, size_t to_ndx) noexcept; + virtual void adj_acc_merge_rows(size_t old_row_ndx, size_t new_row_ndx) noexcept; + virtual void adj_acc_clear_root_table() noexcept; + + enum { + mark_Recursive = 0x01, + mark_LinkTargets = 0x02, + mark_LinkOrigins = 0x04, + }; + + virtual void mark(int type) noexcept; + + virtual void bump_link_origin_table_version() noexcept; + + virtual int compare_values(size_t row1, size_t row2) const noexcept = 0; + + /// Refresh the dirty part of the accessor subtree rooted at this column + /// accessor. + /// + /// The following conditions are necessary and sufficient for the proper + /// operation of this function: + /// + /// - The parent table accessor (excluding its column accessors) is in a + /// valid state (already refreshed). + /// + /// - Every subtable accessor in the subtree is marked dirty if it needs to + /// be refreshed, or if it has a descendant accessor that needs to be + /// refreshed. + /// + /// - This column accessor, as well as all its descendant accessors, are in + /// structural correspondence with the underlying node hierarchy whose + /// root ref is stored in the parent (`Table::m_columns`) (see + /// AccessorConsistencyLevels). + /// + /// - The 'index in parent' property of the cached root array + /// (`root->m_ndx_in_parent`) is valid. + virtual void refresh_accessor_tree(size_t new_col_ndx, const Spec&); + + virtual void verify() const = 0; + virtual void verify(const Table&, size_t col_ndx) const; + virtual void to_dot(std::ostream&, StringData title = StringData()) const = 0; + virtual void do_dump_node_structure(std::ostream&, int level) const = 0; + +#ifdef REALM_DEBUG + void dump_node_structure() const; // To std::cerr (for GDB) + void bptree_to_dot(const Array* root, std::ostream& out) const; +#endif + +protected: + using SliceHandler = BpTreeBase::SliceHandler; + + ColumnBase(size_t column_ndx = npos) + : m_column_ndx(column_ndx) + { + } + ColumnBase(ColumnBase&&) = default; + + // Must not assume more than minimal consistency (see + // AccessorConsistencyLevels). + virtual void do_discard_child_accessors() noexcept + { + } + + //@{ + /// \tparam L Any type with an appropriate `value_type`, %size(), + /// and %get() members. + template + size_t lower_bound(const L& list, T value) const noexcept; + + template + size_t upper_bound(const L& list, T value) const noexcept; + //@} + + // Node functions + + class CreateHandler { + public: + virtual ref_type create_leaf(size_t size) = 0; + ~CreateHandler() noexcept + { + } + }; + + static ref_type create(Allocator&, size_t size, CreateHandler&); + + class LeafToDot; + virtual void leaf_to_dot(MemRef, ArrayParent*, size_t ndx_in_parent, std::ostream&) const = 0; + + template + static int compare_values(const Column* column, size_t row1, size_t row2) noexcept; + +private: + size_t m_column_ndx = npos; + + static ref_type build(size_t* rest_size_ptr, size_t fixed_height, Allocator&, CreateHandler&); +}; + + +// FIXME: Temporary class until all column types have been migrated to use BpTree interface +class ColumnBaseSimple : public ColumnBase { +public: + //@{ + /// Returns the array node at the root of this column, but note + /// that there is no guarantee that this node is an inner B+-tree + /// node or a leaf. This is the case for a MixedColumn in + /// particular. + Array* get_root_array() noexcept + { + return m_array.get(); + } + const Array* get_root_array() const noexcept + { + return m_array.get(); + } + //@} + + Allocator& get_alloc() const noexcept final + { + return m_array->get_alloc(); + } + void destroy() noexcept override + { + if (m_array) + m_array->destroy_deep(); + } + ref_type get_ref() const noexcept final + { + return m_array->get_ref(); + } + MemRef get_mem() const noexcept final + { + return m_array->get_mem(); + } + void detach() noexcept final + { + m_array->detach(); + } + bool is_attached() const noexcept final + { + return m_array->is_attached(); + } + void set_parent(ArrayParent* parent, size_t ndx_in_parent) noexcept final + { + m_array->set_parent(parent, ndx_in_parent); + } + size_t get_ndx_in_parent() const noexcept final + { + return m_array->get_ndx_in_parent(); + } + void set_ndx_in_parent(size_t ndx_in_parent) noexcept override + { + m_array->set_ndx_in_parent(ndx_in_parent); + } + void update_from_parent(size_t old_baseline) noexcept override + { + m_array->update_from_parent(old_baseline); + } + MemRef clone_deep(Allocator& alloc) const override + { + return m_array->clone_deep(alloc); + } + +protected: + ColumnBaseSimple(size_t column_ndx) + : ColumnBase(column_ndx) + { + } + ColumnBaseSimple(Array* root) + : m_array(root) + { + } + std::unique_ptr m_array; + + void replace_root_array(std::unique_ptr new_root) final; + bool root_is_leaf() const noexcept + { + return !m_array->is_inner_bptree_node(); + } + + /// Introduce a new root node which increments the height of the + /// tree by one. + void introduce_new_root(ref_type new_sibling_ref, TreeInsertBase& state, bool is_append); + + static ref_type write(const Array* root, size_t slice_offset, size_t slice_size, size_t table_size, SliceHandler&, + _impl::OutputStream&); + +#if defined(REALM_DEBUG) + void tree_to_dot(std::ostream&) const; +#endif +}; + +class ColumnBaseWithIndex : public ColumnBase { +public: + ~ColumnBaseWithIndex() noexcept override + { + } + void set_ndx_in_parent(size_t ndx) noexcept override; + void update_from_parent(size_t old_baseline) noexcept override; + void refresh_accessor_tree(size_t, const Spec&) override; + void move_assign(ColumnBaseWithIndex& col) noexcept; + void destroy() noexcept override; + + virtual bool supports_search_index() const noexcept override + { + return true; + } + bool has_search_index() const noexcept final + { + return bool(m_search_index); + } + StringIndex* get_search_index() noexcept final + { + return m_search_index.get(); + } + const StringIndex* get_search_index() const noexcept final + { + return m_search_index.get(); + } + void destroy_search_index() noexcept override; + void set_search_index_ref(ref_type ref, ArrayParent* parent, size_t ndx_in_parent) final; + StringIndex* create_search_index() override = 0; + +protected: + using ColumnBase::ColumnBase; + ColumnBaseWithIndex(ColumnBaseWithIndex&&) = default; + std::unique_ptr m_search_index; +}; + + +/// A column (Column) is a single B+-tree, and the root of +/// the column is the root of the B+-tree. All leaf nodes are arrays. +template +class Column : public ColumnBaseWithIndex { +public: + using value_type = T; + using LeafInfo = typename BpTree::LeafInfo; + using LeafType = typename BpTree::LeafType; + + static constexpr bool nullable = ImplicitNull::value; + + struct unattached_root_tag { + }; + + explicit Column() noexcept + : ColumnBaseWithIndex(npos) + , m_tree(Allocator::get_default()) + { + } + REALM_DEPRECATED("Initialize with ref instead") explicit Column(std::unique_ptr root) noexcept; + Column(Allocator&, ref_type, size_t column_ndx = npos); + Column(unattached_root_tag, Allocator&); + Column(Column&&) noexcept = default; + ~Column() noexcept override; + + void init_from_parent(); + void init_from_ref(Allocator&, ref_type); + void init_from_mem(Allocator&, MemRef); + // Accessor concept: + void destroy() noexcept override; + Allocator& get_alloc() const noexcept final; + ref_type get_ref() const noexcept final; + MemRef get_mem() const noexcept final; + void set_parent(ArrayParent* parent, size_t ndx_in_parent) noexcept override; + size_t get_ndx_in_parent() const noexcept final; + void set_ndx_in_parent(size_t ndx) noexcept final; + void update_from_parent(size_t old_baseline) noexcept override; + void refresh_accessor_tree(size_t, const Spec&) override; + void detach() noexcept final; + bool is_attached() const noexcept final; + MemRef clone_deep(Allocator&) const override; + + void move_assign(Column&); + + static size_t get_size_from_ref(ref_type root_ref, Allocator& alloc) + { + return ColumnBase::get_size_from_ref(root_ref, alloc); + } + + size_t size() const noexcept override; + bool is_empty() const noexcept + { + return size() == 0; + } + bool is_nullable() const noexcept override; + + /// Provides access to the leaf that contains the element at the + /// specified index. Upon return \a ndx_in_leaf will be set to the + /// corresponding index relative to the beginning of the leaf. + /// + /// LeafInfo is a struct defined by the underlying BpTree + /// data structure, that provides a way for the caller to do + /// leaf caching without instantiating too many objects along + /// the way. + /// + /// This function cannot be used for modifying operations as it + /// does not ensure the presence of an unbroken chain of parent + /// accessors. For this reason, the identified leaf should always + /// be accessed through the returned const-qualified reference, + /// and never directly through the specfied fallback accessor. + void get_leaf(size_t ndx, size_t& ndx_in_leaf, LeafInfo& inout_leaf) const noexcept; + + // Getting and setting values + T get(size_t ndx) const noexcept; + bool is_null(size_t ndx) const noexcept override; + T back() const noexcept; + void set(size_t, T value); + void set_null(size_t) override; + void add(T value = T{}); + void insert(size_t ndx, T value = T{}, size_t num_rows = 1); + void erase(size_t row_ndx); + void erase(size_t row_ndx, bool is_last); + void move_last_over(size_t row_ndx, size_t last_row_ndx); + void clear(); + + // Index support + StringData get_index_data(size_t ndx, StringIndex::StringConversionBuffer& buffer) const noexcept override; + + // FIXME: Remove these + uint64_t get_uint(size_t ndx) const noexcept; + ref_type get_as_ref(size_t ndx) const noexcept; + void set_uint(size_t ndx, uint64_t value); + void set_as_ref(size_t ndx, ref_type value); + + template + void adjust(size_t ndx, U diff); + + template + void adjust(U diff); + + template + void adjust_ge(T limit, U diff); + + size_t count(T target) const; + + typename ColumnTypeTraits::sum_type sum(size_t start = 0, size_t end = npos, size_t limit = npos, + size_t* return_ndx = nullptr) const; + + typename ColumnTypeTraits::minmax_type maximum(size_t start = 0, size_t end = npos, size_t limit = npos, + size_t* return_ndx = nullptr) const; + + typename ColumnTypeTraits::minmax_type minimum(size_t start = 0, size_t end = npos, size_t limit = npos, + size_t* return_ndx = nullptr) const; + + double average(size_t start = 0, size_t end = npos, size_t limit = npos, size_t* return_ndx = nullptr) const; + + size_t find_first(T value, size_t begin = 0, size_t end = npos) const; + void find_all(Column& out_indices, T value, size_t begin = 0, size_t end = npos) const; + + void populate_search_index(); + StringIndex* create_search_index() override; + inline bool supports_search_index() const noexcept override + { + if (realm::is_any::value) + return false; + else + return true; + } + + + //@{ + /// Find the lower/upper bound for the specified value assuming + /// that the elements are already sorted in ascending order + /// according to ordinary integer comparison. + size_t lower_bound(T value) const noexcept; + size_t upper_bound(T value) const noexcept; + //@} + + using const_iterator = ColumnRandIterator; + + const_iterator cbegin() const + { + return const_iterator(this, 0); // `this` is const in a const method + } + const_iterator cend() const + { + return const_iterator(this, size()); + } + + size_t find_gte(T target, size_t start) const; + + bool compare(const Column&) const noexcept; + int compare_values(size_t row1, size_t row2) const noexcept override; + + static ref_type create(Allocator&, Array::Type leaf_type = Array::type_Normal, size_t size = 0, T value = T{}); + + // Overriding method in ColumnBase + ref_type write(size_t, size_t, size_t, _impl::OutputStream&) const override; + + void insert_rows(size_t, size_t, size_t, bool) override; + void erase_rows(size_t, size_t, size_t, bool) override; + void move_last_row_over(size_t, size_t, bool) override; + + /// \brief Swap the elements at the specified indices. + /// + /// If this \c Column has a search index defined, it will be updated to + /// reflect the changes induced by the swap. + /// + /// Behaviour is undefined if: + /// - \a row_ndx_1 or \a row_ndx_2 point to an invalid element (out-of + /// bounds) + /// - \a row_ndx_1 and \a row_ndx_2 point to the same value + void swap_rows(size_t, size_t) override; + void clear(size_t, bool) override; + + /// \param row_ndx Must be `realm::npos` if appending. + /// \param value The value to store at the specified row. + /// \param num_rows The number of rows to insert. + void insert_without_updating_index(size_t row_ndx, T value, size_t num_rows); + + void verify() const override; + void to_dot(std::ostream&, StringData title) const override; + void do_dump_node_structure(std::ostream&, int) const override; +#ifdef REALM_DEBUG + using ColumnBase::verify; + void tree_to_dot(std::ostream&) const; + MemStats stats() const; +#endif + + //@{ + /// Returns the array node at the root of this column, but note + /// that there is no guarantee that this node is an inner B+-tree + /// node or a leaf. This is the case for a MixedColumn in + /// particular. + Array* get_root_array() noexcept + { + return &m_tree.root(); + } + const Array* get_root_array() const noexcept + { + return &m_tree.root(); + } + //@} + +protected: + bool root_is_leaf() const noexcept + { + return m_tree.root_is_leaf(); + } + void replace_root_array(std::unique_ptr leaf) final + { + m_tree.replace_root(std::move(leaf)); + } + + void set_without_updating_index(size_t row_ndx, T value); + void erase_without_updating_index(size_t row_ndx, bool is_last); + void move_last_over_without_updating_index(size_t row_ndx, size_t last_row_ndx); + void swap_rows_without_updating_index(size_t row_ndx_1, size_t row_ndx_2); + + /// If any element points to an array node, this function recursively + /// destroys that array node. Note that the same is **not** true for + /// IntegerColumn::do_erase() and IntegerColumn::do_move_last_over(). + /// + /// FIXME: Be careful, clear_without_updating_index() currently forgets + /// if the leaf type is Array::type_HasRefs. + void clear_without_updating_index(); + + void leaf_to_dot(MemRef, ArrayParent*, size_t ndx_in_parent, std::ostream&) const override; +#ifdef REALM_DEBUG + static void dump_node_structure(const Array& root, std::ostream&, int level); +#endif + std::pair get_to_dot_parent(size_t ndx_in_parent) const; + +private: + class EraseLeafElem; + class CreateHandler; + class SliceHandler; + + friend class Array; + friend class ColumnBase; + friend class StringIndex; + + BpTree m_tree; + + void do_erase(size_t row_ndx, size_t num_rows_to_erase, bool is_last); +}; + +// Implementation: + + +template <> +inline size_t IntNullColumn::get_size_from_ref(ref_type root_ref, Allocator& alloc) +{ + // FIXME: Speed improvement possible by not creating instance, but tricky! This slow method is OK so far + // because it's only invoked by Table::get_size_from_ref() which is only used for subtables which we + // currently 2016) do not expose publicly. + IntNullColumn inc(alloc, root_ref); + return inc.size(); +} + + +inline bool ColumnBase::supports_search_index() const noexcept +{ + REALM_ASSERT(!has_search_index()); + return false; +} + +inline bool ColumnBase::has_search_index() const noexcept +{ + return get_search_index() != nullptr; +} + +inline StringIndex* ColumnBase::create_search_index() +{ + return nullptr; +} + +inline void ColumnBase::destroy_search_index() noexcept +{ +} + +inline const StringIndex* ColumnBase::get_search_index() const noexcept +{ + return nullptr; +} + +inline StringIndex* ColumnBase::get_search_index() noexcept +{ + return nullptr; +} + +inline void ColumnBase::set_search_index_ref(ref_type, ArrayParent*, size_t) +{ +} + +inline void ColumnBase::discard_child_accessors() noexcept +{ + do_discard_child_accessors(); +} + +inline void ColumnBase::discard_subtable_accessor(size_t) noexcept +{ + // Noop +} + +inline void ColumnBase::adj_acc_insert_rows(size_t, size_t) noexcept +{ + // Noop +} + +inline void ColumnBase::adj_acc_erase_row(size_t) noexcept +{ + // Noop +} + +inline void ColumnBase::adj_acc_move_over(size_t, size_t) noexcept +{ + // Noop +} + +inline void ColumnBase::adj_acc_swap_rows(size_t, size_t) noexcept +{ + // Noop +} + +inline void ColumnBase::adj_acc_move_row(size_t, size_t) noexcept +{ + // Noop +} + +inline void ColumnBase::adj_acc_merge_rows(size_t, size_t) noexcept +{ + // Noop +} + +inline void ColumnBase::adj_acc_clear_root_table() noexcept +{ + // Noop +} + +inline void ColumnBase::mark(int) noexcept +{ + // Noop +} + +inline void ColumnBase::bump_link_origin_table_version() noexcept +{ + // Noop +} + +template +int ColumnBase::compare_values(const Column* column, size_t row1, size_t row2) noexcept +{ + // we negate nullability such that the two ternary statements in this method can look identical to reduce + // risk of bugs + bool v1 = !column->is_null(row1); + bool v2 = !column->is_null(row2); + + if (!v1 || !v2) + return v1 == v2 ? 0 : v1 < v2 ? 1 : -1; + + auto a = column->get(row1); + auto b = column->get(row2); + return a == b ? 0 : a < b ? 1 : -1; +} + +namespace _impl { +template struct IntTypeForSize; +template <> struct IntTypeForSize<1> { using type = uint8_t; }; +template <> struct IntTypeForSize<2> { using type = uint16_t; }; +template <> struct IntTypeForSize<4> { using type = uint32_t; }; +template <> struct IntTypeForSize<8> { using type = uint64_t; }; + +template +int compare_float(Float a_raw, Float b_raw) +{ + bool a_nan = std::isnan(a_raw); + bool b_nan = std::isnan(b_raw); + if (!a_nan && !b_nan) { + // Just compare as IEEE floats + return a_raw == b_raw ? 0 : a_raw < b_raw ? 1 : -1; + } + if (a_nan && b_nan) { + // Compare the nan values (including nulls) as unsigned + using IntType = typename _impl::IntTypeForSize::type; + IntType a = 0, b = 0; + memcpy(&a, &a_raw, sizeof(Float)); + memcpy(&b, &b_raw, sizeof(Float)); + return a == b ? 0 : a < b ? 1 : -1; + } + // One is nan, the other is not + // nans are treated as being less than all non-nan values + return a_nan ? 1 : -1; +} +} // namespace _impl + +template <> +inline int ColumnBase::compare_values>(const Column* column, size_t row1, size_t row2) noexcept +{ + return _impl::compare_float(column->get(row1), column->get(row2)); +} + +template <> +inline int ColumnBase::compare_values>(const Column* column, size_t row1, size_t row2) noexcept +{ + return _impl::compare_float(column->get(row1), column->get(row2)); +} + +template +void Column::set_without_updating_index(size_t ndx, T value) +{ + m_tree.set(ndx, std::move(value)); +} + +template +void Column::set(size_t ndx, T value) +{ + REALM_ASSERT_DEBUG(ndx < size()); + if (has_search_index()) { + m_search_index->set(ndx, value); + } + set_without_updating_index(ndx, std::move(value)); +} + +template +void Column::set_null(size_t ndx) +{ + REALM_ASSERT_DEBUG(ndx < size()); + if (!is_nullable()) { + throw LogicError{LogicError::column_not_nullable}; + } + if (has_search_index()) { + m_search_index->set(ndx, null{}); + } + m_tree.set_null(ndx); +} + +// When a value of a signed type is converted to an unsigned type, the C++ standard guarantees that negative values +// are converted from the native representation to 2's complement, but the opposite conversion is left as undefined. +// realm::util::from_twos_compl() is used here to perform the correct opposite unsigned-to-signed conversion, +// which reduces to a no-op when 2's complement is the native representation of negative values. +template +void Column::set_uint(size_t ndx, uint64_t value) +{ + set(ndx, util::from_twos_compl(value)); +} + +template +void Column::set_as_ref(size_t ndx, ref_type ref) +{ + set(ndx, from_ref(ref)); +} + +template +template +void Column::adjust(size_t ndx, U diff) +{ + REALM_ASSERT_3(ndx, <, size()); + m_tree.adjust(ndx, diff); +} + +template +template +void Column::adjust(U diff) +{ + m_tree.adjust(diff); +} + +template +template +void Column::adjust_ge(T limit, U diff) +{ + m_tree.adjust_ge(limit, diff); +} + +template +size_t Column::count(T target) const +{ + if (has_search_index()) { + return m_search_index->count(target); + } + return to_size_t(aggregate(*this, target, 0, size(), npos, nullptr)); +} + +template +typename ColumnTypeTraits::sum_type Column::sum(size_t start, size_t end, size_t limit, + size_t* return_ndx) const +{ + using sum_type = typename ColumnTypeTraits::sum_type; + if (nullable) + return aggregate(*this, 0, start, end, limit, return_ndx); + else + return aggregate(*this, 0, start, end, limit, return_ndx); +} + +template +double Column::average(size_t start, size_t end, size_t limit, size_t* return_ndx) const +{ + if (end == size_t(-1)) + end = size(); + + auto s = sum(start, end, limit); + size_t cnt = to_size_t(aggregate(*this, 0, start, end, limit, nullptr)); + if (return_ndx) + *return_ndx = cnt; + double avg = double(s) / (cnt == 0 ? 1 : cnt); + return avg; +} + +template +typename ColumnTypeTraits::minmax_type Column::minimum(size_t start, size_t end, size_t limit, + size_t* return_ndx) const +{ + using R = typename ColumnTypeTraits::minmax_type; + return aggregate(*this, 0, start, end, limit, return_ndx); +} + +template +typename ColumnTypeTraits::minmax_type Column::maximum(size_t start, size_t end, size_t limit, + size_t* return_ndx) const +{ + using R = typename ColumnTypeTraits::minmax_type; + return aggregate(*this, 0, start, end, limit, return_ndx); +} + +template +void Column::get_leaf(size_t ndx, size_t& ndx_in_leaf, LeafInfo& inout_leaf_info) const noexcept +{ + m_tree.get_leaf(ndx, ndx_in_leaf, inout_leaf_info); +} + +template +StringData Column::get_index_data(size_t ndx, StringIndex::StringConversionBuffer& buffer) const noexcept +{ + T x = get(ndx); + return to_str(x, buffer); +} + +template +void Column::populate_search_index() +{ + REALM_ASSERT(has_search_index()); + // Populate the index + size_t num_rows = size(); + for (size_t row_ndx = 0; row_ndx != num_rows; ++row_ndx) { + bool is_append = true; + if (is_null(row_ndx)) { + m_search_index->insert(row_ndx, null{}, 1, is_append); // Throws + } + else { + T value = get(row_ndx); + m_search_index->insert(row_ndx, value, 1, is_append); // Throws + } + } +} + +template +StringIndex* Column::create_search_index() +{ + if (realm::is_any::value) + return nullptr; + + REALM_ASSERT(!has_search_index()); + REALM_ASSERT(supports_search_index()); + m_search_index.reset(new StringIndex(this, get_alloc())); // Throws + populate_search_index(); + return m_search_index.get(); +} + +template +size_t Column::find_first(T value, size_t begin, size_t end) const +{ + REALM_ASSERT_3(begin, <=, size()); + REALM_ASSERT(end == npos || (begin <= end && end <= size())); + + if (m_search_index && begin == 0 && end == npos) + return m_search_index->find_first(value); + return m_tree.find_first(value, begin, end); +} + +template +void Column::find_all(IntegerColumn& result, T value, size_t begin, size_t end) const +{ + REALM_ASSERT_3(begin, <=, size()); + REALM_ASSERT(end == npos || (begin <= end && end <= size())); + + if (m_search_index && begin == 0 && end == npos) + return m_search_index->find_all(result, value); + return m_tree.find_all(result, value, begin, end); +} + +inline size_t ColumnBase::get_size_from_ref(ref_type root_ref, Allocator& alloc) +{ + const char* root_header = alloc.translate(root_ref); + bool root_is_leaf = !Array::get_is_inner_bptree_node_from_header(root_header); + if (root_is_leaf) + return Array::get_size_from_header(root_header); + return BpTreeNode::get_bptree_size_from_header(root_header); +} + +template +size_t ColumnBase::lower_bound(const L& list, T value) const noexcept +{ + size_t i = 0; + size_t list_size = list.size(); + while (0 < list_size) { + size_t half = list_size / 2; + size_t mid = i + half; + typename L::value_type probe = list.get(mid); + if (probe < value) { + i = mid + 1; + list_size -= half + 1; + } + else { + list_size = half; + } + } + return i; +} + +template +size_t ColumnBase::upper_bound(const L& list, T value) const noexcept +{ + size_t i = 0; + size_t list_size = list.size(); + while (0 < list_size) { + size_t half = list_size / 2; + size_t mid = i + half; + typename L::value_type probe = list.get(mid); + if (!(value < probe)) { + i = mid + 1; + list_size -= half + 1; + } + else { + list_size = half; + } + } + return i; +} + + +inline ref_type ColumnBase::create(Allocator& alloc, size_t column_size, CreateHandler& handler) +{ + size_t rest_size = column_size; + size_t fixed_height = 0; // Not fixed + return build(&rest_size, fixed_height, alloc, handler); +} + +template +Column::Column(Allocator& alloc, ref_type ref, size_t column_ndx) + : ColumnBaseWithIndex(column_ndx) + , m_tree(BpTreeBase::unattached_tag{}) +{ + // fixme, must m_search_index be copied here? + m_tree.init_from_ref(alloc, ref); +} + +template +Column::Column(unattached_root_tag, Allocator& alloc) + : ColumnBaseWithIndex(npos) + , m_tree(alloc) +{ +} + +template +Column::Column(std::unique_ptr root) noexcept + : m_tree(std::move(root)) +{ +} + +template +Column::~Column() noexcept +{ +} + +template +void Column::init_from_parent() +{ + m_tree.init_from_parent(); +} + +template +void Column::init_from_ref(Allocator& alloc, ref_type ref) +{ + m_tree.init_from_ref(alloc, ref); +} + +template +void Column::init_from_mem(Allocator& alloc, MemRef mem) +{ + m_tree.init_from_mem(alloc, mem); +} + +template +void Column::destroy() noexcept +{ + ColumnBaseWithIndex::destroy(); + m_tree.destroy(); +} + +template +void Column::move_assign(Column& col) +{ + ColumnBaseWithIndex::move_assign(col); + m_tree = std::move(col.m_tree); +} + +template +Allocator& Column::get_alloc() const noexcept +{ + return m_tree.get_alloc(); +} + +template +void Column::set_parent(ArrayParent* parent, size_t ndx_in_parent) noexcept +{ + m_tree.set_parent(parent, ndx_in_parent); +} + +template +size_t Column::get_ndx_in_parent() const noexcept +{ + return m_tree.get_ndx_in_parent(); +} + +template +void Column::set_ndx_in_parent(size_t ndx_in_parent) noexcept +{ + ColumnBaseWithIndex::set_ndx_in_parent(ndx_in_parent); + m_tree.set_ndx_in_parent(ndx_in_parent); +} + +template +void Column::detach() noexcept +{ + m_tree.detach(); +} + +template +bool Column::is_attached() const noexcept +{ + return m_tree.is_attached(); +} + +template +ref_type Column::get_ref() const noexcept +{ + return get_root_array()->get_ref(); +} + +template +MemRef Column::get_mem() const noexcept +{ + return get_root_array()->get_mem(); +} + +template +void Column::update_from_parent(size_t old_baseline) noexcept +{ + ColumnBaseWithIndex::update_from_parent(old_baseline); + m_tree.update_from_parent(old_baseline); +} + +template +MemRef Column::clone_deep(Allocator& alloc) const +{ + return m_tree.clone_deep(alloc); +} + +template +size_t Column::size() const noexcept +{ + return m_tree.size(); +} + +template +bool Column::is_nullable() const noexcept +{ + return nullable; +} + +template +T Column::get(size_t ndx) const noexcept +{ + return m_tree.get(ndx); +} + +template +bool Column::is_null(size_t ndx) const noexcept +{ + return nullable && m_tree.is_null(ndx); +} + +template +T Column::back() const noexcept +{ + return m_tree.back(); +} + +template +ref_type Column::get_as_ref(size_t ndx) const noexcept +{ + return to_ref(get(ndx)); +} + +template +uint64_t Column::get_uint(size_t ndx) const noexcept +{ + static_assert(std::is_convertible::value, "T is not convertible to uint."); + return static_cast(get(ndx)); +} + +template +void Column::add(T value) +{ + insert(npos, std::move(value)); +} + +template +void Column::insert_without_updating_index(size_t row_ndx, T value, size_t num_rows) +{ + size_t column_size = this->size(); // Slow + bool is_append = row_ndx == column_size || row_ndx == npos; + size_t ndx_or_npos_if_append = is_append ? npos : row_ndx; + + m_tree.insert(ndx_or_npos_if_append, std::move(value), num_rows); // Throws +} + +template +void Column::insert(size_t row_ndx, T value, size_t num_rows) +{ + size_t column_size = this->size(); // Slow + bool is_append = row_ndx == column_size || row_ndx == npos; + size_t ndx_or_npos_if_append = is_append ? npos : row_ndx; + + m_tree.insert(ndx_or_npos_if_append, value, num_rows); // Throws + + if (has_search_index()) { + row_ndx = is_append ? column_size : row_ndx; + m_search_index->insert(row_ndx, value, num_rows, is_append); // Throws + } +} + +template +void Column::erase_without_updating_index(size_t row_ndx, bool is_last) +{ + m_tree.erase(row_ndx, is_last); +} + +template +void Column::erase(size_t row_ndx) +{ + REALM_ASSERT(size() >= 1); + size_t last_row_ndx = size() - 1; // Note that size() is slow + bool is_last = (row_ndx == last_row_ndx); + erase(row_ndx, is_last); // Throws +} + +template +void Column::erase(size_t row_ndx, bool is_last) +{ + size_t num_rows_to_erase = 1; + do_erase(row_ndx, num_rows_to_erase, is_last); // Throws +} + +template +void Column::move_last_over_without_updating_index(size_t row_ndx, size_t last_row_ndx) +{ + m_tree.move_last_over(row_ndx, last_row_ndx); +} + +template +void Column::move_last_over(size_t row_ndx, size_t last_row_ndx) +{ + REALM_ASSERT_3(row_ndx, <=, last_row_ndx); + REALM_ASSERT_DEBUG(last_row_ndx + 1 == size()); + + if (has_search_index()) { + // remove the value to be overwritten from index + bool is_last = true; // This tells StringIndex::erase() to not adjust subsequent indexes + m_search_index->erase(row_ndx, is_last); // Throws + + // update index to point to new location + if (row_ndx != last_row_ndx) { + T moved_value = get(last_row_ndx); + m_search_index->update_ref(moved_value, last_row_ndx, row_ndx); // Throws + } + } + + move_last_over_without_updating_index(row_ndx, last_row_ndx); +} + +template +void Column::swap_rows(size_t row_ndx_1, size_t row_ndx_2) +{ + REALM_ASSERT_3(row_ndx_1, <, size()); + REALM_ASSERT_3(row_ndx_2, <, size()); + REALM_ASSERT_DEBUG(row_ndx_1 != row_ndx_2); + + if (has_search_index()) { + T value_1 = get(row_ndx_1); + T value_2 = get(row_ndx_2); + size_t column_size = this->size(); + bool row_ndx_1_is_last = row_ndx_1 == column_size - 1; + bool row_ndx_2_is_last = row_ndx_2 == column_size - 1; + m_search_index->erase(row_ndx_1, row_ndx_1_is_last); + m_search_index->insert(row_ndx_1, value_2, 1, row_ndx_1_is_last); + + m_search_index->erase(row_ndx_2, row_ndx_2_is_last); + m_search_index->insert(row_ndx_2, value_1, 1, row_ndx_2_is_last); + } + + swap_rows_without_updating_index(row_ndx_1, row_ndx_2); +} + +template +void Column::swap_rows_without_updating_index(size_t row_ndx_1, size_t row_ndx_2) +{ + // FIXME: This can be optimized with direct getters and setters. + T value_1 = get(row_ndx_1); + T value_2 = get(row_ndx_2); + m_tree.set(row_ndx_1, value_2); + m_tree.set(row_ndx_2, value_1); +} + +template +void Column::clear_without_updating_index() +{ + m_tree.clear(); // Throws +} + +template +void Column::clear() +{ + if (has_search_index()) { + m_search_index->clear(); + } + clear_without_updating_index(); +} + +template +struct NullOrDefaultValue; +template +struct NullOrDefaultValue::value>::type> { + static T null_or_default_value(bool is_null) + { + if (is_null) { + return null::get_null_float(); + } + else { + return T{}; + } + } +}; +template +struct NullOrDefaultValue, void> { + static util::Optional null_or_default_value(bool is_null) + { + if (is_null) { + return util::none; + } + else { + return util::some(T{}); + } + } +}; +template +struct NullOrDefaultValue::value>::type> { + static T null_or_default_value(bool is_null) + { + REALM_ASSERT(!is_null); + return T{}; + } +}; + +// Implementing pure virtual method of ColumnBase. +template +void Column::insert_rows(size_t row_ndx, size_t num_rows_to_insert, size_t prior_num_rows, bool insert_nulls) +{ + REALM_ASSERT_DEBUG(prior_num_rows == size()); + REALM_ASSERT(row_ndx <= prior_num_rows); + + size_t row_ndx_2 = (row_ndx == prior_num_rows ? realm::npos : row_ndx); + T value = NullOrDefaultValue::null_or_default_value(insert_nulls); + insert(row_ndx_2, value, num_rows_to_insert); // Throws +} + +// Implementing pure virtual method of ColumnBase. +template +void Column::erase_rows(size_t row_ndx, size_t num_rows_to_erase, size_t prior_num_rows, bool) +{ + REALM_ASSERT_DEBUG(prior_num_rows == size()); + REALM_ASSERT(num_rows_to_erase <= prior_num_rows); + REALM_ASSERT(row_ndx <= prior_num_rows - num_rows_to_erase); + + bool is_last = (row_ndx + num_rows_to_erase == prior_num_rows); + do_erase(row_ndx, num_rows_to_erase, is_last); // Throws +} + +// Implementing pure virtual method of ColumnBase. +template +void Column::move_last_row_over(size_t row_ndx, size_t prior_num_rows, bool) +{ + REALM_ASSERT_DEBUG(prior_num_rows == size()); + REALM_ASSERT(row_ndx < prior_num_rows); + + size_t last_row_ndx = prior_num_rows - 1; + move_last_over(row_ndx, last_row_ndx); // Throws +} + +// Implementing pure virtual method of ColumnBase. +template +void Column::clear(size_t, bool) +{ + clear(); // Throws +} + + +template +size_t Column::lower_bound(T value) const noexcept +{ + if (root_is_leaf()) { + auto root = static_cast(get_root_array()); + return root->lower_bound(value); + } + return ColumnBase::lower_bound(*this, value); +} + +template +size_t Column::upper_bound(T value) const noexcept +{ + if (root_is_leaf()) { + auto root = static_cast(get_root_array()); + return root->upper_bound(value); + } + return ColumnBase::upper_bound(*this, value); +} + +// For a *sorted* Column, return first element E for which E >= target or return -1 if none +template +size_t Column::find_gte(T target, size_t start) const +{ + // fixme: slow reference implementation. See Array::find_gte for faster version + size_t ref = 0; + size_t idx; + for (idx = start; idx < size(); ++idx) { + if (get(idx) >= target) { + ref = idx; + break; + } + } + if (idx == size()) + ref = not_found; + + return ref; +} + + +template +bool Column::compare(const Column& c) const noexcept +{ + size_t n = size(); + if (c.size() != n) + return false; + for (size_t i = 0; i < n; ++i) { + bool left_is_null = is_null(i); + bool right_is_null = c.is_null(i); + if (left_is_null != right_is_null) { + return false; + } + if (!left_is_null) { + if (get(i) != c.get(i)) + return false; + } + } + return true; +} + +template +int Column::compare_values(size_t row1, size_t row2) const noexcept +{ + return ColumnBase::compare_values(this, row1, row2); +} + +template +class Column::CreateHandler : public ColumnBase::CreateHandler { +public: + CreateHandler(Array::Type leaf_type, T value, Allocator& alloc) + : m_value(value) + , m_alloc(alloc) + , m_leaf_type(leaf_type) + { + } + ref_type create_leaf(size_t size) override + { + MemRef mem = BpTree::create_leaf(m_leaf_type, size, m_value, m_alloc); // Throws + return mem.get_ref(); + } + +private: + const T m_value; + Allocator& m_alloc; + Array::Type m_leaf_type; +}; + +template +ref_type Column::create(Allocator& alloc, Array::Type leaf_type, size_t size, T value) +{ + CreateHandler handler(leaf_type, std::move(value), alloc); + return ColumnBase::create(alloc, size, handler); +} + +template +ref_type Column::write(size_t slice_offset, size_t slice_size, size_t table_size, _impl::OutputStream& out) const +{ + return m_tree.write(slice_offset, slice_size, table_size, out); +} + +template +void Column::refresh_accessor_tree(size_t new_col_ndx, const Spec& spec) +{ + m_tree.init_from_parent(); + ColumnBaseWithIndex::refresh_accessor_tree(new_col_ndx, spec); +} + +template +void Column::do_erase(size_t row_ndx, size_t num_rows_to_erase, bool is_last) +{ + if (has_search_index()) { + for (size_t i = num_rows_to_erase; i > 0; --i) { + size_t row_ndx_2 = row_ndx + i - 1; + m_search_index->erase(row_ndx_2, is_last); // Throws + } + } + for (size_t i = num_rows_to_erase; i > 0; --i) { + size_t row_ndx_2 = row_ndx + i - 1; + erase_without_updating_index(row_ndx_2, is_last); // Throws + } +} + +template +void Column::verify() const +{ +#ifdef REALM_DEBUG + m_tree.verify(); +#endif +} + +// LCOV_EXCL_START + +template +void Column::to_dot(std::ostream& out, StringData title) const +{ +#ifdef REALM_DEBUG + ref_type ref = get_root_array()->get_ref(); + out << "subgraph cluster_integer_column" << ref << " {" << std::endl; + out << " label = \"Integer column"; + if (title.size() != 0) + out << "\\n'" << title << "'"; + out << "\";" << std::endl; + tree_to_dot(out); + out << "}" << std::endl; +#else + static_cast(out); + static_cast(title); +#endif +} + +template +void Column::leaf_to_dot(MemRef leaf_mem, ArrayParent* parent, size_t ndx_in_parent, std::ostream& out) const +{ +#ifdef REALM_DEBUG + BpTree::leaf_to_dot(leaf_mem, parent, ndx_in_parent, out, get_alloc()); +#else + static_cast(leaf_mem); + static_cast(parent); + static_cast(ndx_in_parent); + static_cast(out); +#endif +} + +template +void Column::do_dump_node_structure(std::ostream& out, int level) const +{ +#ifdef REALM_DEBUG + dump_node_structure(*get_root_array(), out, level); +#else + static_cast(out); + static_cast(level); +#endif +} + +#ifdef REALM_DEBUG + +template +void Column::tree_to_dot(std::ostream& out) const +{ + ColumnBase::bptree_to_dot(get_root_array(), out); +} + + +template +MemStats Column::stats() const +{ + MemStats mem_stats; + get_root_array()->stats(mem_stats); + return mem_stats; +} + +namespace _impl { +void leaf_dumper(MemRef mem, Allocator& alloc, std::ostream& out, int level); +} + +template +void Column::dump_node_structure(const Array& root, std::ostream& out, int level) +{ + root.dump_bptree_structure(out, level, &_impl::leaf_dumper); +} + +#endif + +template +std::pair Column::get_to_dot_parent(size_t ndx_in_parent) const +{ + auto root = get_root_array(); + if (root->is_inner_bptree_node()) { + std::pair p = static_cast(root)->get_bptree_leaf(ndx_in_parent); + return std::make_pair(p.first.get_ref(), p.second); + } + else { + return std::make_pair(root->get_ref(), ndx_in_parent); + } +} + +// LCOV_EXCL_STOP ignore debug functions + + +template +ColumnRandIterator::ColumnRandIterator(const Column* src_col, size_t ndx) + : m_col_ndx(ndx) + , m_col(src_col) +{ +} + +template +bool ColumnRandIterator::operator==(const ColumnRandIterator& rhs) const +{ + return (m_col_ndx == rhs.m_col_ndx); +} + +template +bool ColumnRandIterator::operator!=(const ColumnRandIterator& rhs) const +{ + return !(*this == rhs); +} + +template +bool ColumnRandIterator::operator<(const ColumnRandIterator& rhs) const +{ + return m_col_ndx < rhs.m_col_ndx; +} + +template +bool ColumnRandIterator::operator>(const ColumnRandIterator& rhs) const +{ + return rhs < *this; +} + +template +bool ColumnRandIterator::operator<=(const ColumnRandIterator& rhs) const +{ + return !(rhs < *this); +} + +template +bool ColumnRandIterator::operator>=(const ColumnRandIterator& rhs) const +{ + return !(*this < rhs); +} + +template +ColumnRandIterator& ColumnRandIterator::operator+=(ptrdiff_t movement) +{ + m_col_ndx += movement; + return (*this); +} + +template +ColumnRandIterator& ColumnRandIterator::operator-=(ptrdiff_t movement) +{ + m_col_ndx -= movement; + return (*this); +} + +template +ColumnRandIterator& ColumnRandIterator::operator++() +{ + ++m_col_ndx; + return (*this); +} + +template +ColumnRandIterator& ColumnRandIterator::operator--() +{ + --m_col_ndx; + return (*this); +} + +template +ColumnRandIterator ColumnRandIterator::operator++(int) +{ + auto temp(*this); + ++m_col_ndx; + return temp; +} + +template +ColumnRandIterator ColumnRandIterator::operator--(int) +{ + auto temp(*this); + --m_col_ndx; + return temp; +} + +template +ColumnRandIterator ColumnRandIterator::operator+(ptrdiff_t movement) +{ + return ColumnRandIterator(m_col, m_col_ndx + movement); +} + +template +ColumnRandIterator ColumnRandIterator::operator-(ptrdiff_t movement) +{ + return ColumnRandIterator(m_col, m_col_ndx - movement); +} + +template +ptrdiff_t ColumnRandIterator::operator-(const ColumnRandIterator& right) const +{ + return m_col_ndx - right.m_col_ndx; +} + +template +const ColumnDataType ColumnRandIterator::operator*() const +{ + return m_col->get(m_col_ndx); +} + +template +const ColumnDataType ColumnRandIterator::operator->() const +{ + return m_col->get(m_col_ndx); +} + +template +const ColumnDataType ColumnRandIterator::operator[](ptrdiff_t offset) const +{ + return m_col->get(m_col_ndx + offset); +} + +template +size_t ColumnRandIterator::get_col_ndx() const +{ + return m_col_ndx; +} + +template +std::ostream& operator<<(std::ostream& out, const ColumnRandIterator& it) +{ + out << "ColumnRandIterator at index: " << it.get_col_ndx(); + return out; +} + +} // namespace realm + +#endif // REALM_COLUMN_HPP diff --git a/!main project/Pods/Realm/include/core/realm/column_backlink.hpp b/!main project/Pods/Realm/include/core/realm/column_backlink.hpp new file mode 100644 index 0000000..cbbe9e6 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/column_backlink.hpp @@ -0,0 +1,246 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_COLUMN_BACKLINK_HPP +#define REALM_COLUMN_BACKLINK_HPP + +#include + +#include +#include +#include + +namespace realm { + +/// A column of backlinks (BacklinkColumn) is a single B+-tree, and the root of +/// the column is the root of the B+-tree. All leaf nodes are single arrays of +/// type Array with the hasRefs bit set. +/// +/// The individual values in the column are either refs to Columns containing +/// the row indexes in the origin table that links to it, or in the case where +/// there is a single link, a tagged ref encoding the origin row position. +class BacklinkColumn : public IntegerColumn, public ArrayParent { +public: + BacklinkColumn(Allocator&, ref_type, size_t col_ndx = npos); + ~BacklinkColumn() noexcept override + { + } + + static ref_type create(Allocator&, size_t size = 0); + + bool has_backlinks(size_t row_ndx) const noexcept; + size_t get_backlink_count(size_t row_ndx) const noexcept; + size_t get_backlink(size_t row_ndx, size_t backlink_ndx) const noexcept; + + void add_backlink(size_t row_ndx, size_t origin_row_ndx); + void remove_one_backlink(size_t row_ndx, size_t origin_row_ndx); + void remove_all_backlinks(size_t num_rows); + void update_backlink(size_t row_ndx, size_t old_origin_row_ndx, size_t new_origin_row_ndx); + void swap_backlinks(size_t row_ndx, size_t origin_row_ndx_1, size_t origin_row_ndx_2); + + void add_row(); + + // Link origination info + Table& get_origin_table() const noexcept; + void set_origin_table(Table&) noexcept; + LinkColumnBase& get_origin_column() const noexcept; + size_t get_origin_column_index() const noexcept; + void set_origin_column(LinkColumnBase& column) noexcept; + + void insert_rows(size_t, size_t, size_t, bool) override; + void erase_rows(size_t, size_t, size_t, bool) override; + void move_last_row_over(size_t, size_t, bool) override; + void swap_rows(size_t, size_t) override; + void clear(size_t, bool) override; + void adj_acc_insert_rows(size_t, size_t) noexcept override; + void adj_acc_erase_row(size_t) noexcept override; + void adj_acc_move_over(size_t, size_t) noexcept override; + void adj_acc_swap_rows(size_t, size_t) noexcept override; + void adj_acc_move_row(size_t, size_t) noexcept override; + void adj_acc_merge_rows(size_t, size_t) noexcept override; + void adj_acc_clear_root_table() noexcept override; + void mark(int) noexcept override; + + void bump_link_origin_table_version() noexcept override; + + void cascade_break_backlinks_to(size_t row_ndx, CascadeState& state) override; + void cascade_break_backlinks_to_all_rows(size_t num_rows, CascadeState&) override; + + int compare_values(size_t, size_t) const noexcept override; + + void verify() const override; + void verify(const Table&, size_t) const override; +#ifdef REALM_DEBUG + struct VerifyPair { + size_t origin_row_ndx, target_row_ndx; + bool operator<(const VerifyPair&) const noexcept; + }; + void get_backlinks(std::vector&); // Sorts +#endif + +protected: + // ArrayParent overrides + void update_child_ref(size_t child_ndx, ref_type new_ref) override; + ref_type get_child_ref(size_t child_ndx) const noexcept override; + + std::pair get_to_dot_parent(size_t) const override; + +private: + TableRef m_origin_table; + LinkColumnBase* m_origin_column = nullptr; + + template + size_t for_each_link(size_t row_ndx, bool do_destroy, Func&& f); +}; + + +// Implementation + +inline BacklinkColumn::BacklinkColumn(Allocator& alloc, ref_type ref, size_t col_ndx) + : IntegerColumn(alloc, ref, col_ndx) // Throws +{ +} + +inline ref_type BacklinkColumn::create(Allocator& alloc, size_t size) +{ + return IntegerColumn::create(alloc, Array::type_HasRefs, size); // Throws +} + +inline bool BacklinkColumn::has_backlinks(size_t ndx) const noexcept +{ + return IntegerColumn::get(ndx) != 0; +} + +inline Table& BacklinkColumn::get_origin_table() const noexcept +{ + return *m_origin_table; +} + +inline void BacklinkColumn::set_origin_table(Table& table) noexcept +{ + REALM_ASSERT(!m_origin_table); + m_origin_table = table.get_table_ref(); +} + +inline LinkColumnBase& BacklinkColumn::get_origin_column() const noexcept +{ + return *m_origin_column; +} + +inline size_t BacklinkColumn::get_origin_column_index() const noexcept +{ + return m_origin_column ? m_origin_column->get_column_index() : npos; +} + +inline void BacklinkColumn::set_origin_column(LinkColumnBase& column) noexcept +{ + m_origin_column = &column; +} + +inline void BacklinkColumn::add_row() +{ + IntegerColumn::add(0); +} + +inline void BacklinkColumn::adj_acc_insert_rows(size_t row_ndx, size_t num_rows) noexcept +{ + IntegerColumn::adj_acc_insert_rows(row_ndx, num_rows); + + typedef _impl::TableFriend tf; + tf::mark(*m_origin_table); +} + +inline void BacklinkColumn::adj_acc_erase_row(size_t row_ndx) noexcept +{ + IntegerColumn::adj_acc_erase_row(row_ndx); + + typedef _impl::TableFriend tf; + tf::mark(*m_origin_table); +} + +inline void BacklinkColumn::adj_acc_move_over(size_t from_row_ndx, size_t to_row_ndx) noexcept +{ + IntegerColumn::adj_acc_move_over(from_row_ndx, to_row_ndx); + + typedef _impl::TableFriend tf; + tf::mark(*m_origin_table); +} + +inline void BacklinkColumn::adj_acc_swap_rows(size_t row_ndx_1, size_t row_ndx_2) noexcept +{ + Column::adj_acc_swap_rows(row_ndx_1, row_ndx_2); + + using tf = _impl::TableFriend; + tf::mark(*m_origin_table); +} + +inline void BacklinkColumn::adj_acc_move_row(size_t from_ndx, size_t to_ndx) noexcept +{ + Column::adj_acc_move_row(from_ndx, to_ndx); + + using tf = _impl::TableFriend; + tf::mark(*m_origin_table); +} + +inline void BacklinkColumn::adj_acc_merge_rows(size_t old_row_ndx, size_t new_row_ndx) noexcept +{ + Column::adj_acc_merge_rows(old_row_ndx, new_row_ndx); + + using tf = _impl::TableFriend; + tf::mark(*m_origin_table); +} + +inline void BacklinkColumn::adj_acc_clear_root_table() noexcept +{ + IntegerColumn::adj_acc_clear_root_table(); + + typedef _impl::TableFriend tf; + tf::mark(*m_origin_table); +} + +inline void BacklinkColumn::mark(int type) noexcept +{ + if (type & mark_LinkOrigins) { + typedef _impl::TableFriend tf; + tf::mark(*m_origin_table); + } +} + +inline void BacklinkColumn::bump_link_origin_table_version() noexcept +{ + // It is important to mark connected tables as modified. + // Also see LinkColumnBase::bump_link_origin_table_version(). + typedef _impl::TableFriend tf; + if (m_origin_table) { + bool bump_global = false; + tf::bump_version(*m_origin_table, bump_global); + } +} + +#ifdef REALM_DEBUG + +inline bool BacklinkColumn::VerifyPair::operator<(const VerifyPair& p) const noexcept +{ + return origin_row_ndx < p.origin_row_ndx; +} + +#endif // REALM_DEBUG + +} // namespace realm + +#endif // REALM_COLUMN_BACKLINK_HPP diff --git a/!main project/Pods/Realm/include/core/realm/column_binary.hpp b/!main project/Pods/Realm/include/core/realm/column_binary.hpp new file mode 100644 index 0000000..48e1d11 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/column_binary.hpp @@ -0,0 +1,435 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_COLUMN_BINARY_HPP +#define REALM_COLUMN_BINARY_HPP + +#include +#include +#include + +namespace realm { + + +/// A binary column (BinaryColumn) is a single B+-tree, and the root +/// of the column is the root of the B+-tree. Leaf nodes are either of +/// type ArrayBinary (array of small blobs) or ArrayBigBlobs (array of +/// big blobs). +class BinaryColumn : public ColumnBaseSimple { +public: + typedef BinaryData value_type; + + BinaryColumn(Allocator&, ref_type, bool nullable = false, size_t column_ndx = npos); + + size_t size() const noexcept final; + bool is_empty() const noexcept + { + return size() == 0; + } + bool is_nullable() const noexcept override; + + BinaryData get(size_t ndx) const noexcept; + + /// Return data from position 'pos' and onwards. If the blob is distributed + /// across multiple arrays (if bigger than ~ 16M), you will only get data + /// from one array. 'pos' will be updated to be an index to next available + /// data. It will be 0 if no more data. + BinaryData get_at(size_t ndx, size_t& pos) const noexcept; + + bool is_null(size_t ndx) const noexcept override; + StringData get_index_data(size_t, StringIndex::StringConversionBuffer&) const noexcept final; + + void add(BinaryData value); + void set(size_t ndx, BinaryData value, bool add_zero_term = false); + void set_null(size_t ndx) override; + void insert(size_t ndx, BinaryData value); + void erase(size_t row_ndx); + void erase(size_t row_ndx, bool is_last); + void move_last_over(size_t row_ndx); + void swap_rows(size_t row_ndx_1, size_t row_ndx_2) override; + void clear(); + size_t find_first(BinaryData value) const; + + // Requires that the specified entry was inserted as StringData. + StringData get_string(size_t ndx) const noexcept; + + void add_string(StringData value); + void set_string(size_t ndx, StringData value) override; + void insert_string(size_t ndx, StringData value); + + /// Compare two binary columns for equality. + bool compare_binary(const BinaryColumn&) const; + + int compare_values(size_t row1, size_t row2) const noexcept override; + + static ref_type create(Allocator&, size_t size, bool nullable); + + static size_t get_size_from_ref(ref_type root_ref, Allocator&) noexcept; + + // Overrriding method in ColumnBase + ref_type write(size_t, size_t, size_t, _impl::OutputStream&) const override; + + void insert_rows(size_t, size_t, size_t, bool) override; + void erase_rows(size_t, size_t, size_t, bool) override; + void move_last_row_over(size_t, size_t, bool) override; + void clear(size_t, bool) override; + void update_from_parent(size_t) noexcept override; + void refresh_accessor_tree(size_t, const Spec&) override; + + /// In contrast to update_from_parent(), this function is able to handle + /// cases where the accessed payload data has changed. In particular, it + /// handles cases where the B+-tree switches from having one level (root is + /// a leaf node), to having multiple levels (root is an inner node). Note + /// that this is at the expense of loosing the `noexcept` guarantee. + void update_from_ref(ref_type ref); + + void verify() const override; + void to_dot(std::ostream&, StringData title) const override; + void do_dump_node_structure(std::ostream&, int) const override; + void find_all(IntegerColumn&, BinaryData, size_t, size_t) const + { + // Dummy implementation + REALM_ASSERT(false); + } + + +private: + /// \param row_ndx Must be `realm::npos` if appending. + void do_insert(size_t row_ndx, BinaryData value, bool add_zero_term, size_t num_rows); + + // Called by Array::bptree_insert(). + static ref_type leaf_insert(MemRef leaf_mem, ArrayParent&, size_t ndx_in_parent, Allocator&, size_t insert_ndx, + BpTreeNode::TreeInsert& state); + + struct InsertState : BpTreeNode::TreeInsert { + bool m_add_zero_term; + }; + + class EraseLeafElem; + class CreateHandler; + class SliceHandler; + + void do_move_last_over(size_t row_ndx, size_t last_row_ndx); + void do_clear(); + + /// Root must be a leaf. Upgrades the root leaf if + /// necessary. Returns true if, and only if the root is a 'big + /// blobs' leaf upon return. + bool upgrade_root_leaf(size_t value_size); + + bool m_nullable = false; + + void leaf_to_dot(MemRef, ArrayParent*, size_t ndx_in_parent, std::ostream&) const override; + + friend class BpTreeNode; + friend class ColumnBase; +}; + +class BinaryIterator { +public: + BinaryIterator() + { + } + // TODO: When WriteLogCollector is removed, there is no need for this + BinaryIterator(BinaryData binary) + : m_binary(binary) + { + } + + BinaryIterator(const BinaryColumn* col, size_t ndx) + : m_binary_col(col) + , m_ndx(ndx) + { + } + + BinaryData get_next() noexcept + { + if (!end_of_data) { + if (m_binary_col) { + BinaryData ret = m_binary_col->get_at(m_ndx, m_pos); + end_of_data = (m_pos == 0); + return ret; + } + else if (!m_binary.is_null()) { + end_of_data = true; + return m_binary; + } + } + return {}; + } + +private: + bool end_of_data = false; + const BinaryColumn* m_binary_col = nullptr; + size_t m_ndx = 0; + size_t m_pos = 0; + BinaryData m_binary; +}; + + +// Implementation + +// LCOV_EXCL_START +inline StringData BinaryColumn::get_index_data(size_t, StringIndex::StringConversionBuffer&) const noexcept +{ + REALM_ASSERT(false && "Index not implemented for BinaryColumn."); + REALM_UNREACHABLE(); +} +// LCOV_EXCL_STOP + +inline size_t BinaryColumn::size() const noexcept +{ + if (root_is_leaf()) { + bool is_big = m_array->get_context_flag(); + if (!is_big) { + // Small blobs root leaf + ArrayBinary* leaf = static_cast(m_array.get()); + return leaf->size(); + } + // Big blobs root leaf + ArrayBigBlobs* leaf = static_cast(m_array.get()); + return leaf->size(); + } + // Non-leaf root + return static_cast(m_array.get())->get_bptree_size(); +} + +inline bool BinaryColumn::is_nullable() const noexcept +{ + return m_nullable; +} + +inline void BinaryColumn::update_from_parent(size_t old_baseline) noexcept +{ + if (root_is_leaf()) { + bool is_big = m_array->get_context_flag(); + if (!is_big) { + // Small blobs root leaf + REALM_ASSERT(dynamic_cast(m_array.get())); + ArrayBinary* leaf = static_cast(m_array.get()); + leaf->update_from_parent(old_baseline); + return; + } + // Big blobs root leaf + REALM_ASSERT(dynamic_cast(m_array.get())); + ArrayBigBlobs* leaf = static_cast(m_array.get()); + leaf->update_from_parent(old_baseline); + return; + } + // Non-leaf root + m_array->update_from_parent(old_baseline); +} + +inline BinaryData BinaryColumn::get(size_t ndx) const noexcept +{ + REALM_ASSERT_DEBUG(ndx < size()); + if (root_is_leaf()) { + bool is_big = m_array->get_context_flag(); + BinaryData ret; + if (!is_big) { + // Small blobs root leaf + ArrayBinary* leaf = static_cast(m_array.get()); + ret = leaf->get(ndx); + } + else { + // Big blobs root leaf + ArrayBigBlobs* leaf = static_cast(m_array.get()); + ret = leaf->get(ndx); + } + if (!m_nullable && ret.is_null()) + return BinaryData("", 0); // return empty string (non-null) + return ret; + } + + // Non-leaf root + std::pair p = static_cast(m_array.get())->get_bptree_leaf(ndx); + const char* leaf_header = p.first.get_addr(); + size_t ndx_in_leaf = p.second; + Allocator& alloc = m_array->get_alloc(); + bool is_big = Array::get_context_flag_from_header(leaf_header); + if (!is_big) { + // Small blobs + return ArrayBinary::get(leaf_header, ndx_in_leaf, alloc); + } + // Big blobs + return ArrayBigBlobs::get(leaf_header, ndx_in_leaf, alloc); +} + +inline bool BinaryColumn::is_null(size_t ndx) const noexcept +{ + return m_nullable && get(ndx).is_null(); +} + +inline StringData BinaryColumn::get_string(size_t ndx) const noexcept +{ + BinaryData bin = get(ndx); + REALM_ASSERT_3(0, <, bin.size()); + return StringData(bin.data(), bin.size() - 1); +} + +inline void BinaryColumn::set_string(size_t ndx, StringData value) +{ + if (value.is_null() && !m_nullable) + throw LogicError(LogicError::column_not_nullable); + + BinaryData bin(value.data(), value.size()); + bool add_zero_term = true; + set(ndx, bin, add_zero_term); +} + +inline void BinaryColumn::add(BinaryData value) +{ + if (value.is_null() && !m_nullable) + throw LogicError(LogicError::column_not_nullable); + + size_t row_ndx = realm::npos; + bool add_zero_term = false; + size_t num_rows = 1; + do_insert(row_ndx, value, add_zero_term, num_rows); // Throws +} + +inline void BinaryColumn::insert(size_t row_ndx, BinaryData value) +{ + if (value.is_null() && !m_nullable) + throw LogicError(LogicError::column_not_nullable); + + size_t column_size = this->size(); // Slow + REALM_ASSERT_3(row_ndx, <=, column_size); + size_t row_ndx_2 = row_ndx == column_size ? realm::npos : row_ndx; + bool add_zero_term = false; + size_t num_rows = 1; + do_insert(row_ndx_2, value, add_zero_term, num_rows); // Throws +} + +inline void BinaryColumn::set_null(size_t row_ndx) +{ + set(row_ndx, BinaryData{}); +} + +inline size_t BinaryColumn::find_first(BinaryData value) const +{ + for (size_t t = 0; t < size(); t++) + if (get(t) == value) + return t; + + return not_found; +} + + +inline void BinaryColumn::erase(size_t row_ndx) +{ + size_t last_row_ndx = size() - 1; // Note that size() is slow + bool is_last = row_ndx == last_row_ndx; + erase(row_ndx, is_last); // Throws +} + +inline void BinaryColumn::move_last_over(size_t row_ndx) +{ + size_t last_row_ndx = size() - 1; // Note that size() is slow + do_move_last_over(row_ndx, last_row_ndx); // Throws +} + +inline void BinaryColumn::clear() +{ + do_clear(); // Throws +} + +// Implementing pure virtual method of ColumnBase. +inline void BinaryColumn::insert_rows(size_t row_ndx, size_t num_rows_to_insert, size_t prior_num_rows, + bool insert_nulls) +{ + REALM_ASSERT_DEBUG(prior_num_rows == size()); + REALM_ASSERT(row_ndx <= prior_num_rows); + REALM_ASSERT(!insert_nulls || m_nullable); + + size_t row_ndx_2 = (row_ndx == prior_num_rows ? realm::npos : row_ndx); + BinaryData value = m_nullable ? BinaryData() : BinaryData("", 0); + bool add_zero_term = false; + do_insert(row_ndx_2, value, add_zero_term, num_rows_to_insert); // Throws +} + +// Implementing pure virtual method of ColumnBase. +inline void BinaryColumn::erase_rows(size_t row_ndx, size_t num_rows_to_erase, size_t prior_num_rows, bool) +{ + REALM_ASSERT_DEBUG(prior_num_rows == size()); + REALM_ASSERT(num_rows_to_erase <= prior_num_rows); + REALM_ASSERT(row_ndx <= prior_num_rows - num_rows_to_erase); + + bool is_last = (row_ndx + num_rows_to_erase == prior_num_rows); + for (size_t i = num_rows_to_erase; i > 0; --i) { + size_t row_ndx_2 = row_ndx + i - 1; + erase(row_ndx_2, is_last); // Throws + } +} + +// Implementing pure virtual method of ColumnBase. +inline void BinaryColumn::move_last_row_over(size_t row_ndx, size_t prior_num_rows, bool) +{ + REALM_ASSERT_DEBUG(prior_num_rows == size()); + REALM_ASSERT(row_ndx < prior_num_rows); + + size_t last_row_ndx = prior_num_rows - 1; + do_move_last_over(row_ndx, last_row_ndx); // Throws +} + +// Implementing pure virtual method of ColumnBase. +inline void BinaryColumn::clear(size_t, bool) +{ + do_clear(); // Throws +} + +inline void BinaryColumn::add_string(StringData value) +{ + size_t row_ndx = realm::npos; + BinaryData value_2(value.data(), value.size()); + bool add_zero_term = true; + size_t num_rows = 1; + do_insert(row_ndx, value_2, add_zero_term, num_rows); // Throws +} + +inline void BinaryColumn::insert_string(size_t row_ndx, StringData value) +{ + size_t column_size = this->size(); // Slow + REALM_ASSERT_3(row_ndx, <=, column_size); + size_t row_ndx_2 = row_ndx == column_size ? realm::npos : row_ndx; + BinaryData value_2(value.data(), value.size()); + bool add_zero_term = false; + size_t num_rows = 1; + do_insert(row_ndx_2, value_2, add_zero_term, num_rows); // Throws +} + +inline size_t BinaryColumn::get_size_from_ref(ref_type root_ref, Allocator& alloc) noexcept +{ + const char* root_header = alloc.translate(root_ref); + bool root_is_leaf = !Array::get_is_inner_bptree_node_from_header(root_header); + if (root_is_leaf) { + bool is_big = Array::get_context_flag_from_header(root_header); + if (!is_big) { + // Small blobs leaf + return ArrayBinary::get_size_from_header(root_header, alloc); + } + // Big blobs leaf + return ArrayBigBlobs::get_size_from_header(root_header); + } + return BpTreeNode::get_bptree_size_from_header(root_header); +} + + +} // namespace realm + +#endif // REALM_COLUMN_BINARY_HPP diff --git a/!main project/Pods/Realm/include/core/realm/column_fwd.hpp b/!main project/Pods/Realm/include/core/realm/column_fwd.hpp new file mode 100644 index 0000000..5b93cdc --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/column_fwd.hpp @@ -0,0 +1,58 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_COLUMN_FWD_HPP +#define REALM_COLUMN_FWD_HPP + +#include + +namespace realm { + +// Regular classes +class ColumnBase; +class StringColumn; +class StringEnumColumn; +class BinaryColumn; +class SubtableColumn; +class MixedColumn; +class LinkColumn; +class LinkListColumn; +class TimestampColumn; + +// Templated classes +template +class Column; +template +class BasicColumn; +template +class ColumnRandIterator; + +namespace util { +template +class Optional; +} + +// Shortcuts, aka typedefs. +using IntegerColumn = Column; +using IntNullColumn = Column>; +using DoubleColumn = Column; +using FloatColumn = Column; +using IntegerColumnIterator = ColumnRandIterator; +} // namespace realm + +#endif // REALM_COLUMN_FWD_HPP diff --git a/!main project/Pods/Realm/include/core/realm/column_link.hpp b/!main project/Pods/Realm/include/core/realm/column_link.hpp new file mode 100644 index 0000000..185a6c3 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/column_link.hpp @@ -0,0 +1,181 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_COLUMN_LINK_HPP +#define REALM_COLUMN_LINK_HPP + +#include +#include +#include + +namespace realm { + +/// A link column is an extension of an integer column (Column) and maintains +/// its node structure. +/// +/// The individual values in a link column are indexes of rows in the target +/// table (offset with one to allow zero to indicate null links.) The target +/// table is specified by the table descriptor. +class LinkColumn : public LinkColumnBase { +public: + using LinkColumnBase::LinkColumnBase; + ~LinkColumn() noexcept override; + + static ref_type create(Allocator&, size_t size = 0); + + bool is_nullable() const noexcept override; + + //@{ + + /// is_null_link() is shorthand for `get_link() == realm::npos`, + /// nullify_link() is shorthand foe `set_link(realm::npos)`, and + /// insert_null_link() is shorthand for + /// `insert_link(realm::npos)`. set_link() returns the original link, with + /// `realm::npos` indicating that it was null. + + size_t get_link(size_t row_ndx) const noexcept; + bool is_null(size_t row_ndx) const noexcept override; + bool is_null_link(size_t row_ndx) const noexcept; + size_t set_link(size_t row_ndx, size_t target_row_ndx); + void set_null(size_t row_ndx) override; + void nullify_link(size_t row_ndx); + void insert_link(size_t row_ndx, size_t target_row_ndx); + void insert_null_link(size_t row_ndx); + + //@} + + void insert_rows(size_t, size_t, size_t, bool) override; + void erase_rows(size_t, size_t, size_t, bool) override; + void move_last_row_over(size_t, size_t, bool) override; + void swap_rows(size_t, size_t) override; + void clear(size_t, bool) override; + void cascade_break_backlinks_to(size_t, CascadeState&) override; + void cascade_break_backlinks_to_all_rows(size_t, CascadeState&) override; + + void verify(const Table&, size_t) const override; + +protected: + friend class BacklinkColumn; + void do_nullify_link(size_t row_ndx, size_t old_target_row_ndx) override; + void do_update_link(size_t row_ndx, size_t old_target_row_ndx, size_t new_target_row_ndx) override; + void do_swap_link(size_t row_ndx, size_t target_row_ndx_1, size_t target_row_ndx_2) override; + +private: + void remove_backlinks(size_t row_ndx); +}; + + +// Implementation + +inline LinkColumn::~LinkColumn() noexcept +{ +} + +inline bool LinkColumn::is_nullable() const noexcept +{ + return true; +} + +inline ref_type LinkColumn::create(Allocator& alloc, size_t size) +{ + return IntegerColumn::create(alloc, Array::type_Normal, size); // Throws +} + +inline bool LinkColumn::is_null(size_t row_ndx) const noexcept +{ + // Null is represented by zero + return LinkColumnBase::get(row_ndx) == 0; +} + +inline size_t LinkColumn::get_link(size_t row_ndx) const noexcept +{ + // Map zero to realm::npos, and `n+1` to `n`, where `n` is a target row index. + return to_size_t(LinkColumnBase::get(row_ndx)) - size_t(1); +} + +inline bool LinkColumn::is_null_link(size_t row_ndx) const noexcept +{ + return is_null(row_ndx); +} + +inline size_t LinkColumn::set_link(size_t row_ndx, size_t target_row_ndx) +{ + int_fast64_t old_value = LinkColumnBase::get(row_ndx); + size_t old_target_row_ndx = to_size_t(old_value) - size_t(1); + if (old_target_row_ndx == target_row_ndx) + return old_target_row_ndx; // Nothing to do + if (old_value != 0) + m_backlink_column->remove_one_backlink(old_target_row_ndx, row_ndx); // Throws + + int_fast64_t new_value = int_fast64_t(size_t(1) + target_row_ndx); + LinkColumnBase::set(row_ndx, new_value); // Throws + + if (target_row_ndx != realm::npos) + m_backlink_column->add_backlink(target_row_ndx, row_ndx); // Throws + + return old_target_row_ndx; +} + +inline void LinkColumn::set_null(size_t row_ndx) +{ + set_link(row_ndx, realm::npos); // Throws +} + +inline void LinkColumn::nullify_link(size_t row_ndx) +{ + set_null(row_ndx); // Throws +} + +inline void LinkColumn::insert_link(size_t row_ndx, size_t target_row_ndx) +{ + int_fast64_t value = int_fast64_t(size_t(1) + target_row_ndx); + LinkColumnBase::insert(row_ndx, value); // Throws + + if (target_row_ndx != realm::npos) + m_backlink_column->add_backlink(target_row_ndx, row_ndx); // Throws +} + +inline void LinkColumn::insert_null_link(size_t row_ndx) +{ + insert_link(row_ndx, realm::npos); // Throws +} + +inline void LinkColumn::do_update_link(size_t row_ndx, size_t, size_t new_target_row_ndx) +{ + // Row pos is offset by one, to allow null refs + LinkColumnBase::set(row_ndx, new_target_row_ndx + 1); +} + +inline void LinkColumn::do_swap_link(size_t row_ndx, size_t target_row_ndx_1, size_t target_row_ndx_2) +{ + // Row pos is offset by one, to allow null refs + ++target_row_ndx_1; + ++target_row_ndx_2; + + uint64_t value = LinkColumnBase::get_uint(row_ndx); + if (value == target_row_ndx_1) { + LinkColumnBase::set_uint(row_ndx, target_row_ndx_2); + } + else if (value == target_row_ndx_2) { + LinkColumnBase::set_uint(row_ndx, target_row_ndx_1); + } +} + +} // namespace realm + +#endif // REALM_COLUMN_LINK_HPP diff --git a/!main project/Pods/Realm/include/core/realm/column_linkbase.hpp b/!main project/Pods/Realm/include/core/realm/column_linkbase.hpp new file mode 100644 index 0000000..fa67fab --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/column_linkbase.hpp @@ -0,0 +1,206 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_COLUMN_LINKBASE_HPP +#define REALM_COLUMN_LINKBASE_HPP + +#include + +namespace realm { + +class BacklinkColumn; +class Table; + +// Abstract base class for columns containing links +class LinkColumnBase : public IntegerColumn { +public: + // Create unattached root array aaccessor. + LinkColumnBase(Allocator& alloc, ref_type ref, Table* table, size_t column_ndx); + ~LinkColumnBase() noexcept override; + + bool is_nullable() const noexcept override = 0; + void set_null(size_t) override = 0; + bool is_null(size_t) const noexcept override = 0; + + bool supports_search_index() const noexcept final + { + return false; + } + StringIndex* create_search_index() override; + + bool get_weak_links() const noexcept; + void set_weak_links(bool) noexcept; + + Table& get_target_table() const noexcept; + void set_target_table(Table&) noexcept; + BacklinkColumn& get_backlink_column() const noexcept; + void set_backlink_column(BacklinkColumn&) noexcept; + + void swap_rows(size_t, size_t) override = 0; + + virtual void do_nullify_link(size_t row_ndx, size_t old_target_row_ndx) = 0; + virtual void do_update_link(size_t row_ndx, size_t old_target_row_ndx, size_t new_target_row_ndx) = 0; + virtual void do_swap_link(size_t row_ndx, size_t target_row_ndx_1, size_t target_row_ndx_2) = 0; + + void adj_acc_insert_rows(size_t, size_t) noexcept override; + void adj_acc_erase_row(size_t) noexcept override; + void adj_acc_move_over(size_t, size_t) noexcept override; + void adj_acc_swap_rows(size_t, size_t) noexcept override; + void adj_acc_move_row(size_t, size_t) noexcept override; + void adj_acc_clear_root_table() noexcept override; + void mark(int) noexcept override; + void refresh_accessor_tree(size_t, const Spec&) override; + void bump_link_origin_table_version() noexcept override; + + void verify(const Table&, size_t) const override; + using IntegerColumn::verify; + +protected: + // A pointer to the table that this column is part of. + Table* const m_table; + + TableRef m_target_table; + BacklinkColumn* m_backlink_column = nullptr; + bool m_weak_links = false; // True if these links are weak (not strong) + + /// Call Table::cascade_break_backlinks_to() for the specified target row if + /// it is not already in \a state.rows, and the number of strong links to it + /// has dropped to zero. + void check_cascade_break_backlinks_to(size_t target_table_ndx, size_t target_row_ndx, CascadeState& state); +}; + + +// Implementation + +inline LinkColumnBase::LinkColumnBase(Allocator& alloc, ref_type ref, Table* table, size_t column_ndx) + : IntegerColumn(alloc, ref, column_ndx) // Throws + , m_table(table) +{ +} + +inline LinkColumnBase::~LinkColumnBase() noexcept +{ +} + +inline StringIndex* LinkColumnBase::create_search_index() +{ + return nullptr; +} + +inline bool LinkColumnBase::get_weak_links() const noexcept +{ + return m_weak_links; +} + +inline void LinkColumnBase::set_weak_links(bool value) noexcept +{ + m_weak_links = value; +} + +inline Table& LinkColumnBase::get_target_table() const noexcept +{ + return *m_target_table; +} + +inline void LinkColumnBase::set_target_table(Table& table) noexcept +{ + REALM_ASSERT(!m_target_table); + m_target_table = table.get_table_ref(); +} + +inline BacklinkColumn& LinkColumnBase::get_backlink_column() const noexcept +{ + return *m_backlink_column; +} + +inline void LinkColumnBase::set_backlink_column(BacklinkColumn& column) noexcept +{ + m_backlink_column = &column; +} + +inline void LinkColumnBase::adj_acc_insert_rows(size_t row_ndx, size_t num_rows) noexcept +{ + IntegerColumn::adj_acc_insert_rows(row_ndx, num_rows); + + typedef _impl::TableFriend tf; + tf::mark(*m_target_table); +} + +inline void LinkColumnBase::adj_acc_erase_row(size_t row_ndx) noexcept +{ + IntegerColumn::adj_acc_erase_row(row_ndx); + + typedef _impl::TableFriend tf; + tf::mark(*m_target_table); +} + +inline void LinkColumnBase::adj_acc_move_over(size_t from_row_ndx, size_t to_row_ndx) noexcept +{ + IntegerColumn::adj_acc_move_over(from_row_ndx, to_row_ndx); + + typedef _impl::TableFriend tf; + tf::mark(*m_target_table); +} + +inline void LinkColumnBase::adj_acc_swap_rows(size_t row_ndx_1, size_t row_ndx_2) noexcept +{ + IntegerColumn::adj_acc_swap_rows(row_ndx_1, row_ndx_2); + + typedef _impl::TableFriend tf; + tf::mark(*m_target_table); +} + +inline void LinkColumnBase::adj_acc_move_row(size_t from_ndx, size_t to_ndx) noexcept +{ + IntegerColumn::adj_acc_move_row(from_ndx, to_ndx); + + using tf = _impl::TableFriend; + tf::mark(*m_target_table); +} + +inline void LinkColumnBase::adj_acc_clear_root_table() noexcept +{ + IntegerColumn::adj_acc_clear_root_table(); + + typedef _impl::TableFriend tf; + tf::mark(*m_target_table); +} + +inline void LinkColumnBase::mark(int type) noexcept +{ + if (type & mark_LinkTargets) { + typedef _impl::TableFriend tf; + tf::mark(*m_target_table); + } +} + +inline void LinkColumnBase::bump_link_origin_table_version() noexcept +{ + // It is important to mark connected tables as modified. + // Also see BacklinkColumn::bump_link_origin_table_version(). + typedef _impl::TableFriend tf; + if (m_target_table) { + bool bump_global = false; + tf::bump_version(*m_target_table, bump_global); + } +} + + +} // namespace realm + +#endif // REALM_COLUMN_LINKBASE_HPP diff --git a/!main project/Pods/Realm/include/core/realm/column_linklist.hpp b/!main project/Pods/Realm/include/core/realm/column_linklist.hpp new file mode 100644 index 0000000..3dab1b3 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/column_linklist.hpp @@ -0,0 +1,248 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_COLUMN_LINKLIST_HPP +#define REALM_COLUMN_LINKLIST_HPP + +#include +#include + +#include +#include +#include +#include +#include + +namespace realm { + +namespace _impl { +class TransactLogConvenientEncoder; +} + + +/// A column of link lists (LinkListColumn) is a single B+-tree, and the root of +/// the column is the root of the B+-tree. All leaf nodes are single arrays of +/// type Array with the hasRefs bit set. +/// +/// The individual values in the column are either refs to Columns containing the +/// row positions in the target table, or in the case where they are empty, a zero +/// ref. +class LinkListColumn : public LinkColumnBase, public ArrayParent { +public: + using LinkColumnBase::LinkColumnBase; + using value_type = ConstLinkViewRef; + LinkListColumn(Allocator& alloc, ref_type ref, Table* table, size_t column_ndx); + ~LinkListColumn() noexcept override; + + static ref_type create(Allocator&, size_t size = 0); + + bool is_nullable() const noexcept final; + + bool has_links(size_t row_ndx) const noexcept; + size_t get_link_count(size_t row_ndx) const noexcept; + + ConstLinkViewRef get(size_t row_ndx) const; + LinkViewRef get(size_t row_ndx); + + bool is_null(size_t row_ndx) const noexcept final; + void set_null(size_t row_ndx) final; + + /// Compare two columns for equality. + bool compare_link_list(const LinkListColumn&) const; + + void to_json_row(size_t row_ndx, std::ostream& out) const; + + void insert_rows(size_t, size_t, size_t, bool) override; + void erase_rows(size_t, size_t, size_t, bool) override; + void move_last_row_over(size_t, size_t, bool) override; + void swap_rows(size_t, size_t) override; + void clear(size_t, bool) override; + void cascade_break_backlinks_to(size_t, CascadeState&) override; + void cascade_break_backlinks_to_all_rows(size_t, CascadeState&) override; + void update_from_parent(size_t) noexcept override; + void adj_acc_clear_root_table() noexcept override; + void adj_acc_insert_rows(size_t, size_t) noexcept override; + void adj_acc_erase_row(size_t) noexcept override; + void adj_acc_move_over(size_t, size_t) noexcept override; + void adj_acc_swap_rows(size_t, size_t) noexcept override; + void adj_acc_move_row(size_t, size_t) noexcept override; + void adj_acc_merge_rows(size_t, size_t) noexcept override; + void refresh_accessor_tree(size_t, const Spec&) override; + + void verify() const override; + void verify(const Table&, size_t) const override; + +protected: + void do_discard_child_accessors() noexcept override; + +private: + struct list_entry { + size_t m_row_ndx; + std::weak_ptr m_list; + bool operator<(const list_entry& other) const + { + return m_row_ndx < other.m_row_ndx; + } + }; + + // The accessors stored in `m_list_accessors` are sorted by their row index. + // When a LinkList accessor is destroyed because the last shared_ptr pointing + // to it dies, its entry is implicitly replaced by a tombstone (an entry with + // an empty `m_list`). These tombstones are pruned at a later time by + // `prune_list_accessor_tombstones`. This is done to amortize the O(n) cost + // of `std::vector::erase` that would otherwise be incurred each time an + // accessor is removed. + mutable std::vector m_list_accessors; + mutable std::atomic m_list_accessors_contains_tombstones; + + std::shared_ptr get_ptr(size_t row_ndx) const; + + void do_nullify_link(size_t row_ndx, size_t old_target_row_ndx) override; + void do_update_link(size_t row_ndx, size_t old_target_row_ndx, size_t new_target_row_ndx) override; + void do_swap_link(size_t row_ndx, size_t target_row_ndx_1, size_t target_row_ndx_2) override; + + void unregister_linkview(); + ref_type get_row_ref(size_t row_ndx) const noexcept; + void set_row_ref(size_t row_ndx, ref_type new_ref); + void add_backlink(size_t target_row, size_t source_row); + void remove_backlink(size_t target_row, size_t source_row); + + // ArrayParent overrides + void update_child_ref(size_t child_ndx, ref_type new_ref) override; + ref_type get_child_ref(size_t child_ndx) const noexcept override; + + // These helpers are needed because of the way the B+-tree of links is + // traversed in cascade_break_backlinks_to() and + // cascade_break_backlinks_to_all_rows(). + void cascade_break_backlinks_to__leaf(size_t row_ndx, const Array& link_list_leaf, CascadeState&); + void cascade_break_backlinks_to_all_rows__leaf(const Array& link_list_leaf, CascadeState&); + + void discard_child_accessors() noexcept; + + template + void adj_insert_rows(size_t row_ndx, size_t num_rows_inserted) noexcept; + + template + void adj_erase_rows(size_t row_ndx, size_t num_rows_erased) noexcept; + + template + void adj_move_over(size_t from_row_ndx, size_t to_row_ndx) noexcept; + + template + void adj_move(size_t from_ndx, size_t to_ndx) noexcept; + + template + void adj_swap(size_t row_ndx_1, size_t row_ndx_2) noexcept; + + void prune_list_accessor_tombstones() noexcept; + void validate_list_accessors() const noexcept; + + std::pair get_to_dot_parent(size_t) const override; + + friend class BacklinkColumn; + friend class LinkView; + friend class _impl::TransactLogConvenientEncoder; +}; + + +// Implementation + +inline LinkListColumn::LinkListColumn(Allocator& alloc, ref_type ref, Table* table, size_t column_ndx) + : LinkColumnBase(alloc, ref, table, column_ndx) +{ + m_list_accessors_contains_tombstones.store(false); +} + +inline LinkListColumn::~LinkListColumn() noexcept +{ + discard_child_accessors(); +} + +inline ref_type LinkListColumn::create(Allocator& alloc, size_t size) +{ + return IntegerColumn::create(alloc, Array::type_HasRefs, size); // Throws +} + +inline bool LinkListColumn::is_nullable() const noexcept +{ + return false; +} + +inline bool LinkListColumn::has_links(size_t row_ndx) const noexcept +{ + ref_type ref = LinkColumnBase::get_as_ref(row_ndx); + return (ref != 0); +} + +inline size_t LinkListColumn::get_link_count(size_t row_ndx) const noexcept +{ + ref_type ref = LinkColumnBase::get_as_ref(row_ndx); + if (ref == 0) + return 0; + return ColumnBase::get_size_from_ref(ref, get_alloc()); +} + +inline ConstLinkViewRef LinkListColumn::get(size_t row_ndx) const +{ + return get_ptr(row_ndx); +} + +inline LinkViewRef LinkListColumn::get(size_t row_ndx) +{ + return get_ptr(row_ndx); +} + +inline bool LinkListColumn::is_null(size_t) const noexcept +{ + return false; +} + +inline void LinkListColumn::set_null(size_t) +{ + throw LogicError{LogicError::column_not_nullable}; +} + +inline void LinkListColumn::do_discard_child_accessors() noexcept +{ + discard_child_accessors(); +} + +inline ref_type LinkListColumn::get_row_ref(size_t row_ndx) const noexcept +{ + return LinkColumnBase::get_as_ref(row_ndx); +} + +inline void LinkListColumn::set_row_ref(size_t row_ndx, ref_type new_ref) +{ + LinkColumnBase::set(row_ndx, new_ref); // Throws +} + +inline void LinkListColumn::add_backlink(size_t target_row, size_t source_row) +{ + m_backlink_column->add_backlink(target_row, source_row); // Throws +} + +inline void LinkListColumn::remove_backlink(size_t target_row, size_t source_row) +{ + m_backlink_column->remove_one_backlink(target_row, source_row); // Throws +} + + +} // namespace realm + +#endif // REALM_COLUMN_LINKLIST_HPP diff --git a/!main project/Pods/Realm/include/core/realm/column_mixed.hpp b/!main project/Pods/Realm/include/core/realm/column_mixed.hpp new file mode 100644 index 0000000..5f74c41 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/column_mixed.hpp @@ -0,0 +1,265 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_COLUMN_MIXED_HPP +#define REALM_COLUMN_MIXED_HPP + +#include + +#include +#include +#include +#include +#include + + +namespace realm { + + +// Pre-declarations +class BinaryColumn; + + +/// A mixed column (MixedColumn) is composed of three subcolumns. The first +/// subcolumn is an integer column (Column) and stores value types. The second +/// one stores values and is a subtable parent column (SubtableColumnBase), +/// which is a subclass of an integer column (Column). The last one is a binary +/// column (BinaryColumn) and stores additional data for values of type string +/// or binary data. The last subcolumn is optional. The root of a mixed column +/// is an array node of type Array that stores the root refs of the subcolumns. +class MixedColumn : public ColumnBaseSimple { +public: + /// Create a mixed column wrapper and attach it to a preexisting + /// underlying structure of arrays. + /// + /// \param alloc The memory allocator to change the underlying + /// structure in memory. + /// + /// \param ref The memory reference of the MixedColumn for which + /// this accessor should be creator for. + /// + /// \param table If this column is used as part of a table you + /// must pass a pointer to that table. Otherwise you must pass + /// null + /// + /// \param column_ndx If this column is used as part of a table + /// you must pass the logical index of the column within that + /// table. Otherwise you should pass zero. + MixedColumn(Allocator& alloc, ref_type ref, Table* table, size_t column_ndx); + + ~MixedColumn() noexcept override; + + DataType get_type(size_t ndx) const noexcept; + size_t size() const noexcept final + { + return m_types->size(); + } + bool is_empty() const noexcept + { + return size() == 0; + } + + int64_t get_int(size_t ndx) const noexcept; + bool get_bool(size_t ndx) const noexcept; + OldDateTime get_olddatetime(size_t ndx) const noexcept; + Timestamp get_timestamp(size_t ndx) const noexcept; + float get_float(size_t ndx) const noexcept; + double get_double(size_t ndx) const noexcept; + StringData get_string(size_t ndx) const noexcept; + BinaryData get_binary(size_t ndx) const noexcept; + StringData get_index_data(size_t ndx, StringIndex::StringConversionBuffer& buffer) const noexcept override; + + /// The returned array ref is zero if the specified row does not + /// contain a subtable. + ref_type get_subtable_ref(size_t row_ndx) const noexcept; + + /// The returned size is zero if the specified row does not + /// contain a subtable. + size_t get_subtable_size(size_t row_ndx) const noexcept; + + TableRef get_subtable_accessor(size_t row_ndx) const noexcept override; + + void discard_subtable_accessor(size_t row_ndx) noexcept override; + + /// If the value at the specified index is a subtable, return a + /// TableRef to that accessor for that subtable. Otherwise return + /// null. The accessor will be created if it does not already + /// exist. + TableRef get_subtable_tableref(size_t row_ndx); + + ConstTableRef get_subtable_tableref(size_t subtable_ndx) const; + + void set_int(size_t ndx, int64_t value); + void set_bool(size_t ndx, bool value); + void set_olddatetime(size_t ndx, OldDateTime value); + void set_timestamp(size_t ndx, Timestamp value); + void set_float(size_t ndx, float value); + void set_double(size_t ndx, double value); + void set_string(size_t ndx, StringData value) override; + void set_binary(size_t ndx, BinaryData value); + void set_subtable(size_t ndx, const Table* value); + + void insert_int(size_t ndx, int_fast64_t value); + void insert_bool(size_t ndx, bool value); + void insert_olddatetime(size_t ndx, OldDateTime value); + void insert_timestamp(size_t ndx, Timestamp value); + void insert_float(size_t ndx, float value); + void insert_double(size_t ndx, double value); + void insert_string(size_t ndx, StringData value); + void insert_binary(size_t ndx, BinaryData value); + void insert_subtable(size_t ndx, const Table* value); + + void erase(size_t row_ndx); + void move_last_over(size_t row_ndx); + void clear(); + + /// Compare two mixed columns for equality. + bool compare_mixed(const MixedColumn&) const; + + int compare_values(size_t row1, size_t row2) const noexcept override; + + void discard_child_accessors() noexcept; + + static ref_type create(Allocator&, size_t size = 0); + + static size_t get_size_from_ref(ref_type root_ref, Allocator&) noexcept; + + // Overriding method in ColumnBase + ref_type write(size_t, size_t, size_t, _impl::OutputStream&) const override; + + void insert_rows(size_t, size_t, size_t, bool) override; + void erase_rows(size_t, size_t, size_t, bool) override; + void move_last_row_over(size_t, size_t, bool) override; + void swap_rows(size_t, size_t) override; + void clear(size_t, bool) override; + void update_from_parent(size_t) noexcept override; + void adj_acc_insert_rows(size_t, size_t) noexcept override; + void adj_acc_erase_row(size_t) noexcept override; + void adj_acc_move_over(size_t, size_t) noexcept override; + void adj_acc_swap_rows(size_t, size_t) noexcept override; + void adj_acc_move_row(size_t, size_t) noexcept override; + void adj_acc_clear_root_table() noexcept override; + void mark(int) noexcept override; + void refresh_accessor_tree(size_t, const Spec&) override; + + void verify() const override; + void verify(const Table&, size_t) const override; + void to_dot(std::ostream&, StringData title) const override; + void do_dump_node_structure(std::ostream&, int) const override; + +private: + enum MixedColType { + // NOTE: below numbers must be kept in sync with ColumnType + // Column types used in Mixed + mixcol_Int = 0, + mixcol_Bool = 1, + mixcol_String = 2, + // 3, used for STRING_ENUM in ColumnType + mixcol_Binary = 4, + mixcol_Table = 5, + mixcol_Mixed = 6, + mixcol_OldDateTime = 7, + mixcol_Timestamp = 8, + mixcol_Float = 9, + mixcol_Double = 10, // Positive Double + mixcol_DoubleNeg = 11, // Negative Double + mixcol_IntNeg = 12 // Negative Integers + }; + + class RefsColumn; + + /// Stores the MixedColType of each value at the given index. For + /// values that uses all 64 bits, the type also encodes the sign + /// bit by having distinct types for positive negative values. + std::unique_ptr m_types; + + /// Stores the data for each entry. For a subtable, the stored + /// value is the ref of the subtable. For string, binary data, + /// the stored value is an index within `m_binary_data`. Likewise, + /// for timestamp, an index into `m_timestamp` is stored. For other + /// types the stored value is itself. Since we only have 63 bits + /// available for a non-ref value, the sign of numeric values is + /// encoded as part of the type in `m_types`. + std::unique_ptr m_data; + + /// For string and binary data types, the bytes are stored here. + std::unique_ptr m_binary_data; + + /// Timestamps are stored here. + std::unique_ptr m_timestamp_data; + + void do_erase(size_t row_ndx, size_t num_rows_to_erase, size_t prior_num_rows); + void do_move_last_over(size_t row_ndx, size_t prior_num_rows); + void do_swap_rows(size_t, size_t); + void do_clear(size_t num_rows); + + void create(Allocator&, ref_type, Table*, size_t column_ndx); + void ensure_binary_data_column(); + void ensure_timestamp_column(); + + MixedColType clear_value(size_t ndx, MixedColType new_type); // Returns old type + void clear_value_and_discard_subtab_acc(size_t ndx, MixedColType new_type); + + // Get/set/insert 64-bit values in m_data/m_types + int64_t get_value(size_t ndx) const noexcept; + void set_value(size_t ndx, int64_t value, MixedColType); + void set_int64(size_t ndx, int64_t value, MixedColType pos_type, MixedColType neg_type); + + void insert_value(size_t row_ndx, int_fast64_t types_value, int_fast64_t data_value); + void insert_int(size_t ndx, int_fast64_t value, MixedColType type); + void insert_pos_neg(size_t ndx, int_fast64_t value, MixedColType pos_type, MixedColType neg_type); + + void do_discard_child_accessors() noexcept override; + +#ifdef REALM_DEBUG + void do_verify(const Table*, size_t col_ndx) const; +#endif + void leaf_to_dot(MemRef, ArrayParent*, size_t, std::ostream&) const override; +}; + +// LCOV_EXCL_START +inline StringData MixedColumn::get_index_data(size_t, StringIndex::StringConversionBuffer&) const noexcept +{ + REALM_ASSERT(false && "Index not supported for MixedColumn yet."); + REALM_UNREACHABLE(); + return {}; +} +// LCOV_EXCL_STOP + + +class MixedColumn::RefsColumn : public SubtableColumnBase { +public: + RefsColumn(Allocator& alloc, ref_type ref, Table* table, size_t column_ndx); + ~RefsColumn() noexcept override; + + using SubtableColumnBase::get_subtable_tableref; + + void refresh_accessor_tree(size_t, const Spec&) override; + + friend class MixedColumn; +}; + + +} // namespace realm + + +// Implementation +#include + + +#endif // REALM_COLUMN_MIXED_HPP diff --git a/!main project/Pods/Realm/include/core/realm/column_mixed_tpl.hpp b/!main project/Pods/Realm/include/core/realm/column_mixed_tpl.hpp new file mode 100644 index 0000000..5afdddc --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/column_mixed_tpl.hpp @@ -0,0 +1,514 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#include +#include + +namespace realm { + +inline MixedColumn::MixedColumn(Allocator& alloc, ref_type ref, Table* table, size_t column_ndx) + : ColumnBaseSimple(column_ndx) +{ + create(alloc, ref, table, column_ndx); +} + +inline void MixedColumn::adj_acc_insert_rows(size_t row_ndx, size_t num_rows) noexcept +{ + m_data->adj_acc_insert_rows(row_ndx, num_rows); +} + +inline void MixedColumn::adj_acc_erase_row(size_t row_ndx) noexcept +{ + m_data->adj_acc_erase_row(row_ndx); +} + +inline void MixedColumn::adj_acc_swap_rows(size_t row_ndx_1, size_t row_ndx_2) noexcept +{ + m_data->adj_acc_swap_rows(row_ndx_1, row_ndx_2); +} + +inline void MixedColumn::adj_acc_move_row(size_t from_ndx, size_t to_ndx) noexcept +{ + m_data->adj_acc_move_row(from_ndx, to_ndx); +} + +inline void MixedColumn::adj_acc_move_over(size_t from_row_ndx, size_t to_row_ndx) noexcept +{ + m_data->adj_acc_move_over(from_row_ndx, to_row_ndx); +} + +inline void MixedColumn::adj_acc_clear_root_table() noexcept +{ + m_data->adj_acc_clear_root_table(); +} + +inline ref_type MixedColumn::get_subtable_ref(size_t row_ndx) const noexcept +{ + REALM_ASSERT_3(row_ndx, <, m_types->size()); + if (m_types->get(row_ndx) != type_Table) + return 0; + return m_data->get_as_ref(row_ndx); +} + +inline size_t MixedColumn::get_subtable_size(size_t row_ndx) const noexcept +{ + ref_type top_ref = get_subtable_ref(row_ndx); + if (top_ref == 0) + return 0; + return _impl::TableFriend::get_size_from_ref(top_ref, m_data->get_alloc()); +} + +inline TableRef MixedColumn::get_subtable_accessor(size_t row_ndx) const noexcept +{ + return m_data->get_subtable_accessor(row_ndx); +} + +inline void MixedColumn::discard_subtable_accessor(size_t row_ndx) noexcept +{ + m_data->discard_subtable_accessor(row_ndx); +} + +inline TableRef MixedColumn::get_subtable_tableref(size_t row_ndx) +{ + REALM_ASSERT_3(row_ndx, <, m_types->size()); + if (m_types->get(row_ndx) != type_Table) + return {}; + return m_data->get_subtable_tableref(row_ndx); // Throws +} + +inline ConstTableRef MixedColumn::get_subtable_tableref(size_t subtable_ndx) const +{ + return const_cast(this)->get_subtable_tableref(subtable_ndx); +} + +inline void MixedColumn::discard_child_accessors() noexcept +{ + m_data->discard_child_accessors(); +} + + +// +// Getters +// + +#define REALM_BIT63 0x8000000000000000ULL + +inline int64_t MixedColumn::get_value(size_t ndx) const noexcept +{ + REALM_ASSERT_3(ndx, <, m_types->size()); + + // Shift the unsigned value right - ensuring 0 gets in from left. + // Shifting signed integers right doesn't ensure 0's. + uint64_t value = uint64_t(m_data->get(ndx)) >> 1; + return int64_t(value); +} + +inline int64_t MixedColumn::get_int(size_t ndx) const noexcept +{ + // Get first 63 bits of the integer value + int64_t value = get_value(ndx); + + // restore 'sign'-bit from the column-type + MixedColType col_type = MixedColType(m_types->get(ndx)); + if (col_type == mixcol_IntNeg) { + // FIXME: Bad cast of result of '|' from unsigned to signed + value |= REALM_BIT63; // set sign bit (63) + } + else { + REALM_ASSERT_3(col_type, ==, mixcol_Int); + } + return value; +} + +inline bool MixedColumn::get_bool(size_t ndx) const noexcept +{ + REALM_ASSERT_3(m_types->get(ndx), ==, mixcol_Bool); + + return (get_value(ndx) != 0); +} + +inline OldDateTime MixedColumn::get_olddatetime(size_t ndx) const noexcept +{ + REALM_ASSERT_3(m_types->get(ndx), ==, mixcol_OldDateTime); + + return OldDateTime(get_value(ndx)); +} + +inline float MixedColumn::get_float(size_t ndx) const noexcept +{ + static_assert(std::numeric_limits::is_iec559, "'float' is not IEEE"); + static_assert((sizeof(float) * CHAR_BIT == 32), "Assume 32 bit float."); + REALM_ASSERT_3(m_types->get(ndx), ==, mixcol_Float); + + return type_punning(get_value(ndx)); +} + +inline double MixedColumn::get_double(size_t ndx) const noexcept +{ + static_assert(std::numeric_limits::is_iec559, "'double' is not IEEE"); + static_assert((sizeof(double) * CHAR_BIT == 64), "Assume 64 bit double."); + + int64_t int_val = get_value(ndx); + + // restore 'sign'-bit from the column-type + MixedColType col_type = MixedColType(m_types->get(ndx)); + if (col_type == mixcol_DoubleNeg) + int_val |= REALM_BIT63; // set sign bit (63) + else { + REALM_ASSERT_3(col_type, ==, mixcol_Double); + } + return type_punning(int_val); +} + +inline StringData MixedColumn::get_string(size_t ndx) const noexcept +{ + REALM_ASSERT_3(ndx, <, m_types->size()); + REALM_ASSERT_3(m_types->get(ndx), ==, mixcol_String); + REALM_ASSERT(m_binary_data); + + size_t data_ndx = size_t(int64_t(m_data->get(ndx)) >> 1); + return m_binary_data->get_string(data_ndx); +} + +inline BinaryData MixedColumn::get_binary(size_t ndx) const noexcept +{ + REALM_ASSERT_3(ndx, <, m_types->size()); + REALM_ASSERT_3(m_types->get(ndx), ==, mixcol_Binary); + REALM_ASSERT(m_binary_data); + + size_t data_ndx = size_t(uint64_t(m_data->get(ndx)) >> 1); + return m_binary_data->get(data_ndx); +} + +inline Timestamp MixedColumn::get_timestamp(size_t ndx) const noexcept +{ + REALM_ASSERT_3(ndx, <, m_types->size()); + REALM_ASSERT_3(m_types->get(ndx), ==, mixcol_Timestamp); + REALM_ASSERT(m_timestamp_data); + size_t data_ndx = size_t(uint64_t(m_data->get(ndx)) >> 1); + return m_timestamp_data->get(data_ndx); +} + +// +// Setters +// + +// Set a int64 value. +// Store 63 bit of the value in m_data. Store sign bit in m_types. + +inline void MixedColumn::set_int64(size_t ndx, int64_t value, MixedColType pos_type, MixedColType neg_type) +{ + REALM_ASSERT_3(ndx, <, m_types->size()); + + // If sign-bit is set in value, 'store' it in the column-type + MixedColType coltype = ((value & REALM_BIT63) == 0) ? pos_type : neg_type; + + // Remove refs or binary data (sets type to double) + clear_value_and_discard_subtab_acc(ndx, coltype); // Throws + + // Shift value one bit and set lowest bit to indicate that this is not a ref + value = (value << 1) + 1; + m_data->set(ndx, value); +} + +inline void MixedColumn::set_int(size_t ndx, int64_t value) +{ + set_int64(ndx, value, mixcol_Int, mixcol_IntNeg); // Throws +} + +inline void MixedColumn::set_double(size_t ndx, double value) +{ + int64_t val64 = type_punning(value); + set_int64(ndx, val64, mixcol_Double, mixcol_DoubleNeg); // Throws +} + +inline void MixedColumn::set_value(size_t ndx, int64_t value, MixedColType coltype) +{ + REALM_ASSERT_3(ndx, <, m_types->size()); + + // Remove refs or binary data (sets type to float) + clear_value_and_discard_subtab_acc(ndx, coltype); // Throws + + // Shift value one bit and set lowest bit to indicate that this is not a ref + int64_t v = (value << 1) + 1; + m_data->set(ndx, v); // Throws +} + +inline void MixedColumn::set_float(size_t ndx, float value) +{ + int64_t val64 = type_punning(value); + set_value(ndx, val64, mixcol_Float); // Throws +} + +inline void MixedColumn::set_bool(size_t ndx, bool value) +{ + set_value(ndx, (value ? 1 : 0), mixcol_Bool); // Throws +} + +inline void MixedColumn::set_olddatetime(size_t ndx, OldDateTime value) +{ + set_value(ndx, int64_t(value.get_olddatetime()), mixcol_OldDateTime); // Throws +} + +inline void MixedColumn::set_subtable(size_t ndx, const Table* t) +{ + REALM_ASSERT_3(ndx, <, m_types->size()); + typedef _impl::TableFriend tf; + ref_type ref; + if (t) { + ref = tf::clone(*t, get_alloc()); // Throws + } + else { + ref = tf::create_empty_table(get_alloc()); // Throws + } + // Remove any previous refs or binary data + clear_value_and_discard_subtab_acc(ndx, mixcol_Table); // Throws + m_data->set(ndx, ref); // Throws +} + +// +// Inserts +// + +inline void MixedColumn::insert_value(size_t row_ndx, int_fast64_t types_value, int_fast64_t data_value) +{ + size_t types_size = m_types->size(); // Slow + bool is_append = row_ndx == types_size; + size_t row_ndx_2 = is_append ? realm::npos : row_ndx; + size_t num_rows = 1; + m_types->insert_without_updating_index(row_ndx_2, types_value, num_rows); // Throws + m_data->do_insert(row_ndx_2, data_value, num_rows); // Throws +} + +// Insert a int64 value. +// Store 63 bit of the value in m_data. Store sign bit in m_types. + +inline void MixedColumn::insert_int(size_t ndx, int_fast64_t value, MixedColType type) +{ + int_fast64_t types_value = type; + // Shift value one bit and set lowest bit to indicate that this is not a ref + int_fast64_t data_value = 1 + (value << 1); + insert_value(ndx, types_value, data_value); // Throws +} + +inline void MixedColumn::insert_pos_neg(size_t ndx, int_fast64_t value, MixedColType pos_type, MixedColType neg_type) +{ + // 'store' the sign-bit in the integer-type + MixedColType type = (value & REALM_BIT63) == 0 ? pos_type : neg_type; + int_fast64_t types_value = type; + // Shift value one bit and set lowest bit to indicate that this is not a ref + int_fast64_t data_value = 1 + (value << 1); + insert_value(ndx, types_value, data_value); // Throws +} + +inline void MixedColumn::insert_int(size_t ndx, int_fast64_t value) +{ + insert_pos_neg(ndx, value, mixcol_Int, mixcol_IntNeg); // Throws +} + +inline void MixedColumn::insert_double(size_t ndx, double value) +{ + int_fast64_t value_2 = type_punning(value); + insert_pos_neg(ndx, value_2, mixcol_Double, mixcol_DoubleNeg); // Throws +} + +inline void MixedColumn::insert_float(size_t ndx, float value) +{ + int_fast64_t value_2 = type_punning(value); + insert_int(ndx, value_2, mixcol_Float); // Throws +} + +inline void MixedColumn::insert_bool(size_t ndx, bool value) +{ + int_fast64_t value_2 = int_fast64_t(value); + insert_int(ndx, value_2, mixcol_Bool); // Throws +} + +inline void MixedColumn::insert_olddatetime(size_t ndx, OldDateTime value) +{ + int_fast64_t value_2 = int_fast64_t(value.get_olddatetime()); + insert_int(ndx, value_2, mixcol_OldDateTime); // Throws +} + +inline void MixedColumn::insert_timestamp(size_t ndx, Timestamp value) +{ + ensure_timestamp_column(); + size_t data_ndx = m_timestamp_data->size(); + m_timestamp_data->add(value); // Throws + insert_int(ndx, int_fast64_t(data_ndx), mixcol_Timestamp); +} + +inline void MixedColumn::insert_string(size_t ndx, StringData value) +{ + ensure_binary_data_column(); + size_t blob_ndx = m_binary_data->size(); + m_binary_data->add_string(value); // Throws + + int_fast64_t value_2 = int_fast64_t(blob_ndx); + insert_int(ndx, value_2, mixcol_String); // Throws +} + +inline void MixedColumn::insert_binary(size_t ndx, BinaryData value) +{ + ensure_binary_data_column(); + size_t blob_ndx = m_binary_data->size(); + m_binary_data->add(value); // Throws + + int_fast64_t value_2 = int_fast64_t(blob_ndx); + insert_int(ndx, value_2, mixcol_Binary); // Throws +} + +inline void MixedColumn::insert_subtable(size_t ndx, const Table* t) +{ + typedef _impl::TableFriend tf; + ref_type ref; + if (t) { + ref = tf::clone(*t, get_alloc()); // Throws + } + else { + ref = tf::create_empty_table(get_alloc()); // Throws + } + int_fast64_t types_value = mixcol_Table; + int_fast64_t data_value = int_fast64_t(ref); + insert_value(ndx, types_value, data_value); // Throws +} + +inline void MixedColumn::erase(size_t row_ndx) +{ + size_t num_rows_to_erase = 1; + size_t prior_num_rows = size(); // Note that size() is slow + do_erase(row_ndx, num_rows_to_erase, prior_num_rows); // Throws +} + +inline void MixedColumn::move_last_over(size_t row_ndx) +{ + size_t prior_num_rows = size(); // Note that size() is slow + do_move_last_over(row_ndx, prior_num_rows); // Throws +} + +inline void MixedColumn::swap_rows(size_t row_ndx_1, size_t row_ndx_2) +{ + do_swap_rows(row_ndx_1, row_ndx_2); +} + +inline void MixedColumn::clear() +{ + size_t num_rows = size(); // Note that size() is slow + do_clear(num_rows); // Throws +} + +inline size_t MixedColumn::get_size_from_ref(ref_type root_ref, Allocator& alloc) noexcept +{ + const char* root_header = alloc.translate(root_ref); + ref_type types_ref = to_ref(Array::get(root_header, 0)); + return IntegerColumn::get_size_from_ref(types_ref, alloc); +} + +inline void MixedColumn::clear_value_and_discard_subtab_acc(size_t row_ndx, MixedColType new_type) +{ + MixedColType old_type = clear_value(row_ndx, new_type); + if (old_type == mixcol_Table) + m_data->discard_subtable_accessor(row_ndx); +} + +// Implementing pure virtual method of ColumnBase. +inline void MixedColumn::insert_rows(size_t row_ndx, size_t num_rows_to_insert, size_t prior_num_rows, + bool insert_nulls) +{ + REALM_ASSERT_DEBUG(prior_num_rows == size()); + REALM_ASSERT(row_ndx <= prior_num_rows); + REALM_ASSERT(!insert_nulls); + + size_t row_ndx_2 = (row_ndx == prior_num_rows ? realm::npos : row_ndx); + + int_fast64_t type_value = mixcol_Int; + m_types->insert_without_updating_index(row_ndx_2, type_value, num_rows_to_insert); // Throws + + // The least significant bit indicates that the rest of the bits form an + // integer value, so 1 is actually zero. + int_fast64_t data_value = 1; + m_data->do_insert(row_ndx_2, data_value, num_rows_to_insert); // Throws +} + +// Implementing pure virtual method of ColumnBase. +inline void MixedColumn::erase_rows(size_t row_ndx, size_t num_rows_to_erase, size_t prior_num_rows, bool) +{ + do_erase(row_ndx, num_rows_to_erase, prior_num_rows); // Throws +} + +// Implementing pure virtual method of ColumnBase. +inline void MixedColumn::move_last_row_over(size_t row_ndx, size_t prior_num_rows, bool) +{ + do_move_last_over(row_ndx, prior_num_rows); // Throws +} + +// Implementing pure virtual method of ColumnBase. +inline void MixedColumn::clear(size_t num_rows, bool) +{ + do_clear(num_rows); // Throws +} + +inline void MixedColumn::mark(int type) noexcept +{ + m_data->mark(type); +} + +inline void MixedColumn::refresh_accessor_tree(size_t col_ndx, const Spec& spec) +{ + ColumnBaseSimple::refresh_accessor_tree(col_ndx, spec); + + get_root_array()->init_from_parent(); + m_types->refresh_accessor_tree(col_ndx, spec); // Throws + m_data->refresh_accessor_tree(col_ndx, spec); // Throws + + m_binary_data.reset(); + // See if m_binary_data needs to be created. + if (get_root_array()->size() >= 3) { + ref_type ref = get_root_array()->get_as_ref(2); + m_binary_data.reset(new BinaryColumn(get_alloc(), ref)); // Throws + m_binary_data->set_parent(get_root_array(), 2); + } + + m_timestamp_data.reset(); + // See if m_timestamp_data needs to be created. + if (get_root_array()->size() >= 4) { + ref_type ref = get_root_array()->get_as_ref(3); + // When adding/creating a Mixed column the user cannot specify nullability, so the "true" below + // makes it implicitly nullable, which may not be wanted. But it's OK since Mixed columns are not + // publicly supported + m_timestamp_data.reset(new TimestampColumn(true /*fixme*/, get_alloc(), ref)); // Throws + m_timestamp_data->set_parent(get_root_array(), 3); + } + + if (m_binary_data) { + REALM_ASSERT_3(get_root_array()->size(), >=, 3); + m_binary_data->refresh_accessor_tree(col_ndx, spec); // Throws + } + if (m_timestamp_data) { + REALM_ASSERT_3(get_root_array()->size(), >=, 4); + m_timestamp_data->refresh_accessor_tree(col_ndx, spec); // Throws + } +} + +inline void MixedColumn::RefsColumn::refresh_accessor_tree(size_t col_ndx, const Spec& spec) +{ + SubtableColumnBase::refresh_accessor_tree(col_ndx, spec); // Throws + m_subtable_map.refresh_accessor_tree(); // Throws +} + +} // namespace realm diff --git a/!main project/Pods/Realm/include/core/realm/column_string.hpp b/!main project/Pods/Realm/include/core/realm/column_string.hpp new file mode 100644 index 0000000..e7693ee --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/column_string.hpp @@ -0,0 +1,377 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_COLUMN_STRING_HPP +#define REALM_COLUMN_STRING_HPP + +#include +#include +#include +#include +#include +#include + +namespace realm { + +// Pre-declarations +class StringIndex; + + +/// A string column (StringColumn) is a single B+-tree, and +/// the root of the column is the root of the B+-tree. Leaf nodes are +/// either of type ArrayString (array of small strings), +/// ArrayStringLong (array of medium strings), or ArrayBigBlobs (array +/// of big strings). +/// +/// A string column can optionally be equipped with a search index. If +/// it is, then the root ref of the index is stored in +/// Table::m_columns immediately after the root ref of the string +/// column. +class StringColumn : public ColumnBaseSimple { +public: + typedef StringData value_type; + + StringColumn(Allocator&, ref_type, bool nullable = false, size_t column_ndx = npos); + ~StringColumn() noexcept override; + + void destroy() noexcept override; + + size_t size() const noexcept final; + bool is_empty() const noexcept + { + return size() == 0; + } + + bool is_null(size_t ndx) const noexcept final; + void set_null(size_t ndx) final; + StringData get(size_t ndx) const noexcept; + void set(size_t ndx, StringData); + void add(); + void add(StringData value); + void insert(size_t ndx); + void insert(size_t ndx, StringData value); + void erase(size_t row_ndx); + void move_last_over(size_t row_ndx); + void swap_rows(size_t row_ndx_1, size_t row_ndx_2) override; + void clear(); + + size_t count(StringData value) const; + size_t find_first(StringData value, size_t begin = 0, size_t end = npos) const; + void find_all(IntegerColumn& result, StringData value, size_t begin = 0, size_t end = npos) const; + FindRes find_all_no_copy(StringData value, InternalFindResult& result) const; + + int compare_values(size_t, size_t) const noexcept override; + + //@{ + /// Find the lower/upper bound for the specified value assuming + /// that the elements are already sorted in ascending order + /// according to StringData::operator<(). + size_t lower_bound_string(StringData value) const noexcept; + size_t upper_bound_string(StringData value) const noexcept; + //@} + + void set_string(size_t, StringData) override; + + bool is_nullable() const noexcept final; + + // Search index + StringData get_index_data(size_t ndx, StringIndex::StringConversionBuffer& buffer) const noexcept final; + bool has_search_index() const noexcept override; + void set_search_index_ref(ref_type, ArrayParent*, size_t) override; + StringIndex* get_search_index() noexcept override; + const StringIndex* get_search_index() const noexcept override; + std::unique_ptr release_search_index() noexcept; + bool supports_search_index() const noexcept final + { + return true; + } + StringIndex* create_search_index() override; + + // Simply inserts all column values in the index in a loop + void populate_search_index(); + void destroy_search_index() noexcept override; + + // Optimizing data layout. enforce == true will enforce enumeration; + // enforce == false will auto-evaluate if it should be enumerated or not + bool auto_enumerate(ref_type& keys, ref_type& values, bool enforce = false) const; + + /// Compare two string columns for equality. + bool compare_string(const StringColumn&) const; + + enum LeafType { + leaf_type_Small, ///< ArrayString + leaf_type_Medium, ///< ArrayStringLong + leaf_type_Big ///< ArrayBigBlobs + }; + + std::unique_ptr get_leaf(size_t ndx, size_t& out_ndx_in_parent, LeafType& out_leaf_type) const; + + static ref_type create(Allocator&, size_t size = 0); + + static size_t get_size_from_ref(ref_type root_ref, Allocator&) noexcept; + + // Overrriding method in ColumnBase + ref_type write(size_t, size_t, size_t, _impl::OutputStream&) const override; + + void insert_rows(size_t, size_t, size_t, bool) override; + void erase_rows(size_t, size_t, size_t, bool) override; + void move_last_row_over(size_t, size_t, bool) override; + void clear(size_t, bool) override; + void set_ndx_in_parent(size_t ndx_in_parent) noexcept override; + void update_from_parent(size_t old_baseline) noexcept override; + void refresh_accessor_tree(size_t, const Spec&) override; + + void verify() const override; + void verify(const Table&, size_t) const override; + void to_dot(std::ostream&, StringData title) const override; + void do_dump_node_structure(std::ostream&, int) const override; + +private: + std::unique_ptr m_search_index; + bool m_nullable; + + LeafType get_block(size_t ndx, ArrayParent**, size_t& off, bool use_retval = false) const; + + /// If you are appending and have the size of the column readily available, + /// call the 4 argument version instead. If you are not appending, either + /// one is fine. + /// + /// \param row_ndx Must be `realm::npos` if appending. + void do_insert(size_t row_ndx, StringData value, size_t num_rows); + + /// If you are appending and you do not have the size of the column readily + /// available, call the 3 argument version instead. If you are not + /// appending, either one is fine. + /// + /// \param is_append Must be true if, and only if `row_ndx` is equal to the + /// size of the column (before insertion). + void do_insert(size_t row_ndx, StringData value, size_t num_rows, bool is_append); + + /// \param row_ndx Must be `realm::npos` if appending. + void bptree_insert(size_t row_ndx, StringData value, size_t num_rows); + + // Called by Array::bptree_insert(). + static ref_type leaf_insert(MemRef leaf_mem, ArrayParent&, size_t ndx_in_parent, Allocator&, size_t insert_ndx, + BpTreeNode::TreeInsert& state); + + class EraseLeafElem; + class CreateHandler; + class SliceHandler; + + void do_erase(size_t row_ndx, bool is_last); + void do_move_last_over(size_t row_ndx, size_t last_row_ndx); + void do_swap_rows(size_t row_ndx_1, size_t row_ndx_2); + void do_clear(); + + /// Root must be a leaf. Upgrades the root leaf as + /// necessary. Returns the type of the root leaf as it is upon + /// return. + LeafType upgrade_root_leaf(size_t value_size); + + void refresh_root_accessor(); + + void leaf_to_dot(MemRef, ArrayParent*, size_t ndx_in_parent, std::ostream&) const override; + + friend class BpTreeNode; + friend class ColumnBase; +}; + + +// Implementation: + +inline size_t StringColumn::size() const noexcept +{ + if (root_is_leaf()) { + bool long_strings = m_array->has_refs(); + if (!long_strings) { + // Small strings root leaf + ArrayString* leaf = static_cast(m_array.get()); + return leaf->size(); + } + bool is_big = m_array->get_context_flag(); + if (!is_big) { + // Medium strings root leaf + ArrayStringLong* leaf = static_cast(m_array.get()); + return leaf->size(); + } + // Big strings root leaf + ArrayBigBlobs* leaf = static_cast(m_array.get()); + return leaf->size(); + } + // Non-leaf root + BpTreeNode* node = static_cast(m_array.get()); + return node->get_bptree_size(); +} + +inline void StringColumn::add(StringData value) +{ + REALM_ASSERT(!(value.is_null() && !m_nullable)); + size_t row_ndx = realm::npos; + size_t num_rows = 1; + do_insert(row_ndx, value, num_rows); // Throws +} + +inline void StringColumn::add() +{ + add(m_nullable ? realm::null() : StringData("")); +} + +inline void StringColumn::insert(size_t row_ndx, StringData value) +{ + REALM_ASSERT(!(value.is_null() && !m_nullable)); + size_t column_size = this->size(); + REALM_ASSERT_3(row_ndx, <=, column_size); + size_t num_rows = 1; + bool is_append = row_ndx == column_size; + do_insert(row_ndx, value, num_rows, is_append); // Throws +} + +inline void StringColumn::insert(size_t row_ndx) +{ + insert(row_ndx, m_nullable ? realm::null() : StringData("")); +} + +inline void StringColumn::erase(size_t row_ndx) +{ + size_t last_row_ndx = size() - 1; // Note that size() is slow + bool is_last = row_ndx == last_row_ndx; + do_erase(row_ndx, is_last); // Throws +} + +inline void StringColumn::move_last_over(size_t row_ndx) +{ + size_t last_row_ndx = size() - 1; // Note that size() is slow + do_move_last_over(row_ndx, last_row_ndx); // Throws +} + +inline void StringColumn::swap_rows(size_t row_ndx_1, size_t row_ndx_2) +{ + do_swap_rows(row_ndx_1, row_ndx_2); // Throws +} + +inline void StringColumn::clear() +{ + do_clear(); // Throws +} + +inline int StringColumn::compare_values(size_t row1, size_t row2) const noexcept +{ + StringData a = get(row1); + StringData b = get(row2); + + if (a.is_null() && !b.is_null()) + return 1; + else if (b.is_null() && !a.is_null()) + return -1; + else if (a.is_null() && b.is_null()) + return 0; + + if (a == b) + return 0; + return utf8_compare(a, b) ? 1 : -1; +} + +inline void StringColumn::set_string(size_t row_ndx, StringData value) +{ + REALM_ASSERT(!(value.is_null() && !m_nullable)); + set(row_ndx, value); // Throws +} + +inline bool StringColumn::has_search_index() const noexcept +{ + return m_search_index != 0; +} + +inline StringIndex* StringColumn::get_search_index() noexcept +{ + return m_search_index.get(); +} + +inline const StringIndex* StringColumn::get_search_index() const noexcept +{ + return m_search_index.get(); +} + +inline size_t StringColumn::get_size_from_ref(ref_type root_ref, Allocator& alloc) noexcept +{ + const char* root_header = alloc.translate(root_ref); + bool root_is_leaf = !Array::get_is_inner_bptree_node_from_header(root_header); + if (root_is_leaf) { + bool long_strings = Array::get_hasrefs_from_header(root_header); + if (!long_strings) { + // Small strings leaf + return ArrayString::get_size_from_header(root_header); + } + bool is_big = Array::get_context_flag_from_header(root_header); + if (!is_big) { + // Medium strings leaf + return ArrayStringLong::get_size_from_header(root_header, alloc); + } + // Big strings leaf + return ArrayBigBlobs::get_size_from_header(root_header); + } + + return BpTreeNode::get_bptree_size_from_header(root_header); +} + +// Implementing pure virtual method of ColumnBase. +inline void StringColumn::insert_rows(size_t row_ndx, size_t num_rows_to_insert, size_t prior_num_rows, + bool insert_nulls) +{ + REALM_ASSERT_DEBUG(prior_num_rows == size()); + REALM_ASSERT(row_ndx <= prior_num_rows); + REALM_ASSERT(!insert_nulls || m_nullable); + + StringData value = m_nullable ? realm::null() : StringData(""); + bool is_append = (row_ndx == prior_num_rows); + do_insert(row_ndx, value, num_rows_to_insert, is_append); // Throws +} + +// Implementing pure virtual method of ColumnBase. +inline void StringColumn::erase_rows(size_t row_ndx, size_t num_rows_to_erase, size_t prior_num_rows, bool) +{ + REALM_ASSERT_DEBUG(prior_num_rows == size()); + REALM_ASSERT(num_rows_to_erase <= prior_num_rows); + REALM_ASSERT(row_ndx <= prior_num_rows - num_rows_to_erase); + + bool is_last = (row_ndx + num_rows_to_erase == prior_num_rows); + for (size_t i = num_rows_to_erase; i > 0; --i) { + size_t row_ndx_2 = row_ndx + i - 1; + do_erase(row_ndx_2, is_last); // Throws + } +} + +// Implementing pure virtual method of ColumnBase. +inline void StringColumn::move_last_row_over(size_t row_ndx, size_t prior_num_rows, bool) +{ + REALM_ASSERT_DEBUG(prior_num_rows == size()); + REALM_ASSERT(row_ndx < prior_num_rows); + + size_t last_row_ndx = prior_num_rows - 1; + do_move_last_over(row_ndx, last_row_ndx); // Throws +} + +// Implementing pure virtual method of ColumnBase. +inline void StringColumn::clear(size_t, bool) +{ + do_clear(); // Throws +} + +} // namespace realm + +#endif // REALM_COLUMN_STRING_HPP diff --git a/!main project/Pods/Realm/include/core/realm/column_string_enum.hpp b/!main project/Pods/Realm/include/core/realm/column_string_enum.hpp new file mode 100644 index 0000000..a73fe0c --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/column_string_enum.hpp @@ -0,0 +1,311 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_COLUMN_STRING_ENUM_HPP +#define REALM_COLUMN_STRING_ENUM_HPP + +#include + +namespace realm { + +// Pre-declarations +class StringIndex; + + +/// From the point of view of the application, an enumerated strings column +/// (StringEnumColumn) is like a string column (StringColumn), yet it manages +/// its strings in such a way that each unique string is stored only once. In +/// fact, an enumerated strings column is a combination of two subcolumns; a +/// regular string column (StringColumn) that stores the unique strings, and an +/// integer column that stores one unique string index for each entry in the +/// enumerated strings column. +/// +/// In terms of the underlying node structure, the subcolumn containing the +/// unique strings is not a true part of the enumerated strings column. Instead +/// it is a part of the spec structure that describes the table of which the +/// enumerated strings column is a part. This way, the unique strings can be +/// shared across enumerated strings columns of multiple subtables. This also +/// means that the root of an enumerated strings column coincides with the root +/// of the integer subcolumn, and in some sense, an enumerated strings column is +/// just the integer subcolumn. +/// +/// An enumerated strings column can optionally be equipped with a +/// search index. If it is, then the root ref of the index is stored +/// in Table::m_columns immediately after the root ref of the +/// enumerated strings column. +class StringEnumColumn : public IntegerColumn { +public: + typedef StringData value_type; + + StringEnumColumn(Allocator&, ref_type ref, ref_type keys_ref, bool nullable, size_t column_ndx = npos); + ~StringEnumColumn() noexcept override; + void destroy() noexcept override; + MemRef clone_deep(Allocator& alloc) const override; + + int compare_values(size_t row1, size_t row2) const noexcept override + { + StringData a = get(row1); + StringData b = get(row2); + + if (a.is_null() && !b.is_null()) + return 1; + else if (b.is_null() && !a.is_null()) + return -1; + else if (a.is_null() && b.is_null()) + return 0; + + if (a == b) + return 0; + + return utf8_compare(a, b) ? 1 : -1; + } + + StringData get(size_t ndx) const noexcept; + bool is_null(size_t ndx) const noexcept final; + void set(size_t ndx, StringData value); + void set_null(size_t ndx) override; + void add(); + void add(StringData value); + void insert(size_t ndx); + void insert(size_t ndx, StringData value); + void erase(size_t row_ndx); + void move_last_over(size_t row_ndx); + void swap_rows(size_t row_ndx_1, size_t row_ndx_2) override; + void clear(); + bool is_nullable() const noexcept final; + + size_t count(StringData value) const; + size_t find_first(StringData value, size_t begin = 0, size_t end = npos) const; + void find_all(IntegerColumn& res, StringData value, size_t begin = 0, size_t end = npos) const; + FindRes find_all_no_copy(StringData value, InternalFindResult& result) const; + + size_t count(size_t key_index) const; + size_t find_first(size_t key_index, size_t begin = 0, size_t end = -1) const; + void find_all(IntegerColumn& res, size_t key_index, size_t begin = 0, size_t end = -1) const; + + //@{ + /// Find the lower/upper bound for the specified value assuming + /// that the elements are already sorted in ascending order + /// according to StringData::operator<(). + size_t lower_bound_string(StringData value) const noexcept; + size_t upper_bound_string(StringData value) const noexcept; + //@} + + void set_string(size_t, StringData) override; + + void adjust_keys_ndx_in_parent(int diff) noexcept; + + // Search index + StringData get_index_data(size_t ndx, StringIndex::StringConversionBuffer& buffer) const noexcept final; + bool supports_search_index() const noexcept final + { + return true; + } + StringIndex* create_search_index() override; + void install_search_index(std::unique_ptr) noexcept; + void destroy_search_index() noexcept override; + + // Compare two string columns for equality + bool compare_string(const StringColumn&) const; + bool compare_string(const StringEnumColumn&) const; + + void insert_rows(size_t, size_t, size_t, bool) override; + void erase_rows(size_t, size_t, size_t, bool) override; + void move_last_row_over(size_t, size_t, bool) override; + void clear(size_t, bool) override; + void update_from_parent(size_t) noexcept override; + void refresh_accessor_tree(size_t, const Spec&) override; + + size_t get_key_ndx(StringData value) const; + size_t get_key_ndx_or_add(StringData value); + + StringColumn& get_keys(); + const StringColumn& get_keys() const; + +#ifdef REALM_DEBUG + void verify() const override; + void verify(const Table&, size_t) const override; + void do_dump_node_structure(std::ostream&, int) const override; + void to_dot(std::ostream&, StringData title) const override; +#endif + +private: + // Member variables + StringColumn m_keys; + bool m_nullable; + + /// If you are appending and have the size of the column readily available, + /// call the 4 argument version instead. If you are not appending, either + /// one is fine. + /// + /// \param row_ndx Must be `realm::npos` if appending. + void do_insert(size_t row_ndx, StringData value, size_t num_rows); + + /// If you are appending and you do not have the size of the column readily + /// available, call the 3 argument version instead. If you are not + /// appending, either one is fine. + /// + /// \param is_append Must be true if, and only if `row_ndx` is equal to the + /// size of the column (before insertion). + void do_insert(size_t row_ndx, StringData value, size_t num_rows, bool is_append); + + void do_erase(size_t row_ndx, bool is_last); + void do_move_last_over(size_t row_ndx, size_t last_row_ndx); + void do_clear(); +}; + + +// Implementation: + +inline StringData StringEnumColumn::get(size_t ndx) const noexcept +{ + REALM_ASSERT_3(ndx, <, IntegerColumn::size()); + size_t key_ndx = to_size_t(IntegerColumn::get(ndx)); + StringData sd = m_keys.get(key_ndx); + REALM_ASSERT_DEBUG(!(!m_nullable && sd.is_null())); + return sd; +} + +inline bool StringEnumColumn::is_null(size_t ndx) const noexcept +{ + return is_nullable() && get(ndx).is_null(); +} + +inline void StringEnumColumn::add() +{ + add(m_nullable ? realm::null() : StringData("")); +} + +inline void StringEnumColumn::add(StringData value) +{ + REALM_ASSERT_DEBUG(!(!m_nullable && value.is_null())); + size_t row_ndx = realm::npos; + size_t num_rows = 1; + do_insert(row_ndx, value, num_rows); // Throws +} + +inline void StringEnumColumn::insert(size_t row_ndx) +{ + insert(row_ndx, m_nullable ? realm::null() : StringData("")); +} + +inline void StringEnumColumn::insert(size_t row_ndx, StringData value) +{ + REALM_ASSERT_DEBUG(!(!m_nullable && value.is_null())); + size_t column_size = this->size(); + REALM_ASSERT_3(row_ndx, <=, column_size); + size_t num_rows = 1; + bool is_append = row_ndx == column_size; + do_insert(row_ndx, value, num_rows, is_append); // Throws +} + +inline void StringEnumColumn::erase(size_t row_ndx) +{ + size_t last_row_ndx = size() - 1; // Note that size() is slow + bool is_last = row_ndx == last_row_ndx; + do_erase(row_ndx, is_last); // Throws +} + +inline void StringEnumColumn::move_last_over(size_t row_ndx) +{ + size_t last_row_ndx = size() - 1; // Note that size() is slow + do_move_last_over(row_ndx, last_row_ndx); // Throws +} + +inline void StringEnumColumn::clear() +{ + do_clear(); // Throws +} + +// Overriding virtual method of Column. +inline void StringEnumColumn::insert_rows(size_t row_ndx, size_t num_rows_to_insert, size_t prior_num_rows, + bool insert_nulls) +{ + REALM_ASSERT_DEBUG(prior_num_rows == size()); + REALM_ASSERT(row_ndx <= prior_num_rows); + REALM_ASSERT(!insert_nulls || m_nullable); + + StringData value = m_nullable ? realm::null() : StringData(""); + bool is_append = (row_ndx == prior_num_rows); + do_insert(row_ndx, value, num_rows_to_insert, is_append); // Throws +} + +// Overriding virtual method of Column. +inline void StringEnumColumn::erase_rows(size_t row_ndx, size_t num_rows_to_erase, size_t prior_num_rows, bool) +{ + REALM_ASSERT_DEBUG(prior_num_rows == size()); + REALM_ASSERT(num_rows_to_erase <= prior_num_rows); + REALM_ASSERT(row_ndx <= prior_num_rows - num_rows_to_erase); + + bool is_last = (row_ndx + num_rows_to_erase == prior_num_rows); + for (size_t i = num_rows_to_erase; i > 0; --i) { + size_t row_ndx_2 = row_ndx + i - 1; + do_erase(row_ndx_2, is_last); // Throws + } +} + +// Overriding virtual method of Column. +inline void StringEnumColumn::move_last_row_over(size_t row_ndx, size_t prior_num_rows, bool) +{ + REALM_ASSERT_DEBUG(prior_num_rows == size()); + REALM_ASSERT(row_ndx < prior_num_rows); + + size_t last_row_ndx = prior_num_rows - 1; + do_move_last_over(row_ndx, last_row_ndx); // Throws +} + +// Overriding virtual method of Column. +inline void StringEnumColumn::clear(size_t, bool) +{ + do_clear(); // Throws +} + +inline size_t StringEnumColumn::lower_bound_string(StringData value) const noexcept +{ + return ColumnBase::lower_bound(*this, value); +} + +inline size_t StringEnumColumn::upper_bound_string(StringData value) const noexcept +{ + return ColumnBase::upper_bound(*this, value); +} + +inline void StringEnumColumn::set_string(size_t row_ndx, StringData value) +{ + set(row_ndx, value); // Throws +} + +inline void StringEnumColumn::set_null(size_t row_ndx) +{ + set(row_ndx, realm::null{}); +} + +inline StringColumn& StringEnumColumn::get_keys() +{ + return m_keys; +} + +inline const StringColumn& StringEnumColumn::get_keys() const +{ + return m_keys; +} + + +} // namespace realm + +#endif // REALM_COLUMN_STRING_ENUM_HPP diff --git a/!main project/Pods/Realm/include/core/realm/column_table.hpp b/!main project/Pods/Realm/include/core/realm/column_table.hpp new file mode 100644 index 0000000..0d424c2 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/column_table.hpp @@ -0,0 +1,693 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_COLUMN_TABLE_HPP +#define REALM_COLUMN_TABLE_HPP + +#include +#include + +#include +#include +#include +#include + +namespace realm { + + +/// Base class for any type of column that can contain subtables. +// FIXME: Don't derive from IntegerColumn, but define a BpTree specialization. +class SubtableColumnBase : public IntegerColumn, public Table::Parent { +public: + void discard_child_accessors() noexcept; + + ~SubtableColumnBase() noexcept override; + + static ref_type create(Allocator&, size_t size = 0); + + TableRef get_subtable_accessor(size_t) const noexcept override; + + void insert_rows(size_t, size_t, size_t, bool) override; + void erase_rows(size_t, size_t, size_t, bool) override; + void move_last_row_over(size_t, size_t, bool) override; + void clear(size_t, bool) override; + void swap_rows(size_t, size_t) override; + void discard_subtable_accessor(size_t) noexcept override; + void update_from_parent(size_t) noexcept override; + void adj_acc_insert_rows(size_t, size_t) noexcept override; + void adj_acc_erase_row(size_t) noexcept override; + void adj_acc_move_over(size_t, size_t) noexcept override; + void adj_acc_clear_root_table() noexcept override; + void adj_acc_swap_rows(size_t, size_t) noexcept override; + void adj_acc_move_row(size_t, size_t) noexcept override; + void mark(int) noexcept override; + bool supports_search_index() const noexcept override + { + return false; + } + StringIndex* create_search_index() override + { + return nullptr; + } + bool is_null(size_t ndx) const noexcept override + { + return get_as_ref(ndx) == 0; + } + + void verify() const override; + void verify(const Table&, size_t) const override; + +protected: + /// A pointer to the table that this column is part of. For a free-standing + /// column, this pointer is null. + Table* const m_table; + + struct SubtableMap { + bool empty() const noexcept + { + return m_entries.empty(); + } + Table* find(size_t subtable_ndx) const noexcept; + void add(size_t subtable_ndx, Table*); + // Returns true if, and only if at least one entry was detached and + // removed from the map. + bool detach_and_remove_all() noexcept; + // Returns true if, and only if the entry was found and removed, and it + // was the last entry in the map. + bool detach_and_remove(size_t subtable_ndx) noexcept; + // Returns true if, and only if the entry was found and removed, and it + // was the last entry in the map. + bool remove(Table*) noexcept; + void update_from_parent(size_t old_baseline) const noexcept; + template + void adj_insert_rows(size_t row_ndx, size_t num_rows_inserted) noexcept; + // Returns true if, and only if an entry was found and removed, and it + // was the last entry in the map. + template + bool adj_erase_rows(size_t row_ndx, size_t num_rows_erased) noexcept; + // Returns true if, and only if an entry was found and removed, and it + // was the last entry in the map. + template + bool adj_move_over(size_t from_row_ndx, size_t to_row_ndx) noexcept; + template + void adj_swap_rows(size_t row_ndx_1, size_t row_ndx_2) noexcept; + template + void adj_move_row(size_t from_ndx, size_t to_ndx) noexcept; + void adj_set_null(size_t row_ndx) noexcept; + + void update_accessors(const size_t* col_path_begin, const size_t* col_path_end, + _impl::TableFriend::AccessorUpdater&); + void recursive_mark() noexcept; + void refresh_accessor_tree(); + void verify(const SubtableColumn& parent); + + private: + struct SubtableEntry { + size_t m_subtable_ndx; + Table* m_table; + }; + typedef std::vector entries; + entries m_entries; + }; + + /// Contains all existing accessors that are attached to a subtable in this + /// column. It can map a row index into a pointer to the corresponding + /// accessor when it exists. + /// + /// There is an invariant in force: Either `m_table` is null, or there is an + /// additional referece count on `*m_table` when, and only when the map is + /// non-empty. + mutable SubtableMap m_subtable_map; + mutable std::recursive_mutex m_subtable_map_lock; + + SubtableColumnBase(Allocator&, ref_type, Table*, size_t column_ndx); + + /// Get a TableRef to the accessor of the specified subtable. The + /// accessor will be created if it does not already exist. + /// + /// NOTE: This method must be used only for subtables with + /// independent specs, i.e. for elements of a MixedColumn. + TableRef get_subtable_tableref(size_t subtable_ndx); + + // Overriding method in ArrayParent + void update_child_ref(size_t, ref_type) override; + + // Overriding method in ArrayParent + ref_type get_child_ref(size_t) const noexcept override; + + // Overriding method in Table::Parent + Table* get_parent_table(size_t*) noexcept override; + + // Overriding method in Table::Parent + void child_accessor_destroyed(Table*) noexcept override; + + // Overriding method in Table::Parent + std::recursive_mutex* get_accessor_management_lock() noexcept override + { return &m_subtable_map_lock; } + + /// Assumes that the two tables have the same spec. + static bool compare_subtable_rows(const Table&, const Table&); + + /// Construct a copy of the columns array of the specified table + /// and return just the ref to that array. + /// + /// In the clone, no string column will be of the enumeration + /// type. + ref_type clone_table_columns(const Table*); + + size_t* record_subtable_path(size_t* begin, size_t* end) noexcept override; + + void update_table_accessors(const size_t* col_path_begin, const size_t* col_path_end, + _impl::TableFriend::AccessorUpdater&); + + /// \param row_ndx Must be `realm::npos` if appending. + /// \param value The value to place in any newly created rows. + /// \param num_rows The number of rows to insert. + void do_insert(size_t row_ndx, int_fast64_t value, size_t num_rows); + + std::pair get_to_dot_parent(size_t ndx_in_parent) const override; + + friend class Table; +}; + + +class SubtableColumn : public SubtableColumnBase { +public: + using value_type = ConstTableRef; + /// Create a subtable column accessor and attach it to a + /// preexisting underlying structure of arrays. + /// + /// \param alloc The allocator to provide new memory. + /// + /// \param ref The memory reference of the underlying subtable that + /// we are creating an accessor for. + /// + /// \param table If this column is used as part of a table you must + /// pass a pointer to that table. Otherwise you must pass null. + /// + /// \param column_ndx If this column is used as part of a table + /// you must pass the logical index of the column within that + /// table. Otherwise you should pass zero. + SubtableColumn(Allocator& alloc, ref_type ref, Table* table, size_t column_ndx); + + ~SubtableColumn() noexcept override + { + } + + // Overriding method in Table::Parent + Spec* get_subtable_spec() noexcept override; + + size_t get_subtable_size(size_t ndx) const noexcept; + + /// Get a TableRef to the accessor of the specified subtable. The + /// accessor will be created if it does not already exist. + TableRef get_subtable_tableref(size_t subtable_ndx); + + ConstTableRef get_subtable_tableref(size_t subtable_ndx) const; + + /// This is to be used by the query system that does not need to + /// modify the subtable. Will return a ref object containing a + /// nullptr if there is no table object yet. + ConstTableRef get(size_t subtable_ndx) const + { + int64_t ref = IntegerColumn::get(subtable_ndx); + if (ref) + return get_subtable_tableref(subtable_ndx); + else + return {}; + } + + // When passing a table to add() or insert() it is assumed that + // the table spec is compatible with this column. The number of + // columns must be the same, and the corresponding columns must + // have the same data type (as returned by + // Table::get_column_type()). + + void add(const Table* value = nullptr); + void insert(size_t ndx, const Table* value = nullptr); + void set(size_t ndx, const Table*); + void clear_table(size_t ndx); + void set_null(size_t ndx) override; + + using SubtableColumnBase::insert; + + void erase_rows(size_t, size_t, size_t, bool) override; + void move_last_row_over(size_t, size_t, bool) override; + + /// Compare two subtable columns for equality. + bool compare_table(const SubtableColumn&) const; + + void refresh_accessor_tree(size_t, const Spec&) override; + void refresh_subtable_map(); + +#ifdef REALM_DEBUG + void verify(const Table&, size_t) const override; + void do_dump_node_structure(std::ostream&, int) const override; + void to_dot(std::ostream&, StringData title) const override; +#endif + +private: + mutable size_t m_subspec_ndx; // Unknown if equal to `npos` + + size_t get_subspec_ndx() const noexcept; + + void destroy_subtable(size_t ndx) noexcept; + + void do_discard_child_accessors() noexcept override; +}; + + +// Implementation + +// Overriding virtual method of Column. +inline void SubtableColumnBase::insert_rows(size_t row_ndx, size_t num_rows_to_insert, size_t prior_num_rows, bool) +{ + REALM_ASSERT_DEBUG(prior_num_rows == size()); + REALM_ASSERT(row_ndx <= prior_num_rows); + + size_t row_ndx_2 = (row_ndx == prior_num_rows ? realm::npos : row_ndx); + int_fast64_t value = 0; + do_insert(row_ndx_2, value, num_rows_to_insert); // Throws +} + +// Overriding virtual method of Column. +inline void SubtableColumnBase::erase_rows(size_t row_ndx, size_t num_rows_to_erase, size_t prior_num_rows, + bool broken_reciprocal_backlinks) +{ + IntegerColumn::erase_rows(row_ndx, num_rows_to_erase, prior_num_rows, broken_reciprocal_backlinks); // Throws + + std::lock_guard lg(m_subtable_map_lock); + const bool fix_ndx_in_parent = true; + bool last_entry_removed = m_subtable_map.adj_erase_rows(row_ndx, num_rows_to_erase); + typedef _impl::TableFriend tf; + if (last_entry_removed) + tf::unbind_ptr(*m_table); +} + +// Overriding virtual method of Column. +inline void SubtableColumnBase::move_last_row_over(size_t row_ndx, size_t prior_num_rows, + bool broken_reciprocal_backlinks) +{ + IntegerColumn::move_last_row_over(row_ndx, prior_num_rows, broken_reciprocal_backlinks); // Throws + + std::lock_guard lg(m_subtable_map_lock); + const bool fix_ndx_in_parent = true; + size_t last_row_ndx = prior_num_rows - 1; + bool last_entry_removed = m_subtable_map.adj_move_over(last_row_ndx, row_ndx); + typedef _impl::TableFriend tf; + if (last_entry_removed) + tf::unbind_ptr(*m_table); +} + +inline void SubtableColumnBase::clear(size_t, bool) +{ + discard_child_accessors(); + clear_without_updating_index(); // Throws + // FIXME: This one is needed because + // IntegerColumn::clear_without_updating_index() forgets about the + // leaf type. A better solution should probably be sought after. + get_root_array()->set_type(Array::type_HasRefs); +} + +inline void SubtableColumnBase::swap_rows(size_t row_ndx_1, size_t row_ndx_2) +{ + IntegerColumn::swap_rows(row_ndx_1, row_ndx_2); // Throws + + std::lock_guard lg(m_subtable_map_lock); + const bool fix_ndx_in_parent = true; + m_subtable_map.adj_swap_rows(row_ndx_1, row_ndx_2); +} + +inline void SubtableColumnBase::mark(int type) noexcept +{ + if (type & mark_Recursive) { + std::lock_guard lg(m_subtable_map_lock); + m_subtable_map.recursive_mark(); + } +} + +inline void SubtableColumnBase::adj_acc_insert_rows(size_t row_ndx, size_t num_rows) noexcept +{ + // This function must assume no more than minimal consistency of the + // accessor hierarchy. This means in particular that it cannot access the + // underlying node structure. See AccessorConsistencyLevels. + + std::lock_guard lg(m_subtable_map_lock); + const bool fix_ndx_in_parent = false; + m_subtable_map.adj_insert_rows(row_ndx, num_rows); +} + +inline void SubtableColumnBase::adj_acc_erase_row(size_t row_ndx) noexcept +{ + // This function must assume no more than minimal consistency of the + // accessor hierarchy. This means in particular that it cannot access the + // underlying node structure. See AccessorConsistencyLevels. + + std::lock_guard lg(m_subtable_map_lock); + const bool fix_ndx_in_parent = false; + size_t num_rows_erased = 1; + bool last_entry_removed = m_subtable_map.adj_erase_rows(row_ndx, num_rows_erased); + typedef _impl::TableFriend tf; + if (last_entry_removed) + tf::unbind_ptr(*m_table); +} + +inline void SubtableColumnBase::adj_acc_move_over(size_t from_row_ndx, size_t to_row_ndx) noexcept +{ + // This function must assume no more than minimal consistency of the + // accessor hierarchy. This means in particular that it cannot access the + // underlying node structure. See AccessorConsistencyLevels. + + std::lock_guard lg(m_subtable_map_lock); + const bool fix_ndx_in_parent = false; + bool last_entry_removed = m_subtable_map.adj_move_over(from_row_ndx, to_row_ndx); + typedef _impl::TableFriend tf; + if (last_entry_removed) + tf::unbind_ptr(*m_table); +} + +inline void SubtableColumnBase::adj_acc_clear_root_table() noexcept +{ + // This function must assume no more than minimal consistency of the + // accessor hierarchy. This means in particular that it cannot access the + // underlying node structure. See AccessorConsistencyLevels. + + IntegerColumn::adj_acc_clear_root_table(); + discard_child_accessors(); +} + +inline void SubtableColumnBase::adj_acc_swap_rows(size_t row_ndx_1, size_t row_ndx_2) noexcept +{ + std::lock_guard lg(m_subtable_map_lock); + const bool fix_ndx_in_parent = false; + m_subtable_map.adj_swap_rows(row_ndx_1, row_ndx_2); +} + +inline void SubtableColumnBase::adj_acc_move_row(size_t from_ndx, size_t to_ndx) noexcept +{ + std::lock_guard lg(m_subtable_map_lock); + const bool fix_ndx_in_parent = false; + m_subtable_map.adj_move_row(from_ndx, to_ndx); +} + +inline TableRef SubtableColumnBase::get_subtable_accessor(size_t row_ndx) const noexcept +{ + // This function must assume no more than minimal consistency of the + // accessor hierarchy. This means in particular that it cannot access the + // underlying node structure. See AccessorConsistencyLevels. + std::lock_guard lg(m_subtable_map_lock); + TableRef subtable(m_subtable_map.find(row_ndx)); + return subtable; +} + +inline void SubtableColumnBase::discard_subtable_accessor(size_t row_ndx) noexcept +{ + // This function must assume no more than minimal consistency of the + // accessor hierarchy. This means in particular that it cannot access the + // underlying node structure. See AccessorConsistencyLevels. + + std::lock_guard lg(m_subtable_map_lock); + bool last_entry_removed = m_subtable_map.detach_and_remove(row_ndx); + typedef _impl::TableFriend tf; + if (last_entry_removed) + tf::unbind_ptr(*m_table); +} + +inline void SubtableColumnBase::SubtableMap::add(size_t subtable_ndx, Table* table) +{ + SubtableEntry e; + e.m_subtable_ndx = subtable_ndx; + e.m_table = table; + m_entries.push_back(e); +} + +template +void SubtableColumnBase::SubtableMap::adj_insert_rows(size_t row_ndx, size_t num_rows_inserted) noexcept +{ + for (auto& entry : m_entries) { + if (entry.m_subtable_ndx >= row_ndx) { + entry.m_subtable_ndx += num_rows_inserted; + typedef _impl::TableFriend tf; + if (fix_ndx_in_parent) + tf::set_ndx_in_parent(*(entry.m_table), entry.m_subtable_ndx); + } + } +} + +template +bool SubtableColumnBase::SubtableMap::adj_erase_rows(size_t row_ndx, size_t num_rows_erased) noexcept +{ + if (m_entries.empty()) + return false; + typedef _impl::TableFriend tf; + auto end = m_entries.end(); + auto i = m_entries.begin(); + do { + if (i->m_subtable_ndx >= row_ndx + num_rows_erased) { + i->m_subtable_ndx -= num_rows_erased; + if (fix_ndx_in_parent) + tf::set_ndx_in_parent(*(i->m_table), i->m_subtable_ndx); + } + else if (i->m_subtable_ndx >= row_ndx) { + // Must hold a counted reference while detaching + TableRef table(i->m_table); + tf::detach(*table); + // Move last over + *i = *--end; + continue; + } + ++i; + } while (i != end); + m_entries.erase(end, m_entries.end()); + return m_entries.empty(); +} + + +template +bool SubtableColumnBase::SubtableMap::adj_move_over(size_t from_row_ndx, size_t to_row_ndx) noexcept +{ + typedef _impl::TableFriend tf; + + size_t i = 0, n = m_entries.size(); + // We return true if, and only if we remove the last entry in the map. We + // need special handling for the case, where the set of entries are already + // empty, otherwise the final return statement would return true in this + // case, even though we didn't actually remove an entry. + if (n == 0) + return false; + + while (i < n) { + SubtableEntry& e = m_entries[i]; + if (REALM_UNLIKELY(e.m_subtable_ndx == to_row_ndx)) { + // Must hold a counted reference while detaching + TableRef table(e.m_table); + tf::detach(*table); + // Delete entry by moving last over (faster and avoids invalidating + // iterators) + e = m_entries[--n]; + m_entries.pop_back(); + } + else { + if (REALM_UNLIKELY(e.m_subtable_ndx == from_row_ndx)) { + e.m_subtable_ndx = to_row_ndx; + if (fix_ndx_in_parent) + tf::set_ndx_in_parent(*(e.m_table), e.m_subtable_ndx); + } + ++i; + } + } + return m_entries.empty(); +} + +template +void SubtableColumnBase::SubtableMap::adj_swap_rows(size_t row_ndx_1, size_t row_ndx_2) noexcept +{ + using tf = _impl::TableFriend; + for (auto& entry : m_entries) { + if (REALM_UNLIKELY(entry.m_subtable_ndx == row_ndx_1)) { + entry.m_subtable_ndx = row_ndx_2; + if (fix_ndx_in_parent) + tf::set_ndx_in_parent(*(entry.m_table), entry.m_subtable_ndx); + } + else if (REALM_UNLIKELY(entry.m_subtable_ndx == row_ndx_2)) { + entry.m_subtable_ndx = row_ndx_1; + if (fix_ndx_in_parent) + tf::set_ndx_in_parent(*(entry.m_table), entry.m_subtable_ndx); + } + } +} + + +template +void SubtableColumnBase::SubtableMap::adj_move_row(size_t from_ndx, size_t to_ndx) noexcept +{ + using tf = _impl::TableFriend; + for (auto& entry : m_entries) { + if (entry.m_subtable_ndx == from_ndx) { + entry.m_subtable_ndx = to_ndx; + if (fix_ndx_in_parent) + tf::set_ndx_in_parent(*(entry.m_table), entry.m_subtable_ndx); + } + else { + if (from_ndx < to_ndx) { + // shift the range (from, to] down one + if (entry.m_subtable_ndx <= to_ndx && entry.m_subtable_ndx > from_ndx) { + entry.m_subtable_ndx--; + if (fix_ndx_in_parent) { + tf::set_ndx_in_parent(*(entry.m_table), entry.m_subtable_ndx); + } + } + } else if (from_ndx > to_ndx) { + // shift the range (from, to] up one + if (entry.m_subtable_ndx >= to_ndx && entry.m_subtable_ndx < from_ndx) { + entry.m_subtable_ndx++; + if (fix_ndx_in_parent) { + tf::set_ndx_in_parent(*(entry.m_table), entry.m_subtable_ndx); + } + } + } + } + } +} + +inline void SubtableColumnBase::SubtableMap::adj_set_null(size_t row_ndx) noexcept +{ + Table* table = find(row_ndx); + if (table) + _impl::TableFriend::refresh_accessor_tree(*table); +} + +inline SubtableColumnBase::SubtableColumnBase(Allocator& alloc, ref_type ref, Table* table, size_t column_ndx) + : IntegerColumn(alloc, ref, column_ndx) // Throws + , m_table(table) +{ +} + +inline void SubtableColumnBase::update_child_ref(size_t child_ndx, ref_type new_ref) +{ + set_as_ref(child_ndx, new_ref); +} + +inline ref_type SubtableColumnBase::get_child_ref(size_t child_ndx) const noexcept +{ + return get_as_ref(child_ndx); +} + +inline void SubtableColumnBase::discard_child_accessors() noexcept +{ + std::lock_guard lg(m_subtable_map_lock); + bool last_entry_removed = m_subtable_map.detach_and_remove_all(); + if (last_entry_removed && m_table) + _impl::TableFriend::unbind_ptr(*m_table); +} + +inline SubtableColumnBase::~SubtableColumnBase() noexcept +{ + discard_child_accessors(); +} + +inline bool SubtableColumnBase::compare_subtable_rows(const Table& a, const Table& b) +{ + return _impl::TableFriend::compare_rows(a, b); +} + +inline ref_type SubtableColumnBase::clone_table_columns(const Table* t) +{ + return _impl::TableFriend::clone_columns(*t, get_root_array()->get_alloc()); +} + +inline ref_type SubtableColumnBase::create(Allocator& alloc, size_t size) +{ + return IntegerColumn::create(alloc, Array::type_HasRefs, size); // Throws +} + +inline size_t* SubtableColumnBase::record_subtable_path(size_t* begin, size_t* end) noexcept +{ + if (end == begin) + return 0; // Error, not enough space in buffer + *begin++ = get_column_index(); + if (end == begin) + return 0; // Error, not enough space in buffer + return _impl::TableFriend::record_subtable_path(*m_table, begin, end); +} + +inline void SubtableColumnBase::update_table_accessors(const size_t* col_path_begin, const size_t* col_path_end, + _impl::TableFriend::AccessorUpdater& updater) +{ + // This function must assume no more than minimal consistency of the + // accessor hierarchy. This means in particular that it cannot access the + // underlying node structure. See AccessorConsistencyLevels. + + m_subtable_map.update_accessors(col_path_begin, col_path_end, updater); // Throws +} + +inline void SubtableColumnBase::do_insert(size_t row_ndx, int_fast64_t value, size_t num_rows) +{ + IntegerColumn::insert_without_updating_index(row_ndx, value, num_rows); // Throws + bool is_append = row_ndx == realm::npos; + if (!is_append) { + const bool fix_ndx_in_parent = true; + m_subtable_map.adj_insert_rows(row_ndx, num_rows); + } +} + + +inline SubtableColumn::SubtableColumn(Allocator& alloc, ref_type ref, Table* table, size_t column_ndx) + : SubtableColumnBase(alloc, ref, table, column_ndx) + , m_subspec_ndx(realm::npos) +{ +} + +inline ConstTableRef SubtableColumn::get_subtable_tableref(size_t subtable_ndx) const +{ + return const_cast(this)->get_subtable_tableref(subtable_ndx); +} + +inline void SubtableColumn::refresh_accessor_tree(size_t col_ndx, const Spec& spec) +{ + SubtableColumnBase::refresh_accessor_tree(col_ndx, spec); // Throws + m_subspec_ndx = spec.get_subspec_ndx(col_ndx); + std::lock_guard lg(m_subtable_map_lock); + m_subtable_map.refresh_accessor_tree(); // Throws +} + +inline void SubtableColumn::refresh_subtable_map() +{ + std::lock_guard lg(m_subtable_map_lock); + m_subtable_map.refresh_accessor_tree(); // Throws +} + +inline size_t SubtableColumn::get_subspec_ndx() const noexcept +{ + if (REALM_UNLIKELY(m_subspec_ndx == realm::npos)) { + typedef _impl::TableFriend tf; + m_subspec_ndx = tf::get_spec(*m_table).get_subspec_ndx(get_column_index()); + } + return m_subspec_ndx; +} + +inline Spec* SubtableColumn::get_subtable_spec() noexcept +{ + typedef _impl::TableFriend tf; + return tf::get_spec(*m_table).get_subtable_spec(get_column_index()); +} + + +} // namespace realm + +#endif // REALM_COLUMN_TABLE_HPP diff --git a/!main project/Pods/Realm/include/core/realm/column_timestamp.hpp b/!main project/Pods/Realm/include/core/realm/column_timestamp.hpp new file mode 100644 index 0000000..0cc6d07 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/column_timestamp.hpp @@ -0,0 +1,167 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_COLUMN_TIMESTAMP_HPP +#define REALM_COLUMN_TIMESTAMP_HPP + +#include +#include + +namespace realm { + +// Inherits from ColumnTemplate to get a compare_values() that can be called without knowing the +// column type +class TimestampColumn : public ColumnBaseSimple { +public: + TimestampColumn(bool nullable, Allocator& alloc, ref_type ref, size_t col_ndx = npos); + + static ref_type create(Allocator& alloc, size_t size, bool nullable); + static size_t get_size_from_ref(ref_type root_ref, Allocator& alloc) noexcept; + + /// Get the number of entries in this column. This operation is relatively + /// slow. + size_t size() const noexcept override; + /// Whether or not this column is nullable. + bool is_nullable() const noexcept override; + /// Whether or not the value at \a row_ndx is NULL. If the column is not + /// nullable, always returns false. + bool is_null(size_t row_ndx) const noexcept override; + /// Sets the value at \a row_ndx to be NULL. + /// \throw LogicError Thrown if this column is not nullable. + void set_null(size_t row_ndx) override; + void insert_rows(size_t row_ndx, size_t num_rows_to_insert, size_t prior_num_rows, bool nullable) override; + void erase_rows(size_t row_ndx, size_t num_rows_to_erase, size_t prior_num_rows, + bool broken_reciprocal_backlinks) override; + void move_last_row_over(size_t row_ndx, size_t prior_num_rows, bool broken_reciprocal_backlinks) override; + void clear(size_t num_rows, bool broken_reciprocal_backlinks) override; + void swap_rows(size_t row_ndx_1, size_t row_ndx_2) override; + void destroy() noexcept override; + + bool has_search_index() const noexcept final + { + return bool(m_search_index); + } + StringIndex* get_search_index() noexcept final + { + return m_search_index.get(); + } + StringIndex* get_search_index() const noexcept final + { + return m_search_index.get(); + } + void destroy_search_index() noexcept override; + void set_search_index_ref(ref_type ref, ArrayParent* parent, size_t ndx_in_parent) final; + void populate_search_index(); + StringIndex* create_search_index() override; + bool supports_search_index() const noexcept final + { + return true; + } + + StringData get_index_data(size_t, StringIndex::StringConversionBuffer& buffer) const noexcept override; + ref_type write(size_t slice_offset, size_t slice_size, size_t table_size, _impl::OutputStream&) const override; + void update_from_parent(size_t old_baseline) noexcept override; + void set_ndx_in_parent(size_t ndx) noexcept override; + void refresh_accessor_tree(size_t new_col_ndx, const Spec&) override; + + void verify() const override; + void to_dot(std::ostream&, StringData title = StringData()) const override; + void do_dump_node_structure(std::ostream&, int level) const override; + void leaf_to_dot(MemRef, ArrayParent*, size_t ndx_in_parent, std::ostream&) const override; + void get_seconds_leaf(size_t ndx, size_t& ndx_in_leaf, + BpTree>::LeafInfo& inout_leaf) const noexcept; + void get_nanoseconds_leaf(size_t ndx, size_t& ndx_in_leaf, BpTree::LeafInfo& inout_leaf) const noexcept; + + void add(const Timestamp& ts = Timestamp{}); + Timestamp get(size_t row_ndx) const noexcept; + void set(size_t row_ndx, const Timestamp& ts); + bool compare(const TimestampColumn& c) const noexcept; + int compare_values(size_t row1, size_t row2) const noexcept override; + + Timestamp maximum(size_t* result_index) const; + Timestamp minimum(size_t* result_index) const; + size_t count(Timestamp) const; + void erase(size_t row_ndx, bool is_last); + + template + size_t find(Timestamp value, size_t begin, size_t end) const noexcept + { + // FIXME: Here we can do all sorts of clever optimizations. Use bithack-search on seconds, then for each match + // check nanoseconds, etc. Lots of possibilities. Below code is naive and slow but works. + + Condition cond; + for (size_t t = begin; t < end; t++) { + Timestamp ts = get(t); + if (cond(ts, value, ts.is_null(), value.is_null())) + return t; + } + return npos; + } + + void find_all(IntegerColumn& result, Timestamp value, size_t begin, size_t end) const + { + if (m_search_index && begin == 0 && end == npos) { + m_search_index->find_all(result, value); // Throws + return; + } + REALM_ASSERT(false); + } + + typedef Timestamp value_type; + +private: + std::unique_ptr>> m_seconds; + std::unique_ptr> m_nanoseconds; + + std::unique_ptr m_search_index; + bool m_nullable; + + template + class CreateHandler; + + template + Timestamp minmax(size_t* result_index) const noexcept + { + // Condition is realm::Greater for maximum and realm::Less for minimum. Any non-null value is both larger + // and smaller than a null value. + if (size() == 0) { + if (result_index) + *result_index = npos; + return Timestamp{}; + } + + Timestamp best = get(0); + size_t best_index = best.is_null() ? npos : 0; + + for (size_t i = 1; i < size(); ++i) { + Timestamp candidate = get(i); + // Condition() will return false if any of the two values are null. + if ((best.is_null() && !candidate.is_null()) || Condition()(candidate, best, candidate.is_null(), best.is_null())) { + best = candidate; + best_index = i; + } + } + if (result_index) + *result_index = best_index; + return best; + } +}; + +} // namespace realm + +#endif // REALM_COLUMN_TIMESTAMP_HPP diff --git a/!main project/Pods/Realm/include/core/realm/column_tpl.hpp b/!main project/Pods/Realm/include/core/realm/column_tpl.hpp new file mode 100644 index 0000000..2411007 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/column_tpl.hpp @@ -0,0 +1,143 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_COLUMN_TPL_HPP +#define REALM_COLUMN_TPL_HPP + +#include + +#include +#include +#include + +namespace realm { + +template +class FloatDoubleNode; +template +class IntegerNode; +template +class SequentialGetter; + +template +struct ColumnTypeTraits2; + +template +struct ColumnTypeTraits2 { + typedef IntegerColumn column_type; + typedef ArrayInteger array_type; +}; +template +struct ColumnTypeTraits2 { + typedef IntegerColumn column_type; + typedef ArrayInteger array_type; +}; +template +struct ColumnTypeTraits2 { + typedef FloatColumn column_type; + typedef ArrayFloat array_type; +}; +template +struct ColumnTypeTraits2 { + typedef DoubleColumn column_type; + typedef ArrayDouble array_type; +}; + + +namespace _impl { + +template +struct FindInLeaf { + using LeafType = typename ColType::LeafType; + + template + static bool find(const LeafType& leaf, T target, size_t local_start, size_t local_end, size_t leaf_start, + QueryState& state) + { + Condition cond; + bool cont = true; + // todo, make an additional loop with hard coded `false` instead of is_null(v) for non-nullable columns + bool null_target = null::is_null_float(target); + for (size_t local_index = local_start; cont && local_index < local_end; local_index++) { + auto v = leaf.get(local_index); + if (cond(v, target, null::is_null_float(v), null_target)) { + cont = state.template match(leaf_start + local_index, 0, static_cast(v)); + } + } + return cont; + } +}; + +template <> +struct FindInLeaf { + using LeafType = IntegerColumn::LeafType; + + template + static bool find(const LeafType& leaf, T target, size_t local_start, size_t local_end, size_t leaf_start, + QueryState& state) + { + const int c = Condition::condition; + return leaf.find(c, action, target, local_start, local_end, leaf_start, &state); + } +}; + +template <> +struct FindInLeaf { + using LeafType = IntNullColumn::LeafType; + + template + static bool find(const LeafType& leaf, T target, size_t local_start, size_t local_end, size_t leaf_start, + QueryState& state) + { + constexpr int cond = Condition::condition; + return leaf.find(cond, action, target, local_start, local_end, leaf_start, &state); + } +}; + +} // namespace _impl + +template +R aggregate(const ColType& column, T target, size_t start, size_t end, size_t limit, size_t* return_ndx) +{ + if (end == npos) + end = column.size(); + + QueryState state; + state.init(action, nullptr, limit); + SequentialGetter sg{&column}; + + bool cont = true; + for (size_t s = start; cont && s < end;) { + sg.cache_next(s); + size_t start2 = s - sg.m_leaf_start; + size_t end2 = sg.local_end(end); + cont = _impl::FindInLeaf::template find(*sg.m_leaf_ptr, target, start2, end2, + sg.m_leaf_start, state); + s = sg.m_leaf_start + end2; + } + + if (return_ndx) + *return_ndx = action == act_Sum ? state.m_match_count : state.m_minmax_index; + + return state.m_state; +} + + +} // namespace realm + +#endif // REALM_COLUMN_TPL_HPP diff --git a/!main project/Pods/Realm/include/core/realm/column_type.hpp b/!main project/Pods/Realm/include/core/realm/column_type.hpp new file mode 100644 index 0000000..5a6e21c --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/column_type.hpp @@ -0,0 +1,70 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_COLUMN_TYPE_HPP +#define REALM_COLUMN_TYPE_HPP + +namespace realm { + + +// Note: Enumeration value assignments must be kept in sync with +// . +enum ColumnType { + // Column types + col_type_Int = 0, + col_type_Bool = 1, + col_type_String = 2, + col_type_StringEnum = 3, // double refs + col_type_Binary = 4, + col_type_Table = 5, + col_type_Mixed = 6, + col_type_OldDateTime = 7, + col_type_Timestamp = 8, + col_type_Float = 9, + col_type_Double = 10, + col_type_Reserved4 = 11, // Decimal + col_type_Link = 12, + col_type_LinkList = 13, + col_type_BackLink = 14 +}; + + +// Column attributes can be combined using bitwise or. +enum ColumnAttr { + col_attr_None = 0, + col_attr_Indexed = 1, + + /// Specifies that this column forms a unique constraint. It requires + /// `col_attr_Indexed`. + col_attr_Unique = 2, + + /// Reserved for future use. + col_attr_Reserved = 4, + + /// Specifies that the links of this column are strong, not weak. Applies + /// only to link columns (`type_Link` and `type_LinkList`). + col_attr_StrongLinks = 8, + + /// Specifies that elements in the column can be null. + col_attr_Nullable = 16 +}; + + +} // namespace realm + +#endif // REALM_COLUMN_TYPE_HPP diff --git a/!main project/Pods/Realm/include/core/realm/column_type_traits.hpp b/!main project/Pods/Realm/include/core/realm/column_type_traits.hpp new file mode 100644 index 0000000..78441de --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/column_type_traits.hpp @@ -0,0 +1,168 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_COLUMN_TYPE_TRAITS_HPP +#define REALM_COLUMN_TYPE_TRAITS_HPP + +#include +#include +#include +#include + +namespace realm { + +class OldDateTime; +class ArrayBinary; +class ArrayInteger; +class ArrayIntNull; +template +class BasicArray; + +template +struct ColumnTypeTraits; + +template <> +struct ColumnTypeTraits { + using column_type = Column; + using leaf_type = ArrayInteger; + using sum_type = int64_t; + using minmax_type = int64_t; + static const DataType id = type_Int; + static const ColumnType column_id = col_type_Int; + static const ColumnType real_column_type = col_type_Int; +}; + +template <> +struct ColumnTypeTraits> { + using column_type = Column>; + using leaf_type = ArrayIntNull; + using sum_type = int64_t; + using minmax_type = int64_t; + static const DataType id = type_Int; + static const ColumnType column_id = col_type_Int; + static const ColumnType real_column_type = col_type_Int; +}; + +template <> +struct ColumnTypeTraits : ColumnTypeTraits { + static const DataType id = type_Bool; + static const ColumnType column_id = col_type_Bool; +}; + +template <> +struct ColumnTypeTraits> : ColumnTypeTraits> { + static const DataType id = type_Bool; + static const ColumnType column_id = col_type_Bool; +}; + +template <> +struct ColumnTypeTraits { + using column_type = FloatColumn; + using leaf_type = BasicArray; + using sum_type = double; + using minmax_type = float; + static const DataType id = type_Float; + static const ColumnType column_id = col_type_Float; + static const ColumnType real_column_type = col_type_Float; +}; + +template <> +struct ColumnTypeTraits { + using column_type = DoubleColumn; + using leaf_type = BasicArray; + using sum_type = double; + using minmax_type = double; + static const DataType id = type_Double; + static const ColumnType column_id = col_type_Double; + static const ColumnType real_column_type = col_type_Double; +}; + +template <> +struct ColumnTypeTraits : ColumnTypeTraits { + static const DataType id = type_OldDateTime; + static const ColumnType column_id = col_type_OldDateTime; +}; + +template <> +struct ColumnTypeTraits { + using column_type = TimestampColumn; + static const DataType id = type_Timestamp; + static const ColumnType column_id = col_type_Timestamp; +}; + +template <> +struct ColumnTypeTraits> : ColumnTypeTraits> { + static const DataType id = type_OldDateTime; + static const ColumnType column_id = col_type_OldDateTime; +}; + +template <> +struct ColumnTypeTraits { + using column_type = StringColumn; + static const DataType id = type_String; + static const ColumnType column_id = col_type_String; +}; + +template <> +struct ColumnTypeTraits { + using column_type = BinaryColumn; + using leaf_type = ArrayBinary; + static const DataType id = type_Binary; + static const ColumnType column_id = col_type_Binary; + static const ColumnType real_column_type = col_type_Binary; +}; + +template +struct GetColumnType; +template <> +struct GetColumnType { + using type = IntegerColumn; +}; +template <> +struct GetColumnType { + using type = IntNullColumn; +}; +template +struct GetColumnType { + // FIXME: Null definition + using type = FloatColumn; +}; +template +struct GetColumnType { + // FIXME: Null definition + using type = DoubleColumn; +}; + +// Only purpose is to return 'double' if and only if source column (T) is float and you're doing a sum (A) +template +struct ColumnTypeTraitsSum { + typedef T sum_type; +}; + +template <> +struct ColumnTypeTraitsSum { + typedef double sum_type; +}; + +template +struct ColumnTypeTraitsSum, A> { + using sum_type = int64_t; +}; +} + +#endif // REALM_COLUMN_TYPE_TRAITS_HPP diff --git a/!main project/Pods/Realm/include/core/realm/data_type.hpp b/!main project/Pods/Realm/include/core/realm/data_type.hpp new file mode 100644 index 0000000..37e8a1b --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/data_type.hpp @@ -0,0 +1,67 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_DATA_TYPE_HPP +#define REALM_DATA_TYPE_HPP + +#include + +namespace realm { + +class StringData; +class BinaryData; +class Timestamp; + +typedef int64_t Int; +typedef bool Bool; +typedef float Float; +typedef double Double; +typedef realm::StringData String; +typedef realm::BinaryData Binary; +typedef realm::Timestamp Timestamp; + + +// Note: Value assignments must be kept in sync with +// Note: Value assignments must be kept in sync with +// Note: Value assignments must be kept in sync with +// Note: Value assignments must be kept in sync with "com/realm/ColumnType.java" +// Note: Any change to this enum is a file-format breaking change. +enum DataType { + type_Int = 0, + type_Bool = 1, + type_Float = 9, + type_Double = 10, + type_String = 2, + type_Binary = 4, + type_OldDateTime = 7, + type_Timestamp = 8, + type_Table = 5, + type_Mixed = 6, + type_Link = 12, + type_LinkList = 13 +}; + +/// See Descriptor::set_link_type(). +enum LinkType { + link_Strong, + link_Weak, +}; + +} // namespace realm + +#endif // REALM_DATA_TYPE_HPP diff --git a/!main project/Pods/Realm/include/core/realm/descriptor.hpp b/!main project/Pods/Realm/include/core/realm/descriptor.hpp new file mode 100644 index 0000000..9d37de5 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/descriptor.hpp @@ -0,0 +1,812 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_DESCRIPTOR_HPP +#define REALM_DESCRIPTOR_HPP + +#include + +#include +#include +#include + + +namespace realm { + +namespace _impl { +class DescriptorFriend; +} + + +/// Accessor for table type descriptors. +/// +/// A table type descriptor is an entity that specifies the dynamic +/// type of a Realm table. Objects of this class are accessors +/// through which the descriptor can be inspected and +/// changed. Accessors can become detached, see is_attached() for more +/// on this. The descriptor itself is stored inside the database file, +/// or elsewhere in case of a free-standing table or a table in a +/// free-standing group. +/// +/// The dynamic type consists first, and foremost of an ordered list +/// of column descriptors. Each column descriptor specifies the name +/// and type of the column. +/// +/// When a table has a subtable column, every cell in than column +/// contains a subtable. All those subtables have the same dynamic +/// type, and therefore have a shared descriptor. See is_root() for +/// more on this. +/// +/// The Table class contains convenience methods, such as +/// Table::get_column_count() and Table::add_column(), that allow you +/// to inspect and change the dynamic type of simple tables without +/// resorting to use of descriptors. For example, the following two +/// statements have the same effect: +/// +/// table->add_column(type, name); +/// table->get_descriptor()->add_column(type, name); +/// +/// Note, however, that this equivalence holds only as long as no +/// shared subtable descriptors are involved. +/// +/// \sa Table::get_descriptor() +class Descriptor : public std::enable_shared_from_this { +public: + /// Get the number of columns in the associated tables. + size_t get_column_count() const noexcept; + + /// Get the type of the column at the specified index. + /// + /// The consequences of specifying a column index that is out of + /// range, are undefined. + DataType get_column_type(size_t column_ndx) const noexcept; + + /// Get the name of the column at the specified index. + /// + /// The consequences of specifying a column index that is out of + /// range, are undefined. + StringData get_column_name(size_t column_ndx) const noexcept; + + /// Search for a column with the specified name. + /// + /// This function finds the first column with the specified name, + /// and returns its index. If there are no such columns, it + /// returns `not_found`. + size_t get_column_index(StringData name) const noexcept; + + /// Get the index of the table to which links in the column at the specified + /// index refer. + /// + /// The consequences of specifying a column index that is out of + /// range, are undefined. + /// + /// The consequences of specifying a column index that does not refer + /// to a link column, are undefined. + size_t get_column_link_target(size_t column_ndx) const noexcept; + + /// Get whether or not the specified column is nullable. + /// + /// The consequences of specifying a column index that is out of + /// range, are undefined. + bool is_nullable(size_t column_ndx) const noexcept; + + /// \defgroup descriptor_column_accessors Accessing Columns Via A Descriptor + /// + /// add_column() and add_column_link() are a shorthands for calling + /// insert_column() and insert_column_link(), respectively, with a column + /// index equal to the original number of columns. The returned value is + /// that column index. + /// + /// insert_column() inserts a new column into all the tables associated with + /// this descriptor. If any of the tables are not empty, the new column will + /// be filled with the default value associated with the specified data + /// type. This function cannot be used to insert link-type columns. For + /// that, you have to use insert_column_link() instead. + /// + /// This function modifies the dynamic type of all the tables that + /// share this descriptor. It does this by inserting a new column + /// with the specified name and type into the descriptor at the + /// specified index, and into each of the tables that share this + /// descriptor. + /// + /// insert_column_link() is like insert_column(), but inserts a link-type + /// column to a group-level table. It is not possible to add link-type + /// columns to tables that are not group-level tables. This functions must + /// be used in place of insert_column() when the column type is `type_Link` + /// or `type_LinkList`. A link-type column is associated with a particular + /// target table. All links in a link-type column refer to rows in the + /// target table of that column. The target table must also be a group-level + /// table, and it must belong to the same group as the origin table. + /// + /// \param name Name of new column. All strings are valid column names as + /// long as they are valid UTF-8 encodings and the number of bytes does not + /// exceed `max_column_name_length`. An attempt to add a column with a name + /// that is longer than `max_column_name_length` will cause an exception to + /// be thrown. + /// + /// \param subdesc If a non-null pointer is passed, and the + /// specified type is `type_Table`, then this function + /// automatically retrieves the descriptor associated with the new + /// subtable column, and stores a reference to its accessor in + /// `*subdesc`. + /// + /// \param col_ndx Insert the new column at this index. Preexisting columns + /// at indexes equal to, or greater than `col_ndx` will be shifted to the + /// next higher index. It is an error to specify an index that is greater + /// than the number of columns prior to the insertion. + /// + /// \param link_type See set_link_type(). + /// + /// \sa Table::add_column() + /// \sa Table::insert_column() + /// \sa Table::add_column_link() + /// \sa Table::insert_column_link() + /// \sa is_root() + //@{ + + static const size_t max_column_name_length = 63; + + size_t add_column(DataType type, StringData name, DescriptorRef* subdesc = nullptr, bool nullable = false); + + void insert_column(size_t col_ndx, DataType type, StringData name, DescriptorRef* subdesc = nullptr, + bool nullable = false); + + size_t add_column_link(DataType type, StringData name, Table& target, LinkType = link_Weak); + void insert_column_link(size_t col_ndx, DataType type, StringData name, Table& target, LinkType = link_Weak); + //@} + + /// Remove the specified column from each of the associated + /// tables. If the removed column is the only column in the + /// descriptor, then the table size will drop to zero for all + /// tables that were not already empty. + /// + /// This function modifies the dynamic type of all the tables that + /// share this descriptor. It does this by removing the column at + /// the specified index from the descriptor, and from each of the + /// tables that share this descriptor. The consequences of + /// specifying a column index that is out of range, are undefined. + /// + /// If the removed column was a subtable column, then the + /// associated descriptor accessor will be detached, if it + /// exists. This function will also detach all accessors of + /// subtables of the root table. Only the accessor of the root + /// table will remain attached. The root table is the table + /// associated with the root descriptor. + /// + /// \param col_ndx The index of the column to be removed. It is an error to + /// specify an index that is greater than, or equal to the number of + /// columns. + /// + /// \sa is_root() + /// \sa Table::remove_column() + void remove_column(size_t col_ndx); + + /// Rename the specified column. + /// + /// This function modifies the dynamic type of all the tables that + /// share this descriptor. The consequences of specifying a column + /// index that is out of range, are undefined. + /// + /// This function will detach all accessors of subtables of the + /// root table. Only the accessor of the root table will remain + /// attached. The root table is the table associated with the root + /// descriptor. + /// + /// \param col_ndx The index of the column to be renamed. It is an error to + /// specify an index that is greater than, or equal to the number of + /// columns. + /// + /// \param new_name The new name of the column. + /// + /// \sa is_root() + /// \sa Table::rename_column() + void rename_column(size_t col_ndx, StringData new_name); + + /// If the descriptor is describing a subtable column, the add_search_index() + /// and remove_search_index() will add or remove search indexes of *all* + /// subtables of the subtable column. This may take a while if there are many + /// subtables with many rows each. + bool has_search_index(size_t column_ndx) const noexcept; + void add_search_index(size_t column_ndx); + void remove_search_index(size_t column_ndx); + + /// There are two kinds of links, 'weak' and 'strong'. A strong link is one + /// that implies ownership, i.e., that the origin row (parent) owns the + /// target row (child). Simply stated, this means that when the origin row + /// (parent) is removed, so is the target row (child). If there are multiple + /// strong links to a target row, the origin rows share ownership, and the + /// target row is removed when the last owner disappears. Weak links do not + /// imply ownership, and will be nullified or removed when the target row + /// disappears. + /// + /// To put this in precise terms; when a strong link is broken, and the + /// target row has no other strong links to it, the target row is removed. A + /// row that is implicitly removed in this way, is said to be + /// *cascade-removed*. When a weak link is broken, nothing is + /// cascade-removed. + /// + /// A link is considered broken if + /// + /// - the link is nullified, removed, or replaced by a different link + /// (Row::nullify_link(), Row::set_link(), LinkView::remove_link(), + /// LinkView::set_link(), LinkView::clear()), or if + /// + /// - the origin row is explicitly removed (Row::move_last_over(), + /// Table::clear()), or if + /// + /// - the origin row is cascade-removed, or if + /// + /// - the origin column is removed from the table (Table::remove_column()), + /// or if + /// + /// - the origin table is removed from the group. + /// + /// Note that a link is *not* considered broken when it is replaced by a + /// link to the same target row. I.e., no no rows will be cascade-removed + /// due to such an operation. + /// + /// When a row is explicitly removed (such as by Table::move_last_over()), + /// all links to it are automatically removed or nullified. For single link + /// columns (type_Link), links to the removed row are nullified. For link + /// list columns (type_LinkList), links to the removed row are removed from + /// the list. + /// + /// When a row is cascade-removed there can no longer be any strong links to + /// it, but if there are any weak links, they will be removed or nullified. + /// + /// It is important to understand that this cascade-removal scheme is too + /// simplistic to enable detection and removal of orphaned link-cycles. In + /// this respect, it suffers from the same limitations as a reference + /// counting scheme generally does. + /// + /// It is also important to understand, that the possible presence of a link + /// cycle can cause a row to be cascade-removed as a consequence of being + /// modified. This happens, for example, if two rows, A and B, have strong + /// links to each other, and there are no other strong links to either of + /// them. In this case, if A->B is changed to A->C, then both A and B will + /// be cascade-removed. This can lead to obscure bugs in some applications, + /// such as in the following case: + /// + /// table.set_link(col_ndx_1, row_ndx, ...); + /// table.set_int(col_ndx_2, row_ndx, ...); // Oops, `row_ndx` may no longer refer to the same row + /// + /// To be safe, applications, that may encounter cycles, are advised to + /// adopt the following pattern: + /// + /// Row row = table[row_ndx]; + /// row.set_link(col_ndx_1, ...); + /// if (row) + /// row.set_int(col_ndx_2, ...); // Ok, because we check whether the row has disappeared + /// + /// \param col_ndx The index of the link column (`type_Link` or + /// `type_LinkList`) to be modified. It is an error to specify an index that + /// is greater than, or equal to the number of columns, or to specify the + /// index of a non-link column. + /// + /// \param link_type The type of links the column should store. + void set_link_type(size_t col_ndx, LinkType link_type); + + //@{ + /// Get the descriptor for the specified subtable column. + /// + /// This function provides access to the shared subtable + /// descriptor for the subtables in the specified column. The + /// specified column must be a column whose type is 'table'. The + /// consequences of specifying a column of a different type, or + /// specifying an index that is out of range, are undefined. + /// + /// Note that this function cannot be used with 'mixed' columns, + /// since subtables of that kind have independent dynamic types, + /// and therefore, have independent descriptors. You can only get + /// access to the descriptor of a subtable in a mixed column by + /// first getting access to the subtable itself. + /// + /// \sa is_root() + DescriptorRef get_subdescriptor(size_t column_ndx); + ConstDescriptorRef get_subdescriptor(size_t column_ndx) const; + //@} + + //@{ + /// Returns the parent table descriptor, if any. + /// + /// If this descriptor is the *root descriptor*, then this + /// function returns null. Otherwise it returns the accessor of + /// the parent descriptor. + /// + /// \sa is_root() + DescriptorRef get_parent() noexcept; + ConstDescriptorRef get_parent() const noexcept; + //@} + + //@{ + /// Get the table associated with the root descriptor. + /// + /// \sa get_parent() + /// \sa is_root() + TableRef get_root_table() noexcept; + ConstTableRef get_root_table() const noexcept; + //@} + + //@{ + /// Get the target table associated with the specified link column. This + /// descriptor must be a root descriptor (is_root()), and the specified + /// column must be a link column (`type_Link` or `type_LinkList`). + TableRef get_link_target(size_t col_ndx) noexcept; + ConstTableRef get_link_target(size_t col_ndx) const noexcept; + //@} + + /// Is this a root descriptor? + /// + /// Descriptors of tables with independent dynamic type are root + /// descriptors. Root descriptors are never shared. Tables that + /// are direct members of groups have independent dynamic + /// types. The same is true for free-standing tables and subtables + /// in columns of type 'mixed'. + /// + /// When a table has a column of type 'table', the cells in that + /// column contain subtables. All those subtables have the same + /// dynamic type, and they share a single dynamic type + /// descriptor. Such shared descriptors are never root + /// descriptors. + /// + /// A type descriptor can even be shared by subtables with + /// different parent tables, but only if the parent tables + /// themselves have a shared type descriptor. For example, if a + /// table has a column `foo` of type 'table', and each of the + /// subtables in `foo` has a column `bar` of type 'table', then + /// all the subtables in all the `bar` columns share the same + /// dynamic type descriptor. + /// + /// \sa Table::has_shared_type() + bool is_root() const noexcept; + + /// Determine whether this accessor is still attached. + /// + /// A table descriptor accessor may get detached from the + /// underlying descriptor for various reasons (see below). When it + /// does, it no longer refers to that descriptor, and can no + /// longer be used, except for calling is_attached(). The + /// consequences of calling other methods on a detached accessor + /// are undefined. Descriptor accessors obtained by calling + /// functions in the Realm API are always in the 'attached' + /// state immediately upon return from those functions. + /// + /// A descriptor accessor that is obtained directly from a table + /// becomes detached if the table becomes detached. A shared + /// subtable descriptor accessor that is obtained by a call to + /// get_subdescriptor() becomes detached if the parent descriptor + /// accessor becomes detached, or if the corresponding subtable + /// column is removed. A descriptor accessor does not get detached + /// under any other circumstances. + bool is_attached() const noexcept; + + //@{ + /// \brief Compare two table descriptors. + /// + /// Two table descriptors are equal if they have the same number of columns, + /// and for each column index, the two columns have the same name, data + /// type, and set of attributes. + /// + /// For link columns (`type_Link` and `type_LinkList`), the target table + /// (get_link_target()) of the two columns must be the same. + /// + /// For subtable columns (`type_Table`), the two corresponding + /// subdescriptors must themselves be equal, as if by a recursive call to + /// operator==(). + /// + /// The consequences of comparing a detached descriptor are + /// undefined. + bool operator==(const Descriptor&) const noexcept; + bool operator!=(const Descriptor&) const noexcept; + //@} + + /// If the specified column is optimized to store only unique values, then + /// this function returns the number of unique values currently + /// stored. Otherwise it returns zero. This function is mainly intended for + /// debugging purposes. + size_t get_num_unique_values(size_t column_ndx) const; + + ~Descriptor() noexcept; + +private: + // for initialization through make_shared + struct PrivateTag { + }; + +public: + Descriptor(const PrivateTag&) + : Descriptor() + { + } + +private: + // Table associated with root descriptor. Detached iff null. + TableRef m_root_table; + DescriptorRef m_parent; // Null iff detached or root descriptor. + Spec* m_spec; // Valid if attached. Owned iff valid and `m_parent`. + + // Whenever a subtable descriptor accessor is created, it is + // stored in this map. This ensures that when get_subdescriptor() + // is called to created multiple DescriptorRef objects that + // overlap in time, then they will all refer to the same + // descriptor object. + // + // It also enables the necessary recursive detaching of descriptor + // objects. + struct subdesc_entry { + size_t m_column_ndx; + std::weak_ptr m_subdesc; + subdesc_entry(size_t column_ndx, DescriptorRef); + }; + typedef std::vector subdesc_map; + mutable subdesc_map m_subdesc_map; + + Descriptor() noexcept; + + // Called by the root table if this becomes the root + // descriptor. Otherwise it is called by the descriptor that + // becomes its parent. + // + // Puts this descriptor accessor into the attached state. This + // attaches it to the underlying structure of array nodes. It does + // not establish the parents reference to this descriptor, that is + // the job of the parent. When this function returns, + // is_attached() will return true. + // + // Not idempotent. + // + // The specified table is not allowed to be a subtable with a + // shareable spec. That is, Table::has_shared_spec() must return + // false. + // + // The specified spec must be the spec of the specified table or + // of one of its direct or indirect subtable columns. + // + // When the specified spec is the spec of the root table, the + // parent must be specified as null. When the specified spec is + // not the root spec, a proper parent must be specified. + void attach(Table*, DescriptorRef parent, Spec*) noexcept; + + // Detach accessor from underlying descriptor. Caller must ensure + // that a reference count exists upon return, for example by + // obtaining an extra reference count before the call. + // + // This function is called either by the root table if this is the + // root descriptor, or by the parent descriptor, if it is not. + // + // Puts this descriptor accessor into the detached state. This + // detaches it from the underlying structure of array nodes. It + // also calls detach_subdesc_accessors(). When this function + // returns, is_attached() will return false. + // + // Not idempotent. + void detach() noexcept; + + // Recursively detach all subtable descriptor accessors that + // exist, that is, all subtable descriptor accessors that have + // this descriptor as ancestor. + void detach_subdesc_accessors() noexcept; + + // Record the path in terms of subtable column indexes from the + // root descriptor to this descriptor. If this descriptor is a + // root descriptor, the path is empty. Returns zero if the path is + // too long to fit in the specified buffer. Otherwise the path + // indexes will be stored between `begin_2`and `end`, where + // `begin_2` is the returned pointer. + size_t* record_subdesc_path(size_t* begin, size_t* end) const noexcept; + + // Returns a pointer to the accessor of the specified + // subdescriptor if that accessor exists, otherwise this function + // return null. + DescriptorRef get_subdesc_accessor(size_t column_ndx) noexcept; + + void adj_insert_column(size_t col_ndx) noexcept; + void adj_erase_column(size_t col_ndx) noexcept; + + friend class util::bind_ptr; + friend class util::bind_ptr; + friend class _impl::DescriptorFriend; +}; + + +// Implementation: + +inline size_t Descriptor::get_column_count() const noexcept +{ + REALM_ASSERT(is_attached()); + return m_spec->get_public_column_count(); +} + +inline StringData Descriptor::get_column_name(size_t ndx) const noexcept +{ + REALM_ASSERT(is_attached()); + return m_spec->get_column_name(ndx); +} + +inline DataType Descriptor::get_column_type(size_t ndx) const noexcept +{ + REALM_ASSERT(is_attached()); + return m_spec->get_public_column_type(ndx); +} + +inline bool Descriptor::is_nullable(size_t ndx) const noexcept +{ + REALM_ASSERT(is_attached()); + return m_spec->get_column_attr(ndx) & col_attr_Nullable; +} + +inline size_t Descriptor::get_column_index(StringData name) const noexcept +{ + REALM_ASSERT(is_attached()); + return m_spec->get_column_index(name); +} + +inline size_t Descriptor::get_column_link_target(size_t column_ndx) const noexcept +{ + REALM_ASSERT(is_attached()); + return m_spec->get_opposite_link_table_ndx(column_ndx); +} + +inline size_t Descriptor::add_column(DataType type, StringData name, DescriptorRef* subdesc, bool nullable) +{ + size_t col_ndx = m_spec->get_public_column_count(); + insert_column(col_ndx, type, name, subdesc, nullable); // Throws + return col_ndx; +} + +inline void Descriptor::insert_column(size_t col_ndx, DataType type, StringData name, DescriptorRef* subdesc, + bool nullable) +{ + typedef _impl::TableFriend tf; + + if (REALM_UNLIKELY(!is_attached())) + throw LogicError(LogicError::detached_accessor); + if (REALM_UNLIKELY(col_ndx > get_column_count())) + throw LogicError(LogicError::column_index_out_of_range); + if (REALM_UNLIKELY(tf::is_link_type(ColumnType(type)))) + throw LogicError(LogicError::illegal_type); + + LinkTargetInfo invalid_link; + tf::insert_column(*this, col_ndx, type, name, invalid_link, nullable); // Throws + adj_insert_column(col_ndx); + if (subdesc && type == type_Table) + *subdesc = get_subdescriptor(col_ndx); +} + +inline size_t Descriptor::add_column_link(DataType type, StringData name, Table& target, LinkType link_type) +{ + size_t col_ndx = m_spec->get_public_column_count(); + insert_column_link(col_ndx, type, name, target, link_type); // Throws + return col_ndx; +} + +inline void Descriptor::insert_column_link(size_t col_ndx, DataType type, StringData name, Table& target, + LinkType link_type) +{ + typedef _impl::TableFriend tf; + + if (REALM_UNLIKELY(!is_attached() || !target.is_attached())) + throw LogicError(LogicError::detached_accessor); + if (REALM_UNLIKELY(col_ndx > get_column_count())) + throw LogicError(LogicError::column_index_out_of_range); + if (REALM_UNLIKELY(!tf::is_link_type(ColumnType(type)))) + throw LogicError(LogicError::illegal_type); + if (REALM_UNLIKELY(!is_root())) + throw LogicError(LogicError::wrong_kind_of_descriptor); + // Both origin and target must be group-level tables, and in the same group. + Group* origin_group = tf::get_parent_group(*get_root_table()); + Group* target_group = tf::get_parent_group(target); + if (!origin_group || !target_group) + throw LogicError(LogicError::wrong_kind_of_table); + if (origin_group != target_group) + throw LogicError(LogicError::group_mismatch); + + LinkTargetInfo link(&target); + tf::insert_column(*this, col_ndx, type, name, link); // Throws + adj_insert_column(col_ndx); + + tf::set_link_type(*get_root_table(), col_ndx, link_type); // Throws +} + +inline void Descriptor::remove_column(size_t col_ndx) +{ + typedef _impl::TableFriend tf; + + if (REALM_UNLIKELY(!is_attached())) + throw LogicError(LogicError::detached_accessor); + if (REALM_UNLIKELY(col_ndx >= get_column_count())) + throw LogicError(LogicError::column_index_out_of_range); + + tf::erase_column(*this, col_ndx); // Throws + adj_erase_column(col_ndx); +} + +inline void Descriptor::rename_column(size_t col_ndx, StringData name) +{ + typedef _impl::TableFriend tf; + + if (REALM_UNLIKELY(!is_attached())) + throw LogicError(LogicError::detached_accessor); + if (REALM_UNLIKELY(col_ndx >= get_column_count())) + throw LogicError(LogicError::column_index_out_of_range); + + tf::rename_column(*this, col_ndx, name); // Throws +} + +inline void Descriptor::set_link_type(size_t col_ndx, LinkType link_type) +{ + typedef _impl::TableFriend tf; + + if (REALM_UNLIKELY(!is_attached())) + throw LogicError(LogicError::detached_accessor); + if (REALM_UNLIKELY(col_ndx >= get_column_count())) + throw LogicError(LogicError::column_index_out_of_range); + if (REALM_UNLIKELY(!tf::is_link_type(ColumnType(get_column_type(col_ndx))))) + throw LogicError(LogicError::illegal_type); + + tf::set_link_type(*get_root_table(), col_ndx, link_type); // Throws +} + +inline ConstDescriptorRef Descriptor::get_subdescriptor(size_t column_ndx) const +{ + return const_cast(this)->get_subdescriptor(column_ndx); +} + +inline DescriptorRef Descriptor::get_parent() noexcept +{ + return m_parent; +} + +inline ConstDescriptorRef Descriptor::get_parent() const noexcept +{ + return const_cast(this)->get_parent(); +} + +inline TableRef Descriptor::get_root_table() noexcept +{ + return m_root_table; +} + +inline ConstTableRef Descriptor::get_root_table() const noexcept +{ + return const_cast(this)->get_root_table(); +} + +inline TableRef Descriptor::get_link_target(size_t col_ndx) noexcept +{ + REALM_ASSERT(is_attached()); + REALM_ASSERT(is_root()); + return get_root_table()->get_link_target(col_ndx); +} + +inline ConstTableRef Descriptor::get_link_target(size_t col_ndx) const noexcept +{ + REALM_ASSERT(is_attached()); + REALM_ASSERT(is_root()); + return get_root_table()->get_link_target(col_ndx); +} + +inline bool Descriptor::is_root() const noexcept +{ + return !m_parent; +} + +inline Descriptor::Descriptor() noexcept +{ +} + +inline void Descriptor::attach(Table* table, DescriptorRef parent, Spec* spec) noexcept +{ + REALM_ASSERT(!is_attached()); + REALM_ASSERT(!table->has_shared_type()); + m_root_table.reset(table); + m_parent = parent; + m_spec = spec; +} + +inline bool Descriptor::is_attached() const noexcept +{ + return bool(m_root_table); +} + +inline Descriptor::subdesc_entry::subdesc_entry(size_t n, DescriptorRef d) + : m_column_ndx(n) + , m_subdesc(d) +{ +} + +inline bool Descriptor::operator==(const Descriptor& d) const noexcept +{ + REALM_ASSERT(is_attached()); + REALM_ASSERT(d.is_attached()); + return *m_spec == *d.m_spec; +} + +inline bool Descriptor::operator!=(const Descriptor& d) const noexcept +{ + return !(*this == d); +} + +// The purpose of this class is to give internal access to some, but +// not all of the non-public parts of the Descriptor class. +class _impl::DescriptorFriend { +public: + static DescriptorRef create() + { + return std::make_shared(Descriptor::PrivateTag()); // Throws + } + + static void attach(Descriptor& desc, Table* table, DescriptorRef parent, Spec* spec) noexcept + { + desc.attach(table, parent, spec); + } + + static void detach(Descriptor& desc) noexcept + { + desc.detach(); + } + + static void detach_subdesc_accessors(Descriptor& desc) noexcept + { + desc.detach_subdesc_accessors(); + } + + static Table& get_root_table(Descriptor& desc) noexcept + { + return *desc.m_root_table; + } + + static const Table& get_root_table(const Descriptor& desc) noexcept + { + return *desc.m_root_table; + } + + static Spec& get_spec(Descriptor& desc) noexcept + { + return *desc.m_spec; + } + + static const Spec& get_spec(const Descriptor& desc) noexcept + { + return *desc.m_spec; + } + + static size_t* record_subdesc_path(const Descriptor& desc, size_t* begin, size_t* end) noexcept + { + return desc.record_subdesc_path(begin, end); + } + + static DescriptorRef get_subdesc_accessor(Descriptor& desc, size_t column_ndx) noexcept + { + return desc.get_subdesc_accessor(column_ndx); + } + + static void adj_insert_column(Descriptor& desc, size_t col_ndx) noexcept + { + desc.adj_insert_column(col_ndx); + } + + static void adj_erase_column(Descriptor& desc, size_t col_ndx) noexcept + { + desc.adj_erase_column(col_ndx); + } +}; + +} // namespace realm + +#endif // REALM_DESCRIPTOR_HPP diff --git a/!main project/Pods/Realm/include/core/realm/descriptor_fwd.hpp b/!main project/Pods/Realm/include/core/realm/descriptor_fwd.hpp new file mode 100644 index 0000000..2937724 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/descriptor_fwd.hpp @@ -0,0 +1,33 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_DESCRIPTOR_FWD_HPP +#define REALM_DESCRIPTOR_FWD_HPP + +#include + + +namespace realm { + +class Descriptor; +typedef std::shared_ptr DescriptorRef; +typedef std::shared_ptr ConstDescriptorRef; + +} // namespace realm + +#endif // REALM_DESCRIPTOR_FWD_HPP diff --git a/!main project/Pods/Realm/include/core/realm/disable_sync_to_disk.hpp b/!main project/Pods/Realm/include/core/realm/disable_sync_to_disk.hpp new file mode 100644 index 0000000..f642d6f --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/disable_sync_to_disk.hpp @@ -0,0 +1,37 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_DISABLE_SYNC_TO_DISK_HPP +#define REALM_DISABLE_SYNC_TO_DISK_HPP + +#include + +namespace realm { + +/// Completely disable synchronization with storage device to speed up unit +/// testing. This is an unsafe mode of operation, and should never be used in +/// production. This function is thread safe. +void disable_sync_to_disk(); + +/// Returns true after disable_sync_to_disk() has been called. This function is +/// thread safe. +bool get_disable_sync_to_disk() noexcept; + +} // namespace realm + +#endif // REALM_DISABLE_SYNC_TO_DISK_HPP diff --git a/!main project/Pods/Realm/include/core/realm/exceptions.hpp b/!main project/Pods/Realm/include/core/realm/exceptions.hpp new file mode 100644 index 0000000..7db8d18 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/exceptions.hpp @@ -0,0 +1,338 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_EXCEPTIONS_HPP +#define REALM_EXCEPTIONS_HPP + +#include + +#include +#include + +namespace realm { + +using util::ExceptionWithBacktrace; + +/// Thrown by various functions to indicate that a specified table does not +/// exist. +class NoSuchTable : public ExceptionWithBacktrace { +public: + const char* message() const noexcept override; +}; + + +/// Thrown by various functions to indicate that a specified table name is +/// already in use. +class TableNameInUse : public ExceptionWithBacktrace { +public: + const char* message() const noexcept override; +}; + + +// Thrown by functions that require a table to **not** be the target of link +// columns, unless those link columns are part of the table itself. +class CrossTableLinkTarget : public ExceptionWithBacktrace { +public: + const char* message() const noexcept override; +}; + + +/// Thrown by various functions to indicate that the dynamic type of a table +/// does not match a particular other table type (dynamic or static). +class DescriptorMismatch : public ExceptionWithBacktrace { +public: + const char* message() const noexcept override; +}; + + +/// The FileFormatUpgradeRequired exception can be thrown by the SharedGroup +/// constructor when opening a database that uses a deprecated file format +/// and/or a deprecated history schema, and the user has indicated he does not +/// want automatic upgrades to be performed. This exception indicates that until +/// an upgrade of the file format is performed, the database will be unavailable +/// for read or write operations. +class FileFormatUpgradeRequired : public ExceptionWithBacktrace { +public: + const char* message() const noexcept override; +}; + + +/// Thrown when a sync agent attempts to join a session in which there is +/// already a sync agent. A session may only contain one sync agent at any given +/// time. +class MultipleSyncAgents : public ExceptionWithBacktrace { +public: + const char* message() const noexcept override; +}; + + +/// Thrown when memory can no longer be mapped to. When mmap/remap fails. +class AddressSpaceExhausted : public std::runtime_error { +public: + AddressSpaceExhausted(const std::string& msg); + /// runtime_error::what() returns the msg provided in the constructor. +}; + +/// Thrown when creating references that are too large to be contained in our ref_type (size_t) +class MaximumFileSizeExceeded : public std::runtime_error { +public: + MaximumFileSizeExceeded(const std::string& msg); + /// runtime_error::what() returns the msg provided in the constructor. +}; + +/// Thrown when writing fails because the disk is full. +class OutOfDiskSpace : public std::runtime_error { +public: + OutOfDiskSpace(const std::string& msg); + /// runtime_error::what() returns the msg provided in the constructor. +}; + +// SerialisationError intentionally does not inherit ExceptionWithBacktrace +// because the query-based-sync permissions queries generated on the server +// use a LinksToNode which is not currently serialisable (this limitation can +// be lifted in core 6 given stable ids). Coupled with query metrics which +// serialize all queries, the capturing of the stack for these frequent +// permission queries shows up in performance profiles. +class SerialisationError : public std::runtime_error { +public: + SerialisationError(const std::string& msg); + /// runtime_error::what() returns the msg provided in the constructor. +}; + +// thrown when a user constructed link path is not a valid input +class InvalidPathError : public std::runtime_error { +public: + InvalidPathError(const std::string& msg); + /// runtime_error::what() returns the msg provided in the constructor. +}; + + +/// The \c LogicError exception class is intended to be thrown only when +/// applications (or bindings) violate rules that are stated (or ought to have +/// been stated) in the documentation of the public API, and only in cases +/// where the violation could have been easily and efficiently predicted by the +/// application. In other words, this exception class is for the cases where +/// the error is due to incorrect use of the public API. +/// +/// This class is not supposed to be caught by applications. It is not even +/// supposed to be considered part of the public API, and therefore the +/// documentation of the public API should **not** mention the \c LogicError +/// exception class by name. Note how this contrasts with other exception +/// classes, such as \c NoSuchTable, which are part of the public API, and are +/// supposed to be mentioned in the documentation by name. The \c LogicError +/// exception is part of Realm's private API. +/// +/// In other words, the \c LogicError class should exclusively be used in +/// replacement (or in addition to) asserts (debug or not) in order to +/// guarantee program interruption, while still allowing for complete +/// test-cases to be written and run. +/// +/// To this effect, the special `CHECK_LOGIC_ERROR()` macro is provided as a +/// test framework plugin to allow unit tests to check that the functions in +/// the public API do throw \c LogicError when rules are violated. +/// +/// The reason behind hiding this class from the public API is to prevent users +/// from getting used to the idea that "Undefined Behaviour" equates a specific +/// exception being thrown. The whole point of properly documenting "Undefined +/// Behaviour" cases is to help the user know what the limits are, without +/// constraining the database to handle every and any use-case thrown at it. +/// +/// FIXME: This exception class should probably be moved to the `_impl` +/// namespace, in order to avoid some confusion. +class LogicError : public ExceptionWithBacktrace { +public: + enum ErrorKind { + string_too_big, + binary_too_big, + table_name_too_long, + column_name_too_long, + table_index_out_of_range, + row_index_out_of_range, + column_index_out_of_range, + string_position_out_of_range, + link_index_out_of_range, + bad_version, + illegal_type, + + /// Indicates that an argument has a value that is illegal in combination + /// with another argument, or with the state of an involved object. + illegal_combination, + + /// Indicates a data type mismatch, such as when `Table::find_pkey_int()` is + /// called and the type of the primary key is not `type_Int`. + type_mismatch, + + /// Indicates that two involved tables are not in the same group. + group_mismatch, + + /// Indicates that an involved descriptor is of the wrong kind, i.e., if + /// it is a subtable descriptor, and the function requires a root table + /// descriptor. + wrong_kind_of_descriptor, + + /// Indicates that an involved table is of the wrong kind, i.e., if it + /// is a subtable, and the function requires a root table, or if it is a + /// free-standing table, and the function requires a group-level table. + wrong_kind_of_table, + + /// Indicates that an involved accessor is was detached, i.e., was not + /// attached to an underlying object. + detached_accessor, + + /// Indicates that a specified row index of a target table (a link) is + /// out of range. This is used for disambiguation in cases such as + /// Table::set_link() where one specifies both a row index of the origin + /// table, and a row index of the target table. + target_row_index_out_of_range, + + // Indicates that an involved column lacks a search index. + no_search_index, + + /// Indicates that a modification was attempted that would have produced a + /// duplicate primary value. + unique_constraint_violation, + + /// User attempted to insert null in non-nullable column + column_not_nullable, + + /// Group::open() is called on a group accessor that is already in the + /// attached state. Or Group::open() or Group::commit() is called on a + /// group accessor that is managed by a SharedGroup object. + wrong_group_state, + + /// No active transaction on a particular SharedGroup object (e.g., + /// SharedGroup::commit()), or the active transaction on the SharedGroup + /// object is of the wrong type (read/write), or an attampt was made to + /// initiate a new transaction while one is already in progress on the + /// same SharedGroup object. + wrong_transact_state, + + /// Attempted use of a continuous transaction through a SharedGroup + /// object with no history. See Replication::get_history(). + no_history, + + /// Durability setting (as passed to the SharedGroup constructor) was + /// not consistent across the session. + mixed_durability, + + /// History type (as specified by the Replication implementation passed + /// to the SharedGroup constructor) was not consistent across the + /// session. + mixed_history_type, + + /// History schema version (as specified by the Replication + /// implementation passed to the SharedGroup constructor) was not + /// consistent across the session. + mixed_history_schema_version, + + /// Adding rows to a table with no columns is not supported. + table_has_no_columns, + + /// Referring to a column that has been deleted. + column_does_not_exist, + + /// You can not add index on a subtable of a subtable + subtable_of_subtable_index + }; + + LogicError(ErrorKind message); + + const char* message() const noexcept override; + ErrorKind kind() const noexcept; + +private: + ErrorKind m_kind; +}; + + +// Implementation: + +// LCOV_EXCL_START (Wording of what() strings are not to be tested) + +inline const char* NoSuchTable::message() const noexcept +{ + return "No such table exists"; +} + +inline const char* TableNameInUse::message() const noexcept +{ + return "The specified table name is already in use"; +} + +inline const char* CrossTableLinkTarget::message() const noexcept +{ + return "Table is target of cross-table link columns"; +} + +inline const char* DescriptorMismatch::message() const noexcept +{ + return "Table descriptor mismatch"; +} + +inline const char* FileFormatUpgradeRequired::message() const noexcept +{ + return "Database upgrade required but prohibited"; +} + +inline const char* MultipleSyncAgents::message() const noexcept +{ + return "Multiple sync agents attempted to join the same session"; +} + +// LCOV_EXCL_STOP + +inline AddressSpaceExhausted::AddressSpaceExhausted(const std::string& msg) + : std::runtime_error(msg) +{ +} + +inline MaximumFileSizeExceeded::MaximumFileSizeExceeded(const std::string& msg) + : std::runtime_error(msg) +{ +} + +inline OutOfDiskSpace::OutOfDiskSpace(const std::string& msg) +: std::runtime_error(msg) +{ +} + +inline SerialisationError::SerialisationError(const std::string& msg) + : std::runtime_error(msg) +{ +} + +inline InvalidPathError::InvalidPathError(const std::string& msg) + : runtime_error(msg) +{ +} + +inline LogicError::LogicError(LogicError::ErrorKind k) + : m_kind(k) +{ +} + +inline LogicError::ErrorKind LogicError::kind() const noexcept +{ + return m_kind; +} + + +} // namespace realm + + +#endif // REALM_EXCEPTIONS_HPP diff --git a/!main project/Pods/Realm/include/core/realm/group.hpp b/!main project/Pods/Realm/include/core/realm/group.hpp new file mode 100644 index 0000000..3c0a9e6 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/group.hpp @@ -0,0 +1,1440 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_GROUP_HPP +#define REALM_GROUP_HPP + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace realm { + +class SharedGroup; +namespace _impl { +class GroupFriend; +class TransactLogConvenientEncoder; +class TransactLogParser; +} + + +/// A group is a collection of named tables. +/// +/// Tables occur in the group in an unspecified order, but an order that +/// generally remains fixed. The order is guaranteed to remain fixed between two +/// points in time if no tables are added to, or removed from the group during +/// that time. When tables are added to, or removed from the group, the order +/// may change arbitrarily. +/// +/// If `table` is a table accessor attached to a group-level table, and `group` +/// is a group accessor attached to the group, then the following is guaranteed, +/// even after a change in the table order: +/// +/// \code{.cpp} +/// +/// table == group.get_table(table.get_index_in_group()) +/// +/// \endcode +/// +class Group : private Table::Parent { +public: + /// Construct a free-standing group. This group instance will be + /// in the attached state, but neither associated with a file, nor + /// with an external memory buffer. + Group(); + + enum OpenMode { + /// Open in read-only mode. Fail if the file does not already exist. + mode_ReadOnly, + /// Open in read/write mode. Create the file if it doesn't exist. + mode_ReadWrite, + /// Open in read/write mode. Fail if the file does not already exist. + mode_ReadWriteNoCreate + }; + + /// Equivalent to calling open(const std::string&, const char*, OpenMode) + /// on an unattached group accessor. + explicit Group(const std::string& file, const char* encryption_key = nullptr, OpenMode = mode_ReadOnly); + + /// Equivalent to calling open(BinaryData, bool) on an unattached + /// group accessor. Note that if this constructor throws, the + /// ownership of the memory buffer will remain with the caller, + /// regardless of whether \a take_ownership is set to `true` or + /// `false`. + explicit Group(BinaryData, bool take_ownership = true); + + struct unattached_tag { + }; + + /// Create a Group instance in its unattached state. It may then + /// be attached to a database file later by calling one of the + /// open() methods. You may test whether this instance is + /// currently in its attached state by calling + /// is_attached(). Calling any other method (except the + /// destructor) while in the unattached state has undefined + /// behavior. + Group(unattached_tag) noexcept; + + // FIXME: Implement a proper copy constructor (fairly trivial). + Group(const Group&) = delete; + Group& operator=(const Group&) = delete; + + ~Group() noexcept override; + + /// Attach this Group instance to the specified database file. + /// + /// By default, the specified file is opened in read-only mode + /// (mode_ReadOnly). This allows opening a file even when the + /// caller lacks permission to write to that file. The opened + /// group may still be modified freely, but the changes cannot be + /// written back to the same file using the commit() function. An + /// attempt to do that, will cause an exception to be thrown. When + /// opening in read-only mode, it is an error if the specified + /// file does not already exist in the file system. + /// + /// Alternatively, the file can be opened in read/write mode + /// (mode_ReadWrite). This allows use of the commit() function, + /// but, of course, it also requires that the caller has + /// permission to write to the specified file. When opening in + /// read-write mode, an attempt to create the specified file will + /// be made, if it does not already exist in the file system. + /// + /// In any case, if the file already exists, it must contain a + /// valid Realm database. In many cases invalidity will be + /// detected and cause the InvalidDatabase exception to be thrown, + /// but you should not rely on it. + /// + /// Note that changes made to the database via a Group instance + /// are not automatically committed to the specified file. You + /// may, however, at any time, explicitly commit your changes by + /// calling the commit() method, provided that the specified + /// open-mode is not mode_ReadOnly. Alternatively, you may call + /// write() to write the entire database to a new file. Writing + /// the database to a new file does not end, or in any other way + /// change the association between the Group instance and the file + /// that was specified in the call to open(). + /// + /// A Realm file that contains a history (see Replication::HistoryType) may + /// be opened via Group::open(), as long as the application can ensure that + /// there is no concurrent access to the file (see below for more on + /// concurrency), but if the file is modified via Group::commit() the + /// history will be discarded. To retain the history, the application must + /// instead access the file in shared mode, i.e., via SharedGroup, and + /// supply the right kind of replication plugin (see + /// Replication::get_history_type()). + /// + /// A file that is passed to Group::open(), may not be modified by + /// a third party until after the Group object is + /// destroyed. Behavior is undefined if a file is modified by a + /// third party while any Group object is associated with it. + /// + /// Calling open() on a Group instance that is already in the + /// attached state has undefined behavior. + /// + /// Accessing a Realm database file through manual construction + /// of a Group object does not offer any level of thread safety or + /// transaction safety. When any of those kinds of safety are a + /// concern, consider using a SharedGroup instead. When accessing + /// a database file in read/write mode through a manually + /// constructed Group object, it is entirely the responsibility of + /// the application that the file is not accessed in any way by a + /// third party during the life-time of that group object. It is, + /// on the other hand, safe to concurrently access a database file + /// by multiple manually created Group objects, as long as all of + /// them are opened in read-only mode, and there is no other party + /// that modifies the file concurrently. + /// + /// Do not call this function on a group instance that is managed + /// by a shared group. Doing so will result in undefined behavior. + /// + /// Even if this function throws, it may have the side-effect of + /// creating the specified file, and the file may get left behind + /// in an invalid state. Of course, this can only happen if + /// read/write mode (mode_ReadWrite) was requested, and the file + /// did not already exist. + /// + /// \param file File system path to a Realm database file. + /// + /// \param encryption_key 32-byte key used to encrypt and decrypt + /// the database file, or nullptr to disable encryption. + /// + /// \param mode Specifying a mode that is not mode_ReadOnly + /// requires that the specified file can be opened in read/write + /// mode. In general there is no reason to open a group in + /// read/write mode unless you want to be able to call + /// Group::commit(). + /// + /// \throw util::File::AccessError If the file could not be + /// opened. If the reason corresponds to one of the exception + /// types that are derived from util::File::AccessError, the + /// derived exception type is thrown. Note that InvalidDatabase is + /// among these derived exception types. + void open(const std::string& file, const char* encryption_key = nullptr, OpenMode mode = mode_ReadOnly); + + /// Attach this Group instance to the specified memory buffer. + /// + /// This is similar to constructing a group from a file except + /// that in this case the database is assumed to be stored in the + /// specified memory buffer. + /// + /// If \a take_ownership is `true`, you pass the ownership of the + /// specified buffer to the group. In this case the buffer will + /// eventually be freed using std::free(), so the buffer you pass, + /// must have been allocated using std::malloc(). + /// + /// On the other hand, if \a take_ownership is set to `false`, it + /// is your responsibility to keep the memory buffer alive during + /// the lifetime of the group, and in case the buffer needs to be + /// deallocated afterwards, that is your responsibility too. + /// + /// If this function throws, the ownership of the memory buffer + /// will remain with the caller, regardless of whether \a + /// take_ownership is set to `true` or `false`. + /// + /// Calling open() on a Group instance that is already in the + /// attached state has undefined behavior. + /// + /// Do not call this function on a group instance that is managed + /// by a shared group. Doing so will result in undefined behavior. + /// + /// \throw InvalidDatabase If the specified buffer does not appear + /// to contain a valid database. + void open(BinaryData, bool take_ownership = true); + + /// A group may be created in the unattached state, and then later + /// attached to a file with a call to open(). Calling any method + /// other than open(), and is_attached() on an unattached instance + /// results in undefined behavior. + bool is_attached() const noexcept; + + /// Returns true if, and only if the number of tables in this + /// group is zero. + bool is_empty() const noexcept; + + /// Returns the number of tables in this group. + size_t size() const noexcept; + + /// \defgroup group_table_access Table Accessors + /// + /// has_table() returns true if, and only if this group contains a table + /// with the specified name. + /// + /// find_table() returns the index of the first table in this group with the + /// specified name, or `realm::not_found` if this group does not contain a + /// table with the specified name. + /// + /// get_table_name() returns the name of table at the specified index. + /// + /// The versions of get_table(), that accepts a \a name argument, return the + /// first table with the specified name, or null if no such table exists. + /// + /// add_table() adds a table with the specified name to this group. It + /// throws TableNameInUse if \a require_unique_name is true and \a name + /// clashes with the name of an existing table. If \a require_unique_name is + /// false, it is possible to add more than one table with the same + /// name. Whenever a table is added, the order of the preexisting tables may + /// change arbitrarily, and the new table may not end up as the last one + /// either. But know that you can always call Table::get_index_in_group() on + /// the returned table accessor to find out at which index it ends up. + /// + /// get_or_add_table() checks if a table exists in this group with the specified + /// name. If it doesn't exist, a table is created. + /// + /// get_or_insert_table() works slightly differently from get_or_add_table(), + /// in that it considers the position of the requested table as part of that + /// table's identifying "key", in addition to the name. + /// + /// remove_table() removes the specified table from this group. A table can + /// be removed only when it is not the target of a link column of a + /// different table. Whenever a table is removed, the order of the remaining + /// tables may change arbitrarily. + /// + /// rename_table() changes the name of a preexisting table. If \a + /// require_unique_name is false, it becomes possible to have more than one + /// table with a given name in a single group. + /// + /// The template functions work exactly like their non-template namesakes + /// except as follows: The template versions of get_table() and + /// get_or_add_table() throw DescriptorMismatch if the dynamic type of the + /// specified table does not match the statically specified custom table + /// type. The template versions of add_table() and get_or_add_table() set + /// the dynamic type (descriptor) to match the statically specified custom + /// table type. + /// + /// \param index Index of table in this group. + /// + /// \param name Name of table. All strings are valid table names as long as + /// they are valid UTF-8 encodings and the number of bytes does not exceed + /// `max_table_name_length`. A call to add_table() or get_or_add_table() + /// with a name that is longer than `max_table_name_length` will cause an + /// exception to be thrown. + /// + /// \param new_name New name for preexisting table. + /// + /// \param require_unique_name When set to true (the default), it becomes + /// impossible to add a table with a name that is already in use, or to + /// rename a table to a name that is already in use. + /// + /// \param was_added When specified, the boolean variable is set to true if + /// the table was added, and to false otherwise. If the function throws, the + /// boolean variable retains its original value. + /// + /// \return get_table(), add_table(), and get_or_add_table() return a table + /// accessor attached to the requested (or added) table. get_table() may + /// return null. + /// + /// \throw DescriptorMismatch Thrown by get_table() and get_or_add_table() + /// tf the dynamic table type does not match the statically specified custom + /// table type (\a T). + /// + /// \throw NoSuchTable Thrown by remove_table() and rename_table() if there + /// is no table with the specified \a name. + /// + /// \throw TableNameInUse Thrown by add_table() if \a require_unique_name is + /// true and \a name clashes with the name of a preexisting table. Thrown by + /// rename_table() if \a require_unique_name is true and \a new_name clashes + /// with the name of a preexisting table. + /// + /// \throw CrossTableLinkTarget Thrown by remove_table() if the specified + /// table is the target of a link column of a different table. + /// + //@{ + + static const size_t max_table_name_length = 63; + + bool has_table(StringData name) const noexcept; + size_t find_table(StringData name) const noexcept; + StringData get_table_name(size_t table_ndx) const; + + TableRef get_table(size_t index); + ConstTableRef get_table(size_t index) const; + + TableRef get_table(StringData name); + ConstTableRef get_table(StringData name) const; + + TableRef add_table(StringData name, bool require_unique_name = true); + TableRef insert_table(size_t index, StringData name, bool require_unique_name = true); + TableRef get_or_add_table(StringData name, bool* was_added = nullptr); + TableRef get_or_insert_table(size_t index, StringData name, bool* was_added = nullptr); + + void remove_table(size_t index); + void remove_table(StringData name); + + void rename_table(size_t index, StringData new_name, bool require_unique_name = true); + void rename_table(StringData name, StringData new_name, bool require_unique_name = true); + + //@} + + // Serialization + + /// Write this database to the specified output stream. + /// + /// \param out The destination output stream to write to. + /// + /// \param pad If true, the file is padded to ensure the footer is aligned + /// to the end of a page + void write(std::ostream& out, bool pad = false) const; + + /// Write this database to a new file. It is an error to specify a + /// file that already exists. This is to protect against + /// overwriting a database file that is currently open, which + /// would cause undefined behaviour. + /// + /// \param file A filesystem path. + /// + /// \param encryption_key 32-byte key used to encrypt the database file, + /// or nullptr to disable encryption. + /// + /// \param version If different from 0, the new file will be a full fledged + /// realm file with free list and history info. The version of the commit + /// will be set to the value given here. + /// + /// \throw util::File::AccessError If the file could not be + /// opened. If the reason corresponds to one of the exception + /// types that are derived from util::File::AccessError, the + /// derived exception type is thrown. In particular, + /// util::File::Exists will be thrown if the file exists already. + void write(const std::string& file, const char* encryption_key = nullptr, uint64_t version = 0, + bool write_history = true) const; + + /// Write this database to a memory buffer. + /// + /// Ownership of the returned buffer is transferred to the + /// caller. The memory will have been allocated using + /// std::malloc(). + BinaryData write_to_mem() const; + + /// Commit changes to the attached file. This requires that the + /// attached file is opened in read/write mode. + /// + /// Calling this function on an unattached group, a free-standing + /// group, a group whose attached file is opened in read-only + /// mode, a group that is attached to a memory buffer, or a group + /// that is managed by a shared group, is an error and will result + /// in undefined behavior. + /// + /// Table accesors will remain valid across the commit. Note that + /// this is not the case when working with proper transactions. + void commit(); + + //@{ + /// Some operations on Tables in a Group can cause indirect changes to other + /// fields, including in other Tables in the same Group. Specifically, + /// removing a row will set any links to that row to null, and if it had the + /// last strong links to other rows, will remove those rows. When this + /// happens, The cascade notification handler will be called with a + /// CascadeNotification containing information about what indirect changes + /// will occur, before any changes are made. + /// + /// has_cascade_notification_handler() returns true if and only if there is + /// currently a non-null notification handler registered. + /// + /// set_cascade_notification_handler() replaces the current handler (if any) + /// with the passed in handler. Pass in nullptr to remove the current handler + /// without registering a new one. + /// + /// CascadeNotification contains a vector of rows which will be removed and + /// a vector of links which will be set to null (or removed, for entries in + /// LinkLists). + struct CascadeNotification { + struct row { + /// Non-zero iff the removal of this row is ordered + /// (Table::remove()), as opposed to ordered + /// (Table::move_last_over()). Implicit removals are always + /// unordered. + /// + /// This flag does not take part in comparisons (operator==() and + /// operator<()). + size_t is_ordered_removal : 1; + + /// Index within group of a group-level table. + size_t table_ndx : std::numeric_limits::digits - 1; + + /// Row index which will be removed. + size_t row_ndx; + + row() + : is_ordered_removal(0) + { + } + + bool operator==(const row&) const noexcept; + bool operator!=(const row&) const noexcept; + + /// Trivial lexicographic order + bool operator<(const row&) const noexcept; + }; + + struct link { + const Table* origin_table; ///< A group-level table. + size_t origin_col_ndx; ///< Link column being nullified. + size_t origin_row_ndx; ///< Row in column being nullified. + /// The target row index which is being removed. Mostly relevant for + /// LinkList (to know which entries are being removed), but also + /// valid for Link. + size_t old_target_row_ndx; + }; + + /// A sorted list of rows which will be removed by the current operation. + std::vector rows; + + /// An unordered list of links which will be nullified by the current + /// operation. + std::vector links; + }; + + bool has_cascade_notification_handler() const noexcept; + void set_cascade_notification_handler(std::function new_handler) noexcept; + + //@} + + //@{ + /// During sync operation, schema changes may happen at runtime as connected + /// clients update their schema as part of an app update. Since this is a + /// relatively rare event, no attempt is made at limiting the amount of work + /// the handler is required to do to update its information about table and + /// column indices (i.e., all table and column indices must be recalculated). + /// + /// At the time of writing, only additive schema changes may occur in that + /// scenario. + /// + /// has_schema_change_notification_handler() returns true iff there is currently + /// a non-null notification handler registered. + /// + /// set_schema_change_notification_handler() replaces the current handler (if any) + /// with the passed in handler. Pass in nullptr to remove the current handler + /// without registering a new one. + + bool has_schema_change_notification_handler() const noexcept; + void set_schema_change_notification_handler(std::function new_handler) noexcept; + + //@} + + // Conversion + template + void to_json(S& out, size_t link_depth = 0, std::map* renames = nullptr) const; + void to_string(std::ostream& out) const; + + /// Compare two groups for equality. Two groups are equal if, and + /// only if, they contain the same tables in the same order, that + /// is, for each table T at index I in one of the groups, there is + /// a table at index I in the other group that is equal to T. + /// Tables are equal if they have the same content and the same table name. + bool operator==(const Group&) const; + + /// Compare two groups for inequality. See operator==(). + bool operator!=(const Group& g) const + { + return !(*this == g); + } + + /// Control of what to include when computing memory usage + enum SizeAggregateControl { + size_of_state = 1, ///< size of tables, indexes, toplevel array + size_of_history = 2, ///< size of the in-file history compartment + size_of_freelists = 4, ///< size of the freelists + size_of_all = 7 + }; + /// Compute the sum of the sizes in number of bytes of all the array nodes + /// that currently make up this group. When this group represents a snapshot + /// in a Realm file (such as during a read transaction via a SharedGroup + /// instance), this function computes the footprint of that snapshot within + /// the Realm file. + /// + /// If this group accessor is the detached state, this function returns + /// zero. + size_t compute_aggregated_byte_size(SizeAggregateControl ctrl = SizeAggregateControl::size_of_all) const noexcept; + /// Return the size taken up by the current snapshot. This is in contrast to + /// the number returned by SharedGroup::get_stats() which will return the + /// size of the last snapshot done in that SharedGroup. If the snapshots are + /// identical, the numbers will of course be equal. + size_t get_used_space() const noexcept; + + void verify() const; +#ifdef REALM_DEBUG + void print() const; + void print_free() const; + MemStats get_stats(); + void enable_mem_diagnostics(bool enable = true) + { + m_alloc.enable_debug(enable); + } + void to_dot(std::ostream&) const; + void to_dot() const; // To std::cerr (for GDB) + void to_dot(const char* file_path) const; +#endif + +private: + SlabAlloc m_alloc; + + int m_file_format_version; + /// `m_top` is the root node (or top array) of the Realm, and has the + /// following layout: + /// + ///
+    ///
+    ///                                                     Introduced in file
+    ///   Slot  Value                                       format version
+    ///   ---------------------------------------------------------------------
+    ///    1st   m_table_names
+    ///    2nd   m_tables
+    ///    3rd   Logical file size
+    ///    4th   GroupWriter::m_free_positions (optional)
+    ///    5th   GroupWriter::m_free_lengths   (optional)
+    ///    6th   GroupWriter::m_free_versions  (optional)
+    ///    7th   Transaction number / version  (optional)
+    ///    8th   History type         (optional)             4
+    ///    9th   History ref          (optional)             4
+    ///   10th   History version      (optional)             7
+    ///
+    /// 
+ /// + /// The 'History type' slot stores a value of type + /// Replication::HistoryType. The 'History version' slot stores a history + /// schema version as returned by Replication::get_history_schema_version(). + /// + /// The first three entries are mandatory. In files created by + /// Group::write(), none of the optional entries are present and the size of + /// `m_top` is 3. In files updated by Group::commit(), the 4th and 5th entry + /// are present, and the size of `m_top` is 5. In files updated by way of a + /// transaction (SharedGroup::commit()), the 4th, 5th, 6th, and 7th entry + /// are present, and the size of `m_top` is 7. In files that contain a + /// changeset history, the 8th, 9th, and 10th entry are present, except that + /// if the file was opened in nonshared mode (via Group::open()), and the + /// file format remains at 6 (not previously upgraded to 7 or later), then + /// the 10th entry will be absent. + /// + /// When a group accessor is attached to a newly created file or an empty + /// memory buffer where there is no top array yet, `m_top`, `m_tables`, and + /// `m_table_names` will be left in the detached state until the initiation + /// of the first write transaction. In particular, they will remain in the + /// detached state during read transactions that precede the first write + /// transaction. + Array m_top; + ArrayInteger m_tables; + ArrayString m_table_names; + + typedef std::vector table_accessors; + mutable table_accessors m_table_accessors; + + bool m_attached = false; + const bool m_is_shared; + + std::function m_notify_handler; + std::function m_schema_change_handler; + std::shared_ptr m_metrics; + size_t m_total_rows; + + struct shared_tag { + }; + Group(shared_tag) noexcept; + + void init_array_parents() noexcept; + + void open(ref_type top_ref, const std::string& file_path); + + /// If `top_ref` is not zero, attach this group accessor to the specified + /// underlying node structure. If `top_ref` is zero and \a + /// create_group_when_missing is true, create a new node structure that + /// represents an empty group, and attach this group accessor to it. It is + /// an error to call this function on an already attached group accessor. + void attach(ref_type top_ref, bool create_group_when_missing); + + /// Detach this group accessor from the underlying node structure. If this + /// group accessors is already in the detached state, this function does + /// nothing (idempotency). + void detach() noexcept; + + /// \param writable Must be set to true when, and only when attaching for a + /// write transaction. + void attach_shared(ref_type new_top_ref, size_t new_file_size, bool writable); + + void create_empty_group(); + + void reset_free_space_tracking(); + + void remap(size_t new_file_size); + void remap_and_update_refs(ref_type new_top_ref, size_t new_file_size); + + /// Recursively update refs stored in all cached array + /// accessors. This includes cached array accessors in any + /// currently attached table accessors. This ensures that the + /// group instance itself, as well as any attached table accessor + /// that exists across Group::commit() will remain valid. This + /// function is not appropriate for use in conjunction with + /// commits via shared group. + void update_refs(ref_type top_ref, size_t old_baseline) noexcept; + + // Overriding method in ArrayParent + void update_child_ref(size_t, ref_type) override; + + // Overriding method in ArrayParent + ref_type get_child_ref(size_t) const noexcept override; + + // Overriding method in Table::Parent + StringData get_child_name(size_t) const noexcept override; + + // Overriding method in Table::Parent + void child_accessor_destroyed(Table*) noexcept override; + + // Overriding method in Table::Parent + std::recursive_mutex* get_accessor_management_lock() noexcept override + { return nullptr; } // we don't need locking for group! + + // Overriding method in Table::Parent + Group* get_parent_group() noexcept override; + + class TableWriter; + class DefaultTableWriter; + + static void write(std::ostream&, int file_format_version, TableWriter&, bool no_top_array, + bool pad_for_encryption, uint_fast64_t version_number); + + typedef void (*DescSetter)(Table&); + typedef bool (*DescMatcher)(const Spec&); + + Table* do_get_table(size_t table_ndx, DescMatcher desc_matcher); + const Table* do_get_table(size_t table_ndx, DescMatcher desc_matcher) const; + Table* do_get_table(StringData name, DescMatcher desc_matcher); + const Table* do_get_table(StringData name, DescMatcher desc_matcher) const; + Table* do_insert_table(size_t, StringData name, DescSetter desc_setter, bool require_unique_name); + Table* do_insert_table(size_t, StringData name, DescSetter desc_setter); + Table* do_get_or_add_table(StringData name, DescMatcher desc_matcher, DescSetter setter, bool* was_added); + Table* do_get_or_insert_table(size_t, StringData name, DescMatcher desc_matcher, DescSetter desc_setter, + bool* was_added); + + void create_and_insert_table(size_t new_table_ndx, StringData name); + Table* create_table_accessor(size_t table_ndx); + + void detach_table_accessors() noexcept; // Idempotent + + void mark_all_table_accessors() noexcept; + + void write(util::File& file, const char* encryption_key, uint_fast64_t version_number, bool write_history) const; + void write(std::ostream&, bool pad, uint_fast64_t version_numer, bool write_history) const; + + Replication* get_replication() const noexcept; + void set_replication(Replication*) noexcept; + std::shared_ptr get_metrics() const noexcept; + void set_metrics(std::shared_ptr other) noexcept; + void update_num_objects(); + class TransactAdvancer; + void advance_transact(ref_type new_top_ref, size_t new_file_size, _impl::NoCopyInputStream&); + void refresh_dirty_accessors(); + template + void update_table_indices(F&& map_function); + + /// \brief The version of the format of the node structure (in file or in + /// memory) in use by Realm objects associated with this group. + /// + /// Every group contains a file format version field, which is returned + /// by this function. The file format version field is set to the file format + /// version specified by the attached file (or attached memory buffer) at the + /// time of attachment and the value is used to determine if a file format + /// upgrade is required. + /// + /// A value of zero means that the file format is not yet decided. This is + /// only possible for empty Realms where top-ref is zero. (When group is created + /// with the unattached_tag). The version number will then be determined in the + /// subsequent call to Group::open. + /// + /// In shared mode (when a Realm file is opened via a SharedGroup instance) + /// it can happen that the file format is upgraded asyncronously (via + /// another SharedGroup instance), and in that case the file format version + /// field can get out of date, but only for a short while. It is always + /// guaranteed to be, and remain up to date after the opening process completes + /// (when SharedGroup::do_open() returns). + /// + /// An empty Realm file (one whose top-ref is zero) may specify a file + /// format version of zero to indicate that the format is not yet + /// decided. In that case the file format version must be changed to a proper + /// before the opening process completes (Group::open() or SharedGroup::open()). + /// + /// File format versions: + /// + /// 1 Initial file format version + /// + /// 2 Various changes. + /// + /// 3 Supporting null on string columns broke the file format in following + /// way: Index appends an 'X' character to all strings except the null + /// string, to be able to distinguish between null and empty + /// string. Bumped to 3 because of null support of String columns and + /// because of new format of index. + /// + /// 4 Introduction of optional in-Realm history of changes (additional + /// entries in Group::m_top). Since this change is not forward + /// compatible, the file format version had to be bumped. This change is + /// implemented in a way that achieves backwards compatibility with + /// version 3 (and in turn with version 2). + /// + /// 5 Introduced the new Timestamp column type that replaces DateTime. + /// When opening an older database file, all DateTime columns will be + /// automatically upgraded Timestamp columns. + /// + /// 6 Introduced a new structure for the StringIndex. Moved the commit + /// logs into the Realm file. Changes to the transaction log format + /// including reshuffling instructions. This is the format used in + /// milestone 2.0.0. + /// + /// 7 Introduced "history schema version" as 10th entry in top array. + /// + /// 8 Subtables can now have search index. + /// + /// 9 Replication instruction values shuffled, instr_MoveRow added. + /// + /// IMPORTANT: When introducing a new file format version, be sure to review + /// the file validity checks in Group::open() and SharedGroup::do_open, the file + /// format selection logic in + /// Group::get_target_file_format_version_for_session(), and the file format + /// upgrade logic in Group::upgrade_file_format(). + + int get_file_format_version() const noexcept; + void set_file_format_version(int) noexcept; + int get_committed_file_format_version() const noexcept; + + /// The specified history type must be a value of Replication::HistoryType. + static int get_target_file_format_version_for_session(int current_file_format_version, int history_type) noexcept; + + /// Must be called from within a write transaction + void upgrade_file_format(int target_file_format_version); + + std::pair get_to_dot_parent(size_t ndx_in_parent) const override; + + void send_cascade_notification(const CascadeNotification& notification) const; + void send_schema_change_notification() const; + + static void get_version_and_history_info(const Array& top, _impl::History::version_type& version, + int& history_type, int& history_schema_version) noexcept; + static ref_type get_history_ref(const Array& top) noexcept; + static int get_history_schema_version(const Array& top) noexcept; + void set_history_schema_version(int version); + void set_history_parent(Array& history_root) noexcept; + void prepare_history_parent(Array& history_root, int history_type, int history_schema_version); + static void validate_top_array(const Array& arr, const SlabAlloc& alloc); + + friend class Table; + friend class GroupWriter; + friend class SharedGroup; + friend class _impl::GroupFriend; + friend class _impl::TransactLogConvenientEncoder; + friend class _impl::TransactLogParser; + friend class Replication; + friend class TrivialReplication; + friend class metrics::QueryInfo; + friend class metrics::Metrics; +}; + + +// Implementation + +inline Group::Group(const std::string& file, const char* key, OpenMode mode) + : m_alloc() // Throws + , m_top(m_alloc) + , m_tables(m_alloc) + , m_table_names(m_alloc) + , m_is_shared(false) + , m_total_rows(0) +{ + init_array_parents(); + + open(file, key, mode); // Throws +} + +inline Group::Group(BinaryData buffer, bool take_ownership) + : m_alloc() // Throws + , m_top(m_alloc) + , m_tables(m_alloc) + , m_table_names(m_alloc) + , m_is_shared(false) + , m_total_rows(0) +{ + init_array_parents(); + open(buffer, take_ownership); // Throws +} + +inline Group::Group(unattached_tag) noexcept + : m_alloc() + , // Throws + m_top(m_alloc) + , m_tables(m_alloc) + , m_table_names(m_alloc) + , m_is_shared(false) + , m_total_rows(0) +{ + init_array_parents(); +} + +inline Group* Group::get_parent_group() noexcept +{ + return this; +} + +inline Group::Group(shared_tag) noexcept + : m_alloc() + , // Throws + m_top(m_alloc) + , m_tables(m_alloc) + , m_table_names(m_alloc) + , m_is_shared(true) + , m_total_rows(0) +{ + init_array_parents(); +} + +inline bool Group::is_attached() const noexcept +{ + return m_attached; +} + +inline bool Group::is_empty() const noexcept +{ + if (!is_attached()) + return false; + if (m_table_names.is_attached()) + return m_table_names.is_empty(); + return true; +} + +inline size_t Group::size() const noexcept +{ + if (!is_attached()) + return 0; + if (m_table_names.is_attached()) + return m_table_names.size(); + return 0; +} + +inline StringData Group::get_table_name(size_t table_ndx) const +{ + if (table_ndx >= size()) + throw LogicError(LogicError::table_index_out_of_range); + return m_table_names.get(table_ndx); +} + +inline bool Group::has_table(StringData name) const noexcept +{ + size_t ndx = find_table(name); + return ndx != not_found; +} + +inline size_t Group::find_table(StringData name) const noexcept +{ + if (!is_attached()) + return 0; + if (m_table_names.is_attached()) + return m_table_names.find_first(name); + return not_found; +} + +inline TableRef Group::get_table(size_t table_ndx) +{ + if (!is_attached()) + throw LogicError(LogicError::detached_accessor); + DescMatcher desc_matcher = nullptr; // Do not check descriptor + Table* table = do_get_table(table_ndx, desc_matcher); // Throws + return TableRef(table); +} + +inline ConstTableRef Group::get_table(size_t table_ndx) const +{ + if (!is_attached()) + throw LogicError(LogicError::detached_accessor); + DescMatcher desc_matcher = nullptr; // Do not check descriptor + const Table* table = do_get_table(table_ndx, desc_matcher); // Throws + return ConstTableRef(table); +} + +inline TableRef Group::get_table(StringData name) +{ + if (!is_attached()) + throw LogicError(LogicError::detached_accessor); + DescMatcher desc_matcher = nullptr; // Do not check descriptor + Table* table = do_get_table(name, desc_matcher); // Throws + return TableRef(table); +} + +inline ConstTableRef Group::get_table(StringData name) const +{ + if (!is_attached()) + throw LogicError(LogicError::detached_accessor); + DescMatcher desc_matcher = nullptr; // Do not check descriptor + const Table* table = do_get_table(name, desc_matcher); // Throws + return ConstTableRef(table); +} + +inline TableRef Group::insert_table(size_t table_ndx, StringData name, bool require_unique_name) +{ + if (!is_attached()) + throw LogicError(LogicError::detached_accessor); + DescSetter desc_setter = nullptr; // Do not add any columns + Table* table = do_insert_table(table_ndx, name, desc_setter, require_unique_name); // Throws + return TableRef(table); +} + +inline TableRef Group::add_table(StringData name, bool require_unique_name) +{ + return insert_table(size(), name, require_unique_name); +} + +inline TableRef Group::get_or_insert_table(size_t table_ndx, StringData name, bool* was_added) +{ + if (!is_attached()) + throw LogicError(LogicError::detached_accessor); + DescMatcher desc_matcher = nullptr; // Do not check descriptor + DescSetter desc_setter = nullptr; // Do not add any columns + Table* table = do_get_or_insert_table(table_ndx, name, desc_matcher, desc_setter, was_added); // Throws + return TableRef(table); +} + +inline TableRef Group::get_or_add_table(StringData name, bool* was_added) +{ + if (!is_attached()) + throw LogicError(LogicError::detached_accessor); + DescMatcher desc_matcher = nullptr; // Do not check descriptor + DescSetter desc_setter = nullptr; // Do not add any columns + Table* table = do_get_or_add_table(name, desc_matcher, desc_setter, was_added); // Throws + return TableRef(table); +} + +template +void Group::to_json(S& out, size_t link_depth, std::map* renames) const +{ + if (!is_attached()) + throw LogicError(LogicError::detached_accessor); + + std::map renames2; + renames = renames ? renames : &renames2; + + out << "{" << std::endl; + + for (size_t i = 0; i < m_tables.size(); ++i) { + StringData name = m_table_names.get(i); + std::map& m = *renames; + if (m[name] != "") + name = m[name]; + + ConstTableRef table = get_table(i); + + if (i) + out << ","; + out << "\"" << name << "\""; + out << ":"; + table->to_json(out, link_depth, renames); + out << std::endl; + } + + out << "}" << std::endl; +} + +inline void Group::init_array_parents() noexcept +{ + m_table_names.set_parent(&m_top, 0); + m_tables.set_parent(&m_top, 1); +} + +inline void Group::update_child_ref(size_t child_ndx, ref_type new_ref) +{ + m_tables.set(child_ndx, new_ref); +} + +inline ref_type Group::get_child_ref(size_t child_ndx) const noexcept +{ + return m_tables.get_as_ref(child_ndx); +} + +inline StringData Group::get_child_name(size_t child_ndx) const noexcept +{ + return m_table_names.get(child_ndx); +} + +inline void Group::child_accessor_destroyed(Table*) noexcept +{ + // Ignore +} + +inline bool Group::has_cascade_notification_handler() const noexcept +{ + return !!m_notify_handler; +} + +inline void +Group::set_cascade_notification_handler(std::function new_handler) noexcept +{ + m_notify_handler = std::move(new_handler); +} + +inline void Group::send_cascade_notification(const CascadeNotification& notification) const +{ + if (m_notify_handler) + m_notify_handler(notification); +} + +inline bool Group::has_schema_change_notification_handler() const noexcept +{ + return !!m_schema_change_handler; +} + +inline void Group::set_schema_change_notification_handler(std::function new_handler) noexcept +{ + m_schema_change_handler = std::move(new_handler); +} + +inline void Group::send_schema_change_notification() const +{ + if (m_schema_change_handler) + m_schema_change_handler(); +} + +inline void Group::get_version_and_history_info(const Array& top, _impl::History::version_type& version, + int& history_type, int& history_schema_version) noexcept +{ + using version_type = _impl::History::version_type; + version_type version_2 = 0; + int history_type_2 = 0; + int history_schema_version_2 = 0; + if (top.is_attached()) { + if (top.size() >= 6) { + REALM_ASSERT(top.size() >= 7); + version_2 = version_type(top.get_as_ref_or_tagged(6).get_as_int()); + } + if (top.size() >= 8) { + REALM_ASSERT(top.size() >= 9); + history_type_2 = int(top.get_as_ref_or_tagged(7).get_as_int()); + } + if (top.size() >= 10) { + history_schema_version_2 = int(top.get_as_ref_or_tagged(9).get_as_int()); + } + } + // Version 0 is not a legal initial version, so it has to be set to 1 + // instead. + if (version_2 == 0) + version_2 = 1; + version = version_2; + history_type = history_type_2; + history_schema_version = history_schema_version_2; +} + +inline ref_type Group::get_history_ref(const Array& top) noexcept +{ + bool has_history = (top.is_attached() && top.size() >= 8); + if (has_history) { + // This function is only used is shared mode (from SharedGroup) + REALM_ASSERT(top.size() >= 10); + return top.get_as_ref(8); + } + return 0; +} + +inline int Group::get_history_schema_version(const Array& top) noexcept +{ + bool has_history = (top.is_attached() && top.size() >= 8); + if (has_history) { + // This function is only used is shared mode (from SharedGroup) + REALM_ASSERT(top.size() >= 10); + return int(top.get_as_ref_or_tagged(9).get_as_int()); + } + return 0; +} + +inline void Group::set_history_schema_version(int version) +{ + // This function is only used is shared mode (from SharedGroup) + REALM_ASSERT(m_top.size() >= 10); + m_top.set(9, RefOrTagged::make_tagged(unsigned(version))); // Throws +} + +inline void Group::set_history_parent(Array& history_root) noexcept +{ + history_root.set_parent(&m_top, 8); +} + +class Group::TableWriter { +public: + struct HistoryInfo { + ref_type ref = 0; + int type = 0; + int version = 0; + }; + + virtual ref_type write_names(_impl::OutputStream&) = 0; + virtual ref_type write_tables(_impl::OutputStream&) = 0; + virtual HistoryInfo write_history(_impl::OutputStream&) = 0; + virtual ~TableWriter() noexcept + { + } +}; + +inline const Table* Group::do_get_table(size_t table_ndx, DescMatcher desc_matcher) const +{ + return const_cast(this)->do_get_table(table_ndx, desc_matcher); // Throws +} + +inline const Table* Group::do_get_table(StringData name, DescMatcher desc_matcher) const +{ + return const_cast(this)->do_get_table(name, desc_matcher); // Throws +} + +inline void Group::reset_free_space_tracking() +{ + m_alloc.reset_free_space_tracking(); // Throws +} + +inline Replication* Group::get_replication() const noexcept +{ + return m_alloc.get_replication(); +} + +inline void Group::set_replication(Replication* repl) noexcept +{ + m_alloc.set_replication(repl); +} + +inline std::shared_ptr Group::get_metrics() const noexcept +{ + return m_metrics; +} + +inline void Group::set_metrics(std::shared_ptr shared) noexcept +{ + m_metrics = shared; +} + +// The purpose of this class is to give internal access to some, but +// not all of the non-public parts of the Group class. +class _impl::GroupFriend { +public: + static Allocator& get_alloc(Group& group) noexcept + { + return group.m_alloc; + } + + static const Allocator& get_alloc(const Group& group) noexcept + { + return group.m_alloc; + } + + static ref_type get_top_ref(const Group& group) noexcept + { + return group.m_top.get_ref(); + } + + static Table& get_table(Group& group, size_t ndx_in_group) + { + Group::DescMatcher desc_matcher = 0; // Do not check descriptor + Table* table = group.do_get_table(ndx_in_group, desc_matcher); // Throws + return *table; + } + + static const Table& get_table(const Group& group, size_t ndx_in_group) + { + Group::DescMatcher desc_matcher = 0; // Do not check descriptor + const Table* table = group.do_get_table(ndx_in_group, desc_matcher); // Throws + return *table; + } + + static Table* get_table(Group& group, StringData name) + { + Group::DescMatcher desc_matcher = 0; // Do not check descriptor + Table* table = group.do_get_table(name, desc_matcher); // Throws + return table; + } + + static const Table* get_table(const Group& group, StringData name) + { + Group::DescMatcher desc_matcher = 0; // Do not check descriptor + const Table* table = group.do_get_table(name, desc_matcher); // Throws + return table; + } + + static Table& insert_table(Group& group, size_t table_ndx, StringData name, bool require_unique_name) + { + Group::DescSetter desc_setter = nullptr; // Do not add any columns + return *group.do_insert_table(table_ndx, name, desc_setter, require_unique_name); + } + + static Table& add_table(Group& group, StringData name, bool require_unique_name) + { + return insert_table(group, group.size(), name, require_unique_name); + } + + static Table& get_or_insert_table(Group& group, size_t table_ndx, StringData name, bool* was_inserted) + { + Group::DescMatcher desc_matcher = nullptr; // Do not check descriptor + Group::DescSetter desc_setter = nullptr; // Do not add any columns + return *group.do_get_or_insert_table(table_ndx, name, desc_matcher, desc_setter, was_inserted); + } + + static Table& get_or_add_table(Group& group, StringData name, bool* was_inserted) + { + Group::DescMatcher desc_matcher = nullptr; // Do not check descriptor + Group::DescSetter desc_setter = nullptr; // Do not add any columns + return *group.do_get_or_add_table(name, desc_matcher, desc_setter, was_inserted); + } + + static void send_cascade_notification(const Group& group, const Group::CascadeNotification& notification) + { + group.send_cascade_notification(notification); + } + + static Replication* get_replication(const Group& group) noexcept + { + return group.get_replication(); + } + + static void set_replication(Group& group, Replication* repl) noexcept + { + group.set_replication(repl); + } + + static void detach(Group& group) noexcept + { + group.detach(); + } + + static void attach_shared(Group& group, ref_type new_top_ref, size_t new_file_size, bool writable) + { + group.attach_shared(new_top_ref, new_file_size, writable); // Throws + } + + static void reset_free_space_tracking(Group& group) + { + group.reset_free_space_tracking(); // Throws + } + + static void remap(Group& group, size_t new_file_size) + { + group.remap(new_file_size); // Throws + } + + static void remap_and_update_refs(Group& group, ref_type new_top_ref, size_t new_file_size) + { + group.remap_and_update_refs(new_top_ref, new_file_size); // Throws + } + + static void advance_transact(Group& group, ref_type new_top_ref, size_t new_file_size, + _impl::NoCopyInputStream& in) + { + group.advance_transact(new_top_ref, new_file_size, in); // Throws + } + + static void create_empty_group_when_missing(Group& group) + { + if (!group.m_top.is_attached()) + group.create_empty_group(); // Throws + } + + static void get_version_and_history_info(const Allocator& alloc, ref_type top_ref, + _impl::History::version_type& version, + int& history_type, + int& history_schema_version) noexcept + { + Array top{const_cast(alloc)}; + if (top_ref != 0) + top.init_from_ref(top_ref); + Group::get_version_and_history_info(top, version, history_type, history_schema_version); + } + + static ref_type get_history_ref(const Group& group) noexcept + { + return Group::get_history_ref(group.m_top); + } + + static ref_type get_history_ref(Allocator& alloc, ref_type top_ref) noexcept + { + Array top(alloc); + if (top_ref != 0) + top.init_from_ref(top_ref); + return Group::get_history_ref(top); + } + + static int get_history_schema_version(const Group& group) noexcept + { + return Group::get_history_schema_version(group.m_top); + } + + static int get_history_schema_version(Allocator& alloc, ref_type top_ref) noexcept + { + Array top{alloc}; + if (top_ref != 0) + top.init_from_ref(top_ref); + return Group::get_history_schema_version(top); + } + + static void set_history_schema_version(Group& group, int version) + { + group.set_history_schema_version(version); // Throws + } + + static void set_history_parent(Group& group, Array& history_root) noexcept + { + group.set_history_parent(history_root); + } + + static void prepare_history_parent(Group& group, Array& history_root, int history_type, + int history_schema_version) + { + group.prepare_history_parent(history_root, history_type, history_schema_version); // Throws + } + + static int get_file_format_version(const Group& group) noexcept + { + return group.get_file_format_version(); + } + + static void set_file_format_version(Group& group, int file_format_version) noexcept + { + group.set_file_format_version(file_format_version); + } + + static int get_committed_file_format_version(const Group& group) noexcept + { + return group.get_committed_file_format_version(); + } + + static int get_target_file_format_version_for_session(int current_file_format_version, int history_type) noexcept + { + return Group::get_target_file_format_version_for_session(current_file_format_version, history_type); + } + + static void upgrade_file_format(Group& group, int target_file_format_version) + { + group.upgrade_file_format(target_file_format_version); // Throws + } +}; + + +struct CascadeState : Group::CascadeNotification { + /// If non-null, then no recursion will be performed for rows of that + /// table. The effect is then exactly as if all the rows of that table were + /// added to \a state.rows initially, and then removed again after the + /// explicit invocations of Table::cascade_break_backlinks_to() (one for + /// each initiating row). This is used by Table::clear() to avoid + /// reentrance. + /// + /// Must never be set concurrently with stop_on_link_list_column. + Table* stop_on_table = nullptr; + + /// If non-null, then Table::cascade_break_backlinks_to() will skip the + /// removal of reciprocal backlinks for the link list at + /// stop_on_link_list_row_ndx in this column, and no recursion will happen + /// on its behalf. This is used by LinkView::clear() to avoid reentrance. + /// + /// Must never be set concurrently with stop_on_table. + LinkListColumn* stop_on_link_list_column = nullptr; + + /// Is ignored if stop_on_link_list_column is null. + size_t stop_on_link_list_row_ndx = 0; + + /// If false, the links field is not needed, so any work done just for that + /// can be skipped. + bool track_link_nullifications = false; + + /// If false, weak links are followed too + bool only_strong_links = true; +}; + +inline bool Group::CascadeNotification::row::operator==(const row& r) const noexcept +{ + return table_ndx == r.table_ndx && row_ndx == r.row_ndx; +} + +inline bool Group::CascadeNotification::row::operator!=(const row& r) const noexcept +{ + return !(*this == r); +} + +inline bool Group::CascadeNotification::row::operator<(const row& r) const noexcept +{ + return table_ndx < r.table_ndx || (table_ndx == r.table_ndx && row_ndx < r.row_ndx); +} + +} // namespace realm + +#endif // REALM_GROUP_HPP diff --git a/!main project/Pods/Realm/include/core/realm/group_shared.hpp b/!main project/Pods/Realm/include/core/realm/group_shared.hpp new file mode 100644 index 0000000..e607409 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/group_shared.hpp @@ -0,0 +1,1233 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_GROUP_SHARED_HPP +#define REALM_GROUP_SHARED_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace realm { + +namespace _impl { +class SharedGroupFriend; +class WriteLogCollector; +} + +/// Thrown by SharedGroup::open() if the lock file is already open in another +/// process which can't share mutexes with this process +struct IncompatibleLockFile : std::runtime_error { + IncompatibleLockFile(const std::string& msg) + : std::runtime_error("Incompatible lock file. " + msg) + { + } +}; + +/// Thrown by SharedGroup::open() if the type of history +/// (Replication::HistoryType) in the opened Realm file is incompatible with the +/// mode in which the Realm file is opened. For example, if there is a mismatch +/// between the history type in the file, and the history type associated with +/// the replication plugin passed to SharedGroup::open(). +/// +/// This exception will also be thrown if the history schema version is lower +/// than required, and no migration is possible +/// (Replication::is_upgradable_history_schema()). +struct IncompatibleHistories : util::File::AccessError { + IncompatibleHistories(const std::string& msg, const std::string& path) + : util::File::AccessError("Incompatible histories. " + msg, path) + { + } +}; + +/// A SharedGroup facilitates transactions. +/// +/// When multiple threads or processes need to access a database +/// concurrently, they must do so using transactions. By design, +/// Realm does not allow for multiple threads (or processes) to +/// share a single instance of SharedGroup. Instead, each concurrently +/// executing thread or process must use a separate instance of +/// SharedGroup. +/// +/// Each instance of SharedGroup manages a single transaction at a +/// time. That transaction can be either a read transaction, or a +/// write transaction. +/// +/// Utility classes ReadTransaction and WriteTransaction are provided +/// to make it safe and easy to work with transactions in a scoped +/// manner (by means of the RAII idiom). However, transactions can +/// also be explicitly started (begin_read(), begin_write()) and +/// stopped (end_read(), commit(), rollback()). +/// +/// If a transaction is active when the SharedGroup is destroyed, that +/// transaction is implicitly terminated, either by a call to +/// end_read() or rollback(). +/// +/// Two processes that want to share a database file must reside on +/// the same host. +/// +/// +/// Desired exception behavior (not yet fully implemented) +/// ------------------------------------------------------ +/// +/// - If any data access API function throws an unexpected exception during a +/// read transaction, the shared group accessor is left in state "error +/// during read". +/// +/// - If any data access API function throws an unexpected exception during a +/// write transaction, the shared group accessor is left in state "error +/// during write". +/// +/// - If SharedGroup::begin_write() or SharedGroup::begin_read() throws an +/// unexpected exception, the shared group accessor is left in state "no +/// transaction in progress". +/// +/// - SharedGroup::end_read() and SharedGroup::rollback() do not throw. +/// +/// - If SharedGroup::commit() throws an unexpected exception, the shared group +/// accessor is left in state "error during write" and the transaction was +/// not committed. +/// +/// - If SharedGroup::advance_read() or SharedGroup::promote_to_write() throws +/// an unexpected exception, the shared group accessor is left in state +/// "error during read". +/// +/// - If SharedGroup::commit_and_continue_as_read() or +/// SharedGroup::rollback_and_continue_as_read() throws an unexpected +/// exception, the shared group accessor is left in state "error during +/// write". +/// +/// It has not yet been decided exactly what an "unexpected exception" is, but +/// `std::bad_alloc` is surely one example. On the other hand, an expected +/// exception is one that is mentioned in the function specific documentation, +/// and is used to abort an operation due to a special, but expected condition. +/// +/// States +/// ------ +/// +/// - A newly created shared group accessor is in state "no transaction in +/// progress". +/// +/// - In state "error during read", almost all Realm API functions are +/// illegal on the connected group of accessors. The only valid operations +/// are destruction of the shared group, and SharedGroup::end_read(). If +/// SharedGroup::end_read() is called, the new state becomes "no transaction +/// in progress". +/// +/// - In state "error during write", almost all Realm API functions are +/// illegal on the connected group of accessors. The only valid operations +/// are destruction of the shared group, and SharedGroup::rollback(). If +/// SharedGroup::end_write() is called, the new state becomes "no transaction +/// in progress" +class SharedGroup { +public: + /// \brief Same as calling the corresponding version of open() on a instance + /// constructed in the unattached state. Exception safety note: if the + /// `upgrade_callback` throws, then the file will be closed properly and the + /// upgrade will be aborted. + explicit SharedGroup(const std::string& file, bool no_create = false, + const SharedGroupOptions options = SharedGroupOptions()); + + /// \brief Same as calling the corresponding version of open() on a instance + /// constructed in the unattached state. Exception safety note: if the + /// `upgrade_callback` throws, then the file will be closed properly and + /// the upgrade will be aborted. + explicit SharedGroup(Replication& repl, const SharedGroupOptions options = SharedGroupOptions()); + + struct unattached_tag { + }; + + /// Create a SharedGroup instance in its unattached state. It may + /// then be attached to a database file later by calling + /// open(). You may test whether this instance is currently in its + /// attached state by calling is_attached(). Calling any other + /// function (except the destructor) while in the unattached state + /// has undefined behavior. + SharedGroup(unattached_tag) noexcept; + + ~SharedGroup() noexcept; + + // Disable copying to prevent accessor errors. If you really want another + // instance, open another SharedGroup object on the same file. + SharedGroup(const SharedGroup&) = delete; + SharedGroup& operator=(const SharedGroup&) = delete; + + /// Attach this SharedGroup instance to the specified database file. + /// + /// While at least one instance of SharedGroup exists for a specific + /// database file, a "lock" file will be present too. The lock file will be + /// placed in the same directory as the database file, and its name will be + /// derived by appending ".lock" to the name of the database file. + /// + /// When multiple SharedGroup instances refer to the same file, they must + /// specify the same durability level, otherwise an exception will be + /// thrown. + /// + /// \param file Filesystem path to a Realm database file. + /// + /// \param no_create If the database file does not already exist, it will be + /// created (unless this is set to true.) When multiple threads are involved, + /// it is safe to let the first thread, that gets to it, create the file. + /// + /// \param options See SharedGroupOptions for details of each option. + /// Sensible defaults are provided if this parameter is left out. + /// + /// Calling open() on a SharedGroup instance that is already in the attached + /// state has undefined behavior. + /// + /// \throw util::File::AccessError If the file could not be opened. If the + /// reason corresponds to one of the exception types that are derived from + /// util::File::AccessError, the derived exception type is thrown. Note that + /// InvalidDatabase is among these derived exception types. + /// + /// \throw FileFormatUpgradeRequired only if \a SharedGroupOptions::allow_upgrade + /// is `false` and an upgrade is required. + void open(const std::string& file, bool no_create = false, + const SharedGroupOptions options = SharedGroupOptions()); + + /// Open this group in replication mode. The specified Replication instance + /// must remain in existence for as long as the SharedGroup. + void open(Replication&, const SharedGroupOptions options = SharedGroupOptions()); + + /// Close any open database, returning to the unattached state. + void close() noexcept; + + /// A SharedGroup may be created in the unattached state, and then + /// later attached to a file with a call to open(). Calling any + /// function other than open(), is_attached(), and ~SharedGroup() + /// on an unattached instance results in undefined behavior. + bool is_attached() const noexcept; + +#ifdef REALM_DEBUG + /// Deprecated method, only called from a unit test + /// + /// Reserve disk space now to avoid allocation errors at a later + /// point in time, and to minimize on-disk fragmentation. In some + /// cases, less fragmentation translates into improved + /// performance. + /// + /// When supported by the system, a call to this function will + /// make the database file at least as big as the specified size, + /// and cause space on the target device to be allocated (note + /// that on many systems on-disk allocation is done lazily by + /// default). If the file is already bigger than the specified + /// size, the size will be unchanged, and on-disk allocation will + /// occur only for the initial section that corresponds to the + /// specified size. + /// + /// It is an error to call this function on an unattached shared + /// group. Doing so will result in undefined behavior. + void reserve(size_t size_in_bytes); +#endif + + /// Querying for changes: + /// + /// NOTE: + /// "changed" means that one or more commits has been made to the database + /// since the SharedGroup (on which wait_for_change() is called) last + /// started, committed, promoted or advanced a transaction. If the + /// SharedGroup has not yet begun a transaction, "changed" is undefined. + /// + /// No distinction is made between changes done by another process + /// and changes done by another thread in the same process as the caller. + /// + /// Has db been changed ? + bool has_changed(); + + /// The calling thread goes to sleep until the database is changed, or + /// until wait_for_change_release() is called. After a call to + /// wait_for_change_release() further calls to wait_for_change() will return + /// immediately. To restore the ability to wait for a change, a call to + /// enable_wait_for_change() is required. Return true if the database has + /// changed, false if it might have. + bool wait_for_change(); + + /// release any thread waiting in wait_for_change() on *this* SharedGroup. + void wait_for_change_release(); + + /// re-enable waiting for change + void enable_wait_for_change(); + // Transactions: + + using version_type = _impl::History::version_type; + using VersionID = realm::VersionID; + + /// Thrown by begin_read() if the specified version does not correspond to a + /// bound (or tethered) snapshot. + struct BadVersion; + + /// \defgroup group_shared_transactions + //@{ + + /// begin_read() initiates a new read transaction. A read transaction is + /// bound to, and provides access to a particular snapshot of the underlying + /// Realm (in general the latest snapshot, but see \a version). It cannot be + /// used to modify the Realm, and in that sense, a read transaction is not a + /// real transaction. + /// + /// begin_write() initiates a new write transaction. A write transaction + /// allows the application to both read and modify the underlying Realm + /// file. At most one write transaction can be in progress at any given time + /// for a particular underlying Realm file. If another write transaction is + /// already in progress, begin_write() will block the caller until the other + /// write transaction terminates. No guarantees are made about the order in + /// which multiple concurrent requests will be served. + /// + /// It is an error to call begin_read() or begin_write() on a SharedGroup + /// object with an active read or write transaction. + /// + /// If begin_read() or begin_write() throws, no transaction is initiated, + /// and the application may try to initiate a new read or write transaction + /// later. + /// + /// end_read() terminates the active read transaction. If no read + /// transaction is active, end_read() does nothing. It is an error to call + /// this function on a SharedGroup object with an active write + /// transaction. end_read() does not throw. + /// + /// commit() commits all changes performed in the context of the active + /// write transaction, and thereby terminates that transaction. This + /// produces a new snapshot in the underlying Realm. commit() returns the + /// version associated with the new snapshot. It is an error to call + /// commit() when there is no active write transaction. If commit() throws, + /// no changes will have been committed, and the transaction will still be + /// active, but in a bad state. In that case, the application must either + /// call rollback() to terminate the bad transaction (in which case a new + /// transaction can be initiated), call close() which also terminates the + /// bad transaction, or destroy the SharedGroup object entirely. When the + /// transaction is in a bad state, the application is not allowed to call + /// any method on the Group accessor or on any of its subordinate accessors + /// (Table, Row, Descriptor). Note that the transaction is also left in a + /// bad state when a modifying operation on any subordinate accessor throws. + /// + /// rollback() terminates the active write transaction and discards any + /// changes performed in the context of it. If no write transaction is + /// active, rollback() does nothing. It is an error to call this function in + /// a SharedGroup object with an active read transaction. rollback() does + /// not throw. + /// + /// the Group accessor and all subordinate accessors (Table, Row, + /// Descriptor) that are obtained in the context of a particular read or + /// write transaction will become detached upon termination of that + /// transaction, which means that they can no longer be used to access the + /// underlying objects. + /// + /// Subordinate accessors that were detached at the end of the previous + /// read or write transaction will not be automatically reattached when a + /// new transaction is initiated. The application must reobtain new + /// accessors during a new transaction to regain access to the underlying + /// objects. + /// + /// \param version If specified, this must be the version associated with a + /// *bound* snapshot. A snapshot is said to be bound (or tethered) if there + /// is at least one active read or write transaction bound to it. A read + /// transaction is bound to the snapshot that it provides access to. A write + /// transaction is bound to the latest snapshot available at the time of + /// initiation of the write transaction. If the specified version is not + /// associated with a bound snapshot, this function throws BadVersion. + /// + /// \throw BadVersion Thrown by begin_read() if the specified version does + /// not correspond to a bound (or tethered) snapshot. + + const Group& begin_read(VersionID version = VersionID()); + void end_read() noexcept; + Group& begin_write(); + // Return true (and take the write lock) if there is no other write + // in progress. In case of contention return false immediately. + // If the write lock is obtained, also provide the Group associated + // with the SharedGroup for further operations. + bool try_begin_write(Group*& group); + version_type commit(); + void rollback() noexcept; + // report statistics of last commit done on THIS shared group. + // The free space reported is what can be expected to be freed + // by compact(). This may not correspond to the space which is free + // at the point where get_stats() is called, since that will include + // memory required to hold older versions of data, which still + // needs to be available. The locked space is the amount of memory + // that is free in current version, but being used in still live versions. + // Notice that we will always have two live versions - the current and the + // previous. + void get_stats(size_t& free_space, size_t& used_space, util::Optional locked_space = util::none) const; + //@} + + enum TransactStage { + transact_Ready, + transact_Reading, + transact_Writing, + }; + + /// Get the current transaction type + TransactStage get_transact_stage() const noexcept; + + /// Get a version id which may be used to request a different SharedGroup + /// to start transaction at a specific version. + VersionID get_version_of_current_transaction(); + + /// Report the number of distinct versions currently stored in the database. + /// Note: the database only cleans up versions as part of commit, so ending + /// a read transaction will not immediately release any versions. + uint_fast64_t get_number_of_versions(); + + /// Get the approximate size of the data that would be written to the file if + /// a commit were done at this point. The reported size will always be bigger + /// than what will eventually be needed as we reserve a bit more memory that + /// will be needed. + size_t get_commit_size() const; + + /// Get the size of the currently allocated slab area + size_t get_allocated_size() const; + + /// Compact the database file. + /// - The method will throw if called inside a transaction. + /// - The method will throw if called in unattached state. + /// - The method will return false if other SharedGroups are accessing the + /// database in which case compaction is not done. This is not + /// necessarily an error. + /// It will return true following successful compaction. + /// While compaction is in progress, attempts by other + /// threads or processes to open the database will wait. + /// Be warned that resource requirements for compaction is proportional to + /// the amount of live data in the database. + /// Compaction works by writing the database contents to a temporary + /// database file and then replacing the database with the temporary one. + /// The name of the temporary file is formed by appending + /// ".tmp_compaction_space" to the name of the database + /// + /// If the output_encryption_key is `none` then the file's existing key will + /// be used (if any). If the output_encryption_key is nullptr, the resulting + /// file will be unencrypted. Any other value will change the encryption of + /// the file to the new 64 byte key. + /// + /// FIXME: This function is not yet implemented in an exception-safe manner, + /// therefore, if it throws, the application should not attempt to + /// continue. If may not even be safe to destroy the SharedGroup object. + /// + /// WARNING / FIXME: compact() should NOT be exposed publicly on Windows + /// because it's not crash safe! It may corrupt your database if something fails + bool compact(bool bump_version_number = false, util::Optional output_encryption_key = util::none); + +#ifdef REALM_DEBUG + void test_ringbuf(); +#endif + + /// To handover a table view, query, linkview or row accessor of type T, you + /// must wrap it into a Handover for the transfer. Wrapping and + /// unwrapping of a handover object is done by the methods + /// 'export_for_handover()' and 'import_from_handover()' declared below. + /// 'export_for_handover()' returns a Handover object, and + /// 'import_for_handover()' consumes that object, producing a new accessor + /// which is ready for use in the context of the importing SharedGroup. + /// + /// The Handover always creates a new accessor object at the importing side. + /// For TableViews, there are 3 forms of handover. + /// + /// - with payload move: the payload is handed over and ends up as a payload + /// held by the accessor at the importing side. The accessor on the + /// exporting side will rerun its query and generate a new payload, if + /// TableView::sync_if_needed() is called. If the original payload was in + /// sync at the exporting side, it will also be in sync at the importing + /// side. This is indicated to handover_export() by the argument + /// MutableSourcePayload::Move + /// + /// - with payload copy: a copy of the payload is handed over, so both the + /// accessors on the exporting side *and* the accessors created at the + /// importing side has their own payload. This is indicated to + /// handover_export() by the argument ConstSourcePayload::Copy + /// + /// - without payload: the payload stays with the accessor on the exporting + /// side. On the importing side, the new accessor is created without + /// payload. A call to TableView::sync_if_needed() will trigger generation + /// of a new payload. This form of handover is indicated to + /// handover_export() by the argument ConstSourcePayload::Stay. + /// + /// For all other (non-TableView) accessors, handover is done with payload + /// copy, since the payload is trivial. + /// + /// Handover *without* payload is useful when you want to ship a tableview + /// with its query for execution in a background thread. Handover with + /// *payload move* is useful when you want to transfer the result back. + /// + /// Handover *without* payload or with payload copy is guaranteed *not* to + /// change the accessors on the exporting side. + /// + /// Handover is *not* thread safe and should be carried out + /// by the thread that "owns" the involved accessors. + /// + /// Handover is transitive: + /// If the object being handed over depends on other views + /// (table- or link- ), those objects will be handed over as well. The mode + /// of handover (payload copy, payload move, without payload) is applied + /// recursively. Note: If you are handing over a tableview dependent upon + /// another tableview and using MutableSourcePayload::Move, + /// you are on thin ice! + /// + /// On the importing side, the top-level accessor being created during + /// import takes ownership of all other accessors (if any) being created as + /// part of the import. + + /// Type used to support handover of accessors between shared groups. + template + struct Handover; + + /// thread-safe/const export (mode is Stay or Copy) + /// during export, the following operations on the shared group is locked: + /// - advance_read(), promote_to_write(), commit_and_continue_as_read(), + /// rollback_and_continue_as_read(), close() + template + std::unique_ptr> export_for_handover(const T& accessor, ConstSourcePayload mode); + + // specialization for handover of Rows + template + std::unique_ptr>> export_for_handover(const BasicRow& accessor); + + // destructive export (mode is Move) + template + std::unique_ptr> export_for_handover(T& accessor, MutableSourcePayload mode); + + /// Import an accessor wrapped in a handover object. The import will fail + /// if the importing SharedGroup is viewing a version of the database that + /// is different from the exporting SharedGroup. The call to + /// import_from_handover is not thread-safe. + template + std::unique_ptr import_from_handover(std::unique_ptr> handover); + + // We need two cases for handling of LinkViews, because they are ref counted. + std::unique_ptr> export_linkview_for_handover(const LinkViewRef& accessor); + LinkViewRef import_linkview_from_handover(std::unique_ptr> handover); + + // likewise for Tables. + std::unique_ptr> export_table_for_handover(const TableRef& accessor); + TableRef import_table_from_handover(std::unique_ptr> handover); + + /// When doing handover to background tasks that may be run later, we + /// may want to momentarily pin the current version until the other thread + /// has retrieved it. + /// + /// Pinning can be done in both read- and write-transactions, but with different + /// semantics. When pinning during a read-transaction, the version pinned is the + /// one accessible during the read-transaction. When pinning during a write-transaction, + /// the version pinned will be the last version that was succesfully committed to the + /// realm file at the point in time, when the write-transaction was started. + /// + /// The release is not thread-safe, so it has to be done on the SharedGroup + /// associated with the thread calling unpin_version(), and the SharedGroup + /// must be attached to the realm file at the point of unpinning. + + // Pin version for handover (not thread safe) + VersionID pin_version(); + + // Release pinned version (not thread safe) + void unpin_version(VersionID version); + + std::shared_ptr get_metrics(); + + // Try to grab a exclusive lock of the given realm path's lock file. If the lock + // can be acquired, the callback will be executed with the lock and then return true. + // Otherwise false will be returned directly. + // The lock taken precludes races with other threads or processes accessing the + // files through a SharedGroup. + // It is safe to delete/replace realm files inside the callback. + // WARNING: It is not safe to delete the lock file in the callback. + using CallbackWithLock = std::function; + static bool call_with_lock(const std::string& realm_path, CallbackWithLock callback); + + // Return a list of files/directories core may use of the given realm file path. + // The first element of the pair in the returned list is the path string, the + // second one is to indicate the path is a directory or not. + // The temporary files are not returned by this function. + // It is safe to delete those returned files/directories in the call_with_lock's callback. + static std::vector> get_core_files(const std::string& realm_path); + +private: + struct SharedInfo; + struct ReadCount; + struct ReadLockInfo { + uint_fast64_t m_version = std::numeric_limits::max(); + uint_fast32_t m_reader_idx = 0; + ref_type m_top_ref = 0; + size_t m_file_size = 0; + }; + class ReadLockUnlockGuard; + + // Member variables + size_t m_free_space = 0; + size_t m_locked_space = 0; + size_t m_used_space = 0; + Group m_group; + ReadLockInfo m_read_lock; + uint_fast32_t m_local_max_entry; + util::File m_file; + util::File::Map m_file_map; // Never remapped + util::File::Map m_reader_map; + bool m_wait_for_change_enabled; + std::string m_lockfile_path; + std::string m_lockfile_prefix; + std::string m_db_path; + std::string m_coordination_dir; + const char* m_key; + TransactStage m_transact_stage; + util::InterprocessMutex m_writemutex; +#ifdef REALM_ASYNC_DAEMON + util::InterprocessMutex m_balancemutex; +#endif + util::InterprocessMutex m_controlmutex; +#ifdef REALM_ASYNC_DAEMON + util::InterprocessCondVar m_room_to_write; + util::InterprocessCondVar m_work_to_do; + util::InterprocessCondVar m_daemon_becomes_ready; +#endif + util::InterprocessCondVar m_new_commit_available; + util::InterprocessCondVar m_pick_next_writer; + std::function m_upgrade_callback; + +#if REALM_METRICS + std::shared_ptr m_metrics; +#endif // REALM_METRICS + + void do_open(const std::string& file, bool no_create, bool is_backend, const SharedGroupOptions options); + + // Ring buffer management + bool ringbuf_is_empty() const noexcept; + size_t ringbuf_size() const noexcept; + size_t ringbuf_capacity() const noexcept; + bool ringbuf_is_first(size_t ndx) const noexcept; + void ringbuf_remove_first() noexcept; + size_t ringbuf_find(uint64_t version) const noexcept; + ReadCount& ringbuf_get(size_t ndx) noexcept; + ReadCount& ringbuf_get_first() noexcept; + ReadCount& ringbuf_get_last() noexcept; + void ringbuf_put(const ReadCount& v); + void ringbuf_expand(); + + /// Grab a read lock on the snapshot associated with the specified + /// version. If `version_id == VersionID()`, a read lock will be grabbed on + /// the latest available snapshot. Fails if the snapshot is no longer + /// available. + /// + /// As a side effect update memory mapping to ensure that the ringbuffer + /// entries referenced in the readlock info is accessible. + /// + /// FIXME: It needs to be made more clear exactly under which conditions + /// this function fails. Also, why is it useful to promise anything about + /// detection of bad versions? Can we really promise enough to make such a + /// promise useful to the caller? + void grab_read_lock(ReadLockInfo&, VersionID); + + // Release a specific read lock. The read lock MUST have been obtained by a + // call to grab_read_lock(). + void release_read_lock(ReadLockInfo&) noexcept; + + void do_begin_read(VersionID, bool writable); + void do_end_read() noexcept; + /// return true if write transaction can commence, false otherwise. + bool do_try_begin_write(); + void do_begin_write(); + version_type do_commit(); + void do_end_write() noexcept; + void set_transact_stage(TransactStage stage) noexcept; + + /// Returns the version of the latest snapshot. + version_type get_version_of_latest_snapshot(); + + /// Returns the version of the snapshot bound in the current read or write + /// transaction. It is an error to call this function when no transaction is + /// in progress. + version_type get_version_of_bound_snapshot() const noexcept; + + // make sure the given index is within the currently mapped area. + // if not, expand the mapped area. Returns true if the area is expanded. + bool grow_reader_mapping(uint_fast32_t index); + + // Must be called only by someone that has a lock on the write + // mutex. + void low_level_commit(uint_fast64_t new_version); + + void do_async_commits(); + + /// Upgrade file format and/or history schema + void upgrade_file_format(bool allow_file_format_upgrade, int target_file_format_version, + int current_hist_schema_version, int target_hist_schema_version); + + //@{ + /// See LangBindHelper. + template + void advance_read(O* observer, VersionID); + template + void promote_to_write(O* observer); + version_type commit_and_continue_as_read(); + template + void rollback_and_continue_as_read(O* observer); + //@} + + /// Returns true if, and only if _impl::History::update_early_from_top_ref() + /// was called during the execution of this function. + template + bool do_advance_read(O* observer, VersionID, _impl::History&); + + /// If there is an associated \ref Replication object, then this function + /// returns `repl->get_history()` where `repl` is that Replication object, + /// otherwise this function returns null. + _impl::History* get_history(); + + int get_file_format_version() const noexcept; + + /// finish up the process of starting a write transaction. Internal use only. + void finish_begin_write(); + + void close_internal(std::unique_lock) noexcept; + friend class _impl::SharedGroupFriend; +}; + + +inline void SharedGroup::get_stats(size_t& free_space, size_t& used_space, util::Optional locked_space) const +{ + free_space = m_free_space; + used_space = m_used_space; + if (locked_space) { + *locked_space = m_locked_space; + } +} + + +class ReadTransaction { +public: + ReadTransaction(SharedGroup& sg) + : m_shared_group(sg) + { + m_shared_group.begin_read(); // Throws + } + + ~ReadTransaction() noexcept + { + m_shared_group.end_read(); + } + + bool has_table(StringData name) const noexcept + { + return get_group().has_table(name); + } + + ConstTableRef get_table(size_t table_ndx) const + { + return get_group().get_table(table_ndx); // Throws + } + + ConstTableRef get_table(StringData name) const + { + return get_group().get_table(name); // Throws + } + + const Group& get_group() const noexcept; + + /// Get the version of the snapshot to which this read transaction is bound. + SharedGroup::version_type get_version() const noexcept; + +private: + SharedGroup& m_shared_group; +}; + + +class WriteTransaction { +public: + WriteTransaction(SharedGroup& sg) + : m_shared_group(&sg) + { + m_shared_group->begin_write(); // Throws + } + + ~WriteTransaction() noexcept + { + if (m_shared_group) + m_shared_group->rollback(); + } + + bool has_table(StringData name) const noexcept + { + return get_group().has_table(name); + } + + TableRef get_table(size_t table_ndx) const + { + return get_group().get_table(table_ndx); // Throws + } + + TableRef get_table(StringData name) const + { + return get_group().get_table(name); // Throws + } + + TableRef add_table(StringData name, bool require_unique_name = true) const + { + return get_group().add_table(name, require_unique_name); // Throws + } + + TableRef get_or_add_table(StringData name, bool* was_added = nullptr) const + { + return get_group().get_or_add_table(name, was_added); // Throws + } + + Group& get_group() const noexcept; + + /// Get the version of the snapshot on which this write transaction is + /// based. + SharedGroup::version_type get_version() const noexcept; + + SharedGroup::version_type commit() + { + REALM_ASSERT(m_shared_group); + SharedGroup::version_type new_version = m_shared_group->commit(); + m_shared_group = nullptr; + return new_version; + } + + void rollback() noexcept + { + REALM_ASSERT(m_shared_group); + m_shared_group->rollback(); + m_shared_group = nullptr; + } + +private: + SharedGroup* m_shared_group; +}; + + +// Implementation: + +struct SharedGroup::BadVersion : std::exception { +}; + +inline SharedGroup::SharedGroup(const std::string& file, bool no_create, const SharedGroupOptions options) + : m_group(Group::shared_tag()) + , m_upgrade_callback(std::move(options.upgrade_callback)) +{ + open(file, no_create, options); // Throws +} + +inline SharedGroup::SharedGroup(unattached_tag) noexcept + : m_group(Group::shared_tag()) +{ +} + +inline SharedGroup::SharedGroup(Replication& repl, const SharedGroupOptions options) + : m_group(Group::shared_tag()) + , m_upgrade_callback(std::move(options.upgrade_callback)) +{ + open(repl, options); // Throws +} + +inline void SharedGroup::open(const std::string& path, bool no_create_file, const SharedGroupOptions options) +{ + // Exception safety: Since open() is called from constructors, if it throws, + // it must leave the file closed. + + bool is_backend = false; + do_open(path, no_create_file, is_backend, options); // Throws +} + +inline void SharedGroup::open(Replication& repl, const SharedGroupOptions options) +{ + // Exception safety: Since open() is called from constructors, if it throws, + // it must leave the file closed. + + REALM_ASSERT(!is_attached()); + + repl.initialize(*this); // Throws + + typedef _impl::GroupFriend gf; + gf::set_replication(m_group, &repl); + + std::string file = repl.get_database_path(); + bool no_create = false; + bool is_backend = false; + do_open(file, no_create, is_backend, options); // Throws +} + +inline bool SharedGroup::is_attached() const noexcept +{ + return m_file_map.is_attached(); +} + +inline SharedGroup::TransactStage SharedGroup::get_transact_stage() const noexcept +{ + return m_transact_stage; +} + +inline SharedGroup::version_type SharedGroup::get_version_of_bound_snapshot() const noexcept +{ + return m_read_lock.m_version; +} + +class SharedGroup::ReadLockUnlockGuard { +public: + ReadLockUnlockGuard(SharedGroup& shared_group, ReadLockInfo& read_lock) noexcept + : m_shared_group(shared_group) + , m_read_lock(&read_lock) + { + } + ~ReadLockUnlockGuard() noexcept + { + if (m_read_lock) + m_shared_group.release_read_lock(*m_read_lock); + } + void release() noexcept + { + m_read_lock = 0; + } + +private: + SharedGroup& m_shared_group; + ReadLockInfo* m_read_lock; +}; + + +template +struct SharedGroup::Handover { + std::unique_ptr patch; + std::unique_ptr clone; + VersionID version; +}; + +template +std::unique_ptr> SharedGroup::export_for_handover(const T& accessor, ConstSourcePayload mode) +{ + if (m_transact_stage != transact_Reading) + throw LogicError(LogicError::wrong_transact_state); + std::unique_ptr> result(new Handover()); + // Implementation note: + // often, the return value from clone will be T*, BUT it may be ptr to some + // base of T instead, so we must cast it to T*. This is always safe, because + // no matter the type, clone() will clone the actual accessor instance, and + // hence return an instance of the same type. + result->clone.reset(dynamic_cast(accessor.clone_for_handover(result->patch, mode).release())); + result->version = get_version_of_current_transaction(); + return move(result); +} + + +template +std::unique_ptr>> SharedGroup::export_for_handover(const BasicRow& accessor) +{ + if (m_transact_stage != transact_Reading) + throw LogicError(LogicError::wrong_transact_state); + std::unique_ptr>> result(new Handover>()); + // See implementation note above. + result->clone.reset(dynamic_cast*>(accessor.clone_for_handover(result->patch).release())); + result->version = get_version_of_current_transaction(); + return move(result); +} + + +template +std::unique_ptr> SharedGroup::export_for_handover(T& accessor, MutableSourcePayload mode) +{ + if (m_transact_stage != transact_Reading) + throw LogicError(LogicError::wrong_transact_state); + std::unique_ptr> result(new Handover()); + // see implementation note above. + result->clone.reset(dynamic_cast(accessor.clone_for_handover(result->patch, mode).release())); + result->version = get_version_of_current_transaction(); + return move(result); +} + + +template +std::unique_ptr SharedGroup::import_from_handover(std::unique_ptr> handover) +{ + if (handover->version != get_version_of_current_transaction()) { + throw BadVersion(); + } + std::unique_ptr result = move(handover->clone); + result->apply_and_consume_patch(handover->patch, m_group); + return result; +} + +template +inline void SharedGroup::advance_read(O* observer, VersionID version_id) +{ + if (m_transact_stage != transact_Reading) + throw LogicError(LogicError::wrong_transact_state); + + // It is an error if the new version precedes the currently bound one. + if (version_id.version < m_read_lock.m_version) + throw LogicError(LogicError::bad_version); + + Replication* repl = m_group.get_replication(); + _impl::History* hist = repl ? repl->get_history() : nullptr; // Throws + + if (!hist) + throw LogicError(LogicError::no_history); + + bool hist_updated = do_advance_read(observer, version_id, *hist); // Throws + repl->initiate_transact(Replication::TransactionType::trans_Read, version_id.version, hist_updated); +} + +template +inline void SharedGroup::promote_to_write(O* observer) +{ + if (m_transact_stage != transact_Reading) + throw LogicError(LogicError::wrong_transact_state); + + _impl::History* hist = get_history(); // Throws + if (!hist) + throw LogicError(LogicError::no_history); + + do_begin_write(); // Throws + try { + VersionID version = VersionID(); // Latest + bool history_updated = do_advance_read(observer, version, *hist); // Throws + + Replication* repl = m_group.get_replication(); + REALM_ASSERT(repl); // Presence of `repl` follows from the presence of `hist` + version_type current_version = m_read_lock.m_version; + repl->initiate_transact(Replication::TransactionType::trans_Write, current_version, + history_updated); // Throws + + // If the group has no top array (top_ref == 0), create a new node + // structure for an empty group now, to be ready for modifications. See + // also Group::attach_shared(). + using gf = _impl::GroupFriend; + gf::create_empty_group_when_missing(m_group); // Throws + } + catch (...) { + do_end_write(); + throw; + } + + set_transact_stage(transact_Writing); +} + +template +inline void SharedGroup::rollback_and_continue_as_read(O* observer) +{ + if (m_transact_stage != transact_Writing) + throw LogicError(LogicError::wrong_transact_state); + + _impl::History* hist = get_history(); // Throws + if (!hist) + throw LogicError(LogicError::no_history); + + BinaryData uncommitted_changes = hist->get_uncommitted_changes(); + + // FIXME: We are currently creating two transaction log parsers, one here, + // and one in advance_transact(). That is wasteful as the parser creation is + // expensive. + _impl::SimpleInputStream in(uncommitted_changes.data(), uncommitted_changes.size()); + _impl::TransactLogParser parser; // Throws + _impl::TransactReverser reverser; + parser.parse(in, reverser); // Throws + + if (observer && uncommitted_changes.size()) { + _impl::ReversedNoCopyInputStream reversed_in(reverser); + parser.parse(reversed_in, *observer); // Throws + observer->parse_complete(); // Throws + } + + // Mark all managed space (beyond the attached file) as free. + using gf = _impl::GroupFriend; + gf::reset_free_space_tracking(m_group); // Throws + + ref_type top_ref = m_read_lock.m_top_ref; + size_t file_size = m_read_lock.m_file_size; + _impl::ReversedNoCopyInputStream reversed_in(reverser); + gf::advance_transact(m_group, top_ref, file_size, reversed_in); // Throws + + do_end_write(); + + Replication* repl = gf::get_replication(m_group); + REALM_ASSERT(repl); // Presence of `repl` follows from the presence of `hist` + repl->abort_transact(); + + set_transact_stage(transact_Reading); +} + +template +inline bool SharedGroup::do_advance_read(O* observer, VersionID version_id, _impl::History& hist) +{ + ReadLockInfo new_read_lock; + grab_read_lock(new_read_lock, version_id); // Throws + REALM_ASSERT(new_read_lock.m_version >= m_read_lock.m_version); + if (new_read_lock.m_version == m_read_lock.m_version) { + release_read_lock(new_read_lock); + // _impl::History::update_from_ref() was not called + return false; + } + + ReadLockUnlockGuard g(*this, new_read_lock); + { + version_type new_version = new_read_lock.m_version; + size_t new_file_size = new_read_lock.m_file_size; + + // Synchronize readers view of the file + SlabAlloc& alloc = m_group.m_alloc; + alloc.update_reader_view(new_file_size); + + ref_type hist_ref = _impl::GroupFriend::get_history_ref(alloc, new_read_lock.m_top_ref); + hist.update_from_ref_and_version(hist_ref, new_version); // Throws + } + + if (observer) { + // This has to happen in the context of the originally bound snapshot + // and while the read transaction is still in a fully functional state. + _impl::TransactLogParser parser; + version_type old_version = m_read_lock.m_version; + version_type new_version = new_read_lock.m_version; + _impl::ChangesetInputStream in(hist, old_version, new_version); + parser.parse(in, *observer); // Throws + observer->parse_complete(); // Throws + } + + // The old read lock must be retained for as long as the change history is + // accessed (until Group::advance_transact() returns). This ensures that the + // oldest needed changeset remains in the history, even when the history is + // implemented as a separate unversioned entity outside the Realm (i.e., the + // old implementation and ShortCircuitHistory in + // test_lang_Bind_helper.cpp). On the other hand, if it had been the case, + // that the history was always implemented as a versioned entity, that was + // part of the Realm state, then it would not have been necessary to retain + // the old read lock beyond this point. + + { + version_type old_version = m_read_lock.m_version; + version_type new_version = new_read_lock.m_version; + ref_type new_top_ref = new_read_lock.m_top_ref; + size_t new_file_size = new_read_lock.m_file_size; + _impl::ChangesetInputStream in(hist, old_version, new_version); + m_group.advance_transact(new_top_ref, new_file_size, in); // Throws + } + + g.release(); + release_read_lock(m_read_lock); + m_read_lock = new_read_lock; + + return true; // _impl::History::update_early_from_top_ref() was called +} + +inline _impl::History* SharedGroup::get_history() +{ + using gf = _impl::GroupFriend; + if (Replication* repl = gf::get_replication(m_group)) + return repl->get_history(); + return 0; +} + +inline int SharedGroup::get_file_format_version() const noexcept +{ + using gf = _impl::GroupFriend; + return gf::get_file_format_version(m_group); +} + + +// The purpose of this class is to give internal access to some, but +// not all of the non-public parts of the SharedGroup class. +class _impl::SharedGroupFriend { +public: + static Group& get_group(SharedGroup& sg) noexcept + { + return sg.m_group; + } + + template + static void advance_read(SharedGroup& sg, O* obs, SharedGroup::VersionID ver) + { + sg.advance_read(obs, ver); // Throws + } + + template + static void promote_to_write(SharedGroup& sg, O* obs) + { + sg.promote_to_write(obs); // Throws + } + + static SharedGroup::version_type commit_and_continue_as_read(SharedGroup& sg) + { + return sg.commit_and_continue_as_read(); // Throws + } + + template + static void rollback_and_continue_as_read(SharedGroup& sg, O* obs) + { + sg.rollback_and_continue_as_read(obs); // Throws + } + + static void async_daemon_open(SharedGroup& sg, const std::string& file) + { + bool no_create = true; + bool is_backend = true; + SharedGroupOptions options; + options.durability = SharedGroupOptions::Durability::Async; + options.encryption_key = nullptr; + options.allow_file_format_upgrade = false; + sg.do_open(file, no_create, is_backend, options); // Throws + } + + static int get_file_format_version(const SharedGroup& sg) noexcept + { + return sg.get_file_format_version(); + } + + static SharedGroup::version_type get_version_of_latest_snapshot(SharedGroup& sg) + { + return sg.get_version_of_latest_snapshot(); + } + + static SharedGroup::version_type get_version_of_bound_snapshot(const SharedGroup& sg) noexcept + { + return sg.get_version_of_bound_snapshot(); + } +}; + +inline const Group& ReadTransaction::get_group() const noexcept +{ + using sgf = _impl::SharedGroupFriend; + return sgf::get_group(m_shared_group); +} + +inline SharedGroup::version_type ReadTransaction::get_version() const noexcept +{ + using sgf = _impl::SharedGroupFriend; + return sgf::get_version_of_bound_snapshot(m_shared_group); +} + +inline Group& WriteTransaction::get_group() const noexcept +{ + REALM_ASSERT(m_shared_group); + using sgf = _impl::SharedGroupFriend; + return sgf::get_group(*m_shared_group); +} + +inline SharedGroup::version_type WriteTransaction::get_version() const noexcept +{ + using sgf = _impl::SharedGroupFriend; + return sgf::get_version_of_bound_snapshot(*m_shared_group); +} + +} // namespace realm + +#endif // REALM_GROUP_SHARED_HPP diff --git a/!main project/Pods/Realm/include/core/realm/group_shared_options.hpp b/!main project/Pods/Realm/include/core/realm/group_shared_options.hpp new file mode 100644 index 0000000..73f7d3e --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/group_shared_options.hpp @@ -0,0 +1,118 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_GROUP_SHARED_OPTIONS_HPP +#define REALM_GROUP_SHARED_OPTIONS_HPP + +#include +#include + +namespace realm { + +struct SharedGroupOptions { + + /// The persistence level of the SharedGroup. + /// uint16_t is the type of SharedGroup::SharedInfo::durability + enum class Durability : uint16_t { + Full, + MemOnly, + Async, ///< Not yet supported on windows. + Unsafe // If you use this, you loose ACID property + }; + + explicit SharedGroupOptions(Durability level = Durability::Full, const char* key = nullptr, + bool allow_upgrade = true, + std::function file_upgrade_callback = std::function(), + std::string temp_directory = sys_tmp_dir, bool track_metrics = false, + size_t metrics_history_size = 10000) + : durability(level) + , encryption_key(key) + , allow_file_format_upgrade(allow_upgrade) + , upgrade_callback(file_upgrade_callback) + , temp_dir(temp_directory) + , enable_metrics(track_metrics) + , metrics_buffer_size(metrics_history_size) + + { + } + + explicit SharedGroupOptions(const char* key) + : durability(Durability::Full) + , encryption_key(key) + , allow_file_format_upgrade(true) + , upgrade_callback(std::function()) + , temp_dir(sys_tmp_dir) + , enable_metrics(false) + , metrics_buffer_size(10000) + { + } + + /// The persistence level of the Realm file. See Durability. + Durability durability; + + /// The key to encrypt and decrypt the Realm file with, or nullptr to + /// indicate that encryption should not be used. + const char* encryption_key; + + /// If \a allow_file_format_upgrade is set to `true`, this function will + /// automatically upgrade the file format used in the specified Realm file + /// if necessary (and if it is possible). In order to prevent this, set \a + /// allow_upgrade to `false`. + /// + /// If \a allow_upgrade is set to `false`, only two outcomes are possible: + /// + /// - the specified Realm file is already using the latest file format, and + /// can be used, or + /// + /// - the specified Realm file uses a deprecated file format, resulting a + /// the throwing of FileFormatUpgradeRequired. + bool allow_file_format_upgrade; + + /// Optionally allows a custom function to be called immediately after the + /// Realm file is upgraded. The two parameters in the function are the + /// previous version and the version just upgraded to, respectively. + /// If the callback function throws, the Realm file will safely abort the + /// upgrade (rollback the transaction) but the SharedGroup will not be opened. + std::function upgrade_callback; + + /// A path to a directory where Realm can write temporary files or pipes to. + /// This string should include a trailing slash '/'. + std::string temp_dir; + + /// Controls the feature of collecting various metrics to the SharedGroup. + /// A prerequisite is compiling with REALM_METRICS=ON. + bool enable_metrics; + + /// The maximum number of entries stored by the metrics (if enabled). If this number + /// is exceeded without being consumed, only the most recent entries will be stored. + size_t metrics_buffer_size; + + /// sys_tmp_dir will be used if the temp_dir is empty when creating SharedGroupOptions. + /// It must be writable and allowed to create pipe/fifo file on it. + /// set_sys_tmp_dir is not a thread-safe call and it is only supposed to be called once + // when process starts. + static void set_sys_tmp_dir(const std::string& dir) noexcept { sys_tmp_dir = dir; } + static std::string get_sys_tmp_dir() noexcept { return sys_tmp_dir; } + +private: + static std::string sys_tmp_dir; +}; + +} // end namespace realm + +#endif // REALM_GROUP_SHARED_OPTIONS_HPP diff --git a/!main project/Pods/Realm/include/core/realm/group_writer.hpp b/!main project/Pods/Realm/include/core/realm/group_writer.hpp new file mode 100644 index 0000000..5721a3e --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/group_writer.hpp @@ -0,0 +1,202 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_GROUP_WRITER_HPP +#define REALM_GROUP_WRITER_HPP + +#include // unint8_t etc +#include +#include + +#include +#include +#include +#include +#include + + +namespace realm { + +// Pre-declarations +class Group; +class SlabAlloc; + + +/// This class is not supposed to be reused for multiple write sessions. In +/// particular, do not reuse it in case any of the functions throw. +/// +/// FIXME: Move this class to namespace realm::_impl and to subdir src/realm/impl. +class GroupWriter : public _impl::ArrayWriterBase { +public: + // For groups in transactional mode (Group::m_is_shared), this constructor + // must be called while a write transaction is in progress. + // + // The constructor adds free-space tracking information to the specified + // group, if it is not already present (4th and 5th entry in + // Group::m_top). If the specified group is in transactional mode + // (Group::m_is_shared), the constructor also adds version tracking + // information to the group, if it is not already present (6th and 7th entry + // in Group::m_top). + using Durability = SharedGroupOptions::Durability; + GroupWriter(Group&, Durability dura = Durability::Full); + ~GroupWriter(); + + void set_versions(uint64_t current, uint64_t read_lock) noexcept; + + /// Write all changed array nodes into free space. + /// + /// Returns the new top ref. When in full durability mode, call + /// commit() with the returned top ref. + ref_type write_group(); + + /// Flush changes to physical medium, then write the new top ref + /// to the file header, then flush again. Pass the top ref + /// returned by write_group(). + void commit(ref_type new_top_ref); + + size_t get_file_size() const noexcept; + + ref_type write_array(const char*, size_t, uint32_t) override; + +#ifdef REALM_DEBUG + void dump(); +#endif + + size_t get_free_space_size() const + { + return m_free_space_size; + } + + size_t get_locked_space_size() const + { + return m_locked_space_size; + } + +private: + class MapWindow; + Group& m_group; + SlabAlloc& m_alloc; + ArrayInteger m_free_positions; // 4th slot in Group::m_top + ArrayInteger m_free_lengths; // 5th slot in Group::m_top + ArrayInteger m_free_versions; // 6th slot in Group::m_top + uint64_t m_current_version = 0; + uint64_t m_readlock_version; + size_t m_window_alignment; + size_t m_free_space_size = 0; + size_t m_locked_space_size = 0; + Durability m_durability; + + struct FreeSpaceEntry { + FreeSpaceEntry(size_t r, size_t s, uint64_t v) + : ref(r) + , size(s) + , released_at_version(v) + { + } + size_t ref; + size_t size; + uint64_t released_at_version; + }; + class FreeList : public std::vector { + public: + FreeList() = default; + // Merge adjacent chunks + void merge_adjacent_entries_in_freelist(); + // Copy free space entries to structure where entries are sorted by size + void move_free_in_file_to_size_map(std::multimap& size_map); + }; + // m_free_in_file; + std::vector m_not_free_in_file; + std::multimap m_size_map; + using FreeListElement = std::multimap::iterator; + + void read_in_freelist(); + size_t recreate_freelist(size_t reserve_pos); + // Currently cached memory mappings. We keep as many as 16 1MB windows + // open for writing. The allocator will favor sequential allocation + // from a modest number of windows, depending upon fragmentation, so + // 16 windows should be more than enough. If more than 16 windows are + // needed, the least recently used is sync'ed and closed to make room + // for a new one. The windows are kept in MRU (most recently used) order. + const static int num_map_windows = 16; + std::vector> m_map_windows; + + // Get a suitable memory mapping for later access: + // potentially adding it to the cache, potentially closing + // the least recently used and sync'ing it to disk + MapWindow* get_window(ref_type start_ref, size_t size); + + // Sync all cached memory mappings + void sync_all_mappings(); + + /// Allocate a chunk of free space of the specified size. The + /// specified size must be 8-byte aligned. Extend the file if + /// required. The returned chunk is removed from the amount of + /// remaing free space. The returned chunk is guaranteed to be + /// within a single contiguous memory mapping. + /// + /// \return The position within the database file of the allocated + /// chunk. + size_t get_free_space(size_t size); + + /// Find a block of free space that is at least as big as the + /// specified size and which will allow an allocation that is mapped + /// inside a contiguous address range. The specified size does not + /// need to be 8-byte aligned. Extend the file if required. + /// The returned chunk is not removed from the amount of remaing + /// free space. + /// + /// \return A pair (`chunk_ndx`, `chunk_size`) where `chunk_ndx` + /// is the index of a chunk whose size is at least the requestd + /// size, and `chunk_size` is the size of that chunk. + FreeListElement reserve_free_space(size_t size); + + FreeListElement search_free_space_in_free_list_element(FreeListElement element, size_t size); + + /// Search only a range of the free list for a block as big as the + /// specified size. Return a pair with index and size of the found chunk. + /// \param found indicates whether a suitable block was found. + FreeListElement search_free_space_in_part_of_freelist(size_t size); + + /// Extend the file to ensure that a chunk of free space of the + /// specified size is available. The specified size does not need + /// to be 8-byte aligned. This function guarantees that it will + /// add at most one entry to the free-lists. + /// + /// \return A pair (`chunk_ndx`, `chunk_size`) where `chunk_ndx` + /// is the index of a chunk whose size is at least the requestd + /// size, and `chunk_size` is the size of that chunk. + FreeListElement extend_free_space(size_t requested_size); + + void write_array_at(MapWindow* window, ref_type, const char* data, size_t size); + FreeListElement split_freelist_chunk(FreeListElement, size_t alloc_pos); +}; + + +// Implementation: + +inline void GroupWriter::set_versions(uint64_t current, uint64_t read_lock) noexcept +{ + REALM_ASSERT(read_lock <= current); + m_current_version = current; + m_readlock_version = read_lock; +} + +} // namespace realm + +#endif // REALM_GROUP_WRITER_HPP diff --git a/!main project/Pods/Realm/include/core/realm/handover_defs.hpp b/!main project/Pods/Realm/include/core/realm/handover_defs.hpp new file mode 100644 index 0000000..cd8be27 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/handover_defs.hpp @@ -0,0 +1,105 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_HANDOVER_DEFS +#define REALM_HANDOVER_DEFS + +#include +#include + +namespace realm { + +enum class ConstSourcePayload { Copy, Stay }; +enum class MutableSourcePayload { Move }; + +struct RowBaseHandoverPatch; +struct TableViewHandoverPatch; + +struct TableHandoverPatch { + bool m_is_sub_table; + size_t m_table_num; + size_t m_col_ndx; + size_t m_row_ndx; +}; + +struct LinkViewHandoverPatch { + std::unique_ptr m_table; + size_t m_col_num; + size_t m_row_ndx; +}; + +// Base class for handover patches for query nodes. Subclasses are declared in query_engine.hpp. +struct QueryNodeHandoverPatch { + virtual ~QueryNodeHandoverPatch() = default; +}; + +using QueryNodeHandoverPatches = std::vector>; + +struct QueryHandoverPatch { + std::unique_ptr m_table; + std::unique_ptr table_view_data; + std::unique_ptr link_view_data; + QueryNodeHandoverPatches m_node_data; +}; + +enum class DescriptorType { Sort, Distinct, Limit, Include }; + +struct DescriptorLinkPath { + DescriptorLinkPath(size_t column_index, size_t table_index, bool column_is_backlink) + : col_ndx(column_index) + , table_ndx(table_index) + , is_backlink(column_is_backlink) + { + } + + size_t col_ndx; + size_t table_ndx; + bool is_backlink = false; +}; + +struct DescriptorExport { + DescriptorType type; + std::vector> columns; + std::vector ordering; + size_t limit; +}; + +struct DescriptorOrderingHandoverPatch { + std::vector descriptors; +}; + +struct TableViewHandoverPatch { + std::unique_ptr m_table; + std::unique_ptr linked_row; + size_t linked_col; + bool was_in_sync; + QueryHandoverPatch query_patch; + std::unique_ptr linkview_patch; + std::unique_ptr descriptors_patch; +}; + + +struct RowBaseHandoverPatch { + std::unique_ptr m_table; + size_t row_ndx; +}; + + +} // end namespace Realm + +#endif diff --git a/!main project/Pods/Realm/include/core/realm/history.hpp b/!main project/Pods/Realm/include/core/realm/history.hpp new file mode 100644 index 0000000..9710d0b --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/history.hpp @@ -0,0 +1,35 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_HISTORY_HPP +#define REALM_HISTORY_HPP + +#include +#include + +#include + + +namespace realm { + +std::unique_ptr make_in_realm_history(const std::string& realm_path); + +} // namespace realm + + +#endif // REALM_HISTORY_HPP diff --git a/!main project/Pods/Realm/include/core/realm/impl/array_writer.hpp b/!main project/Pods/Realm/include/core/realm/impl/array_writer.hpp new file mode 100644 index 0000000..f039ad5 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/impl/array_writer.hpp @@ -0,0 +1,44 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_ARRAY_WRITER_HPP +#define REALM_ARRAY_WRITER_HPP + +#include + +namespace realm { +namespace _impl { + +class ArrayWriterBase { +public: + virtual ~ArrayWriterBase() + { + } + + /// Write the specified array data and its checksum into free + /// space. + /// + /// Returns the ref (position in the target stream) of the written copy of + /// the specified array data. + virtual ref_type write_array(const char* data, size_t size, uint32_t checksum) = 0; +}; + +} // namespace impl_ +} // namespace realm + +#endif // REALM_ARRAY_WRITER_HPP diff --git a/!main project/Pods/Realm/include/core/realm/impl/clamped_hex_dump.hpp b/!main project/Pods/Realm/include/core/realm/impl/clamped_hex_dump.hpp new file mode 100644 index 0000000..353e546 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/impl/clamped_hex_dump.hpp @@ -0,0 +1,49 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2015] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ + +#ifndef REALM_IMPL_CLAMPED_HEX_DUMP_HPP +#define REALM_IMPL_CLAMPED_HEX_DUMP_HPP + +#include +#include + +namespace realm { +namespace _impl { + +/// Limit the amount of dumped data to 1024 bytes. For use in connection with +/// logging. +inline std::string clamped_hex_dump(BinaryData blob, std::size_t max_size = 1024) +{ + bool was_clipped = false; + std::size_t size_2 = blob.size(); + if (size_2 > max_size) { + size_2 = max_size; + was_clipped = true; + } + std::string str = util::hex_dump(blob.data(), size_2); // Throws + if (was_clipped) + str += "..."; // Throws + return str; +} + +} // namespace _impl +} // namespace realm + +#endif // REALM_IMPL_CLAMPED_HEX_DUMP_HPP diff --git a/!main project/Pods/Realm/include/core/realm/impl/clock.hpp b/!main project/Pods/Realm/include/core/realm/impl/clock.hpp new file mode 100644 index 0000000..fdf8aa3 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/impl/clock.hpp @@ -0,0 +1,54 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2015] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ + +#ifndef REALM_IMPL_CLOCK_HPP +#define REALM_IMPL_CLOCK_HPP + +#include +#include + +#include + +namespace realm { +namespace _impl { + +inline sync::milliseconds_type realtime_clock_now() noexcept +{ + using clock = std::chrono::system_clock; + auto time_since_epoch = clock::now().time_since_epoch(); + auto millis_since_epoch = + std::chrono::duration_cast(time_since_epoch).count(); + return sync::milliseconds_type(millis_since_epoch); +} + + +inline sync::milliseconds_type monotonic_clock_now() noexcept +{ + using clock = std::chrono::steady_clock; + auto time_since_epoch = clock::now().time_since_epoch(); + auto millis_since_epoch = + std::chrono::duration_cast(time_since_epoch).count(); + return sync::milliseconds_type(millis_since_epoch); +} + +} // namespace _impl +} // namespace realm + +#endif // REALM_IMPL_CLOCK_HPP diff --git a/!main project/Pods/Realm/include/core/realm/impl/cont_transact_hist.hpp b/!main project/Pods/Realm/include/core/realm/impl/cont_transact_hist.hpp new file mode 100644 index 0000000..deb0f8b --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/impl/cont_transact_hist.hpp @@ -0,0 +1,147 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_IMPL_CONT_TRANSACT_HIST_HPP +#define REALM_IMPL_CONT_TRANSACT_HIST_HPP + +#include +#include + +#include +#include + +namespace realm { + +class Group; + +namespace _impl { + +/// Read-only access to history of changesets as needed to enable continuous +/// transactions. +class History { +public: + using version_type = VersionID::version_type; + + virtual ~History() noexcept {} + + /// May be called during any transaction + /// + /// It is a precondition for calls to this function that the reader view is + /// updated - that is, the mapping is updated to provide full visibility to + /// the file. + /// + virtual void update_from_ref_and_version(ref_type ref, version_type version) = 0; + virtual void update_from_parent(version_type version) = 0; + + /// Get all changesets between the specified versions. References to those + /// changesets will be made available in successive entries of `buffer`. The + /// number of retrieved changesets is exactly `end_version - + /// begin_version`. If this number is greater than zero, the changeset made + /// available in `buffer[0]` is the one that brought the database from + /// `begin_version` to `begin_version + 1`. + /// + /// It is an error to specify a version (for \a begin_version or \a + /// end_version) that is outside the range [V,W] where V is the version that + /// immediately precedes the first changeset available in the history as the + /// history appears in the **latest** available snapshot, and W is the + /// version that immediately succeeds the last changeset available in the + /// history as the history appears in the snapshot bound to the **current** + /// transaction. This restriction is necessary to allow for different kinds + /// of implementations of the history (separate standalone history or + /// history as part of versioned Realm state). + /// + /// The callee retains ownership of the memory referenced by those entries, + /// i.e., the memory referenced by `buffer[i].changeset` is **not** handed + /// over to the caller. + /// + /// This function may be called only during a transaction (prior to + /// initiation of commit operation), and only after a successful invocation + /// of update_early_from_top_ref(). In that case, the caller may assume that + /// the memory references stay valid for the remainder of the transaction + /// (up until initiation of the commit operation). + virtual void get_changesets(version_type begin_version, version_type end_version, BinaryIterator* buffer) const + noexcept = 0; + + /// \brief Specify the version of the oldest bound snapshot. + /// + /// This function must be called by the associated SharedGroup object during + /// each successfully committed write transaction. It must be called before + /// the transaction is finalized (Replication::finalize_commit()) or aborted + /// (Replication::abort_transact()), but after the initiation of the commit + /// operation (Replication::prepare_commit()). This allows history + /// implementations to add new history entries before trimming off old ones, + /// and this, in turn, guarantees that the history never becomes empty, + /// except in the initial empty Realm state. + /// + /// The caller must pass the version (\a version) of the oldest snapshot + /// that is currently (or was recently) bound via a transaction of the + /// current session. This gives the history implementation an opportunity to + /// trim off leading (early) history entries. + /// + /// Since this function must be called during a write transaction, there + /// will always be at least one snapshot that is currently bound via a + /// transaction. + /// + /// The caller must guarantee that the passed version (\a version) is less + /// than or equal to `begin_version` in all future invocations of + /// get_changesets(). + /// + /// The caller is allowed to pass a version that is less than the version + /// passed in a preceding invocation. + /// + /// This function should be called as late as possible, to maximize the + /// trimming opportunity, but at a time where the write transaction is still + /// open for additional modifications. This is necessary because some types + /// of histories are stored inside the Realm file. + virtual void set_oldest_bound_version(version_type version) = 0; + + /// Get the list of uncommitted changes accumulated so far in the current + /// write transaction. + /// + /// The callee retains ownership of the referenced memory. The ownership is + /// not handed over to the caller. + /// + /// This function may be called only during a write transaction (prior to + /// initiation of commit operation). In that case, the caller may assume that the + /// returned memory reference stays valid for the remainder of the transaction (up + /// until initiation of the commit operation). + virtual BinaryData get_uncommitted_changes() noexcept = 0; + + virtual void verify() const = 0; + + void set_updated(bool updated) + { + m_updated = updated; + } + + void ensure_updated(version_type version) const + { + if (!m_updated) { + const_cast(this)->update_from_parent(version); + m_updated = true; + } + } + +private: + mutable bool m_updated = false; +}; + +} // namespace _impl +} // namespace realm + +#endif // REALM_IMPL_CONTINUOUS_TRANSACTIONS_HISTORY_HPP diff --git a/!main project/Pods/Realm/include/core/realm/impl/destroy_guard.hpp b/!main project/Pods/Realm/include/core/realm/impl/destroy_guard.hpp new file mode 100644 index 0000000..f5b5e37 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/impl/destroy_guard.hpp @@ -0,0 +1,237 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_IMPL_DESTROY_GUARD_HPP +#define REALM_IMPL_DESTROY_GUARD_HPP + +#include +#include + +namespace realm { +namespace _impl { + + +/// Calls `ptr->destroy()` if the guarded pointer (`ptr`) is not null +/// when the guard is destroyed. For arrays (`T` = `Array`) this means +/// that the array is destroyed in a shallow fashion. See +/// `DeepArrayDestroyGuard` for an alternative. +template +class DestroyGuard { +public: + DestroyGuard() noexcept; + + DestroyGuard(T*) noexcept; + + ~DestroyGuard() noexcept; + + // Default implementations of copy/assign can trigger multiple destructions + DestroyGuard(const DestroyGuard&) = delete; + DestroyGuard& operator=(const DestroyGuard&) = delete; + + void reset(T*) noexcept; + + T* get() const noexcept; + + T* release() noexcept; + +private: + T* m_ptr; +}; + +using ShallowArrayDestroyGuard = DestroyGuard; + + +/// Calls `ptr->destroy_deep()` if the guarded Array pointer (`ptr`) +/// is not null when the guard is destroyed. +class DeepArrayDestroyGuard { +public: + DeepArrayDestroyGuard() noexcept; + + DeepArrayDestroyGuard(Array*) noexcept; + + ~DeepArrayDestroyGuard() noexcept; + + // Default implementations of copy/assign can trigger multiple destructions + DeepArrayDestroyGuard(const DeepArrayDestroyGuard&) = delete; + DeepArrayDestroyGuard& operator=(const DeepArrayDestroyGuard&) = delete; + + void reset(Array*) noexcept; + + Array* get() const noexcept; + + Array* release() noexcept; + +private: + Array* m_ptr; +}; + + +/// Calls `Array::destroy_deep(ref, alloc)` if the guarded 'ref' +/// (`ref`) is not zero when the guard is destroyed. +class DeepArrayRefDestroyGuard { +public: + DeepArrayRefDestroyGuard(Allocator&) noexcept; + + DeepArrayRefDestroyGuard(ref_type, Allocator&) noexcept; + + ~DeepArrayRefDestroyGuard() noexcept; + + // Default implementations of copy/assign can trigger multiple destructions + DeepArrayRefDestroyGuard(const DeepArrayRefDestroyGuard&) = delete; + DeepArrayRefDestroyGuard& operator=(const DeepArrayRefDestroyGuard&) = delete; + + void reset(ref_type) noexcept; + + ref_type get() const noexcept; + + ref_type release() noexcept; + +private: + ref_type m_ref; + Allocator& m_alloc; +}; + + +// Implementation: + +// DestroyGuard + +template +inline DestroyGuard::DestroyGuard() noexcept + : m_ptr(nullptr) +{ +} + +template +inline DestroyGuard::DestroyGuard(T* ptr) noexcept + : m_ptr(ptr) +{ +} + +template +inline DestroyGuard::~DestroyGuard() noexcept +{ + if (m_ptr) + m_ptr->destroy(); +} + +template +inline void DestroyGuard::reset(T* ptr) noexcept +{ + if (m_ptr) + m_ptr->destroy(); + m_ptr = ptr; +} + +template +inline T* DestroyGuard::get() const noexcept +{ + return m_ptr; +} + +template +inline T* DestroyGuard::release() noexcept +{ + T* ptr = m_ptr; + m_ptr = nullptr; + return ptr; +} + + +// DeepArrayDestroyGuard + +inline DeepArrayDestroyGuard::DeepArrayDestroyGuard() noexcept + : m_ptr(nullptr) +{ +} + +inline DeepArrayDestroyGuard::DeepArrayDestroyGuard(Array* ptr) noexcept + : m_ptr(ptr) +{ +} + +inline DeepArrayDestroyGuard::~DeepArrayDestroyGuard() noexcept +{ + if (m_ptr) + m_ptr->destroy_deep(); +} + +inline void DeepArrayDestroyGuard::reset(Array* ptr) noexcept +{ + if (m_ptr) + m_ptr->destroy_deep(); + m_ptr = ptr; +} + +inline Array* DeepArrayDestroyGuard::get() const noexcept +{ + return m_ptr; +} + +inline Array* DeepArrayDestroyGuard::release() noexcept +{ + Array* ptr = m_ptr; + m_ptr = nullptr; + return ptr; +} + + +// DeepArrayRefDestroyGuard + +inline DeepArrayRefDestroyGuard::DeepArrayRefDestroyGuard(Allocator& alloc) noexcept + : m_ref(0) + , m_alloc(alloc) +{ +} + +inline DeepArrayRefDestroyGuard::DeepArrayRefDestroyGuard(ref_type ref, Allocator& alloc) noexcept + : m_ref(ref) + , m_alloc(alloc) +{ +} + +inline DeepArrayRefDestroyGuard::~DeepArrayRefDestroyGuard() noexcept +{ + if (m_ref) + Array::destroy_deep(m_ref, m_alloc); +} + +inline void DeepArrayRefDestroyGuard::reset(ref_type ref) noexcept +{ + if (m_ref) + Array::destroy_deep(m_ref, m_alloc); + m_ref = ref; +} + +inline ref_type DeepArrayRefDestroyGuard::get() const noexcept +{ + return m_ref; +} + +inline ref_type DeepArrayRefDestroyGuard::release() noexcept +{ + ref_type ref = m_ref; + m_ref = 0; + return ref; +} + + +} // namespace _impl +} // namespace realm + +#endif // REALM_IMPL_DESTROY_GUARD_HPP diff --git a/!main project/Pods/Realm/include/core/realm/impl/input_stream.hpp b/!main project/Pods/Realm/include/core/realm/impl/input_stream.hpp new file mode 100644 index 0000000..80db30a --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/impl/input_stream.hpp @@ -0,0 +1,255 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_IMPL_INPUT_STREAM_HPP +#define REALM_IMPL_INPUT_STREAM_HPP + +#include + +#include +#include +#include + + +namespace realm { +namespace _impl { + + +class InputStream { +public: + /// Read bytes from this input stream and place them in the specified + /// buffer. The returned value is the actual number of bytes that were read, + /// and this is some number `n` such that `n <= min(size, m)` where `m` is + /// the number of bytes that could have been read from this stream before + /// reaching its end. Also, `n` cannot be zero unless `m` or `size` is + /// zero. The intention is that `size` should be non-zero, a the return + /// value used as the end-of-input indicator. + /// + /// Implementations are only allowed to block (put the calling thread to + /// sleep) up until the point in time where the first byte can be made + /// availble. + virtual size_t read(char* buffer, size_t size) = 0; + + virtual ~InputStream() noexcept + { + } +}; + + +class SimpleInputStream : public InputStream { +public: + SimpleInputStream(const char* data, size_t size) noexcept + : m_ptr(data) + , m_end(data + size) + { + } + size_t read(char* buffer, size_t size) override + { + size_t n = std::min(size, size_t(m_end - m_ptr)); + const char* begin = m_ptr; + m_ptr += n; + realm::safe_copy_n(begin, n, buffer); + return n; + } + +private: + const char* m_ptr; + const char* const m_end; +}; + + +class NoCopyInputStream { +public: + /// \return if any bytes was read. + /// A value of false indicates end-of-input. + /// If return value is true, \a begin and \a end are + /// updated to reflect the start and limit of a + /// contiguous memory chunk. + virtual bool next_block(const char*& begin, const char*& end) = 0; + + virtual ~NoCopyInputStream() noexcept + { + } +}; + + +class NoCopyInputStreamAdaptor : public NoCopyInputStream { +public: + NoCopyInputStreamAdaptor(InputStream& in, char* buffer, size_t buffer_size) noexcept + : m_in(in) + , m_buffer(buffer) + , m_buffer_size(buffer_size) + { + } + bool next_block(const char*& begin, const char*& end) override + { + size_t n = m_in.read(m_buffer, m_buffer_size); + begin = m_buffer; + end = m_buffer + n; + return n; + } + +private: + InputStream& m_in; + char* m_buffer; + size_t m_buffer_size; +}; + + +class SimpleNoCopyInputStream : public NoCopyInputStream { +public: + SimpleNoCopyInputStream(const char* data, size_t size) + : m_data(data) + , m_size(size) + { + } + + bool next_block(const char*& begin, const char*& end) override + { + if (m_size == 0) + return 0; + size_t size = m_size; + begin = m_data; + end = m_data + size; + m_size = 0; + return size; + } + +private: + const char* m_data; + size_t m_size; +}; + +class MultiLogNoCopyInputStream : public NoCopyInputStream { +public: + MultiLogNoCopyInputStream(const BinaryData* logs_begin, const BinaryData* logs_end) + : m_logs_begin(logs_begin) + , m_logs_end(logs_end) + { + if (m_logs_begin != m_logs_end) + m_curr_buf_remaining_size = m_logs_begin->size(); + } + + size_t read(char* buffer, size_t size) + { + if (m_logs_begin == m_logs_end) + return 0; + for (;;) { + if (m_curr_buf_remaining_size > 0) { + size_t offset = m_logs_begin->size() - m_curr_buf_remaining_size; + const char* data = m_logs_begin->data() + offset; + size_t size_2 = std::min(m_curr_buf_remaining_size, size); + m_curr_buf_remaining_size -= size_2; + // FIXME: Eliminate the need for copying by changing the API of + // Replication::InputStream such that blocks can be handed over + // without copying. This is a straight forward change, but the + // result is going to be more complicated and less conventional. + realm::safe_copy_n(data, size_2, buffer); + return size_2; + } + + ++m_logs_begin; + if (m_logs_begin == m_logs_end) + return 0; + m_curr_buf_remaining_size = m_logs_begin->size(); + } + } + + bool next_block(const char*& begin, const char*& end) override + { + while (m_logs_begin < m_logs_end) { + size_t result = m_logs_begin->size(); + const char* data = m_logs_begin->data(); + m_logs_begin++; + if (result == 0) + continue; // skip empty blocks + begin = data; + end = data + result; + return result; + } + return 0; + } + +private: + const BinaryData* m_logs_begin; + const BinaryData* m_logs_end; + size_t m_curr_buf_remaining_size; +}; + + +class ChangesetInputStream : public NoCopyInputStream { +public: + using version_type = History::version_type; + static constexpr unsigned NB_BUFFERS = 8; + + ChangesetInputStream(History& hist, version_type begin_version, version_type end_version) + : m_history(hist) + , m_begin_version(begin_version) + , m_end_version(end_version) + { + get_changeset(); + } + + bool next_block(const char*& begin, const char*& end) override + { + while (m_valid) { + BinaryData actual = m_changesets_begin->get_next(); + + if (actual.size() > 0) { + begin = actual.data(); + end = actual.data() + actual.size(); + return true; + } + + m_changesets_begin++; + + if (REALM_UNLIKELY(m_changesets_begin == m_changesets_end)) { + get_changeset(); + } + } + return false; // End of input + } + +private: + History& m_history; + version_type m_begin_version, m_end_version; + BinaryIterator m_changesets[NB_BUFFERS]; // Buffer + BinaryIterator* m_changesets_begin = nullptr; + BinaryIterator* m_changesets_end = nullptr; + bool m_valid; + + void get_changeset() + { + auto versions_to_get = m_end_version - m_begin_version; + m_valid = versions_to_get > 0; + if (m_valid) { + if (versions_to_get > NB_BUFFERS) + versions_to_get = NB_BUFFERS; + version_type end_version = m_begin_version + versions_to_get; + m_history.get_changesets(m_begin_version, end_version, m_changesets); + m_begin_version = end_version; + m_changesets_begin = m_changesets; + m_changesets_end = m_changesets_begin + versions_to_get; + } + } +}; + +} // namespace _impl +} // namespace realm + +#endif // REALM_IMPL_INPUT_STREAM_HPP diff --git a/!main project/Pods/Realm/include/core/realm/impl/output_stream.hpp b/!main project/Pods/Realm/include/core/realm/impl/output_stream.hpp new file mode 100644 index 0000000..1d022cd --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/impl/output_stream.hpp @@ -0,0 +1,75 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_IMPL_OUTPUT_STREAM_HPP +#define REALM_IMPL_OUTPUT_STREAM_HPP + +#include +#include + +#include + +#include + +#include + +namespace realm { +namespace _impl { + + +class OutputStream : public ArrayWriterBase { +public: + OutputStream(std::ostream&); + ~OutputStream() noexcept; + + ref_type get_ref_of_next_array() const noexcept; + + void write(const char* data, size_t size); + + ref_type write_array(const char* data, size_t size, uint32_t checksum) override; + +private: + ref_type m_next_ref; + std::ostream& m_out; + + void do_write(const char* data, size_t size); +}; + + +// Implementation: + +inline OutputStream::OutputStream(std::ostream& out) + : m_next_ref(0) + , m_out(out) +{ +} + +inline OutputStream::~OutputStream() noexcept +{ +} + +inline size_t OutputStream::get_ref_of_next_array() const noexcept +{ + return m_next_ref; +} + + +} // namespace _impl +} // namespace realm + +#endif // REALM_IMPL_OUTPUT_STREAM_HPP diff --git a/!main project/Pods/Realm/include/core/realm/impl/sequential_getter.hpp b/!main project/Pods/Realm/include/core/realm/impl/sequential_getter.hpp new file mode 100644 index 0000000..c7dd866 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/impl/sequential_getter.hpp @@ -0,0 +1,130 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_IMPL_SEQUENTIAL_GETTER_HPP +#define REALM_IMPL_SEQUENTIAL_GETTER_HPP + +namespace realm { + +class SequentialGetterBase { +public: + virtual ~SequentialGetterBase() noexcept + { + } +}; + +template +class SequentialGetter : public SequentialGetterBase { +public: + using T = typename ColType::value_type; + using ArrayType = typename ColType::LeafType; + + SequentialGetter() + { + } + + SequentialGetter(const Table& table, size_t column_ndx) + { + init(static_cast(&table.get_column_base(column_ndx))); + } + + SequentialGetter(const ColType* column) + { + init(column); + } + + ~SequentialGetter() noexcept override + { + } + + void init(const ColType* column) + { + REALM_ASSERT(column != nullptr); + m_array_ptr.reset(); // Explicitly destroy the old one first, because we're reusing the memory. + m_array_ptr.reset(new (&m_leaf_accessor_storage) ArrayType(column->get_alloc())); + m_column = column; + m_leaf_end = 0; + } + + REALM_FORCEINLINE bool cache_next(size_t index) + { + // Set m_leaf_ptr to point at the leaf that contains the value at column row `index`. Return whether or not + // the leaf has changed (could be useful to know for caller). + + // FIXME: Below line has been commented away because array leafs might relocate during the lifetime of the + // object that owns this SequentialGetter. Enable again when we have proper support for that. + // if (index >= m_leaf_end || index < m_leaf_start) + { + typename ColType::LeafInfo leaf{&m_leaf_ptr, m_array_ptr.get()}; + size_t ndx_in_leaf; + m_column->get_leaf(index, ndx_in_leaf, leaf); + m_leaf_start = index - ndx_in_leaf; + const size_t leaf_size = m_leaf_ptr->size(); + m_leaf_end = m_leaf_start + leaf_size; + return true; + } + return false; + } + + + REALM_FORCEINLINE T get_next(size_t index) + { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4800) // Disable the Microsoft warning about bool performance issue. +#endif + return m_column->get(index); + + // FIXME: Below optimization is skipped because array leafs might relocate during the lifetime of the + // object that owns this SequentialGetter. Enable again when we have proper support for that. +// +// cache_next(index); +// T av = m_leaf_ptr->get(index - m_leaf_start); +// return av; + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + } + + size_t local_end(size_t global_end) + { + if (global_end > m_leaf_end) + return m_leaf_end - m_leaf_start; + else + return global_end - m_leaf_start; + } + + size_t m_leaf_start = 0; + size_t m_leaf_end = 0; + const ColType* m_column = nullptr; + + const ArrayType* m_leaf_ptr = nullptr; + +private: + // Leaf cache for when the root of the column is not a leaf. + // This dog and pony show is because Array has a reference to Allocator internally, + // but we need to be able to transfer queries between contexts, so init() reinitializes + // the leaf cache in the context of the current column. + typename std::aligned_storage::type m_leaf_accessor_storage; + std::unique_ptr m_array_ptr; +}; + +} // namespace realm + +#endif // REALM_IMPL_SEQUENTIAL_GETTER_HPP diff --git a/!main project/Pods/Realm/include/core/realm/impl/simulated_failure.hpp b/!main project/Pods/Realm/include/core/realm/impl/simulated_failure.hpp new file mode 100644 index 0000000..4958151 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/impl/simulated_failure.hpp @@ -0,0 +1,228 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_IMPL_SIMULATED_FAILURE_HPP +#define REALM_IMPL_SIMULATED_FAILURE_HPP + +#include +#include + +#include + +#ifdef REALM_DEBUG +#define REALM_ENABLE_SIMULATED_FAILURE +#endif + +namespace realm { +namespace _impl { + +class SimulatedFailure : public std::system_error { +public: + enum FailureType { + generic, + slab_alloc__reset_free_space_tracking, + slab_alloc__remap, + shared_group__grow_reader_mapping, + sync_client__read_head, + sync_server__read_head, + _num_failure_types + }; + + class OneShotPrimeGuard; + class RandomPrimeGuard; + + /// Prime the specified failure type on the calling thread for triggering + /// once. + static void prime_one_shot(FailureType); + + /// Prime the specified failure type on the calling thread for triggering + /// randomly \a n out of \a m times. + static void prime_random(FailureType, int n, int m, uint_fast64_t seed = 0); + + /// Unprime the specified failure type on the calling thread. + static void unprime(FailureType) noexcept; + + /// Returns true according to the mode of priming of the specified failure + /// type on the calling thread, but only if REALM_ENABLE_SIMULATED_FAILURE + /// was defined during compilation. If REALM_ENABLE_SIMULATED_FAILURE was + /// not defined, this function always return false. + static bool check_trigger(FailureType) noexcept; + + /// The specified error code is set to `make_error_code(failure_type)` if + /// check_trigger() returns true. Otherwise it is set to + /// `std::error_code()`. Returns a copy of the updated error code. + static std::error_code trigger(FailureType failure_type, std::error_code&) noexcept; + + /// Throws SimulatedFailure if check_trigger() returns true. The exception + /// will be constructed with an error code equal to + /// `make_error_code(failure_type)`. + static void trigger(FailureType failure_type); + + /// Returns true when, and only when REALM_ENABLE_SIMULATED_FAILURE was + /// defined during compilation. + static constexpr bool is_enabled(); + + SimulatedFailure(std::error_code); + +private: +#ifdef REALM_ENABLE_SIMULATED_FAILURE + static void do_prime_one_shot(FailureType); + static void do_prime_random(FailureType, int n, int m, uint_fast64_t seed); + static void do_unprime(FailureType) noexcept; + static bool do_check_trigger(FailureType) noexcept; +#endif +}; + +std::error_code make_error_code(SimulatedFailure::FailureType) noexcept; + +class SimulatedFailure::OneShotPrimeGuard { +public: + OneShotPrimeGuard(FailureType); + ~OneShotPrimeGuard() noexcept; + +private: + const FailureType m_type; +}; + + +class SimulatedFailure::RandomPrimeGuard { +public: + RandomPrimeGuard(FailureType, int n, int m, uint_fast64_t seed = 0); + ~RandomPrimeGuard() noexcept; + +private: + const FailureType m_type; +}; + +std::error_code make_error_code(SimulatedFailure::FailureType) noexcept; + +} // namespace _impl +} // namespace realm + +namespace std { + +template<> struct is_error_code_enum { + static const bool value = true; +}; + +} // namespace std + +namespace realm { +namespace _impl { + + +// Implementation + +inline void SimulatedFailure::prime_one_shot(FailureType failure_type) +{ +#ifdef REALM_ENABLE_SIMULATED_FAILURE + do_prime_one_shot(failure_type); +#else + static_cast(failure_type); +#endif +} + +inline void SimulatedFailure::prime_random(FailureType failure_type, int n, int m, uint_fast64_t seed) +{ +#ifdef REALM_ENABLE_SIMULATED_FAILURE + do_prime_random(failure_type, n, m, seed); +#else + static_cast(failure_type); + static_cast(n); + static_cast(m); + static_cast(seed); +#endif +} + +inline void SimulatedFailure::unprime(FailureType failure_type) noexcept +{ +#ifdef REALM_ENABLE_SIMULATED_FAILURE + do_unprime(failure_type); +#else + static_cast(failure_type); +#endif +} + +inline bool SimulatedFailure::check_trigger(FailureType failure_type) noexcept +{ +#ifdef REALM_ENABLE_SIMULATED_FAILURE + return do_check_trigger(failure_type); +#else + static_cast(failure_type); + return false; +#endif +} + +inline std::error_code SimulatedFailure::trigger(FailureType failure_type, std::error_code& ec) noexcept +{ + if (check_trigger(failure_type)) { + ec = make_error_code(failure_type); + } + else { + ec = std::error_code(); + } + return ec; +} + +inline void SimulatedFailure::trigger(FailureType failure_type) +{ + if (check_trigger(failure_type)) + throw SimulatedFailure(make_error_code(failure_type)); +} + +inline constexpr bool SimulatedFailure::is_enabled() +{ +#ifdef REALM_ENABLE_SIMULATED_FAILURE + return true; +#else + return false; +#endif +} + +inline SimulatedFailure::SimulatedFailure(std::error_code ec) + : std::system_error(ec) +{ +} + +inline SimulatedFailure::OneShotPrimeGuard::OneShotPrimeGuard(FailureType failure_type) + : m_type(failure_type) +{ + prime_one_shot(m_type); +} + +inline SimulatedFailure::OneShotPrimeGuard::~OneShotPrimeGuard() noexcept +{ + unprime(m_type); +} + +inline SimulatedFailure::RandomPrimeGuard::RandomPrimeGuard(FailureType failure_type, int n, int m, + uint_fast64_t seed) + : m_type(failure_type) +{ + prime_random(m_type, n, m, seed); +} + +inline SimulatedFailure::RandomPrimeGuard::~RandomPrimeGuard() noexcept +{ + unprime(m_type); +} + +} // namespace _impl +} // namespace realm + +#endif // REALM_IMPL_SIMULATED_FAILURE_HPP diff --git a/!main project/Pods/Realm/include/core/realm/impl/transact_log.hpp b/!main project/Pods/Realm/include/core/realm/impl/transact_log.hpp new file mode 100644 index 0000000..edace6c --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/impl/transact_log.hpp @@ -0,0 +1,2808 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_IMPL_TRANSACT_LOG_HPP +#define REALM_IMPL_TRANSACT_LOG_HPP + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace realm { +namespace _impl { + +/// Transaction log instruction encoding +/// NOTE: Any change to this enum is a file-format breaking change. +enum Instruction { + instr_InsertGroupLevelTable = 1, + instr_EraseGroupLevelTable = 2, // Remove columnless table from group + instr_RenameGroupLevelTable = 3, + instr_MoveGroupLevelTable = 4, // UNSUPPORTED/UNUSED. FIXME: remove in next breaking change + instr_SelectTable = 5, + instr_Set = 6, + instr_SetUnique = 7, + instr_SetDefault = 8, + instr_AddInteger = 9, // Add value to integer field + instr_NullifyLink = 10, // Set link to null due to target being erased + instr_InsertSubstring = 11, + instr_EraseFromString = 12, + instr_InsertEmptyRows = 13, + instr_EraseRows = 14, // Remove (multiple) rows + instr_SwapRows = 15, + instr_MoveRow = 16, + instr_MergeRows = 17, // Replace links pointing to row A with links to row B + instr_ClearTable = 18, // Remove all rows in selected table + instr_OptimizeTable = 19, + instr_SelectDescriptor = 20, // Select descriptor from currently selected root table + instr_InsertColumn = + 21, // Insert new non-nullable column into to selected descriptor (nullable is instr_InsertNullableColumn) + instr_InsertLinkColumn = 22, // do, but for a link-type column + instr_InsertNullableColumn = 23, // Insert nullable column + instr_EraseColumn = 24, // Remove column from selected descriptor + instr_EraseLinkColumn = 25, // Remove link-type column from selected descriptor + instr_RenameColumn = 26, // Rename column in selected descriptor + instr_MoveColumn = 27, // Move column in selected descriptor (UNSUPPORTED/UNUSED) FIXME: remove + instr_AddSearchIndex = 28, // Add a search index to a column + instr_RemoveSearchIndex = 29, // Remove a search index from a column + instr_SetLinkType = 30, // Strong/weak + instr_SelectLinkList = 31, + instr_LinkListSet = 32, // Assign to link list entry + instr_LinkListInsert = 33, // Insert entry into link list + instr_LinkListMove = 34, // Move an entry within a link list + instr_LinkListSwap = 35, // Swap two entries within a link list + instr_LinkListErase = 36, // Remove an entry from a link list + instr_LinkListNullify = 37, // Remove an entry from a link list due to linked row being erased + instr_LinkListClear = 38, // Ramove all entries from a link list + instr_LinkListSetAll = 39, // Assign to link list entry + instr_AddRowWithKey = 40, // Insert a row with a given key +}; + +class TransactLogStream { +public: + /// Ensure contiguous free space in the transaction log + /// buffer. This method must update `out_free_begin` + /// and `out_free_end` such that they refer to a chunk + /// of free space whose size is at least \a n. + /// + /// \param n The required amount of contiguous free space. Must be + /// small (probably not greater than 1024) + /// \param n Must be small (probably not greater than 1024) + virtual void transact_log_reserve(size_t size, char** out_free_begin, char** out_free_end) = 0; + + /// Copy the specified data into the transaction log buffer. This + /// function should be called only when the specified data does + /// not fit inside the chunk of free space currently referred to + /// by `out_free_begin` and `out_free_end`. + /// + /// This method must update `out_begin` and + /// `out_end` such that, upon return, they still + /// refer to a (possibly empty) chunk of free space. + virtual void transact_log_append(const char* data, size_t size, char** out_free_begin, char** out_free_end) = 0; +}; + +class TransactLogBufferStream : public TransactLogStream { +public: + void transact_log_reserve(size_t size, char** out_free_begin, char** out_free_end) override; + void transact_log_append(const char* data, size_t size, char** out_free_begin, char** out_free_end) override; + + const char* transact_log_data() const; + + util::Buffer m_buffer; +}; + + +// LCOV_EXCL_START (because the NullInstructionObserver is trivial) +class NullInstructionObserver { +public: + /// The following methods are also those that TransactLogParser expects + /// to find on the `InstructionHandler`. + + // No selection needed: + bool select_table(size_t, size_t, const size_t*) + { + return true; + } + bool select_descriptor(size_t, const size_t*) + { + return true; + } + bool select_link_list(size_t, size_t, size_t) + { + return true; + } + bool insert_group_level_table(size_t, size_t, StringData) + { + return true; + } + bool erase_group_level_table(size_t, size_t) + { + return true; + } + bool rename_group_level_table(size_t, StringData) + { + return true; + } + + // Must have table selected: + bool insert_empty_rows(size_t, size_t, size_t, bool) + { + return true; + } + bool add_row_with_key(size_t, size_t, size_t, int64_t) + { + return true; + } + bool erase_rows(size_t, size_t, size_t, bool) + { + return true; + } + bool swap_rows(size_t, size_t) + { + return true; + } + bool move_row(size_t, size_t) + { + return true; + } + bool merge_rows(size_t, size_t) + { + return true; + } + bool clear_table(size_t) + { + return true; + } + bool set_int(size_t, size_t, int_fast64_t, Instruction, size_t) + { + return true; + } + bool add_int(size_t, size_t, int_fast64_t) + { + return true; + } + bool set_bool(size_t, size_t, bool, Instruction) + { + return true; + } + bool set_float(size_t, size_t, float, Instruction) + { + return true; + } + bool set_double(size_t, size_t, double, Instruction) + { + return true; + } + bool set_string(size_t, size_t, StringData, Instruction, size_t) + { + return true; + } + bool set_binary(size_t, size_t, BinaryData, Instruction) + { + return true; + } + bool set_olddatetime(size_t, size_t, OldDateTime, Instruction) + { + return true; + } + bool set_timestamp(size_t, size_t, Timestamp, Instruction) + { + return true; + } + bool set_table(size_t, size_t, Instruction) + { + return true; + } + bool set_mixed(size_t, size_t, const Mixed&, Instruction) + { + return true; + } + bool set_link(size_t, size_t, size_t, size_t, Instruction) + { + return true; + } + bool set_null(size_t, size_t, Instruction, size_t) + { + return true; + } + bool nullify_link(size_t, size_t, size_t) + { + return true; + } + bool insert_substring(size_t, size_t, size_t, StringData) + { + return true; + } + bool erase_substring(size_t, size_t, size_t, size_t) + { + return true; + } + bool optimize_table() + { + return true; + } + + // Must have descriptor selected: + bool insert_link_column(size_t, DataType, StringData, size_t, size_t) + { + return true; + } + bool insert_column(size_t, DataType, StringData, bool) + { + return true; + } + bool erase_link_column(size_t, size_t, size_t) + { + return true; + } + bool erase_column(size_t) + { + return true; + } + bool rename_column(size_t, StringData) + { + return true; + } + bool add_search_index(size_t) + { + return true; + } + bool remove_search_index(size_t) + { + return true; + } + bool set_link_type(size_t, LinkType) + { + return true; + } + + // Must have linklist selected: + bool link_list_set(size_t, size_t, size_t) + { + return true; + } + bool link_list_insert(size_t, size_t, size_t) + { + return true; + } + bool link_list_move(size_t, size_t) + { + return true; + } + bool link_list_swap(size_t, size_t) + { + return true; + } + bool link_list_erase(size_t, size_t) + { + return true; + } + bool link_list_nullify(size_t, size_t) + { + return true; + } + bool link_list_clear(size_t) + { + return true; + } + + void parse_complete() + { + } +}; +// LCOV_EXCL_STOP (NullInstructionObserver) + + +/// See TransactLogConvenientEncoder for information about the meaning of the +/// arguments of each of the functions in this class. +class TransactLogEncoder { +public: + /// The following methods are also those that TransactLogParser expects + /// to find on the `InstructionHandler`. + + // No selection needed: + bool select_table(size_t group_level_ndx, size_t levels, const size_t* path); + bool select_descriptor(size_t levels, const size_t* path); + bool select_link_list(size_t col_ndx, size_t row_ndx, size_t link_target_group_level_ndx); + bool insert_group_level_table(size_t table_ndx, size_t num_tables, StringData name); + bool erase_group_level_table(size_t table_ndx, size_t num_tables); + bool rename_group_level_table(size_t table_ndx, StringData new_name); + + /// Must have table selected. + bool insert_empty_rows(size_t row_ndx, size_t num_rows_to_insert, size_t prior_num_rows, bool unordered); + bool add_row_with_key(size_t row_ndx, size_t prior_num_rows, size_t key_col_ndx, int64_t key); + bool erase_rows(size_t row_ndx, size_t num_rows_to_erase, size_t prior_num_rows, bool unordered); + bool swap_rows(size_t row_ndx_1, size_t row_ndx_2); + bool move_row(size_t from_ndx, size_t to_ndx); + bool merge_rows(size_t row_ndx, size_t new_row_ndx); + bool clear_table(size_t old_table_size); + + bool set_int(size_t col_ndx, size_t row_ndx, int_fast64_t, Instruction = instr_Set, size_t = 0); + bool add_int(size_t col_ndx, size_t row_ndx, int_fast64_t); + bool set_bool(size_t col_ndx, size_t row_ndx, bool, Instruction = instr_Set); + bool set_float(size_t col_ndx, size_t row_ndx, float, Instruction = instr_Set); + bool set_double(size_t col_ndx, size_t row_ndx, double, Instruction = instr_Set); + bool set_string(size_t col_ndx, size_t row_ndx, StringData, Instruction = instr_Set, size_t = 0); + bool set_binary(size_t col_ndx, size_t row_ndx, BinaryData, Instruction = instr_Set); + bool set_olddatetime(size_t col_ndx, size_t row_ndx, OldDateTime, Instruction = instr_Set); + bool set_timestamp(size_t col_ndx, size_t row_ndx, Timestamp, Instruction = instr_Set); + bool set_table(size_t col_ndx, size_t row_ndx, Instruction = instr_Set); + bool set_mixed(size_t col_ndx, size_t row_ndx, const Mixed&, Instruction = instr_Set); + bool set_link(size_t col_ndx, size_t row_ndx, size_t, size_t target_group_level_ndx, Instruction = instr_Set); + bool set_null(size_t col_ndx, size_t row_ndx, Instruction = instr_Set, size_t = 0); + bool nullify_link(size_t col_ndx, size_t row_ndx, size_t target_group_level_ndx); + bool insert_substring(size_t col_ndx, size_t row_ndx, size_t pos, StringData); + bool erase_substring(size_t col_ndx, size_t row_ndx, size_t pos, size_t size); + bool optimize_table(); + + // Must have descriptor selected: + bool insert_link_column(size_t col_ndx, DataType, StringData name, size_t link_target_table_ndx, + size_t backlink_col_ndx); + bool insert_column(size_t col_ndx, DataType, StringData name, bool nullable = false); + bool erase_link_column(size_t col_ndx, size_t link_target_table_ndx, size_t backlink_col_ndx); + bool erase_column(size_t col_ndx); + bool rename_column(size_t col_ndx, StringData new_name); + bool add_search_index(size_t col_ndx); + bool remove_search_index(size_t col_ndx); + bool set_link_type(size_t col_ndx, LinkType); + + // Must have linklist selected: + bool link_list_set(size_t link_ndx, size_t value, size_t prior_size); + bool link_list_set_all(const IntegerColumn& values); + bool link_list_insert(size_t link_ndx, size_t value, size_t prior_size); + bool link_list_move(size_t from_link_ndx, size_t to_link_ndx); + bool link_list_swap(size_t link1_ndx, size_t link2_ndx); + bool link_list_erase(size_t link_ndx, size_t prior_size); + bool link_list_nullify(size_t link_ndx, size_t prior_size); + bool link_list_clear(size_t old_list_size); + + /// End of methods expected by parser. + + + TransactLogEncoder(TransactLogStream& out_stream); + void set_buffer(char* new_free_begin, char* new_free_end); + char* write_position() const + { + return m_transact_log_free_begin; + } + +private: + using IntegerList = std::tuple; + using UnsignedList = std::tuple; + + // Make sure this is in agreement with the actual integer encoding + // scheme (see encode_int()). + static constexpr int max_enc_bytes_per_int = 10; + static constexpr int max_enc_bytes_per_double = sizeof(double); + static constexpr int max_enc_bytes_per_num = + max_enc_bytes_per_int < max_enc_bytes_per_double ? max_enc_bytes_per_double : max_enc_bytes_per_int; +// Space is reserved in chunks to avoid excessive over allocation. +#ifdef REALM_DEBUG + static constexpr int max_numbers_per_chunk = 2; // Increase the chance of chunking in debug mode +#else + static constexpr int max_numbers_per_chunk = 8; +#endif + + // This value is used in Set* instructions in place of the 'type' field in + // the stream to indicate that the value of the Set* instruction is NULL, + // which doesn't have a type. + static constexpr int set_null_sentinel() + { + return -1; + } + + TransactLogStream& m_stream; + + // These two delimit a contiguous region of free space in a + // transaction log buffer following the last written data. It may + // be empty. + char* m_transact_log_free_begin = nullptr; + char* m_transact_log_free_end = nullptr; + + char* reserve(size_t size); + /// \param ptr Must be in the range [m_transact_log_free_begin, m_transact_log_free_end] + void advance(char* ptr) noexcept; + + template + size_t max_size(T); + + size_t max_size_list() + { + return 0; + } + + template + size_t max_size_list(T val, Args... args) + { + return max_size(val) + max_size_list(args...); + } + + template + char* encode(char* ptr, T value); + + char* encode_list(char* ptr) + { + advance(ptr); + return ptr; + } + + template + char* encode_list(char* ptr, T value, Args... args) + { + return encode_list(encode(ptr, value), args...); + } + + template + void append_simple_instr(L... numbers); + + template + void append_mixed_instr(Instruction instr, const Mixed& value, L... numbers); + + template + static char* encode_int(char*, T value); + friend class TransactLogParser; +}; + +class TransactLogConvenientEncoder { +public: + virtual void insert_group_level_table(size_t table_ndx, size_t num_tables, StringData name); + virtual void erase_group_level_table(size_t table_ndx, size_t num_tables); + virtual void rename_group_level_table(size_t table_ndx, StringData new_name); + virtual void insert_column(const Descriptor&, size_t col_ndx, DataType type, StringData name, LinkTargetInfo& link, + bool nullable = false); + virtual void erase_column(const Descriptor&, size_t col_ndx); + virtual void rename_column(const Descriptor&, size_t col_ndx, StringData name); + + virtual void set_int(const Table*, size_t col_ndx, size_t ndx, int_fast64_t value, Instruction variant = instr_Set); + virtual void add_int(const Table*, size_t col_ndx, size_t ndx, int_fast64_t value); + virtual void set_bool(const Table*, size_t col_ndx, size_t ndx, bool value, Instruction variant = instr_Set); + virtual void set_float(const Table*, size_t col_ndx, size_t ndx, float value, Instruction variant = instr_Set); + virtual void set_double(const Table*, size_t col_ndx, size_t ndx, double value, Instruction variant = instr_Set); + virtual void set_string(const Table*, size_t col_ndx, size_t ndx, StringData value, Instruction variant = instr_Set); + virtual void set_binary(const Table*, size_t col_ndx, size_t ndx, BinaryData value, Instruction variant = instr_Set); + virtual void set_olddatetime(const Table*, size_t col_ndx, size_t ndx, OldDateTime value, + Instruction variant = instr_Set); + virtual void set_timestamp(const Table*, size_t col_ndx, size_t ndx, Timestamp value, Instruction variant = instr_Set); + virtual void set_table(const Table*, size_t col_ndx, size_t ndx, Instruction variant = instr_Set); + virtual void set_mixed(const Table*, size_t col_ndx, size_t ndx, const Mixed& value, Instruction variant = instr_Set); + virtual void set_link(const Table*, size_t col_ndx, size_t ndx, size_t value, Instruction variant = instr_Set); + virtual void set_null(const Table*, size_t col_ndx, size_t ndx, Instruction variant = instr_Set); + virtual void set_link_list(const LinkView&, const IntegerColumn& values); + virtual void insert_substring(const Table*, size_t col_ndx, size_t row_ndx, size_t pos, StringData); + virtual void erase_substring(const Table*, size_t col_ndx, size_t row_ndx, size_t pos, size_t size); + + /// \param prior_num_rows The number of rows in the table prior to the + /// modification. + virtual void insert_empty_rows(const Table*, size_t row_ndx, size_t num_rows_to_insert, size_t prior_num_rows); + virtual void add_row_with_key(const Table* t, size_t row_ndx, size_t prior_num_rows, size_t key_col_ndx, + int64_t key); + + /// \param prior_num_rows The number of rows in the table prior to the + /// modification. + virtual void erase_rows(const Table*, size_t row_ndx, size_t num_rows_to_erase, size_t prior_num_rows, + bool is_move_last_over); + + virtual void swap_rows(const Table*, size_t row_ndx_1, size_t row_ndx_2); + virtual void move_row(const Table*, size_t from_ndx, size_t to_ndx); + virtual void merge_rows(const Table*, size_t row_ndx, size_t new_row_ndx); + virtual void add_search_index(const Descriptor&, size_t col_ndx); + virtual void remove_search_index(const Descriptor&, size_t col_ndx); + virtual void set_link_type(const Table*, size_t col_ndx, LinkType); + virtual void clear_table(const Table*, size_t prior_num_rows); + virtual void optimize_table(const Table*); + + virtual void link_list_set(const LinkView&, size_t link_ndx, size_t value); + virtual void link_list_insert(const LinkView&, size_t link_ndx, size_t value); + virtual void link_list_move(const LinkView&, size_t from_link_ndx, size_t to_link_ndx); + virtual void link_list_swap(const LinkView&, size_t link_ndx_1, size_t link_ndx_2); + virtual void link_list_erase(const LinkView&, size_t link_ndx); + virtual void link_list_clear(const LinkView&); + + //@{ + + /// Implicit nullifications due to removal of target row. This is redundant + /// information from the point of view of replication, as the removal of the + /// target row will reproduce the implicit nullifications in the target + /// Realm anyway. The purpose of this instruction is to allow observers + /// (reactor pattern) to be explicitly notified about the implicit + /// nullifications. + + virtual void nullify_link(const Table*, size_t col_ndx, size_t ndx); + virtual void link_list_nullify(const LinkView&, size_t link_ndx); + + //@} + + void on_table_destroyed(const Table*) noexcept; + void on_spec_destroyed(const Spec*) noexcept; + void on_link_list_destroyed(const LinkView&) noexcept; + +protected: + TransactLogConvenientEncoder(TransactLogStream& encoder); + + void reset_selection_caches() noexcept; + void set_buffer(char* new_free_begin, char* new_free_end) + { + m_encoder.set_buffer(new_free_begin, new_free_end); + } + char* write_position() const + { + return m_encoder.write_position(); + } + +private: + TransactLogEncoder m_encoder; + // These are mutable because they are caches. + mutable util::Buffer m_subtab_path_buf; + mutable const Table* m_selected_table; + mutable const Spec* m_selected_spec; + // Has to be atomic to support concurrent reset when a linklist + // is unselected. This can happen on a different thread. In case + // of races, setting of a new value must win. + mutable std::atomic m_selected_link_list; + + void unselect_all() noexcept; + void select_table(const Table*); // unselects descriptor and link list + void select_desc(const Descriptor&); // unselects link list + void select_link_list(const LinkView&); // unselects descriptor + + void record_subtable_path(const Table&, size_t*& out_begin, size_t*& out_end); + void do_select_table(const Table*); + void do_select_desc(const Descriptor&); + void do_select_link_list(const LinkView&); + + friend class TransactReverser; +}; + + +class TransactLogParser { +public: + class BadTransactLog; // Exception + + TransactLogParser(); + ~TransactLogParser() noexcept; + + /// See `TransactLogEncoder` for a list of methods that the `InstructionHandler` must define. + /// parse() promises that the path passed by reference to + /// InstructionHandler::select_descriptor() will remain valid + /// during subsequent calls to all descriptor modifying functions. + template + void parse(InputStream&, InstructionHandler&); + + template + void parse(NoCopyInputStream&, InstructionHandler&); + +private: + util::Buffer m_input_buffer; + + // The input stream is assumed to consist of chunks of memory organised such that + // every instruction resides in a single chunk only. + NoCopyInputStream* m_input; + // pointer into transaction log, each instruction is parsed from m_input_begin and onwards. + // Each instruction are assumed to be contiguous in memory. + const char* m_input_begin; + // pointer to one past current instruction log chunk. If m_input_begin reaches m_input_end, + // a call to next_input_buffer will move m_input_begin and m_input_end to a new chunk of + // memory. Setting m_input_end to 0 disables this check, and is used if it is already known + // that all of the instructions are in memory. + const char* m_input_end; + util::StringBuffer m_string_buffer; + static const int m_max_levels = 1024; + util::Buffer m_path; + + REALM_NORETURN void parser_error() const; + + template + void parse_one(InstructionHandler&); + bool has_next() noexcept; + + template + T read_int(); + + void read_bytes(char* data, size_t size); + BinaryData read_buffer(util::StringBuffer&, size_t size); + + bool read_bool(); + float read_float(); + double read_double(); + + StringData read_string(util::StringBuffer&); + BinaryData read_binary(util::StringBuffer&); + Timestamp read_timestamp(); + void read_mixed(Mixed*); + + // Advance m_input_begin and m_input_end to reflect the next block of instructions + // Returns false if no more input was available + bool next_input_buffer(); + + // return true if input was available + bool read_char(char&); // throws + + bool is_valid_data_type(int type); + bool is_valid_link_type(int type); +}; + + +class TransactLogParser::BadTransactLog : public std::exception { +public: + const char* what() const noexcept override + { + return "Bad transaction log"; + } +}; + + +/// Implementation: + +inline void TransactLogBufferStream::transact_log_reserve(size_t n, char** inout_new_begin, char** out_new_end) +{ + char* data = m_buffer.data(); + REALM_ASSERT(*inout_new_begin >= data); + REALM_ASSERT(*inout_new_begin <= (data + m_buffer.size())); + size_t size = *inout_new_begin - data; + m_buffer.reserve_extra(size, n); + data = m_buffer.data(); // May have changed + *inout_new_begin = data + size; + *out_new_end = data + m_buffer.size(); +} + +inline void TransactLogBufferStream::transact_log_append(const char* data, size_t size, char** out_new_begin, + char** out_new_end) +{ + transact_log_reserve(size, out_new_begin, out_new_end); + *out_new_begin = realm::safe_copy_n(data, size, *out_new_begin); +} + +inline const char* TransactLogBufferStream::transact_log_data() const +{ + return m_buffer.data(); +} + +inline TransactLogEncoder::TransactLogEncoder(TransactLogStream& stream) + : m_stream(stream) +{ +} + +inline void TransactLogEncoder::set_buffer(char* free_begin, char* free_end) +{ + REALM_ASSERT(free_begin <= free_end); + m_transact_log_free_begin = free_begin; + m_transact_log_free_end = free_end; +} + +inline void TransactLogConvenientEncoder::reset_selection_caches() noexcept +{ + unselect_all(); +} + +inline char* TransactLogEncoder::reserve(size_t n) +{ + if (size_t(m_transact_log_free_end - m_transact_log_free_begin) < n) { + m_stream.transact_log_reserve(n, &m_transact_log_free_begin, &m_transact_log_free_end); + } + return m_transact_log_free_begin; +} + +inline void TransactLogEncoder::advance(char* ptr) noexcept +{ + REALM_ASSERT_DEBUG(m_transact_log_free_begin <= ptr); + REALM_ASSERT_DEBUG(ptr <= m_transact_log_free_end); + m_transact_log_free_begin = ptr; +} + + +// The integer encoding is platform independent. Also, it does not +// depend on the type of the specified integer. Integers of any type +// can be encoded as long as the specified buffer is large enough (see +// below). The decoding does not have to use the same type. Decoding +// will fail if, and only if the encoded value falls outside the range +// of the requested destination type. +// +// The encoding uses one or more bytes. It never uses more than 8 bits +// per byte. The last byte in the sequence is the first one that has +// its 8th bit set to zero. +// +// Consider a particular non-negative value V. Let W be the number of +// bits needed to encode V using the trivial binary encoding of +// integers. The total number of bytes produced is then +// ceil((W+1)/7). The first byte holds the 7 least significant bits of +// V. The last byte holds at most 6 bits of V including the most +// significant one. The value of the first bit of the last byte is +// always 2**((N-1)*7) where N is the total number of bytes. +// +// A negative value W is encoded by setting the sign bit to one and +// then encoding the positive result of -(W+1) as described above. The +// advantage of this representation is that it converts small negative +// values to small positive values which require a small number of +// bytes. This would not have been true for 2's complements +// representation, for example. The sign bit is always stored as the +// 7th bit of the last byte. +// +// value bits value + sign max bytes +// -------------------------------------------------- +// int8_t 7 8 2 +// uint8_t 8 9 2 +// int16_t 15 16 3 +// uint16_t 16 17 3 +// int32_t 31 32 5 +// uint32_t 32 33 5 +// int64_t 63 64 10 +// uint64_t 64 65 10 +// +template +char* TransactLogEncoder::encode_int(char* ptr, T value) +{ + static_assert(std::numeric_limits::is_integer, "Integer required"); + bool negative = util::is_negative(value); + if (negative) { + // The following conversion is guaranteed by C++11 to never + // overflow (contrast this with "-value" which indeed could + // overflow). See C99+TC3 section 6.2.6.2 paragraph 2. + REALM_DIAG_PUSH(); + REALM_DIAG_IGNORE_UNSIGNED_MINUS(); + value = -(value + 1); + REALM_DIAG_POP(); + } + // At this point 'value' is always a positive number. Also, small + // negative numbers have been converted to small positive numbers. + REALM_ASSERT(!util::is_negative(value)); + // One sign bit plus number of value bits + const int num_bits = 1 + std::numeric_limits::digits; + // Only the first 7 bits are available per byte. Had it not been + // for the fact that maximum guaranteed bit width of a char is 8, + // this value could have been increased to 15 (one less than the + // number of value bits in 'unsigned'). + const int bits_per_byte = 7; + const int max_bytes = (num_bits + (bits_per_byte - 1)) / bits_per_byte; + static_assert(max_bytes <= max_enc_bytes_per_int, "Bad max_enc_bytes_per_int"); + // An explicit constant maximum number of iterations is specified + // in the hope that it will help the optimizer (to do loop + // unrolling, for example). + typedef unsigned char uchar; + for (int i = 0; i < max_bytes; ++i) { + if (value >> (bits_per_byte - 1) == 0) + break; + *reinterpret_cast(ptr) = uchar((1U << bits_per_byte) | unsigned(value & ((1U << bits_per_byte) - 1))); + ++ptr; + value >>= bits_per_byte; + } + *reinterpret_cast(ptr) = uchar(negative ? (1U << (bits_per_byte - 1)) | unsigned(value) : value); + return ++ptr; +} + +template +char* TransactLogEncoder::encode(char* ptr, T value) +{ + auto value_2 = value + 0; // Perform integral promotion + return encode_int(ptr, value_2); +} + +template <> +inline char* TransactLogEncoder::encode(char* ptr, char value) +{ + // Write the char as-is without encoding. + *ptr++ = value; + return ptr; +} + +template <> +inline char* TransactLogEncoder::encode(char* ptr, Instruction inst) +{ + return encode(ptr, inst); +} + +template <> +inline char* TransactLogEncoder::encode(char* ptr, bool value) +{ + return encode(ptr, value); +} + +template <> +inline char* TransactLogEncoder::encode(char* ptr, float value) +{ + static_assert(std::numeric_limits::is_iec559 && + sizeof(float) * std::numeric_limits::digits == 32, + "Unsupported 'float' representation"); + const char* val_ptr = reinterpret_cast(&value); + return realm::safe_copy_n(val_ptr, sizeof value, ptr); +} + +template <> +inline char* TransactLogEncoder::encode(char* ptr, double value) +{ + static_assert(std::numeric_limits::is_iec559 && + sizeof(double) * std::numeric_limits::digits == 64, + "Unsupported 'double' representation"); + const char* val_ptr = reinterpret_cast(&value); + return realm::safe_copy_n(val_ptr, sizeof value, ptr); +} + +template <> +inline char* TransactLogEncoder::encode(char* ptr, DataType type) +{ + return encode(ptr, type); +} + +template <> +inline char* TransactLogEncoder::encode(char* ptr, StringData s) +{ + ptr = encode_int(ptr, s.size()); + return std::copy_n(s.data(), s.size(), ptr); +} + +template <> +inline char* TransactLogEncoder::encode(char* ptr, + TransactLogEncoder::IntegerList list) +{ + IntegerColumnIterator i = std::get<0>(list); + IntegerColumnIterator end = std::get<1>(list); + + while (end - i > max_numbers_per_chunk) { + for (int j = 0; j < max_numbers_per_chunk; ++j) + ptr = encode_int(ptr, *i++); + advance(ptr); + size_t max_required_bytes_2 = max_enc_bytes_per_num * max_numbers_per_chunk; + ptr = reserve(max_required_bytes_2); // Throws + } + + while (i != end) + ptr = encode_int(ptr, *i++); + + return ptr; +} + +template <> +inline char* TransactLogEncoder::encode(char* ptr, + TransactLogEncoder::UnsignedList list) +{ + const size_t* i = std::get<0>(list); + const size_t* end = std::get<1>(list); + + while (i != end) + ptr = encode_int(ptr, *i++); + + return ptr; +} + +template +size_t TransactLogEncoder::max_size(T) +{ + return max_enc_bytes_per_num; +} + +template <> +inline size_t TransactLogEncoder::max_size(char) +{ + return 1; +} + +template <> +inline size_t TransactLogEncoder::max_size(bool) +{ + return 1; +} + +template <> +inline size_t TransactLogEncoder::max_size(Instruction) +{ + return 1; +} + +template <> +inline size_t TransactLogEncoder::max_size(DataType) +{ + return 1; +} + +template <> +inline size_t TransactLogEncoder::max_size(StringData s) +{ + return max_enc_bytes_per_num + s.size(); +} + +template <> +inline size_t TransactLogEncoder::max_size(IntegerList) +{ + // We only allocate space for 'max_numbers_per_chunk' at a time + return max_enc_bytes_per_num * max_numbers_per_chunk; +} + +template <> +inline size_t TransactLogEncoder::max_size(UnsignedList list) +{ + const size_t* begin = std::get<0>(list); + const size_t* end = std::get<1>(list); + // list contains (end - begin) elements + return max_enc_bytes_per_num * (end - begin); +} + +template +void TransactLogEncoder::append_simple_instr(L... numbers) +{ + size_t max_required_bytes = max_size_list(numbers...); + char* ptr = reserve(max_required_bytes); // Throws + encode_list(ptr, numbers...); +} + +template +void TransactLogEncoder::append_mixed_instr(Instruction instr, const Mixed& value, L... numbers) +{ + DataType type = value.get_type(); + switch (type) { + case type_Int: + append_simple_instr(instr, numbers..., type, value.get_int()); // Throws + return; + case type_Bool: + append_simple_instr(instr, numbers..., type, value.get_bool()); // Throws + return; + case type_Float: + append_simple_instr(instr, numbers..., type, value.get_float()); // Throws + return; + case type_Double: + append_simple_instr(instr, numbers..., type, value.get_double()); // Throws + return; + case type_OldDateTime: { + auto value_2 = value.get_olddatetime().get_olddatetime(); + append_simple_instr(instr, numbers..., type, value_2); // Throws + return; + } + case type_String: { + append_simple_instr(instr, numbers..., type, value.get_string()); // Throws + return; + } + case type_Binary: { + BinaryData value_2 = value.get_binary(); + StringData value_3(value_2.data(), value_2.size()); + append_simple_instr(instr, numbers..., type, value_3); // Throws + return; + } + case type_Timestamp: { + Timestamp ts = value.get_timestamp(); + int64_t seconds = ts.get_seconds(); + int32_t nano_seconds = ts.get_nanoseconds(); + append_simple_instr(instr, numbers..., type, seconds, nano_seconds); // Throws + return; + } + case type_Table: + append_simple_instr(instr, numbers..., type); // Throws + return; + case type_Mixed: + // Mixed in mixed is not possible + REALM_TERMINATE("Mixed in Mixed not possible"); + case type_Link: + case type_LinkList: + // FIXME: Need to handle new link types here. + REALM_TERMINATE("Link types in Mixed not supported."); + } + REALM_TERMINATE("Invalid Mixed."); +} + +inline void TransactLogConvenientEncoder::unselect_all() noexcept +{ + m_selected_table = nullptr; + m_selected_spec = nullptr; + // no race with on_link_list_destroyed since both are setting to nullptr + m_selected_link_list = nullptr; +} + +inline void TransactLogConvenientEncoder::select_table(const Table* table) +{ + if (table != m_selected_table) + do_select_table(table); // Throws + m_selected_spec = nullptr; + // no race with on_link_list_destroyed since both are setting to nullptr + m_selected_link_list = nullptr; +} + +inline void TransactLogConvenientEncoder::select_desc(const Descriptor& desc) +{ + typedef _impl::DescriptorFriend df; + if (&df::get_spec(desc) != m_selected_spec) + do_select_desc(desc); // Throws + // no race with on_link_list_destroyed since both are setting to nullptr + m_selected_link_list = nullptr; +} + +inline void TransactLogConvenientEncoder::select_link_list(const LinkView& list) +{ + // A race between this and a call to on_link_list_destroyed() must + // end up with m_selected_link_list pointing to the list argument given + // here. We assume that the list given to on_link_list_destroyed() can + // *never* be the same as the list argument given here. We resolve the + // race by a) always updating m_selected_link_list in do_select_link_list() + // and b) only atomically and conditionally updating it in + // on_link_list_destroyed(). + if (&list != m_selected_link_list) { + do_select_link_list(list); // Throws + } + m_selected_spec = nullptr; +} + + +inline bool TransactLogEncoder::insert_group_level_table(size_t table_ndx, size_t prior_num_tables, StringData name) +{ + append_simple_instr(instr_InsertGroupLevelTable, table_ndx, prior_num_tables, name); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::insert_group_level_table(size_t table_ndx, size_t prior_num_tables, + StringData name) +{ + unselect_all(); + m_encoder.insert_group_level_table(table_ndx, prior_num_tables, name); // Throws +} + +inline bool TransactLogEncoder::erase_group_level_table(size_t table_ndx, size_t prior_num_tables) +{ + append_simple_instr(instr_EraseGroupLevelTable, table_ndx, prior_num_tables); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::erase_group_level_table(size_t table_ndx, size_t prior_num_tables) +{ + unselect_all(); + m_encoder.erase_group_level_table(table_ndx, prior_num_tables); // Throws +} + +inline bool TransactLogEncoder::rename_group_level_table(size_t table_ndx, StringData new_name) +{ + append_simple_instr(instr_RenameGroupLevelTable, table_ndx, new_name); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::rename_group_level_table(size_t table_ndx, StringData new_name) +{ + unselect_all(); + m_encoder.rename_group_level_table(table_ndx, new_name); // Throws +} + +inline bool TransactLogEncoder::insert_column(size_t col_ndx, DataType type, StringData name, bool nullable) +{ + Instruction instr = (nullable ? instr_InsertNullableColumn : instr_InsertColumn); + append_simple_instr(instr, col_ndx, type, name); // Throws + return true; +} + +inline bool TransactLogEncoder::insert_link_column(size_t col_ndx, DataType type, StringData name, + size_t link_target_table_ndx, size_t backlink_col_ndx) +{ + REALM_ASSERT(_impl::TableFriend::is_link_type(ColumnType(type))); + append_simple_instr(instr_InsertLinkColumn, col_ndx, type, link_target_table_ndx, backlink_col_ndx, + name); // Throws + return true; +} + + +inline void TransactLogConvenientEncoder::insert_column(const Descriptor& desc, size_t col_ndx, DataType type, + StringData name, LinkTargetInfo& link, bool nullable) +{ + select_desc(desc); // Throws + if (link.is_valid()) { + typedef _impl::TableFriend tf; + typedef _impl::DescriptorFriend df; + size_t target_table_ndx = link.m_target_table->get_index_in_group(); + const Table& origin_table = df::get_root_table(desc); + REALM_ASSERT(origin_table.is_group_level()); + const Spec& target_spec = tf::get_spec(*(link.m_target_table)); + size_t origin_table_ndx = origin_table.get_index_in_group(); + size_t backlink_col_ndx = target_spec.find_backlink_column(origin_table_ndx, col_ndx); + REALM_ASSERT_3(backlink_col_ndx, ==, link.m_backlink_col_ndx); + m_encoder.insert_link_column(col_ndx, type, name, target_table_ndx, backlink_col_ndx); // Throws + } + else { + m_encoder.insert_column(col_ndx, type, name, nullable); // Throws + } +} + +inline bool TransactLogEncoder::erase_column(size_t col_ndx) +{ + append_simple_instr(instr_EraseColumn, col_ndx); // Throws + return true; +} + +inline bool TransactLogEncoder::erase_link_column(size_t col_ndx, size_t link_target_table_ndx, + size_t backlink_col_ndx) +{ + append_simple_instr(instr_EraseLinkColumn, col_ndx, link_target_table_ndx, backlink_col_ndx); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::erase_column(const Descriptor& desc, size_t col_ndx) +{ + select_desc(desc); // Throws + + DataType type = desc.get_column_type(col_ndx); + typedef _impl::TableFriend tf; + if (!tf::is_link_type(ColumnType(type))) { + m_encoder.erase_column(col_ndx); // Throws + } + else { // it's a link column: + REALM_ASSERT(desc.is_root()); + typedef _impl::DescriptorFriend df; + const Table& origin_table = df::get_root_table(desc); + REALM_ASSERT(origin_table.is_group_level()); + const Table& target_table = *tf::get_link_target_table_accessor(origin_table, col_ndx); + size_t target_table_ndx = target_table.get_index_in_group(); + const Spec& target_spec = tf::get_spec(target_table); + size_t origin_table_ndx = origin_table.get_index_in_group(); + size_t backlink_col_ndx = target_spec.find_backlink_column(origin_table_ndx, col_ndx); + m_encoder.erase_link_column(col_ndx, target_table_ndx, backlink_col_ndx); // Throws + } +} + +inline bool TransactLogEncoder::rename_column(size_t col_ndx, StringData new_name) +{ + append_simple_instr(instr_RenameColumn, col_ndx, new_name); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::rename_column(const Descriptor& desc, size_t col_ndx, StringData name) +{ + select_desc(desc); // Throws + m_encoder.rename_column(col_ndx, name); // Throws +} + + +inline bool TransactLogEncoder::set_int(size_t col_ndx, size_t ndx, int_fast64_t value, Instruction variant, + size_t prior_num_rows) +{ + REALM_ASSERT_EX(variant == instr_Set || variant == instr_SetDefault || variant == instr_SetUnique, variant); + if (REALM_UNLIKELY(variant == instr_SetUnique)) + append_simple_instr(variant, type_Int, col_ndx, ndx, prior_num_rows, value); // Throws + else + append_simple_instr(variant, type_Int, col_ndx, ndx, value); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::set_int(const Table* t, size_t col_ndx, size_t ndx, int_fast64_t value, + Instruction variant) +{ + select_table(t); // Throws + size_t prior_num_rows = (variant == instr_SetUnique ? t->size() : 0); + m_encoder.set_int(col_ndx, ndx, value, variant, prior_num_rows); // Throws +} + + +inline bool TransactLogEncoder::add_int(size_t col_ndx, size_t ndx, int_fast64_t value) +{ + append_simple_instr(instr_AddInteger, col_ndx, ndx, value); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::add_int(const Table* t, size_t col_ndx, size_t ndx, int_fast64_t value) +{ + select_table(t); // Throws + m_encoder.add_int(col_ndx, ndx, value); +} + +inline bool TransactLogEncoder::set_bool(size_t col_ndx, size_t ndx, bool value, Instruction variant) +{ + REALM_ASSERT_EX(variant == instr_Set || variant == instr_SetDefault, variant); + append_simple_instr(variant, type_Bool, col_ndx, ndx, value); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::set_bool(const Table* t, size_t col_ndx, size_t ndx, bool value, + Instruction variant) +{ + select_table(t); // Throws + m_encoder.set_bool(col_ndx, ndx, value, variant); // Throws +} + +inline bool TransactLogEncoder::set_float(size_t col_ndx, size_t ndx, float value, Instruction variant) +{ + REALM_ASSERT_EX(variant == instr_Set || variant == instr_SetDefault, variant); + append_simple_instr(variant, type_Float, col_ndx, ndx, value); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::set_float(const Table* t, size_t col_ndx, size_t ndx, float value, + Instruction variant) +{ + select_table(t); // Throws + m_encoder.set_float(col_ndx, ndx, value, variant); // Throws +} + +inline bool TransactLogEncoder::set_double(size_t col_ndx, size_t ndx, double value, Instruction variant) +{ + REALM_ASSERT_EX(variant == instr_Set || variant == instr_SetDefault, variant); + append_simple_instr(instr_Set, type_Double, col_ndx, ndx, value); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::set_double(const Table* t, size_t col_ndx, size_t ndx, double value, + Instruction variant) +{ + select_table(t); // Throws + m_encoder.set_double(col_ndx, ndx, value, variant); // Throws +} + +inline bool TransactLogEncoder::set_string(size_t col_ndx, size_t ndx, StringData value, Instruction variant, + size_t prior_num_rows) +{ + REALM_ASSERT_EX(variant == instr_Set || variant == instr_SetDefault || variant == instr_SetUnique, variant); + if (value.is_null()) { + set_null(col_ndx, ndx, variant, prior_num_rows); // Throws + } + else { + if (REALM_UNLIKELY(variant == instr_SetUnique)) + append_simple_instr(variant, type_String, col_ndx, ndx, prior_num_rows, value); // Throws + else + append_simple_instr(variant, type_String, col_ndx, ndx, value); // Throws + } + return true; +} + +inline void TransactLogConvenientEncoder::set_string(const Table* t, size_t col_ndx, size_t ndx, StringData value, + Instruction variant) +{ + select_table(t); // Throws + size_t prior_num_rows = (variant == instr_SetUnique ? t->size() : 0); + m_encoder.set_string(col_ndx, ndx, value, variant, prior_num_rows); // Throws +} + +inline bool TransactLogEncoder::set_binary(size_t col_ndx, size_t row_ndx, BinaryData value, Instruction variant) +{ + REALM_ASSERT_EX(variant == instr_Set || variant == instr_SetDefault, variant); + if (value.is_null()) { + set_null(col_ndx, row_ndx, variant); // Throws + } + else { + StringData value_2(value.data(), value.size()); + append_simple_instr(variant, type_Binary, col_ndx, row_ndx, value_2); // Throws + } + return true; +} + +inline void TransactLogConvenientEncoder::set_binary(const Table* t, size_t col_ndx, size_t ndx, BinaryData value, + Instruction variant) +{ + select_table(t); // Throws + m_encoder.set_binary(col_ndx, ndx, value, variant); // Throws +} + +inline bool TransactLogEncoder::set_olddatetime(size_t col_ndx, size_t ndx, OldDateTime value, Instruction variant) +{ + REALM_ASSERT_EX(variant == instr_Set || variant == instr_SetDefault, variant); + append_simple_instr(variant, type_OldDateTime, col_ndx, ndx, value.get_olddatetime()); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::set_olddatetime(const Table* t, size_t col_ndx, size_t ndx, + OldDateTime value, Instruction variant) +{ + select_table(t); // Throws + m_encoder.set_olddatetime(col_ndx, ndx, value, variant); // Throws +} + +inline bool TransactLogEncoder::set_timestamp(size_t col_ndx, size_t ndx, Timestamp value, Instruction variant) +{ + REALM_ASSERT_EX(variant == instr_Set || variant == instr_SetDefault, variant); + append_simple_instr(variant, type_Timestamp, col_ndx, ndx, value.get_seconds(), + value.get_nanoseconds()); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::set_timestamp(const Table* t, size_t col_ndx, size_t ndx, Timestamp value, + Instruction variant) +{ + select_table(t); // Throws + m_encoder.set_timestamp(col_ndx, ndx, value, variant); // Throws +} + +inline bool TransactLogEncoder::set_table(size_t col_ndx, size_t ndx, Instruction variant) +{ + REALM_ASSERT_EX(variant == instr_Set || variant == instr_SetDefault, variant); + append_simple_instr(variant, type_Table, col_ndx, ndx); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::set_table(const Table* t, size_t col_ndx, size_t ndx, Instruction variant) +{ + select_table(t); // Throws + m_encoder.set_table(col_ndx, ndx, variant); // Throws +} + +inline bool TransactLogEncoder::set_mixed(size_t col_ndx, size_t ndx, const Mixed& value, Instruction variant) +{ + REALM_ASSERT_EX(variant == instr_Set || variant == instr_SetDefault, variant); + append_mixed_instr(variant, value, type_Mixed, col_ndx, ndx); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::set_mixed(const Table* t, size_t col_ndx, size_t ndx, const Mixed& value, + Instruction variant) +{ + select_table(t); // Throws + m_encoder.set_mixed(col_ndx, ndx, value, variant); // Throws +} + +inline bool TransactLogEncoder::set_link(size_t col_ndx, size_t ndx, size_t value, size_t target_group_level_ndx, + Instruction variant) +{ + REALM_ASSERT_EX(variant == instr_Set || variant == instr_SetDefault, variant); + // Map `realm::npos` to zero, and `n` to `n+1`, where `n` is a target row + // index. + size_t value_2 = size_t(1) + value; + append_simple_instr(variant, type_Link, col_ndx, ndx, value_2, target_group_level_ndx); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::set_link(const Table* t, size_t col_ndx, size_t ndx, size_t value, + Instruction variant) +{ + select_table(t); // Throws + size_t target_group_level_ndx = t->get_descriptor()->get_column_link_target(col_ndx); + m_encoder.set_link(col_ndx, ndx, value, target_group_level_ndx, variant); // Throws +} + +inline bool TransactLogEncoder::set_null(size_t col_ndx, size_t ndx, Instruction variant, size_t prior_num_rows) +{ + REALM_ASSERT_EX(variant == instr_Set || variant == instr_SetDefault || variant == instr_SetUnique, variant); + if (REALM_UNLIKELY(variant == instr_SetUnique)) { + append_simple_instr(variant, set_null_sentinel(), col_ndx, ndx, prior_num_rows); // Throws + } + else { + append_simple_instr(variant, set_null_sentinel(), col_ndx, ndx); // Throws + } + return true; +} + +inline void TransactLogConvenientEncoder::set_null(const Table* t, size_t col_ndx, size_t row_ndx, + Instruction variant) +{ + select_table(t); // Throws + size_t prior_num_rows = (variant == instr_SetUnique ? t->size() : 0); + m_encoder.set_null(col_ndx, row_ndx, variant, prior_num_rows); // Throws +} + +inline bool TransactLogEncoder::nullify_link(size_t col_ndx, size_t ndx, size_t target_group_level_ndx) +{ + append_simple_instr(instr_NullifyLink, col_ndx, ndx, target_group_level_ndx); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::nullify_link(const Table* t, size_t col_ndx, size_t ndx) +{ + select_table(t); // Throws + size_t target_group_level_ndx = t->get_descriptor()->get_column_link_target(col_ndx); + m_encoder.nullify_link(col_ndx, ndx, target_group_level_ndx); // Throws +} + +inline bool TransactLogEncoder::insert_substring(size_t col_ndx, size_t row_ndx, size_t pos, StringData value) +{ + append_simple_instr(instr_InsertSubstring, col_ndx, row_ndx, pos, value); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::insert_substring(const Table* t, size_t col_ndx, size_t row_ndx, size_t pos, + StringData value) +{ + if (value.size() > 0) { + select_table(t); // Throws + m_encoder.insert_substring(col_ndx, row_ndx, pos, value); // Throws + } +} + +inline bool TransactLogEncoder::erase_substring(size_t col_ndx, size_t row_ndx, size_t pos, size_t size) +{ + append_simple_instr(instr_EraseFromString, col_ndx, row_ndx, pos, size); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::erase_substring(const Table* t, size_t col_ndx, size_t row_ndx, size_t pos, + size_t size) +{ + if (size > 0) { + select_table(t); // Throws + m_encoder.erase_substring(col_ndx, row_ndx, pos, size); // Throws + } +} + +inline bool TransactLogEncoder::insert_empty_rows(size_t row_ndx, size_t num_rows_to_insert, size_t prior_num_rows, + bool unordered) +{ + append_simple_instr(instr_InsertEmptyRows, row_ndx, num_rows_to_insert, prior_num_rows, unordered); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::insert_empty_rows(const Table* t, size_t row_ndx, size_t num_rows_to_insert, + size_t prior_num_rows) +{ + select_table(t); // Throws + bool unordered = false; + m_encoder.insert_empty_rows(row_ndx, num_rows_to_insert, prior_num_rows, unordered); // Throws +} + +inline bool TransactLogEncoder::add_row_with_key(size_t row_ndx, size_t prior_num_rows, size_t key_col_ndx, + int64_t key) +{ + append_simple_instr(instr_AddRowWithKey, row_ndx, prior_num_rows, key_col_ndx, key); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::add_row_with_key(const Table* t, size_t row_ndx, size_t prior_num_rows, + size_t key_col_ndx, int64_t key) +{ + select_table(t); // Throws + m_encoder.add_row_with_key(row_ndx, prior_num_rows, key_col_ndx, key); // Throws +} + +inline bool TransactLogEncoder::erase_rows(size_t row_ndx, size_t num_rows_to_erase, size_t prior_num_rows, + bool unordered) +{ + append_simple_instr(instr_EraseRows, row_ndx, num_rows_to_erase, prior_num_rows, unordered); // Throws + return true; +} + + +inline void TransactLogConvenientEncoder::erase_rows(const Table* t, size_t row_ndx, size_t num_rows_to_erase, + size_t prior_num_rows, bool is_move_last_over) +{ + select_table(t); // Throws + bool unordered = is_move_last_over; + m_encoder.erase_rows(row_ndx, num_rows_to_erase, prior_num_rows, unordered); // Throws +} + +inline bool TransactLogEncoder::swap_rows(size_t row_ndx_1, size_t row_ndx_2) +{ + append_simple_instr(instr_SwapRows, row_ndx_1, row_ndx_2); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::swap_rows(const Table* t, size_t row_ndx_1, size_t row_ndx_2) +{ + REALM_ASSERT(row_ndx_1 < row_ndx_2); + select_table(t); // Throws + m_encoder.swap_rows(row_ndx_1, row_ndx_2); +} + +inline bool TransactLogEncoder::move_row(size_t from_ndx, size_t to_ndx) +{ + append_simple_instr(instr_MoveRow, from_ndx, to_ndx); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::move_row(const Table* t, size_t from_ndx, size_t to_ndx) +{ + REALM_ASSERT(from_ndx != to_ndx); + select_table(t); // Throws + m_encoder.move_row(from_ndx, to_ndx); +} + +inline bool TransactLogEncoder::merge_rows(size_t row_ndx, size_t new_row_ndx) +{ + append_simple_instr(instr_MergeRows, row_ndx, new_row_ndx); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::merge_rows(const Table* t, size_t row_ndx, size_t new_row_ndx) +{ + select_table(t); // Throws + m_encoder.merge_rows(row_ndx, new_row_ndx); +} + +inline bool TransactLogEncoder::add_search_index(size_t col_ndx) +{ + append_simple_instr(instr_AddSearchIndex, col_ndx); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::add_search_index(const Descriptor& desc, size_t col_ndx) +{ + select_desc(desc); // Throws + m_encoder.add_search_index(col_ndx); // Throws +} + + +inline bool TransactLogEncoder::remove_search_index(size_t col_ndx) +{ + append_simple_instr(instr_RemoveSearchIndex, col_ndx); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::remove_search_index(const Descriptor& desc, size_t col_ndx) +{ + select_desc(desc); // Throws + m_encoder.remove_search_index(col_ndx); // Throws +} + +inline bool TransactLogEncoder::set_link_type(size_t col_ndx, LinkType link_type) +{ + append_simple_instr(instr_SetLinkType, col_ndx, int(link_type)); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::set_link_type(const Table* t, size_t col_ndx, LinkType link_type) +{ + select_table(t); // Throws + m_encoder.set_link_type(col_ndx, link_type); // Throws +} + + +inline bool TransactLogEncoder::clear_table(size_t old_size) +{ + append_simple_instr(instr_ClearTable, old_size); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::clear_table(const Table* t, size_t prior_num_rows) +{ + select_table(t); // Throws + m_encoder.clear_table(prior_num_rows); // Throws +} + +inline bool TransactLogEncoder::optimize_table() +{ + append_simple_instr(instr_OptimizeTable); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::optimize_table(const Table* t) +{ + select_table(t); // Throws + m_encoder.optimize_table(); // Throws +} + +inline bool TransactLogEncoder::link_list_set(size_t link_ndx, size_t value, size_t prior_size) +{ + append_simple_instr(instr_LinkListSet, link_ndx, value, prior_size); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::link_list_set(const LinkView& list, size_t link_ndx, size_t value) +{ + select_link_list(list); // Throws + m_encoder.link_list_set(link_ndx, value, list.size()); // Throws +} + +inline bool TransactLogEncoder::link_list_nullify(size_t link_ndx, size_t prior_size) +{ + append_simple_instr(instr_LinkListNullify, link_ndx, prior_size); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::link_list_nullify(const LinkView& list, size_t link_ndx) +{ + select_link_list(list); // Throws + size_t prior_size = list.size(); // Instruction is emitted before the fact. + m_encoder.link_list_nullify(link_ndx, prior_size); // Throws +} + +inline bool TransactLogEncoder::link_list_set_all(const IntegerColumn& values) +{ + size_t num_values = values.size(); + append_simple_instr( + instr_LinkListSetAll, num_values, + std::make_tuple(IntegerColumnIterator(&values, 0), IntegerColumnIterator(&values, num_values))); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::set_link_list(const LinkView& list, const IntegerColumn& values) +{ + select_link_list(list); // Throws + m_encoder.link_list_set_all(values); // Throws +} + +inline bool TransactLogEncoder::link_list_insert(size_t link_ndx, size_t value, size_t prior_size) +{ + append_simple_instr(instr_LinkListInsert, link_ndx, value, prior_size); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::link_list_insert(const LinkView& list, size_t link_ndx, size_t value) +{ + select_link_list(list); // Throws + size_t prior_size = list.size() - 1; // The instruction is emitted after the fact. + m_encoder.link_list_insert(link_ndx, value, prior_size); // Throws +} + +inline bool TransactLogEncoder::link_list_move(size_t from_link_ndx, size_t to_link_ndx) +{ + REALM_ASSERT(from_link_ndx != to_link_ndx); + append_simple_instr(instr_LinkListMove, from_link_ndx, to_link_ndx); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::link_list_move(const LinkView& list, size_t from_link_ndx, + size_t to_link_ndx) +{ + select_link_list(list); // Throws + m_encoder.link_list_move(from_link_ndx, to_link_ndx); // Throws +} + +inline bool TransactLogEncoder::link_list_swap(size_t link1_ndx, size_t link2_ndx) +{ + append_simple_instr(instr_LinkListSwap, link1_ndx, link2_ndx); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::link_list_swap(const LinkView& list, size_t link1_ndx, size_t link2_ndx) +{ + select_link_list(list); // Throws + m_encoder.link_list_swap(link1_ndx, link2_ndx); // Throws +} + +inline bool TransactLogEncoder::link_list_erase(size_t link_ndx, size_t prior_size) +{ + append_simple_instr(instr_LinkListErase, link_ndx, prior_size); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::link_list_erase(const LinkView& list, size_t link_ndx) +{ + select_link_list(list); // Throws + size_t prior_size = list.size(); // The instruction is emitted before the fact. + m_encoder.link_list_erase(link_ndx, prior_size); // Throws +} + +inline bool TransactLogEncoder::link_list_clear(size_t old_list_size) +{ + append_simple_instr(instr_LinkListClear, old_list_size); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::on_table_destroyed(const Table* t) noexcept +{ + if (m_selected_table == t) + m_selected_table = nullptr; +} + +inline void TransactLogConvenientEncoder::on_spec_destroyed(const Spec* s) noexcept +{ + if (m_selected_spec == s) + m_selected_spec = nullptr; +} + + +inline void TransactLogConvenientEncoder::on_link_list_destroyed(const LinkView& list) noexcept +{ + const LinkView* lw_ptr = &list; + // atomically clear m_selected_link_list iff it already points to 'list': + // (lw_ptr will be modified if the swap fails, but we ignore that) + m_selected_link_list.compare_exchange_strong(lw_ptr, nullptr, std::memory_order_relaxed, + std::memory_order_relaxed); +} + + +inline TransactLogParser::TransactLogParser() + : m_input_buffer(1024) // Throws +{ +} + + +inline TransactLogParser::~TransactLogParser() noexcept +{ +} + + +template +void TransactLogParser::parse(NoCopyInputStream& in, InstructionHandler& handler) +{ + m_input = ∈ + m_input_begin = m_input_end = nullptr; + + while (has_next()) + parse_one(handler); // Throws +} + +template +void TransactLogParser::parse(InputStream& in, InstructionHandler& handler) +{ + NoCopyInputStreamAdaptor in_2(in, m_input_buffer.data(), m_input_buffer.size()); + parse(in_2, handler); // Throws +} + +inline bool TransactLogParser::has_next() noexcept +{ + return m_input_begin != m_input_end || next_input_buffer(); +} + +template +void TransactLogParser::parse_one(InstructionHandler& handler) +{ + char instr_ch = 0; + if (!read_char(instr_ch)) + parser_error(); // Throws + // std::cerr << "parsing " << util::promote(instr) << " @ " << std::hex << long(m_input_begin) << std::dec << + // "\n"; + Instruction instr = Instruction(instr_ch); + switch (instr) { + case instr_SetDefault: + case instr_SetUnique: + case instr_Set: { + int type = read_int(); // Throws + size_t col_ndx = read_int(); // Throws + size_t row_ndx = read_int(); // Throws + size_t prior_num_rows = 0; + if (REALM_UNLIKELY(instr == instr_SetUnique)) + prior_num_rows = read_int(); // Throws + + if (type == TransactLogEncoder::set_null_sentinel()) { + // Special case for set_null + if (!handler.set_null(col_ndx, row_ndx, instr, prior_num_rows)) // Throws + parser_error(); + return; + } + + switch (DataType(type)) { + case type_Int: { + int_fast64_t value = read_int(); // Throws + if (!handler.set_int(col_ndx, row_ndx, value, instr, prior_num_rows)) // Throws + parser_error(); + return; + } + case type_Bool: { + bool value = read_bool(); // Throws + if (!handler.set_bool(col_ndx, row_ndx, value, instr)) // Throws + parser_error(); + return; + } + case type_Float: { + float value = read_float(); // Throws + if (!handler.set_float(col_ndx, row_ndx, value, instr)) // Throws + parser_error(); + return; + } + case type_Double: { + double value = read_double(); // Throws + if (!handler.set_double(col_ndx, row_ndx, value, instr)) // Throws + parser_error(); + return; + } + case type_String: { + StringData value = read_string(m_string_buffer); // Throws + if (!handler.set_string(col_ndx, row_ndx, value, instr, prior_num_rows)) // Throws + parser_error(); + return; + } + case type_Binary: { + BinaryData value = read_binary(m_string_buffer); // Throws + if (!handler.set_binary(col_ndx, row_ndx, value, instr)) // Throws + parser_error(); + return; + } + case type_OldDateTime: { + int_fast64_t value = read_int(); // Throws + if (!handler.set_olddatetime(col_ndx, row_ndx, value, instr)) // Throws + parser_error(); + return; + } + case type_Timestamp: { + int64_t seconds = read_int(); // Throws + int32_t nanoseconds = read_int(); // Throws + Timestamp value = Timestamp(seconds, nanoseconds); + if (!handler.set_timestamp(col_ndx, row_ndx, value, instr)) // Throws + parser_error(); + return; + } + case type_Table: { + if (!handler.set_table(col_ndx, row_ndx, instr)) // Throws + parser_error(); + return; + } + case type_Mixed: { + Mixed value; + read_mixed(&value); // Throws + if (!handler.set_mixed(col_ndx, row_ndx, value, instr)) // Throws + parser_error(); + return; + } + case type_Link: { + size_t value = read_int(); // Throws + // Map zero to realm::npos, and `n+1` to `n`, where `n` is a target row index. + size_t target_row_ndx = size_t(value - 1); + size_t target_group_level_ndx = read_int(); // Throws + if (!handler.set_link(col_ndx, row_ndx, target_row_ndx, target_group_level_ndx, instr)) // Throws + parser_error(); + return; + } + case type_LinkList: { + // Unsupported column type for Set. + parser_error(); + return; + } + } + parser_error(); + return; + } + case instr_AddInteger: { + size_t col_ndx = read_int(); // Throws + size_t row_ndx = read_int(); // Throws + int_fast64_t value = read_int(); // Throws + if (!handler.add_int(col_ndx, row_ndx, value)) // Throws + parser_error(); + return; + } + case instr_NullifyLink: { + size_t col_ndx = read_int(); // Throws + size_t row_ndx = read_int(); // Throws + size_t target_group_level_ndx = read_int(); // Throws + if (!handler.nullify_link(col_ndx, row_ndx, target_group_level_ndx)) // Throws + parser_error(); + return; + } + case instr_InsertSubstring: { + size_t col_ndx = read_int(); // Throws + size_t row_ndx = read_int(); // Throws + size_t pos = read_int(); // Throws + StringData value = read_string(m_string_buffer); // Throws + if (!handler.insert_substring(col_ndx, row_ndx, pos, value)) // Throws + parser_error(); + return; + } + case instr_EraseFromString: { + size_t col_ndx = read_int(); // Throws + size_t row_ndx = read_int(); // Throws + size_t pos = read_int(); // Throws + size_t size = read_int(); // Throws + if (!handler.erase_substring(col_ndx, row_ndx, pos, size)) // Throws + parser_error(); + return; + } + case instr_InsertEmptyRows: { + size_t row_ndx = read_int(); // Throws + size_t num_rows_to_insert = read_int(); // Throws + size_t prior_num_rows = read_int(); // Throws + bool unordered = read_bool(); // Throws + if (!handler.insert_empty_rows(row_ndx, num_rows_to_insert, prior_num_rows, unordered)) // Throws + parser_error(); + return; + } + case instr_AddRowWithKey: { + size_t row_ndx = read_int(); // Throws + size_t prior_num_rows = read_int(); // Throws + size_t key_col_ndx = read_int(); // Throws + int64_t key = read_int(); // Throws + if (!handler.add_row_with_key(row_ndx, prior_num_rows, key_col_ndx, key)) // Throws + parser_error(); + return; + } + case instr_EraseRows: { + size_t row_ndx = read_int(); // Throws + size_t num_rows_to_erase = read_int(); // Throws + size_t prior_num_rows = read_int(); // Throws + bool unordered = read_bool(); // Throws + if (!handler.erase_rows(row_ndx, num_rows_to_erase, prior_num_rows, unordered)) // Throws + parser_error(); + return; + } + case instr_SwapRows: { + size_t row_ndx_1 = read_int(); // Throws + size_t row_ndx_2 = read_int(); // Throws + if (!handler.swap_rows(row_ndx_1, row_ndx_2)) // Throws + parser_error(); + return; + } + case instr_MoveRow: { + size_t from_ndx = read_int(); // Throws + size_t to_ndx = read_int(); // Throws + if (!handler.move_row(from_ndx, to_ndx)) // Throws + parser_error(); + return; + } + case instr_MergeRows: { + size_t row_ndx = read_int(); // Throws + size_t new_row_ndx = read_int(); // Throws + if (!handler.merge_rows(row_ndx, new_row_ndx)) // Throws + parser_error(); + return; + } + case instr_SelectTable: { + int levels = read_int(); // Throws + if (levels < 0 || levels > m_max_levels) + parser_error(); + m_path.reserve(0, 2 * levels); // Throws + size_t* path = m_path.data(); + size_t group_level_ndx = read_int(); // Throws + for (int i = 0; i != levels; ++i) { + size_t col_ndx = read_int(); // Throws + size_t row_ndx = read_int(); // Throws + path[2 * i + 0] = col_ndx; + path[2 * i + 1] = row_ndx; + } + if (!handler.select_table(group_level_ndx, levels, path)) // Throws + parser_error(); + return; + } + case instr_ClearTable: { + size_t old_size = read_int(); // Throws + if (!handler.clear_table(old_size)) // Throws + parser_error(); + return; + } + case instr_LinkListSet: { + size_t link_ndx = read_int(); // Throws + size_t value = read_int(); // Throws + size_t prior_size = read_int(); // Throws + if (!handler.link_list_set(link_ndx, value, prior_size)) // Throws + parser_error(); + return; + } + case instr_LinkListSetAll: { + // todo, log that it's a SetAll we're doing + size_t size = read_int(); // Throws + for (size_t i = 0; i < size; i++) { + size_t link = read_int(); // Throws + if (!handler.link_list_set(i, link, size)) // Throws + parser_error(); + } + return; + } + case instr_LinkListInsert: { + size_t link_ndx = read_int(); // Throws + size_t value = read_int(); // Throws + size_t prior_size = read_int(); // Throws + if (!handler.link_list_insert(link_ndx, value, prior_size)) // Throws + parser_error(); + return; + } + case instr_LinkListMove: { + size_t from_link_ndx = read_int(); // Throws + size_t to_link_ndx = read_int(); // Throws + if (!handler.link_list_move(from_link_ndx, to_link_ndx)) // Throws + parser_error(); + return; + } + case instr_LinkListSwap: { + size_t link1_ndx = read_int(); // Throws + size_t link2_ndx = read_int(); // Throws + if (!handler.link_list_swap(link1_ndx, link2_ndx)) // Throws + parser_error(); + return; + } + case instr_LinkListErase: { + size_t link_ndx = read_int(); // Throws + size_t prior_size = read_int(); // Throws + if (!handler.link_list_erase(link_ndx, prior_size)) // Throws + parser_error(); + return; + } + case instr_LinkListNullify: { + size_t link_ndx = read_int(); // Throws + size_t prior_size = read_int(); // Throws + if (!handler.link_list_nullify(link_ndx, prior_size)) // Throws + parser_error(); + return; + } + case instr_LinkListClear: { + size_t old_list_size = read_int(); // Throws + if (!handler.link_list_clear(old_list_size)) // Throws + parser_error(); + return; + } + case instr_SelectLinkList: { + size_t col_ndx = read_int(); // Throws + size_t row_ndx = read_int(); // Throws + size_t target_group_level_ndx = read_int(); // Throws + if (!handler.select_link_list(col_ndx, row_ndx, target_group_level_ndx)) // Throws + parser_error(); + return; + } + case instr_MoveColumn: { + // FIXME: remove this in the next breaking change. + // This instruction is no longer supported and not used by either + // bindings or sync, so if we see it here, there was a problem parsing. + parser_error(); + return; + } + case instr_AddSearchIndex: { + size_t col_ndx = read_int(); // Throws + if (!handler.add_search_index(col_ndx)) // Throws + parser_error(); + return; + } + case instr_RemoveSearchIndex: { + size_t col_ndx = read_int(); // Throws + if (!handler.remove_search_index(col_ndx)) // Throws + parser_error(); + return; + } + case instr_SetLinkType: { + size_t col_ndx = read_int(); // Throws + int link_type = read_int(); // Throws + if (!is_valid_link_type(link_type)) + parser_error(); + if (!handler.set_link_type(col_ndx, LinkType(link_type))) // Throws + parser_error(); + return; + } + case instr_InsertColumn: + case instr_InsertNullableColumn: { + size_t col_ndx = read_int(); // Throws + int type = read_int(); // Throws + if (!is_valid_data_type(type)) + parser_error(); + if (REALM_UNLIKELY(type == type_Link || type == type_LinkList)) + parser_error(); + StringData name = read_string(m_string_buffer); // Throws + bool nullable = (Instruction(instr) == instr_InsertNullableColumn); + if (REALM_UNLIKELY(nullable && (type == type_Mixed))) { + // Nullability not supported for Mixed columns. + parser_error(); + } + if (!handler.insert_column(col_ndx, DataType(type), name, nullable)) // Throws + parser_error(); + return; + } + case instr_InsertLinkColumn: { + size_t col_ndx = read_int(); // Throws + int type = read_int(); // Throws + if (!is_valid_data_type(type)) + parser_error(); + if (REALM_UNLIKELY(type != type_Link && type != type_LinkList)) + parser_error(); + size_t link_target_table_ndx = read_int(); // Throws + size_t backlink_col_ndx = read_int(); // Throws + StringData name = read_string(m_string_buffer); // Throws + if (!handler.insert_link_column(col_ndx, DataType(type), name, link_target_table_ndx, + backlink_col_ndx)) // Throws + parser_error(); + return; + } + case instr_EraseColumn: { + size_t col_ndx = read_int(); // Throws + if (!handler.erase_column(col_ndx)) // Throws + parser_error(); + return; + } + case instr_EraseLinkColumn: { + size_t col_ndx = read_int(); // Throws + size_t link_target_table_ndx = read_int(); // Throws + size_t backlink_col_ndx = read_int(); // Throws + if (!handler.erase_link_column(col_ndx, link_target_table_ndx, backlink_col_ndx)) // Throws + parser_error(); + return; + } + case instr_RenameColumn: { + size_t col_ndx = read_int(); // Throws + StringData name = read_string(m_string_buffer); // Throws + if (!handler.rename_column(col_ndx, name)) // Throws + parser_error(); + return; + } + case instr_SelectDescriptor: { + int levels = read_int(); // Throws + if (levels < 0 || levels > m_max_levels) + parser_error(); + m_path.reserve(0, levels); // Throws + size_t* path = m_path.data(); + for (int i = 0; i != levels; ++i) { + size_t col_ndx = read_int(); // Throws + path[i] = col_ndx; + } + if (!handler.select_descriptor(levels, path)) // Throws + parser_error(); + return; + } + case instr_InsertGroupLevelTable: { + size_t table_ndx = read_int(); // Throws + size_t num_tables = read_int(); // Throws + StringData name = read_string(m_string_buffer); // Throws + if (!handler.insert_group_level_table(table_ndx, num_tables, name)) // Throws + parser_error(); + return; + } + case instr_EraseGroupLevelTable: { + size_t table_ndx = read_int(); // Throws + size_t prior_num_tables = read_int(); // Throws + if (!handler.erase_group_level_table(table_ndx, prior_num_tables)) // Throws + parser_error(); + return; + } + case instr_RenameGroupLevelTable: { + size_t table_ndx = read_int(); // Throws + StringData new_name = read_string(m_string_buffer); // Throws + if (!handler.rename_group_level_table(table_ndx, new_name)) // Throws + parser_error(); + return; + } + case instr_MoveGroupLevelTable: { + // This instruction is no longer supported and not used by either + // bindings or sync, so if we see it here, there was a problem parsing. + // FIXME: remove this in the next breaking change. + parser_error(); + return; + } + case instr_OptimizeTable: { + if (!handler.optimize_table()) // Throws + parser_error(); + return; + } + } + + throw BadTransactLog(); +} + + +template +T TransactLogParser::read_int() +{ + T value = 0; + int part = 0; + const int max_bytes = (std::numeric_limits::digits + 1 + 6) / 7; + for (int i = 0; i != max_bytes; ++i) { + char c; + if (!read_char(c)) + goto bad_transact_log; + part = static_cast(c); + if (0xFF < part) + goto bad_transact_log; // Only the first 8 bits may be used in each byte + if ((part & 0x80) == 0) { + T p = part & 0x3F; + if (util::int_shift_left_with_overflow_detect(p, i * 7)) + goto bad_transact_log; + value |= p; + break; + } + if (i == max_bytes - 1) + goto bad_transact_log; // Too many bytes + value |= T(part & 0x7F) << (i * 7); + } + if (part & 0x40) { + // The real value is negative. Because 'value' is positive at + // this point, the following negation is guaranteed by C++11 + // to never overflow. See C99+TC3 section 6.2.6.2 paragraph 2. + REALM_DIAG_PUSH(); + REALM_DIAG_IGNORE_UNSIGNED_MINUS(); + value = -value; + REALM_DIAG_POP(); + if (util::int_subtract_with_overflow_detect(value, 1)) + goto bad_transact_log; + } + return value; + +bad_transact_log: + throw BadTransactLog(); +} + + +inline void TransactLogParser::read_bytes(char* data, size_t size) +{ + for (;;) { + const size_t avail = m_input_end - m_input_begin; + if (size <= avail) + break; + realm::safe_copy_n(m_input_begin, avail, data); + if (!next_input_buffer()) + throw BadTransactLog(); + data += avail; + size -= avail; + } + const char* to = m_input_begin + size; + realm::safe_copy_n(m_input_begin, size, data); + m_input_begin = to; +} + + +inline BinaryData TransactLogParser::read_buffer(util::StringBuffer& buf, size_t size) +{ + const size_t avail = m_input_end - m_input_begin; + if (avail >= size) { + m_input_begin += size; + return BinaryData(m_input_begin - size, size); + } + + buf.clear(); + buf.resize(size); // Throws + read_bytes(buf.data(), size); + return BinaryData(buf.data(), size); +} + + +inline bool TransactLogParser::read_bool() +{ + return read_int(); +} + + +inline float TransactLogParser::read_float() +{ + static_assert(std::numeric_limits::is_iec559 && + sizeof(float) * std::numeric_limits::digits == 32, + "Unsupported 'float' representation"); + float value; + read_bytes(reinterpret_cast(&value), sizeof value); // Throws + return value; +} + + +inline double TransactLogParser::read_double() +{ + static_assert(std::numeric_limits::is_iec559 && + sizeof(double) * std::numeric_limits::digits == 64, + "Unsupported 'double' representation"); + double value; + read_bytes(reinterpret_cast(&value), sizeof value); // Throws + return value; +} + + +inline StringData TransactLogParser::read_string(util::StringBuffer& buf) +{ + size_t size = read_int(); // Throws + + if (size > Table::max_string_size) + parser_error(); + + BinaryData buffer = read_buffer(buf, size); + return StringData{buffer.data(), size}; +} + +inline Timestamp TransactLogParser::read_timestamp() +{ + int64_t seconds = read_int(); // Throws + int32_t nanoseconds = read_int(); // Throws + return Timestamp(seconds, nanoseconds); +} + + +inline BinaryData TransactLogParser::read_binary(util::StringBuffer& buf) +{ + size_t size = read_int(); // Throws + + return read_buffer(buf, size); +} + + +inline void TransactLogParser::read_mixed(Mixed* mixed) +{ + DataType type = DataType(read_int()); // Throws + switch (type) { + case type_Int: { + int_fast64_t value = read_int(); // Throws + mixed->set_int(value); + return; + } + case type_Bool: { + bool value = read_bool(); // Throws + mixed->set_bool(value); + return; + } + case type_Float: { + float value = read_float(); // Throws + mixed->set_float(value); + return; + } + case type_Double: { + double value = read_double(); // Throws + mixed->set_double(value); + return; + } + case type_OldDateTime: { + int_fast64_t value = read_int(); // Throws + mixed->set_olddatetime(value); + return; + } + case type_Timestamp: { + Timestamp value = read_timestamp(); // Throws + mixed->set_timestamp(value); + return; + } + case type_String: { + StringData value = read_string(m_string_buffer); // Throws + mixed->set_string(value); + return; + } + case type_Binary: { + BinaryData value = read_binary(m_string_buffer); // Throws + mixed->set_binary(value); + return; + } + case type_Table: { + *mixed = Mixed::subtable_tag(); + return; + } + case type_Mixed: + break; + case type_Link: + case type_LinkList: + // FIXME: Need to handle new link types here + break; + } + throw BadTransactLog(); +} + + +inline bool TransactLogParser::next_input_buffer() +{ + return m_input->next_block(m_input_begin, m_input_end); +} + + +inline bool TransactLogParser::read_char(char& c) +{ + if (m_input_begin == m_input_end && !next_input_buffer()) + return false; + c = *m_input_begin++; + return true; +} + + +inline bool TransactLogParser::is_valid_data_type(int type) +{ + switch (DataType(type)) { + case type_Int: + case type_Bool: + case type_Float: + case type_Double: + case type_String: + case type_Binary: + case type_OldDateTime: + case type_Timestamp: + case type_Table: + case type_Mixed: + case type_Link: + case type_LinkList: + return true; + } + return false; +} + + +inline bool TransactLogParser::is_valid_link_type(int type) +{ + switch (LinkType(type)) { + case link_Strong: + case link_Weak: + return true; + } + return false; +} + + +class TransactReverser { +public: + bool select_table(size_t group_level_ndx, size_t levels, const size_t* path) + { + sync_table(); + m_encoder.select_table(group_level_ndx, levels, path); + m_pending_ts_instr = get_inst(); + return true; + } + + bool select_descriptor(size_t levels, const size_t* path) + { + sync_descriptor(); + m_encoder.select_descriptor(levels, path); + m_pending_ds_instr = get_inst(); + return true; + } + + bool insert_group_level_table(size_t table_ndx, size_t num_tables, StringData) + { + sync_table(); + m_encoder.erase_group_level_table(table_ndx, num_tables + 1); + append_instruction(); + return true; + } + + bool erase_group_level_table(size_t table_ndx, size_t num_tables) + { + sync_table(); + m_encoder.insert_group_level_table(table_ndx, num_tables - 1, ""); + append_instruction(); + return true; + } + + bool rename_group_level_table(size_t, StringData) + { + sync_table(); + return true; + } + + bool optimize_table() + { + return true; // No-op + } + + bool insert_empty_rows(size_t row_ndx, size_t num_rows_to_insert, size_t prior_num_rows, bool unordered) + { + size_t num_rows_to_erase = num_rows_to_insert; + size_t prior_num_rows_2 = prior_num_rows + num_rows_to_insert; + m_encoder.erase_rows(row_ndx, num_rows_to_erase, prior_num_rows_2, unordered); // Throws + append_instruction(); + return true; + } + + bool add_row_with_key(size_t row_ndx, size_t prior_num_rows, size_t, int64_t) + { + bool unordered = true; + m_encoder.erase_rows(row_ndx, 1, prior_num_rows + 1, unordered); // Throws + append_instruction(); + return true; + } + + bool erase_rows(size_t row_ndx, size_t num_rows_to_erase, size_t prior_num_rows, bool unordered) + { + size_t num_rows_to_insert = num_rows_to_erase; + // Number of rows in table after removal, but before inverse insertion + size_t prior_num_rows_2 = prior_num_rows - num_rows_to_erase; + m_encoder.insert_empty_rows(row_ndx, num_rows_to_insert, prior_num_rows_2, unordered); // Throws + append_instruction(); + return true; + } + + bool swap_rows(size_t row_ndx_1, size_t row_ndx_2) + { + m_encoder.swap_rows(row_ndx_1, row_ndx_2); + append_instruction(); + return true; + } + + bool move_row(size_t from_ndx, size_t to_ndx) + { + m_encoder.move_row(to_ndx, from_ndx); + append_instruction(); + return true; + } + + bool merge_rows(size_t row_ndx, size_t new_row_ndx) + { + // There is no instruction we can generate here to change back. + // However, we do need to refresh accessors for any tables + // connected through backlinks, so we generate updates on each + // affected row by merging to itself. + m_encoder.merge_rows(row_ndx, row_ndx); + append_instruction(); + m_encoder.merge_rows(new_row_ndx, new_row_ndx); + append_instruction(); + return true; + } + + bool set_int(size_t col_ndx, size_t row_ndx, int_fast64_t value, Instruction variant, size_t prior_num_rows) + { + m_encoder.set_int(col_ndx, row_ndx, value, variant, prior_num_rows); + append_instruction(); + return true; + } + + bool add_int(size_t col_ndx, size_t row_ndx, int_fast64_t value) + { + m_encoder.add_int(col_ndx, row_ndx, -value); + append_instruction(); + return true; + } + + bool set_bool(size_t col_ndx, size_t row_ndx, bool value, Instruction variant) + { + m_encoder.set_bool(col_ndx, row_ndx, value, variant); + append_instruction(); + return true; + } + + bool set_float(size_t col_ndx, size_t row_ndx, float value, Instruction variant) + { + m_encoder.set_float(col_ndx, row_ndx, value, variant); + append_instruction(); + return true; + } + + bool set_double(size_t col_ndx, size_t row_ndx, double value, Instruction variant) + { + m_encoder.set_double(col_ndx, row_ndx, value, variant); + append_instruction(); + return true; + } + + bool set_string(size_t col_ndx, size_t row_ndx, StringData value, Instruction variant, size_t prior_num_rows) + { + m_encoder.set_string(col_ndx, row_ndx, value, variant, prior_num_rows); + append_instruction(); + return true; + } + + bool set_binary(size_t col_ndx, size_t row_ndx, BinaryData value, Instruction variant) + { + m_encoder.set_binary(col_ndx, row_ndx, value, variant); + append_instruction(); + return true; + } + + bool set_olddatetime(size_t col_ndx, size_t row_ndx, OldDateTime value, Instruction variant) + { + m_encoder.set_olddatetime(col_ndx, row_ndx, value, variant); + append_instruction(); + return true; + } + + bool set_timestamp(size_t col_ndx, size_t row_ndx, Timestamp value, Instruction variant) + { + m_encoder.set_timestamp(col_ndx, row_ndx, value, variant); + append_instruction(); + return true; + } + + bool set_table(size_t col_ndx, size_t row_ndx, Instruction variant) + { + m_encoder.set_table(col_ndx, row_ndx, variant); + append_instruction(); + return true; + } + + bool set_mixed(size_t col_ndx, size_t row_ndx, const Mixed& value, Instruction variant) + { + m_encoder.set_mixed(col_ndx, row_ndx, value, variant); + append_instruction(); + return true; + } + + bool set_null(size_t col_ndx, size_t row_ndx, Instruction variant, size_t prior_num_rows) + { + m_encoder.set_null(col_ndx, row_ndx, variant, prior_num_rows); + append_instruction(); + return true; + } + + bool set_link(size_t col_ndx, size_t row_ndx, size_t value, size_t target_group_level_ndx, Instruction variant) + { + m_encoder.set_link(col_ndx, row_ndx, value, target_group_level_ndx, variant); + append_instruction(); + return true; + } + + bool insert_substring(size_t, size_t, size_t, StringData) + { + return true; // No-op + } + + bool erase_substring(size_t, size_t, size_t, size_t) + { + return true; // No-op + } + + bool clear_table(size_t old_size) + { + bool unordered = false; + m_encoder.insert_empty_rows(0, old_size, 0, unordered); + append_instruction(); + return true; + } + + bool add_search_index(size_t) + { + return true; // No-op + } + + bool remove_search_index(size_t) + { + return true; // No-op + } + + bool set_link_type(size_t, LinkType) + { + return true; // No-op + } + + bool insert_link_column(size_t col_idx, DataType, StringData, size_t target_table_idx, size_t backlink_col_ndx) + { + m_encoder.erase_link_column(col_idx, target_table_idx, backlink_col_ndx); + append_instruction(); + return true; + } + + bool erase_link_column(size_t col_idx, size_t target_table_idx, size_t backlink_col_idx) + { + DataType type = type_Link; // The real type of the column doesn't matter here, + // but the encoder asserts that it's actually a link type. + m_encoder.insert_link_column(col_idx, type, "", target_table_idx, backlink_col_idx); + append_instruction(); + return true; + } + + bool insert_column(size_t col_idx, DataType, StringData, bool) + { + m_encoder.erase_column(col_idx); + append_instruction(); + return true; + } + + bool erase_column(size_t col_idx) + { + m_encoder.insert_column(col_idx, DataType(), ""); + append_instruction(); + return true; + } + + bool rename_column(size_t, StringData) + { + return true; // No-op + } + + bool select_link_list(size_t col_ndx, size_t row_ndx, size_t link_target_group_level_ndx) + { + sync_linkview(); + m_encoder.select_link_list(col_ndx, row_ndx, link_target_group_level_ndx); + m_pending_lv_instr = get_inst(); + return true; + } + + bool link_list_set(size_t row, size_t value, size_t prior_size) + { + m_encoder.link_list_set(row, value, prior_size); + append_instruction(); + return true; + } + + bool link_list_insert(size_t link_ndx, size_t, size_t prior_size) + { + m_encoder.link_list_erase(link_ndx, prior_size + 1); + append_instruction(); + return true; + } + + bool link_list_move(size_t from_link_ndx, size_t to_link_ndx) + { + m_encoder.link_list_move(from_link_ndx, to_link_ndx); + append_instruction(); + return true; + } + + bool link_list_swap(size_t link1_ndx, size_t link2_ndx) + { + m_encoder.link_list_swap(link1_ndx, link2_ndx); + append_instruction(); + return true; + } + + bool link_list_erase(size_t link_ndx, size_t prior_size) + { + m_encoder.link_list_insert(link_ndx, 0, prior_size - 1); + append_instruction(); + return true; + } + + bool link_list_clear(size_t old_list_size) + { + // Append in reverse order because the reversed log is itself applied + // in reverse, and this way it generates all back-insertions rather than + // all front-insertions + for (size_t i = old_list_size; i > 0; --i) { + m_encoder.link_list_insert(i - 1, 0, old_list_size - i); + append_instruction(); + } + return true; + } + + bool nullify_link(size_t col_ndx, size_t row_ndx, size_t target_group_level_ndx) + { + size_t value = 0; + // FIXME: Is zero this right value to pass here, or should + // TransactReverser::nullify_link() also have taken a + // `target_group_level_ndx` argument. + m_encoder.set_link(col_ndx, row_ndx, value, target_group_level_ndx); + append_instruction(); + return true; + } + + bool link_list_nullify(size_t link_ndx, size_t prior_size) + { + m_encoder.link_list_insert(link_ndx, 0, prior_size - 1); + append_instruction(); + return true; + } + +private: + _impl::TransactLogBufferStream m_buffer; + _impl::TransactLogEncoder m_encoder{m_buffer}; + struct Instr { + size_t begin; + size_t end; + }; + std::vector m_instructions; + size_t current_instr_start = 0; + Instr m_pending_ts_instr{0, 0}; + Instr m_pending_ds_instr{0, 0}; + Instr m_pending_lv_instr{0, 0}; + + Instr get_inst() + { + Instr instr; + instr.begin = current_instr_start; + current_instr_start = transact_log_size(); + instr.end = current_instr_start; + return instr; + } + + size_t transact_log_size() const + { + REALM_ASSERT_3(m_encoder.write_position(), >=, m_buffer.transact_log_data()); + return m_encoder.write_position() - m_buffer.transact_log_data(); + } + + void append_instruction() + { + m_instructions.push_back(get_inst()); + } + + void append_instruction(Instr instr) + { + m_instructions.push_back(instr); + } + + void sync_select(Instr& pending_instr) + { + if (pending_instr.begin != pending_instr.end) { + append_instruction(pending_instr); + pending_instr = {0, 0}; + } + } + + void sync_linkview() + { + sync_select(m_pending_lv_instr); + } + + void sync_descriptor() + { + sync_linkview(); + sync_select(m_pending_ds_instr); + } + + void sync_table() + { + sync_descriptor(); + sync_select(m_pending_ts_instr); + } + + friend class ReversedNoCopyInputStream; +}; + + +class ReversedNoCopyInputStream : public NoCopyInputStream { +public: + ReversedNoCopyInputStream(TransactReverser& reverser) + : m_instr_order(reverser.m_instructions) + { + // push any pending select_table or select_descriptor into the buffer + reverser.sync_table(); + + m_buffer = reverser.m_buffer.transact_log_data(); + m_current = m_instr_order.size(); + } + + bool next_block(const char*& begin, const char*& end) override + { + if (m_current != 0) { + m_current--; + begin = m_buffer + m_instr_order[m_current].begin; + end = m_buffer + m_instr_order[m_current].end; + return (end > begin); + } + return false; + } + +private: + const char* m_buffer; + std::vector& m_instr_order; + size_t m_current; +}; + +} // namespace _impl +} // namespace realm + +#endif // REALM_IMPL_TRANSACT_LOG_HPP diff --git a/!main project/Pods/Realm/include/core/realm/index_string.hpp b/!main project/Pods/Realm/include/core/realm/index_string.hpp new file mode 100644 index 0000000..1c27039 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/index_string.hpp @@ -0,0 +1,606 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_INDEX_STRING_HPP +#define REALM_INDEX_STRING_HPP + +#include +#include +#include + +#include +#include + +/* +The StringIndex class is used for both type_String and all integral types, such as type_Bool, type_OldDateTime and +type_Int. When used for integral types, the 64-bit integer is simply casted to a string of 8 bytes through a +pretty simple "wrapper layer" in all public methods. + +The StringIndex data structure is like an "inversed" B+ tree where the leafs contain row indexes and the non-leafs +contain 4-byte chunks of payload. Imagine a table with following strings: + + hello, kitty, kitten, foobar, kitty, foobar + +The topmost level of the index tree contains prefixes of the payload strings of length <= 4. The next level contains +prefixes of the remaining parts of the strings. Unnecessary levels of the tree are optimized away; the prefix "foob" +is shared only by rows that are identical ("foobar"), so "ar" is not needed to be stored in the tree. + + hell kitt foob + | /\ | + 0 en y {3, 5} + | \ + {1, 4} 2 + +Each non-leafs consists of two integer arrays of the same length, one containing payload and the other containing +references to the sublevel nodes. + +The leafs can be either a single value or a Column. If the reference in its parent node has its least significant +bit set, then the remaining upper bits specify the row index at which the string is stored. If the bit is clear, +it must be interpreted as a reference to a Column that stores the row indexes at which the string is stored. + +If a Column is used, then all row indexes are guaranteed to be sorted increasingly, which means you an search in it +using our binary search functions such as upper_bound() and lower_bound(). Each duplicate value will be stored in +the same Column, but Columns may contain more than just duplicates if the depth of the tree exceeds the value +`s_max_offset` This is to avoid stack overflow problems with many of our recursive functions if we have two very +long strings that have a long common prefix but differ in the last couple bytes. If a Column stores more than just +duplicates, then the list is kept sorted in ascending order by string value and within the groups of common +strings, the rows are sorted in ascending order. +*/ + +namespace realm { + +class Spec; +class Timestamp; + +class IndexArray : public Array { +public: + IndexArray(Allocator& allocator) + : Array(allocator) + { + } + + size_t index_string_find_first(StringData value, ColumnBase* column) const; + void index_string_find_all(IntegerColumn& result, StringData value, ColumnBase* column, bool case_insensitive = false) const; + FindRes index_string_find_all_no_copy(StringData value, ColumnBase* column, InternalFindResult& result) const; + size_t index_string_count(StringData value, ColumnBase* column) const; + +private: + template + size_t from_list(StringData value, InternalFindResult& result_ref, const IntegerColumn& rows, + ColumnBase* column) const; + + void from_list_all(StringData value, IntegerColumn& result, const IntegerColumn& rows, ColumnBase* column) const; + + void from_list_all_ins(StringData value, std::vector& result, const IntegerColumn& rows, + ColumnBase* column) const; + + template + size_t index_string(StringData value, InternalFindResult& result_ref, ColumnBase* column) const; + + void index_string_all(StringData value, IntegerColumn& result, ColumnBase* column) const; + + void index_string_all_ins(StringData value, IntegerColumn& result, ColumnBase* column) const; +}; + + +class StringIndex { +public: + StringIndex(ColumnBase* target_column, Allocator&); + StringIndex(ref_type, ArrayParent*, size_t ndx_in_parent, ColumnBase* target_column, Allocator&); + ~StringIndex() noexcept + { + } + + static ref_type create_empty(Allocator& alloc); + + void set_target(ColumnBase* target_column) noexcept; + + // Accessor concept: + Allocator& get_alloc() const noexcept; + void destroy() noexcept; + void detach(); + bool is_attached() const noexcept; + void set_parent(ArrayParent* parent, size_t ndx_in_parent) noexcept; + size_t get_ndx_in_parent() const noexcept; + void set_ndx_in_parent(size_t ndx_in_parent) noexcept; + void update_from_parent(size_t old_baseline) noexcept; + void refresh_accessor_tree(size_t, const Spec&); + ref_type get_ref() const noexcept; + + // StringIndex interface: + + // 12 is the biggest element size of any non-string/binary Realm type + static const size_t string_conversion_buffer_size = 12; + using StringConversionBuffer = std::array; + + bool is_empty() const; + + template + void insert(size_t row_ndx, T value, size_t num_rows, bool is_append); + template + void insert(size_t row_ndx, util::Optional value, size_t num_rows, bool is_append); + + template + void set(size_t row_ndx, T new_value); + template + void set(size_t row_ndx, util::Optional new_value); + + template + void erase(size_t row_ndx, bool is_last); + + template + size_t find_first(T value) const; + template + void find_all(IntegerColumn& result, T value, bool case_insensitive = false) const; + template + FindRes find_all_no_copy(T value, InternalFindResult& result) const; + template + size_t count(T value) const; + template + void update_ref(T value, size_t old_row_ndx, size_t new_row_ndx); + + void clear(); + + void distinct(IntegerColumn& result) const; + bool has_duplicate_values() const noexcept; + + void verify() const; +#ifdef REALM_DEBUG + template + void verify_entries(const T& column) const; + void do_dump_node_structure(std::ostream&, int) const; + void to_dot() const; + void to_dot(std::ostream&, StringData title = StringData()) const; + void to_dot_2(std::ostream&, StringData title = StringData()) const; +#endif + + typedef int32_t key_type; + + // s_max_offset specifies the number of levels of recursive string indexes + // allowed before storing everything in lists. This is to avoid nesting + // to too deep of a level. Since every SubStringIndex stores 4 bytes, this + // means that a StringIndex is helpful for strings of a common prefix up to + // 4 times this limit (200 bytes shared). Lists are stored in sorted order, + // so strings sharing a common prefix of more than this limit will use a + // binary search of approximate complexity log2(n) from `std::lower_bound`. + static const size_t s_max_offset = 200; // max depth * s_index_key_length + static const size_t s_index_key_length = 4; + static key_type create_key(StringData) noexcept; + static key_type create_key(StringData, size_t) noexcept; + +private: + // m_array is a compact representation for storing the children of this StringIndex. + // Children can be: + // 1) a row number + // 2) a reference to a list which stores row numbers (for duplicate strings). + // 3) a reference to a sub-index + // m_array[0] is always a reference to a values array which stores the 4 byte chunk + // of payload data for quick string chunk comparisons. The array stored + // at m_array[0] lines up with the indices of values in m_array[1] so for example + // starting with an empty StringIndex: + // StringColumn::insert(target_row_ndx=42, value="test_string") would result with + // get_array_from_ref(m_array[0])[0] == create_key("test") and + // m_array[1] == 42 + // In this way, m_array which stores one child has a size of two. + // Children are type 1 (row number) if the LSB of the value is set. + // To get the actual row value, shift value down by one. + // If the LSB of the value is 0 then the value is a reference and can be either + // type 2, or type 3 (no shifting in either case). + // References point to a list if the context header flag is NOT set. + // If the header flag is set, references point to a sub-StringIndex (nesting). + std::unique_ptr m_array; + ColumnBase* m_target_column; + + struct inner_node_tag { + }; + StringIndex(inner_node_tag, Allocator&); + + static IndexArray* create_node(Allocator&, bool is_leaf); + + void insert_with_offset(size_t row_ndx, StringData value, size_t offset); + void insert_row_list(size_t ref, size_t offset, StringData value); + void insert_to_existing_list(size_t row, StringData value, IntegerColumn& list); + void insert_to_existing_list_at_lower(size_t row, StringData value, IntegerColumn& list, + const IntegerColumnIterator& lower); + key_type get_last_key() const; + + /// Add small signed \a diff to all elements that are greater than, or equal + /// to \a min_row_ndx. + void adjust_row_indexes(size_t min_row_ndx, int diff); + + struct NodeChange { + size_t ref1; + size_t ref2; + enum ChangeType { change_None, change_InsertBefore, change_InsertAfter, change_Split } type; + NodeChange(ChangeType t, size_t r1 = 0, size_t r2 = 0) + : ref1(r1) + , ref2(r2) + , type(t) + { + } + NodeChange() + : ref1(0) + , ref2(0) + , type(change_None) + { + } + }; + + // B-Tree functions + void TreeInsert(size_t row_ndx, key_type, size_t offset, StringData value); + NodeChange do_insert(size_t ndx, key_type, size_t offset, StringData value); + /// Returns true if there is room or it can join existing entries + bool leaf_insert(size_t row_ndx, key_type, size_t offset, StringData value, bool noextend = false); + void node_insert_split(size_t ndx, size_t new_ref); + void node_insert(size_t ndx, size_t ref); + void do_delete(size_t ndx, StringData, size_t offset); + void do_update_ref(StringData value, size_t row_ndx, size_t new_row_ndx, size_t offset); + + StringData get(size_t ndx, StringConversionBuffer& buffer) const; + + void node_add_key(ref_type ref); + +#ifdef REALM_DEBUG + static void dump_node_structure(const Array& node, std::ostream&, int level); + static void array_to_dot(std::ostream&, const Array&); + static void keys_to_dot(std::ostream&, const Array&, StringData title = StringData()); +#endif +}; + + +class SortedListComparator { +public: + SortedListComparator(ColumnBase& column_values); + bool operator()(int64_t ndx, StringData needle); + bool operator()(StringData needle, int64_t ndx); + +private: + ColumnBase& values; +}; + + +// Implementation: + +template +struct GetIndexData; + +template <> +struct GetIndexData { + static StringData get_index_data(const int64_t& value, StringIndex::StringConversionBuffer& buffer) + { + const char* c = reinterpret_cast(&value); + realm::safe_copy_n(c, sizeof(int64_t), buffer.data()); + return StringData{buffer.data(), sizeof(int64_t)}; + } +}; + +template <> +struct GetIndexData { + static StringData get_index_data(StringData data, StringIndex::StringConversionBuffer&) + { + return data; + } +}; + +template <> +struct GetIndexData { + static StringData get_index_data(null, StringIndex::StringConversionBuffer&) + { + return null{}; + } +}; + +template <> +struct GetIndexData { + static StringData get_index_data(const Timestamp&, StringIndex::StringConversionBuffer&); +}; + +template +struct GetIndexData> { + static StringData get_index_data(const util::Optional& value, StringIndex::StringConversionBuffer& buffer) + { + if (value) + return GetIndexData::get_index_data(*value, buffer); + return null{}; + } +}; + +template <> +struct GetIndexData { + static StringData get_index_data(float, StringIndex::StringConversionBuffer&) + { + REALM_ASSERT_RELEASE(false); // LCOV_EXCL_LINE; Index on float not supported + } +}; + +template <> +struct GetIndexData { + static StringData get_index_data(double, StringIndex::StringConversionBuffer&) + { + REALM_ASSERT_RELEASE(false); // LCOV_EXCL_LINE; Index on float not supported + } +}; + +template <> +struct GetIndexData : GetIndexData { +}; + +// to_str() is used by the integer index. The existing StringIndex is re-used for this +// by making IntegerColumn convert its integers to strings by calling to_str(). + +template +inline StringData to_str(T&& value, StringIndex::StringConversionBuffer& buffer) +{ + return GetIndexData::type>::get_index_data(value, buffer); +} + + +inline StringIndex::StringIndex(ColumnBase* target_column, Allocator& alloc) + : m_array(create_node(alloc, true)) // Throws + , m_target_column(target_column) +{ +} + +inline StringIndex::StringIndex(ref_type ref, ArrayParent* parent, size_t ndx_in_parent, ColumnBase* target_column, + Allocator& alloc) + : m_array(new IndexArray(alloc)) + , m_target_column(target_column) +{ + REALM_ASSERT_EX(Array::get_context_flag_from_header(alloc.translate(ref)), ref, size_t(alloc.translate(ref))); + m_array->init_from_ref(ref); + set_parent(parent, ndx_in_parent); +} + +inline StringIndex::StringIndex(inner_node_tag, Allocator& alloc) + : m_array(create_node(alloc, false)) // Throws + , m_target_column(nullptr) +{ +} + +// Byte order of the key is *reversed*, so that for the integer index, the least significant +// byte comes first, so that it fits little-endian machines. That way we can perform fast +// range-lookups and iterate in order, etc, as future features. This, however, makes the same +// features slower for string indexes. Todo, we should reverse the order conditionally, depending +// on the column type. +inline StringIndex::key_type StringIndex::create_key(StringData str) noexcept +{ + key_type key = 0; + + if (str.size() >= 4) + goto four; + if (str.size() < 2) { + if (str.size() == 0) + goto none; + goto one; + } + if (str.size() == 2) + goto two; + goto three; + +// Create 4 byte index key +// (encoded like this to allow literal comparisons +// independently of endianness) +four: + key |= (key_type(static_cast(str[3])) << 0); +three: + key |= (key_type(static_cast(str[2])) << 8); +two: + key |= (key_type(static_cast(str[1])) << 16); +one: + key |= (key_type(static_cast(str[0])) << 24); +none: + return key; +} + +// Index works as follows: All non-NULL values are stored as if they had appended an 'X' character at the end. So +// "foo" is stored as if it was "fooX", and "" (empty string) is stored as "X". And NULLs are stored as empty strings. +inline StringIndex::key_type StringIndex::create_key(StringData str, size_t offset) noexcept +{ + if (str.is_null()) + return 0; + + if (offset > str.size()) + return 0; + + // for very short strings + size_t tail = str.size() - offset; + if (tail <= sizeof(key_type) - 1) { + char buf[sizeof(key_type)]; + memset(buf, 0, sizeof(key_type)); + buf[tail] = 'X'; + memcpy(buf, str.data() + offset, tail); + return create_key(StringData(buf, tail + 1)); + } + // else fallback + return create_key(str.substr(offset)); +} + +template +void StringIndex::insert(size_t row_ndx, T value, size_t num_rows, bool is_append) +{ + REALM_ASSERT_3(row_ndx, !=, npos); + + // If the new row is inserted after the last row in the table, we don't need + // to adjust any row indexes. + if (!is_append) { + for (size_t i = 0; i < num_rows; ++i) { + size_t row_ndx_2 = row_ndx + i; + adjust_row_indexes(row_ndx_2, 1); // Throws + } + } + + StringConversionBuffer buffer; + + for (size_t i = 0; i < num_rows; ++i) { + size_t row_ndx_2 = row_ndx + i; + size_t offset = 0; // First key from beginning of string + insert_with_offset(row_ndx_2, to_str(value, buffer), offset); // Throws + } +} + +template +void StringIndex::insert(size_t row_ndx, util::Optional value, size_t num_rows, bool is_append) +{ + if (value) { + insert(row_ndx, *value, num_rows, is_append); + } + else { + insert(row_ndx, null{}, num_rows, is_append); + } +} + +template +void StringIndex::set(size_t row_ndx, T new_value) +{ + StringConversionBuffer buffer; + StringConversionBuffer buffer2; + StringData old_value = get(row_ndx, buffer); + StringData new_value2 = to_str(new_value, buffer2); + + // Note that insert_with_offset() throws UniqueConstraintViolation. + + if (REALM_LIKELY(new_value2 != old_value)) { + // We must erase this row first because erase uses find_first which + // might find the duplicate if we insert before erasing. + bool is_last = true; // To avoid updating refs + erase(row_ndx, is_last); // Throws + + size_t offset = 0; // First key from beginning of string + insert_with_offset(row_ndx, new_value2, offset); // Throws + } +} + +template +void StringIndex::set(size_t row_ndx, util::Optional new_value) +{ + if (new_value) { + set(row_ndx, *new_value); + } + else { + set(row_ndx, null{}); + } +} + +template +void StringIndex::erase(size_t row_ndx, bool is_last) +{ + StringConversionBuffer buffer; + StringData value = get(row_ndx, buffer); + + do_delete(row_ndx, value, 0); + + // Collapse top nodes with single item + while (m_array->is_inner_bptree_node()) { + REALM_ASSERT(m_array->size() > 1); // node cannot be empty + if (m_array->size() > 2) + break; + + ref_type ref = m_array->get_as_ref(1); + m_array->set(1, 1); // avoid destruction of the extracted ref + m_array->destroy_deep(); + m_array->init_from_ref(ref); + m_array->update_parent(); + } + + // If it is last item in column, we don't have to update refs + if (!is_last) + adjust_row_indexes(row_ndx, -1); +} + +template +size_t StringIndex::find_first(T value) const +{ + // Use direct access method + StringConversionBuffer buffer; + return m_array->index_string_find_first(to_str(value, buffer), m_target_column); +} + +template +void StringIndex::find_all(IntegerColumn& result, T value, bool case_insensitive) const +{ + // Use direct access method + StringConversionBuffer buffer; + return m_array->index_string_find_all(result, to_str(value, buffer), m_target_column, case_insensitive); +} + +template +FindRes StringIndex::find_all_no_copy(T value, InternalFindResult& result) const +{ + // Use direct access method + StringConversionBuffer buffer; + return m_array->index_string_find_all_no_copy(to_str(value, buffer), m_target_column, result); +} + +template +size_t StringIndex::count(T value) const +{ + // Use direct access method + StringConversionBuffer buffer; + return m_array->index_string_count(to_str(value, buffer), m_target_column); +} + +template +void StringIndex::update_ref(T value, size_t old_row_ndx, size_t new_row_ndx) +{ + StringConversionBuffer buffer; + do_update_ref(to_str(value, buffer), old_row_ndx, new_row_ndx, 0); +} + +inline void StringIndex::destroy() noexcept +{ + return m_array->destroy_deep(); +} + +inline bool StringIndex::is_attached() const noexcept +{ + return m_array->is_attached(); +} + +inline void StringIndex::refresh_accessor_tree(size_t, const Spec&) +{ + m_array->init_from_parent(); +} + +inline ref_type StringIndex::get_ref() const noexcept +{ + return m_array->get_ref(); +} + +inline void StringIndex::set_parent(ArrayParent* parent, size_t ndx_in_parent) noexcept +{ + m_array->set_parent(parent, ndx_in_parent); +} + +inline size_t StringIndex::get_ndx_in_parent() const noexcept +{ + return m_array->get_ndx_in_parent(); +} + +inline void StringIndex::set_ndx_in_parent(size_t ndx_in_parent) noexcept +{ + m_array->set_ndx_in_parent(ndx_in_parent); +} + +inline void StringIndex::update_from_parent(size_t old_baseline) noexcept +{ + m_array->update_from_parent(old_baseline); +} + +} // namespace realm + +#endif // REALM_INDEX_STRING_HPP diff --git a/!main project/Pods/Realm/include/core/realm/lang_bind_helper.hpp b/!main project/Pods/Realm/include/core/realm/lang_bind_helper.hpp new file mode 100644 index 0000000..de287bf --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/lang_bind_helper.hpp @@ -0,0 +1,378 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_LANG_BIND_HELPER_HPP +#define REALM_LANG_BIND_HELPER_HPP + +#include + +#include +#include +#include +#include +#include + +#include + +namespace realm { + + +/// These functions are only to be used by language bindings to gain +/// access to certain memebers that are othewise private. +/// +/// \note Applications are not supposed to call any of these functions +/// directly. +/// +/// All of the get_subtable_ptr() functions bind the table accessor pointer +/// before it is returned (bind_table_ptr()). The caller is then responsible for +/// making the corresponding call to unbind_table_ptr(). +class LangBindHelper { +public: + /// Increment the reference counter of the specified table accessor. This is + /// done automatically by all of the functions in this class that return + /// table accessor pointers, but if the binding/application makes a copy of + /// such a pointer, and the copy needs to have an "independent life", then + /// the binding/application must bind that copy using this function. + static void bind_table_ptr(const Table*) noexcept; + + /// Decrement the reference counter of the specified table accessor. The + /// binding/application must call this function for every bound table + /// accessor pointer object, when that pointer object ends its life. + static void unbind_table_ptr(const Table*) noexcept; + + /// Construct a new freestanding table. The table accessor pointer is bound + /// by the callee before it is returned (bind_table_ptr()). + static Table* new_table(); + + /// Construct a new freestanding table as a copy of the specified one. The + /// table accessor pointer is bound by the callee before it is returned + /// (bind_table_ptr()). + static Table* copy_table(const Table&); + + //@{ + + /// These functions are like their namesakes in Group, but these bypass the + /// construction of a smart-pointer object (TableRef). The table accessor + /// pointer is bound by the callee before it is returned (bind_table_ptr()). + + static Table* get_table(Group&, size_t index_in_group); + static const Table* get_table(const Group&, size_t index_in_group); + + static Table* get_table(Group&, StringData name); + static const Table* get_table(const Group&, StringData name); + + static Table* add_table(Group&, StringData name, bool require_unique_name = true); + static Table* get_or_add_table(Group&, StringData name, bool* was_added = nullptr); + + //@} + + static Table* get_subtable_ptr(Table*, size_t column_ndx, size_t row_ndx); + static const Table* get_subtable_ptr(const Table*, size_t column_ndx, size_t row_ndx); + + // FIXME: This is an 'oddball', do we really need it? If we do, + // please provide a comment that explains why it is needed! + static Table* get_subtable_ptr_during_insert(Table*, size_t col_ndx, size_t row_ndx); + + static Table* get_subtable_ptr(TableView*, size_t column_ndx, size_t row_ndx); + static const Table* get_subtable_ptr(const TableView*, size_t column_ndx, size_t row_ndx); + static const Table* get_subtable_ptr(const ConstTableView*, size_t column_ndx, size_t row_ndx); + + /// Calls parent.set_mixed_subtable(col_ndx, row_ndx, &source). Note + /// that the source table must have a descriptor that is + /// compatible with the target subtable column. + static void set_mixed_subtable(Table& parent, size_t col_ndx, size_t row_ndx, const Table& source); + + static const LinkViewRef& get_linklist_ptr(Row&, size_t col_ndx); + static void unbind_linklist_ptr(const LinkViewRef&); + + using VersionID = SharedGroup::VersionID; + + /// \defgroup lang_bind_helper_transactions Continuous Transactions + /// + /// advance_read() is equivalent to terminating the current read transaction + /// (SharedGroup::end_read()), and initiating a new one + /// (SharedGroup::begin_read()), except that all subordinate accessors + /// (Table, Row, Descriptor) will remain attached to the underlying objects, + /// unless those objects were removed in the target snapshot. By default, + /// the read transaction is advanced to the latest available snapshot, but + /// see SharedGroup::begin_read() for information about \a version. + /// + /// promote_to_write() is equivalent to terminating the current read + /// transaction (SharedGroup::end_read()), and initiating a new write + /// transaction (SharedGroup::begin_write()), except that all subordinate + /// accessors (Table, Row, Descriptor) will remain attached to the + /// underlying objects, unless those objects were removed in the target + /// snapshot. + /// + /// commit_and_continue_as_read() is equivalent to committing the current + /// write transaction (SharedGroup::commit()) and initiating a new read + /// transaction, which is bound to the snapshot produced by the write + /// transaction (SharedGroup::begin_read()), except that all subordinate + /// accessors (Table, Row, Descriptor) will remain attached to the + /// underlying objects. commit_and_continue_as_read() returns the version + /// produced by the committed transaction. + /// + /// rollback_and_continue_as_read() is equivalent to rolling back the + /// current write transaction (SharedGroup::rollback()) and initiating a new + /// read transaction, which is bound to the snapshot, that the write + /// transaction was based on (SharedGroup::begin_read()), except that all + /// subordinate accessors (Table, Row, Descriptor) will remain attached to + /// the underlying objects, unless they were attached to object that were + /// added during the rolled back transaction. + /// + /// If advance_read(), promote_to_write(), commit_and_continue_as_read(), or + /// rollback_and_continue_as_read() throws, the associated group accessor + /// and all of its subordinate accessors are left in a state that may not be + /// fully consistent. Only minimal consistency is guaranteed (see + /// AccessorConsistencyLevels). In this case, the application is required to + /// either destroy the SharedGroup object, forcing all associated accessors + /// to become detached, or take some other equivalent action that involves a + /// complete accessor detachment, such as terminating the transaction in + /// progress. Until then it is an error, and unsafe if the application + /// attempts to access any of those accessors. + /// + /// The application must use SharedGroup::end_read() if it wants to + /// terminate the transaction after advance_read() or promote_to_write() has + /// thrown an exception. Likewise, it must use SharedGroup::rollback() if it + /// wants to terminate the transaction after commit_and_continue_as_read() + /// or rollback_and_continue_as_read() has thrown an exception. + /// + /// \param observer An optional custom replication instruction handler. The + /// application may pass such a handler to observe the sequence of + /// modifications that advances (or rolls back) the state of the Realm. + /// + /// \throw SharedGroup::BadVersion Thrown by advance_read() if the specified + /// version does not correspond to a bound (or tethered) snapshot. + /// + //@{ + + static void advance_read(SharedGroup&, VersionID = VersionID()); + template + static void advance_read(SharedGroup&, O&& observer, VersionID = VersionID()); + static void promote_to_write(SharedGroup&); + template + static void promote_to_write(SharedGroup&, O&& observer); + static SharedGroup::version_type commit_and_continue_as_read(SharedGroup&); + static void rollback_and_continue_as_read(SharedGroup&); + template + static void rollback_and_continue_as_read(SharedGroup&, O&& observer); + + //@} + + /// Returns the name of the specified data type. Examples: + /// + ///
+    ///
+    ///   type_Int          ->  "int"
+    ///   type_Bool         ->  "bool"
+    ///   type_Float        ->  "float"
+    ///   ...
+    ///
+    /// 
+ static const char* get_data_type_name(DataType) noexcept; + + static SharedGroup::version_type get_version_of_latest_snapshot(SharedGroup&); +}; + + +// Implementation: + +inline Table* LangBindHelper::new_table() +{ + typedef _impl::TableFriend tf; + Allocator& alloc = Allocator::get_default(); + size_t ref = tf::create_empty_table(alloc); // Throws + Table::Parent* parent = nullptr; + size_t ndx_in_parent = 0; + Table* table = tf::create_accessor(alloc, ref, parent, ndx_in_parent); // Throws + bind_table_ptr(table); + return table; +} + +inline Table* LangBindHelper::copy_table(const Table& table) +{ + typedef _impl::TableFriend tf; + Allocator& alloc = Allocator::get_default(); + size_t ref = tf::clone(table, alloc); // Throws + Table::Parent* parent = nullptr; + size_t ndx_in_parent = 0; + Table* copy_of_table = tf::create_accessor(alloc, ref, parent, ndx_in_parent); // Throws + bind_table_ptr(copy_of_table); + return copy_of_table; +} + +inline Table* LangBindHelper::get_subtable_ptr(Table* t, size_t column_ndx, size_t row_ndx) +{ + TableRef subtab = t->get_subtable_tableref(column_ndx, row_ndx); // Throws + return subtab.release(); +} + +inline const Table* LangBindHelper::get_subtable_ptr(const Table* t, size_t column_ndx, size_t row_ndx) +{ + ConstTableRef subtab = t->get_subtable_tableref(column_ndx, row_ndx); // Throws + return subtab.release(); +} + +inline Table* LangBindHelper::get_subtable_ptr(TableView* tv, size_t column_ndx, size_t row_ndx) +{ + return get_subtable_ptr(&tv->get_parent(), column_ndx, tv->get_source_ndx(row_ndx)); +} + +inline const Table* LangBindHelper::get_subtable_ptr(const TableView* tv, size_t column_ndx, size_t row_ndx) +{ + return get_subtable_ptr(&tv->get_parent(), column_ndx, tv->get_source_ndx(row_ndx)); +} + +inline const Table* LangBindHelper::get_subtable_ptr(const ConstTableView* tv, size_t column_ndx, size_t row_ndx) +{ + return get_subtable_ptr(&tv->get_parent(), column_ndx, tv->get_source_ndx(row_ndx)); +} + +inline Table* LangBindHelper::get_table(Group& group, size_t index_in_group) +{ + typedef _impl::GroupFriend gf; + Table* table = &gf::get_table(group, index_in_group); // Throws + table->bind_ptr(); + return table; +} + +inline const Table* LangBindHelper::get_table(const Group& group, size_t index_in_group) +{ + typedef _impl::GroupFriend gf; + const Table* table = &gf::get_table(group, index_in_group); // Throws + table->bind_ptr(); + return table; +} + +inline Table* LangBindHelper::get_table(Group& group, StringData name) +{ + typedef _impl::GroupFriend gf; + Table* table = gf::get_table(group, name); // Throws + if (table) + table->bind_ptr(); + return table; +} + +inline const Table* LangBindHelper::get_table(const Group& group, StringData name) +{ + typedef _impl::GroupFriend gf; + const Table* table = gf::get_table(group, name); // Throws + if (table) + table->bind_ptr(); + return table; +} + +inline Table* LangBindHelper::add_table(Group& group, StringData name, bool require_unique_name) +{ + typedef _impl::GroupFriend gf; + Table* table = &gf::add_table(group, name, require_unique_name); // Throws + table->bind_ptr(); + return table; +} + +inline Table* LangBindHelper::get_or_add_table(Group& group, StringData name, bool* was_added) +{ + typedef _impl::GroupFriend gf; + Table* table = &gf::get_or_add_table(group, name, was_added); // Throws + table->bind_ptr(); + return table; +} + +inline void LangBindHelper::unbind_table_ptr(const Table* t) noexcept +{ + t->unbind_ptr(); +} + +inline void LangBindHelper::bind_table_ptr(const Table* t) noexcept +{ + t->bind_ptr(); +} + +inline void LangBindHelper::set_mixed_subtable(Table& parent, size_t col_ndx, size_t row_ndx, const Table& source) +{ + parent.set_mixed_subtable(col_ndx, row_ndx, &source); +} + +inline const LinkViewRef& LangBindHelper::get_linklist_ptr(Row& row, size_t col_ndx) +{ + LinkViewRef* link_view = new LinkViewRef(row.get_linklist(col_ndx)); + return *link_view; +} + +inline void LangBindHelper::unbind_linklist_ptr(const LinkViewRef& link_view) +{ + delete (&link_view); +} + +inline void LangBindHelper::advance_read(SharedGroup& sg, VersionID version) +{ + using sgf = _impl::SharedGroupFriend; + _impl::NullInstructionObserver* observer = nullptr; + sgf::advance_read(sg, observer, version); // Throws +} + +template +inline void LangBindHelper::advance_read(SharedGroup& sg, O&& observer, VersionID version) +{ + using sgf = _impl::SharedGroupFriend; + sgf::advance_read(sg, &observer, version); // Throws +} + +inline void LangBindHelper::promote_to_write(SharedGroup& sg) +{ + using sgf = _impl::SharedGroupFriend; + _impl::NullInstructionObserver* observer = nullptr; + sgf::promote_to_write(sg, observer); // Throws +} + +template +inline void LangBindHelper::promote_to_write(SharedGroup& sg, O&& observer) +{ + using sgf = _impl::SharedGroupFriend; + sgf::promote_to_write(sg, &observer); // Throws +} + +inline SharedGroup::version_type LangBindHelper::commit_and_continue_as_read(SharedGroup& sg) +{ + using sgf = _impl::SharedGroupFriend; + return sgf::commit_and_continue_as_read(sg); // Throws +} + +inline void LangBindHelper::rollback_and_continue_as_read(SharedGroup& sg) +{ + using sgf = _impl::SharedGroupFriend; + _impl::NullInstructionObserver* observer = nullptr; + sgf::rollback_and_continue_as_read(sg, observer); // Throws +} + +template +inline void LangBindHelper::rollback_and_continue_as_read(SharedGroup& sg, O&& observer) +{ + using sgf = _impl::SharedGroupFriend; + sgf::rollback_and_continue_as_read(sg, &observer); // Throws +} + +inline SharedGroup::version_type LangBindHelper::get_version_of_latest_snapshot(SharedGroup& sg) +{ + using sgf = _impl::SharedGroupFriend; + return sgf::get_version_of_latest_snapshot(sg); // Throws +} + +} // namespace realm + +#endif // REALM_LANG_BIND_HELPER_HPP diff --git a/!main project/Pods/Realm/include/core/realm/link_view.hpp b/!main project/Pods/Realm/include/core/realm/link_view.hpp new file mode 100644 index 0000000..14be525 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/link_view.hpp @@ -0,0 +1,397 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_LINK_VIEW_HPP +#define REALM_LINK_VIEW_HPP + +#include +#include +#include +#include + +namespace realm { + +class LinkListColumn; + +namespace _impl { +class LinkListFriend; +class TransactLogConvenientEncoder; +} + + +/// The effect of calling most of the link list functions on a detached accessor +/// is unspecified and may lead to general corruption, or even a crash. The +/// exceptions are is_attached() and the destructor. +/// +/// FIXME: Rename this class to `LinkList`. +class LinkView : public RowIndexes, public std::enable_shared_from_this { +public: + ~LinkView() noexcept; + bool is_attached() const noexcept; + + /// This method will return true if the LinkView is detached (no assert). + bool is_empty() const noexcept; + + /// This method will return 0 if the LinkView is detached (no assert). + size_t size() const noexcept override; + + bool operator==(const LinkView&) const noexcept; + bool operator!=(const LinkView&) const noexcept; + + // Getting links + Table::ConstRowExpr operator[](size_t link_ndx) const noexcept; + Table::RowExpr operator[](size_t link_ndx) noexcept; + Table::ConstRowExpr get(size_t link_ndx) const noexcept; + Table::RowExpr get(size_t link_ndx) noexcept; + + // Modifiers + void add(size_t target_row_ndx); + void insert(size_t link_ndx, size_t target_row_ndx); + void set(size_t link_ndx, size_t target_row_ndx); + /// Move the link at \a from_ndx such that it ends up at \a to_ndx. Other + /// links are shifted as necessary in such a way that their order is + /// preserved. + /// + /// Note that \a to_ndx is the desired final index of the moved link, + /// therefore, `move(1,1)` is a no-op, while `move(1,2)` moves the link at + /// index 1 by one position, such that it ends up at index 2. A side-effect + /// of that, is that the link, that was originally at index 2, is moved to + /// index 1. + void move(size_t from_ndx, size_t to_ndx); + void swap(size_t link1_ndx, size_t link2_ndx); + void remove(size_t link_ndx); + void clear(); + + void sort(size_t column, bool ascending = true); + void sort(SortDescriptor&& order); + + TableView get_sorted_view(size_t column_index, bool ascending = true) const; + TableView get_sorted_view(SortDescriptor order) const; + + /// Remove the target row of the specified link from the target table. This + /// also removes the specified link from this link list, and any other link + /// pointing to that row. This is merely a shorthand for + /// `get_target_table.move_last_over(get(link_ndx))`. + void remove_target_row(size_t link_ndx); + + /// Remove all target rows pointed to by links in this link list, and clear + /// this link list. + void remove_all_target_rows(); + + /// Search this list for a link to the specified target table row (specified + /// by its index in the target table). If found, the index of the link to + /// that row within this list is returned, otherwise `realm::not_found` is + /// returned. + size_t find(size_t target_row_ndx, size_t start = 0) const noexcept; + + const ColumnBase& get_column_base(size_t index) + const override; // FIXME: `ColumnBase` is not part of the public API, so this function must be made private. + const Table& get_origin_table() const noexcept; + Table& get_origin_table() noexcept; + + size_t get_origin_row_index() const noexcept; + + const Table& get_target_table() const noexcept; + Table& get_target_table() noexcept; + + // No-op because LinkViews are always kept in sync. + uint_fast64_t sync_if_needed() const override; + bool is_in_sync() const override + { + return true; + } + +private: + struct ctor_cookie { + }; + + TableRef m_origin_table; + LinkListColumn* m_origin_column; + + using HandoverPatch = LinkViewHandoverPatch; + static void generate_patch(const ConstLinkViewRef& ref, std::unique_ptr& patch); + static LinkViewRef create_from_and_consume_patch(std::unique_ptr& patch, Group& group); + + void detach(); + void set_origin_row_index(size_t row_ndx) noexcept; + + void do_insert(size_t link_ndx, size_t target_row_ndx); + size_t do_set(size_t link_ndx, size_t target_row_ndx); + size_t do_remove(size_t link_ndx); + void do_clear(bool broken_reciprocal_backlinks); + + void do_nullify_link(size_t old_target_row_ndx); + void do_update_link(size_t old_target_row_ndx, size_t new_target_row_ndx); + void do_swap_link(size_t target_row_ndx_1, size_t target_row_ndx_2); + + void refresh_accessor_tree(size_t new_row_ndx) noexcept; + + void update_from_parent(size_t old_baseline) noexcept; + + Replication* get_repl() noexcept; + void repl_unselect() noexcept; + friend class _impl::TransactLogConvenientEncoder; + +#ifdef REALM_DEBUG + void verify(size_t row_ndx) const; +#endif + // allocate using make_shared: + static std::shared_ptr create(Table* origin_table, LinkListColumn&, size_t row_ndx); + static std::shared_ptr create_detached(); + + friend class _impl::LinkListFriend; + friend class LinkListColumn; + friend class LangBindHelper; + friend class SharedGroup; + friend class Query; + friend class TableViewBase; + + // must be public for use by make_shared, but cannot be called from outside, + // because ctor_cookie is private +public: + LinkView(const ctor_cookie&, Table* origin_table, LinkListColumn&, size_t row_ndx); + LinkView(const ctor_cookie&); +}; + + +// Implementation + +inline LinkView::LinkView(const ctor_cookie&, Table* origin_table, LinkListColumn& column, size_t row_ndx) + : RowIndexes(IntegerColumn::unattached_root_tag(), column.get_alloc()) // Throws + , m_origin_table(origin_table->get_table_ref()) + , m_origin_column(&column) +{ + m_row_indexes.set_parent(m_origin_column, row_ndx); + m_row_indexes.init_from_parent(); +} + +// create a detached LinkView. Only partially initialized, as it will never be used for +// anything, but indicating that it is detached. +inline LinkView::LinkView(const ctor_cookie&) + : RowIndexes(IntegerColumn::unattached_root_tag(), Allocator::get_default()) // Throws + , m_origin_table(TableRef()) + , m_origin_column(nullptr) +{ +} + +inline std::shared_ptr LinkView::create(Table* origin_table, LinkListColumn& column, size_t row_ndx) +{ + return std::make_shared(ctor_cookie(), origin_table, column, row_ndx); +} + +inline std::shared_ptr LinkView::create_detached() +{ + return std::make_shared(ctor_cookie()); +} + +inline LinkView::~LinkView() noexcept +{ + if (is_attached()) { + repl_unselect(); + m_origin_column->unregister_linkview(); + } +} + +inline void LinkView::detach() +{ + REALM_ASSERT(is_attached()); + repl_unselect(); + m_origin_table.reset(); + m_row_indexes.detach(); +} + +inline bool LinkView::is_attached() const noexcept +{ + return static_cast(m_origin_table); +} + +inline bool LinkView::is_empty() const noexcept +{ + if (!is_attached()) + return true; + + if (!m_row_indexes.is_attached()) + return true; + + return m_row_indexes.is_empty(); +} + +inline size_t LinkView::size() const noexcept +{ + if (!is_attached()) + return 0; + + if (!m_row_indexes.is_attached()) + return 0; + + return m_row_indexes.size(); +} + +inline bool LinkView::operator==(const LinkView& link_list) const noexcept +{ + Table& target_table_1 = m_origin_column->get_target_table(); + Table& target_table_2 = link_list.m_origin_column->get_target_table(); + if (target_table_1.get_index_in_group() != target_table_2.get_index_in_group()) + return false; + if (!m_row_indexes.is_attached() || m_row_indexes.is_empty()) { + return !link_list.m_row_indexes.is_attached() || link_list.m_row_indexes.is_empty(); + } + return link_list.m_row_indexes.is_attached() && m_row_indexes.compare(link_list.m_row_indexes); +} + +inline bool LinkView::operator!=(const LinkView& link_list) const noexcept +{ + return !(*this == link_list); +} + +inline Table::ConstRowExpr LinkView::get(size_t link_ndx) const noexcept +{ + return const_cast(this)->get(link_ndx); +} + +inline Table::RowExpr LinkView::get(size_t link_ndx) noexcept +{ + REALM_ASSERT(is_attached()); + REALM_ASSERT(m_row_indexes.is_attached()); + REALM_ASSERT_3(link_ndx, <, m_row_indexes.size()); + + Table& target_table = m_origin_column->get_target_table(); + size_t target_row_ndx = to_size_t(m_row_indexes.get(link_ndx)); + return target_table[target_row_ndx]; +} + +inline Table::ConstRowExpr LinkView::operator[](size_t link_ndx) const noexcept +{ + return get(link_ndx); +} + +inline Table::RowExpr LinkView::operator[](size_t link_ndx) noexcept +{ + return get(link_ndx); +} + +inline void LinkView::add(size_t target_row_ndx) +{ + REALM_ASSERT(is_attached()); + size_t ins_pos = (m_row_indexes.is_attached()) ? m_row_indexes.size() : 0; + insert(ins_pos, target_row_ndx); +} + +inline size_t LinkView::find(size_t target_row_ndx, size_t start) const noexcept +{ + REALM_ASSERT(is_attached()); + REALM_ASSERT_3(target_row_ndx, <, m_origin_column->get_target_table().size()); + REALM_ASSERT_3(start, <=, size()); + + if (!m_row_indexes.is_attached()) + return not_found; + + return m_row_indexes.find_first(target_row_ndx, start); +} + +inline const ColumnBase& LinkView::get_column_base(size_t index) const +{ + return get_target_table().get_column_base(index); +} + +inline const Table& LinkView::get_origin_table() const noexcept +{ + return *m_origin_table; +} + +inline Table& LinkView::get_origin_table() noexcept +{ + return *m_origin_table; +} + +inline size_t LinkView::get_origin_row_index() const noexcept +{ + REALM_ASSERT(is_attached()); + return m_row_indexes.get_root_array()->get_ndx_in_parent(); +} + +inline void LinkView::set_origin_row_index(size_t row_ndx) noexcept +{ + REALM_ASSERT(is_attached()); + m_row_indexes.get_root_array()->set_ndx_in_parent(row_ndx); +} + +inline const Table& LinkView::get_target_table() const noexcept +{ + return m_origin_column->get_target_table(); +} + +inline Table& LinkView::get_target_table() noexcept +{ + return m_origin_column->get_target_table(); +} + +inline void LinkView::refresh_accessor_tree(size_t new_row_ndx) noexcept +{ + set_origin_row_index(new_row_ndx); + m_row_indexes.init_from_parent(); +} + +inline void LinkView::update_from_parent(size_t old_baseline) noexcept +{ + if (m_row_indexes.is_attached()) + m_row_indexes.update_from_parent(old_baseline); +} + +inline Replication* LinkView::get_repl() noexcept +{ + typedef _impl::TableFriend tf; + return tf::get_repl(*m_origin_table); +} + + +// The purpose of this class is to give internal access to some, but not all of +// the non-public parts of LinkView. +class _impl::LinkListFriend { +public: + static void do_set(LinkView& list, size_t link_ndx, size_t target_row_ndx) + { + list.do_set(link_ndx, target_row_ndx); + } + + static void do_remove(LinkView& list, size_t link_ndx) + { + list.do_remove(link_ndx); + } + + static void do_clear(LinkView& list) + { + bool broken_reciprocal_backlinks = false; + list.do_clear(broken_reciprocal_backlinks); + } + + static void do_insert(LinkView& list, size_t link_ndx, size_t target_row_ndx) + { + list.do_insert(link_ndx, target_row_ndx); + } + + static const LinkListColumn& get_origin_column(const LinkView& list) + { + REALM_ASSERT(list.is_attached()); + return *list.m_origin_column; + } +}; + +} // namespace realm + +#endif // REALM_LINK_VIEW_HPP diff --git a/!main project/Pods/Realm/include/core/realm/link_view_fwd.hpp b/!main project/Pods/Realm/include/core/realm/link_view_fwd.hpp new file mode 100644 index 0000000..cba8801 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/link_view_fwd.hpp @@ -0,0 +1,32 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_LINK_VIEW_FWD_HPP +#define REALM_LINK_VIEW_FWD_HPP + +#include + +namespace realm { + +class LinkView; +using LinkViewRef = std::shared_ptr; +using ConstLinkViewRef = std::shared_ptr; + +} // namespace realm + +#endif // REALM_LINK_VIEW_FWD_HPP diff --git a/!main project/Pods/Realm/include/core/realm/metrics/metric_timer.hpp b/!main project/Pods/Realm/include/core/realm/metrics/metric_timer.hpp new file mode 100644 index 0000000..27ff716 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/metrics/metric_timer.hpp @@ -0,0 +1,100 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_METRIC_TIMER_HPP +#define REALM_METRIC_TIMER_HPP + +#include + +#include +#include +#include + +namespace realm { +namespace metrics { + +using nanosecond_storage_t = int64_t; + +class MetricTimerResult +{ +public: + MetricTimerResult(); + ~MetricTimerResult(); + nanosecond_storage_t get_elapsed_nanoseconds() const; + void report_nanoseconds(nanosecond_storage_t time); +protected: + nanosecond_storage_t m_elapsed_nanoseconds; +}; + + +class MetricTimer { +public: + MetricTimer(std::shared_ptr destination = nullptr); + ~MetricTimer(); + + void reset(); + + /// Returns elapsed time in nanoseconds since last call to reset(). + nanosecond_storage_t get_elapsed_nanoseconds() const; + /// Same as get_elapsed_time(). + operator nanosecond_storage_t() const; + + /// Format the elapsed time on the form 0h00m, 00m00s, 00.00s, or + /// 000.0ms depending on magnitude. + static void format(nanosecond_storage_t nanoseconds, std::ostream&); + + static std::string format(nanosecond_storage_t nanoseconds); + +private: + using clock_type = std::chrono::high_resolution_clock; + using time_point = std::chrono::time_point; + time_point m_start; + time_point m_paused_at; + std::shared_ptr m_dest; + + time_point get_timer_ticks() const; + nanosecond_storage_t calc_elapsed_nanoseconds(time_point begin, time_point end) const; +}; + + +inline void MetricTimer::reset() +{ + m_start = get_timer_ticks(); +} + +inline nanosecond_storage_t MetricTimer::get_elapsed_nanoseconds() const +{ + return calc_elapsed_nanoseconds(m_start, get_timer_ticks()); +} + +inline MetricTimer::operator nanosecond_storage_t() const +{ + return get_elapsed_nanoseconds(); +} + +inline std::ostream& operator<<(std::ostream& out, const MetricTimer& timer) +{ + MetricTimer::format(timer, out); + return out; +} + + +} // namespace metrics +} // namespace realm + +#endif // REALM_METRIC_TIMER_HPP diff --git a/!main project/Pods/Realm/include/core/realm/metrics/metrics.hpp b/!main project/Pods/Realm/include/core/realm/metrics/metrics.hpp new file mode 100644 index 0000000..27a526d --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/metrics/metrics.hpp @@ -0,0 +1,76 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_METRICS_HPP +#define REALM_METRICS_HPP + +#include + +#include +#include +#include +#include "realm/util/fixed_size_buffer.hpp" + +namespace realm { + +class Group; + +namespace metrics { + +class Metrics { +public: + Metrics(size_t max_history_size); + ~Metrics() noexcept; + size_t num_query_metrics() const; + size_t num_transaction_metrics() const; + + void add_query(QueryInfo info); + void add_transaction(TransactionInfo info); + + void start_read_transaction(); + void start_write_transaction(); + void end_read_transaction(size_t total_size, size_t free_space, size_t num_objects, size_t num_versions, + size_t num_decrypted_pages); + void end_write_transaction(size_t total_size, size_t free_space, size_t num_objects, size_t num_versions, + size_t num_decrypted_pages); + static std::unique_ptr report_fsync_time(const Group& g); + static std::unique_ptr report_write_time(const Group& g); + + using QueryInfoList = util::FixedSizeBuffer; + using TransactionInfoList = util::FixedSizeBuffer; + + // Get the list of metric objects tracked since the last take + std::unique_ptr take_queries(); + std::unique_ptr take_transactions(); +private: + std::unique_ptr m_query_info; + std::unique_ptr m_transaction_info; + + std::unique_ptr m_pending_read; + std::unique_ptr m_pending_write; + + size_t m_max_num_queries; + size_t m_max_num_transactions; +}; + +} // namespace metrics +} // namespace realm + + + +#endif // REALM_METRICS_HPP diff --git a/!main project/Pods/Realm/include/core/realm/metrics/query_info.hpp b/!main project/Pods/Realm/include/core/realm/metrics/query_info.hpp new file mode 100644 index 0000000..54b78d8 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/metrics/query_info.hpp @@ -0,0 +1,71 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_QUERY_INFO_HPP +#define REALM_QUERY_INFO_HPP + +#include +#include +#include + +#include +#include +#include + +namespace realm { + +class Query; // forward declare in namespace realm + +namespace metrics { + +class QueryInfo { +public: + + enum QueryType { + type_Find, + type_FindAll, + type_Count, + type_Sum, + type_Average, + type_Maximum, + type_Minimum, + type_Invalid + }; + + QueryInfo(const Query* query, QueryType type); + ~QueryInfo() noexcept; + + std::string get_description() const; + std::string get_table_name() const; + QueryType get_type() const; + nanosecond_storage_t get_query_time_nanoseconds() const; + + static std::unique_ptr track(const Query* query, QueryType type); + static QueryType type_from_action(Action action); + +private: + std::string m_description; + std::string m_table_name; + QueryType m_type; + std::shared_ptr m_query_time; +}; + +} // namespace metrics +} // namespace realm + +#endif // REALM_QUERY_INFO_HPP diff --git a/!main project/Pods/Realm/include/core/realm/metrics/transaction_info.hpp b/!main project/Pods/Realm/include/core/realm/metrics/transaction_info.hpp new file mode 100644 index 0000000..efcc2a6 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/metrics/transaction_info.hpp @@ -0,0 +1,76 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_TRANSACTION_INFO_HPP +#define REALM_TRANSACTION_INFO_HPP + +#include +#include + +#include +#include + +namespace realm { +namespace metrics { + +class Metrics; + +class TransactionInfo { +public: + enum TransactionType { + read_transaction, + write_transaction + }; + TransactionInfo(TransactionType type); + TransactionInfo(const TransactionInfo&) = default; + ~TransactionInfo() noexcept; + + TransactionType get_transaction_type() const; + // the transaction time is a total amount which includes fsync_time + write_time + user_time + nanosecond_storage_t get_transaction_time_nanoseconds() const; + nanosecond_storage_t get_fsync_time_nanoseconds() const; + nanosecond_storage_t get_write_time_nanoseconds() const; + size_t get_disk_size() const; + size_t get_free_space() const; + size_t get_total_objects() const; + size_t get_num_available_versions() const; + size_t get_num_decrypted_pages() const; + +private: + MetricTimerResult m_transaction_time; + std::shared_ptr m_fsync_time; + std::shared_ptr m_write_time; + MetricTimer m_transact_timer; + + size_t m_realm_disk_size; + size_t m_realm_free_space; + size_t m_total_objects; + TransactionType m_type; + size_t m_num_versions; + size_t m_num_decrypted_pages; + + friend class Metrics; + void update_stats(size_t disk_size, size_t free_space, size_t total_objects, size_t available_versions, + size_t num_decrypted_pages); + void finish_timer(); +}; + +} // namespace metrics +} // namespace realm + +#endif // REALM_TRANSACTION_INFO_HPP diff --git a/!main project/Pods/Realm/include/core/realm/mixed.hpp b/!main project/Pods/Realm/include/core/realm/mixed.hpp new file mode 100644 index 0000000..243ed45 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/mixed.hpp @@ -0,0 +1,732 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_MIXED_HPP +#define REALM_MIXED_HPP + +#include // int64_t - not part of C++03, not even required by C++11 (see C++11 section 18.4.1) + +#include // size_t +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace realm { + + +/// This class represents a polymorphic Realm value. +/// +/// At any particular moment an instance of this class stores a +/// definite value of a definite type. If, for instance, that is an +/// integer value, you may call get_int() to extract that value. You +/// may call get_type() to discover what type of value is currently +/// stored. Calling get_int() on an instance that does not store an +/// integer, has undefined behavior, and likewise for all the other +/// types that can be stored. +/// +/// It is crucial to understand that the act of extracting a value of +/// a particular type requires definite knowledge about the stored +/// type. Calling a getter method for any particular type, that is not +/// the same type as the stored value, has undefined behavior. +/// +/// While values of numeric types are contained directly in a Mixed +/// instance, character and binary data are merely referenced. A Mixed +/// instance never owns the referenced data, nor does it in any other +/// way attempt to manage its lifetime. +/// +/// For compatibility with C style strings, when a string (character +/// data) is stored in a Realm database, it is always followed by a +/// terminating null character. This is also true when strings are +/// stored in a mixed type column. This means that in the following +/// code, if the 'mixed' value of the 8th row stores a string, then \c +/// c_str will always point to a null-terminated string: +/// +/// \code{.cpp} +/// +/// const char* c_str = my_table[7].mixed.data(); // Always null-terminated +/// +/// \endcode +/// +/// Note that this assumption does not hold in general for strings in +/// instances of Mixed. Indeed there is nothing stopping you from +/// constructing a new Mixed instance that refers to a string without +/// a terminating null character. +/// +/// At the present time no soultion has been found that would allow +/// for a Mixed instance to directly store a reference to a table. The +/// problem is roughly as follows: From most points of view, the +/// desirable thing to do, would be to store the table reference in a +/// Mixed instance as a plain pointer without any ownership +/// semantics. This would have no negative impact on the performance +/// of copying and destroying Mixed instances, and it would serve just +/// fine for passing a table as argument when setting the value of an +/// entry in a mixed column. In that case a copy of the referenced +/// table would be inserted into the mixed column. +/// +/// On the other hand, when retrieving a table reference from a mixed +/// column, storing it as a plain pointer in a Mixed instance is no +/// longer an acceptable option. The complex rules for managing the +/// lifetime of a Table instance, that represents a subtable, +/// necessitates the use of a "smart pointer" such as +/// TableRef. Enhancing the Mixed class to be able to act as a +/// TableRef would be possible, but would also lead to several new +/// problems. One problem is the risk of a Mixed instance outliving a +/// stack allocated Table instance that it references. This would be a +/// fatal error. Another problem is the impact that the nontrivial +/// table reference has on the performance of copying and destroying +/// Mixed instances. +/// +/// \sa StringData +class Mixed { +public: + Mixed() noexcept; + + Mixed(bool) noexcept; + Mixed(int64_t) noexcept; + Mixed(float) noexcept; + Mixed(double) noexcept; + Mixed(StringData) noexcept; + Mixed(BinaryData) noexcept; + Mixed(OldDateTime) noexcept; + Mixed(Timestamp) noexcept; + + // These are shortcuts for Mixed(StringData(c_str)), and are + // needed to avoid unwanted implicit conversion of char* to bool. + Mixed(char* c_str) noexcept + { + set_string(c_str); + } + Mixed(const char* c_str) noexcept + { + set_string(c_str); + } + + struct subtable_tag { + }; + Mixed(subtable_tag) noexcept + : m_type(type_Table) + { + } + + ~Mixed() noexcept + { + } + + DataType get_type() const noexcept + { + return m_type; + } + + template + T get() const noexcept; + + int64_t get_int() const noexcept; + bool get_bool() const noexcept; + float get_float() const noexcept; + double get_double() const noexcept; + StringData get_string() const noexcept; + BinaryData get_binary() const noexcept; + OldDateTime get_olddatetime() const noexcept; + Timestamp get_timestamp() const noexcept; + + void set_int(int64_t) noexcept; + void set_bool(bool) noexcept; + void set_float(float) noexcept; + void set_double(double) noexcept; + void set_string(StringData) noexcept; + void set_binary(BinaryData) noexcept; + void set_binary(const char* data, size_t size) noexcept; + void set_olddatetime(OldDateTime) noexcept; + void set_timestamp(Timestamp) noexcept; + + template + friend std::basic_ostream& operator<<(std::basic_ostream&, const Mixed&); + +private: + DataType m_type; + union { + int64_t m_int; + bool m_bool; + float m_float; + double m_double; + const char* m_data; + int_fast64_t m_date; + Timestamp m_timestamp; + }; + size_t m_size = 0; +}; + +// Note: We cannot compare two mixed values, since when the type of +// both is type_Table, we would have to compare the two tables, but +// the mixed values do not provide access to those tables. + +// Note: The mixed values are specified as Wrap. If they were +// not, these operators would apply to simple comparisons, such as int +// vs int64_t, and cause ambiguity. This is because the constructors +// of Mixed are not explicit. + +// Compare mixed with integer +template +bool operator==(Wrap, const T&) noexcept; +template +bool operator!=(Wrap, const T&) noexcept; +template +bool operator==(const T&, Wrap) noexcept; +template +bool operator!=(const T&, Wrap) noexcept; + +// Compare mixed with boolean +bool operator==(Wrap, bool) noexcept; +bool operator!=(Wrap, bool) noexcept; +bool operator==(bool, Wrap) noexcept; +bool operator!=(bool, Wrap) noexcept; + +// Compare mixed with float +bool operator==(Wrap, float); +bool operator!=(Wrap, float); +bool operator==(float, Wrap); +bool operator!=(float, Wrap); + +// Compare mixed with double +bool operator==(Wrap, double); +bool operator!=(Wrap, double); +bool operator==(double, Wrap); +bool operator!=(double, Wrap); + +// Compare mixed with string +bool operator==(Wrap, StringData) noexcept; +bool operator!=(Wrap, StringData) noexcept; +bool operator==(StringData, Wrap) noexcept; +bool operator!=(StringData, Wrap) noexcept; +bool operator==(Wrap, const char* c_str) noexcept; +bool operator!=(Wrap, const char* c_str) noexcept; +bool operator==(const char* c_str, Wrap) noexcept; +bool operator!=(const char* c_str, Wrap) noexcept; +bool operator==(Wrap, char* c_str) noexcept; +bool operator!=(Wrap, char* c_str) noexcept; +bool operator==(char* c_str, Wrap) noexcept; +bool operator!=(char* c_str, Wrap) noexcept; + +// Compare mixed with binary data +bool operator==(Wrap, BinaryData) noexcept; +bool operator!=(Wrap, BinaryData) noexcept; +bool operator==(BinaryData, Wrap) noexcept; +bool operator!=(BinaryData, Wrap) noexcept; + +// Compare mixed with date +bool operator==(Wrap, OldDateTime) noexcept; +bool operator!=(Wrap, OldDateTime) noexcept; +bool operator==(OldDateTime, Wrap) noexcept; +bool operator!=(OldDateTime, Wrap) noexcept; + +// Compare mixed with Timestamp +bool operator==(Wrap, Timestamp) noexcept; +bool operator!=(Wrap, Timestamp) noexcept; +bool operator==(Timestamp, Wrap) noexcept; +bool operator!=(Timestamp, Wrap) noexcept; + +// Implementation: + +inline Mixed::Mixed() noexcept +{ + m_type = type_Int; + m_int = 0; +} + +inline Mixed::Mixed(int64_t v) noexcept +{ + m_type = type_Int; + m_int = v; +} + +inline Mixed::Mixed(bool v) noexcept +{ + m_type = type_Bool; + m_bool = v; +} + +inline Mixed::Mixed(float v) noexcept +{ + m_type = type_Float; + m_float = v; +} + +inline Mixed::Mixed(double v) noexcept +{ + m_type = type_Double; + m_double = v; +} + +inline Mixed::Mixed(StringData v) noexcept +{ + m_type = type_String; + m_data = v.data(); + m_size = v.size(); +} + +inline Mixed::Mixed(BinaryData v) noexcept +{ + m_type = type_Binary; + m_data = v.data(); + m_size = v.size(); +} + +inline Mixed::Mixed(OldDateTime v) noexcept +{ + m_type = type_OldDateTime; + m_date = v.get_olddatetime(); +} + +inline Mixed::Mixed(Timestamp v) noexcept +{ + m_type = type_Timestamp; + m_timestamp = v; +} + +inline int64_t Mixed::get_int() const noexcept +{ + REALM_ASSERT(m_type == type_Int); + return m_int; +} + +inline bool Mixed::get_bool() const noexcept +{ + REALM_ASSERT(m_type == type_Bool); + return m_bool; +} + +inline float Mixed::get_float() const noexcept +{ + REALM_ASSERT(m_type == type_Float); + return m_float; +} + +inline double Mixed::get_double() const noexcept +{ + REALM_ASSERT(m_type == type_Double); + return m_double; +} + +inline StringData Mixed::get_string() const noexcept +{ + REALM_ASSERT(m_type == type_String); + return StringData(m_data, m_size); +} + +inline BinaryData Mixed::get_binary() const noexcept +{ + REALM_ASSERT(m_type == type_Binary); + return BinaryData(m_data, m_size); +} + +inline OldDateTime Mixed::get_olddatetime() const noexcept +{ + REALM_ASSERT(m_type == type_OldDateTime); + return m_date; +} + +inline Timestamp Mixed::get_timestamp() const noexcept +{ + REALM_ASSERT(m_type == type_Timestamp); + return m_timestamp; +} + +template <> +inline int64_t Mixed::get() const noexcept +{ + return get_int(); +} + +template <> +inline bool Mixed::get() const noexcept +{ + return get_bool(); +} + +template <> +inline float Mixed::get() const noexcept +{ + return get_float(); +} + +template <> +inline double Mixed::get() const noexcept +{ + return get_double(); +} + +template <> +inline OldDateTime Mixed::get() const noexcept +{ + return get_olddatetime(); +} + +template <> +inline StringData Mixed::get() const noexcept +{ + return get_string(); +} + +template <> +inline BinaryData Mixed::get() const noexcept +{ + return get_binary(); +} + +template <> +inline Timestamp Mixed::get() const noexcept +{ + return get_timestamp(); +} + + +inline void Mixed::set_int(int64_t v) noexcept +{ + m_type = type_Int; + m_int = v; +} + +inline void Mixed::set_bool(bool v) noexcept +{ + m_type = type_Bool; + m_bool = v; +} + +inline void Mixed::set_float(float v) noexcept +{ + m_type = type_Float; + m_float = v; +} + +inline void Mixed::set_double(double v) noexcept +{ + m_type = type_Double; + m_double = v; +} + +inline void Mixed::set_string(StringData v) noexcept +{ + m_type = type_String; + m_data = v.data(); + m_size = v.size(); +} + +inline void Mixed::set_binary(BinaryData v) noexcept +{ + set_binary(v.data(), v.size()); +} + +inline void Mixed::set_binary(const char* data, size_t size) noexcept +{ + m_type = type_Binary; + m_data = data; + m_size = size; +} + +inline void Mixed::set_olddatetime(OldDateTime v) noexcept +{ + m_type = type_OldDateTime; + m_date = v.get_olddatetime(); +} + +inline void Mixed::set_timestamp(Timestamp v) noexcept +{ + m_type = type_Timestamp; + m_timestamp = v; +} + +// LCOV_EXCL_START +template +inline std::basic_ostream& operator<<(std::basic_ostream& out, const Mixed& m) +{ + out << "Mixed("; + switch (m.m_type) { + case type_Int: + out << m.m_int; + break; + case type_Bool: + out << m.m_bool; + break; + case type_Float: + out << m.m_float; + break; + case type_Double: + out << m.m_double; + break; + case type_String: + out << StringData(m.m_data, m.m_size); + break; + case type_Binary: + out << BinaryData(m.m_data, m.m_size); + break; + case type_OldDateTime: + out << OldDateTime(m.m_date); + break; + case type_Timestamp: + out << Timestamp(m.m_timestamp); + break; + case type_Table: + out << "subtable"; + break; + case type_Mixed: + case type_Link: + case type_LinkList: + REALM_ASSERT(false); + } + out << ")"; + return out; +} +// LCOV_EXCL_STOP + + +// Compare mixed with integer + +template +inline bool operator==(Wrap a, const T& b) noexcept +{ + return Mixed(a).get_type() == type_Int && Mixed(a).get_int() == b; +} + +template +inline bool operator!=(Wrap a, const T& b) noexcept +{ + return Mixed(a).get_type() != type_Int || Mixed(a).get_int() != b; +} + +template +inline bool operator==(const T& a, Wrap b) noexcept +{ + return type_Int == Mixed(b).get_type() && a == Mixed(b).get_int(); +} + +template +inline bool operator!=(const T& a, Wrap b) noexcept +{ + return type_Int != Mixed(b).get_type() || a != Mixed(b).get_int(); +} + + +// Compare mixed with boolean + +inline bool operator==(Wrap a, bool b) noexcept +{ + return Mixed(a).get_type() == type_Bool && Mixed(a).get_bool() == b; +} + +inline bool operator!=(Wrap a, bool b) noexcept +{ + return Mixed(a).get_type() != type_Bool || Mixed(a).get_bool() != b; +} + +inline bool operator==(bool a, Wrap b) noexcept +{ + return type_Bool == Mixed(b).get_type() && a == Mixed(b).get_bool(); +} + +inline bool operator!=(bool a, Wrap b) noexcept +{ + return type_Bool != Mixed(b).get_type() || a != Mixed(b).get_bool(); +} + + +// Compare mixed with float + +inline bool operator==(Wrap a, float b) +{ + return Mixed(a).get_type() == type_Float && Mixed(a).get_float() == b; +} + +inline bool operator!=(Wrap a, float b) +{ + return Mixed(a).get_type() != type_Float || Mixed(a).get_float() != b; +} + +inline bool operator==(float a, Wrap b) +{ + return type_Float == Mixed(b).get_type() && a == Mixed(b).get_float(); +} + +inline bool operator!=(float a, Wrap b) +{ + return type_Float != Mixed(b).get_type() || a != Mixed(b).get_float(); +} + + +// Compare mixed with double + +inline bool operator==(Wrap a, double b) +{ + return Mixed(a).get_type() == type_Double && Mixed(a).get_double() == b; +} + +inline bool operator!=(Wrap a, double b) +{ + return Mixed(a).get_type() != type_Double || Mixed(a).get_double() != b; +} + +inline bool operator==(double a, Wrap b) +{ + return type_Double == Mixed(b).get_type() && a == Mixed(b).get_double(); +} + +inline bool operator!=(double a, Wrap b) +{ + return type_Double != Mixed(b).get_type() || a != Mixed(b).get_double(); +} + + +// Compare mixed with string + +inline bool operator==(Wrap a, StringData b) noexcept +{ + return Mixed(a).get_type() == type_String && Mixed(a).get_string() == b; +} + +inline bool operator!=(Wrap a, StringData b) noexcept +{ + return Mixed(a).get_type() != type_String || Mixed(a).get_string() != b; +} + +inline bool operator==(StringData a, Wrap b) noexcept +{ + return type_String == Mixed(b).get_type() && a == Mixed(b).get_string(); +} + +inline bool operator!=(StringData a, Wrap b) noexcept +{ + return type_String != Mixed(b).get_type() || a != Mixed(b).get_string(); +} + +inline bool operator==(Wrap a, const char* b) noexcept +{ + return a == StringData(b); +} + +inline bool operator!=(Wrap a, const char* b) noexcept +{ + return a != StringData(b); +} + +inline bool operator==(const char* a, Wrap b) noexcept +{ + return StringData(a) == b; +} + +inline bool operator!=(const char* a, Wrap b) noexcept +{ + return StringData(a) != b; +} + +inline bool operator==(Wrap a, char* b) noexcept +{ + return a == StringData(b); +} + +inline bool operator!=(Wrap a, char* b) noexcept +{ + return a != StringData(b); +} + +inline bool operator==(char* a, Wrap b) noexcept +{ + return StringData(a) == b; +} + +inline bool operator!=(char* a, Wrap b) noexcept +{ + return StringData(a) != b; +} + + +// Compare mixed with binary data + +inline bool operator==(Wrap a, BinaryData b) noexcept +{ + return Mixed(a).get_type() == type_Binary && Mixed(a).get_binary() == b; +} + +inline bool operator!=(Wrap a, BinaryData b) noexcept +{ + return Mixed(a).get_type() != type_Binary || Mixed(a).get_binary() != b; +} + +inline bool operator==(BinaryData a, Wrap b) noexcept +{ + return type_Binary == Mixed(b).get_type() && a == Mixed(b).get_binary(); +} + +inline bool operator!=(BinaryData a, Wrap b) noexcept +{ + return type_Binary != Mixed(b).get_type() || a != Mixed(b).get_binary(); +} + + +// Compare mixed with date + +inline bool operator==(Wrap a, OldDateTime b) noexcept +{ + return Mixed(a).get_type() == type_OldDateTime && OldDateTime(Mixed(a).get_olddatetime()) == b; +} + +inline bool operator!=(Wrap a, OldDateTime b) noexcept +{ + return Mixed(a).get_type() != type_OldDateTime || OldDateTime(Mixed(a).get_olddatetime()) != b; +} + +inline bool operator==(OldDateTime a, Wrap b) noexcept +{ + return type_OldDateTime == Mixed(b).get_type() && a == OldDateTime(Mixed(b).get_olddatetime()); +} + +inline bool operator!=(OldDateTime a, Wrap b) noexcept +{ + return type_OldDateTime != Mixed(b).get_type() || a != OldDateTime(Mixed(b).get_olddatetime()); +} + +// Compare mixed with Timestamp + +inline bool operator==(Wrap a, Timestamp b) noexcept +{ + return Mixed(a).get_type() == type_Timestamp && Timestamp(Mixed(a).get_timestamp()) == b; +} + +inline bool operator!=(Wrap a, Timestamp b) noexcept +{ + return Mixed(a).get_type() != type_Timestamp || Timestamp(Mixed(a).get_timestamp()) != b; +} + +inline bool operator==(Timestamp a, Wrap b) noexcept +{ + return type_Timestamp == Mixed(b).get_type() && a == Timestamp(Mixed(b).get_timestamp()); +} + +inline bool operator!=(Timestamp a, Wrap b) noexcept +{ + return type_Timestamp != Mixed(b).get_type() || a != Timestamp(Mixed(b).get_timestamp()); +} + + +} // namespace realm + +#endif // REALM_MIXED_HPP diff --git a/!main project/Pods/Realm/include/core/realm/null.hpp b/!main project/Pods/Realm/include/core/realm/null.hpp new file mode 100644 index 0000000..733e798 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/null.hpp @@ -0,0 +1,172 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_NULL_HPP +#define REALM_NULL_HPP + +#include +#include + +#include +#include +#include +#include + +namespace realm { + +/* +Represents null in Query, find(), get(), set(), etc. + +Float/Double: Realm can both store user-given NaNs and null. Any user-given signaling NaN is converted to +0x7fa00000 (if float) or 0x7ff4000000000000 (if double). Any user-given quiet NaN is converted to +0x7fc00000 (if float) or 0x7ff8000000000000 (if double). So Realm does not preserve the optional bits in +user-given NaNs. + +However, since both clang and gcc on x64 and ARM, and also Java on x64, return these bit patterns when +requesting NaNs, these will actually seem to roundtrip bit-exact for the end-user in most cases. + +If set_null() is called, a null is stored in form of the bit pattern 0xffffffff (if float) or +0xffffffffffffffff (if double). These are quiet NaNs. + +Executing a query that involves a float/double column that contains NaNs gives an undefined result. If +it contains signaling NaNs, it may throw an exception. + +Notes on IEEE: + +A NaN float is any bit pattern `s 11111111 S xxxxxxxxxxxxxxxxxxxxxx` where `s` and `x` are arbitrary, but at +least 1 `x` must be 1. If `S` is 1, it's a quiet NaN, else it's a signaling NaN. + +A NaN doubule is the same as above, but for `s eeeeeeeeeee S xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` + +The `S` bit is at position 22 (float) or 51 (double). +*/ + +struct null { + null() + { + } + operator int64_t() + { + throw(LogicError::type_mismatch); + } + template + operator util::Optional() + { + return util::none; + } + + template + bool operator==(const T&) const + { + REALM_ASSERT(false); + return false; + } + template + bool operator!=(const T&) const + { + REALM_ASSERT(false); + return false; + } + template + bool operator>(const T&) const + { + REALM_ASSERT(false); + return false; + } + template + bool operator>=(const T&) const + { + REALM_ASSERT(false); + return false; + } + template + bool operator<=(const T&) const + { + REALM_ASSERT(false); + return false; + } + template + bool operator<(const T&) const + { + REALM_ASSERT(false); + return false; + } + + /// Returns whether `v` bitwise equals the null bit-pattern + template + static bool is_null_float(T v) + { + T i = null::get_null_float(); + return std::memcmp(&i, &v, sizeof(T)) == 0; + } + + /// Returns the quiet NaNs that represent null for floats/doubles in Realm in stored payload. + template + static T get_null_float() + { + typename std::conditional::value, uint32_t, uint64_t>::type i; + int64_t double_nan = 0x7ff80000000000aa; + i = std::is_same::value ? 0x7fc000aa : static_cast(double_nan); + T d = type_punning(i); + REALM_ASSERT_DEBUG(std::isnan(d)); + REALM_ASSERT_DEBUG(!is_signaling(d)); + return d; + } + + /// Takes a NaN as argument and returns whether or not it's signaling + template + static bool is_signaling(T v) + { + REALM_ASSERT(std::isnan(static_cast(v))); + typename std::conditional::value, uint32_t, uint64_t>::type i; + size_t signal_bit = std::is_same::value ? 22 : 51; // If this bit is set, it's quiet + i = type_punning(v); + return !(i & (1ull << signal_bit)); + } + + /// Converts any signaling or quiet NaN to their their respective bit patterns that are used on x64 gcc+clang, + /// ARM clang and x64 Java. + template + static T to_realm(T v) + { + if (std::isnan(static_cast(v))) { + typename std::conditional::value, uint32_t, uint64_t>::type i; + if (std::is_same::value) { + i = is_signaling(v) ? 0x7fa00000 : 0x7fc00000; + } + else { + i = static_cast(is_signaling(v) ? 0x7ff4000000000000 : 0x7ff8000000000000); + } + return type_punning(i); + } + else { + return v; + } + } +}; + +template +OS& operator<<(OS& os, const null&) +{ + os << "(null)"; + return os; +} + +} // namespace realm + +#endif // REALM_NULL_HPP diff --git a/!main project/Pods/Realm/include/core/realm/olddatetime.hpp b/!main project/Pods/Realm/include/core/realm/olddatetime.hpp new file mode 100644 index 0000000..b662899 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/olddatetime.hpp @@ -0,0 +1,157 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_DATETIME_HPP +#define REALM_DATETIME_HPP + +#include +#include + +namespace realm { + + +class OldDateTime { +public: + OldDateTime() noexcept + : m_time(0) + { + } + + /// Construct from the number of seconds since Jan 1 00:00:00 UTC + /// 1970. + /// FIXME: See if we can make this private again. Required by query_expression.hpp + OldDateTime(int_fast64_t d) noexcept + : m_time(d) + { + } + + /// Return the time as seconds since Jan 1 00:00:00 UTC 1970. + int_fast64_t get_olddatetime() const noexcept + { + return m_time; + } + + friend bool operator==(const OldDateTime&, const OldDateTime&) noexcept; + friend bool operator!=(const OldDateTime&, const OldDateTime&) noexcept; + friend bool operator<(const OldDateTime&, const OldDateTime&) noexcept; + friend bool operator<=(const OldDateTime&, const OldDateTime&) noexcept; + friend bool operator>(const OldDateTime&, const OldDateTime&) noexcept; + friend bool operator>=(const OldDateTime&, const OldDateTime&) noexcept; + + /// Construct from broken down local time. + /// + /// \note This constructor uses std::mktime() to convert the + /// specified local time to seconds since the Epoch, that is, the + /// result depends on the current globally specified time zone + /// setting. + /// + /// \param year The year (the minimum valid value is 1970). + /// + /// \param month The month in the range [1, 12]. + /// + /// \param day The day of the month in the range [1, 31]. + /// + /// \param hours Hours since midnight in the range [0, 23]. + /// + /// \param minutes Minutes after the hour in the range [0, 59]. + /// + /// \param seconds Seconds after the minute in the range [0, + /// 60]. Note that the range allows for leap seconds. + OldDateTime(int year, int month, int day, int hours = 0, int minutes = 0, int seconds = 0); + + template + friend std::basic_ostream& operator<<(std::basic_ostream& out, const OldDateTime&); + + // This is used by query_expression.hpp to generalize its templates and simplify the code *alot*; it is needed + // because OldDateTime is internally stored in an int64_t column. + operator int_fast64_t() noexcept; + +private: + int_fast64_t m_time; // Seconds since Jan 1 00:00:00 UTC 1970. + static std::time_t assemble(int year, int month, int day, int hours, int minutes, int seconds); + template + friend class Value; +}; + + +// Implementation: + +inline bool operator==(const OldDateTime& a, const OldDateTime& b) noexcept +{ + return a.m_time == b.m_time; +} + +inline bool operator!=(const OldDateTime& a, const OldDateTime& b) noexcept +{ + return a.m_time != b.m_time; +} + +inline bool operator<(const OldDateTime& a, const OldDateTime& b) noexcept +{ + return a.m_time < b.m_time; +} + +inline bool operator<=(const OldDateTime& a, const OldDateTime& b) noexcept +{ + return a.m_time <= b.m_time; +} + +inline bool operator>(const OldDateTime& a, const OldDateTime& b) noexcept +{ + return a.m_time > b.m_time; +} + +inline bool operator>=(const OldDateTime& a, const OldDateTime& b) noexcept +{ + return a.m_time >= b.m_time; +} + +inline OldDateTime::operator int_fast64_t() noexcept +{ + return m_time; +} + +inline OldDateTime::OldDateTime(int year, int month, int day, int hours, int minutes, int seconds) + : m_time(assemble(year, month, day, hours, minutes, seconds)) +{ +} + +template +inline std::basic_ostream& operator<<(std::basic_ostream& out, const OldDateTime& d) +{ + out << "OldDateTime(" << d.m_time << ")"; + return out; +} + +inline std::time_t OldDateTime::assemble(int year, int month, int day, int hours, int minutes, int seconds) +{ + std::tm local_time; + local_time.tm_year = year - 1900; + local_time.tm_mon = month - 1; + local_time.tm_mday = day; + local_time.tm_hour = hours; + local_time.tm_min = minutes; + local_time.tm_sec = seconds; + local_time.tm_isdst = -1; + return std::mktime(&local_time); +} + + +} // namespace realm + +#endif // REALM_DATETIME_HPP diff --git a/!main project/Pods/Realm/include/core/realm/owned_data.hpp b/!main project/Pods/Realm/include/core/realm/owned_data.hpp new file mode 100644 index 0000000..c707f9d --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/owned_data.hpp @@ -0,0 +1,96 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_OWNED_DATA_HPP +#define REALM_OWNED_DATA_HPP + +#include + +#include +#include + +namespace realm { + +/// A chunk of owned data. +class OwnedData { +public: + /// Construct a null reference. + OwnedData() noexcept + { + } + + /// If \a data_to_copy is 'null', \a data_size must be zero. + OwnedData(const char* data_to_copy, size_t data_size) + : m_size(data_size) + { + REALM_ASSERT_DEBUG(data_to_copy || data_size == 0); + if (data_to_copy) { + m_data = std::unique_ptr(new char[data_size]); + memcpy(m_data.get(), data_to_copy, data_size); + } + } + + /// If \a unique_data is 'null', \a data_size must be zero. + OwnedData(std::unique_ptr unique_data, size_t data_size) noexcept + : m_data(std::move(unique_data)) + , m_size(data_size) + { + REALM_ASSERT_DEBUG(m_data || m_size == 0); + } + + OwnedData(const OwnedData& other) + : OwnedData(other.m_data.get(), other.m_size) + { + } + OwnedData& operator=(const OwnedData& other); + + OwnedData(OwnedData&&) = default; + OwnedData& operator=(OwnedData&&) = default; + + const char* data() const + { + return m_data.get(); + } + size_t size() const + { + return m_size; + } + +private: + std::unique_ptr m_data; + size_t m_size = 0; +}; + +inline OwnedData& OwnedData::operator=(const OwnedData& other) +{ + if (this != &other) { + if (other.m_data) { + m_data = std::unique_ptr(new char[other.m_size]); + memcpy(m_data.get(), other.m_data.get(), other.m_size); + } + else { + m_data = nullptr; + } + m_size = other.m_size; + } + return *this; +} + +} // namespace realm + +#endif // REALM_OWNED_DATA_HPP diff --git a/!main project/Pods/Realm/include/core/realm/parser/collection_operator_expression.hpp b/!main project/Pods/Realm/include/core/realm/parser/collection_operator_expression.hpp new file mode 100644 index 0000000..4edd5c2 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/parser/collection_operator_expression.hpp @@ -0,0 +1,226 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_COLLECTION_OPERATOR_EXPRESSION_HPP +#define REALM_COLLECTION_OPERATOR_EXPRESSION_HPP + +#include "property_expression.hpp" +#include "parser.hpp" +#include "parser_utils.hpp" + +#include + +namespace realm { +namespace parser { + +template +struct CollectionOperatorGetter; + +template +struct CollectionOperatorExpression +{ + static constexpr parser::Expression::KeyPathOp operation_type = OpType; + std::function table_getter; + PropertyExpression pe; + size_t post_link_col_ndx; + DataType post_link_col_type; + + CollectionOperatorExpression(PropertyExpression&& exp, std::string suffix_path, parser::KeyPathMapping& mapping) + : pe(std::move(exp)) + , post_link_col_ndx(realm::not_found) + { + table_getter = std::bind(&PropertyExpression::table_getter, pe); + + const bool requires_suffix_path = !(OpType == parser::Expression::KeyPathOp::SizeString + || OpType == parser::Expression::KeyPathOp::SizeBinary + || OpType == parser::Expression::KeyPathOp::Count + || OpType == parser::Expression::KeyPathOp::BacklinkCount); + + if (requires_suffix_path) { + Table* pre_link_table = pe.table_getter(); + REALM_ASSERT(pre_link_table); + StringData list_property_name; + if (pe.dest_type_is_backlink()) { + list_property_name = "linking object"; + } else { + list_property_name = pre_link_table->get_column_name(pe.get_dest_ndx()); + } + realm_precondition(pe.get_dest_type() == type_LinkList || pe.dest_type_is_backlink(), + util::format("The '%1' operation must be used on a list property, but '%2' is not a list", + util::collection_operator_to_str(OpType), list_property_name)); + + ConstTableRef post_link_table; + if (pe.dest_type_is_backlink()) { + post_link_table = pe.get_dest_table(); + } else { + post_link_table = pe.get_dest_table()->get_link_target(pe.get_dest_ndx()); + } + REALM_ASSERT(post_link_table); + StringData printable_post_link_table_name = get_printable_table_name(*post_link_table); + + KeyPath suffix_key_path = key_path_from_string(suffix_path); + + realm_precondition(suffix_path.size() > 0 && suffix_key_path.size() > 0, + util::format("A property from object '%1' must be provided to perform operation '%2'", + printable_post_link_table_name, util::collection_operator_to_str(OpType))); + size_t index = 0; + KeyPathElement element = mapping.process_next_path(post_link_table, suffix_key_path, index); + + realm_precondition(suffix_key_path.size() == 1, + util::format("Unable to use '%1' because collection aggreate operations are only supported " + "for direct properties at this time", suffix_path)); + + post_link_col_ndx = element.col_ndx; + post_link_col_type = element.col_type; + } + else { // !requires_suffix_path + if (!pe.link_chain.empty()) { + post_link_col_type = pe.get_dest_type(); + } + + realm_precondition(suffix_path.empty(), + util::format("An extraneous property '%1' was found for operation '%2'", + suffix_path, util::collection_operator_to_str(OpType))); + } + } + template + auto value_of_type_for_query() const + { + return CollectionOperatorGetter::convert(*this); + } +}; + + +// Certain operations are disabled for some types (eg. a sum of timestamps is invalid). +// The operations that are supported have a specialisation with std::enable_if for that type below +// any type/operation combination that is not specialised will get the runtime error from the following +// default implementation. The return type is just a dummy to make things compile. +template +struct CollectionOperatorGetter { + static Columns convert(const CollectionOperatorExpression& op) { + throw std::runtime_error(util::format("Predicate error: comparison of type '%1' with result of '%2' is not supported.", + type_to_str(), + collection_operator_to_str(op.operation_type))); + } +}; + +template +struct CollectionOperatorGetter::value> >{ + static SubColumnAggregate > convert(const CollectionOperatorExpression& expr) + { + if (expr.pe.dest_type_is_backlink()) { + return expr.table_getter()->template column(*expr.pe.get_dest_table(), expr.pe.get_dest_ndx()).template column(expr.post_link_col_ndx).min(); + } else { + return expr.table_getter()->template column(expr.pe.get_dest_ndx()).template column(expr.post_link_col_ndx).min(); + } + } +}; + +template +struct CollectionOperatorGetter::value> >{ + static SubColumnAggregate > convert(const CollectionOperatorExpression& expr) + { + if (expr.pe.dest_type_is_backlink()) { + return expr.table_getter()->template column(*expr.pe.get_dest_table(), expr.pe.get_dest_ndx()).template column(expr.post_link_col_ndx).max(); + } else { + return expr.table_getter()->template column(expr.pe.get_dest_ndx()).template column(expr.post_link_col_ndx).max(); + } + } +}; + +template +struct CollectionOperatorGetter::value> >{ + static SubColumnAggregate > convert(const CollectionOperatorExpression& expr) + { + if (expr.pe.dest_type_is_backlink()) { + return expr.table_getter()->template column(*expr.pe.get_dest_table(), expr.pe.get_dest_ndx()).template column(expr.post_link_col_ndx).sum(); + } else { + return expr.table_getter()->template column(expr.pe.get_dest_ndx()).template column(expr.post_link_col_ndx).sum(); + } + } +}; + +template +struct CollectionOperatorGetter::value> >{ + static SubColumnAggregate > convert(const CollectionOperatorExpression& expr) + { + if (expr.pe.dest_type_is_backlink()) { + return expr.table_getter()->template column(*expr.pe.get_dest_table(), expr.pe.get_dest_ndx()).template column(expr.post_link_col_ndx).average(); + } else { + return expr.table_getter()->template column(expr.pe.get_dest_ndx()).template column(expr.post_link_col_ndx).average(); + } + } +}; + +template +struct CollectionOperatorGetter::value> >{ + static LinkCount convert(const CollectionOperatorExpression& expr) + { + if (expr.pe.dest_type_is_backlink()) { + return expr.table_getter()->template column(*expr.pe.get_dest_table(), expr.pe.get_dest_ndx()).count(); + } else { + return expr.table_getter()->template column(expr.pe.get_dest_ndx()).count(); + } + } +}; + + +template +struct CollectionOperatorGetter::value> >{ + static BacklinkCount convert(const CollectionOperatorExpression& expr) + { + if (expr.pe.link_chain.empty() || expr.pe.get_dest_ndx() == realm::npos) { + // here we are operating on the current table from a "@links.@count" query with no link keypath prefix + return expr.table_getter()->template get_backlink_count(); + } else { + if (expr.pe.dest_type_is_backlink()) { + return expr.table_getter()->template column(*expr.pe.get_dest_table(), expr.pe.get_dest_ndx()).template backlink_count(); + } else { + return expr.table_getter()->template column(expr.pe.get_dest_ndx()).template backlink_count(); + } + } + } +}; + + +template <> +struct CollectionOperatorGetter{ + static SizeOperator > convert(const CollectionOperatorExpression& expr) + { + return expr.table_getter()->template column(expr.pe.get_dest_ndx()).size(); + } +}; + +template <> +struct CollectionOperatorGetter{ + static SizeOperator > convert(const CollectionOperatorExpression& expr) + { + return expr.table_getter()->template column(expr.pe.get_dest_ndx()).size(); + } +}; + +} // namespace parser +} // namespace realm + +#endif // REALM_COLLECTION_OPERATOR_EXPRESSION_HPP diff --git a/!main project/Pods/Realm/include/core/realm/parser/expression_container.hpp b/!main project/Pods/Realm/include/core/realm/parser/expression_container.hpp new file mode 100644 index 0000000..7e596b9 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/parser/expression_container.hpp @@ -0,0 +1,79 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_EXPRESSION_CONTAINER_HPP +#define REALM_EXPRESSION_CONTAINER_HPP + +#include + +#include "collection_operator_expression.hpp" +#include "parser.hpp" +#include "property_expression.hpp" +#include "query_builder.hpp" +#include "subquery_expression.hpp" +#include "value_expression.hpp" + +namespace realm { +namespace parser { + +class ExpressionContainer +{ +public: + ExpressionContainer(Query& query, const parser::Expression& e, query_builder::Arguments& args, parser::KeyPathMapping& mapping); + + bool is_null(); + + PropertyExpression& get_property(); + ValueExpression& get_value(); + CollectionOperatorExpression& get_min(); + CollectionOperatorExpression& get_max(); + CollectionOperatorExpression& get_sum(); + CollectionOperatorExpression& get_avg(); + CollectionOperatorExpression& get_count(); + CollectionOperatorExpression& get_backlink_count(); + CollectionOperatorExpression& get_size_string(); + CollectionOperatorExpression& get_size_binary(); + SubqueryExpression& get_subexpression(); + + DataType check_type_compatibility(DataType type); + DataType get_comparison_type(ExpressionContainer& rhs); + + enum class ExpressionInternal + { + exp_Value, + exp_Property, + exp_OpMin, + exp_OpMax, + exp_OpSum, + exp_OpAvg, + exp_OpCount, + exp_OpSizeString, + exp_OpSizeBinary, + exp_OpBacklinkCount, + exp_SubQuery + }; + + ExpressionInternal type; +private: + util::Any storage; +}; + +} // namespace parser +} // namespace realm + +#endif // REALM_EXPRESSION_CONTAINER_HPP diff --git a/!main project/Pods/Realm/include/core/realm/parser/keypath_mapping.hpp b/!main project/Pods/Realm/include/core/realm/parser/keypath_mapping.hpp new file mode 100644 index 0000000..01ea65f --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/parser/keypath_mapping.hpp @@ -0,0 +1,79 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_KEYPATH_MAPPING_HPP +#define REALM_KEYPATH_MAPPING_HPP + +#include + +#include "parser_utils.hpp" + +#include +#include + +namespace realm { +namespace parser { + +struct KeyPathElement +{ + ConstTableRef table; + size_t col_ndx; + DataType col_type; + bool is_backlink; +}; + +class BacklinksRestrictedError : public std::runtime_error { +public: + BacklinksRestrictedError(const std::string& msg) : std::runtime_error(msg) {} + /// runtime_error::what() returns the msg provided in the constructor. +}; + +struct TableAndColHash { + std::size_t operator () (const std::pair &p) const; +}; + + +// This class holds state which allows aliasing variable names in key paths used in queries. +// It is used to allow variable naming in subqueries such as 'SUBQUERY(list, $obj, $obj.intCol = 5).@count' +// It can also be used to allow querying named backlinks if bindings provide the mappings themselves. +class KeyPathMapping +{ +public: + KeyPathMapping(); + // returns true if added, false if duplicate key already exists + bool add_mapping(ConstTableRef table, std::string name, std::string alias); + void remove_mapping(ConstTableRef table, std::string name); + bool has_mapping(ConstTableRef table, std::string name); + KeyPathElement process_next_path(ConstTableRef table, KeyPath& path, size_t& index); + void set_allow_backlinks(bool allow); + bool backlinks_allowed() const + { + return m_allow_backlinks; + } + void set_backlink_class_prefix(std::string prefix); + static Table* table_getter(TableRef table, const std::vector& links); +protected: + bool m_allow_backlinks; + std::string m_backlink_class_prefix; + std::unordered_map, std::string, TableAndColHash> m_mapping; +}; + +} // namespace parser +} // namespace realm + +#endif // REALM_KEYPATH_MAPPING_HPP diff --git a/!main project/Pods/Realm/include/core/realm/parser/parser.hpp b/!main project/Pods/Realm/include/core/realm/parser/parser.hpp new file mode 100644 index 0000000..f04edc2 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/parser/parser.hpp @@ -0,0 +1,147 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_PARSER_HPP +#define REALM_PARSER_HPP + +#include +#include +#include +#include + +namespace realm { + +namespace parser { + +struct Predicate; + +struct Expression +{ + enum class Type { None, Number, String, KeyPath, Argument, True, False, Null, Timestamp, Base64, SubQuery } type; + enum class KeyPathOp { None, Min, Max, Avg, Sum, Count, SizeString, SizeBinary, BacklinkCount } collection_op; + std::string s; + std::vector time_inputs; + std::string op_suffix; + std::string subquery_path, subquery_var; + std::shared_ptr subquery; + Expression(Type t = Type::None, std::string input = "") : type(t), collection_op(KeyPathOp::None), s(input) {} + Expression(std::vector&& timestamp) : type(Type::Timestamp), collection_op(KeyPathOp::None), time_inputs(timestamp) {} + Expression(std::string prefix, KeyPathOp op, std::string suffix) : type(Type::KeyPath), collection_op(op), s(prefix), op_suffix(suffix) {} +}; + +struct Predicate +{ + enum class Type + { + Comparison, + Or, + And, + True, + False + } type = Type::And; + + enum class Operator + { + None, + Equal, + NotEqual, + LessThan, + LessThanOrEqual, + GreaterThan, + GreaterThanOrEqual, + BeginsWith, + EndsWith, + Contains, + Like, + In + }; + + enum class OperatorOption + { + None, + CaseInsensitive, + }; + + enum class ComparisonType + { + Unspecified, + Any, + All, + None, + }; + + struct Comparison + { + Operator op = Operator::None; + OperatorOption option = OperatorOption::None; + Expression expr[2] = {{Expression::Type::None, ""}, {Expression::Type::None, ""}}; + ComparisonType compare_type = ComparisonType::Unspecified; + }; + + struct Compound + { + std::vector sub_predicates; + }; + + Comparison cmpr; + Compound cpnd; + + bool negate = false; + + Predicate(Type t, bool n = false) : type(t), negate(n) {} +}; + +struct DescriptorOrderingState +{ + struct PropertyState + { + std::string key_path; + std::string table_name; + bool ascending; + }; + struct SingleOrderingState + { + std::vector properties; + size_t limit; + enum class DescriptorType { Sort, Distinct, Limit, Include } type; + }; + std::vector orderings; +}; + +struct ParserResult +{ + ParserResult(Predicate p, DescriptorOrderingState o) + : predicate(p) + , ordering(o) {} + Predicate predicate; + DescriptorOrderingState ordering; +}; + +ParserResult parse(const char* query); // assumes c-style null termination +ParserResult parse(const std::string& query); +ParserResult parse(const realm::StringData& query); + +DescriptorOrderingState parse_include_path(const realm::StringData& path); + +// run the analysis tool to check for cycles in the grammar +// returns the number of problems found and prints some info to std::cout +size_t analyze_grammar(); +} +} + +#endif // REALM_PARSER_HPP diff --git a/!main project/Pods/Realm/include/core/realm/parser/parser_utils.hpp b/!main project/Pods/Realm/include/core/realm/parser/parser_utils.hpp new file mode 100644 index 0000000..12b79c9 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/parser/parser_utils.hpp @@ -0,0 +1,87 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_PARSER_UTILS_HPP +#define REALM_PARSER_UTILS_HPP + +#include +#include +#include "parser.hpp" + +#include +#include + +namespace realm { + +class Table; +class Timestamp; +struct Link; + +namespace util { + +// check a precondition and throw an exception if it is not met +// this should be used iff the condition being false indicates a bug in the caller +// of the function checking its preconditions +#define realm_precondition(condition, message) if (!REALM_LIKELY(condition)) { throw std::logic_error(message); } + + +template +const char* type_to_str(); + +template <> +const char* type_to_str(); +template <> +const char* type_to_str(); +template <> +const char* type_to_str(); +template <> +const char* type_to_str(); +template <> +const char* type_to_str(); +template <> +const char* type_to_str(); +template <> +const char* type_to_str(); +template <> +const char* type_to_str(); + +const char* data_type_to_str(DataType type); +const char* collection_operator_to_str(parser::Expression::KeyPathOp op); +const char* comparison_type_to_str(parser::Predicate::ComparisonType type); + +using KeyPath = std::vector; +KeyPath key_path_from_string(const std::string &s); +std::string key_path_to_string(const KeyPath& keypath); +StringData get_printable_table_name(StringData name); +StringData get_printable_table_name(const Table& table); + +template +T stot(std::string const& s) { + std::istringstream iss(s); + T value; + iss >> value; + if (iss.fail()) { + throw std::invalid_argument(util::format("Cannot convert string '%1'", s)); + } + return value; +} + +} // namespace utils +} // namespace realm + +#endif // REALM_PARSER_UTILS_HPP diff --git a/!main project/Pods/Realm/include/core/realm/parser/property_expression.hpp b/!main project/Pods/Realm/include/core/realm/parser/property_expression.hpp new file mode 100644 index 0000000..8f4a7ec --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/parser/property_expression.hpp @@ -0,0 +1,78 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_PROPERTY_EXPRESSION_HPP +#define REALM_PROPERTY_EXPRESSION_HPP + +#include +#include +#include + +namespace realm { +namespace parser { + +struct PropertyExpression +{ + Query &query; + std::vector link_chain; + DataType get_dest_type() const; + size_t get_dest_ndx() const; + ConstTableRef get_dest_table() const; + bool dest_type_is_backlink() const; + + PropertyExpression(Query &q, const std::string &key_path_string, parser::KeyPathMapping& mapping); + + Table* table_getter() const; + + template + auto value_of_type_for_query() const + { + return this->table_getter()->template column(get_dest_ndx()); + } +}; + +inline DataType PropertyExpression::get_dest_type() const +{ + REALM_ASSERT_DEBUG(link_chain.size() > 0); + return link_chain.back().col_type; +} + +inline bool PropertyExpression::dest_type_is_backlink() const +{ + REALM_ASSERT_DEBUG(link_chain.size() > 0); + return link_chain.back().is_backlink; +} + +inline size_t PropertyExpression::get_dest_ndx() const +{ + REALM_ASSERT_DEBUG(link_chain.size() > 0); + return link_chain.back().col_ndx; +} + +inline ConstTableRef PropertyExpression::get_dest_table() const +{ + REALM_ASSERT_DEBUG(link_chain.size() > 0); + return link_chain.back().table; +} + + +} // namespace parser +} // namespace realm + +#endif // REALM_PROPERTY_EXPRESSION_HPP + diff --git a/!main project/Pods/Realm/include/core/realm/parser/query_builder.hpp b/!main project/Pods/Realm/include/core/realm/parser/query_builder.hpp new file mode 100644 index 0000000..34a72b6 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/parser/query_builder.hpp @@ -0,0 +1,158 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_QUERY_BUILDER_HPP +#define REALM_QUERY_BUILDER_HPP + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace realm { +class Query; +class Realm; +class Table; +template class BasicRowExpr; +using RowExpr = BasicRowExpr
; + +namespace parser { + struct Predicate; + struct DescriptorOrderingState; +} + +namespace query_builder { +class Arguments; + +void apply_predicate(Query& query, const parser::Predicate& predicate, Arguments& arguments, + parser::KeyPathMapping mapping = parser::KeyPathMapping()); + +void apply_ordering(DescriptorOrdering& ordering, ConstTableRef target, const parser::DescriptorOrderingState& state, + Arguments& arguments, parser::KeyPathMapping mapping = parser::KeyPathMapping()); +void apply_ordering(DescriptorOrdering& ordering, ConstTableRef target, const parser::DescriptorOrderingState& state, + parser::KeyPathMapping mapping = parser::KeyPathMapping()); + + +struct AnyContext +{ + template + T unbox(const util::Any& wrapper) { + return util::any_cast(wrapper); + } + bool is_null(const util::Any& wrapper) { + if (!wrapper.has_value()) { + return true; + } + if (wrapper.type() == typeid(realm::null)) { + return true; + } + return false; + } +}; + +class Arguments { +public: + virtual bool bool_for_argument(size_t argument_index) = 0; + virtual long long long_for_argument(size_t argument_index) = 0; + virtual float float_for_argument(size_t argument_index) = 0; + virtual double double_for_argument(size_t argument_index) = 0; + virtual StringData string_for_argument(size_t argument_index) = 0; + virtual BinaryData binary_for_argument(size_t argument_index) = 0; + virtual Timestamp timestamp_for_argument(size_t argument_index) = 0; + virtual size_t object_index_for_argument(size_t argument_index) = 0; + virtual bool is_argument_null(size_t argument_index) = 0; + // dynamic conversion space with lifetime tied to this + // it is used for storing literal binary/string data + std::vector buffer_space; +}; + +template +class ArgumentConverter : public Arguments { +public: + ArgumentConverter(ContextType& context, const ValueType* arguments, size_t count) + : m_ctx(context) + , m_arguments(arguments) + , m_count(count) + {} + + bool bool_for_argument(size_t i) override { return get(i); } + long long long_for_argument(size_t i) override { return get(i); } + float float_for_argument(size_t i) override { return get(i); } + double double_for_argument(size_t i) override { return get(i); } + StringData string_for_argument(size_t i) override { return get(i); } + BinaryData binary_for_argument(size_t i) override { return get(i); } + Timestamp timestamp_for_argument(size_t i) override { return get(i); } + size_t object_index_for_argument(size_t i) override { return get(i).get_index(); } + bool is_argument_null(size_t i) override { return m_ctx.is_null(at(i)); } + +private: + ContextType& m_ctx; + const ValueType* m_arguments; + size_t m_count; + + const ValueType& at(size_t index) const + { + if (index >= m_count) { + std::string error_message; + if (m_count) { + error_message = util::format("Request for argument at index %1 but only %2 argument%3 provided", index, m_count, m_count == 1 ? " is" : "s are"); + } else { + error_message = util::format("Request for argument at index %1 but no arguments are provided", index); + } + throw std::out_of_range(error_message); + } + return m_arguments[index]; + } + + template + T get(size_t index) const + { + return m_ctx.template unbox(at(index)); + } +}; + +class NoArgsError : public std::runtime_error { +public: + NoArgsError() : std::runtime_error("Attempt to retreive an argument when no arguments were given") {} +}; + +class NoArguments : public Arguments { +public: + bool bool_for_argument(size_t) { throw NoArgsError(); } + long long long_for_argument(size_t) { throw NoArgsError(); } + float float_for_argument(size_t) { throw NoArgsError(); } + double double_for_argument(size_t) { throw NoArgsError(); } + StringData string_for_argument(size_t) { throw NoArgsError(); } + BinaryData binary_for_argument(size_t) { throw NoArgsError(); } + Timestamp timestamp_for_argument(size_t) { throw NoArgsError(); } + size_t object_index_for_argument(size_t) { throw NoArgsError(); } + bool is_argument_null(size_t) { throw NoArgsError(); } +}; + +} // namespace query_builder +} // namespace realm + +#endif // REALM_QUERY_BUILDER_HPP diff --git a/!main project/Pods/Realm/include/core/realm/parser/subquery_expression.hpp b/!main project/Pods/Realm/include/core/realm/parser/subquery_expression.hpp new file mode 100644 index 0000000..20c04d1 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/parser/subquery_expression.hpp @@ -0,0 +1,112 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_SUBQUERY_EXPRESSION_HPP +#define REALM_SUBQUERY_EXPRESSION_HPP + +#include +#include +#include +#include + +#include "parser_utils.hpp" + +namespace realm { +namespace parser { + +template +struct SubqueryGetter; + +struct SubqueryExpression +{ + std::string var_name; + Query &query; + Query subquery; + std::vector link_chain; + DataType get_dest_type() const; + size_t get_dest_ndx() const; + ConstTableRef get_dest_table() const; + bool dest_type_is_backlink() const; + + + SubqueryExpression(Query &q, const std::string &key_path_string, const std::string &variable_name, parser::KeyPathMapping &mapping); + Query& get_subquery(); + + Table* table_getter() const; + + template + auto value_of_type_for_query() const + { + return SubqueryGetter::convert(*this); + } +}; + +inline DataType SubqueryExpression::get_dest_type() const +{ + REALM_ASSERT_DEBUG(link_chain.size() > 0); + return link_chain.back().col_type; +} + +inline bool SubqueryExpression::dest_type_is_backlink() const +{ + REALM_ASSERT_DEBUG(link_chain.size() > 0); + return link_chain.back().is_backlink; +} + +inline size_t SubqueryExpression::get_dest_ndx() const +{ + REALM_ASSERT_DEBUG(link_chain.size() > 0); + return link_chain.back().col_ndx; +} + +inline ConstTableRef SubqueryExpression::get_dest_table() const +{ + REALM_ASSERT_DEBUG(link_chain.size() > 0); + return link_chain.back().table; +} + +// Certain operations are disabled for some types (eg. a sum of timestamps is invalid). +// The operations that are supported have a specialisation with std::enable_if for that type below +// any type/operation combination that is not specialised will get the runtime error from the following +// default implementation. The return type is just a dummy to make things compile. +template +struct SubqueryGetter { + static Columns convert(const SubqueryExpression&) { + throw std::runtime_error(util::format("Predicate error: comparison of type '%1' with result of a subquery count is not supported.", + type_to_str())); + } +}; + +template +struct SubqueryGetter::value> >{ + static SubQueryCount convert(const SubqueryExpression& expr) + { + if (expr.dest_type_is_backlink()) { + return expr.table_getter()->template column(*expr.get_dest_table(), expr.get_dest_ndx(), expr.subquery).count(); + } else { + return expr.table_getter()->template column(expr.get_dest_ndx(), expr.subquery).count(); + } + } +}; + +} // namespace parser +} // namespace realm + +#endif // REALM_SUBQUERY_EXPRESSION_HPP + diff --git a/!main project/Pods/Realm/include/core/realm/parser/value_expression.hpp b/!main project/Pods/Realm/include/core/realm/parser/value_expression.hpp new file mode 100644 index 0000000..09ac257 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/parser/value_expression.hpp @@ -0,0 +1,43 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_VALUE_EXPRESSION_HPP +#define REALM_VALUE_EXPRESSION_HPP + +#include "parser.hpp" +#include "query_builder.hpp" + +namespace realm { +namespace parser { + +struct ValueExpression +{ + const parser::Expression* value; + query_builder::Arguments* arguments; + std::function
table_getter; + + ValueExpression(Query& query, query_builder::Arguments* args, const parser::Expression* v); + bool is_null(); + template + RetType value_of_type_for_query(); +}; + +} // namespace parser +} // namespace realm + +#endif // REALM_VALUE_EXPRESSION_HPP diff --git a/!main project/Pods/Realm/include/core/realm/query.hpp b/!main project/Pods/Realm/include/core/realm/query.hpp new file mode 100644 index 0000000..6d9e5a3 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/query.hpp @@ -0,0 +1,483 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_QUERY_HPP +#define REALM_QUERY_HPP + +#include +#include +#include +#include +#include +#include + +#define REALM_MULTITHREAD_QUERY 0 + +#if REALM_MULTITHREAD_QUERY +// FIXME: Use our C++ thread abstraction API since it provides a much +// higher level of encapsulation and safety. +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace realm { + + +// Pre-declarations +class ParentNode; +class Table; +class TableView; +class TableViewBase; +class ConstTableView; +class Array; +class Expression; +class SequentialGetterBase; +class Group; + +namespace metrics { +class QueryInfo; +} + +struct QueryGroup { + enum class State { + Default, + OrCondition, + OrConditionChildren, + }; + + QueryGroup() = default; + + QueryGroup(const QueryGroup&); + QueryGroup& operator=(const QueryGroup&); + + QueryGroup(QueryGroup&&) = default; + QueryGroup& operator=(QueryGroup&&) = default; + + QueryGroup(const QueryGroup&, QueryNodeHandoverPatches&); + + std::unique_ptr m_root_node; + + bool m_pending_not = false; + size_t m_subtable_column = not_found; + State m_state = State::Default; +}; + +class Query final { +public: + Query(const Table& table, TableViewBase* tv = nullptr); + Query(const Table& table, std::unique_ptr); + Query(const Table& table, const LinkViewRef& lv); + Query(); + Query(std::unique_ptr); + ~Query() noexcept; + + Query(const Query& copy); + Query& operator=(const Query& source); + + Query(Query&&); + Query& operator=(Query&&); + + // Find links that point to a specific target row + Query& links_to(size_t column_ndx, const ConstRow& target_row); + // Find links that point to specific target rows + Query& links_to(size_t column_ndx, const std::vector& target_row); + + // Conditions: null + Query& equal(size_t column_ndx, null); + Query& not_equal(size_t column_ndx, null); + + // Conditions: int64_t + Query& equal(size_t column_ndx, int64_t value); + Query& not_equal(size_t column_ndx, int64_t value); + Query& greater(size_t column_ndx, int64_t value); + Query& greater_equal(size_t column_ndx, int64_t value); + Query& less(size_t column_ndx, int64_t value); + Query& less_equal(size_t column_ndx, int64_t value); + Query& between(size_t column_ndx, int64_t from, int64_t to); + + // Conditions: int (we need those because conversion from '1234' is ambiguous with float/double) + Query& equal(size_t column_ndx, int value); + Query& not_equal(size_t column_ndx, int value); + Query& greater(size_t column_ndx, int value); + Query& greater_equal(size_t column_ndx, int value); + Query& less(size_t column_ndx, int value); + Query& less_equal(size_t column_ndx, int value); + Query& between(size_t column_ndx, int from, int to); + + // Conditions: 2 int columns + Query& equal_int(size_t column_ndx1, size_t column_ndx2); + Query& not_equal_int(size_t column_ndx1, size_t column_ndx2); + Query& greater_int(size_t column_ndx1, size_t column_ndx2); + Query& less_int(size_t column_ndx1, size_t column_ndx2); + Query& greater_equal_int(size_t column_ndx1, size_t column_ndx2); + Query& less_equal_int(size_t column_ndx1, size_t column_ndx2); + + // Conditions: float + Query& equal(size_t column_ndx, float value); + Query& not_equal(size_t column_ndx, float value); + Query& greater(size_t column_ndx, float value); + Query& greater_equal(size_t column_ndx, float value); + Query& less(size_t column_ndx, float value); + Query& less_equal(size_t column_ndx, float value); + Query& between(size_t column_ndx, float from, float to); + + // Conditions: 2 float columns + Query& equal_float(size_t column_ndx1, size_t column_ndx2); + Query& not_equal_float(size_t column_ndx1, size_t column_ndx2); + Query& greater_float(size_t column_ndx1, size_t column_ndx2); + Query& greater_equal_float(size_t column_ndx1, size_t column_ndx2); + Query& less_float(size_t column_ndx1, size_t column_ndx2); + Query& less_equal_float(size_t column_ndx1, size_t column_ndx2); + + // Conditions: double + Query& equal(size_t column_ndx, double value); + Query& not_equal(size_t column_ndx, double value); + Query& greater(size_t column_ndx, double value); + Query& greater_equal(size_t column_ndx, double value); + Query& less(size_t column_ndx, double value); + Query& less_equal(size_t column_ndx, double value); + Query& between(size_t column_ndx, double from, double to); + + // Conditions: 2 double columns + Query& equal_double(size_t column_ndx1, size_t column_ndx2); + Query& not_equal_double(size_t column_ndx1, size_t column_ndx2); + Query& greater_double(size_t column_ndx1, size_t column_ndx2); + Query& greater_equal_double(size_t column_ndx1, size_t column_ndx2); + Query& less_double(size_t column_ndx1, size_t column_ndx2); + Query& less_equal_double(size_t column_ndx1, size_t column_ndx2); + + // Conditions: timestamp + Query& equal(size_t column_ndx, Timestamp value); + Query& not_equal(size_t column_ndx, Timestamp value); + Query& greater(size_t column_ndx, Timestamp value); + Query& greater_equal(size_t column_ndx, Timestamp value); + Query& less_equal(size_t column_ndx, Timestamp value); + Query& less(size_t column_ndx, Timestamp value); + + // Conditions: size + Query& size_equal(size_t column_ndx, int64_t value); + Query& size_not_equal(size_t column_ndx, int64_t value); + Query& size_greater(size_t column_ndx, int64_t value); + Query& size_greater_equal(size_t column_ndx, int64_t value); + Query& size_less_equal(size_t column_ndx, int64_t value); + Query& size_less(size_t column_ndx, int64_t value); + Query& size_between(size_t column_ndx, int64_t from, int64_t to); + + // Conditions: bool + Query& equal(size_t column_ndx, bool value); + + // Conditions: date + Query& equal_olddatetime(size_t column_ndx, OldDateTime value) + { + return equal(column_ndx, int64_t(value.get_olddatetime())); + } + Query& not_equal_olddatetime(size_t column_ndx, OldDateTime value) + { + return not_equal(column_ndx, int64_t(value.get_olddatetime())); + } + Query& greater_olddatetime(size_t column_ndx, OldDateTime value) + { + return greater(column_ndx, int64_t(value.get_olddatetime())); + } + Query& greater_equal_olddatetime(size_t column_ndx, OldDateTime value) + { + return greater_equal(column_ndx, int64_t(value.get_olddatetime())); + } + Query& less_olddatetime(size_t column_ndx, OldDateTime value) + { + return less(column_ndx, int64_t(value.get_olddatetime())); + } + Query& less_equal_olddatetime(size_t column_ndx, OldDateTime value) + { + return less_equal(column_ndx, int64_t(value.get_olddatetime())); + } + Query& between_olddatetime(size_t column_ndx, OldDateTime from, OldDateTime to) + { + return between(column_ndx, int64_t(from.get_olddatetime()), int64_t(to.get_olddatetime())); + } + + // Conditions: strings + Query& equal(size_t column_ndx, StringData value, bool case_sensitive = true); + Query& not_equal(size_t column_ndx, StringData value, bool case_sensitive = true); + Query& begins_with(size_t column_ndx, StringData value, bool case_sensitive = true); + Query& ends_with(size_t column_ndx, StringData value, bool case_sensitive = true); + Query& contains(size_t column_ndx, StringData value, bool case_sensitive = true); + Query& like(size_t column_ndx, StringData value, bool case_sensitive = true); + + // These are shortcuts for equal(StringData(c_str)) and + // not_equal(StringData(c_str)), and are needed to avoid unwanted + // implicit conversion of char* to bool. + Query& equal(size_t column_ndx, const char* c_str, bool case_sensitive = true); + Query& not_equal(size_t column_ndx, const char* c_str, bool case_sensitive = true); + + // Conditions: binary data + Query& equal(size_t column_ndx, BinaryData value, bool case_sensitive = true); + Query& not_equal(size_t column_ndx, BinaryData value, bool case_sensitive = true); + Query& begins_with(size_t column_ndx, BinaryData value, bool case_sensitive = true); + Query& ends_with(size_t column_ndx, BinaryData value, bool case_sensitive = true); + Query& contains(size_t column_ndx, BinaryData value, bool case_sensitive = true); + Query& like(size_t column_ndx, BinaryData b, bool case_sensitive = true); + + // Negation + Query& Not(); + + // Grouping + Query& group(); + Query& end_group(); + Query& subtable(size_t column); + Query& end_subtable(); + Query& Or(); + + Query& and_query(const Query& q); + Query& and_query(Query&& q); + Query operator||(const Query& q); + Query operator&&(const Query& q); + Query operator!(); + + + // Searching + size_t find(size_t begin_at_table_row = size_t(0)); + TableView find_all(size_t start = 0, size_t end = size_t(-1), size_t limit = size_t(-1)); + + // Aggregates + size_t count(size_t start = 0, size_t end = size_t(-1), size_t limit = size_t(-1)) const; + + TableView find_all(const DescriptorOrdering& descriptor); + size_t count(const DescriptorOrdering& descriptor); + + int64_t sum_int(size_t column_ndx, size_t* resultcount = nullptr, size_t start = 0, size_t end = size_t(-1), + size_t limit = size_t(-1)) const; + + double average_int(size_t column_ndx, size_t* resultcount = nullptr, size_t start = 0, size_t end = size_t(-1), + size_t limit = size_t(-1)) const; + + int64_t maximum_int(size_t column_ndx, size_t* resultcount = nullptr, size_t start = 0, size_t end = size_t(-1), + size_t limit = size_t(-1), size_t* return_ndx = nullptr) const; + + int64_t minimum_int(size_t column_ndx, size_t* resultcount = nullptr, size_t start = 0, size_t end = size_t(-1), + size_t limit = size_t(-1), size_t* return_ndx = nullptr) const; + + double sum_float(size_t column_ndx, size_t* resultcount = nullptr, size_t start = 0, size_t end = size_t(-1), + size_t limit = size_t(-1)) const; + + double average_float(size_t column_ndx, size_t* resultcount = nullptr, size_t start = 0, size_t end = size_t(-1), + size_t limit = size_t(-1)) const; + + float maximum_float(size_t column_ndx, size_t* resultcount = nullptr, size_t start = 0, size_t end = size_t(-1), + size_t limit = size_t(-1), size_t* return_ndx = nullptr) const; + + float minimum_float(size_t column_ndx, size_t* resultcount = nullptr, size_t start = 0, size_t end = size_t(-1), + size_t limit = size_t(-1), size_t* return_ndx = nullptr) const; + + double sum_double(size_t column_ndx, size_t* resultcount = nullptr, size_t start = 0, size_t end = size_t(-1), + size_t limit = size_t(-1)) const; + + double average_double(size_t column_ndx, size_t* resultcount = nullptr, size_t start = 0, size_t end = size_t(-1), + size_t limit = size_t(-1)) const; + + double maximum_double(size_t column_ndx, size_t* resultcount = nullptr, size_t start = 0, size_t end = size_t(-1), + size_t limit = size_t(-1), size_t* return_ndx = nullptr) const; + + double minimum_double(size_t column_ndx, size_t* resultcount = nullptr, size_t start = 0, size_t end = size_t(-1), + size_t limit = size_t(-1), size_t* return_ndx = nullptr) const; + + OldDateTime maximum_olddatetime(size_t column_ndx, size_t* resultcount = nullptr, size_t start = 0, + size_t end = size_t(-1), size_t limit = size_t(-1), + size_t* return_ndx = nullptr) const; + + OldDateTime minimum_olddatetime(size_t column_ndx, size_t* resultcount = nullptr, size_t start = 0, + size_t end = size_t(-1), size_t limit = size_t(-1), + size_t* return_ndx = nullptr) const; + + Timestamp maximum_timestamp(size_t column_ndx, size_t* return_ndx, size_t start = 0, size_t end = size_t(-1), + size_t limit = size_t(-1)); + + Timestamp minimum_timestamp(size_t column_ndx, size_t* return_ndx, size_t start = 0, size_t end = size_t(-1), + size_t limit = size_t(-1)); + + // Deletion + size_t remove(); + +#if REALM_MULTITHREAD_QUERY + // Multi-threading + TableView find_all_multi(size_t start = 0, size_t end = size_t(-1)); + ConstTableView find_all_multi(size_t start = 0, size_t end = size_t(-1)) const; + int set_threads(unsigned int threadcount); +#endif + + const TableRef& get_table() + { + return m_table; + } + + // True if matching rows are guaranteed to be returned in table order. + bool produces_results_in_table_order() const + { + return !m_view; + } + + // Calls sync_if_needed on the restricting view, if present. + // Returns the current version of the table(s) this query depends on, + // or util::none if the query is not associated with a table. + util::Optional sync_view_if_needed() const; + + std::string validate(); + + std::string get_description() const; + std::string get_description(util::serializer::SerialisationState& state) const; + +private: + Query(Table& table, TableViewBase* tv = nullptr); + void create(); + + void init() const; + size_t find_internal(size_t start = 0, size_t end = size_t(-1)) const; + size_t peek_tablerow(size_t row) const; + void handle_pending_not(); + void set_table(TableRef tr); + +public: + using HandoverPatch = QueryHandoverPatch; + + std::unique_ptr clone_for_handover(std::unique_ptr& patch, ConstSourcePayload mode) const + { + patch.reset(new HandoverPatch); + return std::make_unique(*this, *patch, mode); + } + + std::unique_ptr clone_for_handover(std::unique_ptr& patch, MutableSourcePayload mode) + { + patch.reset(new HandoverPatch); + return std::make_unique(*this, *patch, mode); + } + + void apply_and_consume_patch(std::unique_ptr& patch, Group& dest_group) + { + apply_patch(*patch, dest_group); + patch.reset(); + } + + void apply_patch(HandoverPatch& patch, Group& dest_group); + Query(const Query& source, HandoverPatch& patch, ConstSourcePayload mode); + Query(Query& source, HandoverPatch& patch, MutableSourcePayload mode); + +private: + void fetch_descriptor(); + + void add_expression_node(std::unique_ptr); + + template + Query& equal(size_t column_ndx1, size_t column_ndx2); + + template + Query& less(size_t column_ndx1, size_t column_ndx2); + + template + Query& less_equal(size_t column_ndx1, size_t column_ndx2); + + template + Query& greater(size_t column_ndx1, size_t column_ndx2); + + template + Query& greater_equal(size_t column_ndx1, size_t column_ndx2); + + template + Query& not_equal(size_t column_ndx1, size_t column_ndx2); + + template + Query& add_condition(size_t column_ndx, T value); + + template + Query& add_size_condition(size_t column_ndx, int64_t value); + + template + double average(size_t column_ndx, size_t* resultcount = nullptr, size_t start = 0, size_t end = size_t(-1), + size_t limit = size_t(-1)) const; + + template + R aggregate(R (ColClass::*method)(size_t, size_t, size_t, size_t*) const, size_t column_ndx, size_t* resultcount, + size_t start, size_t end, size_t limit, size_t* return_ndx = nullptr) const; + + void aggregate_internal(Action TAction, DataType TSourceColumn, bool nullable, ParentNode* pn, QueryStateBase* st, + size_t start, size_t end, SequentialGetterBase* source_column) const; + + void find_all(TableViewBase& tv, size_t start = 0, size_t end = size_t(-1), size_t limit = size_t(-1)) const; + size_t do_count(size_t start = 0, size_t end = size_t(-1), size_t limit = size_t(-1)) const; + void delete_nodes() noexcept; + + bool has_conditions() const + { + return m_groups.size() > 0 && m_groups[0].m_root_node; + } + ParentNode* root_node() const + { + REALM_ASSERT(m_groups.size()); + return m_groups[0].m_root_node.get(); + } + + void add_node(std::unique_ptr); + + friend class Table; + friend class TableViewBase; + friend class metrics::QueryInfo; + + std::string error_code; + + std::vector m_groups; + + // Used to access schema while building query: + std::vector m_subtable_path; + + ConstDescriptorRef m_current_descriptor; + TableRef m_table; + + // points to the base class of the restricting view. If the restricting + // view is a link view, m_source_link_view is non-zero. If it is a table view, + // m_source_table_view is non-zero. + RowIndexes* m_view = nullptr; + + // At most one of these can be non-zero, and if so the non-zero one indicates the restricting view. + LinkViewRef m_source_link_view; // link views are refcounted and shared. + TableViewBase* m_source_table_view = nullptr; // table views are not refcounted, and not owned by the query. + std::unique_ptr m_owned_source_table_view; // <--- except when indicated here +}; + +// Implementation: + +inline Query& Query::equal(size_t column_ndx, const char* c_str, bool case_sensitive) +{ + return equal(column_ndx, StringData(c_str), case_sensitive); +} + +inline Query& Query::not_equal(size_t column_ndx, const char* c_str, bool case_sensitive) +{ + return not_equal(column_ndx, StringData(c_str), case_sensitive); +} + +} // namespace realm + +#endif // REALM_QUERY_HPP diff --git a/!main project/Pods/Realm/include/core/realm/query_conditions.hpp b/!main project/Pods/Realm/include/core/realm/query_conditions.hpp new file mode 100644 index 0000000..bc13bbe --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/query_conditions.hpp @@ -0,0 +1,856 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_QUERY_CONDITIONS_HPP +#define REALM_QUERY_CONDITIONS_HPP + +#include +#include + +#include +#include +#include + +namespace realm { + +// Array::VTable only uses the first 4 conditions (enums) in an array of function pointers +enum { cond_Equal, cond_NotEqual, cond_Greater, cond_Less, cond_VTABLE_FINDER_COUNT, cond_None, cond_LeftNotNull }; + +// Quick hack to make "Queries with Integer null columns" able to compile in Visual Studio 2015 which doesn't full +// support sfinae +// (real cause hasn't been investigated yet, cannot exclude that we don't obey c++11 standard) +struct HackClass { + template + bool can_match(A, B, C) + { + REALM_ASSERT(false); + return false; + } + template + bool will_match(A, B, C) + { + REALM_ASSERT(false); + return false; + } +}; + +// Does v2 contain v1? +struct Contains : public HackClass { + bool operator()(StringData v1, const char*, const char*, StringData v2, bool = false, bool = false) const + { + return v2.contains(v1); + } + bool operator()(StringData v1, StringData v2, bool = false, bool = false) const + { + return v2.contains(v1); + } + bool operator()(BinaryData v1, BinaryData v2, bool = false, bool = false) const + { + return v2.contains(v1); + } + bool operator()(StringData v1, const std::array &charmap, StringData v2) const + { + return v2.contains(v1, charmap); + } + + template + bool operator()(A, B) const + { + REALM_ASSERT(false); + return false; + } + template + bool operator()(A, B, C, D) const + { + REALM_ASSERT(false); + return false; + } + bool operator()(int64_t, int64_t, bool, bool) const + { + REALM_ASSERT(false); + return false; + } + + static std::string description() + { + return "CONTAINS"; + } + + static const int condition = -1; +}; + +// Does v2 contain something like v1 (wildcard matching)? +struct Like : public HackClass { + bool operator()(StringData v1, const char*, const char*, StringData v2, bool = false, bool = false) const + { + return v2.like(v1); + } + bool operator()(BinaryData b1, const char*, const char*, BinaryData b2, bool = false, bool = false) const + { + StringData s1(b1.data(), b1.size()); + StringData s2(b2.data(), b2.size()); + return s2.like(s1); + } + bool operator()(StringData v1, StringData v2, bool = false, bool = false) const + { + return v2.like(v1); + } + bool operator()(BinaryData b1, BinaryData b2, bool = false, bool = false) const + { + StringData s1(b1.data(), b1.size()); + StringData s2(b2.data(), b2.size()); + return s2.like(s1); + } + + template + bool operator()(A, B) const + { + REALM_ASSERT(false); + return false; + } + + template + bool operator()(A, B, C, D) const + { + REALM_ASSERT(false); + return false; + } + + bool operator()(int64_t, int64_t, bool, bool) const + { + REALM_ASSERT(false); + return false; + } + + static std::string description() + { + return "LIKE"; + } + + static const int condition = -1; +}; + +// Does v2 begin with v1? +struct BeginsWith : public HackClass { + bool operator()(StringData v1, const char*, const char*, StringData v2, bool = false, bool = false) const + { + return v2.begins_with(v1); + } + bool operator()(StringData v1, StringData v2, bool = false, bool = false) const + { + return v2.begins_with(v1); + } + bool operator()(BinaryData v1, BinaryData v2, bool = false, bool = false) const + { + return v2.begins_with(v1); + } + + template + bool operator()(A, B, C, D) const + { + REALM_ASSERT(false); + return false; + } + template + bool operator()(A, B) const + { + REALM_ASSERT(false); + return false; + } + + static std::string description() + { + return "BEGINSWITH"; + } + + static const int condition = -1; +}; + +// Does v2 end with v1? +struct EndsWith : public HackClass { + bool operator()(StringData v1, const char*, const char*, StringData v2, bool = false, bool = false) const + { + return v2.ends_with(v1); + } + bool operator()(StringData v1, StringData v2, bool = false, bool = false) const + { + return v2.ends_with(v1); + } + bool operator()(BinaryData v1, BinaryData v2, bool = false, bool = false) const + { + return v2.ends_with(v1); + } + + template + bool operator()(A, B) const + { + REALM_ASSERT(false); + return false; + } + template + bool operator()(A, B, C, D) const + { + REALM_ASSERT(false); + return false; + } + + static std::string description() + { + return "ENDSWITH"; + } + + static const int condition = -1; +}; + +struct Equal { + static const int avx = 0x00; // _CMP_EQ_OQ + // bool operator()(const bool v1, const bool v2, bool v1null = false, bool v2null = false) const { return v1 == + // v2; } + bool operator()(StringData v1, const char*, const char*, StringData v2, bool = false, bool = false) const + { + return v1 == v2; + } + bool operator()(BinaryData v1, BinaryData v2, bool = false, bool = false) const + { + return v1 == v2; + } + + template + bool operator()(const T& v1, const T& v2, bool v1null = false, bool v2null = false) const + { + return (v1null && v2null) || (!v1null && !v2null && v1 == v2); + } + static const int condition = cond_Equal; + bool can_match(int64_t v, int64_t lbound, int64_t ubound) + { + return (v >= lbound && v <= ubound); + } + bool will_match(int64_t v, int64_t lbound, int64_t ubound) + { + return (v == 0 && ubound == 0 && lbound == 0); + } + + static std::string description() + { + return "=="; + } +}; + +struct NotEqual { + static const int avx = 0x0B; // _CMP_FALSE_OQ + bool operator()(StringData v1, const char*, const char*, StringData v2, bool = false, bool = false) const + { + return v1 != v2; + } + // bool operator()(BinaryData v1, BinaryData v2, bool = false, bool = false) const { return v1 != v2; } + + template + bool operator()(const T& v1, const T& v2, bool v1null = false, bool v2null = false) const + { + if (!v1null && !v2null) + return v1 != v2; + + if (v1null && v2null) + return false; + + return true; + } + + static const int condition = cond_NotEqual; + bool can_match(int64_t v, int64_t lbound, int64_t ubound) + { + return !(v == 0 && ubound == 0 && lbound == 0); + } + bool will_match(int64_t v, int64_t lbound, int64_t ubound) + { + return (v > ubound || v < lbound); + } + + template + bool operator()(A, B, C, D) const = delete; + + static std::string description() + { + return "!="; + } +}; + +// Does v2 contain v1? +struct ContainsIns : public HackClass { + bool operator()(StringData v1, const char* v1_upper, const char* v1_lower, StringData v2, bool = false, + bool = false) const + { + if (v2.is_null() && !v1.is_null()) + return false; + + if (v1.size() == 0 && !v2.is_null()) + return true; + + return search_case_fold(v2, v1_upper, v1_lower, v1.size()) != v2.size(); + } + + // Slow version, used if caller hasn't stored an upper and lower case version + bool operator()(StringData v1, StringData v2, bool = false, bool = false) const + { + if (v2.is_null() && !v1.is_null()) + return false; + + if (v1.size() == 0 && !v2.is_null()) + return true; + + std::string v1_upper = case_map(v1, true, IgnoreErrors); + std::string v1_lower = case_map(v1, false, IgnoreErrors); + return search_case_fold(v2, v1_upper.c_str(), v1_lower.c_str(), v1.size()) != v2.size(); + } + bool operator()(BinaryData b1, BinaryData b2, bool = false, bool = false) const + { + StringData s1(b1.data(), b1.size()); + StringData s2(b2.data(), b2.size()); + return this->operator()(s1, s2, false, false); + } + + // Case insensitive Boyer-Moore version + bool operator()(StringData v1, const char* v1_upper, const char* v1_lower, const std::array &charmap, StringData v2) const + { + if (v2.is_null() && !v1.is_null()) + return false; + + if (v1.size() == 0 && !v2.is_null()) + return true; + + return contains_ins(v2, v1_upper, v1_lower, v1.size(), charmap); + } + + + template + bool operator()(A, B) const + { + REALM_ASSERT(false); + return false; + } + template + bool operator()(A, B, C, D) const + { + REALM_ASSERT(false); + return false; + } + bool operator()(int64_t, int64_t, bool, bool) const + { + REALM_ASSERT(false); + return false; + } + + static std::string description() + { + return "CONTAINS[c]"; + } + + static const int condition = -1; +}; + +// Does v2 contain something like v1 (wildcard matching)? +struct LikeIns : public HackClass { + bool operator()(StringData v1, const char* v1_upper, const char* v1_lower, StringData v2, bool = false, + bool = false) const + { + if (v2.is_null() || v1.is_null()) { + return (v2.is_null() && v1.is_null()); + } + + return string_like_ins(v2, v1_lower, v1_upper); + } + bool operator()(BinaryData b1, const char* b1_upper, const char* b1_lower, BinaryData b2, bool = false, + bool = false) const + { + if (b2.is_null() || b1.is_null()) { + return (b2.is_null() && b1.is_null()); + } + StringData s2(b2.data(), b2.size()); + + return string_like_ins(s2, b1_lower, b1_upper); + } + + // Slow version, used if caller hasn't stored an upper and lower case version + bool operator()(StringData v1, StringData v2, bool = false, bool = false) const + { + if (v2.is_null() || v1.is_null()) { + return (v2.is_null() && v1.is_null()); + } + + std::string v1_upper = case_map(v1, true, IgnoreErrors); + std::string v1_lower = case_map(v1, false, IgnoreErrors); + return string_like_ins(v2, v1_lower, v1_upper); + } + bool operator()(BinaryData b1, BinaryData b2, bool = false, bool = false) const + { + if (b2.is_null() || b1.is_null()) { + return (b2.is_null() && b1.is_null()); + } + StringData s1(b1.data(), b1.size()); + StringData s2(b2.data(), b2.size()); + + std::string s1_upper = case_map(s1, true, IgnoreErrors); + std::string s1_lower = case_map(s1, false, IgnoreErrors); + return string_like_ins(s2, s1_lower, s1_upper); + } + + template + bool operator()(A, B) const + { + REALM_ASSERT(false); + return false; + } + template + bool operator()(A, B, C, D) const + { + REALM_ASSERT(false); + return false; + } + bool operator()(int64_t, int64_t, bool, bool) const + { + REALM_ASSERT(false); + return false; + } + + static std::string description() + { + return "LIKE[c]"; + } + + static const int condition = -1; +}; + +// Does v2 begin with v1? +struct BeginsWithIns : public HackClass { + bool operator()(StringData v1, const char* v1_upper, const char* v1_lower, StringData v2, bool = false, + bool = false) const + { + if (v2.is_null() && !v1.is_null()) + return false; + return v1.size() <= v2.size() && equal_case_fold(v2.prefix(v1.size()), v1_upper, v1_lower); + } + + // Slow version, used if caller hasn't stored an upper and lower case version + bool operator()(StringData v1, StringData v2, bool = false, bool = false) const + { + if (v2.is_null() && !v1.is_null()) + return false; + + if (v1.size() > v2.size()) + return false; + std::string v1_upper = case_map(v1, true, IgnoreErrors); + std::string v1_lower = case_map(v1, false, IgnoreErrors); + return equal_case_fold(v2.prefix(v1.size()), v1_upper.c_str(), v1_lower.c_str()); + } + bool operator()(BinaryData b1, BinaryData b2, bool = false, bool = false) const + { + StringData s1(b1.data(), b1.size()); + StringData s2(b2.data(), b2.size()); + return this->operator()(s1, s2, false, false); + } + + template + bool operator()(A, B) const + { + REALM_ASSERT(false); + return false; + } + template + bool operator()(A, B, C, D) const + { + REALM_ASSERT(false); + return false; + } + bool operator()(int64_t, int64_t, bool, bool) const + { + REALM_ASSERT(false); + return false; + } + + static std::string description() + { + return "BEGINSWITH[c]"; + } + + static const int condition = -1; +}; + +// Does v2 end with v1? +struct EndsWithIns : public HackClass { + bool operator()(StringData v1, const char* v1_upper, const char* v1_lower, StringData v2, bool = false, + bool = false) const + { + if (v2.is_null() && !v1.is_null()) + return false; + + return v1.size() <= v2.size() && equal_case_fold(v2.suffix(v1.size()), v1_upper, v1_lower); + } + + // Slow version, used if caller hasn't stored an upper and lower case version + bool operator()(StringData v1, StringData v2, bool = false, bool = false) const + { + if (v2.is_null() && !v1.is_null()) + return false; + + if (v1.size() > v2.size()) + return false; + std::string v1_upper = case_map(v1, true, IgnoreErrors); + std::string v1_lower = case_map(v1, false, IgnoreErrors); + return equal_case_fold(v2.suffix(v1.size()), v1_upper.c_str(), v1_lower.c_str()); + } + bool operator()(BinaryData b1, BinaryData b2, bool = false, bool = false) const + { + StringData s1(b1.data(), b1.size()); + StringData s2(b2.data(), b2.size()); + return this->operator()(s1, s2, false, false); + } + + template + bool operator()(A, B) const + { + REALM_ASSERT(false); + return false; + } + template + bool operator()(A, B, C, D) const + { + REALM_ASSERT(false); + return false; + } + bool operator()(int64_t, int64_t, bool, bool) const + { + REALM_ASSERT(false); + return false; + } + + static std::string description() + { + return "ENDSWITH[c]"; + } + + static const int condition = -1; +}; + +struct EqualIns : public HackClass { + bool operator()(StringData v1, const char* v1_upper, const char* v1_lower, StringData v2, bool = false, + bool = false) const + { + if (v1.is_null() != v2.is_null()) + return false; + + return v1.size() == v2.size() && equal_case_fold(v2, v1_upper, v1_lower); + } + + // Slow version, used if caller hasn't stored an upper and lower case version + bool operator()(StringData v1, StringData v2, bool = false, bool = false) const + { + if (v1.is_null() != v2.is_null()) + return false; + + if (v1.size() != v2.size()) + return false; + std::string v1_upper = case_map(v1, true, IgnoreErrors); + std::string v1_lower = case_map(v1, false, IgnoreErrors); + return equal_case_fold(v2, v1_upper.c_str(), v1_lower.c_str()); + } + bool operator()(BinaryData b1, BinaryData b2, bool = false, bool = false) const + { + StringData s1(b1.data(), b1.size()); + StringData s2(b2.data(), b2.size()); + return this->operator()(s1, s2, false, false); + } + + template + bool operator()(A, B) const + { + REALM_ASSERT(false); + return false; + } + template + bool operator()(A, B, C, D) const + { + REALM_ASSERT(false); + return false; + } + bool operator()(int64_t, int64_t, bool, bool) const + { + REALM_ASSERT(false); + return false; + } + + static std::string description() + { + return "==[c]"; + } + + static const int condition = -1; +}; + +struct NotEqualIns : public HackClass { + bool operator()(StringData v1, const char* v1_upper, const char* v1_lower, StringData v2, bool = false, + bool = false) const + { + if (v1.is_null() != v2.is_null()) + return true; + return v1.size() != v2.size() || !equal_case_fold(v2, v1_upper, v1_lower); + } + + // Slow version, used if caller hasn't stored an upper and lower case version + bool operator()(StringData v1, StringData v2, bool = false, bool = false) const + { + if (v1.is_null() != v2.is_null()) + return true; + + if (v1.size() != v2.size()) + return true; + std::string v1_upper = case_map(v1, true, IgnoreErrors); + std::string v1_lower = case_map(v1, false, IgnoreErrors); + return !equal_case_fold(v2, v1_upper.c_str(), v1_lower.c_str()); + } + bool operator()(BinaryData b1, BinaryData b2, bool = false, bool = false) const + { + StringData s1(b1.data(), b1.size()); + StringData s2(b2.data(), b2.size()); + return this->operator()(s1, s2, false, false); + } + + template + bool operator()(A, B) const + { + REALM_ASSERT(false); + return false; + } + template + bool operator()(A, B, C, D) const + { + REALM_ASSERT(false); + return false; + } + + static std::string description() + { + return "!=[c]"; + } + + static const int condition = -1; +}; + +struct Greater { + static const int avx = 0x1E; // _CMP_GT_OQ + template + bool operator()(const T& v1, const T& v2, bool v1null = false, bool v2null = false) const + { + if (v1null || v2null) + return false; + + return v1 > v2; + } + static const int condition = cond_Greater; + template + bool operator()(A, B, C, D) const + { + REALM_ASSERT(false); + return false; + } + + bool can_match(int64_t v, int64_t lbound, int64_t ubound) + { + static_cast(lbound); + return ubound > v; + } + bool will_match(int64_t v, int64_t lbound, int64_t ubound) + { + static_cast(ubound); + return lbound > v; + } + + static std::string description() + { + return ">"; + } +}; + +struct None { + template + bool operator()(const T&, const T&, bool = false, bool = false) const + { + return true; + } + static const int condition = cond_None; + template + bool operator()(A, B, C, D) const + { + REALM_ASSERT(false); + return false; + } + bool can_match(int64_t v, int64_t lbound, int64_t ubound) + { + static_cast(lbound); + static_cast(ubound); + static_cast(v); + return true; + } + bool will_match(int64_t v, int64_t lbound, int64_t ubound) + { + static_cast(lbound); + static_cast(ubound); + static_cast(v); + return true; + } + + static std::string description() + { + return "none"; + } +}; + +struct NotNull { + template + bool operator()(const T&, const T&, bool v = false, bool = false) const + { + return !v; + } + static const int condition = cond_LeftNotNull; + template + bool operator()(A, B, C, D) const + { + REALM_ASSERT(false); + return false; + } + bool can_match(int64_t v, int64_t lbound, int64_t ubound) + { + static_cast(lbound); + static_cast(ubound); + static_cast(v); + return true; + } + bool will_match(int64_t v, int64_t lbound, int64_t ubound) + { + static_cast(lbound); + static_cast(ubound); + static_cast(v); + return true; + } + static std::string description() + { + return "!= NULL"; + } +}; + + +struct Less { + static const int avx = 0x11; // _CMP_LT_OQ + template + bool operator()(const T& v1, const T& v2, bool v1null = false, bool v2null = false) const + { + if (v1null || v2null) + return false; + + return v1 < v2; + } + template + bool operator()(A, B, C, D) const + { + REALM_ASSERT(false); + return false; + } + static const int condition = cond_Less; + bool can_match(int64_t v, int64_t lbound, int64_t ubound) + { + static_cast(ubound); + return lbound < v; + } + bool will_match(int64_t v, int64_t lbound, int64_t ubound) + { + static_cast(lbound); + return ubound < v; + } + static std::string description() + { + return "<"; + } +}; + +struct LessEqual : public HackClass { + static const int avx = 0x12; // _CMP_LE_OQ + template + bool operator()(const T& v1, const T& v2, bool v1null = false, bool v2null = false) const + { + if (v1null && v2null) + return true; + + return (!v1null && !v2null && v1 <= v2); + } + template + bool operator()(A, B, C, D) const + { + REALM_ASSERT(false); + return false; + } + static std::string description() + { + return "<="; + } + static const int condition = -1; +}; + +struct GreaterEqual : public HackClass { + static const int avx = 0x1D; // _CMP_GE_OQ + template + bool operator()(const T& v1, const T& v2, bool v1null = false, bool v2null = false) const + { + if (v1null && v2null) + return true; + + return (!v1null && !v2null && v1 >= v2); + } + template + bool operator()(A, B, C, D) const + { + REALM_ASSERT(false); + return false; + } + static std::string description() + { + return ">="; + } + static const int condition = -1; +}; + + +// CompareLess is a temporary hack to have a generalized way to compare any realm types. Todo, enable correct < +// operator of StringData (currently gives circular header dependency with utf8.hpp) +template +struct CompareLess { + static bool compare(T v1, T v2, bool = false, bool = false) + { + return v1 < v2; + } +}; +template <> +struct CompareLess { + static bool compare(StringData v1, StringData v2, bool = false, bool = false) + { + bool ret = utf8_compare(v1.data(), v2.data()); + return ret; + } +}; + +} // namespace realm + +#endif // REALM_QUERY_CONDITIONS_HPP diff --git a/!main project/Pods/Realm/include/core/realm/query_engine.hpp b/!main project/Pods/Realm/include/core/realm/query_engine.hpp new file mode 100644 index 0000000..806bc2a --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/query_engine.hpp @@ -0,0 +1,2521 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +/* +A query consists of node objects, one for each query condition. Each node contains pointers to all other nodes: + +node1 node2 node3 +------ ----- ----- +node2* node1* node1* +node3* node3* node2* + +The construction of all this takes part in query.cpp. Each node has two important functions: + + aggregate(start, end) + aggregate_local(start, end) + +The aggregate() function executes the aggregate of a query. You can call the method on any of the nodes +(except children nodes of OrNode and SubtableNode) - it has the same behaviour. The function contains +scheduling that calls aggregate_local(start, end) on different nodes with different start/end ranges, +depending on what it finds is most optimal. + +The aggregate_local() function contains a tight loop that tests the condition of its own node, and upon match +it tests all other conditions at that index to report a full match or not. It will remain in the tight loop +after a full match. + +So a call stack with 2 and 9 being local matches of a node could look like this: + +aggregate(0, 10) + node1->aggregate_local(0, 3) + node2->find_first_local(2, 3) + node3->find_first_local(2, 3) + node3->aggregate_local(3, 10) + node1->find_first_local(4, 5) + node2->find_first_local(4, 5) + node1->find_first_local(7, 8) + node2->find_first_local(7, 8) + +find_first_local(n, n + 1) is a function that can be used to test a single row of another condition. Note that +this is very simplified. There are other statistical arguments to the methods, and also, find_first_local() can be +called from a callback function called by an integer Array. + + +Template arguments in methods: +---------------------------------------------------------------------------------------------------- + +TConditionFunction: Each node has a condition from query_conditions.c such as Equal, GreaterEqual, etc + +TConditionValue: Type of values in condition column. That is, int64_t, float, int, bool, etc + +TAction: What to do with each search result, from the enums act_ReturnFirst, act_Count, act_Sum, etc + +TResult: Type of result of actions - float, double, int64_t, etc. Special notes: For act_Count it's + int64_t, for RLM_FIND_ALL it's int64_t which points at destination array. + +TSourceColumn: Type of source column used in actions, or *ignored* if no source column is used (like for + act_Count, act_ReturnFirst) + + +There are two important classes used in queries: +---------------------------------------------------------------------------------------------------- +SequentialGetter Column iterator used to get successive values with leaf caching. Used both for condition columns + and aggregate source column + +AggregateState State of the aggregate - contains a state variable that stores intermediate sum, max, min, + etc, etc. + +*/ + +#ifndef REALM_QUERY_ENGINE_HPP +#define REALM_QUERY_ENGINE_HPP + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#if REALM_X86_OR_X64_TRUE && defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 160040219 +#include +#endif + +namespace realm { + +// Number of matches to find in best condition loop before breaking out to probe other conditions. Too low value gives +// too many constant time overheads everywhere in the query engine. Too high value makes it adapt less rapidly to +// changes in match frequencies. +const size_t findlocals = 64; + +// Average match distance in linear searches where further increase in distance no longer increases query speed +// (because time spent on handling each match becomes insignificant compared to time spent on the search). +const size_t bestdist = 512; + +// Minimum number of matches required in a certain condition before it can be used to compute statistics. Too high +// value can spent too much time in a bad node (with high match frequency). Too low value gives inaccurate statistics. +const size_t probe_matches = 4; + +const size_t bitwidth_time_unit = 64; + +typedef bool (*CallbackDummy)(int64_t); + +class ParentNode { + typedef ParentNode ThisType; + +public: + ParentNode() = default; + virtual ~ParentNode() = default; + + void gather_children(std::vector& v) + { + m_children.clear(); + size_t i = v.size(); + v.push_back(this); + + if (m_child) + m_child->gather_children(v); + + m_children = v; + m_children.erase(m_children.begin() + i); + m_children.insert(m_children.begin(), this); + } + + double cost() const + { + return 8 * bitwidth_time_unit / m_dD + + m_dT; // dt = 1/64 to 1. Match dist is 8 times more important than bitwidth + } + + size_t find_first(size_t start, size_t end); + + virtual void init() + { + // Verify that the cached column accessor is still valid + verify_column(); // throws + + if (m_child) + m_child->init(); + + m_column_action_specializer = nullptr; + } + + void set_table(const Table& table) + { + if (&table == m_table) + return; + + m_table.reset(&table); + if (m_child) + m_child->set_table(table); + table_changed(); + } + + virtual size_t find_first_local(size_t start, size_t end) = 0; + + virtual void aggregate_local_prepare(Action TAction, DataType col_id, bool nullable); + + template + bool column_action_specialization(QueryStateBase* st, SequentialGetterBase* source_column, size_t r) + { + // TResult: type of query result + // TSourceValue: type of aggregate source + using TSourceValue = typename TSourceColumn::value_type; + using TResult = typename ColumnTypeTraitsSum::sum_type; + + // Sum of float column must accumulate in double + static_assert(!(TAction == act_Sum && + (std::is_same::value && !std::is_same::value)), + ""); + + TSourceValue av{}; + // uses_val test because compiler cannot see that IntegerColumn::get has no side effect and result is + // discarded + if (static_cast*>(st)->template uses_val() && source_column != nullptr) { + REALM_ASSERT_DEBUG(dynamic_cast*>(source_column) != nullptr); + av = static_cast*>(source_column)->get_next(r); + } + REALM_ASSERT_DEBUG(dynamic_cast*>(st) != nullptr); + bool cont = static_cast*>(st)->template match(r, 0, av); + return cont; + } + + virtual size_t aggregate_local(QueryStateBase* st, size_t start, size_t end, size_t local_limit, + SequentialGetterBase* source_column); + + + virtual std::string validate() + { + if (error_code != "") + return error_code; + if (m_child == nullptr) + return ""; + else + return m_child->validate(); + } + + ParentNode(const ParentNode& from) + : ParentNode(from, nullptr) + { + } + + ParentNode(const ParentNode& from, QueryNodeHandoverPatches* patches) + : m_child(from.m_child ? from.m_child->clone(patches) : nullptr) + , m_condition_column_idx(from.m_condition_column_idx) + , m_dD(from.m_dD) + , m_dT(from.m_dT) + , m_probes(from.m_probes) + , m_matches(from.m_matches) + , m_table(patches ? ConstTableRef{} : from.m_table) + { + } + + void add_child(std::unique_ptr child) + { + if (m_child) + m_child->add_child(std::move(child)); + else + m_child = std::move(child); + } + + virtual std::unique_ptr clone(QueryNodeHandoverPatches* = nullptr) const = 0; + + virtual void apply_handover_patch(QueryNodeHandoverPatches& patches, Group& group) + { + if (m_child) + m_child->apply_handover_patch(patches, group); + } + + virtual void verify_column() const = 0; + + virtual std::string describe(util::serializer::SerialisationState&) const + { + return ""; + } + + virtual std::string describe_condition() const + { + return "matches"; + } + + virtual std::string describe_expression(util::serializer::SerialisationState& state) const + { + std::string s; + s = describe(state); + if (m_child) { + s = s + " and " + m_child->describe_expression(state); + } + return s; + } + + std::unique_ptr m_child; + std::vector m_children; + size_t m_condition_column_idx = npos; // Column of search criteria + + double m_dD; // Average row distance between each local match at current position + double m_dT = 0.0; // Time overhead of testing index i + 1 if we have just tested index i. > 1 for linear scans, 0 + // for index/tableview + + size_t m_probes = 0; + size_t m_matches = 0; + +protected: + typedef bool (ParentNode::*Column_action_specialized)(QueryStateBase*, SequentialGetterBase*, size_t); + Column_action_specialized m_column_action_specializer; + ConstTableRef m_table; + std::string error_code; + + const ColumnBase& get_column_base(size_t ndx) + { + return m_table->get_column_base(ndx); + } + + template + const ColType& get_column(size_t ndx) + { + auto& col = m_table->get_column_base(ndx); + REALM_ASSERT_DEBUG(dynamic_cast(&col)); + return static_cast(col); + } + + ColumnType get_real_column_type(size_t ndx) + { + return m_table->get_real_column_type(ndx); + } + + template + void copy_getter(SequentialGetter& dst, size_t& dst_idx, const SequentialGetter& src, + const QueryNodeHandoverPatches* patches) + { + if (src.m_column) { + if (patches) + dst_idx = src.m_column->get_column_index(); + else + dst.init(src.m_column); + } + } + + void do_verify_column(const ColumnBase* col, size_t col_ndx = npos) const + { + if (col_ndx == npos) + col_ndx = m_condition_column_idx; + if (m_table && col_ndx != npos) { + m_table->verify_column(col_ndx, col); + } + } + +private: + virtual void table_changed() = 0; +}; + +// For conditions on a subtable (encapsulated in subtable()...end_subtable()). These return the parent row as match if +// and only if one or more subtable rows match the condition. +class SubtableNode : public ParentNode { +public: + SubtableNode(size_t column, std::unique_ptr condition) + : m_condition(std::move(condition)) + { + m_dT = 100.0; + m_condition_column_idx = column; + } + + void init() override + { + ParentNode::init(); + + m_dD = 10.0; + + // m_condition is first node in condition of subtable query. + if (m_condition) { + // Can't call init() here as usual since the subtable can be degenerate + // m_condition->init(table); + std::vector v; + m_condition->gather_children(v); + } + } + + void table_changed() override + { + m_col_type = m_table->get_real_column_type(m_condition_column_idx); + REALM_ASSERT(m_col_type == col_type_Table || m_col_type == col_type_Mixed); + if (m_col_type == col_type_Table) + m_column = &m_table->get_column_table(m_condition_column_idx); + else // Mixed + m_column = &m_table->get_column_mixed(m_condition_column_idx); + } + + void verify_column() const override + { + if (m_table) + m_table->verify_column(m_condition_column_idx, m_column); + } + + std::string validate() override + { + if (error_code != "") + return error_code; + if (m_condition == nullptr) + return "Unbalanced subtable/end_subtable block"; + else + return m_condition->validate(); + } + + std::string describe(util::serializer::SerialisationState&) const override + { + throw SerialisationError("Serialising a query which contains a subtable expression is currently unsupported."); + } + + + size_t find_first_local(size_t start, size_t end) override + { + REALM_ASSERT(m_table); + REALM_ASSERT(m_condition); + + for (size_t s = start; s < end; ++s) { + ConstTableRef subtable; // TBD: optimize this back to Table* + if (m_col_type == col_type_Table) + subtable = static_cast(m_column)->get_subtable_tableref(s); + else { + subtable = static_cast(m_column)->get_subtable_tableref(s); + if (!subtable) + continue; + } + + if (subtable->is_degenerate()) + return not_found; + + m_condition->set_table(*subtable); + m_condition->init(); + const size_t subsize = subtable->size(); + const size_t sub = m_condition->find_first(0, subsize); + + if (sub != not_found) + return s; + } + return not_found; + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return std::unique_ptr(new SubtableNode(*this, patches)); + } + + SubtableNode(const SubtableNode& from, QueryNodeHandoverPatches* patches) + : ParentNode(from, patches) + , m_condition(from.m_condition ? from.m_condition->clone(patches) : nullptr) + , m_column(from.m_column) + , m_col_type(from.m_col_type) + { + if (m_column && patches) + m_condition_column_idx = m_column->get_column_index(); + } + + void apply_handover_patch(QueryNodeHandoverPatches& patches, Group& group) override + { + m_condition->apply_handover_patch(patches, group); + ParentNode::apply_handover_patch(patches, group); + } + + std::unique_ptr m_condition; + const ColumnBase* m_column = nullptr; + ColumnType m_col_type; +}; + +namespace _impl { + +template +struct CostHeuristic; + +template <> +struct CostHeuristic { + static constexpr double dD() + { + return 100.0; + } + static constexpr double dT() + { + return 1.0 / 4.0; + } +}; + +template <> +struct CostHeuristic { + static constexpr double dD() + { + return 100.0; + } + static constexpr double dT() + { + return 1.0 / 4.0; + } +}; + +// FIXME: Add AdaptiveStringColumn, BasicColumn, etc. +} + +class ColumnNodeBase : public ParentNode { +protected: + ColumnNodeBase(size_t column_idx) + { + m_condition_column_idx = column_idx; + } + + ColumnNodeBase(const ColumnNodeBase& from, QueryNodeHandoverPatches* patches) + : ParentNode(from, patches) + , m_last_local_match(from.m_last_local_match) + , m_local_matches(from.m_local_matches) + , m_local_limit(from.m_local_limit) + , m_fastmode_disabled(from.m_fastmode_disabled) + , m_action(from.m_action) + , m_state(from.m_state) + , m_source_column(from.m_source_column) + { + } + + template + bool match_callback(int64_t v) + { + using TSourceValue = typename ColType::value_type; + using QueryStateType = typename ColumnTypeTraitsSum::sum_type; + + size_t i = to_size_t(v); + m_last_local_match = i; + m_local_matches++; + + auto state = static_cast*>(m_state); + auto source_column = static_cast*>(m_source_column); + + // Test remaining sub conditions of this node. m_children[0] is the node that called match_callback(), so skip + // it + for (size_t c = 1; c < m_children.size(); c++) { + m_children[c]->m_probes++; + size_t m = m_children[c]->find_first_local(i, i + 1); + if (m != i) + return true; + } + + bool b; + if (state->template uses_val()) { // Compiler cannot see that IntegerColumn::Get has no side effect + // and result is discarded + TSourceValue av = source_column->get_next(i); + b = state->template match(i, 0, av); + } + else { + b = state->template match(i, 0, TSourceValue{}); + } + + return b; + } + + // Aggregate bookkeeping + size_t m_last_local_match = npos; + size_t m_local_matches = 0; + size_t m_local_limit = 0; + bool m_fastmode_disabled = false; + Action m_action; + QueryStateBase* m_state = nullptr; + SequentialGetterBase* m_source_column = + nullptr; // Column of values used in aggregate (act_FindAll, actReturnFirst, act_Sum, etc) +}; + +template +class IntegerNodeBase : public ColumnNodeBase { + using ThisType = IntegerNodeBase; + +public: + using TConditionValue = typename ColType::value_type; + static const bool nullable = ColType::nullable; + + template + bool find_callback_specialization(size_t s, size_t end_in_leaf) + { + using AggregateColumnType = typename GetColumnType::type; + bool cont; + size_t start_in_leaf = s - this->m_leaf_start; + cont = this->m_leaf_ptr->template find( + m_value, start_in_leaf, end_in_leaf, this->m_leaf_start, nullptr, + std::bind(std::mem_fn(&ThisType::template match_callback), this, + std::placeholders::_1)); + return cont; + } + +protected: + using LeafType = typename ColType::LeafType; + using LeafInfo = typename ColType::LeafInfo; + + size_t aggregate_local_impl(QueryStateBase* st, size_t start, size_t end, size_t local_limit, + SequentialGetterBase* source_column, int c) + { + REALM_ASSERT(m_children.size() > 0); + m_local_matches = 0; + m_local_limit = local_limit; + m_last_local_match = start - 1; + m_state = st; + + // If there are no other nodes than us (m_children.size() == 1) AND the column used for our condition is + // the same as the column used for the aggregate action, then the entire query can run within scope of that + // column only, with no references to other columns: + bool fastmode = should_run_in_fastmode(source_column); + for (size_t s = start; s < end;) { + cache_leaf(s); + + size_t end_in_leaf; + if (end > m_leaf_end) + end_in_leaf = m_leaf_end - m_leaf_start; + else + end_in_leaf = end - m_leaf_start; + + if (fastmode) { + bool cont; + size_t start_in_leaf = s - m_leaf_start; + cont = m_leaf_ptr->find(c, m_action, m_value, start_in_leaf, end_in_leaf, m_leaf_start, + static_cast*>(st)); + if (!cont) + return not_found; + } + // Else, for each match in this node, call our IntegerNodeBase::match_callback to test remaining nodes + // and/or extract + // aggregate payload from aggregate column: + else { + m_source_column = source_column; + bool cont = (this->*m_find_callback_specialized)(s, end_in_leaf); + if (!cont) + return not_found; + } + + if (m_local_matches == m_local_limit) + break; + + s = end_in_leaf + m_leaf_start; + } + + if (m_local_matches == m_local_limit) { + m_dD = (m_last_local_match + 1 - start) / (m_local_matches + 1.0); + return m_last_local_match + 1; + } + else { + m_dD = (end - start) / (m_local_matches + 1.0); + return end; + } + } + + IntegerNodeBase(TConditionValue value, size_t column_idx) + : ColumnNodeBase(column_idx) + , m_value(std::move(value)) + { + } + + IntegerNodeBase(const ThisType& from, QueryNodeHandoverPatches* patches) + : ColumnNodeBase(from, patches) + , m_value(from.m_value) + , m_condition_column(from.m_condition_column) + , m_find_callback_specialized(from.m_find_callback_specialized) + { + if (m_condition_column && patches) + m_condition_column_idx = m_condition_column->get_column_index(); + } + + void table_changed() override + { + m_condition_column = &get_column(m_condition_column_idx); + } + + void verify_column() const override + { + do_verify_column(m_condition_column); + } + + void init() override + { + ColumnNodeBase::init(); + + m_dT = _impl::CostHeuristic::dT(); + m_dD = _impl::CostHeuristic::dD(); + + // Clear leaf cache + m_leaf_end = 0; + m_array_ptr.reset(); // Explicitly destroy the old one first, because we're reusing the memory. + m_array_ptr.reset(new (&m_leaf_cache_storage) LeafType(m_table->get_alloc())); + } + + void get_leaf(const ColType& col, size_t ndx) + { + size_t ndx_in_leaf; + LeafInfo leaf_info{&m_leaf_ptr, m_array_ptr.get()}; + col.get_leaf(ndx, ndx_in_leaf, leaf_info); + m_leaf_start = ndx - ndx_in_leaf; + m_leaf_end = m_leaf_start + m_leaf_ptr->size(); + } + + void cache_leaf(size_t s) + { + if (s >= m_leaf_end || s < m_leaf_start) { + get_leaf(*m_condition_column, s); + size_t w = m_leaf_ptr->get_width(); + m_dT = (w == 0 ? 1.0 / REALM_MAX_BPNODE_SIZE : w / float(bitwidth_time_unit)); + } + } + + bool should_run_in_fastmode(SequentialGetterBase* source_column) const + { + return (m_children.size() == 1 && + (source_column == nullptr || + (!m_fastmode_disabled && + static_cast*>(source_column)->m_column == m_condition_column))); + } + + // Search value: + TConditionValue m_value; + + // Column on which search criteria are applied + const ColType* m_condition_column = nullptr; + + // Leaf cache + using LeafCacheStorage = typename std::aligned_storage::type; + LeafCacheStorage m_leaf_cache_storage; + std::unique_ptr m_array_ptr; + const LeafType* m_leaf_ptr = nullptr; + size_t m_leaf_start = npos; + size_t m_leaf_end = 0; + size_t m_local_end; + + // Aggregate optimization + using TFind_callback_specialized = bool (ThisType::*)(size_t, size_t); + TFind_callback_specialized m_find_callback_specialized = nullptr; +}; + + +template +class IntegerNode : public IntegerNodeBase { + using BaseType = IntegerNodeBase; + using ThisType = IntegerNode; + +public: + static const bool special_null_node = false; + using TConditionValue = typename BaseType::TConditionValue; + + IntegerNode(TConditionValue value, size_t column_ndx) + : BaseType(value, column_ndx) + { + } + IntegerNode(const IntegerNode& from, QueryNodeHandoverPatches* patches) + : BaseType(from, patches) + { + } + + void aggregate_local_prepare(Action action, DataType col_id, bool is_nullable) override + { + this->m_fastmode_disabled = (col_id == type_Float || col_id == type_Double); + this->m_action = action; + this->m_find_callback_specialized = get_specialized_callback(action, col_id, is_nullable); + } + + size_t aggregate_local(QueryStateBase* st, size_t start, size_t end, size_t local_limit, + SequentialGetterBase* source_column) override + { + constexpr int cond = TConditionFunction::condition; + return this->aggregate_local_impl(st, start, end, local_limit, source_column, cond); + } + + size_t find_first_local(size_t start, size_t end) override + { + REALM_ASSERT(this->m_table); + + while (start < end) { + + // Cache internal leaves + if (start >= this->m_leaf_end || start < this->m_leaf_start) { + this->get_leaf(*this->m_condition_column, start); + } + + // FIXME: Create a fast bypass when you just need to check 1 row, which is used alot from within core. + // It should just call array::get and save the initial overhead of find_first() which has become quite + // big. Do this when we have cleaned up core a bit more. + + size_t end2; + if (end > this->m_leaf_end) + end2 = this->m_leaf_end - this->m_leaf_start; + else + end2 = end - this->m_leaf_start; + + size_t s; + s = this->m_leaf_ptr->template find_first(this->m_value, start - this->m_leaf_start, + end2); + + if (s == not_found) { + start = this->m_leaf_end; + continue; + } + else + return s + this->m_leaf_start; + } + + return not_found; + } + + std::string describe(util::serializer::SerialisationState& state) const override + { + return state.describe_column(ParentNode::m_table, IntegerNodeBase::m_condition_column->get_column_index()) + + " " + describe_condition() + " " + util::serializer::print_value(IntegerNodeBase::m_value); + } + + std::string describe_condition() const override + { + return TConditionFunction::description(); + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return std::unique_ptr(new IntegerNode(*this, patches)); + } + +protected: + using TFind_callback_specialized = typename BaseType::TFind_callback_specialized; + + static TFind_callback_specialized get_specialized_callback(Action action, DataType col_id, bool is_nullable) + { + switch (action) { + case act_Count: + return get_specialized_callback_2_int(col_id, is_nullable); + case act_Sum: + return get_specialized_callback_2(col_id, is_nullable); + case act_Max: + return get_specialized_callback_2(col_id, is_nullable); + case act_Min: + return get_specialized_callback_2(col_id, is_nullable); + case act_FindAll: + return get_specialized_callback_2_int(col_id, is_nullable); + case act_CallbackIdx: + return get_specialized_callback_2_int(col_id, is_nullable); + default: + break; + } + REALM_ASSERT(false); // Invalid aggregate function + return nullptr; + } + + template + static TFind_callback_specialized get_specialized_callback_2(DataType col_id, bool is_nullable) + { + switch (col_id) { + case type_Int: + return get_specialized_callback_3(is_nullable); + case type_Float: + return get_specialized_callback_3(is_nullable); + case type_Double: + return get_specialized_callback_3(is_nullable); + default: + break; + } + REALM_ASSERT(false); // Invalid aggregate source column + return nullptr; + } + + template + static TFind_callback_specialized get_specialized_callback_2_int(DataType col_id, bool is_nullable) + { + if (col_id == type_Int) { + return get_specialized_callback_3(is_nullable); + } + REALM_ASSERT(false); // Invalid aggregate source column + return nullptr; + } + + template + static TFind_callback_specialized get_specialized_callback_3(bool is_nullable) + { + if (is_nullable) { + return &BaseType::template find_callback_specialization; + } + else { + return &BaseType::template find_callback_specialization; + } + } +}; + +template +class IntegerNode : public IntegerNodeBase { +public: + using BaseType = IntegerNodeBase; + using TConditionValue = typename BaseType::TConditionValue; + + IntegerNode(TConditionValue value, size_t column_ndx) + : BaseType(value, column_ndx) + { + } + ~IntegerNode() + { + if (m_result) { + m_result->destroy(); + } + } + + void init() override + { + BaseType::init(); + m_nb_needles = m_needles.size(); + + if (has_search_index()) { + if (m_result) { + m_result->clear(); + } + else { + ref_type ref = IntegerColumn::create(Allocator::get_default()); + m_result = std::make_unique(); + m_result->init_from_ref(Allocator::get_default(), ref); + } + + IntegerNodeBase::m_condition_column->find_all(*m_result, this->m_value, 0, realm::npos); + m_index_get = 0; + m_index_end = m_result->size(); + IntegerNodeBase::m_dT = 0; + } + } + + void consume_condition(IntegerNode* other) + { + REALM_ASSERT(this->m_condition_column == other->m_condition_column); + REALM_ASSERT(other->m_needles.empty()); + if (m_needles.empty()) { + m_needles.insert(this->m_value); + } + m_needles.insert(other->m_value); + } + + bool has_search_index() const + { + return IntegerNodeBase::m_condition_column->has_search_index(); + } + + size_t find_first_local(size_t start, size_t end) override + { + REALM_ASSERT(this->m_table); + + if (has_search_index()) { + if (m_index_end == 0) + return not_found; + + if (start <= m_index_last_start) + m_index_get = 0; + else + m_index_last_start = start; + + REALM_ASSERT(m_result); + while (m_index_get < m_index_end) { + // m_results are stored in sorted ascending order, guaranteed by the string index + size_t ndx = size_t(m_result->get(m_index_get)); + if (ndx >= end) { + break; + } + m_index_get++; + if (ndx >= start) { + return ndx; + } + } + return not_found; + } + + + while (start < end) { + // Cache internal leaves + this->cache_leaf(start); + + size_t end2; + if (end > this->m_leaf_end) + end2 = this->m_leaf_end - this->m_leaf_start; + else + end2 = end - this->m_leaf_start; + + auto start2 = start - this->m_leaf_start; + size_t s = realm::npos; + if (m_nb_needles) { + s = find_first_haystack(start2, end2); + } + else if (end2 - start2 == 1) { + if (this->m_leaf_ptr->get(start2) == this->m_value) { + s = start2; + } + } + else { + s = this->m_leaf_ptr->template find_first(this->m_value, start2, end2); + } + + if (s == not_found) { + start = this->m_leaf_end; + continue; + } + else + return s + this->m_leaf_start; + } + + return not_found; + } + + std::string describe(util::serializer::SerialisationState& state) const override + { + REALM_ASSERT(this->m_condition_column != nullptr); + std::string col_descr = state.describe_column(this->m_table, this->m_condition_column->get_column_index()); + + if (m_needles.empty()) { + return col_descr + " " + Equal::description() + " " + + util::serializer::print_value(IntegerNodeBase::m_value); + } + + // FIXME: once the parser supports it, print something like "column IN {n1, n2, n3}" + std::string desc = "("; + bool is_first = true; + for (auto it : m_needles) { + if (!is_first) + desc += " or "; + desc += + col_descr + " " + Equal::description() + " " + util::serializer::print_value(it); // "it" may be null + is_first = false; + } + desc += ")"; + return desc; + } + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return std::unique_ptr(new IntegerNode(*this, patches)); + } + +private: + std::unordered_set m_needles; + std::unique_ptr m_result; + size_t m_nb_needles = 0; + size_t m_index_get = 0; + size_t m_index_last_start = 0; + size_t m_index_end = 0; + + IntegerNode(const IntegerNode& from, QueryNodeHandoverPatches* patches) + : BaseType(from, patches) + , m_needles(from.m_needles) + { + } + size_t find_first_haystack(size_t start, size_t end) + { + const auto not_in_set = m_needles.end(); + // for a small number of conditions, it is faster to do a linear search than to compute the hash + // the decision threshold was determined experimentally to be 22 conditions + bool search = m_nb_needles < 22; + auto cmp_fn = [this, search, not_in_set](const auto& v) { + if (search) { + for (auto it = m_needles.begin(); it != not_in_set; ++it) { + if (*it == v) + return true; + } + return false; + } + else { + return (m_needles.find(v) != not_in_set); + } + }; + for (size_t i = start; i < end; ++i) { + auto val = this->m_leaf_ptr->get(i); + if (cmp_fn(val)) { + return i; + } + } + return realm::npos; + } +}; + + +// This node is currently used for floats and doubles only +template +class FloatDoubleNode : public ParentNode { +public: + using TConditionValue = typename ColType::value_type; + static const bool special_null_node = false; + + FloatDoubleNode(TConditionValue v, size_t column_ndx) + : m_value(v) + { + m_condition_column_idx = column_ndx; + m_dT = 1.0; + } + FloatDoubleNode(null, size_t column_ndx) + : m_value(null::get_null_float()) + { + m_condition_column_idx = column_ndx; + m_dT = 1.0; + } + + void table_changed() override + { + m_condition_column.init(&get_column(m_condition_column_idx)); + } + + void verify_column() const override + { + do_verify_column(m_condition_column.m_column); + } + + void init() override + { + ParentNode::init(); + m_dD = 100.0; + } + + size_t find_first_local(size_t start, size_t end) override + { + TConditionFunction cond; + + auto find = [&](bool nullability) { + bool m_value_nan = nullability ? null::is_null_float(m_value) : false; + for (size_t s = start; s < end; ++s) { + TConditionValue v = m_condition_column.get_next(s); + REALM_ASSERT(!(null::is_null_float(v) && !nullability)); + if (cond(v, m_value, nullability ? null::is_null_float(v) : false, m_value_nan)) + return s; + } + return not_found; + }; + + // This will inline the second case but no the first. Todo, use templated lambda when switching to c++14 + if (m_table->is_nullable(m_condition_column_idx)) + return find(true); + else + return find(false); + } + + std::string describe(util::serializer::SerialisationState& state) const override + { + REALM_ASSERT(m_condition_column.m_column != nullptr); + return state.describe_column(ParentNode::m_table, m_condition_column.m_column->get_column_index()) + + " " + describe_condition() + " " + util::serializer::print_value(FloatDoubleNode::m_value); + } + std::string describe_condition() const override + { + return TConditionFunction::description(); + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return std::unique_ptr(new FloatDoubleNode(*this, patches)); + } + + FloatDoubleNode(const FloatDoubleNode& from, QueryNodeHandoverPatches* patches) + : ParentNode(from, patches) + , m_value(from.m_value) + { + copy_getter(m_condition_column, m_condition_column_idx, from.m_condition_column, patches); + } + +protected: + TConditionValue m_value; + SequentialGetter m_condition_column; +}; + +template +class SizeNode : public ParentNode { +public: + SizeNode(int64_t v, size_t column) + : m_value(v) + { + m_condition_column_idx = column; + } + + void table_changed() override + { + m_condition_column = &get_column(m_condition_column_idx); + } + + void verify_column() const override + { + do_verify_column(m_condition_column); + } + + void init() override + { + ParentNode::init(); + m_dD = 10.0; + } + + size_t find_first_local(size_t start, size_t end) override + { + for (size_t s = start; s < end; ++s) { + TConditionValue v = m_condition_column->get(s); + if (v) { + int64_t sz = m_size_operator(v); + if (TConditionFunction()(sz, m_value)) + return s; + } + } + return not_found; + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return std::unique_ptr(new SizeNode(*this, patches)); + } + + SizeNode(const SizeNode& from, QueryNodeHandoverPatches* patches) + : ParentNode(from, patches) + , m_value(from.m_value) + , m_condition_column(from.m_condition_column) + { + if (m_condition_column && patches) + m_condition_column_idx = m_condition_column->get_column_index(); + } + +private: + using TConditionValue = typename ColType::value_type; + + int64_t m_value; + const ColType* m_condition_column = nullptr; + Size m_size_operator; +}; + + +template +class BinaryNode : public ParentNode { +public: + using TConditionValue = BinaryData; + static const bool special_null_node = false; + + BinaryNode(BinaryData v, size_t column) + : m_value(v) + { + m_dT = 100.0; + m_condition_column_idx = column; + } + + BinaryNode(null, size_t column) + : BinaryNode(BinaryData{}, column) + { + } + + void table_changed() override + { + m_condition_column = &get_column(m_condition_column_idx); + } + + void verify_column() const override + { + do_verify_column(m_condition_column); + } + + void init() override + { + ParentNode::init(); + + m_dD = 100.0; + } + + size_t find_first_local(size_t start, size_t end) override + { + TConditionFunction condition; + for (size_t s = start; s < end; ++s) { + BinaryData value = m_condition_column->get(s); + if (condition(m_value.get(), value)) + return s; + } + return not_found; + } + + virtual std::string describe(util::serializer::SerialisationState& state) const override + { + REALM_ASSERT(m_condition_column != nullptr); + return state.describe_column(ParentNode::m_table, m_condition_column->get_column_index()) + + " " + TConditionFunction::description() + " " + + util::serializer::print_value(BinaryNode::m_value.get()); + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return std::unique_ptr(new BinaryNode(*this, patches)); + } + + BinaryNode(const BinaryNode& from, QueryNodeHandoverPatches* patches) + : ParentNode(from, patches) + , m_value(from.m_value) + , m_condition_column(from.m_condition_column) + { + if (m_condition_column && patches) + m_condition_column_idx = m_condition_column->get_column_index(); + } + +private: + OwnedBinaryData m_value; + const BinaryColumn* m_condition_column; +}; + + +class TimestampNodeBase : public ParentNode { +public: + using TConditionValue = Timestamp; + static const bool special_null_node = false; + using LeafTypeSeconds = typename IntNullColumn::LeafType; + using LeafInfoSeconds = typename IntNullColumn::LeafInfo; + using LeafTypeNanos = typename IntegerColumn::LeafType; + using LeafInfoNanos = typename IntegerColumn::LeafInfo; + + + TimestampNodeBase(Timestamp v, size_t column) + : m_value(v) + , m_needle_seconds(m_value.is_null() ? util::none : util::make_optional(m_value.get_seconds())) + { + m_condition_column_idx = column; + } + + TimestampNodeBase(null, size_t column) + : TimestampNodeBase(Timestamp{}, column) + { + } + + void table_changed() override + { + m_condition_column = &get_column(m_condition_column_idx); + } + + void verify_column() const override + { + do_verify_column(m_condition_column); + } + + void init() override + { + ParentNode::init(); + + m_dD = 100.0; + + // Clear leaf cache + m_leaf_end_seconds = 0; + m_array_ptr_seconds.reset(); // Explicitly destroy the old one first, because we're reusing the memory. + m_array_ptr_seconds.reset(new (&m_leaf_cache_storage_seconds) LeafTypeSeconds(m_table->get_alloc())); + m_leaf_end_nanos = 0; + m_array_ptr_nanos.reset(); // Explicitly destroy the old one first, because we're reusing the memory. + m_array_ptr_nanos.reset(new (&m_leaf_cache_storage_nanos) LeafTypeNanos(m_table->get_alloc())); + m_condition_column_is_nullable = m_condition_column->is_nullable(); + } + +protected: + void get_leaf_seconds(const TimestampColumn& col, size_t ndx) + { + size_t ndx_in_leaf; + LeafInfoSeconds leaf_info_seconds{&m_leaf_ptr_seconds, m_array_ptr_seconds.get()}; + col.get_seconds_leaf(ndx, ndx_in_leaf, leaf_info_seconds); + m_leaf_start_seconds = ndx - ndx_in_leaf; + m_leaf_end_seconds = m_leaf_start_seconds + m_leaf_ptr_seconds->size(); + } + + void get_leaf_nanos(const TimestampColumn& col, size_t ndx) + { + size_t ndx_in_leaf; + LeafInfoNanos leaf_info_nanos{&m_leaf_ptr_nanos, m_array_ptr_nanos.get()}; + col.get_nanoseconds_leaf(ndx, ndx_in_leaf, leaf_info_nanos); + m_leaf_start_nanos = ndx - ndx_in_leaf; + m_leaf_end_nanos = m_leaf_start_nanos + m_leaf_ptr_nanos->size(); + } + + util::Optional get_seconds_and_cache(size_t ndx) + { + // Cache internal leaves + if (ndx >= this->m_leaf_end_seconds || ndx < this->m_leaf_start_seconds) { + this->get_leaf_seconds(*this->m_condition_column, ndx); + } + const size_t ndx_in_leaf = ndx - m_leaf_start_seconds; + return this->m_leaf_ptr_seconds->get(ndx_in_leaf); + } + + int32_t get_nanoseconds_and_cache(size_t ndx) + { + // Cache internal leaves + if (ndx >= this->m_leaf_end_nanos || ndx < this->m_leaf_start_nanos) { + this->get_leaf_nanos(*this->m_condition_column, ndx); + } + return int32_t(this->m_leaf_ptr_nanos->get(ndx - this->m_leaf_start_nanos)); + } + + TimestampNodeBase(const TimestampNodeBase& from, QueryNodeHandoverPatches* patches) + : ParentNode(from, patches) + , m_value(from.m_value) + , m_needle_seconds(from.m_needle_seconds) + , m_condition_column(from.m_condition_column) + , m_condition_column_is_nullable(from.m_condition_column_is_nullable) + { + if (m_condition_column && patches) + m_condition_column_idx = m_condition_column->get_column_index(); + } + + Timestamp m_value; + util::Optional m_needle_seconds; + const TimestampColumn* m_condition_column; + bool m_condition_column_is_nullable = false; + + // Leaf cache seconds + using LeafCacheStorageSeconds = + typename std::aligned_storage::type; + LeafCacheStorageSeconds m_leaf_cache_storage_seconds; + std::unique_ptr m_array_ptr_seconds; + const LeafTypeSeconds* m_leaf_ptr_seconds = nullptr; + size_t m_leaf_start_seconds = npos; + size_t m_leaf_end_seconds = 0; + + // Leaf cache nanoseconds + using LeafCacheStorageNanos = typename std::aligned_storage::type; + LeafCacheStorageNanos m_leaf_cache_storage_nanos; + std::unique_ptr m_array_ptr_nanos; + const LeafTypeNanos* m_leaf_ptr_nanos = nullptr; + size_t m_leaf_start_nanos = npos; + size_t m_leaf_end_nanos = 0; +}; + +template +class TimestampNode : public TimestampNodeBase { +public: + using TimestampNodeBase::TimestampNodeBase; + + template + size_t find_first_local_seconds(size_t start, size_t end) + { + while (start < end) { + // Cache internal leaves + if (start >= this->m_leaf_end_seconds || start < this->m_leaf_start_seconds) { + this->get_leaf_seconds(*this->m_condition_column, start); + } + + size_t end2; + if (end > this->m_leaf_end_seconds) + end2 = this->m_leaf_end_seconds - this->m_leaf_start_seconds; + else + end2 = end - this->m_leaf_start_seconds; + + size_t s = this->m_leaf_ptr_seconds->template find_first( + m_needle_seconds, start - this->m_leaf_start_seconds, end2); + + if (s == not_found) { + start = this->m_leaf_end_seconds; + continue; + } + return s + this->m_leaf_start_seconds; + } + return not_found; + } + + // see query_engine.cpp for operator specialisations + size_t find_first_local(size_t start, size_t end) override + { + REALM_ASSERT(this->m_table); + + size_t ret = m_condition_column->find(m_value, start, end); + return ret; + } + + virtual std::string describe(util::serializer::SerialisationState& state) const override + { + REALM_ASSERT(m_condition_column != nullptr); + return state.describe_column(ParentNode::m_table, m_condition_column->get_column_index()) + " " + + TConditionFunction::description() + " " + util::serializer::print_value(TimestampNode::m_value); + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return std::unique_ptr(new TimestampNode(*this, patches)); + } +}; + +template <> +size_t TimestampNode::find_first_local(size_t start, size_t end); +template <> +size_t TimestampNode::find_first_local(size_t start, size_t end); +template <> +size_t TimestampNode::find_first_local(size_t start, size_t end); +template <> +size_t TimestampNode::find_first_local(size_t start, size_t end); +template <> +size_t TimestampNode::find_first_local(size_t start, size_t end); +template <> +size_t TimestampNode::find_first_local(size_t start, size_t end); +template <> +size_t TimestampNode::find_first_local(size_t start, size_t end); + +class StringNodeBase : public ParentNode { +public: + using TConditionValue = StringData; + static const bool special_null_node = true; + + StringNodeBase(StringData v, size_t column) + : m_value(v.is_null() ? util::none : util::make_optional(std::string(v))) + { + m_condition_column_idx = column; + } + + void table_changed() override + { + m_condition_column = &get_column_base(m_condition_column_idx); + m_column_type = get_real_column_type(m_condition_column_idx); + } + + void verify_column() const override + { + do_verify_column(m_condition_column); + } + + bool has_search_index() const + { + return m_condition_column->has_search_index(); + } + + void init() override + { + ParentNode::init(); + + m_dT = 10.0; + m_probes = 0; + m_matches = 0; + m_end_s = 0; + m_leaf_start = 0; + m_leaf_end = 0; + } + + void clear_leaf_state() + { + m_leaf.reset(nullptr); + } + + StringNodeBase(const StringNodeBase& from, QueryNodeHandoverPatches* patches) + : ParentNode(from, patches) + , m_value(from.m_value) + , m_condition_column(from.m_condition_column) + , m_column_type(from.m_column_type) + , m_leaf_type(from.m_leaf_type) + { + if (m_condition_column && patches) + m_condition_column_idx = m_condition_column->get_column_index(); + } + + virtual std::string describe(util::serializer::SerialisationState& state) const override + { + REALM_ASSERT(m_condition_column != nullptr); + StringData sd; + if (bool(StringNodeBase::m_value)) { + sd = StringData(StringNodeBase::m_value.value()); + } + return state.describe_column(ParentNode::m_table, m_condition_column->get_column_index()) + + " " + describe_condition() + " " + util::serializer::print_value(sd); + } + +protected: + util::Optional m_value; + + const ColumnBase* m_condition_column = nullptr; + ColumnType m_column_type; + + // Used for linear scan through short/long-string + std::unique_ptr m_leaf; + StringColumn::LeafType m_leaf_type; + size_t m_end_s = 0; + size_t m_leaf_start = 0; + size_t m_leaf_end = 0; + + inline StringData get_string(size_t s) + { + StringData t; + + if (m_column_type == col_type_StringEnum) { + // enum + t = static_cast(m_condition_column)->get(s); + } + else { + // short or long + const StringColumn* asc = static_cast(m_condition_column); + REALM_ASSERT_3(s, <, asc->size()); + if (s >= m_end_s || s < m_leaf_start) { + // we exceeded current leaf's range + clear_leaf_state(); + size_t ndx_in_leaf; + m_leaf = asc->get_leaf(s, ndx_in_leaf, m_leaf_type); + m_leaf_start = s - ndx_in_leaf; + + if (m_leaf_type == StringColumn::leaf_type_Small) + m_end_s = m_leaf_start + static_cast(*m_leaf).size(); + else if (m_leaf_type == StringColumn::leaf_type_Medium) + m_end_s = m_leaf_start + static_cast(*m_leaf).size(); + else + m_end_s = m_leaf_start + static_cast(*m_leaf).size(); + } + + if (m_leaf_type == StringColumn::leaf_type_Small) + t = static_cast(*m_leaf).get(s - m_leaf_start); + else if (m_leaf_type == StringColumn::leaf_type_Medium) + t = static_cast(*m_leaf).get(s - m_leaf_start); + else + t = static_cast(*m_leaf).get_string(s - m_leaf_start); + } + return t; + } +}; + +// Conditions for strings. Note that Equal is specialized later in this file! +template +class StringNode : public StringNodeBase { +public: + StringNode(StringData v, size_t column) + : StringNodeBase(v, column) + { + auto upper = case_map(v, true); + auto lower = case_map(v, false); + if (!upper || !lower) { + error_code = "Malformed UTF-8: " + std::string(v); + } + else { + m_ucase = std::move(*upper); + m_lcase = std::move(*lower); + } + } + + void init() override + { + clear_leaf_state(); + + m_dD = 100.0; + + StringNodeBase::init(); + } + + size_t find_first_local(size_t start, size_t end) override + { + TConditionFunction cond; + + for (size_t s = start; s < end; ++s) { + StringData t = get_string(s); + + if (cond(StringData(m_value), m_ucase.c_str(), m_lcase.c_str(), t)) + return s; + } + return not_found; + } + + virtual std::string describe_condition() const override + { + return TConditionFunction::description(); + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return std::unique_ptr(new StringNode(*this, patches)); + } + + StringNode(const StringNode& from, QueryNodeHandoverPatches* patches) + : StringNodeBase(from, patches) + , m_ucase(from.m_ucase) + , m_lcase(from.m_lcase) + { + } + +protected: + std::string m_ucase; + std::string m_lcase; +}; + +// Specialization for Contains condition on Strings - we specialize because we can utilize Boyer-Moore +template <> +class StringNode : public StringNodeBase { +public: + StringNode(StringData v, size_t column) + : StringNodeBase(v, column), m_charmap() + { + if (v.size() == 0) + return; + + // Build a dictionary of char-to-last distances in the search string + // (zero indicates that the char is not in needle) + size_t last_char_pos = v.size()-1; + for (size_t i = 0; i < last_char_pos; ++i) { + // we never jump longer increments than 255 chars, even if needle is longer (to fit in one byte) + uint8_t jump = last_char_pos-i < 255 ? static_cast(last_char_pos-i) : 255; + + unsigned char c = v[i]; + m_charmap[c] = jump; + } + } + + void init() override + { + clear_leaf_state(); + + m_dD = 100.0; + + StringNodeBase::init(); + } + + + size_t find_first_local(size_t start, size_t end) override + { + Contains cond; + + for (size_t s = start; s < end; ++s) { + StringData t = get_string(s); + + if (cond(StringData(m_value), m_charmap, t)) + return s; + } + return not_found; + } + + virtual std::string describe_condition() const override + { + return Contains::description(); + } + + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return std::unique_ptr(new StringNode(*this, patches)); + } + + StringNode(const StringNode& from, QueryNodeHandoverPatches* patches) + : StringNodeBase(from, patches) + , m_charmap(from.m_charmap) + { + } + +protected: + std::array m_charmap; +}; + +// Specialization for ContainsIns condition on Strings - we specialize because we can utilize Boyer-Moore +template <> +class StringNode : public StringNodeBase { +public: + StringNode(StringData v, size_t column) + : StringNodeBase(v, column), m_charmap() + { + auto upper = case_map(v, true); + auto lower = case_map(v, false); + if (!upper || !lower) { + error_code = "Malformed UTF-8: " + std::string(v); + } + else { + m_ucase = std::move(*upper); + m_lcase = std::move(*lower); + } + + if (v.size() == 0) + return; + + // Build a dictionary of char-to-last distances in the search string + // (zero indicates that the char is not in needle) + size_t last_char_pos = m_ucase.size()-1; + for (size_t i = 0; i < last_char_pos; ++i) { + // we never jump longer increments than 255 chars, even if needle is longer (to fit in one byte) + uint8_t jump = last_char_pos-i < 255 ? static_cast(last_char_pos-i) : 255; + + unsigned char uc = m_ucase[i]; + unsigned char lc = m_lcase[i]; + m_charmap[uc] = jump; + m_charmap[lc] = jump; + } + + } + + void init() override + { + clear_leaf_state(); + + m_dD = 100.0; + + StringNodeBase::init(); + } + + + size_t find_first_local(size_t start, size_t end) override + { + ContainsIns cond; + + for (size_t s = start; s < end; ++s) { + StringData t = get_string(s); + // The current behaviour is to return all results when querying for a null string. + // See comment above Query_NextGen_StringConditions on why every string including "" contains null. + if (!bool(m_value)) { + return s; + } + if (cond(StringData(m_value), m_ucase.c_str(), m_lcase.c_str(), m_charmap, t)) + return s; + } + return not_found; + } + + virtual std::string describe_condition() const override + { + return ContainsIns::description(); + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return std::unique_ptr(new StringNode(*this, patches)); + } + + StringNode(const StringNode& from, QueryNodeHandoverPatches* patches) + : StringNodeBase(from, patches) + , m_charmap(from.m_charmap) + , m_ucase(from.m_ucase) + , m_lcase(from.m_lcase) + { + } + +protected: + std::array m_charmap; + std::string m_ucase; + std::string m_lcase; +}; + +class StringNodeEqualBase : public StringNodeBase { +public: + StringNodeEqualBase(StringData v, size_t column) + : StringNodeBase(v, column) + { + } + StringNodeEqualBase(const StringNodeEqualBase& from, QueryNodeHandoverPatches* patches) + : StringNodeBase(from, patches) + { + } + ~StringNodeEqualBase() noexcept override + { + deallocate(); + } + + void deallocate() noexcept; + void init() override; + size_t find_first_local(size_t start, size_t end) override; + + virtual std::string describe_condition() const override + { + return Equal::description(); + } + +protected: + inline BinaryData str_to_bin(const StringData& s) noexcept + { + return BinaryData(s.data(), s.size()); + } + + virtual void _search_index_init() = 0; + virtual size_t _find_first_local(size_t start, size_t end) = 0; + + size_t m_key_ndx = not_found; + size_t m_last_indexed; + + // Used for linear scan through enum-string + SequentialGetter m_cse; + + // Used for index lookup + std::unique_ptr m_index_matches; + bool m_index_matches_destroy = false; + std::unique_ptr> m_index_getter; + size_t m_results_start; + size_t m_results_end; + size_t m_last_start; +}; + +// Specialization for Equal condition on Strings - we specialize because we can utilize indexes (if they exist) for +// Equal. This specialisation also supports combining other StringNode conditions into itself in order to +// optimise the non-indexed linear search that can be happen when many conditions are OR'd together in an "IN" query. +// Future optimization: make specialization for greater, notequal, etc +template <> +class StringNode : public StringNodeEqualBase { +public: + using StringNodeEqualBase::StringNodeEqualBase; + + void _search_index_init() override; + + void consume_condition(StringNode* other); + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return std::unique_ptr(new StringNode(*this, patches)); + } + + std::string describe(util::serializer::SerialisationState& state) const override; + + StringNode(const StringNode& from, QueryNodeHandoverPatches* patches) + : StringNodeEqualBase(from, patches) + { + for (auto it = from.m_needles.begin(); it != from.m_needles.end(); ++it) { + if (it->data() == nullptr && it->size() == 0) { + m_needles.insert(StringData()); // nulls + } + else { + m_needle_storage.emplace_back(StringBuffer()); + m_needle_storage.back().append(it->data(), it->size()); + m_needles.insert(StringData(m_needle_storage.back().data(), m_needle_storage.back().size())); + } + } + } + +private: + template + size_t find_first_in(ArrayType& array, size_t begin, size_t end); + + size_t _find_first_local(size_t start, size_t end) override; + std::unordered_set m_needles; + std::vector m_needle_storage; +}; + + +// Specialization for EqualIns condition on Strings - we specialize because we can utilize indexes (if they exist) for +// EqualIns. +template <> +class StringNode : public StringNodeEqualBase { +public: + StringNode(StringData v, size_t column) + : StringNodeEqualBase(v, column) + { + auto upper = case_map(v, true); + auto lower = case_map(v, false); + if (!upper || !lower) { + error_code = "Malformed UTF-8: " + std::string(v); + } + else { + m_ucase = std::move(*upper); + m_lcase = std::move(*lower); + } + } + + void _search_index_init() override; + + virtual std::string describe_condition() const override + { + return EqualIns::description(); + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return std::unique_ptr(new StringNode(*this, patches)); + } + + StringNode(const StringNode& from, QueryNodeHandoverPatches* patches) + : StringNodeEqualBase(from, patches) + , m_ucase(from.m_ucase) + , m_lcase(from.m_lcase) + { + } + +private: + std::string m_ucase; + std::string m_lcase; + + size_t _find_first_local(size_t start, size_t end) override; +}; + +// OR node contains at least two node pointers: Two or more conditions to OR +// together in m_conditions, and the next AND condition (if any) in m_child. +// +// For 'second.equal(23).begin_group().first.equal(111).Or().first.equal(222).end_group().third().equal(555)', this +// will first set m_conditions[0] = left-hand-side through constructor, and then later, when .first.equal(222) is +// invoked, invocation will set m_conditions[1] = right-hand-side through Query& Query::Or() (see query.cpp). +// In there, m_child is also set to next AND condition (if any exists) following the OR. +class OrNode : public ParentNode { +public: + OrNode(std::unique_ptr condition) + { + m_dT = 50.0; + if (condition) + m_conditions.emplace_back(std::move(condition)); + } + + OrNode(const OrNode& other, QueryNodeHandoverPatches* patches) + : ParentNode(other, patches) + { + for (const auto& condition : other.m_conditions) { + m_conditions.emplace_back(condition->clone(patches)); + } + } + + void table_changed() override + { + for (auto& condition : m_conditions) { + condition->set_table(*m_table); + } + } + + void verify_column() const override + { + for (auto& condition : m_conditions) { + condition->verify_column(); + } + } + + std::string describe(util::serializer::SerialisationState& state) const override + { + std::string s; + for (size_t i = 0; i < m_conditions.size(); ++i) { + if (m_conditions[i]) { + s += m_conditions[i]->describe_expression(state); + if (i != m_conditions.size() - 1) { + s += " or "; + } + } + } + if (m_conditions.size() > 1) { + s = "(" + s + ")"; + } + return s; + } + + void init() override + { + ParentNode::init(); + + m_dD = 10.0; + + std::sort(m_conditions.begin(), m_conditions.end(), + [](auto& a, auto& b) { return a->m_condition_column_idx < b->m_condition_column_idx; }); + + combine_conditions>(); + combine_conditions>(); + combine_conditions>(); + + m_start.clear(); + m_start.resize(m_conditions.size(), 0); + + m_last.clear(); + m_last.resize(m_conditions.size(), 0); + + m_was_match.clear(); + m_was_match.resize(m_conditions.size(), false); + + std::vector v; + for (auto& condition : m_conditions) { + condition->init(); + v.clear(); + condition->gather_children(v); + } + } + + size_t find_first_local(size_t start, size_t end) override + { + if (start >= end) + return not_found; + + size_t index = not_found; + + for (size_t c = 0; c < m_conditions.size(); ++c) { + // out of order search; have to discard cached results + if (start < m_start[c]) { + m_last[c] = 0; + m_was_match[c] = false; + } + // already searched this range and didn't match + else if (m_last[c] >= end) + continue; + // already search this range and *did* match + else if (m_was_match[c] && m_last[c] >= start) { + if (index > m_last[c]) + index = m_last[c]; + continue; + } + + m_start[c] = start; + size_t fmax = std::max(m_last[c], start); + size_t f = m_conditions[c]->find_first(fmax, end); + m_was_match[c] = f != not_found; + m_last[c] = f == not_found ? end : f; + if (f != not_found && index > m_last[c]) + index = m_last[c]; + } + + return index; + } + + std::string validate() override + { + if (error_code != "") + return error_code; + if (m_conditions.size() == 0) + return "Missing left-hand side of OR"; + if (m_conditions.size() == 1) + return "Missing right-hand side of OR"; + std::string s; + if (m_child != 0) + s = m_child->validate(); + if (s != "") + return s; + for (size_t i = 0; i < m_conditions.size(); ++i) { + s = m_conditions[i]->validate(); + if (s != "") + return s; + } + return ""; + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return std::unique_ptr(new OrNode(*this, patches)); + } + + void apply_handover_patch(QueryNodeHandoverPatches& patches, Group& group) override + { + for (auto it = m_conditions.rbegin(); it != m_conditions.rend(); ++it) + (*it)->apply_handover_patch(patches, group); + + ParentNode::apply_handover_patch(patches, group); + } + + std::vector> m_conditions; + +private: + template + void combine_conditions() { + QueryNodeType* first_match = nullptr; + QueryNodeType* advance = nullptr; + auto it = m_conditions.begin(); + while (it != m_conditions.end()) { + // Only try to optimize on QueryNodeType conditions without search index + auto node = it->get(); + if ((first_match = dynamic_cast(node)) && first_match->m_child == nullptr && + !first_match->has_search_index()) { + auto col_ndx = first_match->m_condition_column_idx; + auto next = it + 1; + while (next != m_conditions.end() && (*next)->m_condition_column_idx == col_ndx) { + auto next_node = next->get(); + if ((advance = dynamic_cast(next_node)) && next_node->m_child == nullptr) { + first_match->consume_condition(advance); + next = m_conditions.erase(next); + } + else { + ++next; + } + } + it = next; + } + else { + ++it; + } + } + } + + // start index of the last find for each cond + std::vector m_start; + // last looked at index of the lasft find for each cond + // is a matching index if m_was_match is true + std::vector m_last; + std::vector m_was_match; +}; + + +class NotNode : public ParentNode { +public: + NotNode(std::unique_ptr condition) + : m_condition(std::move(condition)) + { + m_dT = 50.0; + } + + void table_changed() override + { + m_condition->set_table(*m_table); + } + + void verify_column() const override + { + m_condition->verify_column(); + } + + void init() override + { + ParentNode::init(); + + m_dD = 10.0; + + std::vector v; + + m_condition->init(); + v.clear(); + m_condition->gather_children(v); + + // Heuristics bookkeeping: + m_known_range_start = 0; + m_known_range_end = 0; + m_first_in_known_range = not_found; + } + + size_t find_first_local(size_t start, size_t end) override; + + std::string validate() override + { + if (error_code != "") + return error_code; + if (m_condition == 0) + return "Missing argument to Not"; + std::string s; + if (m_child != 0) + s = m_child->validate(); + if (s != "") + return s; + s = m_condition->validate(); + if (s != "") + return s; + return ""; + } + + virtual std::string describe(util::serializer::SerialisationState& state) const override + { + if (m_condition) { + return "!(" + m_condition->describe_expression(state) + ")"; + } + return "!()"; + } + + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return std::unique_ptr(new NotNode(*this, patches)); + } + + NotNode(const NotNode& from, QueryNodeHandoverPatches* patches) + : ParentNode(from, patches) + , m_condition(from.m_condition ? from.m_condition->clone(patches) : nullptr) + , m_known_range_start(from.m_known_range_start) + , m_known_range_end(from.m_known_range_end) + , m_first_in_known_range(from.m_first_in_known_range) + { + } + + void apply_handover_patch(QueryNodeHandoverPatches& patches, Group& group) override + { + m_condition->apply_handover_patch(patches, group); + ParentNode::apply_handover_patch(patches, group); + } + + std::unique_ptr m_condition; + +private: + // FIXME This heuristic might as well be reused for all condition nodes. + size_t m_known_range_start; + size_t m_known_range_end; + size_t m_first_in_known_range; + + bool evaluate_at(size_t rowndx); + void update_known(size_t start, size_t end, size_t first); + size_t find_first_loop(size_t start, size_t end); + size_t find_first_covers_known(size_t start, size_t end); + size_t find_first_covered_by_known(size_t start, size_t end); + size_t find_first_overlap_lower(size_t start, size_t end); + size_t find_first_overlap_upper(size_t start, size_t end); + size_t find_first_no_overlap(size_t start, size_t end); +}; + + +// Compare two columns with eachother row-by-row +template +class TwoColumnsNode : public ParentNode { +public: + using TConditionValue = typename ColType::value_type; + + TwoColumnsNode(size_t column1, size_t column2) + { + m_dT = 100.0; + m_condition_column_idx1 = column1; + m_condition_column_idx2 = column2; + } + + ~TwoColumnsNode() noexcept override + { + delete[] m_value.data(); + } + + void table_changed() override + { + m_getter1.init(&get_column(m_condition_column_idx1)); + m_getter2.init(&get_column(m_condition_column_idx2)); + } + + void verify_column() const override + { + do_verify_column(m_getter1.m_column, m_condition_column_idx1); + do_verify_column(m_getter2.m_column, m_condition_column_idx2); + } + + virtual std::string describe(util::serializer::SerialisationState& state) const override + { + REALM_ASSERT(m_getter1.m_column != nullptr && m_getter2.m_column != nullptr); + return state.describe_column(ParentNode::m_table, m_getter1.m_column->get_column_index()) + + " " + describe_condition() + " " + + state.describe_column(ParentNode::m_table,m_getter2.m_column->get_column_index()); + } + + virtual std::string describe_condition() const override + { + return TConditionFunction::description(); + } + + void init() override + { + ParentNode::init(); + m_dD = 100.0; + } + + size_t find_first_local(size_t start, size_t end) override + { + size_t s = start; + + while (s < end) { + if (std::is_same::value) { + // For int64_t we've created an array intrinsics named compare_leafs which template expands bitwidths + // of boths arrays to make Get faster. + m_getter1.cache_next(s); + m_getter2.cache_next(s); + + QueryState qs; + bool resume = m_getter1.m_leaf_ptr->template compare_leafs( + m_getter2.m_leaf_ptr, s - m_getter1.m_leaf_start, m_getter1.local_end(end), 0, &qs, + CallbackDummy()); + + if (resume) + s = m_getter1.m_leaf_end; + else + return to_size_t(qs.m_state) + m_getter1.m_leaf_start; + } + else { +// This is for float and double. + +#if 0 && defined(REALM_COMPILER_AVX) +// AVX has been disabled because of array alignment (see https://app.asana.com/0/search/8836174089724/5763107052506) +// +// For AVX you can call things like if (sseavx<1>()) to test for AVX, and then utilize _mm256_movemask_ps (VC) +// or movemask_cmp_ps (gcc/clang) +// +// See https://github.com/rrrlasse/realm/tree/AVX for an example of utilizing AVX for a two-column search which has +// been benchmarked to: floats: 288 ms vs 552 by using AVX compared to 2-level-unrolled FPU loop. doubles: 415 ms vs +// 475 (more bandwidth bound). Tests against SSE have not been performed; AVX may not pay off. Please benchmark +#endif + + TConditionValue v1 = m_getter1.get_next(s); + TConditionValue v2 = m_getter2.get_next(s); + TConditionFunction C; + + if (C(v1, v2)) + return s; + else + s++; + } + } + return not_found; + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return std::unique_ptr(new TwoColumnsNode(*this, patches)); + } + + TwoColumnsNode(const TwoColumnsNode& from, QueryNodeHandoverPatches* patches) + : ParentNode(from, patches) + , m_value(from.m_value) + , m_condition_column(from.m_condition_column) + , m_column_type(from.m_column_type) + , m_condition_column_idx1(from.m_condition_column_idx1) + , m_condition_column_idx2(from.m_condition_column_idx2) + { + if (m_condition_column) + m_condition_column_idx = m_condition_column->get_column_index(); + copy_getter(m_getter1, m_condition_column_idx1, from.m_getter1, patches); + copy_getter(m_getter2, m_condition_column_idx2, from.m_getter2, patches); + } + +private: + BinaryData m_value; + const BinaryColumn* m_condition_column = nullptr; + ColumnType m_column_type; + + size_t m_condition_column_idx1 = not_found; + size_t m_condition_column_idx2 = not_found; + + SequentialGetter m_getter1; + SequentialGetter m_getter2; +}; + + +// For Next-Generation expressions like col1 / col2 + 123 > col4 * 100. +class ExpressionNode : public ParentNode { +public: + ExpressionNode(std::unique_ptr); + + void init() override; + size_t find_first_local(size_t start, size_t end) override; + + void table_changed() override; + void verify_column() const override; + + virtual std::string describe(util::serializer::SerialisationState& state) const override; + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override; + void apply_handover_patch(QueryNodeHandoverPatches& patches, Group& group) override; + +private: + ExpressionNode(const ExpressionNode& from, QueryNodeHandoverPatches* patches); + + std::unique_ptr m_expression; +}; + + +struct LinksToNodeHandoverPatch : public QueryNodeHandoverPatch { + std::vector> m_target_rows; + size_t m_origin_column; +}; + +class LinksToNode : public ParentNode { +public: + LinksToNode(size_t origin_column_index, const ConstRow& target_row) + : m_origin_column(origin_column_index) + , m_target_rows(1, target_row) + { + m_dD = 10.0; + m_dT = 50.0; + } + + LinksToNode(size_t origin_column_index, const std::vector& target_rows) + : m_origin_column(origin_column_index) + , m_target_rows(target_rows) + { + m_dD = 10.0; + m_dT = 50.0; + } + + void table_changed() override + { + m_column_type = m_table->get_column_type(m_origin_column); + m_column = &const_cast(m_table.get())->get_column_link_base(m_origin_column); + REALM_ASSERT(m_column_type == type_Link || m_column_type == type_LinkList); + } + + void verify_column() const override + { + do_verify_column(m_column, m_origin_column); + } + + virtual std::string describe(util::serializer::SerialisationState&) const override + { + throw SerialisationError("Serialising a query which links to an object is currently unsupported."); + // We can do something like the following when core gets stable keys + //return describe_column() + " " + describe_condition() + " " + util::serializer::print_value(m_target_row.get_index()); + } + virtual std::string describe_condition() const override + { + return "links to"; + } + + + size_t find_first_local(size_t start, size_t end) override + { + REALM_ASSERT(m_column); + if (m_column_type == type_Link) { + LinkColumn& cl = static_cast(*m_column); + for (auto& row : m_target_rows) { + if (row.is_attached()) { + // LinkColumn stores link to row N as the integer N + 1 + auto pos = cl.find_first(row.get_index() + 1, start, end); + if (pos != realm::npos) { + return pos; + } + } + } + } + else if (m_column_type == type_LinkList) { + LinkListColumn& cll = static_cast(*m_column); + + for (size_t i = start; i < end; i++) { + LinkViewRef lv = cll.get(i); + for (auto& row : m_target_rows) { + if (row.is_attached()) { + if (lv->find(row.get_index()) != not_found) + return i; + } + } + } + } + + return not_found; + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return std::unique_ptr(patches ? new LinksToNode(*this, patches) : new LinksToNode(*this)); + } + + void apply_handover_patch(QueryNodeHandoverPatches& patches, Group& group) override + { + REALM_ASSERT(patches.size()); + std::unique_ptr abstract_patch = std::move(patches.back()); + patches.pop_back(); + + auto patch = dynamic_cast(abstract_patch.get()); + REALM_ASSERT(patch); + + m_origin_column = patch->m_origin_column; + auto sz = patch->m_target_rows.size(); + m_target_rows.resize(sz); + for (size_t i = 0; i < sz; i++) { + m_target_rows[i].apply_and_consume_patch(patch->m_target_rows[i], group); + } + + ParentNode::apply_handover_patch(patches, group); + } + +private: + size_t m_origin_column = npos; + std::vector m_target_rows; + LinkColumnBase* m_column = nullptr; + DataType m_column_type; + + LinksToNode(const LinksToNode& source, QueryNodeHandoverPatches* patches) + : ParentNode(source, patches) + { + auto patch = std::make_unique(); + patch->m_origin_column = source.m_column->get_column_index(); + auto sz = source.m_target_rows.size(); + patch->m_target_rows.resize(sz); + for (size_t i = 0; i < sz; i++) { + ConstRow::generate_patch(source.m_target_rows[i], patch->m_target_rows[i]); + } + patches->push_back(std::move(patch)); + } +}; + +} // namespace realm + +#endif // REALM_QUERY_ENGINE_HPP diff --git a/!main project/Pods/Realm/include/core/realm/query_expression.hpp b/!main project/Pods/Realm/include/core/realm/query_expression.hpp new file mode 100644 index 0000000..bd6e1a8 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/query_expression.hpp @@ -0,0 +1,4145 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +/* +This file lets you write queries in C++ syntax like: Expression* e = (first + 1 / second >= third + 12.3); + +Type conversion/promotion semantics is the same as in the C++ expressions, e.g float + int > double == float + +(float)int > double. + + +Grammar: +----------------------------------------------------------------------------------------------------------------------- + Expression: Subexpr2 Compare Subexpr2 + operator! Expression + + Subexpr2: Value + Columns + Subexpr2 Operator Subexpr2 + power(Subexpr2) // power(x) = x * x, as example of unary operator + + Value: T + + Operator>: +, -, *, / + + Compare: ==, !=, >=, <=, >, < + + T: bool, int, int64_t, float, double, StringData + + +Class diagram +----------------------------------------------------------------------------------------------------------------------- +Subexpr2 + void evaluate(size_t i, ValueBase* destination) + +Compare: public Subexpr2 + size_t find_first(size_t start, size_t end) // main method that executes query + + unique_ptr m_left; // left expression subtree + unique_ptr m_right; // right expression subtree + +Operator: public Subexpr2 + void evaluate(size_t i, ValueBase* destination) + unique_ptr m_left; // left expression subtree + unique_ptr m_right; // right expression subtree + +Value: public Subexpr2 + void evaluate(size_t i, ValueBase* destination) + T m_v[8]; + +Columns: public Subexpr2 + void evaluate(size_t i, ValueBase* destination) + SequentialGetter sg; // class bound to a column, lets you read values in a fast way + Table* m_table; + +class ColumnAccessor<>: public Columns + + +Call diagram: +----------------------------------------------------------------------------------------------------------------------- +Example of 'table.first > 34.6 + table.second': + +size_t Compare::find_first()-------------+ + | | + | | + | | + +--> Columns::evaluate() +--------> Operator::evaluate() + | | + Value::evaluate() Columns::evaluate() + +Operator, Value and Columns have an evaluate(size_t i, ValueBase* destination) method which returns a Value +containing 8 values representing table rows i...i + 7. + +So Value contains 8 concecutive values and all operations are based on these chunks. This is +to save overhead by virtual calls needed for evaluating a query that has been dynamically constructed at runtime. + + +Memory allocation: +----------------------------------------------------------------------------------------------------------------------- +Subexpressions created by the end-user are stack allocated. They are cloned to the heap when passed to UnaryOperator, +Operator, and Compare. Those types own the clones and deallocate them when destroyed. + + +Caveats, notes and todos +----------------------------------------------------------------------------------------------------------------------- + * Perhaps disallow columns from two different tables in same expression + * The name Columns (with s) an be confusing because we also have Column (without s) + * We have Columns::m_table, Query::m_table and ColumnAccessorBase::m_table that point at the same thing, even with + ColumnAccessor<> extending Columns. So m_table is redundant, but this is in order to keep class dependencies and + entanglement low so that the design is flexible (if you perhaps later want a Columns class that is not dependent + on ColumnAccessor) + +Nulls +----------------------------------------------------------------------------------------------------------------------- +First note that at array level, nulls are distinguished between non-null in different ways: +String: + m_data == 0 && m_size == 0 + +Integer, Bool, OldDateTime stored in ArrayIntNull: + value == get(0) (entry 0 determins a magic value that represents nulls) + +Float/double: + null::is_null(value) which tests if value bit-matches one specific bit pattern reserved for null + +The Columns class encapsulates all this into a simple class that, for any type T has + evaluate(size_t index) that reads values from a column, taking nulls in count + get(index) + set(index) + is_null(index) + set_null(index) +*/ + +#ifndef REALM_QUERY_EXPRESSION_HPP +#define REALM_QUERY_EXPRESSION_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +// Normally, if a next-generation-syntax condition is supported by the old query_engine.hpp, a query_engine node is +// created because it's faster (by a factor of 5 - 10). Because many of our existing next-generation-syntax unit +// unit tests are indeed simple enough to fallback to old query_engine, query_expression gets low test coverage. Undef +// flag to get higher query_expression test coverage. This is a good idea to try out each time you develop on/modify +// query_expression. + +#define REALM_OLDQUERY_FALLBACK + +namespace realm { + +template +T minimum(T a, T b) +{ + return a < b ? a : b; +} + +#ifdef REALM_OLDQUERY_FALLBACK +// Hack to avoid template instantiation errors. See create(). Todo, see if we can simplify only_numeric somehow +namespace _impl { + +template +inline T only_numeric(U in) +{ + return static_cast(util::unwrap(in)); +} + +template +inline int only_numeric(const StringData&) +{ + REALM_ASSERT(false); + return 0; +} + +template +inline int only_numeric(const BinaryData&) +{ + REALM_ASSERT(false); + return 0; +} + +template +inline StringData only_string_op_types(T in) +{ + REALM_ASSERT(false); + static_cast(in); + return StringData(); +} + +inline BinaryData only_string_op_types(BinaryData in) +{ + return in; +} + +template <> +inline StringData only_string_op_types(StringData in) +{ + return in; +} + +template +inline T no_timestamp(U in) +{ + return static_cast(util::unwrap(in)); +} + +template +inline int no_timestamp(const Timestamp&) +{ + REALM_ASSERT(false); + return 0; +} + +} // namespace _impl + +#endif // REALM_OLDQUERY_FALLBACK + +template +struct Plus { + T operator()(T v1, T v2) const + { + return v1 + v2; + } + static std::string description() + { + return "+"; + } + typedef T type; +}; + +template +struct Minus { + T operator()(T v1, T v2) const + { + return v1 - v2; + } + static std::string description() + { + return "-"; + } + typedef T type; +}; + +template +struct Div { + T operator()(T v1, T v2) const + { + return v1 / v2; + } + static std::string description() + { + return "/"; + } + typedef T type; +}; + +template +struct Mul { + T operator()(T v1, T v2) const + { + return v1 * v2; + } + static std::string description() + { + return "*"; + } + typedef T type; +}; + +// Unary operator +template +struct Pow { + T operator()(T v) const + { + return v * v; + } + static std::string description() + { + return "^"; + } + typedef T type; +}; + +// Finds a common type for T1 and T2 according to C++ conversion/promotion in arithmetic (float + int => float, etc) +template ::is_integer || std::is_same::value, + bool T2_is_int = std::numeric_limits::is_integer || std::is_same::value, + bool T1_is_widest = (sizeof(T1) > sizeof(T2) || std::is_same::value)> +struct Common; +template +struct Common { + typedef T1 type; +}; +template +struct Common { + typedef T2 type; +}; +template +struct Common { + typedef T1 type; +}; +template +struct Common { + typedef T2 type; +}; + + +struct RowIndex { + enum DetachedTag { + Detached, + }; + + explicit RowIndex() + : m_row_index(npos) + { + } + explicit RowIndex(size_t row_index) + : m_row_index(row_index) + { + } + RowIndex(DetachedTag) + : m_row_index() + { + } + + bool is_attached() const + { + return bool(m_row_index); + } + bool is_null() const + { + return is_attached() && *m_row_index == npos; + } + + bool operator==(const RowIndex& other) const + { + // Row indexes that are detached are never equal to any other row index. + if (!is_attached() || !other.is_attached()) + return false; + return m_row_index == other.m_row_index; + } + bool operator!=(const RowIndex& other) const + { + return !(*this == other); + } +private: + util::Optional m_row_index; +}; + +struct ValueBase { + static const size_t chunk_size = 8; + virtual void export_bool(ValueBase& destination) const = 0; + virtual void export_Timestamp(ValueBase& destination) const = 0; + virtual void export_int(ValueBase& destination) const = 0; + virtual void export_float(ValueBase& destination) const = 0; + virtual void export_int64_t(ValueBase& destination) const = 0; + virtual void export_double(ValueBase& destination) const = 0; + virtual void export_StringData(ValueBase& destination) const = 0; + virtual void export_BinaryData(ValueBase& destination) const = 0; + virtual void export_RowIndex(ValueBase& destination) const = 0; + virtual void export_null(ValueBase& destination) const = 0; + virtual void import(const ValueBase& destination) = 0; + + // If true, all values in the class come from a link list of a single field in the parent table (m_table). If + // false, then values come from successive rows of m_table (query operations are operated on in bulks for speed) + bool m_from_link_list; + + // Number of values stored in the class. + size_t m_values; +}; + +class Expression { +public: + Expression() + { + } + virtual ~Expression() + { + } + + virtual double init() + { + return 50.0; // Default dT + } + + virtual size_t find_first(size_t start, size_t end) const = 0; + virtual void set_base_table(const Table* table) = 0; + virtual void verify_column() const = 0; + virtual const Table* get_base_table() const = 0; + virtual std::string description(util::serializer::SerialisationState& state) const = 0; + + virtual std::unique_ptr clone(QueryNodeHandoverPatches*) const = 0; + virtual void apply_handover_patch(QueryNodeHandoverPatches&, Group&) + { + } +}; + +template +std::unique_ptr make_expression(Args&&... args) +{ + return std::unique_ptr(new T(std::forward(args)...)); +} + +class Subexpr { +public: + virtual ~Subexpr() + { + } + + virtual std::unique_ptr clone(QueryNodeHandoverPatches* = nullptr) const = 0; + virtual void apply_handover_patch(QueryNodeHandoverPatches&, Group&) + { + } + + // When the user constructs a query, it always "belongs" to one single base/parent table (regardless of + // any links or not and regardless of any queries assembled with || or &&). When you do a Query::find(), + // then Query::m_table is set to this table, and set_base_table() is called on all Columns and LinkMaps in + // the query expression tree so that they can set/update their internals as required. + // + // During thread-handover of a Query, set_base_table() is also called to make objects point at the new table + // instead of the old one from the old thread. + virtual void set_base_table(const Table*) + { + } + + virtual void verify_column() const = 0; + virtual std::string description(util::serializer::SerialisationState& state) const = 0; + + // Recursively fetch tables of columns in expression tree. Used when user first builds a stand-alone expression + // and + // binds it to a Query at a later time + virtual const Table* get_base_table() const + { + return nullptr; + } + + virtual bool has_constant_evaluation() const + { + return false; + } + + virtual bool has_search_index() const + { + return false; + } + + virtual std::vector find_all(util::Optional) const + { + return {}; + } + + virtual void evaluate(size_t index, ValueBase& destination) = 0; +}; + +template +std::unique_ptr make_subexpr(Args&&... args) +{ + return std::unique_ptr(new T(std::forward(args)...)); +} + +template +class Columns; +template +class Value; +class ConstantStringValue; +template +class Subexpr2; +template +class Operator; +template +class UnaryOperator; +template +class SizeOperator; +template +class Compare; +template +class UnaryLinkCompare; +class ColumnAccessorBase; + + +// Handle cases where left side is a constant (int, float, int64_t, double, StringData) +template +Query create(L left, const Subexpr2& right) +{ +// Purpose of below code is to intercept the creation of a condition and test if it's supported by the old +// query_engine.hpp which is faster. If it's supported, create a query_engine.hpp node, otherwise create a +// query_expression.hpp node. +// +// This method intercepts only Value Subexpr2. Interception of Subexpr2 Subexpr is elsewhere. + +#ifdef REALM_OLDQUERY_FALLBACK // if not defined, then never fallback to query_engine.hpp; always use query_expression + const Columns* column = dynamic_cast*>(&right); + // TODO: recognize size operator expressions + // auto size_operator = dynamic_cast, Subexpr>*>(&right); + + if (column && ((std::numeric_limits::is_integer && std::numeric_limits::is_integer) || + (std::is_same::value && std::is_same::value) || + (std::is_same::value && std::is_same::value) || + (std::is_same::value && std::is_same::value) || + (std::is_same::value && std::is_same::value) || + (std::is_same::value && std::is_same::value)) && + !column->links_exist()) { + const Table* t = column->get_base_table(); + Query q = Query(*t); + + if (std::is_same::value) + q.greater(column->column_ndx(), _impl::only_numeric(left)); + else if (std::is_same::value) + q.less(column->column_ndx(), _impl::only_numeric(left)); + else if (std::is_same::value) + q.equal(column->column_ndx(), left); + else if (std::is_same::value) + q.not_equal(column->column_ndx(), left); + else if (std::is_same::value) + q.greater_equal(column->column_ndx(), _impl::only_numeric(left)); + else if (std::is_same::value) + q.less_equal(column->column_ndx(), _impl::only_numeric(left)); + else if (std::is_same::value) + q.equal(column->column_ndx(), _impl::only_string_op_types(left), false); + else if (std::is_same::value) + q.not_equal(column->column_ndx(), _impl::only_string_op_types(left), false); + else if (std::is_same::value) + q.begins_with(column->column_ndx(), _impl::only_string_op_types(left)); + else if (std::is_same::value) + q.begins_with(column->column_ndx(), _impl::only_string_op_types(left), false); + else if (std::is_same::value) + q.ends_with(column->column_ndx(), _impl::only_string_op_types(left)); + else if (std::is_same::value) + q.ends_with(column->column_ndx(), _impl::only_string_op_types(left), false); + else if (std::is_same::value) + q.contains(column->column_ndx(), _impl::only_string_op_types(left)); + else if (std::is_same::value) + q.contains(column->column_ndx(), _impl::only_string_op_types(left), false); + else if (std::is_same::value) + q.like(column->column_ndx(), _impl::only_string_op_types(left)); + else if (std::is_same::value) + q.like(column->column_ndx(), _impl::only_string_op_types(left), false); + else { + // query_engine.hpp does not support this Cond. Please either add support for it in query_engine.hpp or + // fallback to using use 'return new Compare<>' instead. + REALM_ASSERT(false); + } + // Return query_engine.hpp node + return q; + } + else +#endif + { + // Return query_expression.hpp node + using CommonType = typename Common::type; + using ValueType = + typename std::conditional::value, ConstantStringValue, Value>::type; + return make_expression>(make_subexpr(left), right.clone()); + } +} + + +// All overloads where left-hand-side is Subexpr2: +// +// left-hand-side operator right-hand-side +// Subexpr2 +, -, *, /, <, >, ==, !=, <=, >= R, Subexpr2 +// +// For L = R = {int, int64_t, float, double, StringData, Timestamp}: +template +class Overloads { + typedef typename Common::type CommonType; + + std::unique_ptr clone_subexpr() const + { + return static_cast&>(*this).clone(); + } + +public: + // Arithmetic, right side constant + Operator> operator+(R right) const + { + return {clone_subexpr(), make_subexpr>(right)}; + } + Operator> operator-(R right) const + { + return {clone_subexpr(), make_subexpr>(right)}; + } + Operator> operator*(R right) const + { + return {clone_subexpr(), make_subexpr>(right)}; + } + Operator> operator/(R right) const + { + return {clone_subexpr(), make_subexpr>(right)}; + } + + // Arithmetic, right side subexpression + Operator> operator+(const Subexpr2& right) const + { + return {clone_subexpr(), right.clone()}; + } + Operator> operator-(const Subexpr2& right) const + { + return {clone_subexpr(), right.clone()}; + } + Operator> operator*(const Subexpr2& right) const + { + return {clone_subexpr(), right.clone()}; + } + Operator> operator/(const Subexpr2& right) const + { + return {clone_subexpr(), right.clone()}; + } + + // Compare, right side constant + Query operator>(R right) + { + return create(right, static_cast&>(*this)); + } + Query operator<(R right) + { + return create(right, static_cast&>(*this)); + } + Query operator>=(R right) + { + return create(right, static_cast&>(*this)); + } + Query operator<=(R right) + { + return create(right, static_cast&>(*this)); + } + Query operator==(R right) + { + return create(right, static_cast&>(*this)); + } + Query operator!=(R right) + { + return create(right, static_cast&>(*this)); + } + + // Purpose of this method is to intercept the creation of a condition and test if it's supported by the old + // query_engine.hpp which is faster. If it's supported, create a query_engine.hpp node, otherwise create a + // query_expression.hpp node. + // + // This method intercepts Subexpr2 Subexpr2 only. Value Subexpr2 is intercepted elsewhere. + template + Query create2(const Subexpr2& right) + { +#ifdef REALM_OLDQUERY_FALLBACK // if not defined, never fallback query_engine; always use query_expression + // Test if expressions are of type Columns. Other possibilities are Value and Operator. + const Columns* left_col = dynamic_cast*>(static_cast*>(this)); + const Columns* right_col = dynamic_cast*>(&right); + + // query_engine supports 'T-column ' for T = {int64_t, float, double}, op = {<, >, ==, !=, <=, + // >=}, + // but only if both columns are non-nullable, and aren't in linked tables. + if (left_col && right_col && std::is_same::value && !left_col->is_nullable() && + !right_col->is_nullable() && !left_col->links_exist() && !right_col->links_exist() && + !std::is_same::value) { + const Table* t = left_col->get_base_table(); + Query q = Query(*t); + + if (std::numeric_limits::is_integer || std::is_same::value) { + if (std::is_same::value) + q.less_int(left_col->column_ndx(), right_col->column_ndx()); + else if (std::is_same::value) + q.greater_int(left_col->column_ndx(), right_col->column_ndx()); + else if (std::is_same::value) + q.equal_int(left_col->column_ndx(), right_col->column_ndx()); + else if (std::is_same::value) + q.not_equal_int(left_col->column_ndx(), right_col->column_ndx()); + else if (std::is_same::value) + q.less_equal_int(left_col->column_ndx(), right_col->column_ndx()); + else if (std::is_same::value) + q.greater_equal_int(left_col->column_ndx(), right_col->column_ndx()); + else { + REALM_ASSERT(false); + } + } + else if (std::is_same::value) { + if (std::is_same::value) + q.less_float(left_col->column_ndx(), right_col->column_ndx()); + else if (std::is_same::value) + q.greater_float(left_col->column_ndx(), right_col->column_ndx()); + else if (std::is_same::value) + q.equal_float(left_col->column_ndx(), right_col->column_ndx()); + else if (std::is_same::value) + q.not_equal_float(left_col->column_ndx(), right_col->column_ndx()); + else if (std::is_same::value) + q.less_equal_float(left_col->column_ndx(), right_col->column_ndx()); + else if (std::is_same::value) + q.greater_equal_float(left_col->column_ndx(), right_col->column_ndx()); + else { + REALM_ASSERT(false); + } + } + else if (std::is_same::value) { + if (std::is_same::value) + q.less_double(left_col->column_ndx(), right_col->column_ndx()); + else if (std::is_same::value) + q.greater_double(left_col->column_ndx(), right_col->column_ndx()); + else if (std::is_same::value) + q.equal_double(left_col->column_ndx(), right_col->column_ndx()); + else if (std::is_same::value) + q.not_equal_double(left_col->column_ndx(), right_col->column_ndx()); + else if (std::is_same::value) + q.less_equal_double(left_col->column_ndx(), right_col->column_ndx()); + else if (std::is_same::value) + q.greater_equal_double(left_col->column_ndx(), right_col->column_ndx()); + else { + REALM_ASSERT(false); + } + } + else { + REALM_ASSERT(false); + } + // Return query_engine.hpp node + return q; + } + else +#endif + { + // Return query_expression.hpp node + return make_expression::type>>(clone_subexpr(), right.clone()); + } + } + + // Compare, right side subexpression + Query operator==(const Subexpr2& right) + { + return create2(right); + } + Query operator!=(const Subexpr2& right) + { + return create2(right); + } + Query operator>(const Subexpr2& right) + { + return create2(right); + } + Query operator<(const Subexpr2& right) + { + return create2(right); + } + Query operator>=(const Subexpr2& right) + { + return create2(right); + } + Query operator<=(const Subexpr2& right) + { + return create2(right); + } +}; + +// With this wrapper class we can define just 20 overloads inside Overloads instead of 5 * 20 = 100. Todo: We +// can +// consider if it's simpler/better to remove this class completely and just list all 100 overloads manually anyway. +template +class Subexpr2 : public Subexpr, + public Overloads, + public Overloads, + public Overloads, + public Overloads, + public Overloads, + public Overloads, + public Overloads, + public Overloads, + public Overloads, + public Overloads { +public: + virtual ~Subexpr2() + { + } + +#define RLM_U2(t, o) using Overloads::operator o; +#define RLM_U(o) \ + RLM_U2(int, o) \ + RLM_U2(float, o) \ + RLM_U2(double, o) \ + RLM_U2(int64_t, o) \ + RLM_U2(StringData, o) RLM_U2(bool, o) RLM_U2(OldDateTime, o) RLM_U2(Timestamp, o) RLM_U2(null, o) + RLM_U(+) RLM_U(-) RLM_U(*) RLM_U(/) RLM_U(>) RLM_U(<) RLM_U(==) RLM_U(!=) RLM_U(>=) RLM_U(<=) +}; + +// Subexpr2 only provides equality comparisons. Their implementations can be found later in this file. +template <> +class Subexpr2 : public Subexpr { +}; + +template <> +class Subexpr2 : public Subexpr, public Overloads { +public: + Query equal(StringData sd, bool case_sensitive = true); + Query equal(const Subexpr2& col, bool case_sensitive = true); + Query not_equal(StringData sd, bool case_sensitive = true); + Query not_equal(const Subexpr2& col, bool case_sensitive = true); + Query begins_with(StringData sd, bool case_sensitive = true); + Query begins_with(const Subexpr2& col, bool case_sensitive = true); + Query ends_with(StringData sd, bool case_sensitive = true); + Query ends_with(const Subexpr2& col, bool case_sensitive = true); + Query contains(StringData sd, bool case_sensitive = true); + Query contains(const Subexpr2& col, bool case_sensitive = true); + Query like(StringData sd, bool case_sensitive = true); + Query like(const Subexpr2& col, bool case_sensitive = true); +}; + +template <> +class Subexpr2 : public Subexpr, public Overloads { +public: + Query equal(BinaryData sd, bool case_sensitive = true); + Query equal(const Subexpr2& col, bool case_sensitive = true); + Query not_equal(BinaryData sd, bool case_sensitive = true); + Query not_equal(const Subexpr2& col, bool case_sensitive = true); + Query begins_with(BinaryData sd, bool case_sensitive = true); + Query begins_with(const Subexpr2& col, bool case_sensitive = true); + Query ends_with(BinaryData sd, bool case_sensitive = true); + Query ends_with(const Subexpr2& col, bool case_sensitive = true); + Query contains(BinaryData sd, bool case_sensitive = true); + Query contains(const Subexpr2& col, bool case_sensitive = true); + Query like(BinaryData sd, bool case_sensitive = true); + Query like(const Subexpr2& col, bool case_sensitive = true); +}; + + +/* +This class is used to store N values of type T = {int64_t, bool, OldDateTime or StringData}, and allows an entry +to be null too. It's used by the Value class for internal storage. + +To indicate nulls, we could have chosen a separate bool vector or some other bitmask construction. But for +performance, we customize indication of nulls to match the same indication that is used in the persisted database +file + +Queries in query_expression.hpp execute by processing chunks of 8 rows at a time. Assume you have a column: + + price (int) = {1, 2, 3, null, 1, 6, 6, 9, 5, 2, null} + +And perform a query: + + Query q = (price + 2 == 5); + +query_expression.hpp will then create a NullableVector = {5, 5, 5, 5, 5, 5, 5, 5} and then read values +NullableVector = {1, 2, 3, null, 1, 6, 6, 9} from the column, and then perform `+` and `==` on these chunks. + +See the top of this file for more information on all this. + +Assume the user specifies the null constant in a query: + +Query q = (price == null) + +The query system will then construct a NullableVector of type `null` (NullableVector). This allows compile +time optimizations for these cases. +*/ + +template +struct NullableVector { + using Underlying = typename util::RemoveOptional::type; + using t_storage = + typename std::conditional::value || std::is_same::value, + int64_t, Underlying>::type; + + NullableVector() + { + } + + NullableVector& operator=(const NullableVector& other) + { + if (this != &other) { + init(other.m_size); + realm::safe_copy_n(other.m_first, other.m_size, m_first); + m_null = other.m_null; + } + return *this; + } + + NullableVector(const NullableVector& other) + { + init(other.m_size); + realm::safe_copy_n(other.m_first, other.m_size, m_first); + m_null = other.m_null; + } + + ~NullableVector() + { + dealloc(); + } + + T operator[](size_t index) const + { + REALM_ASSERT_3(index, <, m_size); + return static_cast(m_first[index]); + } + + inline bool is_null(size_t index) const + { + REALM_ASSERT((std::is_same::value)); + return m_first[index] == m_null; + } + + inline void set_null(size_t index) + { + REALM_ASSERT((std::is_same::value)); + m_first[index] = m_null; + } + + template + typename std::enable_if::value, void>::type set(size_t index, t_storage value) + { + REALM_ASSERT((std::is_same::value)); + + // If value collides with magic null value, then switch to a new unique representation for null + if (REALM_UNLIKELY(value == m_null)) { + // adding a prime will generate 2^64 unique values. Todo: Only works on 2's complement architecture + uint64_t candidate = static_cast(m_null) + 0xfffffffbULL; + while (std::find(m_first, m_first + m_size, static_cast(candidate)) != m_first + m_size) + candidate += 0xfffffffbULL; + std::replace(m_first, m_first + m_size, m_null, static_cast(candidate)); + } + m_first[index] = value; + } + + template + typename std::enable_if::value, + void>::type + set(size_t index, t_storage value) + { + m_first[index] = value; + } + + inline util::Optional get(size_t index) const + { + if (is_null(index)) + return util::none; + + return util::make_optional((*this)[index]); + } + + inline void set(size_t index, util::Optional value) + { + if (value) { + Underlying v = *value; + set(index, v); + } + else { + set_null(index); + } + } + + void fill(T value) + { + for (size_t t = 0; t < m_size; t++) { + if (std::is_same::value) + set_null(t); + else + set(t, value); + } + } + + void init(size_t size) + { + if (size == m_size) + return; + + dealloc(); + m_size = size; + if (m_size > 0) { + if (m_size > prealloc) + m_first = reinterpret_cast(new t_storage[m_size]); + else + m_first = m_cache; + } + } + + void init(size_t size, T values) + { + init(size); + fill(values); + } + + void dealloc() + { + if (m_first) { + if (m_size > prealloc) + delete[] m_first; + m_first = nullptr; + } + } + + t_storage m_cache[prealloc]; + t_storage* m_first = &m_cache[0]; + size_t m_size = 0; + + int64_t m_null = reinterpret_cast(&m_null); // choose magic value to represent nulls +}; + +// Double +// NOTE: fails in gcc 4.8 without `inline`. Do not remove. Same applies for all methods below. +template <> +inline bool NullableVector::is_null(size_t index) const +{ + return null::is_null_float(m_first[index]); +} + +template <> +inline void NullableVector::set_null(size_t index) +{ + m_first[index] = null::get_null_float(); +} + +// Float +template <> +inline bool NullableVector::is_null(size_t index) const +{ + return null::is_null_float(m_first[index]); +} + +template <> +inline void NullableVector::set_null(size_t index) +{ + m_first[index] = null::get_null_float(); +} + + +// Null +template <> +inline void NullableVector::set_null(size_t) +{ + return; +} +template <> +inline bool NullableVector::is_null(size_t) const +{ + return true; +} + + +// OldDateTime +template <> +inline bool NullableVector::is_null(size_t index) const +{ + return m_first[index].get_olddatetime() == m_null; +} + + +template <> +inline void NullableVector::set_null(size_t index) +{ + m_first[index] = m_null; +} + +// StringData + +template <> +inline bool NullableVector::is_null(size_t index) const +{ + return m_first[index].is_null(); +} + +template <> +inline void NullableVector::set_null(size_t index) +{ + m_first[index] = StringData(); +} + +// BinaryData + +template <> +inline bool NullableVector::is_null(size_t index) const +{ + return m_first[index].is_null(); +} + +template <> +inline void NullableVector::set_null(size_t index) +{ + m_first[index] = BinaryData(); +} + +// RowIndex +template <> +inline bool NullableVector::is_null(size_t index) const +{ + return m_first[index].is_null(); +} +template <> +inline void NullableVector::set_null(size_t index) +{ + m_first[index] = RowIndex(); +} + + +// Timestamp + +template <> +inline bool NullableVector::is_null(size_t index) const +{ + return m_first[index].is_null(); +} + +template <> +inline void NullableVector::set_null(size_t index) +{ + m_first[index] = Timestamp{}; +} + +// ConstTableRef +template <> +inline bool NullableVector::is_null(size_t index) const +{ + return !bool(m_first[index]); +} +template <> +inline void NullableVector::set_null(size_t index) +{ + m_first[index].reset(); +} + +template +struct OperatorOptionalAdapter { + template + util::Optional operator()(const util::Optional& left, const util::Optional& right) + { + if (!left || !right) + return util::none; + return Operator()(*left, *right); + } + + template + util::Optional operator()(const util::Optional& arg) + { + if (!arg) + return util::none; + return Operator()(*arg); + } +}; + + +struct TrueExpression : Expression { + size_t find_first(size_t start, size_t end) const override + { + REALM_ASSERT(start <= end); + if (start != end) + return start; + + return realm::not_found; + } + void set_base_table(const Table*) override + { + } + const Table* get_base_table() const override + { + return nullptr; + } + void verify_column() const override + { + } + std::string description(util::serializer::SerialisationState&) const override + { + return "TRUEPREDICATE"; + } + std::unique_ptr clone(QueryNodeHandoverPatches*) const override + { + return std::unique_ptr(new TrueExpression(*this)); + } +}; + + +struct FalseExpression : Expression { + size_t find_first(size_t, size_t) const override + { + return realm::not_found; + } + void set_base_table(const Table*) override + { + } + void verify_column() const override + { + } + std::string description(util::serializer::SerialisationState&) const override + { + return "FALSEPREDICATE"; + } + const Table* get_base_table() const override + { + return nullptr; + } + std::unique_ptr clone(QueryNodeHandoverPatches*) const override + { + return std::unique_ptr(new FalseExpression(*this)); + } +}; + + +// Stores N values of type T. Can also exchange data with other ValueBase of different types +template +class Value : public ValueBase, public Subexpr2 { +public: + Value() + { + init(false, 1, T()); + } + Value(T v) + { + init(false, 1, v); + } + + Value(bool from_link_list, size_t values) + { + init(from_link_list, values, T()); + } + + Value(bool from_link_list, size_t values, T v) + { + init(from_link_list, values, v); + } + + Value(const Value&) = default; + Value& operator=(const Value&) = default; + + void init(bool from_link_list, size_t values, T v) + { + m_storage.init(values, v); + ValueBase::m_from_link_list = from_link_list; + ValueBase::m_values = values; + } + + void init(bool from_link_list, size_t values) + { + m_storage.init(values); + ValueBase::m_from_link_list = from_link_list; + ValueBase::m_values = values; + } + + void verify_column() const override + { + } + + std::string description(util::serializer::SerialisationState&) const override + { + if (ValueBase::m_from_link_list) { + return util::serializer::print_value(util::to_string(ValueBase::m_values) + + (ValueBase::m_values == 1 ? " value" : " values")); + } + if (m_storage.m_size > 0) { + return util::serializer::print_value(m_storage[0]); + } + return ""; + } + + bool has_constant_evaluation() const override + { + return true; + } + + void evaluate(size_t, ValueBase& destination) override + { + destination.import(*this); + } + + + template + REALM_FORCEINLINE void fun(const Value* left, const Value* right) + { + OperatorOptionalAdapter o; + + if (!left->m_from_link_list && !right->m_from_link_list) { + // Operate on values one-by-one (one value is one row; no links) + size_t min = std::min(left->m_values, right->m_values); + init(false, min); + + for (size_t i = 0; i < min; i++) { + m_storage.set(i, o(left->m_storage.get(i), right->m_storage.get(i))); + } + } + else if (left->m_from_link_list && right->m_from_link_list) { + // FIXME: Many-to-many links not supported yet. Need to specify behaviour + REALM_ASSERT_DEBUG(false); + } + else if (!left->m_from_link_list && right->m_from_link_list) { + // Right values come from link. Left must come from single row. + REALM_ASSERT_DEBUG(left->m_values > 0); + init(true, right->m_values); + + auto left_value = left->m_storage.get(0); + for (size_t i = 0; i < right->m_values; i++) { + m_storage.set(i, o(left_value, right->m_storage.get(i))); + } + } + else if (left->m_from_link_list && !right->m_from_link_list) { + // Same as above, but with left values coming from links + REALM_ASSERT_DEBUG(right->m_values > 0); + init(true, left->m_values); + + auto right_value = right->m_storage.get(0); + for (size_t i = 0; i < left->m_values; i++) { + m_storage.set(i, o(left->m_storage.get(i), right_value)); + } + } + } + + template + REALM_FORCEINLINE void fun(const Value* value) + { + init(value->m_from_link_list, value->m_values); + + OperatorOptionalAdapter o; + for (size_t i = 0; i < value->m_values; i++) { + m_storage.set(i, o(value->m_storage.get(i))); + } + } + + + // Below import and export methods are for type conversion between float, double, int64_t, etc. + template + typename std::enable_if::value>::type REALM_FORCEINLINE + export2(ValueBase& destination) const + { + Value& d = static_cast&>(destination); + d.init(ValueBase::m_from_link_list, ValueBase::m_values, D()); + for (size_t t = 0; t < ValueBase::m_values; t++) { + if (m_storage.is_null(t)) + d.m_storage.set_null(t); + else { + d.m_storage.set(t, static_cast(m_storage[t])); + } + } + } + + template + typename std::enable_if::value>::type REALM_FORCEINLINE export2(ValueBase&) const + { + // export2 is instantiated for impossible conversions like T=StringData, D=int64_t. These are never + // performed at runtime but would result in a compiler error if we did not provide this implementation. + REALM_ASSERT_DEBUG(false); + } + + REALM_FORCEINLINE void export_Timestamp(ValueBase& destination) const override + { + export2(destination); + } + + REALM_FORCEINLINE void export_bool(ValueBase& destination) const override + { + export2(destination); + } + + REALM_FORCEINLINE void export_int64_t(ValueBase& destination) const override + { + export2(destination); + } + + REALM_FORCEINLINE void export_float(ValueBase& destination) const override + { + export2(destination); + } + + REALM_FORCEINLINE void export_int(ValueBase& destination) const override + { + export2(destination); + } + + REALM_FORCEINLINE void export_double(ValueBase& destination) const override + { + export2(destination); + } + REALM_FORCEINLINE void export_StringData(ValueBase& destination) const override + { + export2(destination); + } + REALM_FORCEINLINE void export_BinaryData(ValueBase& destination) const override + { + export2(destination); + } + REALM_FORCEINLINE void export_RowIndex(ValueBase& destination) const override + { + export2(destination); + } + REALM_FORCEINLINE void export_null(ValueBase& destination) const override + { + Value& d = static_cast&>(destination); + d.init(m_from_link_list, m_values); + } + + REALM_FORCEINLINE void import(const ValueBase& source) override + { + if (std::is_same::value) + source.export_int(*this); + else if (std::is_same::value) + source.export_Timestamp(*this); + else if (std::is_same::value) + source.export_bool(*this); + else if (std::is_same::value) + source.export_float(*this); + else if (std::is_same::value) + source.export_double(*this); + else if (std::is_same::value || std::is_same::value || + std::is_same::value) + source.export_int64_t(*this); + else if (std::is_same::value) + source.export_StringData(*this); + else if (std::is_same::value) + source.export_BinaryData(*this); + else if (std::is_same::value) + source.export_RowIndex(*this); + else if (std::is_same::value) + source.export_null(*this); + else + REALM_ASSERT_DEBUG(false); + } + + // Given a TCond (==, !=, >, <, >=, <=) and two Value, return index of first match + template + REALM_FORCEINLINE static size_t compare_const(const Value* left, Value* right) + { + TCond c; + + size_t sz = right->ValueBase::m_values; + bool left_is_null = left->m_storage.is_null(0); + for (size_t m = 0; m < sz; m++) { + if (c(left->m_storage[0], right->m_storage[m], left_is_null, right->m_storage.is_null(m))) + return right->m_from_link_list ? 0 : m; + } + + return not_found; // no match + } + + template + REALM_FORCEINLINE static size_t compare(Value* left, Value* right) + { + TCond c; + + if (!left->m_from_link_list && !right->m_from_link_list) { + // Compare values one-by-one (one value is one row; no link lists) + size_t min = minimum(left->ValueBase::m_values, right->ValueBase::m_values); + for (size_t m = 0; m < min; m++) { + + if (c(left->m_storage[m], right->m_storage[m], left->m_storage.is_null(m), + right->m_storage.is_null(m))) + return m; + } + } + else if (left->m_from_link_list && right->m_from_link_list) { + // FIXME: Many-to-many links not supported yet. Need to specify behaviour + REALM_ASSERT_DEBUG(false); + } + else if (!left->m_from_link_list && right->m_from_link_list) { + // Right values come from link list. Left must come from single row. Semantics: Match if at least 1 + // linked-to-value fulfills the condition + REALM_ASSERT_DEBUG(left->m_values > 0); + for (size_t r = 0; r < right->m_values; r++) { + if (c(left->m_storage[0], right->m_storage[r], left->m_storage.is_null(0), + right->m_storage.is_null(r))) + return 0; + } + } + else if (left->m_from_link_list && !right->m_from_link_list) { + // Same as above, but with left values coming from link list. + REALM_ASSERT_DEBUG(right->m_values > 0); + for (size_t l = 0; l < left->m_values; l++) { + if (c(left->m_storage[l], right->m_storage[0], left->m_storage.is_null(l), + right->m_storage.is_null(0))) + return 0; + } + } + + return not_found; // no match + } + + std::unique_ptr clone(QueryNodeHandoverPatches*) const override + { + return make_subexpr>(*this); + } + + NullableVector m_storage; +}; + +class ConstantStringValue : public Value { +public: + ConstantStringValue(const StringData& string) + : Value() + , m_string(string.is_null() ? util::none : util::make_optional(std::string(string))) + { + init(false, 1, m_string); + } + + std::unique_ptr clone(QueryNodeHandoverPatches*) const override + { + return std::unique_ptr(new ConstantStringValue(*this)); + } + +private: + ConstantStringValue(const ConstantStringValue& other) + : Value() + , m_string(other.m_string) + { + init(other.m_from_link_list, other.m_values, m_string); + } + + util::Optional m_string; +}; + +// All overloads where left-hand-side is L: +// +// left-hand-side operator right-hand-side +// L +, -, *, /, <, >, ==, !=, <=, >= Subexpr2 +// +// For L = R = {int, int64_t, float, double, Timestamp}: +// Compare numeric values +template +Query operator>(double left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator>(float left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator>(int left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator>(int64_t left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator>(Timestamp left, const Subexpr2& right) +{ + return create(left, right); +} + +template +Query operator<(double left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator<(float left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator<(int left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator<(int64_t left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator<(Timestamp left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator==(double left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator==(float left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator==(int left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator==(int64_t left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator==(Timestamp left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator>=(double left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator>=(float left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator>=(int left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator>=(int64_t left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator>=(Timestamp left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator<=(double left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator<=(float left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator<=(int left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator<=(int64_t left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator<=(Timestamp left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator!=(double left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator!=(float left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator!=(int left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator!=(int64_t left, const Subexpr2& right) +{ + return create(left, right); +} +template +Query operator!=(Timestamp left, const Subexpr2& right) +{ + return create(left, right); +} + +// Arithmetic +template +Operator::type>> operator+(double left, const Subexpr2& right) +{ + return {make_subexpr>(left), right.clone()}; +} +template +Operator::type>> operator+(float left, const Subexpr2& right) +{ + return {make_subexpr>(left), right.clone()}; +} +template +Operator::type>> operator+(int left, const Subexpr2& right) +{ + return {make_subexpr>(left), right.clone()}; +} +template +Operator::type>> operator+(int64_t left, const Subexpr2& right) +{ + return {make_subexpr>(left), right.clone()}; +} +template +Operator::type>> operator-(double left, const Subexpr2& right) +{ + return {make_subexpr>(left), right.clone()}; +} +template +Operator::type>> operator-(float left, const Subexpr2& right) +{ + return {make_subexpr>(left), right.clone()}; +} +template +Operator::type>> operator-(int left, const Subexpr2& right) +{ + return {make_subexpr>(left), right.clone()}; +} +template +Operator::type>> operator-(int64_t left, const Subexpr2& right) +{ + return {make_subexpr>(left), right.clone()}; +} +template +Operator::type>> operator*(double left, const Subexpr2& right) +{ + return {make_subexpr>(left), right.clone()}; +} +template +Operator::type>> operator*(float left, const Subexpr2& right) +{ + return {make_subexpr>(left), right.clone()}; +} +template +Operator::type>> operator*(int left, const Subexpr2& right) +{ + return {make_subexpr>(left), right.clone()}; +} +template +Operator::type>> operator*(int64_t left, const Subexpr2& right) +{ + return {make_subexpr>(left), right.clone()}; +} +template +Operator::type>> operator/(double left, const Subexpr2& right) +{ + return {make_subexpr>(left), right.clone()}; +} +template +Operator::type>> operator/(float left, const Subexpr2& right) +{ + return {make_subexpr>(left), right.clone()}; +} +template +Operator::type>> operator/(int left, const Subexpr2& right) +{ + return {make_subexpr>(left), right.clone()}; +} +template +Operator::type>> operator/(int64_t left, const Subexpr2& right) +{ + return {make_subexpr>(left), right.clone()}; +} + +// Unary operators +template +UnaryOperator> power(const Subexpr2& left) +{ + return {left.clone()}; +} + +// Classes used for LinkMap (see below). +struct LinkMapFunction { + // Your consume() method is given row index of the linked-to table as argument, and you must return whether or + // not you want the LinkMapFunction to exit (return false) or continue (return true) harvesting the link tree + // for the current main table row index (it will be a link tree if you have multiple type_LinkList columns + // in a link()->link() query. + virtual bool consume(size_t row_index) = 0; +}; + +struct FindNullLinks : public LinkMapFunction { + bool consume(size_t row_index) override + { + static_cast(row_index); + m_has_link = true; + return false; // we've found a row index, so this can't be a null-link, so exit link harvesting + } + + bool m_has_link = false; +}; + +struct MakeLinkVector : public LinkMapFunction { + MakeLinkVector(std::vector& result) + : m_links(result) + { + } + + bool consume(size_t row_index) override + { + m_links.push_back(row_index); + return true; // continue evaluation + } + std::vector& m_links; +}; + +struct UnaryLinkResult : public LinkMapFunction { + UnaryLinkResult() + : m_result(realm::not_found) + { + } + bool consume(size_t row_index) override + { + m_result = row_index; + return false; // exit search, only one result ever expected + } + size_t m_result; +}; + +struct CountLinks : public LinkMapFunction { + bool consume(size_t) override + { + m_link_count++; + return true; + } + + size_t result() const + { + return m_link_count; + } + + size_t m_link_count = 0; +}; + +struct CountBacklinks : public LinkMapFunction { + CountBacklinks(const Table* t) + : m_table(t) + { + } + + bool consume(size_t row_index) override + { + m_link_count += m_table->get_backlink_count(row_index); + return true; + } + + size_t result() const + { + return m_link_count; + } + + const Table* m_table; + size_t m_link_count = 0; +}; + + +/* +The LinkMap and LinkMapFunction classes are used for query conditions on links themselves (contrary to conditions on +the value payload they point at). + +MapLink::map_links() takes a row index of the link column as argument and follows any link chain stated in the query +(through the link()->link() methods) until the final payload table is reached, and then applies LinkMapFunction on +the linked-to row index(es). + +If all link columns are type_Link, then LinkMapFunction is only invoked for a single row index. If one or more +columns are type_LinkList, then it may result in multiple row indexes. + +The reason we use this map pattern is that we can exit the link-tree-traversal as early as possible, e.g. when we've +found the first link that points to row '5'. Other solutions could be a std::vector harvest_all_links(), or an +iterator pattern. First solution can't exit, second solution requires internal state. +*/ +class LinkMap { +public: + LinkMap() = default; + LinkMap(const Table* table, std::vector columns) + : m_link_column_indexes(std::move(columns)) + { + set_base_table(table); + } + + LinkMap(LinkMap const& other, QueryNodeHandoverPatches* patches) + : LinkMap(other) + { + if (!patches) + return; + + m_link_column_indexes.clear(); + const Table* table = base_table(); + m_tables.clear(); + for (auto column : m_link_columns) { + m_link_column_indexes.push_back(column->get_column_index()); + if (table->get_real_column_type(m_link_column_indexes.back()) == col_type_BackLink) + table = &static_cast(column)->get_origin_table(); + else + table = &static_cast(column)->get_target_table(); + } + } + + void set_base_table(const Table* table) + { + if (table == base_table()) + return; + + m_tables.clear(); + m_tables.push_back(table); + m_link_columns.clear(); + m_link_types.clear(); + m_only_unary_links = true; + + for (size_t link_column_index : m_link_column_indexes) { + // Link column can be either LinkList or single Link + const Table* t = m_tables.back(); + ColumnType type = t->get_real_column_type(link_column_index); + REALM_ASSERT(Table::is_link_type(type) || type == col_type_BackLink); + m_link_types.push_back(type); + + if (type == col_type_LinkList) { + const LinkListColumn& cll = t->get_column_link_list(link_column_index); + m_link_columns.push_back(&cll); + m_only_unary_links = false; + m_tables.push_back(&cll.get_target_table()); + } + else if (type == col_type_Link) { + const LinkColumn& cl = t->get_column_link(link_column_index); + m_link_columns.push_back(&cl); + m_tables.push_back(&cl.get_target_table()); + } + else if (type == col_type_BackLink) { + const BacklinkColumn& bl = t->get_column_backlink(link_column_index); + m_link_columns.push_back(&bl); + m_only_unary_links = false; + m_tables.push_back(&bl.get_origin_table()); + } + } + } + + void verify_columns() const + { + for (size_t i = 0; i < m_link_column_indexes.size(); i++) { + m_tables[i]->verify_column(m_link_column_indexes[i], m_link_columns[i]); + } + } + + virtual std::string description(util::serializer::SerialisationState& state) const + { + std::string s; + for (size_t i = 0; i < m_link_column_indexes.size(); ++i) { + if (i < m_tables.size() && m_tables[i]) { + s += state.get_column_name(m_tables[i]->get_table_ref(), m_link_column_indexes[i]); + if (i != m_link_column_indexes.size() - 1) { + s += util::serializer::value_separator; + } + } + } + return s; + } + + size_t get_unary_link_or_not_found(size_t index) const + { + REALM_ASSERT(m_only_unary_links); + UnaryLinkResult res; + map_links(index, res); + return res.m_result; + } + + std::vector get_links(size_t index) const + { + std::vector res; + get_links(index, res); + return res; + } + + std::vector get_origin_ndxs(size_t index, size_t column = 0) const; + + size_t count_links(size_t row) + { + CountLinks counter; + map_links(row, counter); + return counter.result(); + } + + size_t count_all_backlinks(size_t row) + { + CountBacklinks counter(target_table()); + map_links(row, counter); + return counter.result(); + } + + void map_links(size_t row, LinkMapFunction& lm) const + { + map_links(0, row, lm); + } + + bool only_unary_links() const + { + return m_only_unary_links; + } + + const Table* base_table() const + { + return m_tables.empty() ? nullptr : m_tables[0]; + } + + const Table* target_table() const + { + REALM_ASSERT(!m_tables.empty()); + return m_tables.back(); + } + + bool links_exist() const + { + return !m_link_columns.empty(); + } + + std::vector m_link_columns; + +private: + void map_links(size_t column, size_t row, LinkMapFunction& lm) const + { + bool last = (column + 1 == m_link_columns.size()); + ColumnType type = m_link_types[column]; + if (type == col_type_Link) { + const LinkColumn& cl = *static_cast(m_link_columns[column]); + size_t r = to_size_t(cl.get(row)); + if (r == 0) + return; + r--; // LinkColumn stores link to row N as N + 1 + if (last) { + bool continue2 = lm.consume(r); + if (!continue2) + return; + } + else + map_links(column + 1, r, lm); + } + else if (type == col_type_LinkList) { + const LinkListColumn& cll = *static_cast(m_link_columns[column]); + ConstLinkViewRef lvr = cll.get(row); + for (size_t t = 0; t < lvr->size(); t++) { + size_t r = lvr->get(t).get_index(); + if (last) { + bool continue2 = lm.consume(r); + if (!continue2) + return; + } + else + map_links(column + 1, r, lm); + } + } + else if (type == col_type_BackLink) { + const BacklinkColumn& bl = *static_cast(m_link_columns[column]); + size_t count = bl.get_backlink_count(row); + for (size_t i = 0; i < count; ++i) { + size_t r = bl.get_backlink(row, i); + if (last) { + bool continue2 = lm.consume(r); + if (!continue2) + return; + } + else + map_links(column + 1, r, lm); + } + } + } + + void get_links(size_t row, std::vector& result) const + { + MakeLinkVector mlv = MakeLinkVector(result); + map_links(row, mlv); + } + + std::vector m_link_column_indexes; + std::vector m_link_types; + std::vector m_tables; + bool m_only_unary_links = true; + + template + friend Query compare(const Subexpr2&, const ConstRow&); +}; + +template +Query string_compare(const Subexpr2& left, T right, bool case_insensitive); +template +Query string_compare(const Subexpr2& left, const Subexpr2& right, bool case_insensitive); + +template +Value make_value_for_link(bool only_unary_links, size_t size) +{ + Value value; + if (only_unary_links) { + REALM_ASSERT(size <= 1); + value.init(false, 1); + value.m_storage.set_null(0); + } + else { + value.init(true, size); + } + return value; +} + + +// If we add a new Realm type T and quickly want Query support for it, then simply inherit from it like +// `template <> class Columns : public SimpleQuerySupport` and you're done. Any operators of the set +// { ==, >=, <=, !=, >, < } that are supported by T will be supported by the "query expression syntax" +// automatically. NOTE: This method of Query support will be slow because it goes through Table::get. +// To get faster Query support, either add SequentialGetter support (faster) or create a query_engine.hpp +// node for it (super fast). + +template +class SimpleQuerySupport : public Subexpr2 { +public: + SimpleQuerySupport(size_t column, const Table* table, std::vector links = {}) + : m_column_ndx(column) + , m_link_map(table, std::move(links)) + { + m_column = &m_link_map.target_table()->get_column_base(m_column_ndx); + } + + bool is_nullable() const noexcept + { + return m_link_map.base_table()->is_nullable(m_column->get_column_index()); + } + + const Table* get_base_table() const override + { + return m_link_map.base_table(); + } + + void set_base_table(const Table* table) override + { + if (table != get_base_table()) { + m_link_map.set_base_table(table); + m_column = &m_link_map.target_table()->get_column_base(m_column_ndx); + } + } + + bool has_search_index() const override + { + return m_link_map.target_table()->has_search_index(m_column_ndx); + } + + std::vector find_all(util::Optional value) const override + { + std::vector ret; + ref_type ref = IntegerColumn::create(Allocator::get_default()); + IntegerColumn result; + result.init_from_ref(Allocator::get_default(), ref); + + T val{}; + if (value) { + val = value->get(); + } + + auto col = dynamic_cast::column_type*>(m_column); + col->find_all(result, val, 0, realm::npos); + + auto sz = result.size(); + for (size_t i = 0; i < sz; i++) { + auto ndxs = m_link_map.get_origin_ndxs(size_t(result.get(i))); + ret.insert(ret.end(), ndxs.begin(), ndxs.end()); + } + result.destroy(); + + return ret; + } + + void verify_column() const override + { + // verify links + m_link_map.verify_columns(); + // verify target table + const Table* target_table = m_link_map.target_table(); + if (target_table && m_column_ndx != npos) { + target_table->verify_column(m_column_ndx, m_column); + } + } + + void evaluate(size_t index, ValueBase& destination) override + { + Value& d = static_cast&>(destination); + size_t col = column_ndx(); + + if (links_exist()) { + if (m_link_map.only_unary_links()) { + const Table* target_table = m_link_map.target_table(); + d.init(false, 1); + d.m_storage.set_null(0); + size_t link_translation_index = this->m_link_map.get_unary_link_or_not_found(index); + if (link_translation_index != realm::not_found) { + d.m_storage.set(0, target_table->get(col, link_translation_index)); + } + } + else { + std::vector links = m_link_map.get_links(index); + constexpr bool has_only_unary_links = false; + Value v = make_value_for_link(has_only_unary_links, links.size()); + for (size_t t = 0; t < links.size(); t++) { + size_t link_to = links[t]; + v.m_storage.set(t, m_link_map.target_table()->template get(col, link_to)); + } + destination.import(v); + } + } + else { + // Not a link column + const Table* target_table = m_link_map.target_table(); + for (size_t t = 0; t < destination.m_values && index + t < target_table->size(); t++) { + d.m_storage.set(t, target_table->get(col, index + t)); + } + } + } + + bool links_exist() const + { + return m_link_map.m_link_columns.size() > 0; + } + + bool only_unary_links() const + { + return m_link_map.only_unary_links(); + } + + LinkMap get_link_map() const + { + return m_link_map; + } + + virtual std::string description(util::serializer::SerialisationState& state) const override + { + return state.describe_columns(m_link_map, m_column_ndx); + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches = nullptr) const override + { + return make_subexpr>(static_cast&>(*this), patches); + } + + SimpleQuerySupport(SimpleQuerySupport const& other, QueryNodeHandoverPatches* patches) + : Subexpr2(other) + , m_column_ndx(other.m_column_ndx) + , m_column(other.m_column) + , m_link_map(other.m_link_map, patches) + { + if (patches && m_column) { + m_column_ndx = column_ndx(); + m_column = nullptr; + } + } + + size_t column_ndx() const + { + return m_column->get_column_index(); + } + + SizeOperator> size() + { + return SizeOperator>(this->clone(nullptr)); + } + +private: + // Column index of payload column of m_table + mutable size_t m_column_ndx; + const ColumnBase* m_column; + LinkMap m_link_map; +}; + + +template <> +class Columns : public SimpleQuerySupport { + using SimpleQuerySupport::SimpleQuerySupport; +}; + +template <> +class Columns : public SimpleQuerySupport { + using SimpleQuerySupport::SimpleQuerySupport; +}; + +template <> +class Columns : public SimpleQuerySupport { +public: + Columns(size_t column, const Table* table, std::vector links = {}) + : SimpleQuerySupport(column, table, links) + { + } + + Columns(Columns const& other, QueryNodeHandoverPatches* patches = nullptr) + : SimpleQuerySupport(other, patches) + { + } + + Columns(Columns&& other) + : SimpleQuerySupport(other) + { + } +}; + +template +Query string_compare(const Subexpr2& left, T right, bool case_sensitive) +{ + StringData sd(right); + if (case_sensitive) + return create(sd, left); + else + return create(sd, left); +} + +template +Query string_compare(const Subexpr2& left, const Subexpr2& right, bool case_sensitive) +{ + if (case_sensitive) + return make_expression>(right.clone(), left.clone()); + else + return make_expression>(right.clone(), left.clone()); +} + +template +Query binary_compare(const Subexpr2& left, T right, bool case_sensitive) +{ + BinaryData data(right); + if (case_sensitive) + return create(data, left); + else + return create(data, left); +} + +template +Query binary_compare(const Subexpr2& left, const Subexpr2& right, bool case_sensitive) +{ + if (case_sensitive) + return make_expression>(right.clone(), left.clone()); + else + return make_expression>(right.clone(), left.clone()); +} + + +// Columns == Columns +inline Query operator==(const Columns& left, const Columns& right) +{ + return string_compare(left, right, true); +} + +// Columns != Columns +inline Query operator!=(const Columns& left, const Columns& right) +{ + return string_compare(left, right, true); +} + +// String == Columns +template +Query operator==(T left, const Columns& right) +{ + return operator==(right, left); +} + +// String != Columns +template +Query operator!=(T left, const Columns& right) +{ + return operator!=(right, left); +} + +// Columns == String +template +Query operator==(const Columns& left, T right) +{ + return string_compare(left, right, true); +} + +// Columns != String +template +Query operator!=(const Columns& left, T right) +{ + return string_compare(left, right, true); +} + + +inline Query operator==(const Columns& left, BinaryData right) +{ + return create(right, left); +} + +inline Query operator==(BinaryData left, const Columns& right) +{ + return create(left, right); +} + +inline Query operator!=(const Columns& left, BinaryData right) +{ + return create(right, left); +} + +inline Query operator!=(BinaryData left, const Columns& right) +{ + return create(left, right); +} + + +// This class is intended to perform queries on the *pointers* of links, contrary to performing queries on *payload* +// in linked-to tables. Queries can be "find first link that points at row X" or "find first null-link". Currently +// only "find first null link" and "find first non-null link" is supported. More will be added later. When we add +// more, I propose to remove the template argument from this class and instead template it by +// a criteria-class (like the FindNullLinks class below in find_first()) in some generalized fashion. +template +class UnaryLinkCompare : public Expression { +public: + UnaryLinkCompare(LinkMap lm) + : m_link_map(std::move(lm)) + { + } + + void set_base_table(const Table* table) override + { + m_link_map.set_base_table(table); + } + + void verify_column() const override + { + m_link_map.verify_columns(); + } + + // Return main table of query (table on which table->where()... is invoked). Note that this is not the same as + // any linked-to payload tables + const Table* get_base_table() const override + { + return m_link_map.base_table(); + } + + size_t find_first(size_t start, size_t end) const override + { + for (; start < end;) { + FindNullLinks fnl; + m_link_map.map_links(start, fnl); + if (fnl.m_has_link == has_links) + return start; + + start++; + } + + return not_found; + } + + virtual std::string description(util::serializer::SerialisationState& state) const override + { + return state.describe_columns(m_link_map, realm::npos) + (has_links ? " != NULL" : " == NULL"); + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return std::unique_ptr(new UnaryLinkCompare(*this, patches)); + } + +private: + UnaryLinkCompare(const UnaryLinkCompare& other, QueryNodeHandoverPatches* patches = nullptr) + : Expression(other) + , m_link_map(other.m_link_map, patches) + { + } + + mutable LinkMap m_link_map; +}; + +class LinkCount : public Subexpr2 { +public: + LinkCount(LinkMap link_map) + : m_link_map(std::move(link_map)) + { + } + LinkCount(LinkCount const& other, QueryNodeHandoverPatches* patches) + : Subexpr2(other) + , m_link_map(other.m_link_map, patches) + { + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return make_subexpr(*this, patches); + } + + const Table* get_base_table() const override + { + return m_link_map.base_table(); + } + + void set_base_table(const Table* table) override + { + m_link_map.set_base_table(table); + } + + void verify_column() const override + { + m_link_map.verify_columns(); + } + + void evaluate(size_t index, ValueBase& destination) override + { + size_t count = m_link_map.count_links(index); + destination.import(Value(false, 1, count)); + } + + virtual std::string description(util::serializer::SerialisationState& state) const override + { + return state.describe_columns(m_link_map, realm::npos) + util::serializer::value_separator + "@count"; + } + +private: + LinkMap m_link_map; +}; + +// Gives a count of all backlinks across all columns for the specified row. +// The unused template parameter is a hack to avoid a circular dependency between table.hpp and query_expression.hpp. +template +class BacklinkCount : public Subexpr2 { +public: + BacklinkCount(LinkMap link_map) + : m_link_map(std::move(link_map)) + { + } + BacklinkCount(const Table* table, std::vector links = {}) + : m_link_map(table, std::move(links)) + { + } + BacklinkCount(BacklinkCount const& other, QueryNodeHandoverPatches* patches) + : Subexpr2(other) + , m_link_map(other.m_link_map, patches) + { + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return make_subexpr >(*this, patches); + } + + const Table* get_base_table() const override + { + return m_link_map.base_table(); + } + + void set_base_table(const Table* table) override + { + m_link_map.set_base_table(table); + } + + void verify_column() const override + { + m_link_map.verify_columns(); + } + + void evaluate(size_t index, ValueBase& destination) override + { + size_t count; + if (m_link_map.links_exist()) { + count = m_link_map.count_all_backlinks(index); + } + else { + count = m_link_map.target_table()->get_backlink_count(index); + } + destination.import(Value(false, 1, count)); + } + + virtual std::string description(util::serializer::SerialisationState& state) const override + { + std::string s; + if (m_link_map.links_exist()) { + s += state.describe_columns(m_link_map, realm::npos) + util::serializer::value_separator; + } + s += "@links.@count"; + return s; + } +private: + LinkMap m_link_map; +}; + + +template +class SizeOperator : public Subexpr2 { +public: + SizeOperator(std::unique_ptr left) + : m_expr(std::move(left)) + { + } + + // See comment in base class + void set_base_table(const Table* table) override + { + m_expr->set_base_table(table); + } + + void verify_column() const override + { + m_expr->verify_column(); + } + + // Recursively fetch tables of columns in expression tree. Used when user first builds a stand-alone expression + // and binds it to a Query at a later time + const Table* get_base_table() const override + { + return m_expr->get_base_table(); + } + + // destination = operator(left) + void evaluate(size_t index, ValueBase& destination) override + { + REALM_ASSERT_DEBUG(dynamic_cast*>(&destination) != nullptr); + Value* d = static_cast*>(&destination); + REALM_ASSERT(d); + + Value v; + m_expr->evaluate(index, v); + + size_t sz = v.m_values; + d->init(v.m_from_link_list, sz); + + for (size_t i = 0; i < sz; i++) { + auto elem = v.m_storage.get(i); + if (!elem) { + d->m_storage.set_null(i); + } + else { + d->m_storage.set(i, oper()(*elem)); + } + } + } + + std::string description(util::serializer::SerialisationState& state) const override + { + if (m_expr) { + return m_expr->description(state) + util::serializer::value_separator + "@size"; + } + return "@size"; + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return std::unique_ptr(new SizeOperator(*this, patches)); + } + + void apply_handover_patch(QueryNodeHandoverPatches& patches, Group& group) override + { + m_expr->apply_handover_patch(patches, group); + } + +private: + SizeOperator(const SizeOperator& other, QueryNodeHandoverPatches* patches) + : m_expr(other.m_expr->clone(patches)) + { + } + + typedef typename oper::type T; + std::unique_ptr m_expr; +}; + +struct ConstantRowValueHandoverPatch : public QueryNodeHandoverPatch { + std::unique_ptr row_patch; +}; + +class ConstantRowValue : public Subexpr2 { +public: + ConstantRowValue(const ConstRow& row) + : m_row(row) + { + } + + void set_base_table(const Table*) override + { + } + + void verify_column() const override + { + } + + const Table* get_base_table() const override + { + return nullptr; + } + + void evaluate(size_t, ValueBase& destination) override + { + if (m_row.is_attached()) { + Value v(RowIndex(m_row.get_index())); + destination.import(v); + } + else { + Value v(RowIndex::Detached); + destination.import(v); + } + } + + virtual std::string description(util::serializer::SerialisationState&) const override + { + throw SerialisationError("Serialising a query which links to an object is currently unsupported."); + // TODO: we can do something like the following when core gets stable keys: + //if (!m_row.is_attached()) { + // return util::serializer::print_value("detached object"); + //} + //return util::serializer::print_value(m_row.get_index()); + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return std::unique_ptr(new ConstantRowValue(*this, patches)); + } + + void apply_handover_patch(QueryNodeHandoverPatches& patches, Group& group) override + { + REALM_ASSERT(patches.size()); + std::unique_ptr abstract_patch = std::move(patches.back()); + patches.pop_back(); + + auto patch = dynamic_cast(abstract_patch.get()); + REALM_ASSERT(patch); + + m_row.apply_and_consume_patch(patch->row_patch, group); + } + +private: + ConstantRowValue(const ConstantRowValue& source, QueryNodeHandoverPatches* patches) + : m_row(patches ? ConstRow() : source.m_row) + { + if (!patches) + return; + + std::unique_ptr patch(new ConstantRowValueHandoverPatch); + ConstRow::generate_patch(source.m_row, patch->row_patch); + patches->emplace_back(patch.release()); + } + + ConstRow m_row; +}; + +template +class SubColumns; + +// This is for LinkList and BackLink too since they're declared as typedefs of Link. +template <> +class Columns : public Subexpr2 { +public: + Query is_null() + { + if (m_link_map.m_link_columns.size() > 1) + throw util::runtime_error("Combining link() and is_null() is currently not supported"); + // Todo, it may be useful to support the above, but we would need to figure out an intuitive behaviour + return make_expression>(m_link_map); + } + + Query is_not_null() + { + if (m_link_map.m_link_columns.size() > 1) + throw util::runtime_error("Combining link() and is_not_null() is currently not supported"); + // Todo, it may be useful to support the above, but we would need to figure out an intuitive behaviour + return make_expression>(m_link_map); + } + + LinkCount count() const + { + return LinkCount(m_link_map); + } + + template + BacklinkCount backlink_count() const + { + return BacklinkCount(m_link_map); + } + + template + SubColumns column(size_t column_ndx) const + { + return SubColumns(Columns(column_ndx, m_link_map.target_table()), m_link_map); + } + + const LinkMap& link_map() const + { + return m_link_map; + } + + const Table* get_base_table() const override + { + return m_link_map.base_table(); + } + void set_base_table(const Table* table) override + { + m_link_map.set_base_table(table); + } + + void verify_column() const override + { + m_link_map.verify_columns(); + } + + std::string description(util::serializer::SerialisationState& state) const override + { + return state.describe_columns(m_link_map, realm::npos); + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return std::unique_ptr(new Columns(*this, patches)); + } + + void evaluate(size_t index, ValueBase& destination) override; + + +private: + LinkMap m_link_map; + friend class Table; + + Columns(size_t column_ndx, const Table* table, const std::vector& links = {}) + : m_link_map(table, links) + { + static_cast(column_ndx); + } + Columns(const Columns& other, QueryNodeHandoverPatches* patches) + : Subexpr2(other) + , m_link_map(other.m_link_map, patches) + { + } +}; + +template +class ListColumns; +template +class ListColumnAggregate; +namespace aggregate_operations { +template +class Minimum; +template +class Maximum; +template +class Sum; +template +class Average; +} + +template <> +class Columns : public Subexpr2 { +public: + const Table* get_base_table() const override + { + return m_link_map.base_table(); + } + + void set_base_table(const Table* table) override + { + m_link_map.set_base_table(table); + m_column = &m_link_map.target_table()->get_column_table(m_column_ndx); + } + + void verify_column() const override + { + m_link_map.verify_columns(); + m_link_map.target_table()->verify_column(m_column_ndx, m_column); + } + + std::string description(util::serializer::SerialisationState&) const override + { + throw SerialisationError("Serialisation of query expressions involving subtables is not yet supported."); + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return std::unique_ptr(new Columns(*this, patches)); + } + + void evaluate(size_t index, ValueBase& destination) override + { + evaluate_internal(index, destination, ValueBase::chunk_size); + } + + void evaluate_internal(size_t index, ValueBase& destination, size_t nb_elements); + + template + ListColumns column(size_t ndx) const + { + return ListColumns(ndx, Columns(*this, nullptr)); + } + + template + ListColumns list() const + { + return column(0); + } + + SizeOperator> size() + { + return SizeOperator>(this->clone(nullptr)); + } + +private: + LinkMap m_link_map; + size_t m_column_ndx; + const SubtableColumn* m_column = nullptr; + friend class Table; + template + friend class ListColumnsBase; + template + friend class ListColumnAggregate; + + Columns(size_t column_ndx, const Table* table, const std::vector& links = {}) + : m_link_map(table, links) + , m_column_ndx(column_ndx) + , m_column(&m_link_map.target_table()->get_column_table(column_ndx)) + { + } + + Columns(const Columns& other, QueryNodeHandoverPatches* patches) + : Subexpr2(other) + , m_link_map(other.m_link_map, patches) + , m_column_ndx(other.m_column_ndx) + , m_column(other.m_column) + { + if (m_column && patches) + m_column_ndx = m_column->get_column_index(); + } +}; + +template +class ListColumnsBase : public Subexpr2 { +public: + ListColumnsBase(size_t column_ndx, Columns column) + : m_column_ndx(column_ndx) + , m_subtable_column(std::move(column)) + { + } + + ListColumnsBase(const ListColumnsBase& other, QueryNodeHandoverPatches* patches) + : m_column_ndx(other.m_column_ndx) + , m_subtable_column(other.m_subtable_column, patches) + { + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return make_subexpr>(*this, patches); + } + + const Table* get_base_table() const override + { + return m_subtable_column.get_base_table(); + } + + void set_base_table(const Table* table) override + { + m_subtable_column.set_base_table(table); + } + + void verify_column() const override + { + m_subtable_column.verify_column(); + } + + void evaluate(size_t index, ValueBase& destination) override + { + Value subtables; + m_subtable_column.evaluate_internal(index, subtables, 1); + size_t sz = 0; + for (size_t i = 0; i < subtables.m_values; i++) { + auto val = subtables.m_storage[i]; + if (val) + sz += val->size(); + } + auto v = make_value_for_link::type>(false, sz); + size_t k = 0; + for (size_t i = 0; i < subtables.m_values; i++) { + auto table = subtables.m_storage[i]; + if (table) { + size_t s = table->size(); + for (size_t j = 0; j < s; j++) { + if (!table->is_null(m_column_ndx, j)) { + v.m_storage.set(k++, table->get(m_column_ndx, j)); + } + } + } + } + destination.import(v); + } + + virtual std::string description(util::serializer::SerialisationState&) const override + { + throw SerialisationError("Serialisation of subtable expressions is not yet supported."); + } + + ListColumnAggregate> min() const + { + return {m_column_ndx, m_subtable_column}; + } + + ListColumnAggregate> max() const + { + return {m_column_ndx, m_subtable_column}; + } + + ListColumnAggregate> sum() const + { + return {m_column_ndx, m_subtable_column}; + } + + ListColumnAggregate> average() const + { + return {m_column_ndx, m_subtable_column}; + } + + +private: + // Storing the column index here could be a potential problem if the column + // changes id due to insertion/deletion. + size_t m_column_ndx; + Columns m_subtable_column; +}; + +template +class ListColumns : public ListColumnsBase { +public: + using ListColumnsBase::ListColumnsBase; +}; + +template <> +class ListColumns : public ListColumnsBase { +public: + ListColumns(size_t column_ndx, Columns column) + : ListColumnsBase(column_ndx, column) + { + } + + ListColumns(const ListColumnsBase& other, QueryNodeHandoverPatches* patches) + : ListColumnsBase(other, patches) + { + } + + ListColumns(ListColumns&& other) + : ListColumnsBase(other) + { + } +}; + +template +class ListColumnAggregate : public Subexpr2 { +public: + using R = typename Operation::ResultType; + + ListColumnAggregate(size_t column_ndx, Columns column) + : m_column_ndx(column_ndx) + , m_subtable_column(std::move(column)) + { + } + + ListColumnAggregate(const ListColumnAggregate& other, QueryNodeHandoverPatches* patches) + : m_column_ndx(other.m_column_ndx) + , m_subtable_column(other.m_subtable_column, patches) + { + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return make_subexpr(*this, patches); + } + + const Table* get_base_table() const override + { + return m_subtable_column.get_base_table(); + } + + void set_base_table(const Table* table) override + { + m_subtable_column.set_base_table(table); + } + + void verify_column() const override + { + m_subtable_column.verify_column(); + } + + void evaluate(size_t index, ValueBase& destination) override + { + Value subtables; + m_subtable_column.evaluate_internal(index, subtables, 1); + REALM_ASSERT_DEBUG(subtables.m_values > 0 || subtables.m_from_link_list); + size_t sz = subtables.m_values; + // The result is an aggregate value for each table + auto v = make_value_for_link(!subtables.m_from_link_list, sz); + for (unsigned i = 0; i < sz; i++) { + auto table = subtables.m_storage[i]; + Operation op; + if (table) { + size_t s = table->size(); + for (unsigned j = 0; j < s; j++) { + op.accumulate(table->get(m_column_ndx, j)); + } + } + if (op.is_null()) { + v.m_storage.set_null(i); + } + else { + v.m_storage.set(i, op.result()); + } + } + destination.import(v); + } + + virtual std::string description(util::serializer::SerialisationState&) const override + { + throw SerialisationError("Serialisation of queries involving subtable expressions is not yet supported."); + } + +private: + size_t m_column_ndx; + Columns m_subtable_column; +}; + +template +Query compare(const Subexpr2& left, const ConstRow& row) +{ + static_assert(std::is_same::value || std::is_same::value, + "Links can only be compared for equality."); + const Columns* column = dynamic_cast*>(&left); + if (column) { + const LinkMap& link_map = column->link_map(); + REALM_ASSERT(link_map.target_table() == row.get_table() || !row.is_attached()); +#ifdef REALM_OLDQUERY_FALLBACK + if (link_map.m_link_columns.size() == 1) { + // We can fall back to Query::links_to for != and == operations on links, but only + // for == on link lists. This is because negating query.links_to() is equivalent to + // to "ALL linklist != row" rather than the "ANY linklist != row" semantics we're after. + if (link_map.m_link_types[0] == col_type_Link || + (link_map.m_link_types[0] == col_type_LinkList && std::is_same::value)) { + const Table* t = column->get_base_table(); + Query query(*t); + + if (std::is_same::value) { + // Negate the following `links_to`. + query.Not(); + } + query.links_to(link_map.m_link_column_indexes[0], row); + return query; + } + } +#endif + } + return make_expression>(left.clone(), make_subexpr(row)); +} + +inline Query operator==(const Subexpr2& left, const ConstRow& row) +{ + return compare(left, row); +} +inline Query operator!=(const Subexpr2& left, const ConstRow& row) +{ + return compare(left, row); +} +inline Query operator==(const ConstRow& row, const Subexpr2& right) +{ + return compare(right, row); +} +inline Query operator!=(const ConstRow& row, const Subexpr2& right) +{ + return compare(right, row); +} + +template +Query compare(const Subexpr2& left, null) +{ + static_assert(std::is_same::value || std::is_same::value, + "Links can only be compared for equality."); + return make_expression>(left.clone(), make_subexpr>()); +} + +inline Query operator==(const Subexpr2& left, null) +{ + return compare(left, null()); +} +inline Query operator!=(const Subexpr2& left, null) +{ + return compare(left, null()); +} +inline Query operator==(null, const Subexpr2& right) +{ + return compare(right, null()); +} +inline Query operator!=(null, const Subexpr2& right) +{ + return compare(right, null()); +} + + +template +class Columns : public Subexpr2 { +public: + using ColType = typename ColumnTypeTraits::column_type; + + Columns(size_t column, const Table* table, std::vector links = {}) + : m_link_map(table, std::move(links)) + , m_column_ndx(column) + , m_nullable(m_link_map.target_table()->is_nullable(m_column_ndx)) + { + } + + Columns(const Columns& other, QueryNodeHandoverPatches* patches = nullptr) + : m_link_map(other.m_link_map, patches) + , m_column_ndx(other.m_column_ndx) + , m_nullable(other.m_nullable) + { + if (!other.m_sg) + return; + + if (patches) { + m_column_ndx = other.get_column_base().get_column_index(); + } + else { + if (m_nullable && std::is_same::value) { + init(&other.get_column_base()); + } + else { + init(&other.get_column_base()); + } + } + } + + Columns& operator=(const Columns& other) + { + if (this != &other) { + m_link_map = other.m_link_map; + m_sg.reset(); + m_column_ndx = other.m_column_ndx; + m_nullable = other.m_nullable; + } + return *this; + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return make_subexpr>(*this, patches); + } + + // See comment in base class + void set_base_table(const Table* table) override + { + if (m_sg && table == get_base_table()) + return; + + m_link_map.set_base_table(table); + m_nullable = m_link_map.target_table()->is_nullable(m_column_ndx); + + const ColumnBase* c = &m_link_map.target_table()->get_column_base(m_column_ndx); + if (m_nullable && std::is_same::value) { + init(c); + } + else { + init(c); + } + } + + bool has_search_index() const override + { + return m_link_map.target_table()->has_search_index(m_column_ndx); + } + + std::vector find_all(util::Optional value) const override + { + std::vector ret; + ref_type ref = IntegerColumn::create(Allocator::get_default()); + IntegerColumn result; + result.init_from_ref(Allocator::get_default(), ref); + if (m_nullable && std::is_same::value) { + util::Optional val; + if (value) { + val = value->get_int(); + } + auto sgc = static_cast*>(m_sg.get()); + sgc->m_column->find_all(result, val, 0, realm::npos); + } + else { + T val{}; + if (value) { + val = value->get(); + } + auto sgc = static_cast*>(m_sg.get()); + sgc->m_column->find_all(result, val, 0, realm::npos); + } + + auto sz = result.size(); + for (size_t i = 0; i < sz; i++) { + auto ndxs = m_link_map.get_origin_ndxs(size_t(result.get(i))); + ret.insert(ret.end(), ndxs.begin(), ndxs.end()); + } + result.destroy(); + + return ret; + } + + + void verify_column() const override + { + // verify links + m_link_map.verify_columns(); + // verify target table + const Table* target_table = m_link_map.target_table(); + if (target_table && m_column_ndx != npos) { + target_table->verify_column(m_column_ndx, &get_column_base()); + } + } + + template + void init(const ColumnBase* c) + { + REALM_ASSERT_DEBUG(dynamic_cast(c)); + if (m_sg == nullptr) { + m_sg.reset(new SequentialGetter()); + } + static_cast&>(*m_sg).init(static_cast(c)); + } + + // Recursively fetch tables of columns in expression tree. Used when user first builds a stand-alone expression + // and binds it to a Query at a later time + const Table* get_base_table() const override + { + return m_link_map.base_table(); + } + + template + void evaluate_internal(size_t index, ValueBase& destination) + { + REALM_ASSERT_DEBUG(m_sg.get()); + REALM_ASSERT_DEBUG(dynamic_cast*>(m_sg.get())); + + using U = typename ColType2::value_type; + auto sgc = static_cast*>(m_sg.get()); + REALM_ASSERT_DEBUG(sgc->m_column); + + if (links_exist()) { + // LinkList with more than 0 values. Create Value with payload for all fields + + std::vector links = m_link_map.get_links(index); + auto v = make_value_for_link::type>(m_link_map.only_unary_links(), + links.size()); + + for (size_t t = 0; t < links.size(); t++) { + size_t link_to = links[t]; + sgc->cache_next(link_to); + + if (sgc->m_column->is_null(link_to)) + v.m_storage.set_null(t); + else + v.m_storage.set(t, sgc->get_next(link_to)); + } + destination.import(v); + } + else { + // Not a Link column + // make sequential getter load the respective leaf to access data at column row 'index' + sgc->cache_next(index); + size_t colsize = sgc->m_column->size(); + + // Now load `ValueBase::chunk_size` rows from from the leaf into m_storage. If it's an integer + // leaf, then it contains the method get_chunk() which copies these values in a super fast way (first + // case of the `if` below. Otherwise, copy the values one by one in a for-loop (the `else` case). + if (std::is_same::value && index + ValueBase::chunk_size <= sgc->m_leaf_end) { + Value v(false, ValueBase::chunk_size); + + // If you want to modify 'default_size' then update Array::get_chunk() + REALM_ASSERT_3(ValueBase::chunk_size, ==, 8); + + auto sgc_2 = static_cast*>(m_sg.get()); + sgc_2->m_leaf_ptr->get_chunk(index - sgc->m_leaf_start, v.m_storage.m_first); + + destination.import(v); + } + else { + size_t rows = colsize - index; + if (rows > ValueBase::chunk_size) + rows = ValueBase::chunk_size; + Value::type> v(false, rows); + + for (size_t t = 0; t < rows; t++) + v.m_storage.set(t, sgc->get_next(index + t)); + + destination.import(v); + } + } + } + + virtual std::string description(util::serializer::SerialisationState& state) const override + { + return state.describe_columns(m_link_map, m_column_ndx); + } + + // Load values from Column into destination + void evaluate(size_t index, ValueBase& destination) override + { + if (m_nullable && std::is_same::value) { + evaluate_internal(index, destination); + } + else { + evaluate_internal(index, destination); + } + } + + bool links_exist() const + { + return m_link_map.m_link_columns.size() > 0; + } + + bool only_unary_links() const + { + return m_link_map.only_unary_links(); + } + + bool is_nullable() const + { + return m_nullable; + } + + LinkMap get_link_map() const + { + return m_link_map; + } + + size_t column_ndx() const noexcept + { + return m_sg ? get_column_base().get_column_index() : m_column_ndx; + } + +private: + LinkMap m_link_map; + + // Fast (leaf caching) value getter for payload column (column in table on which query condition is executed) + std::unique_ptr m_sg; + + // Column index of payload column of m_table + size_t m_column_ndx; + + // set to false by default for stand-alone Columns declaration that are not yet associated with any table + // or oclumn. Call init() to update it or use a constructor that takes table + column index as argument. + bool m_nullable = false; + + const ColumnBase& get_column_base() const noexcept + { + if (m_nullable && std::is_same::value) + return *static_cast&>(*m_sg).m_column; + else + return *static_cast&>(*m_sg).m_column; + } +}; + +template +class SubColumnAggregate; + +template +class SubColumns : public Subexpr { +public: + SubColumns(Columns column, LinkMap link_map) + : m_column(std::move(column)) + , m_link_map(std::move(link_map)) + { + } + + std::unique_ptr clone(QueryNodeHandoverPatches*) const override + { + return make_subexpr>(*this); + } + + const Table* get_base_table() const override + { + return m_link_map.base_table(); + } + + void set_base_table(const Table* table) override + { + m_link_map.set_base_table(table); + m_column.set_base_table(m_link_map.target_table()); + } + + void verify_column() const override + { + m_link_map.verify_columns(); + m_column.verify_column(); + } + + void evaluate(size_t, ValueBase&) override + { + // SubColumns can only be used in an expression in conjunction with its aggregate methods. + REALM_ASSERT(false); + } + + virtual std::string description(util::serializer::SerialisationState&) const override + { + return ""; // by itself there are no conditions, see SubColumnAggregate + } + + SubColumnAggregate> min() const + { + return {m_column, m_link_map}; + } + + SubColumnAggregate> max() const + { + return {m_column, m_link_map}; + } + + SubColumnAggregate> sum() const + { + return {m_column, m_link_map}; + } + + SubColumnAggregate> average() const + { + return {m_column, m_link_map}; + } + +private: + Columns m_column; + LinkMap m_link_map; +}; + +template +class SubColumnAggregate : public Subexpr2 { +public: + SubColumnAggregate(Columns column, LinkMap link_map) + : m_column(std::move(column)) + , m_link_map(std::move(link_map)) + { + } + SubColumnAggregate(SubColumnAggregate const& other, QueryNodeHandoverPatches* patches) + : m_column(other.m_column, patches) + , m_link_map(other.m_link_map, patches) + { + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return make_subexpr(*this, patches); + } + + const Table* get_base_table() const override + { + return m_link_map.base_table(); + } + + void set_base_table(const Table* table) override + { + m_link_map.set_base_table(table); + m_column.set_base_table(m_link_map.target_table()); + } + + void verify_column() const override + { + m_link_map.verify_columns(); + m_column.verify_column(); + } + + void evaluate(size_t index, ValueBase& destination) override + { + std::vector links = m_link_map.get_links(index); + std::sort(links.begin(), links.end()); + + Operation op; + for (size_t link_index = 0; link_index < links.size();) { + Value value; + size_t link = links[link_index]; + m_column.evaluate(link, value); + + // Columns::evaluate fetches values in chunks of ValueBase::default_size. Process all values + // within the chunk that came from rows that we link to. + const auto& value_storage = value.m_storage; + for (size_t value_index = 0; value_index < value.m_values;) { + if (!value_storage.is_null(value_index)) { + op.accumulate(value_storage[value_index]); + } + if (++link_index >= links.size()) { + break; + } + + size_t previous_link = link; + link = links[link_index]; + value_index += link - previous_link; + } + } + if (op.is_null()) { + destination.import(Value(false, 1, null())); + } + else { + destination.import(Value(false, 1, op.result())); + } + } + + virtual std::string description(util::serializer::SerialisationState& state) const override + { + util::serializer::SerialisationState empty_state; + return state.describe_columns(m_link_map, realm::npos) + util::serializer::value_separator + Operation::description() + util::serializer::value_separator + m_column.description(empty_state); + } + +private: + Columns m_column; + LinkMap m_link_map; +}; + +struct SubQueryCountHandoverPatch : QueryNodeHandoverPatch { + QueryHandoverPatch m_query; +}; + +class SubQueryCount : public Subexpr2 { +public: + SubQueryCount(Query q, LinkMap link_map) + : m_query(std::move(q)) + , m_link_map(std::move(link_map)) + { + } + + const Table* get_base_table() const override + { + return m_link_map.base_table(); + } + + void set_base_table(const Table* table) override + { + m_link_map.set_base_table(table); + } + + void verify_column() const override + { + m_link_map.verify_columns(); + } + + void evaluate(size_t index, ValueBase& destination) override + { + std::vector links = m_link_map.get_links(index); + std::sort(links.begin(), links.end()); + + size_t count = std::accumulate(links.begin(), links.end(), size_t(0), [this](size_t running_count, size_t link) { + return running_count + m_query.count(link, link + 1, 1); + }); + + destination.import(Value(false, 1, size_t(count))); + } + + virtual std::string description(util::serializer::SerialisationState& state) const override + { + REALM_ASSERT(m_link_map.base_table() != nullptr); + std::string target = state.describe_columns(m_link_map, realm::npos); + std::string var_name = state.get_variable_name(m_link_map.base_table()->get_table_ref()); + state.subquery_prefix_list.push_back(var_name); + std::string desc = "SUBQUERY(" + target + ", " + var_name + ", " + m_query.get_description(state) + ")" + + util::serializer::value_separator + "@count"; + state.subquery_prefix_list.pop_back(); + return desc; + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + if (patches) + return std::unique_ptr(new SubQueryCount(*this, patches)); + + return make_subexpr(*this); + } + + void apply_handover_patch(QueryNodeHandoverPatches& patches, Group& group) override + { + REALM_ASSERT(patches.size()); + std::unique_ptr abstract_patch = std::move(patches.back()); + patches.pop_back(); + + auto patch = dynamic_cast(abstract_patch.get()); + REALM_ASSERT(patch); + + m_query.apply_patch(patch->m_query, group); + } + +private: + SubQueryCount(const SubQueryCount& other, QueryNodeHandoverPatches* patches) + : m_link_map(other.m_link_map, patches) + { + std::unique_ptr patch(new SubQueryCountHandoverPatch); + m_query = Query(other.m_query, patch->m_query, ConstSourcePayload::Copy); + patches->emplace_back(patch.release()); + } + + Query m_query; + LinkMap m_link_map; +}; + +// The unused template parameter is a hack to avoid a circular dependency between table.hpp and query_expression.hpp. +template +class SubQuery { +public: + SubQuery(Columns link_column, Query query) + : m_query(std::move(query)) + , m_link_map(link_column.link_map()) + { + REALM_ASSERT(m_link_map.target_table() == m_query.get_table()); + } + + SubQueryCount count() const + { + return SubQueryCount(m_query, m_link_map); + } + +private: + Query m_query; + LinkMap m_link_map; +}; + +namespace aggregate_operations { +template +class BaseAggregateOperation { + static_assert(std::is_same::value || std::is_same::value || std::is_same::value, + "Numeric aggregates can only be used with subcolumns of numeric types"); + +public: + using ResultType = R; + + void accumulate(T value) + { + m_count++; + m_result = Derived::apply(m_result, value); + } + + bool is_null() const + { + return m_count == 0; + } + ResultType result() const + { + return m_result; + } + +protected: + size_t m_count = 0; + ResultType m_result = Derived::initial_value(); +}; + +template +class Minimum : public BaseAggregateOperation> { +public: + static T initial_value() + { + return std::numeric_limits::max(); + } + static T apply(T a, T b) + { + return std::min(a, b); + } + static std::string description() + { + return "@min"; + } +}; + +template +class Maximum : public BaseAggregateOperation> { +public: + static T initial_value() + { + return std::numeric_limits::min(); + } + static T apply(T a, T b) + { + return std::max(a, b); + } + static std::string description() + { + return "@max"; + } +}; + +template +class Sum : public BaseAggregateOperation> { +public: + static T initial_value() + { + return T(); + } + static T apply(T a, T b) + { + return a + b; + } + bool is_null() const + { + return false; + } + static std::string description() + { + return "@sum"; + } +}; + +template +class Average : public BaseAggregateOperation, double> { + using Base = BaseAggregateOperation, double>; + +public: + static double initial_value() + { + return 0; + } + static double apply(double a, T b) + { + return a + b; + } + double result() const + { + return Base::m_result / Base::m_count; + } + static std::string description() + { + return "@avg"; + } + +}; +} + +template +class UnaryOperator : public Subexpr2 { +public: + UnaryOperator(std::unique_ptr left) + : m_left(std::move(left)) + { + } + + UnaryOperator(const UnaryOperator& other, QueryNodeHandoverPatches* patches) + : m_left(other.m_left->clone(patches)) + { + } + + UnaryOperator& operator=(const UnaryOperator& other) + { + if (this != &other) { + m_left = other.m_left->clone(); + } + return *this; + } + + UnaryOperator(UnaryOperator&&) = default; + UnaryOperator& operator=(UnaryOperator&&) = default; + + // See comment in base class + void set_base_table(const Table* table) override + { + m_left->set_base_table(table); + } + + void verify_column() const override + { + m_left->verify_column(); + } + + // Recursively fetch tables of columns in expression tree. Used when user first builds a stand-alone expression + // and binds it to a Query at a later time + const Table* get_base_table() const override + { + return m_left->get_base_table(); + } + + // destination = operator(left) + void evaluate(size_t index, ValueBase& destination) override + { + Value result; + Value left; + m_left->evaluate(index, left); + result.template fun(&left); + destination.import(result); + } + + virtual std::string description(util::serializer::SerialisationState& state) const override + { + if (m_left) { + return m_left->description(state); + } + return ""; + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return make_subexpr(*this, patches); + } + + void apply_handover_patch(QueryNodeHandoverPatches& patches, Group& group) override + { + m_left->apply_handover_patch(patches, group); + } + +private: + typedef typename oper::type T; + std::unique_ptr m_left; +}; + + +template +class Operator : public Subexpr2 { +public: + Operator(std::unique_ptr left, std::unique_ptr right) + : m_left(std::move(left)) + , m_right(std::move(right)) + { + } + + Operator(const Operator& other, QueryNodeHandoverPatches* patches) + : m_left(other.m_left->clone(patches)) + , m_right(other.m_right->clone(patches)) + { + } + + Operator& operator=(const Operator& other) + { + if (this != &other) { + m_left = other.m_left->clone(); + m_right = other.m_right->clone(); + } + return *this; + } + + Operator(Operator&&) = default; + Operator& operator=(Operator&&) = default; + + // See comment in base class + void set_base_table(const Table* table) override + { + m_left->set_base_table(table); + m_right->set_base_table(table); + } + + void verify_column() const override + { + m_left->verify_column(); + m_right->verify_column(); + } + + // Recursively fetch tables of columns in expression tree. Used when user first builds a stand-alone expression + // and + // binds it to a Query at a later time + const Table* get_base_table() const override + { + const Table* l = m_left->get_base_table(); + const Table* r = m_right->get_base_table(); + + // Queries do not support multiple different tables; all tables must be the same. + REALM_ASSERT(l == nullptr || r == nullptr || l == r); + + // nullptr pointer means expression which isn't yet associated with any table, or is a Value + return l ? l : r; + } + + // destination = operator(left, right) + void evaluate(size_t index, ValueBase& destination) override + { + Value result; + Value left; + Value right; + m_left->evaluate(index, left); + m_right->evaluate(index, right); + result.template fun(&left, &right); + destination.import(result); + } + + virtual std::string description(util::serializer::SerialisationState& state) const override + { + std::string s; + if (m_left) { + s += m_left->description(state); + } + s += (" " + oper::description() + " "); + if (m_right) { + s += m_right->description(state); + } + return s; + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return make_subexpr(*this, patches); + } + + void apply_handover_patch(QueryNodeHandoverPatches& patches, Group& group) override + { + m_right->apply_handover_patch(patches, group); + m_left->apply_handover_patch(patches, group); + } + +private: + typedef typename oper::type T; + std::unique_ptr m_left; + std::unique_ptr m_right; +}; + +namespace { +template +inline Mixed get_mixed(const Value& val) +{ + return Mixed(val.m_storage[0]); +} + +template <> +inline Mixed get_mixed(const Value&) +{ + REALM_ASSERT(false); + return Mixed(); +} + +template <> +inline Mixed get_mixed(const Value& val) +{ + return Mixed(int64_t(val.m_storage[0])); +} +} // namespace + +template +class Compare : public Expression { +public: + Compare(std::unique_ptr left, std::unique_ptr right) + : m_left(std::move(left)) + , m_right(std::move(right)) + { + m_left_is_const = m_left->has_constant_evaluation(); + if (m_left_is_const) { + m_left->evaluate(-1/*unused*/, m_left_value); + } + } + + // See comment in base class + void set_base_table(const Table* table) override + { + m_left->set_base_table(table); + m_right->set_base_table(table); + } + + double init() override + { + double dT = m_left_is_const ? 10.0 : 50.0; + if (std::is_same::value && m_left_is_const && m_right->has_search_index()) { + if (m_left_value.m_storage.is_null(0)) { + m_matches = m_right->find_all(util::Optional()); + } + else { + m_matches = m_right->find_all(get_mixed(m_left_value)); + } + // Sort + std::sort(m_matches.begin(), m_matches.end()); + // Remove all duplicates + m_matches.erase(std::unique(m_matches.begin(), m_matches.end()), m_matches.end()); + + m_has_matches = true; + m_index_get = 0; + m_index_end = m_matches.size(); + dT = 0; + } + + return dT; + } + + void verify_column() const override + { + m_left->verify_column(); + m_right->verify_column(); + } + + // Recursively fetch tables of columns in expression tree. Used when user first builds a stand-alone expression + // and binds it to a Query at a later time + const Table* get_base_table() const override + { + const Table* l = m_left->get_base_table(); + const Table* r = m_right->get_base_table(); + + // All main tables in each subexpression of a query (table.columns() or table.link()) must be the same. + REALM_ASSERT(l == nullptr || r == nullptr || l == r); + + // nullptr pointer means expression which isn't yet associated with any table, or is a Value + return l ? l : r; + } + + size_t find_first(size_t start, size_t end) const override + { + if (m_has_matches) { + if (m_index_end == 0) + return not_found; + + if (start <= m_index_last_start) + m_index_get = 0; + else + m_index_last_start = start; + + while (m_index_get < m_index_end) { + size_t ndx = m_matches[m_index_get]; + if (ndx >= end) { + break; + } + m_index_get++; + if (ndx >= start) { + return ndx; + } + } + return not_found; + } + + size_t match; + + Value left; + Value right; + + for (; start < end;) { + if (m_left_is_const) { + m_right->evaluate(start, right); + match = Value::template compare_const(&m_left_value, &right); + } + else { + m_left->evaluate(start, left); + m_right->evaluate(start, right); + match = Value::template compare(&left, &right); + } + + if (match != not_found && match + start < end) + return start + match; + + size_t rows = + (left.m_from_link_list || right.m_from_link_list) ? 1 : minimum(right.m_values, left.m_values); + start += rows; + } + + return not_found; // no match + } + + virtual std::string description(util::serializer::SerialisationState& state) const override + { + if (std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value) { + // these string conditions have the arguments reversed but the order is important + // operations ==, and != can be reversed because the produce the same results both ways + return util::serializer::print_value(m_right->description(state) + " " + TCond::description() + + " " + m_left->description(state)); + } + return util::serializer::print_value(m_left->description(state) + " " + TCond::description() + + " " + m_right->description(state)); + } + + std::unique_ptr clone(QueryNodeHandoverPatches* patches) const override + { + return std::unique_ptr(new Compare(*this, patches)); + } + + void apply_handover_patch(QueryNodeHandoverPatches& patches, Group& group) override + { + m_right->apply_handover_patch(patches, group); + m_left->apply_handover_patch(patches, group); + } + +private: + Compare(const Compare& other, QueryNodeHandoverPatches* patches) + : m_left(other.m_left->clone(patches)) + , m_right(other.m_right->clone(patches)) + , m_left_is_const(other.m_left_is_const) + { + if (m_left_is_const) { + m_left->evaluate(-1/*unused*/, m_left_value); + } + } + + std::unique_ptr m_left; + std::unique_ptr m_right; + bool m_left_is_const; + Value m_left_value; + bool m_has_matches = false; + std::vector m_matches; + mutable size_t m_index_get = 0; + mutable size_t m_index_last_start = 0; + size_t m_index_end = 0; +}; + +} +#endif // REALM_QUERY_EXPRESSION_HPP diff --git a/!main project/Pods/Realm/include/core/realm/query_operators.hpp b/!main project/Pods/Realm/include/core/realm/query_operators.hpp new file mode 100644 index 0000000..203af84 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/query_operators.hpp @@ -0,0 +1,71 @@ +/************************************************************************* + * + * Copyright 2017 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_QUERY_OPERATORS_HPP +#define REALM_QUERY_OPERATORS_HPP + +#include +#include +#include +#include + +namespace realm { + +// This is not supported in the general case +template +struct Size; + +template <> +struct Size { + int64_t operator()(StringData v) const + { + return v.size(); + } + typedef StringData type; +}; + +template <> +struct Size { + int64_t operator()(BinaryData v) const + { + return v.size(); + } + typedef BinaryData type; +}; + +template <> +struct Size { + int64_t operator()(ConstTableRef v) const + { + return v->size(); + } + typedef ConstTableRef type; +}; + +template <> +struct Size { + int64_t operator()(ConstLinkViewRef v) const + { + return v->size(); + } + typedef ConstLinkViewRef type; +}; + +} // namespace realm + +#endif // REALM_QUERY_OPERATORS_HPP diff --git a/!main project/Pods/Realm/include/core/realm/realm_nmmintrin.h b/!main project/Pods/Realm/include/core/realm/realm_nmmintrin.h new file mode 100644 index 0000000..8144da6 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/realm_nmmintrin.h @@ -0,0 +1,182 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_NMMINTRIN_H +#define REALM_NMMINTRIN_H + +/* + We must support runtime detection of CPU support of SSE when distributing Realm as a closed source library. + + This is a problem on gcc and llvm: To use SSE intrinsics we need to pass -msse on the command line (to get offered + __builtin_ accessors used by intrinsics functions). However, the -msse flag allows gcc to emit SSE instructions + in its code generation/optimization. This is unwanted because the binary would crash on non-SSE CPUs. + + Since there exists no flag in gcc that enables intrinsics but probits SSE in code generation, we define our + own intrinsics to be assembled by the back end assembler and omit passing -msse to gcc. +*/ + +#ifndef _MSC_VER + +#ifdef REALM_COMPILER_SSE +#include // SSE2 (using __m128i) +#endif + +namespace realm { + +#if 0 +#ifdef REALM_COMPILER_AVX +typedef float __m256 __attribute__((__vector_size__(32), __may_alias__)); +typedef double __m256d __attribute__((__vector_size__(32), __may_alias__)); + +const int _CMP_EQ_OQ = 0x00; // Equal (ordered, non-signaling) +const int _CMP_NEQ_OQ = 0x0c; // Not-equal (ordered, non-signaling) +const int _CMP_LT_OQ = 0x11; // Less-than (ordered, non-signaling) +const int _CMP_LE_OQ = 0x12; // Less-than-or-equal (ordered, non-signaling) +const int _CMP_GE_OQ = 0x1d; // Greater-than-or-equal (ordered, non-signaling) +const int _CMP_GT_OQ = 0x1e; // Greater-than (ordered, non-signaling) + + +template +static int movemask_cmp_ps(__m256* y1, __m256* y2) +{ + int ret; + __asm__("vmovaps %0, %%ymm0" : : "m"(*y1) : "%xmm0" ); + __asm__("vmovaps %0, %%ymm1" : : "m"(*y2) : "%xmm1" ); + __asm__("vcmpps %0, %%ymm0, %%ymm1, %%ymm0" : : "I"(op) : "%xmm0" ); + __asm__("vmovmskps %%ymm0, %0" : "=r"(ret) : : ); + return ret; +} + +template +static inline int movemask_cmp_pd(__m256d* y1, __m256d* y2) +{ + int ret; + __asm__("vmovapd %0, %%ymm0" : : "m"(*y1) : "%xmm0" ); + __asm__("vmovapd %0, %%ymm1" : : "m"(*y2) : "%xmm1" ); + __asm__("vcmppd %0, %%ymm0, %%ymm1, %%ymm0" : : "I"(op) : "%xmm0" ); + __asm__("vmovmskpd %%ymm0, %0" : "=r"(ret) : : ); + return ret; +} + + + +static inline int movemask_cmp_ps(__m256* y1, __m256* y2, int op) +{ + // todo, use constexpr; + if (op == _CMP_EQ_OQ) + return movemask_cmp_ps<_CMP_NEQ_OQ>(y1, y2); + else if (op == _CMP_NEQ_OQ) + return movemask_cmp_ps<_CMP_NEQ_OQ>(y1, y2); + else if (op == _CMP_LT_OQ) + return movemask_cmp_ps<_CMP_LT_OQ>(y1, y2); + else if (op == _CMP_LE_OQ) + return movemask_cmp_ps<_CMP_LE_OQ>(y1, y2); + else if (op == _CMP_GE_OQ) + return movemask_cmp_ps<_CMP_GE_OQ>(y1, y2); + else if (op == _CMP_GT_OQ) + return movemask_cmp_ps<_CMP_GT_OQ>(y1, y2); + + REALM_ASSERT(false); + return 0; +} + +static inline int movemask_cmp_pd(__m256d* y1, __m256d* y2, int op) +{ + // todo, use constexpr; + if (op == _CMP_EQ_OQ) + return movemask_cmp_pd<_CMP_NEQ_OQ>(y1, y2); + else if (op == _CMP_NEQ_OQ) + return movemask_cmp_pd<_CMP_NEQ_OQ>(y1, y2); + else if (op == _CMP_LT_OQ) + return movemask_cmp_pd<_CMP_LT_OQ>(y1, y2); + else if (op == _CMP_LE_OQ) + return movemask_cmp_pd<_CMP_LE_OQ>(y1, y2); + else if (op == _CMP_GE_OQ) + return movemask_cmp_pd<_CMP_GE_OQ>(y1, y2); + else if (op == _CMP_GT_OQ) + return movemask_cmp_pd<_CMP_GT_OQ>(y1, y2); + + REALM_ASSERT(false); + return 0; +} + + +#endif +#endif + +// Instructions introduced by SSE 3 and 4.2 +static inline __m128i _mm_cmpgt_epi64(__m128i xmm1, __m128i xmm2) +{ + __asm__("pcmpgtq %1, %0" : "+x" (xmm1) : "xm" (xmm2)); + return xmm1; +} + +static inline __m128i _mm_cmpeq_epi64(__m128i xmm1, __m128i xmm2) +{ + __asm__("pcmpeqq %1, %0" : "+x" (xmm1) : "xm" (xmm2)); + return xmm1; +} + +static inline __m128i __attribute__((always_inline)) _mm_min_epi8(__m128i xmm1, __m128i xmm2) +{ + __asm__("pminsb %1, %0" : "+x" (xmm1) : "xm" (xmm2)); + return xmm1; +} + +static inline __m128i __attribute__((always_inline)) _mm_max_epi8(__m128i xmm1, __m128i xmm2) +{ + __asm__("pmaxsb %1, %0" : "+x" (xmm1) : "xm" (xmm2)); + return xmm1; +} + +static inline __m128i __attribute__((always_inline)) _mm_max_epi32(__m128i xmm1, __m128i xmm2) +{ + __asm__("pmaxsd %1, %0" : "+x" (xmm1) : "xm" (xmm2)); + return xmm1; +} + +static inline __m128i __attribute__((always_inline)) _mm_min_epi32(__m128i xmm1, __m128i xmm2) +{ + __asm__("pminsd %1, %0" : "+x" (xmm1) : "xm" (xmm2)); + return xmm1; +} + +static inline __m128i __attribute__((always_inline)) _mm_cvtepi8_epi16(__m128i xmm2) +{ + __m128i xmm1; + __asm__("pmovsxbw %1, %0" : "=x" (xmm1) : "xm" (xmm2) : "xmm1"); + return xmm1; +} +static inline __m128i __attribute__((always_inline)) _mm_cvtepi16_epi32(__m128i xmm2) +{ + __m128i xmm1; + asm("pmovsxwd %1, %0" : "=x" (xmm1) : "xm" (xmm2)); + return xmm1; +} + +static inline __m128i __attribute__((always_inline)) _mm_cvtepi32_epi64(__m128i xmm2) +{ + __m128i xmm1; + __asm__("pmovsxdq %1, %0" : "=x" (xmm1) : "xm" (xmm2)); + return xmm1; +} + +} // namespace realm + +#endif +#endif diff --git a/!main project/Pods/Realm/include/core/realm/replication.hpp b/!main project/Pods/Realm/include/core/realm/replication.hpp new file mode 100644 index 0000000..92a5782 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/replication.hpp @@ -0,0 +1,512 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_REPLICATION_HPP +#define REALM_REPLICATION_HPP + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace realm { +namespace util { +class Logger; +} + +// FIXME: Be careful about the possibility of one modification function being called by another where both do +// transaction logging. + +// FIXME: The current table/subtable selection scheme assumes that a TableRef of a subtable is not accessed after any +// modification of one of its ancestor tables. + +// FIXME: Checking on same Table* requires that ~Table checks and nullifies on match. Another option would be to store +// m_selected_table as a TableRef. Yet another option would be to assign unique identifiers to each Table instance via +// Allocator. Yet another option would be to explicitely invalidate subtables recursively when parent is modified. + +/// Replication is enabled by passing an instance of an implementation of this +/// class to the SharedGroup constructor. +class Replication : public _impl::TransactLogConvenientEncoder, protected _impl::TransactLogStream { +public: + // Be sure to keep this type aligned with what is actually used in + // SharedGroup. + using version_type = _impl::History::version_type; + using InputStream = _impl::NoCopyInputStream; + class TransactLogApplier; + class Interrupted; // Exception + class SimpleIndexTranslator; + + enum class TransactionType { trans_Read, trans_Write }; + + /// CAUTION: These values are stored in Realm files, so value reassignment + /// is not allowed. + enum HistoryType { + /// No history available. No support for either continuous transactions + /// or inter-client synchronization. + hist_None = 0, + + /// Out-of-Realm history supporting continuous transactions. + /// + /// NOTE: This history type is no longer in use. The value needs to stay + /// reserved in case someone tries to open an old Realm file. + hist_OutOfRealm = 1, + + /// In-Realm history supporting continuous transactions + /// (make_in_realm_history()). + hist_InRealm = 2, + + /// In-Realm history supporting continuous transactions and client-side + /// synchronization protocol (realm::sync::ClientHistory). + hist_SyncClient = 3, + + /// In-Realm history supporting continuous transactions and server-side + /// synchronization protocol (realm::_impl::ServerHistory). + hist_SyncServer = 4 + }; + + virtual std::string get_database_path() const = 0; + + /// Called during construction of the associated SharedGroup object. + /// + /// \param shared_group The assocoated SharedGroup object. + virtual void initialize(SharedGroup& shared_group) = 0; + + /// Called by the associated SharedGroup object when a session is + /// initiated. A *session* is a sequence of of temporally overlapping + /// accesses to a specific Realm file, where each access consists of a + /// SharedGroup object through which the Realm file is open. Session + /// initiation occurs during the first opening of the Realm file within such + /// a session. + /// + /// Session initiation fails if this function throws. + /// + /// \param version The current version of the associated Realm. Out-of-Realm + /// history implementation can use this to trim off history entries that + /// were successfully added to the history, but for which the corresponding + /// subsequent commits on the Realm file failed. + /// + /// The default implementation does nothing. + virtual void initiate_session(version_type version) = 0; + + /// Called by the associated SharedGroup object when a session is + /// terminated. See initiate_session() for the definition of a + /// session. Session termination occurs upon closing the Realm through the + /// last SharedGroup object within the session. + /// + /// The default implementation does nothing. + virtual void terminate_session() noexcept = 0; + + /// \defgroup replication_transactions + //@{ + + /// From the point of view of the Replication class, a transaction is + /// initiated when, and only when the associated SharedGroup object calls + /// initiate_transact() and the call is successful. The associated + /// SharedGroup object must terminate every initiated transaction either by + /// calling finalize_commit() or by calling abort_transact(). It may only + /// call finalize_commit(), however, after calling prepare_commit(), and + /// only when prepare_commit() succeeds. If prepare_commit() fails (i.e., + /// throws) abort_transact() must still be called. + /// + /// The associated SharedGroup object is supposed to terminate a transaction + /// as soon as possible, and is required to terminate it before attempting + /// to initiate a new one. + /// + /// initiate_transact() is called by the associated SharedGroup object as + /// part of the initiation of a transaction, and at a time where the caller + /// has acquired exclusive write access to the local Realm. The Replication + /// implementation is allowed to perform "precursor transactions" on the + /// local Realm at this time. During the initiated transaction, the + /// associated SharedGroup object must inform the Replication object of all + /// modifying operations by calling set_value() and friends. + /// + /// FIXME: There is currently no way for implementations to perform + /// precursor transactions, since a regular transaction would cause a dead + /// lock when it tries to acquire a write lock. Consider giving access to + /// special non-locking precursor transactions via an extra argument to this + /// function. + /// + /// prepare_commit() serves as the first phase of a two-phase commit. This + /// function is called by the associated SharedGroup object immediately + /// before the commit operation on the local Realm. The associated + /// SharedGroup object will then, as the second phase, either call + /// finalize_commit() or abort_transact() depending on whether the commit + /// operation succeeded or not. The Replication implementation is allowed to + /// modify the Realm via the associated SharedGroup object at this time + /// (important to in-Realm histories). + /// + /// initiate_transact() and prepare_commit() are allowed to block the + /// calling thread if, for example, they need to communicate over the + /// network. If a calling thread is blocked in one of these functions, it + /// must be possible to interrupt the blocking operation by having another + /// thread call interrupt(). The contract is as follows: When interrupt() is + /// called, then any execution of initiate_transact() or prepare_commit(), + /// initiated before the interruption, must complete without blocking, or + /// the execution must be aborted by throwing an Interrupted exception. If + /// initiate_transact() or prepare_commit() throws Interrupted, it counts as + /// a failed operation. + /// + /// finalize_commit() is called by the associated SharedGroup object + /// immediately after a successful commit operation on the local Realm. This + /// happens at a time where modification of the Realm is no longer possible + /// via the associated SharedGroup object. In the case of in-Realm + /// histories, the changes are automatically finalized as part of the commit + /// operation performed by the caller prior to the invocation of + /// finalize_commit(), so in that case, finalize_commit() might not need to + /// do anything. + /// + /// abort_transact() is called by the associated SharedGroup object to + /// terminate a transaction without committing. That is, any transaction + /// that is not terminated by finalize_commit() is terminated by + /// abort_transact(). This could be due to an explicit rollback, or due to a + /// failed commit attempt. + /// + /// Note that finalize_commit() and abort_transact() are not allowed to + /// throw. + /// + /// \param current_version The version of the snapshot that the current + /// transaction is based on. + /// + /// \param history_updated Pass true only when the history has already been + /// updated to reflect the currently bound snapshot, such as when + /// _impl::History::update_early_from_top_ref() was called during the + /// transition from a read transaction to the current write transaction. + /// + /// \return prepare_commit() returns the version of the new snapshot + /// produced by the transaction. + /// + /// \throw Interrupted Thrown by initiate_transact() and prepare_commit() if + /// a blocking operation was interrupted. + + void initiate_transact(TransactionType transaction_type, version_type current_version, bool history_updated); + version_type prepare_commit(version_type current_version); + void finalize_commit() noexcept; + void abort_transact() noexcept; + + //@} + + + /// Interrupt any blocking call to a function in this class. This function + /// may be called asyncronously from any thread, but it may not be called + /// from a system signal handler. + /// + /// Some of the public function members of this class may block, but only + /// when it it is explicitely stated in the documention for those functions. + /// + /// FIXME: Currently we do not state blocking behaviour for all the + /// functions that can block. + /// + /// After any function has returned with an interruption indication, the + /// only functions that may safely be called are abort_transact() and the + /// destructor. If a client, after having received an interruption + /// indication, calls abort_transact() and then clear_interrupt(), it may + /// resume normal operation through this Replication object. + void interrupt() noexcept; + + /// May be called by a client to reset this Replication object after an + /// interrupted transaction. It is not an error to call this function in a + /// situation where no interruption has occured. + void clear_interrupt() noexcept; + + /// Apply a changeset to the specified group. + /// + /// \param changeset The changes to be applied. + /// + /// \param group The destination group to apply the changeset to. + /// + /// \param logger If specified, and the library was compiled in debug mode, + /// then a line describing each individual operation is writted to the + /// specified logger. + /// + /// \throw BadTransactLog If the changeset could not be successfully parsed, + /// or ended prematurely. + static void apply_changeset(InputStream& changeset, Group& group, util::Logger* logger = nullptr); + + /// Returns the type of history maintained by this Replication + /// implementation, or \ref hist_None if no history is maintained by it. + /// + /// This type is used to ensure that all session participants agree on + /// history type, and that the Realm file contains a compatible type of + /// history, at the beginning of a new session. + /// + /// As a special case, if there is no top array (Group::m_top) at the + /// beginning of a new session, then the history type is still undecided and + /// all history types (as returned by get_history_type()) are threfore + /// allowed for the session initiator. Note that this case only arises if + /// there was no preceding session, or if no transaction was sucessfully + /// committed during any of the preceding sessions. As soon as a transaction + /// is successfully committed, the Realm contains at least a top array, and + /// from that point on, the history type is generally fixed, although still + /// subject to certain allowed changes (as mentioned below). + /// + /// For the sake of backwards compatibility with older Realm files that does + /// not store any history type, the following rule shall apply: + /// + /// - If the top array of a Realm file (Group::m_top) does not contain a + /// history type, because it is too short, it shall be understood as + /// implicitly storing the type \ref hist_None. + /// + /// Note: In what follows, the meaning of *preceding session* is: The last + /// preceding session that modified the Realm by sucessfully committing a + /// new snapshot. + /// + /// It shall be allowed to switch to a \ref hist_InRealm history if the + /// stored history type is \ref hist_None. This can be done simply by adding + /// a new history to the Realm file. This is possible because histories of + /// this type a transient in nature, and need not survive from one session + /// to the next. + /// + /// On the other hand, as soon as a history of type \ref hist_InRealm is + /// added to a Realm file, that history type is binding for all subsequent + /// sessions. In theory, this constraint is not necessary, and a later + /// switch to \ref hist_None would be possible because of the transient + /// nature of it, however, because the \ref hist_InRealm history remains in + /// the Realm file, there are practical complications, and for that reason, + /// such switching shall not be supported. + /// + /// The \ref hist_SyncClient history type can only be used if the stored + /// history type is also \ref hist_SyncClient, or when there is no top array + /// yet. Likewise, the \ref hist_SyncServer history type can only be used if + /// the stored history type is also \ref hist_SyncServer, or when there is + /// no top array yet. Additionally, when the stored history type is \ref + /// hist_SyncClient or \ref hist_SyncServer, then all subsequent sessions + /// must have the same type. These restrictions apply because such a history + /// needs to be maintained persistently across sessions. + /// + /// In general, if there is no stored history type (no top array) at the + /// beginning of a new session, or if the stored type disagrees with what is + /// returned by get_history_type() (which is possible due to particular + /// allowed changes of history type), the actual history type (as returned + /// by get_history_type()) used during that session, must be stored in the + /// Realm during the first successfully committed transaction in that + /// session. But note that there is still no need to expand the top array to + /// store the history type \ref hist_None, due to the rule mentioned above. + /// + /// This function must return \ref hist_None when, and only when + /// get_history() returns null. + virtual HistoryType get_history_type() const noexcept = 0; + + /// Returns the schema version of the history maintained by this Replication + /// implementation, or 0 if no history is maintained by it. All session + /// participants must agree on history schema version. + /// + /// Must return 0 if get_history_type() returns \ref hist_None. + virtual int get_history_schema_version() const noexcept = 0; + + /// Implementation may assume that this function is only ever called with a + /// stored schema version that is less than what was returned by + /// get_history_schema_version(). + virtual bool is_upgradable_history_schema(int stored_schema_version) const noexcept = 0; + + /// The implementation may assume that this function is only ever called if + /// is_upgradable_history_schema() was called with the same stored schema + /// version, and returned true. This implies that the specified stored + /// schema version is always strictly less than what was returned by + /// get_history_schema_version(). + virtual void upgrade_history_schema(int stored_schema_version) = 0; + + /// Returns an object that gives access to the history of changesets in a + /// way that allows for continuous transactions to work + /// (Group::advance_transact() in particular). + /// + /// This function must return null when, and only when get_history_type() + /// returns \ref hist_None. + virtual _impl::History* get_history() = 0; + + /// Returns false by default, but must return true if, and only if this + /// history object represents a session participant that is a sync + /// agent. This is used to enforce the "maximum one sync agent per session" + /// constraint. + virtual bool is_sync_agent() const noexcept; + + virtual ~Replication() noexcept + { + } + +protected: + Replication(); + + + //@{ + + /// do_initiate_transact() is called by initiate_transact(), and likewise + /// for do_prepare_commit), do_finalize_commit(), and do_abort_transact(). + /// + /// With respect to exception safety, the Replication implementation has two + /// options: It can prepare to accept the accumulated changeset in + /// do_prepapre_commit() by allocating all required resources, and delay the + /// actual acceptance to do_finalize_commit(), which requires that the final + /// acceptance can be done without any risk of failure. Alternatively, the + /// Replication implementation can fully accept the changeset in + /// do_prepapre_commit() (allowing for failure), and then discard that + /// changeset during the next invocation of do_initiate_transact() if + /// `current_version` indicates that the previous transaction failed. + + virtual void do_initiate_transact(TransactionType, version_type current_version) = 0; + virtual version_type do_prepare_commit(version_type orig_version) = 0; + virtual void do_finalize_commit() noexcept = 0; + virtual void do_abort_transact() noexcept = 0; + + //@} + + + virtual void do_interrupt() noexcept = 0; + + virtual void do_clear_interrupt() noexcept = 0; + + friend class _impl::TransactReverser; +}; + + +class Replication::Interrupted : public std::exception { +public: + const char* what() const noexcept override + { + return "Interrupted"; + } +}; + + +class TrivialReplication : public Replication { +public: + ~TrivialReplication() noexcept + { + } + + std::string get_database_path() const override; +protected: + typedef Replication::version_type version_type; + + TrivialReplication(const std::string& database_file); + + virtual version_type prepare_changeset(const char* data, size_t size, version_type orig_version) = 0; + virtual void finalize_changeset() noexcept = 0; + + static void apply_changeset(const char* data, size_t size, SharedGroup& target, util::Logger* logger = nullptr); + + BinaryData get_uncommitted_changes() const noexcept; + + void initialize(SharedGroup&) override; + void do_initiate_transact(TransactionType, version_type) override; + version_type do_prepare_commit(version_type orig_version) override; + void do_finalize_commit() noexcept override; + void do_abort_transact() noexcept override; + void do_interrupt() noexcept override; + void do_clear_interrupt() noexcept override; + void transact_log_reserve(size_t n, char** new_begin, char** new_end) override; + void transact_log_append(const char* data, size_t size, char** new_begin, char** new_end) override; + +private: + const std::string m_database_file; + util::Buffer m_transact_log_buffer; + void internal_transact_log_reserve(size_t, char** new_begin, char** new_end); + + size_t transact_log_size(); +}; + + +// Implementation: + +inline Replication::Replication() + : _impl::TransactLogConvenientEncoder(static_cast<_impl::TransactLogStream&>(*this)) +{ +} + +inline void Replication::initiate_transact(TransactionType transaction_type, version_type current_version, + bool history_updated) +{ + if (auto hist = get_history()) { + hist->set_updated(history_updated); + } + do_initiate_transact(transaction_type, current_version); + reset_selection_caches(); +} + +inline Replication::version_type Replication::prepare_commit(version_type orig_version) +{ + return do_prepare_commit(orig_version); +} + +inline void Replication::finalize_commit() noexcept +{ + do_finalize_commit(); +} + +inline void Replication::abort_transact() noexcept +{ + do_abort_transact(); +} + +inline void Replication::interrupt() noexcept +{ + do_interrupt(); +} + +inline void Replication::clear_interrupt() noexcept +{ + do_clear_interrupt(); +} + +inline bool Replication::is_sync_agent() const noexcept +{ + return false; +} + +inline TrivialReplication::TrivialReplication(const std::string& database_file) + : m_database_file(database_file) +{ +} + +inline BinaryData TrivialReplication::get_uncommitted_changes() const noexcept +{ + const char* data = m_transact_log_buffer.data(); + size_t size = write_position() - data; + return BinaryData(data, size); +} + +inline size_t TrivialReplication::transact_log_size() +{ + return write_position() - m_transact_log_buffer.data(); +} + +inline void TrivialReplication::transact_log_reserve(size_t n, char** new_begin, char** new_end) +{ + internal_transact_log_reserve(n, new_begin, new_end); +} + +inline void TrivialReplication::internal_transact_log_reserve(size_t n, char** new_begin, char** new_end) +{ + char* data = m_transact_log_buffer.data(); + size_t size = write_position() - data; + m_transact_log_buffer.reserve_extra(size, n); + data = m_transact_log_buffer.data(); // May have changed + *new_begin = data + size; + *new_end = data + m_transact_log_buffer.size(); +} + +} // namespace realm + +#endif // REALM_REPLICATION_HPP diff --git a/!main project/Pods/Realm/include/core/realm/row.hpp b/!main project/Pods/Realm/include/core/realm/row.hpp new file mode 100644 index 0000000..66b7c58 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/row.hpp @@ -0,0 +1,862 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_ROW_HPP +#define REALM_ROW_HPP + +#include + +#include +#include +#include +#include +#include + +namespace realm { + +template +class BasicRow; + + +/// This class is a "mixin" and contains the common set of functions for several +/// distinct row-like classes. +/// +/// There is a direct and natural correspondance between the functions in this +/// class and functions in Table of the same name. For example: +/// +/// table[i].get_int(j) == table.get_int(i,j) +/// +/// The effect of calling most of the row accessor functions on a detached +/// accessor is unspecified and may lead to general corruption, and/or a +/// crash. The exceptions are is_attached(), detach(), get_table(), get_index(), +/// and the destructor. Note however, that get_index() will still return an +/// unspecified value for a deatched accessor. +/// +/// When a row accessor is evaluated in a boolean context, it evaluates to true +/// if, and only if it is attached. +/// +/// \tparam T A const or non-const table type (currently either `Table` or +/// `const Table`). +/// +/// \tparam R A specific row accessor class (BasicRow or BasicRowExpr) providing +/// members `T* impl_get_table() const`, `size_t impl_get_row_ndx() +/// const`, and `void impl_detach()`. Neither are allowed to throw. +/// +/// \sa Table +/// \sa BasicRow +template +class RowFuncs { +public: + typedef T table_type; + + typedef BasicTableRef ConstTableRef; + typedef BasicTableRef TableRef; // Same as ConstTableRef if `T` is 'const' + + typedef typename util::CopyConst::type L; + using ConstLinkViewRef = std::shared_ptr; + using LinkViewRef = std::shared_ptr; // Same as ConstLinkViewRef if `T` is 'const' + + int_fast64_t get_int(size_t col_ndx) const noexcept; + bool get_bool(size_t col_ndx) const noexcept; + float get_float(size_t col_ndx) const noexcept; + double get_double(size_t col_ndx) const noexcept; + StringData get_string(size_t col_ndx) const noexcept; + BinaryData get_binary(size_t col_ndx) const noexcept; + OldDateTime get_olddatetime(size_t col_ndx) const noexcept; + Timestamp get_timestamp(size_t col_ndx) const noexcept; + ConstTableRef get_subtable(size_t col_ndx) const; + TableRef get_subtable(size_t col_ndx); + size_t get_subtable_size(size_t col_ndx) const noexcept; + size_t get_link(size_t col_ndx) const noexcept; + bool is_null_link(size_t col_ndx) const noexcept; + bool is_null(size_t col_ndx) const noexcept; + ConstLinkViewRef get_linklist(size_t col_ndx) const; + LinkViewRef get_linklist(size_t col_ndx); + bool linklist_is_empty(size_t col_ndx) const noexcept; + size_t get_link_count(size_t col_ndx) const noexcept; + Mixed get_mixed(size_t col_ndx) const noexcept; + DataType get_mixed_type(size_t col_ndx) const noexcept; + + template + U get(size_t col_ndx) const noexcept; + + void set_int(size_t col_ndx, int_fast64_t value); + void set_int_unique(size_t col_ndx, int_fast64_t value); + void add_int(size_t col_ndx, int_fast64_t value); + void set_bool(size_t col_ndx, bool value); + void set_float(size_t col_ndx, float value); + void set_double(size_t col_ndx, double value); + void set_string(size_t col_ndx, StringData value); + void set_string_unique(size_t col_ndx, StringData value); + void set_binary(size_t col_ndx, BinaryData value); + void set_olddatetime(size_t col_ndx, OldDateTime value); + void set_timestamp(size_t col_ndx, Timestamp value); + void set_subtable(size_t col_ndx, const Table* value); + void set_link(size_t col_ndx, size_t value); + void nullify_link(size_t col_ndx); + void set_mixed(size_t col_ndx, Mixed value); + void set_mixed_subtable(size_t col_ndx, const Table* value); + void set_null(size_t col_ndx); + void set_null_unique(size_t col_ndx); + + template + void set(size_t col_ndx, U&& value, bool is_default = false); + + template + void set_unique(size_t col_ndx, U&& value); + + void insert_substring(size_t col_ndx, size_t pos, StringData); + void remove_substring(size_t col_ndx, size_t pos, size_t size); + + //@{ + /// Note that these operations will cause the row accessor to be detached. + void remove(); + void move_last_over(); + //@} + + size_t get_backlink_count() const noexcept; + size_t get_backlink_count(const Table& src_table, size_t src_col_ndx) const noexcept; + size_t get_backlink(const Table& src_table, size_t src_col_ndx, size_t backlink_ndx) const noexcept; + + size_t get_column_count() const noexcept; + DataType get_column_type(size_t col_ndx) const noexcept; + StringData get_column_name(size_t col_ndx) const noexcept; + size_t get_column_index(StringData name) const noexcept; + + /// Returns true if, and only if this accessor is currently attached to a + /// row. + /// + /// A row accesor may get detached from the underlying row for various + /// reasons (see below). When it does, it no longer refers to anything, and + /// can no longer be used, except for calling is_attached(), detach(), + /// get_table(), get_index(), and the destructor. The consequences of + /// calling other methods on a detached row accessor are unspecified. There + /// are a few Realm functions (Table::find_pkey_int()) that return a + /// detached row accessor to indicate a 'null' result. In all other cases, + /// however, row accessors obtained by calling functions in the Realm API + /// are always in the 'attached' state immediately upon return from those + /// functions. + /// + /// A row accessor becomes detached if the underlying row is removed, if the + /// associated table accessor becomes detached, or if the detach() method is + /// called. A row accessor does not become detached for any other reason. + bool is_attached() const noexcept; + + /// Detach this accessor from the row it was attached to. This function has + /// no effect if the accessor was already detached (idempotency). + void detach() noexcept; + + /// The table containing the row to which this accessor is currently + /// bound. For a detached accessor, the returned value is null. + const table_type* get_table() const noexcept; + table_type* get_table() noexcept; + + /// The index of the row to which this accessor is currently bound. For a + /// detached accessor, the returned value is unspecified. + size_t get_index() const noexcept; + + explicit operator bool() const noexcept; + +private: + const T* table() const noexcept; + T* table() noexcept; + size_t row_ndx() const noexcept; +}; + + +/// This class is a special kind of row accessor. It differes from a real row +/// accessor (BasicRow) by having a trivial and fast copy constructor and +/// descructor. It is supposed to be used as the return type of functions such +/// as Table::operator[](), and then to be used as a basis for constructing a +/// real row accessor. Objects of this class are intended to only ever exist as +/// temporaries. +/// +/// In contrast to a real row accessor (`BasicRow`), objects of this class do +/// not keep the parent table "alive", nor are they maintained (adjusted) across +/// row insertions and row removals like real row accessors are. +/// +/// \sa BasicRow +template +class BasicRowExpr : public RowFuncs> { +public: + BasicRowExpr() noexcept = default; + + template + BasicRowExpr(const BasicRowExpr&) noexcept; + + template + BasicRowExpr(const BasicRow&) noexcept; + +private: + T* m_table = nullptr; // nullptr if detached. + size_t m_row_ndx = 0; // Undefined if detached. + + BasicRowExpr(T*, size_t init_row_ndx) noexcept; + + T* impl_get_table() const noexcept; + size_t impl_get_row_ndx() const noexcept; + void impl_detach() noexcept; + + // Make impl_get_table(), impl_get_row_ndx(), and impl_detach() accessible + // from RowFuncs. + friend class RowFuncs>; + + // Make m_table and m_row_ndx accessible from BasicRowExpr(const + // BasicRowExpr&) for any U. + template + friend class BasicRowExpr; + + // Make m_table and m_row_ndx accessible from + // BasicRow::BaicRow(BasicRowExpr) for any U. + template + friend class BasicRow; + + // Make BasicRowExpr(T*, size_t) accessible from Table. + friend class Table; +}; + +// fwd decl +class Group; + +class RowBase { +protected: + TableRef m_table; // nullptr if detached. + size_t m_row_ndx = -1; // Undefined if detached. + + void attach(Table*, size_t row_ndx) noexcept; + void reattach(Table*, size_t row_ndx) noexcept; + void impl_detach() noexcept; + + RowBase() + { + } + + RowBase(const RowBase&) = delete; + using HandoverPatch = RowBaseHandoverPatch; + + RowBase(const RowBase& source, HandoverPatch& patch); + +public: + static void generate_patch(const RowBase& source, HandoverPatch& patch); + void apply_patch(HandoverPatch& patch, Group& group); + +private: + RowBase* m_prev = nullptr; // nullptr if first, undefined if detached. + RowBase* m_next = nullptr; // nullptr if last, undefined if detached. + + // Table needs to be able to modify m_table and m_row_ndx. + friend class Table; +}; + + +/// An accessor class for table rows (a.k.a. a "row accessor"). +/// +/// For as long as it remains attached, a row accessor will keep the parent +/// table accessor alive. In case the lifetime of the parent table is not +/// managed by reference counting (such as when the table is an automatic +/// variable on the stack), the destruction of the table will cause all +/// remaining row accessors to be detached. +/// +/// While attached, a row accessor is bound to a particular row of the parent +/// table. If that row is removed, the accesssor becomes detached. If rows are +/// inserted or removed before it (at lower row index), then the accessor is +/// automatically adjusted to account for the change in index of the row to +/// which the accessor is bound. In other words, a row accessor is bound to the +/// contents of a row, not to a row index. See also is_attached(). +/// +/// Row accessors are created and used as follows: +/// +/// Row row = table[7]; // 8th row of `table` +/// ConstRow crow = ctable[2]; // 3rd row of const `ctable` +/// Row first_row = table.front(); +/// Row last_row = table.back(); +/// +/// float v = row.get_float(1); // Get the float in the 2nd column +/// row.set_string(0, "foo"); // Update the string in the 1st column +/// +/// Table* t = row.get_table(); // The parent table +/// size_t i = row.get_index(); // The current row index +/// +/// \sa RowFuncs +template +class BasicRow : private RowBase, public RowFuncs> { +public: + BasicRow() noexcept; + + template + BasicRow(BasicRowExpr) noexcept; + + BasicRow(const BasicRow&) noexcept; + + template + BasicRow(const BasicRow&) noexcept; + + template + BasicRow& operator=(BasicRowExpr) noexcept; + + template + BasicRow& operator=(BasicRow) noexcept; + + BasicRow& operator=(const BasicRow&) noexcept; + + ~BasicRow() noexcept; + +private: + T* impl_get_table() const noexcept; + size_t impl_get_row_ndx() const noexcept; + + // Make impl_get_table(), impl_get_row_ndx(), and impl_detach() accessible + // from RowFuncs. + friend class RowFuncs>; + + // Make m_table and m_row_ndx accessible from BasicRow(const BasicRow&) + // for any U. + template + friend class BasicRow; + + // Make m_table and m_row_ndx accessible from BasicRowExpr(const + // BasicRow&) for any U. + template + friend class BasicRowExpr; + +public: + std::unique_ptr> clone_for_handover(std::unique_ptr& patch) const + { + patch.reset(new HandoverPatch); + std::unique_ptr> retval(new BasicRow(*this, *patch)); + return retval; + } + + static void generate_patch(const BasicRow& row, std::unique_ptr& patch) + { + patch.reset(new HandoverPatch); + RowBase::generate_patch(row, *patch); + } + + void apply_and_consume_patch(std::unique_ptr& patch, Group& group) + { + apply_patch(*patch, group); + patch.reset(); + } + + void apply_patch(HandoverPatch& patch, Group& group) + { + RowBase::apply_patch(patch, group); + } + +private: + BasicRow(const BasicRow& source, HandoverPatch& patch) + : RowBase(source, patch) + { + } + friend class SharedGroup; +}; + +typedef BasicRow
Row; +typedef BasicRow ConstRow; + + +// Implementation + +template +inline int_fast64_t RowFuncs::get_int(size_t col_ndx) const noexcept +{ + return table()->get_int(col_ndx, row_ndx()); +} + +template +inline bool RowFuncs::get_bool(size_t col_ndx) const noexcept +{ + return table()->get_bool(col_ndx, row_ndx()); +} + +template +inline float RowFuncs::get_float(size_t col_ndx) const noexcept +{ + return table()->get_float(col_ndx, row_ndx()); +} + +template +inline double RowFuncs::get_double(size_t col_ndx) const noexcept +{ + return table()->get_double(col_ndx, row_ndx()); +} + +template +inline StringData RowFuncs::get_string(size_t col_ndx) const noexcept +{ + return table()->get_string(col_ndx, row_ndx()); +} + +template +inline BinaryData RowFuncs::get_binary(size_t col_ndx) const noexcept +{ + return table()->get_binary(col_ndx, row_ndx()); +} + +template +inline OldDateTime RowFuncs::get_olddatetime(size_t col_ndx) const noexcept +{ + return table()->get_olddatetime(col_ndx, row_ndx()); +} + +template +inline Timestamp RowFuncs::get_timestamp(size_t col_ndx) const noexcept +{ + return table()->get_timestamp(col_ndx, row_ndx()); +} + +template +inline typename RowFuncs::ConstTableRef RowFuncs::get_subtable(size_t col_ndx) const +{ + return table()->get_subtable(col_ndx, row_ndx()); // Throws +} + +template +inline typename RowFuncs::TableRef RowFuncs::get_subtable(size_t col_ndx) +{ + return table()->get_subtable(col_ndx, row_ndx()); // Throws +} + +template +inline size_t RowFuncs::get_subtable_size(size_t col_ndx) const noexcept +{ + return table()->get_subtable_size(col_ndx, row_ndx()); +} + +template +inline size_t RowFuncs::get_link(size_t col_ndx) const noexcept +{ + return table()->get_link(col_ndx, row_ndx()); +} + +template +inline bool RowFuncs::is_null_link(size_t col_ndx) const noexcept +{ + return table()->is_null_link(col_ndx, row_ndx()); +} + +template +inline bool RowFuncs::is_null(size_t col_ndx) const noexcept +{ + return table()->is_null(col_ndx, row_ndx()); +} + +template +inline typename RowFuncs::ConstLinkViewRef RowFuncs::get_linklist(size_t col_ndx) const +{ + return table()->get_linklist(col_ndx, row_ndx()); // Throws +} + +template +inline typename RowFuncs::LinkViewRef RowFuncs::get_linklist(size_t col_ndx) +{ + return table()->get_linklist(col_ndx, row_ndx()); // Throws +} + +template +inline bool RowFuncs::linklist_is_empty(size_t col_ndx) const noexcept +{ + return table()->linklist_is_empty(col_ndx, row_ndx()); +} + +template +inline size_t RowFuncs::get_link_count(size_t col_ndx) const noexcept +{ + return table()->get_link_count(col_ndx, row_ndx()); +} + +template +inline Mixed RowFuncs::get_mixed(size_t col_ndx) const noexcept +{ + return table()->get_mixed(col_ndx, row_ndx()); +} + +template +inline DataType RowFuncs::get_mixed_type(size_t col_ndx) const noexcept +{ + return table()->get_mixed_type(col_ndx, row_ndx()); +} + +template +template +inline U RowFuncs::get(size_t col_ndx) const noexcept +{ + return table()->template get(col_ndx, row_ndx()); +} + +template +inline void RowFuncs::set_int(size_t col_ndx, int_fast64_t value) +{ + table()->set_int(col_ndx, row_ndx(), value); // Throws +} + +template +inline void RowFuncs::set_int_unique(size_t col_ndx, int_fast64_t value) +{ + table()->set_int_unique(col_ndx, row_ndx(), value); // Throws +} + +template +inline void RowFuncs::add_int(size_t col_ndx, int_fast64_t value) +{ + table()->add_int(col_ndx, row_ndx(), value); // Throws +} + +template +inline void RowFuncs::set_bool(size_t col_ndx, bool value) +{ + table()->set_bool(col_ndx, row_ndx(), value); // Throws +} + +template +inline void RowFuncs::set_float(size_t col_ndx, float value) +{ + table()->set_float(col_ndx, row_ndx(), value); // Throws +} + +template +inline void RowFuncs::set_double(size_t col_ndx, double value) +{ + table()->set_double(col_ndx, row_ndx(), value); // Throws +} + +template +inline void RowFuncs::set_string(size_t col_ndx, StringData value) +{ + table()->set_string(col_ndx, row_ndx(), value); // Throws +} + +template +inline void RowFuncs::set_string_unique(size_t col_ndx, StringData value) +{ + table()->set_string_unique(col_ndx, row_ndx(), value); // Throws +} + +template +inline void RowFuncs::set_binary(size_t col_ndx, BinaryData value) +{ + table()->set_binary(col_ndx, row_ndx(), value); // Throws +} + +template +inline void RowFuncs::set_olddatetime(size_t col_ndx, OldDateTime value) +{ + table()->set_olddatetime(col_ndx, row_ndx(), value); // Throws +} + +template +inline void RowFuncs::set_timestamp(size_t col_ndx, Timestamp value) +{ + table()->set_timestamp(col_ndx, row_ndx(), value); // Throws +} + +template +inline void RowFuncs::set_subtable(size_t col_ndx, const Table* value) +{ + table()->set_subtable(col_ndx, row_ndx(), value); // Throws +} + +template +inline void RowFuncs::set_link(size_t col_ndx, size_t value) +{ + table()->set_link(col_ndx, row_ndx(), value); // Throws +} + +template +inline void RowFuncs::nullify_link(size_t col_ndx) +{ + table()->nullify_link(col_ndx, row_ndx()); // Throws +} + +template +inline void RowFuncs::set_mixed(size_t col_ndx, Mixed value) +{ + table()->set_mixed(col_ndx, row_ndx(), value); // Throws +} + +template +inline void RowFuncs::set_mixed_subtable(size_t col_ndx, const Table* value) +{ + table()->set_mixed_subtable(col_ndx, row_ndx(), value); // Throws +} + +template +inline void RowFuncs::set_null(size_t col_ndx) +{ + table()->set_null(col_ndx, row_ndx()); // Throws +} + +template +inline void RowFuncs::set_null_unique(size_t col_ndx) +{ + table()->set_null_unique(col_ndx, row_ndx()); // Throws +} + +template +template +inline void RowFuncs::set(size_t col_ndx, U&& value, bool is_default) +{ + table()->set(col_ndx, row_ndx(), std::forward(value), is_default); // Throws +} + +template +template +inline void RowFuncs::set_unique(size_t col_ndx, U&& value) +{ + table()->set_unique(col_ndx, row_ndx(), std::forward(value)); // Throws +} + +template +inline void RowFuncs::insert_substring(size_t col_ndx, size_t pos, StringData value) +{ + table()->insert_substring(col_ndx, row_ndx(), pos, value); // Throws +} + +template +inline void RowFuncs::remove_substring(size_t col_ndx, size_t pos, size_t size) +{ + table()->remove_substring(col_ndx, row_ndx(), pos, size); // Throws +} + +template +inline void RowFuncs::remove() +{ + table()->remove(row_ndx()); // Throws +} + +template +inline void RowFuncs::move_last_over() +{ + table()->move_last_over(row_ndx()); // Throws +} + +template +inline size_t RowFuncs::get_backlink_count() const noexcept +{ + return table()->get_backlink_count(row_ndx()); +} + +template +inline size_t RowFuncs::get_backlink_count(const Table& src_table, size_t src_col_ndx) const noexcept +{ + return table()->get_backlink_count(row_ndx(), src_table, src_col_ndx); +} + +template +inline size_t RowFuncs::get_backlink(const Table& src_table, size_t src_col_ndx, size_t backlink_ndx) const + noexcept +{ + return table()->get_backlink(row_ndx(), src_table, src_col_ndx, backlink_ndx); +} + +template +inline size_t RowFuncs::get_column_count() const noexcept +{ + return table()->get_column_count(); +} + +template +inline DataType RowFuncs::get_column_type(size_t col_ndx) const noexcept +{ + return table()->get_column_type(col_ndx); +} + +template +inline StringData RowFuncs::get_column_name(size_t col_ndx) const noexcept +{ + return table()->get_column_name(col_ndx); +} + +template +inline size_t RowFuncs::get_column_index(StringData name) const noexcept +{ + return table()->get_column_index(name); +} + +template +inline bool RowFuncs::is_attached() const noexcept +{ + return static_cast(this)->impl_get_table(); +} + +template +inline void RowFuncs::detach() noexcept +{ + static_cast(this)->impl_detach(); +} + +template +inline const T* RowFuncs::get_table() const noexcept +{ + return table(); +} + +template +inline T* RowFuncs::get_table() noexcept +{ + return table(); +} + +template +inline size_t RowFuncs::get_index() const noexcept +{ + return row_ndx(); +} + +template +inline RowFuncs::operator bool() const noexcept +{ + return is_attached(); +} + +template +inline const T* RowFuncs::table() const noexcept +{ + return static_cast(this)->impl_get_table(); +} + +template +inline T* RowFuncs::table() noexcept +{ + return static_cast(this)->impl_get_table(); +} + +template +inline size_t RowFuncs::row_ndx() const noexcept +{ + return static_cast(this)->impl_get_row_ndx(); +} + + +template +template +inline BasicRowExpr::BasicRowExpr(const BasicRowExpr& expr) noexcept + : m_table(expr.m_table) + , m_row_ndx(expr.m_row_ndx) +{ +} + +template +template +inline BasicRowExpr::BasicRowExpr(const BasicRow& row) noexcept + : m_table(row.m_table.get()) + , m_row_ndx(row.m_row_ndx) +{ +} + +template +inline BasicRowExpr::BasicRowExpr(T* init_table, size_t init_row_ndx) noexcept + : m_table(init_table) + , m_row_ndx(init_row_ndx) +{ +} + +template +inline T* BasicRowExpr::impl_get_table() const noexcept +{ + return m_table; +} + +template +inline size_t BasicRowExpr::impl_get_row_ndx() const noexcept +{ + return m_row_ndx; +} + +template +inline void BasicRowExpr::impl_detach() noexcept +{ + m_table = nullptr; +} + + +template +inline BasicRow::BasicRow() noexcept +{ +} + +template +inline BasicRow::BasicRow(const BasicRow& row) noexcept + : RowBase() +{ + attach(const_cast(row.m_table.get()), row.m_row_ndx); +} + +template +template +inline BasicRow::BasicRow(BasicRowExpr expr) noexcept +{ + T* expr_table = expr.m_table; // Check that pointer types are compatible + attach(const_cast(expr_table), expr.m_row_ndx); +} + +template +template +inline BasicRow::BasicRow(const BasicRow& row) noexcept +{ + T* row_table = row.m_table.get(); // Check that pointer types are compatible + attach(const_cast(row_table), row.m_row_ndx); +} + +template +template +inline BasicRow& BasicRow::operator=(BasicRowExpr expr) noexcept +{ + T* expr_table = expr.m_table; // Check that pointer types are compatible + reattach(const_cast(expr_table), expr.m_row_ndx); + return *this; +} + +template +template +inline BasicRow& BasicRow::operator=(BasicRow row) noexcept +{ + T* row_table = row.m_table.get(); // Check that pointer types are compatible + reattach(const_cast(row_table), row.m_row_ndx); + return *this; +} + +template +inline BasicRow& BasicRow::operator=(const BasicRow& row) noexcept +{ + reattach(const_cast(row.m_table.get()), row.m_row_ndx); + return *this; +} + +template +inline BasicRow::~BasicRow() noexcept +{ + RowBase::impl_detach(); +} + +template +inline T* BasicRow::impl_get_table() const noexcept +{ + return m_table.get(); +} + +template +inline size_t BasicRow::impl_get_row_ndx() const noexcept +{ + return m_row_ndx; +} + +} // namespace realm + +#endif // REALM_ROW_HPP diff --git a/!main project/Pods/Realm/include/core/realm/spec.hpp b/!main project/Pods/Realm/include/core/realm/spec.hpp new file mode 100644 index 0000000..6b793f0 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/spec.hpp @@ -0,0 +1,388 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_SPEC_HPP +#define REALM_SPEC_HPP + +#include +#include +#include +#include +#include +#include + +namespace realm { + +class Table; + +class Spec { +public: + ~Spec() noexcept; + + Allocator& get_alloc() const noexcept; + + bool has_strong_link_columns() noexcept; + + void insert_column(size_t column_ndx, ColumnType type, StringData name, ColumnAttr attr = col_attr_None); + void rename_column(size_t column_ndx, StringData new_name); + + /// Erase the column at the specified index, and move columns at + /// succeeding indexes to the next lower index. + /// + /// This function is guaranteed to *never* throw if the spec is + /// used in a non-transactional context, or if the spec has + /// already been successfully modified within the current write + /// transaction. + void erase_column(size_t column_ndx); + + //@{ + // If a new Spec is constructed from the returned subspec + // reference, it is the responsibility of the application that the + // parent Spec object (this) is kept alive for at least as long as + // the new Spec object. + Spec* get_subtable_spec(size_t column_ndx) noexcept; + //@} + + // Column info + size_t get_column_count() const noexcept; + size_t get_public_column_count() const noexcept; + DataType get_public_column_type(size_t column_ndx) const noexcept; + ColumnType get_column_type(size_t column_ndx) const noexcept; + StringData get_column_name(size_t column_ndx) const noexcept; + + /// Returns size_t(-1) if the specified column is not found. + size_t get_column_index(StringData name) const noexcept; + + // Column Attributes + ColumnAttr get_column_attr(size_t column_ndx) const noexcept; + + size_t get_subspec_ndx(size_t column_ndx) const noexcept; + ref_type get_subspec_ref(size_t subspec_ndx) const noexcept; + Spec* get_subspec_by_ndx(size_t subspec_ndx) noexcept; + const Spec* get_subspec_by_ndx(size_t subspec_ndx) const noexcept; + + // Auto Enumerated string columns + void upgrade_string_to_enum(size_t column_ndx, ref_type keys_ref, ArrayParent*& keys_parent, size_t& keys_ndx); + size_t get_enumkeys_ndx(size_t column_ndx) const noexcept; + ref_type get_enumkeys_ref(size_t column_ndx, ArrayParent** keys_parent = nullptr, + size_t* keys_ndx = nullptr) noexcept; + + // Links + size_t get_opposite_link_table_ndx(size_t column_ndx) const noexcept; + void set_opposite_link_table_ndx(size_t column_ndx, size_t table_ndx); + + // Backlinks + bool has_backlinks() const noexcept; + size_t first_backlink_column_index() const noexcept; + size_t backlink_column_count() const noexcept; + void set_backlink_origin_column(size_t backlink_col_ndx, size_t origin_col_ndx); + size_t get_origin_column_ndx(size_t backlink_col_ndx) const noexcept; + size_t find_backlink_column(size_t origin_table_ndx, size_t origin_col_ndx) const noexcept; + + /// Get position in `Table::m_columns` of the specified column. It may be + /// different from the specified logical column index due to the presence of + /// search indexes, since their top refs are stored in Table::m_columns as + /// well. + size_t get_column_ndx_in_parent(size_t column_ndx) const; + + //@{ + /// Compare two table specs for equality. + bool operator==(const Spec&) const noexcept; + bool operator!=(const Spec&) const noexcept; + //@} + + void detach() noexcept; + void destroy() noexcept; + + size_t get_ndx_in_parent() const noexcept; + void set_ndx_in_parent(size_t) noexcept; + +#ifdef REALM_DEBUG + void verify() const; + void to_dot(std::ostream&, StringData title = StringData()) const; +#endif + +private: + // Underlying array structure. + // + // `m_subspecs` contains one entry for each subtable column, one entry for + // each link or link list columns, two entries for each backlink column, and + // zero entries for all other column types. For subtable columns the entry + // is a ref pointing to the subtable spec, for link and link list columns it + // is the group-level table index of the target table, and for backlink + // columns the first entry is the group-level table index of the origin + // table, and the second entry is the index of the origin column in the + // origin table. + Array m_top; + ArrayInteger m_types; // 1st slot in m_top + ArrayString m_names; // 2nd slot in m_top + ArrayInteger m_attr; // 3rd slot in m_top + Array m_subspecs; // 4th slot in m_top (optional) + Array m_enumkeys; // 5th slot in m_top (optional) + struct SubspecPtr { + SubspecPtr(bool is_spec_ptr = false) + : m_is_spec_ptr(is_spec_ptr) + { + } + std::unique_ptr m_spec; + bool m_is_spec_ptr; + }; + using SubspecPtrs = std::vector; + SubspecPtrs m_subspec_ptrs; + bool m_has_strong_link_columns; + + Spec(Allocator&) noexcept; // Unattached + + bool init(ref_type) noexcept; + void init(MemRef) noexcept; + void update_has_strong_link_columns() noexcept; + void reset_subspec_ptrs(); + void adj_subspec_ptrs(); + + // Returns true in case the ref has changed. + bool init_from_parent() noexcept; + + ref_type get_ref() const noexcept; + + /// Called in the context of Group::commit() to ensure that + /// attached table accessors stay valid across a commit. Please + /// note that this works only for non-transactional commits. Table + /// accessors obtained during a transaction are always detached + /// when the transaction ends. + bool update_from_parent(size_t old_baseline) noexcept; + + void set_parent(ArrayParent*, size_t ndx_in_parent) noexcept; + + void set_column_type(size_t column_ndx, ColumnType type); + void set_column_attr(size_t column_ndx, ColumnAttr attr); + + /// Construct an empty spec and return just the reference to the + /// underlying memory. + static MemRef create_empty_spec(Allocator&); + + struct ColumnInfo { + size_t m_column_ref_ndx = 0; ///< Index within Table::m_columns + bool m_has_search_index = false; + }; + + ColumnInfo get_column_info(size_t column_ndx) const noexcept; + + size_t get_subspec_ndx_after(size_t column_ndx, size_t skip_column_ndx) const noexcept; + size_t get_subspec_entries_for_col_type(ColumnType type) const noexcept; + bool has_subspec() const noexcept; + + // Returns false if the spec has no columns, otherwise it returns + // true and sets `type` to the type of the first column. + static bool get_first_column_type_from_ref(ref_type, Allocator&, ColumnType& type) noexcept; + + friend class Replication; + friend class Group; + friend class Table; +}; + +// Implementation: + +inline Allocator& Spec::get_alloc() const noexcept +{ + return m_top.get_alloc(); +} + +inline bool Spec::has_strong_link_columns() noexcept +{ + return m_has_strong_link_columns; +} + +inline ref_type Spec::get_subspec_ref(size_t subspec_ndx) const noexcept +{ + REALM_ASSERT(subspec_ndx < m_subspecs.size()); + + // Note that this addresses subspecs directly, indexing + // by number of sub-table columns + return m_subspecs.get_as_ref(subspec_ndx); +} + +// Uninitialized Spec (call init() to init) +inline Spec::Spec(Allocator& alloc) noexcept + : m_top(alloc) + , m_types(alloc) + , m_names(alloc) + , m_attr(alloc) + , m_subspecs(alloc) + , m_enumkeys(alloc) +{ +} + +inline Spec* Spec::get_subtable_spec(size_t column_ndx) noexcept +{ + REALM_ASSERT(column_ndx < get_column_count()); + REALM_ASSERT(get_column_type(column_ndx) == col_type_Table); + size_t subspec_ndx = get_subspec_ndx(column_ndx); + return get_subspec_by_ndx(subspec_ndx); +} + +inline const Spec* Spec::get_subspec_by_ndx(size_t subspec_ndx) const noexcept +{ + return const_cast(this)->get_subspec_by_ndx(subspec_ndx); +} + +inline bool Spec::init_from_parent() noexcept +{ + ref_type ref = m_top.get_ref_from_parent(); + return init(ref); +} + +inline void Spec::destroy() noexcept +{ + m_top.destroy_deep(); +} + +inline size_t Spec::get_ndx_in_parent() const noexcept +{ + return m_top.get_ndx_in_parent(); +} + +inline void Spec::set_ndx_in_parent(size_t ndx) noexcept +{ + m_top.set_ndx_in_parent(ndx); +} + +inline ref_type Spec::get_ref() const noexcept +{ + return m_top.get_ref(); +} + +inline void Spec::set_parent(ArrayParent* parent, size_t ndx_in_parent) noexcept +{ + m_top.set_parent(parent, ndx_in_parent); +} + +inline void Spec::rename_column(size_t column_ndx, StringData new_name) +{ + REALM_ASSERT(column_ndx < m_types.size()); + m_names.set(column_ndx, new_name); +} + +inline size_t Spec::get_column_count() const noexcept +{ + // This is the total count of columns, including backlinks (not public) + return m_types.size(); +} + +inline size_t Spec::get_public_column_count() const noexcept +{ + // Backlinks are the last columns, and do not have names, so getting + // the number of names gives us the count of user facing columns + return m_names.size(); +} + +inline ColumnType Spec::get_column_type(size_t ndx) const noexcept +{ + REALM_ASSERT(ndx < get_column_count()); + return ColumnType(m_types.get(ndx)); +} + +inline void Spec::set_column_type(size_t column_ndx, ColumnType type) +{ + REALM_ASSERT(column_ndx < get_column_count()); + + // At this point we only support upgrading to string enum + REALM_ASSERT(ColumnType(m_types.get(column_ndx)) == col_type_String); + REALM_ASSERT(type == col_type_StringEnum); + + m_types.set(column_ndx, type); // Throws + + update_has_strong_link_columns(); +} + +inline ColumnAttr Spec::get_column_attr(size_t ndx) const noexcept +{ + REALM_ASSERT(ndx < get_column_count()); + return ColumnAttr(m_attr.get(ndx)); +} + +inline void Spec::set_column_attr(size_t column_ndx, ColumnAttr attr) +{ + REALM_ASSERT(column_ndx < get_column_count()); + + // At this point we only allow one attr at a time + // so setting it will overwrite existing. In the future + // we will allow combinations. + m_attr.set(column_ndx, attr); + + update_has_strong_link_columns(); +} + +inline StringData Spec::get_column_name(size_t ndx) const noexcept +{ + REALM_ASSERT(ndx < get_column_count()); + return m_names.get(ndx); +} + +inline size_t Spec::get_column_index(StringData name) const noexcept +{ + return m_names.find_first(name); +} + +inline bool Spec::get_first_column_type_from_ref(ref_type top_ref, Allocator& alloc, ColumnType& type) noexcept +{ + const char* top_header = alloc.translate(top_ref); + ref_type types_ref = to_ref(Array::get(top_header, 0)); + const char* types_header = alloc.translate(types_ref); + if (Array::get_size_from_header(types_header) == 0) + return false; + type = ColumnType(Array::get(types_header, 0)); + return true; +} + +inline bool Spec::has_backlinks() const noexcept +{ + // backlinks are always last and do not have names. + return m_names.size() < m_types.size(); + + // Fixme: It's bad design that backlinks are stored and recognized like this. Backlink columns + // should be a column type like any other, and we should find another way to hide them away from + // the user. +} + +inline size_t Spec::first_backlink_column_index() const noexcept +{ + return m_names.size(); +} + +inline size_t Spec::backlink_column_count() const noexcept +{ + return m_types.size() - m_names.size(); +} + +// Spec will have a subspec when it contains a column which is one of: +// link, linklist, backlink, or subtable. It is possible for m_top.size() +// to contain an entry for m_subspecs (at index 3) but this reference +// may be empty if the spec contains enumkeys (at index 4) but no subspec types. +inline bool Spec::has_subspec() const noexcept +{ + return (m_top.size() >= 4) && (m_top.get_as_ref(3) != 0); +} + +inline bool Spec::operator!=(const Spec& s) const noexcept +{ + return !(*this == s); +} + +} // namespace realm + +#endif // REALM_SPEC_HPP diff --git a/!main project/Pods/Realm/include/core/realm/string_data.hpp b/!main project/Pods/Realm/include/core/realm/string_data.hpp new file mode 100644 index 0000000..0917fc9 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/string_data.hpp @@ -0,0 +1,410 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_STRING_HPP +#define REALM_STRING_HPP + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace realm { + +/// Selects CityHash64 on 64-bit platforms, and Murmur2 on 32-bit platforms. +/// This is what libc++ does, and it is a good general choice for a +/// non-cryptographic hash function (suitable for std::unordered_map etc.). +size_t murmur2_or_cityhash(const unsigned char* data, size_t len) noexcept; + +uint_least32_t murmur2_32(const unsigned char* data, size_t len) noexcept; +uint_least64_t cityhash_64(const unsigned char* data, size_t len) noexcept; + + +/// A reference to a chunk of character data. +/// +/// An instance of this class can be thought of as a type tag on a region of +/// memory. It does not own the referenced memory, nor does it in any other way +/// attempt to manage the lifetime of it. +/// +/// A null character inside the referenced region is considered a part of the +/// string by Realm. +/// +/// For compatibility with C-style strings, when a string is stored in a Realm +/// database, it is always followed by a terminating null character, regardless +/// of whether the string itself has internal null characters. This means that +/// when a StringData object is extracted from Realm, the referenced region is +/// guaranteed to be followed immediately by an extra null character, but that +/// null character is not inside the referenced region. Therefore, all of the +/// following forms are guaranteed to return a pointer to a null-terminated +/// string: +/// +/// \code{.cpp} +/// +/// group.get_table_name(...).data() +/// table.get_column_name().data() +/// table.get_string(...).data() +/// table.get_mixed(...).get_string().data() +/// +/// \endcode +/// +/// Note that in general, no assumptions can be made about what follows a string +/// that is referenced by a StringData object, or whether anything follows it at +/// all. In particular, the receiver of a StringData object cannot assume that +/// the referenced string is followed by a null character unless there is an +/// externally provided guarantee. +/// +/// This class makes it possible to distinguish between a 'null' reference and a +/// reference to the empty string (see is_null()). +/// +/// \sa BinaryData +/// \sa Mixed +class StringData { +public: + /// Construct a null reference. + StringData() noexcept; + + /// If \a external_data is 'null', \a data_size must be zero. + StringData(const char* external_data, size_t data_size) noexcept; + + template + StringData(const std::basic_string&); + + template + operator std::basic_string() const; + + // StringData does not store data, callers must manage their own strings. + template + StringData(const std::basic_string&&) = delete; + + template + StringData(const util::Optional>&); + + StringData(const null&) noexcept; + + /// Initialize from a zero terminated C style string. Pass null to construct + /// a null reference. + StringData(const char* c_str) noexcept; + + char operator[](size_t i) const noexcept; + + const char* data() const noexcept; + size_t size() const noexcept; + + /// Is this a null reference? + /// + /// An instance of StringData is a null reference when, and only when the + /// stored size is zero (size()) and the stored pointer is the null pointer + /// (data()). + /// + /// In the case of the empty string, the stored size is still zero, but the + /// stored pointer is **not** the null pointer. It could for example point + /// to the empty string literal. Note that the actual value of the pointer + /// is immaterial in this case (as long as it is not zero), because when the + /// size is zero, it is an error to dereference the pointer. + /// + /// Conversion of a StringData object to `bool` yields the logical negation + /// of the result of calling this function. In other words, a StringData + /// object is converted to true if it is not the null reference, otherwise + /// it is converted to false. + bool is_null() const noexcept; + + friend bool operator==(const StringData&, const StringData&) noexcept; + friend bool operator!=(const StringData&, const StringData&) noexcept; + + //@{ + /// Trivial bytewise lexicographical comparison. + friend bool operator<(const StringData&, const StringData&) noexcept; + friend bool operator>(const StringData&, const StringData&) noexcept; + friend bool operator<=(const StringData&, const StringData&) noexcept; + friend bool operator>=(const StringData&, const StringData&) noexcept; + //@} + + bool begins_with(StringData) const noexcept; + bool ends_with(StringData) const noexcept; + bool contains(StringData) const noexcept; + bool contains(StringData d, const std::array &charmap) const noexcept; + + // Wildcard matching ('?' for single char, '*' for zero or more chars) + // case insensitive version in unicode.hpp + bool like(StringData) const noexcept; + + //@{ + /// Undefined behavior if \a n, \a i, or i+n is greater than + /// size(). + StringData prefix(size_t n) const noexcept; + StringData suffix(size_t n) const noexcept; + StringData substr(size_t i, size_t n) const noexcept; + StringData substr(size_t i) const noexcept; + //@} + + template + friend std::basic_ostream& operator<<(std::basic_ostream&, const StringData&); + + explicit operator bool() const noexcept; + + /// If the StringData is NULL, the hash is 0. Otherwise, the function + /// `murmur2_or_cityhash()` is called on the data. + size_t hash() const noexcept; + +private: + const char* m_data; + size_t m_size; + + static bool matchlike(const StringData& text, const StringData& pattern) noexcept; + static bool matchlike_ins(const StringData& text, const StringData& pattern_upper, + const StringData& pattern_lower) noexcept; + + friend bool string_like_ins(StringData, StringData) noexcept; + friend bool string_like_ins(StringData, StringData, StringData) noexcept; +}; + + +// Implementation: + +inline StringData::StringData() noexcept + : m_data(nullptr) + , m_size(0) +{ +} + +inline StringData::StringData(const char* external_data, size_t data_size) noexcept + : m_data(external_data) + , m_size(data_size) +{ + REALM_ASSERT_DEBUG(external_data || data_size == 0); +} + +template +inline StringData::StringData(const std::basic_string& s) + : m_data(s.data()) + , m_size(s.size()) +{ +} + +template +inline StringData::operator std::basic_string() const +{ + return std::basic_string(m_data, m_size); +} + +template +inline StringData::StringData(const util::Optional>& s) + : m_data(s ? s->data() : nullptr) + , m_size(s ? s->size() : 0) +{ +} + +inline StringData::StringData(const null&) noexcept + : m_data(nullptr) + , m_size(0) +{ +} + +inline StringData::StringData(const char* c_str) noexcept + : m_data(c_str) + , m_size(0) +{ + if (c_str) + m_size = std::char_traits::length(c_str); +} + +inline char StringData::operator[](size_t i) const noexcept +{ + return m_data[i]; +} + +inline const char* StringData::data() const noexcept +{ + return m_data; +} + +inline size_t StringData::size() const noexcept +{ + return m_size; +} + +inline bool StringData::is_null() const noexcept +{ + return !m_data; +} + +inline bool operator==(const StringData& a, const StringData& b) noexcept +{ + return a.m_size == b.m_size && a.is_null() == b.is_null() && safe_equal(a.m_data, a.m_data + a.m_size, b.m_data); +} + +inline bool operator!=(const StringData& a, const StringData& b) noexcept +{ + return !(a == b); +} + +inline bool operator<(const StringData& a, const StringData& b) noexcept +{ + if (a.is_null() && !b.is_null()) { + // Null strings are smaller than all other strings, and not + // equal to empty strings. + return true; + } + return std::lexicographical_compare(a.m_data, a.m_data + a.m_size, b.m_data, b.m_data + b.m_size); +} + +inline bool operator>(const StringData& a, const StringData& b) noexcept +{ + return b < a; +} + +inline bool operator<=(const StringData& a, const StringData& b) noexcept +{ + return !(b < a); +} + +inline bool operator>=(const StringData& a, const StringData& b) noexcept +{ + return !(a < b); +} + +inline bool StringData::begins_with(StringData d) const noexcept +{ + if (is_null() && !d.is_null()) + return false; + return d.m_size <= m_size && safe_equal(m_data, m_data + d.m_size, d.m_data); +} + +inline bool StringData::ends_with(StringData d) const noexcept +{ + if (is_null() && !d.is_null()) + return false; + return d.m_size <= m_size && safe_equal(m_data + m_size - d.m_size, m_data + m_size, d.m_data); +} + +inline bool StringData::contains(StringData d) const noexcept +{ + if (is_null() && !d.is_null()) + return false; + + return d.m_size == 0 || std::search(m_data, m_data + m_size, d.m_data, d.m_data + d.m_size) != m_data + m_size; +} + +/// This method takes an array that maps chars to distance that can be moved (and zero for chars not in needle), +/// allowing the method to apply Boyer-Moore for quick substring search +/// The map is calculated in the StringNode class (so it can be reused across searches) +inline bool StringData::contains(StringData d, const std::array &charmap) const noexcept +{ + if (is_null() && !d.is_null()) + return false; + + size_t needle_size = d.size(); + if (needle_size == 0) + return true; + + // Prepare vars to avoid lookups in loop + size_t last_char_pos = d.size()-1; + unsigned char lastChar = d[last_char_pos]; + + // Do Boyer-Moore search + size_t p = last_char_pos; + while (p < m_size) { + unsigned char c = m_data[p]; // Get candidate for last char + + if (c == lastChar) { + StringData candidate = substr(p-needle_size+1, needle_size); + if (candidate == d) + return true; // text found! + } + + // If we don't have a match, see how far we can move char_pos + if (charmap[c] == 0) + p += needle_size; // char was not present in search string + else + p += charmap[c]; + } + + return false; +} + +inline bool StringData::like(StringData d) const noexcept +{ + if (is_null() || d.is_null()) { + return (is_null() && d.is_null()); + } + + return matchlike(*this, d); +} + +inline StringData StringData::prefix(size_t n) const noexcept +{ + return substr(0, n); +} + +inline StringData StringData::suffix(size_t n) const noexcept +{ + return substr(m_size - n); +} + +inline StringData StringData::substr(size_t i, size_t n) const noexcept +{ + return StringData(m_data + i, n); +} + +inline StringData StringData::substr(size_t i) const noexcept +{ + return substr(i, m_size - i); +} + +template +inline std::basic_ostream& operator<<(std::basic_ostream& out, const StringData& d) +{ + for (const char* i = d.m_data; i != d.m_data + d.m_size; ++i) + out << *i; + return out; +} + +inline StringData::operator bool() const noexcept +{ + return !is_null(); +} + +inline size_t StringData::hash() const noexcept +{ + if (is_null()) + return 0; + auto unsigned_data = reinterpret_cast(m_data); + return murmur2_or_cityhash(unsigned_data, m_size); +} + +} // namespace realm + +namespace std { +template <> +struct hash<::realm::StringData> { + inline size_t operator()(const ::realm::StringData& str) const noexcept + { + return str.hash(); + } +}; +} // namespace std + +#endif // REALM_STRING_HPP diff --git a/!main project/Pods/Realm/include/core/realm/sync/changeset.hpp b/!main project/Pods/Realm/include/core/realm/sync/changeset.hpp new file mode 100644 index 0000000..7a79240 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/sync/changeset.hpp @@ -0,0 +1,701 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2017] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ + +#ifndef REALM_SYNC_CHANGESET_HPP +#define REALM_SYNC_CHANGESET_HPP + +#include +#include +#include +#include + +#include + +namespace realm { +namespace sync { + +using InternStrings = util::metered::vector; + +struct BadChangesetError : ExceptionWithBacktrace { + const char* m_message; + BadChangesetError() : BadChangesetError("Bad changeset") {} + BadChangesetError(const char* msg) : m_message(msg) {} + const char* message() const noexcept override + { + return m_message; + } +}; + +struct Changeset { + struct Range; + using timestamp_type = uint_fast64_t; + using file_ident_type = uint_fast64_t; + using version_type = uint_fast64_t; // FIXME: Get from `History`. + using StringBuffer = util::BasicStringBuffer; + + Changeset(); + struct share_buffers_tag {}; + Changeset(const Changeset&, share_buffers_tag); + Changeset(Changeset&&) = default; + Changeset& operator=(Changeset&&) = default; + Changeset(const Changeset&) = delete; + Changeset& operator=(const Changeset&) = delete; + + InternString intern_string(StringData); // Slow! + InternString find_string(StringData) const noexcept; // Slow! + StringData string_data() const noexcept; + + StringBuffer& string_buffer() noexcept; + const StringBuffer& string_buffer() const noexcept; + const InternStrings& interned_strings() const noexcept; + InternStrings& interned_strings() noexcept; + + StringBufferRange get_intern_string(InternString) const noexcept; + util::Optional try_get_intern_string(InternString) const noexcept; + util::Optional try_get_string(StringBufferRange) const noexcept; + StringData get_string(StringBufferRange) const noexcept; + StringData get_string(InternString) const noexcept; + StringBufferRange append_string(StringData); + + /// Mark the changeset as "dirty" (i.e. modified by the merge algorithm). + void set_dirty(bool dirty = true) noexcept; + + /// Whether or not the changeset is "dirty" (i.e. has been modified by the + /// merge algorithm). + bool is_dirty() const noexcept; + + // Interface to imitate std::vector: + template struct IteratorImpl; + using iterator = IteratorImpl; + using const_iterator = IteratorImpl; + using value_type = Instruction; + iterator begin() noexcept; + iterator end() noexcept; + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + bool empty() const noexcept; + + /// Size of the Changeset, not counting tombstones. + /// + /// FIXME: This is an O(n) operation. + size_t size() const noexcept; + + void clear() noexcept; + + //@{ + /// Insert instructions, invalidating all iterators. + iterator insert(const_iterator pos, Instruction); + template + iterator insert(const_iterator pos, InputIt begin, InputIt end); + //@} + + /// Erase an instruction, invalidating all iterators. + iterator erase(const_iterator); + + /// Insert an instruction at the end, invalidating all iterators. + void push_back(const Instruction&); + + //@{ + /// Insert instructions at \a position without invalidating other + /// iterators. + /// + /// Only iterators created before any call to `insert_stable()` may be + /// considered stable across calls to `insert_stable()`. In addition, + /// "iterator stability" has a very specific meaning here: Other copies of + /// \a position in the program will point to the newly inserted elements + /// after calling `insert_stable()`, rather than point to the value at the + /// position prior to insertion. This is different from, say, a tree + /// structure, where iterator stability signifies the property that + /// iterators keep pointing to the same element after insertion before or + /// after that position. + /// + /// For the purpose of supporting `ChangesetIndex`, and the OT merge + /// algorithm, these semantics are acceptable, since prepended instructions + /// can never create new object or table references. + iterator insert_stable(const_iterator position, Instruction); + template + iterator insert_stable(const_iterator position, InputIt begin, InputIt end); + //@} + + /// Erase instruction at \a position without invalidating other iterators. + /// If erasing the object would invalidate other iterators, it is turned + /// into a tombstone instead, and subsequent derefencing of the iterator + /// will return `nullptr`. An iterator pointing to a tombstone remains valid + /// and can be incremented. + /// + /// Only iterators created before any call to `insert_stable()` may be + /// considered stable across calls to `erase_stable()`. If other copies of + /// \a position exist in the program, they will either point to the + /// subsequent element if that element was previously inserted with + /// `insert_stable()`, or otherwise it will be turned into a tombstone. + iterator erase_stable(const_iterator position); + +#if REALM_DEBUG + struct Reflector; + struct Printer; + void verify() const; + void print(std::ostream&) const; + void print() const; // prints to std::err +#endif + + /// The version that this changeset produced. Note: This may not be the + /// version produced by this changeset on the client on which this changeset + /// originated, but may for instance be the version produced on the server + /// after receiving and re-sending this changeset to another client. + /// + /// FIXME: The explanation above is confusing. The truth is that if this + /// changeset was received by a client from the server, then \a version is + /// the version that was produced on the server by this changeset. + /// + /// FIXME: This property, as well as \a last_integrated_remote_version, \a + /// origin_timestamp, and \a origin_file_ident should probably be removed + /// from this class, as they are not a logical part of a changeset, and also + /// are difficult to document without knowing more about what context the + /// changeset object occurs. Also, functions such as + /// InstructionApplier::apply() that a changeset as argument, but do not + /// care about those properties. + version_type version = 0; + + /// On clients, the last integrated server version. On the server, this is + /// the last integrated client version. + /// + /// FIXME: The explanation above is confusing. The truth is that if this + /// changeset was received by a client from the server, then \a + /// last_integrated_remote_version is the last client version that was + /// integrated by the server at the server version referencened by \a + /// version. + version_type last_integrated_remote_version = 0; + + /// Timestamp at origin when the original untransformed changeset was + /// produced. + timestamp_type origin_timestamp = 0; + + /// The identifier of the file in the context of which the original + /// untransformed changeset was produced. + file_ident_type origin_file_ident = 0; + +private: + struct MultiInstruction { + util::metered::vector instructions; + }; + static_assert(sizeof(MultiInstruction) <= Instruction::max_instruction_size, "Instruction::max_instruction_size too low"); + + // In order to achieve iterator semi-stability (just enough to be able to + // run the merge algorithm while maintaining a ChangesetIndex), a Changeset + // is really a list of lists. A Changeset is a vector of + // `InstructionContainer`s, and each `InstructionContainer` represents 0-N + // "real" instructions. + // + // As an optimization, there is a special case for when the + // `InstructionContainer` represents exactly 1 instruction, in which case it + // is represented inside the `InstructionContainer` without any additional + // allocations or indirections. The `InstructionContainer` derived from + // the `Instruction` struct, and co-opts the `type` field such that if the + // (invalid) value of `type` is 0xff, the contents of the `Instruction` are + // instead interpreted as an instance of `MultiInstruction`, which holds + // a vector of `Instruction`s. + // + // The size of the `MultiInstruction` may also be zero, in which case it is + // considered a "tombstone" - always as a result of a call to + // `Changeset::erase_stable()`. The potential existence of these tombstones + // is the reason that the value type of `Changeset::iterator` is + // `Instruction*`, rather than `Instruction&`. + // + // FIXME: It would be better if `Changeset::iterator::value_type` could be + // `util::Optional`, but this is prevented by a bug in + // `util::Optional`. + struct InstructionContainer : Instruction { + InstructionContainer(); + InstructionContainer(const Instruction& instr); + InstructionContainer(InstructionContainer&&) noexcept; + InstructionContainer(const InstructionContainer&); + ~InstructionContainer(); + InstructionContainer& operator=(InstructionContainer&&) noexcept; + InstructionContainer& operator=(const InstructionContainer&); + + bool is_multi() const noexcept; + void convert_to_multi(); + void insert(size_t position, Instruction instr); + void erase(size_t position); + size_t size() const noexcept; + bool is_empty() const noexcept; + + Instruction& at(size_t pos) noexcept; + const Instruction& at(size_t pos) const noexcept; + + MultiInstruction& get_multi() noexcept; + const MultiInstruction& get_multi() const noexcept; + }; + + util::metered::vector m_instructions; + std::shared_ptr m_string_buffer; + std::shared_ptr m_strings; + bool m_is_dirty = false; + + iterator const_iterator_to_iterator(const_iterator); +}; + +/// An iterator type that hides the implementation details of the support for +/// iterator stability. +/// +/// A `Changeset::iterator` is composed of an +/// `std::vector::iterator` and a `size_t` representing +/// the index into the current `InstructionContainer`. If that container is +/// empty, and the position is zero, the iterator is pointing to a tombstone. +template +struct Changeset::IteratorImpl { + using list_type = util::metered::vector; + using inner_iterator_type = std::conditional_t; + + // reference_type is a pointer because we have no way to create a reference + // to a tombstone instruction. Alternatively, it could have been + // `util::Optional`, but that runs into other issues. + using reference_type = std::conditional_t; + + using pointer_type = std::conditional_t; + using difference_type = std::ptrdiff_t; + + IteratorImpl() : m_pos(0) {} + template + IteratorImpl(const IteratorImpl& other, std::enable_if_t* = nullptr) + : m_inner(other.m_inner), m_pos(other.m_pos) {} + IteratorImpl(inner_iterator_type inner, size_t pos = 0) : m_inner(inner), m_pos(pos) {} + + inline IteratorImpl& operator++() + { + ++m_pos; + if (m_pos >= m_inner->size()) { + ++m_inner; + m_pos = 0; + } + return *this; + } + + IteratorImpl operator++(int) + { + auto copy = *this; + ++(*this); + return copy; + } + + IteratorImpl& operator--() + { + if (m_pos == 0) { + --m_inner; + m_pos = m_inner->size(); + if (m_pos != 0) + --m_pos; + } + else { + --m_pos; + } + return *this; + } + + IteratorImpl operator--(int) + { + auto copy = *this; + --(*this); + return copy; + } + + reference_type operator*() const + { + if (m_inner->size()) { + return &m_inner->at(m_pos); + } + // It was a tombstone. + return nullptr; + } + + pointer_type operator->() const + { + if (m_inner->size()) { + return &m_inner->at(m_pos); + } + // It was a tombstone. + return nullptr; + } + + bool operator==(const IteratorImpl& other) const + { + return m_inner == other.m_inner && m_pos == other.m_pos; + } + + bool operator!=(const IteratorImpl& other) const + { + return !(*this == other); + } + + bool operator<(const IteratorImpl& other) const + { + if (m_inner == other.m_inner) + return m_pos < other.m_pos; + return m_inner < other.m_inner; + } + + bool operator<=(const IteratorImpl& other) const + { + if (m_inner == other.m_inner) + return m_pos <= other.m_pos; + return m_inner < other.m_inner; + } + + bool operator>(const IteratorImpl& other) const + { + if (m_inner == other.m_inner) + return m_pos > other.m_pos; + return m_inner > other.m_inner; + } + + bool operator>=(const IteratorImpl& other) const + { + if (m_inner == other.m_inner) + return m_pos >= other.m_pos; + return m_inner > other.m_inner; + } + + inner_iterator_type m_inner; + size_t m_pos; +}; + +struct Changeset::Range { + iterator begin; + iterator end; +}; + +#if REALM_DEBUG +struct Changeset::Reflector { + struct Tracer { + virtual void name(StringData) = 0; + virtual void field(StringData, StringData) = 0; + virtual void field(StringData, ObjectID) = 0; + virtual void field(StringData, int64_t) = 0; + virtual void field(StringData, double) = 0; + virtual void after_each() {} + virtual void before_each() {} + }; + + Reflector(Tracer& tracer, const Changeset& log) : + m_tracer(tracer), m_log(log) + {} + + void visit_all() const; +private: + Tracer& m_tracer; + const Changeset& m_log; + + friend struct Instruction; // permit access for visit() +#define REALM_DEFINE_REFLECTOR_VISITOR(X) void operator()(const Instruction::X&) const; + REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_DEFINE_REFLECTOR_VISITOR) +#undef REALM_DEFINE_REFLECTOR_VISITOR +}; + +struct Changeset::Printer : Changeset::Reflector::Tracer { + explicit Printer(std::ostream& os) : m_out(os) + {} + + // ChangesetReflector::Tracer interface: + void name(StringData) final; + void field(StringData, StringData) final; + void field(StringData, ObjectID) final; + void field(StringData, int64_t) final; + void field(StringData, double) final; + void after_each() final; + +private: + std::ostream& m_out; + bool m_first = true; + void pad_or_ellipsis(StringData, int width) const; + void print_field(StringData name, std::string value); +}; +#endif // REALM_DEBUG + + + +/// Implementation: + +inline Changeset::iterator Changeset::begin() noexcept +{ + return m_instructions.begin(); +} + +inline Changeset::iterator Changeset::end() noexcept +{ + return m_instructions.end(); +} + +inline Changeset::const_iterator Changeset::begin() const noexcept +{ + return m_instructions.begin(); +} + +inline Changeset::const_iterator Changeset::end() const noexcept +{ + return m_instructions.end(); +} + +inline Changeset::const_iterator Changeset::cbegin() const noexcept +{ + return m_instructions.cbegin(); +} + +inline Changeset::const_iterator Changeset::cend() const noexcept +{ + return m_instructions.end(); +} + +inline bool Changeset::empty() const noexcept +{ + return size() == 0; +} + +inline size_t Changeset::size() const noexcept +{ + size_t sum = 0; + for (auto& x: m_instructions) + sum += x.size(); + return sum; +} + +inline void Changeset::clear() noexcept +{ + m_instructions.clear(); +} + +inline util::Optional Changeset::try_get_intern_string(InternString string) const noexcept +{ + if (string.value >= m_strings->size()) + return util::none; + return (*m_strings)[string.value]; +} + +inline StringBufferRange Changeset::get_intern_string(InternString string) const noexcept +{ + auto str = try_get_intern_string(string); + REALM_ASSERT(str); + return *str; +} + +inline InternStrings& Changeset::interned_strings() noexcept +{ + return *m_strings; +} + +inline const InternStrings& Changeset::interned_strings() const noexcept +{ + return *m_strings; +} + +inline auto Changeset::string_buffer() noexcept -> StringBuffer& +{ + return *m_string_buffer; +} + +inline auto Changeset::string_buffer() const noexcept -> const StringBuffer& +{ + return *m_string_buffer; +} + +inline util::Optional Changeset::try_get_string(StringBufferRange range) const noexcept +{ + if (range.offset > m_string_buffer->size()) + return util::none; + if (range.offset + range.size > m_string_buffer->size()) + return util::none; + return StringData{m_string_buffer->data() + range.offset, range.size}; +} + +inline StringData Changeset::get_string(StringBufferRange range) const noexcept +{ + auto string = try_get_string(range); + REALM_ASSERT(string); + return *string; +} + +inline StringData Changeset::get_string(InternString string) const noexcept +{ + return get_string(get_intern_string(string)); +} + +inline StringData Changeset::string_data() const noexcept +{ + return StringData{m_string_buffer->data(), m_string_buffer->size()}; +} + +inline StringBufferRange Changeset::append_string(StringData string) +{ + m_string_buffer->reserve(1024); // we expect more strings + size_t offset = m_string_buffer->size(); + m_string_buffer->append(string.data(), string.size()); + return StringBufferRange{uint32_t(offset), uint32_t(string.size())}; +} + +inline bool Changeset::is_dirty() const noexcept +{ + return m_is_dirty; +} + +inline void Changeset::set_dirty(bool dirty) noexcept +{ + m_is_dirty = dirty; +} + +inline Changeset::iterator Changeset::insert(const_iterator pos, Instruction instr) +{ + Instruction* p = &instr; + return insert(pos, p, p + 1); +} + +template +inline Changeset::iterator Changeset::insert(const_iterator pos, InputIt begin, InputIt end) +{ + if (pos.m_pos == 0) + return m_instructions.insert(pos.m_inner, begin, end); + return insert_stable(pos, begin, end); +} + +inline Changeset::iterator Changeset::erase(const_iterator pos) +{ + if (pos.m_inner->size() <= 1) + return m_instructions.erase(pos.m_inner); + return erase_stable(pos); +} + +inline Changeset::iterator Changeset::insert_stable(const_iterator pos, Instruction instr) +{ + Instruction* p = &instr; + return insert_stable(pos, p, p + 1); +} + +template +inline Changeset::iterator Changeset::insert_stable(const_iterator cpos, InputIt begin, InputIt end) +{ + iterator pos = const_iterator_to_iterator(cpos); + size_t i = 0; + for (auto it = begin; it != end; ++it, ++i) { + pos.m_inner->insert(pos.m_pos + i, *it); + } + return pos; +} + +inline Changeset::iterator Changeset::erase_stable(const_iterator cpos) +{ + auto pos = const_iterator_to_iterator(cpos); + auto begin = m_instructions.begin(); + auto end = m_instructions.end(); + REALM_ASSERT(pos.m_inner >= begin); + REALM_ASSERT(pos.m_inner < end); + pos.m_inner->erase(pos.m_pos); + if (pos.m_pos >= pos.m_inner->size()) { + do { + ++pos.m_inner; + } while (pos.m_inner != end && pos.m_inner->is_empty()); + pos.m_pos = 0; + } + return pos; +} + +inline void Changeset::push_back(const Instruction& instr) +{ + m_instructions.emplace_back(instr); +} + +inline auto Changeset::const_iterator_to_iterator(const_iterator cpos) -> iterator +{ + size_t offset = cpos.m_inner - m_instructions.cbegin(); + return iterator{m_instructions.begin() + offset, cpos.m_pos}; +} + +inline Changeset::InstructionContainer::~InstructionContainer() +{ + if (is_multi()) { + get_multi().~MultiInstruction(); + } + // Instruction subtypes are required to be POD-types (trivially + // destructible), and this is checked by a static_assert in + // instructions.hpp. Therefore, it is safe to do nothing if this is not a + // multi-instruction. +} + +inline bool Changeset::InstructionContainer::is_multi() const noexcept +{ + return type == Type(InstrTypeMultiInstruction); +} + +inline size_t Changeset::InstructionContainer::size() const noexcept +{ + if (is_multi()) + return get_multi().instructions.size(); + return 1; +} + +inline bool Changeset::InstructionContainer::is_empty() const noexcept +{ + if (is_multi()) { + return get_multi().instructions.empty(); + } + return false; +} + +inline Instruction& Changeset::InstructionContainer::at(size_t pos) noexcept +{ + REALM_ASSERT(pos < size()); + if (is_multi()) + return get_multi().instructions[pos]; + return *this; +} + +inline const Instruction& Changeset::InstructionContainer::at(size_t pos) const noexcept +{ + REALM_ASSERT(pos < size()); + if (is_multi()) + return get_multi().instructions[pos]; + return *this; +} + +inline Changeset::MultiInstruction& Changeset::InstructionContainer::get_multi() noexcept +{ + REALM_ASSERT(is_multi()); + return *reinterpret_cast(&m_storage); +} + +inline const Changeset::MultiInstruction& Changeset::InstructionContainer::get_multi() const noexcept +{ + REALM_ASSERT(is_multi()); + return *reinterpret_cast(&m_storage); +} + +} // namespace sync +} // namespace realm + +namespace std { + +template +struct iterator_traits> { + using difference_type = std::ptrdiff_t; + using iterator_category = std::bidirectional_iterator_tag; +}; + +} // namespace std + +#endif // REALM_SYNC_CHANGESET_HPP diff --git a/!main project/Pods/Realm/include/core/realm/sync/changeset_cooker.hpp b/!main project/Pods/Realm/include/core/realm/sync/changeset_cooker.hpp new file mode 100644 index 0000000..3d003be --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/sync/changeset_cooker.hpp @@ -0,0 +1,40 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2015] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ + +#include + +#ifndef REALM_SYNC_CHANGESET_COOKER_HPP +#define REALM_SYNC_CHANGESET_COOKER_HPP + +namespace realm { +namespace sync { + +/// Copy raw changesets unmodified. +class TrivialChangesetCooker: public ClientHistory::ChangesetCooker { +public: + bool cook_changeset(const Group&, const char* changeset, + std::size_t changeset_size, + util::AppendBuffer&) override; +}; + +} // namespace sync +} // namespace realm + +#endif // REALM_SYNC_CHANGESET_COOKER_HPP diff --git a/!main project/Pods/Realm/include/core/realm/sync/changeset_encoder.hpp b/!main project/Pods/Realm/include/core/realm/sync/changeset_encoder.hpp new file mode 100644 index 0000000..5c3618a --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/sync/changeset_encoder.hpp @@ -0,0 +1,122 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2017] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ + +#ifndef REALM_SYNC_CHANGESET_ENCODER_HPP +#define REALM_SYNC_CHANGESET_ENCODER_HPP + +#include +#include +#include + +namespace realm { +namespace sync { + +struct ChangesetEncoder: InstructionHandler { + using Buffer = util::AppendBuffer; + + Buffer release() noexcept; + void reset() noexcept; + const Buffer& buffer() const noexcept; + InternString intern_string(StringData); + + void set_intern_string(uint32_t index, StringBufferRange) override; + // FIXME: This doesn't copy the input, but the drawback is that there can + // only be a single StringBufferRange per instruction. Luckily, no + // instructions exist that require two or more. + StringBufferRange add_string_range(StringData) override; + void operator()(const Instruction&) override; + +#define REALM_DEFINE_INSTRUCTION_HANDLER(X) void operator()(const Instruction::X&); + REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_DEFINE_INSTRUCTION_HANDLER) +#undef REALM_DEFINE_INSTRUCTION_HANDLER + + void encode_single(const Changeset& log); + +protected: + template static void encode(E& encoder, const Instruction&); + + StringData get_string(StringBufferRange) const noexcept; + +private: + template + void append(Instruction::Type t, Args&&...); + void append_string(StringBufferRange); // does not intern the string + void append_bytes(const void*, size_t); + + template void append_int(T); + void append_payload(const Instruction::Payload&); + void append_value(DataType); + void append_value(bool); + void append_value(uint8_t); + void append_value(int64_t); + void append_value(uint32_t); + void append_value(uint64_t); + void append_value(float); + void append_value(double); + void append_value(InternString); + void append_value(sync::ObjectID); + void append_value(Timestamp); + + Buffer m_buffer; + util::metered::map m_intern_strings_rev; + StringData m_string_range; +}; + +template +void encode_changeset(const Changeset&, util::AppendBuffer& out_buffer); + + +// Implementation + +inline auto ChangesetEncoder::buffer() const noexcept -> const Buffer& +{ + return m_buffer; +} + +inline void ChangesetEncoder::operator()(const Instruction& instr) +{ + encode(*this, instr); // Throws +} + +template inline void ChangesetEncoder::encode(E& encoder, const Instruction& instr) +{ + instr.visit(encoder); // Throws +} + +inline StringData ChangesetEncoder::get_string(StringBufferRange range) const noexcept +{ + const char* data = m_string_range.data() + range.offset; + std::size_t size = std::size_t(range.size); + return StringData{data, size}; +} + +template +void encode_changeset(const Changeset& changeset, util::AppendBuffer& out_buffer) +{ + ChangesetEncoder encoder; + encoder.encode_single(changeset); // Throws + auto& buffer = encoder.buffer(); + out_buffer.append(buffer.data(), buffer.size()); // Throws +} + +} // namespace sync +} // namespace realm + +#endif // REALM_SYNC_CHANGESET_ENCODER_HPP diff --git a/!main project/Pods/Realm/include/core/realm/sync/changeset_parser.hpp b/!main project/Pods/Realm/include/core/realm/sync/changeset_parser.hpp new file mode 100644 index 0000000..f331569 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/sync/changeset_parser.hpp @@ -0,0 +1,47 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2017] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ + +#ifndef REALM_SYNC_CHANGESET_PARSER_HPP +#define REALM_SYNC_CHANGESET_PARSER_HPP + +#include +#include + +namespace realm { +namespace sync { + +struct ChangesetParser { + /// Throws BadChangesetError if parsing fails. + /// + /// FIXME: Consider using std::error_code instead of throwing exceptions on + /// parse errors. + void parse(_impl::NoCopyInputStream&, InstructionHandler&); +private: + struct State; +}; + +void parse_changeset(_impl::NoCopyInputStream&, Changeset& out_log); +void parse_changeset(_impl::InputStream&, Changeset& out_log); + + +} // namespace sync +} // namespace realm + +#endif // REALM_SYNC_CHANGESET_PARSER_HPP diff --git a/!main project/Pods/Realm/include/core/realm/sync/client.hpp b/!main project/Pods/Realm/include/core/realm/sync/client.hpp new file mode 100644 index 0000000..9521130 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/sync/client.hpp @@ -0,0 +1,1364 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2012] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ +#ifndef REALM_SYNC_CLIENT_HPP +#define REALM_SYNC_CLIENT_HPP + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace realm { +namespace sync { + + +class Client { +public: + enum class Error; + + enum class ReconnectMode { + /// This is the mode that should always be used in production. In this + /// mode the client uses a scheme for determining a reconnect delay that + /// prevents it from creating too many connection requests in a short + /// amount of time (i.e., a server hammering protection mechanism). + normal, + + /// For testing purposes only. + /// + /// Never reconnect automatically after the connection is closed due to + /// an error. Allow immediate reconnect if the connection was closed + /// voluntarily (e.g., due to sessions being abandoned). + /// + /// In this mode, Client::cancel_reconnect_delay() and + /// Session::cancel_reconnect_delay() can still be used to trigger + /// another reconnection attempt (with no delay) after an error has + /// caused the connection to be closed. + testing + }; + + using port_type = util::network::Endpoint::port_type; + using RoundtripTimeHandler = void(milliseconds_type roundtrip_time); + + static constexpr milliseconds_type default_connect_timeout = 120000; // 2 minutes + static constexpr milliseconds_type default_connection_linger_time = 30000; // 30 seconds + static constexpr milliseconds_type default_ping_keepalive_period = 60000; // 1 minute + static constexpr milliseconds_type default_pong_keepalive_timeout = 120000; // 2 minutes + static constexpr milliseconds_type default_fast_reconnect_limit = 60000; // 1 minute + + struct Config { + Config() {} + + /// An optional custom platform description to be sent to server as part + /// of a user agent description (HTTP `User-Agent` header). + /// + /// If left empty, the platform description will be whatever is returned + /// by util::get_platform_info(). + std::string user_agent_platform_info; + + /// Optional information about the application to be added to the user + /// agent description as sent to the server. The intention is that the + /// application describes itself using the following (rough) syntax: + /// + /// ::= ( )* + /// ::= "/" [
] + /// ::= ()+ + /// ::= ( | "." | "-" | "_")* + ///
::= + /// ::= "(" ( | )* ")" + /// + /// Where `` is a single space character, `` is a decimal + /// digit, `` is any alphanumeric character, and `` is + /// any character other than `(` and `)`. + /// + /// When multiple levels are present, the innermost layer (the one that + /// is closest to this API) should appear first. + /// + /// Example: + /// + /// RealmJS/2.13.0 RealmStudio/2.9.0 + /// + /// Note: The user agent description is not intended for machine + /// interpretation, but should still follow the specified syntax such + /// that it remains easily interpretable by human beings. + std::string user_agent_application_info; + + /// The maximum number of Realm files that will be kept open + /// concurrently by this client. The client keeps a cache of open Realm + /// files for efficiency reasons. + long max_open_files = 256; + + /// An optional logger to be used by the client. If no logger is + /// specified, the client will use an instance of util::StderrLogger + /// with the log level threshold set to util::Logger::Level::info. The + /// client does not require a thread-safe logger, and it guarantees that + /// all logging happens either on behalf of the constructor or on behalf + /// of the invocation of run(). + util::Logger* logger = nullptr; + + /// Use ports 80 and 443 by default instead of 7800 and 7801 + /// respectively. Ideally, these default ports should have been made + /// available via a different URI scheme instead (http/https or ws/wss). + bool enable_default_port_hack = true; + + /// For testing purposes only. + ReconnectMode reconnect_mode = ReconnectMode::normal; + + /// Create a separate connection for each session. For testing purposes + /// only. + /// + /// FIXME: This setting needs to be true for now, due to limitations in + /// the load balancer. + bool one_connection_per_session = true; + + /// Do not access the local file system. Sessions will act as if + /// initiated on behalf of an empty (or nonexisting) local Realm + /// file. Received DOWNLOAD messages will be accepted, but otherwise + /// ignored. No UPLOAD messages will be generated. For testing purposes + /// only. + /// + /// Many operations, such as serialized transactions, are not suppored + /// in this mode. + bool dry_run = false; + + /// The default changeset cooker to be used by new sessions. Can be + /// overridden by Session::Config::changeset_cooker. + /// + /// \sa make_client_history(), TrivialChangesetCooker. + std::shared_ptr changeset_cooker; + + /// The maximum number of milliseconds to allow for a connection to + /// become fully established. This includes the time to resolve the + /// network address, the TCP connect operation, the SSL handshake, and + /// the WebSocket handshake. + milliseconds_type connect_timeout = default_connect_timeout; + + /// The number of milliseconds to keep a connection open after all + /// sessions have been abandoned (or suspended by errors). + /// + /// The purpose of this linger time is to avoid close/reopen cycles + /// during short periods of time where there are no sessions interested + /// in using the connection. + /// + /// If the connection gets closed due to an error before the linger time + /// expires, the connection will be kept closed until there are sessions + /// willing to use it again. + milliseconds_type connection_linger_time = default_connection_linger_time; + + /// The client will send PING messages periodically to allow the server + /// to detect dead connections (heartbeat). This parameter specifies the + /// time, in milliseconds, between these PING messages. When scheduling + /// the next PING message, the client will deduct a small random amount + /// from the specified value to help spread the load on the server from + /// many clients. + milliseconds_type ping_keepalive_period = default_ping_keepalive_period; + + /// Whenever the server receives a PING message, it is supposed to + /// respond with a PONG messsage to allow the client to detect dead + /// connections (heartbeat). This parameter specifies the time, in + /// milliseconds, that the client will wait for the PONG response + /// message before it assumes that the connection is dead, and + /// terminates it. + milliseconds_type pong_keepalive_timeout = default_pong_keepalive_timeout; + + /// The maximum amount of time, in milliseconds, since the loss of a + /// prior connection, for a new connection to be considered a *fast + /// reconnect*. + /// + /// In general, when a client establishes a connection to the server, + /// the uploading process remains suspended until the initial + /// downloading process completes (as if by invocation of + /// Session::async_wait_for_download_completion()). However, to avoid + /// unnecessary latency in change propagation during ongoing + /// application-level activity, if the new connection is established + /// less than a certain amount of time (`fast_reconnect_limit`) since + /// the client was previously connected to the server, then the + /// uploading process will be activated immediately. + /// + /// For now, the purpose of the general delaying of the activation of + /// the uploading process, is to increase the chance of multiple initial + /// transactions on the client-side, to be uploaded to, and processed by + /// the server as a single unit. In the longer run, the intention is + /// that the client should upload transformed (from reciprocal history), + /// rather than original changesets when applicable to reduce the need + /// for changeset to be transformed on both sides. The delaying of the + /// upload process will increase the number of cases where this is + /// possible. + /// + /// FIXME: Currently, the time between connections is not tracked across + /// sessions, so if the application closes its session, and opens a new + /// one immediately afterwards, the activation of the upload process + /// will be delayed unconditionally. + milliseconds_type fast_reconnect_limit = default_fast_reconnect_limit; + + /// Set to true to completely disable delaying of the upload process. In + /// this mode, the upload process will be activated immediately, and the + /// value of `fast_reconnect_limit` is ignored. + /// + /// For testing purposes only. + bool disable_upload_activation_delay = false; + + /// If `disable_upload_compaction` is true, every changeset will be + /// compacted before it is uploaded to the server. Compaction will + /// reduce the size of a changeset if the same field is set multiple + /// times or if newly created objects are deleted within the same + /// transaction. Log compaction increeses CPU usage and memory + /// consumption. + bool disable_upload_compaction = false; + + /// Set the `TCP_NODELAY` option on all TCP/IP sockets. This disables + /// the Nagle algorithm. Disabling it, can in some cases be used to + /// decrease latencies, but possibly at the expense of scalability. Be + /// sure to research the subject before you enable this option. + bool tcp_no_delay = false; + + /// The specified function will be called whenever a PONG message is + /// received on any connection. The round-trip time in milliseconds will + /// be pased to the function. The specified function will always be + /// called by the client's event loop thread, i.e., the thread that + /// calls `Client::run()`. This feature is mainly for testing purposes. + std::function roundtrip_time_handler; + + /// Disable sync to disk (fsync(), msync()) for all realm files managed + /// by this client. + /// + /// Testing/debugging feature. Should never be enabled in production. + bool disable_sync_to_disk = false; + }; + + /// \throw util::EventLoop::Implementation::NotAvailable if no event loop + /// implementation was specified, and + /// util::EventLoop::Implementation::get_default() throws it. + Client(Config = {}); + Client(Client&&) noexcept; + ~Client() noexcept; + + /// Run the internal event-loop of the client. At most one thread may + /// execute run() at any given time. The call will not return until somebody + /// calls stop(). + void run(); + + /// See run(). + /// + /// Thread-safe. + void stop() noexcept; + + /// \brief Cancel current or next reconnect delay for all servers. + /// + /// This corresponds to calling Session::cancel_reconnect_delay() on all + /// bound sessions, but will also cancel reconnect delays applying to + /// servers for which there are currently no bound sessions. + /// + /// Thread-safe. + void cancel_reconnect_delay(); + + /// \brief Wait for session termination to complete. + /// + /// Wait for termination of all sessions whose termination was initiated + /// prior this call (the completion condition), or until the client's event + /// loop thread exits from Client::run(), whichever happens + /// first. Termination of a session can be initiated implicitly (e.g., via + /// destruction of the session object), or explicitly by Session::detach(). + /// + /// Note: After session termination (when this function returns true) no + /// session specific callback function can be called or continue to execute, + /// and the client is guaranteed to no longer have a Realm file open on + /// behalf of the terminated session. + /// + /// CAUTION: If run() returns while a wait operation is in progress, this + /// waiting function will return immediately, even if the completion + /// condition is not yet satisfied. The completion condition is guaranteed + /// to be satisfied only when these functions return true. If it returns + /// false, session specific callback functions may still be executing or get + /// called, and the associated Realm files may still not have been closed. + /// + /// If a new wait operation is initiated while another wait operation is in + /// progress by another thread, the waiting period of fist operation may, or + /// may not get extended. The application must not assume either. + /// + /// Note: Session termination does not imply that the client has received an + /// UNBOUND message from the server (see the protocol specification). This + /// may happen later. + /// + /// \return True only if the completion condition was satisfied. False if + /// the client's event loop thread exited from Client::run() in which case + /// the completion condition may, or may not have been satisfied. + /// + /// Note: These functions are fully thread-safe. That is, they may be called + /// by any thread, and by multiple threads concurrently. + bool wait_for_session_terminations_or_client_stopped(); + + /// Returns false if the specified URL is invalid. + bool decompose_server_url(const std::string& url, ProtocolEnvelope& protocol, + std::string& address, port_type& port, std::string& path) const; + +private: + class Impl; + std::unique_ptr m_impl; + friend class Session; +}; + + +class BadServerUrl; // Exception + + +/// \brief Client-side representation of a Realm file synchronization session. +/// +/// A synchronization session deals with precisely one local Realm file. To +/// synchronize multiple local Realm files, you need multiple sessions. +/// +/// A session object is always associated with a particular client object (\ref +/// Client). The application must ensure that the destruction of the associated +/// client object never happens before the destruction of the session +/// object. The consequences of a violation are unspecified. +/// +/// A session object is always associated with a particular local Realm file, +/// however, a session object does not represent a session until it is bound to +/// a server side Realm, i.e., until bind() is called. From the point of view of +/// the thread that calls bind(), the session starts precisely when the +/// execution of bind() starts, i.e., before bind() returns. +/// +/// At most one session is allowed to exist for a particular local Realm file +/// (file system inode) at any point in time. Multiple session objects may +/// coexists for a single file, as long as bind() has been called on at most one +/// of them. Additionally, two bound session objects for the same file are +/// allowed to exist at different times, if they have no overlap in time (in +/// their bound state), as long as they are associated with the same client +/// object, or with two different client objects that do not overlap in +/// time. This means, in particular, that it is an error to create two bound +/// session objects for the same local Realm file, it they are associated with +/// two different client objects that overlap in time, even if the session +/// objects do not overlap in time (in their bound state). It is the +/// responsibility of the application to ensure that these rules are adhered +/// to. The consequences of a violation are unspecified. +/// +/// Thread-safety: It is safe for multiple threads to construct, use (with some +/// exceptions), and destroy session objects concurrently, regardless of whether +/// those session objects are associated with the same, or with different Client +/// objects. Please note that some of the public member functions are fully +/// thread-safe, while others are not. +/// +/// Callback semantics: All session specific callback functions will be executed +/// by the event loop thread, i.e., the thread that calls Client::run(). No +/// callback function will be called before Session::bind() is called. Callback +/// functions that are specified prior to calling bind() (e.g., any passed to +/// set_progress_handler()) may start to execute before bind() returns, as long +/// as some thread is executing Client::run(). Likewise, completion handlers, +/// such as those passed to async_wait_for_sync_completion() may start to +/// execute before the submitting function returns. All session specific +/// callback functions (including completion handlers) are guaranteed to no +/// longer be executing when session termination completes, and they are +/// guaranteed to not be called after session termination completes. Termination +/// is an event that completes asynchronously with respect to the application, +/// but is initiated by calling detach(), or implicitely by destroying a session +/// object. After having initiated one or more session terminations, the +/// application can wait for those terminations to complete by calling +/// Client::wait_for_session_terminations_or_client_stopped(). Since callback +/// functinos are always executed by the event loop thread, they are also +/// guaranteed to not be executing after Client::run() has returned. +class Session { +public: + using port_type = util::network::Endpoint::port_type; + using SyncTransactCallback = void(VersionID old_version, VersionID new_version); + using ProgressHandler = void(std::uint_fast64_t downloaded_bytes, + std::uint_fast64_t downloadable_bytes, + std::uint_fast64_t uploaded_bytes, + std::uint_fast64_t uploadable_bytes, + std::uint_fast64_t progress_version, + std::uint_fast64_t snapshot_version); + using WaitOperCompletionHandler = std::function; + using SerialTransactChangeset = util::Buffer; + using SerialTransactInitiationHandler = std::function; + using SerialTransactCompletionHandler = std::function; + using SSLVerifyCallback = bool(const std::string& server_address, + port_type server_port, + const char* pem_data, + size_t pem_size, + int preverify_ok, + int depth); + + struct Config { + Config() {} + + /// server_address is the fully qualified host name, or IP address of + /// the server. + std::string server_address = "localhost"; + + /// server_port is the port at which the server listens. If server_port + /// is zero, the default port for the specified protocol is used. See + /// ProtocolEnvelope for information on default ports. + port_type server_port = 0; + + /// server_path is the virtual path by which the server identifies the + /// Realm. This path must always be an absolute path, and must therefore + /// always contain a leading slash (`/`). Further more, each segment of the + /// virtual path must consist of one or more characters that are either + /// alpha-numeric or in (`_`, `-`, `.`), and each segment is not allowed to + /// equal `.` or `..`, and must not end with `.realm`, `.realm.lock`, or + /// `.realm.management`. These rules are necessary because the server + /// currently reserves the right to use the specified path as part of the + /// file system path of a Realm file. It is expected that these rules will + /// be significantly relaxed in the future by completely decoupling the + /// virtual paths from actual file system paths. + std::string server_path = "/"; + + /// The protocol used for communicating with the server. See + /// ProtocolEnvelope. + ProtocolEnvelope protocol_envelope = ProtocolEnvelope::realm; + + /// url_prefix is a prefix that is prepended to the server_path + /// in the HTTP GET request that initiates a sync connection. The value + /// specified here must match with the server's expectation. Changing + /// the value of url_prefix should be matched with a corresponding + /// change of the server side proxy. + std::string url_prefix = "/realm-sync"; + + /// authorization_header_name is the name of the HTTP header containing + /// the Realm access token. The value of the HTTP header is + /// "Realm-Access-Token version=1 token=....". + /// authorization_header_name does not participate in session + /// multiplexing partitioning. + std::string authorization_header_name = "Authorization"; + + /// custom_http_headers is a map of custom HTTP headers. The keys of the map + /// are HTTP header names, and the values are the corresponding HTTP + /// header values. + /// If "Authorization" is used as a custom header name, + /// authorization_header_name must be set to anther value. + std::map custom_http_headers; + + /// Sessions can be multiplexed over the same TCP/SSL connection. + /// Sessions might share connection if they have identical server_address, + /// server_port, and protocol. multiplex_ident is a parameter that allows + /// finer control over session multiplexing. If two sessions have distinct + /// multiplex_ident, they will never share connection. The typical use of + /// multilex_ident is to give sessions with incompatible SSL requirements + /// distinct multiplex_idents. + /// multiplex_ident can be any string and the value has no meaning except + /// for partitioning the sessions. + std::string multiplex_ident; + + /// Controls whether the server certificate is verified for SSL + /// connections. It should generally be true in production. + bool verify_servers_ssl_certificate = true; + + /// ssl_trust_certificate_path is the path of a trust/anchor + /// certificate used by the client to verify the server certificate. + /// ssl_trust_certificate_path is only used if the protocol is ssl and + /// verify_servers_ssl_certificate is true. + /// + /// A server certificate is verified by first checking that the + /// certificate has a valid signature chain back to a trust/anchor + /// certificate, and secondly checking that the server_address matches + /// a host name contained in the certificate. The host name of the + /// certificate is stored in either Common Name or the Alternative + /// Subject Name (DNS section). + /// + /// If ssl_trust_certificate_path is None (default), ssl_verify_callback + /// (see below) is used if set, and the default device trust/anchor + /// store is used otherwise. + util::Optional ssl_trust_certificate_path; + + /// If Client::Config::ssl_verify_callback is set, that function is called + /// to verify the certificate, unless verify_servers_ssl_certificate is + /// false. + + /// ssl_verify_callback is used to implement custom SSL certificate + /// verification. it is only used if the protocol is SSL, + /// verify_servers_ssl_certificate is true and ssl_trust_certificate_path + /// is None. + /// + /// The signature of ssl_verify_callback is + /// + /// bool(const std::string& server_address, + /// port_type server_port, + /// const char* pem_data, + /// size_t pem_size, + /// int preverify_ok, + /// int depth); + /// + /// server address and server_port is the address and port of the server + /// that a SSL connection is being established to. They are identical to + /// the server_address and server_port set in this config file and are + /// passed for convenience. + /// pem_data is the certificate of length pem_size in + /// the PEM format. preverify_ok is OpenSSL's preverification of the + /// certificate. preverify_ok is either 0, or 1. If preverify_ok is 1, + /// OpenSSL has accepted the certificate and it will generally be safe + /// to trust that certificate. depth represents the position of the + /// certificate in the certificate chain sent by the server. depth = 0 + /// represents the actual server certificate that should contain the + /// host name(server address) of the server. The highest depth is the + /// root certificate. + /// The callback function will receive the certificates starting from + /// the root certificate and moving down the chain until it reaches the + /// server's own certificate with a host name. The depth of the last + /// certificate is 0. The depth of the first certificate is chain + /// length - 1. + /// + /// The return value of the callback function decides whether the + /// client accepts the certificate. If the return value is false, the + /// processing of the certificate chain is interrupted and the SSL + /// connection is rejected. If the return value is true, the verification + /// process continues. If the callback function returns true for all + /// presented certificates including the depth == 0 certificate, the + /// SSL connection is accepted. + /// + /// A recommended way of using the callback function is to return true + /// if preverify_ok = 1 and depth > 0, + /// always check the host name if depth = 0, + /// and use an independent verification step if preverify_ok = 0. + /// + /// Another possible way of using the callback is to collect all the + /// certificates until depth = 0, and present the entire chain for + /// independent verification. + std::function ssl_verify_callback; + + /// signed_user_token is a cryptographically signed token describing the + /// identity and access rights of the current user. + std::string signed_user_token; + + /// If not null, overrides whatever is specified by + /// Client::Config::changeset_cooker. + /// + /// The shared ownership over the cooker will be relinquished shortly + /// after the destruction of the session object as long as the event + /// loop of the client is being executed (Client::run()). + /// + /// CAUTION: ChangesetCooker::cook_changeset() of the specified cooker + /// may get called before the call to bind() returns, and it may get + /// called (or continue to execute) after the session object is + /// destroyed. Please see "Callback semantics" section under Client for + /// more on this. + /// + /// \sa make_client_history(), TrivialChangesetCooker. + std::shared_ptr changeset_cooker; + + /// The encryption key the SharedGroup will be opened with. + util::Optional> encryption_key; + + /// ClientReset is used for both async open and client reset. If + /// client_reset is not util::none, the sync client will perform + /// async open for this session if the local Realm does not exist, and + /// client reset if the local Realm exists. If client_reset is + /// util::none, an ordinary sync session will take place. + /// + /// A session will perform async open by downloading a state Realm, and + /// some metadata, from the server, patching up the metadata part of + /// the Realm and finally move the downloaded Realm into the path of + /// the local Realm. After completion of async open, the application + /// can open and use the Realm. + /// + /// A session will perform client reset by downloading a state Realm, and + /// some metadata, from the server. After download, the state Realm will + /// be integrated into the local Realm in a write transaction. The + /// application is free to use the local realm during the entire client + /// reset. Like a DOWNLOAD message, the application will not be able + /// to perform a write transaction at the same time as the sync client + /// performs its own write transaction. Client reset is not more + /// disturbing for the application than any DOWNLOAD message. The + /// application can listen to change notifications from the client + /// reset exactly as in a DOWNLOAD message. + /// + /// The client reset will recover non-uploaded changes in the local + /// Realm if and only if 'recover_local_changes' is true. In case, + /// 'recover_local_changes' is false, the local Realm state will hence + /// be set to the server's state (server wins). + /// + /// Async open and client reset require a private directory for + /// metadata. This directory must be specified in the option + /// 'metadata_dir'. The metadata_dir must not be touched during async + /// open or client reset. The metadata_dir can safely be removed at + /// times where async open or client reset do not take place. The sync + /// client attempts to clean up metadata_dir. The metadata_dir can be + /// reused across app restarts to resume an interrupted download. It is + /// recommended to leave the metadata_dir unchanged except when it is + /// known that async open or client reset is done. + /// + /// The recommended usage of async open is to use it for the initial + /// bootstrap if Realm usage is not needed until after the server state + /// has been downloaded. + /// + /// The recommended usage of client reset is after a previous session + /// encountered an error that implies the need for a client reset. It + /// is not recommended to persist the need for a client reset. The + /// application should just attempt to synchronize in the usual fashion + /// and only after hitting an error, start a new session with a client + /// reset. In other words, if the application crashes during a client reset, + /// the application should attempt to perform ordinary synchronization + /// after restart and switch to client reset if needed. + /// + /// Error codes that imply the need for a client reset are the session + /// level error codes: + /// + /// bad_client_file_ident = 208, // Bad client file identifier (IDENT) + /// bad_server_version = 209, // Bad server version (IDENT, UPLOAD) + /// bad_client_version = 210, // Bad client version (IDENT, UPLOAD) + /// diverging_histories = 211, // Diverging histories (IDENT) + /// + /// However, other errors such as bad changeset (UPLOAD) could also be resolved + /// with a client reset. Client reset can even be used without any prior error + /// if so desired. + /// + /// After completion of async open and client reset, the sync client + /// will continue synchronizing with the server in the usual fashion. + /// + /// The progress of async open and client reset can be tracked with the + /// standard progress handler. + /// + /// Async open and client reset are done when the progress handler + /// arguments satisfy "progress_version > 0". However, if the + /// application wants to ensure that it has all data present on the + /// server, it should wait for download completion using either + /// void async_wait_for_download_completion(WaitOperCompletionHandler) + /// or + /// bool wait_for_download_complete_or_client_stopped(). + /// + /// The option 'require_recent_state_realm' is used for async open to + /// request a recent state Realm. A recent state Realm is never empty + /// (unless there is no data), and is recent in the sense that it was + /// produced by the current incarnation of the server. Recent does not + /// mean the absolutely newest possible state Realm, since that might + /// lead to too excessive work on the server. Setting + /// 'require_recent_state_realm' to true might lead to more work + /// performed by the server but it ensures that more data is downloaded + /// using async open instead of ordinary synchronization. It is + /// recommended to set 'require_recent_state_realm' to true. Client + /// reset always downloads a recent state Realm. + struct ClientReset { + std::string metadata_dir; + bool recover_local_changes = true; + bool require_recent_state_realm = true; + }; + util::Optional client_reset_config; + + struct ProxyConfig { + enum class Type { HTTP, HTTPS } type; + std::string address; + port_type port; + }; + util::Optional proxy_config; + + /// Set to true to disable the upload process for this session. This + /// includes the sending of empty UPLOAD messages. + /// + /// This feature exists exclusively for testing purposes at this time. + bool disable_upload = false; + + /// Set to true to disable sending of empty UPLOAD messages for this + /// session. + /// + /// This feature exists exclusively for testing purposes at this time. + bool disable_empty_upload = false; + + /// Set to true to cause the integration of the first received changeset + /// (in a DOWNLOAD message) to fail. + /// + /// This feature exists exclusively for testing purposes at this time. + bool simulate_integration_error = false; + }; + + /// \brief Start a new session for the specified client-side Realm. + /// + /// Note that the session is not fully activated until you call bind(). + /// Also note that if you call set_sync_transact_callback(), it must be + /// done before calling bind(). + /// + /// \param realm_path The file-system path of a local client-side Realm + /// file. + Session(Client&, std::string realm_path, Config = {}); + + /// This leaves the right-hand side session object detached. See "Thread + /// safety" section under detach(). + Session(Session&&) noexcept; + + /// Create a detached session object (see detach()). + Session() noexcept; + + /// Implies detachment. See "Thread safety" section under detach(). + ~Session() noexcept; + + /// Detach the object on the left-hand side, then "steal" the session from + /// the object on the right-hand side, if there is one. This leaves the + /// object on the right-hand side detached. See "Thread safety" section + /// under detach(). + Session& operator=(Session&&) noexcept; + + /// Detach this sesion object from the client object (Client). If the + /// session object is already detached, this function has no effect + /// (idempotency). + /// + /// Detachment initiates session termination, which is an event that takes + /// place shortly therafter in the context of the client's event loop + /// thread. + /// + /// A detached session object may be destroyed, move-assigned to, and moved + /// from. Apart from that, it is an error to call any function other than + /// detach() on a detached session object. + /// + /// Thread safety: Detachment is not a thread-safe operation. This means + /// that detach() may not be executed by two threads concurrently, and may + /// not execute concurrently with object destruction. Additionally, + /// detachment must not execute concurrently with a moving operation + /// involving the session object on the left or right-hand side. See move + /// constructor and assigment operator. + void detach() noexcept; + + /// \brief Set a function to be called when the local Realm has changed due + /// to integration of a downloaded changeset. + /// + /// Specify the callback function that will be called when one or more + /// transactions are performed to integrate downloaded changesets into the + /// client-side Realm, that is associated with this session. + /// + /// The callback function will always be called by the thread that executes + /// the event loop (Client::run()), but not until bind() is called. If the + /// callback function throws an exception, that exception will "travel" out + /// through Client::run(). + /// + /// Note: Any call to this function must have returned before bind() is + /// called. If this function is called multiple times, each call overrides + /// the previous setting. + /// + /// Note: This function is **not thread-safe**. That is, it is an error if + /// it is called while another thread is executing any member function on + /// the same Session object. + /// + /// CAUTION: The specified callback function may get called before the call + /// to bind() returns, and it may get called (or continue to execute) after + /// the session object is destroyed. Please see "Callback semantics" section + /// under Session for more on this. + void set_sync_transact_callback(std::function); + + /// \brief Set a handler to monitor the state of download and upload + /// progress. + /// + /// The handler must have signature + /// + /// void(uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes, + /// uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes, + /// uint_fast64_t progress_version); + /// + /// downloaded_bytes is the size in bytes of all downloaded changesets. + /// downloadable_bytes is equal to downloaded_bytes plus an estimate of + /// the size of the remaining server history. + /// + /// uploaded_bytes is the size in bytes of all locally produced changesets + /// that have been received and acknowledged by the server. + /// uploadable_bytes is the size in bytes of all locally produced changesets. + /// + /// Due to the nature of the merge rules, it is possible that the size of an + /// uploaded changeset uploaded from one client is not equal to the size of + /// the changesets that other clients will download. + /// + /// Typical uses of this function: + /// + /// Upload completion can be checked by + /// + /// bool upload_complete = (uploaded_bytes == uploadable_bytes); + /// + /// Download completion could be checked by + /// + /// bool download_complete = (downloaded_bytes == downloadable_bytes); + /// + /// However, download completion might never be reached because the server + /// can receive new changesets from other clients. downloadable_bytes can + /// decrease for two reasons: server side compaction and changesets of + /// local origin. Code using downloadable_bytes must not assume that it + /// is increasing. + /// + /// Upload progress can be calculated by caching an initial value of + /// uploaded_bytes from the last, or next, callback. Then + /// + /// double upload_progress = + /// (uploaded_bytes - initial_uploaded_bytes) + /// ------------------------------------------- + /// (uploadable_bytes - initial_uploaded_bytes) + /// + /// Download progress can be calculates similarly: + /// + /// double download_progress = + /// (downloaded_bytes - initial_downloaded_bytes) + /// ----------------------------------------------- + /// (downloadable_bytes - initial_downloaded_bytes) + /// + /// progress_version is 0 at the start of a session. When at least one + /// DOWNLOAD message has been received from the server, progress_version is + /// positive. progress_version can be used to ensure that the reported + /// progress contains information obtained from the server in the current + /// session. The server will send a message as soon as possible, and the + /// progress handler will eventually be called with a positive progress_version + /// unless the session is interrupted before a message from the server has + /// been received. + /// + /// The handler is called on the event loop thread.The handler after bind(), + /// after each DOWNLOAD message, and after each local transaction + /// (nonsync_transact_notify). + /// + /// set_progress_handler() is not thread safe and it must be called before + /// bind() is called. Subsequent calls to set_progress_handler() overwrite + /// the previous calls. Typically, this function is called once per session. + /// + /// CAUTION: The specified callback function may get called before the call + /// to bind() returns, and it may get called (or continue to execute) after + /// the session object is destroyed. Please see "Callback semantics" section + /// under Session for more on this. + void set_progress_handler(std::function); + + enum class ConnectionState { disconnected, connecting, connected }; + + /// \brief Information about an error causing a session to be temporarily + /// disconnected from the server. + /// + /// In general, the connection will be automatically reestablished + /// later. Whether this happens quickly, generally depends on \ref + /// is_fatal. If \ref is_fatal is true, it means that the error is deemed to + /// be of a kind that is likely to persist, and cause all future reconnect + /// attempts to fail. In that case, if another attempt is made at + /// reconnecting, the delay will be substantial (at least an hour). + /// + /// \ref error_code specifies the error that caused the connection to be + /// closed. For the list of errors reported by the server, see \ref + /// ProtocolError (or `protocol.md`). For the list of errors corresponding + /// to protocol violations that are detected by the client, see + /// Client::Error. The error may also be a system level error, or an error + /// from one of the potential intermediate protocol layers (SSL or + /// WebSocket). + /// + /// \ref detailed_message is the most detailed message available to describe + /// the error. It is generally equal to `error_code.message()`, but may also + /// be a more specific message (one that provides extra context). The + /// purpose of this message is mostly to aid in debugging. For non-debugging + /// purposes, `error_code.message()` should generally be considered + /// sufficient. + /// + /// \sa set_connection_state_change_listener(). + struct ErrorInfo { + std::error_code error_code; + bool is_fatal; + const std::string& detailed_message; + }; + + using ConnectionStateChangeListener = void(ConnectionState, const ErrorInfo*); + + /// \brief Install a connection state change listener. + /// + /// Sets a function to be called whenever the state of the underlying + /// network connection changes between "disconnected", "connecting", and + /// "connected". The initial state is always "disconnected". The next state + /// after "disconnected" is always "connecting". The next state after + /// "connecting" is either "connected" or "disconnected". The next state + /// after "connected" is always "disconnected". A switch to the + /// "disconnected" state only happens when an error occurs. + /// + /// Whenever the installed function is called, an ErrorInfo object is passed + /// when, and only when the passed state is ConnectionState::disconnected. + /// + /// When multiple sessions share a single connection, the state changes will + /// be reported for each session in turn. + /// + /// The callback function will always be called by the thread that executes + /// the event loop (Client::run()), but not until bind() is called. If the + /// callback function throws an exception, that exception will "travel" out + /// through Client::run(). + /// + /// Note: Any call to this function must have returned before bind() is + /// called. If this function is called multiple times, each call overrides + /// the previous setting. + /// + /// Note: This function is **not thread-safe**. That is, it is an error if + /// it is called while another thread is executing any member function on + /// the same Session object. + /// + /// CAUTION: The specified callback function may get called before the call + /// to bind() returns, and it may get called (or continue to execute) after + /// the session object is destroyed. Please see "Callback semantics" section + /// under Session for more on this. + void set_connection_state_change_listener(std::function); + + //@{ + /// Deprecated! Use set_connection_state_change_listener() instead. + using ErrorHandler = void(std::error_code, bool is_fatal, const std::string& detailed_message); + void set_error_handler(std::function); + //@} + + /// @{ \brief Bind this session to the specified server side Realm. + /// + /// No communication takes place on behalf of this session before the + /// session is bound, but as soon as the session becomes bound, the server + /// will start to push changes to the client, and vice versa. + /// + /// If a callback function was set using set_sync_transact_callback(), then + /// that callback function will start to be called as changesets are + /// downloaded and integrated locally. It is important to understand that + /// callback functions are executed by the event loop thread (Client::run()) + /// and the callback function may therefore be called before bind() returns. + /// + /// Note: It is an error if this function is called more than once per + /// Session object. + /// + /// Note: This function is **not thread-safe**. That is, it is an error if + /// it is called while another thread is executing any member function on + /// the same Session object. + /// + /// bind() binds this session to the specified server side Realm using the + /// parameters specified in the Session::Config object. + /// + /// The two other forms of bind() are convenience functions. + /// void bind(std::string server_address, std::string server_path, + /// std::string signed_user_token, port_type server_port = 0, + /// ProtocolEnvelope protocol = ProtocolEnvelope::realm); + /// replaces the corresponding parameters from the Session::Config object + /// before the session is bound. + /// void bind(std::string server_url, std::string signed_user_token) parses + /// the \param server_url and replaces the parameters in the Session::Config object + /// before the session is bound. + /// + /// \param server_url For example "realm://sync.realm.io/test". See + /// server_address, server_path, and server_port in Session::Config for + /// information about the individual components of the URL. See + /// ProtocolEnvelope for the list of available URL schemes and the + /// associated default ports. + /// + /// \throw BadServerUrl if the specified server URL is malformed. + void bind(); + void bind(std::string server_url, std::string signed_user_token); + void bind(std::string server_address, std::string server_path, + std::string signed_user_token, port_type server_port = 0, + ProtocolEnvelope protocol = ProtocolEnvelope::realm); + /// @} + + /// \brief Refresh the access token associated with this session. + /// + /// This causes the REFRESH protocol message to be sent to the server. See + /// ProtocolEnvelope. It is an error to pass a token with a different user + /// identity than the token used to initiate the session. + /// + /// In an on-going session the application may expect the access token to + /// expire at a certain time and schedule acquisition of a fresh access + /// token (using a refresh token or by other means) in due time to provide a + /// better user experience, and seamless connectivity to the server. + /// + /// If the application does not proactively refresh an expiring token, the + /// session will eventually be disconnected. The application can detect this + /// by monitoring the connection state + /// (set_connection_state_change_listener()), and check whether the error + /// code is `ProtocolError::token_expired`. Such a session can then be + /// revived by calling refresh() with a newly acquired access token. + /// + /// Due to protocol techicalities, a race condition exists that can cause a + /// session to become, and remain disconnected after a new access token has + /// been passed to refresh(). The application can work around this race + /// condition by detecting the `ProtocolError::token_expired` error, and + /// always initiate a token renewal in this case. + /// + /// It is an error to call this function before calling `Client::bind()`. + /// + /// Note: This function is thread-safe. + /// + /// \param signed_user_token A cryptographically signed token describing the + /// identity and access rights of the current user. See ProtocolEnvelope. + void refresh(std::string signed_user_token); + + /// \brief Inform the synchronization agent about changes of local origin. + /// + /// This function must be called by the application after a transaction + /// performed on its behalf, that is, after a transaction that is not + /// performed to integrate a changeset that was downloaded from the server. + /// + /// It is an error to call this function before bind() has been called, and + /// has returned. + /// + /// Note: This function is fully thread-safe. That is, it may be called by + /// any thread, and by multiple threads concurrently. + void nonsync_transact_notify(version_type new_version); + + /// @{ \brief Wait for upload, download, or upload+download completion. + /// + /// async_wait_for_upload_completion() initiates an asynchronous wait for + /// upload to complete, async_wait_for_download_completion() initiates an + /// asynchronous wait for download to complete, and + /// async_wait_for_sync_completion() initiates an asynchronous wait for + /// upload and download to complete. + /// + /// Upload is considered complete when all non-empty changesets of local + /// origin have been uploaded to the server, and the server has acknowledged + /// reception of them. Changesets of local origin introduced after the + /// initiation of the session (after bind() is called) will generally not be + /// considered for upload unless they are announced to this client through + /// nonsync_transact_notify() prior to the initiation of the wait operation, + /// i.e., prior to the invocation of async_wait_for_upload_completion() or + /// async_wait_for_sync_completion(). Unannounced changesets may get picked + /// up, but there is no guarantee that they will be, however, if a certain + /// changeset is announced, then all previous changesets are implicitly + /// announced. Also all preexisting changesets are implicitly announced + /// when the session is initiated. + /// + /// Download is considered complete when all non-empty changesets of remote + /// origin have been downloaded from the server, and integrated into the + /// local Realm state. To know what is currently outstanding on the server, + /// the client always sends a special "marker" message to the server, and + /// waits until it has downloaded all outstanding changesets that were + /// present on the server at the time when the server received that marker + /// message. Each call to async_wait_for_download_completion() and + /// async_wait_for_sync_completion() therefore requires a full client <-> + /// server round-trip. + /// + /// If a new wait operation is initiated while another wait operation is in + /// progress by another thread, the waiting period of first operation may, + /// or may not get extended. The application must not assume either. The + /// application may assume, however, that async_wait_for_upload_completion() + /// will not affect the waiting period of + /// async_wait_for_download_completion(), and vice versa. + /// + /// It is an error to call these functions before bind() has been called, + /// and has returned. + /// + /// The specified completion handlers will always be executed by the thread + /// that executes the event loop (the thread that calls Client::run()). If + /// the handler throws an exception, that exception will "travel" out + /// through Client::run(). + /// + /// If incomplete wait operations exist when the session is terminated, + /// those wait operations will be canceled. Session termination is an event + /// that happens in the context of the client's event loop thread shortly + /// after the destruction of the session object. The std::error_code + /// argument passed to the completion handler of a canceled wait operation + /// will be `util::error::operation_aborted`. For uncanceled wait operations + /// it will be `std::error_code{}`. Note that as long as the client's event + /// loop thread is running, all completion handlers will be called + /// regardless of whether the operations get canceled or not. + /// + /// CAUTION: The specified completion handlers may get called before the + /// call to the waiting function returns, and it may get called (or continue + /// to execute) after the session object is destroyed. Please see "Callback + /// semantics" section under Session for more on this. + /// + /// Note: These functions are fully thread-safe. That is, they may be called + /// by any thread, and by multiple threads concurrently. + void async_wait_for_sync_completion(WaitOperCompletionHandler); + void async_wait_for_upload_completion(WaitOperCompletionHandler); + void async_wait_for_download_completion(WaitOperCompletionHandler); + /// @} + + /// @{ \brief Synchronous wait for upload or download completion. + /// + /// These functions are synchronous equivalents of + /// async_wait_for_upload_completion() and + /// async_wait_for_download_completion() respectively. This means that they + /// block the caller until the completion condition is satisfied, or the + /// client's event loop thread exits from Client::run(), whichever happens + /// first. + /// + /// It is an error to call these functions before bind() has been called, + /// and has returned. + /// + /// CAUTION: If Client::run() returns while a wait operation is in progress, + /// these waiting functions return immediately, even if the completion + /// condition is not yet satisfied. The completion condition is guaranteed + /// to be satisfied only when these functions return true. + /// + /// \return True only if the completion condition was satisfied. False if + /// the client's event loop thread exited from Client::run() in which case + /// the completion condition may, or may not have been satisfied. + /// + /// Note: These functions are fully thread-safe. That is, they may be called + /// by any thread, and by multiple threads concurrently. + bool wait_for_upload_complete_or_client_stopped(); + bool wait_for_download_complete_or_client_stopped(); + /// @} + + /// \brief Cancel the current or next reconnect delay for the server + /// associated with this session. + /// + /// When the network connection is severed, or an attempt to establish + /// connection fails, a certain delay will take effect before the client + /// will attempt to reestablish the connection. This delay will generally + /// grow with the number of unsuccessful reconnect attempts, and can grow to + /// over a minute. In some cases however, the application will know when it + /// is a good time to stop waiting and retry immediately. One example is + /// when a device has been offline for a while, and the operating system + /// then tells the application that network connectivity has been restored. + /// + /// Clearly, this function should not be called too often and over extended + /// periods of time, as that would effectively disable the built-in "server + /// hammering" protection. + /// + /// It is an error to call this function before bind() has been called, and + /// has returned. + /// + /// This function is fully thread-safe. That is, it may be called by any + /// thread, and by multiple threads concurrently. + void cancel_reconnect_delay(); + + /// \brief Change address of server for this session. + void override_server(std::string address, port_type); + + /// \brief Initiate a serialized transaction. + /// + /// Asynchronously waits for completion of any serialized transactions, that + /// are already in progress via the same session object, then waits for + /// the download process to complete (async_wait_for_download_completion()), + /// then pauses the upload process. The upload process will be resumed when + /// async_try_complete_serial_transact() or abort_serial_transact() is + /// called. + /// + /// Changesets produced by local transactions, that are committed after the + /// completion of the initiation of a serialized transaction, are guaranteed + /// to not be uploaded until after (or during) the completion of that + /// serialized transaction (async_try_complete_serial_transact()). + /// + /// If the initiation of a serialized transaction is successfully completed, + /// that is, if the specified handler gets called with an std::error_code + /// argument that evaluates to false in a boolean context, then the + /// application is required to eventually call + /// async_try_complete_serial_transact() to complete the transaction, or + /// abort_serial_transact() to abort it. If + /// async_try_complete_serial_transact() fails (throws), the application is + /// required to follow up with a call to abort_serial_transact(). + /// + /// If the session object is destroyed before initiation process completes, + /// the specified handler will be called with error + /// `util::error::operation_aborted`. Currently, this is the only possible + /// error that can be reported through this handler. + /// + /// This feature is only available when the server supports version 28, or + /// later, of the synchronization protocol. See + /// get_current_protocol_version(). + /// + /// This feature is not currently supported with Partial Synchronization, + /// and in a server cluster, it is currently only supported on the root + /// node. + void async_initiate_serial_transact(SerialTransactInitiationHandler); + + /// \brief Complete a serialized transaction. + /// + /// Initiate the completion of the serialized transaction. This involves + /// sending the specified changeset to the server, and waiting for the + /// servers response. + /// + /// If the session object is destroyed before completion process completes, + /// the specified handler will be called with error + /// `util::error::operation_aborted`. + /// + /// Otherwise, if the server does not support serialized transactions, the + /// specified handler will be called with error + /// `util::MiscExtErrors::operation_not_supported`. This happens if the + /// negotiated protocol version is too old, if serialized transactions are + /// disallowed by the server, or if it is not allowed for the Realm file in + /// question (partial synchronization). + /// + /// Otherwise, the specified handler will be called with an error code + /// argument that evaluates to false in a boolean context, and the + /// `accepted` argument will be true if, and only if the transaction was + /// accepted by the server. + /// + /// \param upload_anchor The upload cursor associated with the snapshot on + /// which the specified changeset is based. Use + /// sync::ClientHistory::get_upload_anchor_of_current_transact() to obtain + /// it. Note that + /// sync::ClientHistory::get_upload_anchor_of_current_transact() needs to be + /// called during the transaction that is used to produce the changeset of + /// the serialized transaction. + /// + /// \param changeset A changeset obtained from an aborted transaction on the + /// Realm file associated with this session. Use + /// sync::ClientHistory::get_sync_changeset() to obtain it. The transaction, + /// which is used to produce teh changeset, needs to be rolled back rather + /// than committed, because the decision of whether to accept the changes + /// need to be delegated to the server. Note that + /// sync::ClientHistory::get_sync_Changeset_of_current_transact() needs to + /// be called at the end of the transaction, that is used to produce the + /// changeset, but before the rollback operation. + void async_try_complete_serial_transact(UploadCursor upload_anchor, + SerialTransactChangeset changeset, + SerialTransactCompletionHandler); + + /// \brief Abort a serialized transaction. + /// + /// Must be called if async_try_complete_serial_transact() fails, i.e., if + /// it throws, or if async_try_complete_serial_transact() is not called at + /// all. Must not be called if async_try_complete_serial_transact() + /// succeeds, i.e., if it does not throw. + /// + /// Will resume upload process. + void abort_serial_transact() noexcept; + +private: + class Impl; + Impl* m_impl = nullptr; + + void abandon() noexcept; + void async_wait_for(bool upload_completion, bool download_completion, + WaitOperCompletionHandler); +}; + + +/// \brief Protocol errors discovered by the client. +/// +/// These errors will terminate the network connection (disconnect all sessions +/// associated with the affected connection), and the error will be reported to +/// the application via the connection state change listeners of the affected +/// sessions. +enum class Client::Error { + connection_closed = 100, ///< Connection closed (no error) + unknown_message = 101, ///< Unknown type of input message + bad_syntax = 102, ///< Bad syntax in input message head + limits_exceeded = 103, ///< Limits exceeded in input message + bad_session_ident = 104, ///< Bad session identifier in input message + bad_message_order = 105, ///< Bad input message order + bad_client_file_ident = 106, ///< Bad client file identifier (IDENT) + bad_progress = 107, ///< Bad progress information (DOWNLOAD) + bad_changeset_header_syntax = 108, ///< Bad syntax in changeset header (DOWNLOAD) + bad_changeset_size = 109, ///< Bad changeset size in changeset header (DOWNLOAD) + bad_origin_file_ident = 110, ///< Bad origin file identifier in changeset header (DOWNLOAD) + bad_server_version = 111, ///< Bad server version in changeset header (DOWNLOAD) + bad_changeset = 112, ///< Bad changeset (DOWNLOAD) + bad_request_ident = 113, ///< Bad request identifier (MARK) + bad_error_code = 114, ///< Bad error code (ERROR), + bad_compression = 115, ///< Bad compression (DOWNLOAD) + bad_client_version = 116, ///< Bad last integrated client version in changeset header (DOWNLOAD) + ssl_server_cert_rejected = 117, ///< SSL server certificate rejected + pong_timeout = 118, ///< Timeout on reception of PONG respone message + bad_client_file_ident_salt = 119, ///< Bad client file identifier salt (IDENT) + bad_file_ident = 120, ///< Bad file identifier (ALLOC) + connect_timeout = 121, ///< Sync connection was not fully established in time + bad_timestamp = 122, ///< Bad timestamp (PONG) + bad_protocol_from_server = 123, ///< Bad or missing protocol version information from server + client_too_old_for_server = 124, ///< Protocol version negotiation failed: Client is too old for server + client_too_new_for_server = 125, ///< Protocol version negotiation failed: Client is too new for server + protocol_mismatch = 126, ///< Protocol version negotiation failed: No version supported by both client and server + bad_state_message = 127, ///< Bad values in state message (STATE) + missing_protocol_feature = 128, ///< Requested feature missing in negotiated protocol version + bad_serial_transact_status = 129, ///< Bad status of serialized transaction (TRANSACT) + bad_object_id_substitutions = 130, ///< Bad encoded object identifier substitutions (TRANSACT) + http_tunnel_failed = 131, ///< Failed to establish HTTP tunnel with configured proxy +}; + +const std::error_category& client_error_category() noexcept; + +std::error_code make_error_code(Client::Error) noexcept; + +std::ostream& operator<<(std::ostream& os, Session::Config::ProxyConfig::Type); + +} // namespace sync +} // namespace realm + +namespace std { + +template<> struct is_error_code_enum { + static const bool value = true; +}; + +} // namespace std + +namespace realm { +namespace sync { + + + +// Implementation + +class BadServerUrl: public std::exception { +public: + const char* what() const noexcept override + { + return "Bad server URL"; + } +}; + +inline Session::Session(Session&& sess) noexcept: + m_impl{sess.m_impl} +{ + sess.m_impl = nullptr; +} + +inline Session::Session() noexcept +{ +} + +inline Session::~Session() noexcept +{ + if (m_impl) + abandon(); +} + +inline Session& Session::operator=(Session&& sess) noexcept +{ + if (m_impl) + abandon(); + m_impl = sess.m_impl; + sess.m_impl = nullptr; + return *this; +} + +inline void Session::detach() noexcept +{ + if (m_impl) + abandon(); + m_impl = nullptr; +} + +inline void Session::set_error_handler(std::function handler) +{ + auto handler_2 = [handler=std::move(handler)](ConnectionState state, + const ErrorInfo* error_info) { + if (state != ConnectionState::disconnected) + return; + REALM_ASSERT(error_info); + std::error_code ec = error_info->error_code; + bool is_fatal = error_info->is_fatal; + const std::string& detailed_message = error_info->detailed_message; + handler(ec, is_fatal, detailed_message); // Throws + }; + set_connection_state_change_listener(std::move(handler_2)); // Throws +} + +inline void Session::async_wait_for_sync_completion(WaitOperCompletionHandler handler) +{ + bool upload_completion = true, download_completion = true; + async_wait_for(upload_completion, download_completion, std::move(handler)); // Throws +} + +inline void Session::async_wait_for_upload_completion(WaitOperCompletionHandler handler) +{ + bool upload_completion = true, download_completion = false; + async_wait_for(upload_completion, download_completion, std::move(handler)); // Throws +} + +inline void Session::async_wait_for_download_completion(WaitOperCompletionHandler handler) +{ + bool upload_completion = false, download_completion = true; + async_wait_for(upload_completion, download_completion, std::move(handler)); // Throws +} + +} // namespace sync +} // namespace realm + +#endif // REALM_SYNC_CLIENT_HPP diff --git a/!main project/Pods/Realm/include/core/realm/sync/crypto.hpp b/!main project/Pods/Realm/include/core/realm/sync/crypto.hpp new file mode 100644 index 0000000..40ade0e --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/sync/crypto.hpp @@ -0,0 +1,51 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2015] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ + +#ifndef REALM_SYNC_CRYPTO_HPP +#define REALM_SYNC_CRYPTO_HPP + +#include +#include + +#include +#include + +namespace realm { +namespace sync { +namespace crypto { + +/// The digest functions calculate the message digest of the input in \param +/// in_buffer of size \param in_buffer_size. The digest is placed in \param +/// out_buffer. The caller must guarantee that the output buffer is large +/// enough to contain the digest. +/// +/// The functions throw if the underlying platform dependent implementations +/// throw. Typically, exceptions are "out of memory" errors. +/// +/// sha1() calculates the SHA-1 hash value of output size 20. +/// sha256() calculates the SHA-256 hash value of output size 32. +void sha1(const char* in_buffer, size_t in_buffer_size, unsigned char* out_buffer); +void sha256(const char* in_buffer, size_t in_buffer_size, unsigned char* out_buffer); + +} // namespace crypto +} // namespace sync +} // namespace realm + +#endif // REALM_SYNC_CRYPTO_HPP diff --git a/!main project/Pods/Realm/include/core/realm/sync/crypto_server.hpp b/!main project/Pods/Realm/include/core/realm/sync/crypto_server.hpp new file mode 100644 index 0000000..c241a71 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/sync/crypto_server.hpp @@ -0,0 +1,91 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2015] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ + +#ifndef REALM_SYNC_CRYPTO_SERVER_HPP +#define REALM_SYNC_CRYPTO_SERVER_HPP + +#include +#include + +#include +#include + +namespace realm { +namespace sync { + +struct CryptoError: std::runtime_error { + CryptoError(std::string message) : std::runtime_error(std::move(message)) {} +}; + +/// This class represents a public/private keypair, or more commonly a single public +/// key used for verifying signatures. +/// +/// Only RSA keys are supported for now. +/// +/// Its methods correspond roughly to the EVP_PKEY_* set of functionality found in +/// the OpenSSL library. +class PKey { +public: + PKey(PKey&&); + PKey& operator=(PKey&&); + ~PKey(); + + /// Load RSA public key from \a pemfile. + static PKey load_public(const std::string& pemfile); + /// Load RSA public key from a PEM buffer + static PKey load_public(BinaryData pem_buffer); + + /// Load RSA public/private keypair from \a pemfile. + static PKey load_private(const std::string& pemfile); + /// Load RSA public/private keypair from a PEM buffer + static PKey load_private(BinaryData pem_buffer); + + /// Whether or not the key can be used for signing. + /// + /// True if the private part is loaded. + bool can_sign() const noexcept; + + /// Whether or not the key can be used for verifying. + /// + /// Always true for RSA keys. + bool can_verify() const noexcept; + + /// Sign \a message with the loaded key, if the private part is + /// loaded. Store the signed message as binary data in \a signature. + /// + /// If a private key is not loaded, throws an exception of type CryptoError. + void sign(BinaryData message, util::Buffer& signature) const; + + /// Verify that \a signature is a valid digest of \a message. + /// + /// Returns true if the signature is valid, otherwise false. If an error occurs while + /// attempting verification, an exception of type CryptoError is thrown. + bool verify(BinaryData message, BinaryData signature) const; + +private: + PKey(); + struct Impl; + std::unique_ptr m_impl; +}; + +} // namespace sync +} // namespace realm + +#endif // REALM_SYNC_CRYPTO_SERVER_HPP diff --git a/!main project/Pods/Realm/include/core/realm/sync/feature_token.hpp b/!main project/Pods/Realm/include/core/realm/sync/feature_token.hpp new file mode 100644 index 0000000..86db015 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/sync/feature_token.hpp @@ -0,0 +1,69 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2012] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ +#ifndef REALM_SYNC_FEATURE_TOKEN_HPP +#define REALM_SYNC_FEATURE_TOKEN_HPP + +#include + +#if !REALM_MOBILE && !defined(REALM_EXCLUDE_FEATURE_TOKENS) +#define REALM_HAVE_FEATURE_TOKENS 1 +#else +#define REALM_HAVE_FEATURE_TOKENS 0 +#endif + +#if REALM_HAVE_FEATURE_TOKENS + +#include + +#include + +namespace realm { +namespace sync { + +class FeatureGate { +public: + + // The constructor takes a JWT token as argument. + // The constructor throws a std::runtime_error if + // the token is invalid. An invalid token is a token + // that has bad syntax, is not signed by Realm, or is + // expired. + FeatureGate(StringData token); + + // Constructs a feature gate without any features. + FeatureGate(); + ~FeatureGate(); + + FeatureGate(FeatureGate&&); + FeatureGate& operator=(FeatureGate&&); + + bool has_feature(StringData feature_name); + +private: + struct Impl; + std::unique_ptr m_impl; +}; + + +} // namespace sync +} // namespace realm + +#endif // REALM_HAVE_FEATURE_TOKENS +#endif // REALM_SYNC_FEATURE_TOKEN_HPP diff --git a/!main project/Pods/Realm/include/core/realm/sync/fingerprint.hpp b/!main project/Pods/Realm/include/core/realm/sync/fingerprint.hpp new file mode 100644 index 0000000..13b54ab --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/sync/fingerprint.hpp @@ -0,0 +1,53 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2018] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ +#ifndef REALM_ENCRYPT_FINGERPRINT_HPP +#define REALM_ENCRYPT_FINGERPRINT_HPP + +#include +#include + +#include + +namespace realm { +namespace encrypt { + +// calculate_fingerprint() calculates, and returns, a fingerprint of an +// encryption key. The input key can be util::none in order to calculate a +// fingerprint even in the case of unencrypted Realms. +// +// An intruder cannot recover an unknown encryption_key from the fingerprint, +// and it is safe to save the fingerprint in a file together with the encrypted +// Realms. +// +// calculate_fingerprint() can be considered opaque, but currently the +// fingerprint is a colon separated hex representation of the SHA-256 hash of +// the encryption key. +std::string calculate_fingerprint(const util::Optional> encryption_key); + +// verify_fingerprint() returns true if `fingerprint` was obtained previously +// from calculate_fingerprint() with `encryption_key` as argument. Otherwise, +// verify_fingerprint() returns false with extremely high probability. +bool verify_fingerprint(const std::string& fingerprint, + const util::Optional> encryption_key); + +} // namespace encrypt +} // namespace realm + +#endif // REALM_ENCRYPT_FINGERPRINT_HPP diff --git a/!main project/Pods/Realm/include/core/realm/sync/history.hpp b/!main project/Pods/Realm/include/core/realm/sync/history.hpp new file mode 100644 index 0000000..5fc571f --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/sync/history.hpp @@ -0,0 +1,613 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2015] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifndef REALM_SYNC_HISTORY_HPP +#define REALM_SYNC_HISTORY_HPP + + +namespace realm { +namespace _impl { + +struct ObjectIDHistoryState; + +} // namespace _impl +} // namespace realm + + +namespace realm { +namespace sync { + +struct VersionInfo { + /// Realm snapshot version. + version_type realm_version = 0; + + /// The synchronization version corresponding to `realm_version`. + /// + /// In the context of the client-side history type `sync_version.version` + /// will currently always be equal to `realm_version` and + /// `sync_version.salt` will always be zero. + SaltedVersion sync_version = {0, 0}; +}; + + +struct SerialTransactSubstitutions { + struct Class { + InternString name; + std::size_t substitutions_end; + }; + std::vector classes; + std::vector> substitutions; +}; + + +timestamp_type generate_changeset_timestamp() noexcept; + +// FIXME: in C++17, switch to using std::timespec in place of last two +// arguments. +void map_changeset_timestamp(timestamp_type, std::time_t& seconds_since_epoch, + long& nanoseconds) noexcept; + + +/// Thrown if changeset cooking is not either consistently on or consistently +/// off during synchronization (ClientHistory::set_sync_progress() and +/// ClientHistory::integrate_server_changesets()). +class InconsistentUseOfCookedHistory; + +/// Thrown if a bad server version is passed to +/// ClientHistory::get_cooked_status(). +class BadCookedServerVersion; + + +class ClientHistoryBase : + public InstructionReplication { +public: + using SyncTransactCallback = void(VersionID old_version, VersionID new_version); + + /// Get the version of the latest snapshot of the associated Realm, as well + /// as the client file identifier and the synchronization progress as they + /// are stored in that snapshot. + /// + /// The returned current client version is the version produced by the last + /// changeset in the history. The type of version returned here, is the one + /// that identifies an entry in the sync history. Whether this is the same + /// as the snapshot number of the Realm file depends on the history + /// implementation. + /// + /// The returned client file identifier is the one that was last stored by + /// set_client_file_ident(), or `SaltedFileIdent{0, 0}` if + /// set_client_file_ident() has never been called. + /// + /// The returned SyncProgress is the one that was last stored by + /// set_sync_progress(), or `SyncProgress{}` if set_sync_progress() has + /// never been called. + virtual void get_status(version_type& current_client_version, + SaltedFileIdent& client_file_ident, + SyncProgress& progress) const = 0; + + /// Stores the server assigned client file identifier in the associated + /// Realm file, such that it is available via get_status() during future + /// synchronization sessions. It is an error to set this identifier more + /// than once per Realm file. + /// + /// \param client_file_ident The server assigned client-side file + /// identifier. A client-side file identifier is a non-zero positive integer + /// strictly less than 2**64. The server guarantees that all client-side + /// file identifiers generated on behalf of a particular server Realm are + /// unique with respect to each other. The server is free to generate + /// identical identifiers for two client files if they are associated with + /// different server Realms. + /// + /// \param fix_up_object_ids The object ids that depend on client file ident + /// will be fixed in both state and history if this parameter is true. If + /// it is known that there are no objects to fix, it can be set to false to + /// achieve higher performance. + /// + /// The client is required to obtain the file identifier before engaging in + /// synchronization proper, and it must store the identifier and use it to + /// reestablish the connection between the client file and the server file + /// when engaging in future synchronization sessions. + virtual void set_client_file_ident(SaltedFileIdent client_file_ident, + bool fix_up_object_ids) = 0; + + /// Stores the synchronization progress in the associated Realm file in a + /// way that makes it available via get_status() during future + /// synchronization sessions. Progress is reported by the server in the + /// DOWNLOAD message. + /// + /// See struct SyncProgress for a description of \a progress. + /// + /// \param downloadable_bytes If specified, and if the implementation cares + /// about byte-level progress, this function updates the persistent record + /// of the estimate of the number of remaining bytes to be downloaded. + /// + /// \throw InconsistentUseOfCookedHistory If a changeset cooker has been + /// attached to this history object, and the Realm file does not have a + /// cooked history, and a cooked history can no longer be added because some + /// synchronization has already happened. Or if no changeset cooker has been + /// attached, and the Realm file does have a cooked history. + virtual void set_sync_progress(const SyncProgress& progress, + const std::uint_fast64_t* downloadable_bytes, VersionInfo&) = 0; + + struct UploadChangeset { + timestamp_type origin_timestamp; + file_ident_type origin_file_ident; + UploadCursor progress; + ChunkedBinaryData changeset; + std::unique_ptr buffer; + }; + + /// \brief Scan through the history for changesets to be uploaded. + /// + /// This function scans the history for changesets to be uploaded, i.e., for + /// changesets that are not empty, and were not produced by integration of + /// changesets recieved from the server. The scan begins at the position + /// specified by the initial value of \a upload_progress.client_version, and + /// ends no later than at the position specified by \a end_version. + /// + /// The implementation is allowed to end the scan before \a end_version, + /// such as to limit the combined size of returned changesets. However, if + /// the specified range contains any changesets that are supposed to be + /// uploaded, this function must return at least one. + /// + /// Upon return, \a upload_progress will have been updated to point to the + /// position from which the next scan should resume. This must be a position + /// after the last returned changeset, and before any remaining changesets + /// that are supposed to be uploaded, although never a position that + /// succeeds \a end_version. + /// + /// The value passed as \a upload_progress by the caller, must either be one + /// that was produced by an earlier invocation of + /// find_uploadable_changesets(), one that was returned by get_status(), or + /// one that was received by the client in a DOWNLOAD message from the + /// server. When the value comes from a DOWNLOAD message, it is supposed to + /// reflect a value of UploadChangeset::progress produced by an earlier + /// invocation of find_uploadable_changesets(). + /// + /// Found changesets are added to \a uploadable_changesets. + /// + /// \param locked_server_version will be set to the value that should be + /// used as `` in a DOWNLOAD message. + /// + /// For changesets of local origin, UploadChangeset::origin_file_ident will + /// be zero. + virtual void find_uploadable_changesets(UploadCursor& upload_progress, version_type end_version, + std::vector& uploadable_changesets, + version_type& locked_server_version) const = 0; + + using RemoteChangeset = Transformer::RemoteChangeset; + + // FIXME: Apparently, this feature is expected by object store, but why? + // What is it ultimately used for? (@tgoyne) + class SyncTransactReporter { + public: + virtual void report_sync_transact(VersionID old_version, VersionID new_version) = 0; + protected: + ~SyncTransactReporter() {} + }; + + enum class IntegrationError { + bad_origin_file_ident, + bad_changeset + }; + + /// \brief Integrate a sequence of changesets received from the server using + /// a single Realm transaction. + /// + /// Each changeset will be transformed as if by a call to + /// Transformer::transform_remote_changeset(), and then applied to the + /// associated Realm. + /// + /// As a final step, each changeset will be added to the local history (list + /// of applied changesets). + /// + /// This function checks whether the specified changesets specify valid + /// remote origin file identifiers and whether the changesets contain valid + /// sequences of instructions. The caller must already have ensured that the + /// origin file identifiers are strictly positive and not equal to the file + /// identifier assigned to this client by the server. + /// + /// If any of the changesets are invalid, this function returns false and + /// sets `integration_error` to the appropriate value. If they are all + /// deemed valid, this function updates \a version_info to reflect the new + /// version produced by the transaction. + /// + /// \param progress The synchronization progress is what was received in the + /// DOWNLOAD message along with the specified changesets. The progress will + /// be persisted along with the changesets. + /// + /// \param downloadable_bytes If specified, and if the implementation cares + /// about byte-level progress, this function updates the persistent record + /// of the estimate of the number of remaining bytes to be downloaded. + /// + /// \param num_changesets The number of passed changesets. Must be non-zero. + /// + /// \param transact_reporter An optional callback which will be called with the + /// version immediately processing the sync transaction and that of the sync + /// transaction. + /// + /// \throw InconsistentUseOfCookedHistory If a changeset cooker has been + /// attached to this history object, and the Realm file does not have a + /// cooked history, and a cooked history can no longer be added because some + /// synchronization has already happened. Or if no changeset cooker has been + /// attached, and the Realm file does have a cooked history. + virtual bool integrate_server_changesets(const SyncProgress& progress, + const std::uint_fast64_t* downloadable_bytes, + const RemoteChangeset* changesets, + std::size_t num_changesets, VersionInfo& new_version, + IntegrationError& integration_error, util::Logger&, + SyncTransactReporter* transact_reporter = nullptr, + const SerialTransactSubstitutions* = nullptr) = 0; + +protected: + ClientHistoryBase(const std::string& realm_path); +}; + + + +class ClientHistory : public ClientHistoryBase { +public: + class ChangesetCooker; + class Config; + + /// Get the persisted upload/download progress in bytes. + virtual void get_upload_download_bytes(std::uint_fast64_t& downloaded_bytes, + std::uint_fast64_t& downloadable_bytes, + std::uint_fast64_t& uploaded_bytes, + std::uint_fast64_t& uploadable_bytes, + std::uint_fast64_t& snapshot_version) = 0; + + /// See set_cooked_progress(). + struct CookedProgress { + std::int_fast64_t changeset_index = 0; + std::int_fast64_t intrachangeset_progress = 0; + }; + + /// Get information about the current state of the cooked history including + /// the point of progress of its consumption. + /// + /// \param server_version The server version associated with the last cooked + /// changeset that should be skipped. See `/doc/cooked_history.md` for an + /// explanation of the rationale behind this. Specifying zero means that no + /// changesets should be skipped. It is an error to specify a nonzero server + /// version that is not the server version associated with any of of the + /// cooked changesets, or to specify a nonzero server version that precedes + /// the one, that is associated with the last cooked changeset that was + /// marked as consumed. Doing so, will cause BadCookedServerVersion to be + /// thrown. + /// + /// \param num_changesets Set to the total number of produced cooked + /// changesets over the lifetime of the Realm file to which this history + /// accessor object is attached. This is the number of previously consumed + /// changesets plus the number of unconsumed changesets remaining in the + /// Realm file. + /// + /// \param progress The point of progress of the consumption of the cooked + /// history. Initially, and until explicitly modified by + /// set_cooked_progress(), both `CookedProgress::changeset_index` and + /// `CookedProgress::intrachangeset_progress` are zero. If a nonzero value + /// was passed for \a server_version, \a progress will be transparently + /// adjusted to account for the skipped changesets. See also \a + /// num_skipped_changesets. If one or more changesets are skipped, + /// `CookedProgress::intrachangeset_progress` will be set to zero. + /// + /// \param num_skipped_changesets The number of skipped changesets. See also + /// \a server_version. + /// + /// \throw BadCookedServerVersion See \a server_version. + virtual void get_cooked_status(version_type server_version, std::int_fast64_t& num_changesets, + CookedProgress& progress, + std::int_fast64_t& num_skipped_changesets) const = 0; + + /// Fetch the cooked changeset at the specified index. + /// + /// Cooked changesets are made available in the order they are produced by + /// the changeset cooker (ChangesetCooker). + /// + /// Behaviour is undefined if the specified index is less than the index + /// (CookedProgress::changeset_index) returned by get_cooked_progress(), or + /// if it is greater than, or equal to the total number of cooked changesets + /// (as returned by get_num_cooked_changesets()). + /// + /// The callee must append the bytes of the located cooked changeset to the + /// specified buffer, which does not have to be empty initially. + /// + /// \param server_version Will be set to the version produced on the server + /// by an earlier form of the retreived changeset. If the cooked changeset + /// was produced (as output of cooker) before migration of the client-side + /// history compartment to schema version 2, then \a server_version will be + /// set to zero instead, because the real value is unkown. Zero is not a + /// possible value in any other case. + virtual void get_cooked_changeset(std::int_fast64_t index, + util::AppendBuffer&, + version_type& server_version) const = 0; + + /// Persistently stores the point of progress of the consumer of cooked + /// changesets. + /// + /// The changeset index (CookedProgress::changeset_index) is the index (as + /// passed to get_cooked_changeset()) of the first unconsumed cooked + /// changset. Changesets at lower indexes will no longer be available. + /// + /// The intrachangeset progress field + /// (CookedProgress::intrachangeset_progress) will be faithfully persisted, + /// but will otherwise be treated as an opaque object by the history + /// internals. + /// + /// As well as allowing for later retrieval, the specification of the point + /// of progress of the consumer of cooked changesets also has the effect of + /// trimming obsolete cooked changesets from the Realm file (i.e., removal + /// of all changesets at indexes lower than + /// CookedProgress::intrachangeset_progress). Indeed, if this function is + /// never called, but cooked changesets are continually being produced, then + /// the Realm file will grow without bounds. + /// + /// It is an error if the specified index (CookedProgress::changeset_index) + /// is lower than the index returned by get_cooked_progress(), and if it is + /// higher that the value returned by get_num_cooked_changesets(). + /// + /// \return The snapshot number produced by the transaction performed + /// internally in set_cooked_progress(). This is also the client-side sync + /// version, and it should be passed to + /// sync::Session::nonsync_transact_notify() if a synchronization session is + /// in progress for the same file while set_cooked_progress() is + /// called. Doing so, ensures that the server will be notified about the + /// released server versions as soon as possible. + /// + /// \throw InconsistentUseOfCookedHistory If this file does not have a + /// cooked history and one can no longer be added because changesets of + /// remote origin has already been integrated. + virtual version_type set_cooked_progress(CookedProgress) = 0; + + /// \brief Get the number of cooked changesets so far produced for this + /// Realm. + /// + /// This is the same thing as is returned via \a num_changesets by + /// get_cooked_status(). + std::int_fast64_t get_num_cooked_changesets() const noexcept; + + /// \brief Returns the persisted progress that was last stored by + /// set_cooked_progress(). + /// + /// This is the same thing as is returned via \a progress by + /// get_cooked_status() when invoked with a server version of zero. + CookedProgress get_cooked_progress() const noexcept; + + /// Same as get_cooked_changeset(std::int_fast64_t, + /// util::AppendBuffer&, version_type&) but does not retreived the + /// server version. + void get_cooked_changeset(std::int_fast64_t index, util::AppendBuffer&) const; + + /// Return an upload cursor as it would be when the uploading process + /// reaches the snapshot to which the current transaction is bound. + /// + /// **CAUTION:** Must be called only while a transaction (read or write) is + /// in progress via the SharedGroup object associated with this history + /// object. + virtual UploadCursor get_upload_anchor_of_current_transact() const = 0; + + /// Return the synchronization changeset of the current transaction as it + /// would be if that transaction was committed at this time. + /// + /// The returned memory reference may be invalidated by subsequent + /// operations on the Realm state. + /// + /// **CAUTION:** Must be called only while a write transaction is in + /// progress via the SharedGroup object associated with this history object. + virtual util::StringView get_sync_changeset_of_current_transact() const noexcept = 0; + +protected: + ClientHistory(const std::string& realm_path); +}; + + +/// \brief Abstract interface for changeset cookers. +/// +/// Note, it is completely up to the application to decide what a cooked +/// changeset is. History objects (instances of ClientHistory) are required to +/// treat cooked changesets as opaque entities. For an example of a concrete +/// changeset cooker, see TrivialChangesetCooker which defines the cooked +/// changesets to be identical copies of the raw changesets. +class ClientHistory::ChangesetCooker { +public: + virtual ~ChangesetCooker() {} + + /// \brief An opportunity to produce a cooked changeset. + /// + /// When the implementation chooses to produce a cooked changeset, it must + /// write the cooked changeset to the specified buffer, and return + /// true. When the implementation chooses not to produce a cooked changeset, + /// it must return false. The implementation is allowed to write to the + /// buffer, and return false, and in that case, the written data will be + /// ignored. + /// + /// \param prior_state The state of the local Realm on which the specified + /// raw changeset is based. + /// + /// \param changeset, changeset_size The raw changeset. + /// + /// \param buffer The buffer to which the cooked changeset must be written. + /// + /// \return True if a cooked changeset was produced. Otherwise false. + virtual bool cook_changeset(const Group& prior_state, + const char* changeset, std::size_t changeset_size, + util::AppendBuffer& buffer) = 0; +}; + + +class ClientHistory::Config { +public: + Config() {} + + /// Must be set to true if, and only if the created history object + /// represents (is owned by) the sync agent of the specified Realm file. At + /// most one such instance is allowed to participate in a Realm file access + /// session at any point in time. Ordinarily the sync agent is encapsulated + /// by the sync::Client class, and the history instance representing the + /// agent is created transparently by sync::Client (one history instance per + /// sync::Session object). + bool owner_is_sync_agent = false; + + /// If a changeset cooker is specified, then the created history object will + /// allow for a cooked changeset to be produced for each changeset of remote + /// origin; that is, for each changeset that is integrated during the + /// execution of ClientHistory::integrate_remote_changesets(). If no + /// changeset cooker is specified, then no cooked changesets will be + /// produced on behalf of the created history object. + /// + /// ClientHistory::integrate_remote_changesets() will pass each incoming + /// changeset to the cooker after operational transformation; that is, when + /// the chageset is ready to be applied to the local Realm state. + std::shared_ptr changeset_cooker; +}; + +/// \brief Create a "sync history" implementation of the realm::Replication +/// interface. +/// +/// The intended role for such an object is as a plugin for new +/// realm::SharedGroup objects. +std::unique_ptr make_client_history(const std::string& realm_path, + ClientHistory::Config = {}); + + + +// Implementation + +inline timestamp_type generate_changeset_timestamp() noexcept +{ + namespace chrono = std::chrono; + // Unfortunately, C++11 does not specify what the epoch is for + // `chrono::system_clock` (or for any other clock). It is believed, however, + // that there is a de-facto standard, that the Epoch for + // `chrono::system_clock` is the Unix epoch, i.e., 1970-01-01T00:00:00Z. See + // http://stackoverflow.com/a/29800557/1698548. Additionally, it is assumed + // that leap seconds are not included in the value returned by + // time_since_epoch(), i.e., that it conforms to POSIX time. This is known + // to be true on Linux. + // + // FIXME: Investigate under which conditions OS X agrees with POSIX about + // not including leap seconds in the value returned by time_since_epoch(). + // + // FIXME: Investigate whether Microsoft Windows agrees with POSIX about + // about not including leap seconds in the value returned by + // time_since_epoch(). + auto time_since_epoch = chrono::system_clock::now().time_since_epoch(); + std::uint_fast64_t millis_since_epoch = + chrono::duration_cast(time_since_epoch).count(); + // `offset_in_millis` is the number of milliseconds between + // 1970-01-01T00:00:00Z and 2015-01-01T00:00:00Z not counting leap seconds. + std::uint_fast64_t offset_in_millis = 1420070400000ULL; + return timestamp_type(millis_since_epoch - offset_in_millis); +} + +inline void map_changeset_timestamp(timestamp_type timestamp, std::time_t& seconds_since_epoch, + long& nanoseconds) noexcept +{ + std::uint_fast64_t offset_in_millis = 1420070400000ULL; + std::uint_fast64_t millis_since_epoch = std::uint_fast64_t(offset_in_millis + timestamp); + seconds_since_epoch = std::time_t(millis_since_epoch / 1000); + nanoseconds = long(millis_since_epoch % 1000 * 1000000L); +} + +class InconsistentUseOfCookedHistory : public std::exception { +public: + InconsistentUseOfCookedHistory(const char* message) noexcept : + m_message{message} + { + } + const char* what() const noexcept override final + { + return m_message; + } +private: + const char* m_message; +}; + +class BadCookedServerVersion : public std::exception { +public: + BadCookedServerVersion(const char* message) noexcept : + m_message{message} + { + } + const char* what() const noexcept override final + { + return m_message; + } +private: + const char* m_message; +}; + +inline ClientHistoryBase::ClientHistoryBase(const std::string& realm_path) : + InstructionReplication{realm_path} // Throws +{ +} + +inline ClientHistory::ClientHistory(const std::string& realm_path) : + ClientHistoryBase{realm_path} // Throws +{ +} + +inline std::int_fast64_t ClientHistory::get_num_cooked_changesets() const noexcept +{ + version_type server_version = 0; // Skip nothing + std::int_fast64_t num_changesets = 0; + ClientHistory::CookedProgress progress; + std::int_fast64_t num_skipped_changesets = 0; + get_cooked_status(server_version, num_changesets, progress, num_skipped_changesets); + REALM_ASSERT(progress.changeset_index <= num_changesets); + REALM_ASSERT(num_skipped_changesets == 0); + return num_changesets; +} + +inline auto ClientHistory::get_cooked_progress() const noexcept -> CookedProgress +{ + version_type server_version = 0; // Skip nothing + std::int_fast64_t num_changesets = 0; + ClientHistory::CookedProgress progress; + std::int_fast64_t num_skipped_changesets = 0; + get_cooked_status(server_version, num_changesets, progress, num_skipped_changesets); + REALM_ASSERT(progress.changeset_index <= num_changesets); + REALM_ASSERT(num_skipped_changesets == 0); + return progress; +} + +inline void ClientHistory::get_cooked_changeset(std::int_fast64_t index, + util::AppendBuffer& buffer) const +{ + version_type server_version; // Dummy + get_cooked_changeset(index, buffer, server_version); // Throws +} + +} // namespace sync +} // namespace realm + +#endif // REALM_SYNC_HISTORY_HPP diff --git a/!main project/Pods/Realm/include/core/realm/sync/instruction_applier.hpp b/!main project/Pods/Realm/include/core/realm/sync/instruction_applier.hpp new file mode 100644 index 0000000..92bb573 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/sync/instruction_applier.hpp @@ -0,0 +1,147 @@ +/************************************************************************* + * + * Copyright 2017 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_SYNC_IMPL_INSTRUCTION_APPLIER_HPP +#define REALM_SYNC_IMPL_INSTRUCTION_APPLIER_HPP + +#include +#include +#include +#include + + +namespace realm { +namespace sync { + +struct Changeset; + +struct InstructionApplier { + explicit InstructionApplier(Group&, TableInfoCache&) noexcept; + + /// Throws BadChangesetError if application fails due to a problem with the + /// changeset. + /// + /// FIXME: Consider using std::error_code instead of throwing + /// BadChangesetError. + void apply(const Changeset&, util::Logger*); + + void begin_apply(const Changeset&, util::Logger*) noexcept; + void end_apply() noexcept; + +protected: + StringData get_string(InternString) const; + StringData get_string(StringBufferRange) const; +#define REALM_DECLARE_INSTRUCTION_HANDLER(X) void operator()(const Instruction::X&); + REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_DECLARE_INSTRUCTION_HANDLER) +#undef REALM_DECLARE_INSTRUCTION_HANDLER + friend struct Instruction; // to allow visitor + + template static void apply(A& applier, const Changeset&, util::Logger*); + + // Allows for in-place modification of changeset while applying it + template static void apply(A& applier, Changeset&, util::Logger*); + + TableRef table_for_class_name(StringData) const; // Throws + REALM_NORETURN void bad_transaction_log(const char*) const; + + Group& m_group; + TableInfoCache& m_table_info_cache; + LinkViewRef m_selected_link_list; + TableRef m_selected_table; + TableRef m_selected_array; + TableRef m_link_target_table; + + template + void log(const char* fmt, Args&&... args) + { + if (m_logger) { + m_logger->trace(fmt, std::forward(args)...); // Throws + } + } + +private: + const Changeset* m_log = nullptr; + util::Logger* m_logger = nullptr; +}; + + + + +// Implementation + +inline InstructionApplier::InstructionApplier(Group& group, TableInfoCache& table_info_cache) noexcept: + m_group(group), + m_table_info_cache(table_info_cache) +{ +} + +inline void InstructionApplier::begin_apply(const Changeset& log, util::Logger* logger) noexcept +{ + m_log = &log; + m_logger = logger; +} + +inline void InstructionApplier::end_apply() noexcept +{ + m_log = nullptr; + m_logger = nullptr; + m_selected_table = TableRef{}; + m_selected_array = TableRef{}; + m_selected_link_list = LinkViewRef{}; + m_link_target_table = TableRef{}; +} + +template +inline void InstructionApplier::apply(A& applier, const Changeset& changeset, util::Logger* logger) +{ + applier.begin_apply(changeset, logger); + for (auto instr : changeset) { + if (!instr) + continue; + instr->visit(applier); // Throws +#if REALM_DEBUG + applier.m_table_info_cache.verify(); +#endif + } + applier.end_apply(); +} + +template +inline void InstructionApplier::apply(A& applier, Changeset& changeset, util::Logger* logger) +{ + applier.begin_apply(changeset, logger); + for (auto instr : changeset) { + if (!instr) + continue; + instr->visit(applier); // Throws +#if REALM_DEBUG + applier.m_table_info_cache.verify(); +#endif + } + applier.end_apply(); +} + +inline void InstructionApplier::apply(const Changeset& log, util::Logger* logger) +{ + apply(*this, log, logger); // Throws +} + +} // namespace sync +} // namespace realm + +#endif // REALM_SYNC_IMPL_INSTRUCTION_APPLIER_HPP diff --git a/!main project/Pods/Realm/include/core/realm/sync/instruction_replication.hpp b/!main project/Pods/Realm/include/core/realm/sync/instruction_replication.hpp new file mode 100644 index 0000000..b66e332 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/sync/instruction_replication.hpp @@ -0,0 +1,238 @@ +/************************************************************************* + * + * Copyright 2017 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_SYNC_IMPL_INSTRUCTION_REPLICATION_HPP +#define REALM_SYNC_IMPL_INSTRUCTION_REPLICATION_HPP + +#include +#include +#include +#include + +namespace realm { +namespace sync { + + +class InstructionReplication: public TrivialReplication, public ObjectIDProvider { +public: + enum class TableBehavior { + Class, + Array, + Ignore + }; + + explicit InstructionReplication(const std::string& realm_path); + void set_short_circuit(bool) noexcept; + bool is_short_circuited() const noexcept; + + // reset() resets the encoder, the selected tables and the cache. It is + // called by do_initiate_transact(), but can be called at the other times + // as well. + virtual void reset(); + + ChangesetEncoder& get_instruction_encoder() noexcept; + const ChangesetEncoder& get_instruction_encoder() const noexcept; + + //@{ + /// Generate instructions for Object Store tables. These must be called + /// prior to calling the equivalent functions in Core's API. When creating a + /// class-like table, `add_class()` must be called prior to + /// `Group::insert_group_level_table()`. Similarly, `create_object()` or + /// `create_object_with_primary_key()` must be called prior to + /// `Table::insert_empty_row()` and/or `Table::set_int_unique()` or + /// `Table::set_string_unique()` or `Table::set_null_unique()`. + /// + /// If a class-like table is added, or an object-like row is inserted, + /// without calling these methods first, an exception will be thrown. + /// + /// A "class-like table" is defined as a table whose name begins with + /// "class_" (this is the convention used by Object Store). Non-class-like + /// tables can be created and modified using Core's API without calling + /// these functions, because they do not result in instructions being + /// emitted. + void add_class(StringData table_name); + void add_class_with_primary_key(StringData table_name, DataType pk_type, StringData pk_field, bool nullable); + void create_object(const Table*, ObjectID); + void create_object_with_primary_key(const Table*, ObjectID, StringData); + void create_object_with_primary_key(const Table*, ObjectID, int_fast64_t); + void create_object_with_primary_key(const Table*, ObjectID, realm::util::None); + void prepare_erase_table(StringData table_name); + //@} + + // TrivialReplication interface: + void initialize(SharedGroup&) override; + + // TransactLogConvenientEncoder interface: + void insert_group_level_table(size_t table_ndx, size_t num_tables, StringData name) override; + void erase_group_level_table(size_t table_ndx, size_t num_tables) override; + void rename_group_level_table(size_t table_ndx, StringData new_name) override; + void insert_column(const Descriptor&, size_t col_ndx, DataType type, StringData name, LinkTargetInfo& link, + bool nullable = false) override; + void erase_column(const Descriptor&, size_t col_ndx) override; + void rename_column(const Descriptor&, size_t col_ndx, StringData name) override; + + void set_int(const Table*, size_t col_ndx, size_t ndx, int_fast64_t value, _impl::Instruction variant) override; + void add_int(const Table*, size_t col_ndx, size_t ndx, int_fast64_t value) override; + void set_bool(const Table*, size_t col_ndx, size_t ndx, bool value, _impl::Instruction variant) override; + void set_float(const Table*, size_t col_ndx, size_t ndx, float value, _impl::Instruction variant) override; + void set_double(const Table*, size_t col_ndx, size_t ndx, double value, _impl::Instruction variant) override; + void set_string(const Table*, size_t col_ndx, size_t ndx, StringData value, _impl::Instruction variant) override; + void set_binary(const Table*, size_t col_ndx, size_t ndx, BinaryData value, _impl::Instruction variant) override; + void set_olddatetime(const Table*, size_t col_ndx, size_t ndx, OldDateTime value, + _impl::Instruction variant) override; + void set_timestamp(const Table*, size_t col_ndx, size_t ndx, Timestamp value, _impl::Instruction variant) override; + void set_table(const Table*, size_t col_ndx, size_t ndx, _impl::Instruction variant) override; + void set_mixed(const Table*, size_t col_ndx, size_t ndx, const Mixed& value, _impl::Instruction variant) override; + void set_link(const Table*, size_t col_ndx, size_t ndx, size_t value, _impl::Instruction variant) override; + void set_null(const Table*, size_t col_ndx, size_t ndx, _impl::Instruction variant) override; + void set_link_list(const LinkView&, const IntegerColumn& values) override; + void insert_substring(const Table*, size_t col_ndx, size_t row_ndx, size_t pos, StringData) override; + void erase_substring(const Table*, size_t col_ndx, size_t row_ndx, size_t pos, size_t size) override; + void insert_empty_rows(const Table*, size_t row_ndx, size_t num_rows_to_insert, size_t prior_num_rows) override; + void add_row_with_key(const Table*, size_t row_ndx, size_t prior_num_rows, size_t key_col_ndx, int64_t key) override; + void erase_rows(const Table*, size_t row_ndx, size_t num_rows_to_erase, size_t prior_num_rowsp, + bool is_move_last_over) override; + void swap_rows(const Table*, size_t row_ndx_1, size_t row_ndx_2) override; + void move_row(const Table*, size_t row_ndx_1, size_t row_ndx_2) override; + void merge_rows(const Table*, size_t row_ndx, size_t new_row_ndx) override; + void add_search_index(const Descriptor&, size_t col_ndx) override; + void remove_search_index(const Descriptor&, size_t col_ndx) override; + void set_link_type(const Table*, size_t col_ndx, LinkType) override; + void clear_table(const Table*, size_t prior_num_rows) override; + void optimize_table(const Table*) override; + void link_list_set(const LinkView&, size_t ndx, size_t value) override; + void link_list_insert(const LinkView&, size_t ndx, size_t value) override; + void link_list_move(const LinkView&, size_t from_ndx, size_t to_ndx) override; + void link_list_swap(const LinkView&, size_t ndx_1, size_t ndx_2) override; + void link_list_erase(const LinkView&, size_t ndx) override; + void link_list_clear(const LinkView&) override; + void nullify_link(const Table*, size_t col_ndx, size_t ndx) override; + void link_list_nullify(const LinkView&, size_t ndx) override; + + template + void emit(T instruction); + + TableBehavior select_table(const Table*); + const Table* selected_table() const noexcept; + +protected: + // Replication interface: + void do_initiate_transact(TransactionType, version_type current_version) override; +private: + bool m_short_circuit = false; + + ChangesetEncoder m_encoder; + SharedGroup* m_sg = nullptr; + std::unique_ptr m_cache; + + + // FIXME: The base class already caches this. + ConstTableRef m_selected_table; + TableBehavior m_selected_table_behavior; // cache + ConstLinkViewRef m_selected_link_list = nullptr; + + // Consistency checks: + std::string m_table_being_created; + std::string m_table_being_created_primary_key; + std::string m_table_being_erased; + util::Optional m_object_being_created; + + REALM_NORETURN void unsupported_instruction(); // Throws TransformError + TableBehavior select_table(const Descriptor&); + TableBehavior select_table_inner(const Table* table); + bool select_link_list(const LinkView&); // returns true if table behavior != ignored + + TableBehavior get_table_behavior(const Table*) const; + + template + void set(const Table*, size_t row_ndx, size_t col_ndx, T payload, + _impl::Instruction variant); + template + void set_pk(const Table*, size_t row_ndx, size_t col_ndx, T payload, + _impl::Instruction variant); + template + auto as_payload(T value); +}; + +inline void InstructionReplication::set_short_circuit(bool b) noexcept +{ + m_short_circuit = b; +} + +inline bool InstructionReplication::is_short_circuited() const noexcept +{ + return m_short_circuit; +} + +inline ChangesetEncoder& InstructionReplication::get_instruction_encoder() noexcept +{ + return m_encoder; +} + +inline const ChangesetEncoder& InstructionReplication::get_instruction_encoder() const noexcept +{ + return m_encoder; +} + +template +inline void InstructionReplication::emit(T instruction) +{ + REALM_ASSERT(!m_short_circuit); + m_encoder(instruction); +} + +inline auto InstructionReplication::select_table(const Table* table) -> TableBehavior +{ + if (m_selected_table == table) { + return m_selected_table_behavior; + } + return select_table_inner(table); +} + +inline const Table* InstructionReplication::selected_table() const noexcept +{ + return m_selected_table.get(); +} + +// Temporarily short-circuit replication +class TempShortCircuitReplication { +public: + TempShortCircuitReplication(InstructionReplication& bridge): m_bridge(bridge) + { + m_was_short_circuited = bridge.is_short_circuited(); + bridge.set_short_circuit(true); + } + + ~TempShortCircuitReplication() + { + m_bridge.set_short_circuit(m_was_short_circuited); + } + + bool was_short_circuited() const noexcept + { + return m_was_short_circuited; + } +private: + InstructionReplication& m_bridge; + bool m_was_short_circuited; +}; + +} // namespace sync +} // namespace realm + +#endif // REALM_SYNC_IMPL_INSTRUCTION_REPLICATION_HPP diff --git a/!main project/Pods/Realm/include/core/realm/sync/instructions.hpp b/!main project/Pods/Realm/include/core/realm/sync/instructions.hpp new file mode 100644 index 0000000..4c8051a --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/sync/instructions.hpp @@ -0,0 +1,421 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2015] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ + +#ifndef REALM_IMPL_INSTRUCTIONS_HPP +#define REALM_IMPL_INSTRUCTIONS_HPP + +#include +#include +#include // string conversion, debug prints +#include // shared_ptr +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace realm { +namespace sync { + +// CAUTION: Any change to the order or number of instructions is a +// protocol-breaking change! +#define REALM_FOR_EACH_INSTRUCTION_TYPE(X) \ + X(SelectTable) \ + X(SelectField) \ + X(AddTable) \ + X(EraseTable) \ + X(CreateObject) \ + X(EraseObject) \ + X(Set) \ + X(AddInteger) \ + X(InsertSubstring) \ + X(EraseSubstring) \ + X(ClearTable) \ + X(AddColumn) \ + X(EraseColumn) \ + X(ArraySet) \ + X(ArrayInsert) \ + X(ArrayMove) \ + X(ArraySwap) \ + X(ArrayErase) \ + X(ArrayClear) \ + + +enum class ContainerType { + None = 0, + Reserved0 = 1, + Array = 2, + Set = 3, + Dictionary = 4, +}; + +struct Instruction { + // Base classes for instructions with common fields. They enable the merge + // algorithm to reuse some code without resorting to templates, and can be + // combined to allow optimal memory layout of instructions (size <= 64). + struct PayloadInstructionBase; + struct ObjectInstructionBase; + struct FieldInstructionBase; + +#define REALM_DECLARE_INSTRUCTION_STRUCT(X) struct X; + REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_DECLARE_INSTRUCTION_STRUCT) +#undef REALM_DECLARE_INSTRUCTION_STRUCT + + enum class Type: uint8_t { +#define REALM_DEFINE_INSTRUCTION_TYPE(X) X, + REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_DEFINE_INSTRUCTION_TYPE) +#undef REALM_DEFINE_INSTRUCTION_TYPE + }; + + struct Payload; + template struct GetType; + template struct GetInstructionType; + + Instruction() {} + template Instruction(T instr); + + static const size_t max_instruction_size = 64; + std::aligned_storage_t m_storage; + Type type; + + template auto visit(F&& lambda); + template auto visit(F&& lambda) const; + + template T& get_as() + { + REALM_ASSERT(type == GetInstructionType::value); + return *reinterpret_cast(&m_storage); + } + + template const T& get_as() const + { + return const_cast(this)->template get_as(); + } + + bool operator==(const Instruction& other) const noexcept; + + bool operator!=(const Instruction& other) const noexcept + { + return !(*this == other); + } +}; + +// 0x3f is the largest value that fits in a single byte in the variable-length +// encoded integer instruction format. +static constexpr uint8_t InstrTypeInternString = 0x3f; + +// This instruction code is only ever used internally by the Changeset class +// to allow insertion/removal while keeping iterators stable. Should never +// make it onto the wire. +static constexpr uint8_t InstrTypeMultiInstruction = 0xff; + +struct StringBufferRange { + uint32_t offset, size; +}; + +struct InternString { + static const InternString npos; + explicit constexpr InternString(uint32_t v = uint32_t(-1)) noexcept : value(v) {} + + uint32_t value; + + bool operator==(const InternString& other) const noexcept { return value == other.value; } + bool operator<(const InternString& other) const noexcept { return value < other.value; } + + explicit operator bool() const noexcept { return (value != npos.value); } +}; + +struct Instruction::Payload { + struct Link { + sync::ObjectID target; // can be nothing = null + InternString target_table; + }; + + union Data { + bool boolean; + int64_t integer; + float fnum; + double dnum; + StringBufferRange str; + Timestamp timestamp; + Link link; + + Data() noexcept {} + Data(const Data&) noexcept = default; + Data& operator=(const Data&) noexcept = default; + }; + Data data; + int8_t type; // -1 = null, -2 = implicit_nullify + + Payload(): Payload(realm::util::none) {} + explicit Payload(bool value) noexcept: type(type_Bool) { data.boolean = value; } + explicit Payload(int64_t value) noexcept: type(type_Int) { data.integer = value; } + explicit Payload(float value) noexcept: type(type_Float) { data.fnum = value; } + explicit Payload(double value) noexcept: type(type_Double) { data.dnum = value; } + explicit Payload(Link value) noexcept: type(type_Link) { data.link = value; } + explicit Payload(StringBufferRange value) noexcept: type(type_String) { data.str = value; } + explicit Payload(realm::util::None, bool implicit_null = false) noexcept { + type = (implicit_null ? -2 : -1); + } + explicit Payload(Timestamp value) noexcept: type(value.is_null() ? -1 : type_Timestamp) + { + data.timestamp = value; + } + + Payload(const Payload&) noexcept = default; + Payload& operator=(const Payload&) noexcept = default; + + bool is_null() const; + bool is_implicit_null() const; +}; + +struct Instruction::ObjectInstructionBase { + sync::ObjectID object; +}; + +struct Instruction::FieldInstructionBase + : Instruction::ObjectInstructionBase +{ + InternString field; +}; + +struct Instruction::PayloadInstructionBase { + Payload payload; +}; + + +struct Instruction::SelectTable { + InternString table; +}; + +struct Instruction::SelectField + : Instruction::FieldInstructionBase +{ + InternString link_target_table; +}; + +struct Instruction::AddTable { + InternString table; + InternString primary_key_field; + DataType primary_key_type; + bool has_primary_key; + bool primary_key_nullable; +}; + +struct Instruction::EraseTable { + InternString table; +}; + +struct Instruction::CreateObject + : Instruction::PayloadInstructionBase + , Instruction::ObjectInstructionBase +{ + bool has_primary_key; +}; + +struct Instruction::EraseObject + : Instruction::ObjectInstructionBase +{}; + +struct Instruction::Set + : Instruction::PayloadInstructionBase + , Instruction::FieldInstructionBase +{ + bool is_default; +}; + +struct Instruction::AddInteger + : Instruction::FieldInstructionBase +{ + int64_t value; +}; + +struct Instruction::InsertSubstring + : Instruction::FieldInstructionBase +{ + StringBufferRange value; + uint32_t pos; +}; + +struct Instruction::EraseSubstring + : Instruction::FieldInstructionBase +{ + uint32_t pos; + uint32_t size; +}; + +struct Instruction::ClearTable { +}; + +struct Instruction::ArraySet { + Instruction::Payload payload; + uint32_t ndx; + uint32_t prior_size; +}; + +struct Instruction::ArrayInsert { + // payload carries the value in case of LinkList + // payload is empty in case of Array, Dict or any other container type + Instruction::Payload payload; + uint32_t ndx; + uint32_t prior_size; +}; + +struct Instruction::ArrayMove { + uint32_t ndx_1; + uint32_t ndx_2; +}; + +struct Instruction::ArrayErase { + uint32_t ndx; + uint32_t prior_size; + bool implicit_nullify; +}; + +struct Instruction::ArraySwap { + uint32_t ndx_1; + uint32_t ndx_2; +}; + +struct Instruction::ArrayClear { + uint32_t prior_size; +}; + + +// If container_type != ContainerType::none, creates a subtable: +// +---+---+-------+ +// | a | b | c | +// +---+---+-------+ +// | | | +---+ | +// | | | | v | | +// | | | +---+ | +// | 1 | 2 | | 3 | | +// | | | | 4 | | +// | | | | 5 | | +// | | | +---+ | +// +---+---+-------+ +struct Instruction::AddColumn { + InternString field; + InternString link_target_table; + DataType type; + ContainerType container_type; + bool nullable; +}; + +struct Instruction::EraseColumn { + InternString field; +}; + +struct InstructionHandler { + /// Notify the handler that an InternString meta-instruction was found. + virtual void set_intern_string(uint32_t index, StringBufferRange) = 0; + + /// Notify the handler of the string value. The handler guarantees that the + /// returned string range is valid at least until the next invocation of + /// add_string_range(). + /// + /// Instances of `StringBufferRange` passed to operator() after invoking + /// this function are assumed to refer to ranges in this buffer. + virtual StringBufferRange add_string_range(StringData) = 0; + + /// Handle an instruction. + virtual void operator()(const Instruction&) = 0; +}; + + +/// Implementation: + +#define REALM_DEFINE_INSTRUCTION_GET_TYPE(X) \ + template <> struct Instruction::GetType { using Type = Instruction::X; }; \ + template <> struct Instruction::GetInstructionType { static const Instruction::Type value = Instruction::Type::X; }; + REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_DEFINE_INSTRUCTION_GET_TYPE) +#undef REALM_DEFINE_INSTRUCTION_GET_TYPE + +template +Instruction::Instruction(T instr) : type(GetInstructionType::value) +{ + new(&m_storage) T(std::move(instr)); +} + +template +inline auto Instruction::visit(F&& lambda) +{ + switch (type) { +#define REALM_VISIT_INSTRUCTION(X) \ + case Type::X: \ + return lambda(get_as()); + REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_VISIT_INSTRUCTION) +#undef REALM_VISIT_INSTRUCTION + } + REALM_UNREACHABLE(); +} + +template +inline auto Instruction::visit(F&& lambda) const +{ + switch (type) { +#define REALM_VISIT_INSTRUCTION(X) \ + case Type::X: \ + return lambda(get_as()); + REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_VISIT_INSTRUCTION) +#undef REALM_VISIT_INSTRUCTION + } + REALM_UNREACHABLE(); +} + +inline bool Instruction::operator==(const Instruction& other) const noexcept +{ + if (type != other.type) + return false; + size_t valid_size; + switch (type) { +#define REALM_COMPARE_INSTRUCTION(X) \ + case Type::X: valid_size = sizeof(Instruction::X); break; + REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_COMPARE_INSTRUCTION) +#undef REALM_COMPARE_INSTRUCTION + default: REALM_UNREACHABLE(); + } + + // This relies on all instruction types being PODs to work. + return std::memcmp(&m_storage, &other.m_storage, valid_size) == 0; +} + +inline bool Instruction::Payload::is_null() const +{ + return type < 0; +} + +inline bool Instruction::Payload::is_implicit_null() const +{ + return type == -2; +} + +std::ostream& operator<<(std::ostream&, Instruction::Type); + +} // namespace _impl +} // namespace realm + +#endif // REALM_IMPL_INSTRUCTIONS_HPP diff --git a/!main project/Pods/Realm/include/core/realm/sync/object.hpp b/!main project/Pods/Realm/include/core/realm/sync/object.hpp new file mode 100644 index 0000000..8529fa0 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/sync/object.hpp @@ -0,0 +1,262 @@ +/************************************************************************* + * + * Copyright 2017 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_SYNC_OBJECT_HPP +#define REALM_SYNC_OBJECT_HPP + +#include +#include +#include +#include + +#include + +#include + +/// This file presents a convenience API for making changes to a Realm file that +/// adhere to the conventions of assigning stable IDs to every object. + +namespace realm { + +class Group; +class ReadTransaction; +class WriteTransaction; + +namespace sync { + +class SyncHistory; + +extern const char object_id_column_name[]; // "!OID" +extern const char array_value_column_name[]; // "!ARRAY_VALUE" + +struct TableInfoCache; + +/// Determine whether the Group has a sync-type history, and therefore whether +/// it supports globally stable object IDs. +/// +/// The Group does not need to be in a transaction. +bool has_object_ids(const Group&); + +/// Determine whether object IDs for objects without primary keys are globally +/// stable. This is true if and only if the Group has been in touch with the +/// server (or is the server), and will remain true forever thereafter. +/// +/// It is an error to call this function for groups that do not have object IDs +/// (i.e. where `has_object_ids()` returns false). +/// +/// The Group is assumed to be in a read transaction. +bool is_object_id_stability_achieved(const Group&); + +/// Create a table with an object ID column. +/// +/// It is an error to add tables to Groups with a sync history type directly. +/// This function or related functions must be used instead. +/// +/// The resulting table will be born with 1 column, which is a column used +/// in the maintenance of object IDs. +/// +/// NOTE: The table name must begin with the prefix "class_" in accordance with +/// Object Store conventions. +/// +/// The Group must be in a write transaction. +TableRef create_table(Group&, StringData name); + +/// Create a table with an object ID column and a primary key column. +/// +/// It is an error to add tables to Groups with a sync history type directly. +/// This function or related functions must be used instead. +/// +/// The resulting table will be born with 2 columns, which is a column used +/// in the maintenance of object IDs and the requested primary key column. +/// The primary key column must have either integer or string type, and it +/// will be given the name provided in the argument \a pk_column_name. +/// +/// The 'pk' metadata table is updated with information about the primary key +/// column. If the 'pk' table does not yet exist, it is created. +/// +/// Please note: The 'pk' metadata table will not be synchronized directly, +/// so subsequent updates to it will be lost (as they constitute schema-breaking +/// changes). +/// +/// NOTE: The table name must begin with the prefix "class_" in accordance with +/// Object Store conventions. +/// +/// The Group must be in a write transaction. +TableRef create_table_with_primary_key(Group&, StringData name, DataType pk_type, + StringData pk_column_name, bool nullable = false); + + +//@{ +/// Erase table and update metadata. +/// +/// It is an error to erase tables via the Group API, because it does not +/// correctly update metadata tables (such as the `pk` table). +void erase_table(Group& g, TableInfoCache& table_info_cache, StringData name); +void erase_table(Group& g, TableInfoCache& table_info_cache, TableRef); +//@} + +/// Create an array column with the specified element type. +/// +/// The result will be a column of type type_Table with one subcolumn named +/// "!ARRAY_VALUE" of the specified element type and nullability. +/// +/// Return the column index of the inserted array column. +size_t add_array_column(Table&, DataType element_type, StringData column_name, bool is_nullable = false); + + +//@{ +/// Calculate the object ID from the argument, where the argument is a primary +/// key value. +ObjectID object_id_for_primary_key(StringData); +ObjectID object_id_for_primary_key(util::Optional); +//@} + +/// Determine whether it is safe to call `object_id_for_row()` on tables without +/// primary keys. If the table has a primary key, always returns true. +bool has_globally_stable_object_ids(const Table&); + +bool table_has_primary_key(const TableInfoCache&, const Table&); + +/// Get the globally unique object ID for the row. +/// +/// If the table has a primary key, this is guaranteed to succeed. Otherwise, if +/// the server has not been contacted yet (`has_globally_stable_object_ids()` +/// returns false), an exception is thrown. +ObjectID object_id_for_row(const TableInfoCache&, const Table&, size_t); + +/// Get the index of the row with the object ID. +/// +/// \returns realm::npos if the object does not exist in the table. +size_t row_for_object_id(const TableInfoCache&, const Table&, ObjectID); + +//@{ +/// Add a row to the table and populate the object ID with an appropriate value. +/// +/// In the variant which takes an ObjectID parameter, a check is performed to see +/// if the object already exists. If it does, the row index of the existing object +/// is returned. +/// +/// If the table has a primary key column, an exception is thrown. +/// +/// \returns the row index of the object. +size_t create_object(const TableInfoCache&, Table&); +size_t create_object(const TableInfoCache&, Table&, ObjectID); +//@} + +//@{ +/// Create an object with a primary key value and populate the object ID with an +/// appropriate value. +/// +/// If the table does not have a primary key column (as indicated by the Object +/// Store's metadata in the special "pk" table), or the type of the primary key +/// column does not match the argument provided, an exception is thrown. +/// +/// The primary key column's value is populated with the appropriate +/// `set_int_unique()`, `set_string_unique()`, or `set_null_unique()` method +/// called on \a table. +/// +/// If an object with the given primary key value already exists, its row number +/// is returned without creating any new objects. +/// +/// These are convenience functions, equivalent to the following: +/// - Add an empty row to the table. +/// - Obtain an `ObjectID` with `object_id_for_primary_key()`. +/// - Obtain a local object ID with `global_to_local_object_id()`. +/// - Store the local object ID in the object ID column. +/// - Call `set_int_unique()`,`set_string_unique()`, or `set_null_unique()` +/// to set the primary key value. +/// +/// \returns the row index of the created object. +size_t create_object_with_primary_key(const TableInfoCache&, Table&, util::Optional primary_key); +size_t create_object_with_primary_key(const TableInfoCache&, Table&, StringData primary_key); +//@} + +struct TableInfoCache { + const Group& m_group; + + explicit TableInfoCache(const ReadTransaction&); + explicit TableInfoCache(const WriteTransaction&); + + // Implicit conversion deliberately allowed for the purpose of calling the above + // functions without constructing a cache manually. + TableInfoCache(const Group&); + TableInfoCache(TableInfoCache&&) noexcept = default; + + struct TableInfo { + struct VTable; + + StringData name; + const VTable* vtable; + size_t object_id_index = size_t(-1); + size_t primary_key_index; + DataType primary_key_type = DataType(-1); + bool primary_key_nullable = false; + mutable size_t last_row_index = size_t(-1); + mutable ObjectID last_object_id; + + void clear_last_object() const + { + last_row_index = size_t(-1); + last_object_id = {}; + } + }; + + mutable std::vector> m_table_info; + + const TableInfo& get_table_info(const Table&) const; + const TableInfo& get_table_info(size_t table_index) const; + void clear(); + void clear_last_object(const Table&); + void verify(); +}; + + +/// Migrate a server-side Realm file whose history type is +/// `Replication::hist_SyncServer` and whose history schema version is 0 (i.e., +/// Realm files without stable identifiers). +void import_from_legacy_format(const Group& old_group, Group& new_group, util::Logger&); + +using TableNameBuffer = std::array; +StringData table_name_to_class_name(StringData); +StringData class_name_to_table_name(StringData, TableNameBuffer&); + + +// Implementation: + +inline StringData table_name_to_class_name(StringData table_name) +{ + REALM_ASSERT(table_name.begins_with("class_")); + return table_name.substr(6); +} + + +inline StringData class_name_to_table_name(StringData class_name, TableNameBuffer& buffer) +{ + constexpr const char class_prefix[] = "class_"; + constexpr size_t class_prefix_len = sizeof(class_prefix) - 1; + char* p = std::copy_n(class_prefix, class_prefix_len, buffer.data()); + size_t len = std::min(class_name.size(), buffer.size() - class_prefix_len); + std::copy_n(class_name.data(), len, p); + return StringData(buffer.data(), class_prefix_len + len); +} + +} // namespace sync +} // namespace realm + +#endif // REALM_SYNC_OBJECT_HPP + diff --git a/!main project/Pods/Realm/include/core/realm/sync/object_id.hpp b/!main project/Pods/Realm/include/core/realm/sync/object_id.hpp new file mode 100644 index 0000000..e3e96e0 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/sync/object_id.hpp @@ -0,0 +1,312 @@ +/************************************************************************* + * + * Copyright 2017 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_SYNC_OBJECT_ID_HPP +#define REALM_SYNC_OBJECT_ID_HPP + +#include // std::hash +#include +#include // operator<< +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +// Only set this to one when testing the code paths that exercise object ID +// hash collisions. It artificially limits the "optimistic" local ID to use +// only the lower 15 bits of the ID rather than the lower 63 bits, making it +// feasible to generate collisions within reasonable time. +#define REALM_EXERCISE_OBJECT_ID_COLLISION 0 + +namespace realm { + +class Group; + +namespace sync { + +/// ObjectIDs are globally unique for a given class (table), and up to 128 bits +/// wide. They are represented as two 64-bit integers, each of which may +/// frequently be small, for best on-wire compressibility. +struct ObjectID { + constexpr ObjectID(uint64_t hi, uint64_t lo); + static ObjectID from_string(StringData); + static bool from_string(StringData, ObjectID&) noexcept; + + // FIXME: Remove "empty" ObjectIDs, wrap in Optional instead. + constexpr ObjectID(realm::util::None = realm::util::none); + constexpr ObjectID(const ObjectID&) noexcept = default; + ObjectID& operator=(const ObjectID&) noexcept = default; + + constexpr uint64_t lo() const { return m_lo; } + constexpr uint64_t hi() const { return m_hi; } + + std::string to_string() const; + + constexpr bool operator<(const ObjectID& other) const; + constexpr bool operator==(const ObjectID& other) const; + constexpr bool operator!=(const ObjectID& other) const; + + explicit constexpr operator bool() const noexcept; + +private: + uint64_t m_lo; + uint64_t m_hi; +}; + +/// Implementors of this interface should define a way to map from 128-bit +/// on-write ObjectIDs to local 64-bit object IDs. +/// +/// The three object ID types are: +/// a. Object IDs for objects in tables without primary keys. +/// b. Object IDs for objects in tables with integer primary keys. +/// c. Object IDs for objects in tables with other primary key types. +/// +/// For integer primary keys (b), the Object ID is just the integer value. +/// +/// For objects without primary keys (a), a "squeezed" tuple of the +/// client_file_ident and a peer-local sequence number is used as the local +/// Object ID. The on-write Object ID is the "unsqueezed" format. The methods on +/// this interface ending in "_squeezed" aid in the creation and conversion of +/// these IDs. +/// +/// For objects with other types of primary keys (c), the ObjectID +/// is a 128-bit hash of the primary key value. However, the local object ID +/// must be a 64-bit integer, because that is the maximum size integer that +/// Realm is able to store. The solution is to optimistically use the lower 63 +/// bits of the on-wire Object ID, and use a local ID with the upper 64th bit +/// set when there is a collision in the lower 63 bits between two different +/// hash values. +class ObjectIDProvider { +public: + using LocalObjectID = int_fast64_t; + + /// Calculate optimistic local ID that may collide with others. It is up to + /// the caller to ensure that collisions are detected and that + /// allocate_local_id_after_collision() is called to obtain a non-colliding + /// ID. + static LocalObjectID get_optimistic_local_id_hashed(ObjectID global_id); + + /// Find the local 64-bit object ID for the provided global 128-bit ID. + virtual LocalObjectID global_to_local_object_id_hashed(size_t table_ndx, ObjectID global_id) const = 0; + + /// After a local ID collision has been detected, this function may be + /// called to obtain a non-colliding local ID in such a way that subsequence + /// calls to global_to_local_object_id() will return the correct local ID + /// for both \a incoming_id and \a colliding_id. + virtual LocalObjectID allocate_local_id_after_hash_collision(size_t table_ndx, + ObjectID incoming_id, + ObjectID colliding_id, + LocalObjectID colliding_local_id) = 0; + static LocalObjectID make_tagged_local_id_after_hash_collision(uint64_t sequence_number); + virtual void free_local_id_after_hash_collision(size_t table_ndx, ObjectID object_id) = 0; + + /// Some Object IDs are generated as a tuple of the client_file_ident and a + /// local sequence number. This function takes the next number in the + /// sequence for the given table and returns an appropriate globally unique + /// ObjectID. + virtual ObjectID allocate_object_id_squeezed(size_t table_ndx) = 0; + static LocalObjectID global_to_local_object_id_squeezed(ObjectID); + static ObjectID local_to_global_object_id_squeezed(LocalObjectID); + + virtual void table_erased(size_t table_ndx) = 0; + + virtual int_fast64_t get_client_file_ident() const = 0; +}; + +// ObjectIDSet is a set of (table name, object id) +class ObjectIDSet { +public: + + void insert(StringData table, ObjectID object_id); + void erase(StringData table, ObjectID object_id); + bool contains(StringData table, ObjectID object_id) const noexcept; + bool empty() const noexcept; + + // A map from table name to a set of object ids. + util::metered::map> m_objects; +}; + +// FieldSet is a set of fields in tables. A field is defined by a +// table name, a column in the table and an object id for the row. +class FieldSet { +public: + + void insert(StringData table, StringData column, ObjectID object_id); + void erase(StringData table, StringData column, ObjectID object_id); + bool contains(StringData table, ObjectID object_id) const noexcept; + bool contains(StringData table, StringData column, ObjectID object_id) const noexcept; + bool empty() const noexcept; + + // A map from table name to a map from column name to a set of + // object ids. + util::metered::map< + std::string, + util::metered::map> + > m_fields; +}; + +struct GlobalID { + StringData table_name; + ObjectID object_id; + + bool operator==(const GlobalID& other) const; + bool operator!=(const GlobalID& other) const; + bool operator<(const GlobalID& other) const; +}; + + + + +/// Implementation + +constexpr ObjectID::ObjectID(uint64_t hi, uint64_t lo) : m_lo(lo), m_hi(hi) +{ +} + +constexpr ObjectID::ObjectID(realm::util::None) : m_lo(-1), m_hi(-1) +{ +} + +constexpr bool ObjectID::operator<(const ObjectID& other) const +{ + return (m_hi == other.m_hi) ? (m_lo < other.m_lo) : (m_hi < other.m_hi); +} + +constexpr bool ObjectID::operator==(const ObjectID& other) const +{ + return m_hi == other.m_hi && m_lo == other.m_lo; +} + +constexpr bool ObjectID::operator!=(const ObjectID& other) const +{ + return !(*this == other); +} + +constexpr ObjectID::operator bool() const noexcept +{ + return (*this != ObjectID{}); +} + +inline bool GlobalID::operator==(const GlobalID& other) const +{ + return object_id == other.object_id && table_name == other.table_name; +} + +inline bool GlobalID::operator!=(const GlobalID& other) const +{ + return !(*this == other); +} + +inline bool GlobalID::operator<(const GlobalID& other) const +{ + if (table_name == other.table_name) + return object_id < other.object_id; + return table_name < other.table_name; +} + + +std::ostream& operator<<(std::ostream&, const realm::sync::ObjectID&); +std::istream& operator>>(std::istream&, realm::sync::ObjectID&); + +inline ObjectIDProvider::LocalObjectID +ObjectIDProvider::get_optimistic_local_id_hashed(ObjectID global_id) +{ +#if REALM_EXERCISE_OBJECT_ID_COLLISION + const uint64_t optimistic_mask = 0xff; +#else + const uint64_t optimistic_mask = 0x7fffffffffffffff; +#endif + static_assert(optimistic_mask < 0x8000000000000000, "optimistic Object ID mask must leave the 64th bit zero"); + return global_id.lo() & optimistic_mask; +} + +inline ObjectIDProvider::LocalObjectID +ObjectIDProvider::make_tagged_local_id_after_hash_collision(uint64_t sequence_number) +{ + REALM_ASSERT(sequence_number < 0x8000000000000000); + return 0x8000000000000000 | sequence_number; +} + +inline ObjectIDProvider::LocalObjectID +ObjectIDProvider::global_to_local_object_id_squeezed(ObjectID object_id) +{ + REALM_ASSERT(object_id.hi() <= std::numeric_limits::max()); + REALM_ASSERT(object_id.lo() <= std::numeric_limits::max()); + + uint64_t a = object_id.lo() & 0xff; + uint64_t b = (object_id.hi() & 0xff) << 8; + uint64_t c = (object_id.lo() & 0xffffff00) << 8; + uint64_t d = (object_id.hi() & 0xffffff00) << 32; + union { + uint64_t u; + int64_t s; + } bitcast; + bitcast.u = a | b | c | d; + return bitcast.s; +} + +inline ObjectID +ObjectIDProvider::local_to_global_object_id_squeezed(LocalObjectID squeezed) +{ + union { + uint64_t u; + int64_t s; + } bitcast; + bitcast.s = squeezed; + + uint64_t u = bitcast.u; + + uint64_t lo = (u & 0xff) | ((u & 0xffffff0000) >> 8); + uint64_t hi = ((u & 0xff00) >> 8) | ((u & 0xffffff0000000000) >> 32); + return ObjectID{hi, lo}; +} + +inline bool ObjectIDSet::empty() const noexcept +{ + return m_objects.empty(); +} + +inline bool FieldSet::empty() const noexcept +{ + return m_fields.empty(); +} + +} // namespace sync +} // namespace realm + +namespace std { + +template <> +struct hash { + size_t operator()(realm::sync::ObjectID oid) const + { + return std::hash{}(oid.lo()) ^ std::hash{}(oid.hi()); + } +}; + +} // namespace std + +#endif // REALM_SYNC_OBJECT_ID_HPP + diff --git a/!main project/Pods/Realm/include/core/realm/sync/permissions.hpp b/!main project/Pods/Realm/include/core/realm/sync/permissions.hpp new file mode 100644 index 0000000..073f2d6 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/sync/permissions.hpp @@ -0,0 +1,446 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2015] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ + +#ifndef REALM_SYNC_PERMISSIONS_HPP +#define REALM_SYNC_PERMISSIONS_HPP + +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace realm { +namespace sync { + +/// Permissions Schema: +/// +/// class___Role: +/// string name PRIMARY_KEY; +/// User[] members; +/// +/// class___Permission: +/// __Role role; +/// bool canRead; +/// bool canUpdate; +/// bool canDelete; +/// bool canSetPermissions; +/// bool canQuery; +/// bool canCreate; +/// bool canModifySchema; +/// +/// class___Realm: +/// int id PRIMARY_KEY = 0; // singleton object +/// __Permission[] permissions; +/// +/// class___User: +/// string id PRIMARY_KEY; +/// __Role role; +/// +/// class___Class: +/// string name PRIMARY_KEY; +/// __Permission[] permissions; +/// +/// class_: +/// __Permission[] ; +/// __Role ; +/// + +static constexpr char g_roles_table_name[] = "class___Role"; +static constexpr char g_permissions_table_name[] = "class___Permission"; +static constexpr char g_users_table_name[] = "class___User"; +static constexpr char g_classes_table_name[] = "class___Class"; +static constexpr char g_realms_table_name[] = "class___Realm"; + + +/// Create the permissions schema if it doesn't already exist. +void create_permissions_schema(Group&); + +/// Set up the basic "everyone" role and default permissions. The default is to +/// set up some very permissive defaults, where "everyone" can do everything. +void set_up_basic_permissions(Group& group, TableInfoCache& table_info_cache, bool permissive = true); +// Convenience function that creates a new TableInfoCache. +void set_up_basic_permissions(Group& group, bool permissive = true); + +/// Set up some basic permissions for the class. The default is to set up some +/// very permissive default, where "everyone" can do everything in the class. +void set_up_basic_permissions_for_class(Group&, StringData class_name, bool permissive = true); +// void set_up_basic_default_permissions_for_class(Group&, TableRef klass, bool permissive = true); + +/// Return the index of the ACL in the class, if one exists. If no ACL column is +/// defined in the class, returns `npos`. +size_t find_permissions_column(const Group&, ConstTableRef); + +//@{ +/// Convenience functions to check permisions data +/// The functions must be called inside a read (or write) transaction. +bool permissions_schema_exist(const Group&); + +bool user_exist(const Group&, StringData user_id); +//@} + + +/// Perform a query as user \a user_id, returning only the results that the +/// user has access to read. If the user is an admin, there is no need to call +/// this function, since admins can always read everything. +/// +/// If the target table of the query does not have object-level permissions, +/// the query results will be returned without any additional filtering. +/// +/// If the target table of the query has object-level permissions, but the +/// permissions schema of this Realm is invalid, an exception of type +/// `InvalidPermissionsSchema` is thrown. +/// +/// LIMIT and DISTINCT will be applied *after* permission filters. +/// +/// The resulting TableView can be used like any other query result. +/// +/// Note: Class-level and Realm-level permissions are not taken into account in +/// the resulting TableView, since there is no way to represent this in the +/// query engine. +ConstTableView query_with_permissions(Query query, StringData user_id, + const DescriptorOrdering* ordering = nullptr); + +struct InvalidPermissionsSchema : util::runtime_error { + using util::runtime_error::runtime_error; +}; + +//@{ +/// Convenience function to modify permission data. +/// +/// When a role or user has not already been defined in the Realm, these +/// functions create them on-demand. +void set_realm_permissions_for_role(Group&, StringData role_name, + uint_least32_t privileges); +void set_class_permissions_for_role(Group&, StringData class_name, + StringData role_name, uint_least32_t privileges); +// void set_default_object_permissions_for_role(Group&, StringData class_name, +// StringData role_name, +// uint_least32_t privileges); +void set_object_permissions_for_role(Group&, TableRef table, size_t row_ndx, + StringData role_name, uint_least32_t privileges); + +void add_user_to_role(Group&, StringData user_id, StringData role_name); +//@} + +/// The Privilege enum is intended to be used in a bitfield. +enum class Privilege : uint_least32_t { + None = 0, + + /// The user can read the object (i.e. it can participate in the user's + /// subscription. + /// + /// NOTE: On objects, it is a prerequisite that the object's class is also + /// readable by the user. + /// + /// FIXME: Until we get asynchronous links, any object that is reachable + /// through links from another readable/queryable object is also readable, + /// regardless of whether the user specifically does not have read access. + Read = 1, + + /// The user can modify the fields of the object. + /// + /// NOTE: On objects, it is a prerequisite that the object's class is also + /// updatable by the user. When applied to a Class object, it does not + /// imply that the user can modify the schema of the class, only the + /// objects of that class. + /// + /// NOTE: This does not imply the SetPermissions privilege. + Update = 2, + + /// The user can delete the object. + /// + /// NOTE: When applied to a Class object, it has no effect on whether + /// objects of that class can be deleted by the user. + /// + /// NOTE: This implies the ability to implicitly nullify links pointing + /// to the object from other objects, even if the user does not have + /// permission to modify those objects in the normal way. + Delete = 4, + + //@{ + /// The user can modify the object's permissions. + /// + /// NOTE: The user will only be allowed to assign permissions at or below + /// their own privilege level. + SetPermissions = 8, + Share = SetPermissions, + //@} + + /// When applied to a Class object, the user can query objects in that + /// class. + /// + /// Has no effect when applied to objects other than Class. + Query = 16, + + /// When applied to a Class object, the user may create objects in that + /// class. + /// + /// NOTE: The user implicitly has Update and SetPermissions + /// (but not necessarily Delete permission) within the same + /// transaction as the object was created. + /// + /// NOTE: Even when a user has CreateObject rights, a CreateObject + /// operation may still be rejected by the server, if the object has a + /// primary key and the object already exists, but is not accessible by the + /// user. + Create = 32, + + /// When applied as a "Realm" privilege, the user can add classes and add + /// columns to classes. + /// + /// NOTE: When applied to a class or object, this has no effect. + ModifySchema = 64, + + /// + /// Aggregate permissions for compatibility: + /// + Download = Read | Query, + Upload = Update | Delete | Create, + DeleteRealm = Upload, // FIXME: This seems overly permissive +}; + +inline constexpr uint_least32_t operator|(Privilege a, Privilege b) +{ + return static_cast(a) | static_cast(b); +} + +inline constexpr uint_least32_t operator|(uint_least32_t a, Privilege b) +{ + return a | static_cast(b); +} + +inline constexpr uint_least32_t operator&(Privilege a, Privilege b) +{ + return static_cast(a) & static_cast(b); +} + +inline constexpr uint_least32_t operator&(uint_least32_t a, Privilege b) +{ + return a & static_cast(b); +} + +inline uint_least32_t& operator|=(uint_least32_t& a, Privilege b) +{ + return a |= static_cast(b); +} + +inline constexpr uint_least32_t operator~(Privilege p) +{ + return ~static_cast(p); +} + +struct PermissionsCache { + /// Each element is the index of a row in the `class___Roles` table. + using RoleList = std::vector; + + PermissionsCache(const Group& g, TableInfoCache& table_info_cache, + StringData user_identity, bool is_admin = false); + + bool is_admin() const noexcept; + + /// Leaves out any role that has no permission objects linking to it. + RoleList get_users_list_of_roles(); + + /// Get Realm-level privileges for the current user. + /// + /// The user must have Read access at the Realm level to be able to see + /// anything in the file. + /// + /// The user must have Update access at the Realm level to be able to make + /// any changes at all in the Realm file. + /// + /// If no Realm-level permissions are defined, no access is granted for any + /// user. + uint_least32_t get_realm_privileges(); + + /// Get class-level privileges for the current user and the given class. + /// + /// If the class does not have any class-level privileges defined, no access + /// is granted to the class. + /// + /// Calling this function is equivalent to calling `get_object_privileges()` + /// with an object of the type `__Class`. + /// + /// NOTE: This function only considers class-level permissions. It does not + /// mask the returned value by the Realm-level permissions. See `can()`. + uint_least32_t get_class_privileges(StringData class_name); + + /// Get object-level privileges for the current user and the given object. + /// + /// If the object's class has an ACL property (a linklist to the + /// `__Permission` class), and it isn't empty, the user's privileges is the + /// OR'ed privileges for the intersection of roles that have a defined + /// permission on the object and the roles of which the user is a member. + /// + /// If the object's ACL property is empty (but the column exists), no access + /// is granted to anyone. + /// + /// If the object does not exist in the table, the returned value is + /// equivalent to that of an object with an empty ACL property, i.e. no + /// privileges are granted. Note that the existence of the column is checked + /// first, so an absent ACL property (granting all privileges) takes + /// precedence over an absent object (granting no privileges) in terms of + /// calculating permissions. + /// + /// NOTE: This function only considers object-level permissions (per-object + /// ACLs or default object permissions). It does not mask the returned value + /// by the object's class-level permissions, or by the Realm-level + /// permissions. See `can()`. + uint_least32_t get_object_privileges(GlobalID); + + /// Get object-level privileges without adding it to the cache. + uint_least32_t get_object_privileges_nocache(GlobalID); + + //@{ + /// Check permissions for the object, taking all levels of permission into + /// account. + /// + /// This method only returns `true` if the user has Realm-level access to + /// the object, class-level access to the object, and object-level access to + /// the object. + /// + /// In the version where the first argument is a mask of privileges, the + /// method only returns `true` when all privileges are satisfied. + bool can(Privilege privilege, GlobalID object_id); + bool can(uint_least32_t privileges, GlobalID object_id); + //@} + + /// Invalidate all cache entries pertaining to the object. + /// + /// The object may be an instance of `__Class`. + void object_permissions_modified(GlobalID); + + /// Register the object as created in this transaction, meaning that the + /// user gets full privileges until the end of the transaction. + void object_created(GlobalID); + + /// Invalidate all cache entries pertaining to the class. + // void default_object_permissions_modified(StringData class_name); + + /// Invalidate all cached permissions. + void clear(); + + /// Check that all cache permissions correspond to the current permission + /// state in the database. + void verify(); + +private: + const Group& group; + TableInfoCache& m_table_info_cache; + std::string user_id; + bool m_is_admin; + util::Optional realm_privileges; + util::metered::map object_privileges; + ObjectIDSet created_objects; + + // uint_least32_t get_default_object_privileges(ConstTableRef); + uint_least32_t get_privileges_for_permissions(ConstLinkViewRef); + friend struct InstructionApplierWithPermissionCheck; +}; + +inline bool PermissionsCache::is_admin() const noexcept +{ + return m_is_admin; +} + +/// PermissionCorrections is a struct that describes some changes that must be +/// sent to the client because the client tried to perform changes to a database +/// that it wasn't allowed to make. +struct PermissionCorrections { + using TableColumnSet = util::metered::map>; + using TableSet = util::metered::set; + + // Objects that a client tried to delete without being allowed. + ObjectIDSet recreate_objects; + + // Objects that a client tried to create without being allowed. + ObjectIDSet erase_objects; + + // Fields that were illegally modified by the client and must be reset. + // + // Objects mentioned in `recreate_objects` and `erase_objects` are not + // mentioned here. + FieldSet reset_fields; + + // Columns that were illegally added by the client. + TableColumnSet erase_columns; + + // Columns that were illegally removed by the client. + TableColumnSet recreate_columns; + + // Tables that were illegally added by the client. + // std::set erase_tables; + TableSet erase_tables; + + // Tables that were illegally removed by the client. + TableSet recreate_tables; + + bool empty() const noexcept; +}; + +// Function for printing out a permission correction object. Useful for debugging purposes. +std::ostream& operator<<(std::ostream&, const PermissionCorrections&); + + + +/// InstructionApplierWithPermissionCheck conditionally applies each +/// instruction, and builds a `PermissionCorrections` struct based on the +/// illicit changes. The member `m_corrections` can be used to synthesize a +/// changeset that can be sent to the client to revert the illicit changes that +/// were detected by the applier. +struct InstructionApplierWithPermissionCheck { + explicit InstructionApplierWithPermissionCheck(Group& reference_realm, + bool is_admin, + StringData user_identity); + ~InstructionApplierWithPermissionCheck(); + + /// Apply \a incoming_changeset, checking permissions in the process. + /// Populates `m_corrections`. + void apply(const Changeset& incoming_changeset, util::Logger*); + + PermissionCorrections m_corrections; + +private: + struct Impl; + std::unique_ptr m_impl; +}; + + +// Implementation: + +inline bool PermissionCorrections::empty() const noexcept +{ + return recreate_objects.empty() && erase_objects.empty() + && reset_fields.empty() && erase_columns.empty() + && recreate_columns.empty() && erase_tables.empty() + && recreate_tables.empty(); +} + +} // namespace sync +} // namespace realm + + +#endif // REALM_SYNC_PERMISSIONS_HPP diff --git a/!main project/Pods/Realm/include/core/realm/sync/protocol.hpp b/!main project/Pods/Realm/include/core/realm/sync/protocol.hpp new file mode 100644 index 0000000..1178218 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/sync/protocol.hpp @@ -0,0 +1,439 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2016] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ +#ifndef REALM_SYNC_PROTOCOL_HPP +#define REALM_SYNC_PROTOCOL_HPP + +#include +#include + +#include + + + +// NOTE: The protocol specification is in `/doc/protocol.md` + + +namespace realm { +namespace sync { + +// Protocol versions: +// +// 1 Initial version. +// +// 2 Introduces the UNBOUND message (sent from server to client in +// response to a BIND message). +// +// 3 Introduces the ERROR message (sent from server to client before the +// server closes a connection). Introduces MARK message from client to +// server, and MARK response message from server to client as a way for the +// client to wait for download to complete. +// +// 4 User token and signature are now passed as a single string (see +// /doc/protocol.md for details). Also, `application_ident` parameter +// removed from IDENT message. +// +// 5 IDENT message renamed to CLIENT, and ALLOC message (client->server) +// renamed to IDENT. Also, parameter added to CLIENT +// message. Also, the protocol has been changed to make the clients +// acquisition of a server allocated file identifier pair be part of a +// session from the servers point of view. File identifier and version +// parameters moved from the BIND message to a new IDENT message sent by +// client when it has obtained the file identifier pair. Both the new IDENT +// message and the ALLOC message sent by the server are now properly +// associated with a session. +// +// 6 Server session IDs have been added to the IDENT, DOWNLOAD, and PROGRESS +// messages, and the "Divergent history" error code was added as an +// indication that a server version / session ID pair does not match the +// server's history. +// +// 7 FIXME: Who introduced version 7? Please describe what changed. +// +// 8 Error code (`bad_authentication`) moved from 200-range to 300-range +// because it is now session specific. Other error codes were renumbered. +// +// 9 New format of the DOWNLOAD message to support progress reporting on the +// client +// +// 10 Error codes reordered (now categorized as either connection or session +// level errors). +// +// 11 Bugfixes in Link List and ChangeLinkTargets merge rules, that +// make previous versions incompatible. +// +// 12 FIXME What was 12? +// +// 13 Bugfixes in Link List and ChangeLinkTargets merge rules, that +// make previous versions incompatible. +// +// 14 Further bugfixes related to primary keys and link lists. Add support for +// LinkListSwap. +// +// 15 Deleting an object with a primary key deletes all objects on other +// with the same primary key. +// +// 16 Downloadable bytes added to DOWNLOAD message. It is used for download progress +// by the client +// +// 17 Added PING and PONG messages. It is used for rtt monitoring and dead +// connection detection by both the client and the server. +// +// 18 Enhanced the session_ident to accept values of size up to at least 63 bits. +// +// 19 New instruction log format with stable object IDs and arrays of +// primitives (Generalized LinkList* commands to Container* commands) +// Message format is identical to version 18. +// +// 20 Added support for log compaction in DOWNLOAD message. +// +// 21 Removed "class_" prefix in instructions referencing tables. +// +// 22 Fixed a bug in the merge rule of MOVE vs SWAP. +// +// 23 Introduced full support for session specific ERROR messages. Removed the +// obsolete concept of a "server file identifier". Added support for relayed +// subtier client file identifier allocation. For this purpose, the message +// that was formerly known as ALLOC was renamed to IDENT, and a new ALLOC +// message was added in both directions. Added the ability for an UPLOAD +// message to carry a per-changeset origin client file identifier. Added +// `` parameter to DOWNLOAD message. Added new error +// codes 215 "Unsupported session-level feature" and 216 "Bad origin client +// file identifier (UPLOAD)". +// +// 24 Support schema-breaking instructions. Official support for partial sync. +// +// 25 Include "last server version" in the UPLOAD message for history trimming +// on the server. +// +// 26 Four new protocol error codes, 217, 218, 219, and 220. +// +// The downloadable_bytes field in the DOWNLOAD message denotes the byte +// size of the changesets following those in the DOWNLOAD +// message. Previously, downloadable_bytes denoted the total byte size of +// the entire history. +// +// Introduction of protocol version flexibility on client side (strictly +// speaking, this is a change that transcends the sync protocol). +// +// 27 STATE_REQUEST, STATE, CLIENT_VERSION_REQUEST and CLIENT_VERSION messages +// introduced. These messages are used for client reset and async open. +// +// 28 Introduction of TRANSACT message (serialized transactions). New session +// level error code 221 "Serialized transaction before upload completion". +// +// Also added new parameters ``, ``, ``, and `` to STATE_REQUEST message. +// +// 29 Addition of `` and `` +// to the UPLOAD message. Together, these constitute an upload cursor that +// marks the reached position in the client-side history of the uploading +// process. +// +// Removal of ``, and addition of ``. `` was replaced in part by `` as described above, and in part by the new ``. The purpose of `` is to allow +// the client to lock parts of the server-side history that precede +// ``. It is intended to be used in the future in +// conjunction with the cooked history. +// +// 30 New error code, 222 "Client file has expired", was added. New parameter +// `` added to BIND message. +// +constexpr int get_current_protocol_version() noexcept +{ + return 30; +} + + +/// Supported protocol envelopes: +/// +/// Alternative (*) +/// Name Envelope URL scheme Default port default port +/// ------------------------------------------------------------------------ +/// realm WebSocket realm: 7800 80 +/// realms WebSocket + SSL realms: 7801 443 +/// ws WebSocket ws: 80 +/// wss WebSocket + SSL wss: 443 +/// +/// *) When Client::Config::enable_default_port_hack is true +/// +enum class ProtocolEnvelope { realm, realms, ws, wss }; + +inline bool is_ssl(ProtocolEnvelope protocol) noexcept +{ + switch (protocol) { + case ProtocolEnvelope::realm: + case ProtocolEnvelope::ws: + break; + case ProtocolEnvelope::realms: + case ProtocolEnvelope::wss: + return true; + } + return false; +} + + +// These integer types are selected so that they accomodate the requirements of +// the protocol specification (`/doc/protocol.md`). +using file_ident_type = std::uint_fast64_t; +using version_type = Replication::version_type; +using salt_type = std::int_fast64_t; +using timestamp_type = std::uint_fast64_t; +using session_ident_type = std::uint_fast64_t; +using request_ident_type = std::uint_fast64_t; +using milliseconds_type = std::int_fast64_t; + +constexpr file_ident_type get_max_file_ident() +{ + return 0x0'7FFF'FFFF'FFFF'FFFF; +} + + +struct SaltedFileIdent { + file_ident_type ident; + /// History divergence and identity spoofing protection. + salt_type salt; +}; + +struct SaltedVersion { + version_type version; + /// History divergence protection. + salt_type salt; +}; + + +/// \brief A client's reference to a position in the server-side history. +/// +/// A download cursor refers to a position in the server-side history. If +/// `server_version` is zero, the position is at the beginning of the history, +/// otherwise the position is after the entry whose changeset produced that +/// version. In general, positions are to be understood as places between two +/// adjacent history entries. +/// +/// `last_integrated_client_version` is the version produced on the client by +/// the last changeset that was sent to the server and integrated into the +/// server-side Realm state at the time indicated by the history position +/// specified by `server_version`, or zero if no changesets from the client were +/// integrated by the server at that point in time. +struct DownloadCursor { + version_type server_version; + version_type last_integrated_client_version; +}; + +/// Checks that `dc.last_integrated_client_version` is zero if +/// `dc.server_version` is zero. +bool is_consistent(DownloadCursor dc) noexcept; + +/// Checks that `a.last_integrated_client_version` and +/// `b.last_integrated_client_version` are equal, if `a.server_version` and +/// `b.server_version` are equal. Otherwise checks that +/// `a.last_integrated_client_version` is less than, or equal to +/// `b.last_integrated_client_version`, if `a.server_version` is less than +/// `b.server_version`. Otherwise checks that `a.last_integrated_client_version` +/// is greater than, or equal to `b.last_integrated_client_version`. +bool are_mutually_consistent(DownloadCursor a, DownloadCursor b) noexcept; + + +/// \brief The server's reference to a position in the client-side history. +/// +/// An upload cursor refers to a position in the client-side history. If +/// `client_version` is zero, the position is at the beginning of the history, +/// otherwise the position is after the entry whose changeset produced that +/// version. In general, positions are to be understood as places between two +/// adjacent history entries. +/// +/// `last_integrated_server_version` is the version produced on the server by +/// the last changeset that was sent to the client and integrated into the +/// client-side Realm state at the time indicated by the history position +/// specified by `client_version`, or zero if no changesets from the server were +/// integrated by the client at that point in time. +struct UploadCursor { + version_type client_version; + version_type last_integrated_server_version; +}; + +/// Checks that `uc.last_integrated_server_version` is zero if +/// `uc.client_version` is zero. +bool is_consistent(UploadCursor uc) noexcept; + +/// Checks that `a.last_integrated_server_version` and +/// `b.last_integrated_server_version` are equal, if `a.client_version` and +/// `b.client_version` are equal. Otherwise checks that +/// `a.last_integrated_server_version` is less than, or equal to +/// `b.last_integrated_server_version`, if `a.client_version` is less than +/// `b.client_version`. Otherwise checks that `a.last_integrated_server_version` +/// is greater than, or equal to `b.last_integrated_server_version`. +bool are_mutually_consistent(UploadCursor a, UploadCursor b) noexcept; + + +/// A client's record of the current point of progress of the synchronization +/// process. The client must store this persistently in the local Realm file. +struct SyncProgress { + /// The last server version that the client has heard about. + SaltedVersion latest_server_version = {0, 0}; + + /// The last server version integrated, or about to be integrated by the + /// client. + DownloadCursor download = {0, 0}; + + /// The last client version integrated by the server. + UploadCursor upload = {0, 0}; +}; + + +/// An indication of the final status of an attempt at performing a serialized +/// transaction. +enum class SerialTransactStatus { + /// The transaction was accepted and successful. + accepted = 1, + + /// The transaction was rejected because the servers history contained + /// causally unrelated changes. I.e., the requesting client lost a race to + /// be served first. The client should try again. + rejected = 2, + + /// The server did not support serialized transactions at all, or did not + /// support it on the targeted Realm in particular. + not_supported = 3, +}; + + + +/// \brief Protocol errors discovered by the server, and reported to the client +/// by way of ERROR messages. +/// +/// These errors will be reported to the client-side application via the error +/// handlers of the affected sessions. +/// +/// ATTENTION: Please remember to update is_session_level_error() when +/// adding/removing error codes. +enum class ProtocolError { + // Connection level and protocol errors + connection_closed = 100, // Connection closed (no error) + other_error = 101, // Other connection level error + unknown_message = 102, // Unknown type of input message + bad_syntax = 103, // Bad syntax in input message head + limits_exceeded = 104, // Limits exceeded in input message + wrong_protocol_version = 105, // Wrong protocol version (CLIENT) (obsolete) + bad_session_ident = 106, // Bad session identifier in input message + reuse_of_session_ident = 107, // Overlapping reuse of session identifier (BIND) + bound_in_other_session = 108, // Client file bound in other session (IDENT) + bad_message_order = 109, // Bad input message order + bad_decompression = 110, // Error in decompression (UPLOAD) + bad_changeset_header_syntax = 111, // Bad syntax in a changeset header (UPLOAD) + bad_changeset_size = 112, // Bad size specified in changeset header (UPLOAD) + bad_changesets = 113, // Bad changesets (UPLOAD) + + // Session level errors + session_closed = 200, // Session closed (no error) + other_session_error = 201, // Other session level error + token_expired = 202, // Access token expired + bad_authentication = 203, // Bad user authentication (BIND, REFRESH) + illegal_realm_path = 204, // Illegal Realm path (BIND) + no_such_realm = 205, // No such Realm (BIND) + permission_denied = 206, // Permission denied (STATE_REQUEST, BIND, REFRESH) + bad_server_file_ident = 207, // Bad server file identifier (IDENT) (obsolete!) + bad_client_file_ident = 208, // Bad client file identifier (IDENT) + bad_server_version = 209, // Bad server version (IDENT, UPLOAD, TRANSACT) + bad_client_version = 210, // Bad client version (IDENT, UPLOAD) + diverging_histories = 211, // Diverging histories (IDENT) + bad_changeset = 212, // Bad changeset (UPLOAD) + superseded = 213, // Superseded by new session for same client-side file (deprecated) + disabled_session = 213, // Alias for `superseded` (deprecated) + partial_sync_disabled = 214, // Partial sync disabled (BIND, STATE_REQUEST) + unsupported_session_feature = 215, // Unsupported session-level feature + bad_origin_file_ident = 216, // Bad origin file identifier (UPLOAD) + bad_client_file = 217, // Synchronization no longer possible for client-side file + server_file_deleted = 218, // Server file was deleted while session was bound to it + client_file_blacklisted = 219, // Client file has been blacklisted (IDENT) + user_blacklisted = 220, // User has been blacklisted (BIND) + transact_before_upload = 221, // Serialized transaction before upload completion + client_file_expired = 222, // Client file has expired +}; + +constexpr bool is_session_level_error(ProtocolError); + +/// Returns null if the specified protocol error code is not defined by +/// ProtocolError. +const char* get_protocol_error_message(int error_code) noexcept; + +const std::error_category& protocol_error_category() noexcept; + +std::error_code make_error_code(ProtocolError) noexcept; + +} // namespace sync +} // namespace realm + +namespace std { + +template<> struct is_error_code_enum { + static const bool value = true; +}; + +} // namespace std + +namespace realm { +namespace sync { + + + + + +// Implementation + +inline bool is_consistent(DownloadCursor dc) noexcept +{ + return (dc.server_version != 0 || dc.last_integrated_client_version == 0); +} + +inline bool are_mutually_consistent(DownloadCursor a, DownloadCursor b) noexcept +{ + if (a.server_version < b.server_version) + return (a.last_integrated_client_version <= b.last_integrated_client_version); + if (a.server_version > b.server_version) + return (a.last_integrated_client_version >= b.last_integrated_client_version); + return (a.last_integrated_client_version == b.last_integrated_client_version); +} + +inline bool is_consistent(UploadCursor uc) noexcept +{ + return (uc.client_version != 0 || uc.last_integrated_server_version == 0); +} + +inline bool are_mutually_consistent(UploadCursor a, UploadCursor b) noexcept +{ + if (a.client_version < b.client_version) + return (a.last_integrated_server_version <= b.last_integrated_server_version); + if (a.client_version > b.client_version) + return (a.last_integrated_server_version >= b.last_integrated_server_version); + return (a.last_integrated_server_version == b.last_integrated_server_version); +} + +constexpr bool is_session_level_error(ProtocolError error) +{ + return int(error) >= 200 && int(error) <= 299; +} + +} // namespace sync +} // namespace realm + +#endif // REALM_SYNC_PROTOCOL_HPP diff --git a/!main project/Pods/Realm/include/core/realm/sync/transform.hpp b/!main project/Pods/Realm/include/core/realm/sync/transform.hpp new file mode 100644 index 0000000..e1b42bb --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/sync/transform.hpp @@ -0,0 +1,353 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2015] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ + +#ifndef REALM_SYNC_TRANSFORM_HPP +#define REALM_SYNC_TRANSFORM_HPP + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace realm { +namespace sync { + +struct Changeset; + +/// Represents an entry in the history of changes in a sync-enabled Realm +/// file. Server and client use different history formats, but this class is +/// used both on the server and the client side. Each history entry corresponds +/// to a version of the Realm state. For server and client-side histories, these +/// versions are referred to as *server versions* and *client versions* +/// respectively. These versions may, or may not correspond to Realm snapshot +/// numbers (on the server-side they currently do not). +/// +/// FIXME: Move this class into a separate header +/// (``). +class HistoryEntry { +public: + /// The time of origination of the changes referenced by this history entry, + /// meassured as the number of milliseconds since 2015-01-01T00:00:00Z, not + /// including leap seconds. For changes of local origin, this is the local + /// time at the point when the local transaction was committed. For changes + /// of remote origin, it is the remote time of origin at the peer (client or + /// server) identified by `origin_file_ident`. + timestamp_type origin_timestamp; + + /// The identifier of the file in the context of which the initial + /// untransformed changeset originated, or zero if the changeset originated + /// on the local peer (client or server). + /// + /// For example, when a changeset "travels" from a file with identifier 2 on + /// client A, through a file with identifier 1 on the server, to a file with + /// identifier 3 on client B, then `origin_file_ident` will be 0 on client + /// A, 2 on the server, and 2 on client B. On the other hand, if the server + /// was the originator of the changeset, then `origin_file_ident` would be + /// zero on the server, and 1 on both clients. + file_ident_type origin_file_ident; + + /// For changes of local origin on the client side, this is the last server + /// version integrated locally prior to this history entry. In other words, + /// it is a copy of `remote_version` of the last preceding history entry + /// that carries changes of remote origin, or zero if there is no such + /// preceding history entry. + /// + /// For changes of local origin on the server-side, this is always zero. + /// + /// For changes of remote origin, this is the version produced within the + /// remote-side Realm file by the change that gave rise to this history + /// entry. The remote-side Realm file is not always the same Realm file from + /// which the change originated. On the client side, the remote side is + /// always the server side, and `remote_version` is always a server version + /// (since clients do not speak directly to each other). On the server side, + /// the remote side is always a client side, and `remote_version` is always + /// a client version. + version_type remote_version; + + /// Referenced memory is not owned by this class. + ChunkedBinaryData changeset; +}; + + + +/// The interface between the sync history and the operational transformer +/// (Transformer) for the purpose of transforming changesets received from a +/// particular *remote peer*. +class TransformHistory { +public: + /// Get the first history entry where the produced version is greater than + /// `begin_version` and less than or equal to `end_version`, and whose + /// changeset is neither empty, nor produced by integration of a changeset + /// received from the remote peer associated with this history. + /// + /// If \a buffer is non-null, memory will be allocated and transferred to + /// \a buffer. The changeset will be copied into the newly allocated memory. + /// + /// If \a buffer is null, the changeset is not copied out of the Realm, + /// and entry.changeset.data() does not point to the changeset. + /// The changeset in the Realm could be chunked, hence it is not possible + /// to point to it with BinaryData. entry.changeset.size() always gives the + /// size of the changeset. + /// + /// \param begin_version, end_version The range of versions to consider. If + /// `begin_version` is equal to `end_version`, it is the empty range. If + /// `begin_version` is zero, it means that everything preceeding + /// `end_version` is to be considered, which is again the empty range if + /// `end_version` is also zero. Zero is a special value in that no changeset + /// produces that version. It is an error if `end_version` precedes + /// `begin_version`, or if `end_version` is zero and `begin_version` is not. + /// + /// \return The version produced by the changeset of the located history + /// entry, or zero if no history entry exists matching the specified and + /// implied criteria. + virtual version_type find_history_entry(version_type begin_version, version_type end_version, + HistoryEntry& entry) const noexcept = 0; + + /// Get the specified reciprocal changeset. The targeted history entry is + /// the one whose untransformed changeset produced the specified version. + virtual ChunkedBinaryData get_reciprocal_transform(version_type version) const = 0; + + /// Replace the specified reciprocally transformed changeset. The targeted + /// history entry is the one whose untransformed changeset produced the + /// specified version. + /// + /// \param encoded_changeset The new reciprocally transformed changeset. + virtual void set_reciprocal_transform(version_type version, BinaryData encoded_changeset) = 0; + + virtual ~TransformHistory() noexcept {} +}; + + + +class TransformError; // Exception + +class Transformer { +public: + class RemoteChangeset; + class Reporter; + + /// Produce operationally transformed versions of the specified changesets, + /// which are assumed to be received from a particular remote peer, P, + /// represented by the specified transform history. Note that P is not + /// necessarily the peer on which the changes originated. + /// + /// Operational transformation is carried out between the specified + /// changesets and all causally unrelated changesets in the local history. A + /// changeset in the local history is causally unrelated if, and only if it + /// occurs after the local changeset that produced + /// `remote_changeset.last_integrated_local_version` and is not a produced + /// by integration of a changeset received from P. This assumes that + /// `remote_changeset.last_integrated_local_version` is set to the local + /// version produced by the last local changeset, that was integrated by P + /// before P produced the specified changeset. + /// + /// The operational transformation is reciprocal (two-way), so it also + /// transforms the causally unrelated local changesets. This process does + /// not modify the history itself (the changesets available through + /// TransformHistory::get_history_entry()), instead the reciprocally + /// transformed changesets are stored separately, and individually for each + /// remote peer, such that they can participate in transformation of the + /// next incoming changeset from P. + /// + /// In general, if A and B are two causally unrelated (alternative) + /// changesets based on the same version V, then the operational + /// transformation between A and B produces changesets A' and B' such that + /// both of the concatenated changesets A + B' and B + A' produce the same + /// final state when applied to V. Operational transformation is meaningful + /// only when carried out between alternative changesets based on the same + /// version. + /// + /// \param local_file_ident The identifier of the local Realm file. The + /// transformer uses this as the actual origin file identifier for + /// changesets where HistoryEntry::origin_file_ident is zero, i.e., when the + /// changeset is of local origin. The specified identifier must never be + /// zero. + /// + /// \return The size of the transformed version of the specified + /// changesets. Upon return, the transformed changesets are concatenated + /// and placed in \a output_buffer. + /// + /// \throw TransformError Thrown if operational transformation fails due to + /// a problem with the specified changeset. + /// + /// FIXME: Consider using std::error_code instead of throwing + /// TransformError. + virtual void transform_remote_changesets(TransformHistory&, + file_ident_type local_file_ident, + version_type current_local_version, + Changeset* changesets, + std::size_t num_changesets, + Reporter* = nullptr, + util::Logger* = nullptr) = 0; + + virtual ~Transformer() noexcept {} +}; + +std::unique_ptr make_transformer(); + +} // namespace sync + + +namespace _impl { + +class TransformerImpl : public sync::Transformer { +public: + using Changeset = sync::Changeset; + using file_ident_type = sync::file_ident_type; + using HistoryEntry = sync::HistoryEntry; + using Instruction = sync::Instruction; + using TransformHistory = sync::TransformHistory; + using version_type = sync::version_type; + + TransformerImpl(); + + void transform_remote_changesets(TransformHistory&, file_ident_type, version_type, Changeset*, + std::size_t, Reporter*, util::Logger*) override; + + struct Side; + struct MajorSide; + struct MinorSide; + +protected: + virtual void merge_changesets(file_ident_type local_file_ident, + Changeset* their_changesets, + std::size_t their_size, + // our_changesets is a pointer-pointer because these changesets + // are held by the reciprocal transform cache. + Changeset** our_changesets, + std::size_t our_size, + Reporter* reporter, + util::Logger* logger); + +private: + util::metered::map> m_reciprocal_transform_cache; + + TransactLogParser m_changeset_parser; + + Changeset& get_reciprocal_transform(TransformHistory&, file_ident_type local_file_ident, + version_type version, const HistoryEntry&); + void flush_reciprocal_transform_cache(TransformHistory&); + + static size_t emit_changesets(const Changeset*, + size_t num_changesets, + util::Buffer& output_buffer); + + struct Discriminant; + struct Transformer; + struct MergeTracer; + + template + void merge_instructions(LeftSide&, RightSide&); +}; + +} // namespace _impl + + +namespace sync { + +class Transformer::RemoteChangeset { +public: + /// The version produced on the remote peer by this changeset. + /// + /// On the server, the remote peer is the client from which the changeset + /// originated, and `remote_version` is the client version produced by the + /// changeset on that client. + /// + /// On a client, the remote peer is the server, and `remote_version` is the + /// server version produced by this changeset on the server. Since the + /// server is never the originator of changes, this changeset must in turn + /// have been produced on the server by integration of a changeset uploaded + /// by some other client. + version_type remote_version = 0; + + /// The last local version that has been integrated into `remote_version`. + /// + /// A local version, L, has been integrated into a remote version, R, when, + /// and only when L is the latest local version such that all preceeding + /// changesets in the local history have been integrated by the remote peer + /// prior to R. + /// + /// On the server, this is the last server version integrated into the + /// client version `remote_version`. On a client, it is the last client + /// version integrated into the server version `remote_version`. + version_type last_integrated_local_version = 0; + + /// The changeset itself. + ChunkedBinaryData data; + + /// Same meaning as `HistoryEntry::origin_timestamp`. + timestamp_type origin_timestamp = 0; + + /// Same meaning as `HistoryEntry::origin_file_ident`. + file_ident_type origin_file_ident = 0; + + /// If the changeset was compacted during download, the size of the original + /// changeset. + std::size_t original_changeset_size = 0; + + RemoteChangeset() {} + RemoteChangeset(version_type rv, version_type lv, ChunkedBinaryData d, timestamp_type ot, + file_ident_type fi); +}; + + + +class Transformer::Reporter { +public: + virtual void on_changesets_merged(long num_merges) = 0; +}; + + + +void parse_remote_changeset(const Transformer::RemoteChangeset&, Changeset&); + + + + +// Implementation + +class TransformError: public std::runtime_error { +public: + TransformError(const std::string& message): + std::runtime_error(message) + { + } +}; + +inline Transformer::RemoteChangeset::RemoteChangeset(version_type rv, version_type lv, + ChunkedBinaryData d, timestamp_type ot, + file_ident_type fi): + remote_version(rv), + last_integrated_local_version(lv), + data(d), + origin_timestamp(ot), + origin_file_ident(fi) +{ +} + +} // namespace sync +} // namespace realm + +#endif // REALM_SYNC_TRANSFORM_HPP diff --git a/!main project/Pods/Realm/include/core/realm/sync/version.hpp b/!main project/Pods/Realm/include/core/realm/sync/version.hpp new file mode 100644 index 0000000..688bfe4 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/sync/version.hpp @@ -0,0 +1,34 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2013] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ +#ifndef REALM_SYNC_VERSION_HPP +#define REALM_SYNC_VERSION_HPP + +#include + +#define REALM_SYNC_VER_MAJOR 4 +#define REALM_SYNC_VER_MINOR 9 +#define REALM_SYNC_VER_PATCH 4 +#define REALM_SYNC_PRODUCT_NAME "realm-sync" + +#define REALM_SYNC_VER_STRING REALM_QUOTE(REALM_SYNC_VER_MAJOR) "." \ + REALM_QUOTE(REALM_SYNC_VER_MINOR) "." REALM_QUOTE(REALM_SYNC_VER_PATCH) +#define REALM_SYNC_VER_CHUNK "[" REALM_SYNC_PRODUCT_NAME "-" REALM_SYNC_VER_STRING "]" + +#endif // REALM_SYNC_VERSION_HPP diff --git a/!main project/Pods/Realm/include/core/realm/table.hpp b/!main project/Pods/Realm/include/core/realm/table.hpp new file mode 100644 index 0000000..1535b57 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/table.hpp @@ -0,0 +1,2807 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_TABLE_HPP +#define REALM_TABLE_HPP + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace realm { + +class BacklinkColumn; +template +class BacklinkCount; +class BinaryColumy; +class ConstTableView; +class Group; +class LinkColumn; +class LinkColumnBase; +class LinkListColumn; +class LinkView; +class SortDescriptor; +class StringIndex; +class TableView; +class TableViewBase; +class TimestampColumn; +template +class Columns; +template +class SubQuery; +struct LinkTargetInfo; + +struct SubTable { +}; +struct Link { +}; +typedef Link LinkList; +typedef Link BackLink; + +namespace _impl { +class TableFriend; +} +namespace metrics { +class QueryInfo; +} + +class Replication; + + +/// FIXME: Table assignment (from any group to any group) could be made aliasing +/// safe as follows: Start by cloning source table into target allocator. On +/// success, assign, and then deallocate any previous structure at the target. +/// +/// FIXME: It might be desirable to have a 'table move' feature between two +/// places inside the same group (say from a subtable or a mixed column to group +/// level). This could be done in a very efficient manner. +/// +/// FIXME: When compiling in debug mode, all public non-static table functions +/// should REALM_ASSERT(is_attached()). +class Table { +public: + /// Construct a new freestanding top-level table with static + /// lifetime. + /// + /// This constructor should be used only when placing a table + /// instance on the stack, and it is then the responsibility of + /// the application that there are no objects of type TableRef or + /// ConstTableRef that refer to it, or to any of its subtables, + /// when it goes out of scope. To create a top-level table with + /// dynamic lifetime, use Table::create() instead. + Table(Allocator& = Allocator::get_default()); + + /// Construct a copy of the specified table as a new freestanding + /// top-level table with static lifetime. + /// + /// This constructor should be used only when placing a table + /// instance on the stack, and it is then the responsibility of + /// the application that there are no objects of type TableRef or + /// ConstTableRef that refer to it, or to any of its subtables, + /// when it goes out of scope. To create a top-level table with + /// dynamic lifetime, use Table::copy() instead. + Table(const Table&, Allocator& = Allocator::get_default()); + + ~Table() noexcept; + + Allocator& get_alloc() const; + + /// Construct a new freestanding top-level table with dynamic lifetime. + static TableRef create(Allocator& = Allocator::get_default()); + + /// Construct a copy of the specified table as a new freestanding top-level + /// table with dynamic lifetime. + TableRef copy(Allocator& = Allocator::get_default()) const; + + /// Returns true if, and only if this accessor is currently attached to an + /// underlying table. + /// + /// A table accessor may get detached from the underlying row for various + /// reasons (see below). When it does, it no longer refers to anything, and + /// can no longer be used, except for calling is_attached(). The + /// consequences of calling other non-static functions on a detached table + /// accessor are unspecified. Table accessors obtained by calling functions in + /// the Realm API are always in the 'attached' state immediately upon + /// return from those functions. + /// + /// A table accessor of a free-standing table never becomes detached (except + /// during its eventual destruction). A group-level table accessor becomes + /// detached if the underlying table is removed from the group, or when the + /// group accessor is destroyed. A subtable accessor becomes detached if the + /// underlying subtable is removed, or if the parent table accessor is + /// detached. A table accessor does not become detached for any other reason + /// than those mentioned here. + /// + /// FIXME: High level language bindings will probably want to be able to + /// explicitely detach a group and all tables of that group if any modifying + /// operation fails (e.g. memory allocation failure) (and something similar + /// for freestanding tables) since that leaves the group in state where any + /// further access is disallowed. This way they will be able to reliably + /// intercept any attempt at accessing such a failed group. + /// + /// FIXME: The C++ documentation must state that if any modifying operation + /// on a group (incl. tables, subtables, and specs) or on a free standing + /// table (incl. subtables and specs) fails, then any further access to that + /// group (except ~Group()) or freestanding table (except ~Table()) has + /// undefined behaviour and is considered an error on behalf of the + /// application. Note that even Table::is_attached() is disallowed in this + /// case. + bool is_attached() const noexcept; + + /// Get the name of this table, if it has one. Only group-level tables have + /// names. For a table of any other kind, this function returns the empty + /// string. + StringData get_name() const noexcept; + + // Whether or not elements can be null. + bool is_nullable(size_t col_ndx) const; + + // Returns the link type for the given column. + // Throws an LogicError if target column is not a link column. + LinkType get_link_type(size_t col_ndx) const; + + //@{ + /// Conventience functions for inspecting the dynamic table type. + /// + /// These functions behave as if they were called on the descriptor returned + /// by get_descriptor(). + size_t get_column_count() const noexcept; + DataType get_column_type(size_t column_ndx) const noexcept; + StringData get_column_name(size_t column_ndx) const noexcept; + size_t get_column_index(StringData name) const noexcept; + typedef util::Optional> BacklinkOrigin; + BacklinkOrigin find_backlink_origin(StringData origin_table_name, StringData origin_col_name) const noexcept; + //@} + + //@{ + /// Convenience functions for manipulating the dynamic table type. + /// + /// These function must be called only for tables with independent dynamic + /// type. A table has independent dynamic type if the function + /// has_shared_type() returns false. A table that is a direct member of a + /// group has independent dynamic type. So does a free-standing table, and a + /// subtable in a column of type 'mixed'. All other tables have shared + /// dynamic type. The consequences of calling any of these functions for a + /// table with shared dynamic type are undefined. + /// + /// Apart from that, these functions behave as if they were called on the + /// descriptor returned by get_descriptor(). Note especially that the + /// `_link` suffixed functions must be used when inserting link-type + /// columns. + /// + /// If you need to change the shared dynamic type of the subtables in a + /// subtable column, consider using the API offered by the Descriptor class. + /// + /// \sa has_shared_type() + /// \sa get_descriptor() + + size_t add_column(DataType type, StringData name, bool nullable = false, DescriptorRef* subdesc = nullptr); + void insert_column(size_t column_ndx, DataType type, StringData name, bool nullable = false, + DescriptorRef* subdesc = nullptr); + + // Todo, these prototypes only exist for backwards compatibility. We should remove them because they are error + // prone (optional arguments and implicit bool to null-ptr conversion) + size_t add_column(DataType type, StringData name, DescriptorRef* subdesc) + { + return add_column(type, name, false, subdesc); + } + void insert_column(size_t column_ndx, DataType type, StringData name, DescriptorRef* subdesc) + { + insert_column(column_ndx, type, name, false, subdesc); + } + + size_t add_column_link(DataType type, StringData name, Table& target, LinkType link_type = link_Weak); + void insert_column_link(size_t column_ndx, DataType type, StringData name, Table& target, + LinkType link_type = link_Weak); + void remove_column(size_t column_ndx); + void rename_column(size_t column_ndx, StringData new_name); + //@} + //@{ + + /// has_search_index() returns true if, and only if a search index has been + /// added to the specified column. Rather than throwing, it returns false if + /// the table accessor is detached or the specified index is out of range. + /// + /// add_search_index() adds a search index to the specified column of the + /// table. It has no effect if a search index has already been added to the + /// specified column (idempotency). + /// + /// remove_search_index() removes the search index from the specified column + /// of the table. It has no effect if the specified column has no search + /// index. The search index cannot be removed from the primary key of a + /// table. + /// + /// This table must be a root table; that is, it must have an independent + /// descriptor. Freestanding tables, group-level tables, and subtables in a + /// column of type 'mixed' are all examples of root tables. See add_column() + /// for more on this. If you want to manipulate subtable indexes, you must use + /// the Descriptor interface. + /// + /// \param column_ndx The index of a column of the table. + + bool has_search_index(size_t column_ndx) const noexcept; + void add_search_index(size_t column_ndx); + void remove_search_index(size_t column_ndx); + + //@} + + //@{ + /// Get the dynamic type descriptor for this table. + /// + /// Every table has an associated descriptor that specifies its dynamic + /// type. For simple tables, that is, tables without subtable columns, the + /// dynamic type can be inspected and modified directly using member + /// functions such as get_column_count() and add_column(). For more complex + /// tables, the type is best managed through the associated descriptor + /// object which is returned by this function. + /// + /// \sa has_shared_type() + DescriptorRef get_descriptor(); + ConstDescriptorRef get_descriptor() const; + //@} + + //@{ + /// Get the dynamic type descriptor for the column with the + /// specified index. That column must have type 'table'. + /// + /// This is merely a shorthand for calling `get_subdescriptor(column_ndx)` + /// on the descriptor returned by `get_descriptor()`. + DescriptorRef get_subdescriptor(size_t column_ndx); + ConstDescriptorRef get_subdescriptor(size_t column_ndx) const; + //@} + + //@{ + /// Get access to an arbitrarily nested dynamic type descriptor. + /// + /// The returned descriptor is the one you would get by calling + /// Descriptor::get_subdescriptor() once for each entry in the specified + /// path, starting with the descriptor returned by get_descriptor(). The + /// path is allowed to be empty. + typedef std::vector path_vec; + DescriptorRef get_subdescriptor(const path_vec& path); + ConstDescriptorRef get_subdescriptor(const path_vec& path) const; + //@} + + //@{ + /// Convenience functions for manipulating nested table types. + /// + /// These functions behave as if they were called on the descriptor returned + /// by `get_subdescriptor(path)`. These function must be called only on + /// tables with independent dynamic type. + /// + /// \return The value returned by add_subcolumn(), is the index of + /// the added column within the descriptor referenced by the + /// specified path. + /// + /// \sa Descriptor::add_column() + /// \sa has_shared_type() + size_t add_subcolumn(const path_vec& path, DataType type, StringData name); + void insert_subcolumn(const path_vec& path, size_t column_ndx, DataType type, StringData name); + void remove_subcolumn(const path_vec& path, size_t column_ndx); + void rename_subcolumn(const path_vec& path, size_t column_ndx, StringData new_name); + //@} + + /// Does this table share its type with other tables? + /// + /// Tables that are direct members of groups have independent + /// dynamic types. The same is true for free-standing tables and + /// subtables in coulmns of type 'mixed'. For such tables, this + /// function returns false. + /// + /// When a table has a column of type 'table', the cells in that + /// column contain subtables. All those subtables have the same + /// dynamic type, and they share a single type descriptor. For all + /// such subtables, this function returns true. See + /// Descriptor::is_root() for more on this. + /// + /// Please note that Table functions that modify the dynamic type + /// directly, such as add_column(), are only allowed to be used on + /// tables with non-shared type. If you need to modify a shared + /// type, you will have to do that through the descriptor returned + /// by get_descriptor(), but note that it will then affect all the + /// tables sharing that descriptor. + /// + /// \sa get_descriptor() + /// \sa Descriptor::is_root() + bool has_shared_type() const noexcept; + + + template + Columns column(size_t column); // FIXME: Should this one have been declared noexcept? + template + Columns column(const Table& origin, size_t origin_column_ndx); + // BacklinkCount is a total count per row and therefore not attached to a specific column + template + BacklinkCount get_backlink_count(); + + template + SubQuery column(size_t column, Query subquery); + template + SubQuery column(const Table& origin, size_t origin_column_ndx, Query subquery); + + // Table size and deletion + bool is_empty() const noexcept; + size_t size() const noexcept; + + typedef BasicRowExpr
RowExpr; + typedef BasicRowExpr ConstRowExpr; + + RowExpr get(size_t row_ndx) noexcept; + ConstRowExpr get(size_t row_ndx) const noexcept; + + RowExpr front() noexcept; + ConstRowExpr front() const noexcept; + + RowExpr back() noexcept; + ConstRowExpr back() const noexcept; + + RowExpr operator[](size_t row_ndx) noexcept; + ConstRowExpr operator[](size_t row_ndx) const noexcept; + + + //@{ + + /// Row handling. + /// + /// remove() removes the specified row from the table and shifts all rows at + /// higher index to fill the vacated slot. This operation assumes that the + /// table is ordered, and it is therefore allowed only on tables **without** + /// link columns, as link columns are only allowed in unordered tables. + /// + /// move_last_over() removes the specified row from the table, and if it is + /// not the last row in the table, it then moves the last row into the + /// vacated slot. This operation assumes that the table is unordered, and it + /// may therfore be used on tables with link columns. + /// + /// remove_recursive() will delete linked rows if the removed link was the + /// last one holding on to the row in question. This will be done recursively. + /// + /// The removal of a row from an unordered table (move_last_over()) may + /// cause other linked rows to be cascade-removed. The clearing of a table + /// may also cause linked rows to be cascade-removed, but in this respect, + /// the effect is exactly as if each row had been removed individually. See + /// Descriptor::set_link_type() for details. + + size_t add_empty_row(size_t num_rows = 1); + void insert_empty_row(size_t row_ndx, size_t num_rows = 1); + size_t add_row_with_key(size_t col_ndx, util::Optional key); + size_t add_row_with_keys(size_t col_1_ndx, int64_t key1, size_t col_2_ndx, StringData key2); + void remove(size_t row_ndx); + void remove_recursive(size_t row_ndx); + void remove_last(); + void move_last_over(size_t row_ndx); + void clear(); + void swap_rows(size_t row_ndx_1, size_t row_ndx_2); + void move_row(size_t from_ndx, size_t to_ndx); + //@} + + /// Replaces all links to \a row_ndx with links to \a new_row_ndx. + /// + /// This operation is usually followed by Table::move_last_over() + /// as part of Table::set_int_unique() or Table::set_string_unique() + /// or Table::set_null_unique() detecting a collision. + /// + /// \sa Table::move_last_over() + /// \sa Table::set_int_unique() + /// \sa Table::set_string_unique() + /// \sa Table::set_null_unique() + void merge_rows(size_t row_ndx, size_t new_row_ndx); + + //@{ + + /// Get cell values. + /// Will assert if the requested type does not match the column type. + /// + /// When fetching from a nullable column and the value is null, a default + /// value will be returned, except for object like types (StringData, + /// BinaryData, Timestamp) which have support for storing nulls. In that + /// case, call the `is_null()` method on the returned object to check + /// whether the stored value was null. If nullability matters and returning + /// a default value is unacceptable, check Table::is_null() before getting a + /// cell value. + /// + /// \sa Table::is_nullable(size_t col_ndx) + /// \sa Table::is_null(size_t col_ndx, size_t row_ndx) + /// \sa StringData::is_null() + int64_t get_int(size_t column_ndx, size_t row_ndx) const noexcept; + bool get_bool(size_t column_ndx, size_t row_ndx) const noexcept; + OldDateTime get_olddatetime(size_t column_ndx, size_t row_ndx) const noexcept; + float get_float(size_t column_ndx, size_t row_ndx) const noexcept; + double get_double(size_t column_ndx, size_t row_ndx) const noexcept; + StringData get_string(size_t column_ndx, size_t row_ndx) const noexcept; + BinaryData get_binary(size_t column_ndx, size_t row_ndx) const noexcept; + BinaryIterator get_binary_iterator(size_t column_ndx, size_t row_ndx) const noexcept; + Mixed get_mixed(size_t column_ndx, size_t row_ndx) const noexcept; + DataType get_mixed_type(size_t column_ndx, size_t row_ndx) const noexcept; + Timestamp get_timestamp(size_t column_ndx, size_t row_ndx) const noexcept; + + //@} + + /// Return data from position 'pos' and onwards. If the blob is distributed + /// across multiple arrays, you will only get data from one array. 'pos' + /// will be updated to be an index to next available data. It will be 0 + /// if no more data. + BinaryData get_binary_at(size_t col_ndx, size_t ndx, size_t& pos) const noexcept; + + template + T get(size_t c, size_t r) const noexcept; + + size_t get_link(size_t column_ndx, size_t row_ndx) const noexcept; + bool is_null_link(size_t column_ndx, size_t row_ndx) const noexcept; + LinkViewRef get_linklist(size_t column_ndx, size_t row_ndx); + ConstLinkViewRef get_linklist(size_t column_ndx, size_t row_ndx) const; + size_t get_link_count(size_t column_ndx, size_t row_ndx) const noexcept; + bool linklist_is_empty(size_t column_ndx, size_t row_ndx) const noexcept; + bool is_null(size_t column_ndx, size_t row_ndx) const noexcept; + + TableRef get_link_target(size_t column_ndx) noexcept; + ConstTableRef get_link_target(size_t column_ndx) const noexcept; + + //@{ + + /// Set cell values. + /// + /// It is an error to specify a column index, row index, or string position + /// that is out of range. + /// + /// The number of bytes in a string value must not exceed `max_string_size`, + /// and the number of bytes in a binary data value must not exceed + /// `max_binary_size`. String must also contain valid UTF-8 encodings. These + /// requirements also apply when modifying a string with insert_substring() + /// and remove_substring(), and for strings in a mixed columnt. Passing, or + /// producing an oversized string or binary data value will cause an + /// exception to be thrown. + /// + /// The "unique" variants (set_int_unique(), set_string_unique(), set_null_unique()) + /// are intended to be used in the implementation of primary key support. They + /// check if the given column already contains one or more values that are + /// equal to \a value, and if there are conflicts, it calls + /// Table::merge_rows() for the row_ndx to be replaced by the + /// existing row, followed by a Table::move_last_over() of row_ndx. The + /// return value is always a row index of a row that contains \a value in + /// the specified column, possibly different from \a row_ndx if a conflict + /// occurred. Users intending to implement primary keys must therefore + /// manually check for duplicates if they want to raise an error instead. + /// + /// NOTE: It is an error to call either function after adding elements to a + /// linklist in the object. In general, calling set_int_unique() or + /// set_string_unique() or set_null_unique() should be the first thing that + /// happens after creating a row. These limitations are imposed by limitations + /// in the Realm Object Server and may be relaxed in the future. A violation of + /// these rules results in a LogicError being thrown. + /// + /// add_int() adds a 64-bit signed integer to the current value of the + /// cell. If the addition would cause signed integer overflow or + /// underflow, the addition "wraps around" with semantics similar to + /// unsigned integer arithmetic, such that Table::max_integer + 1 == + /// Table::min_integer and Table::min_integer - 1 == Table::max_integer. + /// Note that the wrapping is platform-independent (all platforms wrap in + /// the same way regardless of integer representation). If the existing + /// value in the cell is null, a LogicError exception is thrown. + /// + /// insert_substring() inserts the specified string into the currently + /// stored string at the specified position. The position must be less than + /// or equal to the size of the currently stored string. + /// + /// remove_substring() removes the specified byte range from the currently + /// stored string. The beginning of the range (\a pos) must be less than or + /// equal to the size of the currently stored string. If the specified range + /// extends beyond the end of the currently stored string, it will be + /// silently clamped. + /// + /// String level modifications performed via insert_substring() and + /// remove_substring() are mergable and subject to operational + /// transformation. That is, the effect of two causally unrelated + /// modifications will in general both be retained during synchronization. + + static const size_t max_string_size = 0xFFFFF8 - Array::header_size - 1; + static const size_t max_binary_size = 0xFFFFF8 - Array::header_size; + + // FIXME: These limits should be chosen independently of the underlying + // platform's choice to define int64_t and independent of the integer + // representation. The current values only work for 2's complement, which is + // not guaranteed by the standard. + static constexpr int_fast64_t max_integer = std::numeric_limits::max(); + static constexpr int_fast64_t min_integer = std::numeric_limits::min(); + + template + void set(size_t c, size_t r, T value, bool is_default = false); + + template + size_t set_unique(size_t c, size_t r, T value); + + void set_int(size_t column_ndx, size_t row_ndx, int_fast64_t value, bool is_default = false); + size_t set_int_unique(size_t column_ndx, size_t row_ndx, int_fast64_t value); + void set_bool(size_t column_ndx, size_t row_ndx, bool value, bool is_default = false); + void set_olddatetime(size_t column_ndx, size_t row_ndx, OldDateTime value, bool is_default = false); + void set_timestamp(size_t column_ndx, size_t row_ndx, Timestamp value, bool is_default = false); + template + void set_enum(size_t column_ndx, size_t row_ndx, E value); + void set_float(size_t column_ndx, size_t row_ndx, float value, bool is_default = false); + void set_double(size_t column_ndx, size_t row_ndx, double value, bool is_default = false); + void set_string(size_t column_ndx, size_t row_ndx, StringData value, bool is_default = false); + size_t set_string_unique(size_t column_ndx, size_t row_ndx, StringData value); + void set_binary(size_t column_ndx, size_t row_ndx, BinaryData value, bool is_default = false); + void set_mixed(size_t column_ndx, size_t row_ndx, Mixed value, bool is_default = false); + void set_link(size_t column_ndx, size_t row_ndx, size_t target_row_ndx, bool is_default = false); + void nullify_link(size_t column_ndx, size_t row_ndx); + void set_null(size_t column_ndx, size_t row_ndx, bool is_default = false); + void set_null_unique(size_t col_ndx, size_t row_ndx); + + // Sync needs to store blobs bigger than 16 M. This function can be used for that. Data should be read + // out again using the get_binary_at() function. Should not be used for user data as normal get_binary() + // will just return null if the data is bigger than the limit. + void set_binary_big(size_t column_ndx, size_t row_ndx, BinaryData value, bool is_default = false); + + void add_int(size_t column_ndx, size_t row_ndx, int_fast64_t value); + + void insert_substring(size_t col_ndx, size_t row_ndx, size_t pos, StringData); + void remove_substring(size_t col_ndx, size_t row_ndx, size_t pos, size_t substring_size = realm::npos); + + //@} + + /// Assumes that the specified column is a subtable column (in + /// particular, not a mixed column) and that the specified table + /// has a spec that is compatible with that column, that is, the + /// number of columns must be the same, and corresponding columns + /// must have identical data types (as returned by + /// get_column_type()). + void set_subtable(size_t col_ndx, size_t row_ndx, const Table*); + void set_mixed_subtable(size_t col_ndx, size_t row_ndx, const Table*); + + + // Sub-tables (works on columns whose type is either 'subtable' or + // 'mixed', for a value in a mixed column that is not a subtable, + // get_subtable() returns null, get_subtable_size() returns zero, + // and clear_subtable() replaces the value with an empty table.) + // Currently, subtables of subtables are not supported. + TableRef get_subtable(size_t column_ndx, size_t row_ndx); + ConstTableRef get_subtable(size_t column_ndx, size_t row_ndx) const; + size_t get_subtable_size(size_t column_ndx, size_t row_ndx) const noexcept; + void clear_subtable(size_t column_ndx, size_t row_ndx); + + // Backlinks + size_t get_backlink_count(size_t row_ndx, bool only_strong_links = false) const noexcept; + size_t get_backlink_count(size_t row_ndx, const Table& origin, size_t origin_col_ndx) const noexcept; + size_t get_backlink(size_t row_ndx, const Table& origin, size_t origin_col_ndx, size_t backlink_ndx) const + noexcept; + + + //@{ + + /// If this accessor is attached to a subtable, then that subtable has a + /// parent table, and the subtable either resides in a column of type + /// `table` or of type `mixed` in that parent. In that case + /// get_parent_table() returns a reference to the accessor associated with + /// the parent, and get_parent_row_index() returns the index of the row in + /// which the subtable resides. In all other cases (free-standing and + /// group-level tables), get_parent_table() returns null and + /// get_parent_row_index() returns realm::npos. + /// + /// If this accessor is attached to a subtable, and \a column_ndx_out is + /// specified, then `*column_ndx_out` is set to the index of the column of + /// the parent table in which the subtable resides. If this accessor is not + /// attached to a subtable, then `*column_ndx_out` will retain its original + /// value upon return. + + TableRef get_parent_table(size_t* column_ndx_out = nullptr) noexcept; + ConstTableRef get_parent_table(size_t* column_ndx_out = nullptr) const noexcept; + size_t get_parent_row_index() const noexcept; + + //@} + + + /// Only group-level unordered tables can be used as origins or targets of + /// links. + bool is_group_level() const noexcept; + + /// If this table is a group-level table, then this function returns the + /// index of this table within the group. Otherwise it returns realm::npos. + size_t get_index_in_group() const noexcept; + + // Aggregate functions + size_t count_int(size_t column_ndx, int64_t value) const; + size_t count_string(size_t column_ndx, StringData value) const; + size_t count_float(size_t column_ndx, float value) const; + size_t count_double(size_t column_ndx, double value) const; + + int64_t sum_int(size_t column_ndx) const; + double sum_float(size_t column_ndx) const; + double sum_double(size_t column_ndx) const; + int64_t maximum_int(size_t column_ndx, size_t* return_ndx = nullptr) const; + float maximum_float(size_t column_ndx, size_t* return_ndx = nullptr) const; + double maximum_double(size_t column_ndx, size_t* return_ndx = nullptr) const; + OldDateTime maximum_olddatetime(size_t column_ndx, size_t* return_ndx = nullptr) const; + Timestamp maximum_timestamp(size_t column_ndx, size_t* return_ndx = nullptr) const; + int64_t minimum_int(size_t column_ndx, size_t* return_ndx = nullptr) const; + float minimum_float(size_t column_ndx, size_t* return_ndx = nullptr) const; + double minimum_double(size_t column_ndx, size_t* return_ndx = nullptr) const; + OldDateTime minimum_olddatetime(size_t column_ndx, size_t* return_ndx = nullptr) const; + Timestamp minimum_timestamp(size_t column_ndx, size_t* return_ndx = nullptr) const; + double average_int(size_t column_ndx, size_t* value_count = nullptr) const; + double average_float(size_t column_ndx, size_t* value_count = nullptr) const; + double average_double(size_t column_ndx, size_t* value_count = nullptr) const; + + // Searching + template + size_t find_first(size_t column_ndx, T value) const; + + size_t find_first_link(size_t target_row_index) const; + size_t find_first_int(size_t column_ndx, int64_t value) const; + size_t find_first_bool(size_t column_ndx, bool value) const; + size_t find_first_olddatetime(size_t column_ndx, OldDateTime value) const; + size_t find_first_timestamp(size_t column_ndx, Timestamp value) const; + size_t find_first_float(size_t column_ndx, float value) const; + size_t find_first_double(size_t column_ndx, double value) const; + size_t find_first_string(size_t column_ndx, StringData value) const; + size_t find_first_binary(size_t column_ndx, BinaryData value) const; + size_t find_first_null(size_t column_ndx) const; + + TableView find_all_link(size_t target_row_index); + ConstTableView find_all_link(size_t target_row_index) const; + TableView find_all_int(size_t column_ndx, int64_t value); + ConstTableView find_all_int(size_t column_ndx, int64_t value) const; + TableView find_all_bool(size_t column_ndx, bool value); + ConstTableView find_all_bool(size_t column_ndx, bool value) const; + TableView find_all_olddatetime(size_t column_ndx, OldDateTime value); + ConstTableView find_all_olddatetime(size_t column_ndx, OldDateTime value) const; + TableView find_all_float(size_t column_ndx, float value); + ConstTableView find_all_float(size_t column_ndx, float value) const; + TableView find_all_double(size_t column_ndx, double value); + ConstTableView find_all_double(size_t column_ndx, double value) const; + TableView find_all_string(size_t column_ndx, StringData value); + ConstTableView find_all_string(size_t column_ndx, StringData value) const; + TableView find_all_binary(size_t column_ndx, BinaryData value); + ConstTableView find_all_binary(size_t column_ndx, BinaryData value) const; + TableView find_all_null(size_t column_ndx); + ConstTableView find_all_null(size_t column_ndx) const; + + /// The following column types are supported: String, Integer, OldDateTime, Bool + TableView get_distinct_view(size_t column_ndx); + ConstTableView get_distinct_view(size_t column_ndx) const; + + TableView get_sorted_view(size_t column_ndx, bool ascending = true); + ConstTableView get_sorted_view(size_t column_ndx, bool ascending = true) const; + + TableView get_sorted_view(SortDescriptor order); + ConstTableView get_sorted_view(SortDescriptor order) const; + + TableView get_range_view(size_t begin, size_t end); + ConstTableView get_range_view(size_t begin, size_t end) const; + + TableView get_backlink_view(size_t row_ndx, Table* src_table, size_t src_col_ndx); + + + // Pivot / aggregate operation types. Experimental! Please do not document method publicly. + enum AggrType { + aggr_count, + aggr_sum, + aggr_avg, + aggr_min, + aggr_max, + }; + + // Simple pivot aggregate method. Experimental! Please do not document method publicly. + void aggregate(size_t group_by_column, size_t aggr_column, AggrType op, Table& result, + const IntegerColumn* viewrefs = nullptr) const; + + /// Report the current versioning counter for the table. The versioning counter is guaranteed to + /// change when the contents of the table changes after advance_read() or promote_to_write(), or + /// immediately after calls to methods which change the table. The term "change" means "change of + /// value": The storage layout of the table may change, for example due to optimization, but this + /// is not considered a change of a value. This means that you *cannot* use a non-changing version + /// count to indicate that object addresses (e.g. strings, binary data) remain the same. + /// The versioning counter *may* change (but is not required to do so) when another table linked + /// from this table, or linking to this table, is changed. The version counter *may* also change + /// without any apparent reason. + uint_fast64_t get_version_counter() const noexcept; + +private: + template + TableView find_all(size_t column_ndx, T value); + +public: + //@{ + /// Find the lower/upper bound according to a column that is + /// already sorted in ascending order. + /// + /// For an integer column at index 0, and an integer value '`v`', + /// lower_bound_int(0,v) returns the index '`l`' of the first row + /// such that `get_int(0,l) ≥ v`, and upper_bound_int(0,v) + /// returns the index '`u`' of the first row such that + /// `get_int(0,u) > v`. In both cases, if no such row is found, + /// the returned value is the number of rows in the table. + /// + /// 3 3 3 4 4 4 5 6 7 9 9 9 + /// ^ ^ ^ ^ ^ + /// | | | | | + /// | | | | -- Lower and upper bound of 15 + /// | | | | + /// | | | -- Lower and upper bound of 8 + /// | | | + /// | | -- Upper bound of 4 + /// | | + /// | -- Lower bound of 4 + /// | + /// -- Lower and upper bound of 1 + /// + /// These functions are similar to std::lower_bound() and + /// std::upper_bound(). + /// + /// The string versions assume that the column is sorted according + /// to StringData::operator<(). + size_t lower_bound_int(size_t column_ndx, int64_t value) const noexcept; + size_t upper_bound_int(size_t column_ndx, int64_t value) const noexcept; + size_t lower_bound_bool(size_t column_ndx, bool value) const noexcept; + size_t upper_bound_bool(size_t column_ndx, bool value) const noexcept; + size_t lower_bound_float(size_t column_ndx, float value) const noexcept; + size_t upper_bound_float(size_t column_ndx, float value) const noexcept; + size_t lower_bound_double(size_t column_ndx, double value) const noexcept; + size_t upper_bound_double(size_t column_ndx, double value) const noexcept; + size_t lower_bound_string(size_t column_ndx, StringData value) const noexcept; + size_t upper_bound_string(size_t column_ndx, StringData value) const noexcept; + //@} + + // Queries + // Using where(tv) is the new method to perform queries on TableView. The 'tv' can have any order; it does not + // need to be sorted, and, resulting view retains its order. + Query where(TableViewBase* tv = nullptr) + { + return Query(*this, tv); + } + + // FIXME: We need a ConstQuery class or runtime check against modifications in read transaction. + Query where(TableViewBase* tv = nullptr) const + { + return Query(*this, tv); + } + + // Perform queries on a LinkView. The returned Query holds a reference to lv. + Query where(const LinkViewRef& lv) + { + return Query(*this, lv); + } + + //@{ + /// WARNING: The link() and backlink() methods will alter a state on the Table object and return a reference to itself. + /// Be aware if assigning the return value of link() to a variable; this might be an error! + + /// This is an error: + + /// Table& cats = owners->link(1); + /// auto& dogs = owners->link(2); + + /// Query q = person_table->where() + /// .and_query(cats.column(5).equal("Fido")) + /// .Or() + /// .and_query(dogs.column(6).equal("Meowth")); + + /// Instead, do this: + + /// Query q = owners->where() + /// .and_query(person_table->link(1).column(5).equal("Fido")) + /// .Or() + /// .and_query(person_table->link(2).column(6).equal("Meowth")); + + /// The two calls to link() in the errorneous example will append the two values 0 and 1 to an internal vector in the + /// owners table, and we end up with three references to that same table: owners, cats and dogs. They are all the same + /// table, its vector has the values {0, 1}, so a query would not make any sense. + Table& link(size_t link_column); + Table& backlink(const Table& origin, size_t origin_col_ndx); + //@} + + // Optimizing. enforce == true will enforce enumeration of all string columns; + // enforce == false will auto-evaluate if they should be enumerated or not + void optimize(bool enforce = false); + + /// Write this table (or a slice of this table) to the specified + /// output stream. + /// + /// The output will have the same format as any other Realm + /// database file, such as those produced by Group::write(). In + /// this case, however, the resulting database file will contain + /// exactly one table, and that table will contain only the + /// specified slice of the source table (this table). + /// + /// The new table will always have the same dynamic type (see + /// Descriptor) as the source table (this table), and unless it is + /// overridden (\a override_table_name), the new table will have + /// the same name as the source table (see get_name()). Indexes + /// (see add_search_index()) will not be carried over to the new + /// table. + /// + /// \param out The destination output stream buffer. + /// + /// \param offset Index of first row to include (if `slice_size > + /// 0`). Must be less than, or equal to size(). + /// + /// \param slice_size Number of rows to include. May be zero. If + /// `slice_size > size() - offset`, then the effective size of + /// the written slice will be `size() - offset`. + /// + /// \param override_table_name Custom name to write out instead of + /// the actual table name. + /// + /// \throw std::out_of_range If `offset > size()`. + /// + /// FIXME: While this function does provided a maximally efficient + /// way of serializing part of a table, it offers little in terms + /// of general utility. This is unfortunate, because it pulls + /// quite a large amount of code into the core library to support + /// it. + void write(std::ostream& out, size_t offset = 0, size_t slice_size = npos, + StringData override_table_name = StringData()) const; + + // Conversion + void to_json(std::ostream& out, size_t link_depth = 0, + std::map* renames = nullptr) const; + void to_string(std::ostream& out, size_t limit = 500) const; + void row_to_string(size_t row_ndx, std::ostream& out) const; + + // Get a reference to this table + TableRef get_table_ref() + { + return TableRef(this); + } + ConstTableRef get_table_ref() const + { + return ConstTableRef(this); + } + + /// \brief Compare two tables for equality. + /// + /// Two tables are equal if they have equal descriptors + /// (`Descriptor::operator==()`) and equal contents. Equal descriptors imply + /// that the two tables have the same columns in the same order. Equal + /// contents means that the two tables must have the same number of rows, + /// and that for each row index, the two rows must have the same values in + /// each column. + /// + /// In mixed columns, both the value types and the values are required to be + /// equal. + /// + /// For a particular row and column, if the two values are themselves tables + /// (subtable and mixed columns) value equality implies a recursive + /// invocation of `Table::operator==()`. + bool operator==(const Table&) const; + + /// \brief Compare two tables for inequality. + /// + /// See operator==(). + bool operator!=(const Table& t) const; + + /// A subtable in a column of type 'table' (which shares descriptor with + /// other subtables in the same column) is initially in a degenerate state + /// where it takes up a minimal amout of space. This function returns true + /// if, and only if the table accessor is attached to such a subtable. This + /// function is mainly intended for debugging purposes. + bool is_degenerate() const; + + /// Compute the sum of the sizes in number of bytes of all the array nodes + /// that currently make up this table. See also + /// Group::compute_aggregate_byte_size(). + /// + /// If this table accessor is the detached state, this function returns + /// zero. + size_t compute_aggregated_byte_size() const noexcept; + + // Debug + void verify() const; +#ifdef REALM_DEBUG + void to_dot(std::ostream&, StringData title = StringData()) const; + void print() const; + MemStats stats() const; + void dump_node_structure() const; // To std::cerr (for GDB) + void dump_node_structure(std::ostream&, int level) const; +#endif + + class Parent; + using HandoverPatch = TableHandoverPatch; + static void generate_patch(const Table* ref, std::unique_ptr& patch); + static TableRef create_from_and_consume_patch(std::unique_ptr& patch, Group& group); + +protected: + /// Get a pointer to the accessor of the specified subtable. The + /// accessor will be created if it does not already exist. + /// + /// The returned table pointer must **always** end up being + /// wrapped in some instantiation of BasicTableRef<>. + TableRef get_subtable_tableref(size_t col_ndx, size_t row_ndx); + + /// See non-const get_subtable_tableref(). + ConstTableRef get_subtable_tableref(size_t col_ndx, size_t row_ndx) const; + + /// Compare the rows of two tables under the assumption that the two tables + /// have the same number of columns, and the same data type at each column + /// index (as expressed through the DataType enum). + bool compare_rows(const Table&) const; + + void set_into_mixed(Table* parent, size_t col_ndx, size_t row_ndx) const; + + void check_lists_are_empty(size_t row_ndx) const; + +private: + class SliceWriter; + + // Number of rows in this table + size_t m_size; + + // Underlying array structure. `m_top` is in use only for root tables; that + // is, for tables with independent descriptor. `m_columns` contains a ref + // for each column and search index in order of the columns. A search index + // ref always occurs immediately after the ref of the column to which the + // search index belongs. + // + // A subtable column (a column of type `type_table`) is essentially just a + // column of 'refs' pointing to the root node of each subtable. + // + // To save space in the database file, a subtable in such a column always + // starts out in a degenerate form where nothing is allocated on its behalf, + // and a null 'ref' is stored in the corresponding slot of the column. A + // subtable remains in this degenerate state until the first row is added to + // the subtable. + // + // For this scheme to work, it must be (and is) possible to create a table + // accessor that refers to a degenerate subtable. A table accessor (instance + // of `Table`) refers to a degenerate subtable if, and only if `m_columns` + // is unattached. + // + // FIXME: The fact that `m_columns` may be detached means that many + // functions (even non-modifying functions) need to check for that before + // accessing the contents of the table. This incurs a runtime + // overhead. Consider whether this overhead can be eliminated by having + // `Table::m_columns` always attached to something, and then detect the + // degenerate state in a different way. + Array m_top; + Array m_columns; // 2nd slot in m_top (for root tables) + + // Management class for the spec object. Only if the table has an independent + // spec, the spec object should be deleted when the table object is deleted. + // If the table has a shared spec, the spec object is managed by the spec object + // of the containing table. + class SpecPtr { + public: + ~SpecPtr() + { + optionally_delete(); + } + void manage(Spec* ptr) + { + optionally_delete(); + m_p = ptr; + m_is_managed = true; + } + void detach() + { + if (m_is_managed) { + m_p->detach(); + } + } + SpecPtr& operator=(Spec* ptr) + { + optionally_delete(); + m_p = ptr; + m_is_managed = false; + return *this; + } + Spec* operator->() const + { + return m_p; + } + Spec* get() const + { + return m_p; + } + Spec& operator*() const + { + return *m_p; + } + operator bool() const + { + return m_p != nullptr; + } + bool is_managed() const + { + return m_is_managed; + } + + private: + Spec* m_p = nullptr; + bool m_is_managed = false; + + void optionally_delete() + { + if (m_is_managed) { + delete m_p; + } + } + }; + + SpecPtr m_spec; // 1st slot in m_top (for root tables) + + // Is guaranteed to be empty for a detached accessor. Otherwise it is empty + // when the table accessor is attached to a degenerate subtable (unattached + // `m_columns`), otherwise it contains precisely one column accessor for + // each column in the table, in order. + // + // In some cases an entry may be null. This is currently possible only in + // connection with Group::advance_transact(), but it means that several + // member functions must be prepared to handle these null entries; in + // particular, detach(), ~Table(), functions called on behalf of detach() + // and ~Table(), and functiones called on behalf of + // Group::advance_transact(). + typedef std::vector column_accessors; + column_accessors m_cols; + + mutable std::atomic m_ref_count; + + // If this table is a root table (has independent descriptor), + // then Table::m_descriptor refers to the accessor of its + // descriptor when, and only when the descriptor accessor + // exists. This is used to ensure that at most one descriptor + // accessor exists for each underlying descriptor at any given + // point in time. Subdescriptors are kept unique by means of a + // registry in the parent descriptor. Table::m_descriptor is + // always null for tables with shared descriptor. + mutable std::weak_ptr m_descriptor; + + // Table view instances + // Access needs to be protected by m_accessor_mutex + typedef std::vector views; + mutable views m_views; + + // Points to first bound row accessor, or is null if there are none. + mutable RowBase* m_row_accessors = nullptr; + + // Mutex which must be locked any time the row accessor chain or m_views is used + mutable util::Mutex m_accessor_mutex; + + // Used for queries: Items are added with link() method during buildup of query + mutable std::vector m_link_chain; + + /// Used only in connection with Group::advance_transact() and + /// Table::refresh_accessor_tree(). + mutable bool m_mark; + + mutable uint_fast64_t m_version; + + void erase_row(size_t row_ndx, bool is_move_last_over); + void batch_erase_rows(const IntegerColumn& row_indexes, bool is_move_last_over); + void do_remove(size_t row_ndx, bool broken_reciprocal_backlinks); + void do_move_last_over(size_t row_ndx, bool broken_reciprocal_backlinks); + void do_swap_rows(size_t row_ndx_1, size_t row_ndx_2); + void do_move_row(size_t from_ndx, size_t to_ndx); + void do_merge_rows(size_t row_ndx, size_t new_row_ndx); + void do_clear(bool broken_reciprocal_backlinks); + size_t do_set_link(size_t col_ndx, size_t row_ndx, size_t target_row_ndx); + template + size_t do_find_unique(ColType& col, size_t ndx, T&& value, bool& conflict); + template + size_t do_set_unique_null(ColType& col, size_t ndx, bool& conflict); + template + size_t do_set_unique(ColType& column, size_t row_ndx, T&& value, bool& conflict); + + void _add_search_index(size_t column_ndx); + void _remove_search_index(size_t column_ndx); + + void rebuild_search_index(size_t current_file_format_version); + + // Upgrades OldDateTime columns to Timestamp columns + void upgrade_olddatetime(); + + // Indicate that the current global state version has been "observed". Until this + // happens, bumping of the global version counter can be bypassed, as any query + // checking for a version change will see the older version change anyways. + // Also returns the table-local version. + uint64_t observe_version() const noexcept; + + /// Update the version of this table and all tables which have links to it. + /// This causes all views referring to those tables to go out of sync, so that + /// calls to sync_if_needed() will bring the view up to date by reexecuting the + /// query. + /// + /// \param bump_global chooses whether the global versioning counter must be + /// bumped first as part of the update. This is the normal mode of operation, + /// when a change is made to the table. When calling recursively (following links + /// or going to the parent table), the parameter should be set to false to correctly + /// prune traversal. + void bump_version(bool bump_global = true) const noexcept; + + /// Disable copying assignment. + /// + /// It could easily be implemented by calling assign(), but the + /// non-checking nature of the low-level dynamically typed API + /// makes it too risky to offer this feature as an + /// operator. + /// + /// FIXME: assign() has not yet been implemented, but the + /// intention is that it will copy the rows of the argument table + /// into this table after clearing the original contents, and for + /// target tables without a shared spec, it would also copy the + /// spec. For target tables with shared spec, it would be an error + /// to pass an argument table with an incompatible spec, but + /// assign() would not check for spec compatibility. This would + /// make it ideal as a basis for implementing operator=() for + /// typed tables. + Table& operator=(const Table&) = delete; + + /// Used when constructing an accessor whose lifetime is going to be managed + /// by reference counting. The lifetime of accessors of free-standing tables + /// allocated on the stack by the application is not managed by reference + /// counting, so that is a case where this tag must **not** be specified. + class ref_count_tag { + }; + + /// Create an uninitialized accessor whose lifetime is managed by reference + /// counting. + Table(ref_count_tag, Allocator&); + + void init(ref_type top_ref, ArrayParent*, size_t ndx_in_parent, bool skip_create_column_accessors = false); + void init(Spec* shared_spec, ArrayParent* parent_column, size_t parent_row_ndx); + + static void do_insert_column(Descriptor&, size_t col_ndx, DataType type, StringData name, + LinkTargetInfo& link_target_info, bool nullable = false); + static void do_insert_column_unless_exists(Descriptor&, size_t col_ndx, DataType type, StringData name, + LinkTargetInfo& link, bool nullable = false, + bool* was_inserted = nullptr); + static void do_erase_column(Descriptor&, size_t col_ndx); + static void do_rename_column(Descriptor&, size_t col_ndx, StringData name); + + static void do_add_search_index(Descriptor&, size_t col_ndx); + static void do_remove_search_index(Descriptor&, size_t col_ndx); + + struct InsertSubtableColumns; + struct EraseSubtableColumns; + struct RenameSubtableColumns; + + void insert_root_column(size_t col_ndx, DataType type, StringData name, LinkTargetInfo& link_target, + bool nullable = false); + void erase_root_column(size_t col_ndx); + void do_insert_root_column(size_t col_ndx, ColumnType, StringData name, bool nullable = false); + void do_erase_root_column(size_t col_ndx); + void do_set_link_type(size_t col_ndx, LinkType); + void insert_backlink_column(size_t origin_table_ndx, size_t origin_col_ndx, size_t backlink_col_ndx); + void erase_backlink_column(size_t origin_table_ndx, size_t origin_col_ndx); + void update_link_target_tables(size_t old_col_ndx_begin, size_t new_col_ndx_begin); + void update_link_target_tables_after_column_move(size_t moved_from, size_t moved_to); + + struct SubtableUpdater { + virtual void update(const SubtableColumn&, Array& subcolumns) = 0; + virtual void update_accessor(Table&) = 0; + virtual ~SubtableUpdater() + { + } + }; + static void update_subtables(Descriptor&, SubtableUpdater*); + void update_subtables(const size_t* col_path_begin, const size_t* col_path_end, SubtableUpdater*); + + struct AccessorUpdater { + virtual void update(Table&) = 0; + virtual void update_parent(Table&) = 0; + virtual ~AccessorUpdater() + { + } + }; + void update_accessors(const size_t* col_path_begin, const size_t* col_path_end, AccessorUpdater&); + + void create_degen_subtab_columns(); + ColumnBase* create_column_accessor(ColumnType, size_t col_ndx, size_t ndx_in_parent); + void destroy_column_accessors() noexcept; + + /// Called in the context of Group::commit() to ensure that + /// attached table accessors stay valid across a commit. Please + /// note that this works only for non-transactional commits. Table + /// accessors obtained during a transaction are always detached + /// when the transaction ends. + void update_from_parent(size_t old_baseline) noexcept; + + // Support function for conversions + void to_string_header(std::ostream& out, std::vector& widths) const; + void to_string_row(size_t row_ndx, std::ostream& out, const std::vector& widths) const; + + // recursive methods called by to_json, to follow links + void to_json(std::ostream& out, size_t link_depth, std::map& renames, + std::vector& followed) const; + void to_json_row(size_t row_ndx, std::ostream& out, size_t link_depth, + std::map& renames, std::vector& followed) const; + void to_json_row(size_t row_ndx, std::ostream& out, size_t link_depth = 0, + std::map* renames = nullptr) const; + + // Detach accessor from underlying table. Caller must ensure that + // a reference count exists upon return, for example by obtaining + // an extra reference count before the call. + // + // This function puts this table accessor into the detached + // state. This detaches it from the underlying structure of array + // nodes. It also recursively detaches accessors for subtables, + // and the type descriptor accessor. When this function returns, + // is_attached() will return false. + // + // This function may be called for a table accessor that is + // already in the detached state (idempotency). + // + // It is also valid to call this function for a table accessor + // that has not yet been detached, but whose underlying structure + // of arrays have changed in an unpredictable/unknown way. This + // kind of change generally happens when a modifying table + // operation fails, and also when one transaction is ended and a + // new one is started. + void detach() noexcept; + + /// Detach and remove all attached row, link list, and subtable + /// accessors. This function does not discard the descriptor accessor, if + /// any, and it does not discard column accessors either. + void discard_child_accessors() noexcept; + + void discard_row_accessors() noexcept; + + // Detach the type descriptor accessor if it exists. + void discard_desc_accessor() noexcept; + + void bind_ptr() const noexcept; + void unbind_ptr() const noexcept; + bool has_references() const noexcept; + + void register_view(const TableViewBase* view); + void unregister_view(const TableViewBase* view) noexcept; + void move_registered_view(const TableViewBase* old_addr, const TableViewBase* new_addr) noexcept; + void discard_views() noexcept; + + void register_row_accessor(RowBase*) const noexcept; + void unregister_row_accessor(RowBase*) const noexcept; + void do_unregister_row_accessor(RowBase*) const noexcept; + + class UnbindGuard; + + ColumnType get_real_column_type(size_t column_ndx) const noexcept; + + /// If this table is a group-level table, the parent group is returned, + /// otherwise null is returned. + Group* get_parent_group() const noexcept; + + const ColumnBase& get_column_base(size_t column_ndx) const noexcept; + ColumnBase& get_column_base(size_t column_ndx); + + const ColumnBaseWithIndex& get_column_base_indexed(size_t ndx) const noexcept; + ColumnBaseWithIndex& get_column_base_indexed(size_t ndx); + + template + T& get_column(size_t ndx); + + template + const T& get_column(size_t ndx) const noexcept; + + IntegerColumn& get_column(size_t column_ndx); + const IntegerColumn& get_column(size_t column_ndx) const noexcept; + IntNullColumn& get_column_int_null(size_t column_ndx); + const IntNullColumn& get_column_int_null(size_t column_ndx) const noexcept; + FloatColumn& get_column_float(size_t column_ndx); + const FloatColumn& get_column_float(size_t column_ndx) const noexcept; + DoubleColumn& get_column_double(size_t column_ndx); + const DoubleColumn& get_column_double(size_t column_ndx) const noexcept; + StringColumn& get_column_string(size_t column_ndx); + const StringColumn& get_column_string(size_t column_ndx) const noexcept; + BinaryColumn& get_column_binary(size_t column_ndx); + const BinaryColumn& get_column_binary(size_t column_ndx) const noexcept; + StringEnumColumn& get_column_string_enum(size_t column_ndx); + const StringEnumColumn& get_column_string_enum(size_t column_ndx) const noexcept; + SubtableColumn& get_column_table(size_t column_ndx); + const SubtableColumn& get_column_table(size_t column_ndx) const noexcept; + MixedColumn& get_column_mixed(size_t column_ndx); + const MixedColumn& get_column_mixed(size_t column_ndx) const noexcept; + TimestampColumn& get_column_timestamp(size_t column_ndx); + const TimestampColumn& get_column_timestamp(size_t column_ndx) const noexcept; + const LinkColumnBase& get_column_link_base(size_t ndx) const noexcept; + LinkColumnBase& get_column_link_base(size_t ndx); + const LinkColumn& get_column_link(size_t ndx) const noexcept; + LinkColumn& get_column_link(size_t ndx); + const LinkListColumn& get_column_link_list(size_t ndx) const noexcept; + LinkListColumn& get_column_link_list(size_t ndx); + const BacklinkColumn& get_column_backlink(size_t ndx) const noexcept; + BacklinkColumn& get_column_backlink(size_t ndx); + + void verify_column(size_t col_ndx, const ColumnBase* col) const; + + void instantiate_before_change(); + void validate_column_type(const ColumnBase& col, ColumnType expected_type, size_t ndx) const; + + static size_t get_size_from_ref(ref_type top_ref, Allocator&) noexcept; + static size_t get_size_from_ref(ref_type spec_ref, ref_type columns_ref, Allocator&) noexcept; + + const Table* get_parent_table_ptr(size_t* column_ndx_out = nullptr) const noexcept; + Table* get_parent_table_ptr(size_t* column_ndx_out = nullptr) noexcept; + + /// Create an empty table with independent spec and return just + /// the reference to the underlying memory. + static ref_type create_empty_table(Allocator&); + + /// Create a column of the specified type, fill it with the + /// specified number of default values, and return just the + /// reference to the underlying memory. + static ref_type create_column(ColumnType column_type, size_t num_default_values, bool nullable, Allocator&); + + /// Construct a copy of the columns array of this table using the + /// specified allocator and return just the ref to that array. + /// + /// In the clone, no string column will be of the enumeration + /// type. + ref_type clone_columns(Allocator&) const; + + /// Construct a complete copy of this table (including its spec) + /// using the specified allocator and return just the ref to the + /// new top array. + ref_type clone(Allocator&) const; + + /// True for `col_type_Link` and `col_type_LinkList`. + static bool is_link_type(ColumnType) noexcept; + + void connect_opposite_link_columns(size_t link_col_ndx, Table& target_table, size_t backlink_col_ndx) noexcept; + + //@{ + + /// Cascading removal of strong links. + /// + /// cascade_break_backlinks_to() removes all backlinks pointing to the row + /// at \a row_ndx. Additionally, if this causes the number of **strong** + /// backlinks originating from a particular opposite row (target row of + /// corresponding forward link) to drop to zero, and that row is not already + /// in \a state.rows, then that row is added to \a state.rows, and + /// cascade_break_backlinks_to() is called recursively for it. This + /// operation is the first half of the cascading row removal operation. The + /// second half is performed by passing the resulting contents of \a + /// state.rows to remove_backlink_broken_rows(). + /// + /// Operations that trigger cascading row removal due to explicit removal of + /// one or more rows (the *initiating rows*), should add those rows to \a + /// rows initially, and then call cascade_break_backlinks_to() once for each + /// of them in turn. This is opposed to carrying out the explicit row + /// removals independently, which is also possible, but does require that + /// any initiating rows, that end up in \a state.rows due to link cycles, + /// are removed before passing \a state.rows to + /// remove_backlink_broken_rows(). In the case of clear(), where all rows of + /// a table are explicitly removed, it is better to use + /// cascade_break_backlinks_to_all_rows(), and then carry out the table + /// clearing as an independent step. For operations that trigger cascading + /// row removal for other reasons than explicit row removal, \a state.rows + /// must be empty initially, but cascade_break_backlinks_to() must still be + /// called for each of the initiating rows. + /// + /// When the last non-recursive invocation of cascade_break_backlinks_to() + /// returns, all forward links originating from a row in \a state.rows have + /// had their reciprocal backlinks removed, so remove_backlink_broken_rows() + /// does not perform reciprocal backlink removal at all. Additionally, all + /// remaining backlinks originating from rows in \a state.rows are + /// guaranteed to point to rows that are **not** in \a state.rows. This is + /// true because any backlink that was pointing to a row in \a state.rows + /// has been removed by one of the invocations of + /// cascade_break_backlinks_to(). The set of forward links, that correspond + /// to these remaining backlinks, is precisely the set of forward links that + /// need to be removed/nullified by remove_backlink_broken_rows(), which it + /// does by way of reciprocal forward link removal. Note also, that while + /// all the rows in \a state.rows can have remaining **weak** backlinks + /// originating from them, only the initiating rows in \a state.rows can + /// have remaining **strong** backlinks originating from them. This is true + /// because a non-initiating row is added to \a state.rows only when the + /// last backlink originating from it is lost. + /// + /// Each row removal is replicated individually (as opposed to one + /// replication instruction for the entire cascading operation). This is + /// done because it provides an easy way for Group::advance_transact() to + /// know which tables are affected by the cascade. Note that this has + /// several important consequences: First of all, the replication log + /// receiver must execute the row removal instructions in a non-cascading + /// fashion, meaning that there will be an asymmetry between the two sides + /// in how the effect of the cascade is brought about. While this is fine + /// for simple 1-to-1 replication, it may end up interfering badly with + /// *transaction merging*, when that feature is introduced. Imagine for + /// example that the cascade initiating operation gets canceled during + /// conflict resolution, but some, or all of the induced row removals get to + /// stay. That would break causal consistency. It is important, however, for + /// transaction merging that the cascaded row removals are explicitly + /// mentioned in the replication log, such that they can be used to adjust + /// row indexes during the *operational transform*. + /// + /// cascade_break_backlinks_to_all_rows() has the same affect as calling + /// cascade_break_backlinks_to() once for each row in the table. When + /// calling this function, \a state.stop_on_table must be set to the origin + /// table (origin table of corresponding forward links), and \a + /// state.stop_on_link_list_column must be null. + /// + /// It is immaterial which table remove_backlink_broken_rows() is called on, + /// as long it that table is in the same group as the removed rows. + + void cascade_break_backlinks_to(size_t row_ndx, CascadeState& state); + void cascade_break_backlinks_to_all_rows(CascadeState& state); + void remove_backlink_broken_rows(const CascadeState&); + + //@} + + /// Used by query. Follows chain of link columns and returns final target table + const Table* get_link_chain_target(const std::vector& link_chain) const; + + /// Remove the specified row by the 'move last over' method. + void do_move_last_over(size_t row_ndx); + + // Precondition: 1 <= end - begin + size_t* record_subtable_path(size_t* begin, size_t* end) const noexcept; + + /// Check if an accessor exists for the specified subtable. If it does, + /// return a pointer to it, otherwise return null. This function assumes + /// that the specified column index in a valid index into `m_cols` but does + /// not otherwise assume more than minimal accessor consistency (see + /// AccessorConsistencyLevels.) + TableRef get_subtable_accessor(size_t col_ndx, size_t row_ndx) noexcept; + + /// Unless the column accessor is missing, this function returns the + /// accessor for the target table of the specified link-type column. The + /// column accessor is said to be missing if `m_cols[col_ndx]` is null, and + /// this can happen only during certain operations such as the updating of + /// the accessor tree when a read transaction is advanced. Note that for + /// link type columns, the target table accessor exists when, and only when + /// the origin table accessor exists. This function assumes that the + /// specified column index in a valid index into `m_cols` and that the + /// column is a link-type column. Beyond that, it assume nothing more than + /// minimal accessor consistency (see AccessorConsistencyLevels.) + Table* get_link_target_table_accessor(size_t col_ndx) noexcept; + + void discard_subtable_accessor(size_t col_ndx, size_t row_ndx) noexcept; + + void adj_acc_insert_rows(size_t row_ndx, size_t num_rows) noexcept; + void adj_acc_erase_row(size_t row_ndx) noexcept; + void adj_acc_swap_rows(size_t row_ndx_1, size_t row_ndx_2) noexcept; + void adj_acc_move_row(size_t from_ndx, size_t to_ndx) noexcept; + void adj_acc_merge_rows(size_t old_row_ndx, size_t new_row_ndx) noexcept; + + /// Adjust this table accessor and its subordinates after move_last_over() + /// (or its inverse). + /// + /// First, any row, subtable, or link list accessors registered as being at + /// \a to_row_ndx will be detached, as that row is assumed to have been + /// replaced. Next, any row, subtable, or link list accessors registered as + /// being at \a from_row_ndx, will be reregistered as being at \a + /// to_row_ndx, as the row at \a from_row_ndx is assumed to have been moved + /// to \a to_row_ndx. + /// + /// Crucially, if \a to_row_ndx is equal to \a from_row_ndx, then row, + /// subtable, or link list accessors at that row are **still detached**. + /// + /// Additionally, this function causes all link-adjacent tables to be marked + /// (dirty). Two tables are link-adjacent if one is the target table of a + /// link column of the other table. Note that this marking follows these + /// relations in both directions, but only to a depth of one. + /// + /// When this function is used in connection with move_last_over(), set \a + /// to_row_ndx to the index of the row to be removed, and set \a + /// from_row_ndx to the index of the last row in the table. As mentioned + /// earlier, this function can also be used in connection with the **inverse + /// of** move_last_over(), which is an operation that vacates a row by + /// moving its contents into a new last row of the table. In that case, set + /// \a to_row_ndx to one plus the index of the last row in the table, and + /// set \a from_row_ndx to the index of the row to be vacated. + /// + /// This function is used as part of Table::refresh_accessor_tree() to + /// promote the state of the accessors from Minimal Consistency into + /// Structural Correspondence, so it must be able to execute without + /// accessing the underlying array nodes. + void adj_acc_move_over(size_t from_row_ndx, size_t to_row_ndx) noexcept; + + void adj_acc_clear_root_table() noexcept; + void adj_acc_clear_nonroot_table() noexcept; + void adj_row_acc_insert_rows(size_t row_ndx, size_t num_rows) noexcept; + void adj_row_acc_erase_row(size_t row_ndx) noexcept; + void adj_row_acc_swap_rows(size_t row_ndx_1, size_t row_ndx_2) noexcept; + void adj_row_acc_move_row(size_t from_ndx, size_t to_ndx) noexcept; + void adj_row_acc_merge_rows(size_t old_row_ndx, size_t new_row_ndx) noexcept; + + /// Called by adj_acc_move_over() to adjust row accessors. + void adj_row_acc_move_over(size_t from_row_ndx, size_t to_row_ndx) noexcept; + + void adj_insert_column(size_t col_ndx); + void adj_erase_column(size_t col_ndx) noexcept; + + bool is_marked() const noexcept; + void mark() noexcept; + void unmark() noexcept; + void recursive_mark() noexcept; + void mark_link_target_tables(size_t col_ndx_begin) noexcept; + void mark_opposite_link_tables() noexcept; + + Replication* get_repl() noexcept; + + void set_ndx_in_parent(size_t ndx_in_parent) noexcept; + + /// Refresh the part of the accessor tree that is rooted at this + /// table. Subtable accessors will be refreshed only if they are marked + /// (Table::m_mark), and this applies recursively to subtables of + /// subtables. All refreshed table accessors (including this one) will be + /// unmarked upon return. + /// + /// The following conditions are necessary and sufficient for the proper + /// operation of this function: + /// + /// - This table must be a group-level table, or a subtable. It must not be + /// a free-standing table (because a free-standing table has no parent). + /// + /// - The `index in parent` property is correct. The `index in parent` + /// property of the table is the `index in parent` property of + /// `m_columns` for subtables with shared descriptor, and the `index in + /// parent` property of `m_top` for all other tables. + /// + /// - If this table has shared descriptor, then the `index in parent` + /// property of the contained spec accessor is correct. + /// + /// - The parent accessor is in a valid state (already refreshed). If the + /// parent is a group, then the group accessor (excluding its table + /// accessors) must be in a valid state. If the parent is a table, then + /// the table accessor (excluding its subtable accessors) must be in a + /// valid state. + /// + /// - Every descendant subtable accessor is marked if it needs to be + /// refreshed, or if it has a descendant accessor that needs to be + /// refreshed. + /// + /// - This table accessor, as well as all its descendant accessors, are in + /// structural correspondence with the underlying node hierarchy whose + /// root ref is stored in the parent (see AccessorConsistencyLevels). + void refresh_accessor_tree(); + + void refresh_spec_accessor(); + + void refresh_column_accessors(size_t col_ndx_begin = 0); + + // Look for link columns starting from col_ndx_begin. + // If a link column is found, follow the link and update it's + // backlink column accessor if it is in different table. + void refresh_link_target_accessors(size_t col_ndx_begin = 0); + + bool is_cross_table_link_target() const noexcept; + std::recursive_mutex* get_parent_accessor_management_lock() const; +#ifdef REALM_DEBUG + void to_dot_internal(std::ostream&) const; +#endif + + friend class SubtableNode; + friend class _impl::TableFriend; + friend class Query; + friend class metrics::QueryInfo; + template + friend class util::bind_ptr; + template + friend class SimpleQuerySupport; + friend class LangBindHelper; + friend class TableViewBase; + template + friend class Columns; + friend class Columns; + friend class ParentNode; + template + friend class SequentialGetter; + friend struct util::serializer::SerialisationState; + friend class RowBase; + friend class LinksToNode; + friend class LinkMap; + friend class LinkView; + friend class Group; +}; + +class Table::Parent : public ArrayParent { +public: + ~Parent() noexcept override + { + } + +protected: + virtual StringData get_child_name(size_t child_ndx) const noexcept; + + /// If children are group-level tables, then this function returns the + /// group. Otherwise it returns null. + virtual Group* get_parent_group() noexcept; + + /// If children are subtables, then this function returns the + /// parent table. Otherwise it returns null. + /// + /// If \a column_ndx_out is not null, this function must assign the index of + /// the column within the parent table to `*column_ndx_out` when , and only + /// when this table parent is a column in a parent table. + virtual Table* get_parent_table(size_t* column_ndx_out = nullptr) noexcept; + + virtual Spec* get_subtable_spec() noexcept; + + /// Must be called whenever a child table accessor is about to be destroyed. + /// + /// Note that the argument is a pointer to the child Table rather than its + /// `ndx_in_parent` property. This is because only minimal accessor + /// consistency can be assumed by this function. + virtual void child_accessor_destroyed(Table* child) noexcept = 0; + + + virtual size_t* record_subtable_path(size_t* begin, size_t* end) noexcept; + virtual std::recursive_mutex* get_accessor_management_lock() noexcept = 0; + + friend class Table; +}; + + +// Implementation: + + +inline uint_fast64_t Table::get_version_counter() const noexcept +{ + return observe_version(); +} + +inline uint64_t Table::observe_version() const noexcept +{ + m_top.get_alloc().observe_version(); + return m_version; +} + +inline void Table::bump_version(bool bump_global) const noexcept +{ + if (bump_global) { + // This is only set on initial entry through an operation on the same + // table. recursive calls (via parent or via backlinks) must be done + // with bump_global=false. + m_top.get_alloc().bump_global_version(); + } + if (m_top.get_alloc().should_propagate_version(m_version)) { + if (const Table* parent = get_parent_table_ptr()) + parent->bump_version(false); + // Recurse through linked tables, use m_mark to avoid infinite recursion + for (auto& column_ptr : m_cols) { + // We may meet a null pointer in place of a backlink column, pending + // replacement with a new one. This can happen ONLY when creation of + // the corresponding forward link column in the origin table is + // pending as well. In this case it is ok to just ignore the zeroed + // backlink column, because the origin table is guaranteed to also + // be refreshed/marked dirty and hence have it's version bumped. + if (column_ptr != nullptr) + column_ptr->bump_link_origin_table_version(); + } + } +} + +inline void Table::remove(size_t row_ndx) +{ + bool is_move_last_over = false; + erase_row(row_ndx, is_move_last_over); // Throws +} + +inline void Table::move_last_over(size_t row_ndx) +{ + bool is_move_last_over = true; + erase_row(row_ndx, is_move_last_over); // Throws +} + +inline void Table::remove_last() +{ + if (!is_empty()) + remove(size() - 1); +} + +// A good place to start if you want to understand the memory ordering +// chosen for the operations below is http://preshing.com/20130922/acquire-and-release-fences/ +inline void Table::bind_ptr() const noexcept +{ + m_ref_count.fetch_add(1, std::memory_order_relaxed); +} + +inline void Table::unbind_ptr() const noexcept +{ + // The delete operation runs the destructor, and the destructor + // must always see all changes to the object being deleted. + // Within each thread, we know that unbind_ptr will always happen after + // any changes, so it is a convenient place to do a release. + // The release will then be observed by the acquire fence in + // the case where delete is actually called (the count reaches 0) + if (m_ref_count.fetch_sub(1, std::memory_order_release) != 1) { + return; + } + + std::atomic_thread_fence(std::memory_order_acquire); + + std::recursive_mutex* lock = get_parent_accessor_management_lock(); + if (lock) { + std::lock_guard lg(*lock); + if (m_ref_count == 0) + delete this; + } + else { + delete this; + } +} + +inline bool Table::has_references() const noexcept +{ + return m_ref_count.load() > 0; +} + +inline void Table::register_view(const TableViewBase* view) +{ + util::LockGuard lock(m_accessor_mutex); + // Casting away constness here - operations done on tableviews + // through m_views are all internal and preserving "some" kind + // of logical constness. + m_views.push_back(const_cast(view)); +} + +inline bool Table::is_attached() const noexcept +{ + // Note that it is not possible to tie the state of attachment of a table to + // the state of attachment of m_top, because tables with shared spec do not + // have a 'top' array. Neither is it possible to tie it to the state of + // attachment of m_columns, because subtables with shared spec start out in + // a degenerate form where they do not have a 'columns' array. For these + // reasons, it is neccessary to define the notion of attachment for a table + // as follows: A table is attached if, and ony if m_column stores a non-null + // parent pointer. This works because even for degenerate subtables, + // m_columns is initialized with the correct parent pointer. + return m_columns.has_parent(); +} + +inline StringData Table::get_name() const noexcept +{ + REALM_ASSERT(is_attached()); + const Array& real_top = m_top.is_attached() ? m_top : m_columns; + ArrayParent* parent = real_top.get_parent(); + if (!parent) + return StringData(""); + size_t index_in_parent = real_top.get_ndx_in_parent(); + REALM_ASSERT(dynamic_cast(parent)); + return static_cast(parent)->get_child_name(index_in_parent); +} + +inline size_t Table::get_column_count() const noexcept +{ + REALM_ASSERT(is_attached()); + return m_spec->get_public_column_count(); +} + +inline StringData Table::get_column_name(size_t ndx) const noexcept +{ + REALM_ASSERT_3(ndx, <, get_column_count()); + return m_spec->get_column_name(ndx); +} + +inline size_t Table::get_column_index(StringData name) const noexcept +{ + REALM_ASSERT(is_attached()); + return m_spec->get_column_index(name); +} + +inline ColumnType Table::get_real_column_type(size_t ndx) const noexcept +{ + REALM_ASSERT_3(ndx, <, m_spec->get_column_count()); + return m_spec->get_column_type(ndx); +} + +inline DataType Table::get_column_type(size_t ndx) const noexcept +{ + REALM_ASSERT_3(ndx, <, m_spec->get_column_count()); + return m_spec->get_public_column_type(ndx); +} + +template +inline Col& Table::get_column(size_t ndx) +{ + ColumnBase& col = get_column_base(ndx); +#ifdef REALM_DEBUG + validate_column_type(col, col_type, ndx); +#endif + REALM_ASSERT(typeid(Col) == typeid(col)); + return static_cast(col); +} + +template +inline const Col& Table::get_column(size_t ndx) const noexcept +{ + const ColumnBase& col = get_column_base(ndx); +#ifdef REALM_DEBUG + validate_column_type(col, col_type, ndx); +#endif + REALM_ASSERT(typeid(Col) == typeid(col)); + return static_cast(col); +} + +inline bool Table::has_shared_type() const noexcept +{ + REALM_ASSERT(is_attached()); + return !m_top.is_attached(); +} + +inline void Table::verify_column(size_t col_ndx, const ColumnBase* col) const +{ + // Check if the column exists at the expected location + if (REALM_LIKELY(col_ndx < m_cols.size() && m_cols[col_ndx] == col)) + return; + // The column might be elsewhere in the list + for (auto c : m_cols) { + if (c == col) + return; + } + throw LogicError(LogicError::column_does_not_exist); +} + +class Table::UnbindGuard { +public: + UnbindGuard(Table* table) noexcept + : m_table(table) + { + } + + ~UnbindGuard() noexcept + { + if (m_table) + m_table->unbind_ptr(); + } + + Table& operator*() const noexcept + { + return *m_table; + } + + Table* operator->() const noexcept + { + return m_table; + } + + Table* get() const noexcept + { + return m_table; + } + + Table* release() noexcept + { + Table* table = m_table; + m_table = nullptr; + return table; + } + +private: + Table* m_table; +}; + + +inline Table::Table(Allocator& alloc) + : m_top(alloc) + , m_columns(alloc) +{ + m_ref_count = 1; // Explicitly managed lifetime + + ref_type ref = create_empty_table(alloc); // Throws + Parent* parent = nullptr; + size_t ndx_in_parent = 0; + init(ref, parent, ndx_in_parent); +} + +inline Table::Table(const Table& t, Allocator& alloc) + : m_top(alloc) + , m_columns(alloc) +{ + m_ref_count = 1; // Explicitly managed lifetime + + ref_type ref = t.clone(alloc); // Throws + Parent* parent = nullptr; + size_t ndx_in_parent = 0; + init(ref, parent, ndx_in_parent); +} + +inline Table::Table(ref_count_tag, Allocator& alloc) + : m_top(alloc) + , m_columns(alloc) +{ + m_ref_count = 0; // Lifetime managed by reference counting +} + +inline Allocator& Table::get_alloc() const +{ + return m_top.get_alloc(); +} + +inline TableRef Table::create(Allocator& alloc) +{ + std::unique_ptr
table(new Table(ref_count_tag(), alloc)); // Throws + ref_type ref = create_empty_table(alloc); // Throws + Parent* parent = nullptr; + size_t ndx_in_parent = 0; + table->init(ref, parent, ndx_in_parent); // Throws + return table.release()->get_table_ref(); +} + +inline TableRef Table::copy(Allocator& alloc) const +{ + std::unique_ptr
table(new Table(ref_count_tag(), alloc)); // Throws + ref_type ref = clone(alloc); // Throws + Parent* parent = nullptr; + size_t ndx_in_parent = 0; + table->init(ref, parent, ndx_in_parent); // Throws + return table.release()->get_table_ref(); +} + +// For use by queries +template +inline Columns Table::column(size_t column_ndx) +{ + std::vector link_chain = std::move(m_link_chain); + m_link_chain.clear(); + + // Check if user-given template type equals Realm type. Todo, we should clean up and reuse all our + // type traits (all the is_same() cases below). + const Table* table = get_link_chain_target(link_chain); + + realm::DataType ct = table->get_column_type(column_ndx); + if (std::is_same::value && ct != type_Int) + throw LogicError(LogicError::type_mismatch); + else if (std::is_same::value && ct != type_Bool) + throw LogicError(LogicError::type_mismatch); + else if (std::is_same::value && ct != type_OldDateTime) + throw LogicError(LogicError::type_mismatch); + else if (std::is_same::value && ct != type_Float) + throw LogicError(LogicError::type_mismatch); + else if (std::is_same::value && ct != type_Double) + throw LogicError(LogicError::type_mismatch); + + if (std::is_same::value || std::is_same::value || std::is_same::value) { + link_chain.push_back(column_ndx); + } + + return Columns(column_ndx, this, std::move(link_chain)); +} + +template +inline Columns Table::column(const Table& origin, size_t origin_col_ndx) +{ + static_assert(std::is_same::value, ""); + + size_t origin_table_ndx = origin.get_index_in_group(); + const Table& current_target_table = *get_link_chain_target(m_link_chain); + size_t backlink_col_ndx = current_target_table.m_spec->find_backlink_column(origin_table_ndx, origin_col_ndx); + + std::vector link_chain = std::move(m_link_chain); + m_link_chain.clear(); + link_chain.push_back(backlink_col_ndx); + + return Columns(backlink_col_ndx, this, std::move(link_chain)); +} + +template +inline BacklinkCount Table::get_backlink_count() +{ + std::vector link_chain = std::move(m_link_chain); + m_link_chain.clear(); + return BacklinkCount(this, std::move(link_chain)); +} + +template +SubQuery Table::column(size_t column_ndx, Query subquery) +{ + static_assert(std::is_same::value, "A subquery must involve a link list or backlink column"); + return SubQuery(column(column_ndx), std::move(subquery)); +} + +template +SubQuery Table::column(const Table& origin, size_t origin_col_ndx, Query subquery) +{ + static_assert(std::is_same::value, "A subquery must involve a link list or backlink column"); + return SubQuery(column(origin, origin_col_ndx), std::move(subquery)); +} + +inline Table& Table::link(size_t link_column) +{ + m_link_chain.push_back(link_column); + return *this; +} + +inline Table& Table::backlink(const Table& origin, size_t origin_col_ndx) +{ + size_t origin_table_ndx = origin.get_index_in_group(); + const Table& current_target_table = *get_link_chain_target(m_link_chain); + size_t backlink_col_ndx = current_target_table.m_spec->find_backlink_column(origin_table_ndx, origin_col_ndx); + return link(backlink_col_ndx); +} + +inline bool Table::is_empty() const noexcept +{ + return m_size == 0; +} + +inline size_t Table::size() const noexcept +{ + return m_size; +} + +inline Table::RowExpr Table::get(size_t row_ndx) noexcept +{ + REALM_ASSERT_3(row_ndx, <, size()); + return RowExpr(this, row_ndx); +} + +inline Table::ConstRowExpr Table::get(size_t row_ndx) const noexcept +{ + REALM_ASSERT_3(row_ndx, <, size()); + return ConstRowExpr(this, row_ndx); +} + +inline Table::RowExpr Table::front() noexcept +{ + return get(0); +} + +inline Table::ConstRowExpr Table::front() const noexcept +{ + return get(0); +} + +inline Table::RowExpr Table::back() noexcept +{ + return get(m_size - 1); +} + +inline Table::ConstRowExpr Table::back() const noexcept +{ + return get(m_size - 1); +} + +inline Table::RowExpr Table::operator[](size_t row_ndx) noexcept +{ + return get(row_ndx); +} + +inline Table::ConstRowExpr Table::operator[](size_t row_ndx) const noexcept +{ + return get(row_ndx); +} + +inline size_t Table::add_empty_row(size_t num_rows) +{ + size_t row_ndx = m_size; + insert_empty_row(row_ndx, num_rows); // Throws + return row_ndx; // Return index of first new row +} + +inline ConstTableRef Table::get_subtable_tableref(size_t col_ndx, size_t row_ndx) const +{ + return const_cast(this)->get_subtable_tableref(col_ndx, row_ndx); // Throws +} + +inline bool Table::is_null_link(size_t col_ndx, size_t row_ndx) const noexcept +{ + return get_link(col_ndx, row_ndx) == realm::npos; +} + +inline ConstTableRef Table::get_link_target(size_t col_ndx) const noexcept +{ + return const_cast(this)->get_link_target(col_ndx); +} + +template +inline void Table::set_enum(size_t column_ndx, size_t row_ndx, E value) +{ + set_int(column_ndx, row_ndx, value); +} + +inline void Table::nullify_link(size_t col_ndx, size_t row_ndx) +{ + set_link(col_ndx, row_ndx, realm::npos); +} + +inline TableRef Table::get_subtable(size_t column_ndx, size_t row_ndx) +{ + return get_subtable_tableref(column_ndx, row_ndx); +} + +inline ConstTableRef Table::get_subtable(size_t column_ndx, size_t row_ndx) const +{ + return get_subtable_tableref(column_ndx, row_ndx); +} + +inline ConstTableRef Table::get_parent_table(size_t* column_ndx_out) const noexcept +{ + return ConstTableRef(get_parent_table_ptr(column_ndx_out)); +} + +inline TableRef Table::get_parent_table(size_t* column_ndx_out) noexcept +{ + return TableRef(get_parent_table_ptr(column_ndx_out)); +} + +inline bool Table::is_group_level() const noexcept +{ + return bool(get_parent_group()); +} + +inline bool Table::operator==(const Table& t) const +{ + return *m_spec == *t.m_spec && compare_rows(t); // Throws +} + +inline bool Table::operator!=(const Table& t) const +{ + return !(*this == t); // Throws +} + +inline bool Table::is_degenerate() const +{ + if (!is_attached()) { + throw LogicError{LogicError::detached_accessor}; + } + + return !m_columns.is_attached(); +} + +inline void Table::set_into_mixed(Table* parent, size_t col_ndx, size_t row_ndx) const +{ + parent->set_mixed_subtable(col_ndx, row_ndx, this); +} + +inline size_t Table::get_size_from_ref(ref_type top_ref, Allocator& alloc) noexcept +{ + const char* top_header = alloc.translate(top_ref); + std::pair p = Array::get_two(top_header, 0); + ref_type spec_ref = to_ref(p.first), columns_ref = to_ref(p.second); + return get_size_from_ref(spec_ref, columns_ref, alloc); +} + +inline Table* Table::get_parent_table_ptr(size_t* column_ndx_out) noexcept +{ + const Table* parent = const_cast(this)->get_parent_table_ptr(column_ndx_out); + return const_cast(parent); +} + +inline bool Table::is_link_type(ColumnType col_type) noexcept +{ + return col_type == col_type_Link || col_type == col_type_LinkList; +} + +inline size_t* Table::record_subtable_path(size_t* begin, size_t* end) const noexcept +{ + const Array& real_top = m_top.is_attached() ? m_top : m_columns; + size_t index_in_parent = real_top.get_ndx_in_parent(); + REALM_ASSERT_3(begin, <, end); + *begin++ = index_in_parent; + ArrayParent* parent = real_top.get_parent(); + REALM_ASSERT(parent); + REALM_ASSERT(dynamic_cast(parent)); + return static_cast(parent)->record_subtable_path(begin, end); +} + +inline size_t* Table::Parent::record_subtable_path(size_t* begin, size_t*) noexcept +{ + return begin; +} + +inline bool Table::is_marked() const noexcept +{ + return m_mark; +} + +inline void Table::mark() noexcept +{ + m_mark = true; +} + +inline void Table::unmark() noexcept +{ + m_mark = false; +} + +inline Replication* Table::get_repl() noexcept +{ + return m_top.get_alloc().get_replication(); +} + +inline void Table::set_ndx_in_parent(size_t ndx_in_parent) noexcept +{ + if (m_top.is_attached()) { + // Root table (independent descriptor) + m_top.set_ndx_in_parent(ndx_in_parent); + } + else { + // Subtable with shared descriptor + m_columns.set_ndx_in_parent(ndx_in_parent); + } +} + +// Declare our explicit specializations so that the inline wrappers don't try +// to instantiate them +template<> int64_t Table::get(size_t, size_t) const noexcept; +template<> util::Optional Table::get>(size_t, size_t) const noexcept; +template<> bool Table::get(size_t, size_t) const noexcept; +template<> Optional Table::get>(size_t, size_t) const noexcept; +template<> float Table::get(size_t, size_t) const noexcept; +template<> util::Optional Table::get>(size_t, size_t) const noexcept; +template<> double Table::get(size_t, size_t) const noexcept; +template<> util::Optional Table::get>(size_t, size_t) const noexcept; +template<> OldDateTime Table::get(size_t, size_t) const noexcept; +template<> Timestamp Table::get(size_t, size_t) const noexcept; +template<> StringData Table::get(size_t, size_t) const noexcept; +template<> BinaryData Table::get(size_t, size_t) const noexcept; +template<> BinaryIterator Table::get(size_t, size_t) const noexcept; +template<> Mixed Table::get(size_t, size_t) const noexcept; + +template<> void Table::set(size_t, size_t, int64_t, bool); +template<> void Table::set(size_t, size_t, bool, bool); +template<> void Table::set(size_t, size_t, float, bool); +template<> void Table::set(size_t, size_t, double, bool); +template<> void Table::set(size_t, size_t, OldDateTime, bool); +template<> void Table::set(size_t, size_t, Timestamp, bool); +template<> void Table::set(size_t, size_t, StringData, bool); +template<> void Table::set(size_t, size_t, BinaryData, bool); +template<> void Table::set(size_t, size_t, Mixed, bool); +template<> void Table::set(size_t, size_t, null, bool); + +template<> size_t Table::set_unique(size_t, size_t, int64_t); +template<> size_t Table::set_unique(size_t, size_t, StringData); +template<> size_t Table::set_unique(size_t, size_t, null); + + +inline int64_t Table::get_int(size_t col_ndx, size_t ndx) const noexcept +{ + if (is_nullable(col_ndx)) + return get>(col_ndx, ndx).value_or(0); + else + return get(col_ndx, ndx); +} + +inline size_t Table::set_int_unique(size_t col_ndx, size_t ndx, int_fast64_t value) +{ + return set_unique(col_ndx, ndx, value); +} + +inline void Table::set_int(size_t col_ndx, size_t ndx, int_fast64_t value, bool is_default) +{ + return set(col_ndx, ndx, value, is_default); +} + +inline Timestamp Table::get_timestamp(size_t col_ndx, size_t ndx) const noexcept +{ + return get(col_ndx, ndx); +} + +inline void Table::set_timestamp(size_t col_ndx, size_t ndx, Timestamp value, bool is_default) +{ + return set(col_ndx, ndx, value, is_default); +} + +inline bool Table::get_bool(size_t col_ndx, size_t ndx) const noexcept +{ + if (is_nullable(col_ndx)) + return get>(col_ndx, ndx).value_or(false); + else + return get(col_ndx, ndx); +} + +inline void Table::set_bool(size_t col_ndx, size_t ndx, bool value, bool is_default) +{ + return set(col_ndx, ndx, value, is_default); +} + +inline OldDateTime Table::get_olddatetime(size_t col_ndx, size_t ndx) const noexcept +{ + return get(col_ndx, ndx); +} + +inline void Table::set_olddatetime(size_t col_ndx, size_t ndx, OldDateTime value, bool is_default) +{ + return set(col_ndx, ndx, value, is_default); +} + +inline float Table::get_float(size_t col_ndx, size_t ndx) const noexcept +{ + float f = get(col_ndx, ndx); + return null::is_null_float(f) ? 0.0f : f; +} + +inline void Table::set_float(size_t col_ndx, size_t ndx, float value, bool is_default) +{ + return set(col_ndx, ndx, value, is_default); +} + +inline double Table::get_double(size_t col_ndx, size_t ndx) const noexcept +{ + double d = get(col_ndx, ndx); + return null::is_null_float(d) ? 0.0 : d; +} + +inline void Table::set_double(size_t col_ndx, size_t ndx, double value, bool is_default) +{ + return set(col_ndx, ndx, value, is_default); +} + +inline StringData Table::get_string(size_t col_ndx, size_t ndx) const noexcept +{ + return get(col_ndx, ndx); +} + +inline void Table::set_string(size_t col_ndx, size_t ndx, StringData value, bool is_default) +{ + return set(col_ndx, ndx, value, is_default); +} + +inline size_t Table::set_string_unique(size_t col_ndx, size_t ndx, StringData value) +{ + return set_unique(col_ndx, ndx, value); +} + +inline BinaryData Table::get_binary(size_t col_ndx, size_t ndx) const noexcept +{ + return get(col_ndx, ndx); +} + +inline BinaryIterator Table::get_binary_iterator(size_t col_ndx, size_t ndx) const noexcept +{ + return get(col_ndx, ndx); +} + +inline void Table::set_binary(size_t col_ndx, size_t ndx, BinaryData value, bool is_default) +{ + set(col_ndx, ndx, value, is_default); +} + +inline Mixed Table::get_mixed(size_t col_ndx, size_t ndx) const noexcept +{ + return get(col_ndx, ndx); +} + +inline void Table::set_mixed(size_t col_ndx, size_t ndx, Mixed value, bool is_default) +{ + set(col_ndx, ndx, value, is_default); +} + +inline void Table::set_null(size_t col_ndx, size_t ndx, bool is_default) +{ + set(col_ndx, ndx, null(), is_default); +} + +inline void Table::set_null_unique(size_t col_ndx, size_t ndx) +{ + set_unique(col_ndx, ndx, null()); +} + + +// This class groups together information about the target of a link column +// This is not a valid link if the target table == nullptr +struct LinkTargetInfo { + LinkTargetInfo(Table* target = nullptr, size_t backlink_ndx = realm::npos) + : m_target_table(target) + , m_backlink_col_ndx(backlink_ndx) + { + } + bool is_valid() const + { + return (m_target_table != nullptr); + } + Table* m_target_table; + size_t m_backlink_col_ndx; // a value of npos indicates the backlink should be appended +}; + +// The purpose of this class is to give internal access to some, but +// not all of the non-public parts of the Table class. +class _impl::TableFriend { +public: + typedef Table::UnbindGuard UnbindGuard; + + static ref_type create_empty_table(Allocator& alloc) + { + return Table::create_empty_table(alloc); // Throws + } + + static ref_type clone(const Table& table, Allocator& alloc) + { + return table.clone(alloc); // Throws + } + + static ref_type clone_columns(const Table& table, Allocator& alloc) + { + return table.clone_columns(alloc); // Throws + } + + static Table* create_accessor(Allocator& alloc, ref_type top_ref, Table::Parent* parent, size_t ndx_in_parent) + { + std::unique_ptr
table(new Table(Table::ref_count_tag(), alloc)); // Throws + table->init(top_ref, parent, ndx_in_parent); // Throws + return table.release(); + } + + static Table* create_accessor(Spec* shared_spec, Table::Parent* parent_column, size_t parent_row_ndx) + { + Allocator& alloc = shared_spec->get_alloc(); + std::unique_ptr
table(new Table(Table::ref_count_tag(), alloc)); // Throws + table->init(shared_spec, parent_column, parent_row_ndx); // Throws + return table.release(); + } + + // Intended to be used only by Group::create_table_accessor() + static Table* create_incomplete_accessor(Allocator& alloc, ref_type top_ref, Table::Parent* parent, + size_t ndx_in_parent) + { + std::unique_ptr
table(new Table(Table::ref_count_tag(), alloc)); // Throws + bool skip_create_column_accessors = true; + table->init(top_ref, parent, ndx_in_parent, skip_create_column_accessors); // Throws + return table.release(); + } + + // Intended to be used only by Group::create_table_accessor() + static void complete_accessor(Table& table) + { + table.refresh_column_accessors(); // Throws + } + + static void set_top_parent(Table& table, ArrayParent* parent, size_t ndx_in_parent) noexcept + { + table.m_top.set_parent(parent, ndx_in_parent); + } + + static void update_from_parent(Table& table, size_t old_baseline) noexcept + { + table.update_from_parent(old_baseline); + } + + static void detach(Table& table) noexcept + { + table.detach(); + } + + static void discard_row_accessors(Table& table) noexcept + { + table.discard_row_accessors(); + } + + static void discard_child_accessors(Table& table) noexcept + { + table.discard_child_accessors(); + } + + static void discard_subtable_accessor(Table& table, size_t col_ndx, size_t row_ndx) noexcept + { + table.discard_subtable_accessor(col_ndx, row_ndx); + } + + static void bind_ptr(Table& table) noexcept + { + table.bind_ptr(); + } + + static void unbind_ptr(Table& table) noexcept + { + table.unbind_ptr(); + } + + static bool compare_rows(const Table& a, const Table& b) + { + return a.compare_rows(b); // Throws + } + + static size_t get_size_from_ref(ref_type ref, Allocator& alloc) noexcept + { + return Table::get_size_from_ref(ref, alloc); + } + + static size_t get_size_from_ref(ref_type spec_ref, ref_type columns_ref, Allocator& alloc) noexcept + { + return Table::get_size_from_ref(spec_ref, columns_ref, alloc); + } + + static Spec& get_spec(Table& table) noexcept + { + return *table.m_spec; + } + + static const Spec& get_spec(const Table& table) noexcept + { + return *table.m_spec; + } + + static ColumnBase& get_column(const Table& table, size_t col_ndx) + { + return *table.m_cols[col_ndx]; + } + + static void do_remove(Table& table, size_t row_ndx) + { + bool broken_reciprocal_backlinks = false; + table.do_remove(row_ndx, broken_reciprocal_backlinks); // Throws + } + + static void do_move_last_over(Table& table, size_t row_ndx) + { + bool broken_reciprocal_backlinks = false; + table.do_move_last_over(row_ndx, broken_reciprocal_backlinks); // Throws + } + + static void do_swap_rows(Table& table, size_t row_ndx_1, size_t row_ndx_2) + { + table.do_swap_rows(row_ndx_1, row_ndx_2); // Throws + } + + static void do_move_row(Table& table, size_t from_ndx, size_t to_ndx) + { + table.do_move_row(from_ndx, to_ndx); // Throws + } + + static void do_merge_rows(Table& table, size_t row_ndx, size_t new_row_ndx) + { + table.do_merge_rows(row_ndx, new_row_ndx); // Throws + } + + static void do_clear(Table& table) + { + bool broken_reciprocal_backlinks = false; + table.do_clear(broken_reciprocal_backlinks); // Throws + } + + static void do_set_link(Table& table, size_t col_ndx, size_t row_ndx, size_t target_row_ndx) + { + table.do_set_link(col_ndx, row_ndx, target_row_ndx); // Throws + } + + static size_t get_backlink_count(const Table& table, size_t row_ndx, bool only_strong_links) noexcept + { + return table.get_backlink_count(row_ndx, only_strong_links); + } + + static void cascade_break_backlinks_to(Table& table, size_t row_ndx, CascadeState& state) + { + table.cascade_break_backlinks_to(row_ndx, state); // Throws + } + + static void remove_backlink_broken_rows(Table& table, const CascadeState& rows) + { + table.remove_backlink_broken_rows(rows); // Throws + } + + static size_t* record_subtable_path(const Table& table, size_t* begin, size_t* end) noexcept + { + return table.record_subtable_path(begin, end); + } + + static void insert_column(Descriptor& desc, size_t column_ndx, DataType type, StringData name, + LinkTargetInfo& link, bool nullable = false) + { + Table::do_insert_column(desc, column_ndx, type, name, link, nullable); // Throws + } + + static void insert_column_unless_exists(Descriptor& desc, size_t column_ndx, DataType type, StringData name, + LinkTargetInfo link, bool nullable = false, bool* was_inserted = nullptr) + { + Table::do_insert_column_unless_exists(desc, column_ndx, type, name, link, nullable, was_inserted); // Throws + } + + static void erase_column(Descriptor& desc, size_t column_ndx) + { + Table::do_erase_column(desc, column_ndx); // Throws + } + + static void rename_column(Descriptor& desc, size_t column_ndx, StringData name) + { + Table::do_rename_column(desc, column_ndx, name); // Throws + } + + static void add_search_index(Descriptor& desc, size_t column_ndx) + { + Table::do_add_search_index(desc, column_ndx); // Throws + } + + static void remove_search_index(Descriptor& desc, size_t column_ndx) + { + Table::do_remove_search_index(desc, column_ndx); // Throws + } + + static void set_link_type(Table& table, size_t column_ndx, LinkType link_type) + { + table.do_set_link_type(column_ndx, link_type); // Throws + } + + static void erase_row(Table& table, size_t row_ndx, bool is_move_last_over) + { + table.erase_row(row_ndx, is_move_last_over); // Throws + } + + static void batch_erase_rows(Table& table, const IntegerColumn& row_indexes, bool is_move_last_over) + { + table.batch_erase_rows(row_indexes, is_move_last_over); // Throws + } + + static TableRef get_subtable_accessor(Table& table, size_t col_ndx, size_t row_ndx) noexcept + { + return table.get_subtable_accessor(col_ndx, row_ndx); + } + + static const Table* get_link_target_table_accessor(const Table& table, size_t col_ndx) noexcept + { + return const_cast(table).get_link_target_table_accessor(col_ndx); + } + + static Table* get_link_target_table_accessor(Table& table, size_t col_ndx) noexcept + { + return table.get_link_target_table_accessor(col_ndx); + } + + static void adj_acc_insert_rows(Table& table, size_t row_ndx, size_t num_rows) noexcept + { + table.adj_acc_insert_rows(row_ndx, num_rows); + } + + static void adj_acc_erase_row(Table& table, size_t row_ndx) noexcept + { + table.adj_acc_erase_row(row_ndx); + } + + static void adj_acc_swap_rows(Table& table, size_t row_ndx_1, size_t row_ndx_2) noexcept + { + table.adj_acc_swap_rows(row_ndx_1, row_ndx_2); + } + + static void adj_acc_move_row(Table& table, size_t from_ndx, size_t to_ndx) noexcept + { + table.adj_acc_move_row(from_ndx, to_ndx); + } + + static void adj_acc_merge_rows(Table& table, size_t row_ndx_1, size_t row_ndx_2) noexcept + { + table.adj_acc_merge_rows(row_ndx_1, row_ndx_2); + } + + static void adj_acc_move_over(Table& table, size_t from_row_ndx, size_t to_row_ndx) noexcept + { + table.adj_acc_move_over(from_row_ndx, to_row_ndx); + } + + static void adj_acc_clear_root_table(Table& table) noexcept + { + table.adj_acc_clear_root_table(); + } + + static void adj_acc_clear_nonroot_table(Table& table) noexcept + { + table.adj_acc_clear_nonroot_table(); + } + + static void adj_insert_column(Table& table, size_t col_ndx) + { + table.adj_insert_column(col_ndx); // Throws + } + + static void adj_add_column(Table& table) + { + size_t num_cols = table.m_cols.size(); + table.adj_insert_column(num_cols); // Throws + } + + static void adj_erase_column(Table& table, size_t col_ndx) noexcept + { + table.adj_erase_column(col_ndx); + } + + static bool is_marked(const Table& table) noexcept + { + return table.is_marked(); + } + + static void mark(Table& table) noexcept + { + table.mark(); + } + + static void unmark(Table& table) noexcept + { + table.unmark(); + } + + static void recursive_mark(Table& table) noexcept + { + table.recursive_mark(); + } + + static void mark_link_target_tables(Table& table, size_t col_ndx_begin) noexcept + { + table.mark_link_target_tables(col_ndx_begin); + } + + static void mark_opposite_link_tables(Table& table) noexcept + { + table.mark_opposite_link_tables(); + } + + static DescriptorRef get_root_table_desc_accessor(Table& root_table) noexcept + { + return root_table.m_descriptor.lock(); + } + + typedef Table::AccessorUpdater AccessorUpdater; + static void update_accessors(Table& table, const size_t* col_path_begin, const size_t* col_path_end, + AccessorUpdater& updater) + { + table.update_accessors(col_path_begin, col_path_end, updater); // Throws + } + + static void refresh_accessor_tree(Table& table) + { + table.refresh_accessor_tree(); // Throws + } + + static void refresh_spec_accessor(Table& table) + { + table.refresh_spec_accessor(); // Throws + } + + static void set_ndx_in_parent(Table& table, size_t ndx_in_parent) noexcept + { + table.set_ndx_in_parent(ndx_in_parent); + } + + static bool is_link_type(ColumnType type) noexcept + { + return Table::is_link_type(type); + } + + static void bump_version(Table& table, bool bump_global = true) noexcept + { + table.bump_version(bump_global); + } + + static bool is_cross_table_link_target(const Table& table) + { + return table.is_cross_table_link_target(); + } + + static Group* get_parent_group(const Table& table) noexcept + { + return table.get_parent_group(); + } + + static Replication* get_repl(Table& table) noexcept + { + return table.get_repl(); + } + + static void register_view(Table& table, const TableViewBase* view) + { + table.register_view(view); // Throws + } + + static void unregister_view(Table& table, const TableViewBase* view) noexcept + { + table.unregister_view(view); + } + + static bool has_references(const Table& table) noexcept + { + return table.has_references(); + } +}; + + +} // namespace realm + +#endif // REALM_TABLE_HPP diff --git a/!main project/Pods/Realm/include/core/realm/table_ref.hpp b/!main project/Pods/Realm/include/core/realm/table_ref.hpp new file mode 100644 index 0000000..6e5c02b --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/table_ref.hpp @@ -0,0 +1,481 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_TABLE_REF_HPP +#define REALM_TABLE_REF_HPP + +#include +#include + +#include + +namespace realm { + + +class Table; + + +/// A reference-counting "smart pointer" for referring to table +/// accessors. +/// +/// The purpose of this smart pointer is to keep the referenced table +/// accessor alive for as long as anybody is referring to it, however, +/// for stack allocated table accessors, the lifetime is necessarily +/// determined by scope (see below). +/// +/// Please take note of the distinction between a "table" and a "table +/// accessor" here. A table accessor is an instance of `Table`, +/// and it may, or may not be attached to an +/// actual table at any specific point in time, but this state of +/// attachment of the accessor has nothing to do with the function of +/// the smart pointer. Also, in the rest of the documentation of this +/// class, whenever you see `Table::%foo`, you are supposed to read it +/// as, `Table::%foo`. +/// +/// +/// Table accessors are either created directly by an application via +/// a call to one of the public table constructors, or they are +/// created internally by the Realm library, such as when the +/// application calls Group::get_table(), Table::get_subtable(), or +/// Table::create(). +/// +/// Applications can safely assume that all table accessors, created +/// internally by the Realm library, have a lifetime that is managed +/// by reference counting. This means that the application can prolong +/// the lifetime of *such* table accessors indefinitely by holding on +/// to at least one smart pointer, but note that the guarantee of the +/// continued existence of the accessor, does not imply that the +/// accessor remains attached to the underlying table (see +/// Table::is_attached() for details). Accessors whose lifetime are +/// controlled by reference counting are destroyed exactly when the +/// reference count drops to zero. +/// +/// When an application creates a new table accessor by a direct call +/// to one of the public constructors, the lifetime of that table +/// accessor is *not*, and cannot be managed by reference +/// counting. This is true regardless of the way the accessor is +/// created (i.e., regardless of whether it is an automatic variable +/// on the stack, or created on the heap using `new`). However, for +/// convenience, but with one important caveat, it is still possible +/// to use smart pointers to refer to such accessors. The caveat is +/// that no smart pointers are allowed to refer to the accessor at the +/// point in time when its destructor is called. It is entirely the +/// responsibility of the application to ensure that this requirement +/// is met. Failing to do so, will result in undefined +/// behavior. Finally, please note that an application is always free +/// to use Table::create() as an alternative to creating free-standing +/// top-level tables on the stack, and that this is indeed neccessary +/// when fully reference counted lifetimes are required. +/// +/// So, at any time, and for any table accessor, an application can +/// call Table::get_table_ref() to obtain a smart pointer that refers +/// to that table, however, while that is always possible and safe, it +/// is not always possible to extend the lifetime of an accessor by +/// holding on to a smart pointer. The question of whether that is +/// possible, depends directly on the way the accessor was created. +/// +/// +/// Apart from keeping track of the number of references, these smart +/// pointers behaves almost exactly like regular pointers. In +/// particular, it is possible to dereference a TableRef and get a +/// `Table&` out of it, however, if you are not careful, this can +/// easily lead to dangling references: +/// +/// \code{.cpp} +/// +/// Table& sub_1 = *(table.get_subtable(0,0)); +/// sub_1.add_empty_row(); // Oops, sub_1 may be dangling! +/// +/// \endcode +/// +/// Whether `sub_1` is actually dangling in the example above will +/// depend on whether other references to the same subtable accessor +/// already exist, but it is never wise to rely in this. Here is a +/// safe and proper alternative: +/// +/// \code{.cpp} +/// +/// TableRef sub_2 = table.get_subtable(0,0); +/// sub_2.add_empty_row(); // Safe! +/// +/// void do_something(Table&); +/// do_something(*(table.get_subtable(0,0))); // Also safe! +/// +/// \endcode +/// +/// +/// \sa Table +/// \sa TableRef +template +class BasicTableRef : util::bind_ptr { +public: + constexpr BasicTableRef() noexcept + { + } + ~BasicTableRef() noexcept + { + } + + // Copy construct + BasicTableRef(const BasicTableRef& r) noexcept + : util::bind_ptr(r) + { + } + template + BasicTableRef(const BasicTableRef& r) noexcept + : util::bind_ptr(r) + { + } + + // Copy assign + BasicTableRef& operator=(const BasicTableRef&) noexcept; + template + BasicTableRef& operator=(const BasicTableRef&) noexcept; + + // Move construct + BasicTableRef(BasicTableRef&& r) noexcept + : util::bind_ptr(std::move(r)) + { + } + template + BasicTableRef(BasicTableRef&& r) noexcept + : util::bind_ptr(std::move(r)) + { + } + + // Move assign + BasicTableRef& operator=(BasicTableRef&&) noexcept; + template + BasicTableRef& operator=(BasicTableRef&&) noexcept; + + //@{ + /// Comparison + template + bool operator==(const BasicTableRef&) const noexcept; + + template + bool operator==(U*) const noexcept; + + template + bool operator!=(const BasicTableRef&) const noexcept; + + template + bool operator!=(U*) const noexcept; + + template + bool operator<(const BasicTableRef&) const noexcept; + + template + bool operator<(U*) const noexcept; + + template + bool operator>(const BasicTableRef&) const noexcept; + + template + bool operator>(U*) const noexcept; + + template + bool operator<=(const BasicTableRef&) const noexcept; + + template + bool operator<=(U*) const noexcept; + + template + bool operator>=(const BasicTableRef&) const noexcept; + + template + bool operator>=(U*) const noexcept; +//@} + +// Dereference +#ifdef __clang__ + // Clang has a bug that causes it to effectively ignore the 'using' declaration. + T& operator*() const noexcept + { + return util::bind_ptr::operator*(); + } +#else + using util::bind_ptr::operator*; +#endif + using util::bind_ptr::operator->; + + using util::bind_ptr::operator bool; + + T* get() const noexcept + { + return util::bind_ptr::get(); + } + void reset() noexcept + { + util::bind_ptr::reset(); + } + void reset(T* t) noexcept + { + util::bind_ptr::reset(t); + } + + void swap(BasicTableRef& r) noexcept + { + this->util::bind_ptr::swap(r); + } + friend void swap(BasicTableRef& a, BasicTableRef& b) noexcept + { + a.swap(b); + } + + template + friend BasicTableRef unchecked_cast(BasicTableRef
) noexcept; + + template + friend BasicTableRef unchecked_cast(BasicTableRef) noexcept; + +private: + template + struct GetRowAccType { + typedef void type; + }; + + typedef typename GetRowAccType::type RowAccessor; + +public: + /// Same as 'table[i]' where 'table' is the referenced table. + RowAccessor operator[](size_t i) const noexcept + { + return (*this->get())[i]; + } + + explicit BasicTableRef(T* t) noexcept + : util::bind_ptr(t) + { + } + + T* release() { return util::bind_ptr::release(); } +private: + friend class SubtableColumnBase; + friend class Table; + friend class Group; + + template + friend class BasicTableRef; + + typedef typename util::bind_ptr::casting_move_tag casting_move_tag; + template + BasicTableRef(BasicTableRef* r, casting_move_tag) noexcept + : util::bind_ptr(r, casting_move_tag()) + { + } +}; + + +typedef BasicTableRef
TableRef; +typedef BasicTableRef ConstTableRef; + + +template +inline std::basic_ostream& operator<<(std::basic_ostream& out, const BasicTableRef& p) +{ + out << static_cast(&*p); + return out; +} + +template +inline BasicTableRef unchecked_cast(TableRef t) noexcept +{ + return BasicTableRef(&t, typename BasicTableRef::casting_move_tag()); +} + +template +inline BasicTableRef unchecked_cast(ConstTableRef t) noexcept +{ + return BasicTableRef(&t, typename BasicTableRef::casting_move_tag()); +} + + +//@{ +/// Comparison +template +bool operator==(T*, const BasicTableRef&) noexcept; +template +bool operator!=(T*, const BasicTableRef&) noexcept; +template +bool operator<(T*, const BasicTableRef&) noexcept; +template +bool operator>(T*, const BasicTableRef&) noexcept; +template +bool operator<=(T*, const BasicTableRef&) noexcept; +template +bool operator>=(T*, const BasicTableRef&) noexcept; +//@} + + +// Implementation: + +template +inline BasicTableRef& BasicTableRef::operator=(const BasicTableRef& r) noexcept +{ + this->util::bind_ptr::operator=(r); + return *this; +} + +template +template +inline BasicTableRef& BasicTableRef::operator=(const BasicTableRef& r) noexcept +{ + this->util::bind_ptr::operator=(r); + return *this; +} + +template +inline BasicTableRef& BasicTableRef::operator=(BasicTableRef&& r) noexcept +{ + this->util::bind_ptr::operator=(std::move(r)); + return *this; +} + +template +template +inline BasicTableRef& BasicTableRef::operator=(BasicTableRef&& r) noexcept +{ + this->util::bind_ptr::operator=(std::move(r)); + return *this; +} + +template +template +bool BasicTableRef::operator==(const BasicTableRef& p) const noexcept +{ + return get() == p.get(); +} + +template +template +bool BasicTableRef::operator==(U* p) const noexcept +{ + return get() == p; +} + +template +template +bool BasicTableRef::operator!=(const BasicTableRef& p) const noexcept +{ + return get() != p.get(); +} + +template +template +bool BasicTableRef::operator!=(U* p) const noexcept +{ + return get() != p; +} + +template +template +bool BasicTableRef::operator<(const BasicTableRef& p) const noexcept +{ + return get() < p.get(); +} + +template +template +bool BasicTableRef::operator<(U* p) const noexcept +{ + return get() < p; +} + +template +template +bool BasicTableRef::operator>(const BasicTableRef& p) const noexcept +{ + return get() > p.get(); +} + +template +template +bool BasicTableRef::operator>(U* p) const noexcept +{ + return get() > p; +} + +template +template +bool BasicTableRef::operator<=(const BasicTableRef& p) const noexcept +{ + return get() <= p.get(); +} + +template +template +bool BasicTableRef::operator<=(U* p) const noexcept +{ + return get() <= p; +} + +template +template +bool BasicTableRef::operator>=(const BasicTableRef& p) const noexcept +{ + return get() >= p.get(); +} + +template +template +bool BasicTableRef::operator>=(U* p) const noexcept +{ + return get() >= p; +} + +template +bool operator==(T* a, const BasicTableRef& b) noexcept +{ + return b == a; +} + +template +bool operator!=(T* a, const BasicTableRef& b) noexcept +{ + return b != a; +} + +template +bool operator<(T* a, const BasicTableRef& b) noexcept +{ + return b > a; +} + +template +bool operator>(T* a, const BasicTableRef& b) noexcept +{ + return b < a; +} + +template +bool operator<=(T* a, const BasicTableRef& b) noexcept +{ + return b >= a; +} + +template +bool operator>=(T* a, const BasicTableRef& b) noexcept +{ + return b <= a; +} + + +} // namespace realm + +#endif // REALM_TABLE_REF_HPP diff --git a/!main project/Pods/Realm/include/core/realm/table_view.hpp b/!main project/Pods/Realm/include/core/realm/table_view.hpp new file mode 100644 index 0000000..3989da0 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/table_view.hpp @@ -0,0 +1,1614 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_TABLE_VIEW_HPP +#define REALM_TABLE_VIEW_HPP + +#include +#include +#include +#include +#include + +namespace realm { + +// Views, tables and synchronization between them: +// +// Views are built through queries against either tables or another view. +// Views may be restricted to only hold entries provided by another view. +// this other view is called the "restricting view". +// Views may be sorted in ascending or descending order of values in one ore more columns. +// +// Views remember the query from which it was originally built. +// Views remember the table from which it was originally built. +// Views remember a restricting view if one was used when it was originally built. +// Views remember the sorting criteria (columns and direction) +// +// A view may be operated in one of two distinct modes: *reflective* and *imperative*. +// Sometimes the term "reactive" is used instead of "reflective" with the same meaning. +// +// Reflective views: +// - A reflective view *always* *reflect* the result of running the query. +// If the underlying tables or tableviews change, the reflective view changes as well. +// A reflective view may need to rerun the query it was generated from, a potentially +// costly operation which happens on demand. +// - It does not matter whether changes are explicitly done within the transaction, or +// occur implicitly as part of advance_read() or promote_to_write(). +// +// Imperative views: +// - An imperative view only *initially* holds the result of the query. An imperative +// view *never* reruns the query. To force the view to match it's query (by rerunning it), +// the view must be operated in reflective mode. +// An imperative view can be modified explicitly. References can be added, removed or +// changed. +// +// - In imperative mode, the references in the view tracks movement of the referenced data: +// If you delete an entry which is referenced from a view, said reference is detached, +// not removed. +// - It does not matter whether the delete is done in-line (as part of the current transaction), +// or if it is done implicitly as part of advance_read() or promote_to_write(). +// +// The choice between reflective and imperative views might eventually be represented by a +// switch on the tableview, but isn't yet. For now, clients (bindings) must call sync_if_needed() +// to get reflective behavior. +// +// Use cases: +// +// 1. Presenting data +// The first use case (and primary motivator behind the reflective view) is to just track +// and present the state of the database. In this case, the view is operated in reflective +// mode, it is not modified within the transaction, and it is not used to modify data in +// other parts of the database. +// +// 2. Handover +// The second use case is "handover." The implicit rerun of the query in our first use case +// may be too costly to be acceptable on the main thread. Instead you want to run the query +// on a worker thread, but display it on the main thread. To achieve this, you need two +// SharedGroups locked on to the same version of the database. If you have that, you can +// *handover* a view from one thread/SharedGroup to the other. +// +// Handover is a two-step procedure. First, the accessors are *exported* from one SharedGroup, +// called the sourcing group, then it is *imported* into another SharedGroup, called the +// receiving group. The thread associated with the sourcing SharedGroup will be +// responsible for the export operation, while the thread associated with the receiving +// SharedGroup will do the import operation. +// +// 3. Iterating a view and changing data +// The third use case (and a motivator behind the imperative view) is when you want +// to make changes to the database in accordance with a query result. Imagine you want to +// find all employees with a salary below a limit and raise their salaries to the limit (pseudocode): +// +// promote_to_write(); +// view = table.where().less_than(salary_column,limit).find_all(); +// for (size_t i = 0; i < view.size(); ++i) { +// view.set_int(salary_column, i, limit); +// // add this to get reflective mode: view.sync_if_needed(); +// } +// commit_and_continue_as_read(); +// +// This is idiomatic imperative code and it works if the view is operated in imperative mode. +// +// If the view is operated in reflective mode, the behaviour surprises most people: When the +// first salary is changed, the entry no longer fullfills the query, so it is dropped from the +// view implicitly. view[0] is removed, view[1] moves to view[0] and so forth. But the next +// loop iteration has i=1 and refers to view[1], thus skipping view[0]. The end result is that +// every other employee get a raise, while the others don't. +// +// 4. Iterating intermixed with implicit updates +// This leads us to use case 4, which is similar to use case 3, but uses promote_to_write() +// intermixed with iterating a view. This is actually quite important to some, who do not want +// to end up with a large write transaction. +// +// view = table.where().less_than(salary_column,limit).find_all(); +// for (size_t i = 0; i < view.size(); ++i) { +// promote_to_write(); +// view.set_int(salary_column, i, limit); +// commit_and_continue_as_write(); +// } +// +// Anything can happen at the call to promote_to_write(). The key question then becomes: how +// do we support a safe way of realising the original goal (raising salaries) ? +// +// using the imperative operating mode: +// +// view = table.where().less_than(salary_column,limit).find_all(); +// for (size_t i = 0; i < view.size(); ++i) { +// promote_to_write(); +// // add r.sync_if_needed(); to get reflective mode +// if (r.is_row_attached(i)) { +// Row r = view[i]; +// r.set_int(salary_column, limit); +// } +// commit_and_continue_as_write(); +// } +// +// This is safe, and we just aim for providing low level safety: is_row_attached() can tell +// if the reference is valid, and the references in the view continue to point to the +// same object at all times, also following implicit updates. The rest is up to the +// application logic. +// +// It is important to see, that there is no guarantee that all relevant employees get +// their raise in cases whith concurrent updates. At every call to promote_to_write() new +// employees may be added to the underlying table, but as the view is in imperative mode, +// these new employees are not added to the view. Also at promote_to_write() an existing +// employee could recieve a (different, larger) raise which would then be overwritten and lost. +// However, these are problems that you should expect, since the activity is spread over multiple +// transactions. + + +/// Common base class for TableView and ConstTableView. +class TableViewBase : public RowIndexes { +public: + // - not in use / implemented yet: ... explicit calls to sync_if_needed() must be used + // to get 'reflective' mode. + // enum mode { mode_Reflective, mode_Imperative }; + // void set_operating_mode(mode); + // mode get_operating_mode(); + bool is_empty() const noexcept; + + // Tells if the table that this TableView points at still exists or has been deleted. + bool is_attached() const noexcept; + + bool is_row_attached(size_t row_ndx) const noexcept; + size_t size() const noexcept; + size_t num_attached_rows() const noexcept; + + // Get the query used to create this TableView + // The query will have a null source table if this tv was not created from + // a query + const Query& get_query() const noexcept; + + // Column information + const ColumnBase& get_column_base(size_t index) const; + + size_t get_column_count() const noexcept; + StringData get_column_name(size_t column_ndx) const noexcept; + size_t get_column_index(StringData name) const; + DataType get_column_type(size_t column_ndx) const noexcept; + + // Getting values + int64_t get_int(size_t column_ndx, size_t row_ndx) const noexcept; + bool get_bool(size_t column_ndx, size_t row_ndx) const noexcept; + OldDateTime get_olddatetime(size_t column_ndx, size_t row_ndx) const noexcept; + Timestamp get_timestamp(size_t column_ndx, size_t row_ndx) const noexcept; + float get_float(size_t column_ndx, size_t row_ndx) const noexcept; + double get_double(size_t column_ndx, size_t row_ndx) const noexcept; + StringData get_string(size_t column_ndx, size_t row_ndx) const noexcept; + BinaryData get_binary(size_t column_ndx, size_t row_ndx) const noexcept; + Mixed get_mixed(size_t column_ndx, size_t row_ndx) const noexcept; + DataType get_mixed_type(size_t column_ndx, size_t row_ndx) const noexcept; + size_t get_link(size_t column_ndx, size_t row_ndx) const noexcept; + + // Links + bool is_null_link(size_t column_ndx, size_t row_ndx) const noexcept; + + // Subtables + size_t get_subtable_size(size_t column_ndx, size_t row_ndx) const noexcept; + + // Searching + template + size_t find_first(size_t column_ndx, T value) const; + + size_t find_first_int(size_t column_ndx, int64_t value) const; + size_t find_first_bool(size_t column_ndx, bool value) const; + size_t find_first_olddatetime(size_t column_ndx, OldDateTime value) const; + size_t find_first_float(size_t column_ndx, float value) const; + size_t find_first_double(size_t column_ndx, double value) const; + size_t find_first_string(size_t column_ndx, StringData value) const; + size_t find_first_binary(size_t column_ndx, BinaryData value) const; + size_t find_first_timestamp(size_t column_ndx, Timestamp value) const; + + // Aggregate functions. count_target is ignored by all except Count. Hack because of bug in optional + // arguments in clang and vs2010 (fixed in 2012) + template + R aggregate(R (ColType::*aggregateMethod)(size_t, size_t, size_t, size_t*) const, size_t column_ndx, + T count_target, size_t* return_ndx = nullptr) const; + + int64_t sum_int(size_t column_ndx) const; + int64_t maximum_int(size_t column_ndx, size_t* return_ndx = nullptr) const; + int64_t minimum_int(size_t column_ndx, size_t* return_ndx = nullptr) const; + double average_int(size_t column_ndx, size_t* value_count = nullptr) const; + size_t count_int(size_t column_ndx, int64_t target) const; + + double sum_float(size_t column_ndx) const; + float maximum_float(size_t column_ndx, size_t* return_ndx = nullptr) const; + float minimum_float(size_t column_ndx, size_t* return_ndx = nullptr) const; + double average_float(size_t column_ndx, size_t* value_count = nullptr) const; + size_t count_float(size_t column_ndx, float target) const; + + double sum_double(size_t column_ndx) const; + double maximum_double(size_t column_ndx, size_t* return_ndx = nullptr) const; + double minimum_double(size_t column_ndx, size_t* return_ndx = nullptr) const; + double average_double(size_t column_ndx, size_t* value_count = nullptr) const; + size_t count_double(size_t column_ndx, double target) const; + + OldDateTime maximum_olddatetime(size_t column_ndx, size_t* return_ndx = nullptr) const; + OldDateTime minimum_olddatetime(size_t column_ndx, size_t* return_ndx = nullptr) const; + + Timestamp minimum_timestamp(size_t column_ndx, size_t* return_ndx = nullptr) const; + Timestamp maximum_timestamp(size_t column_ndx, size_t* return_ndx = nullptr) const; + size_t count_timestamp(size_t column_ndx, Timestamp target) const; + + // Simple pivot aggregate method. Experimental! Please do not + // document method publicly. + void aggregate(size_t group_by_column, size_t aggr_column, Table::AggrType op, Table& result) const; + + // Get row index in the source table this view is "looking" at. + size_t get_source_ndx(size_t row_ndx) const noexcept; + + /// Search this view for the specified source table row (specified by its + /// index in the source table). If found, the index of that row within this + /// view is returned, otherwise `realm::not_found` is returned. + size_t find_by_source_ndx(size_t source_ndx) const noexcept; + + // Conversion + void to_json(std::ostream&, size_t link_depth = 0, std::map* renames = nullptr) const; + void to_string(std::ostream&, size_t limit = 500) const; + void row_to_string(size_t row_ndx, std::ostream&) const; + + // Determine if the view is 'in sync' with the underlying table + // as well as other views used to generate the view. Note that updates + // through views maintains synchronization between view and table. + // It doesnt by itself maintain other views as well. So if a view + // is generated from another view (not a table), updates may cause + // that view to be outdated, AND as the generated view depends upon + // it, it too will become outdated. + bool is_in_sync() const; + + // Tells if this TableView depends on a LinkList or row that has been deleted. + bool depends_on_deleted_object() const; + + // Synchronize a view to match a table or tableview from which it + // has been derived. Synchronization is achieved by rerunning the + // query used to generate the view. If derived from another view, that + // view will be synchronized as well. + // + // "live" or "reactive" views are implemented by calling sync_if_needed + // before any of the other access-methods whenever the view may have become + // outdated. + // + // This will make the TableView empty and in sync with the highest possible table version + // if the TableView depends on an object (LinkView or row) that has been deleted. + uint_fast64_t sync_if_needed() const; + + // Sort m_row_indexes according to one column + void sort(size_t column, bool ascending = true); + + // Sort m_row_indexes according to multiple columns + void sort(SortDescriptor order); + + // Remove rows that are duplicated with respect to the column set passed as argument. + // distinct() will preserve the original order of the row pointers, also if the order is a result of sort() + // If two rows are indentical (for the given set of distinct-columns), then the last row is removed. + // You can call sync_if_needed() to update the distinct view, just like you can for a sorted view. + // Each time you call distinct() it will compound on the previous calls + void distinct(size_t column); + void distinct(DistinctDescriptor columns); + void limit(LimitDescriptor limit); + void include(IncludeDescriptor include_paths); + IncludeDescriptor get_include_descriptors(); + + // Replace the order of sort and distinct operations, bypassing manually + // calling sort and distinct. This is a convenience method for bindings. + void apply_descriptor_ordering(DescriptorOrdering new_ordering); + + // Gets a readable and parsable string which completely describes the sort and + // distinct operations applied to this view. + std::string get_descriptor_ordering_description() const; + + // Returns whether the rows are guaranteed to be in table order. + // This is true only of unsorted TableViews created from either: + // - Table::find_all() + // - Query::find_all() when the query is not restricted to a view. + bool is_in_table_order() const; + + virtual ~TableViewBase() noexcept; + + virtual std::unique_ptr clone() const = 0; + +protected: + // This TableView can be "born" from 4 different sources: + // - LinkView + // - Query::find_all() + // - Table::get_distinct_view() + // - Table::get_backlink_view() + // Return the version of the source it was created from. + uint64_t outside_version() const; + + void do_sync(); + + // Null if, and only if, the view is detached. + mutable TableRef m_table; + + // The link column that this view contain backlinks for. + const BacklinkColumn* m_linked_column = nullptr; + // The target row that rows in this view link to. + ConstRow m_linked_row; + + // If this TableView was created from a LinkView, then this reference points to it. Otherwise it's 0 + mutable ConstLinkViewRef m_linkview_source; + + // m_distinct_column_source != npos if this view was created from distinct values in a column of m_table. + size_t m_distinct_column_source = npos; + + // Stores the ordering criteria of applied sort and distinct operations. + DescriptorOrdering m_descriptor_ordering; + + // A valid query holds a reference to its table which must match our m_table. + // hence we can use a query with a null table reference to indicate that the view + // was NOT generated by a query, but follows a table directly. + Query m_query; + // parameters for findall, needed to rerun the query + size_t m_start; + size_t m_end; + size_t m_limit; + + mutable util::Optional m_last_seen_version; + + size_t m_num_detached_refs = 0; + /// Construct null view (no memory allocated). + TableViewBase(); + + /// Construct empty view, ready for addition of row indices. + TableViewBase(Table* parent); + TableViewBase(Table* parent, Query& query, size_t start, size_t end, size_t limit); + TableViewBase(Table* parent, size_t column, BasicRowExpr row); + TableViewBase(Table* parent, ConstLinkViewRef link_view); + + enum DistinctViewTag { DistinctView }; + TableViewBase(DistinctViewTag, Table* parent, size_t column_ndx); + + /// Copy constructor. + TableViewBase(const TableViewBase&); + + /// Move constructor. + TableViewBase(TableViewBase&&) noexcept; + + TableViewBase& operator=(const TableViewBase&); + TableViewBase& operator=(TableViewBase&&) noexcept; + + template + static R find_all_integer(V*, size_t, int64_t); + + template + static R find_all_float(V*, size_t, float); + + template + static R find_all_double(V*, size_t, double); + + template + static R find_all_string(V*, size_t, StringData); + + using HandoverPatch = TableViewHandoverPatch; + + // handover machinery entry points based on dynamic type. These methods: + // a) forward their calls to the static type entry points. + // b) new/delete patch data structures. + virtual std::unique_ptr clone_for_handover(std::unique_ptr& patch, + ConstSourcePayload mode) const = 0; + + virtual std::unique_ptr clone_for_handover(std::unique_ptr& patch, + MutableSourcePayload mode) = 0; + + void apply_and_consume_patch(std::unique_ptr& patch, Group& group) + { + apply_patch(*patch, group); + patch.reset(); + } + // handover machinery entry points based on static type + void apply_patch(HandoverPatch& patch, Group& group); + TableViewBase(const TableViewBase& source, HandoverPatch& patch, ConstSourcePayload mode); + TableViewBase(TableViewBase& source, HandoverPatch& patch, MutableSourcePayload mode); + +private: + void allocate_row_indexes(); + void detach() const noexcept; // may have to remove const + size_t find_first_integer(size_t column_ndx, int64_t value) const; + template + Timestamp minmax_timestamp(size_t column_ndx, size_t* return_ndx) const; + + friend class Table; + friend class Query; + friend class SharedGroup; + + // Called by table to adjust any row references: + void adj_row_acc_insert_rows(size_t row_ndx, size_t num_rows) noexcept; + void adj_row_acc_erase_row(size_t row_ndx) noexcept; + void adj_row_acc_move_over(size_t from_row_ndx, size_t to_row_ndx) noexcept; + void adj_row_acc_swap_rows(size_t row_ndx_1, size_t row_ndx_2) noexcept; + void adj_row_acc_move_row(size_t from_row_ndx, size_t to_row_ndx) noexcept; + void adj_row_acc_clear() noexcept; +}; + + +inline void TableViewBase::detach() const noexcept // may have to remove const +{ + m_table = TableRef(); +} + + +class ConstTableView; + + +enum class RemoveMode { ordered, unordered }; + + +/// A TableView gives read and write access to the parent table. +/// +/// A 'const TableView' cannot be changed (e.g. sorted), nor can the +/// parent table be modified through it. +/// +/// A TableView is both copyable and movable. +class TableView : public TableViewBase { +public: + using TableViewBase::TableViewBase; + + TableView() = default; + + // Rows + typedef BasicRowExpr
RowExpr; + typedef BasicRowExpr ConstRowExpr; + RowExpr get(size_t row_ndx) noexcept; + ConstRowExpr get(size_t row_ndx) const noexcept; + RowExpr front() noexcept; + ConstRowExpr front() const noexcept; + RowExpr back() noexcept; + ConstRowExpr back() const noexcept; + RowExpr operator[](size_t row_ndx) noexcept; + ConstRowExpr operator[](size_t row_ndx) const noexcept; + + // Setting values + void set_int(size_t column_ndx, size_t row_ndx, int64_t value); + void set_bool(size_t column_ndx, size_t row_ndx, bool value); + void set_olddatetime(size_t column_ndx, size_t row_ndx, OldDateTime value); + void set_timestamp(size_t column_ndx, size_t row_ndx, Timestamp value); + template + void set_enum(size_t column_ndx, size_t row_ndx, E value); + void set_float(size_t column_ndx, size_t row_ndx, float value); + void set_double(size_t column_ndx, size_t row_ndx, double value); + void set_string(size_t column_ndx, size_t row_ndx, StringData value); + void set_binary(size_t column_ndx, size_t row_ndx, BinaryData value); + void set_mixed(size_t column_ndx, size_t row_ndx, Mixed value); + void set_subtable(size_t column_ndx, size_t row_ndx, const Table* table); + void set_link(size_t column_ndx, size_t row_ndx, size_t target_row_ndx); + + // Subtables + TableRef get_subtable(size_t column_ndx, size_t row_ndx); + ConstTableRef get_subtable(size_t column_ndx, size_t row_ndx) const; + void clear_subtable(size_t column_ndx, size_t row_ndx); + + // Links + TableRef get_link_target(size_t column_ndx) noexcept; + ConstTableRef get_link_target(size_t column_ndx) const noexcept; + void nullify_link(size_t column_ndx, size_t row_ndx); + + /// \defgroup table_view_removes + //@{ + /// \brief Remove the specified row (or rows) from the underlying table. + /// + /// remove() removes the specified row from the underlying table, + /// remove_last() removes the last row in the table view from the underlying + /// table, and clear removes all the rows in the table view from the + /// underlying table. + /// + /// When rows are removed from the underlying table, they will by necessity + /// also be removed from the table view. + /// + /// The order of the remaining rows in the the table view will be maintained + /// regardless of the value passed for \a underlying_mode. + /// + /// \param row_ndx The index within this table view of the row to be + /// removed. + /// + /// \param underlying_mode If set to RemoveMode::ordered (the default), the + /// rows will be removed from the underlying table in a way that maintains + /// the order of the remaining rows in the underlying table. If set to + /// RemoveMode::unordered, the order of the remaining rows in the underlying + /// table will not in general be maintaind, but the operation will generally + /// be much faster. In any case, the order of remaining rows in the table + /// view will not be affected. + void remove(size_t row_ndx, RemoveMode underlying_mode = RemoveMode::ordered); + void remove_last(RemoveMode underlying_mode = RemoveMode::ordered); + void clear(RemoveMode underlying_mode = RemoveMode::ordered); + //@} + + // Searching (Int and String) + TableView find_all_int(size_t column_ndx, int64_t value); + ConstTableView find_all_int(size_t column_ndx, int64_t value) const; + TableView find_all_bool(size_t column_ndx, bool value); + ConstTableView find_all_bool(size_t column_ndx, bool value) const; + TableView find_all_olddatetime(size_t column_ndx, OldDateTime value); + ConstTableView find_all_olddatetime(size_t column_ndx, OldDateTime value) const; + TableView find_all_float(size_t column_ndx, float value); + ConstTableView find_all_float(size_t column_ndx, float value) const; + TableView find_all_double(size_t column_ndx, double value); + ConstTableView find_all_double(size_t column_ndx, double value) const; + TableView find_all_string(size_t column_ndx, StringData value); + ConstTableView find_all_string(size_t column_ndx, StringData value) const; + // FIXME: Need: TableView find_all_binary(size_t column_ndx, BinaryData value); + // FIXME: Need: ConstTableView find_all_binary(size_t column_ndx, BinaryData value) const; + + Table& get_parent() noexcept; + const Table& get_parent() const noexcept; + + std::unique_ptr clone() const override + { + return std::unique_ptr(new TableView(*this)); + } + + std::unique_ptr clone_for_handover(std::unique_ptr& patch, + ConstSourcePayload mode) const override + { + patch.reset(new HandoverPatch); + std::unique_ptr retval(new TableView(*this, *patch, mode)); + return retval; + } + + std::unique_ptr clone_for_handover(std::unique_ptr& patch, + MutableSourcePayload mode) override + { + patch.reset(new HandoverPatch); + std::unique_ptr retval(new TableView(*this, *patch, mode)); + return retval; + } + +private: + TableView(Table& parent); + TableView(Table& parent, Query& query, size_t start, size_t end, size_t limit); + TableView(Table& parent, ConstLinkViewRef); + + TableView(DistinctViewTag, Table& parent, size_t column_ndx); + + TableView find_all_integer(size_t column_ndx, int64_t value); + ConstTableView find_all_integer(size_t column_ndx, int64_t value) const; + + friend class ConstTableView; + friend class Table; + friend class Query; + friend class TableViewBase; + friend class LinkView; +}; + + +/// A ConstTableView gives read access to the parent table, but no +/// write access. The view itself, though, can be changed, for +/// example, it can be sorted. +/// +/// Note that methods are declared 'const' if, and only if they leave +/// the view unmodified, and this is irrespective of whether they +/// modify the parent table. +/// +/// A ConstTableView has both copy and move semantics. See TableView +/// for more on this. +class ConstTableView : public TableViewBase { +public: + using TableViewBase::TableViewBase; + + ConstTableView() = default; + + ConstTableView(const TableView&); + ConstTableView(TableView&&); + ConstTableView& operator=(const TableView&); + ConstTableView& operator=(TableView&&); + + // Rows + typedef BasicRowExpr ConstRowExpr; + ConstRowExpr get(size_t row_ndx) const noexcept; + ConstRowExpr front() const noexcept; + ConstRowExpr back() const noexcept; + ConstRowExpr operator[](size_t row_ndx) const noexcept; + + // Subtables + ConstTableRef get_subtable(size_t column_ndx, size_t row_ndx) const; + + // Links + ConstTableRef get_link_target(size_t column_ndx) const noexcept; + + // Searching (Int and String) + ConstTableView find_all_int(size_t column_ndx, int64_t value) const; + ConstTableView find_all_bool(size_t column_ndx, bool value) const; + ConstTableView find_all_olddatetime(size_t column_ndx, OldDateTime value) const; + ConstTableView find_all_float(size_t column_ndx, float value) const; + ConstTableView find_all_double(size_t column_ndx, double value) const; + ConstTableView find_all_string(size_t column_ndx, StringData value) const; + + const Table& get_parent() const noexcept; + + std::unique_ptr clone() const override + { + return std::unique_ptr(new ConstTableView(*this)); + } + + std::unique_ptr clone_for_handover(std::unique_ptr& patch, + ConstSourcePayload mode) const override + { + patch.reset(new HandoverPatch); + std::unique_ptr retval(new ConstTableView(*this, *patch, mode)); + return retval; + } + + std::unique_ptr clone_for_handover(std::unique_ptr& patch, + MutableSourcePayload mode) override + { + patch.reset(new HandoverPatch); + std::unique_ptr retval(new ConstTableView(*this, *patch, mode)); + return retval; + } + +private: + ConstTableView(const Table& parent); + + ConstTableView find_all_integer(size_t column_ndx, int64_t value) const; + + friend class TableView; + friend class Table; + friend class Query; + friend class TableViewBase; +}; + + +// ================================================================================================ +// TableViewBase Implementation: + +inline const Query& TableViewBase::get_query() const noexcept +{ + return m_query; +} + +inline bool TableViewBase::is_empty() const noexcept +{ + return m_row_indexes.is_empty(); +} + +inline bool TableViewBase::is_attached() const noexcept +{ + return bool(m_table); +} + +inline bool TableViewBase::is_row_attached(size_t row_ndx) const noexcept +{ + return m_row_indexes.get(row_ndx) != detached_ref; +} + +inline size_t TableViewBase::size() const noexcept +{ + return m_row_indexes.size(); +} + +inline size_t TableViewBase::num_attached_rows() const noexcept +{ + return m_row_indexes.size() - m_num_detached_refs; +} + +inline size_t TableViewBase::get_source_ndx(size_t row_ndx) const noexcept +{ + return to_size_t(m_row_indexes.get(row_ndx)); +} + +inline size_t TableViewBase::find_by_source_ndx(size_t source_ndx) const noexcept +{ + REALM_ASSERT(source_ndx < m_table->size()); + return m_row_indexes.find_first(source_ndx); +} + +inline void TableViewBase::allocate_row_indexes() +{ + // FIXME: This code is unreasonably complicated because it uses `IntegerColumn` as + // a free-standing container, and beause `IntegerColumn` does not conform to the + // RAII idiom (nor should it). + Allocator& alloc = m_row_indexes.get_alloc(); + _impl::DeepArrayRefDestroyGuard ref_guard(alloc); + ref_guard.reset(IntegerColumn::create(alloc)); // Throws + m_table->register_view(this); // Throws + m_row_indexes.init_from_ref(alloc, ref_guard.release()); +} + +inline TableViewBase::TableViewBase() + : RowIndexes(IntegerColumn::unattached_root_tag(), Allocator::get_default()) // Throws +{ + ref_type ref = IntegerColumn::create(m_row_indexes.get_alloc()); // Throws + m_row_indexes.get_root_array()->init_from_ref(ref); +} + +inline TableViewBase::TableViewBase(Table* parent) + : RowIndexes(IntegerColumn::unattached_root_tag(), Allocator::get_default()) + , m_table(parent->get_table_ref()) // Throws + , m_last_seen_version(m_table ? util::make_optional(m_table->m_version) : util::none) +{ + allocate_row_indexes(); +} + +inline TableViewBase::TableViewBase(Table* parent, Query& query, size_t start, size_t end, size_t lim) + : RowIndexes(IntegerColumn::unattached_root_tag(), Allocator::get_default()) // Throws + , m_table(parent->get_table_ref()) + , m_query(query) + , m_start(start) + , m_end(end) + , m_limit(lim) + , m_last_seen_version(outside_version()) +{ + allocate_row_indexes(); +} + +inline TableViewBase::TableViewBase(Table* parent, size_t column, BasicRowExpr row) + : RowIndexes(IntegerColumn::unattached_root_tag(), Allocator::get_default()) + , m_table(parent->get_table_ref()) // Throws + , m_linked_column(&parent->get_column_link_base(column).get_backlink_column()) + , m_linked_row(row) + , m_last_seen_version(m_table ? util::make_optional(m_table->m_version) : util::none) +{ + allocate_row_indexes(); +} + +inline TableViewBase::TableViewBase(DistinctViewTag, Table* parent, size_t column_ndx) + : RowIndexes(IntegerColumn::unattached_root_tag(), Allocator::get_default()) + , m_table(parent->get_table_ref()) // Throws + , m_distinct_column_source(column_ndx) + , m_last_seen_version(m_table ? util::make_optional(m_table->m_version) : util::none) +{ + REALM_ASSERT(m_distinct_column_source != npos); + + allocate_row_indexes(); +} + +inline TableViewBase::TableViewBase(Table* parent, ConstLinkViewRef link_view) + : RowIndexes(IntegerColumn::unattached_root_tag(), Allocator::get_default()) + , m_table(parent->get_table_ref()) // Throws + , m_linkview_source(std::move(link_view)) + , m_last_seen_version(m_table ? util::make_optional(m_table->m_version) : util::none) +{ + REALM_ASSERT(m_linkview_source); + + allocate_row_indexes(); +} + +inline TableViewBase::TableViewBase(const TableViewBase& tv) + : RowIndexes(IntegerColumn::unattached_root_tag(), Allocator::get_default()) + , m_table(tv.m_table) + , m_linked_column(tv.m_linked_column) + , m_linked_row(tv.m_linked_row) + , m_linkview_source(tv.m_linkview_source) + , m_distinct_column_source(tv.m_distinct_column_source) + , m_descriptor_ordering(std::move(tv.m_descriptor_ordering)) + , m_query(tv.m_query) + , m_start(tv.m_start) + , m_end(tv.m_end) + , m_limit(tv.m_limit) + , m_last_seen_version(tv.m_last_seen_version) + , m_num_detached_refs(tv.m_num_detached_refs) +{ + // FIXME: This code is unreasonably complicated because it uses `IntegerColumn` as + // a free-standing container, and because `IntegerColumn` does not conform to the + // RAII idiom (nor should it). + Allocator& alloc = m_row_indexes.get_alloc(); + MemRef mem = tv.m_row_indexes.get_root_array()->clone_deep(alloc); // Throws + _impl::DeepArrayRefDestroyGuard ref_guard(mem.get_ref(), alloc); + if (m_table) + m_table->register_view(this); // Throws + m_row_indexes.init_from_mem(alloc, mem); + ref_guard.release(); + RowIndexes::m_limit_count = tv.m_limit_count; +} + +inline TableViewBase::TableViewBase(TableViewBase&& tv) noexcept + : RowIndexes(std::move(tv.m_row_indexes)) + , m_table(std::move(tv.m_table)) + , m_linked_column(tv.m_linked_column) + , m_linked_row(tv.m_linked_row) + , m_linkview_source(std::move(tv.m_linkview_source)) + , m_distinct_column_source(tv.m_distinct_column_source) + , m_descriptor_ordering(std::move(tv.m_descriptor_ordering)) + , m_query(std::move(tv.m_query)) + , m_start(tv.m_start) + , m_end(tv.m_end) + , m_limit(tv.m_limit) + , + // if we are created from a table view which is outdated, take care to use the outdated + // version number so that we can later trigger a sync if needed. + m_last_seen_version(tv.m_last_seen_version) + , m_num_detached_refs(tv.m_num_detached_refs) +{ + RowIndexes::m_limit_count = tv.m_limit_count; + if (m_table) + m_table->move_registered_view(&tv, this); +} + +inline TableViewBase::~TableViewBase() noexcept +{ + if (m_table) { + m_table->unregister_view(this); + m_table = TableRef(); + } + m_row_indexes.destroy(); // Shallow +} + +inline TableViewBase& TableViewBase::operator=(TableViewBase&& tv) noexcept +{ + if (m_table) + m_table->unregister_view(this); + m_table = std::move(tv.m_table); + if (m_table) + m_table->move_registered_view(&tv, this); + + m_row_indexes.move_assign(tv.m_row_indexes); + m_limit_count = tv.m_limit_count; + m_query = std::move(tv.m_query); + m_num_detached_refs = tv.m_num_detached_refs; + m_last_seen_version = tv.m_last_seen_version; + m_start = tv.m_start; + m_end = tv.m_end; + m_limit = tv.m_limit; + m_linked_column = tv.m_linked_column; + m_linked_row = tv.m_linked_row; + m_linkview_source = std::move(tv.m_linkview_source); + m_descriptor_ordering = std::move(tv.m_descriptor_ordering); + m_distinct_column_source = tv.m_distinct_column_source; + + return *this; +} + +inline TableViewBase& TableViewBase::operator=(const TableViewBase& tv) +{ + if (this == &tv) + return *this; + + if (m_table != tv.m_table) { + if (m_table) + m_table->unregister_view(this); + m_table = tv.m_table; + if (m_table) + m_table->register_view(this); + } + + Allocator& alloc = m_row_indexes.get_alloc(); + MemRef mem = tv.m_row_indexes.get_root_array()->clone_deep(alloc); // Throws + _impl::DeepArrayRefDestroyGuard ref_guard(mem.get_ref(), alloc); + m_row_indexes.destroy(); + m_row_indexes.get_root_array()->init_from_mem(mem); + ref_guard.release(); + m_limit_count = tv.m_limit_count; + + m_query = tv.m_query; + m_num_detached_refs = tv.m_num_detached_refs; + m_last_seen_version = tv.m_last_seen_version; + m_start = tv.m_start; + m_end = tv.m_end; + m_limit = tv.m_limit; + m_linked_column = tv.m_linked_column; + m_linked_row = tv.m_linked_row; + m_linkview_source = tv.m_linkview_source; + m_descriptor_ordering = tv.m_descriptor_ordering; + m_distinct_column_source = tv.m_distinct_column_source; + + return *this; +} + +#define REALM_ASSERT_COLUMN(column_ndx) \ + REALM_ASSERT(m_table); \ + REALM_ASSERT(column_ndx < m_table->get_column_count()) + +#define REALM_ASSERT_ROW(row_ndx) \ + REALM_ASSERT(m_table); \ + REALM_ASSERT(row_ndx < m_row_indexes.size()) + +#define REALM_ASSERT_COLUMN_AND_TYPE(column_ndx, column_type) \ + REALM_ASSERT_COLUMN(column_ndx); \ + REALM_DIAG_PUSH(); \ + REALM_DIAG_IGNORE_TAUTOLOGICAL_COMPARE(); \ + REALM_ASSERT(m_table->get_column_type(column_ndx) == column_type || \ + (m_table->get_column_type(column_ndx) == type_OldDateTime && column_type == type_Int)); \ + REALM_DIAG_POP() + +#define REALM_ASSERT_INDEX(column_ndx, row_ndx) \ + REALM_ASSERT_COLUMN(column_ndx); \ + REALM_ASSERT(row_ndx < m_row_indexes.size()) + +#define REALM_ASSERT_INDEX_AND_TYPE(column_ndx, row_ndx, column_type) \ + REALM_ASSERT_COLUMN_AND_TYPE(column_ndx, column_type); \ + REALM_ASSERT(row_ndx < m_row_indexes.size()) + +#define REALM_ASSERT_INDEX_AND_TYPE_TABLE_OR_MIXED(column_ndx, row_ndx) \ + REALM_ASSERT_COLUMN(column_ndx); \ + REALM_DIAG_PUSH(); \ + REALM_DIAG_IGNORE_TAUTOLOGICAL_COMPARE(); \ + REALM_ASSERT(m_table->get_column_type(column_ndx) == type_Table || \ + (m_table->get_column_type(column_ndx) == type_Mixed)); \ + REALM_DIAG_POP(); \ + REALM_ASSERT(row_ndx < m_row_indexes.size()) + +// Column information + +inline const ColumnBase& TableViewBase::get_column_base(size_t index) const +{ + return m_table->get_column_base(index); +} + +inline size_t TableViewBase::get_column_count() const noexcept +{ + REALM_ASSERT(m_table); + return m_table->get_column_count(); +} + +inline StringData TableViewBase::get_column_name(size_t column_ndx) const noexcept +{ + REALM_ASSERT(m_table); + return m_table->get_column_name(column_ndx); +} + +inline size_t TableViewBase::get_column_index(StringData name) const +{ + REALM_ASSERT(m_table); + return m_table->get_column_index(name); +} + +inline DataType TableViewBase::get_column_type(size_t column_ndx) const noexcept +{ + REALM_ASSERT(m_table); + return m_table->get_column_type(column_ndx); +} + + +// Getters + + +inline int64_t TableViewBase::get_int(size_t column_ndx, size_t row_ndx) const noexcept +{ + REALM_ASSERT_INDEX(column_ndx, row_ndx); + + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + return m_table->get_int(column_ndx, to_size_t(real_ndx)); +} + +inline bool TableViewBase::get_bool(size_t column_ndx, size_t row_ndx) const noexcept +{ + REALM_ASSERT_INDEX_AND_TYPE(column_ndx, row_ndx, type_Bool); + + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + return m_table->get_bool(column_ndx, to_size_t(real_ndx)); +} + +inline OldDateTime TableViewBase::get_olddatetime(size_t column_ndx, size_t row_ndx) const noexcept +{ + REALM_ASSERT_INDEX_AND_TYPE(column_ndx, row_ndx, type_OldDateTime); + + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + return m_table->get_olddatetime(column_ndx, to_size_t(real_ndx)); +} + +inline Timestamp TableViewBase::get_timestamp(size_t column_ndx, size_t row_ndx) const noexcept +{ + REALM_ASSERT_INDEX_AND_TYPE(column_ndx, row_ndx, type_Timestamp); + + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + return m_table->get_timestamp(column_ndx, to_size_t(real_ndx)); +} + +inline float TableViewBase::get_float(size_t column_ndx, size_t row_ndx) const noexcept +{ + REALM_ASSERT_INDEX_AND_TYPE(column_ndx, row_ndx, type_Float); + + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + return m_table->get_float(column_ndx, to_size_t(real_ndx)); +} + +inline double TableViewBase::get_double(size_t column_ndx, size_t row_ndx) const noexcept +{ + REALM_ASSERT_INDEX_AND_TYPE(column_ndx, row_ndx, type_Double); + + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + return m_table->get_double(column_ndx, to_size_t(real_ndx)); +} + +inline StringData TableViewBase::get_string(size_t column_ndx, size_t row_ndx) const noexcept +{ + REALM_ASSERT_INDEX_AND_TYPE(column_ndx, row_ndx, type_String); + + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + return m_table->get_string(column_ndx, to_size_t(real_ndx)); +} + +inline BinaryData TableViewBase::get_binary(size_t column_ndx, size_t row_ndx) const noexcept +{ + REALM_ASSERT_INDEX_AND_TYPE(column_ndx, row_ndx, type_Binary); + + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + return m_table->get_binary(column_ndx, to_size_t(real_ndx)); +} + +inline Mixed TableViewBase::get_mixed(size_t column_ndx, size_t row_ndx) const noexcept +{ + REALM_ASSERT_INDEX_AND_TYPE(column_ndx, row_ndx, type_Mixed); + + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + return m_table->get_mixed(column_ndx, to_size_t(real_ndx)); +} + +inline DataType TableViewBase::get_mixed_type(size_t column_ndx, size_t row_ndx) const noexcept +{ + REALM_ASSERT_INDEX_AND_TYPE(column_ndx, row_ndx, type_Mixed); + + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + return m_table->get_mixed_type(column_ndx, to_size_t(real_ndx)); +} + +inline size_t TableViewBase::get_subtable_size(size_t column_ndx, size_t row_ndx) const noexcept +{ + REALM_ASSERT_INDEX_AND_TYPE_TABLE_OR_MIXED(column_ndx, row_ndx); + + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + return m_table->get_subtable_size(column_ndx, to_size_t(real_ndx)); +} + +inline size_t TableViewBase::get_link(size_t column_ndx, size_t row_ndx) const noexcept +{ + REALM_ASSERT_INDEX_AND_TYPE(column_ndx, row_ndx, type_Link); + + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + return m_table->get_link(column_ndx, to_size_t(real_ndx)); +} + +inline TableRef TableView::get_link_target(size_t column_ndx) noexcept +{ + return m_table->get_link_target(column_ndx); +} + +inline ConstTableRef TableView::get_link_target(size_t column_ndx) const noexcept +{ + return m_table->get_link_target(column_ndx); +} + +inline ConstTableRef ConstTableView::get_link_target(size_t column_ndx) const noexcept +{ + return m_table->get_link_target(column_ndx); +} + +inline bool TableViewBase::is_null_link(size_t column_ndx, size_t row_ndx) const noexcept +{ + REALM_ASSERT_INDEX_AND_TYPE(column_ndx, row_ndx, type_Link); + + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + return m_table->is_null_link(column_ndx, to_size_t(real_ndx)); +} + + +// Searching + + +inline size_t TableViewBase::find_first_int(size_t column_ndx, int64_t value) const +{ + REALM_ASSERT_COLUMN_AND_TYPE(column_ndx, type_Int); + return find_first_integer(column_ndx, value); +} + +inline size_t TableViewBase::find_first_bool(size_t column_ndx, bool value) const +{ + REALM_ASSERT_COLUMN_AND_TYPE(column_ndx, type_Bool); + return find_first_integer(column_ndx, value ? 1 : 0); +} + +inline size_t TableViewBase::find_first_olddatetime(size_t column_ndx, OldDateTime value) const +{ + REALM_ASSERT_COLUMN_AND_TYPE(column_ndx, type_OldDateTime); + return find_first_integer(column_ndx, int64_t(value.get_olddatetime())); +} + +inline size_t TableViewBase::find_first_integer(size_t column_ndx, int64_t value) const +{ + return find_first(column_ndx, value); +} + +inline size_t TableViewBase::find_first_float(size_t column_ndx, float value) const +{ + return find_first(column_ndx, value); +} + +inline size_t TableViewBase::find_first_double(size_t column_ndx, double value) const +{ + return find_first(column_ndx, value); +} + +inline size_t TableViewBase::find_first_string(size_t column_ndx, StringData value) const +{ + return find_first(column_ndx, value); +} + +inline size_t TableViewBase::find_first_binary(size_t column_ndx, BinaryData value) const +{ + return find_first(column_ndx, value); +} + +inline size_t TableViewBase::find_first_timestamp(size_t column_ndx, Timestamp value) const +{ + return find_first(column_ndx, value); +} + + +template +R TableViewBase::find_all_integer(V* view, size_t column_ndx, int64_t value) +{ + typedef typename std::remove_const::type TNonConst; + return view->m_table->where(const_cast(view)).equal(column_ndx, value).find_all(); +} + +template +R TableViewBase::find_all_float(V* view, size_t column_ndx, float value) +{ + typedef typename std::remove_const::type TNonConst; + return view->m_table->where(const_cast(view)).equal(column_ndx, value).find_all(); +} + +template +R TableViewBase::find_all_double(V* view, size_t column_ndx, double value) +{ + typedef typename std::remove_const::type TNonConst; + return view->m_table->where(const_cast(view)).equal(column_ndx, value).find_all(); +} + +template +R TableViewBase::find_all_string(V* view, size_t column_ndx, StringData value) +{ + typedef typename std::remove_const::type TNonConst; + return view->m_table->where(const_cast(view)).equal(column_ndx, value).find_all(); +} + + +//-------------------------- TableView, ConstTableView implementation: + +inline ConstTableView::ConstTableView(const TableView& tv) + : TableViewBase(tv) +{ +} + +inline ConstTableView::ConstTableView(TableView&& tv) + : TableViewBase(std::move(tv)) +{ +} + +inline void TableView::remove_last(RemoveMode underlying_mode) +{ + if (!is_empty()) + remove(size() - 1, underlying_mode); +} + +inline Table& TableView::get_parent() noexcept +{ + return *m_table; +} + +inline const Table& TableView::get_parent() const noexcept +{ + return *m_table; +} + +inline const Table& ConstTableView::get_parent() const noexcept +{ + return *m_table; +} + +inline TableView::TableView(Table& parent) + : TableViewBase(&parent) +{ +} + +inline TableView::TableView(Table& parent, Query& query, size_t start, size_t end, size_t lim) + : TableViewBase(&parent, query, start, end, lim) +{ +} + +inline TableView::TableView(Table& parent, ConstLinkViewRef link_view) +: TableViewBase(&parent, std::move(link_view)) +{ +} + +inline TableView::TableView(TableViewBase::DistinctViewTag, Table& parent, size_t column_ndx) + : TableViewBase(TableViewBase::DistinctView, &parent, column_ndx) +{ +} + +inline ConstTableView::ConstTableView(const Table& parent) + : TableViewBase(const_cast(&parent)) +{ +} + +inline ConstTableView& ConstTableView::operator=(const TableView& tv) +{ + TableViewBase::operator=(tv); + return *this; +} + +inline ConstTableView& ConstTableView::operator=(TableView&& tv) +{ + TableViewBase::operator=(std::move(tv)); + return *this; +} + + +// - string +inline TableView TableView::find_all_string(size_t column_ndx, StringData value) +{ + return TableViewBase::find_all_string(this, column_ndx, value); +} + +inline ConstTableView TableView::find_all_string(size_t column_ndx, StringData value) const +{ + return TableViewBase::find_all_string(this, column_ndx, value); +} + +inline ConstTableView ConstTableView::find_all_string(size_t column_ndx, StringData value) const +{ + return TableViewBase::find_all_string(this, column_ndx, value); +} + +// - float +inline TableView TableView::find_all_float(size_t column_ndx, float value) +{ + return TableViewBase::find_all_float(this, column_ndx, value); +} + +inline ConstTableView TableView::find_all_float(size_t column_ndx, float value) const +{ + return TableViewBase::find_all_float(this, column_ndx, value); +} + +inline ConstTableView ConstTableView::find_all_float(size_t column_ndx, float value) const +{ + return TableViewBase::find_all_float(this, column_ndx, value); +} + + +// - double +inline TableView TableView::find_all_double(size_t column_ndx, double value) +{ + return TableViewBase::find_all_double(this, column_ndx, value); +} + +inline ConstTableView TableView::find_all_double(size_t column_ndx, double value) const +{ + return TableViewBase::find_all_double(this, column_ndx, value); +} + +inline ConstTableView ConstTableView::find_all_double(size_t column_ndx, double value) const +{ + return TableViewBase::find_all_double(this, column_ndx, value); +} + + +// -- 3 variants of the 3 find_all_{int, bool, date} all based on integer + +inline TableView TableView::find_all_integer(size_t column_ndx, int64_t value) +{ + return TableViewBase::find_all_integer(this, column_ndx, value); +} + +inline ConstTableView TableView::find_all_integer(size_t column_ndx, int64_t value) const +{ + return TableViewBase::find_all_integer(this, column_ndx, value); +} + +inline ConstTableView ConstTableView::find_all_integer(size_t column_ndx, int64_t value) const +{ + return TableViewBase::find_all_integer(this, column_ndx, value); +} + + +inline TableView TableView::find_all_int(size_t column_ndx, int64_t value) +{ + REALM_ASSERT_COLUMN_AND_TYPE(column_ndx, type_Int); + return find_all_integer(column_ndx, value); +} + +inline TableView TableView::find_all_bool(size_t column_ndx, bool value) +{ + REALM_ASSERT_COLUMN_AND_TYPE(column_ndx, type_Bool); + return find_all_integer(column_ndx, value ? 1 : 0); +} + +inline TableView TableView::find_all_olddatetime(size_t column_ndx, OldDateTime value) +{ + REALM_ASSERT_COLUMN_AND_TYPE(column_ndx, type_OldDateTime); + return find_all_integer(column_ndx, int64_t(value.get_olddatetime())); +} + + +inline ConstTableView TableView::find_all_int(size_t column_ndx, int64_t value) const +{ + REALM_ASSERT_COLUMN_AND_TYPE(column_ndx, type_Int); + return find_all_integer(column_ndx, value); +} + +inline ConstTableView TableView::find_all_bool(size_t column_ndx, bool value) const +{ + REALM_ASSERT_COLUMN_AND_TYPE(column_ndx, type_Bool); + return find_all_integer(column_ndx, value ? 1 : 0); +} + +inline ConstTableView TableView::find_all_olddatetime(size_t column_ndx, OldDateTime value) const +{ + REALM_ASSERT_COLUMN_AND_TYPE(column_ndx, type_OldDateTime); + return find_all_integer(column_ndx, int64_t(value.get_olddatetime())); +} + + +inline ConstTableView ConstTableView::find_all_int(size_t column_ndx, int64_t value) const +{ + REALM_ASSERT_COLUMN_AND_TYPE(column_ndx, type_Int); + return find_all_integer(column_ndx, value); +} + +inline ConstTableView ConstTableView::find_all_bool(size_t column_ndx, bool value) const +{ + REALM_ASSERT_COLUMN_AND_TYPE(column_ndx, type_Bool); + return find_all_integer(column_ndx, value ? 1 : 0); +} + +inline ConstTableView ConstTableView::find_all_olddatetime(size_t column_ndx, OldDateTime value) const +{ + REALM_ASSERT_COLUMN_AND_TYPE(column_ndx, type_OldDateTime); + return find_all_integer(column_ndx, int64_t(value.get_olddatetime())); +} + + +// Rows + + +inline TableView::RowExpr TableView::get(size_t row_ndx) noexcept +{ + REALM_ASSERT_ROW(row_ndx); + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + return m_table->get(to_size_t(real_ndx)); +} + +inline TableView::ConstRowExpr TableView::get(size_t row_ndx) const noexcept +{ + REALM_ASSERT_ROW(row_ndx); + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + return m_table->get(to_size_t(real_ndx)); +} + +inline ConstTableView::ConstRowExpr ConstTableView::get(size_t row_ndx) const noexcept +{ + REALM_ASSERT_ROW(row_ndx); + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + return m_table->get(to_size_t(real_ndx)); +} + +inline TableView::RowExpr TableView::front() noexcept +{ + return get(0); +} + +inline TableView::ConstRowExpr TableView::front() const noexcept +{ + return get(0); +} + +inline ConstTableView::ConstRowExpr ConstTableView::front() const noexcept +{ + return get(0); +} + +inline TableView::RowExpr TableView::back() noexcept +{ + size_t last_row_ndx = size() - 1; + return get(last_row_ndx); +} + +inline TableView::ConstRowExpr TableView::back() const noexcept +{ + size_t last_row_ndx = size() - 1; + return get(last_row_ndx); +} + +inline ConstTableView::ConstRowExpr ConstTableView::back() const noexcept +{ + size_t last_row_ndx = size() - 1; + return get(last_row_ndx); +} + +inline TableView::RowExpr TableView::operator[](size_t row_ndx) noexcept +{ + return get(row_ndx); +} + +inline TableView::ConstRowExpr TableView::operator[](size_t row_ndx) const noexcept +{ + return get(row_ndx); +} + +inline ConstTableView::ConstRowExpr ConstTableView::operator[](size_t row_ndx) const noexcept +{ + return get(row_ndx); +} + + +// Subtables + + +inline TableRef TableView::get_subtable(size_t column_ndx, size_t row_ndx) +{ + REALM_ASSERT_INDEX_AND_TYPE_TABLE_OR_MIXED(column_ndx, row_ndx); + + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + return m_table->get_subtable(column_ndx, to_size_t(real_ndx)); +} + +inline ConstTableRef TableView::get_subtable(size_t column_ndx, size_t row_ndx) const +{ + REALM_ASSERT_INDEX_AND_TYPE_TABLE_OR_MIXED(column_ndx, row_ndx); + + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + return m_table->get_subtable(column_ndx, to_size_t(real_ndx)); +} + +inline ConstTableRef ConstTableView::get_subtable(size_t column_ndx, size_t row_ndx) const +{ + REALM_ASSERT_INDEX_AND_TYPE_TABLE_OR_MIXED(column_ndx, row_ndx); + + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + return m_table->get_subtable(column_ndx, to_size_t(real_ndx)); +} + +inline void TableView::clear_subtable(size_t column_ndx, size_t row_ndx) +{ + REALM_ASSERT_INDEX_AND_TYPE_TABLE_OR_MIXED(column_ndx, row_ndx); + + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + return m_table->clear_subtable(column_ndx, to_size_t(real_ndx)); +} + + +// Setters + + +inline void TableView::set_int(size_t column_ndx, size_t row_ndx, int64_t value) +{ + REALM_ASSERT_INDEX_AND_TYPE(column_ndx, row_ndx, type_Int); + + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + m_table->set_int(column_ndx, to_size_t(real_ndx), value); +} + +inline void TableView::set_bool(size_t column_ndx, size_t row_ndx, bool value) +{ + REALM_ASSERT_INDEX_AND_TYPE(column_ndx, row_ndx, type_Bool); + + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + m_table->set_bool(column_ndx, to_size_t(real_ndx), value); +} + +inline void TableView::set_olddatetime(size_t column_ndx, size_t row_ndx, OldDateTime value) +{ + REALM_ASSERT_INDEX_AND_TYPE(column_ndx, row_ndx, type_OldDateTime); + + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + m_table->set_olddatetime(column_ndx, to_size_t(real_ndx), value); +} + +inline void TableView::set_timestamp(size_t column_ndx, size_t row_ndx, Timestamp value) +{ + REALM_ASSERT_INDEX_AND_TYPE(column_ndx, row_ndx, type_Timestamp); + + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + m_table->set_timestamp(column_ndx, to_size_t(real_ndx), value); +} + +inline void TableView::set_float(size_t column_ndx, size_t row_ndx, float value) +{ + REALM_ASSERT_INDEX_AND_TYPE(column_ndx, row_ndx, type_Float); + + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + m_table->set_float(column_ndx, to_size_t(real_ndx), value); +} + +inline void TableView::set_double(size_t column_ndx, size_t row_ndx, double value) +{ + REALM_ASSERT_INDEX_AND_TYPE(column_ndx, row_ndx, type_Double); + + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + m_table->set_double(column_ndx, to_size_t(real_ndx), value); +} + +template +inline void TableView::set_enum(size_t column_ndx, size_t row_ndx, E value) +{ + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + m_table->set_int(column_ndx, real_ndx, value); +} + +inline void TableView::set_string(size_t column_ndx, size_t row_ndx, StringData value) +{ + REALM_ASSERT_INDEX_AND_TYPE(column_ndx, row_ndx, type_String); + + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + m_table->set_string(column_ndx, to_size_t(real_ndx), value); +} + +inline void TableView::set_binary(size_t column_ndx, size_t row_ndx, BinaryData value) +{ + REALM_ASSERT_INDEX_AND_TYPE(column_ndx, row_ndx, type_Binary); + + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + m_table->set_binary(column_ndx, to_size_t(real_ndx), value); +} + +inline void TableView::set_mixed(size_t column_ndx, size_t row_ndx, Mixed value) +{ + REALM_ASSERT_INDEX_AND_TYPE(column_ndx, row_ndx, type_Mixed); + + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + m_table->set_mixed(column_ndx, to_size_t(real_ndx), value); +} + +inline void TableView::set_subtable(size_t column_ndx, size_t row_ndx, const Table* value) +{ + REALM_ASSERT_INDEX_AND_TYPE_TABLE_OR_MIXED(column_ndx, row_ndx); + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + m_table->set_subtable(column_ndx, to_size_t(real_ndx), value); +} + +inline void TableView::set_link(size_t column_ndx, size_t row_ndx, size_t target_row_ndx) +{ + REALM_ASSERT_INDEX_AND_TYPE(column_ndx, row_ndx, type_Link); + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + m_table->set_link(column_ndx, to_size_t(real_ndx), target_row_ndx); +} + +inline void TableView::nullify_link(size_t column_ndx, size_t row_ndx) +{ + REALM_ASSERT_INDEX_AND_TYPE(column_ndx, row_ndx, type_Link); + const int64_t real_ndx = m_row_indexes.get(row_ndx); + REALM_ASSERT(real_ndx != detached_ref); + m_table->nullify_link(column_ndx, to_size_t(real_ndx)); +} + +} // namespace realm + +#endif // REALM_TABLE_VIEW_HPP diff --git a/!main project/Pods/Realm/include/core/realm/timestamp.hpp b/!main project/Pods/Realm/include/core/realm/timestamp.hpp new file mode 100644 index 0000000..2108245 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/timestamp.hpp @@ -0,0 +1,187 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_TIMESTAMP_HPP +#define REALM_TIMESTAMP_HPP + +#include +#include +#include +#include +#include + +namespace realm { + +class Timestamp { +public: + // Construct from the number of seconds and nanoseconds since the UNIX epoch: 00:00:00 UTC on 1 January 1970 + // + // To split a native nanosecond representation, only division and modulo are necessary: + // + // s = native_nano / nanoseconds_per_second + // n = native_nano % nanoseconds_per_second + // Timestamp ts(s, n); + // + // To convert back into native nanosecond representation, simple multiply and add: + // + // native_nano = ts.s * nanoseconds_per_second + ts.n + // + // Specifically this allows the nanosecond part to become negative (only) for Timestamps before the UNIX epoch. + // Usually this will not need special attention, but for reference, valid Timestamps will have one of the + // following sign combinations: + // + // s | n + // ----- + // + | + + // + | 0 + // 0 | + + // 0 | 0 + // 0 | - + // - | 0 + // - | - + // + // Examples: + // The UNIX epoch is constructed by Timestamp(0, 0) + // Relative times are constructed as follows: + // +1 second is constructed by Timestamp(1, 0) + // +1 nanosecond is constructed by Timestamp(0, 1) + // +1.1 seconds (1100 milliseconds after the epoch) is constructed by Timestamp(1, 100000000) + // -1.1 seconds (1100 milliseconds before the epoch) is constructed by Timestamp(-1, -100000000) + // + Timestamp(int64_t seconds, int32_t nanoseconds) + : m_seconds(seconds) + , m_nanoseconds(nanoseconds) + , m_is_null(false) + { + REALM_ASSERT_EX(-nanoseconds_per_second < nanoseconds && nanoseconds < nanoseconds_per_second, nanoseconds); + const bool both_non_negative = seconds >= 0 && nanoseconds >= 0; + const bool both_non_positive = seconds <= 0 && nanoseconds <= 0; + REALM_ASSERT_EX(both_non_negative || both_non_positive, both_non_negative, both_non_positive); + } + Timestamp(realm::null) + : m_is_null(true) + { + } + template + Timestamp(std::chrono::time_point tp) + : m_is_null(false) + { + int64_t native_nano = std::chrono::duration_cast(tp.time_since_epoch()).count(); + m_seconds = native_nano / nanoseconds_per_second; + m_nanoseconds = static_cast(native_nano % nanoseconds_per_second); + } + Timestamp() + : Timestamp(null{}) + { + } + + bool is_null() const + { + return m_is_null; + } + + int64_t get_seconds() const noexcept + { + REALM_ASSERT(!m_is_null); + return m_seconds; + } + + int32_t get_nanoseconds() const noexcept + { + REALM_ASSERT(!m_is_null); + return m_nanoseconds; + } + + template + std::chrono::time_point get_time_point() + { + REALM_ASSERT(!m_is_null); + + int64_t native_nano = m_seconds * nanoseconds_per_second + m_nanoseconds; + auto duration = std::chrono::duration_cast(std::chrono::duration{native_nano}); + + return std::chrono::time_point(duration); + } + + // Note that only == and != operators work if one of the Timestamps are null! Else use realm::Greater, + // realm::Less, etc, instead. This is in order to collect all treatment of null behaviour in a single place for all + // types (query_conditions.hpp) to ensure that all types sort and compare null vs. non-null in the same manner, + // especially for int/float where we cannot override operators. This design is open for discussion, though, + // because it has usability drawbacks + bool operator==(const Timestamp& rhs) const + { + if (is_null() && rhs.is_null()) + return true; + + if (is_null() != rhs.is_null()) + return false; + + return m_seconds == rhs.m_seconds && m_nanoseconds == rhs.m_nanoseconds; + } + bool operator!=(const Timestamp& rhs) const + { + return !(*this == rhs); + } + bool operator>(const Timestamp& rhs) const + { + REALM_ASSERT(!is_null()); + REALM_ASSERT(!rhs.is_null()); + return (m_seconds > rhs.m_seconds) || (m_seconds == rhs.m_seconds && m_nanoseconds > rhs.m_nanoseconds); + } + bool operator<(const Timestamp& rhs) const + { + REALM_ASSERT(!is_null()); + REALM_ASSERT(!rhs.is_null()); + return (m_seconds < rhs.m_seconds) || (m_seconds == rhs.m_seconds && m_nanoseconds < rhs.m_nanoseconds); + } + bool operator<=(const Timestamp& rhs) const + { + REALM_ASSERT(!is_null()); + REALM_ASSERT(!rhs.is_null()); + return *this < rhs || *this == rhs; + } + bool operator>=(const Timestamp& rhs) const + { + REALM_ASSERT(!is_null()); + REALM_ASSERT(!rhs.is_null()); + return *this > rhs || *this == rhs; + } + Timestamp& operator=(const Timestamp& rhs) = default; + + template + friend std::basic_ostream& operator<<(std::basic_ostream& out, const Timestamp&); + static constexpr int32_t nanoseconds_per_second = 1000000000; + +private: + int64_t m_seconds; + int32_t m_nanoseconds; + bool m_is_null; +}; + +// LCOV_EXCL_START +template +inline std::basic_ostream& operator<<(std::basic_ostream& out, const Timestamp& d) +{ + out << "Timestamp(" << d.m_seconds << ", " << d.m_nanoseconds << ")"; + return out; +} +// LCOV_EXCL_STOP + +} // namespace realm + +#endif // REALM_TIMESTAMP_HPP diff --git a/!main project/Pods/Realm/include/core/realm/unicode.hpp b/!main project/Pods/Realm/include/core/realm/unicode.hpp new file mode 100644 index 0000000..d8b3d33 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/unicode.hpp @@ -0,0 +1,163 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UNICODE_HPP +#define REALM_UNICODE_HPP + +#include +#include +#include + +#include +#include +#include + + +namespace realm { + +enum string_compare_method_t { + STRING_COMPARE_CORE, + STRING_COMPARE_CPP11, + STRING_COMPARE_CALLBACK, + STRING_COMPARE_CORE_SIMILAR +}; + +extern StringCompareCallback string_compare_callback; +extern string_compare_method_t string_compare_method; + +// Description for set_string_compare_method(): +// +// Short summary: iOS language binding: call +// set_string_compare_method() for fast but slightly inaccurate sort in some countries, or +// set_string_compare_method(2, callbackptr) for slow but precise sort (see callbackptr below) +// +// Different countries ('locales') have different sorting order for strings and letters. Because there unfortunatly +// doesn't exist any unified standardized way to compare strings in C++ on multiple platforms, we need this method. +// +// It determins how sorting a TableView by a String column must take place. The 'method' argument can be: +// +// 0: Fast core-only compare (no OS/framework calls). LIMITATIONS: Works only upto 'Latin Extended 2' (unicodes +// 0...591). Also, sorting order is according to 'en_US' so it may be slightly inaccurate for some countries. +// 'callback' argument is ignored. +// +// Return value: Always 'true' +// +// 1: Native C++11 method if core is compiled as C++11. Gives precise sorting according +// to user's current locale. LIMITATIONS: Currently works only on Windows and on Linux with clang. Does NOT work on +// iOS (due to only 'C' locale being available in CoreFoundation, which puts 'Z' before 'a'). Unknown if works on +// Windows Phone / Android. Furthermore it does NOT work on Linux with gcc 4.7 or 4.8 (lack of c++11 feature that +// can convert utf8->wstring without calls to setlocale()). +// +// Return value: 'true' if supported, otherwise 'false' (if so, then previous setting, if any, is preserved). +// +// 2: Callback method. Language binding / C++ user must provide a utf-8 callback method of prototype: +// bool callback(const char* string1, const char* string2) where 'callback' must return bool(string1 < string2). +// +// Return value: Always 'true' +// +// Default is method = 0 if the function is never called +// +// NOT THREAD SAFE! Call once during initialization or make sure it's not called simultaneously with different +// arguments. The setting is remembered per-process; it does NOT need to be called prior to each sort +bool set_string_compare_method(string_compare_method_t method, StringCompareCallback callback); + + +// Return size in bytes of utf8 character. No error checking +size_t sequence_length(char lead); + +// Limitations for case insensitive string search +// Case insensitive search (equal, begins_with, ends_with, like and contains) +// only works for unicodes 0...0x7f which is the same as the 0...127 +// ASCII character set (letters a-z and A-Z). + +// In does *not* work for the 0...255 ANSI character set that contains +// characters from many European countries like Germany, France, Denmark, +// etc. + +// It also does not work for characters from non-western countries like +// Japan, Russia, Arabia, etc. + +// If there exists characters outside the ASCII range either in the text +// to be searched for, or in the Realm string column which is searched +// in, then the compare yields a random result such that the row may or +// may not be included in the result set. + +// Return bool(string1 < string2) +bool utf8_compare(StringData string1, StringData string2); + +// Return unicode value of character. +uint32_t utf8value(const char* character); + +inline bool equal_sequence(const char*& begin, const char* end, const char* begin2); + +// FIXME: The current approach to case insensitive comparison requires +// that case mappings can be done in a way that does not change he +// number of bytes used to encode the individual Unicode +// character. This is not generally the case, so, as far as I can see, +// this approach has no future. +// +// FIXME: The current approach to case insensitive comparison relies +// on checking each "haystack" character against the corresponding +// character in both a lower cased and an upper cased version of the +// "needle". While this leads to efficient comparison, it ignores the +// fact that "case folding" is the only correct approach to case +// insensitive comparison in a locale agnostic Unicode +// environment. +// +// See +// http://www.w3.org/International/wiki/Case_folding +// http://userguide.icu-project.org/transforms/casemappings#TOC-Case-Folding. +// +// The ideal API would probably be something like this: +// +// case_fold: utf_8 -> case_folded +// equal_case_fold: (needle_case_folded, single_haystack_entry_utf_8) -> found +// search_case_fold: (needle_case_folded, huge_haystack_string_utf_8) -> found_at_position +// +// The case folded form would probably be using UTF-32 or UTF-16. + + +/// If successful, returns a string of the same size as \a source. +/// Returns none if invalid UTF-8 encoding was encountered. +util::Optional case_map(StringData source, bool upper); + +enum IgnoreErrorsTag { IgnoreErrors }; +std::string case_map(StringData source, bool upper, IgnoreErrorsTag); + +/// Assumes that the sizes of \a needle_upper and \a needle_lower are +/// identical to the size of \a haystack. Returns false if the needle +/// is different from the haystack. +bool equal_case_fold(StringData haystack, const char* needle_upper, const char* needle_lower); + +/// Assumes that the sizes of \a needle_upper and \a needle_lower are +/// both equal to \a needle_size. Returns haystack.size() if the +/// needle was not found. +size_t search_case_fold(StringData haystack, const char* needle_upper, const char* needle_lower, size_t needle_size); + +/// Assumes that the sizes of \a needle_upper and \a needle_lower are +/// both equal to \a needle_size. Returns false if the +/// needle was not found. +bool contains_ins(StringData haystack, const char* needle_upper, const char* needle_lower, size_t needle_size, const std::array &charmap); + +/// Case insensitive wildcard matching ('?' for single char, '*' for zero or more chars) +bool string_like_ins(StringData text, StringData pattern) noexcept; +bool string_like_ins(StringData text, StringData upper, StringData lower) noexcept; + +} // namespace realm + +#endif // REALM_UNICODE_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/aes_cryptor.hpp b/!main project/Pods/Realm/include/core/realm/util/aes_cryptor.hpp new file mode 100644 index 0000000..23720b1 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/aes_cryptor.hpp @@ -0,0 +1,113 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#if REALM_ENABLE_ENCRYPTION + +#if REALM_PLATFORM_APPLE +#include +#elif defined(_WIN32) +#include +#include +#include +#pragma comment(lib, "bcrypt.lib") +#else +#include +#include +#endif + +namespace realm { +namespace util { + +struct iv_table; +class EncryptedFileMapping; + +class AESCryptor { +public: + AESCryptor(const uint8_t* key); + ~AESCryptor() noexcept; + + void set_file_size(off_t new_size); + + bool read(FileDesc fd, off_t pos, char* dst, size_t size); + void write(FileDesc fd, off_t pos, const char* src, size_t size) noexcept; + +private: + enum EncryptionMode { +#if REALM_PLATFORM_APPLE + mode_Encrypt = kCCEncrypt, + mode_Decrypt = kCCDecrypt +#elif defined(_WIN32) + mode_Encrypt = 0, + mode_Decrypt = 1 +#else + mode_Encrypt = 1, + mode_Decrypt = 0 +#endif + }; + +#if REALM_PLATFORM_APPLE + CCCryptorRef m_encr; + CCCryptorRef m_decr; +#elif defined(_WIN32) + BCRYPT_KEY_HANDLE m_aes_key_handle; +#else + uint8_t m_aesKey[32]; + EVP_CIPHER_CTX* m_ctx; +#endif + + uint8_t m_hmacKey[32]; + std::vector m_iv_buffer; + std::unique_ptr m_rw_buffer; + std::unique_ptr m_dst_buffer; + + void calc_hmac(const void* src, size_t len, uint8_t* dst, const uint8_t* key) const; + bool check_hmac(const void* data, size_t len, const uint8_t* hmac) const; + void crypt(EncryptionMode mode, off_t pos, char* dst, const char* src, const char* stored_iv) noexcept; + iv_table& get_iv_table(FileDesc fd, off_t data_pos) noexcept; + void handle_error(); +}; + +struct ReaderInfo { + const void* reader_ID; + uint64_t version; +}; + +struct SharedFileInfo { + FileDesc fd; + AESCryptor cryptor; + std::vector mappings; + uint64_t last_scanned_version = 0; + uint64_t current_version = 0; + size_t num_decrypted_pages = 0; + size_t num_reclaimed_pages = 0; + size_t progress_index = 0; + std::vector readers; + + SharedFileInfo(const uint8_t* key, FileDesc file_descriptor); +}; +} +} + +#endif // REALM_ENABLE_ENCRYPTION diff --git a/!main project/Pods/Realm/include/core/realm/util/allocation_metrics.hpp b/!main project/Pods/Realm/include/core/realm/util/allocation_metrics.hpp new file mode 100644 index 0000000..10176e7 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/allocation_metrics.hpp @@ -0,0 +1,326 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2018] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ +#ifndef REALM_UTIL_ALLOCATION_METRICS_HPP +#define REALM_UTIL_ALLOCATION_METRICS_HPP + +#include +#include + +#include + +namespace realm { +namespace util { + +/// Designate a name to be used in heap allocation metrics. +/// +/// An instance can be used with `AllocationMetricsContext::get_metric()` to +/// obtain an instance of `MeteredAllocator` that counts +/// allocations/deallocations towards this name, within that context. +/// +/// Instances of `AllocationMetricName` should be statically allocated. When an +/// instance has been initialized, it must not be destroyed until the program +/// terminates. This is to ensure that iterating over existing names is +/// thread-safe and lock-free. +/// +/// Similarly, when an instance of `AllocationMetricsContext` has been +/// allocated, no further instances of AllocationMetricName must be +/// instantiated. +struct AllocationMetricName { + explicit AllocationMetricName(const char* name) noexcept; + + /// Get the string name. + /// + /// This method is thread-safe. + const char* name() const noexcept; + + /// Get the index of this metric. The index corresponds to an allocator + /// inside the current instance of AllocatorMetricTenant. + /// + /// This method is thread-safe. + size_t index() const noexcept; + + /// Get the next name. The names are returned in no particular order. + /// + /// This method is thread-safe. + const AllocationMetricName* next() const noexcept; + + /// Get the first name in the internal list of names, for the purpose + /// of iterating over all names in the program. + /// + /// This method is thread-safe. + static const AllocationMetricName* get_top() noexcept; + static const AllocationMetricName* find(const char* name) noexcept; +private: + const char* m_name; + size_t m_index; // Index into `AllocationMetricsContext::m_metrics`. + + // This is used to iterate over all existing components. Instances of + // AllocationMetricName are expected to be statically allocated. + const AllocationMetricName* m_next = nullptr; +}; + + +/// A heap memory allocator that keeps track of how much was +/// allocated/deallocated throughout its lifetime. +/// +/// Memory is allocated with `DefaultAllocator`. +/// +/// All methods on instances of this class are thread-safe. +class MeteredAllocator final : public AllocatorBase { +public: + MeteredAllocator() noexcept; + + static MeteredAllocator& unknown() noexcept; + + /// Return the currently allocated number of bytes. + /// + /// This method is thread-safe, but may temporarily return slightly + /// inaccurate results if allocations/deallocations are happening while it + /// is being called. + std::size_t get_currently_allocated_bytes() const noexcept; + + /// Return the total number of bytes that have been allocated (including + /// allocations that have since been freed). + /// + /// This method is thread-safe. + std::size_t get_total_allocated_bytes() const noexcept; + + /// Return the total number of bytes that have been freed. + /// + /// This method is thread-safe. + std::size_t get_total_deallocated_bytes() const noexcept; + + // AllocatorBase interface: + + /// Return a reference to an MeteredAllocator that belongs to the current + /// AllocationMetricsContext (if any) and the current AllocationMetricNameScope + /// (if any). + /// + /// The returned reference is valid for the duration of the lifetime of the + /// instance of AllocationMetricsContext that is "current" at the time of + /// calling this function, and namely it is valid beyond the lifetime of + /// the current AllocationMetricNameScope. + /// + /// If there is no current AllocationMetricsContext, the global "unknown" + /// tenant will be used. + /// + /// If no metric name is currently in scope (through the use of + /// AllocationMetricNameScope), allocations and deallocations will be counted + /// towards the default "unknown" metric. + /// + /// This method is thread-safe. + static MeteredAllocator& get_default() noexcept; + + /// Allocate memory, accounting for the allocation in metrics. + /// + /// This method is thread-safe. + void* allocate(size_t size, size_t align) override final; + + /// Free memory, accounting for the deallocation in metrics. + /// + /// This method is thread-safe. + void free(void* ptr, size_t size) noexcept override final; + + /// Notify metrics that an allocation happened. + /// + /// This method is thread-safe. + void did_allocate_bytes(std::size_t) noexcept; + + /// Notify metrics that a deallocation happened. + /// + /// This method is thread-safe. + void did_free_bytes(std::size_t) noexcept; + +private: + std::atomic m_allocated_bytes; + // These members are spaced by 64 bytes to prevent false sharing + // (inter-processor CPU locks when multiple processes are modifying them + // concurrently). + char dummy[56]; + std::atomic m_deallocated_bytes; + char dummy2[56]; // Prevent false sharing with the next element. +}; + +/// `AllocationMetricsContext` represents a runtime scope for metrics, such as +/// for instance a server running in a multi-tenant scenario, where each tenant +/// would have one context associated with it. +/// +/// `AllocationMetricsContext` is not available on mobile, due to lack of +/// thread-local storage support on iOS. +struct AllocationMetricsContext { +public: + AllocationMetricsContext(); + ~AllocationMetricsContext(); + +#if !REALM_MOBILE + /// Get the thread-specific AllocationMetricsContext. If none has been set, a + /// reference to a globally-allocated "unknown" tenant will be returned. + static AllocationMetricsContext& get_current() noexcept; +#endif + + /// Get the statically-allocated "unknown" tenant. + static AllocationMetricsContext& get_unknown(); + + MeteredAllocator& get_metric(const AllocationMetricName& name) noexcept; +private: + std::unique_ptr m_metrics; + + // In debug builds, this is incremented/decremented by + // `AllocationMetricsContextScope`, and checked in the destructor, to avoid + // dangling references. + std::atomic m_refcount; + friend class AllocationMetricsContextScope; +}; + +/// Open a scope where metered memory allocations are counted towards the given +/// name. +/// +/// Creating an instance of this class causes calls to +/// `MeteredAllocator::get_default()` from the current thread to return a +/// reference to an allocator that accounts for allocations/deallocations +/// under the named metric specified as the constructor argument. +/// +/// When such an instance is destroyed, the previous scope will come back +/// in effect (if one exists; if none exists, the "unknown" metric will be +/// used). +/// +/// It is usually an error to create instances of this class with non-scope +/// lifetime, for example on the heap. For that reason, `operator new` is +/// disabled as a precaution. +/// +/// If no `AllocationMetricsContext` is current (by instantiation of +/// `AllocationMetricsContextScope`), metrics recorded in the scope introduced +/// by this instance will count towards the "unknown" context, accessible by +/// calling `AllocationMetricsContext::get_unknown()`. +class AllocationMetricNameScope final { +public: + /// Establish a scope under which all allocations will be tracked as + /// belonging to \a name. + explicit AllocationMetricNameScope(const AllocationMetricName& name) noexcept; + ~AllocationMetricNameScope(); + AllocationMetricNameScope(AllocationMetricNameScope&&) = delete; + AllocationMetricNameScope& operator=(AllocationMetricNameScope&&) = delete; + + void* operator new(std::size_t) = delete; +private: + const AllocationMetricName& m_name; + const AllocationMetricName* m_previous = nullptr; +}; + +/// Open a scope using the given context for allocation metrics. +/// +/// Creating an instance of this class causes calls to +/// `AllocationMetricsContext::get_current()` to return the provided +/// instance. This function is called when by `MeteredAllocator::get_default()` +/// to return an instance that belongs to the given context. +/// +/// When the instance is destroyed, the previous context will become active, or +/// the "unknown" context if there was none. +/// +/// It is usually an error to create instances of this class with non-scope +/// lifetime, for example on the heap. For that reason, `operator new` is +/// disabled as a precaution. +class AllocationMetricsContextScope final { +public: + explicit AllocationMetricsContextScope(AllocationMetricsContext& context) noexcept; + ~AllocationMetricsContextScope(); + AllocationMetricsContextScope(AllocationMetricsContextScope&&) = delete; + AllocationMetricsContextScope& operator=(AllocationMetricsContextScope&&) = delete; + + void* operator new(std::size_t) = delete; + +private: + AllocationMetricsContext& m_context; + AllocationMetricsContext& m_previous; +}; + + +/// Convenience STL-compatible allocator that counts allocations as part of the +/// current AllocationMetricNameScope. +template +using MeteredSTLAllocator = STLAllocator; + + +// Implementation: + +inline const char* AllocationMetricName::name() const noexcept +{ + return m_name; +} + +inline size_t AllocationMetricName::index() const noexcept +{ + return m_index; +} + +inline const AllocationMetricName* AllocationMetricName::next() const noexcept +{ + return m_next; +} + +inline std::size_t MeteredAllocator::get_currently_allocated_bytes() const noexcept +{ + return get_total_allocated_bytes() - get_total_deallocated_bytes(); +} + +inline std::size_t MeteredAllocator::get_total_allocated_bytes() const noexcept +{ + return m_allocated_bytes.load(std::memory_order_relaxed); +} + +inline std::size_t MeteredAllocator::get_total_deallocated_bytes() const noexcept +{ + return m_deallocated_bytes.load(std::memory_order_relaxed); +} + +inline void* MeteredAllocator::allocate(size_t size, size_t align) +{ + void* ptr = DefaultAllocator::get_default().allocate(size, align); + did_allocate_bytes(size); + return ptr; +} + +inline void MeteredAllocator::free(void* ptr, size_t size) noexcept +{ + DefaultAllocator::get_default().free(ptr, size); + did_free_bytes(size); +} + +inline void MeteredAllocator::did_allocate_bytes(std::size_t size) noexcept +{ +#if !REALM_MOBILE + m_allocated_bytes.fetch_add(size, std::memory_order_relaxed); +#else + static_cast(size); +#endif +} + +inline void MeteredAllocator::did_free_bytes(std::size_t size) noexcept +{ +#if !REALM_MOBILE + m_deallocated_bytes.fetch_add(size, std::memory_order_relaxed); +#else + static_cast(size); +#endif +} +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_ALLOCATION_METRICS_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/allocator.hpp b/!main project/Pods/Realm/include/core/realm/util/allocator.hpp new file mode 100644 index 0000000..c0a0a83 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/allocator.hpp @@ -0,0 +1,398 @@ +/************************************************************************* + * + * Copyright 2018 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_ALLOCATOR_HPP +#define REALM_UTIL_ALLOCATOR_HPP + +#include +#include +#include + +namespace realm { +namespace util { + +/// Dynamic heap allocation interface. +/// +/// Implementors may optionally implement a static method `get_default()`, which +/// should return a reference to an allocator instance. This allows +/// `STLAllocator` to be default-constructed. +/// +/// NOTE: This base class is not related to the `realm::Allocator` interface, +/// which is used in the context of allocating memory inside a Realm file. +struct AllocatorBase { + static constexpr std::size_t max_alignment = 16; // FIXME: This is arch-dependent + + /// Allocate \a size bytes at aligned at \a align. + /// + /// May throw `std::bad_alloc` if allocation fails. May **NOT** return + /// an invalid pointer (such as `nullptr`). + virtual void* allocate(std::size_t size, std::size_t align) = 0; + + /// Free the previously allocated block of memory. \a size is not required + /// to be accurate, and is only provided for statistics and debugging + /// purposes. + /// + /// \a ptr may be `nullptr`, in which case this shall be a noop. + virtual void free(void* ptr, size_t size) noexcept = 0; +}; + +/// Implementation of AllocatorBase that uses `operator new`/`operator delete`. +/// +/// Using this allocator with standard containers is zero-overhead: No +/// additional storage is required at any level. +struct DefaultAllocator final : AllocatorBase { + /// Return a reference to a global singleton. + /// + /// This method is thread-safe. + static DefaultAllocator& get_default() noexcept; + + /// Allocate memory (using `operator new`). + /// + /// \a align must not exceed `max_alignment` before C++17. + /// + /// This method is thread-safe. + void* allocate(std::size_t size, std::size_t align) final; + + /// Free memory (using `operator delete`). + /// + /// If \a ptr equals `nullptr`, this is a no-op. + /// + /// This method is thread-safe. + void free(void* ptr, std::size_t size) noexcept final; + +private: + static DefaultAllocator g_instance; + DefaultAllocator() + { + } +}; + +template +struct STLDeleter; + +namespace detail { +/// Base class for things that hold a reference to an allocator. The default +/// implementation carries a pointer to the allocator instance. Singleton +/// allocators (such as `DefaultAllocator`) may specialize this class such that +/// no extra storage is needed. +template +struct GetAllocator { + // Note: Some allocators may not define get_default(). This is OK, and + // this constructor will not be instantiated (SFINAE). + GetAllocator() noexcept + : m_allocator(&Allocator::get_default()) + { + } + + template + GetAllocator(A& allocator) noexcept + : m_allocator(&allocator) + { + } + + template + GetAllocator& operator=(const GetAllocator& other) noexcept + { + m_allocator = &other.get_allocator(); + return *this; + } + + Allocator& get_allocator() const noexcept + { + return *m_allocator; + } + + bool operator==(const GetAllocator& other) const noexcept + { + return m_allocator == other.m_allocator; + } + + bool operator!=(const GetAllocator& other) const noexcept + { + return m_allocator != other.m_allocator; + } + + Allocator* m_allocator; +}; + +/// Specialization for `DefaultAllocator` that has zero size, i.e. no extra +/// storage requirements compared with `std::allocator`. +template <> +struct GetAllocator { + GetAllocator() noexcept + { + } + + GetAllocator(DefaultAllocator&) noexcept + { + } + + DefaultAllocator& get_allocator() const noexcept + { + return DefaultAllocator::get_default(); + } + + bool operator==(const GetAllocator&) const noexcept + { + return true; + } + + bool operator!=(const GetAllocator&) const noexcept + { + return false; + } +}; +} // namespace detail + +/// STL-compatible static dispatch bridge to a dynamic implementation of +/// `AllocatorBase`. Wraps a pointer to an object that adheres to the +/// `AllocatorBase` interface. It is optional whether the `Allocator` class +/// template argument actually derives from `AllocatorBase`. +/// +/// The intention is that users of this class can set `Allocator` to the +/// nearest-known base class of the expected allocator implementations, such +/// that appropriate devirtualization can take place. +template +struct STLAllocator : detail::GetAllocator { + using value_type = T; + using Deleter = STLDeleter; + + // These typedefs are optional, but GCC 4.9 requires them when using the + // allocator together with std::map, std::basic_string, etc. + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using reference = T&; + using const_reference = const T&; + using pointer = T*; + using const_pointer = const T*; + + /// The default constructor is only availble when the static method + /// `Allocator::get_default()` exists. + STLAllocator() noexcept + { + } + + constexpr STLAllocator(Allocator& base) noexcept + : detail::GetAllocator(base) + { + } + template + constexpr STLAllocator(const STLAllocator& other) noexcept + : detail::GetAllocator(other.get_allocator()) + { + } + + STLAllocator& operator=(const STLAllocator& other) noexcept = default; + + T* allocate(std::size_t n) + { + static_assert(alignof(T) <= Allocator::max_alignment, "Over-aligned allocation"); + void* ptr = this->get_allocator().allocate(sizeof(T) * n, alignof(T)); + return static_cast(ptr); + } + + void deallocate(T* ptr, std::size_t n) noexcept + { + this->get_allocator().free(ptr, sizeof(T) * n); + } + + operator Allocator&() const + { + return this->get_allocator(); + } + + template + struct rebind { + using other = STLAllocator; + }; + + // construct() and destroy() are optional, but are required by some + // containers under GCC 4.9 (verified for at least std::list). + template + void construct(T* ptr, Args&&... args) + { + ::new (ptr) T(std::forward(args)...); + } + + template + void destroy(U* ptr) + { + ptr->~U(); + } + +private: + template + friend struct STLAllocator; +}; + +template +struct STLDeleter : detail::GetAllocator { + // The reason for this member is to accurately pass `size` to `free()` when + // deallocating. `sizeof(T)` may not be good enough, because the pointer may + // have been cast to a relative type of different size. + size_t m_size; + + explicit STLDeleter(Allocator& allocator) noexcept + : STLDeleter(0, allocator) + { + } + explicit STLDeleter(size_t size, Allocator& allocator) noexcept + : detail::GetAllocator(allocator) + , m_size(size) + { + } + + template + STLDeleter(const STLDeleter& other) noexcept + : detail::GetAllocator(other.get_allocator()) + , m_size(other.m_size) + { + } + + void operator()(T* ptr) + { + ptr->~T(); + this->get_allocator().free(ptr, m_size); + } +}; + +template +struct STLDeleter : detail::GetAllocator { + // Note: Array-allocated pointers cannot be upcast to base classes, because + // of array slicing. + size_t m_count; + explicit STLDeleter(Allocator& allocator) noexcept + : STLDeleter(0, allocator) + { + } + explicit STLDeleter(size_t count, Allocator& allocator) noexcept + : detail::GetAllocator(allocator) + , m_count(count) + { + } + + template + STLDeleter(const STLDeleter& other) noexcept + : detail::GetAllocator(other.get_allocator()) + , m_count(other.m_count) + { + } + + template + STLDeleter& operator=(const STLDeleter& other) noexcept + { + static_cast&>(*this) = + static_cast&>(other); + m_count = other.m_count; + return *this; + } + + void operator()(T* ptr) + { + for (size_t i = 0; i < m_count; ++i) { + ptr[i].~T(); + } + this->get_allocator().free(ptr, m_count * sizeof(T)); + } +}; + +/// make_unique with custom allocator (non-array version) +template +auto make_unique(Allocator& allocator, Args&&... args) + -> std::enable_if_t::value, std::unique_ptr>> +{ + void* memory = allocator.allocate(sizeof(T), alignof(T)); // Throws + T* ptr; + try { + ptr = new (memory) T(std::forward(args)...); // Throws + } + catch (...) { + allocator.free(memory, sizeof(T)); + throw; + } + std::unique_ptr> result{ptr, STLDeleter{sizeof(T), allocator}}; + return result; +} + +/// make_unique with custom allocator supporting `get_default()` +/// (non-array-version) +template +auto make_unique(Args&&... args) + -> std::enable_if_t::value, std::unique_ptr>> +{ + return make_unique(Allocator::get_default(), std::forward(args)...); +} + +/// make_unique with custom allocator (array version) +template +auto make_unique(Allocator& allocator, size_t count) + -> std::enable_if_t::value, std::unique_ptr>> +{ + using T = std::remove_extent_t; + void* memory = allocator.allocate(sizeof(T) * count, alignof(T)); // Throws + T* ptr = reinterpret_cast(memory); + size_t constructed = 0; + try { + // FIXME: Can't use array placement new, because MSVC has a buggy + // implementation of it. :-( + while (constructed < count) { + new (&ptr[constructed]) T; // Throws + ++constructed; + } + } + catch (...) { + for (size_t i = 0; i < constructed; ++i) { + ptr[i].~T(); + } + allocator.free(memory, sizeof(T) * count); + throw; + } + std::unique_ptr> result{ptr, STLDeleter{count, allocator}}; + return result; +} + +/// make_unique with custom allocator supporting `get_default()` (array version) +template +auto make_unique(size_t count) + -> std::enable_if_t::value, std::unique_ptr>> +{ + return make_unique(Allocator::get_default(), count); +} + + +// Implementation: + +inline DefaultAllocator& DefaultAllocator::get_default() noexcept +{ + return g_instance; +} + +inline void* DefaultAllocator::allocate(std::size_t size, std::size_t) +{ + return new char[size]; +} + +inline void DefaultAllocator::free(void* ptr, std::size_t) noexcept +{ + delete[] static_cast(ptr); +} + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_ALLOCATOR_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/any.hpp b/!main project/Pods/Realm/include/core/realm/util/any.hpp new file mode 100644 index 0000000..5ac72b2 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/any.hpp @@ -0,0 +1,165 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2017 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_UTIL_ANY_HPP +#define REALM_UTIL_ANY_HPP + +#include +#include +#include +#include + +#include + +namespace realm { +namespace util { + +using bad_cast = ExceptionWithBacktrace; + +// A naive implementation of C++17's std::any +// This does not perform the small-object optimization or make any particular +// attempt at being performant +class Any final { +public: + // Constructors + + Any() = default; + Any(Any&&) noexcept = default; + ~Any() = default; + Any& operator=(Any&&) noexcept = default; + + Any(Any const& rhs) + : m_value(rhs.m_value ? rhs.m_value->copy() : nullptr) + { + } + + template::type, Any>::value>::type> + Any(T&& value) + : m_value(std::make_unique::type>>(std::forward(value))) + { + } + + Any& operator=(Any const& rhs) + { + m_value = rhs.m_value ? rhs.m_value->copy() : nullptr; + return *this; + } + + template::type, Any>::value>::type> + Any& operator=(T&& value) + { + m_value = std::make_unique::type>>(std::forward(value)); + return *this; + } + + // Modifiers + + void reset() noexcept { m_value.reset(); } + void swap(Any& rhs) noexcept { std::swap(m_value, rhs.m_value); } + + // Observers + + bool has_value() const noexcept { return m_value != nullptr; } + std::type_info const& type() const noexcept { return m_value ? m_value->type() : typeid(void); } + +private: + struct ValueBase { + virtual ~ValueBase() noexcept { } + virtual std::type_info const& type() const noexcept = 0; + virtual std::unique_ptr copy() const = 0; + }; + template + struct Value : ValueBase { + T value; + template Value(U&& v) : value(std::forward(v)) { } + + std::type_info const& type() const noexcept override { return typeid(T); } + std::unique_ptr copy() const override + { + return std::make_unique>(value); + } + }; + std::unique_ptr m_value; + + template + friend const T* any_cast(const Any* operand) noexcept; + template + friend T* any_cast(Any* operand) noexcept; + + template + const T* cast() const noexcept + { + return &static_cast*>(m_value.get())->value; + } + + template + T* cast() noexcept + { + return &static_cast*>(m_value.get())->value; + } +}; + +template +T any_cast(Any const& value) +{ + auto ptr = any_cast::type>::type>(&value); + if (!ptr) + throw bad_cast(); + return *ptr; +} + +template +T any_cast(Any& value) +{ + auto ptr = any_cast::type>(&value); + if (!ptr) + throw bad_cast(); + return *ptr; +} + +template +T any_cast(Any&& value) +{ + auto ptr = any_cast::type>(&value); + if (!ptr) + throw bad_cast(); + return std::move(*ptr); +} + +template +T* any_cast(Any* value) noexcept +{ + return value && value->type() == typeid(T) ? value->cast() : nullptr; +} + +template +const T* any_cast(const Any* value) noexcept +{ + return value && value->type() == typeid(T) ? value->cast() : nullptr; +} +} // namespace util +} // namespace realm + +namespace std { +inline void swap(realm::util::Any& lhs, realm::util::Any& rhs) noexcept +{ + lhs.swap(rhs); +} +} // namespace std + +#endif // REALM_UTIL_ANY_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/assert.hpp b/!main project/Pods/Realm/include/core/realm/util/assert.hpp new file mode 100644 index 0000000..5e75066 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/assert.hpp @@ -0,0 +1,107 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_ASSERT_HPP +#define REALM_UTIL_ASSERT_HPP + +#include +#include + +#if REALM_ENABLE_ASSERTIONS || defined(REALM_DEBUG) +#define REALM_ASSERTIONS_ENABLED 1 +#else +#define REALM_ASSERTIONS_ENABLED 0 +#endif + +#define REALM_ASSERT_RELEASE(condition) \ + (REALM_LIKELY(condition) ? static_cast(0) \ + : realm::util::terminate("Assertion failed: " #condition, __FILE__, __LINE__)) + +#if REALM_ASSERTIONS_ENABLED +#define REALM_ASSERT(condition) REALM_ASSERT_RELEASE(condition) +#else +#define REALM_ASSERT(condition) static_cast(sizeof bool(condition)) +#endif + +#ifdef REALM_DEBUG +#define REALM_ASSERT_DEBUG(condition) REALM_ASSERT_RELEASE(condition) +#else +#define REALM_ASSERT_DEBUG(condition) static_cast(sizeof bool(condition)) +#endif + +#define REALM_STRINGIFY(X) #X + +#define REALM_ASSERT_RELEASE_EX(condition, ...) \ + (REALM_LIKELY(condition) ? static_cast(0) \ + : realm::util::terminate_with_info("Assertion failed: " #condition, __LINE__, __FILE__, \ + REALM_STRINGIFY((__VA_ARGS__)), __VA_ARGS__)) + +#ifdef REALM_DEBUG +#define REALM_ASSERT_DEBUG_EX REALM_ASSERT_RELEASE_EX +#else +#define REALM_ASSERT_DEBUG_EX(condition, ...) static_cast(sizeof bool(condition)) +#endif + +// Becase the assert is used in noexcept methods, it's a bad idea to allocate +// buffer space for the message so therefore we must pass it to terminate which +// will 'cerr' it for us without needing any buffer +#if REALM_ENABLE_ASSERTIONS || defined(REALM_DEBUG) + +#define REALM_ASSERT_EX REALM_ASSERT_RELEASE_EX + +#define REALM_ASSERT_3(left, cmp, right) \ + (REALM_LIKELY((left)cmp(right)) ? static_cast(0) \ + : realm::util::terminate("Assertion failed: " \ + "" #left " " #cmp " " #right, \ + __FILE__, __LINE__, left, right)) + +#define REALM_ASSERT_7(left1, cmp1, right1, logical, left2, cmp2, right2) \ + (REALM_LIKELY(((left1)cmp1(right1))logical((left2)cmp2(right2))) \ + ? static_cast(0) \ + : realm::util::terminate("Assertion failed: " \ + "" #left1 " " #cmp1 " " #right1 " " #logical " " \ + "" #left2 " " #cmp2 " " #right2, \ + __FILE__, __LINE__, left1, right1, left2, right2)) + +#define REALM_ASSERT_11(left1, cmp1, right1, logical1, left2, cmp2, right2, logical2, left3, cmp3, right3) \ + (REALM_LIKELY(((left1)cmp1(right1))logical1((left2)cmp2(right2)) logical2((left3)cmp3(right3))) \ + ? static_cast(0) \ + : realm::util::terminate("Assertion failed: " \ + "" #left1 " " #cmp1 " " #right1 " " #logical1 " " \ + "" #left2 " " #cmp2 " " #right2 " " #logical2 " " \ + "" #left3 " " #cmp3 " " #right3, \ + __FILE__, __LINE__, left1, right1, left2, right2, left3, right3)) +#else +#define REALM_ASSERT_EX(condition, ...) static_cast(sizeof bool(condition)) +#define REALM_ASSERT_3(left, cmp, right) static_cast(sizeof bool((left)cmp(right))) +#define REALM_ASSERT_7(left1, cmp1, right1, logical, left2, cmp2, right2) \ + static_cast(sizeof bool(((left1)cmp1(right1))logical((left2)cmp2(right2)))) +#define REALM_ASSERT_11(left1, cmp1, right1, logical1, left2, cmp2, right2, logical2, left3, cmp3, right3) \ + static_cast(sizeof bool(((left1)cmp1(right1))logical1((left2)cmp2(right2)) logical2((left3)cmp3(right3)))) +#endif + +#define REALM_UNREACHABLE() realm::util::terminate("Unreachable code", __FILE__, __LINE__) +#ifdef REALM_COVER +#define REALM_COVER_NEVER(x) false +#define REALM_COVER_ALWAYS(x) true +#else +#define REALM_COVER_NEVER(x) (x) +#define REALM_COVER_ALWAYS(x) (x) +#endif + +#endif // REALM_UTIL_ASSERT_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/backtrace.hpp b/!main project/Pods/Realm/include/core/realm/util/backtrace.hpp new file mode 100644 index 0000000..8011023 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/backtrace.hpp @@ -0,0 +1,226 @@ +/************************************************************************* + * + * Copyright 2018 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_BACKTRACE_HPP +#define REALM_UTIL_BACKTRACE_HPP + +#include +#include +#include + +namespace realm { +namespace util { + +/// Backtrace encapsulates a stack trace, usually as captured by `backtrace()` +/// and `backtrace_symbols()` (or platform-specific equivalents). +struct Backtrace { + /// Capture a symbolicated stack trace, excluding the call to `capture()` + /// itself. If any error occurs while capturing the stack trace or + /// translating symbol names, a `Backtrace` object is returned containing a + /// single line describing the error. + /// + /// This function only allocates memory as part of calling + /// `backtrace_symbols()` (or the current platform's equivalent). + static Backtrace capture() noexcept; + + /// Print the backtrace to the stream. Each line is separated by a newline. + /// The format of the output is unspecified. + void print(std::ostream&) const; + + /// Construct an empty stack trace. + Backtrace() noexcept + { + } + + /// Move constructor. This operation cannot fail. + Backtrace(Backtrace&&) noexcept; + + /// Copy constructor. See the copy assignment operator. + Backtrace(const Backtrace&) noexcept; + + ~Backtrace(); + + /// Move assignment operator. This operation cannot fail. + Backtrace& operator=(Backtrace&&) noexcept; + + /// Copy assignment operator. Copying a `Backtrace` object may result in a + /// memory allocation. If such an allocation fails, the backtrace is + /// replaced with a single line describing the error. + Backtrace& operator=(const Backtrace&) noexcept; + +private: + Backtrace(void* memory, const char* const* strs, size_t len) + : m_memory(memory) + , m_strs(strs) + , m_len(len) + { + } + Backtrace(void* memory, size_t len) + : m_memory(memory) + , m_strs(static_cast(memory)) + , m_len(len) + { + } + + // m_memory is a pointer to the memory block returned by + // `backtrace_symbols()`. It is usually equal to `m_strs`, except in the + // case where an error has occurred and `m_strs` points to statically + // allocated memory describing the error. + // + // When `m_memory` is non-null, the memory is owned by this object. + void* m_memory = nullptr; + + // A pointer to a list of string pointers describing the stack trace (same + // format as returned by `backtrace_symbols()`). + const char* const* m_strs = nullptr; + + // Number of entries in this stack trace. + size_t m_len = 0; +}; + +namespace detail { + +class ExceptionWithBacktraceBase { +public: + ExceptionWithBacktraceBase() + : m_backtrace(util::Backtrace::capture()) + { + } + const util::Backtrace& backtrace() const noexcept + { + return m_backtrace; + } + virtual const char* message() const noexcept = 0; + +protected: + util::Backtrace m_backtrace; + // Cannot use Optional here, because Optional wants to use + // ExceptionWithBacktrace. + mutable bool m_has_materialized_message = false; + mutable std::string m_materialized_message; + + // Render the message and the backtrace into m_message_with_backtrace. If an + // exception is thrown while rendering the message, the message without the + // backtrace will be returned. + const char* materialize_message() const noexcept; +}; + +} // namespace detail + +/// Base class for exceptions that record a stack trace of where they were +/// thrown. +/// +/// The template argument is expected to be an exception type conforming to the +/// standard library exception API (`std::exception` and friends). +/// +/// It is possible to opt in to exception backtraces in two ways, (a) as part of +/// the exception type, in which case the backtrace will always be included for +/// all exceptions of that type, or (b) at the call-site of an opaque exception +/// type, in which case it is up to the throw-site to decide whether a backtrace +/// should be included. +/// +/// Example (a): +/// ``` +/// class MyException : ExceptionWithBacktrace { +/// public: +/// const char* message() const noexcept override +/// { +/// return "MyException error message"; +/// } +/// }; +/// +/// ... +/// +/// try { +/// throw MyException{}; +/// } +/// catch (const MyException& ex) { +/// // Print the backtrace without the message: +/// std::cerr << ex.backtrace() << "\n"; +/// // Print the exception message and the backtrace: +/// std::cerr << ex.what() << "\n"; +/// // Print the exception message without the backtrace: +/// std::cerr << ex.message() << "\n"; +/// } +/// ``` +/// +/// Example (b): +/// ``` +/// class MyException : std::exception { +/// public: +/// const char* what() const noexcept override +/// { +/// return "MyException error message"; +/// } +/// }; +/// +/// ... +/// +/// try { +/// throw ExceptionWithBacktrace{}; +/// } +/// catch (const MyException& ex) { +/// // Print the exception message and the backtrace: +/// std::cerr << ex.what() << "\n"; +/// } +/// ``` +template +class ExceptionWithBacktrace : public Base, public detail::ExceptionWithBacktraceBase { +public: + template + inline ExceptionWithBacktrace(Args&&... args) + : Base(std::forward(args)...) + , detail::ExceptionWithBacktraceBase() // backtrace captured here + { + } + + /// Return the message of the exception, including the backtrace of where + /// the exception was thrown. + const char* what() const noexcept final + { + return materialize_message(); + } + + /// Return the message of the exception without the backtrace. The default + /// implementation calls `Base::what()`. + const char* message() const noexcept override + { + return Base::what(); + } +}; + +// Wrappers for standard exception types with backtrace support +using runtime_error = ExceptionWithBacktrace; +using range_error = ExceptionWithBacktrace; +using overflow_error = ExceptionWithBacktrace; +using underflow_error = ExceptionWithBacktrace; +using bad_alloc = ExceptionWithBacktrace; +using invalid_argument = ExceptionWithBacktrace; +using out_of_range = ExceptionWithBacktrace; +using logic_error = ExceptionWithBacktrace; + +} // namespace util +} // namespace realm + +inline std::ostream& operator<<(std::ostream& os, const realm::util::Backtrace& bt) +{ + bt.print(os); + return os; +} + +#endif // REALM_UTIL_BACKTRACE_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/base64.hpp b/!main project/Pods/Realm/include/core/realm/util/base64.hpp new file mode 100644 index 0000000..8d808a6 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/base64.hpp @@ -0,0 +1,79 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_BASE64_HPP +#define REALM_UTIL_BASE64_HPP + +#include +#include +#include + +namespace realm { +namespace util { + + +/// base64_encode() encodes the bnary data in \param in_buffer of size \param in_buffer_size. +/// The encoded data is placed in \param out_buffer. The size of \param \out_buffer is passed in +/// \param out_buffer_size. The output buffer \param out_buffer must be +/// large enough to hold the base64 encoded data. The size can be obtained from the function +/// base64_encoded_size. \param out_buffer_size is only used to assert that the output buffer is +/// large enough. +size_t base64_encode(const char *in_buffer, size_t in_buffer_size, char* out_buffer, size_t out_buffer_size) noexcept; + +/// base64_encoded_size() returns the exact size of the base64 encoded +/// data as a function of the size of the input data. +inline size_t base64_encoded_size(size_t in_buffer_size) noexcept +{ + return 4 * ((in_buffer_size + 2) / 3); +} + + +/// Decode base64-encoded string in input, and places the result in out_buffer. +/// The length of the out_buffer must be at least 3 * input.size() / 4. +/// +/// The input must be padded base64 (i.e. the number of non-whitespace +/// characters in the input must be a multiple of 4). Whitespace (spaces, tabs, +/// newlines) is ignored. +/// +/// The algorithm stops when the first character not in the base64 character +/// set is encountered, or when the end of the input is reached. +/// +/// \returns the number of successfully decoded bytes written to out_buffer, or +/// none if the whole input was not valid base64. +Optional base64_decode(StringData input, char* out_buffer, size_t out_buffer_len) noexcept; + +/// Return an upper bound on the decoded size of a Base64-encoded data +/// stream of length \a base64_size. The returned value is suitable for +/// allocation of buffers containing decoded data. +inline size_t base64_decoded_size(size_t base64_size) noexcept +{ + return (base64_size * 3 + 3) / 4; +} + + + +/// base64_decode_to_vector() is a convenience function that decodes \param +/// encoded and returns the result in a std::vector with the correct size. +/// This function returns none if the input is invalid. +Optional> base64_decode_to_vector(StringData encoded); + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_BASE64_HPP + diff --git a/!main project/Pods/Realm/include/core/realm/util/basic_system_errors.hpp b/!main project/Pods/Realm/include/core/realm/util/basic_system_errors.hpp new file mode 100644 index 0000000..8f7a626 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/basic_system_errors.hpp @@ -0,0 +1,89 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_BASIC_SYSTEM_ERRORS_HPP +#define REALM_UTIL_BASIC_SYSTEM_ERRORS_HPP + +#include +#include + + +namespace realm { +namespace util { +namespace error { + +enum basic_system_errors { + /// Address family not supported by protocol. + address_family_not_supported = EAFNOSUPPORT, + + /// Invalid argument. + invalid_argument = EINVAL, + + /// Cannot allocate memory. + no_memory = ENOMEM, + + /// Operation cancelled. + operation_aborted = ECANCELED, + + /// Connection aborted. + connection_aborted = ECONNABORTED, + + /// Connection reset by peer + connection_reset = ECONNRESET, + + /// Broken pipe + broken_pipe = EPIPE, + + /// Resource temporarily unavailable + resource_unavailable_try_again = EAGAIN, +}; + +std::error_code make_error_code(basic_system_errors) noexcept; + +} // namespace error +} // namespace util +} // namespace realm + +namespace std { + +template <> +class is_error_code_enum { +public: + static const bool value = true; +}; + +} // namespace std + +namespace realm { +namespace util { + +std::error_code make_basic_system_error_code(int) noexcept; + + +// implementation + +inline std::error_code make_basic_system_error_code(int err) noexcept +{ + using namespace error; + return make_error_code(basic_system_errors(err)); +} + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_BASIC_SYSTEM_ERRORS_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/bind_ptr.hpp b/!main project/Pods/Realm/include/core/realm/util/bind_ptr.hpp new file mode 100644 index 0000000..e5b8a99 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/bind_ptr.hpp @@ -0,0 +1,484 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_BIND_PTR_HPP +#define REALM_UTIL_BIND_PTR_HPP + +#include +#include +#include +#include + +#include +#include + + +namespace realm { +namespace util { + +class bind_ptr_base { +public: + struct adopt_tag { + }; +}; + + +/// A generic intrusive smart pointer that binds itself explicitely to +/// the target object. +/// +/// This class is agnostic towards what 'binding' means for the target +/// object, but a common use is 'reference counting'. See RefCountBase +/// for an example of that. +/// +/// This smart pointer implementation assumes that the target object +/// destructor never throws. +template +class bind_ptr : public bind_ptr_base { +public: + constexpr bind_ptr() noexcept + : m_ptr(nullptr) + { + } + ~bind_ptr() noexcept + { + unbind(); + } + + explicit bind_ptr(T* p) noexcept + { + bind(p); + } + template + explicit bind_ptr(U* p) noexcept + { + bind(p); + } + + bind_ptr(T* p, adopt_tag) noexcept + { + m_ptr = p; + } + template + bind_ptr(U* p, adopt_tag) noexcept + { + m_ptr = p; + } + + // Copy construct + bind_ptr(const bind_ptr& p) noexcept + { + bind(p.m_ptr); + } + template + bind_ptr(const bind_ptr& p) noexcept + { + bind(p.m_ptr); + } + + // Copy assign + bind_ptr& operator=(const bind_ptr& p) noexcept + { + bind_ptr(p).swap(*this); + return *this; + } + template + bind_ptr& operator=(const bind_ptr& p) noexcept + { + bind_ptr(p).swap(*this); + return *this; + } + + // Move construct + bind_ptr(bind_ptr&& p) noexcept + : m_ptr(p.release()) + { + } + template + bind_ptr(bind_ptr&& p) noexcept + : m_ptr(p.release()) + { + } + + // Move assign + bind_ptr& operator=(bind_ptr&& p) noexcept + { + bind_ptr(std::move(p)).swap(*this); + return *this; + } + template + bind_ptr& operator=(bind_ptr&& p) noexcept + { + bind_ptr(std::move(p)).swap(*this); + return *this; + } + + //@{ + // Comparison + template + bool operator==(const bind_ptr&) const noexcept; + + template + bool operator==(U*) const noexcept; + + template + bool operator!=(const bind_ptr&) const noexcept; + + template + bool operator!=(U*) const noexcept; + + template + bool operator<(const bind_ptr&) const noexcept; + + template + bool operator<(U*) const noexcept; + + template + bool operator>(const bind_ptr&) const noexcept; + + template + bool operator>(U*) const noexcept; + + template + bool operator<=(const bind_ptr&) const noexcept; + + template + bool operator<=(U*) const noexcept; + + template + bool operator>=(const bind_ptr&) const noexcept; + + template + bool operator>=(U*) const noexcept; + //@} + + // Dereference + T& operator*() const noexcept + { + return *m_ptr; + } + T* operator->() const noexcept + { + return m_ptr; + } + + explicit operator bool() const noexcept + { + return m_ptr != 0; + } + + T* get() const noexcept + { + return m_ptr; + } + void reset() noexcept + { + bind_ptr().swap(*this); + } + void reset(T* p) noexcept + { + bind_ptr(p).swap(*this); + } + template + void reset(U* p) noexcept + { + bind_ptr(p).swap(*this); + } + + T* release() noexcept + { + T* const p = m_ptr; + m_ptr = nullptr; + return p; + } + + void swap(bind_ptr& p) noexcept + { + std::swap(m_ptr, p.m_ptr); + } + friend void swap(bind_ptr& a, bind_ptr& b) noexcept + { + a.swap(b); + } + +protected: + struct casting_move_tag { + }; + template + bind_ptr(bind_ptr* p, casting_move_tag) noexcept + : m_ptr(static_cast(p->release())) + { + } + +private: + T* m_ptr; + + void bind(T* p) noexcept + { + if (p) + p->bind_ptr(); + m_ptr = p; + } + void unbind() noexcept + { + if (m_ptr) + m_ptr->unbind_ptr(); + } + + template + friend class bind_ptr; +}; + + +template +inline std::basic_ostream& operator<<(std::basic_ostream& out, const bind_ptr& p) +{ + out << static_cast(p.get()); + return out; +} + + +//@{ +// Comparison +template +bool operator==(T*, const bind_ptr&) noexcept; +template +bool operator!=(T*, const bind_ptr&) noexcept; +template +bool operator<(T*, const bind_ptr&) noexcept; +template +bool operator>(T*, const bind_ptr&) noexcept; +template +bool operator<=(T*, const bind_ptr&) noexcept; +template +bool operator>=(T*, const bind_ptr&) noexcept; +//@} + + +/// Polymorphic convenience base class for reference counting objects. +/// +/// Together with bind_ptr, this class delivers simple instrusive +/// reference counting. +/// +/// \sa bind_ptr +class RefCountBase { +public: + RefCountBase() noexcept + : m_ref_count(0) + { + } + virtual ~RefCountBase() noexcept + { + REALM_ASSERT(m_ref_count == 0); + } + + RefCountBase(const RefCountBase&) = delete; + RefCountBase(RefCountBase&&) = delete; + + void operator=(const RefCountBase&) = delete; + void operator=(RefCountBase&&) = delete; + +protected: + void bind_ptr() const noexcept + { + ++m_ref_count; + } + void unbind_ptr() const noexcept + { + if (--m_ref_count == 0) + delete this; + } + +private: + mutable unsigned long m_ref_count; + + template + friend class bind_ptr; +}; + + +/// Same as RefCountBase, but this one makes the copying of, and the +/// destruction of counted references thread-safe. +/// +/// \sa RefCountBase +/// \sa bind_ptr +class AtomicRefCountBase { +public: + AtomicRefCountBase() noexcept + : m_ref_count(0) + { + } + virtual ~AtomicRefCountBase() noexcept + { + REALM_ASSERT(m_ref_count == 0); + } + + AtomicRefCountBase(const AtomicRefCountBase&) = delete; + AtomicRefCountBase(AtomicRefCountBase&&) = delete; + + void operator=(const AtomicRefCountBase&) = delete; + void operator=(AtomicRefCountBase&&) = delete; + +protected: + // FIXME: Operators ++ and -- as used below use + // std::memory_order_seq_cst. This can be optimized. + void bind_ptr() const noexcept + { + ++m_ref_count; + } + void unbind_ptr() const noexcept + { + if (--m_ref_count == 0) { + delete this; + } + } + +private: + mutable std::atomic m_ref_count; + + template + friend class bind_ptr; +}; + + +// Implementation: + +template +template +bool bind_ptr::operator==(const bind_ptr& p) const noexcept +{ + return m_ptr == p.m_ptr; +} + +template +template +bool bind_ptr::operator==(U* p) const noexcept +{ + return m_ptr == p; +} + +template +template +bool bind_ptr::operator!=(const bind_ptr& p) const noexcept +{ + return m_ptr != p.m_ptr; +} + +template +template +bool bind_ptr::operator!=(U* p) const noexcept +{ + return m_ptr != p; +} + +template +template +bool bind_ptr::operator<(const bind_ptr& p) const noexcept +{ + return m_ptr < p.m_ptr; +} + +template +template +bool bind_ptr::operator<(U* p) const noexcept +{ + return m_ptr < p; +} + +template +template +bool bind_ptr::operator>(const bind_ptr& p) const noexcept +{ + return m_ptr > p.m_ptr; +} + +template +template +bool bind_ptr::operator>(U* p) const noexcept +{ + return m_ptr > p; +} + +template +template +bool bind_ptr::operator<=(const bind_ptr& p) const noexcept +{ + return m_ptr <= p.m_ptr; +} + +template +template +bool bind_ptr::operator<=(U* p) const noexcept +{ + return m_ptr <= p; +} + +template +template +bool bind_ptr::operator>=(const bind_ptr& p) const noexcept +{ + return m_ptr >= p.m_ptr; +} + +template +template +bool bind_ptr::operator>=(U* p) const noexcept +{ + return m_ptr >= p; +} + +template +bool operator==(T* a, const bind_ptr& b) noexcept +{ + return b == a; +} + +template +bool operator!=(T* a, const bind_ptr& b) noexcept +{ + return b != a; +} + +template +bool operator<(T* a, const bind_ptr& b) noexcept +{ + return b > a; +} + +template +bool operator>(T* a, const bind_ptr& b) noexcept +{ + return b < a; +} + +template +bool operator<=(T* a, const bind_ptr& b) noexcept +{ + return b >= a; +} + +template +bool operator>=(T* a, const bind_ptr& b) noexcept +{ + return b <= a; +} + + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_BIND_PTR_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/buffer.hpp b/!main project/Pods/Realm/include/core/realm/util/buffer.hpp new file mode 100644 index 0000000..d65bdaf --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/buffer.hpp @@ -0,0 +1,302 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_BUFFER_HPP +#define REALM_UTIL_BUFFER_HPP + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace realm { +namespace util { + + +/// A simple buffer concept that owns a region of memory and knows its +/// size. +template +class Buffer { +public: + Buffer(Allocator& alloc = Allocator::get_default()) noexcept + : m_data(nullptr, STLDeleter{alloc}) + , m_size(0) + { + } + explicit Buffer(size_t initial_size, Allocator& alloc = Allocator::get_default()); + Buffer(Buffer&&) noexcept = default; + Buffer& operator=(Buffer&&) noexcept = default; + + T& operator[](size_t i) noexcept + { + return m_data[i]; + } + const T& operator[](size_t i) const noexcept + { + return m_data[i]; + } + + T* data() noexcept + { + return m_data.get(); + } + const T* data() const noexcept + { + return m_data.get(); + } + size_t size() const noexcept + { + return m_size; + } + + /// False iff the data() returns null. + explicit operator bool() const noexcept + { + return bool(m_data); + } + + /// Discards the original contents. + void set_size(size_t new_size); + + /// \param new_size Specifies the new buffer size. + /// \param copy_begin, copy_end Specifies a range of element + /// values to be retained. \a copy_end must be less than, or equal + /// to size(). + /// + /// \param copy_to Specifies where the retained range should be + /// copied to. `\a copy_to + \a copy_end - \a copy_begin` must be + /// less than, or equal to \a new_size. + void resize(size_t new_size, size_t copy_begin, size_t copy_end, size_t copy_to); + + void reserve(size_t used_size, size_t min_capacity); + + void reserve_extra(size_t used_size, size_t min_extra_capacity); + + /// Release the internal buffer to the caller. + REALM_NODISCARD std::unique_ptr> release() noexcept; + + friend void swap(Buffer& a, Buffer& b) noexcept + { + using std::swap; + swap(a.m_data, b.m_data); + swap(a.m_size, b.m_size); + } + + Allocator& get_allocator() const noexcept + { + return m_data.get_deleter().get_allocator(); + } + +private: + std::unique_ptr> m_data; + size_t m_size; +}; + + +/// A buffer that can be efficiently resized. It acheives this by +/// using an underlying buffer that may be larger than the logical +/// size, and is automatically expanded in progressively larger steps. +template +class AppendBuffer { +public: + AppendBuffer(Allocator& alloc = Allocator::get_default()) noexcept; + AppendBuffer(AppendBuffer&&) noexcept = default; + AppendBuffer& operator=(AppendBuffer&&) noexcept = default; + + /// Returns the current size of the buffer. + size_t size() const noexcept; + + /// Gives read and write access to the elements. + T* data() noexcept; + + /// Gives read access the elements. + const T* data() const noexcept; + + /// Append the specified elements. This increases the size of this + /// buffer by \a append_data_size. If the caller has previously requested + /// a minimum capacity that is greater than, or equal to the + /// resulting size, this function is guaranteed to not throw. + void append(const T* append_data, size_t append_data_size); + + /// If the specified size is less than the current size, then the + /// buffer contents is truncated accordingly. If the specified + /// size is greater than the current size, then the extra elements + /// will have undefined values. If the caller has previously + /// requested a minimum capacity that is greater than, or equal to + /// the specified size, this function is guaranteed to not throw. + void resize(size_t new_size); + + /// This operation does not change the size of the buffer as + /// returned by size(). If the specified capacity is less than the + /// current capacity, this operation has no effect. + void reserve(size_t min_capacity); + + /// Set the size to zero. The capacity remains unchanged. + void clear() noexcept; + + /// Release the underlying buffer and reset the size. Note: The returned + /// buffer may be larger than the amount of data appended to this buffer. + /// Callers should call `size()` prior to releasing the buffer to know the + /// usable/logical size. + REALM_NODISCARD Buffer release() noexcept; + +private: + util::Buffer m_buffer; + size_t m_size; +}; + + +// Implementation: + +class BufferSizeOverflow : public std::exception { +public: + const char* what() const noexcept override + { + return "Buffer size overflow"; + } +}; + +template +inline Buffer::Buffer(size_t initial_size, A& alloc) + : m_data(util::make_unique(alloc, initial_size)) // Throws + , m_size(initial_size) +{ +} + +template +inline void Buffer::set_size(size_t new_size) +{ + m_data = util::make_unique(get_allocator(), new_size); // Throws + m_size = new_size; +} + +template +inline void Buffer::resize(size_t new_size, size_t copy_begin, size_t copy_end, size_t copy_to) +{ + auto new_data = util::make_unique(get_allocator(), new_size); // Throws + realm::safe_copy_n(m_data.get() + copy_begin, copy_end - copy_begin, new_data.get() + copy_to); + m_data = std::move(new_data); + m_size = new_size; +} + +template +inline void Buffer::reserve(size_t used_size, size_t min_capacity) +{ + size_t current_capacity = m_size; + if (REALM_LIKELY(current_capacity >= min_capacity)) + return; + size_t new_capacity = current_capacity; + + // Use growth factor 1.5. + if (REALM_UNLIKELY(int_multiply_with_overflow_detect(new_capacity, 3))) + new_capacity = std::numeric_limits::max(); + new_capacity /= 2; + + if (REALM_UNLIKELY(new_capacity < min_capacity)) + new_capacity = min_capacity; + resize(new_capacity, 0, used_size, 0); // Throws +} + +template +inline void Buffer::reserve_extra(size_t used_size, size_t min_extra_capacity) +{ + size_t min_capacity = used_size; + if (REALM_UNLIKELY(int_add_with_overflow_detect(min_capacity, min_extra_capacity))) + throw BufferSizeOverflow(); + reserve(used_size, min_capacity); // Throws +} + +template +inline std::unique_ptr> Buffer::release() noexcept +{ + m_size = 0; + return std::move(m_data); +} + + +template +inline AppendBuffer::AppendBuffer(A& alloc) noexcept + : m_buffer(alloc) + , m_size(0) +{ +} + +template +inline size_t AppendBuffer::size() const noexcept +{ + return m_size; +} + +template +inline T* AppendBuffer::data() noexcept +{ + return m_buffer.data(); +} + +template +inline const T* AppendBuffer::data() const noexcept +{ + return m_buffer.data(); +} + +template +inline void AppendBuffer::append(const T* append_data, size_t append_data_size) +{ + m_buffer.reserve_extra(m_size, append_data_size); // Throws + realm::safe_copy_n(append_data, append_data_size, m_buffer.data() + m_size); + m_size += append_data_size; +} + +template +inline void AppendBuffer::reserve(size_t min_capacity) +{ + m_buffer.reserve(m_size, min_capacity); +} + +template +inline void AppendBuffer::resize(size_t new_size) +{ + reserve(new_size); + m_size = new_size; +} + +template +inline void AppendBuffer::clear() noexcept +{ + m_size = 0; +} + +template +inline Buffer AppendBuffer::release() noexcept +{ + m_size = 0; + return std::move(m_buffer); +} + + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_BUFFER_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/buffer_stream.hpp b/!main project/Pods/Realm/include/core/realm/util/buffer_stream.hpp new file mode 100644 index 0000000..be5064e --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/buffer_stream.hpp @@ -0,0 +1,151 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2016] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ + +#ifndef REALM_UTIL_BUFFER_STREAM_HPP +#define REALM_UTIL_BUFFER_STREAM_HPP + +#include +#include + +namespace realm { +namespace util { + + +template, class A = std::allocator > +class BasicResettableExpandableOutputStreambuf: public std::basic_stringbuf { +public: + using char_type = typename std::basic_stringbuf::char_type; + + /// Reset current writing position (std::basic_streambuf::pptr()) to the + /// beginning of the output buffer without reallocating buffer memory. + void reset() noexcept; + + //@{ + /// Get a pointer to the beginning of the output buffer + /// (std::basic_streambuf::pbase()). Note that this will change as the + /// buffer is reallocated. + char_type* data() noexcept; + const char_type* data() const noexcept; + //@} + + /// Get the number of bytes written to the output buffer since the creation + /// of the stream buffer, or since the last invocation of reset() + /// (std::basic_streambuf::pptr() - std::basic_streambuf::pbase()). + std::size_t size() const noexcept; +}; + + +template, class A = std::allocator > +class BasicResettableExpandableBufferOutputStream: public std::basic_ostream { +public: + using char_type = typename std::basic_ostream::char_type; + + BasicResettableExpandableBufferOutputStream(); + + /// Calls BasicResettableExpandableOutputStreambuf::reset(). + void reset() noexcept; + + //@{ + /// Calls BasicResettableExpandableOutputStreambuf::data(). + char_type* data() noexcept; + const char_type* data() const noexcept; + //@} + + /// Calls BasicResettableExpandableOutputStreambuf::size(). + std::size_t size() const noexcept; + +private: + BasicResettableExpandableOutputStreambuf m_streambuf; +}; + + +using ResettableExpandableBufferOutputStream = BasicResettableExpandableBufferOutputStream; + + + + +// Implementation + +template +inline void BasicResettableExpandableOutputStreambuf::reset() noexcept +{ + char_type* pbeg = this->pbase(); + char_type* pend = this->epptr(); + this->setp(pbeg, pend); +} + +template +inline typename BasicResettableExpandableOutputStreambuf::char_type* +BasicResettableExpandableOutputStreambuf::data() noexcept +{ + return this->pbase(); +} + +template +inline const typename BasicResettableExpandableOutputStreambuf::char_type* +BasicResettableExpandableOutputStreambuf::data() const noexcept +{ + return this->pbase(); +} + +template +inline std::size_t BasicResettableExpandableOutputStreambuf::size() const noexcept +{ + std::size_t size = std::size_t(this->pptr() - this->pbase()); + return size; +} + +template +inline BasicResettableExpandableBufferOutputStream:: +BasicResettableExpandableBufferOutputStream(): + std::basic_ostream(&m_streambuf) // Throws +{ +} + +template +inline void BasicResettableExpandableBufferOutputStream::reset() noexcept +{ + m_streambuf.reset(); +} + +template +inline typename BasicResettableExpandableBufferOutputStream::char_type* +BasicResettableExpandableBufferOutputStream::data() noexcept +{ + return m_streambuf.data(); +} + +template +inline const typename BasicResettableExpandableBufferOutputStream::char_type* +BasicResettableExpandableBufferOutputStream::data() const noexcept +{ + return m_streambuf.data(); +} + +template +inline std::size_t BasicResettableExpandableBufferOutputStream::size() const noexcept +{ + return m_streambuf.size(); +} + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_BUFFER_STREAM_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/call_with_tuple.hpp b/!main project/Pods/Realm/include/core/realm/util/call_with_tuple.hpp new file mode 100644 index 0000000..7d2eab0 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/call_with_tuple.hpp @@ -0,0 +1,66 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_CALL_WITH_TUPLE_HPP +#define REALM_UTIL_CALL_WITH_TUPLE_HPP + +#include +#include + +namespace realm { +namespace _impl { + +/// \cond doxygen_skip +/// Doxygen warns about a recursive class relation, but this is intentional. + +template +struct Indexes { +}; +template +struct GenIndexes : GenIndexes { +}; +template +struct GenIndexes<0, I...> { + typedef Indexes type; +}; + +/// \endcond + +template +auto call_with_tuple(F func, std::tuple args, Indexes) -> decltype(func(std::get(args)...)) +{ + static_cast(args); // Prevent GCC warning when tuple is empty + return func(std::get(args)...); +} + +} // namespace _impl + +namespace util { + +template +auto call_with_tuple(F func, std::tuple args) + -> decltype(_impl::call_with_tuple(std::move(func), std::move(args), + typename _impl::GenIndexes::type())) +{ + return _impl::call_with_tuple(std::move(func), std::move(args), typename _impl::GenIndexes::type()); +} + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_CALL_WITH_TUPLE_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/cf_ptr.hpp b/!main project/Pods/Realm/include/core/realm/util/cf_ptr.hpp new file mode 100644 index 0000000..a1ec431 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/cf_ptr.hpp @@ -0,0 +1,108 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_CF_PTR_HPP +#define REALM_UTIL_CF_PTR_HPP + +#include + +#if REALM_PLATFORM_APPLE + +#include + +namespace realm { +namespace util { + +template +class CFPtr { +public: + explicit CFPtr(Ref ref = nullptr) noexcept + : m_ref(ref) + { + } + + CFPtr(CFPtr&& rg) noexcept + : m_ref(rg.m_ref) + { + rg.m_ref = nullptr; + } + + ~CFPtr() noexcept + { + if (m_ref) + CFRelease(m_ref); + } + + CFPtr& operator=(CFPtr&& rg) noexcept + { + REALM_ASSERT(!m_ref || m_ref != rg.m_ref); + if (m_ref) + CFRelease(m_ref); + m_ref = rg.m_ref; + rg.m_ref = nullptr; + return *this; + } + + explicit operator bool() const noexcept + { + return bool(m_ref); + } + + Ref get() const noexcept + { + return m_ref; + } + + Ref release() noexcept + { + Ref ref = m_ref; + m_ref = nullptr; + return ref; + } + + void reset(Ref ref = nullptr) noexcept + { + REALM_ASSERT(!m_ref || m_ref != ref); + if (m_ref) + CFRelease(m_ref); + m_ref = ref; + } + +private: + Ref m_ref; +}; + +template +CFPtr adoptCF(Ref ptr) +{ + return CFPtr(ptr); +} + +template +CFPtr retainCF(Ref ptr) +{ + CFRetain(ptr); + return CFPtr(ptr); +} +} +} + + +#endif // REALM_PLATFORM_APPLE + +#endif // REALM_UTIL_CF_PTR_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/cf_str.hpp b/!main project/Pods/Realm/include/core/realm/util/cf_str.hpp new file mode 100644 index 0000000..a1ae1ec --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/cf_str.hpp @@ -0,0 +1,54 @@ +/************************************************************************* + * + * Copyright 2020 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_CF_STR_HPP +#define REALM_UTIL_CF_STR_HPP + +#include + +#if REALM_PLATFORM_APPLE + +#include + +namespace realm { +namespace util { + +inline std::string cfstring_to_std_string(CFStringRef cf_str) +{ + std::string ret; + // If the CFString happens to store UTF-8 we can read its data directly + if (const char *utf8 = CFStringGetCStringPtr(cf_str, kCFStringEncodingUTF8)) { + ret = utf8; + return ret; + } + + // Otherwise we need to convert the CFString to UTF-8 + CFIndex length = CFStringGetLength(cf_str); + CFIndex max_size = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1; + ret.resize(max_size); + CFStringGetCString(cf_str, &ret[0], max_size, kCFStringEncodingUTF8); + ret.resize(strlen(ret.c_str())); + return ret; +} + +} +} + +#endif // REALM_PLATFORM_APPLE + +#endif // REALM_UTIL_CF_STR_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/circular_buffer.hpp b/!main project/Pods/Realm/include/core/realm/util/circular_buffer.hpp new file mode 100644 index 0000000..4ae07cc --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/circular_buffer.hpp @@ -0,0 +1,1011 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2016] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ + +#ifndef REALM_UTIL_CIRCULAR_BUFFER_HPP +#define REALM_UTIL_CIRCULAR_BUFFER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace realm { +namespace util { + +/// \brief A container backed by a "circular buffer". +/// +/// This container is similar to std::deque in that it offers efficient element +/// insertion and removal at both ends. Insertion at either end occurs in +/// amortized constant time. Removal at either end occurs in constant time. +/// +/// As opposed to std::deque, this container allows for reservation of buffer +/// space, such that value insertion can be guaranteed to not reallocate buffer +/// memory, and to not throw. +/// +/// More specifically, a single insert operation, that inserts zero or more +/// values at either end, is guaranteed to not reallocate buffer memory if the +/// prior capacity (capacity()) is greater than, or equal to the prior size +/// (size()) plus the number of inserted values. Further more, such an operation +/// is guaranteed to not throw if the capacity is sufficient, and the relevant +/// constructor of the value type does not throw, and, in the case of inserting +/// a range of values specified as a pair of iterators, if no exception is +/// thrown while operating on those iterators. +/// +/// This container uses a single contiguous chunk of memory as backing storage, +/// but it allows for the logical sequence of values to wrap around from the +/// end, to the beginning of that chunk. Because the logical sequence of values +/// can have a storage-wise discontinuity of this kind, this container does not +/// meet the requirements of `ContiguousContainer` (as defined by C++17). +/// +/// When the first element is removed (pop_front()), iterators pointing to the +/// removed element will be invalidated. All other iterators, including "end +/// iterators" (end()), will remain valid. +/// +/// When the last element is removed (pop_back()), iterators pointing to the +/// removed element will become "end iterators" (end()), and "end iterators" +/// will be invalidated. All other iterators will remain valid. +/// +/// When an element is inserted at the front (push_front()), and the prior +/// capacity (capacity()) is strictly greater than the prior size (size()), all +/// iterators remain valid. +/// +/// When an element is inserted at the back (push_back()), and the prior +/// capacity (capacity()) is strictly greater than the prior size (size()), "end +/// iterators" (end()) become iterators to the inserted element, and all other +/// iterators remain valid. +/// +/// Operations pop_front(), pop_back(), and clear(), are guaranteed to leave the +/// capacity unchanged. +/// +/// Iterators are of the "random access" kind (std::random_access_iterator_tag). +template class CircularBuffer { +private: + template class Iter; + + template using RequireIter = + std::enable_if_t::iterator_category, + std::input_iterator_tag>::value>; + +public: + static_assert(std::is_nothrow_destructible::value, ""); + + using value_type = T; + using size_type = std::size_t; + using reference = value_type&; + using const_reference = const value_type&; + using iterator = Iter; + using const_iterator = Iter; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + CircularBuffer() noexcept; + CircularBuffer(const CircularBuffer&); + CircularBuffer(CircularBuffer&&) noexcept; + CircularBuffer(std::initializer_list); + explicit CircularBuffer(size_type size); + CircularBuffer(size_type size, const T& value); + template> CircularBuffer(I begin, I end); + ~CircularBuffer() noexcept; + + CircularBuffer& operator=(const CircularBuffer&); + CircularBuffer& operator=(CircularBuffer&&) noexcept; + CircularBuffer& operator=(std::initializer_list); + + void assign(std::initializer_list); + void assign(size_type size, const T& value); + template> void assign(I begin, I end); + + // Element access + + reference at(size_type); + const_reference at(size_type) const; + + reference operator[](size_type) noexcept; + const_reference operator[](size_type) const noexcept; + + reference front() noexcept; + const_reference front() const noexcept; + + reference back() noexcept; + const_reference back() const noexcept; + + // Iterators + + iterator begin() noexcept; + const_iterator begin() const noexcept; + const_iterator cbegin() const noexcept; + + iterator end() noexcept; + const_iterator end() const noexcept; + const_iterator cend() const noexcept; + + reverse_iterator rbegin() noexcept; + const_reverse_iterator rbegin() const noexcept; + const_reverse_iterator crbegin() const noexcept; + + reverse_iterator rend() noexcept; + const_reverse_iterator rend() const noexcept; + const_reverse_iterator crend() const noexcept; + + // Size / capacity + + bool empty() const noexcept; + size_type size() const noexcept; + size_type capacity() const noexcept; + + void reserve(size_type capacity); + void shrink_to_fit(); + + // Modifiers + + reference push_front(const T&); + reference push_back(const T&); + + reference push_front(T&&); + reference push_back(T&&); + + template reference emplace_front(Args&&...); + template reference emplace_back(Args&&...); + + void pop_front() noexcept; + void pop_back() noexcept; + + // FIXME: emplace(const_iterator i, ...) -> j = unwrap(i.m_index); if (j >= (m_size+1)/2) insert_near_back(j, ...); else insert_near_front(j, ...); + + void clear() noexcept; + void resize(size_type size); + void resize(size_type size, const T& value); + + void swap(CircularBuffer&) noexcept; + + // Comparison + + template bool operator==(const CircularBuffer&) const + noexcept(noexcept(std::declval() == std::declval())); + template bool operator!=(const CircularBuffer&) const + noexcept(noexcept(std::declval() == std::declval())); + template bool operator<(const CircularBuffer&) const + noexcept(noexcept(std::declval() < std::declval())); + template bool operator>(const CircularBuffer&) const + noexcept(noexcept(std::declval() < std::declval())); + template bool operator<=(const CircularBuffer&) const + noexcept(noexcept(std::declval() < std::declval())); + template bool operator>=(const CircularBuffer&) const + noexcept(noexcept(std::declval() < std::declval())); + +private: + using Strut = typename std::aligned_storage::type; + std::unique_ptr m_memory_owner; + + // Index of first element in allocated memory chunk. + size_type m_begin = 0; + + // The number of elements within the allocated memory chunk, that are + // currently in use, i.e., the logical size of the circular buffer. + size_type m_size = 0; + + // Number of elements of type T that will fit into the currently allocated + // memory chunk. + // + // Except when m_size is zero, m_allocated_size must be strictly greater + // than m_size. This is required to ensure that the iterators returned by + // begin() and end() are equal only when the buffer is empty. + // + // INVARIANT: m_size == 0 ? m_allocated_size == 0 : m_size < m_allocated_size + size_type m_allocated_size = 0; + + T* get_memory_ptr() noexcept; + + // Assumption: index < m_allocated_size + size_type circular_inc(size_type index) noexcept; + size_type circular_dec(size_type index) noexcept; + size_type wrap(size_type index) noexcept; + size_type unwrap(size_type index) noexcept; + + template void copy(I begin, I end); + template void copy(I begin, I end, std::input_iterator_tag); + template void copy(I begin, I end, std::forward_iterator_tag); + + void destroy(size_type offset = 0) noexcept; + + void realloc(size_type new_allocated_size); +}; + + +template void swap(CircularBuffer&, CircularBuffer&) noexcept; + + + + +// Implementation + +template template class CircularBuffer::Iter : + public std::iterator { +public: + using difference_type = std::ptrdiff_t; + + Iter() noexcept + { + } + + template Iter(const Iter& i) noexcept + { + operator=(i); + } + + template Iter& operator=(const Iter& i) noexcept + { + // Check constness convertability + static_assert(std::is_convertible::value, ""); + m_buffer = i.m_buffer; + m_index = i.m_index; + return *this; + } + + U& operator*() const noexcept + { + T* memory = m_buffer->get_memory_ptr(); + return memory[m_index]; + } + + U* operator->() const noexcept + { + return &operator*(); + } + + U& operator[](difference_type i) const noexcept + { + Iter j = *this; + j += i; + return *j; + } + + Iter& operator++() noexcept + { + m_index = m_buffer->circular_inc(m_index); + return *this; + } + + Iter& operator--() noexcept + { + m_index = m_buffer->circular_dec(m_index); + return *this; + } + + Iter operator++(int) noexcept + { + size_type i = m_index; + operator++(); + return Iter{m_buffer, i}; + } + + Iter operator--(int) noexcept + { + size_type i = m_index; + operator--(); + return Iter{m_buffer, i}; + } + + Iter& operator+=(difference_type value) noexcept + { + // Care is needed to avoid unspecified arithmetic behaviour here. We can + // assume, however, that if `i` is the unwrapped (logical) index of the + // element pointed to by this iterator, then the mathematical value of i + // + value is representable in `size_type` (otherwise the resulting + // iterator would escape the boundaries of the buffer). We can therefore + // safely perform the addition in the unsigned domain of unwrapped + // element indexes, and rely on two's complement representation for + // negative values. + size_type i = m_buffer->unwrap(m_index); + i += size_type(value); + m_index = m_buffer->wrap(i); + return *this; + } + + Iter& operator-=(difference_type value) noexcept + { + // Care is needed to avoid unspecified arithmetic behaviour here. See + // the comment in the implementation of operator+=(). + size_type i = m_buffer->unwrap(m_index); + i -= size_type(value); + m_index = m_buffer->wrap(i); + return *this; + } + + Iter operator+(difference_type value) const noexcept + { + Iter i = *this; + i += value; + return i; + } + + Iter operator-(difference_type value) const noexcept + { + Iter i = *this; + i -= value; + return i; + } + + friend Iter operator+(difference_type value, const Iter& i) noexcept + { + Iter j = i; + j += value; + return j; + } + + template difference_type operator-(const Iter& i) const noexcept + { + REALM_ASSERT(m_buffer == i.m_buffer); + size_type i_1 = m_buffer->unwrap(m_index); + size_type i_2 = i.m_buffer->unwrap(i.m_index); + return from_twos_compl(size_type(i_1 - i_2)); + } + + template bool operator==(const Iter& i) const noexcept + { + REALM_ASSERT(m_buffer == i.m_buffer); + return (m_index == i.m_index); + } + + template bool operator!=(const Iter& i) const noexcept + { + return !operator==(i); + } + + template bool operator<(const Iter& i) const noexcept + { + REALM_ASSERT(m_buffer == i.m_buffer); + size_type i_1 = m_buffer->unwrap(m_index); + size_type i_2 = i.m_buffer->unwrap(i.m_index); + return (i_1 < i_2); + } + + template bool operator>(const Iter& i) const noexcept + { + return (i < *this); + } + + template bool operator<=(const Iter& i) const noexcept + { + return !operator>(i); + } + + template bool operator>=(const Iter& i) const noexcept + { + return !operator<(i); + } + +private: + CircularBuffer* m_buffer = nullptr; + + // Index of iterator position from beginning of allocated memory, i.e., from + // beginning of m_buffer->get_memory_ptr(). + size_type m_index = 0; + + Iter(CircularBuffer* buffer, size_type index) noexcept : + m_buffer{buffer}, + m_index{index} + { + } + + friend class CircularBuffer; + template friend class Iter; +}; + +template inline CircularBuffer::CircularBuffer() noexcept +{ +} + +template inline CircularBuffer::CircularBuffer(const CircularBuffer& buffer) +{ + try { + copy(buffer.begin(), buffer.end()); // Throws + } + catch (...) { + // If an exception was thrown above, the destructor will not be called, + // so we need to manually destroy the copies that were already made. + destroy(); + throw; + } +} + +template inline CircularBuffer::CircularBuffer(CircularBuffer&& buffer) noexcept : + m_memory_owner{std::move(buffer.m_memory_owner)}, + m_begin{buffer.m_begin}, + m_size{buffer.m_size}, + m_allocated_size{buffer.m_allocated_size} +{ + buffer.m_begin = 0; + buffer.m_size = 0; + buffer.m_allocated_size = 0; +} + +template inline CircularBuffer::CircularBuffer(std::initializer_list list) +{ + try { + copy(list.begin(), list.end()); // Throws + } + catch (...) { + // If an exception was thrown above, the destructor will not be called, + // so we need to manually destroy the copies that were already made. + destroy(); + throw; + } +} + +template inline CircularBuffer::CircularBuffer(size_type count) +{ + try { + resize(count); // Throws + } + catch (...) { + // If an exception was thrown above, the destructor will not be called, + // so we need to manually destroy the instances that were already + // created. + destroy(); + throw; + } +} + +template inline CircularBuffer::CircularBuffer(size_type count, const T& value) +{ + try { + resize(count, value); // Throws + } + catch (...) { + // If an exception was thrown above, the destructor will not be called, + // so we need to manually destroy the copies that were already made. + destroy(); + throw; + } +} + +template template inline CircularBuffer::CircularBuffer(I begin, I end) +{ + try { + copy(begin, end); // Throws + } + catch (...) { + // If an exception was thrown above, the destructor will not be called, + // so we need to manually destroy the copies that were already made. + destroy(); + throw; + } +} + +template inline CircularBuffer::~CircularBuffer() noexcept +{ + destroy(); +} + +template +inline auto CircularBuffer::operator=(const CircularBuffer& buffer) -> CircularBuffer& +{ + clear(); + copy(buffer.begin(), buffer.end()); // Throws + return *this; +} + +template +inline auto CircularBuffer::operator=(CircularBuffer&& buffer) noexcept -> CircularBuffer& +{ + destroy(); + m_memory_owner = std::move(buffer.m_memory_owner); + m_begin = buffer.m_begin; + m_size = buffer.m_size; + m_allocated_size = buffer.m_allocated_size; + buffer.m_begin = 0; + buffer.m_size = 0; + buffer.m_allocated_size = 0; + return *this; +} + +template +inline auto CircularBuffer::operator=(std::initializer_list list) -> CircularBuffer& +{ + clear(); + copy(list.begin(), list.end()); // Throws + return *this; +} + +template inline void CircularBuffer::assign(std::initializer_list list) +{ + clear(); + copy(list.begin(), list.end()); // Throws +} + +template inline void CircularBuffer::assign(size_type count, const T& value) +{ + clear(); + resize(count, value); // Throws +} + +template template inline void CircularBuffer::assign(I begin, I end) +{ + clear(); + copy(begin, end); // Throws +} + +template inline auto CircularBuffer::at(size_type i) -> reference +{ + if (REALM_LIKELY(i < m_size)) + return operator[](i); + throw util::out_of_range{"Index"}; +} + +template inline auto CircularBuffer::at(size_type i) const -> const_reference +{ + return const_cast(this)->at(i); // Throws +} + +template +inline auto CircularBuffer::operator[](size_type i) noexcept -> reference +{ + REALM_ASSERT(i < m_size); + T* memory = get_memory_ptr(); + size_type j = wrap(i); + return memory[j]; +} + +template +inline auto CircularBuffer::operator[](size_type i) const noexcept -> const_reference +{ + return const_cast(this)->operator[](i); +} + +template inline auto CircularBuffer::front() noexcept -> reference +{ + return operator[](0); +} + +template inline auto CircularBuffer::front() const noexcept -> const_reference +{ + return operator[](0); +} + +template inline auto CircularBuffer::back() noexcept -> reference +{ + return operator[](m_size-1); +} + +template +inline auto CircularBuffer::back() const noexcept -> const_reference +{ + return operator[](m_size-1); +} + +template inline auto CircularBuffer::begin() noexcept -> iterator +{ + return iterator{this, m_begin}; +} + +template inline auto CircularBuffer::begin() const noexcept -> const_iterator +{ + return const_cast(this)->begin(); +} + +template inline auto CircularBuffer::cbegin() const noexcept -> const_iterator +{ + return begin(); +} + +template inline auto CircularBuffer::end() noexcept -> iterator +{ + size_type i = wrap(m_size); + return iterator{this, i}; +} + +template inline auto CircularBuffer::end() const noexcept -> const_iterator +{ + return const_cast(this)->end(); +} + +template inline auto CircularBuffer::cend() const noexcept -> const_iterator +{ + return end(); +} + +template inline auto CircularBuffer::rbegin() noexcept -> reverse_iterator +{ + return std::reverse_iterator(end()); +} + +template inline auto CircularBuffer::rbegin() const noexcept -> const_reverse_iterator +{ + return const_cast(this)->rbegin(); +} + +template inline auto CircularBuffer::crbegin() const noexcept -> const_reverse_iterator +{ + return rbegin(); +} + +template inline auto CircularBuffer::rend() noexcept -> reverse_iterator +{ + return std::reverse_iterator(begin()); +} + +template inline auto CircularBuffer::rend() const noexcept -> const_reverse_iterator +{ + return const_cast(this)->rend(); +} + +template inline auto CircularBuffer::crend() const noexcept -> const_reverse_iterator +{ + return rend(); +} + +template inline bool CircularBuffer::empty() const noexcept +{ + return (m_size == 0); +} + +template inline auto CircularBuffer::size() const noexcept -> size_type +{ + return m_size; +} + +template void CircularBuffer::reserve(size_type capacity) +{ + if (capacity == 0) + return; + + // An extra element of capacity is needed such that the end iterator can + // always point one beyond the last element without becomeing equal to an + // iterator to the first element. + size_type min_allocated_size = capacity; + if (REALM_UNLIKELY(int_add_with_overflow_detect(min_allocated_size, 1))) + throw util::overflow_error{"Capacity"}; + + if (min_allocated_size <= m_allocated_size) + return; + + size_type new_allocated_size = m_allocated_size; + if (REALM_UNLIKELY(int_multiply_with_overflow_detect(new_allocated_size, 2))) + new_allocated_size = std::numeric_limits::max(); + if (new_allocated_size < min_allocated_size) + new_allocated_size = min_allocated_size; + realloc(new_allocated_size); // Throws +} + +template inline void CircularBuffer::shrink_to_fit() +{ + if (m_size > 0) { + // An extra element of capacity is needed such that the end iterator can + // always point one beyond the last element without becomeing equal to + // an iterator to the first element. + size_type new_allocated_size = m_size + 1; + if (new_allocated_size < m_allocated_size) + realloc(new_allocated_size); // Throws + } + else { + m_memory_owner.reset(); + m_begin = 0; + m_allocated_size = 0; + } +} + +template inline auto CircularBuffer::capacity() const noexcept -> size_type +{ + return (m_allocated_size > 0 ? m_allocated_size - 1 : 0); +} + +template inline auto CircularBuffer::push_front(const T& value) -> reference +{ + return emplace_front(value); // Throws +} + +template inline auto CircularBuffer::push_back(const T& value) -> reference +{ + return emplace_back(value); // Throws +} + +template inline auto CircularBuffer::push_front(T&& value) -> reference +{ + return emplace_front(value); // Throws +} + +template inline auto CircularBuffer::push_back(T&& value) -> reference +{ + return emplace_back(value); // Throws +} + +template +template inline auto CircularBuffer::emplace_front(Args&&... args) -> reference +{ + size_type new_size = m_size + 1; + reserve(new_size); // Throws + REALM_ASSERT(m_allocated_size > 0); + T* memory = get_memory_ptr(); + size_type i = circular_dec(m_begin); + new (&memory[i]) T(std::forward(args)...); // Throws + m_begin = i; + m_size = new_size; + return memory[i]; +} + +template +template inline auto CircularBuffer::emplace_back(Args&&... args) -> reference +{ + size_type new_size = m_size + 1; + reserve(new_size); // Throws + REALM_ASSERT(m_allocated_size > 0); + T* memory = get_memory_ptr(); + size_type i = wrap(m_size); + new (&memory[i]) T(std::forward(args)...); // Throws + m_size = new_size; + return memory[i]; +} + +template inline void CircularBuffer::pop_front() noexcept +{ + REALM_ASSERT(m_size > 0); + T* memory = get_memory_ptr(); + memory[m_begin].~T(); + m_begin = circular_inc(m_begin); + --m_size; +} + +template inline void CircularBuffer::pop_back() noexcept +{ + REALM_ASSERT(m_size > 0); + T* memory = get_memory_ptr(); + size_type new_size = m_size - 1; + size_type i = wrap(new_size); + memory[i].~T(); + m_size = new_size; +} + +template inline void CircularBuffer::clear() noexcept +{ + destroy(); + m_begin = 0; + m_size = 0; +} + +template inline void CircularBuffer::resize(size_type size) +{ + if (size <= m_size) { + size_type offset = size; + destroy(offset); + m_size = size; + return; + } + reserve(size); // Throws + T* memory = get_memory_ptr(); + size_type i = wrap(m_size); + do { + new (&memory[i]) T(); // Throws + i = circular_inc(i); + ++m_size; + } + while (m_size < size); +} + +template inline void CircularBuffer::resize(size_type size, const T& value) +{ + if (size <= m_size) { + size_type offset = size; + destroy(offset); + m_size = size; + return; + } + reserve(size); // Throws + T* memory = get_memory_ptr(); + size_type i = wrap(m_size); + do { + new (&memory[i]) T(value); // Throws + i = circular_inc(i); + ++m_size; + } + while (m_size < size); +} + +template inline void CircularBuffer::swap(CircularBuffer& buffer) noexcept +{ + std::swap(m_memory_owner, buffer.m_memory_owner); + std::swap(m_begin, buffer.m_begin); + std::swap(m_size, buffer.m_size); + std::swap(m_allocated_size, buffer.m_allocated_size); +} + +template template +inline bool CircularBuffer::operator==(const CircularBuffer& buffer) const + noexcept(noexcept(std::declval() == std::declval())) +{ + return std::equal(begin(), end(), buffer.begin(), buffer.end()); // Throws +} + +template template +inline bool CircularBuffer::operator!=(const CircularBuffer& buffer) const + noexcept(noexcept(std::declval() == std::declval())) +{ + return !operator==(buffer); // Throws +} + +template template +inline bool CircularBuffer::operator<(const CircularBuffer& buffer) const + noexcept(noexcept(std::declval() < std::declval())) +{ + return std::lexicographical_compare(begin(), end(), buffer.begin(), buffer.end()); // Throws +} + +template template +inline bool CircularBuffer::operator>(const CircularBuffer& buffer) const + noexcept(noexcept(std::declval() < std::declval())) +{ + return (buffer < *this); // Throws +} + +template template +inline bool CircularBuffer::operator<=(const CircularBuffer& buffer) const + noexcept(noexcept(std::declval() < std::declval())) +{ + return !operator>(buffer); // Throws +} + +template template +inline bool CircularBuffer::operator>=(const CircularBuffer& buffer) const + noexcept(noexcept(std::declval() < std::declval())) +{ + return !operator<(buffer); // Throws +} + +template inline T* CircularBuffer::get_memory_ptr() noexcept +{ + return static_cast(static_cast(m_memory_owner.get())); +} + +template +inline auto CircularBuffer::circular_inc(size_type index) noexcept -> size_type +{ + size_type index_2 = index + 1; + if (REALM_LIKELY(index_2 < m_allocated_size)) + return index_2; + return 0; +} + +template +inline auto CircularBuffer::circular_dec(size_type index) noexcept -> size_type +{ + if (REALM_LIKELY(index > 0)) + return index - 1; + return m_allocated_size - 1; +} + +template +inline auto CircularBuffer::wrap(size_type index) noexcept -> size_type +{ + size_type top = m_allocated_size - m_begin; + if (index < top) + return m_begin + index; + return index - top; +} + +template +inline auto CircularBuffer::unwrap(size_type index) noexcept -> size_type +{ + if (index >= m_begin) + return index - m_begin; + return m_allocated_size - (m_begin - index); +} + +template template inline void CircularBuffer::copy(I begin, I end) +{ + using iterator_category = typename std::iterator_traits::iterator_category; + copy(begin, end, iterator_category{}); // Throws +} + +template template +inline void CircularBuffer::copy(I begin, I end, std::input_iterator_tag) +{ + for (I j = begin; j != end; ++j) + push_back(*j); // Throws +} + +template template +inline void CircularBuffer::copy(I begin, I end, std::forward_iterator_tag) +{ + REALM_ASSERT(m_begin == 0); + REALM_ASSERT(m_size == 0); + size_type size = std::distance(begin, end); + reserve(size); // Throws + T* memory = get_memory_ptr(); + for (I i = begin; i != end; ++i) { + new (&memory[m_size]) T(*i); // Throws + ++m_size; + } +} + +template inline void CircularBuffer::destroy(size_type offset) noexcept +{ + T* memory = get_memory_ptr(); + size_type j = m_begin; + for (size_type i = offset; i < m_size; ++i) { + memory[j].~T(); + j = circular_inc(j); + } +} + +template void CircularBuffer::realloc(size_type new_allocated_size) +{ + REALM_ASSERT(new_allocated_size > 1); + REALM_ASSERT(new_allocated_size > m_size); + + // Allocate new buffer + std::unique_ptr new_memory_owner = + std::make_unique(new_allocated_size); // Throws + T* memory = get_memory_ptr(); + + // Move or copy elements to new buffer + { + T* new_memory = static_cast(static_cast(new_memory_owner.get())); + size_type i = 0; + try { + size_type j = m_begin; + while (i < m_size) { + new (&new_memory[i]) T(std::move_if_noexcept(memory[j])); // Throws + ++i; + j = circular_inc(j); + } + } + catch (...) { + // If an exception was thrown above, we know that elements were + // copied, and not moved (assuming that T is copy constructable if + // it is not nothrow move constructible), so we need to back out by + // destroying the copies that were already made. + for (size_type j = 0; j < i; ++j) + new_memory[j].~T(); + throw; + } + } + + // Destroy old elements + { + size_type j = m_begin; + for (size_type i = 0; i < m_size; ++i) { + memory[j].~T(); + j = circular_inc(j); + } + } + + m_memory_owner = std::move(new_memory_owner); + m_begin = 0; + m_allocated_size = new_allocated_size; +} + +template inline void swap(CircularBuffer& a, CircularBuffer& b) noexcept +{ + a.swap(b); +} + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_CIRCULAR_BUFFER_HPP + diff --git a/!main project/Pods/Realm/include/core/realm/util/config.h b/!main project/Pods/Realm/include/core/realm/util/config.h new file mode 100644 index 0000000..0ebbbe2 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/config.h @@ -0,0 +1,23 @@ +// Version information +#define REALM_VERSION "" + +// Specific headers +#define HAVE_MALLOC_H 0 + +// Realm-specific configuration +#define REALM_MAX_BPNODE_SIZE 1000 +#define REALM_ENABLE_ASSERTIONS 1 +#define REALM_ENABLE_ALLOC_SET_ZERO 0 +#define REALM_ENABLE_ENCRYPTION 1 +#define REALM_ENABLE_MEMDEBUG 0 +#define REALM_VALGRIND 0 +#define REALM_METRICS 1 +#define REALM_ASAN 0 +#define REALM_TSAN 0 + +#define REALM_INSTALL_PREFIX "/usr/local" +#define REALM_INSTALL_INCLUDEDIR "include" +#define REALM_INSTALL_BINDIR "bin" +#define REALM_INSTALL_LIBDIR "lib" +#define REALM_INSTALL_LIBEXECDIR "libexec" +#define REALM_INSTALL_EXEC_PREFIX "/usr/local" diff --git a/!main project/Pods/Realm/include/core/realm/util/demangle.hpp b/!main project/Pods/Realm/include/core/realm/util/demangle.hpp new file mode 100644 index 0000000..7af4fb8 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/demangle.hpp @@ -0,0 +1,54 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2015] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ +#ifndef REALM_UTIL_DEMANGLE_HPP +#define REALM_UTIL_DEMANGLE_HPP + +#include +#include + +namespace realm { +namespace util { + + +/// Demangle the specified C++ ABI identifier. +/// +/// See for example +/// http://gcc.gnu.org/onlinedocs/libstdc++/latest-doxygen/namespaceabi.html +std::string demangle(const std::string&); + + +/// Get the demangled name of the specified type. +template inline std::string get_type_name() +{ + return demangle(typeid(T).name()); +} + + +/// Get the demangled name of the type of the specified argument. +template inline std::string get_type_name(const T& v) +{ + return demangle(typeid(v).name()); +} + + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_DEMANGLE_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/duplicating_logger.hpp b/!main project/Pods/Realm/include/core/realm/util/duplicating_logger.hpp new file mode 100644 index 0000000..774df3c --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/duplicating_logger.hpp @@ -0,0 +1,63 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2016] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ + +#ifndef REALM_UTIL_DUPLICATING_LOGGER_HPP +#define REALM_UTIL_DUPLICATING_LOGGER_HPP + +#include + + +namespace realm { +namespace util { + +/// The log level threshold of a logger of this type will be decided by the +/// associated base logger. Therefore, the log level threshold specified via the +/// auxiliary logger will be ignored. +/// +/// Loggers of this type are thread-safe if the base logger and the auxiliary +/// loggers are both thread-safe. +class DuplicatingLogger : public Logger { +public: + explicit DuplicatingLogger(Logger& base_logger, Logger& aux_logger) noexcept; + +protected: + void do_log(Logger::Level, std::string message) override; + +private: + Logger& m_base_logger; + Logger& m_aux_logger; +}; + + + + +// Implementation + +inline DuplicatingLogger::DuplicatingLogger(Logger& base_logger, Logger& aux_logger) noexcept : + Logger{base_logger.level_threshold}, + m_base_logger{base_logger}, m_aux_logger{aux_logger} +{ +} + + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_DUPLICATING_LOGGER_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/encrypted_file_mapping.hpp b/!main project/Pods/Realm/include/core/realm/util/encrypted_file_mapping.hpp new file mode 100644 index 0000000..6f5fd3c --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/encrypted_file_mapping.hpp @@ -0,0 +1,181 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_ENCRYPTED_FILE_MAPPING_HPP +#define REALM_UTIL_ENCRYPTED_FILE_MAPPING_HPP + +#include +#include +#include + +#if REALM_ENABLE_ENCRYPTION + +typedef size_t (*Header_to_size)(const char* addr); + +#include + +namespace realm { +namespace util { + +struct SharedFileInfo; +class EncryptedFileMapping; + +class EncryptedFileMapping { +public: + // Adds the newly-created object to file.mappings iff it's successfully constructed + EncryptedFileMapping(SharedFileInfo& file, size_t file_offset, void* addr, size_t size, File::AccessMode access); + ~EncryptedFileMapping(); + + // Default implementations of copy/assign can trigger multiple destructions + EncryptedFileMapping(const EncryptedFileMapping&) = delete; + EncryptedFileMapping& operator=(const EncryptedFileMapping&) = delete; + + // Write all dirty pages to disk and mark them read-only + // Does not call fsync + void flush() noexcept; + + // Sync this file to disk + void sync() noexcept; + + // Make sure that memory in the specified range is synchronized with any + // changes made globally visible through call to write_barrier + void read_barrier(const void* addr, size_t size, Header_to_size header_to_size); + + // Ensures that any changes made to memory in the specified range + // becomes visible to any later calls to read_barrier() + void write_barrier(const void* addr, size_t size) noexcept; + + // Set this mapping to a new address and size + // Flushes any remaining dirty pages from the old mapping + void set(void* new_addr, size_t new_size, size_t new_file_offset); + + size_t collect_decryption_count() + { + return m_num_decrypted; + } + // reclaim any untouched pages - this is thread safe with respect to + // concurrent access/touching of pages - but must be called with the mutex locked. + void reclaim_untouched(size_t& progress_ptr, size_t& accumulated_savings) noexcept; + + bool contains_page(size_t page_in_file) const; + size_t get_local_index_of_address(const void* addr, size_t offset = 0) const; + + size_t get_end_index() + { + return m_first_page + m_page_state.size(); + } + size_t get_start_index() + { + return m_first_page; + } + +private: + SharedFileInfo& m_file; + + size_t m_page_shift; + size_t m_blocks_per_page; + + void* m_addr = nullptr; + + size_t m_first_page; + size_t m_num_decrypted; // 1 for every page decrypted + + enum PageState { + Touched = 1, // a ref->ptr translation has taken place + UpToDate = 2, // the page is fully up to date + PartiallyUpToDate = 4, // the page is valid for old translations, but requires re-decryption for new + Dirty = 8 // the page has been modified with respect to what's on file. + }; + std::vector m_page_state; + // little helpers: + inline void clear(PageState& ps, int p) + { + ps = PageState(ps & ~p); + } + inline bool is_not(PageState& ps, int p) + { + return (ps & p) == 0; + } + inline bool is(PageState& ps, int p) + { + return (ps & p) != 0; + } + inline void set(PageState& ps, int p) + { + ps = PageState(ps | p); + } + // 1K pages form a chunk - this array allows us to skip entire chunks during scanning + std::vector m_chunk_dont_scan; + static constexpr int page_to_chunk_shift = 10; + static constexpr size_t page_to_chunk_factor = size_t(1) << page_to_chunk_shift; + + File::AccessMode m_access; + +#ifdef REALM_DEBUG + std::unique_ptr m_validate_buffer; +#endif + + char* page_addr(size_t local_page_ndx) const noexcept; + + void mark_outdated(size_t local_page_ndx) noexcept; + bool copy_up_to_date_page(size_t local_page_ndx) noexcept; + void refresh_page(size_t local_page_ndx); + void write_page(size_t local_page_ndx) noexcept; + void write_and_update_all(size_t local_page_ndx, size_t begin_offset, size_t end_offset) noexcept; + void reclaim_page(size_t page_ndx); + void validate_page(size_t local_page_ndx) noexcept; + void validate() noexcept; +}; + +inline size_t EncryptedFileMapping::get_local_index_of_address(const void* addr, size_t offset) const +{ + REALM_ASSERT_EX(addr >= m_addr, addr, m_addr); + + size_t local_ndx = ((reinterpret_cast(addr) - reinterpret_cast(m_addr) + offset) >> m_page_shift); + REALM_ASSERT_EX(local_ndx < m_page_state.size(), local_ndx, m_page_state.size()); + return local_ndx; +} + +inline bool EncryptedFileMapping::contains_page(size_t page_in_file) const +{ + // first check for (page_in_file >= m_first_page) so that the following + // subtraction using unsigned types never wraps under 0 + return page_in_file >= m_first_page && page_in_file - m_first_page < m_page_state.size(); +} + + +} +} + +#endif // REALM_ENABLE_ENCRYPTION + +namespace realm { +namespace util { + +/// Thrown by EncryptedFileMapping if a file opened is non-empty and does not +/// contain valid encrypted data +struct DecryptionFailed : util::File::AccessError { + DecryptionFailed() + : util::File::AccessError("Decryption failed", std::string()) + { + } +}; +} +} + +#endif // REALM_UTIL_ENCRYPTED_FILE_MAPPING_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/enum.hpp b/!main project/Pods/Realm/include/core/realm/util/enum.hpp new file mode 100644 index 0000000..c11d166 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/enum.hpp @@ -0,0 +1,224 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2016] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ + +#ifndef REALM_UTIL_ENUM_HPP +#define REALM_UTIL_ENUM_HPP + +#include +#include +#include +#include + + +namespace realm { +namespace util { + +/// This template class allows you to endow a fundamental `enum` type with +/// information about how to print out the individual values, and how to parse +/// them. +/// +/// Here is an example: +/// +/// // Declaration +/// +/// enum class Color { orange, purple, brown }; +/// +/// struct ColorSpec { static EnumAssoc map[]; }; +/// using ColorEnum = Enum; +/// +/// // Implementation +/// +/// EnumAssoc ColorSpec::map[] = { +/// { int(Color::orange), "orange" }, +/// { int(Color::purple), "purple" }, +/// { int(Color::brown), "brown" }, +/// { 0, 0 } +/// }; +/// +/// // Application +/// +/// ColorEnum color = Color::purple; +/// +/// std::cout << color; // Write a color +/// std::cin >> color; // Read a color +/// +/// The current implementation is restricted to enumeration types whose values +/// can all be represented in a regular integer. +template class Enum { +public: + using base_enum_type = E; + + Enum(E = {}) noexcept; + + operator E() const noexcept; + + const std::string& str() const; + + bool str(const std::string*&) const noexcept; + + /// \return True if, and only if successful. + static bool parse(const std::string& string, E& value); + +private: + E m_value = E{}; +}; + +template +std::basic_ostream& operator<<(std::basic_ostream&, + const Enum&); + +template +std::basic_istream& operator>>(std::basic_istream&, + Enum&); + + +struct EnumAssoc { + const int value; + const char* const name; +}; + + + + +// Implementation + +} // namespace util + +namespace _impl { + +class EnumMapper { +public: + EnumMapper(const util::EnumAssoc*, bool ignore_case); + + bool parse(const std::string& string, int& value, bool ignore_case) const; + + std::map value_to_name; + std::map name_to_value; +}; + +template const EnumMapper& get_enum_mapper() +{ + static EnumMapper mapper{S::map, ignore_case}; // Throws + return mapper; +} + +} // namespace _impl + +namespace util { + +template +inline Enum::Enum(E value) noexcept : + m_value{value} +{ +} + +template +inline Enum::operator E() const noexcept +{ + return m_value; +} + +template +inline const std::string& Enum::str() const +{ + return _impl::get_enum_mapper().val_to_name.at(m_value); // Throws +} + +template +inline bool Enum::str(const std::string*& string) const noexcept +{ + const auto& value_to_name = _impl::get_enum_mapper().value_to_name; + auto i = value_to_name.find(int(m_value)); + if (i == value_to_name.end()) + return false; + string = &i->second; + return true; +} + +template +inline bool Enum::parse(const std::string& string, E& value) +{ + int value_2; + if (!_impl::get_enum_mapper().parse(string, value_2, ignore_case)) // Throws + return false; + value = E(value_2); + return true; +} + +template +inline std::basic_ostream& operator<<(std::basic_ostream& out, + const Enum& e) +{ + const std::string* string; + if (e.str(string)) { + out << *string; + } + else { + out << int(E(e)); + } + return out; +} + +template +std::basic_istream& operator>>(std::basic_istream& in, + Enum& e) +{ + if (in.bad() || in.fail()) + return in; + std::string string; + const std::ctype& ctype = std::use_facet>(in.getloc()); + C underscore(ctype.widen('_')); + for (;;) { + C ch; + // Allow white-spaces to be skipped when stream is configured + // that way + if (string.empty()) { + in >> ch; + } + else { + in.get(ch); + } + if (!in) { + if (in.bad()) + return in; + in.clear(in.rdstate() & ~std::ios_base::failbit); + break; + } + if (!ctype.is(std::ctype_base::alnum, ch) && ch != underscore) { + in.unget(); + break; + } + char ch_2 = ctype.narrow(ch, '\0'); + string += ch_2; + } + E value = E{}; + if (!Enum::parse(string, value)) { // Throws + in.setstate(std::ios_base::badbit); + } + else { + e = value; + } + return in; +} + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_ENUM_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/errno.hpp b/!main project/Pods/Realm/include/core/realm/util/errno.hpp new file mode 100644 index 0000000..4907f36 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/errno.hpp @@ -0,0 +1,39 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_ERRNO_HPP +#define REALM_UTIL_ERRNO_HPP + +#include + +#include + + +namespace realm { +namespace util { + +// Get the error message for a given error code, and append it to `prefix` +inline std::string get_errno_msg(const char* prefix, int err) +{ + return prefix + make_basic_system_error_code(err).message(); +} + +} // namespace util +} // namespace realm + +#endif diff --git a/!main project/Pods/Realm/include/core/realm/util/features.h b/!main project/Pods/Realm/include/core/realm/util/features.h new file mode 100644 index 0000000..ed6d55a --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/features.h @@ -0,0 +1,344 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_FEATURES_H +#define REALM_UTIL_FEATURES_H + +#ifdef _MSC_VER +#pragma warning(disable : 4800) // Visual Studio int->bool performance warnings +#endif + +#if defined(_WIN32) && !defined(NOMINMAX) +#define NOMINMAX +#endif + +#ifndef REALM_NO_CONFIG +#include +#endif + +/* The maximum number of elements in a B+-tree node. Applies to inner nodes and + * to leaves. The minimum allowable value is 2. + */ +#ifndef REALM_MAX_BPNODE_SIZE +#define REALM_MAX_BPNODE_SIZE 1000 +#endif + + +#define REALM_QUOTE_2(x) #x +#define REALM_QUOTE(x) REALM_QUOTE_2(x) + +/* See these links for information about feature check macroes in GCC, + * Clang, and MSVC: + * + * http://gcc.gnu.org/projects/cxx0x.html + * http://clang.llvm.org/cxx_status.html + * http://clang.llvm.org/docs/LanguageExtensions.html#checks-for-standard-language-features + * http://msdn.microsoft.com/en-us/library/vstudio/hh567368.aspx + * http://sourceforge.net/p/predef/wiki/Compilers + */ + + +/* Compiler is GCC and version is greater than or equal to the specified version */ +#define REALM_HAVE_AT_LEAST_GCC(maj, min) \ + (__GNUC__ > (maj) || __GNUC__ == (maj) && __GNUC_MINOR__ >= (min)) + +#if defined(__clang__) +#define REALM_HAVE_CLANG_FEATURE(feature) __has_feature(feature) +#define REALM_HAVE_CLANG_WARNING(warning) __has_warning(warning) +#else +#define REALM_HAVE_CLANG_FEATURE(feature) 0 +#define REALM_HAVE_CLANG_WARNING(warning) 0 +#endif + +#ifdef __has_cpp_attribute +#define REALM_HAS_CPP_ATTRIBUTE(attr) __has_cpp_attribute(attr) +#else +#define REALM_HAS_CPP_ATTRIBUTE(attr) 0 +#endif + +#if REALM_HAS_CPP_ATTRIBUTE(clang::fallthrough) +#define REALM_FALLTHROUGH [[clang::fallthrough]] +#elif REALM_HAS_CPP_ATTRIBUTE(gnu::fallthrough) +#define REALM_FALLTHROUGH [[gnu::fallthrough]] +#elif REALM_HAS_CPP_ATTRIBUTE(fallthrough) +#define REALM_FALLTHROUGH [[fallthrough]] +#else +#define REALM_FALLTHROUGH +#endif + +// This should be renamed to REALM_UNREACHABLE as soon as REALM_UNREACHABLE is renamed to +// REALM_ASSERT_NOT_REACHED which will better reflect its nature +#if defined(__GNUC__) || defined(__clang__) +#define REALM_COMPILER_HINT_UNREACHABLE __builtin_unreachable +#else +#define REALM_COMPILER_HINT_UNREACHABLE abort +#endif + +#if defined(__GNUC__) // clang or GCC +#define REALM_PRAGMA(v) _Pragma(REALM_QUOTE_2(v)) +#elif defined(_MSC_VER) // VS +#define REALM_PRAGMA(v) __pragma(v) +#else +#define REALM_PRAGMA(v) +#endif + +#if defined(__clang__) +#define REALM_DIAG(v) REALM_PRAGMA(clang diagnostic v) +#elif defined(__GNUC__) +#define REALM_DIAG(v) REALM_PRAGMA(GCC diagnostic v) +#else +#define REALM_DIAG(v) +#endif + +#define REALM_DIAG_PUSH() REALM_DIAG(push) +#define REALM_DIAG_POP() REALM_DIAG(pop) + +#ifdef _MSC_VER +#define REALM_VS_WARNING_DISABLE #pragma warning (default: 4297) +#endif + +#if REALM_HAVE_CLANG_WARNING("-Wtautological-compare") || REALM_HAVE_AT_LEAST_GCC(6, 0) +#define REALM_DIAG_IGNORE_TAUTOLOGICAL_COMPARE() REALM_DIAG(ignored "-Wtautological-compare") +#else +#define REALM_DIAG_IGNORE_TAUTOLOGICAL_COMPARE() +#endif + +#ifdef _MSC_VER +# define REALM_DIAG_IGNORE_UNSIGNED_MINUS() REALM_PRAGMA(warning(disable:4146)) +#else +#define REALM_DIAG_IGNORE_UNSIGNED_MINUS() +#endif + +/* Compiler is MSVC (Microsoft Visual C++) */ +#if defined(_MSC_VER) && _MSC_VER >= 1600 +#define REALM_HAVE_AT_LEAST_MSVC_10_2010 1 +#endif +#if defined(_MSC_VER) && _MSC_VER >= 1700 +#define REALM_HAVE_AT_LEAST_MSVC_11_2012 1 +#endif +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#define REALM_HAVE_AT_LEAST_MSVC_12_2013 1 +#endif + + +/* The way to specify that a function never returns. */ +#if REALM_HAVE_AT_LEAST_GCC(4, 8) || REALM_HAVE_CLANG_FEATURE(cxx_attributes) +#define REALM_NORETURN [[noreturn]] +#elif __GNUC__ +#define REALM_NORETURN __attribute__((noreturn)) +#elif defined(_MSC_VER) +#define REALM_NORETURN __declspec(noreturn) +#else +#define REALM_NORETURN +#endif + + +/* The way to specify that a variable or type is intended to possibly + * not be used. Use it to suppress a warning from the compiler. */ +#if __GNUC__ +#define REALM_UNUSED __attribute__((unused)) +#else +#define REALM_UNUSED +#endif + +/* The way to specify that a function is deprecated + * not be used. Use it to suppress a warning from the compiler. */ +#if __GNUC__ +#define REALM_DEPRECATED(x) [[deprecated(x)]] +#else +#define REALM_DEPRECATED(x) __declspec(deprecated(x)) +#endif + + +#if __GNUC__ || defined __INTEL_COMPILER +#define REALM_UNLIKELY(expr) __builtin_expect(!!(expr), 0) +#define REALM_LIKELY(expr) __builtin_expect(!!(expr), 1) +#else +#define REALM_UNLIKELY(expr) (expr) +#define REALM_LIKELY(expr) (expr) +#endif + + +#if defined(__GNUC__) || defined(__HP_aCC) +#define REALM_FORCEINLINE inline __attribute__((always_inline)) +#elif defined(_MSC_VER) +#define REALM_FORCEINLINE __forceinline +#else +#define REALM_FORCEINLINE inline +#endif + + +#if defined(__GNUC__) || defined(__HP_aCC) +#define REALM_NOINLINE __attribute__((noinline)) +#elif defined(_MSC_VER) +#define REALM_NOINLINE __declspec(noinline) +#else +#define REALM_NOINLINE +#endif + + +// FIXME: Change this to use [[nodiscard]] in C++17. +#if defined(__GNUC__) || defined(__HP_aCC) +#define REALM_NODISCARD __attribute__((warn_unused_result)) +#elif defined(_MSC_VER) +#define REALM_NODISCARD _Check_return_ +#else +#define REALM_NODISCARD +#endif + + +/* Thread specific data (only for POD types) */ +#if defined __clang__ +#define REALM_THREAD_LOCAL __thread +#else +#define REALM_THREAD_LOCAL thread_local +#endif + + +#if defined ANDROID +#define REALM_ANDROID 1 +#else +#define REALM_ANDROID 0 +#endif + +#if defined _WIN32 +# include +# if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM) +# define REALM_WINDOWS 1 +# define REALM_UWP 0 +# elif WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) +# define REALM_WINDOWS 0 +# define REALM_UWP 1 +# endif +#else +#define REALM_WINDOWS 0 +#define REALM_UWP 0 +#endif + +// Some documentation of the defines provided by Apple: +// http://developer.apple.com/library/mac/documentation/Porting/Conceptual/PortingUnix/compiling/compiling.html#//apple_ref/doc/uid/TP40002850-SW13 +#if defined __APPLE__ && defined __MACH__ +#define REALM_PLATFORM_APPLE 1 +/* Apple OSX and iOS (Darwin). */ +#include +#include +#if TARGET_OS_IPHONE == 1 +/* Device (iPhone or iPad) or simulator. */ +#define REALM_IOS 1 +#else +#define REALM_IOS 0 +#endif +#if TARGET_OS_WATCH == 1 +/* Device (Apple Watch) or simulator. */ +#define REALM_WATCHOS 1 +#else +#define REALM_WATCHOS 0 +#endif +#if TARGET_OS_TV +/* Device (Apple TV) or simulator. */ +#define REALM_TVOS 1 +#else +#define REALM_TVOS 0 +#endif +#else +#define REALM_PLATFORM_APPLE 0 +#define REALM_IOS 0 +#define REALM_WATCHOS 0 +#define REALM_TVOS 0 +#endif + +// asl_log is deprecated in favor of os_log as of the following versions: +// macos(10.12), ios(10.0), watchos(3.0), tvos(10.0) +// versions are defined in /usr/include/Availability.h +// __MAC_10_12 101200 +// __IPHONE_10_0 100000 +// __WATCHOS_3_0 30000 +// __TVOS_10_0 100000 +#if REALM_PLATFORM_APPLE \ + && ( \ + (REALM_IOS && defined(__IPHONE_OS_VERSION_MIN_REQUIRED) \ + && __IPHONE_OS_VERSION_MIN_REQUIRED >= 100000) \ + || (REALM_TVOS && defined(__TV_OS_VERSION_MIN_REQUIRED) \ + && __TV_OS_VERSION_MIN_REQUIRED >= 100000) \ + || (REALM_WATCHOS && defined(__WATCH_OS_VERSION_MIN_REQUIRED) \ + && __WATCH_OS_VERSION_MIN_REQUIRED >= 30000) \ + || (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) \ + && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200) \ + ) +#define REALM_APPLE_OS_LOG 1 +#else +#define REALM_APPLE_OS_LOG 0 +#endif + +#if REALM_ANDROID || REALM_IOS || REALM_WATCHOS || REALM_TVOS || REALM_UWP +#define REALM_MOBILE 1 +#else +#define REALM_MOBILE 0 +#endif + + +#if defined(REALM_DEBUG) && !defined(REALM_COOKIE_CHECK) +#define REALM_COOKIE_CHECK +#endif + +#if !REALM_IOS && !REALM_WATCHOS && !REALM_TVOS && !defined(_WIN32) && !REALM_ANDROID +#define REALM_ASYNC_DAEMON +#endif + +// We're in i686 mode +#if defined(__i386) || defined(__i386__) || defined(__i686__) || defined(_M_I86) || defined(_M_IX86) +#define REALM_ARCHITECTURE_X86_32 1 +#else +#define REALM_ARCHITECTURE_X86_32 0 +#endif + +// We're in amd64 mode +#if defined(__amd64) || defined(__amd64__) || defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ + defined(_M_AMD64) +#define REALM_ARCHITECTURE_X86_64 1 +#else +#define REALM_ARCHITECTURE_X86_64 0 +#endif + +// Address Sanitizer +#if defined(__has_feature) // Clang +# if __has_feature(address_sanitizer) +# define REALM_SANITIZE_ADDRESS 1 +# else +# define REALM_SANITIZE_ADDRESS 0 +# endif +#elif defined(__SANITIZE_ADDRESS__) && __SANITIZE_ADDRESS__ // GCC +# define REALM_SANITIZE_ADDRESS 1 +#else +# define REALM_SANITIZE_ADDRESS 0 +#endif + +// Thread Sanitizer +#if defined(__has_feature) // Clang +# if __has_feature(thread_sanitizer) +# define REALM_SANITIZE_THREAD 1 +# else +# define REALM_SANITIZE_THREAD 0 +# endif +#elif defined(__SANITIZE_THREAD__) && __SANITIZE_THREAD__ // GCC +# define REALM_SANITIZE_THREAD 1 +#else +# define REALM_SANITIZE_THREAD 0 +#endif + +#endif /* REALM_UTIL_FEATURES_H */ diff --git a/!main project/Pods/Realm/include/core/realm/util/fifo_helper.hpp b/!main project/Pods/Realm/include/core/realm/util/fifo_helper.hpp new file mode 100644 index 0000000..6ca8d75 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/fifo_helper.hpp @@ -0,0 +1,43 @@ +/************************************************************************* + * + * Copyright 2019 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_FIFO_HELPER_HPP +#define REALM_UTIL_FIFO_HELPER_HPP + +#include + +namespace realm { +namespace util { + +// Attempts to create a FIFO file at the location determined by `path`. +// If creating the FIFO at this location fails, an exception is thrown. +// If a FIFO already exists at the given location, this method does nothing. +void create_fifo(std::string path); // throws + +// Same as above, but returns `false` if the FIFO could not be created instead of throwing. +bool try_create_fifo(const std::string& path); + +// Ensure that a path representing a directory ends with `/` +inline std::string normalize_dir(const std::string& path) { + return (!path.empty() && path.back() != '/') ? path + '/' : path; +} + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_FIFO_HELPER_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/file.hpp b/!main project/Pods/Realm/include/core/realm/util/file.hpp new file mode 100644 index 0000000..eb7ced0 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/file.hpp @@ -0,0 +1,1331 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_FILE_HPP +#define REALM_UTIL_FILE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include // POSIX.1-2001 +#endif + +#if defined(_MSC_VER) && _MSC_VER >= 1900 // compiling with at least Visual Studio 2015 +#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING // switch to once we switch to C++17 +#include +namespace std { + namespace filesystem = std::experimental::filesystem::v1; +} +#define REALM_HAVE_STD_FILESYSTEM 1 +#else +#define REALM_HAVE_STD_FILESYSTEM 0 +#endif + +#include +#include +#include +#include +#include + + +namespace realm { +namespace util { + +class EncryptedFileMapping; + +/// Create the specified directory in the file system. +/// +/// \throw File::AccessError If the directory could not be created. If +/// the reason corresponds to one of the exception types that are +/// derived from File::AccessError, the derived exception type is +/// thrown (as long as the underlying system provides the information +/// to unambiguously distinguish that particular reason). +void make_dir(const std::string& path); + +/// Same as make_dir() except that this one returns false, rather than throwing +/// an exception, if the specified directory already existed. If the directory +// did not already exist and was newly created, this returns true. +bool try_make_dir(const std::string& path); + +/// Remove the specified empty directory path from the file system. It is an +/// error if the specified path is not a directory, or if it is a nonempty +/// directory. In so far as the specified path is a directory, std::remove(const +/// char*) is equivalent to this function. +/// +/// \throw File::AccessError If the directory could not be removed. If the +/// reason corresponds to one of the exception types that are derived from +/// File::AccessError, the derived exception type is thrown (as long as the +/// underlying system provides the information to unambiguously distinguish that +/// particular reason). +void remove_dir(const std::string& path); + +/// Same as remove_dir() except that this one returns false, rather +/// than throwing an exception, if the specified directory did not +/// exist. If the directory did exist, and was deleted, this function +/// returns true. +bool try_remove_dir(const std::string& path); + +/// Remove the specified directory after removing all its contents. Files +/// (nondirectory entries) will be removed as if by a call to File::remove(), +/// and empty directories as if by a call to remove_dir(). +/// +/// \throw File::AccessError If removal of the directory, or any of its contents +/// fail. +/// +/// remove_dir_recursive() assumes that no other process or thread is making +/// simultaneous changes in the directory. +void remove_dir_recursive(const std::string& path); + +/// Same as remove_dir_recursive() except that this one returns false, rather +/// than throwing an exception, if the specified directory did not +/// exist. If the directory did exist, and was deleted, this function +/// returns true. +/// +/// try_remove_dir_recursive() assumes that no other process or thread is making +/// simultaneous changes in the directory. +bool try_remove_dir_recursive(const std::string& path); + +/// Create a new unique directory for temporary files. The absolute +/// path to the new directory is returned without a trailing slash. +std::string make_temp_dir(); + +size_t page_size(); + + +/// This class provides a RAII abstraction over the concept of a file +/// descriptor (or file handle). +/// +/// Locks are automatically and immediately released when the File +/// instance is closed. +/// +/// You can use CloseGuard and UnlockGuard to acheive exception-safe +/// closing or unlocking prior to the File instance being detroyed. +/// +/// A single File instance must never be accessed concurrently by +/// multiple threads. +/// +/// You can write to a file via an std::ostream as follows: +/// +/// \code{.cpp} +/// +/// File::Streambuf my_streambuf(&my_file); +/// std::ostream out(&my_strerambuf); +/// out << 7945.9; +/// +/// \endcode +class File { +public: + enum Mode { + mode_Read, ///< access_ReadOnly, create_Never (fopen: rb) + mode_Update, ///< access_ReadWrite, create_Never (fopen: rb+) + mode_Write, ///< access_ReadWrite, create_Auto, flag_Trunc (fopen: wb+) + mode_Append ///< access_ReadWrite, create_Auto, flag_Append (fopen: ab+) + }; + + /// Equivalent to calling open(const std::string&, Mode) on a + /// default constructed instance. + explicit File(const std::string& path, Mode = mode_Read); + + /// Create an instance that is not initially attached to an open + /// file. + File() noexcept; + + ~File() noexcept; + + File(File&&) noexcept; + File& operator=(File&&) noexcept; + + // Disable copying by l-value. Copying an open file will create a scenario + // where the same file descriptor will be opened once but closed twice. + File(const File&) = delete; + File& operator=(const File&) = delete; + + /// Calling this function on an instance that is already attached + /// to an open file has undefined behavior. + /// + /// \throw AccessError If the file could not be opened. If the + /// reason corresponds to one of the exception types that are + /// derived from AccessError, the derived exception type is thrown + /// (as long as the underlying system provides the information to + /// unambiguously distinguish that particular reason). + void open(const std::string& path, Mode = mode_Read); + + /// This function is idempotent, that is, it is valid to call it + /// regardless of whether this instance currently is attached to + /// an open file. + void close() noexcept; + + /// Check whether this File instance is currently attached to an + /// open file. + bool is_attached() const noexcept; + + enum AccessMode { + access_ReadOnly, + access_ReadWrite, + }; + + enum CreateMode { + create_Auto, ///< Create the file if it does not already exist. + create_Never, ///< Fail if the file does not already exist. + create_Must ///< Fail if the file already exists. + }; + + enum { + flag_Trunc = 1, ///< Truncate the file if it already exists. + flag_Append = 2 ///< Move to end of file before each write. + }; + + /// See open(const std::string&, Mode). + /// + /// Specifying access_ReadOnly together with a create mode that is + /// not create_Never, or together with a non-zero \a flags + /// argument, results in undefined behavior. Specifying flag_Trunc + /// together with create_Must results in undefined behavior. + void open(const std::string& path, AccessMode, CreateMode, int flags); + + /// Same as open(path, access_ReadWrite, create_Auto, 0), except + /// that this one returns an indication of whether a new file was + /// created, or an existing file was opened. + void open(const std::string& path, bool& was_created); + + /// Read data into the specified buffer and return the number of + /// bytes read. If the returned number of bytes is less than \a + /// size, then the end of the file has been reached. + /// + /// Calling this function on an instance, that is not currently + /// attached to an open file, has undefined behavior. + size_t read(char* data, size_t size); + static size_t read_static(FileDesc fd, char* data, size_t size); + + /// Write the specified data to this file. + /// + /// Calling this function on an instance, that is not currently + /// attached to an open file, has undefined behavior. + /// + /// Calling this function on an instance, that was opened in + /// read-only mode, has undefined behavior. + void write(const char* data, size_t size); + static void write_static(FileDesc fd, const char* data, size_t size); + + // Tells current file pointer of fd + static uint64_t get_file_pos(FileDesc fd); + + /// Calls write(s.data(), s.size()). + void write(const std::string& s) + { + write(s.data(), s.size()); + } + + /// Calls read(data, N). + template + size_t read(char (&data)[N]) + { + return read(data, N); + } + + /// Calls write(data(), N). + template + void write(const char (&data)[N]) + { + write(data, N); + } + + /// Plays the same role as off_t in POSIX + typedef int_fast64_t SizeType; + + /// Calling this function on an instance that is not attached to + /// an open file has undefined behavior. + SizeType get_size() const; + static SizeType get_size_static(FileDesc fd); + + /// If this causes the file to grow, then the new section will + /// have undefined contents. Setting the size with this function + /// does not necessarily allocate space on the target device. If + /// you want to ensure allocation, call alloc(). Calling this + /// function will generally affect the read/write offset + /// associated with this File instance. + /// + /// Calling this function on an instance that is not attached to + /// an open file has undefined behavior. Calling this function on + /// a file that is opened in read-only mode, is an error. + void resize(SizeType); + + /// Same effect as prealloc_if_supported(original_size, new_size); + /// + /// The downside is that this function is not guaranteed to have + /// atomic behaviour on all systems, that is, two processes, or + /// two threads should never call this function concurrently for + /// the same underlying file even though they access the file + /// through distinct File instances. + /// + /// \sa prealloc_if_supported() + void prealloc(size_t new_size); + + /// When supported by the system, allocate space on the target + /// device for the specified region of the file. If the region + /// extends beyond the current end of the file, the file size is + /// increased as necessary. + /// + /// On systems that do not support this operation, this function + /// has no effect. You may call is_prealloc_supported() to + /// determine if it is supported on your system. + /// + /// Calling this function on an instance, that is not attached to + /// an open file, has undefined behavior. Calling this function on + /// a file, that is opened in read-only mode, is an error. + /// + /// This function is guaranteed to have atomic behaviour, that is, + /// there is never any risk of the file size being reduced even + /// with concurrently executing invocations. + /// + /// \sa prealloc() + /// \sa is_prealloc_supported() + bool prealloc_if_supported(SizeType offset, size_t size); + + /// See prealloc_if_supported(). + static bool is_prealloc_supported(); + + /// Reposition the read/write offset of this File + /// instance. Distinct File instances have separate independent + /// offsets, as long as the cucrrent process is not forked. + void seek(SizeType); + static void seek_static(FileDesc, SizeType); + + /// Flush in-kernel buffers to disk. This blocks the caller until the + /// synchronization operation is complete. On POSIX systems this function + /// calls `fsync()`. On Apple platforms if calls `fcntl()` with command + /// `F_FULLFSYNC`. + void sync(); + + /// Place an exclusive lock on this file. This blocks the caller + /// until all other locks have been released. + /// + /// Locks acquired on distinct File instances have fully recursive + /// behavior, even if they are acquired in the same process (or + /// thread) and are attached to the same underlying file. + /// + /// Calling this function on an instance that is not attached to + /// an open file, or on an instance that is already locked has + /// undefined behavior. + void lock_exclusive(); + + /// Place an shared lock on this file. This blocks the caller + /// until all other exclusive locks have been released. + /// + /// Locks acquired on distinct File instances have fully recursive + /// behavior, even if they are acquired in the same process (or + /// thread) and are attached to the same underlying file. + /// + /// Calling this function on an instance that is not attached to + /// an open file, or on an instance that is already locked has + /// undefined behavior. + void lock_shared(); + + /// Non-blocking version of lock_exclusive(). Returns true iff it + /// succeeds. + bool try_lock_exclusive(); + + /// Non-blocking version of lock_shared(). Returns true iff it + /// succeeds. + bool try_lock_shared(); + + /// Release a previously acquired lock on this file. This function + /// is idempotent. + void unlock() noexcept; + + /// Set the encryption key used for this file. Must be called before any + /// mappings are created or any data is read from or written to the file. + /// + /// \param key A 64-byte encryption key, or null to disable encryption. + void set_encryption_key(const char* key); + + /// Get the encryption key set by set_encryption_key(), + /// null_ptr if no key set. + const char* get_encryption_key(); + enum { + /// If possible, disable opportunistic flushing of dirted + /// pages of a memory mapped file to physical medium. On some + /// systems this cannot be disabled. On other systems it is + /// the default behavior. An explicit call to sync_map() will + /// flush the buffers regardless of whether this flag is + /// specified or not. + map_NoSync = 1 + }; + + /// Map this file into memory. The file is mapped as shared + /// memory. This allows two processes to interact under exatly the + /// same rules as applies to the interaction via regular memory of + /// multiple threads inside a single process. + /// + /// This File instance does not need to remain in existence after + /// the mapping is established. + /// + /// Multiple concurrent mappings may be created from the same File + /// instance. + /// + /// Specifying access_ReadWrite for a file that is opened in + /// read-only mode, is an error. + /// + /// Calling this function on an instance that is not attached to + /// an open file, or one that is attached to an empty file has + /// undefined behavior. + /// + /// Calling this function with a size that is greater than the + /// size of the file has undefined behavior. + void* map(AccessMode, size_t size, int map_flags = 0, size_t offset = 0) const; + + /// The same as unmap(old_addr, old_size) followed by map(a, + /// new_size, map_flags), but more efficient on some systems. + /// + /// The old address range must have been acquired by a call to + /// map() or remap() on this File instance, the specified access + /// mode and flags must be the same as the ones specified + /// previously, and this File instance must not have been reopend + /// in the meantime. Failing to adhere to these rules will result + /// in undefined behavior. + /// + /// If this function throws, the old address range will remain + /// mapped. + void* remap(void* old_addr, size_t old_size, AccessMode a, size_t new_size, int map_flags = 0, + size_t file_offset = 0) const; + +#if REALM_ENABLE_ENCRYPTION + void* map(AccessMode, size_t size, EncryptedFileMapping*& mapping, int map_flags = 0, size_t offset = 0) const; +#endif + /// Unmap the specified address range which must have been + /// previously returned by map(). + static void unmap(void* addr, size_t size) noexcept; + + /// Flush in-kernel buffers to disk. This blocks the caller until + /// the synchronization operation is complete. The specified + /// address range must be (a subset of) one that was previously returned by + /// map(). + static void sync_map(FileDesc fd, void* addr, size_t size); + + /// Check whether the specified file or directory exists. Note + /// that a file or directory that resides in a directory that the + /// calling process has no access to, will necessarily be reported + /// as not existing. + static bool exists(const std::string& path); + + /// Check whether the specified path exists and refers to a directory. If + /// the referenced file system object resides in an inaccessible directory, + /// this function returns false. + static bool is_dir(const std::string& path); + + /// Remove the specified file path from the file system. It is an error if + /// the specified path is a directory. If the specified file is a symbolic + /// link, the link is removed, leaving the liked file intact. In so far as + /// the specified path is not a directory, std::remove(const char*) is + /// equivalent to this function. + /// + /// The specified file must not be open by the calling process. If + /// it is, this function has undefined behaviour. Note that an + /// open memory map of the file counts as "the file being open". + /// + /// \throw AccessError If the specified directory entry could not + /// be removed. If the reason corresponds to one of the exception + /// types that are derived from AccessError, the derived exception + /// type is thrown (as long as the underlying system provides the + /// information to unambiguously distinguish that particular + /// reason). + static void remove(const std::string& path); + + /// Same as remove() except that this one returns false, rather + /// than throwing an exception, if the specified file does not + /// exist. If the file did exist, and was deleted, this function + /// returns true. + static bool try_remove(const std::string& path); + + /// Change the path of a directory entry. This can be used to + /// rename a file, and/or to move it from one directory to + /// another. This function is equivalent to std::rename(const + /// char*, const char*). + /// + /// \throw AccessError If the path of the directory entry could + /// not be changed. If the reason corresponds to one of the + /// exception types that are derived from AccessError, the derived + /// exception type is thrown (as long as the underlying system + /// provides the information to unambiguously distinguish that + /// particular reason). + static void move(const std::string& old_path, const std::string& new_path); + + /// Copy the file at the specified origin path to the specified target path. + static void copy(const std::string& origin_path, const std::string& target_path); + + /// Compare the two files at the specified paths for equality. Returns true + /// if, and only if they are equal. + static bool compare(const std::string& path_1, const std::string& path_2); + + /// Check whether two open file descriptors refer to the same + /// underlying file, that is, if writing via one of them, will + /// affect what is read from the other. In UNIX this boils down to + /// comparing inode numbers. + /// + /// Both instances have to be attached to open files. If they are + /// not, this function has undefined behavior. + bool is_same_file(const File&) const; + static bool is_same_file_static(FileDesc f1, FileDesc f2); + + // FIXME: Get rid of this method + bool is_removed() const; + + /// Resolve the specified path against the specified base directory. + /// + /// If \a path is absolute, or if \a base_dir is empty, \p path is returned + /// unmodified, otherwise \a path is resolved against \a base_dir. + /// + /// Examples (assuming POSIX): + /// + /// resolve("file", "dir") -> "dir/file" + /// resolve("../baz", "/foo/bar") -> "/foo/baz" + /// resolve("foo", ".") -> "./foo" + /// resolve(".", "/foo/") -> "/foo" + /// resolve("..", "foo") -> "." + /// resolve("../..", "foo") -> ".." + /// resolve("..", "..") -> "../.." + /// resolve("", "") -> "." + /// resolve("", "/") -> "/." + /// resolve("..", "/") -> "/." + /// resolve("..", "foo//bar") -> "foo" + /// + /// This function does not access the file system. + /// + /// \param path The path to be resolved. An empty string produces the same + /// result as as if "." was passed. The result has a trailing directory + /// separator (`/`) if, and only if this path has a trailing directory + /// separator. + /// + /// \param base_dir The base directory path, which may be relative or + /// absolute. A final directory separator (`/`) is optional. The empty + /// string is interpreted as a relative path. + static std::string resolve(const std::string& path, const std::string& base_dir); + + using ForEachHandler = std::function; + + /// Scan the specified directory recursivle, and report each file + /// (nondirectory entry) via the specified handler. + /// + /// The first argument passed to the handler is the name of a file (not the + /// whole path), and the second argument is the directory in which that file + /// resides. The directory will be specified as a path, and relative to \a + /// dir_path. The directory will be the empty string for files residing + /// directly in \a dir_path. + /// + /// If the handler returns false, scanning will be aborted immediately, and + /// for_each() will return false. Otherwise for_each() will return true. + /// + /// Scanning is done as if by a recursive set of DirScanner objects. + static bool for_each(const std::string& dir_path, ForEachHandler handler); + + struct UniqueID { +#ifdef _WIN32 // Windows version +// FIXME: This is not implemented for Windows +#else + // NDK r10e has a bug in sys/stat.h dev_t ino_t are 4 bytes, + // but stat.st_dev and st_ino are 8 bytes. So we just use uint64 instead. + dev_t device; + uint_fast64_t inode; +#endif + }; + // Return the unique id for the current opened file descriptor. + // Same UniqueID means they are the same file. + UniqueID get_unique_id() const; + // Return the file descriptor for the file + FileDesc get_descriptor() const; + // Return the path of the open file, or an empty string if + // this file has never been opened. + std::string get_path() const; + // Return false if the file doesn't exist. Otherwise uid will be set. + static bool get_unique_id(const std::string& path, UniqueID& uid); + + class ExclusiveLock; + class SharedLock; + + template + class Map; + + class CloseGuard; + class UnlockGuard; + class UnmapGuard; + + class Streambuf; + + // Exceptions + class AccessError; + class PermissionDenied; + class NotFound; + class Exists; + +private: +#ifdef _WIN32 + void* m_fd; + bool m_have_lock; // Only valid when m_fd is not null +#else + int m_fd; +#endif + std::unique_ptr m_encryption_key = nullptr; + std::string m_path; + + bool lock(bool exclusive, bool non_blocking); + void open_internal(const std::string& path, AccessMode, CreateMode, int flags, bool* success); + + struct MapBase { + void* m_addr = nullptr; + size_t m_size = 0; + FileDesc m_fd; + + MapBase() noexcept; + ~MapBase() noexcept; + + // Disable copying. Copying an opened MapBase will create a scenario + // where the same memory will be mapped once but unmapped twice. + MapBase(const MapBase&) = delete; + MapBase& operator=(const MapBase&) = delete; + + void map(const File&, AccessMode, size_t size, int map_flags, size_t offset = 0); + void remap(const File&, AccessMode, size_t size, int map_flags); + void unmap() noexcept; + void sync(); +#if REALM_ENABLE_ENCRYPTION + util::EncryptedFileMapping* m_encrypted_mapping = nullptr; + inline util::EncryptedFileMapping* get_encrypted_mapping() const + { + return m_encrypted_mapping; + } +#else + inline util::EncryptedFileMapping* get_encrypted_mapping() const + { + return nullptr; + } +#endif + }; +}; + + +class File::ExclusiveLock { +public: + ExclusiveLock(File& f) + : m_file(f) + { + f.lock_exclusive(); + } + ~ExclusiveLock() noexcept + { + m_file.unlock(); + } + // Disable copying. It is not how this class should be used. + ExclusiveLock(const ExclusiveLock&) = delete; + ExclusiveLock& operator=(const ExclusiveLock&) = delete; + +private: + File& m_file; +}; + +class File::SharedLock { +public: + SharedLock(File& f) + : m_file(f) + { + f.lock_shared(); + } + ~SharedLock() noexcept + { + m_file.unlock(); + } + // Disable copying. It is not how this class should be used. + SharedLock(const SharedLock&) = delete; + SharedLock& operator=(const SharedLock&) = delete; + +private: + File& m_file; +}; + + +/// This class provides a RAII abstraction over the concept of a +/// memory mapped file. +/// +/// Once created, the Map instance makes no reference to the File +/// instance that it was based upon, and that File instance may be +/// destroyed before the Map instance is destroyed. +/// +/// Multiple concurrent mappings may be created from the same File +/// instance. +/// +/// You can use UnmapGuard to acheive exception-safe unmapping prior +/// to the Map instance being detroyed. +/// +/// A single Map instance must never be accessed concurrently by +/// multiple threads. +template +class File::Map : private MapBase { +public: + /// Equivalent to calling map() on a default constructed instance. + explicit Map(const File&, AccessMode = access_ReadOnly, size_t size = sizeof(T), int map_flags = 0); + + explicit Map(const File&, size_t offset, AccessMode = access_ReadOnly, size_t size = sizeof(T), + int map_flags = 0); + + /// Create an instance that is not initially attached to a memory + /// mapped file. + Map() noexcept; + + ~Map() noexcept; + + // Disable copying. Copying an opened Map will create a scenario + // where the same memory will be mapped once but unmapped twice. + Map(const Map&) = delete; + Map& operator=(const Map&) = delete; + + /// Move the mapping from another Map object to this Map object + File::Map& operator=(File::Map&& other) + { + if (m_addr) + unmap(); + m_addr = other.get_addr(); + m_size = other.m_size; + other.m_addr = 0; + other.m_size = 0; +#if REALM_ENABLE_ENCRYPTION + m_encrypted_mapping = other.m_encrypted_mapping; + other.m_encrypted_mapping = nullptr; +#endif + return *this; + } + + /// See File::map(). + /// + /// Calling this function on a Map instance that is already + /// attached to a memory mapped file has undefined behavior. The + /// returned pointer is the same as what will subsequently be + /// returned by get_addr(). + T* map(const File&, AccessMode = access_ReadOnly, size_t size = sizeof(T), int map_flags = 0, size_t offset = 0); + + /// See File::unmap(). This function is idempotent, that is, it is + /// valid to call it regardless of whether this instance is + /// currently attached to a memory mapped file. + void unmap() noexcept; + + /// See File::remap(). + /// + /// Calling this function on a Map instance that is not currently + /// attached to a memory mapped file has undefined behavior. The + /// returned pointer is the same as what will subsequently be + /// returned by get_addr(). + T* remap(const File&, AccessMode = access_ReadOnly, size_t size = sizeof(T), int map_flags = 0); + + /// See File::sync_map(). + /// + /// Calling this function on an instance that is not currently + /// attached to a memory mapped file, has undefined behavior. + void sync(); + + /// Check whether this Map instance is currently attached to a + /// memory mapped file. + bool is_attached() const noexcept; + + /// Returns a pointer to the beginning of the memory mapped file, + /// or null if this instance is not currently attached. + T* get_addr() const noexcept; + + /// Returns the size of the mapped region, or zero if this + /// instance does not currently refer to a memory mapped + /// file. When this instance refers to a memory mapped file, the + /// returned value will always be identical to the size passed to + /// the constructor or to map(). + size_t get_size() const noexcept; + + /// Release the currently attached memory mapped file from this + /// Map instance. The address range may then be unmapped later by + /// a call to File::unmap(). + T* release() noexcept; + +#if REALM_ENABLE_ENCRYPTION + /// Get the encrypted file mapping corresponding to this mapping + inline EncryptedFileMapping* get_encrypted_mapping() const + { + return m_encrypted_mapping; + } +#else + inline EncryptedFileMapping* get_encrypted_mapping() const + { + return nullptr; + } +#endif + + friend class UnmapGuard; +}; + + +class File::CloseGuard { +public: + CloseGuard(File& f) noexcept + : m_file(&f) + { + } + ~CloseGuard() noexcept + { + if (m_file) + m_file->close(); + } + void release() noexcept + { + m_file = nullptr; + } + // Disallow the default implementation of copy/assign, this is not how this + // class is intended to be used. For example we could get unexpected + // behaviour if one CloseGuard is copied and released but the other is not. + CloseGuard(const CloseGuard&) = delete; + CloseGuard& operator=(const CloseGuard&) = delete; + +private: + File* m_file; +}; + + +class File::UnlockGuard { +public: + UnlockGuard(File& f) noexcept + : m_file(&f) + { + } + ~UnlockGuard() noexcept + { + if (m_file) + m_file->unlock(); + } + void release() noexcept + { + m_file = nullptr; + } + // Disallow the default implementation of copy/assign, this is not how this + // class is intended to be used. For example we could get unexpected + // behaviour if one UnlockGuard is copied and released but the other is not. + UnlockGuard(const UnlockGuard&) = delete; + UnlockGuard& operator=(const UnlockGuard&) = delete; + +private: + File* m_file; +}; + + +class File::UnmapGuard { +public: + template + UnmapGuard(Map& m) noexcept + : m_map(&m) + { + } + ~UnmapGuard() noexcept + { + if (m_map) + m_map->unmap(); + } + void release() noexcept + { + m_map = nullptr; + } + // Disallow the default implementation of copy/assign, this is not how this + // class is intended to be used. For example we could get unexpected + // behaviour if one UnmapGuard is copied and released but the other is not. + UnmapGuard(const UnmapGuard&) = delete; + UnmapGuard& operator=(const UnmapGuard&) = delete; + +private: + MapBase* m_map; +}; + + +/// Only output is supported at this point. +class File::Streambuf : public std::streambuf { +public: + explicit Streambuf(File*, size_t = 4096); + ~Streambuf() noexcept; + + // Disable copying + Streambuf(const Streambuf&) = delete; + Streambuf& operator=(const Streambuf&) = delete; + +private: + File& m_file; + std::unique_ptr const m_buffer; + + int_type overflow(int_type) override; + int sync() override; + pos_type seekpos(pos_type, std::ios_base::openmode) override; + void flush(); +}; + +/// Used for any I/O related exception. Note the derived exception +/// types that are used for various specific types of errors. +class File::AccessError : public ExceptionWithBacktrace { +public: + AccessError(const std::string& msg, const std::string& path); + + /// Return the associated file system path, or the empty string if there is + /// no associated file system path, or if the file system path is unknown. + std::string get_path() const; + + const char* message() const noexcept + { + m_buffer = std::runtime_error::what(); + if (m_path.size() > 0) + m_buffer += (std::string(" Path: ") + m_path); + return m_buffer.c_str(); + } + +private: + std::string m_path; + mutable std::string m_buffer; +}; + + +/// Thrown if the user does not have permission to open or create +/// the specified file in the specified access mode. +class File::PermissionDenied : public AccessError { +public: + PermissionDenied(const std::string& msg, const std::string& path); +}; + + +/// Thrown if the directory part of the specified path was not +/// found, or create_Never was specified and the file did no +/// exist. +class File::NotFound : public AccessError { +public: + NotFound(const std::string& msg, const std::string& path); +}; + + +/// Thrown if create_Always was specified and the file did already +/// exist. +class File::Exists : public AccessError { +public: + Exists(const std::string& msg, const std::string& path); +}; + + +class DirScanner { +public: + DirScanner(const std::string& path, bool allow_missing = false); + ~DirScanner() noexcept; + bool next(std::string& name); + +private: +#ifndef _WIN32 + DIR* m_dirp; +#elif REALM_HAVE_STD_FILESYSTEM + std::filesystem::directory_iterator m_iterator; +#endif +}; + + +// Implementation: + +inline File::File(const std::string& path, Mode m) +{ +#ifdef _WIN32 + m_fd = nullptr; +#else + m_fd = -1; +#endif + + open(path, m); +} + +inline File::File() noexcept +{ +#ifdef _WIN32 + m_fd = nullptr; +#else + m_fd = -1; +#endif +} + +inline File::~File() noexcept +{ + close(); +} + +inline File::File(File&& f) noexcept +{ +#ifdef _WIN32 + m_fd = f.m_fd; + m_have_lock = f.m_have_lock; + f.m_fd = nullptr; +#else + m_fd = f.m_fd; + f.m_fd = -1; +#endif + m_encryption_key = std::move(f.m_encryption_key); +} + +inline File& File::operator=(File&& f) noexcept +{ + close(); +#ifdef _WIN32 + m_fd = f.m_fd; + m_have_lock = f.m_have_lock; + f.m_fd = nullptr; +#else + m_fd = f.m_fd; + f.m_fd = -1; +#endif + m_encryption_key = std::move(f.m_encryption_key); + return *this; +} + +inline void File::open(const std::string& path, Mode m) +{ + AccessMode a = access_ReadWrite; + CreateMode c = create_Auto; + int flags = 0; + switch (m) { + case mode_Read: + a = access_ReadOnly; + c = create_Never; + break; + case mode_Update: + c = create_Never; + break; + case mode_Write: + flags = flag_Trunc; + break; + case mode_Append: + flags = flag_Append; + break; + } + open(path, a, c, flags); +} + +inline void File::open(const std::string& path, AccessMode am, CreateMode cm, int flags) +{ + open_internal(path, am, cm, flags, nullptr); +} + + +inline void File::open(const std::string& path, bool& was_created) +{ + while (1) { + bool success; + open_internal(path, access_ReadWrite, create_Must, 0, &success); + if (success) { + was_created = true; + return; + } + open_internal(path, access_ReadWrite, create_Never, 0, &success); + if (success) { + was_created = false; + return; + } + } +} + +inline bool File::is_attached() const noexcept +{ +#ifdef _WIN32 + return (m_fd != nullptr); +#else + return 0 <= m_fd; +#endif +} + +inline void File::lock_exclusive() +{ + lock(true, false); +} + +inline void File::lock_shared() +{ + lock(false, false); +} + +inline bool File::try_lock_exclusive() +{ + return lock(true, true); +} + +inline bool File::try_lock_shared() +{ + return lock(false, true); +} + +inline File::MapBase::MapBase() noexcept +{ + m_addr = nullptr; +} + +inline File::MapBase::~MapBase() noexcept +{ + unmap(); +} + +inline void File::MapBase::map(const File& f, AccessMode a, size_t size, int map_flags, size_t offset) +{ + REALM_ASSERT(!m_addr); +#if REALM_ENABLE_ENCRYPTION + m_addr = f.map(a, size, m_encrypted_mapping, map_flags, offset); +#else + m_addr = f.map(a, size, map_flags, offset); +#endif + m_size = size; + m_fd = f.m_fd; +} + +inline void File::MapBase::unmap() noexcept +{ + if (!m_addr) + return; + File::unmap(m_addr, m_size); + m_addr = nullptr; +#if REALM_ENABLE_ENCRYPTION + m_encrypted_mapping = nullptr; +#endif + m_fd = 0; +} + +inline void File::MapBase::remap(const File& f, AccessMode a, size_t size, int map_flags) +{ + REALM_ASSERT(m_addr); + + //m_addr = f.remap(m_addr, m_size, a, size, map_flags); + // missing sync() here? + unmap(); + map(f, a, size, map_flags); + m_size = size; + m_fd = f.m_fd; +} + +inline void File::MapBase::sync() +{ + REALM_ASSERT(m_addr); + + File::sync_map(m_fd, m_addr, m_size); +} + +template +inline File::Map::Map(const File& f, AccessMode a, size_t size, int map_flags) +{ + map(f, a, size, map_flags); +} + +template +inline File::Map::Map(const File& f, size_t offset, AccessMode a, size_t size, int map_flags) +{ + map(f, a, size, map_flags, offset); +} + +template +inline File::Map::Map() noexcept +{ +} + +template +inline File::Map::~Map() noexcept +{ +} + +template +inline T* File::Map::map(const File& f, AccessMode a, size_t size, int map_flags, size_t offset) +{ + MapBase::map(f, a, size, map_flags, offset); + return static_cast(m_addr); +} + +template +inline void File::Map::unmap() noexcept +{ + MapBase::unmap(); +} + +template +inline T* File::Map::remap(const File& f, AccessMode a, size_t size, int map_flags) +{ + MapBase::remap(f, a, size, map_flags); + return static_cast(m_addr); +} + +template +inline void File::Map::sync() +{ + MapBase::sync(); +} + +template +inline bool File::Map::is_attached() const noexcept +{ + return (m_addr != nullptr); +} + +template +inline T* File::Map::get_addr() const noexcept +{ + return static_cast(m_addr); +} + +template +inline size_t File::Map::get_size() const noexcept +{ + return m_addr ? m_size : 0; +} + +template +inline T* File::Map::release() noexcept +{ + T* addr = static_cast(m_addr); + m_addr = nullptr; + m_fd = 0; + return addr; +} + + +inline File::Streambuf::Streambuf(File* f, size_t buffer_size) + : m_file(*f) + , m_buffer(new char[buffer_size]) +{ + char* b = m_buffer.get(); + setp(b, b + buffer_size); +} + +inline File::Streambuf::~Streambuf() noexcept +{ + try { + if (m_file.is_attached()) + flush(); + } + catch (...) { + // Errors deliberately ignored + } +} + +inline File::Streambuf::int_type File::Streambuf::overflow(int_type c) +{ + flush(); + if (c == traits_type::eof()) + return traits_type::not_eof(c); + *pptr() = traits_type::to_char_type(c); + pbump(1); + return c; +} + +inline int File::Streambuf::sync() +{ + flush(); + return 0; +} + +inline File::Streambuf::pos_type File::Streambuf::seekpos(pos_type pos, std::ios_base::openmode) +{ + flush(); + SizeType pos2 = 0; + if (int_cast_with_overflow_detect(std::streamsize(pos), pos2)) + throw util::overflow_error("Seek position overflow"); + m_file.seek(pos2); + return pos; +} + +inline void File::Streambuf::flush() +{ + size_t n = pptr() - pbase(); + if (n > 0) { + m_file.write(pbase(), n); + setp(m_buffer.get(), epptr()); + } +} + +inline File::AccessError::AccessError(const std::string& msg, const std::string& path) + : ExceptionWithBacktrace(msg) + , m_path(path) +{ +} + +inline std::string File::AccessError::get_path() const +{ + return m_path; +} + +inline File::PermissionDenied::PermissionDenied(const std::string& msg, const std::string& path) + : AccessError(msg, path) +{ +} + +inline File::NotFound::NotFound(const std::string& msg, const std::string& path) + : AccessError(msg, path) +{ +} + +inline File::Exists::Exists(const std::string& msg, const std::string& path) + : AccessError(msg, path) +{ +} + +inline bool operator==(const File::UniqueID& lhs, const File::UniqueID& rhs) +{ +#ifdef _WIN32 // Windows version + throw util::runtime_error("Not yet supported"); +#else // POSIX version + return lhs.device == rhs.device && lhs.inode == rhs.inode; +#endif +} + +inline bool operator!=(const File::UniqueID& lhs, const File::UniqueID& rhs) +{ + return !(lhs == rhs); +} + +inline bool operator<(const File::UniqueID& lhs, const File::UniqueID& rhs) +{ +#ifdef _WIN32 // Windows version + throw util::runtime_error("Not yet supported"); +#else // POSIX version + if (lhs.device < rhs.device) + return true; + if (lhs.device > rhs.device) + return false; + if (lhs.inode < rhs.inode) + return true; + return false; +#endif +} + +inline bool operator>(const File::UniqueID& lhs, const File::UniqueID& rhs) +{ + return rhs < lhs; +} + +inline bool operator<=(const File::UniqueID& lhs, const File::UniqueID& rhs) +{ + return !(lhs > rhs); +} + +inline bool operator>=(const File::UniqueID& lhs, const File::UniqueID& rhs) +{ + return !(lhs < rhs); +} + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_FILE_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/file_mapper.hpp b/!main project/Pods/Realm/include/core/realm/util/file_mapper.hpp new file mode 100644 index 0000000..d02f15d --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/file_mapper.hpp @@ -0,0 +1,179 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_FILE_MAPPER_HPP +#define REALM_UTIL_FILE_MAPPER_HPP + +#include +#include +#include +#include + +#include + +namespace realm { +namespace util { + +void* mmap(FileDesc fd, size_t size, File::AccessMode access, size_t offset, const char* encryption_key); +void munmap(void* addr, size_t size); +void* mremap(FileDesc fd, size_t file_offset, void* old_addr, size_t old_size, File::AccessMode a, size_t new_size, + const char* encryption_key); +void msync(FileDesc fd, void* addr, size_t size); + +// A function which may be given to encryption_read_barrier. If present, the read barrier is a +// a barrier for a full array. If absent, the read barrier is a barrier only for the address +// range give as argument. If the barrier is for a full array, it will read the array header +// and determine the address range from the header. +using HeaderToSize = size_t (*)(const char* addr); +class EncryptedFileMapping; + +class PageReclaimGovernor { +public: + // Called by the page reclaimer with the current load (in bytes) and + // must return the target load (also in bytes). Returns no_match if no + // target can be set + static constexpr int64_t no_match = -1; + virtual std::function current_target_getter(size_t load) = 0; + virtual void report_target_result(int64_t) = 0; +}; + +// Set a page reclaim governor. The governor is an object with a method which will be called periodically +// and must return a 'target' amount of memory to hold decrypted pages. The page reclaim daemon +// will then try to release pages to meet the target. The governor is called with the current +// amount of data used, for the purpose of logging - or possibly for computing the target +// +// The governor is called approximately once per second. +// +// If no governor is installed, the page reclaim daemon will not start. +void set_page_reclaim_governor(PageReclaimGovernor* governor); + +// Use the default governor. The default governor is used automatically if nothing else is set, so +// this funciton is mostly useful for tests where changing back to the default could be desirable. +inline void set_page_reclaim_governor_to_default() +{ + set_page_reclaim_governor(nullptr); +} + +// Retrieves the number of in memory decrypted pages, across all open files. +size_t get_num_decrypted_pages(); + +// Retrieves the +// - amount of memory used for decrypted pages, across all open files. +// - current target for the reclaimer (desired number of decrypted pages) +// - current workload size for the reclaimer, across all open files. +struct decrypted_memory_stats_t { + size_t memory_size; + size_t reclaimer_target; + size_t reclaimer_workload; +}; + +decrypted_memory_stats_t get_decrypted_memory_stats(); + +#if REALM_ENABLE_ENCRYPTION + +void encryption_note_reader_start(SharedFileInfo& info, const void* reader_id); +void encryption_note_reader_end(SharedFileInfo& info, const void* reader_id) noexcept; + +SharedFileInfo* get_file_info_for_file(File& file); + +// This variant allows the caller to obtain direct access to the encrypted file mapping +// for optimization purposes. +void* mmap(FileDesc fd, size_t size, File::AccessMode access, size_t offset, const char* encryption_key, + EncryptedFileMapping*& mapping); + +void do_encryption_read_barrier(const void* addr, size_t size, HeaderToSize header_to_size, + EncryptedFileMapping* mapping); + +void do_encryption_write_barrier(const void* addr, size_t size, EncryptedFileMapping* mapping); + +void inline encryption_read_barrier(const void* addr, size_t size, EncryptedFileMapping* mapping, + HeaderToSize header_to_size = nullptr) +{ + if (mapping) + do_encryption_read_barrier(addr, size, header_to_size, mapping); +} + +void inline encryption_write_barrier(const void* addr, size_t size, EncryptedFileMapping* mapping) +{ + if (mapping) + do_encryption_write_barrier(addr, size, mapping); +} + + +extern util::Mutex& mapping_mutex; + +inline void do_encryption_read_barrier(const void* addr, size_t size, HeaderToSize header_to_size, + EncryptedFileMapping* mapping) +{ + UniqueLock lock(mapping_mutex); + mapping->read_barrier(addr, size, header_to_size); +} + +inline void do_encryption_write_barrier(const void* addr, size_t size, EncryptedFileMapping* mapping) +{ + LockGuard lock(mapping_mutex); + mapping->write_barrier(addr, size); +} + +#else + +void inline set_page_reclaim_governor(PageReclaimGovernor*) +{ +} + +size_t inline get_num_decrypted_pages() +{ + return 0; +} + +void inline encryption_read_barrier(const void*, size_t, EncryptedFileMapping*, HeaderToSize = nullptr) +{ +} + +void inline encryption_write_barrier(const void*, size_t) +{ +} + +void inline encryption_write_barrier(const void*, size_t, EncryptedFileMapping*) +{ +} + +#endif + +// helpers for encrypted Maps +template +void encryption_read_barrier(File::Map& map, size_t index, size_t num_elements = 1) +{ + T* addr = map.get_addr(); + encryption_read_barrier(addr + index, sizeof(T) * num_elements, map.get_encrypted_mapping()); +} + +template +void encryption_write_barrier(File::Map& map, size_t index, size_t num_elements = 1) +{ + T* addr = map.get_addr(); + encryption_write_barrier(addr + index, sizeof(T) * num_elements, map.get_encrypted_mapping()); +} + +File::SizeType encrypted_size_to_data_size(File::SizeType size) noexcept; +File::SizeType data_size_to_encrypted_size(File::SizeType size) noexcept; + +size_t round_up_to_page_size(size_t size) noexcept; +} +} +#endif diff --git a/!main project/Pods/Realm/include/core/realm/util/fixed_size_buffer.hpp b/!main project/Pods/Realm/include/core/realm/util/fixed_size_buffer.hpp new file mode 100644 index 0000000..8a512ee --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/fixed_size_buffer.hpp @@ -0,0 +1,135 @@ +/************************************************************************* + * + * Copyright 2019 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_FIXED_SIZE_BUFFER_HPP +#define REALM_UTIL_FIXED_SIZE_BUFFER_HPP + +#include +#include +#include + +namespace realm { +namespace util { + +/// This is a buffer with a fixed size. You can only insert elements. +/// When the number of elements inserted matches the size of the buffer, +/// additional insertions will overwrite the oldest elements. +template +class FixedSizeBuffer { +public: + class iterator; + + FixedSizeBuffer(size_t sz) + : m_size(sz) + { + if (sz == 0) + throw std::runtime_error("FixedSizeBuffer size cannot be 0"); + m_buffer.reserve(sz); + } + size_t size() + { + return m_buffer.size(); + } + void insert(const T& val) + { + if (m_buffer.size() < m_size) { + m_buffer.emplace_back(val); + } + else { + m_buffer[m_oldest] = val; + ++m_oldest; + if (m_oldest == m_size) + m_oldest = 0; + } + } + T& at(size_t n) + { + auto idx = (n + m_oldest) % m_size; + return m_buffer[idx]; + } + T& operator[](size_t n) + { + return at(n); + } + iterator begin() + { + return iterator(*this, 0); + } + iterator end() + { + return iterator(*this, m_buffer.size()); + } + +private: + std::vector m_buffer; + size_t m_size; + size_t m_oldest = 0; +}; + +template +class FixedSizeBuffer::iterator { +public: + typedef std::forward_iterator_tag iterator_category; + typedef T value_type; + typedef ptrdiff_t difference_type; + typedef T* pointer; + typedef T& reference; + + iterator(FixedSizeBuffer& b, size_t ndx) + : m_cb(b) + , m_ndx(ndx) + { + } + pointer operator->() + { + return &m_cb[m_ndx]; + } + reference operator*() + { + return m_cb[m_ndx]; + } + iterator& operator++() + { + ++m_ndx; + return *this; + } + iterator operator++(int) + { + iterator tmp(*this); + operator++(); + return tmp; + } + bool operator!=(const iterator& rhs) + { + return m_ndx != rhs.m_ndx; + } + bool operator==(const iterator& rhs) + { + return m_ndx == rhs.m_ndx; + } + +private: + FixedSizeBuffer& m_cb; + size_t m_ndx; +}; + +} // namespace util +} // namespace realm + + +#endif /* REALM_UTIL_FIXED_SIZE_BUFFER_HPP */ diff --git a/!main project/Pods/Realm/include/core/realm/util/flat_map.hpp b/!main project/Pods/Realm/include/core/realm/util/flat_map.hpp new file mode 100644 index 0000000..a3d6b54 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/flat_map.hpp @@ -0,0 +1,201 @@ + +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2017] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ + +#ifndef REALM_UTIL_FLAT_MAP_HPP +#define REALM_UTIL_FLAT_MAP_HPP + +#include +#include // std::pair +#include // std::lower_bound etc. +#include + +#include + +namespace realm { +namespace util { + +template >, class Cmp = std::less<>> +struct FlatMap { + using value_type = std::pair; + using key_type = K; + using mapped_type = V; + FlatMap() {} + FlatMap(const FlatMap&) = default; + FlatMap(FlatMap&&) = default; + FlatMap& operator=(const FlatMap&) = default; + FlatMap& operator=(FlatMap&&) = default; + + V& at(K key) + { + auto it = lower_bound(key); + if (it == end() || it->first != key) + it = m_data.emplace(it, std::move(key), V{}); // Throws + return it->second; + } + + const V& at(const K& key) const + { + auto it = find(key); + if (it == end()) + throw util::out_of_range("no such key"); + return it->second; + } + + V& operator[](const K& key) + { + return at(key); // Throws + } + + using iterator = typename Container::iterator; + using const_iterator = typename Container::const_iterator; + iterator begin() noexcept { return m_data.begin(); } + iterator end() noexcept { return m_data.end(); } + const_iterator begin() const noexcept { return m_data.begin(); } + const_iterator end() const noexcept { return m_data.end(); } + + + bool empty() const noexcept { return m_data.empty(); } + size_t size() const noexcept { return m_data.size(); } + void clear() noexcept { m_data.clear(); } + + std::pair insert(value_type value) + { + auto it = lower_bound(value.first); + if (it != end() && it->first == value.first) { + return std::make_pair(it, false); + } + return std::make_pair(m_data.emplace(it, std::move(value)), true); // Throws + } + + template + std::pair insert(P pair) + { + return insert(value_type{std::get<0>(pair), std::get<1>(pair)}); + } + + template + void insert(InputIt first, InputIt last) + { + for (auto it = first; it != last; ++it) { + insert(*it); + } + } + + template + std::pair emplace(Args&&... args) + { + value_type value{std::forward(args)...}; + return insert(std::move(value)); + } + + template + std::pair emplace_hint(const_iterator pos, Args&&... args) + { + static_cast(pos); // FIXME: TODO + return emplace(std::forward(args)...); + } + + iterator erase(const_iterator pos) noexcept(std::is_nothrow_move_assignable::value) + { + return m_data.erase(pos); + } + + iterator erase(const_iterator first, const_iterator last) noexcept(std::is_nothrow_move_assignable::value) + { + return m_data.erase(first, last); + } + + size_t erase(const K& key) noexcept(std::is_nothrow_move_assignable::value) + { + auto it = find(key); + if (it != end()) { + erase(it); + return 1; + } + return 0; + } + + void swap(FlatMap& other) + { + m_data.swap(other.m_data); + } + + template + size_t count(const Key& key) const noexcept + { + return find(key) == end() ? 0 : 1; + } + + template + iterator find(const Key& key) noexcept + { + const FlatMap* This = this; + const_iterator pos = This->find(key); + return iterator{begin() + (pos - This->begin())}; + } + + template + const_iterator find(const Key& key) const noexcept + { + auto it = lower_bound(key); + if (it != end() && it->first != key) { + return end(); + } + return it; + } + + template + iterator lower_bound(const Key& key) noexcept + { + const FlatMap* This = this; + const_iterator pos = This->lower_bound(key); + return iterator{begin() + (pos - This->begin())}; + } + + template + const_iterator lower_bound(const Key& key) const noexcept + { + auto it = std::lower_bound(begin(), end(), key, [](const value_type& a, const Key& b) { + return Cmp{}(a.first, b); + }); + return it; + } + + // FIXME: Not implemented yet. + template + iterator upper_bound(const Key&) noexcept; + // FIXME: Not implemented yet. + template + const_iterator upper_bound(const Key&) const noexcept; + + void reserve(size_t size) + { + m_data.reserve(size); // Throws + } + +private: + Container m_data; +}; + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_FLAT_MAP_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/get_file_size.hpp b/!main project/Pods/Realm/include/core/realm/util/get_file_size.hpp new file mode 100644 index 0000000..29ca1a7 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/get_file_size.hpp @@ -0,0 +1,45 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2015] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ +#ifndef REALM_UTIL_GET_FILE_SIZE_HPP +#define REALM_UTIL_GET_FILE_SIZE_HPP + +#include + +namespace realm { +namespace util { + +/// FIXME: This function ought to be moved to in the +/// realm-core repository. +util::File::SizeType get_file_size(const std::string& path); + + + +// Implementation + +inline util::File::SizeType get_file_size(const std::string& path) +{ + util::File file{path}; // Throws + return file.get_size(); // Throws +} + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_GET_FILE_SIZE_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/hex_dump.hpp b/!main project/Pods/Realm/include/core/realm/util/hex_dump.hpp new file mode 100644 index 0000000..08f5d2b --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/hex_dump.hpp @@ -0,0 +1,54 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_HEX_DUMP_HPP +#define REALM_UTIL_HEX_DUMP_HPP + +#include +#include +#include +#include +#include +#include + +#include + +namespace realm { +namespace util { + +template +std::string hex_dump(const T* data, size_t size, const char* separator = " ", int min_digits = -1) +{ + using U = typename std::make_unsigned::type; + + if (min_digits < 0) + min_digits = (std::numeric_limits::digits + 3) / 4; + + std::ostringstream out; + for (const T* i = data; i != data + size; ++i) { + if (i != data) + out << separator; + out << std::setw(min_digits) << std::setfill('0') << std::hex << std::uppercase << util::promote(U(*i)); + } + return out.str(); +} + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_HEX_DUMP_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/http.hpp b/!main project/Pods/Realm/include/core/realm/util/http.hpp new file mode 100644 index 0000000..91da876 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/http.hpp @@ -0,0 +1,541 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2016] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ + +#ifndef REALM_UTIL_HTTP_HPP +#define REALM_UTIL_HTTP_HPP + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace realm { +namespace util { +enum class HTTPParserError { + None = 0, + ContentTooLong, + HeaderLineTooLong, + MalformedResponse, + MalformedRequest, + BadRequest, +}; +std::error_code make_error_code(HTTPParserError); +} // namespace util +} // namespace realm + +namespace std { +template<> struct is_error_code_enum : std::true_type {}; +} + +namespace realm { +namespace util { + +/// See: https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html +/// +/// It is guaranteed that the backing integer value of this enum corresponds +/// to the numerical code representing the status. +enum class HTTPStatus { + Unknown = 0, + + Continue = 100, + SwitchingProtocols = 101, + + Ok = 200, + Created = 201, + Accepted = 202, + NonAuthoritative = 203, + NoContent = 204, + ResetContent = 205, + PartialContent = 206, + + MultipleChoices = 300, + MovedPermanently = 301, + Found = 302, + SeeOther = 303, + NotModified = 304, + UseProxy = 305, + SwitchProxy = 306, + TemporaryRedirect = 307, + PermanentRedirect = 308, + + BadRequest = 400, + Unauthorized = 401, + PaymentRequired = 402, + Forbidden = 403, + NotFound = 404, + MethodNotAllowed = 405, + NotAcceptable = 406, + ProxyAuthenticationRequired = 407, + RequestTimeout = 408, + Conflict = 409, + Gone = 410, + LengthRequired = 411, + PreconditionFailed = 412, + PayloadTooLarge = 413, + UriTooLong = 414, + UnsupportedMediaType = 415, + RangeNotSatisfiable = 416, + ExpectationFailed = 417, + ImATeapot = 418, + MisdirectedRequest = 421, + UpgradeRequired = 426, + PreconditionRequired = 428, + TooManyRequests = 429, + RequestHeaderFieldsTooLarge = 431, + UnavailableForLegalReasons = 451, + + InternalServerError = 500, + NotImplemented = 501, + BadGateway = 502, + ServiceUnavailable = 503, + GatewayTimeout = 504, + HttpVersionNotSupported = 505, + VariantAlsoNegotiates = 506, + NotExtended = 510, + NetworkAuthenticationRequired = 511, +}; + +bool valid_http_status_code(unsigned int code); + +/// See: https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html +enum class HTTPMethod { + Options, + Get, + Head, + Post, + Put, + Delete, + Trace, + Connect, +}; + +struct HTTPAuthorization { + std::string scheme; + std::map values; +}; + +HTTPAuthorization parse_authorization(const std::string&); + +class HeterogeneousCaseInsensitiveCompare { +public: + using is_transparent = std::true_type; + template bool operator()(const A& a, const B& b) const noexcept + { + return comp(StringView(a), StringView(b)); + } +private: + bool comp(StringView a, StringView b) const noexcept + { + auto cmp = [](char lhs, char rhs) { + return std::tolower(lhs, std::locale::classic()) < + std::tolower(rhs, std::locale::classic()); + }; + return std::lexicographical_compare(begin(a), end(a), begin(b), end(b), cmp); + } +}; + +/// Case-insensitive map suitable for storing HTTP headers. +using HTTPHeaders = std::map; + +struct HTTPRequest { + HTTPMethod method = HTTPMethod::Get; + HTTPHeaders headers; + std::string path; + + /// If the request object has a body, the Content-Length header MUST be + /// set to a string representation of the number of bytes in the body. + /// FIXME: Relax this restriction, and also support Transfer-Encoding + /// and other HTTP/1.1 features. + Optional body; +}; + +struct HTTPResponse { + HTTPStatus status = HTTPStatus::Unknown; + std::string reason; + HTTPHeaders headers; + + // A body is only read from the response stream if the server sent the + // Content-Length header. + // FIXME: Support other transfer methods, including Transfer-Encoding and + // HTTP/1.1 features. + Optional body; +}; + + +/// Serialize HTTP request to output stream. +std::ostream& operator<<(std::ostream&, const HTTPRequest&); +/// Serialize HTTP response to output stream. +std::ostream& operator<<(std::ostream&, const HTTPResponse&); +/// Serialize HTTP method to output stream ("GET", "POST", etc.). +std::ostream& operator<<(std::ostream&, HTTPMethod); +/// Serialize HTTP status to output stream, include reason string ("200 OK" etc.) +std::ostream& operator<<(std::ostream&, HTTPStatus); + + +struct HTTPParserBase { + util::Logger& logger; + + // FIXME: Generally useful? + struct CallocDeleter { + void operator()(void* ptr) + { + std::free(ptr); + } + }; + + HTTPParserBase(util::Logger& logger_2): + logger {logger_2} + { + // Allocating read buffer with calloc to avoid accidentally spilling + // data from other sessions in case of a buffer overflow exploit. + m_read_buffer.reset(static_cast(std::calloc(read_buffer_size, 1))); + } + virtual ~HTTPParserBase() {} + + std::string m_write_buffer; + std::unique_ptr m_read_buffer; + Optional m_found_content_length; + static const size_t read_buffer_size = 8192; + static const size_t max_header_line_length = read_buffer_size; + + /// Parses the contents of m_read_buffer as a HTTP header line, + /// and calls on_header() as appropriate. on_header() will be called at + /// most once per invocation. + /// Returns false if the contents of m_read_buffer is not a valid HTTP + /// header line. + bool parse_header_line(size_t len); + + virtual std::error_code on_first_line(StringData line) = 0; + virtual void on_header(StringData key, StringData value) = 0; + virtual void on_body(StringData body) = 0; + virtual void on_complete(std::error_code = std::error_code{}) = 0; + + /// If the input matches a known HTTP method string, return the appropriate + /// HTTPMethod enum value. Otherwise, returns none. + static Optional parse_method_string(StringData method); + + /// Interpret line as the first line of an HTTP request. If the return value + /// is true, out_method and out_uri have been assigned the appropriate + /// values found in the request line. + static bool parse_first_line_of_request(StringData line, HTTPMethod& out_method, + StringData& out_uri); + + /// Interpret line as the first line of an HTTP response. If the return + /// value is true, out_status and out_reason have been assigned the + /// appropriate values found in the response line. + static bool parse_first_line_of_response(StringData line, HTTPStatus& out_status, + StringData& out_reason, util::Logger& logger); + + void set_write_buffer(const HTTPRequest&); + void set_write_buffer(const HTTPResponse&); +}; + + +template +struct HTTPParser: protected HTTPParserBase { + explicit HTTPParser(Socket& socket, util::Logger& logger): + HTTPParserBase(logger), + m_socket(socket) + {} + + void read_first_line() + { + auto handler = [this](std::error_code ec, size_t n) { + if (ec == error::operation_aborted) { + return; + } + if (ec) { + on_complete(ec); + return; + } + ec = on_first_line(StringData(m_read_buffer.get(), n)); + if (ec) { + on_complete(ec); + return; + } + read_headers(); + }; + m_socket.async_read_until(m_read_buffer.get(), max_header_line_length, '\n', + std::move(handler)); + } + + void read_headers() + { + auto handler = [this](std::error_code ec, size_t n) { + if (ec == error::operation_aborted) { + return; + } + if (ec) { + on_complete(ec); + return; + } + if (n <= 2) { + read_body(); + return; + } + if (!parse_header_line(n)) { + on_complete(HTTPParserError::BadRequest); + return; + } + + // FIXME: Limit the total size of headers. Apache uses 8K. + read_headers(); + }; + m_socket.async_read_until(m_read_buffer.get(), max_header_line_length, '\n', + std::move(handler)); + } + + void read_body() + { + if (m_found_content_length) { + // FIXME: Support longer bodies. + // FIXME: Support multipart and other body types (no body shaming). + if (*m_found_content_length > read_buffer_size) { + on_complete(HTTPParserError::ContentTooLong); + return; + } + + auto handler = [this](std::error_code ec, size_t n) { + if (ec == error::operation_aborted) { + return; + } + if (!ec) { + on_body(StringData(m_read_buffer.get(), n)); + } + on_complete(ec); + }; + m_socket.async_read(m_read_buffer.get(), *m_found_content_length, + std::move(handler)); + } + else { + // No body, just finish. + on_complete(); + } + } + + void write_buffer(std::function handler) + { + m_socket.async_write(m_write_buffer.data(), m_write_buffer.size(), + std::move(handler)); + } + + Socket& m_socket; +}; + + +template +struct HTTPClient: protected HTTPParser { + using Handler = void(HTTPResponse, std::error_code); + + explicit HTTPClient(Socket& socket, util::Logger& logger) : HTTPParser(socket, logger) {} + + /// Serialize and send \a request over the connected socket asynchronously. + /// + /// When the response has been received, or an error occurs, \a handler will + /// be invoked with the appropriate parameters. The HTTPResponse object + /// passed to \a handler will only be complete in non-error conditions, but + /// may be partially populated. + /// + /// It is an error to start a request before the \a handler of a previous + /// request has been invoked. It is permitted to call async_request() from + /// the handler, unless an error has been reported representing a condition + /// where the underlying socket is no longer able to communicate (for + /// example, if it has been closed). + /// + /// If a request is already in progress, an exception will be thrown. + /// + /// This method is *NOT* thread-safe. + void async_request(const HTTPRequest& request, std::function handler) + { + if (REALM_UNLIKELY(m_handler)) { + throw util::runtime_error("Request already in progress."); + } + this->set_write_buffer(request); + m_handler = std::move(handler); + this->write_buffer([this](std::error_code ec, size_t bytes_written) { + static_cast(bytes_written); + if (ec == error::operation_aborted) { + return; + } + if (ec) { + this->on_complete(ec); + return; + } + this->read_first_line(); + }); + } + +private: + std::function m_handler; + HTTPResponse m_response; + + std::error_code on_first_line(StringData line) override final + { + HTTPStatus status; + StringData reason; + if (this->parse_first_line_of_response(line, status, reason, this->logger)) { + m_response.status = status; + m_response.reason = reason; + return std::error_code{}; + } + return HTTPParserError::MalformedResponse; + } + + void on_header(StringData key, StringData value) override final + { + // FIXME: Multiple headers with the same key should show up as a + // comma-separated list of their values, rather than overwriting. + m_response.headers[std::string(key)] = std::string(value); + } + + void on_body(StringData body) override final + { + m_response.body = std::string(body); + } + + void on_complete(std::error_code ec) override final + { + auto handler = std::move(m_handler); + m_handler = nullptr; + handler(std::move(m_response), ec); + } +}; + + +template +struct HTTPServer: protected HTTPParser { + using RequestHandler = void(HTTPRequest, std::error_code); + using RespondHandler = void(std::error_code); + + explicit HTTPServer(Socket& socket, util::Logger& logger): HTTPParser(socket, logger) + {} + + /// Receive a request on the underlying socket asynchronously. + /// + /// This function starts an asynchronous read operation and keeps reading + /// until an HTTP request has been received. \a handler is invoked when a + /// request has been received, or an error occurs. + /// + /// After a request is received, callers MUST invoke async_send_response() + /// to provide the client with a valid HTTP response, unless the error + /// passed to the handler represents a condition where the underlying socket + /// is no longer able to communicate (for example, if it has been closed). + /// + /// It is an error to attempt to receive a request before any previous + /// requests have been fully responded to, i.e. the \a handler argument of + /// async_send_response() must have been invoked before attempting to + /// receive the next request. + /// + /// This function is *NOT* thread-safe. + void async_receive_request(std::function handler) + { + if (REALM_UNLIKELY(m_request_handler)) { + throw util::runtime_error("Response already in progress."); + } + m_request_handler = std::move(handler); + this->read_first_line(); + } + + /// Send an HTTP response to a client asynchronously. + /// + /// This function starts an asynchronous write operation on the underlying + /// socket. \a handler is invoked when the response has been written to the + /// socket, or an error occurs. + /// + /// It is an error to call async_receive_request() again before \a handler + /// has been invoked, and it is an error to call async_send_response() + /// before the \a handler of a previous invocation has been invoked. + /// + /// This function is *NOT* thread-safe. + void async_send_response(const HTTPResponse& response, + std::function handler) + { + if (REALM_UNLIKELY(!m_request_handler)) { + throw util::runtime_error("No request in progress."); + } + if (m_respond_handler) { + // FIXME: Proper exception type. + throw util::runtime_error("Already responding to request"); + } + m_respond_handler = std::move(handler); + this->set_write_buffer(response); + this->write_buffer([this](std::error_code ec, size_t) { + if (ec == error::operation_aborted) { + return; + } + m_request_handler = nullptr; + auto handler = std::move(m_respond_handler); + handler(ec); + });; + } + +private: + std::function m_request_handler; + std::function m_respond_handler; + HTTPRequest m_request; + + std::error_code on_first_line(StringData line) override final + { + HTTPMethod method; + StringData uri; + if (this->parse_first_line_of_request(line, method, uri)) { + m_request.method = method; + m_request.path = uri; + return std::error_code{}; + } + return HTTPParserError::MalformedRequest; + } + + void on_header(StringData key, StringData value) override final + { + // FIXME: Multiple headers with the same key should show up as a + // comma-separated list of their values, rather than overwriting. + m_request.headers[std::string(key)] = std::string(value); + } + + void on_body(StringData body) override final + { + m_request.body = std::string(body); + } + + void on_complete(std::error_code ec) override final + { + // Deliberately not nullifying m_request_handler so that we can + // check for invariants in async_send_response. + m_request_handler(std::move(m_request), ec); + } +}; + + +std::string make_http_host(bool is_ssl, StringView address, std::uint_fast16_t port); + +} // namespace util +} // namespace realm + + +#endif // REALM_UTIL_HTTP_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/inspect.hpp b/!main project/Pods/Realm/include/core/realm/util/inspect.hpp new file mode 100644 index 0000000..84a669d --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/inspect.hpp @@ -0,0 +1,76 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_INSPECT_HPP +#define REALM_UTIL_INSPECT_HPP + +#include + +namespace realm { +namespace util { + +// LCOV_EXCL_START +// +// Because these are templated functions, every combination of output stream +// type and value(s) type(s) generates a new function. This makes LCOV/GCOVR +// report over 70 functions in this file, with only 6.6% function coverage, +// even though line coverage is at 100%. + +template +void inspect_value(OS& os, const T& value) +{ + os << value; +} + +template +void inspect_value(OS& os, const std::string& value) +{ + // FIXME: Escape the string. + os << "\"" << value << "\""; +} + +template +void inspect_value(OS& os, const char* value) +{ + // FIXME: Escape the string. + os << "\"" << value << "\""; +} + +template +void inspect_all(OS&) +{ + // No-op +} + +/// Convert all arguments to strings, and quote string arguments. +template +void inspect_all(OS& os, First&& first, Args&&... args) +{ + inspect_value(os, std::forward(first)); + if (sizeof...(Args) != 0) { + os << ", "; + } + inspect_all(os, std::forward(args)...); +} + +// LCOV_EXCL_STOP + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_INSPECT_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/interprocess_condvar.hpp b/!main project/Pods/Realm/include/core/realm/util/interprocess_condvar.hpp new file mode 100644 index 0000000..0e5b586 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/interprocess_condvar.hpp @@ -0,0 +1,151 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_INTERPROCESS_CONDVAR +#define REALM_UTIL_INTERPROCESS_CONDVAR + + +#include +#include +#include +#include +#include +#include +#include + +// Condvar Emulation is required if RobustMutex emulation is enabled +#if defined(REALM_ROBUST_MUTEX_EMULATION) || defined(_WIN32) +#define REALM_CONDVAR_EMULATION +#endif + +namespace realm { +namespace util { + + +/// Condition variable for use in synchronization monitors. +/// This condition variable uses emulation based on named pipes +/// for the inter-process case, if enabled by REALM_CONDVAR_EMULATION. +/// +/// FIXME: This implementation will never release/delete pipes. This is unlikely +/// to be a problem as long as only a modest number of different database names +/// are in use +/// +/// A InterprocessCondVar is always process shared. +class InterprocessCondVar { +public: + InterprocessCondVar(); + ~InterprocessCondVar() noexcept; + + // Disable copying. Copying an open file will create a scenario + // where the same file descriptor will be opened once but closed twice. + InterprocessCondVar(const InterprocessCondVar&) = delete; + InterprocessCondVar& operator=(const InterprocessCondVar&) = delete; + +/// To use the InterprocessCondVar, you also must place a structure of type +/// InterprocessCondVar::SharedPart in memory shared by multiple processes +/// or in a memory mapped file, and use set_shared_part() to associate +/// the condition variable with it's shared part. You must initialize +/// the shared part using InterprocessCondVar::init_shared_part(), but only before +/// first use and only when you have exclusive access to the shared part. + +#ifdef REALM_CONDVAR_EMULATION + struct SharedPart { +#ifdef _WIN32 + // Number of waiting threads. + int32_t m_waiters_count; + size_t m_was_broadcast; +#else + uint64_t signal_counter; + uint64_t wait_counter; +#endif + }; +#else + typedef CondVar SharedPart; +#endif + + /// You need to bind the emulation to a SharedPart in shared/mmapped memory. + /// The SharedPart is assumed to have been initialized (possibly by another process) + /// earlier through a call to init_shared_part. + void set_shared_part(SharedPart& shared_part, std::string path, std::string condvar_name, std::string tmp_path); + + /// Initialize the shared part of a process shared condition variable. + /// A process shared condition variables may be represented by any number of + /// InterprocessCondVar instances in any number of different processes, + /// all sharing a common SharedPart instance, which must be in shared memory. + static void init_shared_part(SharedPart& shared_part); + + /// Release any system resources allocated for the shared part. This should + /// be used *only* when you are certain, that nobody is using it. + void release_shared_part(); + + /// Wait for someone to call notify() or notify_all() on this condition + /// variable. The call to wait() may return spuriously, so the caller should + /// always re-evaluate the condition on which to wait and loop on wait() + /// if necessary. + void wait(InterprocessMutex& m, const struct timespec* tp); + + /// If any threads are waiting for this condition, wake up at least one. + /// (Current implementation may actually wake all :-O ). The caller must + /// hold the lock associated with the condvar at the time of calling notify() + void notify() noexcept; + + /// Wake up every thread that is currently waiting on this condition. + /// The caller must hold the lock associated with the condvar at the time + /// of calling notify_all(). + void notify_all() noexcept; + + /// Cleanup and release system resources if possible. + void close() noexcept; + +private: + // non-zero if a shared part has been registered (always 0 on process local instances) + SharedPart* m_shared_part = nullptr; +#ifdef REALM_CONDVAR_EMULATION + // keep the path to allocated system resource so we can remove them again + std::string m_resource_path; + // pipe used for emulation. When using a named pipe, m_fd_read is read-write and m_fd_write is unused. + // When using an anonymous pipe (currently only for tvOS) m_fd_read is read-only and m_fd_write is write-only. + int m_fd_read = -1; + int m_fd_write = -1; + +#ifdef _WIN32 + // Semaphore used to queue up threads waiting for the condition to + // become signaled. + HANDLE m_sema = 0; + // An auto-reset event used by the broadcast/signal thread to wait + // for all the waiting thread(s) to wake up and be released from the + // semaphore. + HANDLE m_waiters_done = 0; + std::string m_name; + + // Serialize access to m_waiters_count + InterprocessMutex m_waiters_lockcount; +#endif + +#endif +}; + + +// Implementation: + + +} // namespace util +} // namespace realm + + +#endif diff --git a/!main project/Pods/Realm/include/core/realm/util/interprocess_mutex.hpp b/!main project/Pods/Realm/include/core/realm/util/interprocess_mutex.hpp new file mode 100644 index 0000000..b3af1c6 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/interprocess_mutex.hpp @@ -0,0 +1,375 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_INTERPROCESS_MUTEX +#define REALM_UTIL_INTERPROCESS_MUTEX + +#include +#include +#include +#include +#include +#include +#include + +// Enable this only on platforms where it might be needed +#if REALM_PLATFORM_APPLE || REALM_ANDROID +#define REALM_ROBUST_MUTEX_EMULATION +#endif + +namespace realm { +namespace util { + +// fwd decl to support friend decl below +class InterprocessCondVar; + + +/// Emulation of a Robust Mutex. +/// A Robust Mutex is an interprocess mutex which will automatically +/// release any locks held by a process when it crashes. Contrary to +/// Posix robust mutexes, this robust mutex is not capable of informing +/// participants that they have been granted a lock after a crash of +/// the process holding it (though it could be added if needed). + +class InterprocessMutex { +public: + InterprocessMutex(); + ~InterprocessMutex() noexcept; + + // Disable copying. Copying a locked Mutex will create a scenario + // where the same file descriptor will be locked once but unlocked twice. + InterprocessMutex(const InterprocessMutex&) = delete; + InterprocessMutex& operator=(const InterprocessMutex&) = delete; + +#if defined(REALM_ROBUST_MUTEX_EMULATION) || defined(_WIN32) + struct SharedPart { + }; +#else + using SharedPart = RobustMutex; +#endif + + /// You need to bind the emulation to a SharedPart in shared/mmapped memory. + /// The SharedPart is assumed to have been initialized (possibly by another process) + /// elsewhere. + void set_shared_part(SharedPart& shared_part, const std::string& path, const std::string& mutex_name); + void set_shared_part(SharedPart& shared_part, File&& lock_file); + + /// Destroy shared object. Potentially release system resources. Caller must + /// ensure that the shared_part is not in use at the point of call. + void release_shared_part(); + + /// Lock the mutex. If the mutex is already locked, wait for it to be unlocked. + void lock(); + + /// Non-blocking attempt to lock the mutex. Returns true if the lock is obtained. + /// If the lock can not be obtained return false immediately. + bool try_lock(); + + /// Unlock the mutex + void unlock(); + + /// Attempt to check if the mutex is valid (only relevant if not emulating) + bool is_valid() noexcept; + + static bool is_robust_on_this_platform() + { +#ifdef REALM_ROBUST_MUTEX_EMULATION + return true; // we're faking it! +#else + return RobustMutex::is_robust_on_this_platform(); +#endif + } + +private: +#ifdef REALM_ROBUST_MUTEX_EMULATION + struct LockInfo { + File m_file; + Mutex m_local_mutex; + LockInfo() {} + ~LockInfo() noexcept; + // Disable copying. + LockInfo(const LockInfo&) = delete; + LockInfo& operator=(const LockInfo&) = delete; + }; + /// InterprocessMutex created on the same file (same inode on POSIX) share the same LockInfo. + /// LockInfo will be saved in a static map as a weak ptr and use the UniqueID as the key. + /// Operations on the map need to be protected by s_mutex + static std::map>* s_info_map; + static Mutex* s_mutex; + /// We manually initialize these static variables when first needed, + /// creating them on the heap so that they last for the entire lifetime + /// of the process. The destructor of these is never called; the + /// process will clean up their memory when exiting. It is not enough + /// to count instances of InterprocessMutex and clean up these statics when + /// the count reaches zero because the program can create more + /// InterprocessMutex instances before the process ends, so we really need + /// these variables for the entire lifetime of the process. + static std::once_flag s_init_flag; + static void initialize_statics(); + + /// Only used for release_shared_part + std::string m_filename; + File::UniqueID m_fileuid; + std::shared_ptr m_lock_info; + + /// Free the lock info hold by this instance. + /// If it is the last reference, underly resources will be freed as well. + void free_lock_info(); +#else + SharedPart* m_shared_part = nullptr; + +#ifdef _WIN32 + HANDLE m_handle = 0; +#endif + +#endif + friend class InterprocessCondVar; +}; + +inline InterprocessMutex::InterprocessMutex() +{ +#ifdef REALM_ROBUST_MUTEX_EMULATION + std::call_once(s_init_flag, initialize_statics); +#endif +} + +inline InterprocessMutex::~InterprocessMutex() noexcept +{ +#ifdef _WIN32 + if (m_handle) { + bool b = CloseHandle(m_handle); + REALM_ASSERT_RELEASE(b); + } +#endif + +#ifdef REALM_ROBUST_MUTEX_EMULATION + free_lock_info(); +#endif +} + +#ifdef REALM_ROBUST_MUTEX_EMULATION +inline InterprocessMutex::LockInfo::~LockInfo() noexcept +{ + if (m_file.is_attached()) { + m_file.close(); + } +} + +inline void InterprocessMutex::free_lock_info() +{ + // It has not been initialized yet. + if (!m_lock_info) + return; + + std::lock_guard guard(*s_mutex); + + m_lock_info.reset(); + if ((*s_info_map)[m_fileuid].expired()) { + s_info_map->erase(m_fileuid); + } + m_filename.clear(); +} + +inline void InterprocessMutex::initialize_statics() +{ + s_mutex = new Mutex(); + s_info_map = new std::map>(); +} +#endif + +inline void InterprocessMutex::set_shared_part(SharedPart& shared_part, const std::string& path, + const std::string& mutex_name) +{ +#ifdef REALM_ROBUST_MUTEX_EMULATION + static_cast(shared_part); + + free_lock_info(); + + m_filename = path + "." + mutex_name + ".mx"; + + std::lock_guard guard(*s_mutex); + + // Try to get the file uid if the file exists + if (File::get_unique_id(m_filename, m_fileuid)) { + auto result = s_info_map->find(m_fileuid); + if (result != s_info_map->end()) { + // File exists and the lock info has been created in the map. + m_lock_info = result->second.lock(); + return; + } + } + + // LockInfo has not been created yet. + m_lock_info = std::make_shared(); + // Always use mod_Write to open file and retreive the uid in case other process + // deletes the file. + m_lock_info->m_file.open(m_filename, File::mode_Write); + m_fileuid = m_lock_info->m_file.get_unique_id(); + + (*s_info_map)[m_fileuid] = m_lock_info; +#elif defined(_WIN32) + if (m_handle) { + bool b = CloseHandle(m_handle); + REALM_ASSERT_RELEASE(b); + } + // replace backslashes because they're significant in object namespace names + std::string path_escaped = path; + std::replace(path_escaped.begin(), path_escaped.end(), '\\', '/'); + std::string name = "Local\\realm_named_intermutex_" + path_escaped + mutex_name; + + std::wstring wname(name.begin(), name.end()); + m_handle = CreateMutexW(0, false, wname.c_str()); + if (!m_handle) { + throw std::system_error(GetLastError(), std::system_category(), "Error opening mutex"); + } +#else + m_shared_part = &shared_part; + static_cast(path); + static_cast(mutex_name); +#endif +} + +inline void InterprocessMutex::set_shared_part(SharedPart& shared_part, File&& lock_file) +{ +#ifdef REALM_ROBUST_MUTEX_EMULATION + static_cast(shared_part); + + free_lock_info(); + + std::lock_guard guard(*s_mutex); + + m_fileuid = lock_file.get_unique_id(); + auto result = s_info_map->find(m_fileuid); + if (result == s_info_map->end()) { + m_lock_info = std::make_shared(); + m_lock_info->m_file = std::move(lock_file); + (*s_info_map)[m_fileuid] = m_lock_info; + } + else { + // File exists and the lock info has been created in the map. + m_lock_info = result->second.lock(); + lock_file.close(); + } +#else + m_shared_part = &shared_part; + static_cast(lock_file); +#endif +} + +inline void InterprocessMutex::release_shared_part() +{ +#ifdef REALM_ROBUST_MUTEX_EMULATION + if (!m_filename.empty()) + File::try_remove(m_filename); + + free_lock_info(); +#else + m_shared_part = nullptr; +#endif +} + +inline void InterprocessMutex::lock() +{ +#ifdef REALM_ROBUST_MUTEX_EMULATION + std::unique_lock mutex_lock(m_lock_info->m_local_mutex); + m_lock_info->m_file.lock_exclusive(); + mutex_lock.release(); +#else + +#ifdef _WIN32 + DWORD d = WaitForSingleObject(m_handle, INFINITE); + REALM_ASSERT_RELEASE(d != WAIT_FAILED); +#else + REALM_ASSERT(m_shared_part); + m_shared_part->lock([]() {}); +#endif +#endif +} + +inline bool InterprocessMutex::try_lock() +{ +#ifdef REALM_ROBUST_MUTEX_EMULATION + std::unique_lock mutex_lock(m_lock_info->m_local_mutex, std::try_to_lock_t()); + if (!mutex_lock.owns_lock()) { + return false; + } + bool success = m_lock_info->m_file.try_lock_exclusive(); + if (success) { + mutex_lock.release(); + return true; + } + else { + return false; + } +#else + +#ifdef _WIN32 + DWORD ret = WaitForSingleObject(m_handle, 0); + REALM_ASSERT_RELEASE(ret != WAIT_FAILED); + + if (ret == WAIT_OBJECT_0) { + return true; + } + else { + return false; + } +#else + REALM_ASSERT(m_shared_part); + return m_shared_part->try_lock([]() {}); +#endif +#endif +} + + +inline void InterprocessMutex::unlock() +{ +#ifdef REALM_ROBUST_MUTEX_EMULATION + m_lock_info->m_file.unlock(); + m_lock_info->m_local_mutex.unlock(); +#else +#ifdef _WIN32 + bool b = ReleaseMutex(m_handle); + REALM_ASSERT_RELEASE(b); +#else + REALM_ASSERT(m_shared_part); + m_shared_part->unlock(); +#endif +#endif +} + + +inline bool InterprocessMutex::is_valid() noexcept +{ +#ifdef REALM_ROBUST_MUTEX_EMULATION + return true; +#elif defined(_WIN32) + // There is no safe way of testing if the m_handle mutex handle is valid on Windows, without having bad side effects + // for the cases where it is indeed invalid. If m_handle contains an arbitrary value, it might by coincidence be equal + // to a real live handle of another kind. This excludes a try_lock implementation and many other ideas. + return true; +#else + REALM_ASSERT(m_shared_part); + return m_shared_part->is_valid(); +#endif +} + + +} // namespace util +} // namespace realm + +#endif // #ifndef REALM_UTIL_INTERPROCESS_MUTEX diff --git a/!main project/Pods/Realm/include/core/realm/util/json_parser.hpp b/!main project/Pods/Realm/include/core/realm/util/json_parser.hpp new file mode 100644 index 0000000..bc6664a --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/json_parser.hpp @@ -0,0 +1,545 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2016] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ + +#ifndef REALM_UTIL_JSON_PARSER_HPP +#define REALM_UTIL_JSON_PARSER_HPP + +#include +#include +#include +#include + +#include + +namespace realm { +namespace util { + +/// A JSON parser that neither allocates heap memory nor throws exceptions. +/// +/// The parser takes as input a range of characters, and emits a stream of events +/// representing the structure of the JSON document. +/// +/// Parser errors are represented as `std::error_condition`s. +class JSONParser { +public: + using InputIterator = const char*; + + enum class EventType { + number, + string, + boolean, + null, + array_begin, + array_end, + object_begin, + object_end + }; + + using Range = StringData; + + struct Event { + EventType type; + Range range; + Event(EventType type): type(type) {} + + union { + bool boolean; + double number; + }; + + StringData escaped_string_value() const noexcept; + + /// Unescape the string value into \a buffer. + /// The type of this event must be EventType::string. + /// + /// \param buffer is a pointer to a buffer big enough to hold the + /// unescaped string value. The unescaped string is guaranteed to be + /// shorter than the escaped string, so escaped_string_value().size() can + /// be used as an upper bound. Unicode sequences of the form "\uXXXX" + /// will be converted to UTF-8 sequences. Note that the escaped form of + /// a unicode point takes exactly 6 bytes, which is also the maximum + /// possible length of a UTF-8 encoded codepoint. + StringData unescape_string(char* buffer) const noexcept; + }; + + enum class Error { + unexpected_token = 1, + unexpected_end_of_stream = 2 + }; + + JSONParser(StringData); + + /// Parse the input data, and call f repeatedly with an argument of type Event + /// representing the token that the parser encountered. + /// + /// The stream of events is "flat", which is to say that it is the responsibility + /// of the function f to keep track of any nested object structures as it deems + /// appropriate. + /// + /// This function is guaranteed to never throw, as long as f never throws. + template + std::error_condition parse(F&& f) noexcept(noexcept(f(std::declval()))); + + class ErrorCategory: public std::error_category { + public: + const char* name() const noexcept final; + std::string message(int) const final; + }; + static const ErrorCategory error_category; +private: + enum Token: char { + object_begin = '{', + object_end = '}', + array_begin = '[', + array_end = ']', + colon = ':', + comma = ',', + dquote = '"', + escape = '\\', + minus = '-', + space = ' ', + tab = '\t', + cr = '\r', + lf = '\n', + }; + + InputIterator m_current; + InputIterator m_end; + + template + std::error_condition parse_object(F&& f) noexcept(noexcept(f(std::declval()))); + template + std::error_condition parse_pair(F&& f) noexcept(noexcept(f(std::declval()))); + template + std::error_condition parse_array(F&& f) noexcept(noexcept(f(std::declval()))); + template + std::error_condition parse_number(F&& f) noexcept(noexcept(f(std::declval()))); + template + std::error_condition parse_string(F&& f) noexcept(noexcept(f(std::declval()))); + template + std::error_condition parse_value(F&& f) noexcept(noexcept(f(std::declval()))); + template + std::error_condition parse_boolean(F&& f) noexcept(noexcept(f(std::declval()))); + template + std::error_condition parse_null(F&& f) noexcept(noexcept(f(std::declval()))); + + std::error_condition expect_token(char, Range& out_range) noexcept; + std::error_condition expect_token(Token, Range& out_range) noexcept; + + // Returns true unless EOF was reached. + bool peek_char(char& out_c) noexcept; + bool peek_token(Token& out_t) noexcept; + bool is_whitespace(Token t) noexcept; + void skip_whitespace() noexcept; +}; + +std::error_condition make_error_condition(JSONParser::Error e); + +} // namespace util +} // namespace realm + +namespace std { +template<> +struct is_error_condition_enum { + static const bool value = true; +}; +} + +namespace realm { +namespace util { + +/// Implementation: + + +inline JSONParser::JSONParser(StringData input): + m_current(input.data()), m_end(input.data() + input.size()) +{ +} + +template +std::error_condition JSONParser::parse(F&& f) noexcept(noexcept(f(std::declval()))) +{ + return parse_value(f); +} + +template +std::error_condition JSONParser::parse_object(F&& f) noexcept(noexcept(f(std::declval()))) +{ + Event event{EventType::object_begin}; + auto ec = expect_token(Token::object_begin, event.range); + if (ec) + return ec; + ec = f(event); + if (ec) + return ec; + + while (true) { + ec = expect_token(Token::object_end, event.range); + if (!ec) { + // End of object + event.type = EventType::object_end; + ec = f(event); + if (ec) + return ec; + break; + } + + if (ec != Error::unexpected_token) + return ec; + + ec = parse_pair(f); + if (ec) + return ec; + + skip_whitespace(); + + Token t; + if (peek_token(t)) { + if (t == Token::object_end) { + // Fine, will terminate on next iteration + } + else if (t == Token::comma) + ++m_current; // OK, because peek_char returned true + else + return Error::unexpected_token; + } + else { + return Error::unexpected_end_of_stream; + } + } + + return std::error_condition{}; +} + +template +std::error_condition JSONParser::parse_pair(F&& f) noexcept(noexcept(f(std::declval()))) +{ + skip_whitespace(); + + auto ec = parse_string(f); + if (ec) + return ec; + + skip_whitespace(); + + Token t; + if (peek_token(t)) { + if (t == Token::colon) { + ++m_current; + } + else { + return Error::unexpected_token; + } + } + + return parse_value(f); +} + +template +std::error_condition JSONParser::parse_array(F&& f) noexcept(noexcept(f(std::declval()))) +{ + Event event{EventType::array_begin}; + auto ec = expect_token(Token::array_begin, event.range); + if (ec) + return ec; + ec = f(event); + if (ec) + return ec; + + while (true) { + ec = expect_token(Token::array_end, event.range); + if (!ec) { + // End of array + event.type = EventType::array_end; + ec = f(event); + if (ec) + return ec; + break; + } + + if (ec != Error::unexpected_token) + return ec; + + ec = parse_value(f); + if (ec) + return ec; + + skip_whitespace(); + + Token t; + if (peek_token(t)) { + if (t == Token::array_end) { + // Fine, will terminate next iteration. + } + else if (t == Token::comma) + ++m_current; // OK, because peek_char returned true + else + return Error::unexpected_token; + } + else { + return Error::unexpected_end_of_stream; + } + } + + return std::error_condition{}; +} + +template +std::error_condition JSONParser::parse_number(F&& f) noexcept(noexcept(f(std::declval()))) +{ + static const size_t buffer_size = 64; + char buffer[buffer_size] = {0}; + size_t bytes_to_copy = std::min(m_end - m_current, buffer_size - 1); + if (bytes_to_copy == 0) + return Error::unexpected_end_of_stream; + + if (std::isspace(*m_current)) { + // JSON has a different idea of what constitutes whitespace than isspace(), + // but strtod() uses isspace() to skip initial whitespace. We have already + // skipped whitespace that JSON considers valid, so if there is any whitespace + // at m_current now, it is invalid according to JSON, and so is an error. + return Error::unexpected_token; + } + + switch (m_current[0]) { + case 'N': + // strtod() parses "NAN", JSON does not. + case 'I': + // strtod() parses "INF", JSON does not. + case 'p': + case 'P': + // strtod() may parse exponent notation, JSON does not. + return Error::unexpected_token; + case '0': + if (bytes_to_copy > 2 && (m_current[1] == 'x' || m_current[1] == 'X')) { + // strtod() parses hexadecimal, JSON does not. + return Error::unexpected_token; + } + } + + std::copy(m_current, m_current + bytes_to_copy, buffer); + + char* endp = nullptr; + Event event{EventType::number}; + event.number = std::strtod(buffer, &endp); + + if (endp == buffer) { + return Error::unexpected_token; + } + size_t num_bytes_consumed = endp - buffer; + m_current += num_bytes_consumed; + return f(event); +} + +template +std::error_condition JSONParser::parse_string(F&& f) noexcept(noexcept(f(std::declval()))) +{ + InputIterator p = m_current; + if (p >= m_end) + return Error::unexpected_end_of_stream; + + auto count_num_escapes_backwards = [](const char* p, const char* begin) -> size_t { + size_t result = 0; + for (; p > begin && *p == Token::escape; ++p) + ++result; + return result; + }; + + Token t = static_cast(*p); + InputIterator inner_end; + if (t == Token::dquote) { + inner_end = m_current; + do { + inner_end = std::find(inner_end + 1, m_end, Token::dquote); + if (inner_end == m_end) + return Error::unexpected_end_of_stream; + } while (count_num_escapes_backwards(inner_end - 1, m_current) % 2 == 1); + + Event event{EventType::string}; + event.range = Range(m_current, inner_end - m_current + 1); + m_current = inner_end + 1; + return f(event); + } + return Error::unexpected_token; +} + +template +std::error_condition JSONParser::parse_boolean(F&& f) noexcept(noexcept(f(std::declval()))) +{ + auto first_nonalpha = std::find_if_not(m_current, m_end, [](auto c) { return std::isalpha(c); }); + + Event event{EventType::boolean}; + event.range = Range(m_current, first_nonalpha - m_current); + if (event.range == "true") { + event.boolean = true; + m_current += 4; + return f(event); + } + else if (event.range == "false") { + event.boolean = false; + m_current += 5; + return f(event); + } + + return Error::unexpected_token; +} + +template +std::error_condition JSONParser::parse_null(F&& f) noexcept(noexcept(f(std::declval()))) +{ + auto first_nonalpha = std::find_if_not(m_current, m_end, [](auto c) { return std::isalpha(c); }); + + Event event{EventType::null}; + event.range = Range(m_current, first_nonalpha - m_current); + if (event.range == "null") { + m_current += 4; + return f(event); + } + + return Error::unexpected_token; +} + +template +std::error_condition JSONParser::parse_value(F&& f) noexcept(noexcept(f(std::declval()))) +{ + skip_whitespace(); + + if (m_current >= m_end) + return Error::unexpected_end_of_stream; + + if (*m_current == Token::object_begin) + return parse_object(f); + + if (*m_current == Token::array_begin) + return parse_array(f); + + if (*m_current == 't' || *m_current == 'f') + return parse_boolean(f); + + if (*m_current == 'n') + return parse_null(f); + + if (*m_current == Token::dquote) + return parse_string(f); + + return parse_number(f); +} + +inline +bool JSONParser::is_whitespace(Token t) noexcept +{ + switch (t) { + case Token::space: + case Token::tab: + case Token::cr: + case Token::lf: + return true; + default: + return false; + } +} + +inline +void JSONParser::skip_whitespace() noexcept +{ + while (m_current < m_end && is_whitespace(static_cast(*m_current))) + ++m_current; +} + +inline +std::error_condition JSONParser::expect_token(char c, Range& out_range) noexcept +{ + skip_whitespace(); + if (m_current == m_end) + return Error::unexpected_end_of_stream; + if (*m_current == c) { + out_range = Range(m_current, 1); + ++m_current; + return std::error_condition{}; + } + return Error::unexpected_token; +} + +inline +std::error_condition JSONParser::expect_token(Token t, Range& out_range) noexcept +{ + return expect_token(static_cast(t), out_range); +} + +inline +bool JSONParser::peek_char(char& out_c) noexcept +{ + if (m_current < m_end) { + out_c = *m_current; + return true; + } + return false; +} + +inline +bool JSONParser::peek_token(Token& out_t) noexcept +{ + if (m_current < m_end) { + out_t = static_cast(*m_current); + return true; + } + return false; +} + +inline +StringData JSONParser::Event::escaped_string_value() const noexcept +{ + REALM_ASSERT(type == EventType::string); + REALM_ASSERT(range.size() >= 2); + return StringData(range.data() + 1, range.size() - 2); +} + +template +OS& operator<<(OS& os, JSONParser::EventType type) +{ + switch (type) { + case JSONParser::EventType::number: os << "number"; return os; + case JSONParser::EventType::string: os << "string"; return os; + case JSONParser::EventType::boolean: os << "boolean"; return os; + case JSONParser::EventType::null: os << "null"; return os; + case JSONParser::EventType::array_begin: os << "["; return os; + case JSONParser::EventType::array_end: os << "]"; return os; + case JSONParser::EventType::object_begin: os << "{"; return os; + case JSONParser::EventType::object_end: os << "}"; return os; + } + REALM_UNREACHABLE(); +} + +template +OS& operator<<(OS& os, const JSONParser::Event& e) { + os << e.type; + switch (e.type) { + case JSONParser::EventType::number: return os << "(" << e.number << ")"; + case JSONParser::EventType::string: return os << "(" << e.range << ")"; + case JSONParser::EventType::boolean: return os << "(" << e.boolean << ")"; + default: return os; + } +} + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_JSON_PARSER_HPP + diff --git a/!main project/Pods/Realm/include/core/realm/util/load_file.hpp b/!main project/Pods/Realm/include/core/realm/util/load_file.hpp new file mode 100644 index 0000000..14090fc --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/load_file.hpp @@ -0,0 +1,36 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2015] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ +#ifndef REALM_UTIL_LOAD_FILE_HPP +#define REALM_UTIL_LOAD_FILE_HPP + +#include + +namespace realm { +namespace util { + +// FIXME: These functions ought to be moved to in the +// realm-core repository. +std::string load_file(const std::string& path); +std::string load_file_and_chomp(const std::string& path); + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_LOAD_FILE_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/logger.hpp b/!main project/Pods/Realm/include/core/realm/util/logger.hpp new file mode 100644 index 0000000..0946208 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/logger.hpp @@ -0,0 +1,511 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_LOGGER_HPP +#define REALM_UTIL_LOGGER_HPP + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace realm { +namespace util { + + +/// All Logger objects store a reference to a LevelThreshold object which it +/// uses to efficiently query about the current log level threshold +/// (`level_threshold.get()`). All messages logged with a level that is lower +/// than the current threshold will be dropped. For the sake of efficiency, this +/// test happens before the message is formatted. +/// +/// A logger is not inherently thread-safe, but specific implementations can be +/// (see ThreadSafeLogger). For a logger to be thread-safe, the implementation +/// of do_log() must be thread-safe and the referenced LevelThreshold object +/// must have a thread-safe get() method. +/// +/// Examples: +/// +/// logger.error("Overlong message from master coordinator"); +/// logger.info("Listening for peers on %1:%2", listen_address, listen_port); +class Logger { +public: + template + void trace(const char* message, Params&&...); + template + void debug(const char* message, Params&&...); + template + void detail(const char* message, Params&&...); + template + void info(const char* message, Params&&...); + template + void warn(const char* message, Params&&...); + template + void error(const char* message, Params&&...); + template + void fatal(const char* message, Params&&...); + + /// Specifies criticality when passed to log(). Functions as a criticality + /// threshold when returned from LevelThreshold::get(). + /// + /// error Be silent unless when there is an error. + /// warn Be silent unless when there is an error or a warning. + /// info Reveal information about what is going on, but in a + /// minimalistic fashion to avoid general overhead from logging + /// and to keep volume down. + /// detail Same as 'info', but prioritize completeness over minimalism. + /// debug Reveal information that can aid debugging, no longer paying + /// attention to efficiency. + /// trace A version of 'debug' that allows for very high volume + /// output. + enum class Level { all, trace, debug, detail, info, warn, error, fatal, off }; + + template + void log(Level, const char* message, Params&&...); + + /// Shorthand for `int(level) >= int(level_threshold.get())`. + bool would_log(Level level) const noexcept; + + class LevelThreshold; + + const LevelThreshold& level_threshold; + + virtual ~Logger() noexcept; + +protected: + Logger(const LevelThreshold&) noexcept; + + static void do_log(Logger&, Level, std::string message); + + virtual void do_log(Level, std::string message) = 0; + + static const char* get_level_prefix(Level) noexcept; + +private: + struct State; + + template + REALM_NOINLINE void do_log(Level, const char* message, Params&&...); + void log_impl(State&); + template + void log_impl(State&, Param&&, Params&&...); + template + static void subst(State&, Param&&); +}; + +template +std::basic_ostream& operator<<(std::basic_ostream&, Logger::Level); + +template +std::basic_istream& operator>>(std::basic_istream&, Logger::Level&); + +class Logger::LevelThreshold { +public: + virtual Level get() const noexcept = 0; +}; + + +/// A root logger that is not thread-safe and allows for the log level threshold +/// to be changed over time. The initial log level threshold is +/// Logger::Level::info. +class RootLogger : private Logger::LevelThreshold, public Logger { +public: + void set_level_threshold(Level) noexcept; + +protected: + RootLogger(); + +private: + Level m_level_threshold = Level::info; + Level get() const noexcept override final; +}; + + +/// A logger that writes to STDERR. This logger is not thread-safe. +/// +/// Since this class is a RootLogger, it contains modifiable a log level +/// threshold. +class StderrLogger : public RootLogger { +protected: + void do_log(Level, std::string) override final; +}; + + +/// A logger that writes to a stream. This logger is not thread-safe. +/// +/// Since this class is a RootLogger, it contains modifiable a log level +/// threshold. +class StreamLogger : public RootLogger { +public: + explicit StreamLogger(std::ostream&) noexcept; + +protected: + void do_log(Level, std::string) override final; + +private: + std::ostream& m_out; +}; + + +/// A logger that writes to a file. This logger is not thread-safe. +/// +/// Since this class is a RootLogger, it contains modifiable a log level +/// threshold. +class FileLogger : public StreamLogger { +public: + explicit FileLogger(std::string path); + explicit FileLogger(util::File); + +private: + util::File m_file; + util::File::Streambuf m_streambuf; + std::ostream m_out; +}; + + +/// A thread-safe logger. This logger ignores the level threshold of the base +/// logger. Instead, it introduces new a LevelThreshold object with a fixed +/// value to achieve thread safety. +class ThreadSafeLogger : private Logger::LevelThreshold, public Logger { +public: + explicit ThreadSafeLogger(Logger& base_logger, Level = Level::info); + +protected: + void do_log(Level, std::string) override final; + +private: + const Level m_level_threshold; // Immutable for thread safety + Logger& m_base_logger; + Mutex m_mutex; + Level get() const noexcept override final; +}; + + +/// A logger that adds a fixed prefix to each message. This logger inherits the +/// LevelThreshold object of the specified base logger. This logger is +/// thread-safe if, and only if the base logger is thread-safe. +class PrefixLogger : public Logger { +public: + PrefixLogger(std::string prefix, Logger& base_logger) noexcept; + +protected: + void do_log(Level, std::string) override final; + +private: + const std::string m_prefix; + Logger& m_base_logger; +}; + + +// Implementation + +struct Logger::State { + Logger::Level m_level; + std::string m_message; + std::string m_search; + int m_param_num = 1; + std::ostringstream m_formatter; + std::locale m_locale = std::locale::classic(); + State(Logger::Level level, const char* s) + : m_level(level) + , m_message(s) + , m_search(m_message) + { + m_formatter.imbue(m_locale); + } +}; + +template +inline void Logger::trace(const char* message, Params&&... params) +{ + log(Level::trace, message, std::forward(params)...); // Throws +} + +template +inline void Logger::debug(const char* message, Params&&... params) +{ + log(Level::debug, message, std::forward(params)...); // Throws +} + +template +inline void Logger::detail(const char* message, Params&&... params) +{ + log(Level::detail, message, std::forward(params)...); // Throws +} + +template +inline void Logger::info(const char* message, Params&&... params) +{ + log(Level::info, message, std::forward(params)...); // Throws +} + +template +inline void Logger::warn(const char* message, Params&&... params) +{ + log(Level::warn, message, std::forward(params)...); // Throws +} + +template +inline void Logger::error(const char* message, Params&&... params) +{ + log(Level::error, message, std::forward(params)...); // Throws +} + +template +inline void Logger::fatal(const char* message, Params&&... params) +{ + log(Level::fatal, message, std::forward(params)...); // Throws +} + +template +inline void Logger::log(Level level, const char* message, Params&&... params) +{ + if (would_log(level)) + do_log(level, message, std::forward(params)...); // Throws +} + +inline bool Logger::would_log(Level level) const noexcept +{ + return int(level) >= int(level_threshold.get()); +} + +inline Logger::~Logger() noexcept +{ +} + +inline Logger::Logger(const LevelThreshold& lt) noexcept + : level_threshold(lt) +{ +} + +inline void Logger::do_log(Logger& logger, Level level, std::string message) +{ + logger.do_log(level, std::move(message)); // Throws +} + +template +void Logger::do_log(Level level, const char* message, Params&&... params) +{ + State state(level, message); + log_impl(state, std::forward(params)...); // Throws +} + +inline void Logger::log_impl(State& state) +{ + do_log(state.m_level, std::move(state.m_message)); // Throws +} + +template +inline void Logger::log_impl(State& state, Param&& param, Params&&... params) +{ + subst(state, std::forward(param)); // Throws + log_impl(state, std::forward(params)...); // Throws +} + +template +void Logger::subst(State& state, Param&& param) +{ + state.m_formatter << "%" << state.m_param_num; + std::string key = state.m_formatter.str(); + state.m_formatter.str(std::string()); + std::string::size_type j = state.m_search.find(key); + if (j != std::string::npos) { + state.m_formatter << std::forward(param); + std::string str = state.m_formatter.str(); + state.m_formatter.str(std::string()); + state.m_message.replace(j, key.size(), str); + state.m_search.replace(j, key.size(), std::string(str.size(), '\0')); + } + ++state.m_param_num; +} + +template +std::basic_ostream& operator<<(std::basic_ostream& out, Logger::Level level) +{ + switch (level) { + case Logger::Level::all: + out << "all"; + return out; + case Logger::Level::trace: + out << "trace"; + return out; + case Logger::Level::debug: + out << "debug"; + return out; + case Logger::Level::detail: + out << "detail"; + return out; + case Logger::Level::info: + out << "info"; + return out; + case Logger::Level::warn: + out << "warn"; + return out; + case Logger::Level::error: + out << "error"; + return out; + case Logger::Level::fatal: + out << "fatal"; + return out; + case Logger::Level::off: + out << "off"; + return out; + } + REALM_ASSERT(false); + return out; +} + +template +std::basic_istream& operator>>(std::basic_istream& in, Logger::Level& level) +{ + std::basic_string str; + auto check = [&](const char* name) { + size_t n = strlen(name); + if (n != str.size()) + return false; + for (size_t i = 0; i < n; ++i) { + if (in.widen(name[i]) != str[i]) + return false; + } + return true; + }; + if (in >> str) { + if (check("all")) { + level = Logger::Level::all; + } + else if (check("trace")) { + level = Logger::Level::trace; + } + else if (check("debug")) { + level = Logger::Level::debug; + } + else if (check("detail")) { + level = Logger::Level::detail; + } + else if (check("info")) { + level = Logger::Level::info; + } + else if (check("warn")) { + level = Logger::Level::warn; + } + else if (check("error")) { + level = Logger::Level::error; + } + else if (check("fatal")) { + level = Logger::Level::fatal; + } + else if (check("off")) { + level = Logger::Level::off; + } + else { + in.setstate(std::ios_base::failbit); + } + } + return in; +} + +inline void RootLogger::set_level_threshold(Level new_level_threshold) noexcept +{ + m_level_threshold = new_level_threshold; +} + +inline RootLogger::RootLogger() + : Logger::LevelThreshold() + , Logger(static_cast(*this)) +{ +} + +inline Logger::Level RootLogger::get() const noexcept +{ + return m_level_threshold; +} + +inline void StderrLogger::do_log(Level level, std::string message) +{ + std::cerr << get_level_prefix(level) << message << '\n'; // Throws + std::cerr.flush(); // Throws +} + +inline StreamLogger::StreamLogger(std::ostream& out) noexcept + : m_out(out) +{ +} + +inline void StreamLogger::do_log(Level level, std::string message) +{ + m_out << get_level_prefix(level) << message << '\n'; // Throws + m_out.flush(); // Throws +} + +inline FileLogger::FileLogger(std::string path) + : StreamLogger(m_out) + , m_file(path, util::File::mode_Write) // Throws + , m_streambuf(&m_file) // Throws + , m_out(&m_streambuf) // Throws +{ +} + +inline FileLogger::FileLogger(util::File file) + : StreamLogger(m_out) + , m_file(std::move(file)) + , m_streambuf(&m_file) // Throws + , m_out(&m_streambuf) // Throws +{ +} + +inline ThreadSafeLogger::ThreadSafeLogger(Logger& base_logger, Level threshold) + : Logger::LevelThreshold() + , Logger(static_cast(*this)) + , m_level_threshold(threshold) + , m_base_logger(base_logger) +{ +} + +inline void ThreadSafeLogger::do_log(Level level, std::string message) +{ + LockGuard l(m_mutex); + Logger::do_log(m_base_logger, level, message); // Throws +} + +inline Logger::Level ThreadSafeLogger::get() const noexcept +{ + return m_level_threshold; +} + +inline PrefixLogger::PrefixLogger(std::string prefix, Logger& base_logger) noexcept + : Logger(base_logger.level_threshold) + , m_prefix(std::move(prefix)) + , m_base_logger(base_logger) +{ +} + +inline void PrefixLogger::do_log(Level level, std::string message) +{ + Logger::do_log(m_base_logger, level, m_prefix + message); // Throws +} + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_LOGGER_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/memory_stream.hpp b/!main project/Pods/Realm/include/core/realm/util/memory_stream.hpp new file mode 100644 index 0000000..81a4852 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/memory_stream.hpp @@ -0,0 +1,212 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_MEMORY_STREAM_HPP +#define REALM_UTIL_MEMORY_STREAM_HPP + +#include +#include +#include +#include + +namespace realm { +namespace util { + +class MemoryInputStreambuf : public std::streambuf { +public: + MemoryInputStreambuf(); + ~MemoryInputStreambuf() noexcept; + + /// Behavior is undefined if the size of the specified buffer exceeds + /// PTRDIFF_MAX. + void set_buffer(const char* begin, const char* end) noexcept; + +private: + const char* m_begin; + const char* m_end; + const char* m_curr; + + int_type underflow() override; + int_type uflow() override; + int_type pbackfail(int_type) override; + std::streamsize showmanyc() override; + pos_type seekoff(off_type, std::ios_base::seekdir, std::ios_base::openmode) override; + pos_type seekpos(pos_type, std::ios_base::openmode) override; + + pos_type do_seekoff(off_type, std::ios_base::seekdir, std::ios_base::openmode); +}; + + +class MemoryOutputStreambuf : public std::streambuf { +public: + MemoryOutputStreambuf(); + ~MemoryOutputStreambuf() noexcept; + + /// Behavior is undefined if the size of the specified buffer exceeds + /// PTRDIFF_MAX. + void set_buffer(char* begin, char* end) noexcept; + + /// Returns the amount of data written to the buffer. + size_t size() const noexcept; +}; + + +class MemoryInputStream : public std::istream { +public: + MemoryInputStream(); + ~MemoryInputStream() noexcept; + + /// \{ Behavior is undefined if the size of the specified buffer exceeds + /// PTRDIFF_MAX. + void set_buffer(const char* begin, const char* end) noexcept; + template void set_buffer(const char (&buffer)[N]) noexcept; + void set_string(const std::string&) noexcept; + void set_c_string(const char* c_str) noexcept; + /// \} + +private: + MemoryInputStreambuf m_streambuf; +}; + + +class MemoryOutputStream : public std::ostream { +public: + MemoryOutputStream(); + ~MemoryOutputStream() noexcept; + + /// \{ Behavior is undefined if the size of the specified buffer exceeds + /// PTRDIFF_MAX. + void set_buffer(char* begin, char* end) noexcept; + template void set_buffer(char (&buffer)[N]) noexcept; + /// \} + + /// Returns the amount of data written to the underlying buffer. + size_t size() const noexcept; + +private: + MemoryOutputStreambuf m_streambuf; +}; + + +// Implementation + +inline MemoryInputStreambuf::MemoryInputStreambuf() + : m_begin(nullptr) + , m_end(nullptr) + , m_curr(nullptr) +{ +} + +inline MemoryInputStreambuf::~MemoryInputStreambuf() noexcept +{ +} + +inline void MemoryInputStreambuf::set_buffer(const char* b, const char* e) noexcept +{ + m_begin = b; + m_end = e; + m_curr = b; +} + + +inline MemoryOutputStreambuf::MemoryOutputStreambuf() +{ +} + +inline MemoryOutputStreambuf::~MemoryOutputStreambuf() noexcept +{ +} + +inline void MemoryOutputStreambuf::set_buffer(char* b, char* e) noexcept +{ + setp(b, e); +} + +inline size_t MemoryOutputStreambuf::size() const noexcept +{ + return pptr() - pbase(); +} + + +inline MemoryInputStream::MemoryInputStream() + : std::istream(&m_streambuf) +{ +} + +inline MemoryInputStream::~MemoryInputStream() noexcept +{ +} + +inline void MemoryInputStream::set_buffer(const char* b, const char* e) noexcept +{ + m_streambuf.set_buffer(b, e); + clear(); +} + +template inline void MemoryInputStream::set_buffer(const char (&buffer)[N]) noexcept +{ + const char* b = buffer; + const char* e = b + N; + set_buffer(b, e); +} + +inline void MemoryInputStream::set_string(const std::string& str) noexcept +{ + const char* b = str.data(); + const char* e = b + str.size(); + set_buffer(b, e); +} + +inline void MemoryInputStream::set_c_string(const char* c_str) noexcept +{ + const char* b = c_str; + const char* e = b + traits_type::length(c_str); + set_buffer(b, e); +} + + +inline MemoryOutputStream::MemoryOutputStream() + : std::ostream(&m_streambuf) +{ +} + +inline MemoryOutputStream::~MemoryOutputStream() noexcept +{ +} + +inline void MemoryOutputStream::set_buffer(char* b, char* e) noexcept +{ + m_streambuf.set_buffer(b, e); + clear(); +} + +template +inline void MemoryOutputStream::set_buffer(char (&buffer)[N]) noexcept +{ + set_buffer(buffer, buffer + N); +} + +inline size_t MemoryOutputStream::size() const noexcept +{ + return m_streambuf.size(); +} + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_MEMORY_STREAM_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/metered/deque.hpp b/!main project/Pods/Realm/include/core/realm/util/metered/deque.hpp new file mode 100644 index 0000000..41c1b71 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/metered/deque.hpp @@ -0,0 +1,37 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2018] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ +#ifndef REALM_UTIL_METERED_DEQUE_HPP +#define REALM_UTIL_METERED_DEQUE_HPP + +#include +#include + +namespace realm { +namespace util { +namespace metered { +/// Vector with metered allocation +template > +using deque = std::deque; +} // namespace metered +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_METERED_DEQUE_HPP + diff --git a/!main project/Pods/Realm/include/core/realm/util/metered/map.hpp b/!main project/Pods/Realm/include/core/realm/util/metered/map.hpp new file mode 100644 index 0000000..be03f9e --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/metered/map.hpp @@ -0,0 +1,41 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2018] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ +#ifndef REALM_UTIL_METERED_MAP_HPP +#define REALM_UTIL_METERED_MAP_HPP + +#include +#include + +namespace realm { +namespace util { +namespace metered { +/// Map with metered allocation. Additionally, the default Compare is changed to +/// `std::less<>` instead of `std::less`, which allows heterogenous lookup. +template , + class Alloc = MeteredSTLAllocator>> +using map = std::map; +} // namespace metered +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_METERED_MAP_HPP + diff --git a/!main project/Pods/Realm/include/core/realm/util/metered/set.hpp b/!main project/Pods/Realm/include/core/realm/util/metered/set.hpp new file mode 100644 index 0000000..95f1e73 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/metered/set.hpp @@ -0,0 +1,38 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2018] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ +#ifndef REALM_UTIL_METERED_SET_HPP +#define REALM_UTIL_METERED_SET_HPP + +#include +#include + +namespace realm { +namespace util { +namespace metered { +/// Set with metered allocation. Additionally, the default Compare is changed to +/// `std::less<>` instead of `std::less`, which allows heterogenous lookup. +template , class Alloc = MeteredSTLAllocator> +using set = std::set; +} // namespace metered +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_METERED_SET_HPP + diff --git a/!main project/Pods/Realm/include/core/realm/util/metered/string.hpp b/!main project/Pods/Realm/include/core/realm/util/metered/string.hpp new file mode 100644 index 0000000..b3a52db --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/metered/string.hpp @@ -0,0 +1,36 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2018] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ +#ifndef REALM_UTIL_METERED_STRING_HPP +#define REALM_UTIL_METERED_STRING_HPP + +#include +#include + +namespace realm { +namespace util { +namespace metered { +/// String with metered allocation +using string = std::basic_string, MeteredSTLAllocator>; +} // namespace metered +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_METERED_STRING_HPP + diff --git a/!main project/Pods/Realm/include/core/realm/util/metered/unordered_map.hpp b/!main project/Pods/Realm/include/core/realm/util/metered/unordered_map.hpp new file mode 100644 index 0000000..8ead21a --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/metered/unordered_map.hpp @@ -0,0 +1,41 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2018] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ +#ifndef REALM_UTIL_METERED_UNORDERED_MAP_HPP +#define REALM_UTIL_METERED_UNORDERED_MAP_HPP + +#include +#include + +namespace realm { +namespace util { +namespace metered { +/// Unordered map with metered allocation +template , + class KeyEqual = std::equal_to, + class Alloc = MeteredSTLAllocator>> +using unordered_map = std::unordered_map; +} // namespace metered +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_METERED_UNORDERED_MAP_HPP + diff --git a/!main project/Pods/Realm/include/core/realm/util/metered/unordered_set.hpp b/!main project/Pods/Realm/include/core/realm/util/metered/unordered_set.hpp new file mode 100644 index 0000000..ad0dc5f --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/metered/unordered_set.hpp @@ -0,0 +1,40 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2018] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ +#ifndef REALM_UTIL_METERED_UNORDERED_SET_HPP +#define REALM_UTIL_METERED_UNORDERED_SET_HPP + +#include +#include + +namespace realm { +namespace util { +namespace metered { +/// Unordered set with metered allocation +template , + class KeyEqual = std::equal_to, + class Alloc = MeteredSTLAllocator> +using unordered_set = std::unordered_set; +} // namespace metered +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_METERED_UNORDERED_SET_HPP + diff --git a/!main project/Pods/Realm/include/core/realm/util/metered/vector.hpp b/!main project/Pods/Realm/include/core/realm/util/metered/vector.hpp new file mode 100644 index 0000000..cbbf177 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/metered/vector.hpp @@ -0,0 +1,37 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2018] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ +#ifndef REALM_UTIL_METERED_VECTOR_HPP +#define REALM_UTIL_METERED_VECTOR_HPP + +#include +#include + +namespace realm { +namespace util { +namespace metered { +/// Vector with metered allocation +template > +using vector = std::vector; +} // namespace metered +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_METERED_VECTOR_HPP + diff --git a/!main project/Pods/Realm/include/core/realm/util/misc_errors.hpp b/!main project/Pods/Realm/include/core/realm/util/misc_errors.hpp new file mode 100644 index 0000000..9335ba9 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/misc_errors.hpp @@ -0,0 +1,49 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_MISC_ERRORS_HPP +#define REALM_UTIL_MISC_ERRORS_HPP + +#include + + +namespace realm { +namespace util { +namespace error { + +enum misc_errors { + unknown = 1, +}; + +std::error_code make_error_code(misc_errors); + +} // namespace error +} // namespace util +} // namespace realm + +namespace std { + +template <> +class is_error_code_enum { +public: + static const bool value = true; +}; + +} // namespace std + +#endif // REALM_UTIL_MISC_ERRORS_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/misc_ext_errors.hpp b/!main project/Pods/Realm/include/core/realm/util/misc_ext_errors.hpp new file mode 100644 index 0000000..6b70998 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/misc_ext_errors.hpp @@ -0,0 +1,72 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2015] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ +#ifndef REALM_UTIL_MISC_EXT_ERRORS_HPP +#define REALM_UTIL_MISC_EXT_ERRORS_HPP + +#include + +namespace realm { +namespace util { + +/// FIXME: The intention is that this enum will be merged into, and subsumed by +/// util::MiscErrors in `` in the core library. +enum class MiscExtErrors { + /// End of input. + end_of_input = 1, + + /// Premature end of input. That is, end of input at an unexpected, or + /// illegal place in an input stream. + premature_end_of_input, + + /// Delimiter not found. + delim_not_found, + + /// Operation not supported + operation_not_supported, +}; + +class MiscExtErrorCategory : public std::error_category { +public: + const char* name() const noexcept override final; + std::string message(int) const override final; +}; + +/// The error category associated with MiscErrors. The name of this category is +/// `realm.util.misc_ext`. +extern MiscExtErrorCategory misc_ext_error_category; + +inline std::error_code make_error_code(MiscExtErrors err) +{ + return std::error_code(int(err), misc_ext_error_category); +} + +} // namespace util +} // namespace realm + +namespace std { + +template<> class is_error_code_enum { +public: + static const bool value = true; +}; + +} // namespace std + +#endif // REALM_UTIL_MISC_EXT_ERRORS_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/miscellaneous.hpp b/!main project/Pods/Realm/include/core/realm/util/miscellaneous.hpp new file mode 100644 index 0000000..c45e4f3 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/miscellaneous.hpp @@ -0,0 +1,49 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_MISCELLANEOUS_HPP +#define REALM_UTIL_MISCELLANEOUS_HPP + +#include + +namespace realm { +namespace util { + +// FIXME: Replace this with std::add_const_t when we switch over to C++14 by +// default. +/// \brief Adds const qualifier, unless T already has the const qualifier +template +using add_const_t = typename std::add_const::type; + +// FIXME: Replace this with std::as_const when we switch over to C++17 by +// default. +/// \brief Forms an lvalue reference to const T +template +constexpr add_const_t& as_const(T& v) noexcept +{ + return v; +} + +/// \brief Disallows rvalue arguments +template +add_const_t& as_const(const T&&) = delete; + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_MISCELLANEOUS_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/network.hpp b/!main project/Pods/Realm/include/core/realm/util/network.hpp new file mode 100644 index 0000000..6ff5b91 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/network.hpp @@ -0,0 +1,3708 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2015] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ +#ifndef REALM_UTIL_NETWORK_HPP +#define REALM_UTIL_NETWORK_HPP + +#include +#include +#include +#include +#include +#include + +#include + +#ifdef _WIN32 +# include +# include +# include +# include +# pragma comment(lib, "Ws2_32.lib") +#else +# include +# include +# include +#endif + +#include +#include +#include +#include +#include +#include +#include + +// Linux epoll +// +// Require Linux kernel version >= 2.6.27 such that we have epoll_create1(), +// `O_CLOEXEC`, and `EPOLLRDHUP`. +#if defined(__linux__) +# include +# if !defined(REALM_HAVE_EPOLL) +# if !defined(REALM_DISABLE_UTIL_NETWORK_EPOLL) +# if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27) +# define REALM_HAVE_EPOLL 1 +# endif +# endif +# endif +#endif +#if !defined(REALM_HAVE_EPOLL) +# define REALM_HAVE_EPOLL 0 +#endif + +// FreeBSD Kqueue. +// +// Available on Mac OS X, FreeBSD, NetBSD, OpenBSD +#if (defined(__MACH__) && defined(__APPLE__)) || defined(__FreeBSD__) || \ + defined(__NetBSD__) || defined(__OpenBSD__) +# if !defined(REALM_HAVE_KQUEUE) +# if !defined(REALM_DISABLE_UTIL_NETWORK_KQUEUE) +# define REALM_HAVE_KQUEUE 1 +# endif +# endif +#endif +#if !defined(REALM_HAVE_KQUEUE) +# define REALM_HAVE_KQUEUE 0 +#endif + + + +// FIXME: Unfinished business around `Address::m_ip_v6_scope_id`. + + +namespace realm { +namespace util { + +/// \brief TCP/IP networking API. +/// +/// The design of this networking API is heavily inspired by the ASIO C++ +/// library (http://think-async.com). +/// +/// +/// ### Thread safety +/// +/// A *service context* is a set of objects consisting of an instance of +/// Service, and all the objects that are associated with that instance (\ref +/// Resolver, \ref Socket`, \ref Acceptor`, \ref DeadlineTimer, \ref Trigger, +/// and \ref ssl::Stream). +/// +/// In general, it is unsafe for two threads to call functions on the same +/// object, or on different objects in the same service context. This also +/// applies to destructors. Notable exceptions are the fully thread-safe +/// functions, such as Service::post(), Service::stop(), and Service::reset(). +/// +/// On the other hand, it is always safe for two threads to call functions on +/// objects belonging to different service contexts. +/// +/// One implication of these rules is that at most one thread must execute run() +/// at any given time, and if one thread is executing run(), then no other +/// thread is allowed to access objects in the same service context (with the +/// mentioned exceptions). +/// +/// Unless otherwise specified, free-standing objects, such as \ref +/// StreamProtocol, \ref Address, \ref Endpoint, and \ref Endpoint::List are +/// fully thread-safe as long as they are not mutated. If one thread is mutating +/// such an object, no other thread may access it. Note that these free-standing +/// objects are not associcated with an instance of Service, and are therefore +/// not part of a service context. +/// +/// +/// ### Comparison with ASIO +/// +/// There is a crucial difference between the two libraries in regards to the +/// guarantees that are provided about the cancelability of asynchronous +/// operations. The Realm networking library (this library) considers an +/// asynchronous operation to be complete precisely when the completion handler +/// starts to execute, and it guarantees that such an operation is cancelable up +/// until that point in time. In particular, if `cancel()` is called on a socket +/// or a deadline timer object before the completion handler starts to execute, +/// then that operation will be canceled, and will receive +/// `error::operation_aborted`. This guarantee is possible to provide (and free +/// of ambiguities) precisely because this library prohibits multiple threads +/// from executing the event loop concurrently, and because `cancel()` is +/// allowed to be called only from a completion handler (executed by the event +/// loop thread) or while no thread is executing the event loop. This guarantee +/// allows for safe destruction of sockets and deadline timers as long as the +/// completion handlers react appropriately to `error::operation_aborted`, in +/// particular, that they do not attempt to access the socket or deadline timer +/// object in such cases. +/// +/// ASIO, on the other hand, allows for an asynchronous operation to complete +/// and become **uncancellable** before the completion handler starts to +/// execute. For this reason, it is possible with ASIO to get the completion +/// handler of an asynchronous wait operation to start executing and receive an +/// error code other than "operation aborted" at a point in time where +/// `cancel()` has already been called on the deadline timer object, or even at +/// a point in timer where the deadline timer has been destroyed. This seems +/// like an inevitable consequence of the fact that ASIO allows for multiple +/// threads to execute the event loop concurrently. This generally forces ASIO +/// applications to invent ways of extending the lifetime of deadline timer and +/// socket objects until the completion handler starts executing. +/// +/// IMPORTANT: Even if ASIO is used in a way where at most one thread executes +/// the event loop, there is still no guarantee that an asynchronous operation +/// remains cancelable up until the point in time where the completion handler +/// starts to execute. +namespace network { + +std::string host_name(); + + +class StreamProtocol; +class Address; +class Endpoint; +class Service; +class Resolver; +class SocketBase; +class Socket; +class Acceptor; +class DeadlineTimer; +class Trigger; +class ReadAheadBuffer; +namespace ssl { +class Stream; +} // namespace ssl + + +/// \brief An IP protocol descriptor. +class StreamProtocol { +public: + static StreamProtocol ip_v4(); + static StreamProtocol ip_v6(); + + bool is_ip_v4() const; + bool is_ip_v6() const; + + int protocol() const; + int family() const; + + StreamProtocol(); + ~StreamProtocol() noexcept {} + +private: + int m_family; + int m_socktype; + int m_protocol; + + friend class Service; + friend class SocketBase; +}; + + +/// \brief An IP address (IPv4 or IPv6). +class Address { +public: + bool is_ip_v4() const; + bool is_ip_v6() const; + + template + friend std::basic_ostream& operator<<(std::basic_ostream&, const Address&); + + Address(); + ~Address() noexcept {} + +private: + using ip_v4_type = in_addr; + using ip_v6_type = in6_addr; + union union_type { + ip_v4_type m_ip_v4; + ip_v6_type m_ip_v6; + }; + union_type m_union; + std::uint_least32_t m_ip_v6_scope_id = 0; + bool m_is_ip_v6 = false; + + friend Address make_address(const char*, std::error_code&) noexcept; + friend class Endpoint; +}; + +Address make_address(const char* c_str); +Address make_address(const char* c_str, std::error_code& ec) noexcept; +Address make_address(const std::string&); +Address make_address(const std::string&, std::error_code& ec) noexcept; + + +/// \brief An IP endpoint. +/// +/// An IP endpoint is a triplet (`protocol`, `address`, `port`). +class Endpoint { +public: + using port_type = std::uint_fast16_t; + class List; + + StreamProtocol protocol() const; + Address address() const; + port_type port() const; + + Endpoint(); + Endpoint(const StreamProtocol&, port_type); + Endpoint(const Address&, port_type); + ~Endpoint() noexcept {} + + using data_type = sockaddr; + data_type* data(); + const data_type* data() const; + +private: + StreamProtocol m_protocol; + + using sockaddr_base_type = sockaddr; + using sockaddr_ip_v4_type = sockaddr_in; + using sockaddr_ip_v6_type = sockaddr_in6; + union sockaddr_union_type { + sockaddr_base_type m_base; + sockaddr_ip_v4_type m_ip_v4; + sockaddr_ip_v6_type m_ip_v6; + }; + sockaddr_union_type m_sockaddr_union; + + friend class Service; + friend class Resolver; + friend class SocketBase; + friend class Socket; +}; + + +/// \brief A list of IP endpoints. +class Endpoint::List { +public: + using iterator = const Endpoint*; + + iterator begin() const noexcept; + iterator end() const noexcept; + std::size_t size() const noexcept; + bool empty() const noexcept; + + List() noexcept = default; + List(List&&) noexcept = default; + ~List() noexcept = default; + + List& operator=(List&&) noexcept = default; + +private: + Buffer m_endpoints; + + friend class Service; +}; + + +/// \brief TCP/IP networking service. +class Service { +public: + Service(); + ~Service() noexcept; + + /// \brief Execute the event loop. + /// + /// Execute completion handlers of completed asynchronous operations, or + /// wait for more completion handlers to become ready for + /// execution. Handlers submitted via post() are considered immeditely + /// ready. If there are no completion handlers ready for execution, and + /// there are no asynchronous operations in progress, run() returns. + /// + /// All completion handlers, including handlers submitted via post() will be + /// executed from run(), that is, by the thread that executes run(). If no + /// thread executes run(), then the completion handlers will not be + /// executed. + /// + /// Exceptions thrown by completion handlers will always propagate back + /// through run(). + /// + /// Syncronous operations (e.g., Socket::connect()) execute independently of + /// the event loop, and do not require that any thread calls run(). + void run(); + + /// @{ \brief Stop event loop execution. + /// + /// stop() puts the event loop into the stopped mode. If a thread is + /// currently executing run(), it will be made to return in a timely + /// fashion, that is, without further blocking. If a thread is currently + /// blocked in run(), it will be unblocked. Handlers that can be executed + /// immediately, may, or may not be executed before run() returns, but new + /// handlers submitted by these, will not be executed before run() + /// returns. Also, if a handler is submitted by a call to post, and that + /// call happens after stop() returns, then that handler is guaranteed to + /// not be executed before run() returns (assuming that reset() is not called + /// before run() returns). + /// + /// The event loop will remain in the stopped mode until reset() is + /// called. If reset() is called before run() returns, it may, or may not + /// cause run() to resume normal operation without returning. + /// + /// Both stop() and reset() are thread-safe, that is, they may be called by + /// any thread. Also, both of these function may be called from completion + /// handlers (including posted handlers). + void stop() noexcept; + void reset() noexcept; + /// @} + + /// \brief Submit a handler to be executed by the event loop thread. + /// + /// Register the sepcified completion handler for immediate asynchronous + /// execution. The specified handler will be executed by an expression on + /// the form `handler()`. If the the handler object is movable, it will + /// never be copied. Otherwise, it will be copied as necessary. + /// + /// This function is thread-safe, that is, it may be called by any + /// thread. It may also be called from other completion handlers. + /// + /// The handler will never be called as part of the execution of post(). It + /// will always be called by a thread that is executing run(). If no thread + /// is currently executing run(), the handler will not be executed until a + /// thread starts executing run(). If post() is called while another thread + /// is executing run(), the handler may be called before post() returns. If + /// post() is called from another completion handler, the submitted handler + /// is guaranteed to not be called during the execution of post(). + /// + /// Completion handlers added through post() will be executed in the order + /// that they are added. More precisely, if post() is called twice to add + /// two handlers, A and B, and the execution of post(A) ends before the + /// beginning of the execution of post(B), then A is guaranteed to execute + /// before B. + template void post(H handler); + + /// Argument `saturation` is the fraction of time that is not spent + /// sleeping. Argument `inefficiency` is the fraction of time not spent + /// sleeping, and not spent executing completion handlers. Both values are + /// guaranteed to always be in the range 0 to 1 (both inclusive). The value + /// passed as `inefficiency` is guaranteed to always be less than, or equal + /// to the value passed as `saturation`. + using EventLoopMetricsHandler = void(double saturation, double inefficiency); + + /// \brief Report event loop metrics via the specified handler. + /// + /// The handler will be called approximately every 30 seconds. + /// + /// report_event_loop_metrics() must be called prior to any invocation of + /// run(). report_event_loop_metrics() is not thread-safe. + /// + /// This feature is only available if + /// `REALM_UTIL_NETWORK_EVENT_LOOP_METRICS` was defined during + /// compilation. When the feature is not available, the specified handler + /// will never be called. + void report_event_loop_metrics(std::function); + +private: + enum class Want { nothing = 0, read, write }; + + template class OperQueue; + class Descriptor; + class AsyncOper; + class ResolveOperBase; + class WaitOperBase; + class TriggerExecOperBase; + class PostOperBase; + template class PostOper; + class IoOper; + class UnusedOper; // Allocated, but currently unused memory + + template class BasicStreamOps; + + struct OwnersOperDeleter { + void operator()(AsyncOper*) const noexcept; + }; + struct LendersOperDeleter { + void operator()(AsyncOper*) const noexcept; + }; + using OwnersOperPtr = std::unique_ptr; + using LendersOperPtr = std::unique_ptr; + using LendersResolveOperPtr = std::unique_ptr; + using LendersWaitOperPtr = std::unique_ptr; + using LendersIoOperPtr = std::unique_ptr; + + class IoReactor; + class Impl; + const std::unique_ptr m_impl; + + template + static std::unique_ptr alloc(OwnersOperPtr&, Args&&...); + + using PostOperConstr = PostOperBase*(void* addr, std::size_t size, Impl&, void* cookie); + void do_post(PostOperConstr, std::size_t size, void* cookie); + template + static PostOperBase* post_oper_constr(void* addr, std::size_t size, Impl&, void* cookie); + static void recycle_post_oper(Impl&, PostOperBase*) noexcept; + static void trigger_exec(Impl&, TriggerExecOperBase&) noexcept; + static void reset_trigger_exec(Impl&, TriggerExecOperBase&) noexcept; + + using clock = std::chrono::steady_clock; + + friend class Resolver; + friend class SocketBase; + friend class Socket; + friend class Acceptor; + friend class DeadlineTimer; + friend class Trigger; + friend class ReadAheadBuffer; + friend class ssl::Stream; +}; + + +template class Service::OperQueue { +public: + using LendersOperPtr = std::unique_ptr; + bool empty() const noexcept; + void push_back(LendersOperPtr) noexcept; + template void push_back(OperQueue&) noexcept; + LendersOperPtr pop_front() noexcept; + void clear() noexcept; + OperQueue() noexcept = default; + OperQueue(OperQueue&&) noexcept; + ~OperQueue() noexcept; +private: + Oper* m_back = nullptr; + template friend class OperQueue; +}; + + +class Service::Descriptor { +public: +#ifdef _WIN32 + using native_handle_type = SOCKET; +#else + using native_handle_type = int; +#endif + + Impl& service_impl; + + Descriptor(Impl& service) noexcept; + ~Descriptor() noexcept; + + /// \param in_blocking_mode Must be true if, and only if the passed file + /// descriptor refers to a file description in which the file status flag + /// O_NONBLOCK is not set. + /// + /// The passed file descriptor must have the file descriptor flag FD_CLOEXEC + /// set. + void assign(native_handle_type fd, bool in_blocking_mode) noexcept; + void close() noexcept; + native_handle_type release() noexcept; + + bool is_open() const noexcept; + + native_handle_type native_handle() const noexcept; + bool in_blocking_mode() const noexcept; + + void accept(Descriptor&, StreamProtocol, Endpoint*, std::error_code&) noexcept; + std::size_t read_some(char* buffer, std::size_t size, std::error_code&) noexcept; + std::size_t write_some(const char* data, std::size_t size, std::error_code&) noexcept; + + /// \tparam Oper An operation type inherited from IoOper with an initate() + /// function that initiates the operation and figures out whether it needs + /// to read from, or write to the underlying descriptor to + /// proceed. `initiate()` must return Want::read if the operation needs to + /// read, or Want::write if the operation needs to write. If the operation + /// completes immediately (e.g. due to a failure during initialization), + /// `initiate()` must return Want::nothing. + template + void initiate_oper(std::unique_ptr, Args&&...); + + void ensure_blocking_mode(); + void ensure_nonblocking_mode(); + +private: + native_handle_type m_fd = -1; + bool m_in_blocking_mode; // Not in nonblocking mode + +#if REALM_HAVE_EPOLL || REALM_HAVE_KQUEUE + bool m_read_ready; + bool m_write_ready; + bool m_imminent_end_of_input; // Kernel has seen the end of input + bool m_is_registered; + OperQueue m_suspended_read_ops, m_suspended_write_ops; + + void deregister_for_async() noexcept; +#endif + + bool assume_read_would_block() const noexcept; + bool assume_write_would_block() const noexcept; + + void set_read_ready(bool) noexcept; + void set_write_ready(bool) noexcept; + + void set_nonblock_flag(bool value); + void add_initiated_oper(LendersIoOperPtr, Want); + + void do_close() noexcept; + native_handle_type do_release() noexcept; + + friend class IoReactor; +}; + + +class Resolver { +public: + class Query; + + Resolver(Service&); + ~Resolver() noexcept; + + /// Thread-safe. + Service& get_service() noexcept; + + /// @{ \brief Resolve the specified query to one or more endpoints. + Endpoint::List resolve(const Query&); + Endpoint::List resolve(const Query&, std::error_code&); + /// @} + + /// \brief Perform an asynchronous resolve operation. + /// + /// Initiate an asynchronous resolve operation. The completion handler will + /// be called when the operation completes. The operation completes when it + /// succeeds, or an error occurs. + /// + /// The completion handler is always executed by the event loop thread, + /// i.e., by a thread that is executing Service::run(). Conversely, the + /// completion handler is guaranteed to not be called while no thread is + /// executing Service::run(). The execution of the completion handler is + /// always deferred to the event loop, meaning that it never happens as a + /// synchronous side effect of the execution of async_resolve(), even when + /// async_resolve() is executed by the event loop thread. The completion + /// handler is guaranteed to be called eventually, as long as there is time + /// enough for the operation to complete or fail, and a thread is executing + /// Service::run() for long enough. + /// + /// The operation can be canceled by calling cancel(), and will be + /// automatically canceled if the resolver object is destroyed. If the + /// operation is canceled, it will fail with `error::operation_aborted`. The + /// operation remains cancelable up until the point in time where the + /// completion handler starts to execute. This means that if cancel() is + /// called before the completion handler starts to execute, then the + /// completion handler is guaranteed to have `error::operation_aborted` + /// passed to it. This is true regardless of whether cancel() is called + /// explicitly or implicitly, such as when the resolver is destroyed. + /// + /// The specified handler will be executed by an expression on the form + /// `handler(ec, endpoints)` where `ec` is the error code and `endpoints` is + /// an object of type `Endpoint::List`. If the the handler object is + /// movable, it will never be copied. Otherwise, it will be copied as + /// necessary. + /// + /// It is an error to start a new resolve operation (synchronous or + /// asynchronous) while an asynchronous resolve operation is in progress via + /// the same resolver object. An asynchronous resolve operation is + /// considered complete as soon as the completion handler starts to + /// execute. This means that a new resolve operation can be started from the + /// completion handler. + template void async_resolve(Query, H handler); + + /// \brief Cancel all asynchronous operations. + /// + /// Cause all incomplete asynchronous operations, that are associated with + /// this resolver (at most one), to fail with `error::operation_aborted`. An + /// asynchronous operation is complete precisely when its completion handler + /// starts executing. + /// + /// Completion handlers of canceled operations will become immediately ready + /// to execute, but will never be executed directly as part of the execution + /// of cancel(). + /// + /// Cancellation happens automatically when the resolver object is destroyed. + void cancel() noexcept; + +private: + template class ResolveOper; + + Service::Impl& m_service_impl; + + Service::OwnersOperPtr m_resolve_oper; + + void initiate_oper(Service::LendersResolveOperPtr); +}; + + +class Resolver::Query { +public: + enum { + /// Locally bound socket endpoint (server side) + passive = AI_PASSIVE, + + /// Ignore families without a configured non-loopback address + address_configured = AI_ADDRCONFIG + }; + + Query(std::string service_port, int init_flags = passive|address_configured); + Query(const StreamProtocol&, std::string service_port, + int init_flags = passive|address_configured); + Query(std::string host_name, std::string service_port, + int init_flags = address_configured); + Query(const StreamProtocol&, std::string host_name, std::string service_port, + int init_flags = address_configured); + + ~Query() noexcept; + + int flags() const; + StreamProtocol protocol() const; + std::string host() const; + std::string service() const; + +private: + int m_flags; + StreamProtocol m_protocol; + std::string m_host; // hostname + std::string m_service; // port + + friend class Service; +}; + + +class SocketBase { +public: + using native_handle_type = Service::Descriptor::native_handle_type; + + ~SocketBase() noexcept; + + /// Thread-safe. + Service& get_service() noexcept; + + bool is_open() const noexcept; + native_handle_type native_handle() const noexcept; + + /// @{ \brief Open the socket for use with the specified protocol. + /// + /// It is an error to call open() on a socket that is already open. + void open(const StreamProtocol&); + std::error_code open(const StreamProtocol&, std::error_code&); + /// @} + + /// \brief Close this socket. + /// + /// If the socket is open, it will be closed. If it is already closed (or + /// never opened), this function does nothing (idempotency). + /// + /// A socket is automatically closed when destroyed. + /// + /// When the socket is closed, any incomplete asynchronous operation will be + /// canceled (as if cancel() was called). + void close() noexcept; + + /// \brief Cancel all asynchronous operations. + /// + /// Cause all incomplete asynchronous operations, that are associated with + /// this socket, to fail with `error::operation_aborted`. An asynchronous + /// operation is complete precisely when its completion handler starts + /// executing. + /// + /// Completion handlers of canceled operations will become immediately ready + /// to execute, but will never be executed directly as part of the execution + /// of cancel(). + void cancel() noexcept; + + template + void get_option(O& opt) const; + + template + std::error_code get_option(O& opt, std::error_code&) const; + + template + void set_option(const O& opt); + + template + std::error_code set_option(const O& opt, std::error_code&); + + void bind(const Endpoint&); + std::error_code bind(const Endpoint&, std::error_code&); + + Endpoint local_endpoint() const; + Endpoint local_endpoint(std::error_code&) const; + + /// Release the ownership of this socket object over the native handle and + /// return the native handle to the caller. The caller assumes ownership + /// over the returned handle. The socket is left in a closed + /// state. Incomplete asynchronous operations will be canceled as if close() + /// had been called. + /// + /// If called on a closed socket, this function is a no-op, and returns the + /// same value as would be returned by native_handle() + native_handle_type release_native_handle() noexcept; + +private: + enum opt_enum { + opt_ReuseAddr, ///< `SOL_SOCKET`, `SO_REUSEADDR` + opt_Linger, ///< `SOL_SOCKET`, `SO_LINGER` + opt_NoDelay, ///< `IPPROTO_TCP`, `TCP_NODELAY` (disable the Nagle algorithm) + }; + + template class Option; + +public: + using reuse_address = Option; + using no_delay = Option; + + // linger struct defined by POSIX sys/socket.h. + struct linger_opt; + using linger = Option; + +protected: + Service::Descriptor m_desc; + +private: + StreamProtocol m_protocol; + +protected: + Service::OwnersOperPtr m_read_oper; // Read or accept + Service::OwnersOperPtr m_write_oper; // Write or connect + + SocketBase(Service&); + + const StreamProtocol& get_protocol() const noexcept; + std::error_code do_assign(const StreamProtocol&, native_handle_type, std::error_code&); + void do_close() noexcept; + + void get_option(opt_enum, void* value_data, std::size_t& value_size, std::error_code&) const; + void set_option(opt_enum, const void* value_data, std::size_t value_size, std::error_code&); + void map_option(opt_enum, int& level, int& option_name) const; + + friend class Acceptor; +}; + + +template class SocketBase::Option { +public: + Option(T value = T()); + T value() const; + +private: + T m_value; + + void get(const SocketBase&, std::error_code&); + void set(SocketBase&, std::error_code&) const; + + friend class SocketBase; +}; + +struct SocketBase::linger_opt { + linger_opt(bool enable, int timeout_seconds = 0) + { + m_linger.l_onoff = enable ? 1 : 0; + m_linger.l_linger = timeout_seconds; + } + + ::linger m_linger; + + operator ::linger() const { return m_linger; } + + bool enabled() const { return m_linger.l_onoff != 0; } + int timeout() const { return m_linger.l_linger; } +}; + + +/// Switching between synchronous and asynchronous operations is allowed, but +/// only in a nonoverlapping fashion. That is, a synchronous operation is not +/// allowed to run concurrently with an asynchronous one on the same +/// socket. Note that an asynchronous operation is considered to be running +/// until its completion handler starts executing. +class Socket : public SocketBase { +public: + Socket(Service&); + + /// \brief Create a socket with an already-connected native socket handle. + /// + /// This constructor is shorthand for creating the socket with the + /// one-argument constructor, and then calling the two-argument assign() + /// with the specified protocol and native handle. + Socket(Service&, const StreamProtocol&, native_handle_type); + + ~Socket() noexcept; + + void connect(const Endpoint&); + std::error_code connect(const Endpoint&, std::error_code&); + + /// @{ \brief Perform a synchronous read operation. + /// + /// read() will not return until the specified buffer is full, or an error + /// occurs. Reaching the end of input before the buffer is filled, is + /// considered an error, and will cause the operation to fail with + /// MiscExtErrors::end_of_input. + /// + /// read_until() will not return until the specified buffer contains the + /// specified delimiter, or an error occurs. If the buffer is filled before + /// the delimiter is found, the operation fails with + /// MiscExtErrors::delim_not_found. Otherwise, if the end of input is + /// reached before the delimiter is found, the operation fails with + /// MiscExtErrors::end_of_input. If the operation succeeds, the last byte + /// placed in the buffer is the delimiter. + /// + /// The versions that take a ReadAheadBuffer argument will read through that + /// buffer. This allows for fewer larger reads on the underlying + /// socket. Since unconsumed data may be left in the read-ahead buffer after + /// a read operation returns, it is important that the same read-ahead + /// buffer is passed to the next read operation. + /// + /// The versions of read() and read_until() that do not take an + /// `std::error_code&` argument will throw std::system_error on failure. + /// + /// The versions that do take an `std::error_code&` argument will set \a ec + /// to `std::error_code()` on success, and to something else on failure. On + /// failure they will return the number of bytes placed in the specified + /// buffer before the error occured. + /// + /// \return The number of bytes places in the specified buffer upon return. + std::size_t read(char* buffer, std::size_t size); + std::size_t read(char* buffer, std::size_t size, std::error_code& ec); + std::size_t read(char* buffer, std::size_t size, ReadAheadBuffer&); + std::size_t read(char* buffer, std::size_t size, ReadAheadBuffer&, std::error_code& ec); + std::size_t read_until(char* buffer, std::size_t size, char delim, ReadAheadBuffer&); + std::size_t read_until(char* buffer, std::size_t size, char delim, ReadAheadBuffer&, + std::error_code& ec); + /// @} + + /// @{ \brief Perform a synchronous write operation. + /// + /// write() will not return until all the specified bytes have been written + /// to the socket, or an error occurs. + /// + /// The versions of write() that does not take an `std::error_code&` + /// argument will throw std::system_error on failure. When it succeeds, it + /// always returns \a size. + /// + /// The versions that does take an `std::error_code&` argument will set \a + /// ec to `std::error_code()` on success, and to something else on + /// failure. On success it returns \a size. On faulure it returns the number + /// of bytes written before the failure occured. + std::size_t write(const char* data, std::size_t size); + std::size_t write(const char* data, std::size_t size, std::error_code& ec); + /// @} + + /// @{ \brief Read at least one byte from this socket. + /// + /// If \a size is zero, both versions of read_some() will return zero + /// without blocking. Read errors may or may not be detected in this case. + /// + /// Otherwise, if \a size is greater than zero, and at least one byte is + /// immediately available, that is, without blocking, then both versions + /// will read at least one byte (but generally as many immediately available + /// bytes as will fit into the specified buffer), and return without + /// blocking. + /// + /// Otherwise, both versions will block the calling thread until at least one + /// byte becomes available, or an error occurs. + /// + /// In this context, it counts as an error, if the end of input is reached + /// before at least one byte becomes available (see + /// MiscExtErrors::end_of_input). + /// + /// If no error occurs, both versions will return the number of bytes placed + /// in the specified buffer, which is generally as many as are immediately + /// available at the time when the first byte becomes available, although + /// never more than \a size. + /// + /// If no error occurs, the three-argument version will set \a ec to + /// indicate success. + /// + /// If an error occurs, the two-argument version will throw + /// `std::system_error`, while the three-argument version will set \a ec to + /// indicate the error, and return zero. + /// + /// As long as \a size is greater than zero, the two argument version will + /// always return a value that is greater than zero, while the three + /// argument version will return a value greater than zero when, and only + /// when \a ec is set to indicate success (no error, and no end of input). + std::size_t read_some(char* buffer, std::size_t size); + std::size_t read_some(char* buffer, std::size_t size, std::error_code& ec); + /// @} + + /// @{ \brief Write at least one byte to this socket. + /// + /// If \a size is zero, both versions of write_some() will return zero + /// without blocking. Write errors may or may not be detected in this case. + /// + /// Otherwise, if \a size is greater than zero, and at least one byte can be + /// written immediately, that is, without blocking, then both versions will + /// write at least one byte (but generally as many as can be written + /// immediately), and return without blocking. + /// + /// Otherwise, both versions will block the calling thread until at least one + /// byte can be written, or an error occurs. + /// + /// If no error occurs, both versions will return the number of bytes + /// written, which is generally as many as can be written immediately at the + /// time when the first byte can be written. + /// + /// If no error occurs, the three-argument version will set \a ec to + /// indicate success. + /// + /// If an error occurs, the two-argument version will throw + /// `std::system_error`, while the three-argument version will set \a ec to + /// indicate the error, and return zero. + /// + /// As long as \a size is greater than zero, the two argument version will + /// always return a value that is greater than zero, while the three + /// argument version will return a value greater than zero when, and only + /// when \a ec is set to indicate success. + std::size_t write_some(const char* data, std::size_t size); + std::size_t write_some(const char* data, std::size_t size, std::error_code&); + /// @} + + /// \brief Perform an asynchronous connect operation. + /// + /// Initiate an asynchronous connect operation. The completion handler is + /// called when the operation completes. The operation completes when the + /// connection is established, or an error occurs. + /// + /// The completion handler is always executed by the event loop thread, + /// i.e., by a thread that is executing Service::run(). Conversely, the + /// completion handler is guaranteed to not be called while no thread is + /// executing Service::run(). The execution of the completion handler is + /// always deferred to the event loop, meaning that it never happens as a + /// synchronous side effect of the execution of async_connect(), even when + /// async_connect() is executed by the event loop thread. The completion + /// handler is guaranteed to be called eventually, as long as there is time + /// enough for the operation to complete or fail, and a thread is executing + /// Service::run() for long enough. + /// + /// The operation can be canceled by calling cancel(), and will be + /// automatically canceled if the socket is closed. If the operation is + /// canceled, it will fail with `error::operation_aborted`. The operation + /// remains cancelable up until the point in time where the completion + /// handler starts to execute. This means that if cancel() is called before + /// the completion handler starts to execute, then the completion handler is + /// guaranteed to have `error::operation_aborted` passed to it. This is true + /// regardless of whether cancel() is called explicitly or implicitly, such + /// as when the socket is destroyed. + /// + /// If the socket is not already open, it will be opened as part of the + /// connect operation as if by calling `open(ep.protocol())`. If the opening + /// operation succeeds, but the connect operation fails, the socket will be + /// left in the opened state. + /// + /// The specified handler will be executed by an expression on the form + /// `handler(ec)` where `ec` is the error code. If the the handler object is + /// movable, it will never be copied. Otherwise, it will be copied as + /// necessary. + /// + /// It is an error to start a new connect operation (synchronous or + /// asynchronous) while an asynchronous connect operation is in progress. An + /// asynchronous connect operation is considered complete as soon as the + /// completion handler starts to execute. + /// + /// \param ep The remote endpoint of the connection to be established. + template void async_connect(const Endpoint& ep, H handler); + + /// @{ \brief Perform an asynchronous read operation. + /// + /// Initiate an asynchronous buffered read operation on the associated + /// socket. The completion handler will be called when the operation + /// completes, or an error occurs. + /// + /// async_read() will continue reading until the specified buffer is full, + /// or an error occurs. If the end of input is reached before the buffer is + /// filled, the operation fails with MiscExtErrors::end_of_input. + /// + /// async_read_until() will continue reading until the specified buffer + /// contains the specified delimiter, or an error occurs. If the buffer is + /// filled before a delimiter is found, the operation fails with + /// MiscExtErrors::delim_not_found. Otherwise, if the end of input is + /// reached before a delimiter is found, the operation fails with + /// MiscExtErrors::end_of_input. Otherwise, if the operation succeeds, the + /// last byte placed in the buffer is the delimiter. + /// + /// The versions that take a ReadAheadBuffer argument will read through that + /// buffer. This allows for fewer larger reads on the underlying + /// socket. Since unconsumed data may be left in the read-ahead buffer after + /// a read operation completes, it is important that the same read-ahead + /// buffer is passed to the next read operation. + /// + /// The completion handler is always executed by the event loop thread, + /// i.e., by a thread that is executing Service::run(). Conversely, the + /// completion handler is guaranteed to not be called while no thread is + /// executing Service::run(). The execution of the completion handler is + /// always deferred to the event loop, meaning that it never happens as a + /// synchronous side effect of the execution of async_read() or + /// async_read_until(), even when async_read() or async_read_until() is + /// executed by the event loop thread. The completion handler is guaranteed + /// to be called eventually, as long as there is time enough for the + /// operation to complete or fail, and a thread is executing Service::run() + /// for long enough. + /// + /// The operation can be canceled by calling cancel() on the associated + /// socket, and will be automatically canceled if the associated socket is + /// closed. If the operation is canceled, it will fail with + /// `error::operation_aborted`. The operation remains cancelable up until + /// the point in time where the completion handler starts to execute. This + /// means that if cancel() is called before the completion handler starts to + /// execute, then the completion handler is guaranteed to have + /// `error::operation_aborted` passed to it. This is true regardless of + /// whether cancel() is called explicitly or implicitly, such as when the + /// socket is destroyed. + /// + /// The specified handler will be executed by an expression on the form + /// `handler(ec, n)` where `ec` is the error code, and `n` is the number of + /// bytes placed in the buffer (of type `std::size_t`). `n` is guaranteed to + /// be less than, or equal to \a size. If the the handler object is movable, + /// it will never be copied. Otherwise, it will be copied as necessary. + /// + /// It is an error to start a read operation before the associated socket is + /// connected. + /// + /// It is an error to start a new read operation (synchronous or + /// asynchronous) while an asynchronous read operation is in progress. An + /// asynchronous read operation is considered complete as soon as the + /// completion handler starts executing. This means that a new read + /// operation can be started from the completion handler of another + /// asynchronous buffered read operation. + template void async_read(char* buffer, std::size_t size, H handler); + template void async_read(char* buffer, std::size_t size, ReadAheadBuffer&, H handler); + template void async_read_until(char* buffer, std::size_t size, char delim, + ReadAheadBuffer&, H handler); + /// @} + + /// \brief Perform an asynchronous write operation. + /// + /// Initiate an asynchronous write operation. The completion handler is + /// called when the operation completes. The operation completes when all + /// the specified bytes have been written to the socket, or an error occurs. + /// + /// The completion handler is always executed by the event loop thread, + /// i.e., by a thread that is executing Service::run(). Conversely, the + /// completion handler is guaranteed to not be called while no thread is + /// executing Service::run(). The execution of the completion handler is + /// always deferred to the event loop, meaning that it never happens as a + /// synchronous side effect of the execution of async_write(), even when + /// async_write() is executed by the event loop thread. The completion + /// handler is guaranteed to be called eventually, as long as there is time + /// enough for the operation to complete or fail, and a thread is executing + /// Service::run() for long enough. + /// + /// The operation can be canceled by calling cancel(), and will be + /// automatically canceled if the socket is closed. If the operation is + /// canceled, it will fail with `error::operation_aborted`. The operation + /// remains cancelable up until the point in time where the completion + /// handler starts to execute. This means that if cancel() is called before + /// the completion handler starts to execute, then the completion handler is + /// guaranteed to have `error::operation_aborted` passed to it. This is true + /// regardless of whether cancel() is called explicitly or implicitly, such + /// as when the socket is destroyed. + /// + /// The specified handler will be executed by an expression on the form + /// `handler(ec, n)` where `ec` is the error code, and `n` is the number of + /// bytes written (of type `std::size_t`). If the the handler object is + /// movable, it will never be copied. Otherwise, it will be copied as + /// necessary. + /// + /// It is an error to start an asynchronous write operation before the + /// socket is connected. + /// + /// It is an error to start a new write operation (synchronous or + /// asynchronous) while an asynchronous write operation is in progress. An + /// asynchronous write operation is considered complete as soon as the + /// completion handler starts to execute. This means that a new write + /// operation can be started from the completion handler of another + /// asynchronous write operation. + template void async_write(const char* data, std::size_t size, H handler); + + template void async_read_some(char* buffer, std::size_t size, H handler); + template void async_write_some(const char* data, std::size_t size, H handler); + + enum shutdown_type { +#ifdef _WIN32 + /// Shutdown the receiving side of the socket. + shutdown_receive = SD_RECEIVE, + + /// Shutdown the sending side of the socket. + shutdown_send = SD_SEND, + + /// Shutdown both sending and receiving side of the socket. + shutdown_both = SD_BOTH +#else + shutdown_receive = SHUT_RD, + shutdown_send = SHUT_WR, + shutdown_both = SHUT_RDWR +#endif + }; + + /// @{ \brief Shut down the connected sockets sending and/or receiving + /// side. + /// + /// It is an error to call this function when the socket is not both open + /// and connected. + void shutdown(shutdown_type); + std::error_code shutdown(shutdown_type, std::error_code&); + /// @} + + /// @{ \brief Initialize socket with an already-connected native socket + /// handle. + /// + /// The specified native handle must refer to a socket that is already fully + /// open and connected. + /// + /// If the assignment operation succeeds, this socket object has taken + /// ownership of the specified native handle, and the handle will be closed + /// when the socket object is destroyed, (or when close() is called). If the + /// operation fails, the caller still owns the specified native handle. + /// + /// It is an error to call connect() or async_connect() on a socket object + /// that is initialized this way (unless it is first closed). + /// + /// It is an error to call this function on a socket object that is already + /// open. + void assign(const StreamProtocol&, native_handle_type); + std::error_code assign(const StreamProtocol&, native_handle_type, std::error_code&); + /// @} + + /// Returns a reference to this socket, as this socket is the lowest layer + /// of a stream. + Socket& lowest_layer() noexcept; + +private: + using Want = Service::Want; + using StreamOps = Service::BasicStreamOps; + + class ConnectOperBase; + template class ConnectOper; + + using LendersConnectOperPtr = std::unique_ptr; + + // `ec` untouched on success, but no immediate completion + bool initiate_async_connect(const Endpoint&, std::error_code& ec); + // `ec` untouched on success + std::error_code finalize_async_connect(std::error_code& ec) noexcept; + + // See Service::BasicStreamOps for details on these these 6 functions. + void do_init_read_async(std::error_code&, Want&) noexcept; + void do_init_write_async(std::error_code&, Want&) noexcept; + std::size_t do_read_some_sync(char* buffer, std::size_t size, + std::error_code&) noexcept; + std::size_t do_write_some_sync(const char* data, std::size_t size, + std::error_code&) noexcept; + std::size_t do_read_some_async(char* buffer, std::size_t size, + std::error_code&, Want&) noexcept; + std::size_t do_write_some_async(const char* data, std::size_t size, + std::error_code&, Want&) noexcept; + + friend class Service::BasicStreamOps; + friend class Service::BasicStreamOps; + friend class ReadAheadBuffer; + friend class ssl::Stream; +}; + + +/// Switching between synchronous and asynchronous operations is allowed, but +/// only in a nonoverlapping fashion. That is, a synchronous operation is not +/// allowed to run concurrently with an asynchronous one on the same +/// acceptor. Note that an asynchronous operation is considered to be running +/// until its completion handler starts executing. +class Acceptor : public SocketBase { +public: + Acceptor(Service&); + ~Acceptor() noexcept; + + static constexpr int max_connections = SOMAXCONN; + + void listen(int backlog = max_connections); + std::error_code listen(int backlog, std::error_code&); + + void accept(Socket&); + void accept(Socket&, Endpoint&); + std::error_code accept(Socket&, std::error_code&); + std::error_code accept(Socket&, Endpoint&, std::error_code&); + + /// @{ \brief Perform an asynchronous accept operation. + /// + /// Initiate an asynchronous accept operation. The completion handler will + /// be called when the operation completes. The operation completes when the + /// connection is accepted, or an error occurs. If the operation succeeds, + /// the specified local socket will have become connected to a remote + /// socket. + /// + /// The completion handler is always executed by the event loop thread, + /// i.e., by a thread that is executing Service::run(). Conversely, the + /// completion handler is guaranteed to not be called while no thread is + /// executing Service::run(). The execution of the completion handler is + /// always deferred to the event loop, meaning that it never happens as a + /// synchronous side effect of the execution of async_accept(), even when + /// async_accept() is executed by the event loop thread. The completion + /// handler is guaranteed to be called eventually, as long as there is time + /// enough for the operation to complete or fail, and a thread is executing + /// Service::run() for long enough. + /// + /// The operation can be canceled by calling cancel(), and will be + /// automatically canceled if the acceptor is closed. If the operation is + /// canceled, it will fail with `error::operation_aborted`. The operation + /// remains cancelable up until the point in time where the completion + /// handler starts to execute. This means that if cancel() is called before + /// the completion handler starts to execute, then the completion handler is + /// guaranteed to have `error::operation_aborted` passed to it. This is true + /// regardless of whether cancel() is called explicitly or implicitly, such + /// as when the acceptor is destroyed. + /// + /// The specified handler will be executed by an expression on the form + /// `handler(ec)` where `ec` is the error code. If the the handler object is + /// movable, it will never be copied. Otherwise, it will be copied as + /// necessary. + /// + /// It is an error to start a new accept operation (synchronous or + /// asynchronous) while an asynchronous accept operation is in progress. An + /// asynchronous accept operation is considered complete as soon as the + /// completion handler starts executing. This means that a new accept + /// operation can be started from the completion handler. + /// + /// \param sock This is the local socket, that upon successful completion + /// will have become connected to the remote socket. It must be in the + /// closed state (Socket::is_open()) when async_accept() is called. + /// + /// \param ep Upon completion, the remote peer endpoint will have been + /// assigned to this variable. + template void async_accept(Socket& sock, H handler); + template void async_accept(Socket& sock, Endpoint& ep, H handler); + /// @} + +private: + using Want = Service::Want; + + class AcceptOperBase; + template class AcceptOper; + + using LendersAcceptOperPtr = std::unique_ptr; + + std::error_code accept(Socket&, Endpoint*, std::error_code&); + Want do_accept_async(Socket&, Endpoint*, std::error_code&) noexcept; + + template void async_accept(Socket&, Endpoint*, H); +}; + + +/// \brief A timer object supporting asynchronous wait operations. +class DeadlineTimer { +public: + DeadlineTimer(Service&); + ~DeadlineTimer() noexcept; + + /// Thread-safe. + Service& get_service() noexcept; + + /// \brief Perform an asynchronous wait operation. + /// + /// Initiate an asynchronous wait operation. The completion handler becomes + /// ready to execute when the expiration time is reached, or an error occurs + /// (cancellation counts as an error here). The expiration time is the time + /// of initiation plus the specified delay. The error code passed to the + /// complition handler will **never** indicate success, unless the + /// expiration time was reached. + /// + /// The completion handler is always executed by the event loop thread, + /// i.e., by a thread that is executing Service::run(). Conversely, the + /// completion handler is guaranteed to not be called while no thread is + /// executing Service::run(). The execution of the completion handler is + /// always deferred to the event loop, meaning that it never happens as a + /// synchronous side effect of the execution of async_wait(), even when + /// async_wait() is executed by the event loop thread. The completion + /// handler is guaranteed to be called eventually, as long as there is time + /// enough for the operation to complete or fail, and a thread is executing + /// Service::run() for long enough. + /// + /// The operation can be canceled by calling cancel(), and will be + /// automatically canceled if the timer is destroyed. If the operation is + /// canceled, it will fail with `error::operation_aborted`. The operation + /// remains cancelable up until the point in time where the completion + /// handler starts to execute. This means that if cancel() is called before + /// the completion handler starts to execute, then the completion handler is + /// guaranteed to have `error::operation_aborted` passed to it. This is true + /// regardless of whether cancel() is called explicitly or implicitly, such + /// as when the timer is destroyed. + /// + /// The specified handler will be executed by an expression on the form + /// `handler(ec)` where `ec` is the error code. If the the handler object is + /// movable, it will never be copied. Otherwise, it will be copied as + /// necessary. + /// + /// It is an error to start a new asynchronous wait operation while an + /// another one is in progress. An asynchronous wait operation is in + /// progress until its completion handler starts executing. + template + void async_wait(std::chrono::duration delay, H handler); + + /// \brief Cancel an asynchronous wait operation. + /// + /// If an asynchronous wait operation, that is associated with this deadline + /// timer, is in progress, cause it to fail with + /// `error::operation_aborted`. An asynchronous wait operation is in + /// progress until its completion handler starts executing. + /// + /// Completion handlers of canceled operations will become immediately ready + /// to execute, but will never be executed directly as part of the execution + /// of cancel(). + /// + /// Cancellation happens automatically when the timer object is destroyed. + void cancel() noexcept; + +private: + template class WaitOper; + + using clock = Service::clock; + + Service::Impl& m_service_impl; + Service::OwnersOperPtr m_wait_oper; + + void initiate_oper(Service::LendersWaitOperPtr); +}; + + +/// \brief Register a function whose invocation can be triggered repeatedly. +/// +/// While the function is always executed by the event loop thread, the +/// triggering of its execution can be done by any thread, and the triggering +/// operation is guaranteed to never throw. +/// +/// The function is guaranteed to not be called after the Trigger object is +/// destroyed. +/// +/// It is safe to destroy the Trigger object during execution of the function. +/// +/// Note that even though the trigger() function is thread-safe, the Trigger +/// object, as a whole, is not. In particular, construction and destruction must +/// not be considered thread-safe. +/// +/// ### Relation to post() +/// +/// For a particular execution of trigger() and a particular invocation of +/// Service::post(), if the execution of trigger() ends before the execution of +/// Service::post() begins, then it is guaranteed that the function associated +/// with the trigger gets to execute at least once after the execution of +/// trigger() begins, and before the post handler gets to execute. +class Trigger { +public: + template Trigger(Service&, F func); + ~Trigger() noexcept; + + Trigger() noexcept = default; + Trigger(Trigger&&) noexcept = default; + Trigger& operator=(Trigger&&) noexcept = default; + + /// \brief Trigger another invocation of the associated function. + /// + /// An invocation of trigger() puts the Trigger object into the triggered + /// state. It remains in the triggered state until shortly before the + /// function starts to execute. While the Trigger object is in the triggered + /// state, trigger() has no effect. This means that the number of executions + /// of the function will generally be less that the number of times + /// trigger() is invoked(). + /// + /// A particular invocation of trigger() ensures that there will be at least + /// one invocation of the associated function whose execution begins after + /// the beginning of the execution of trigger(), so long as the event loop + /// thread does not exit prematurely from run(). + /// + /// If trigger() is invoked from the event loop thread, the next execution + /// of the associated function will not begin until after trigger returns(), + /// effectively preventing reentrancy for the associated function. + /// + /// If trigger() is invoked from another thread, the associated function may + /// start to execute before trigger() returns. + /// + /// Note that the associated function can retrigger itself, i.e., if the + /// associated function calls trigger(), then that will lead to another + /// invocation of the associated function, but not until the first + /// invocation ends (no reentrance). + /// + /// This function is thread-safe. + void trigger() noexcept; + +private: + template class ExecOper; + + util::bind_ptr m_exec_oper; +}; + + +class ReadAheadBuffer { +public: + ReadAheadBuffer(); + + /// Discard any buffered data. + void clear() noexcept; + +private: + using Want = Service::Want; + + char* m_begin = nullptr; + char* m_end = nullptr; + static constexpr std::size_t s_size = 1024; + const std::unique_ptr m_buffer; + + bool empty() const noexcept; + bool read(char*& begin, char* end, int delim, std::error_code&) noexcept; + template void refill_sync(S& stream, std::error_code&) noexcept; + template bool refill_async(S& stream, std::error_code&, Want&) noexcept; + + template friend class Service::BasicStreamOps; +}; + + +enum class ResolveErrors { + /// Host not found (authoritative). + host_not_found = 1, + + /// Host not found (non-authoritative). + host_not_found_try_again, + + /// The query is valid but does not have associated address data. + no_data, + + /// A non-recoverable error occurred. + no_recovery, + + /// The service is not supported for the given socket type. + service_not_found, + + /// The socket type is not supported. + socket_type_not_supported +}; + +class ResolveErrorCategory : public std::error_category { +public: + const char* name() const noexcept override final; + std::string message(int) const override final; +}; + +/// The error category associated with ResolveErrors. The name of this category is +/// `realm.util.network.resolve`. +extern ResolveErrorCategory resolve_error_category; + +inline std::error_code make_error_code(ResolveErrors err) +{ + return std::error_code(int(err), resolve_error_category); +} + +} // namespace network +} // namespace util +} // namespace realm + +namespace std { + +template<> class is_error_code_enum { +public: + static const bool value = true; +}; + +} // namespace std + +namespace realm { +namespace util { +namespace network { + + + + + +// Implementation + +// ---------------- StreamProtocol ---------------- + +inline StreamProtocol StreamProtocol::ip_v4() +{ + StreamProtocol prot; + prot.m_family = AF_INET; + return prot; +} + +inline StreamProtocol StreamProtocol::ip_v6() +{ + StreamProtocol prot; + prot.m_family = AF_INET6; + return prot; +} + +inline bool StreamProtocol::is_ip_v4() const +{ + return m_family == AF_INET; +} + +inline bool StreamProtocol::is_ip_v6() const +{ + return m_family == AF_INET6; +} + +inline int StreamProtocol::family() const +{ + return m_family; +} + +inline int StreamProtocol::protocol() const +{ + return m_protocol; +} + +inline StreamProtocol::StreamProtocol() : + m_family{AF_UNSPEC}, // Allow both IPv4 and IPv6 + m_socktype{SOCK_STREAM}, // Or SOCK_DGRAM for UDP + m_protocol{0} // Any protocol +{ +} + +// ---------------- Address ---------------- + +inline bool Address::is_ip_v4() const +{ + return !m_is_ip_v6; +} + +inline bool Address::is_ip_v6() const +{ + return m_is_ip_v6; +} + +template +inline std::basic_ostream& operator<<(std::basic_ostream& out, const Address& addr) +{ + // FIXME: Not taking `addr.m_ip_v6_scope_id` into account. What does ASIO + // do? + union buffer_union { + char ip_v4[INET_ADDRSTRLEN]; + char ip_v6[INET6_ADDRSTRLEN]; + }; + char buffer[sizeof (buffer_union)]; + int af = addr.m_is_ip_v6 ? AF_INET6 : AF_INET; +#ifdef _WIN32 + void* src = const_cast(reinterpret_cast(&addr.m_union)); +#else + const void* src = &addr.m_union; +#endif + const char* ret = ::inet_ntop(af, src, buffer, sizeof buffer); + if (ret == 0) { + std::error_code ec = make_basic_system_error_code(errno); + throw std::system_error(ec); + } + out << ret; + return out; +} + +inline Address::Address() +{ + m_union.m_ip_v4 = ip_v4_type(); +} + +inline Address make_address(const char* c_str) +{ + std::error_code ec; + Address addr = make_address(c_str, ec); + if (ec) + throw std::system_error(ec); + return addr; +} + +inline Address make_address(const std::string& str) +{ + std::error_code ec; + Address addr = make_address(str, ec); + if (ec) + throw std::system_error(ec); + return addr; +} + +inline Address make_address(const std::string& str, std::error_code& ec) noexcept +{ + return make_address(str.c_str(), ec); +} + +// ---------------- Endpoint ---------------- + +inline StreamProtocol Endpoint::protocol() const +{ + return m_protocol; +} + +inline Address Endpoint::address() const +{ + Address addr; + if (m_protocol.is_ip_v4()) { + addr.m_union.m_ip_v4 = m_sockaddr_union.m_ip_v4.sin_addr; + } + else { + addr.m_union.m_ip_v6 = m_sockaddr_union.m_ip_v6.sin6_addr; + addr.m_ip_v6_scope_id = m_sockaddr_union.m_ip_v6.sin6_scope_id; + addr.m_is_ip_v6 = true; + } + return addr; +} + +inline Endpoint::port_type Endpoint::port() const +{ + return ntohs(m_protocol.is_ip_v4() ? m_sockaddr_union.m_ip_v4.sin_port : + m_sockaddr_union.m_ip_v6.sin6_port); +} + +inline Endpoint::data_type* Endpoint::data() +{ + return &m_sockaddr_union.m_base; +} + +inline const Endpoint::data_type* Endpoint::data() const +{ + return &m_sockaddr_union.m_base; +} + +inline Endpoint::Endpoint() : + Endpoint{StreamProtocol::ip_v4(), 0} +{ +} + +inline Endpoint::Endpoint(const StreamProtocol& protocol, port_type port) : + m_protocol{protocol} +{ + int family = m_protocol.family(); + if (family == AF_INET) { + m_sockaddr_union.m_ip_v4 = sockaddr_ip_v4_type(); // Clear + m_sockaddr_union.m_ip_v4.sin_family = AF_INET; + m_sockaddr_union.m_ip_v4.sin_port = htons(port); + m_sockaddr_union.m_ip_v4.sin_addr.s_addr = INADDR_ANY; + } + else if (family == AF_INET6) { + m_sockaddr_union.m_ip_v6 = sockaddr_ip_v6_type(); // Clear + m_sockaddr_union.m_ip_v6.sin6_family = AF_INET6; + m_sockaddr_union.m_ip_v6.sin6_port = htons(port); + } + else { + m_sockaddr_union.m_ip_v4 = sockaddr_ip_v4_type(); // Clear + m_sockaddr_union.m_ip_v4.sin_family = AF_UNSPEC; + m_sockaddr_union.m_ip_v4.sin_port = htons(port); + m_sockaddr_union.m_ip_v4.sin_addr.s_addr = INADDR_ANY; + } +} + +inline Endpoint::Endpoint(const Address& addr, port_type port) +{ + if (addr.m_is_ip_v6) { + m_protocol = StreamProtocol::ip_v6(); + m_sockaddr_union.m_ip_v6.sin6_family = AF_INET6; + m_sockaddr_union.m_ip_v6.sin6_port = htons(port); + m_sockaddr_union.m_ip_v6.sin6_flowinfo = 0; + m_sockaddr_union.m_ip_v6.sin6_addr = addr.m_union.m_ip_v6; + m_sockaddr_union.m_ip_v6.sin6_scope_id = addr.m_ip_v6_scope_id; + } + else { + m_protocol = StreamProtocol::ip_v4(); + m_sockaddr_union.m_ip_v4.sin_family = AF_INET; + m_sockaddr_union.m_ip_v4.sin_port = htons(port); + m_sockaddr_union.m_ip_v4.sin_addr = addr.m_union.m_ip_v4; + } +} + +inline Endpoint::List::iterator Endpoint::List::begin() const noexcept +{ + return m_endpoints.data(); +} + +inline Endpoint::List::iterator Endpoint::List::end() const noexcept +{ + return m_endpoints.data() + m_endpoints.size(); +} + +inline std::size_t Endpoint::List::size() const noexcept +{ + return m_endpoints.size(); +} + +inline bool Endpoint::List::empty() const noexcept +{ + return m_endpoints.size() == 0; +} + +// ---------------- Service::OperQueue ---------------- + +template inline bool Service::OperQueue::empty() const noexcept +{ + return !m_back; +} + +template inline void Service::OperQueue::push_back(LendersOperPtr op) noexcept +{ + REALM_ASSERT(!op->m_next); + if (m_back) { + op->m_next = m_back->m_next; + m_back->m_next = op.get(); + } + else { + op->m_next = op.get(); + } + m_back = op.release(); +} + +template template +inline void Service::OperQueue::push_back(OperQueue& q) noexcept +{ + if (!q.m_back) + return; + if (m_back) + std::swap(m_back->m_next, q.m_back->m_next); + m_back = q.m_back; + q.m_back = nullptr; +} + +template inline auto Service::OperQueue::pop_front() noexcept -> LendersOperPtr +{ + Oper* op = nullptr; + if (m_back) { + op = static_cast(m_back->m_next); + if (op != m_back) { + m_back->m_next = op->m_next; + } + else { + m_back = nullptr; + } + op->m_next = nullptr; + } + return LendersOperPtr(op); +} + +template inline void Service::OperQueue::clear() noexcept +{ + if (m_back) { + LendersOperPtr op(m_back); + while (op->m_next != m_back) + op.reset(static_cast(op->m_next)); + m_back = nullptr; + } +} + +template inline Service::OperQueue::OperQueue(OperQueue&& q) noexcept : + m_back{q.m_back} +{ + q.m_back = nullptr; +} + +template inline Service::OperQueue::~OperQueue() noexcept +{ + clear(); +} + +// ---------------- Service::Descriptor ---------------- + +inline Service::Descriptor::Descriptor(Impl& s) noexcept : + service_impl{s} +{ +} + +inline Service::Descriptor::~Descriptor() noexcept +{ + if (is_open()) + close(); +} + +inline void Service::Descriptor::assign(native_handle_type fd, bool in_blocking_mode) noexcept +{ + REALM_ASSERT(!is_open()); + m_fd = fd; + m_in_blocking_mode = in_blocking_mode; +#if REALM_HAVE_EPOLL || REALM_HAVE_KQUEUE + m_read_ready = false; + m_write_ready = false; + m_imminent_end_of_input = false; + m_is_registered = false; +#endif +} + +inline void Service::Descriptor::close() noexcept +{ + REALM_ASSERT(is_open()); +#if REALM_HAVE_EPOLL || REALM_HAVE_KQUEUE + if (m_is_registered) + deregister_for_async(); + m_is_registered = false; +#endif + do_close(); +} + +inline auto Service::Descriptor::release() noexcept -> native_handle_type +{ + REALM_ASSERT(is_open()); +#if REALM_HAVE_EPOLL || REALM_HAVE_KQUEUE + if (m_is_registered) + deregister_for_async(); + m_is_registered = false; +#endif + return do_release(); +} + +inline bool Service::Descriptor::is_open() const noexcept +{ + return (m_fd != -1); +} + +inline auto Service::Descriptor::native_handle() const noexcept -> native_handle_type +{ + return m_fd; +} + +inline bool Service::Descriptor::in_blocking_mode() const noexcept +{ + return m_in_blocking_mode; +} + +template +inline void Service::Descriptor::initiate_oper(std::unique_ptr op, + Args&&... args) +{ + Service::Want want = op->initiate(std::forward(args)...); // Throws + add_initiated_oper(std::move(op), want); // Throws +} + +inline void Service::Descriptor::ensure_blocking_mode() +{ + // Assuming that descriptors are either used mostly in blocking mode, or + // mostly in nonblocking mode. + if (REALM_UNLIKELY(!m_in_blocking_mode)) { + bool value = false; + set_nonblock_flag(value); // Throws + m_in_blocking_mode = true; + } +} + +inline void Service::Descriptor::ensure_nonblocking_mode() +{ + // Assuming that descriptors are either used mostly in blocking mode, or + // mostly in nonblocking mode. + if (REALM_UNLIKELY(m_in_blocking_mode)) { + bool value = true; + set_nonblock_flag(value); // Throws + m_in_blocking_mode = false; + } +} + +inline bool Service::Descriptor::assume_read_would_block() const noexcept +{ +#if REALM_HAVE_EPOLL || REALM_HAVE_KQUEUE + return !m_in_blocking_mode && !m_read_ready; +#else + return false; +#endif +} + +inline bool Service::Descriptor::assume_write_would_block() const noexcept +{ +#if REALM_HAVE_EPOLL || REALM_HAVE_KQUEUE + return !m_in_blocking_mode && !m_write_ready; +#else + return false; +#endif +} + +inline void Service::Descriptor::set_read_ready(bool value) noexcept +{ +#if REALM_HAVE_EPOLL || REALM_HAVE_KQUEUE + m_read_ready = value; +#else + // No-op + static_cast(value); +#endif +} + +inline void Service::Descriptor::set_write_ready(bool value) noexcept +{ +#if REALM_HAVE_EPOLL || REALM_HAVE_KQUEUE + m_write_ready = value; +#else + // No-op + static_cast(value); +#endif +} + +// ---------------- Service ---------------- + +class Service::AsyncOper { +public: + bool in_use() const noexcept; + bool is_complete() const noexcept; + bool is_canceled() const noexcept; + void cancel() noexcept; + /// Every object of type \ref AsyncOper must be destroyed either by a call + /// to this function or to recycle(). This function recycles the operation + /// object (commits suicide), even if it throws. + virtual void recycle_and_execute() = 0; + /// Every object of type \ref AsyncOper must be destroyed either by a call + /// to recycle_and_execute() or to this function. This function destroys the + /// object (commits suicide). + virtual void recycle() noexcept = 0; + /// Must be called when the owner dies, and the object is in use (not an + /// instance of UnusedOper). + virtual void orphan() noexcept = 0; +protected: + AsyncOper(std::size_t size, bool in_use) noexcept; + virtual ~AsyncOper() noexcept {} + void set_is_complete(bool value) noexcept; + template + void do_recycle_and_execute(bool orphaned, H& handler, Args&&...); + void do_recycle(bool orphaned) noexcept; +private: + std::size_t m_size; // Allocated number of bytes + bool m_in_use = false; + // Set to true when the operation completes successfully or fails. If the + // operation is canceled before this happens, it will never be set to + // true. Always false when not in use + bool m_complete = false; + // Set to true when the operation is canceled. Always false when not in use. + bool m_canceled = false; + AsyncOper* m_next = nullptr; // Always null when not in use + template + void do_recycle_and_execute_helper(bool orphaned, bool& was_recycled, H handler, Args...); + friend class Service; +}; + +class Service::ResolveOperBase : public AsyncOper { +public: + ResolveOperBase(std::size_t size, Resolver& resolver, Resolver::Query query) noexcept : + AsyncOper{size, true}, + m_resolver{&resolver}, + m_query{std::move(query)} + { + } + void complete() noexcept + { + set_is_complete(true); + } + void recycle() noexcept override final + { + bool orphaned = !m_resolver; + REALM_ASSERT(orphaned); + // Note: do_recycle() commits suicide. + do_recycle(orphaned); + } + void orphan() noexcept override final + { + m_resolver = nullptr; + } +protected: + Resolver* m_resolver; + Resolver::Query m_query; + Endpoint::List m_endpoints; + std::error_code m_error_code; + friend class Service; +}; + +class Service::WaitOperBase : public AsyncOper { +public: + WaitOperBase(std::size_t size, DeadlineTimer& timer, + clock::time_point expiration_time) noexcept : + AsyncOper{size, true}, // Second argument is `in_use` + m_timer{&timer}, + m_expiration_time{expiration_time} + { + } + void complete() noexcept + { + set_is_complete(true); + } + void recycle() noexcept override final + { + bool orphaned = !m_timer; + REALM_ASSERT(orphaned); + // Note: do_recycle() commits suicide. + do_recycle(orphaned); + } + void orphan() noexcept override final + { + m_timer = nullptr; + } +protected: + DeadlineTimer* m_timer; + clock::time_point m_expiration_time; + friend class Service; +}; + +class Service::TriggerExecOperBase : public AsyncOper, public AtomicRefCountBase { +public: + TriggerExecOperBase(Impl& service) noexcept : + AsyncOper{0, false}, // First arg is `size` (unused), second arg is `in_use` + m_service{&service} + { + } + void recycle() noexcept override final + { + REALM_ASSERT(in_use()); + REALM_ASSERT(!m_service); + // Note: Potential suicide when `self` goes out of scope + util::bind_ptr self{this, bind_ptr_base::adopt_tag{}}; + } + void orphan() noexcept override final + { + REALM_ASSERT(m_service); + m_service = nullptr; + } + void trigger() noexcept + { + REALM_ASSERT(m_service); + Service::trigger_exec(*m_service, *this); + } +protected: + Impl* m_service; +}; + +class Service::PostOperBase : public AsyncOper { +public: + PostOperBase(std::size_t size, Impl& service) noexcept : + AsyncOper{size, true}, // Second argument is `in_use` + m_service{service} + { + } + void recycle() noexcept override final + { + // Service::recycle_post_oper() destroys this operation object + Service::recycle_post_oper(m_service, this); + } + void orphan() noexcept override final + { + REALM_ASSERT(false); // Never called + } +protected: + Impl& m_service; +}; + +template class Service::PostOper : public PostOperBase { +public: + PostOper(std::size_t size, Impl& service, H handler) : + PostOperBase{size, service}, + m_handler{std::move(handler)} + { + } + void recycle_and_execute() override final + { + // Recycle the operation object before the handler is exceuted, such + // that the memory is available for a new post operation that might be + // initiated during the execution of the handler. + bool was_recycled = false; + try { + H handler = std::move(m_handler); // Throws + // Service::recycle_post_oper() destroys this operation object + Service::recycle_post_oper(m_service, this); + was_recycled = true; + handler(); // Throws + } + catch (...) { + if (!was_recycled) { + // Service::recycle_post_oper() destroys this operation object + Service::recycle_post_oper(m_service, this); + } + throw; + } + } +private: + H m_handler; +}; + +class Service::IoOper : public AsyncOper { +public: + IoOper(std::size_t size) noexcept : + AsyncOper{size, true} // Second argument is `in_use` + { + } + virtual Descriptor& descriptor() noexcept = 0; + /// Advance this operation and figure out out whether it needs to read from, + /// or write to the underlying descriptor to advance further. This function + /// must return Want::read if the operation needs to read, or Want::write if + /// the operation needs to write to advance further. If the operation + /// completes (due to success or failure), this function must return + /// Want::nothing. + virtual Want advance() noexcept = 0; +}; + +class Service::UnusedOper : public AsyncOper { +public: + UnusedOper(std::size_t size) noexcept : + AsyncOper{size, false} // Second argument is `in_use` + { + } + void recycle_and_execute() override final + { + // Must never be called + REALM_ASSERT(false); + } + void recycle() noexcept override final + { + // Must never be called + REALM_ASSERT(false); + } + void orphan() noexcept override final + { + // Must never be called + REALM_ASSERT(false); + } +}; + +// `S` must be a stream class with the following member functions: +// +// Socket& lowest_layer() noexcept; +// +// void do_init_read_async(std::error_code& ec, Want& want) noexcept; +// void do_init_write_async(std::error_code& ec, Want& want) noexcept; +// +// std::size_t do_read_some_sync(char* buffer, std::size_t size, +// std::error_code& ec) noexcept; +// std::size_t do_write_some_sync(const char* data, std::size_t size, +// std::error_code& ec) noexcept; +// std::size_t do_read_some_async(char* buffer, std::size_t size, +// std::error_code& ec, Want& want) noexcept; +// std::size_t do_write_some_async(const char* data, std::size_t size, +// std::error_code& ec, Want& want) noexcept; +// +// If an error occurs during any of these 6 functions, the `ec` argument must be +// set accordingly. Otherwise the `ec` argument must be set to +// `std::error_code()`. +// +// The do_init_*_async() functions must update the `want` argument to indicate +// how the operation must be initiated: +// +// Want::read Wait for read readiness, then call do_*_some_async(). +// Want::write Wait for write readiness, then call do_*_some_async(). +// Want::nothing Call do_*_some_async() immediately without waiting for +// read or write readiness. +// +// If end-of-input occurs while reading, do_read_some_*() must fail, set `ec` to +// MiscExtErrors::end_of_input, and return zero. +// +// If an error occurs during reading or writing, do_*_some_sync() must set `ec` +// accordingly (to something other than `std::system_error()`) and return +// zero. Otherwise they must set `ec` to `std::system_error()` and return the +// number of bytes read or written, which **must** be at least 1. If the +// underlying socket is in nonblocking mode, and no bytes could be immediately +// read or written, these functions must fail with +// `error::resource_unavailable_try_again`. +// +// If an error occurs during reading or writing, do_*_some_async() must set `ec` +// accordingly (to something other than `std::system_error()`), `want` to +// `Want::nothing`, and return zero. Otherwise they must set `ec` to +// `std::system_error()` and return the number of bytes read or written, which +// must be zero if no bytes could be immediately read or written. Note, in this +// case it is not an error if the underlying socket is in nonblocking mode, and +// no bytes could be immediately read or written. When these functions succeed, +// but return zero because no bytes could be immediately read or written, they +// must set `want` to something other than `Want::nothing`. +// +// If no error occurs, do_*_some_async() must set `want` to indicate how the +// operation should proceed if additional data needs to be read or written, or +// if no bytes were transferred: +// +// Want::read Wait for read readiness, then call do_*_some_async() again. +// Want::write Wait for write readiness, then call do_*_some_async() again. +// Want::nothing Call do_*_some_async() again without waiting for read or +// write readiness. +// +// NOTE: If, for example, do_read_some_async() sets `want` to `Want::write`, it +// means that the stream needs to write data to the underlying TCP socket before +// it is able to deliver any additional data to the caller. While such a +// situation will never occur on a raw TCP socket, it can occur on an SSL stream +// (Secure Socket Layer). +// +// When do_*_some_async() returns `n`, at least one of the following conditions +// must be true: +// +// n > 0 Bytes were transferred. +// ec != std::error_code() An error occured. +// want != Want::nothing Wait for read/write readiness. +// +// This is of critical importance, as it is the only way we can avoid falling +// into a busy loop of repeated invocations of do_*_some_async(). +// +// NOTE: do_*_some_async() are allowed to set `want` to `Want::read` or +// `Want::write`, even when they succesfully transfer a nonzero number of bytes. +template class Service::BasicStreamOps { +public: + class StreamOper; + class ReadOperBase; + class WriteOperBase; + class BufferedReadOperBase; + template class ReadOper; + template class WriteOper; + template class BufferedReadOper; + + using LendersReadOperPtr = std::unique_ptr; + using LendersWriteOperPtr = std::unique_ptr; + using LendersBufferedReadOperPtr = std::unique_ptr; + + // Synchronous read + static std::size_t read(S& stream, char* buffer, std::size_t size, + std::error_code& ec) + { + REALM_ASSERT(!stream.lowest_layer().m_read_oper || + !stream.lowest_layer().m_read_oper->in_use()); + stream.lowest_layer().m_desc.ensure_blocking_mode(); // Throws + char* begin = buffer; + char* end = buffer + size; + char* curr = begin; + for (;;) { + if (curr == end) { + ec = std::error_code(); // Success + break; + } + char* buffer_2 = curr; + std::size_t size_2 = std::size_t(end - curr); + std::size_t n = stream.do_read_some_sync(buffer_2, size_2, ec); + if (REALM_UNLIKELY(ec)) + break; + REALM_ASSERT(n > 0); + REALM_ASSERT(n <= size_2); + curr += n; + } + std::size_t n = std::size_t(curr - begin); + return n; + } + + // Synchronous write + static std::size_t write(S& stream, const char* data, std::size_t size, + std::error_code& ec) + { + REALM_ASSERT(!stream.lowest_layer().m_write_oper || + !stream.lowest_layer().m_write_oper->in_use()); + stream.lowest_layer().m_desc.ensure_blocking_mode(); // Throws + const char* begin = data; + const char* end = data + size; + const char* curr = begin; + for (;;) { + if (curr == end) { + ec = std::error_code(); // Success + break; + } + const char* data_2 = curr; + std::size_t size_2 = std::size_t(end - curr); + std::size_t n = stream.do_write_some_sync(data_2, size_2, ec); + if (REALM_UNLIKELY(ec)) + break; + REALM_ASSERT(n > 0); + REALM_ASSERT(n <= size_2); + curr += n; + } + std::size_t n = std::size_t(curr - begin); + return n; + } + + // Synchronous read + static std::size_t buffered_read(S& stream, char* buffer, std::size_t size, int delim, + ReadAheadBuffer& rab, std::error_code& ec) + { + REALM_ASSERT(!stream.lowest_layer().m_read_oper || + !stream.lowest_layer().m_read_oper->in_use()); + stream.lowest_layer().m_desc.ensure_blocking_mode(); // Throws + char* begin = buffer; + char* end = buffer + size; + char* curr = begin; + for (;;) { + bool complete = rab.read(curr, end, delim, ec); + if (complete) + break; + + rab.refill_sync(stream, ec); + if (REALM_UNLIKELY(ec)) + break; + } + std::size_t n = (curr - begin); + return n; + } + + // Synchronous read + static std::size_t read_some(S& stream, char* buffer, std::size_t size, + std::error_code& ec) + { + REALM_ASSERT(!stream.lowest_layer().m_read_oper || + !stream.lowest_layer().m_read_oper->in_use()); + stream.lowest_layer().m_desc.ensure_blocking_mode(); // Throws + return stream.do_read_some_sync(buffer, size, ec); + } + + // Synchronous write + static std::size_t write_some(S& stream, const char* data, std::size_t size, + std::error_code& ec) + { + REALM_ASSERT(!stream.lowest_layer().m_write_oper || + !stream.lowest_layer().m_write_oper->in_use()); + stream.lowest_layer().m_desc.ensure_blocking_mode(); // Throws + return stream.do_write_some_sync(data, size, ec); + } + + template + static void async_read(S& stream, char* buffer, std::size_t size, bool is_read_some, H handler) + { + char* begin = buffer; + char* end = buffer + size; + LendersReadOperPtr op = + Service::alloc>(stream.lowest_layer().m_read_oper, stream, is_read_some, + begin, end, std::move(handler)); // Throws + stream.lowest_layer().m_desc.initiate_oper(std::move(op)); // Throws + } + + template + static void async_write(S& stream, const char* data, std::size_t size, bool is_write_some, + H handler) + { + const char* begin = data; + const char* end = data + size; + LendersWriteOperPtr op = + Service::alloc>(stream.lowest_layer().m_write_oper, stream, is_write_some, + begin, end, std::move(handler)); // Throws + stream.lowest_layer().m_desc.initiate_oper(std::move(op)); // Throws + } + + template + static void async_buffered_read(S& stream, char* buffer, std::size_t size, int delim, + ReadAheadBuffer& rab, H handler) + { + char* begin = buffer; + char* end = buffer + size; + LendersBufferedReadOperPtr op = + Service::alloc>(stream.lowest_layer().m_read_oper, stream, + begin, end, delim, rab, + std::move(handler)); // Throws + stream.lowest_layer().m_desc.initiate_oper(std::move(op)); // Throws + } +}; + +template class Service::BasicStreamOps::StreamOper : public IoOper { +public: + StreamOper(std::size_t size, S& stream) noexcept : + IoOper{size}, + m_stream{&stream} + { + } + void recycle() noexcept override final + { + bool orphaned = !m_stream; + REALM_ASSERT(orphaned); + // Note: do_recycle() commits suicide. + do_recycle(orphaned); + } + void orphan() noexcept override final + { + m_stream = nullptr; + } + Descriptor& descriptor() noexcept override final + { + return m_stream->lowest_layer().m_desc; + } +protected: + S* m_stream; + std::error_code m_error_code; +}; + +template class Service::BasicStreamOps::ReadOperBase : public StreamOper { +public: + ReadOperBase(std::size_t size, S& stream, bool is_read_some, char* begin, char* end) noexcept : + StreamOper{size, stream}, + m_is_read_some{is_read_some}, + m_begin{begin}, + m_end{end} + { + } + Want initiate() + { + auto& s = *this; + REALM_ASSERT(this == s.m_stream->lowest_layer().m_read_oper.get()); + REALM_ASSERT(!s.is_complete()); + REALM_ASSERT(s.m_curr <= s.m_end); + Want want = Want::nothing; + if (REALM_UNLIKELY(s.m_curr == s.m_end)) { + s.set_is_complete(true); // Success + } + else { + s.m_stream->lowest_layer().m_desc.ensure_nonblocking_mode(); // Throws + s.m_stream->do_init_read_async(s.m_error_code, want); + if (want == Want::nothing) { + if (REALM_UNLIKELY(s.m_error_code)) { + s.set_is_complete(true); // Failure + } + else { + want = advance(); + } + } + } + return want; + } + Want advance() noexcept override final + { + auto& s = *this; + REALM_ASSERT(!s.is_complete()); + REALM_ASSERT(!s.is_canceled()); + REALM_ASSERT(!s.m_error_code); + REALM_ASSERT(s.m_curr < s.m_end); + REALM_ASSERT(!s.m_is_read_some || s.m_curr == m_begin); + for (;;) { + // Read into callers buffer + char* buffer = s.m_curr; + std::size_t size = std::size_t(s.m_end - s.m_curr); + Want want = Want::nothing; + std::size_t n = s.m_stream->do_read_some_async(buffer, size, s.m_error_code, want); + REALM_ASSERT(n > 0 || s.m_error_code || want != Want::nothing); // No busy loop, please + bool got_nothing = (n == 0); + if (got_nothing) { + if (REALM_UNLIKELY(s.m_error_code)) { + s.set_is_complete(true); // Failure + return Want::nothing; + } + // Got nothing, but want something + return want; + } + REALM_ASSERT(!s.m_error_code); + // Check for completion + REALM_ASSERT(n <= size); + s.m_curr += n; + if (s.m_is_read_some || s.m_curr == s.m_end) { + s.set_is_complete(true); // Success + return Want::nothing; + } + if (want != Want::nothing) + return want; + REALM_ASSERT(n < size); + } + } +protected: + const bool m_is_read_some; + char* const m_begin; // May be dangling after cancellation + char* const m_end; // May be dangling after cancellation + char* m_curr = m_begin; // May be dangling after cancellation +}; + +template class Service::BasicStreamOps::WriteOperBase : public StreamOper { +public: + WriteOperBase(std::size_t size, S& stream, bool is_write_some, + const char* begin, const char* end) noexcept : + StreamOper{size, stream}, + m_is_write_some{is_write_some}, + m_begin{begin}, + m_end{end} + { + } + Want initiate() + { + auto& s = *this; + REALM_ASSERT(this == s.m_stream->lowest_layer().m_write_oper.get()); + REALM_ASSERT(!s.is_complete()); + REALM_ASSERT(s.m_curr <= s.m_end); + Want want = Want::nothing; + if (REALM_UNLIKELY(s.m_curr == s.m_end)) { + s.set_is_complete(true); // Success + } + else { + s.m_stream->lowest_layer().m_desc.ensure_nonblocking_mode(); // Throws + s.m_stream->do_init_write_async(s.m_error_code, want); + if (want == Want::nothing) { + if (REALM_UNLIKELY(s.m_error_code)) { + s.set_is_complete(true); // Failure + } + else { + want = advance(); + } + } + } + return want; + } + Want advance() noexcept override final + { + auto& s = *this; + REALM_ASSERT(!s.is_complete()); + REALM_ASSERT(!s.is_canceled()); + REALM_ASSERT(!s.m_error_code); + REALM_ASSERT(s.m_curr < s.m_end); + REALM_ASSERT(!s.m_is_write_some || s.m_curr == s.m_begin); + for (;;) { + // Write from callers buffer + const char* data = s.m_curr; + std::size_t size = std::size_t(s.m_end - s.m_curr); + Want want = Want::nothing; + std::size_t n = s.m_stream->do_write_some_async(data, size, s.m_error_code, want); + REALM_ASSERT(n > 0 || s.m_error_code || want != Want::nothing); // No busy loop, please + bool wrote_nothing = (n == 0); + if (wrote_nothing) { + if (REALM_UNLIKELY(s.m_error_code)) { + s.set_is_complete(true); // Failure + return Want::nothing; + } + // Wrote nothing, but want something written + return want; + } + REALM_ASSERT(!s.m_error_code); + // Check for completion + REALM_ASSERT(n <= size); + s.m_curr += n; + if (s.m_is_write_some || s.m_curr == s.m_end) { + s.set_is_complete(true); // Success + return Want::nothing; + } + if (want != Want::nothing) + return want; + REALM_ASSERT(n < size); + } + } +protected: + const bool m_is_write_some; + const char* const m_begin; // May be dangling after cancellation + const char* const m_end; // May be dangling after cancellation + const char* m_curr = m_begin; // May be dangling after cancellation +}; + +template class Service::BasicStreamOps::BufferedReadOperBase : public StreamOper { +public: + BufferedReadOperBase(std::size_t size, S& stream, char* begin, char* end, int delim, + ReadAheadBuffer& rab) noexcept : + StreamOper{size, stream}, + m_read_ahead_buffer{rab}, + m_begin{begin}, + m_end{end}, + m_delim{delim} + { + } + Want initiate() + { + auto& s = *this; + REALM_ASSERT(this == s.m_stream->lowest_layer().m_read_oper.get()); + REALM_ASSERT(!s.is_complete()); + Want want = Want::nothing; + bool complete = s.m_read_ahead_buffer.read(s.m_curr, s.m_end, s.m_delim, s.m_error_code); + if (complete) { + s.set_is_complete(true); // Success or failure + } + else { + s.m_stream->lowest_layer().m_desc.ensure_nonblocking_mode(); // Throws + s.m_stream->do_init_read_async(s.m_error_code, want); + if (want == Want::nothing) { + if (REALM_UNLIKELY(s.m_error_code)) { + s.set_is_complete(true); // Failure + } + else { + want = advance(); + } + } + } + return want; + } + Want advance() noexcept override final + { + auto& s = *this; + REALM_ASSERT(!s.is_complete()); + REALM_ASSERT(!s.is_canceled()); + REALM_ASSERT(!s.m_error_code); + REALM_ASSERT(s.m_read_ahead_buffer.empty()); + REALM_ASSERT(s.m_curr < s.m_end); + for (;;) { + // Fill read-ahead buffer from stream (is empty now) + Want want = Want::nothing; + bool nonempty = s.m_read_ahead_buffer.refill_async(*s.m_stream, s.m_error_code, want); + REALM_ASSERT(nonempty || s.m_error_code || + want != Want::nothing); // No busy loop, please + bool got_nothing = !nonempty; + if (got_nothing) { + if (REALM_UNLIKELY(s.m_error_code)) { + s.set_is_complete(true); // Failure + return Want::nothing; + } + // Got nothing, but want something + return want; + } + // Transfer buffered data to callers buffer + bool complete = + s.m_read_ahead_buffer.read(s.m_curr, s.m_end, s.m_delim, s.m_error_code); + if (complete) { + s.set_is_complete(true); // Success or failure (delim_not_found) + return Want::nothing; + } + if (want != Want::nothing) + return want; + } + } +protected: + ReadAheadBuffer& m_read_ahead_buffer; // May be dangling after cancellation + char* const m_begin; // May be dangling after cancellation + char* const m_end; // May be dangling after cancellation + char* m_curr = m_begin; // May be dangling after cancellation + const int m_delim; +}; + +template template +class Service::BasicStreamOps::ReadOper : public ReadOperBase { +public: + ReadOper(std::size_t size, S& stream, bool is_read_some, char* begin, char* end, H handler) : + ReadOperBase{size, stream, is_read_some, begin, end}, + m_handler{std::move(handler)} + { + } + void recycle_and_execute() override final + { + auto& s = *this; + REALM_ASSERT(s.is_complete() || s.is_canceled()); + REALM_ASSERT(s.is_complete() == (s.m_error_code || s.m_curr == s.m_end || + (s.m_is_read_some && s.m_curr != s.m_begin))); + REALM_ASSERT(s.m_curr >= s.m_begin); + bool orphaned = !s.m_stream; + std::error_code ec = s.m_error_code; + if (s.is_canceled()) + ec = error::operation_aborted; + std::size_t num_bytes_transferred = std::size_t(s.m_curr - s.m_begin); + // Note: do_recycle_and_execute() commits suicide. + s.template do_recycle_and_execute(orphaned, s.m_handler, ec, + num_bytes_transferred); // Throws + } +private: + H m_handler; +}; + +template template +class Service::BasicStreamOps::WriteOper : public WriteOperBase { +public: + WriteOper(std::size_t size, S& stream, bool is_write_some, + const char* begin, const char* end, H handler) : + WriteOperBase{size, stream, is_write_some, begin, end}, + m_handler{std::move(handler)} + { + } + void recycle_and_execute() override final + { + auto& s = *this; + REALM_ASSERT(s.is_complete() || s.is_canceled()); + REALM_ASSERT(s.is_complete() == (s.m_error_code || s.m_curr == s.m_end || + (s.m_is_write_some && s.m_curr != s.m_begin))); + REALM_ASSERT(s.m_curr >= s.m_begin); + bool orphaned = !s.m_stream; + std::error_code ec = s.m_error_code; + if (s.is_canceled()) + ec = error::operation_aborted; + std::size_t num_bytes_transferred = std::size_t(s.m_curr - s.m_begin); + // Note: do_recycle_and_execute() commits suicide. + s.template do_recycle_and_execute(orphaned, s.m_handler, ec, + num_bytes_transferred); // Throws + } +private: + H m_handler; +}; + +template template +class Service::BasicStreamOps::BufferedReadOper : public BufferedReadOperBase { +public: + BufferedReadOper(std::size_t size, S& stream, char* begin, char* end, int delim, + ReadAheadBuffer& rab, H handler) : + BufferedReadOperBase{size, stream, begin, end, delim, rab}, + m_handler{std::move(handler)} + { + } + void recycle_and_execute() override final + { + auto& s = *this; + REALM_ASSERT(s.is_complete() || (s.is_canceled() && !s.m_error_code)); + REALM_ASSERT(s.is_canceled() || s.m_error_code || + (s.m_delim != std::char_traits::eof() ? + s.m_curr > s.m_begin && s.m_curr[-1] == + std::char_traits::to_char_type(s.m_delim) : + s.m_curr == s.m_end)); + REALM_ASSERT(s.m_curr >= s.m_begin); + bool orphaned = !s.m_stream; + std::error_code ec = s.m_error_code; + if (s.is_canceled()) + ec = error::operation_aborted; + std::size_t num_bytes_transferred = std::size_t(s.m_curr - s.m_begin); + // Note: do_recycle_and_execute() commits suicide. + s.template do_recycle_and_execute(orphaned, s.m_handler, ec, + num_bytes_transferred); // Throws + } +private: + H m_handler; +}; + +template inline void Service::post(H handler) +{ + do_post(&Service::post_oper_constr, sizeof (PostOper), &handler); +} + +inline void Service::OwnersOperDeleter::operator()(AsyncOper* op) const noexcept +{ + if (op->in_use()) { + op->orphan(); + } + else { + void* addr = op; + op->~AsyncOper(); + delete[] static_cast(addr); + } +} + +inline void Service::LendersOperDeleter::operator()(AsyncOper* op) const noexcept +{ + op->recycle(); // Suicide +} + +template std::unique_ptr +Service::alloc(OwnersOperPtr& owners_ptr, Args&&... args) +{ + void* addr = owners_ptr.get(); + std::size_t size; + if (REALM_LIKELY(addr)) { + REALM_ASSERT(!owners_ptr->in_use()); + size = owners_ptr->m_size; + // We can use static dispatch in the destructor call here, since an + // object, that is not in use, is always an instance of UnusedOper. + REALM_ASSERT(dynamic_cast(owners_ptr.get())); + static_cast(owners_ptr.get())->UnusedOper::~UnusedOper(); + if (REALM_UNLIKELY(size < sizeof (Oper))) { + owners_ptr.release(); + delete[] static_cast(addr); + goto no_object; + } + } + else { + no_object: + addr = new char[sizeof (Oper)]; // Throws + size = sizeof (Oper); + owners_ptr.reset(static_cast(addr)); + } + std::unique_ptr lenders_ptr; + try { + lenders_ptr.reset(new (addr) Oper(size, std::forward(args)...)); // Throws + } + catch (...) { + new (addr) UnusedOper(size); // Does not throw + throw; + } + return lenders_ptr; +} + +template inline Service::PostOperBase* +Service::post_oper_constr(void* addr, std::size_t size, Impl& service, void* cookie) +{ + H& handler = *static_cast(cookie); + return new (addr) PostOper(size, service, std::move(handler)); // Throws +} + +inline bool Service::AsyncOper::in_use() const noexcept +{ + return m_in_use; +} + +inline bool Service::AsyncOper::is_complete() const noexcept +{ + return m_complete; +} + +inline void Service::AsyncOper::cancel() noexcept +{ + REALM_ASSERT(m_in_use); + REALM_ASSERT(!m_canceled); + m_canceled = true; +} + +inline Service::AsyncOper::AsyncOper(std::size_t size, bool is_in_use) noexcept : + m_size{size}, + m_in_use{is_in_use} +{ +} + +inline bool Service::AsyncOper::is_canceled() const noexcept +{ + return m_canceled; +} + +inline void Service::AsyncOper::set_is_complete(bool value) noexcept +{ + REALM_ASSERT(!m_complete); + REALM_ASSERT(!value || m_in_use); + m_complete = value; +} + +template +inline void Service::AsyncOper::do_recycle_and_execute(bool orphaned, H& handler, Args&&... args) +{ + // Recycle the operation object before the handler is exceuted, such that + // the memory is available for a new post operation that might be initiated + // during the execution of the handler. + bool was_recycled = false; + try { + // We need to copy or move all arguments to be passed to the handler, + // such that there is no risk of references to the recycled operation + // object being passed to the handler (the passed arguments may be + // references to members of the recycled operation object). The easiest + // way to achive this, is by forwarding the reference arguments (passed + // to this function) to a helper function whose arguments have + // nonreference type (`Args...` rather than `Args&&...`). + // + // Note that the copying and moving of arguments may throw, and it is + // important that the operation is still recycled even if that + // happens. For that reason, copying and moving of arguments must not + // happen until we are in a scope (this scope) that catches and deals + // correctly with such exceptions. + do_recycle_and_execute_helper(orphaned, was_recycled, std::move(handler), + std::forward(args)...); // Throws + } + catch (...) { + if (!was_recycled) + do_recycle(orphaned); + throw; + } +} + +template +inline void Service::AsyncOper::do_recycle_and_execute_helper(bool orphaned, bool& was_recycled, + H handler, Args... args) +{ + do_recycle(orphaned); + was_recycled = true; + handler(std::move(args)...); // Throws +} + +inline void Service::AsyncOper::do_recycle(bool orphaned) noexcept +{ + REALM_ASSERT(in_use()); + void* addr = this; + std::size_t size = m_size; + this->~AsyncOper(); // Suicide + if (orphaned) { + delete[] static_cast(addr); + } + else { + new (addr) UnusedOper(size); + } +} + +// ---------------- Resolver ---------------- + +template class Resolver::ResolveOper : public Service::ResolveOperBase { +public: + ResolveOper(std::size_t size, Resolver& r, Query q, H handler) : + ResolveOperBase{size, r, std::move(q)}, + m_handler{std::move(handler)} + { + } + void recycle_and_execute() override final + { + REALM_ASSERT(is_complete() || (is_canceled() && !m_error_code)); + REALM_ASSERT(is_canceled() || m_error_code || !m_endpoints.empty()); + bool orphaned = !m_resolver; + std::error_code ec = m_error_code; + if (is_canceled()) + ec = error::operation_aborted; + // Note: do_recycle_and_execute() commits suicide. + do_recycle_and_execute(orphaned, m_handler, ec, std::move(m_endpoints)); // Throws + } +private: + H m_handler; +}; + +inline Resolver::Resolver(Service& service) : + m_service_impl{*service.m_impl} +{ +} + +inline Resolver::~Resolver() noexcept +{ + cancel(); +} + +inline Endpoint::List Resolver::resolve(const Query& q) +{ + std::error_code ec; + Endpoint::List list = resolve(q, ec); + if (REALM_UNLIKELY(ec)) + throw std::system_error(ec); + return list; +} + +template void Resolver::async_resolve(Query query, H handler) +{ + Service::LendersResolveOperPtr op = + Service::alloc>(m_resolve_oper, *this, + std::move(query), + std::move(handler)); // Throws + initiate_oper(std::move(op)); // Throws +} + +inline Resolver::Query::Query(std::string service_port, int init_flags) : + m_flags{init_flags}, + m_service{service_port} +{ +} + +inline Resolver::Query::Query(const StreamProtocol& prot, std::string service_port, + int init_flags) : + m_flags{init_flags}, + m_protocol{prot}, + m_service{service_port} +{ +} + +inline Resolver::Query::Query(std::string host_name, std::string service_port, int init_flags) : + m_flags{init_flags}, + m_host{host_name}, + m_service{service_port} +{ +} + +inline Resolver::Query::Query(const StreamProtocol& prot, std::string host_name, + std::string service_port, int init_flags) : + m_flags{init_flags}, + m_protocol{prot}, + m_host{host_name}, + m_service{service_port} +{ +} + +inline Resolver::Query::~Query() noexcept +{ +} + +inline int Resolver::Query::flags() const +{ + return m_flags; +} + +inline StreamProtocol Resolver::Query::protocol() const +{ + return m_protocol; +} + +inline std::string Resolver::Query::host() const +{ + return m_host; +} + +inline std::string Resolver::Query::service() const +{ + return m_service; +} + +// ---------------- SocketBase ---------------- + +inline SocketBase::SocketBase(Service& service) : + m_desc{*service.m_impl} +{ +} + +inline SocketBase::~SocketBase() noexcept +{ + close(); +} + +inline bool SocketBase::is_open() const noexcept +{ + return m_desc.is_open(); +} + +inline auto SocketBase::native_handle() const noexcept -> native_handle_type +{ + return m_desc.native_handle(); +} + +inline void SocketBase::open(const StreamProtocol& prot) +{ + std::error_code ec; + if (open(prot, ec)) + throw std::system_error(ec); +} + +inline void SocketBase::close() noexcept +{ + if (!is_open()) + return; + cancel(); + m_desc.close(); +} + +template +inline void SocketBase::get_option(O& opt) const +{ + std::error_code ec; + if (get_option(opt, ec)) + throw std::system_error(ec); +} + +template +inline std::error_code SocketBase::get_option(O& opt, std::error_code& ec) const +{ + opt.get(*this, ec); + return ec; +} + +template +inline void SocketBase::set_option(const O& opt) +{ + std::error_code ec; + if (set_option(opt, ec)) + throw std::system_error(ec); +} + +template +inline std::error_code SocketBase::set_option(const O& opt, std::error_code& ec) +{ + opt.set(*this, ec); + return ec; +} + +inline void SocketBase::bind(const Endpoint& ep) +{ + std::error_code ec; + if (bind(ep, ec)) + throw std::system_error(ec); +} + +inline Endpoint SocketBase::local_endpoint() const +{ + std::error_code ec; + Endpoint ep = local_endpoint(ec); + if (ec) + throw std::system_error(ec); + return ep; +} + +inline auto SocketBase::release_native_handle() noexcept -> native_handle_type +{ + if (is_open()) { + cancel(); + return m_desc.release(); + } + return m_desc.native_handle(); +} + +inline const StreamProtocol& SocketBase::get_protocol() const noexcept +{ + return m_protocol; +} + +template +inline SocketBase::Option::Option(T init_value) : + m_value{init_value} +{ +} + +template +inline T SocketBase::Option::value() const +{ + return m_value; +} + +template +inline void SocketBase::Option::get(const SocketBase& sock, std::error_code& ec) +{ + union { + U value; + char strut[sizeof (U) + 1]; + }; + std::size_t value_size = sizeof strut; + sock.get_option(opt_enum(opt), &value, value_size, ec); + if (!ec) { + REALM_ASSERT(value_size == sizeof value); + m_value = T(value); + } +} + +template +inline void SocketBase::Option::set(SocketBase& sock, std::error_code& ec) const +{ + U value_to_set = U(m_value); + sock.set_option(opt_enum(opt), &value_to_set, sizeof value_to_set, ec); +} + +// ---------------- Socket ---------------- + +class Socket::ConnectOperBase : public Service::IoOper { +public: + ConnectOperBase(std::size_t size, Socket& sock) noexcept : + IoOper{size}, + m_socket{&sock} + { + } + Want initiate(const Endpoint& ep) + { + REALM_ASSERT(this == m_socket->m_write_oper.get()); + if (m_socket->initiate_async_connect(ep, m_error_code)) { // Throws + set_is_complete(true); // Failure, or immediate completion + return Want::nothing; + } + return Want::write; + } + Want advance() noexcept override final + { + REALM_ASSERT(!is_complete()); + REALM_ASSERT(!is_canceled()); + REALM_ASSERT(!m_error_code); + m_socket->finalize_async_connect(m_error_code); + set_is_complete(true); + return Want::nothing; + } + void recycle() noexcept override final + { + bool orphaned = !m_socket; + REALM_ASSERT(orphaned); + // Note: do_recycle() commits suicide. + do_recycle(orphaned); + } + void orphan() noexcept override final + { + m_socket = nullptr; + } + Service::Descriptor& descriptor() noexcept override final + { + return m_socket->m_desc; + } +protected: + Socket* m_socket; + std::error_code m_error_code; +}; + +template class Socket::ConnectOper : public ConnectOperBase { +public: + ConnectOper(std::size_t size, Socket& sock, H handler) : + ConnectOperBase{size, sock}, + m_handler{std::move(handler)} + { + } + void recycle_and_execute() override final + { + REALM_ASSERT(is_complete() || (is_canceled() && !m_error_code)); + bool orphaned = !m_socket; + std::error_code ec = m_error_code; + if (is_canceled()) + ec = error::operation_aborted; + // Note: do_recycle_and_execute() commits suicide. + do_recycle_and_execute(orphaned, m_handler, ec); // Throws + } +private: + H m_handler; +}; + +inline Socket::Socket(Service& service) : + SocketBase{service} +{ +} + +inline Socket::Socket(Service& service, const StreamProtocol& prot, + native_handle_type native_socket) : + SocketBase{service} +{ + assign(prot, native_socket); // Throws +} + +inline Socket::~Socket() noexcept +{ +} + +inline void Socket::connect(const Endpoint& ep) +{ + std::error_code ec; + if (connect(ep, ec)) // Throws + throw std::system_error(ec); +} + +inline std::size_t Socket::read(char* buffer, std::size_t size) +{ + std::error_code ec; + read(buffer, size, ec); // Throws + if (ec) + throw std::system_error(ec); + return size; +} + +inline std::size_t Socket::read(char* buffer, std::size_t size, std::error_code& ec) +{ + return StreamOps::read(*this, buffer, size, ec); // Throws +} + +inline std::size_t Socket::read(char* buffer, std::size_t size, ReadAheadBuffer& rab) +{ + std::error_code ec; + read(buffer, size, rab, ec); // Throws + if (ec) + throw std::system_error(ec); + return size; +} + +inline std::size_t Socket::read(char* buffer, std::size_t size, ReadAheadBuffer& rab, + std::error_code& ec) +{ + int delim = std::char_traits::eof(); + return StreamOps::buffered_read(*this, buffer, size, delim, rab, ec); // Throws +} + +inline std::size_t Socket::read_until(char* buffer, std::size_t size, char delim, + ReadAheadBuffer& rab) +{ + std::error_code ec; + std::size_t n = read_until(buffer, size, delim, rab, ec); // Throws + if (ec) + throw std::system_error(ec); + return n; +} + +inline std::size_t Socket::read_until(char* buffer, std::size_t size, char delim, + ReadAheadBuffer& rab, std::error_code& ec) +{ + int delim_2 = std::char_traits::to_int_type(delim); + return StreamOps::buffered_read(*this, buffer, size, delim_2, rab, ec); // Throws +} + +inline std::size_t Socket::write(const char* data, std::size_t size) +{ + std::error_code ec; + write(data, size, ec); // Throws + if (ec) + throw std::system_error(ec); + return size; +} + +inline std::size_t Socket::write(const char* data, std::size_t size, std::error_code& ec) +{ + return StreamOps::write(*this, data, size, ec); // Throws +} + +inline std::size_t Socket::read_some(char* buffer, std::size_t size) +{ + std::error_code ec; + std::size_t n = read_some(buffer, size, ec); // Throws + if (ec) + throw std::system_error(ec); + return n; +} + +inline std::size_t Socket::read_some(char* buffer, std::size_t size, std::error_code& ec) +{ + return StreamOps::read_some(*this, buffer, size, ec); // Throws +} + +inline std::size_t Socket::write_some(const char* data, std::size_t size) +{ + std::error_code ec; + std::size_t n = write_some(data, size, ec); // Throws + if (ec) + throw std::system_error(ec); + return n; +} + +inline std::size_t Socket::write_some(const char* data, std::size_t size, std::error_code& ec) +{ + return StreamOps::write_some(*this, data, size, ec); // Throws +} + +template inline void Socket::async_connect(const Endpoint& ep, H handler) +{ + LendersConnectOperPtr op = + Service::alloc>(m_write_oper, *this, std::move(handler)); // Throws + m_desc.initiate_oper(std::move(op), ep); // Throws +} + +template inline void Socket::async_read(char* buffer, std::size_t size, H handler) +{ + bool is_read_some = false; + StreamOps::async_read(*this, buffer, size, is_read_some, std::move(handler)); // Throws +} + +template +inline void Socket::async_read(char* buffer, std::size_t size, ReadAheadBuffer& rab, H handler) +{ + int delim = std::char_traits::eof(); + StreamOps::async_buffered_read(*this, buffer, size, delim, rab, std::move(handler)); // Throws +} + +template +inline void Socket::async_read_until(char* buffer, std::size_t size, char delim, + ReadAheadBuffer& rab, H handler) +{ + int delim_2 = std::char_traits::to_int_type(delim); + StreamOps::async_buffered_read(*this, buffer, size, delim_2, rab, std::move(handler)); // Throws +} + +template inline void Socket::async_write(const char* data, std::size_t size, H handler) +{ + bool is_write_some = false; + StreamOps::async_write(*this, data, size, is_write_some, std::move(handler)); // Throws +} + +template inline void Socket::async_read_some(char* buffer, std::size_t size, H handler) +{ + bool is_read_some = true; + StreamOps::async_read(*this, buffer, size, is_read_some, std::move(handler)); // Throws +} + +template +inline void Socket::async_write_some(const char* data, std::size_t size, H handler) +{ + bool is_write_some = true; + StreamOps::async_write(*this, data, size, is_write_some, std::move(handler)); // Throws +} + +inline void Socket::shutdown(shutdown_type what) +{ + std::error_code ec; + if (shutdown(what, ec)) // Throws + throw std::system_error(ec); +} + +inline void Socket::assign(const StreamProtocol& prot, native_handle_type native_socket) +{ + std::error_code ec; + if (assign(prot, native_socket, ec)) // Throws + throw std::system_error(ec); +} + +inline std::error_code Socket::assign(const StreamProtocol& prot, + native_handle_type native_socket, std::error_code& ec) +{ + return do_assign(prot, native_socket, ec); // Throws +} + +inline Socket& Socket::lowest_layer() noexcept +{ + return *this; +} + +inline void Socket::do_init_read_async(std::error_code&, Want& want) noexcept +{ + want = Want::read; // Wait for read readiness before proceeding +} + +inline void Socket::do_init_write_async(std::error_code&, Want& want) noexcept +{ + want = Want::write; // Wait for write readiness before proceeding +} + +inline std::size_t Socket::do_read_some_sync(char* buffer, std::size_t size, + std::error_code& ec) noexcept +{ + return m_desc.read_some(buffer, size, ec); +} + +inline std::size_t Socket::do_write_some_sync(const char* data, std::size_t size, + std::error_code& ec) noexcept +{ + return m_desc.write_some(data, size, ec); +} + +inline std::size_t Socket::do_read_some_async(char* buffer, std::size_t size, + std::error_code& ec, Want& want) noexcept +{ + std::error_code ec_2; + std::size_t n = m_desc.read_some(buffer, size, ec_2); + bool success = (!ec_2 || ec_2 == error::resource_unavailable_try_again); + if (REALM_UNLIKELY(!success)) { + ec = ec_2; + want = Want::nothing; // Failure + return 0; + } + ec = std::error_code(); + want = Want::read; // Success + return n; +} + +inline std::size_t Socket::do_write_some_async(const char* data, std::size_t size, + std::error_code& ec, Want& want) noexcept +{ + std::error_code ec_2; + std::size_t n = m_desc.write_some(data, size, ec_2); + bool success = (!ec_2 || ec_2 == error::resource_unavailable_try_again); + if (REALM_UNLIKELY(!success)) { + ec = ec_2; + want = Want::nothing; // Failure + return 0; + } + ec = std::error_code(); + want = Want::write; // Success + return n; +} + +// ---------------- Acceptor ---------------- + +class Acceptor::AcceptOperBase : public Service::IoOper { +public: + AcceptOperBase(std::size_t size, Acceptor& a, Socket& s, Endpoint* e) : + IoOper{size}, + m_acceptor{&a}, + m_socket{s}, + m_endpoint{e} + { + } + Want initiate() + { + REALM_ASSERT(this == m_acceptor->m_read_oper.get()); + REALM_ASSERT(!is_complete()); + m_acceptor->m_desc.ensure_nonblocking_mode(); // Throws + return Want::read; + } + Want advance() noexcept override final + { + REALM_ASSERT(!is_complete()); + REALM_ASSERT(!is_canceled()); + REALM_ASSERT(!m_error_code); + REALM_ASSERT(!m_socket.is_open()); + Want want = m_acceptor->do_accept_async(m_socket, m_endpoint, m_error_code); + if (want == Want::nothing) + set_is_complete(true); // Success or failure + return want; + } + void recycle() noexcept override final + { + bool orphaned = !m_acceptor; + REALM_ASSERT(orphaned); + // Note: do_recycle() commits suicide. + do_recycle(orphaned); + } + void orphan() noexcept override final + { + m_acceptor = nullptr; + } + Service::Descriptor& descriptor() noexcept override final + { + return m_acceptor->m_desc; + } +protected: + Acceptor* m_acceptor; + Socket& m_socket; // May be dangling after cancellation + Endpoint* const m_endpoint; // May be dangling after cancellation + std::error_code m_error_code; +}; + +template class Acceptor::AcceptOper : public AcceptOperBase { +public: + AcceptOper(std::size_t size, Acceptor& a, Socket& s, Endpoint* e, H handler) : + AcceptOperBase{size, a, s, e}, + m_handler{std::move(handler)} + { + } + void recycle_and_execute() override final + { + REALM_ASSERT(is_complete() || (is_canceled() && !m_error_code)); + REALM_ASSERT(is_canceled() || m_error_code || m_socket.is_open()); + bool orphaned = !m_acceptor; + std::error_code ec = m_error_code; + if (is_canceled()) + ec = error::operation_aborted; + // Note: do_recycle_and_execute() commits suicide. + do_recycle_and_execute(orphaned, m_handler, ec); // Throws + } +private: + H m_handler; +}; + +inline Acceptor::Acceptor(Service& service) : + SocketBase{service} +{ +} + +inline Acceptor::~Acceptor() noexcept +{ +} + +inline void Acceptor::listen(int backlog) +{ + std::error_code ec; + if (listen(backlog, ec)) // Throws + throw std::system_error(ec); +} + +inline void Acceptor::accept(Socket& sock) +{ + std::error_code ec; + if (accept(sock, ec)) // Throws + throw std::system_error(ec); +} + +inline void Acceptor::accept(Socket& sock, Endpoint& ep) +{ + std::error_code ec; + if (accept(sock, ep, ec)) // Throws + throw std::system_error(ec); +} + +inline std::error_code Acceptor::accept(Socket& sock, std::error_code& ec) +{ + Endpoint* ep = nullptr; + return accept(sock, ep, ec); // Throws +} + +inline std::error_code Acceptor::accept(Socket& sock, Endpoint& ep, std::error_code& ec) +{ + return accept(sock, &ep, ec); // Throws +} + +template inline void Acceptor::async_accept(Socket& sock, H handler) +{ + Endpoint* ep = nullptr; + async_accept(sock, ep, std::move(handler)); // Throws +} + +template inline void Acceptor::async_accept(Socket& sock, Endpoint& ep, H handler) +{ + async_accept(sock, &ep, std::move(handler)); // Throws +} + +inline std::error_code Acceptor::accept(Socket& socket, Endpoint* ep, std::error_code& ec) +{ + REALM_ASSERT(!m_read_oper || !m_read_oper->in_use()); + if (REALM_UNLIKELY(socket.is_open())) + throw util::runtime_error("Socket is already open"); + m_desc.ensure_blocking_mode(); // Throws + m_desc.accept(socket.m_desc, m_protocol, ep, ec); + return ec; +} + +inline Acceptor::Want Acceptor::do_accept_async(Socket& socket, Endpoint* ep, + std::error_code& ec) noexcept +{ + std::error_code ec_2; + m_desc.accept(socket.m_desc, m_protocol, ep, ec_2); + if (ec_2 == error::resource_unavailable_try_again) + return Want::read; + ec = ec_2; + return Want::nothing; +} + +template inline void Acceptor::async_accept(Socket& sock, Endpoint* ep, H handler) +{ + if (REALM_UNLIKELY(sock.is_open())) + throw util::runtime_error("Socket is already open"); + LendersAcceptOperPtr op = Service::alloc>(m_read_oper, *this, sock, ep, + std::move(handler)); // Throws + m_desc.initiate_oper(std::move(op)); // Throws +} + +// ---------------- DeadlineTimer ---------------- + +template +class DeadlineTimer::WaitOper : public Service::WaitOperBase { +public: + WaitOper(std::size_t size, DeadlineTimer& timer, clock::time_point expiration_time, + H handler) : + Service::WaitOperBase{size, timer, expiration_time}, + m_handler{std::move(handler)} + { + } + void recycle_and_execute() override final + { + bool orphaned = !m_timer; + std::error_code ec; + if (is_canceled()) + ec = error::operation_aborted; + // Note: do_recycle_and_execute() commits suicide. + do_recycle_and_execute(orphaned, m_handler, ec); // Throws + } +private: + H m_handler; +}; + +inline DeadlineTimer::DeadlineTimer(Service& service) : + m_service_impl{*service.m_impl} +{ +} + +inline DeadlineTimer::~DeadlineTimer() noexcept +{ + cancel(); +} + +template +inline void DeadlineTimer::async_wait(std::chrono::duration delay, H handler) +{ + clock::time_point now = clock::now(); + // FIXME: This method of detecting overflow does not work. Comparison + // between distinct duration types is not overflow safe. Overflow easily + // happens in the implied conversion of arguments to the common duration + // type (std::common_type<>). + auto max_add = clock::time_point::max() - now; + if (delay > max_add) + throw util::overflow_error("Expiration time overflow"); + clock::time_point expiration_time = now + delay; + Service::LendersWaitOperPtr op = + Service::alloc>(m_wait_oper, *this, expiration_time, + std::move(handler)); // Throws + initiate_oper(std::move(op)); // Throws +} + +// ---------------- Trigger ---------------- + +template +class Trigger::ExecOper : public Service::TriggerExecOperBase { +public: + ExecOper(Service::Impl& service_impl, H handler) : + Service::TriggerExecOperBase{service_impl}, + m_handler{std::move(handler)} + { + } + void recycle_and_execute() override final + { + REALM_ASSERT(in_use()); + // Note: Potential suicide when `self` goes out of scope + util::bind_ptr self{this, bind_ptr_base::adopt_tag{}}; + if (m_service) { + Service::reset_trigger_exec(*m_service, *this); + m_handler(); // Throws + } + } +private: + H m_handler; +}; + +template inline Trigger::Trigger(Service& service, H handler) : + m_exec_oper{new ExecOper{*service.m_impl, std::move(handler)}} // Throws +{ +} + +inline Trigger::~Trigger() noexcept +{ + if (m_exec_oper) + m_exec_oper->orphan(); +} + +inline void Trigger::trigger() noexcept +{ + REALM_ASSERT(m_exec_oper); + m_exec_oper->trigger(); +} + +// ---------------- ReadAheadBuffer ---------------- + +inline ReadAheadBuffer::ReadAheadBuffer() : + m_buffer{new char[s_size]} // Throws +{ +} + +inline void ReadAheadBuffer::clear() noexcept +{ + m_begin = nullptr; + m_end = nullptr; +} + +inline bool ReadAheadBuffer::empty() const noexcept +{ + return (m_begin == m_end); +} + +template inline void ReadAheadBuffer::refill_sync(S& stream, std::error_code& ec) noexcept +{ + char* buffer = m_buffer.get(); + std::size_t size = s_size; + static_assert(noexcept(stream.do_read_some_sync(buffer, size, ec)), ""); + std::size_t n = stream.do_read_some_sync(buffer, size, ec); + if (REALM_UNLIKELY(n == 0)) + return; + REALM_ASSERT(!ec); + REALM_ASSERT(n <= size); + m_begin = m_buffer.get(); + m_end = m_begin + n; +} + +template +inline bool ReadAheadBuffer::refill_async(S& stream, std::error_code& ec, Want& want) noexcept +{ + char* buffer = m_buffer.get(); + std::size_t size = s_size; + static_assert(noexcept(stream.do_read_some_async(buffer, size, ec, want)), ""); + std::size_t n = stream.do_read_some_async(buffer, size, ec, want); + if (n == 0) + return false; + REALM_ASSERT(!ec); + REALM_ASSERT(n <= size); + m_begin = m_buffer.get(); + m_end = m_begin + n; + return true; +} + +} // namespace network +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_NETWORK_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/network_ssl.hpp b/!main project/Pods/Realm/include/core/realm/util/network_ssl.hpp new file mode 100644 index 0000000..3284bf8 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/network_ssl.hpp @@ -0,0 +1,1378 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2015] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ +#ifndef REALM_UTIL_NETWORK_SSL_HPP +#define REALM_UTIL_NETWORK_SSL_HPP + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#if REALM_HAVE_OPENSSL +# include +# include +#elif REALM_HAVE_SECURE_TRANSPORT +# include +# include +# include + +#define REALM_HAVE_KEYCHAIN_APIS (TARGET_OS_MAC && !TARGET_OS_IPHONE) + +#endif + +// FIXME: Add necessary support for customizing the SSL server and client +// configurations. + +// FIXME: Currently, the synchronous SSL operations (handshake, read, write, +// shutdown) do not automatically retry if the underlying SSL function returns +// with SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE. This normally never +// happens, but it can happen according to the man pages, but in the case of +// SSL_write(), only when a renegotiation has to take place. It is likely that +// the solution is to to wrap the SSL calls inside a loop, such that they keep +// retrying until they succeed, however, such a simple scheme will fail if the +// synchronous operations were to be used with an underlying TCP socket in +// nonblocking mode. Currently, the underlying TCP socket is always in blocking +// mode when performing synchronous operations, but that may continue to be the +// case in teh future. + + +namespace realm { +namespace util { +namespace network { +namespace ssl { + +enum class Errors { + certificate_rejected = 1, +}; + +class ErrorCategory : public std::error_category { +public: + const char* name() const noexcept override final; + std::string message(int) const override final; + bool equivalent(const std::error_code&, int) const noexcept override final; +}; + +/// The error category associated with \ref Errors. The name of this category is +/// `realm.util.network.ssl`. +extern ErrorCategory error_category; + +inline std::error_code make_error_code(Errors err) +{ + return std::error_code(int(err), error_category); +} + +inline std::error_condition make_error_condition(Errors err) +{ + return std::error_condition(int(err), error_category); +} + +} // namespace ssl +} // namespace network +} // namespace util +} // namespace realm + +namespace std { + +template<> class is_error_condition_enum { +public: + static const bool value = true; +}; + +} // namespace std + +namespace realm { +namespace util { +namespace network { + +class OpensslErrorCategory : public std::error_category { +public: + const char* name() const noexcept override final; + std::string message(int) const override final; +}; + +/// The error category associated with error codes produced by the third-party +/// library, OpenSSL. The name of this category is `openssl`. +extern OpensslErrorCategory openssl_error_category; + +class SecureTransportErrorCategory : public std::error_category { +public: + const char* name() const noexcept override final; + std::string message(int) const override final; +}; + +/// The error category associated with error codes produced by Apple's +/// SecureTransport library. The name of this category is `securetransport`. +extern SecureTransportErrorCategory secure_transport_error_category; + + +namespace ssl { + +class ProtocolNotSupported; + + +/// `VerifyMode::none` corresponds to OpenSSL's `SSL_VERIFY_NONE`, and +/// `VerifyMode::peer` to `SSL_VERIFY_PEER`. +enum class VerifyMode { none, peer }; + + +class Context { +public: + Context(); + ~Context() noexcept; + + /// File must be in PEM format. Corresponds to OpenSSL's + /// `SSL_CTX_use_certificate_chain_file()`. + void use_certificate_chain_file(const std::string& path); + + /// File must be in PEM format. Corresponds to OpenSSL's + /// `SSL_CTX_use_PrivateKey_file()`. + void use_private_key_file(const std::string& path); + + /// Calling use_default_verify() will make a client use the device + /// default certificates for server verification. For OpenSSL, + /// use_default_verify() corresponds to + /// SSL_CTX_set_default_verify_paths(SSL_CTX*); + void use_default_verify(); + + /// The verify file is a PEM file containing trust certificates that the + /// client will use to verify the server certificate. If use_verify_file() + /// is not called, the default device trust store will be used. + /// use_verify_file() corresponds roughly to OpenSSL's + /// SSL_CTX_load_verify_locations(). + void use_verify_file(const std::string& path); + +private: + void ssl_init(); + void ssl_destroy() noexcept; + void ssl_use_certificate_chain_file(const std::string& path, std::error_code&); + void ssl_use_private_key_file(const std::string& path, std::error_code&); + void ssl_use_default_verify(std::error_code&); + void ssl_use_verify_file(const std::string& path, std::error_code&); + +#if REALM_HAVE_OPENSSL + SSL_CTX* m_ssl_ctx = nullptr; + +#elif REALM_HAVE_SECURE_TRANSPORT + +#if REALM_HAVE_KEYCHAIN_APIS + std::error_code open_temporary_keychain_if_needed(); + std::error_code update_identity_if_needed(); + + util::CFPtr m_keychain; + std::string m_keychain_path; + + util::CFPtr m_certificate; + util::CFPtr m_private_key; + util::CFPtr m_identity; + + util::CFPtr m_certificate_chain; + +#else + using SecKeychainRef = std::nullptr_t; + +#endif // REALM_HAVE_KEYCHAIN_APIS + static util::CFPtr load_pem_file(const std::string& path, SecKeychainRef, std::error_code&); + + util::CFPtr m_trust_anchors; + util::CFPtr m_pinned_certificate; + +#endif + + friend class Stream; +}; + + +/// Switching between synchronous and asynchronous operations is allowed, but +/// only in a nonoverlapping fashion. That is, a synchronous operation is not +/// allowed to run concurrently with an asynchronous one on the same +/// stream. Note that an asynchronous operation is considered to be running +/// until its completion handler starts executing. +class Stream { +public: + using port_type = util::network::Endpoint::port_type; + using SSLVerifyCallback = bool(const std::string& server_address, + port_type server_port, + const char* pem_data, + size_t pem_size, + int preverify_ok, + int depth); + + enum HandshakeType { client, server }; + + util::Logger* logger = nullptr; + + Stream(Socket&, Context&, HandshakeType); + ~Stream() noexcept; + + /// \brief set_logger() set a logger for the stream class. If + /// set_logger() is not called, no logging will take place by + /// the Stream class. + void set_logger(util::Logger*); + + /// \brief Set the certificate verification mode for this SSL stream. + /// + /// Corresponds to OpenSSL's `SSL_set_verify()` with null passed as + /// `verify_callback`. + /// + /// Clients should always set it to `VerifyMode::peer`, such that the client + /// verifies the servers certificate. Servers should only set it to + /// `VerifyMode::peer` if they want to request a certificate from the + /// client. When testing with self-signed certificates, it is necessary to + /// set it to `VerifyMode::none` for clients too. + /// + /// It is an error if this function is called after the handshake operation + /// is initiated. + /// + /// The default verify mode is `VerifyMode::none`. + void set_verify_mode(VerifyMode); + + /// \brief Check the certificate against a host_name. + /// + /// set_check_host() includes a host name check in the + /// certificate verification. It is typically used by clients + /// to secure that the received certificate has a common name + /// or subject alternative name that matches \param host_name. + /// + /// set_check_host() is only useful if verify_mode is + /// set to VerifyMode::peer. + void set_check_host(std::string host_name); + const std::string& get_host_name(); + + /// get_server_port() and set_server_port() are getter and setter for + /// the server port. They are only used by the verify callback function + /// below. + port_type get_server_port(); + void set_server_port(port_type server_port); + + /// If use_verify_callback() is called, the SSL certificate chain of + /// the server is presented to callback, one certificate at a time. + /// The SSL connection is accepted if and only if callback returns true + /// for all certificates. + /// The signature of \param callback is + /// + /// bool(const std::string& server_address, + /// port_type server_port, + /// const char* pem_data, + /// size_t pem_size, + /// int preverify_ok, + /// int depth); + // + /// server address and server_port is the address and port of the server + /// that a SSL connection is being established to. + /// pem_data is the certificate of length pem_size in + /// the PEM format. preverify_ok is OpenSSL's preverification of the + /// certificate. preverify_ok is either 0, or 1. If preverify_ok is 1, + /// OpenSSL has accepted the certificate and it will generally be safe + /// to trust that certificate. depth represents the position of the + /// certificate in the certificate chain sent by the server. depth = 0 + /// represents the actual server certificate that should contain the + /// host name(server address) of the server. The highest depth is the + /// root certificate. + /// The callback function will receive the certificates starting from + /// the root certificate and moving down the chain until it reaches the + /// server's own certificate with a host name. The depth of the last + /// certificate is 0. The depth of the first certificate is chain + /// length - 1. + /// + /// The return value of the callback function decides whether the + /// client accepts the certificate. If the return value is false, the + /// processing of the certificate chain is interrupted and the SSL + /// connection is rejected. If the return value is true, the verification + /// process continues. If the callback function returns true for all + /// presented certificates including the depth == 0 certificate, the + /// SSL connection is accepted. + /// + /// A recommended way of using the callback function is to return true + /// if preverify_ok = 1 and depth > 0, + /// always check the host name if depth = 0, + /// and use an independent verification step if preverify_ok = 0. + /// + /// Another possible way of using the callback is to collect all the + /// certificates until depth = 0, and present the entire chain for + /// independent verification. + void use_verify_callback(const std::function& callback); + +#ifdef REALM_INCLUDE_CERTS + /// use_included_certificates() loads a set of certificates that are + /// included in the header file src/realm/noinst/root_certs.hpp. By using + /// the included certificates, the client can verify a server in the case + /// where the relevant certificate cannot be found, or is absent, in the + /// system trust store. This function is only implemented for OpenSSL. + void use_included_certificates(); +#endif + + /// @{ + /// + /// Read and write operations behave the same way as they do on \ref + /// network::Socket, except that after cancellation of asynchronous + /// operations (`lowest_layer().cancel()`), the stream may be left in a bad + /// state (see below). + /// + /// The handshake operation must complete successfully before any read, + /// write, or shutdown operations are performed. + /// + /// The shutdown operation sends the shutdown alert to the peer, and + /// returns/completes as soon as the alert message has been written to the + /// underlying socket. It is an error if the shutdown operation is initiated + /// while there are read or write operations in progress. No read or write + /// operations are allowed to be initiated after the shutdown operation has + /// been initiated. When the shutdown operation has completed, it is safe to + /// close the underlying socket (`lowest_layer().close()`). + /// + /// If a write operation is executing while, or is initiated after a close + /// notify alert is received from the remote peer, the write operation will + /// fail with error::broken_pipe. + /// + /// Callback functions for async read and write operations must take two + /// arguments, an std::error_code(), and an integer of a type std::size_t + /// indicating the number of transferred bytes (other types are allowed as + /// long as implicit conversion can take place). + /// + /// Callback functions for async handshake and shutdown operations must take + /// a single argument of type std::error_code() (other types are allowed as + /// long as implicit conversion can take place). + /// + /// Resumption of stream operation after cancellation of asynchronous + /// operations is not supported (does not work). Since the shutdown + /// operation involves network communication, that operation is also not + /// allowed after cancellation. The only thing that is allowed, is to + /// destroy the stream object. Other stream objects are not affected. + + void handshake(); + std::error_code handshake(std::error_code&); + + std::size_t read(char* buffer, std::size_t size); + std::size_t read(char* buffer, std::size_t size, std::error_code& ec); + std::size_t read(char* buffer, std::size_t size, ReadAheadBuffer&); + std::size_t read(char* buffer, std::size_t size, ReadAheadBuffer&, std::error_code& ec); + std::size_t read_until(char* buffer, std::size_t size, char delim, ReadAheadBuffer&); + std::size_t read_until(char* buffer, std::size_t size, char delim, ReadAheadBuffer&, + std::error_code& ec); + + std::size_t write(const char* data, std::size_t size); + std::size_t write(const char* data, std::size_t size, std::error_code& ec); + + std::size_t read_some(char* buffer, std::size_t size); + std::size_t read_some(char* buffer, std::size_t size, std::error_code&); + + std::size_t write_some(const char* data, std::size_t size); + std::size_t write_some(const char* data, std::size_t size, std::error_code&); + + void shutdown(); + std::error_code shutdown(std::error_code&); + + template void async_handshake(H handler); + + template void async_read(char* buffer, std::size_t size, H handler); + template void async_read(char* buffer, std::size_t size, ReadAheadBuffer&, H handler); + template void async_read_until(char* buffer, std::size_t size, char delim, + ReadAheadBuffer&, H handler); + + template void async_write(const char* data, std::size_t size, H handler); + + template void async_read_some(char* buffer, std::size_t size, H handler); + + template void async_write_some(const char* data, std::size_t size, H handler); + + template void async_shutdown(H handler); + + /// @} + + /// Returns a reference to the underlying socket. + Socket& lowest_layer() noexcept; + +private: + using Want = Service::Want; + using StreamOps = Service::BasicStreamOps; + + class HandshakeOperBase; + template class HandshakeOper; + class ShutdownOperBase; + template class ShutdownOper; + + using LendersHandshakeOperPtr = std::unique_ptr; + using LendersShutdownOperPtr = std::unique_ptr; + + Socket& m_tcp_socket; + Context& m_ssl_context; + const HandshakeType m_handshake_type; + + // The host name that the certificate should be checked against. + // The host name is called server address in the certificate verify + // callback function. + std::string m_host_name; + + // The port of the server which is used in the certificate verify + // callback function. + port_type m_server_port; + + // The callback for certificate verification and an + // opaque argument that will be supplied to the callback. + const std::function* m_ssl_verify_callback = nullptr; + + bool m_valid_certificate_in_chain = false; + + + // See Service::BasicStreamOps for details on these these 6 functions. + void do_init_read_async(std::error_code&, Want&) noexcept; + void do_init_write_async(std::error_code&, Want&) noexcept; + std::size_t do_read_some_sync(char* buffer, std::size_t size, + std::error_code&) noexcept; + std::size_t do_write_some_sync(const char* data, std::size_t size, + std::error_code&) noexcept; + std::size_t do_read_some_async(char* buffer, std::size_t size, + std::error_code&, Want&) noexcept; + std::size_t do_write_some_async(const char* data, std::size_t size, + std::error_code&, Want&) noexcept; + + // The meaning of the arguments and return values of ssl_read() and + // ssl_write() are identical to do_read_some_async() and + // do_write_some_async() respectively, except that when the return value is + // nonzero, `want` is always `Want::nothing`, meaning that after bytes have + // been transferred, ssl_read() and ssl_write() must be called again to + // figure out whether it is necessary to wait for read or write readiness. + // + // The first invocation of ssl_shutdown() must send the shutdown alert to + // the peer. In blocking mode it must wait until the alert has been sent. In + // nonblocking mode, it must keep setting `want` to something other than + // `Want::nothing` until the alert has been sent. When the shutdown alert + // has been sent, it is safe to shut down the sending side of the underlying + // socket. On failure, ssl_shutdown() must set `ec` to something different + // than `std::error_code()` and return false. On success, it must set `ec` + // to `std::error_code()`, and return true if a shutdown alert from the peer + // has already been received, otherwise it must return false. When it sets + // `want` to something other than `Want::nothing`, it must set `ec` to + // `std::error_code()` and return false. + // + // The second invocation of ssl_shutdown() (after the first invocation + // completed) must wait for reception on the peers shutdown alert. + // + // Note: The semantics around the second invocation of shutdown is currently + // unused by the higher level API, because of a requirement of compatibility + // with Apple's Secure Transport API. + void ssl_init(); + void ssl_destroy() noexcept; + void ssl_set_verify_mode(VerifyMode, std::error_code&); + void ssl_set_check_host(std::string, std::error_code&); + void ssl_use_verify_callback(const std::function&, std::error_code&); + void ssl_use_included_certificates(std::error_code&); + + void ssl_handshake(std::error_code&, Want& want) noexcept; + bool ssl_shutdown(std::error_code& ec, Want& want) noexcept; + std::size_t ssl_read(char* buffer, std::size_t size, + std::error_code&, Want& want) noexcept; + std::size_t ssl_write(const char* data, std::size_t size, + std::error_code&, Want& want) noexcept; + +#if REALM_HAVE_OPENSSL + class BioMethod; + static BioMethod s_bio_method; + SSL* m_ssl = nullptr; + std::error_code m_bio_error_code; + + int m_ssl_index = -1; + + template + std::size_t ssl_perform(Oper oper, std::error_code& ec, Want& want) noexcept; + + int do_ssl_accept() noexcept; + int do_ssl_connect() noexcept; + int do_ssl_shutdown() noexcept; + int do_ssl_read(char* buffer, std::size_t size) noexcept; + int do_ssl_write(const char* data, std::size_t size) noexcept; + + static int bio_write(BIO*, const char*, int) noexcept; + static int bio_read(BIO*, char*, int) noexcept; + static int bio_puts(BIO*, const char*) noexcept; + static long bio_ctrl(BIO*, int, long, void*) noexcept; + static int bio_create(BIO*) noexcept; + static int bio_destroy(BIO*) noexcept; + + // verify_callback_using_hostname is used as an argument to OpenSSL's SSL_set_verify function. + // verify_callback_using_hostname verifies that the certificate is valid and contains + // m_host_name as a Common Name or Subject Alternative Name. + static int verify_callback_using_hostname(int preverify_ok, X509_STORE_CTX *ctx) noexcept; + + // verify_callback_using_delegate() is also used as an argument to OpenSSL's set_verify_function. + // verify_callback_using_delegate() calls out to the user supplied verify callback. + static int verify_callback_using_delegate(int preverify_ok, X509_STORE_CTX *ctx) noexcept; + + // verify_callback_using_root_certs is used by OpenSSL to handle certificate verification + // using the included root certifictes. + static int verify_callback_using_root_certs(int preverify_ok, X509_STORE_CTX *ctx); +#elif REALM_HAVE_SECURE_TRANSPORT + util::CFPtr m_ssl; + VerifyMode m_verify_mode = VerifyMode::none; + + enum class BlockingOperation { + read, + write, + }; + util::Optional m_last_operation; + + // Details of the underlying I/O error that lead to errSecIO being returned + // from a SecureTransport function. + std::error_code m_last_error; + + // The number of bytes accepted by SSWrite() but not yet confirmed to be + // written to the underlying socket. + std::size_t m_num_partially_written_bytes = 0; + + template + std::size_t ssl_perform(Oper oper, std::error_code& ec, Want& want) noexcept; + + std::pair do_ssl_handshake() noexcept; + std::pair do_ssl_shutdown() noexcept; + std::pair do_ssl_read(char* buffer, std::size_t size) noexcept; + std::pair do_ssl_write(const char* data, std::size_t size) noexcept; + + static OSStatus tcp_read(SSLConnectionRef, void*, std::size_t* length) noexcept; + static OSStatus tcp_write(SSLConnectionRef, const void*, std::size_t* length) noexcept; + + OSStatus tcp_read(void*, std::size_t* length) noexcept; + OSStatus tcp_write(const void*, std::size_t* length) noexcept; + + OSStatus verify_peer() noexcept; +#endif + + friend class Service::BasicStreamOps; + friend class network::ReadAheadBuffer; +}; + + +// Implementation + +class ProtocolNotSupported : public std::exception { +public: + const char* what() const noexcept override final; +}; + +inline Context::Context() +{ + ssl_init(); // Throws +} + +inline Context::~Context() noexcept +{ + ssl_destroy(); +} + +inline void Context::use_certificate_chain_file(const std::string& path) +{ + std::error_code ec; + ssl_use_certificate_chain_file(path, ec); // Throws + if (ec) + throw std::system_error(ec); +} + +inline void Context::use_private_key_file(const std::string& path) +{ + std::error_code ec; + ssl_use_private_key_file(path, ec); // Throws + if (ec) + throw std::system_error(ec); +} + +inline void Context::use_default_verify() +{ + std::error_code ec; + ssl_use_default_verify(ec); + if (ec) + throw std::system_error(ec); +} + +inline void Context::use_verify_file(const std::string& path) +{ + std::error_code ec; + ssl_use_verify_file(path, ec); + if (ec) { + throw std::system_error(ec); + } +} + +class Stream::HandshakeOperBase : public Service::IoOper { +public: + HandshakeOperBase(std::size_t size, Stream& stream) : + IoOper{size}, + m_stream{&stream} + { + } + Want initiate() + { + REALM_ASSERT(this == m_stream->m_tcp_socket.m_read_oper.get()); + REALM_ASSERT(!is_complete()); + m_stream->m_tcp_socket.m_desc.ensure_nonblocking_mode(); // Throws + return advance(); + } + Want advance() noexcept override final + { + REALM_ASSERT(!is_complete()); + REALM_ASSERT(!is_canceled()); + REALM_ASSERT(!m_error_code); + Want want = Want::nothing; + m_stream->ssl_handshake(m_error_code, want); + set_is_complete(want == Want::nothing); + return want; + } + void recycle() noexcept override final + { + bool orphaned = !m_stream; + REALM_ASSERT(orphaned); + // Note: do_recycle() commits suicide. + do_recycle(orphaned); + } + void orphan() noexcept override final + { + m_stream = nullptr; + } + Service::Descriptor& descriptor() noexcept override final + { + return m_stream->lowest_layer().m_desc; + } +protected: + Stream* m_stream; + std::error_code m_error_code; +}; + +template class Stream::HandshakeOper : public HandshakeOperBase { +public: + HandshakeOper(std::size_t size, Stream& stream, H handler) : + HandshakeOperBase{size, stream}, + m_handler{std::move(handler)} + { + } + void recycle_and_execute() override final + { + REALM_ASSERT(is_complete() || is_canceled()); + bool orphaned = !m_stream; + std::error_code ec = m_error_code; + if (is_canceled()) + ec = error::operation_aborted; + // Note: do_recycle_and_execute() commits suicide. + do_recycle_and_execute(orphaned, m_handler, ec); // Throws + } +private: + H m_handler; +}; + +class Stream::ShutdownOperBase : public Service::IoOper { +public: + ShutdownOperBase(std::size_t size, Stream& stream) : + IoOper{size}, + m_stream{&stream} + { + } + Want initiate() + { + REALM_ASSERT(this == m_stream->m_tcp_socket.m_write_oper.get()); + REALM_ASSERT(!is_complete()); + m_stream->m_tcp_socket.m_desc.ensure_nonblocking_mode(); // Throws + return advance(); + } + Want advance() noexcept override final + { + REALM_ASSERT(!is_complete()); + REALM_ASSERT(!is_canceled()); + REALM_ASSERT(!m_error_code); + Want want = Want::nothing; + m_stream->ssl_shutdown(m_error_code, want); + if (want == Want::nothing) + set_is_complete(true); + return want; + } + void recycle() noexcept override final + { + bool orphaned = !m_stream; + REALM_ASSERT(orphaned); + // Note: do_recycle() commits suicide. + do_recycle(orphaned); + } + void orphan() noexcept override final + { + m_stream = nullptr; + } + Service::Descriptor& descriptor() noexcept override final + { + return m_stream->lowest_layer().m_desc; + } +protected: + Stream* m_stream; + std::error_code m_error_code; +}; + +template class Stream::ShutdownOper : public ShutdownOperBase { +public: + ShutdownOper(std::size_t size, Stream& stream, H handler) : + ShutdownOperBase{size, stream}, + m_handler{std::move(handler)} + { + } + void recycle_and_execute() override final + { + REALM_ASSERT(is_complete() || is_canceled()); + bool orphaned = !m_stream; + std::error_code ec = m_error_code; + if (is_canceled()) + ec = error::operation_aborted; + // Note: do_recycle_and_execute() commits suicide. + do_recycle_and_execute(orphaned, m_handler, ec); // Throws + } +private: + H m_handler; +}; + +inline Stream::Stream(Socket& socket, Context& context, HandshakeType type) : + m_tcp_socket{socket}, + m_ssl_context{context}, + m_handshake_type{type} +{ + ssl_init(); // Throws +} + +inline Stream::~Stream() noexcept +{ + m_tcp_socket.cancel(); + ssl_destroy(); +} + +inline void Stream::set_logger(util::Logger* logger) +{ + this->logger = logger; +} + +inline void Stream::set_verify_mode(VerifyMode mode) +{ + std::error_code ec; + ssl_set_verify_mode(mode, ec); // Throws + if (ec) + throw std::system_error(ec); +} + +inline void Stream::set_check_host(std::string host_name) +{ + m_host_name = host_name; + std::error_code ec; + ssl_set_check_host(host_name, ec); + if (ec) + throw std::system_error(ec); +} + +inline const std::string& Stream::get_host_name() +{ + return m_host_name; +} + +inline Stream::port_type Stream::get_server_port() +{ + return m_server_port; +} + +inline void Stream::set_server_port(port_type server_port) +{ + m_server_port = server_port; +} + +inline void Stream::use_verify_callback(const std::function& callback) +{ + std::error_code ec; + ssl_use_verify_callback(callback, ec); // Throws + if (ec) + throw std::system_error(ec); +} + +#ifdef REALM_INCLUDE_CERTS +inline void Stream::use_included_certificates() +{ + std::error_code ec; + ssl_use_included_certificates(ec); // Throws + if (ec) + throw std::system_error(ec); +} +#endif + +inline void Stream::handshake() +{ + std::error_code ec; + if (handshake(ec)) // Throws + throw std::system_error(ec); +} + +inline std::size_t Stream::read(char* buffer, std::size_t size) +{ + std::error_code ec; + read(buffer, size, ec); // Throws + if (ec) + throw std::system_error(ec); + return size; +} + +inline std::size_t Stream::read(char* buffer, std::size_t size, std::error_code& ec) +{ + return StreamOps::read(*this, buffer, size, ec); // Throws +} + +inline std::size_t Stream::read(char* buffer, std::size_t size, ReadAheadBuffer& rab) +{ + std::error_code ec; + read(buffer, size, rab, ec); // Throws + if (ec) + throw std::system_error(ec); + return size; +} + +inline std::size_t Stream::read(char* buffer, std::size_t size, ReadAheadBuffer& rab, + std::error_code& ec) +{ + int delim = std::char_traits::eof(); + return StreamOps::buffered_read(*this, buffer, size, delim, rab, ec); // Throws +} + +inline std::size_t Stream::read_until(char* buffer, std::size_t size, char delim, + ReadAheadBuffer& rab) +{ + std::error_code ec; + std::size_t n = read_until(buffer, size, delim, rab, ec); // Throws + if (ec) + throw std::system_error(ec); + return n; +} + +inline std::size_t Stream::read_until(char* buffer, std::size_t size, char delim, + ReadAheadBuffer& rab, std::error_code& ec) +{ + int delim_2 = std::char_traits::to_int_type(delim); + return StreamOps::buffered_read(*this, buffer, size, delim_2, rab, ec); // Throws +} + +inline std::size_t Stream::write(const char* data, std::size_t size) +{ + std::error_code ec; + write(data, size, ec); // Throws + if (ec) + throw std::system_error(ec); + return size; +} + +inline std::size_t Stream::write(const char* data, std::size_t size, std::error_code& ec) +{ + return StreamOps::write(*this, data, size, ec); // Throws +} + +inline std::size_t Stream::read_some(char* buffer, std::size_t size) +{ + std::error_code ec; + std::size_t n = read_some(buffer, size, ec); // Throws + if (ec) + throw std::system_error(ec); + return n; +} + +inline std::size_t Stream::read_some(char* buffer, std::size_t size, std::error_code& ec) +{ + return StreamOps::read_some(*this, buffer, size, ec); // Throws +} + +inline std::size_t Stream::write_some(const char* data, std::size_t size) +{ + std::error_code ec; + std::size_t n = write_some(data, size, ec); // Throws + if (ec) + throw std::system_error(ec); + return n; +} + +inline std::size_t Stream::write_some(const char* data, std::size_t size, std::error_code& ec) +{ + return StreamOps::write_some(*this, data, size, ec); // Throws +} + +inline void Stream::shutdown() +{ + std::error_code ec; + if (shutdown(ec)) // Throws + throw std::system_error(ec); +} + +template inline void Stream::async_handshake(H handler) +{ + LendersHandshakeOperPtr op = + Service::alloc>(m_tcp_socket.m_read_oper, *this, + std::move(handler)); // Throws + m_tcp_socket.m_desc.initiate_oper(std::move(op)); // Throws +} + +template inline void Stream::async_read(char* buffer, std::size_t size, H handler) +{ + bool is_read_some = false; + StreamOps::async_read(*this, buffer, size, is_read_some, std::move(handler)); // Throws +} + +template +inline void Stream::async_read(char* buffer, std::size_t size, ReadAheadBuffer& rab, H handler) +{ + int delim = std::char_traits::eof(); + StreamOps::async_buffered_read(*this, buffer, size, delim, rab, std::move(handler)); // Throws +} + +template +inline void Stream::async_read_until(char* buffer, std::size_t size, char delim, + ReadAheadBuffer& rab, H handler) +{ + int delim_2 = std::char_traits::to_int_type(delim); + StreamOps::async_buffered_read(*this, buffer, size, delim_2, rab, std::move(handler)); // Throws +} + +template inline void Stream::async_write(const char* data, std::size_t size, H handler) +{ + bool is_write_some = false; + StreamOps::async_write(*this, data, size, is_write_some, std::move(handler)); // Throws +} + +template inline void Stream::async_read_some(char* buffer, std::size_t size, H handler) +{ + bool is_read_some = true; + StreamOps::async_read(*this, buffer, size, is_read_some, std::move(handler)); // Throws +} + +template inline void Stream::async_write_some(const char* data, std::size_t size, H handler) +{ + bool is_write_some = true; + StreamOps::async_write(*this, data, size, is_write_some, std::move(handler)); // Throws +} + +template inline void Stream::async_shutdown(H handler) +{ + LendersShutdownOperPtr op = + Service::alloc>(m_tcp_socket.m_write_oper, *this, + std::move(handler)); // Throws + m_tcp_socket.m_desc.initiate_oper(std::move(op)); // Throws +} + +inline void Stream::do_init_read_async(std::error_code&, Want& want) noexcept +{ + want = Want::nothing; // Proceed immediately unless there is an error +} + +inline void Stream::do_init_write_async(std::error_code&, Want& want) noexcept +{ + want = Want::nothing; // Proceed immediately unless there is an error +} + +inline std::size_t Stream::do_read_some_sync(char* buffer, std::size_t size, + std::error_code& ec) noexcept +{ + Want want = Want::nothing; + std::size_t n = do_read_some_async(buffer, size, ec, want); + if (n == 0 && want != Want::nothing) + ec = error::resource_unavailable_try_again; + return n; +} + +inline std::size_t Stream::do_write_some_sync(const char* data, std::size_t size, + std::error_code& ec) noexcept +{ + Want want = Want::nothing; + std::size_t n = do_write_some_async(data, size, ec, want); + if (n == 0 && want != Want::nothing) + ec = error::resource_unavailable_try_again; + return n; +} + +inline std::size_t Stream::do_read_some_async(char* buffer, std::size_t size, + std::error_code& ec, Want& want) noexcept +{ + return ssl_read(buffer, size, ec, want); +} + +inline std::size_t Stream::do_write_some_async(const char* data, std::size_t size, + std::error_code& ec, Want& want) noexcept +{ + return ssl_write(data, size, ec, want); +} + +inline Socket& Stream::lowest_layer() noexcept +{ + return m_tcp_socket; +} + + +#if REALM_HAVE_OPENSSL + +inline void Stream::ssl_handshake(std::error_code& ec, Want& want) noexcept +{ + auto perform = [this]() noexcept { + switch (m_handshake_type) { + case client: + return do_ssl_connect(); + case server: + return do_ssl_accept(); + } + REALM_ASSERT(false); + return 0; + }; + std::size_t n = ssl_perform(std::move(perform), ec, want); + REALM_ASSERT(n == 0 || n == 1); + if (want == Want::nothing && n == 0 && !ec) { + // End of input on TCP socket + ec = MiscExtErrors::premature_end_of_input; + } +} + +inline std::size_t Stream::ssl_read(char* buffer, std::size_t size, + std::error_code& ec, Want& want) noexcept +{ + auto perform = [this, buffer, size]() noexcept { + return do_ssl_read(buffer, size); + }; + std::size_t n = ssl_perform(std::move(perform), ec, want); + if (want == Want::nothing && n == 0 && !ec) { + // End of input on TCP socket + if (SSL_get_shutdown(m_ssl) & SSL_RECEIVED_SHUTDOWN) { + ec = MiscExtErrors::end_of_input; + } + else { + ec = MiscExtErrors::premature_end_of_input; + } + } + return n; +} + +inline std::size_t Stream::ssl_write(const char* data, std::size_t size, + std::error_code& ec, Want& want) noexcept +{ + // While OpenSSL is able to continue writing after we have received the + // close notify alert fro the remote peer, Apple's Secure Transport API is + // not, so to achieve common behaviour, we make sure that any such attempt + // will result in an `error::broken_pipe` error. + if ((SSL_get_shutdown(m_ssl) & SSL_RECEIVED_SHUTDOWN) != 0) { + ec = error::broken_pipe; + want = Want::nothing; + return 0; + } + auto perform = [this, data, size]() noexcept { + return do_ssl_write(data, size); + }; + std::size_t n = ssl_perform(std::move(perform), ec, want); + if (want == Want::nothing && n == 0 && !ec) { + // End of input on TCP socket + ec = MiscExtErrors::premature_end_of_input; + } + return n; +} + +inline bool Stream::ssl_shutdown(std::error_code& ec, Want& want) noexcept +{ + auto perform = [this]() noexcept { + return do_ssl_shutdown(); + }; + std::size_t n = ssl_perform(std::move(perform), ec, want); + REALM_ASSERT(n == 0 || n == 1); + if (want == Want::nothing && n == 0 && !ec) { + // The first invocation of SSL_shutdown() does not signal completion + // until the shutdown alert has been sent to the peer, or an error + // occurred (does not wait for acknowledgment). + // + // The second invocation (after a completed first invocation) does not + // signal completion until the peers shutdown alert has been received, + // or an error occurred. + // + // It is believed that: + // + // If this is the first time SSL_shutdown() is called, and + // `SSL_get_shutdown() & SSL_SENT_SHUTDOWN` evaluates to nonzero, then a + // zero return value means "partial success" (shutdown alert was sent, + // but the peers shutdown alert was not yet received), and 1 means "full + // success" (peers shutdown alert has already been received). + // + // If this is the first time SSL_shutdown() is called, and + // `SSL_get_shutdown() & SSL_SENT_SHUTDOWN` valuates to zero, then a + // zero return value means "premature end of input", and 1 is supposedly + // not a possibility. + // + // If this is the second time SSL_shutdown() is called (after the first + // call has returned zero), then a zero return value means "premature + // end of input", and 1 means "full success" (peers shutdown alert has + // now been received). + if ((SSL_get_shutdown(m_ssl) & SSL_SENT_SHUTDOWN) == 0) + ec = MiscExtErrors::premature_end_of_input; + } + return (n > 0); +} + +// Provides a homogeneous, and mostly quirks-free interface across the OpenSSL +// operations (handshake, read, write, shutdown). +// +// First of all, if the operation remains incomplete (neither successfully +// completed, nor failed), ssl_perform() will set `ec` to `std::system_error()`, +// `want` to something other than `Want::nothing`, and return zero. Note that +// read and write operations are partial in the sense that they do not need to +// read or write everything before completing successfully. They only need to +// read or write at least one byte to complete successfully. +// +// Such a situation will normally only happen when the underlying TCP socket is +// in nonblocking mode, and the read/write requirements of the operation could +// not be immediately accommodated. However, as is noted in the SSL_write() man +// page, it can also happen in blocking mode (at least while writing). +// +// If an error occurred, ssl_perform() will set `ec` to something other than +// `std::system_error()`, `want` to `Want::nothing`, and return 0. +// +// If no error occurred, and the operation completed (`!ec && want == +// Want::nothing`), then the return value indicates the outcome of the +// operation. +// +// In general, a nonzero value means "full" success, and a zero value means +// "partial" success, however, a zero result can also generally mean "premature +// end of input" / "unclean protocol termination". +// +// Assuming there is no premature end of input, then for reads and writes, the +// returned value is the number of transferred bytes. Zero for read on end of +// input. Never zero for write. For handshake it is always 1. For shutdown it is +// 1 if the peer shutdown alert was already received, otherwise it is zero. +// +// ssl_read() should use `SSL_get_shutdown() & SSL_RECEIVED_SHUTDOWN` to +// distinguish between the two possible meanings of zero. +// +// ssl_shutdown() should use `SSL_get_shutdown() & SSL_SENT_SHUTDOWN` to +// distinguish between the two possible meanings of zero. +template +std::size_t Stream::ssl_perform(Oper oper, std::error_code& ec, Want& want) noexcept +{ + ERR_clear_error(); + m_bio_error_code = std::error_code(); // Success + int ret = oper(); + int ssl_error = SSL_get_error(m_ssl, ret); + int sys_error = int(ERR_get_error()); + + // Guaranteed by the documentation of SSL_get_error() + REALM_ASSERT((ret > 0) == (ssl_error == SSL_ERROR_NONE)); + + REALM_ASSERT(!m_bio_error_code || ssl_error == SSL_ERROR_SYSCALL); + + // Judging from various comments in the man pages, and from experience with + // the API, it seems that, + // + // ret=0, ssl_error=SSL_ERROR_SYSCALL, sys_error=0 + // + // is supposed to be an indicator of "premature end of input" / "unclean + // protocol termination", while + // + // ret=0, ssl_error=SSL_ERROR_ZERO_RETURN + // + // is supposed to be an indicator of the following success conditions: + // + // - Mature end of input / clean protocol termination. + // + // - Successful transmission of the shutdown alert, but no prior reception + // of shutdown alert from peer. + // + // Unfortunately, as is also remarked in various places in the man pages, + // those two success conditions may actually result in `ret=0, + // ssl_error=SSL_ERROR_SYSCALL, sys_error=0` too, and it seems that they + // almost always do. + // + // This means that we cannot properly discriminate between these conditions + // in ssl_perform(), and will have to defer to the caller to interpret the + // situation. Since thay cannot be properly told apart, we report all + // `ret=0, ssl_error=SSL_ERROR_SYSCALL, sys_error=0` and `ret=0, + // ssl_error=SSL_ERROR_ZERO_RETURN` cases as the latter. + switch (ssl_error) { + case SSL_ERROR_NONE: + ec = std::error_code(); // Success + want = Want::nothing; + return std::size_t(ret); // ret > 0 + case SSL_ERROR_ZERO_RETURN: + ec = std::error_code(); // Success + want = Want::nothing; + return 0; + case SSL_ERROR_WANT_READ: + ec = std::error_code(); // Success + want = Want::read; + return 0; + case SSL_ERROR_WANT_WRITE: + ec = std::error_code(); // Success + want = Want::write; + return 0; + case SSL_ERROR_SYSCALL: + if (REALM_UNLIKELY(sys_error != 0)) { + ec = make_basic_system_error_code(sys_error); + } + else if (REALM_UNLIKELY(m_bio_error_code)) { + ec = m_bio_error_code; + } + else if (ret == 0) { + // ret = 0, ssl_eror = SSL_ERROR_SYSCALL, sys_error = 0 + // + // See remarks above! + ec = std::error_code(); // Success + } + else { + // ret = -1, ssl_eror = SSL_ERROR_SYSCALL, sys_error = 0 + // + // This situation arises in OpenSSL version >= 1.1. + // It has been observed in the SSL_connect call if the + // other endpoint terminates the connection during + // SSL_connect. The OpenSSL documentation states + // that ret = -1 implies an underlying BIO error and + // that errno should be consulted. However, + // errno = 0(Undefined error) in the observed case. + // At the moment. we will report + // MiscExtErrors::premature_end_of_input. + // If we see this error case occurring in other situations in + // the future, we will have to update this case. + ec = MiscExtErrors::premature_end_of_input; + } + want = Want::nothing; + return 0; + case SSL_ERROR_SSL: + ec = std::error_code(sys_error, openssl_error_category); + want = Want::nothing; + return 0; + default: + break; + } + // We are not supposed to ever get here + REALM_ASSERT(false); + return 0; +} + +inline int Stream::do_ssl_accept() noexcept +{ + int ret = SSL_accept(m_ssl); + return ret; +} + +inline int Stream::do_ssl_connect() noexcept +{ + int ret = SSL_connect(m_ssl); + return ret; +} + +inline int Stream::do_ssl_read(char* buffer, std::size_t size) noexcept +{ + int size_2 = int(size); + if (size > unsigned(std::numeric_limits::max())) + size_2 = std::size_t(std::numeric_limits::max()); + int ret = SSL_read(m_ssl, buffer, size_2); + return ret; +} + +inline int Stream::do_ssl_write(const char* data, std::size_t size) noexcept +{ + int size_2 = int(size); + if (size > unsigned(std::numeric_limits::max())) + size_2 = std::size_t(std::numeric_limits::max()); + int ret = SSL_write(m_ssl, data, size_2); + return ret; +} + +inline int Stream::do_ssl_shutdown() noexcept +{ + int ret = SSL_shutdown(m_ssl); + return ret; +} + +#elif REALM_HAVE_SECURE_TRANSPORT + +// Provides a homogeneous, and mostly quirks-free interface across the SecureTransport +// operations (handshake, read, write, shutdown). +// +// First of all, if the operation remains incomplete (neither successfully +// completed, nor failed), ssl_perform() will set `ec` to `std::system_error()`, +// `want` to something other than `Want::nothing`, and return zero. +// +// If an error occurred, ssl_perform() will set `ec` to something other than +// `std::system_error()`, `want` to `Want::nothing`, and return 0. +// +// If no error occurred, and the operation completed (`!ec && want == +// Want::nothing`), then the return value indicates the outcome of the +// operation. +// +// In general, a nonzero value means "full" success, and a zero value means +// "partial" success, however, a zero result can also generally mean "premature +// end of input" / "unclean protocol termination". +// +// Assuming there is no premature end of input, then for reads and writes, the +// returned value is the number of transferred bytes. Zero for read on end of +// input. Never zero for write. For handshake it is always 1. For shutdown it is +// 1 if the peer shutdown alert was already received, otherwise it is zero. +template +std::size_t Stream::ssl_perform(Oper oper, std::error_code& ec, Want& want) noexcept +{ + OSStatus result; + std::size_t n; + std::tie(result, n) = oper(); + + if (result == noErr) { + ec = std::error_code(); + want = Want::nothing; + return n; + } + + if (result == errSSLWouldBlock) { + REALM_ASSERT(m_last_operation); + ec = std::error_code(); + want = m_last_operation == BlockingOperation::read ? Want::read : Want::write; + m_last_operation = {}; + return n; + } + + if (result == errSSLClosedGraceful) { + ec = MiscExtErrors::end_of_input; + want = Want::nothing; + return n; + } + + if (result == errSSLClosedAbort || result == errSSLClosedNoNotify) { + ec = MiscExtErrors::premature_end_of_input; + want = Want::nothing; + return n; + } + + if (result == errSecIO) { + // A generic I/O error means something went wrong at a lower level. Use the error + // code we smuggled out of our lower-level functions to provide a more specific error. + REALM_ASSERT(m_last_error); + ec = m_last_error; + want = Want::nothing; + return n; + } + + ec = std::error_code(result, secure_transport_error_category); + want = Want::nothing; + return 0; +} +#endif // REALM_HAVE_OPENSSL / REALM_HAVE_SECURE_TRANSPORT + +} // namespace ssl +} // namespace network +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_NETWORK_SSL_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/optional.hpp b/!main project/Pods/Realm/include/core/realm/util/optional.hpp new file mode 100644 index 0000000..57a2958 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/optional.hpp @@ -0,0 +1,742 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#pragma once +#ifndef REALM_UTIL_OPTIONAL_HPP +#define REALM_UTIL_OPTIONAL_HPP + +#include +#include + +#include // std::logic_error +#include // std::less + +namespace realm { +namespace util { + +template +class Optional; + +// some() should be the equivalent of the proposed C++17 `make_optional`. +template +Optional some(Args&&...); +template +struct Some; + +// Note: Should conform with the future std::nullopt_t and std::in_place_t. +struct None { + constexpr explicit None(int) + { + } +}; +static constexpr None none{0}; +struct InPlace { + constexpr InPlace() + { + } +}; +static constexpr InPlace in_place; + +// Note: Should conform with the future std::bad_optional_access. +struct BadOptionalAccess : ExceptionWithBacktrace { + using ExceptionWithBacktrace::ExceptionWithBacktrace; +}; + +} // namespace util + +namespace _impl { + +template ::value> +struct OptionalStorage; + +template +struct TypeIsAssignableToOptional { + // Constraints from [optional.object.assign.18] + static const bool value = (std::is_same::type, T>::value && + std::is_constructible::value && std::is_assignable::value); +}; + +} // namespace _impl + +namespace util { + +// Note: Should conform with the future std::optional. +template +class Optional : private _impl::OptionalStorage { +public: + using value_type = T; + + constexpr Optional(); + constexpr Optional(None); + Optional(Optional&& other); + Optional(const Optional& other); + + constexpr Optional(T&& value); + constexpr Optional(const T& value); + + template + constexpr Optional(InPlace tag, Args&&...); + // FIXME: std::optional specifies an std::initializer_list constructor overload as well. + + Optional& operator=(None); + Optional& operator=(Optional&& other); + Optional& operator=(const Optional& other); + + template ::value>::type> + Optional& operator=(U&& value); + + explicit constexpr operator bool() const; + constexpr const T& value() const; // Throws + T& value(); // Throws, FIXME: Can be constexpr with C++14 + constexpr const T& operator*() const; // Throws + T& operator*(); // Throws, FIXME: Can be constexpr with C++14 + constexpr const T* operator->() const; // Throws + T* operator->(); // Throws, FIXME: Can be constexpr with C++14 + + template + constexpr T value_or(U&& value) const &; + + template + T value_or(U&& value) &&; + + void swap(Optional& other); // FIXME: Add noexcept() clause + + template + void emplace(Args&&...); + // FIXME: std::optional specifies an std::initializer_list overload for `emplace` as well. +private: + using Storage = _impl::OptionalStorage; + using Storage::m_engaged; + using Storage::m_value; + + constexpr bool is_engaged() const + { + return m_engaged; + } + void set_engaged(bool b) + { + m_engaged = b; + } + void clear(); +}; + + +/// An Optional is functionally equivalent to a bool. +/// Note: C++17 does not (yet) specify this specialization, but it is convenient +/// as a "safer bool", especially in the presence of `fmap`. +/// Disabled for compliance with std::optional. +// template <> +// class Optional { +// public: +// Optional() {} +// Optional(None) {} +// Optional(Optional&&) = default; +// Optional(const Optional&) = default; +// explicit operator bool() const { return m_engaged; } +// private: +// bool m_engaged = false; +// friend struct Some; +// }; + +/// An Optional is a non-owning nullable pointer that throws on dereference. +// FIXME: Visual Studio 2015's constexpr support isn't sufficient to allow Optional to compile +// in constexpr contexts. +template +class Optional { +public: + using value_type = T&; + using target_type = typename std::decay::type; + + constexpr Optional() + { + } + constexpr Optional(None) + { + } // FIXME: Was a delegating constructor, but not fully supported in VS2015 + Optional(const Optional& other) = default; + template + Optional(const Optional& other) + : m_ptr(other.m_ptr) + { + } + template + Optional(std::reference_wrapper ref) + : m_ptr(&ref.get()) + { + } + + constexpr Optional(T& init_value) + : m_ptr(&init_value) + { + } + Optional(T&& value) = delete; // Catches accidental references to rvalue temporaries. + + Optional& operator=(None) + { + m_ptr = nullptr; + return *this; + } + Optional& operator=(const Optional& other) + { + m_ptr = other.m_ptr; + return *this; + } + + template + Optional& operator=(std::reference_wrapper ref) + { + m_ptr = &ref.get(); + return *this; + } + + explicit constexpr operator bool() const + { + return m_ptr; + } + constexpr const target_type& value() const; // Throws + target_type& value(); // Throws + constexpr const target_type& operator*() const + { + return value(); + } + target_type& operator*() + { + return value(); + } + constexpr const target_type* operator->() const + { + return &value(); + } + target_type* operator->() + { + return &value(); + } + + void swap(Optional other); // FIXME: Add noexcept() clause +private: + T* m_ptr = nullptr; + + template + friend class Optional; +}; + + +template +struct RemoveOptional { + using type = T; +}; +template +struct RemoveOptional> { + using type = typename RemoveOptional::type; // Remove recursively +}; + + +/// Implementation: + +template +struct Some { + template + static Optional some(Args&&... args) + { + return Optional{std::forward(args)...}; + } +}; + +/// Disabled for compliance with std::optional. +// template <> +// struct Some { +// static Optional some() +// { +// Optional opt; +// opt.m_engaged = true; +// return opt; +// } +// }; + +template +Optional some(Args&&... args) +{ + return Some::some(std::forward(args)...); +} + + +template +constexpr Optional::Optional() + : Storage(none) +{ +} + +template +constexpr Optional::Optional(None) + : Storage(none) +{ +} + +template +Optional::Optional(Optional&& other) + : Storage(none) +{ + if (other.m_engaged) { + new (&m_value) T(std::move(other.m_value)); + m_engaged = true; + } +} + +template +Optional::Optional(const Optional& other) + : Storage(none) +{ + if (other.m_engaged) { + new (&m_value) T(other.m_value); + m_engaged = true; + } +} + +template +constexpr Optional::Optional(T&& r_value) + : Storage(std::move(r_value)) +{ +} + +template +constexpr Optional::Optional(const T& l_value) + : Storage(l_value) +{ +} + +template +template +constexpr Optional::Optional(InPlace, Args&&... args) + : Storage(std::forward(args)...) +{ +} + +template +void Optional::clear() +{ + if (m_engaged) { + m_value.~T(); + m_engaged = false; + } +} + +template +Optional& Optional::operator=(None) +{ + clear(); + return *this; +} + +template +Optional& Optional::operator=(Optional&& other) +{ + if (m_engaged) { + if (other.m_engaged) { + m_value = std::move(other.m_value); + } + else { + clear(); + } + } + else { + if (other.m_engaged) { + new (&m_value) T(std::move(other.m_value)); + m_engaged = true; + } + } + return *this; +} + +template +Optional& Optional::operator=(const Optional& other) +{ + if (m_engaged) { + if (other.m_engaged) { + m_value = other.m_value; + } + else { + clear(); + } + } + else { + if (other.m_engaged) { + new (&m_value) T(other.m_value); + m_engaged = true; + } + } + return *this; +} + +template +template +Optional& Optional::operator=(U&& r_value) +{ + if (m_engaged) { + m_value = std::forward(r_value); + } + else { + new (&m_value) T(std::forward(r_value)); + m_engaged = true; + } + return *this; +} + +template +constexpr Optional::operator bool() const +{ + return m_engaged; +} + +template +constexpr const T& Optional::value() const +{ + return m_engaged ? m_value : (throw BadOptionalAccess{"bad optional access"}, m_value); +} + +template +T& Optional::value() +{ + if (!m_engaged) { + throw BadOptionalAccess{"bad optional access"}; + } + return m_value; +} + +template +constexpr const typename Optional::target_type& Optional::value() const +{ + return m_ptr ? *m_ptr : (throw BadOptionalAccess{"bad optional access"}, *m_ptr); +} + +template +typename Optional::target_type& Optional::value() +{ + if (!m_ptr) { + throw BadOptionalAccess{"bad optional access"}; + } + return *m_ptr; +} + +template +constexpr const T& Optional::operator*() const +{ + // Note: This differs from std::optional, which doesn't throw. + return value(); +} + +template +T& Optional::operator*() +{ + // Note: This differs from std::optional, which doesn't throw. + return value(); +} + +template +constexpr const T* Optional::operator->() const +{ + // Note: This differs from std::optional, which doesn't throw. + return &value(); +} + +template +T* Optional::operator->() +{ + // Note: This differs from std::optional, which doesn't throw. + return &value(); +} + +template +template +constexpr T Optional::value_or(U&& otherwise) const & +{ + return m_engaged ? T{m_value} : T{std::forward(otherwise)}; +} + +template +template +T Optional::value_or(U&& otherwise) && +{ + if (is_engaged()) { + return T(std::move(m_value)); + } + else { + return T(std::forward(otherwise)); + } +} + +template +void Optional::swap(Optional& other) +{ + // FIXME: This might be optimizable. + Optional tmp = std::move(other); + other = std::move(*this); + *this = std::move(tmp); +} + +template +template +void Optional::emplace(Args&&... args) +{ + clear(); + new (&m_value) T(std::forward(args)...); + m_engaged = true; +} + + +template +constexpr Optional::type> make_optional(T&& value) +{ + using Type = typename std::decay::type; + return some(std::forward(value)); +} + +template +bool operator==(const Optional& lhs, const Optional& rhs) +{ + if (!lhs && !rhs) { + return true; + } + if (lhs && rhs) { + return *lhs == *rhs; + } + return false; +} + +template +bool operator!=(const Optional& lhs, const Optional& rhs) +{ + return !(lhs == rhs); +} + +template +bool operator<(const Optional& lhs, const Optional& rhs) +{ + if (!rhs) { + return false; + } + if (!lhs) { + return true; + } + return std::less{}(*lhs, *rhs); +} + +template +bool operator>(const util::Optional& lhs, const util::Optional& rhs) +{ + if (!lhs) { + return false; + } + if (!rhs) { + return true; + } + return std::greater{}(*lhs, *rhs); +} + +template +bool operator==(const Optional& lhs, None) +{ + return !bool(lhs); +} + +template +bool operator!=(const Optional& lhs, None) +{ + return bool(lhs); +} + +template +bool operator<(const Optional& lhs, None) +{ + static_cast(lhs); + return false; +} + +template +bool operator==(None, const Optional& rhs) +{ + return !bool(rhs); +} + +template +bool operator!=(None, const Optional& rhs) +{ + return bool(rhs); +} + +template +bool operator<(None, const Optional& rhs) +{ + return bool(rhs); +} + +template +bool operator==(const Optional& lhs, const U& rhs) +{ + return lhs ? *lhs == rhs : false; +} + +template +bool operator<(const Optional& lhs, const T& rhs) +{ + return lhs ? std::less{}(*lhs, rhs) : true; +} + +template +bool operator==(const T& lhs, const Optional& rhs) +{ + return rhs ? lhs == *rhs : false; +} + +template +bool operator<(const T& lhs, const Optional& rhs) +{ + return rhs ? std::less{}(lhs, *rhs) : false; +} + +template +auto operator>>(Optional lhs, F&& rhs) -> decltype(fmap(lhs, std::forward(rhs))) +{ + return fmap(lhs, std::forward(rhs)); +} + +template +OS& operator<<(OS& os, const Optional& rhs) +{ + if (rhs) { + os << "some(" << *rhs << ")"; + } + else { + os << "none"; + } + return os; +} + +template +T unwrap(T&& value) +{ + return value; +} + +template +T unwrap(util::Optional&& value) +{ + return *value; +} + +template +T unwrap(const util::Optional& value) +{ + return *value; +} + +template +T unwrap(util::Optional& value) +{ + return *value; +} + +} // namespace util + +namespace _impl { + +// T is trivially destructible. +template +struct OptionalStorage { + union { + T m_value; + char m_null_state; + }; + bool m_engaged = false; + + constexpr OptionalStorage(realm::util::None) + : m_null_state() + { + } + constexpr OptionalStorage(T&& value) + : m_value(std::move(value)) + , m_engaged(true) + { + } + + template + constexpr OptionalStorage(Args&&... args) + : m_value(args...) + , m_engaged(true) + { + } +}; + +// T is not trivially destructible. +template +struct OptionalStorage { + union { + T m_value; + char m_null_state; + }; + bool m_engaged = false; + + constexpr OptionalStorage(realm::util::None) + : m_null_state() + { + } + constexpr OptionalStorage(T&& value) + : m_value(std::move(value)) + , m_engaged(true) + { + } + + template + constexpr OptionalStorage(Args&&... args) + : m_value(args...) + , m_engaged(true) + { + } + + ~OptionalStorage() + { + if (m_engaged) + m_value.~T(); + } +}; + +} // namespace _impl + +using util::none; + +} // namespace realm + + +// for convienence, inject a default hash implementation into the std namespace +namespace std +{ + template + struct hash> + { + std::size_t operator()(realm::util::Optional const& o) const noexcept + { + if (bool(o) == false) { + return 0; // any choice will collide with some std::hash + } else { + return std::hash{}(*o); + } + } + }; +} + + +#endif // REALM_UTIL_OPTIONAL_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/overload.hpp b/!main project/Pods/Realm/include/core/realm/util/overload.hpp new file mode 100644 index 0000000..49188ae --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/overload.hpp @@ -0,0 +1,70 @@ +/************************************************************************* + * + * Copyright 2017 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_OVERLOAD_HPP +#define REALM_UTIL_OVERLOAD_HPP + +#include + +namespace realm { + +namespace _impl { + +template +struct Overloaded; + +} // namespace _impl + + +namespace util { + +// Declare an overload set using lambdas or other function objects. +// A minimal version of C++ Library Evolution Working Group proposal P0051R2. + +template +_impl::Overloaded overload(Fns&&... f) +{ + return _impl::Overloaded(std::forward(f)...); +} + +} // namespace util + + +namespace _impl { + +template +struct Overloaded : Fn, Overloaded { + template + Overloaded(U&& fn, Rest&&... rest) : Fn(std::forward(fn)), Overloaded(std::forward(rest)...) { } + + using Fn::operator(); + using Overloaded::operator(); +}; + +template +struct Overloaded : Fn { + template + Overloaded(U&& fn) : Fn(std::forward(fn)) { } + + using Fn::operator(); +}; + +} // namespace _impl +} // namespace realm + +#endif // REALM_UTIL_OVERLOAD_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/parent_dir.hpp b/!main project/Pods/Realm/include/core/realm/util/parent_dir.hpp new file mode 100644 index 0000000..0e74e22 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/parent_dir.hpp @@ -0,0 +1,37 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2015] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ +#ifndef REALM_UTIL_PARENT_DIR_HPP +#define REALM_UTIL_PARENT_DIR_HPP + +#include + +namespace realm { +namespace util { + +/// Same effect as std::filesystem::path::parent_path(). +/// +/// FIXME: This function ought to be moved to in the +/// realm-core repository. +std::string parent_dir(const std::string& path); + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_PARENT_DIR_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/platform_info.hpp b/!main project/Pods/Realm/include/core/realm/util/platform_info.hpp new file mode 100644 index 0000000..16ae43a --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/platform_info.hpp @@ -0,0 +1,64 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2015] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ +#ifndef REALM_UTIL_PLATFORM_INFO_HPP +#define REALM_UTIL_PLATFORM_INFO_HPP + +#include + + +namespace realm { +namespace util { + +/// Get a description of the current system platform. +/// +/// Returns a space-separated concatenation of `osname`, `sysname`, `release`, +/// `version`, and `machine` as returned by get_platform_info(PlatformInfo&). +std::string get_platform_info(); + + +struct PlatformInfo { + std::string osname; ///< Equivalent to `uname -o` (Linux). + std::string sysname; ///< Equivalent to `uname -s`. + std::string release; ///< Equivalent to `uname -r`. + std::string version; ///< Equivalent to `uname -v`. + std::string machine; ///< Equivalent to `uname -m`. +}; + +/// Get a description of the current system platform. +void get_platform_info(PlatformInfo&); + + + + +// Implementation + +inline std::string get_platform_info() +{ + PlatformInfo info; + get_platform_info(info); // Throws + return (info.osname + " " + info.sysname + " " + info.release + " " + info.version + " " + + info.machine); // Throws +} + + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_PLATFORM_INFO_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/priority_queue.hpp b/!main project/Pods/Realm/include/core/realm/util/priority_queue.hpp new file mode 100644 index 0000000..a2a28c8 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/priority_queue.hpp @@ -0,0 +1,304 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + + +#pragma once +#ifndef REALM_UTIL_PRIORITY_QUEUE_HPP +#define REALM_UTIL_PRIORITY_QUEUE_HPP + +#include +#include +#include + +namespace realm { +namespace util { + + +/// PriorityQueue corresponds exactly to `std::priority_queue`, but has the extra feature +/// of allowing iteration and erasure of elements in the queue. +/// +/// PriorityQueue only allows const access to its elements, because non-const access +/// would open up the risk of changing the ordering of the elements. +/// +/// Note: As opposed to `std::priority_queue`, this does not store elements in a heap +/// internally. Instead, elements are stored in sorted order. Users of this class are +/// allowed to operate on this assumption. +template , class Compare = std::less> +class PriorityQueue : private Compare { +public: + using container_type = Container; + using value_type = typename Container::value_type; + using size_type = typename Container::size_type; + using reference = typename Container::reference; + using const_reference = typename Container::const_reference; + using const_reverse_iterator = typename Container::const_reverse_iterator; + using const_iterator = typename Container::const_iterator; + + //@{ + /// Construct a PriorityQueue, optionally providing a comparator object. + PriorityQueue(const Compare& comparator, const Container& cont); + + explicit PriorityQueue(const Compare& comparator = Compare{}, Container&& cont = Container{}); + + template + PriorityQueue(InputIt first, InputIt last, const Compare& comparator, const Container& cont); + + template + PriorityQueue(InputIt first, InputIt last, const Compare& comparator = Compare{}, Container&& cont = Container{}); + //@} + // Skipping Allocator-specific template constructors. + + PriorityQueue(const PriorityQueue&) = default; + PriorityQueue(PriorityQueue&&) = default; + PriorityQueue& operator=(const PriorityQueue&) = default; + PriorityQueue& operator=(PriorityQueue&&) = default; + + bool empty() const; + size_type size() const; + + //@{ + /// Push an element to the priority queue. + /// + /// If insertion to the underlying `Container` invalidates + /// iterators and references, any iterators and references into this + /// priority queue are also invalidated. By default, this is the case. + void push(const T& value); + void push(T&& value); + //@} + + /// Pop the largest element from the priority queue. + /// + /// If `pop_back` on the underlying `Container` invalidates + /// iterators and references, any iterators and reference into this + /// priority queue are also invalidated. By default, this is *NOT* the case. + /// + /// Calling `pop()` on an empty priority queue is undefined. + void pop(); + + /// Return a reference to the largest element of the priority queue. + /// + /// Calling `top()` on an empty priority queue is undefined. + const_reference top() const; + + /// Pop the top of the queue and return it by moving it out of the queue. + /// + /// Note: This method does not exist in `std::priority_queue`. + /// + /// Calling `pop_top()` on an empty priorty queue is undefined. + value_type pop_top(); + + // FIXME: emplace() deliberately omitted for simplicity. + + /// Swap the contents of this priority queue with the contents of \a other. + void swap(PriorityQueue& other); + + // Not in std::priority_queue: + + /// Return an iterator to the beginning of the queue (smallest element first). + const_iterator begin() const; + + /// Return an iterator to the end of the queue (largest element last); + const_iterator end() const; + + /// Return a reverse iterator into the priority queue (largest element first). + const_reverse_iterator rbegin() const; + + /// Return a reverse iterator representing the end of the priority queue (smallest element last). + const_reverse_iterator rend() const; + + /// Erase element pointed to by \a it. + /// + /// Note: This function differs from `std::priority_queue` by returning the erased + /// element using move semantics. + /// + /// Calling `erase()` with a beyond-end iterator (such as what is returned by `end()`) + /// is undefined. + value_type erase(const_iterator it); + + /// Remove all elements from the priority queue. + void clear(); + + /// Calls `reserve()` on the underlying `Container`. + void reserve(size_type); + +private: + Container m_queue; + + const Compare& compare() const; + Compare& compare(); +}; + + +/// Implementation + +template +PriorityQueue::PriorityQueue(const Compare& comparator, const Container& cont) + : Compare(comparator) + , m_queue(cont) +{ +} + +template +PriorityQueue::PriorityQueue(const Compare& comparator, Container&& cont) + : Compare(comparator) + , m_queue(std::move(cont)) +{ +} + +template +template +PriorityQueue::PriorityQueue(InputIt first, InputIt last, const Compare& comparator, + const Container& cont) + : Compare(comparator) + , m_queue(cont) +{ + for (auto it = first; it != last; ++it) { + push(*it); + } +} + +template +template +PriorityQueue::PriorityQueue(InputIt first, InputIt last, const Compare& comparator, + Container&& cont) + : Compare(comparator) + , m_queue(std::move(cont)) +{ + for (auto it = first; it != last; ++it) { + push(*it); + } +} + +template +typename PriorityQueue::size_type PriorityQueue::size() const +{ + return m_queue.size(); +} + +template +bool PriorityQueue::empty() const +{ + return m_queue.empty(); +} + +template +void PriorityQueue::push(const T& element) +{ + auto it = std::lower_bound(m_queue.begin(), m_queue.end(), element, compare()); + m_queue.insert(it, element); +} + +template +void PriorityQueue::push(T&& element) +{ + auto it = std::lower_bound(m_queue.begin(), m_queue.end(), element, compare()); + m_queue.insert(it, std::move(element)); +} + +template +void PriorityQueue::pop() +{ + m_queue.pop_back(); +} + +template +typename PriorityQueue::const_reference PriorityQueue::top() const +{ + return m_queue.back(); +} + +template +typename PriorityQueue::value_type PriorityQueue::pop_top() +{ + value_type value = std::move(m_queue.back()); + m_queue.pop_back(); + return value; +} + +template +Compare& PriorityQueue::compare() +{ + return *this; +} + +template +const Compare& PriorityQueue::compare() const +{ + return *this; +} + +template +typename PriorityQueue::const_iterator PriorityQueue::begin() const +{ + return m_queue.begin(); +} + +template +typename PriorityQueue::const_iterator PriorityQueue::end() const +{ + return m_queue.end(); +} + +template +typename PriorityQueue::const_reverse_iterator +PriorityQueue::rbegin() const +{ + return m_queue.rbegin(); +} + +template +typename PriorityQueue::const_reverse_iterator +PriorityQueue::rend() const +{ + return m_queue.rend(); +} + +template +typename PriorityQueue::value_type +PriorityQueue::erase(const_iterator it) +{ + // Convert to non-const iterator: + auto non_const_iterator = m_queue.begin() + (it - m_queue.begin()); + value_type value = std::move(*non_const_iterator); + m_queue.erase(non_const_iterator); + return value; +} + +template +void PriorityQueue::clear() +{ + m_queue.clear(); +} + +template +void PriorityQueue::reserve(size_type sz) +{ + m_queue.reserve(sz); +} + +template +void PriorityQueue::swap(PriorityQueue& other) +{ + using std::swap; + swap(m_queue, other.m_queue); + swap(compare(), other.compare()); +} +} +} + +#endif // REALM_UTIL_PRIORITY_QUEUE_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/quote.hpp b/!main project/Pods/Realm/include/core/realm/util/quote.hpp new file mode 100644 index 0000000..7959a44 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/quote.hpp @@ -0,0 +1,175 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2015] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ +#ifndef REALM_UTIL_QUOTE_HPP +#define REALM_UTIL_QUOTE_HPP + +#include + +namespace realm { +namespace util { + +template struct Quote { + bool smart; + util::BasicStringView view; +}; + + +/// Mark text for quotation during output to stream. +/// +/// If `out` is an output stream, and `str` is a string (e.g., an std::string), +/// then +/// +/// out << quoted(str) +/// +/// will write `str` in quoted form to `out`. +/// +/// Quotation involves bracketing the text in double quotes (`"`), and escaping +/// special characters according to the rules of C/C++ string literals. In this +/// case, the special characters are `"` and `\` as well as those that are not +/// printable (!std::isprint()). +/// +/// Quotation happens as the string is written to a stream, so there is no +/// intermediate representation of the quoted string. +template Quote quoted(util::BasicStringView) noexcept; + + +/// Same as quoted(), except that in this case, quotation is elided when the +/// specified string consists of a single printable word. Or, to be more +/// precise, quotation is elided if the string is nonempty, consists entirely of +/// printable charcters (std::isprint()), does not contain space (` `), and does +/// not conatian quotation (`"`) or backslash (`\`). +template Quote smart_quoted(util::BasicStringView) noexcept; + + +template +std::basic_ostream& operator<<(std::basic_ostream&, Quote); + + + + + +// Implementation + +template inline Quote quoted(util::BasicStringView view) noexcept +{ + bool smart = false; + return {smart, view}; +} + +template +inline Quote smart_quoted(util::BasicStringView view) noexcept +{ + bool smart = true; + return {smart, view}; +} + +template +inline std::basic_ostream& operator<<(std::basic_ostream& out, Quote quoted) +{ + std::locale loc = out.getloc(); + const std::ctype& ctype = std::use_facet>(loc); + util::BasicStringView view = quoted.view; + if (quoted.smart && !view.empty()) { + for (C ch : view) { + if (ch == '"' || ch == '\\' || !ctype.is(ctype.graph, ch)) + goto quote; + } + return out << view; // Throws + } + quote: + typename std::basic_ostream::sentry sentry{out}; + if (REALM_LIKELY(sentry)) { + C dquote = ctype.widen('"'); + C bslash = ctype.widen('\\'); + out.put(dquote); // Throws + bool follows_hex = false; + for (C ch : view) { + if (REALM_LIKELY(ctype.is(ctype.print, ch))) { + if (REALM_LIKELY(!follows_hex || !ctype.is(ctype.xdigit, ch))) { + if (REALM_LIKELY(ch != '"' || ch != '\\')) + goto put_char; + goto escape_char; + } + } + switch (ch) { + case '\a': + ch = ctype.widen('a'); + goto escape_char; + case '\b': + ch = ctype.widen('b'); + goto escape_char; + case '\f': + ch = ctype.widen('f'); + goto escape_char; + case '\n': + ch = ctype.widen('n'); + goto escape_char; + case '\r': + ch = ctype.widen('r'); + goto escape_char; + case '\t': + ch = ctype.widen('t'); + goto escape_char; + case '\v': + ch = ctype.widen('v'); + goto escape_char; + } + goto numeric; + escape_char: + out.put(bslash); // Throws + put_char: + out.put(ch); // Throws + next: + follows_hex = false; + continue; + numeric: + out.put(bslash); // Throws + using D = typename std::make_unsigned::type; + char digits[] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + D val = ch; + if (val < 512) { + out.put(ctype.widen(digits[val / 64 ])); // Throws + out.put(ctype.widen(digits[val % 64 / 8])); // Throws + out.put(ctype.widen(digits[val % 8])); // Throws + goto next; + } + out.put(ctype.widen('x')); // Throws + const int max_hex_digits = (std::numeric_limits::digits + 3) / 4; + C buffer[max_hex_digits]; + int i = max_hex_digits; + while (val != 0) { + buffer[--i] = ctype.widen(digits[val % 16]); + val /= 16; + } + out.write(buffer + i, max_hex_digits - i); // Throws + follows_hex = true; + } + out.put(dquote); // Throws + } + return out; +} + + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_QUOTE_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/random.hpp b/!main project/Pods/Realm/include/core/realm/util/random.hpp new file mode 100644 index 0000000..d0c7fe9 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/random.hpp @@ -0,0 +1,137 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2016] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ + +#ifndef REALM_UTIL_RANDOM_HPP +#define REALM_UTIL_RANDOM_HPP + +#include +#include +#include +#include +#include +#include + +namespace realm { +namespace util { + +/// Perform a nondeterministc seeding of the specified pseudo random number +/// generator. +/// +/// \tparam Engine A type that satisfies UniformRandomBitGenerator as defined by +/// the C++ standard. +/// +/// \tparam state_size The number of words of type Engine::result_type that make +/// up the engine state. +/// +/// Thread-safe. +/// +/// FIXME: Move this to core repo, as it is generally useful. +template +void seed_prng_nondeterministically(Engine&); + +template +std::string generate_random_lower_case_string(Engine& engine, size_t size); + + +// Implementation + +} // namespace util + +namespace _impl { + +void get_extra_seed_entropy(unsigned int& extra_entropy_1, unsigned int& extra_entropy_2, + unsigned int& extra_entropy_3); + +} // namespace _impl + +namespace util { + +template void seed_prng_nondeterministically(Engine& engine) +{ + // This implementation was informed and inspired by + // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0205r0.html. + // + // The number of bits of entropy needed is `state_size * + // std::numeric_limits::digits` (assuming that + // the engine uses all available bits in each word). + // + // Each invocation of `std::random_device::operator()` gives us + // `std::numeric_limits::digits` bits (assuming maximum + // entropy). Note that `std::random_device::result_type` must be `unsigned + // int`, `std::random_device::min()` must return zero, and + // `std::random_device::max()` must return `std::numeric_limits::max()`. + // + // Ideally, we could have used `std::random_device::entropy()` as the actual + // number of bits of entropy produced per invocation of + // `std::random_device::operator()`, however, it is incorrectly implemented + // on many platform. Also, it is supposed to return zero when + // `std::random_device` is just a PRNG, but that would leave us with no way + // to continue. + // + // When the actual entropy from `std::random_device` is less than maximum, + // the seeding will be less than optimal. For example, if the actual entropy + // is only half of the maximum, then the seeding will only produce half the + // entrpy that it ought to, but that will generally still be a good seeding. + // + // For the (assumed) rare cases where `std::random_device` is a PRGN that is + // not nondeterministically seeded, we include a bit of extra entropy taken + // from such places as the current time and the ID of the executing process + // (when available). + + constexpr long seed_bits_needed = state_size * + long(std::numeric_limits::digits); + constexpr int seed_bits_per_device_invocation = + std::numeric_limits::digits; + constexpr size_t seed_words_needed = + size_t((seed_bits_needed + (seed_bits_per_device_invocation - 1)) / + seed_bits_per_device_invocation); // Rounding up + constexpr int num_extra = 3; + std::array seed_values; + std::random_device rnddev; + std::generate(seed_values.begin(), seed_values.end()-num_extra, std::ref(rnddev)); + + unsigned int extra_entropy[3]; + _impl::get_extra_seed_entropy(extra_entropy[0], extra_entropy[1], extra_entropy[2]); + static_assert(num_extra == sizeof extra_entropy / sizeof extra_entropy[0], "Mismatch"); + std::copy(extra_entropy, extra_entropy+num_extra, seed_values.end()-num_extra); + + std::seed_seq seed_seq(seed_values.begin(), seed_values.end()); + engine.seed(seed_seq); +} + +template +std::string generate_random_lower_case_string(Engine& engine, size_t size) +{ + std::uniform_int_distribution dist(0, 25); + std::string str; + str.reserve(size); + for (size_t i = 0; i < size; ++i) { + short val = dist(engine); + char c = 'a' + char(val); + str.push_back(c); + } + return str; +} + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_RANDOM_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/resource_limits.hpp b/!main project/Pods/Realm/include/core/realm/util/resource_limits.hpp new file mode 100644 index 0000000..858624a --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/resource_limits.hpp @@ -0,0 +1,83 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2015] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ +#ifndef REALM_UTIL_RESOURCE_LIMITS_HPP +#define REALM_UTIL_RESOURCE_LIMITS_HPP + +namespace realm { +namespace util { + + +enum class Resource { + /// The maximum size, in bytes, of the core file produced when the memory + /// image of this process is dumped. If the memory image is larger than the + /// limit, the core file will not be created. Same as `RLIMIT_CORE` of + /// POSIX. + core_dump_size, + + /// The maximum CPU time, in seconds, available to this process. If the + /// limit is exceeded, the process will be killed. Same as `RLIMIT_CPU` of + /// POSIX. + cpu_time, + + /// The maximum size, in bytes, of the data segment of this process. If the + /// limit is exceede, std::malloc() will fail with `errno` equal to + /// `ENOMEM`. Same as `RLIMIT_DATA` of POSIX. + data_segment_size, + + /// The maximum size, in bytes, of a file that is modified by this + /// process. If the limit is exceede, the process will be killed. Same as + /// `RLIMIT_FSIZE` of POSIX. + file_size, + + /// One plus the maximum file descriptor value that can be opened by this + /// process. Same as `RLIMIT_NOFILE` of POSIX. + num_open_files, + + /// The maximum size, in bytes, of the stack of the main thread of this + /// process. If the limit is exceede, the process is killed. Same as + /// `RLIMIT_STACK` of POSIX. + stack_size, + + /// The maximum size, in bytes, of the process's virtual memory (address + /// space). If the limit is exceeded due to heap allocation, std::malloc() + /// will fail with `errno` equal to `ENOMEM`. If the limit is exceeded due + /// to explicit memory mapping, mmap() will fail with `errno` equal to + /// `ENOMEM`. If the limit is exceeded due to stack expansion, the process + /// will be killed. Same as `RLIMIT_AS` of POSIX. + virtual_memory_size +}; + + +bool system_has_rlimit(Resource) noexcept; + + +//@{ +/// Get or set resouce limits. A negative value means 'unlimited', both when +/// getting and when setting. +long get_hard_rlimit(Resource); +long get_soft_rlimit(Resource); +void set_soft_rlimit(Resource, long value); +//@} + + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_RESOURCE_LIMITS_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/safe_int_ops.hpp b/!main project/Pods/Realm/include/core/realm/util/safe_int_ops.hpp new file mode 100644 index 0000000..15030ad --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/safe_int_ops.hpp @@ -0,0 +1,623 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_SAFE_INT_OPS_HPP +#define REALM_UTIL_SAFE_INT_OPS_HPP + +#ifdef _WIN32 +#undef max // collides with numeric_limits::max called later in this header file +#undef min // collides with numeric_limits::min called later in this header file +#endif + +#include + +#include +#include +#include + +namespace realm { +namespace util { + + +/// Perform integral or floating-point promotion on the argument. This +/// is useful for example when printing a number of arbitrary numeric +/// type to 'stdout', since it will convert values of character-like +/// types to regular integer types, which will then be printed as +/// numbers rather characters. +template +typename Promote::type promote(T value) noexcept; + + +/// This function allows you to test for a negative value in any +/// numeric type, even when the type is unsigned. Normally, when the +/// type is unsigned, such a test will produce a compiler warning. +template +bool is_negative(T value) noexcept; + + +/// Cast the specified value to the specified unsigned type reducing +/// the value (or in case of negative values, the two's complement +/// representation) modulo `2**N` where `N` is the number of value +/// bits (or digits) in the unsigned target type. This is usefull in +/// cases where the target type may be `bool`, but need not be `bool`. +template +To cast_to_unsigned(From) noexcept; + + +//@{ + +/// Compare two integers of the same, or of different type, and +/// produce the expected result according to the natural +/// interpretation of the operation. +/// +/// Note that in general a standard comparison between a signed and an +/// unsigned integer type is unsafe, and it often generates a compiler +/// warning. An example is a 'less than' comparison between a negative +/// value of type 'int' and a small positive value of type +/// 'unsigned'. In this case the negative value will be converted to +/// 'unsigned' producing a large positive value which, in turn, will +/// lead to the counter intuitive result of 'false'. +/// +/// Please note that these operation incur absolutely no overhead when +/// the two types have the same signedness. +/// +/// These functions check at compile time that both types have valid +/// specializations of std::numeric_limits<> and that both are indeed +/// integers. +/// +/// These functions make absolutely no assumptions about the platform +/// except that it complies with at least C++03. + +template +inline bool int_equal_to(A, B) noexcept; +template +inline bool int_not_equal_to(A, B) noexcept; +template +inline bool int_less_than(A, B) noexcept; +template +inline bool int_less_than_or_equal(A, B) noexcept; +template +inline bool int_greater_than(A, B) noexcept; +template +inline bool int_greater_than_or_equal(A, B) noexcept; + +//@} + + +//@{ + +/// Check for overflow in integer variable `lval` while adding integer +/// `rval` to it, or while subtracting integer `rval` from it. Returns +/// true on positive or negative overflow. +/// +/// Both `lval` and `rval` must be of an integer type for which a +/// specialization of std::numeric_limits<> exists. The two types need +/// not be the same, in particular, one can be signed and the other +/// one can be unsigned. +/// +/// These functions are especially well suited for cases where \a rval +/// is a compile-time constant. +/// +/// These functions check at compile time that both types have valid +/// specializations of std::numeric_limits<> and that both are indeed +/// integers. +/// +/// These functions make absolutely no assumptions about the platform +/// except that it complies with at least C++03. + +template +inline bool int_add_with_overflow_detect(L& lval, R rval) noexcept; + +template +inline bool int_subtract_with_overflow_detect(L& lval, R rval) noexcept; + +//@} + + +/// Check for positive overflow when multiplying two positive integers +/// of the same, or of different type. Returns true on overflow. +/// +/// \param lval Must not be negative. Both signed and unsigned types +/// can be used. +/// +/// \param rval Must be stricly greater than zero. Both signed and +/// unsigned types can be used. +/// +/// This function is especially well suited for cases where \a rval is +/// a compile-time constant. +/// +/// This function checks at compile time that both types have valid +/// specializations of std::numeric_limits<> and that both are indeed +/// integers. +/// +/// This function makes absolutely no assumptions about the platform +/// except that it complies with at least C++03. +template +inline bool int_multiply_with_overflow_detect(L& lval, R rval) noexcept; + + +/// Checks for positive overflow when performing a bitwise shift to +/// the left on a non-negative value of arbitrary integer +/// type. Returns true on overflow. +/// +/// \param lval Must not be negative. Both signed and unsigned types +/// can be used. +/// +/// \param i Must be non-negative and such that L(1)>>i has a +/// value that is defined by the C++03 standard. In particular, the +/// value of i must not exceed the number of bits of storage type T as +/// shifting by this amount is not defined by the standard. +/// +/// This function makes absolutely no assumptions about the platform +/// except that it complies with at least C++03. +template +inline bool int_shift_left_with_overflow_detect(T& lval, int i) noexcept; + + +//@{ + +/// Check for overflow when casting an integer value from one type to +/// another. While the first function is a mere check, the second one +/// also carries out the cast, but only when there is no +/// overflow. Both return true on overflow. +/// +/// These functions check at compile time that both types have valid +/// specializations of std::numeric_limits<> and that both are indeed +/// integers. +/// +/// These functions make absolutely no assumptions about the platform +/// except that it complies with at least C++03. + +template +bool int_cast_has_overflow(From from) noexcept; + +template +bool int_cast_with_overflow_detect(From from, To& to) noexcept; + +//@} + + +/// Convert negative values from two's complement representation to the +/// platforms native representation. +/// +/// If `To` is an unsigned type, this function does nothing beyond casting the +/// specified value to `To`. Otherwise, `To` is a signed type, and negative +/// values will be converted from two's complement representation in unsigned +/// `From` to the platforms native representation in `To`. +/// +/// For signed `To` the result is well-defined if, and only if the value with +/// the specified two's complement representation is representable in the +/// specified signed type. While this is generally the case when using +/// corresponding signed/unsigned type pairs, it is not guaranteed by the +/// standard. However, if you know that the signed type has at least as many +/// value bits as the unsigned type, then the result is always +/// well-defined. Note that a 'value bit' in this context is the same as a +/// 'digit' from the point of view of `std::numeric_limits`. +/// +/// On platforms that use two's complement representation of negative values, +/// this function is expected to be completely optimized away. This has been +/// observed to be true with both GCC 4.8 and Clang 3.2. +/// +/// Note that the **opposite** direction (from the platforms native +/// representation to two's complement) is trivially handled by casting the +/// signed value to a value of a sufficiently wide unsigned integer type. An +/// unsigned type will be sufficiently wide if it has at least one more value +/// bit than the signed type. +/// +/// Interestingly, the C++ language offers no direct way of doing what this +/// function does, yet, this function is implemented in a way that makes no +/// assumption about the underlying platform except what is guaranteed by C++11. +/// +/// \tparam From The unsigned type used to store the two's complement +/// representation. +/// +/// \tparam To A signed or unsigned integer type. +template +To from_twos_compl(From twos_compl) noexcept; + + +// Implementation: + +template +inline typename Promote::type promote(T value) noexcept +{ + typedef typename Promote::type promoted_type; + promoted_type value_2 = promoted_type(value); + return value_2; +} + +} // namespace util + +namespace _impl { + +template +struct IsNegative { + static bool test(T value) noexcept + { + return value < 0; + } +}; +template +struct IsNegative { + static bool test(T) noexcept + { + return false; + } +}; + +template +struct CastToUnsigned { + template + static To cast(From value) noexcept + { + return To(value); + } +}; +template <> +struct CastToUnsigned { + template + static bool cast(From value) noexcept + { + return bool(unsigned(value) & 1); + } +}; + +template +struct SafeIntBinopsImpl { +}; + +// (unsigned, unsigned) (all size combinations) +// +// This implementation utilizes the fact that overflow in unsigned +// arithmetic is guaranteed to be handled by reduction modulo 2**N +// where N is the number of bits in the unsigned type. The purpose of +// the bitwise 'and' with lim_l::max() is to make a cast to bool +// behave the same way as casts to other unsigned integer types. +// Finally, this implementation uses the fact that if modular addition +// overflows, then the result must be a value that is less than both +// operands. Also, if modular subtraction overflows, then the result +// must be a value that is greater than the first operand. +template +struct SafeIntBinopsImpl { + typedef std::numeric_limits lim_l; + typedef std::numeric_limits lim_r; + static const int needed_bits_l = lim_l::digits; + static const int needed_bits_r = lim_r::digits; + static const int needed_bits = needed_bits_l >= needed_bits_r ? needed_bits_l : needed_bits_r; + typedef typename util::FastestUnsigned::type common_unsigned; + static bool equal(L l, R r) noexcept + { + return common_unsigned(l) == common_unsigned(r); + } + static bool less(L l, R r) noexcept + { + return common_unsigned(l) < common_unsigned(r); + } + static bool add(L& lval, R rval) noexcept + { + L lval_2 = util::cast_to_unsigned(lval + rval); + bool overflow = common_unsigned(lval_2) < common_unsigned(rval); + if (REALM_UNLIKELY(overflow)) + return true; + lval = lval_2; + return false; + } + static bool sub(L& lval, R rval) noexcept + { + common_unsigned lval_2 = common_unsigned(lval) - common_unsigned(rval); + bool overflow = lval_2 > common_unsigned(lval); + if (REALM_UNLIKELY(overflow)) + return true; + lval = util::cast_to_unsigned(lval_2); + return false; + } +}; + +// (unsigned, signed) (all size combinations) +template +struct SafeIntBinopsImpl { + typedef std::numeric_limits lim_l; + typedef std::numeric_limits lim_r; + static const int needed_bits_l = lim_l::digits; + static const int needed_bits_r = lim_r::digits + 1; + static const int needed_bits = needed_bits_l >= needed_bits_r ? needed_bits_l : needed_bits_r; + typedef typename util::FastestUnsigned::type common_unsigned; + typedef std::numeric_limits lim_cu; + static bool equal(L l, R r) noexcept + { + return (lim_l::digits > lim_r::digits) ? r >= 0 && l == util::cast_to_unsigned(r) : R(l) == r; + } + static bool less(L l, R r) noexcept + { + return (lim_l::digits > lim_r::digits) ? r >= 0 && l < util::cast_to_unsigned(r) : R(l) < r; + } + static bool add(L& lval, R rval) noexcept + { + common_unsigned lval_2 = lval + common_unsigned(rval); + bool overflow; + if (lim_l::digits < lim_cu::digits) { + overflow = common_unsigned(lval_2) > common_unsigned(lim_l::max()); + } + else { + overflow = (lval_2 < common_unsigned(lval)) == (rval >= 0); + } + if (REALM_UNLIKELY(overflow)) + return true; + lval = util::cast_to_unsigned(lval_2); + return false; + } + static bool sub(L& lval, R rval) noexcept + { + common_unsigned lval_2 = lval - common_unsigned(rval); + bool overflow; + if (lim_l::digits < lim_cu::digits) { + overflow = common_unsigned(lval_2) > common_unsigned(lim_l::max()); + } + else { + overflow = (common_unsigned(lval_2) > common_unsigned(lval)) == (rval >= 0); + } + if (REALM_UNLIKELY(overflow)) + return true; + lval = util::cast_to_unsigned(lval_2); + return false; + } +}; + +// (signed, unsigned) (all size combinations) +template +struct SafeIntBinopsImpl { + typedef std::numeric_limits lim_l; + typedef std::numeric_limits lim_r; + static const int needed_bits_l = lim_l::digits + 1; + static const int needed_bits_r = lim_r::digits; + static const int needed_bits = needed_bits_l >= needed_bits_r ? needed_bits_l : needed_bits_r; + typedef typename util::FastestUnsigned::type common_unsigned; + static bool equal(L l, R r) noexcept + { + return (lim_l::digits < lim_r::digits) ? l >= 0 && util::cast_to_unsigned(l) == r : l == L(r); + } + static bool less(L l, R r) noexcept + { + return (lim_l::digits < lim_r::digits) ? l < 0 || util::cast_to_unsigned(l) < r : l < L(r); + } + static bool add(L& lval, R rval) noexcept + { + common_unsigned max_add = common_unsigned(lim_l::max()) - common_unsigned(lval); + bool overflow = common_unsigned(rval) > max_add; + if (REALM_UNLIKELY(overflow)) + return true; + lval = util::from_twos_compl(common_unsigned(lval) + rval); + return false; + } + static bool sub(L& lval, R rval) noexcept + { + common_unsigned max_sub = common_unsigned(lval) - common_unsigned(lim_l::min()); + bool overflow = common_unsigned(rval) > max_sub; + if (REALM_UNLIKELY(overflow)) + return true; + lval = util::from_twos_compl(common_unsigned(lval) - rval); + return false; + } +}; + +// (signed, signed) (all size combinations) +template +struct SafeIntBinopsImpl { + typedef std::numeric_limits lim_l; + static bool equal(L l, R r) noexcept + { + return l == r; + } + static bool less(L l, R r) noexcept + { + return l < r; + } + static bool add(L& lval, R rval) noexcept + { + // Note that both subtractions below occur in a signed type + // that is at least as wide as both of the two types. Note + // also that any signed type guarantees that there is no + // overflow when subtracting two negative values or two + // non-negative value. See C99 (adopted as subset of C++11) + // section 6.2.6.2 "Integer types" paragraph 2. + if (rval < 0) { + if (REALM_UNLIKELY(lval < lim_l::min() - rval)) + return true; + } + else { + if (REALM_UNLIKELY(lval > lim_l::max() - rval)) + return true; + } + // The following statement has exactly the same effect as + // `lval += rval`. + lval = L(lval + rval); + return false; + } + static bool sub(L& lval, R rval) noexcept + { + // Note that both subtractions below occur in a signed type + // that is at least as wide as both of the two types. Note + // also that there can be no overflow when adding a negative + // value to a non-negative value, or when adding a + // non-negative value to a negative one. + if (rval < 0) { + if (REALM_UNLIKELY(lval > lim_l::max() + rval)) + return true; + } + else { + if (REALM_UNLIKELY(lval < lim_l::min() + rval)) + return true; + } + // The following statement has exactly the same effect as + // `lval += rval`. + lval = L(lval - rval); + return false; + } +}; + +template +struct SafeIntBinops : SafeIntBinopsImpl::is_signed, std::numeric_limits::is_signed> { + typedef std::numeric_limits lim_l; + typedef std::numeric_limits lim_r; + static_assert(lim_l::is_specialized && lim_r::is_specialized, + "std::numeric_limits<> must be specialized for both types"); + static_assert(lim_l::is_integer && lim_r::is_integer, "Both types must be integers"); +}; + +} // namespace _impl + +namespace util { + +template +inline bool is_negative(T value) noexcept +{ + return _impl::IsNegative::is_signed>::test(value); +} + +template +inline To cast_to_unsigned(From value) noexcept +{ + return _impl::CastToUnsigned::cast(value); +} + +template +inline bool int_equal_to(A a, B b) noexcept +{ + return _impl::SafeIntBinops::equal(a, b); +} + +template +inline bool int_not_equal_to(A a, B b) noexcept +{ + return !_impl::SafeIntBinops::equal(a, b); +} + +template +inline bool int_less_than(A a, B b) noexcept +{ + return _impl::SafeIntBinops::less(a, b); +} + +template +inline bool int_less_than_or_equal(A a, B b) noexcept +{ + return !_impl::SafeIntBinops::less(b, a); // Not greater than +} + +template +inline bool int_greater_than(A a, B b) noexcept +{ + return _impl::SafeIntBinops::less(b, a); +} + +template +inline bool int_greater_than_or_equal(A a, B b) noexcept +{ + return !_impl::SafeIntBinops::less(a, b); // Not less than +} + +template +inline bool int_add_with_overflow_detect(L& lval, R rval) noexcept +{ + return _impl::SafeIntBinops::add(lval, rval); +} + +template +inline bool int_subtract_with_overflow_detect(L& lval, R rval) noexcept +{ + return _impl::SafeIntBinops::sub(lval, rval); +} + +template +inline bool int_multiply_with_overflow_detect(L& lval, R rval) noexcept +{ + // FIXME: Check if the following optimizes better (if it works at all): + // L lval_2 = L(lval * rval); + // bool overflow = rval != 0 && (lval_2 / rval) != lval; + typedef std::numeric_limits lim_l; + typedef std::numeric_limits lim_r; + static_assert(lim_l::is_specialized && lim_r::is_specialized, + "std::numeric_limits<> must be specialized for both types"); + static_assert(lim_l::is_integer && lim_r::is_integer, "Both types must be integers"); + REALM_ASSERT(int_greater_than_or_equal(lval, 0)); + REALM_ASSERT(int_greater_than(rval, 0)); + if (int_less_than(lim_l::max() / rval, lval)) + return true; + lval = L(lval * rval); + return false; +} + +template +inline bool int_shift_left_with_overflow_detect(T& lval, int i) noexcept +{ + typedef std::numeric_limits lim; + static_assert(lim::is_specialized, "std::numeric_limits<> must be specialized for T"); + static_assert(lim::is_integer, "T must be an integer type"); + REALM_ASSERT(int_greater_than_or_equal(lval, 0)); + if ((lim::max() >> i) < lval) + return true; + lval <<= i; + return false; +} + +template +inline bool int_cast_has_overflow(From from) noexcept +{ + typedef std::numeric_limits lim_to; + return int_less_than(from, lim_to::min()) || int_less_than(lim_to::max(), from); +} + +template +inline bool int_cast_with_overflow_detect(From from, To& to) noexcept +{ + if (REALM_LIKELY(!int_cast_has_overflow(from))) { + to = To(from); + return false; + } + return true; +} + +template +inline To from_twos_compl(From twos_compl) noexcept +{ + typedef std::numeric_limits lim_f; + typedef std::numeric_limits lim_t; + static_assert(lim_f::is_specialized && lim_t::is_specialized, + "std::numeric_limits<> must be specialized for both types"); + static_assert(lim_f::is_integer && lim_t::is_integer, "Both types must be integers"); + static_assert(!lim_f::is_signed, "`From` must be unsigned"); + To native; + int sign_bit_pos = lim_f::digits - 1; + From sign_bit = From(1) << sign_bit_pos; + bool non_negative = !lim_t::is_signed || (twos_compl & sign_bit) == 0; + if (non_negative) { + // Non-negative value + native = To(twos_compl); + } + else { + // Negative value + native = To(-1 - To(From(-1) - twos_compl)); + } + return native; +} + + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_SAFE_INT_OPS_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/scope_exit.hpp b/!main project/Pods/Realm/include/core/realm/util/scope_exit.hpp new file mode 100644 index 0000000..5410d19 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/scope_exit.hpp @@ -0,0 +1,72 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_SCOPE_EXIT_HPP +#define REALM_UTIL_SCOPE_EXIT_HPP + +#include +#include + +#include + +namespace realm { +namespace util { + +template +class ScopeExit { +public: + explicit ScopeExit(const H& handler) noexcept(std::is_nothrow_copy_constructible::value) + : m_handler(handler) + { + } + + explicit ScopeExit(H&& handler) noexcept(std::is_nothrow_move_constructible::value) + : m_handler(std::move(handler)) + { + } + + ScopeExit(ScopeExit&& se) noexcept(std::is_nothrow_move_constructible::value) + : m_handler(std::move(se.m_handler)) + { + se.m_handler = none; + } + + ~ScopeExit() noexcept + { + if (m_handler) + (*m_handler)(); + } + + static_assert(noexcept(std::declval()()), "Handler must be nothrow executable"); + static_assert(std::is_nothrow_destructible::value, "Handler must be nothrow destructible"); + +private: + util::Optional m_handler; +}; + +template +ScopeExit::type> make_scope_exit(H&& handler) noexcept( + noexcept(ScopeExit::type>(std::forward(handler)))) +{ + return ScopeExit::type>(std::forward(handler)); +} + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_SCOPE_EXIT_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/serializer.hpp b/!main project/Pods/Realm/include/core/realm/util/serializer.hpp new file mode 100644 index 0000000..5f7fc03 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/serializer.hpp @@ -0,0 +1,92 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_SERIALIZER_HPP +#define REALM_UTIL_SERIALIZER_HPP + +#include +#include + +#include +#include +#include + +namespace realm { + +class BinaryData; +struct null; +struct RowIndex; +class StringData; +class Timestamp; +class LinkMap; + +namespace util { +namespace serializer { + + +// Definitions +template +std::string print_value(T value); + +template +std::string print_value(Optional value); + +const static std::string value_separator = "."; + +// Specializations declared here to be defined in the cpp file +template <> std::string print_value<>(BinaryData); +template <> std::string print_value<>(bool); +template <> std::string print_value<>(realm::null); +template <> std::string print_value<>(StringData); +template <> std::string print_value<>(realm::Timestamp); +template <> std::string print_value<>(realm::RowIndex); + +// General implementation for most types +template +std::string print_value(T value) +{ + std::stringstream ss; + ss << value; + return ss.str(); +} + +template +std::string print_value(Optional value) +{ + if (bool(value)) { + return print_value(*value); + } else { + return "NULL"; + } +} + +struct SerialisationState +{ + std::string describe_column(ConstTableRef table, size_t col_ndx); + std::string describe_columns(const LinkMap& link_map, size_t target_col_ndx); + std::string get_column_name(ConstTableRef table, size_t col_ndx); + std::string get_backlink_column_name(ConstTableRef from, size_t col_ndx); + std::string get_variable_name(ConstTableRef table); + std::vector subquery_prefix_list; +}; + +} // namespace serializer +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_SERIALIZER_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/shared_ptr.hpp b/!main project/Pods/Realm/include/core/realm/util/shared_ptr.hpp new file mode 100644 index 0000000..1a34701 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/shared_ptr.hpp @@ -0,0 +1,131 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_SHARED_PTR_HPP +#define REALM_SHARED_PTR_HPP + +#include // size_t + +namespace realm { +namespace util { + +template +class SharedPtr { +public: + SharedPtr(T* p) + { + init(p); + } + + SharedPtr() + { + init(0); + } + + ~SharedPtr() + { + decref(); + } + + SharedPtr(const SharedPtr& o) + : m_ptr(o.m_ptr) + , m_count(o.m_count) + { + incref(); + } + + SharedPtr& operator=(const SharedPtr& o) + { + // if (m_ptr == o.m_ptr) + if (this == &o) + return *this; + decref(); + m_ptr = o.m_ptr; + m_count = o.m_count; + incref(); + return *this; + } + + T* operator->() const + { + return m_ptr; + } + + T& operator*() const + { + return *m_ptr; + } + + T* get() const + { + return m_ptr; + } + + bool operator==(const SharedPtr& o) const + { + return m_ptr == o.m_ptr; + } + + bool operator!=(const SharedPtr& o) const + { + return m_ptr != o.m_ptr; + } + + bool operator<(const SharedPtr& o) const + { + return m_ptr < o.m_ptr; + } + + size_t ref_count() const + { + return *m_count; + } + +private: + void init(T* p) + { + m_ptr = p; + try { + m_count = new size_t(1); + } + catch (...) { + delete p; + throw; + } + } + + void decref() + { + if (--(*m_count) == 0) { + delete m_ptr; + delete m_count; + } + } + + void incref() + { + ++(*m_count); + } + + T* m_ptr; + size_t* m_count; +}; +} +} + +#endif diff --git a/!main project/Pods/Realm/include/core/realm/util/signal_blocker.hpp b/!main project/Pods/Realm/include/core/realm/util/signal_blocker.hpp new file mode 100644 index 0000000..bb1489b --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/signal_blocker.hpp @@ -0,0 +1,79 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2016] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ + +#ifndef REALM_UTIL_SIGNAL_BLOCKER_HPP +#define REALM_UTIL_SIGNAL_BLOCKER_HPP + +#include + +#include + + +namespace realm { +namespace util { + +/// \brief Block all signals from being delivered to the instantiating thread. +/// +/// On platforms that support POSIX signals, the constructor will set the signal +/// mask such that all signals are blocked from being delivered to the calling +/// thread, and the destructor will restore the signal mask to its original +/// value. +/// +/// This scheme assumes that it is always the same thread that constructs and +/// destroys a particular instance of SignalBlocker, and that, for a particular +/// thread, two SignalBlocker objects never overlap in time, and the signal mask +/// is never modified by other means while a SignalBlocker object exists. +class SignalBlocker { +public: + SignalBlocker() noexcept; + ~SignalBlocker() noexcept; + +private: +#ifndef _WIN32 + ::sigset_t m_orig_mask; +#endif +}; + + + +// Implementation + +inline SignalBlocker::SignalBlocker() noexcept +{ +#ifndef _WIN32 + ::sigset_t mask; + sigfillset(&mask); + int ret = ::pthread_sigmask(SIG_BLOCK, &mask, &m_orig_mask); + REALM_ASSERT(ret == 0); +#endif +} + +inline SignalBlocker::~SignalBlocker() noexcept +{ +#ifndef _WIN32 + int ret = ::pthread_sigmask(SIG_SETMASK, &m_orig_mask, nullptr); + REALM_ASSERT(ret == 0); +#endif +} + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_SIGNAL_BLOCKER_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/string_buffer.hpp b/!main project/Pods/Realm/include/core/realm/util/string_buffer.hpp new file mode 100644 index 0000000..71736bd --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/string_buffer.hpp @@ -0,0 +1,209 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_STRING_BUFFER_HPP +#define REALM_UTIL_STRING_BUFFER_HPP + +#include +#include +#include + +#include +#include + +namespace realm { +namespace util { + + +// FIXME: In C++17, this can be replaced with std::string (since +// std::string::data() can return a mutable pointer in C++17). +template +class BasicStringBuffer { +public: + BasicStringBuffer() noexcept; + + std::string str() const; + + /// Returns the current size of the string in this buffer. This + /// size does not include the terminating zero. + size_t size() const noexcept; + + /// Gives read and write access to the bytes of this buffer. The + /// caller may read and write from *c_str() up to, but not + /// including, *(c_str()+size()). + char* data() noexcept; + + /// Gives read access to the bytes of this buffer. The caller may + /// read from *c_str() up to, but not including, + /// *(c_str()+size()). + const char* data() const noexcept; + + /// Guarantees that the returned string is zero terminated, that + /// is, *(c_str()+size()) is zero. The caller may read from + /// *c_str() up to and including *(c_str()+size()). + const char* c_str() const noexcept; + + void append(const std::string&); + + void append(const char* append_data, size_t append_size); + + /// Append a zero-terminated string to this buffer. + void append_c_str(const char* c_string); + + /// The specified size is understood as not including the + /// terminating zero. If the specified size is less than the + /// current size, then the string is truncated accordingly. If the + /// specified size is greater than the current size, then the + /// extra characters will have undefined values, however, there + /// will be a terminating zero at *(c_str()+size()), and the + /// original terminating zero will also be left in place such that + /// from the point of view of c_str(), the size of the string is + /// unchanged. + void resize(size_t new_size); + + /// The specified minimum capacity is understood as not including + /// the terminating zero. This operation does not change the size + /// of the string in the buffer as returned by size(). If the + /// specified capacity is less than the current capacity, this + /// operation has no effect. + void reserve(size_t min_capacity); + + /// Set size to zero. The capacity remains unchanged. + void clear() noexcept; + +private: + util::Buffer m_buffer; + size_t m_size; // Excluding the terminating zero + void reallocate(size_t min_capacity); +}; + +using StringBuffer = BasicStringBuffer; + + +// Implementation: + +template +BasicStringBuffer::BasicStringBuffer() noexcept + : m_size(0) +{ +} + +template +std::string BasicStringBuffer::str() const +{ + return std::string(m_buffer.data(), m_size); +} + +template +size_t BasicStringBuffer::size() const noexcept +{ + return m_size; +} + +template +char* BasicStringBuffer::data() noexcept +{ + return m_buffer.data(); +} + +template +const char* BasicStringBuffer::data() const noexcept +{ + return m_buffer.data(); +} + +template +const char* BasicStringBuffer::c_str() const noexcept +{ + static const char zero = 0; + const char* d = data(); + return d ? d : &zero; +} + +template +void BasicStringBuffer::append(const std::string& s) +{ + return append(s.data(), s.size()); +} + +template +void BasicStringBuffer::append_c_str(const char* c_string) +{ + append(c_string, std::strlen(c_string)); +} + +template +void BasicStringBuffer::reserve(size_t min_capacity) +{ + size_t capacity = m_buffer.size(); + if (capacity == 0 || capacity - 1 < min_capacity) + reallocate(min_capacity); +} + +template +void BasicStringBuffer::resize(size_t new_size) +{ + reserve(new_size); + // Note that even reserve(0) will attempt to allocate a + // buffer, so we can safely write the truncating zero at this + // time. + m_size = new_size; + m_buffer[new_size] = 0; +} + +template +void BasicStringBuffer::clear() noexcept +{ + if (m_buffer.size() == 0) + return; + m_size = 0; + m_buffer[0] = 0; +} + +template +void BasicStringBuffer::append(const char* append_data, size_t append_data_size) +{ + size_t new_size = m_size; + if (int_add_with_overflow_detect(new_size, append_data_size)) + throw util::BufferSizeOverflow(); + reserve(new_size); // Throws + realm::safe_copy_n(append_data, append_data_size, m_buffer.data() + m_size); + m_size = new_size; + m_buffer[new_size] = 0; // Add zero termination +} + + +template +void BasicStringBuffer::reallocate(size_t min_capacity) +{ + size_t min_capacity_2 = min_capacity; + // Make space for zero termination + if (int_add_with_overflow_detect(min_capacity_2, 1)) + throw util::BufferSizeOverflow(); + size_t new_capacity = m_buffer.size(); + if (int_multiply_with_overflow_detect(new_capacity, 2)) + new_capacity = std::numeric_limits::max(); // LCOV_EXCL_LINE + if (new_capacity < min_capacity_2) + new_capacity = min_capacity_2; + m_buffer.resize(new_capacity, 0, m_size, 0); // Throws +} + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_STRING_BUFFER_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/string_view.hpp b/!main project/Pods/Realm/include/core/realm/util/string_view.hpp new file mode 100644 index 0000000..74fdcb6 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/string_view.hpp @@ -0,0 +1,478 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2015] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ +#ifndef REALM_UTIL_STRING_VIEW_HPP +#define REALM_UTIL_STRING_VIEW_HPP + +#include +#include +#include +#include +#include +#include +#include + +#include + + +namespace realm { +namespace util { + +template> class BasicStringView { +public: + using value_type = C; + using traits_type = T; + using pointer = C*; + using const_pointer = const C*; + using reference = C&; + using const_reference = const C&; + using iterator = const_pointer; + using const_iterator = const_pointer; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + static constexpr size_type npos = size_type(-1); + + BasicStringView() noexcept; + BasicStringView(const std::basic_string&) noexcept; + BasicStringView(const char* data, size_type size) noexcept; + BasicStringView(const char* c_str) noexcept; + + explicit operator std::basic_string() const; + + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + + const_reverse_iterator rbegin() const noexcept; + const_reverse_iterator rend() const noexcept; + const_reverse_iterator crbegin() const noexcept; + const_reverse_iterator crend() const noexcept; + + const_reference operator[](size_type i) const noexcept; + const_reference at(size_type i) const; + const_reference front() const noexcept; + const_reference back() const noexcept; + + const_pointer data() const noexcept; + size_type size() const noexcept; + bool empty() const noexcept; + + BasicStringView substr(size_type i = 0, size_type n = npos) const; + int compare(BasicStringView other) const noexcept; + size_type find(BasicStringView, size_type i = 0) const noexcept; + size_type find(C ch, size_type i = 0) const noexcept; + size_type find_first_of(BasicStringView, size_type i = 0) const noexcept; + size_type find_first_of(C ch, size_type i = 0) const noexcept; + size_type find_first_not_of(BasicStringView, size_type i = 0) const noexcept; + size_type find_first_not_of(C ch, size_type i = 0) const noexcept; + +private: + const char* m_data = nullptr; + std::size_t m_size = 0; +}; + +template bool operator==(BasicStringView, BasicStringView) noexcept; +template bool operator!=(BasicStringView, BasicStringView) noexcept; +template bool operator< (BasicStringView, BasicStringView) noexcept; +template bool operator> (BasicStringView, BasicStringView) noexcept; +template bool operator<=(BasicStringView, BasicStringView) noexcept; +template bool operator>=(BasicStringView, BasicStringView) noexcept; + +template +bool operator==(std::decay_t>, BasicStringView) noexcept; +template +bool operator!=(std::decay_t>, BasicStringView) noexcept; +template +bool operator< (std::decay_t>, BasicStringView) noexcept; +template +bool operator> (std::decay_t>, BasicStringView) noexcept; +template +bool operator<=(std::decay_t>, BasicStringView) noexcept; +template +bool operator>=(std::decay_t>, BasicStringView) noexcept; + +template +bool operator==(BasicStringView, std::decay_t>) noexcept; +template +bool operator!=(BasicStringView, std::decay_t>) noexcept; +template +bool operator< (BasicStringView, std::decay_t>) noexcept; +template +bool operator> (BasicStringView, std::decay_t>) noexcept; +template +bool operator<=(BasicStringView, std::decay_t>) noexcept; +template +bool operator>=(BasicStringView, std::decay_t>) noexcept; + + +template +std::basic_ostream& operator<<(std::basic_ostream&, BasicStringView); + + +using StringView = BasicStringView; + + + + + +// Implementation + +template +inline BasicStringView::BasicStringView() noexcept +{ +} + +template +inline BasicStringView::BasicStringView(const std::basic_string& str) noexcept : + m_data{str.data()}, + m_size{str.size()} +{ +} + +template +inline BasicStringView::BasicStringView(const char* data, size_type size) noexcept : + m_data{data}, + m_size{size} +{ +} + +template +inline BasicStringView::BasicStringView(const char* c_str) noexcept : + m_data{c_str}, + m_size{T::length(c_str)} +{ +} + +template +inline BasicStringView::operator std::basic_string() const +{ + return {m_data, m_size}; // Throws +} + +template +inline auto BasicStringView::begin() const noexcept -> const_iterator +{ + return m_data; +} + +template +inline auto BasicStringView::end() const noexcept -> const_iterator +{ + return m_data + m_size; +} + +template +inline auto BasicStringView::cbegin() const noexcept -> const_iterator +{ + return begin(); +} + +template +inline auto BasicStringView::cend() const noexcept -> const_iterator +{ + return end(); +} + +template +inline auto BasicStringView::rbegin() const noexcept -> const_reverse_iterator +{ + return const_reverse_iterator{end()}; +} + +template +inline auto BasicStringView::rend() const noexcept -> const_reverse_iterator +{ + return const_reverse_iterator{begin()}; +} + +template +inline auto BasicStringView::crbegin() const noexcept -> const_reverse_iterator +{ + return rbegin(); +} + +template +inline auto BasicStringView::crend() const noexcept -> const_reverse_iterator +{ + return rend(); +} + +template +inline auto BasicStringView::operator[](size_type i) const noexcept -> const_reference +{ + return m_data[i]; +} + +template +inline auto BasicStringView::at(size_type i) const -> const_reference +{ + if (REALM_LIKELY(i < m_size)) + return m_data[i]; + throw std::out_of_range("index"); +} + +template +inline auto BasicStringView::front() const noexcept -> const_reference +{ + return m_data[0]; +} + +template +inline auto BasicStringView::back() const noexcept -> const_reference +{ + return m_data[m_size - 1]; +} + +template +inline auto BasicStringView::data() const noexcept -> const_pointer +{ + return m_data; +} + +template +inline auto BasicStringView::size() const noexcept -> size_type +{ + return m_size; +} + +template +inline bool BasicStringView::empty() const noexcept +{ + return (size() == 0); +} + +template +inline BasicStringView BasicStringView::substr(size_type i, size_type n) const +{ + if (REALM_LIKELY(i <= m_size)) { + size_type m = std::min(n, m_size - i); + return BasicStringView{m_data + i, m}; + } + throw std::out_of_range("index"); +} + +template +inline int BasicStringView::compare(BasicStringView other) const noexcept +{ + size_type n = std::min(m_size, other.m_size); + int ret = T::compare(m_data, other.m_data, n); + if (REALM_LIKELY(ret != 0)) + return ret; + if (m_size < other.m_size) + return -1; + if (m_size > other.m_size) + return 1; + return 0; +} + +template +inline auto BasicStringView::find(BasicStringView v, size_type i) const noexcept -> + size_type +{ + if (REALM_LIKELY(!v.empty())) { + if (REALM_LIKELY(i < m_size)) { + const C* p = std::search(begin() + i, end(), v.begin(), v.end()); + if (p != end()) + return size_type(p - begin()); + } + return npos; + } + return i; +} + +template +inline auto BasicStringView::find(C ch, size_type i) const noexcept -> size_type +{ + if (REALM_LIKELY(i < m_size)) { + const C* p = std::find(begin() + i, end(), ch); + if (p != end()) + return size_type(p - begin()); + } + return npos; +} + +template +inline auto BasicStringView::find_first_of(BasicStringView v, + size_type i) const noexcept -> size_type +{ + for (size_type j = i; j < m_size; ++j) { + if (REALM_LIKELY(v.find(m_data[j]) == npos)) + continue; + return j; + } + return npos; +} + +template +inline auto BasicStringView::find_first_of(C ch, size_type i) const noexcept -> size_type +{ + for (size_type j = i; j < m_size; ++j) { + if (REALM_UNLIKELY(m_data[j] == ch)) + return j; + } + return npos; +} + +template +inline auto BasicStringView::find_first_not_of(BasicStringView v, + size_type i) const noexcept -> size_type +{ + for (size_type j = i; j < m_size; ++j) { + if (REALM_UNLIKELY(v.find(m_data[j]) == npos)) + return j; + } + return npos; +} + +template +inline auto BasicStringView::find_first_not_of(C ch, size_type i) const noexcept -> size_type +{ + for (size_type j = i; j < m_size; ++j) { + if (REALM_UNLIKELY(m_data[j] != ch)) + return j; + } + return npos; +} + +template +inline bool operator==(BasicStringView lhs, BasicStringView rhs) noexcept +{ + return (lhs.compare(rhs) == 0); +} + +template +inline bool operator!=(BasicStringView lhs, BasicStringView rhs) noexcept +{ + return (lhs.compare(rhs) != 0); +} + +template +inline bool operator<(BasicStringView lhs, BasicStringView rhs) noexcept +{ + return (lhs.compare(rhs) < 0); +} + +template +inline bool operator>(BasicStringView lhs, BasicStringView rhs) noexcept +{ + return (lhs.compare(rhs) > 0); +} + +template +inline bool operator<=(BasicStringView lhs, BasicStringView rhs) noexcept +{ + return (lhs.compare(rhs) <= 0); +} + +template +inline bool operator>=(BasicStringView lhs, BasicStringView rhs) noexcept +{ + return (lhs.compare(rhs) >= 0); +} + +template +inline bool operator==(std::decay_t> lhs, BasicStringView rhs) noexcept +{ + return (lhs.compare(rhs) == 0); +} + +template +inline bool operator!=(std::decay_t> lhs, BasicStringView rhs) noexcept +{ + return (lhs.compare(rhs) != 0); +} + +template +inline bool operator<(std::decay_t> lhs, BasicStringView rhs) noexcept +{ + return (lhs.compare(rhs) < 0); +} + +template +inline bool operator>(std::decay_t> lhs, BasicStringView rhs) noexcept +{ + return (lhs.compare(rhs) > 0); +} + +template +inline bool operator<=(std::decay_t> lhs, BasicStringView rhs) noexcept +{ + return (lhs.compare(rhs) <= 0); +} + +template +inline bool operator>=(std::decay_t> lhs, BasicStringView rhs) noexcept +{ + return (lhs.compare(rhs) >= 0); +} + +template +inline bool operator==(BasicStringView lhs, std::decay_t> rhs) noexcept +{ + return (lhs.compare(rhs) == 0); +} + +template +inline bool operator!=(BasicStringView lhs, std::decay_t> rhs) noexcept +{ + return (lhs.compare(rhs) != 0); +} + +template +inline bool operator<(BasicStringView lhs, std::decay_t> rhs) noexcept +{ + return (lhs.compare(rhs) < 0); +} + +template +inline bool operator>(BasicStringView lhs, std::decay_t> rhs) noexcept +{ + return (lhs.compare(rhs) > 0); +} + +template +inline bool operator<=(BasicStringView lhs, std::decay_t> rhs) noexcept +{ + return (lhs.compare(rhs) <= 0); +} + +template +inline bool operator>=(BasicStringView lhs, std::decay_t> rhs) noexcept +{ + return (lhs.compare(rhs) >= 0); +} + +template +inline std::basic_ostream& operator<<(std::basic_ostream& out, + BasicStringView view) +{ + typename std::basic_ostream::sentry sentry{out}; + if (REALM_LIKELY(sentry)) + out.write(view.data(), view.size()); + return out; +} + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_STRING_VIEW_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/substitute.hpp b/!main project/Pods/Realm/include/core/realm/util/substitute.hpp new file mode 100644 index 0000000..b551f31 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/substitute.hpp @@ -0,0 +1,373 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2015] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ +#ifndef REALM_UTIL_SUBSTITUTE_HPP +#define REALM_UTIL_SUBSTITUTE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +namespace realm { +namespace util { +namespace _private { + +class SubstituterBase { +protected: + template struct FindArg1; + template struct FindArg2; + static StderrLogger s_default_logger; +}; + +} // namespace _private + + +struct SubstituterConfig { + /// Allow parsing to be considered successful even when syntax errors are + /// detected. When enabled, logging will happen on `warn`, instead of + /// `error` level. + bool lenient = false; + + /// The logger to be used by default. If left unspecified, the default + /// logger is one that logs to STDERR. In any case, logging happens only + /// during parsing. + Logger* logger = nullptr; +}; + + +/// Perform variable substitutions in text. +/// +/// A variable reference generally has the form `@{}`, where `` is +/// the variable name. For example, if the variable name is `x`, then `@{x}` is +/// a reference to that variable. If the variable name consists of a single +/// letter, then a shorter form of reference, `@` is available, i.e., +/// since `x` is a single letter, `@x` is a reference to `x`. As a special rule, +/// `@@` is substituted by `@`. +/// +/// Example of use: +/// +/// struct CtxA { int y = 0; }; +/// struct CtxB { int x = 0; }; +/// using Subst = Substituter; +/// Subst subst; +/// subst["x"] = &CtxB::x; +/// subst["y"] = [](std::ostream& out, const CtxA& a, const CtxB&) { +/// out << a.y; +/// }; +/// Subst::Template templ; +/// if (subst.parse("<@x:@y>\n", templ)) { +/// CtxA a; +/// CtxB b; +/// for (int i = 0; i < 3; ++i) { +/// templ.expand(std::cout, a, b); +/// a.y += 1; +/// b.x += 2; +/// } +/// } +/// +/// This code should write +/// +/// <0:0> +/// <2:1> +/// <4:2> +/// +/// to STDOUT. +template class Substituter : private _private::SubstituterBase { +public: + using EvalFunc = void(std::ostream&, A&...); + class ProtoDef; + class Template; + + Substituter(SubstituterConfig = {}) noexcept; + + ProtoDef operator[](const char* name) noexcept; + + bool expand(StringView text, std::ostream&, A&&...) const; + + bool parse(StringView text, Template&) const; + bool parse(StringView text, Template&, Logger&) const; + +private: + using size_type = StringView::size_type; + struct Substitution; + + const bool m_lenient; + Logger& m_logger; + + using Variables = std::map>; + Variables m_variables; + + void define(const char* name, std::function); +}; + + + +template class Substituter::ProtoDef { +public: + template void operator=(T*); + template void operator=(T C::*); + void operator=(std::function); + +private: + Substituter& m_substituter; + const char* m_name; + + ProtoDef(Substituter& substituter, const char* name) noexcept; + + friend class Substituter; +}; + + + +template class Substituter::Template { +public: + /// Uses std::locale::classic(). + std::string expand(A&&...) const; + + void expand(std::ostream&, A...) const; + + bool refers_to(const char* name) const noexcept; + +private: + StringView m_text; + std::vector m_substitutions; + + friend class Substituter; +}; + + + + + +// Implementation + +namespace _private { + +template struct SubstituterBase::FindArg2 { + static const T& find(const A&, const B&... b) noexcept + { + return FindArg1::find(b...); + } +}; + +template +struct SubstituterBase::FindArg2 { + static const T& find(const A& a, const B&...) noexcept + { + return a; + } +}; + +template struct SubstituterBase::FindArg1 { + static const T& find(const A& a, const B&... b) noexcept + { + using P = typename std::remove_reference::type*; + return FindArg2::value, A, B...>::find(a, b...); + } +}; + +} // namespace _private + +template struct Substituter::Substitution { + size_type begin, end; + const typename Variables::value_type* var_def; +}; + +template inline Substituter::Substituter(SubstituterConfig config) noexcept : + m_lenient{config.lenient}, + m_logger{config.logger ? *config.logger : s_default_logger} +{ +} + +template inline auto Substituter::operator[](const char* name) noexcept -> ProtoDef +{ + return ProtoDef{*this, name}; +} + +template +inline bool Substituter::expand(StringView text, std::ostream& out, A&&... arg) const +{ + Template templ; + if (parse(text, templ)) { // Throws + templ.expand(out, std::forward(arg)...); // Throws + return true; + } + return false; +} + +template inline bool Substituter::parse(StringView text, Template& templ) const +{ + return parse(text, templ, m_logger); // Throws +} + +template +bool Substituter::parse(StringView text, Template& templ, Logger& logger) const +{ + bool error = false; + Logger::Level log_level = (m_lenient ? Logger::Level::warn : Logger::Level::error); + std::vector substitutions; + StringView var_name; + size_type curr = 0; + size_type end = text.size(); + for (;;) { + size_type i = text.find('@', curr); + if (i == StringView::npos) + break; + if (i + 1 == end) { + logger.log(log_level, "Unterminated `@` at end of text"); // Throws + error = true; + break; + } + char ch = text[i + 1]; + if (ch == '{') { + size_type j = text.find('}', i + 2); + if (j == StringView::npos) { + logger.log(log_level, "Unterminated `@{`"); // Throws + error = true; + curr = i + 2; + continue; + } + var_name = text.substr(i + 2, j - (i + 2)); + curr = j + 1; + } + else { + var_name = text.substr(i + 1, 1); // Throws + curr = i + 2; + } + const typename Variables::value_type* var_def = nullptr; + if (ch != '@') { + auto k = m_variables.find(var_name); + if (k == m_variables.end()) { + logger.log(log_level, "Undefined variable `%1` in substitution `%2`", var_name, + text.substr(i, curr - i)); // Throws + error = true; + continue; + } + var_def = &*k; + } + substitutions.push_back({i, curr, var_def}); // Throws + } + if (error && !m_lenient) + return false; + templ.m_text = text; + templ.m_substitutions = std::move(substitutions); + return true; +} + +template +inline void Substituter::define(const char* name, std::function func) +{ + auto p = m_variables.emplace(name, std::move(func)); // Throws + bool was_inserted = p.second; + if (!was_inserted) + throw std::runtime_error("Multiple definitions for same variable name"); +} + +template template inline void Substituter::ProtoDef::operator=(T* var) +{ + *this = [var](std::ostream& out, const A&...) { + out << *var; // Throws + }; +} + +template +template inline void Substituter::ProtoDef::operator=(T C::* var) +{ + *this = [var](std::ostream& out, const A&... arg) { + const C& obj = FindArg1::find(arg...); + out << obj.*var; // Throws + }; +} + +template +inline void Substituter::ProtoDef::operator=(std::function func) +{ + m_substituter.define(m_name, std::move(func)); // Throws +} + +template +inline Substituter::ProtoDef::ProtoDef(Substituter& substituter, const char* name) noexcept : + m_substituter{substituter}, + m_name{name} +{ +} + +template std::string Substituter::Template::expand(A&&... arg) const +{ + std::ostringstream out; + out.imbue(std::locale::classic()); + expand(out, std::forward(arg)...); // Throws + std::string str = std::move(out).str(); // Throws + return str; +} + +template void Substituter::Template::expand(std::ostream& out, A... arg) const +{ + std::ios_base::fmtflags flags = out.flags(); + try { + size_type curr = 0; + for (const Substitution& subst: m_substitutions) { + out << m_text.substr(curr, subst.begin - curr); // Throws + if (subst.var_def) { + const std::function& eval_func = subst.var_def->second; + eval_func(out, arg...); // Throws + out.flags(flags); + } + else { + out << "@"; // Throws + } + curr = subst.end; + } + out << m_text.substr(curr); // Throws + } + catch (...) { + out.flags(flags); + throw; + } +} + +template +inline bool Substituter::Template::refers_to(const char* name) const noexcept +{ + StringView name_2 = name; + for (const auto& subst: m_substitutions) { + if (subst.var_def) { + if (name_2 != subst.var_def->first) + continue; + return true; + } + } + return false; +} + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_SUBSTITUTE_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/system_process.hpp b/!main project/Pods/Realm/include/core/realm/util/system_process.hpp new file mode 100644 index 0000000..c84addd --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/system_process.hpp @@ -0,0 +1,204 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2015] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ +#ifndef REALM_UTIL_SYSTEM_PROCESS_HPP +#define REALM_UTIL_SYSTEM_PROCESS_HPP + +#include +#include +#include +#include + +#include + + +namespace realm { +namespace util { +namespace sys_proc { + +using Environment = std::map; + +/// This function is safe to call only when the caller can be sure that there +/// are no threads that modify the environment concurrently. +/// +/// When possible, call this function from the main thread before any other +/// threads are created, such as early in `main()`. +Environment copy_local_environment(); + + +struct ExitInfo { + /// If nonzero, the process was killed by a signal. The value is the + /// signal number. + int killed_by_signal = 0; + + /// Zero if the process was killed by a signal, otherwise this is the value + /// returned by the `main()` function, or passed to `exit()`. + /// + /// On a POSIX system, if an error occurs during ::execve(), that is, after + /// ::fork(), an exit status of 127 will be used (aligned with + /// ::posix_spawn()). + int status = 0; + + /// In some cases, ChildHandle::join() will set `signal_name` when it sets + /// `killed_by_signal` to a non-zero value. In those cases, `signal_name` is + /// set to point to a null-terminated string specifying the name of the + /// signal that killed the child process. + const char* signal_name = nullptr; + + /// Returns true if, and only if both `killed_by_signal` and `status` are + /// zero. + explicit operator bool() const noexcept; +}; + + +struct SpawnConfig { + /// When set to true, the child process will be able to use a + /// ParentDeathGuard to detect the destruction of the SystemProcess object + /// in the parent process, even when this happens implicitly due to abrupt + /// termination of the parent process. + bool parent_death_guard = false; + + /// If a logger is specified here, the child process will be able to + /// instantiate a ParentLogger object, and messages logged through that + /// ParentLogger object will be transported to the parent process and + /// submitted to the logger pointed to by `logger`. The specified logger is + /// guaranteed to only be accessed while ChildHandle::join() is executing, + /// and only by the thread that executes ChildHandle::join(). See + /// ParentLogger for further details. + Logger* logger = nullptr; +}; + + +class ChildHandle { +public: + /// Wait for the child process to exit. + /// + /// If a logger was passed to spawn() (SpawnConfig::logger), then this + /// function will also transport log messages from the child to the parent + /// process while waiting for the child process to exit. See ParentLogger + /// for details. + ExitInfo join(); + + ChildHandle(ChildHandle&&) noexcept; + ~ChildHandle() noexcept; + +private: + class Impl; + std::unique_ptr m_impl; + + ChildHandle(Impl*) noexcept; + + friend ChildHandle spawn(const std::string&, const std::vector&, + const Environment&, const SpawnConfig&); +}; + + +/// Returns true if, and only if the spawn() functions work on this platform. If +/// this function returns false, the spawn() functions will throw. +bool is_spawn_supported() noexcept; + + +//@{ +/// Spawn a child process. +ChildHandle spawn(const std::string& path, const std::vector& args = {}, + const Environment& = {}); +ChildHandle spawn(const std::string& path, const std::vector& args, + const Environment&, const SpawnConfig&); +//@} + + +/// Force a child process to terminate immediately if the parent process is +/// terminated, or if the parent process destroys the ChildHandle object +/// representing the child process. +/// +/// If a child process instantiates an object of this type, and keeps it alive, +/// and the child process was spawned with support for detection of parent +/// termination (SpawnConfig::parent_death_guard), then the child process will +/// be killed shortly after the parent destroys its ChildHandle object, even +/// when this happens implicitly due to abrupt termination of the parent +/// process. +/// +/// If a child process instantiates an object of this type, that object must be +/// instantiated by the main thread, and before any other thread is spawned in +/// the child process. +/// +/// In order for the guard to have the intended effect, it must be instantiated +/// immediately in the child process, and be kept alive for as long as the child +/// process is running. +class ParentDeathGuard { +public: + ParentDeathGuard(); + ~ParentDeathGuard() noexcept; + +private: + std::thread m_thread; + int m_stop_pipe_write = -1; +}; + + +/// A logger that can transport log messages from the child to the parent +/// process. +/// +/// If the parent process specifies a logger when spawning a child process +/// (SpawnConfig::logger), then that child process can instantiate a +/// ParentLogger object, and messages logged through it will be transported to +/// the parent process. While the parent process is executing +/// ChildHandle::join(), those messages will be written to the logger specified +/// by the parent process. +/// +/// If a child process instantiates an object of this type, that object must be +/// instantiated by the main thread, and before any other thread is spawned in +/// the child process. +/// +/// At most one ParentLogger object may be instantiated per child process. +/// +/// This logger is **not** thread-safe. +class ParentLogger : public RootLogger { +public: + ParentLogger(); + ~ParentLogger() noexcept; + +protected: + void do_log(Level, std::string) override final; + +private: + int m_pipe_write = -1; +}; + + + + +// Implementation + +inline ExitInfo::operator bool() const noexcept +{ + return (killed_by_signal == 0 && status == 0); +} + +inline ChildHandle spawn(const std::string& path, const std::vector& args, + const Environment& env) +{ + return spawn(path, args, env, SpawnConfig{}); // Throws +} + +} // namespace sys_proc +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_SYSTEM_PROCESS_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/terminate.hpp b/!main project/Pods/Realm/include/core/realm/util/terminate.hpp new file mode 100644 index 0000000..4e6034e --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/terminate.hpp @@ -0,0 +1,59 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_TERMINATE_HPP +#define REALM_UTIL_TERMINATE_HPP + +#include + +#include +#include +#include + +#define REALM_TERMINATE(msg) realm::util::terminate((msg), __FILE__, __LINE__) + +namespace realm { +namespace util { + +REALM_NORETURN void terminate(const char* message, const char* file, long line, + std::initializer_list&& = {}) noexcept; +REALM_NORETURN void terminate_with_info(const char* message, const char* file, long line, + const char* interesting_names, + std::initializer_list&& = {}) noexcept; + +// LCOV_EXCL_START +template +REALM_NORETURN void terminate(const char* message, const char* file, long line, Ts... infos) noexcept +{ + static_assert(sizeof...(infos) == 2 || sizeof...(infos) == 4 || sizeof...(infos) == 6, + "Called realm::util::terminate() with wrong number of arguments"); + terminate(message, file, line, {Printable(infos)...}); +} + +template +REALM_NORETURN void terminate_with_info(const char* assert_message, int line, const char* file, + const char* interesting_names, Args&&... interesting_values) noexcept +{ + terminate_with_info(assert_message, file, line, interesting_names, {Printable(interesting_values)...}); +} +// LCOV_EXCL_STOP + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_TERMINATE_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/thread.hpp b/!main project/Pods/Realm/include/core/realm/util/thread.hpp new file mode 100644 index 0000000..8ca5e38 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/thread.hpp @@ -0,0 +1,759 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_THREAD_HPP +#define REALM_UTIL_THREAD_HPP + +#include + +#ifdef _WIN32 +#include +#include // for windows non-interprocess condvars we use std::condition_variable +#include +#include // _getpid() +#else +#include +#endif + +// Use below line to enable a thread bug detection tool. Note: Will make program execution slower. +// #include <../test/pthread_test.hpp> + +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace realm { +namespace util { + + +/// A separate thread of execution. +/// +/// This class is a C++03 compatible reproduction of a subset of std::thread +/// from C++11 (when discounting Thread::start(), Thread::set_name(), and +/// Thread::get_name()). +class Thread { +public: + Thread(); + ~Thread() noexcept; + + template + explicit Thread(F func); + + // Disable copying. It is an error to copy this Thread class. + Thread(const Thread&) = delete; + Thread& operator=(const Thread&) = delete; + + Thread(Thread&&); + + /// This method is an extension of the API provided by + /// std::thread. This method exists because proper move semantics + /// is unavailable in C++03. If move semantics had been available, + /// calling `start(func)` would have been equivalent to `*this = + /// Thread(func)`. Please see std::thread::operator=() for + /// details. + template + void start(F func); + + bool joinable() noexcept; + + void join(); + + // If supported by the platform, set the name of the calling thread (mainly + // for debugging purposes). The name will be silently clamped to whatever + // limit the platform places on these names. Linux places a limit of 15 + // characters for these names. + static void set_name(const std::string&); + + // If supported by the platform, this function assigns the name of the + // calling thread to \a name, and returns true, otherwise it does nothing + // and returns false. + static bool get_name(std::string& name); + +private: + +#ifdef _WIN32 + std::thread m_std_thread; +#else + pthread_t m_id; +#endif + bool m_joinable; + typedef void* (*entry_func_type)(void*); + + void start(entry_func_type, void* arg); + + template + static void* entry_point(void*) noexcept; + + REALM_NORETURN static void create_failed(int); + REALM_NORETURN static void join_failed(int); +}; + + +/// Low-level mutual exclusion device. +class Mutex { +public: + Mutex(); + ~Mutex() noexcept; + + struct process_shared_tag { + }; + /// Initialize this mutex for use across multiple processes. When + /// constructed this way, the instance may be placed in memory + /// shared by multiple processes, as well as in a memory mapped + /// file. Such a mutex remains valid even after the constructing + /// process terminates. Deleting the instance (freeing the memory + /// or deleting the file) without first calling the destructor is + /// legal and will not cause any system resources to be leaked. + Mutex(process_shared_tag); + + // Disable copying. + Mutex(const Mutex&) = delete; + Mutex& operator=(const Mutex&) = delete; + + friend class LockGuard; + friend class UniqueLock; + friend class InterprocessCondVar; + + void lock() noexcept; + bool try_lock() noexcept; + void unlock() noexcept; + +protected: +#ifdef _WIN32 + // Used for non-process-shared mutex. We only know at runtime whether or not to use it, depending on if we call + // Mutex::Mutex(process_shared_tag) + CRITICAL_SECTION m_critical_section; +#else + pthread_mutex_t m_impl = PTHREAD_MUTEX_INITIALIZER; +#endif + + struct no_init_tag { + }; + Mutex(no_init_tag) + { + } + + void init_as_regular(); + void init_as_process_shared(bool robust_if_available); + + REALM_NORETURN static void init_failed(int); + REALM_NORETURN static void attr_init_failed(int); + REALM_NORETURN static void destroy_failed(int) noexcept; + REALM_NORETURN static void lock_failed(int) noexcept; + +private: + friend class CondVar; + friend class RobustMutex; +}; + + +/// A simple mutex ownership wrapper. +class LockGuard { +public: + LockGuard(Mutex&) noexcept; + ~LockGuard() noexcept; + +private: + Mutex& m_mutex; + friend class CondVar; +}; + + +/// See UniqueLock. +struct defer_lock_tag { +}; + +/// A general-purpose mutex ownership wrapper supporting deferred +/// locking as well as repeated unlocking and relocking. +class UniqueLock { +public: + UniqueLock(Mutex&) noexcept; + UniqueLock(Mutex&, defer_lock_tag) noexcept; + ~UniqueLock() noexcept; + + void lock() noexcept; + void unlock() noexcept; + bool holds_lock() noexcept; + +private: + Mutex* m_mutex; + bool m_is_locked; +}; + + +/// A robust version of a process-shared mutex. +/// +/// A robust mutex is one that detects whether a thread (or process) +/// has died while holding a lock on the mutex. +/// +/// When the present platform does not offer support for robust +/// mutexes, this mutex class behaves as a regular process-shared +/// mutex, which means that if a thread dies while holding a lock, any +/// future attempt at locking will block indefinitely. +class RobustMutex : private Mutex { +public: + RobustMutex(); + ~RobustMutex() noexcept; + + static bool is_robust_on_this_platform() noexcept; + + class NotRecoverable; + + /// \param recover_func If the present platform does not support + /// robust mutexes, this function is never called. Otherwise it is + /// called if, and only if a thread has died while holding a + /// lock. The purpose of the function is to reestablish a + /// consistent shared state. If it fails to do this by throwing an + /// exception, the mutex enters the 'unrecoverable' state where + /// any future attempt at locking it will fail and cause + /// NotRecoverable to be thrown. This function is advised to throw + /// NotRecoverable when it fails, but it may throw any exception. + /// + /// \throw NotRecoverable If thrown by the specified recover + /// function, or if the mutex has entered the 'unrecoverable' + /// state due to a different thread throwing from its recover + /// function. + template + void lock(Func recover_func); + + template + bool try_lock(Func recover_func); + + void unlock() noexcept; + + /// Low-level locking of robust mutex. + /// + /// If the present platform does not support robust mutexes, this + /// function always returns true. Otherwise it returns false if, + /// and only if a thread has died while holding a lock. + /// + /// \note Most application should never call this function + /// directly. It is called automatically when using the ordinary + /// lock() function. + /// + /// \throw NotRecoverable If this mutex has entered the "not + /// recoverable" state. It enters this state if + /// mark_as_consistent() is not called between a call to + /// robust_lock() that returns false and the corresponding call to + /// unlock(). + bool low_level_lock(); + + /// Low-level try-lock of robust mutex + /// + /// If the present platform does not support robust mutexes, this + /// function always returns 0 or 1. Otherwise it returns -1 if, + /// and only if a thread has died while holding a lock. + /// + /// Returns 1 if the lock is succesfully obtained. + /// Returns 0 if the lock is held by somebody else (not obtained) + /// Returns -1 if a thread has died while holding a lock. + /// + /// \note Most application should never call this function + /// directly. It is called automatically when using the ordinary + /// lock() function. + /// + /// \throw NotRecoverable If this mutex has entered the "not + /// recoverable" state. It enters this state if + /// mark_as_consistent() is not called between a call to + /// robust_lock() that returns false and the corresponding call to + /// unlock(). + int try_low_level_lock(); + + /// Pull this mutex out of the 'inconsistent' state. + /// + /// Must be called only after low_level_lock() has returned false. + /// + /// \note Most application should never call this function + /// directly. It is called automatically when using the ordinary + /// lock() function. + void mark_as_consistent() noexcept; + + /// Attempt to check if this mutex is a valid object. + /// + /// This attempts to trylock() the mutex, and if that fails returns false if + /// the return value indicates that the low-level mutex is invalid (which is + /// distinct from 'inconsistent'). Although pthread_mutex_trylock() may + /// return EINVAL if the argument is not an initialized mutex object, merely + /// attempting to check if an arbitrary blob of memory is a mutex object may + /// involve undefined behavior, so it is only safe to assume that this + /// function will run correctly when it is known that the mutex object is + /// valid. + bool is_valid() noexcept; + + friend class CondVar; +}; + +class RobustMutex::NotRecoverable : public std::exception { +public: + const char* what() const noexcept override + { + return "Failed to recover consistent state of shared memory"; + } +}; + + +/// A simple robust mutex ownership wrapper. +class RobustLockGuard { +public: + /// \param m the mutex to guard + /// \param func See RobustMutex::lock(). + template + RobustLockGuard(RobustMutex& m, TFunc func); + ~RobustLockGuard() noexcept; + +private: + RobustMutex& m_mutex; + friend class CondVar; +}; + + +/// Condition variable for use in synchronization monitors. +class CondVar { +public: + CondVar(); + ~CondVar() noexcept; + + struct process_shared_tag { + }; + + /// Initialize this condition variable for use across multiple + /// processes. When constructed this way, the instance may be + /// placed in memory shared by multimple processes, as well as in + /// a memory mapped file. Such a condition variable remains valid + /// even after the constructing process terminates. Deleting the + /// instance (freeing the memory or deleting the file) without + /// first calling the destructor is legal and will not cause any + /// system resources to be leaked. + CondVar(process_shared_tag); + + /// Wait for another thread to call notify() or notify_all(). + void wait(LockGuard& l) noexcept; + template + void wait(RobustMutex& m, Func recover_func, const struct timespec* tp = nullptr); + + /// If any threads are wating for this condition, wake up at least + /// one. + void notify() noexcept; + + /// Wake up every thread that is currently wating on this + /// condition. + void notify_all() noexcept; + +private: +#ifdef _WIN32 + CONDITION_VARIABLE m_condvar = CONDITION_VARIABLE_INIT; +#else + pthread_cond_t m_impl; +#endif + + REALM_NORETURN static void init_failed(int); + REALM_NORETURN static void attr_init_failed(int); + REALM_NORETURN static void destroy_failed(int) noexcept; + void handle_wait_error(int error); +}; + + +// Implementation: + +inline Thread::Thread() + : m_joinable(false) +{ +} + +template +inline Thread::Thread(F func) + : m_joinable(true) +{ + std::unique_ptr func2(new F(func)); // Throws + start(&Thread::entry_point, func2.get()); // Throws + func2.release(); +} + +inline Thread::Thread(Thread&& thread) +{ +#ifndef _WIN32 + m_id = thread.m_id; + m_joinable = thread.m_joinable; + thread.m_joinable = false; +#endif +} + +template +inline void Thread::start(F func) +{ + if (m_joinable) + std::terminate(); + std::unique_ptr func2(new F(func)); // Throws + start(&Thread::entry_point, func2.get()); // Throws + func2.release(); + m_joinable = true; +} + +inline Thread::~Thread() noexcept +{ + if (m_joinable) + REALM_TERMINATE("Destruction of joinable thread"); +} + +inline bool Thread::joinable() noexcept +{ + return m_joinable; +} + +inline void Thread::start(entry_func_type entry_func, void* arg) +{ +#ifdef _WIN32 + m_std_thread = std::thread(entry_func, arg); +#else + const pthread_attr_t* attr = nullptr; // Use default thread attributes + int r = pthread_create(&m_id, attr, entry_func, arg); + if (REALM_UNLIKELY(r != 0)) + create_failed(r); // Throws +#endif +} + +template +inline void* Thread::entry_point(void* cookie) noexcept +{ + std::unique_ptr func(static_cast(cookie)); + try { + (*func)(); + } + catch (...) { + std::terminate(); + } + return 0; +} + + +inline Mutex::Mutex() +{ + init_as_regular(); +} + +inline Mutex::Mutex(process_shared_tag) +{ + bool robust_if_available = false; + init_as_process_shared(robust_if_available); +} + +inline Mutex::~Mutex() noexcept +{ +#ifndef _WIN32 + int r = pthread_mutex_destroy(&m_impl); + if (REALM_UNLIKELY(r != 0)) + destroy_failed(r); +#else + DeleteCriticalSection(&m_critical_section); +#endif +} + +inline void Mutex::init_as_regular() +{ +#ifndef _WIN32 + int r = pthread_mutex_init(&m_impl, 0); + if (REALM_UNLIKELY(r != 0)) + init_failed(r); +#else + InitializeCriticalSection(&m_critical_section); +#endif +} + +inline void Mutex::lock() noexcept +{ +#ifdef _WIN32 + EnterCriticalSection(&m_critical_section); +#else + int r = pthread_mutex_lock(&m_impl); + if (REALM_LIKELY(r == 0)) + return; + lock_failed(r); +#endif +} + +inline bool Mutex::try_lock() noexcept +{ +#ifdef _WIN32 + return TryEnterCriticalSection(&m_critical_section); +#else + int r = pthread_mutex_trylock(&m_impl); + if (r == EBUSY) { + return false; + } + else if (r == 0) { + return true; + } + lock_failed(r); +#endif +} + +inline void Mutex::unlock() noexcept +{ +#ifdef _WIN32 + LeaveCriticalSection(&m_critical_section); +#else + int r = pthread_mutex_unlock(&m_impl); + REALM_ASSERT(r == 0); +#endif +} + + +inline LockGuard::LockGuard(Mutex& m) noexcept + : m_mutex(m) +{ + m_mutex.lock(); +} + +inline LockGuard::~LockGuard() noexcept +{ + m_mutex.unlock(); +} + + +inline UniqueLock::UniqueLock(Mutex& m) noexcept + : m_mutex(&m) +{ + m_mutex->lock(); + m_is_locked = true; +} + +inline UniqueLock::UniqueLock(Mutex& m, defer_lock_tag) noexcept + : m_mutex(&m) +{ + m_is_locked = false; +} + +inline UniqueLock::~UniqueLock() noexcept +{ + if (m_is_locked) + m_mutex->unlock(); +} + +inline bool UniqueLock::holds_lock() noexcept +{ + return m_is_locked; +} + +inline void UniqueLock::lock() noexcept +{ + m_mutex->lock(); + m_is_locked = true; +} + +inline void UniqueLock::unlock() noexcept +{ + m_mutex->unlock(); + m_is_locked = false; +} + +template +inline RobustLockGuard::RobustLockGuard(RobustMutex& m, TFunc func) + : m_mutex(m) +{ + m_mutex.lock(func); +} + +inline RobustLockGuard::~RobustLockGuard() noexcept +{ + m_mutex.unlock(); +} + + +inline RobustMutex::RobustMutex() + : Mutex(no_init_tag()) +{ + bool robust_if_available = true; + init_as_process_shared(robust_if_available); +} + +inline RobustMutex::~RobustMutex() noexcept +{ +} + +template +inline void RobustMutex::lock(Func recover_func) +{ + bool no_thread_has_died = low_level_lock(); // Throws + if (REALM_LIKELY(no_thread_has_died)) + return; + try { + recover_func(); // Throws + mark_as_consistent(); + // If we get this far, the protected memory has been + // brought back into a consistent state, and the mutex has + // been notified about this. This means that we can safely + // enter the applications critical section. + } + catch (...) { + // Unlocking without first calling mark_as_consistent() + // means that the mutex enters the "not recoverable" + // state, which will cause all future attempts at locking + // to fail. + unlock(); + throw; + } +} + +template +inline bool RobustMutex::try_lock(Func recover_func) +{ + int lock_result = try_low_level_lock(); // Throws + if (lock_result == 0) return false; + bool no_thread_has_died = lock_result == 1; + if (REALM_LIKELY(no_thread_has_died)) + return true; + try { + recover_func(); // Throws + mark_as_consistent(); + // If we get this far, the protected memory has been + // brought back into a consistent state, and the mutex has + // been notified aboit this. This means that we can safely + // enter the applications critical section. + } + catch (...) { + // Unlocking without first calling mark_as_consistent() + // means that the mutex enters the "not recoverable" + // state, which will cause all future attempts at locking + // to fail. + unlock(); + throw; + } + return true; +} + +inline void RobustMutex::unlock() noexcept +{ + Mutex::unlock(); +} + + +inline CondVar::CondVar() +{ +#ifndef _WIN32 + int r = pthread_cond_init(&m_impl, 0); + if (REALM_UNLIKELY(r != 0)) + init_failed(r); +#endif +} + +inline CondVar::~CondVar() noexcept +{ +#ifndef _WIN32 + int r = pthread_cond_destroy(&m_impl); + if (REALM_UNLIKELY(r != 0)) + destroy_failed(r); +#endif +} + +inline void CondVar::wait(LockGuard& l) noexcept +{ +#ifdef _WIN32 + SleepConditionVariableCS(&m_condvar, &l.m_mutex.m_critical_section, INFINITE); +#else + int r = pthread_cond_wait(&m_impl, &l.m_mutex.m_impl); + if (REALM_UNLIKELY(r != 0)) + REALM_TERMINATE("pthread_cond_wait() failed"); +#endif +} + +template +inline void CondVar::wait(RobustMutex& m, Func recover_func, const struct timespec* tp) +{ + int r; + + if (!tp) { +#ifdef _WIN32 + if (!SleepConditionVariableCS(&m_condvar, &m.m_critical_section, INFINITE)) + r = GetLastError(); + else + r = 0; +#else + r = pthread_cond_wait(&m_impl, &m.m_impl); +#endif + } + else { +#ifdef _WIN32 + if (!SleepConditionVariableCS(&m_condvar, &m.m_critical_section, tp->tv_sec / 1000)) { + r = GetLastError(); + if (r == ERROR_TIMEOUT) + return; + } else { + r = 0 + } +#else + r = pthread_cond_timedwait(&m_impl, &m.m_impl, tp); + if (r == ETIMEDOUT) + return; +#endif + } + + if (REALM_LIKELY(r == 0)) + return; + + handle_wait_error(r); + + try { + recover_func(); // Throws + m.mark_as_consistent(); + // If we get this far, the protected memory has been + // brought back into a consistent state, and the mutex has + // been notified aboit this. This means that we can safely + // enter the applications critical section. + } + catch (...) { + // Unlocking without first calling mark_as_consistent() + // means that the mutex enters the "not recoverable" + // state, which will cause all future attempts at locking + // to fail. + m.unlock(); + throw; + } +} + +inline void CondVar::notify() noexcept +{ +#ifdef _WIN32 + WakeConditionVariable(&m_condvar); +#else + int r = pthread_cond_signal(&m_impl); + REALM_ASSERT(r == 0); +#endif +} + +inline void CondVar::notify_all() noexcept +{ +#ifdef _WIN32 + WakeAllConditionVariable(&m_condvar); +#else + int r = pthread_cond_broadcast(&m_impl); + REALM_ASSERT(r == 0); +#endif +} + + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_THREAD_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/thread_exec_guard.hpp b/!main project/Pods/Realm/include/core/realm/util/thread_exec_guard.hpp new file mode 100644 index 0000000..b779e5a --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/thread_exec_guard.hpp @@ -0,0 +1,334 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2015] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ +#ifndef REALM_UTIL_THREAD_EXEC_GUARD_HPP +#define REALM_UTIL_THREAD_EXEC_GUARD_HPP + +#include +#include +#include + +#include +#include + + +namespace realm { +namespace util { + +/// Execute a `R::run()` using a managed thread. +/// +/// \tparam R The type of the runnable object. This type must satisfy the +/// requirements of the Runnable concept. See ThreadExecGuardWithParent. +template class ThreadExecGuard { +public: + explicit ThreadExecGuard(R& runnable); + + ThreadExecGuard(ThreadExecGuard&&) = default; + + /// If start() or start_with_signals_blocked() was successfully executed, + /// and stop_and_rethrow() has not been called, call `R::stop()`, and then + /// wait for the thread to terminate (join). + ~ThreadExecGuard() noexcept = default; + + // @{ + /// Launch a thread and make it execute `R::run()` of the associated + /// "runnable" object. + /// + /// At most one of these functions are allowed to be called on a particular + /// guard object, and it must only be called once. + void start(); + void start(const std::string& thread_name); + void start_with_signals_blocked(); + void start_with_signals_blocked(const std::string& thread_name); + // @} + + /// If start() or start_with_signals_blocked() was successfully executed, + /// call `R::stop()`, wait for the thread to terminate (join), and then, if + /// an exception was thrown by `R::run()`, rethrow it. + void stop_and_rethrow(); + +private: + struct State { + R& runnable; + util::Thread thread; + std::exception_ptr exception; + State(R&) noexcept; + ~State() noexcept; + void start(const std::string* thread_name); + void stop_and_rethrow(); + }; + + std::unique_ptr m_state; +}; + + +/// Execute a `R::run()` using a managed thread. +/// +/// \tparam R The type of the runnable object. This type must satisfy the +/// requirements of the Runnable concept. See below. +/// +/// \tparam P The type of the object representing the parent thread. This type +/// must satisfy the requirements of the Stoppable concept. See below. +/// +/// A type satisfies the requirements of the *Stoppable* concept, if +/// - it has a nonthrowing member function named `stop()`, and +/// - `stop()` is thread-safe, and +/// - `stop()` is idempotent (can be called multiple times). +/// +/// A type satisfies the requirements of the *Runnable* concept, if +/// - it satisfies the requirements of the Stoppable concept, and +/// - it has a member function named `run()`, and +/// - `run()` will stop executing within a reasonable amount of time after +/// `stop()` has been called. +/// +template class ThreadExecGuardWithParent { +public: + explicit ThreadExecGuardWithParent(R& runnable, P& parent); + + ThreadExecGuardWithParent(ThreadExecGuardWithParent&&) = default; + + /// If start() or start_with_signals_blocked() was successfully executed, + /// and stop_and_rethrow() has not been called, call `R::stop()`, and then + /// wait for the thread to terminate (join). + ~ThreadExecGuardWithParent() noexcept = default; + + // @{ + /// Launch a thread and make it execute `R::run()` of the associated + /// "runnable" object. + /// + /// If `R::run()` throws, call `P::stop()` on the specified parent. + /// + /// At most one of these functions are allowed to be called on a particular + /// guard object, and it must only be called once. + void start(); + void start(const std::string& thread_name); + void start_with_signals_blocked(); + void start_with_signals_blocked(const std::string& thread_name); + // @} + + /// If start() or start_with_signals_blocked() was successfully executed, + /// call `R::stop()`, wait for the thread to terminate (join), and then, if + /// an exception was thrown by `R::run()`, rethrow it. + void stop_and_rethrow(); + +private: + struct State { + R& runnable; + P& parent; + util::Thread thread; + std::exception_ptr exception; + State(R&, P&) noexcept; + ~State() noexcept; + void start(const std::string* thread_name); + void stop_and_rethrow(); + }; + + std::unique_ptr m_state; +}; + + +template ThreadExecGuard make_thread_exec_guard(R& runnable); + +template +ThreadExecGuardWithParent make_thread_exec_guard(R& runnable, P& parent); + + + + +// Implementation + +template inline ThreadExecGuard::ThreadExecGuard(R& runnable) : + m_state{std::make_unique(runnable)} // Throws +{ +} + +template inline void ThreadExecGuard::start() +{ + const std::string* thread_name = nullptr; + m_state->start(thread_name); // Throws +} + +template inline void ThreadExecGuard::start(const std::string& thread_name) +{ + m_state->start(&thread_name); // Throws +} + +template inline void ThreadExecGuard::start_with_signals_blocked() +{ + SignalBlocker sb; + const std::string* thread_name = nullptr; + m_state->start(thread_name); // Throws +} + +template +inline void ThreadExecGuard::start_with_signals_blocked(const std::string& thread_name) +{ + SignalBlocker sb; + m_state->start(&thread_name); // Throws +} + +template inline void ThreadExecGuard::stop_and_rethrow() +{ + m_state->stop_and_rethrow(); // Throws +} + +template inline ThreadExecGuard::State::State(R& r) noexcept : + runnable{r} +{ +} + +template inline ThreadExecGuard::State::~State() noexcept +{ + if (thread.joinable()) { + runnable.stop(); + thread.join(); + } +} + +template inline void ThreadExecGuard::State::start(const std::string* thread_name) +{ + bool set_thread_name = false; + std::string thread_name_2; + if (thread_name) { + set_thread_name = true; + thread_name_2 = *thread_name; // Throws (copy) + } + auto run = [this, set_thread_name, thread_name=std::move(thread_name_2)]() noexcept { + try { + if (set_thread_name) + util::Thread::set_name(thread_name); // Throws + runnable.run(); // Throws + } + catch (...) { + exception = std::current_exception(); + } + }; + thread.start(std::move(run)); // Throws +} + +template inline void ThreadExecGuard::State::stop_and_rethrow() +{ + if (thread.joinable()) { + runnable.stop(); + thread.join(); + if (exception) + std::rethrow_exception(exception); // Throws + } +} + +template +inline ThreadExecGuardWithParent::ThreadExecGuardWithParent(R& runnable, P& parent) : + m_state{std::make_unique(runnable, parent)} // Throws +{ +} + +template inline void ThreadExecGuardWithParent::start() +{ + const std::string* thread_name = nullptr; + m_state->start(thread_name); // Throws +} + +template +inline void ThreadExecGuardWithParent::start(const std::string& thread_name) +{ + m_state->start(&thread_name); // Throws +} + +template inline void ThreadExecGuardWithParent::start_with_signals_blocked() +{ + SignalBlocker sb; + const std::string* thread_name = nullptr; + m_state->start(thread_name); // Throws +} + +template +inline void ThreadExecGuardWithParent::start_with_signals_blocked(const std::string& thread_name) +{ + SignalBlocker sb; + m_state->start(&thread_name); // Throws +} + +template inline void ThreadExecGuardWithParent::stop_and_rethrow() +{ + m_state->stop_and_rethrow(); // Throws +} + +template +inline ThreadExecGuardWithParent::State::State(R& r, P& p) noexcept : + runnable{r}, + parent{p} +{ +} + +template inline ThreadExecGuardWithParent::State::~State() noexcept +{ + if (thread.joinable()) { + runnable.stop(); + thread.join(); + } +} + +template +inline void ThreadExecGuardWithParent::State::start(const std::string* thread_name) +{ + bool set_thread_name = false; + std::string thread_name_2; + if (thread_name) { + set_thread_name = true; + thread_name_2 = *thread_name; // Throws (copy) + } + auto run = [this, set_thread_name, thread_name=std::move(thread_name_2)]() noexcept { + try { + if (set_thread_name) + util::Thread::set_name(thread_name); // Throws + runnable.run(); // Throws + } + catch (...) { + exception = std::current_exception(); + parent.stop(); + } + }; + thread.start(std::move(run)); // Throws +} + +template inline void ThreadExecGuardWithParent::State::stop_and_rethrow() +{ + if (thread.joinable()) { + runnable.stop(); + thread.join(); + if (exception) + std::rethrow_exception(exception); // Throws + } +} + +template inline ThreadExecGuard make_thread_exec_guard(R& runnable) +{ + return ThreadExecGuard{runnable}; // Throws +} + +template +inline ThreadExecGuardWithParent make_thread_exec_guard(R& runnable, P& parent) +{ + return ThreadExecGuardWithParent{runnable, parent}; // Throws +} + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_THREAD_EXEC_GUARD_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/time.hpp b/!main project/Pods/Realm/include/core/realm/util/time.hpp new file mode 100644 index 0000000..75d9861 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/time.hpp @@ -0,0 +1,94 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2016] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ + +#ifndef REALM_UTIL_TIME_HPP +#define REALM_UTIL_TIME_HPP + +#include +#include +#include +#include +#include +#include + + +namespace realm { +namespace util { + +/// Thread safe version of std::localtime(). Uses localtime_r() on POSIX. +std::tm localtime(std::time_t); + +/// Thread safe version of std::gmtime(). Uses gmtime_r() on POSIX. +std::tm gmtime(std::time_t); + +/// Similar to std::put_time() from . See std::put_time() for +/// information about the format string. This function is provided because +/// std::put_time() is unavailable in GCC 4. This function is thread safe. +/// +/// The default format is ISO 8601 date and time. +template +void put_time(std::basic_ostream&, const std::tm&, const C* format = "%FT%T%z"); + +// @{ +/// These functions combine localtime() or gmtime() with put_time() and +/// std::ostringstream. For detals on the format string, see +/// std::put_time(). These function are thread safe. +std::string format_local_time(std::time_t, const char* format = "%FT%T%z"); +std::string format_utc_time(std::time_t, const char* format = "%FT%T%z"); +// @} + +/// The local time since the epoch in microseconds. +/// +/// FIXME: This function has nothing to do with local time. +double local_time_microseconds(); + + + + +// Implementation + +template +inline void put_time(std::basic_ostream& out, const std::tm& tm, const C* format) +{ + const auto& facet = std::use_facet>(out.getloc()); // Throws + facet.put(std::ostreambuf_iterator(out), out, ' ', &tm, + format, format + T::length(format)); // Throws +} + +inline std::string format_local_time(std::time_t time, const char* format) +{ + std::tm tm = util::localtime(time); + std::ostringstream out; + util::put_time(out, tm, format); // Throws + return out.str(); // Throws +} + +inline std::string format_utc_time(std::time_t time, const char* format) +{ + std::tm tm = util::gmtime(time); + std::ostringstream out; + util::put_time(out, tm, format); // Throws + return out.str(); // Throws +} + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_TIME_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/timestamp_formatter.hpp b/!main project/Pods/Realm/include/core/realm/util/timestamp_formatter.hpp new file mode 100644 index 0000000..47e4ce3 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/timestamp_formatter.hpp @@ -0,0 +1,110 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2016] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ + +#ifndef REALM_UTIL_TIMESTAMP_FORMATTER_HPP +#define REALM_UTIL_TIMESTAMP_FORMATTER_HPP + +#include +#include +#include +#include + +#include +#include +#include +#include + + +namespace realm { +namespace util { + +class TimestampFormatter { +public: + using char_type = char; + using string_view_type = util::BasicStringView; + + enum class Precision { seconds, milliseconds, microseconds, nanoseconds }; + + /// Default configuration for corresponds to local time in ISO 8601 date and + /// time format. + struct Config { + Config() {} + + bool utc_time = false; + + Precision precision = Precision::seconds; + + /// The format of the timestamp as understood by std::put_time(), except + /// that the first occurrence of `%S` (also taking into account the `%S` + /// that is an implicit part of `%T`) is expanded to `SS.fff` if \ref + /// precision is Precision::milliseconds, or to `SS.ffffff` if \ref + /// precision is Precision::microseconds, or to `SS.fffffffff` if \ref + /// precision is Precision::nanoseconds, where `SS` is what `%S` expands + /// to conventionally. + const char* format = "%FT%T%z"; + }; + + TimestampFormatter(Config = {}); + + // FIXME: Use std::timespec in C++17. + string_view_type format(std::time_t time, long nanoseconds); + + template string_view_type format(std::chrono::time_point); + +private: + using memory_output_stream_type = util::MemoryOutputStream; + using format_segments_type = std::pair; + + const bool m_utc_time; + const Precision m_precision; + const format_segments_type m_format_segments; + char_type m_buffer[64]; + memory_output_stream_type m_out; + + static format_segments_type make_format_segments(const Config&); +}; + + + + + +// Implementation + +template +inline auto TimestampFormatter::format(std::chrono::time_point time) -> string_view_type +{ + using clock_type = B; + using time_point_type = std::chrono::time_point; + std::time_t time_2 = clock_type::to_time_t(time); + time_point_type time_3 = clock_type::from_time_t(time_2); + if (REALM_UNLIKELY(time_3 > time)) { + --time_2; + time_3 = clock_type::from_time_t(time_2); + } + long nanoseconds = + int(std::chrono::duration_cast(time - time_3).count()); + REALM_ASSERT(nanoseconds >= 0 && nanoseconds < 1000000000); + return format(time_2, nanoseconds); // Throws +} + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_TIMESTAMP_FORMATTER_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/timestamp_logger.hpp b/!main project/Pods/Realm/include/core/realm/util/timestamp_logger.hpp new file mode 100644 index 0000000..a9789a5 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/timestamp_logger.hpp @@ -0,0 +1,49 @@ +/************************************************************************* + * + * REALM CONFIDENTIAL + * __________________ + * + * [2011] - [2016] Realm Inc + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Realm Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Realm Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Realm Incorporated. + * + **************************************************************************/ + +#ifndef REALM_UTIL_TIMESTAMP_LOGGER_HPP +#define REALM_UTIL_TIMESTAMP_LOGGER_HPP + +#include +#include + + +namespace realm { +namespace util { + +class TimestampStderrLogger : public RootLogger { +public: + using Precision = TimestampFormatter::Precision; + using Config = TimestampFormatter::Config; + + explicit TimestampStderrLogger(Config = {}); + +protected: + void do_log(Logger::Level, std::string message) override; + +private: + TimestampFormatter m_formatter; +}; + + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_TIMESTAMP_LOGGER_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/to_string.hpp b/!main project/Pods/Realm/include/core/realm/util/to_string.hpp new file mode 100644 index 0000000..c3fac65 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/to_string.hpp @@ -0,0 +1,126 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_TO_STRING_HPP +#define REALM_UTIL_TO_STRING_HPP + +#include +#include + +namespace realm { +namespace util { + +class Printable { +public: + Printable(bool value) + : m_type(Type::Bool) + , m_uint(value) + { + } + Printable(unsigned char value) + : m_type(Type::Uint) + , m_uint(value) + { + } + Printable(unsigned int value) + : m_type(Type::Uint) + , m_uint(value) + { + } + Printable(unsigned long value) + : m_type(Type::Uint) + , m_uint(value) + { + } + Printable(unsigned long long value) + : m_type(Type::Uint) + , m_uint(value) + { + } + Printable(char value) + : m_type(Type::Int) + , m_int(value) + { + } + Printable(int value) + : m_type(Type::Int) + , m_int(value) + { + } + Printable(long value) + : m_type(Type::Int) + , m_int(value) + { + } + Printable(long long value) + : m_type(Type::Int) + , m_int(value) + { + } + Printable(const char* value) + : m_type(Type::String) + , m_string(value) + { + } + Printable(std::string const& value) + : m_type(Type::String) + , m_string(value.c_str()) + { + } + + + void print(std::ostream& out, bool quote) const; + std::string str() const; + + static void print_all(std::ostream& out, const std::initializer_list& values, bool quote); + +private: + enum class Type { + Bool, + Int, + Uint, + String, + } m_type; + + union { + uintmax_t m_uint; + intmax_t m_int; + const char* m_string; + }; +}; + + +template +std::string to_string(const T& v) +{ + return Printable(v).str(); +} + +std::string format(const char* fmt, std::initializer_list); + +template +std::string format(const char* fmt, Args&&... args) +{ + return format(fmt, {Printable(args)...}); +} + + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_TO_STRING_HPP diff --git a/!main project/Pods/Realm/include/core/realm/util/type_list.hpp b/!main project/Pods/Realm/include/core/realm/util/type_list.hpp new file mode 100644 index 0000000..da847c7 --- /dev/null +++ b/!main project/Pods/Realm/include/core/realm/util/type_list.hpp @@ -0,0 +1,244 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_UTIL_TYPE_LIST_HPP +#define REALM_UTIL_TYPE_LIST_HPP + +namespace realm { +namespace util { + + +/// The 'cons' operator for building lists of types. +/// +/// \tparam H The head of the list, that is, the first type in the +/// list. +/// +/// \tparam T The tail of the list, that is, the list of types +/// following the head. It is 'void' if nothing follows the head, +/// otherwise it matches TypeCons. +/// +/// Note that 'void' is interpreted as a zero-length list. +template +struct TypeCons { + typedef H head; + typedef T tail; +}; + + +/// Append a type the the end of a type list. The resulting type list +/// is available as TypeAppend::type. +/// +/// \tparam List A list of types constructed using TypeCons<>. Note +/// that 'void' is interpreted as a zero-length list. +/// +/// \tparam T The new type to be appended. +template +struct TypeAppend { + typedef TypeCons::type> type; +}; +/// Base case for empty type list. +template +struct TypeAppend { + typedef TypeCons type; +}; + + +/// Get an element from the specified list of types. The result is +/// available as TypeAt::type. +/// +/// \tparam List A list of types constructed using TypeCons<>. Note +/// that 'void' is interpreted as a zero-length list. +/// +/// \tparam i The index of the list element to get. +template +struct TypeAt { + typedef typename TypeAt::type type; +}; +/// Base case for empty type list. +template +struct TypeAt { + typedef typename List::head type; +}; + + +/// Count the number of elements in the specified list of types. The +/// result is available as TypeCount::value. +/// +/// \tparam List The list of types, constructed using TypeCons<>. Note +/// that 'void' is interpreted as a zero-length list. +template +struct TypeCount { + static const int value = 1 + TypeCount::value; +}; +/// Base case for empty type list. +template <> +struct TypeCount { + static const int value = 0; +}; + + +/// Find the first type in the specified list that satisfies the +/// specified predicate. +/// +/// \tparam List The list of types, constructed using TypeCons<>. Note +/// that 'void' is interpreted as a zero-length list. +/// +/// \tparam Pred Must be such that `Pred::%value` is true if, and +/// only if the predicate is satisfied for `T`. +template class Pred> +struct FindType { +private: + typedef typename List::head type_1; + typedef typename FindType::type type_2; + +public: + typedef typename std::conditional::value, type_1, type_2>::type type; +}; +/// Base case for empty type list. +template